// 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 {RpcError} from '@protobuf-ts/runtime-rpc';
import {ProfileType, ProfileTypesResponse} from '@parca/client';
import {Select, type SelectElement} from '@parca/components';
import {TEST_IDS, testId} from '@parca/test-utils';
interface WellKnownProfile {
name: string;
help: string;
}
interface WellKnownProfiles {
[key: string]: WellKnownProfile;
}
export const wellKnownProfiles: WellKnownProfiles = {
'block:contentions:count:contentions:count': {
name: 'Block Contentions Total',
help: 'Stack traces that led to blocking on synchronization primitives.',
},
'block:delay:nanoseconds:contentions:count': {
name: 'Block Contention Time Total',
help: 'Time delayed stack traces caused by blocking on synchronization primitives.',
},
'fgprof:samples:count:wallclock:nanoseconds:delta': {
name: 'Fgprof Samples Total',
help: 'CPU profile samples observed regardless of their current On/Off CPU scheduling status',
},
'fgprof:time:nanoseconds:wallclock:nanoseconds:delta': {
name: 'Fgprof Samples Time Total',
help: 'CPU profile measured regardless of their current On/Off CPU scheduling status in nanoseconds',
},
'goroutine:goroutine:count:goroutine:count': {
name: 'Goroutine Created Total',
help: 'Stack traces that created all current goroutines.',
},
'memory:alloc_objects:count:space:bytes': {
name: 'Memory Allocated Objects Total',
help: 'A sampling of all past memory allocations by objects.',
},
'memory:alloc_space:bytes:space:bytes': {
name: 'Memory Allocated Bytes Total',
help: 'A sampling of all past memory allocations in bytes.',
},
'memory:alloc_objects:count:space:bytes:delta': {
name: 'Memory Allocated Objects Delta',
help: 'A sampling of all memory allocations during the observation by objects.',
},
'memory:alloc_space:bytes:space:bytes:delta': {
name: 'Memory Allocated Bytes Delta',
help: 'A sampling of all memory allocations during the observation in bytes.',
},
'memory:inuse_objects:count:space:bytes': {
name: 'Memory In-Use Objects',
help: 'A sampling of memory allocations of live objects by objects.',
},
'memory:inuse_space:bytes:space:bytes': {
name: 'Memory In-Use Bytes',
help: 'A sampling of memory allocations of live objects by bytes.',
},
'mutex:contentions:count:contentions:count': {
name: 'Mutex Contentions Total',
help: 'Stack traces of holders of contended mutexes.',
},
'mutex:delay:nanoseconds:contentions:count': {
name: 'Mutex Contention Time Total',
help: 'Time delayed stack traces caused by contended mutexes.',
},
'process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta': {
name: 'Process CPU Nanoseconds',
help: 'CPU profile measured by the process itself in nanoseconds.',
},
'process_cpu:samples:count:cpu:nanoseconds:delta': {
name: 'Process CPU Samples',
help: 'CPU profile samples observed by the process itself.',
},
'parca_agent_cpu:samples:count:cpu:nanoseconds:delta': {
name: 'CPU Samples',
help: 'CPU profile samples observed by Parca Agent.',
},
'otel_profiling_agent_on_cpu:samples:count:cpu:nanoseconds:delta': {
name: 'On-CPU Samples',
help: 'On CPU profile samples observed by the Otel Profiling Agent.',
},
'go_opentelemetry_io_ebpf_profiler:samples:count:cpu:nanoseconds:delta': {
name: 'On-CPU',
help: 'On CPU profile samples as observed by the OpenTelemetry eBPF Profiler.',
},
'parca_agent:samples:count:cpu:nanoseconds:delta': {
name: 'On-CPU',
help: 'On CPU profile samples as observed by the Parca Agent.',
},
'parca_agent:wallclock:nanoseconds:samples:count:delta': {
name: 'Off-CPU',
help: 'Time spent off the CPU as observed by the Parca Agent.',
},
// deprecated with PC sampling
'parca_agent:cuda:nanoseconds:cuda:nanoseconds:delta': {
name: 'On-GPU',
help: 'Time spent on the GPU.',
},
// use this for combined PC stall and kernel timing, not the default
'parca_agent:gpu_time:nanoseconds:gpu_time:nanoseconds:delta': {
name: 'On-GPU',
help: 'Time spent on the GPU. Combines kernel execution time and PC stall samples; the gpu_view label distinguishes the two views.',
},
'parca_agent:gpu_kernel_time:nanoseconds:gpu_kernel_time:nanoseconds:delta': {
name: 'GPU Kernels',
help: 'GPU kernel execution time measured via CUDA runtime callbacks.',
},
'parca_agent:gpu_pcsample:count:gpu_pcsample:nanoseconds:delta': {
name: 'GPU Instructions',
help: 'PC samples from the GPU — all PC activity, not just stalls; shows the instruction/stall reason.',
},
};
export function flexibleWellKnownProfileMatching(name: string): WellKnownProfile | undefined {
const prefixExcludedName = name.split(':').slice(1).join(':');
const deltaExcludedName = prefixExcludedName.replace(/:delta$/, '');
const requiredKey = Object.keys(wellKnownProfiles).find(key => {
if (key.includes(deltaExcludedName)) {
return true;
}
return false;
});
return requiredKey != null ? wellKnownProfiles[requiredKey] : undefined;
}
export function profileSelectElement(
name: string,
flexibleKnownProfilesDetection: boolean
): SelectElement {
const wellKnown: WellKnownProfile | undefined = !flexibleKnownProfilesDetection
? wellKnownProfiles[name]
: flexibleWellKnownProfileMatching(name);
if (wellKnown === undefined) return {active: <>{name}>, expanded: <>{name}>};
const title = wellKnown.name.replace(/ /g, '\u00a0');
return {
active: <>{title}>,
expanded: (
<>
{title} {wellKnown.help}
>
),
};
}
export const constructProfileName = (type: ProfileType): string => {
return `${type.name}:${type.sampleType}:${type.sampleUnit}:${type.periodType}:${type.periodUnit}${
type.delta ? ':delta' : ''
}`;
};
export const normalizeProfileTypesData = (types: ProfileType[]): string[] => {
return types.map(constructProfileName).sort((a: string, b: string): number => {
return a.localeCompare(b);
});
};
interface Props {
profileTypesData?: ProfileTypesResponse;
loading?: boolean;
error: RpcError | undefined;
selectedKey: string | undefined;
flexibleKnownProfilesDetection?: boolean;
onSelection: (value: string | undefined) => void;
disabled?: boolean;
}
const ProfileTypeSelector = ({
profileTypesData,
loading = false,
error,
selectedKey,
onSelection,
flexibleKnownProfilesDetection = false,
disabled,
}: Props): JSX.Element => {
const profileNames =
(error === undefined || error == null) &&
profileTypesData !== undefined &&
profileTypesData != null
? normalizeProfileTypesData(profileTypesData.types)
: [];
const profileLabels = (
profileNames.length > 0 ? profileNames : selectedKey != null ? [selectedKey] : []
).map(name => ({
key: name,
element: profileSelectElement(name, flexibleKnownProfilesDetection),
}));
return (
);
};
export default ProfileTypeSelector;