// Copyright 2022 The Parca Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import {describe, expect, test} from 'vitest'; import { CUDA_SASS_INSTRUCTION_LABEL, CUDA_STALL_REASON_LABEL, SASS_SOURCE_URL, STALL_SOURCE_URL, gpuFrameInfo, gpuFrameInfosFromLabels, } from './gpuFrameDescriptions'; describe('gpuFrameInfo', () => { test.each([ ['STS', 'Store to Shared Memory'], ['ISETP', 'Integer Compare And Set Predicate'], ['IMAD', 'Integer Multiply And Add'], ['MOV', 'Move'], ['FFMA', 'FP32 Fused Multiply and Add'], ['LDG', 'Load from Global Memory'], ['LDCU', 'Load a Value from Constant Memory into a Uniform Register'], ['HGMMA', 'Matrix Multiply and Accumulate Across a Warpgroup'], ['UTMALDG', 'Tensor Load from Global to Shared Memory'], ['LDT', 'Load Matrix from Tensor Memory to Register File'], ])('returns SASS info for %s with verbatim description %j', (mnemonic, description) => { const info = gpuFrameInfo(mnemonic); expect(info?.kind).toBe('sass'); expect(info?.entry.description).toBe(description); expect(info?.entry.reasonLabel.length).toBeGreaterThan(0); expect(info?.sourceUrl).toBe(SASS_SOURCE_URL); }); test.each([ ['smsp__pcsamp_warps_issue_stalled_long_scoreboard', 'Long Scoreboard'], ['smsp__pcsamp_warps_issue_stalled_short_scoreboard', 'Short Scoreboard'], ['smsp__pcsamp_warps_issue_stalled_barrier', 'Barrier'], ['smsp__pcsamp_warps_issue_stalled_drain', 'Drain'], ])('returns stall info for %s with reasonLabel %j and per-frame deep link', (reason, label) => { const info = gpuFrameInfo(reason); expect(info?.kind).toBe('stall'); expect(info?.entry.description.length).toBeGreaterThan(0); expect(info?.entry.reasonLabel).toBe(label); expect(info?.sourceUrl).toBe(`${STALL_SOURCE_URL}:~:text=${reason}`); }); test.each([['main'], ['at::native::add'], [''], ['']])( 'returns undefined for non-GPU frame name %j', name => { expect(gpuFrameInfo(name)).toBeUndefined(); } ); }); describe('gpuFrameInfosFromLabels', () => { test('returns a single SASS info for the cuda_sass_instruction label', () => { const infos = gpuFrameInfosFromLabels([[CUDA_SASS_INSTRUCTION_LABEL, 'STS']]); expect(infos).toHaveLength(1); expect(infos[0].kind).toBe('sass'); expect(infos[0].entry.description).toBe('Store to Shared Memory'); }); test('returns a single stall info for the cuda_stall_reason label', () => { const infos = gpuFrameInfosFromLabels([ [CUDA_STALL_REASON_LABEL, 'smsp__pcsamp_warps_issue_stalled_long_scoreboard'], ]); expect(infos).toHaveLength(1); expect(infos[0].kind).toBe('stall'); expect(infos[0].entry.reasonLabel).toBe('Long Scoreboard'); }); test('returns SASS first then stall when both labels are present', () => { const infos = gpuFrameInfosFromLabels([ [CUDA_STALL_REASON_LABEL, 'smsp__pcsamp_warps_issue_stalled_long_scoreboard'], [CUDA_SASS_INSTRUCTION_LABEL, 'STS'], ]); expect(infos.map(i => i.kind)).toEqual(['sass', 'stall']); }); test('ignores non-cuda labels and unknown values', () => { expect(gpuFrameInfosFromLabels([['service', 'api']])).toEqual([]); expect(gpuFrameInfosFromLabels([[CUDA_SASS_INSTRUCTION_LABEL, 'NOT_A_REAL_OPCODE']])).toEqual( [] ); expect(gpuFrameInfosFromLabels([])).toEqual([]); }); });