mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-09-29 22:34:33 +00:00
160 lines
4.2 KiB
TypeScript
160 lines
4.2 KiB
TypeScript
import {useCallback, useMemo} from 'react';
|
|
import styled from '@emotion/styled';
|
|
|
|
import type {SelectOption} from 'sentry/components/compactSelect';
|
|
import {CompactSelect} from 'sentry/components/compactSelect';
|
|
import {IconList} from 'sentry/icons';
|
|
import {t, tn} from 'sentry/locale';
|
|
import {space} from 'sentry/styles/space';
|
|
import {defined} from 'sentry/utils';
|
|
import type {FlamegraphState} from 'sentry/utils/profiling/flamegraph/flamegraphStateProvider/flamegraphContext';
|
|
import type {ProfileGroup} from 'sentry/utils/profiling/profile/importProfile';
|
|
import type {Profile} from 'sentry/utils/profiling/profile/profile';
|
|
import {makeFormatter} from 'sentry/utils/profiling/units/units';
|
|
|
|
export interface FlamegraphThreadSelectorProps {
|
|
onThreadIdChange: (threadId: Profile['threadId']) => void;
|
|
profileGroup: ProfileGroup;
|
|
threadId: FlamegraphState['profiles']['threadId'];
|
|
}
|
|
|
|
function FlamegraphThreadSelector({
|
|
threadId,
|
|
onThreadIdChange,
|
|
profileGroup,
|
|
}: FlamegraphThreadSelectorProps) {
|
|
const [profileOptions, emptyProfileOptions]: [
|
|
SelectOption<number>[],
|
|
SelectOption<number>[],
|
|
] = useMemo(() => {
|
|
const profiles: SelectOption<number>[] = [];
|
|
const emptyProfiles: SelectOption<number>[] = [];
|
|
const activeThreadId =
|
|
typeof profileGroup.activeProfileIndex === 'number'
|
|
? profileGroup.profiles[profileGroup.activeProfileIndex]?.threadId
|
|
: undefined;
|
|
const sortedProfiles = [...profileGroup.profiles].sort(
|
|
compareProfiles(activeThreadId)
|
|
);
|
|
|
|
sortedProfiles.forEach(profile => {
|
|
const option = {
|
|
label: profile.name ? profile.name : `tid(${profile.threadId})`,
|
|
value: profile.threadId,
|
|
details: (
|
|
<ThreadLabelDetails
|
|
duration={makeFormatter(profile.unit)(profile.duration)}
|
|
// plus 1 because the last sample always has a weight of 0
|
|
// and is not included in the raw weights
|
|
samples={profile.rawWeights.length + 1}
|
|
/>
|
|
),
|
|
};
|
|
|
|
if (profile.rawWeights.length > 0) {
|
|
profiles.push(option);
|
|
return;
|
|
}
|
|
emptyProfiles.push(option);
|
|
return;
|
|
});
|
|
|
|
return [profiles, emptyProfiles];
|
|
}, [profileGroup]);
|
|
|
|
const handleChange: (opt: SelectOption<any>) => void = useCallback(
|
|
opt => {
|
|
if (defined(opt) && typeof opt.value === 'number') {
|
|
onThreadIdChange(opt.value);
|
|
}
|
|
},
|
|
[onThreadIdChange]
|
|
);
|
|
|
|
return (
|
|
<StyledCompactSelect
|
|
triggerProps={{
|
|
icon: <IconList />,
|
|
size: 'xs',
|
|
}}
|
|
options={[
|
|
{key: 'profiles', label: t('Profiles'), options: profileOptions},
|
|
{
|
|
key: 'empty-profiles',
|
|
label: t('Empty Profiles'),
|
|
options: emptyProfileOptions,
|
|
},
|
|
]}
|
|
value={threadId ?? 0}
|
|
onChange={handleChange}
|
|
searchable
|
|
/>
|
|
);
|
|
}
|
|
|
|
interface ThreadLabelDetailsProps {
|
|
duration: string;
|
|
samples: number;
|
|
}
|
|
|
|
function ThreadLabelDetails(props: ThreadLabelDetailsProps) {
|
|
return (
|
|
<DetailsContainer>
|
|
<div>{props.duration}</div>
|
|
<div>{tn('%s sample', '%s samples', props.samples)}</div>
|
|
</DetailsContainer>
|
|
);
|
|
}
|
|
|
|
type ProfileLight = {
|
|
name: Profile['name'];
|
|
threadId: Profile['threadId'];
|
|
};
|
|
|
|
export function compareProfiles(activeThreadId?: number) {
|
|
return function (a: ProfileLight, b: ProfileLight): number {
|
|
// if one is the active thread id, it should be first
|
|
if (defined(activeThreadId)) {
|
|
if (a.threadId === activeThreadId) {
|
|
return -1;
|
|
}
|
|
if (b.threadId === activeThreadId) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
// if neither has a name, we use the thread id
|
|
if (!b.name && !a.name) {
|
|
return a.threadId > b.threadId ? 1 : -1;
|
|
}
|
|
|
|
// if one doesn't have a name, the other is first
|
|
if (!b.name) {
|
|
return -1;
|
|
}
|
|
if (!a.name) {
|
|
return 1;
|
|
}
|
|
|
|
// if both has a name, we use the name
|
|
return a.name > b.name ? 1 : -1;
|
|
};
|
|
}
|
|
|
|
const DetailsContainer = styled('div')`
|
|
display: flex;
|
|
flex-direction: row;
|
|
justify-content: space-between;
|
|
gap: ${space(1)};
|
|
`;
|
|
|
|
const StyledCompactSelect = styled(CompactSelect)`
|
|
width: 14ch;
|
|
min-width: 14ch;
|
|
|
|
> button {
|
|
width: 100%;
|
|
}
|
|
`;
|
|
export {FlamegraphThreadSelector};
|