@parca/profile 0.19.113 → 0.19.114
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/dist/ProfileExplorer/ProfileExplorerSingle.d.ts.map +1 -1
- package/dist/ProfileExplorer/ProfileExplorerSingle.js +9 -3
- package/dist/ProfileFlameChart/SamplesStrips/SamplesGraph/index.d.ts +31 -0
- package/dist/ProfileFlameChart/SamplesStrips/SamplesGraph/index.d.ts.map +1 -0
- package/dist/{MetricsGraphStrips/AreaGraph → ProfileFlameChart/SamplesStrips/SamplesGraph}/index.js +32 -60
- package/dist/{MetricsGraphStrips/MetricsGraphStrips.stories.d.ts → ProfileFlameChart/SamplesStrips/SamplesStrips.stories.d.ts} +4 -3
- package/dist/ProfileFlameChart/SamplesStrips/SamplesStrips.stories.d.ts.map +1 -0
- package/dist/{MetricsGraphStrips/MetricsGraphStrips.stories.js → ProfileFlameChart/SamplesStrips/SamplesStrips.stories.js} +5 -4
- package/dist/{MetricsGraphStrips → ProfileFlameChart/SamplesStrips}/index.d.ts +5 -4
- package/dist/ProfileFlameChart/SamplesStrips/index.d.ts.map +1 -0
- package/dist/ProfileFlameChart/SamplesStrips/index.js +141 -0
- package/dist/ProfileFlameChart/index.d.ts +20 -0
- package/dist/ProfileFlameChart/index.d.ts.map +1 -0
- package/dist/ProfileFlameChart/index.js +155 -0
- package/dist/ProfileFlameGraph/index.d.ts.map +1 -1
- package/dist/ProfileFlameGraph/index.js +0 -1
- package/dist/ProfileMetricsGraph/hooks/useQueryRange.d.ts +2 -1
- package/dist/ProfileMetricsGraph/hooks/useQueryRange.d.ts.map +1 -1
- package/dist/ProfileMetricsGraph/hooks/useQueryRange.js +11 -21
- package/dist/ProfileMetricsGraph/index.d.ts.map +1 -1
- package/dist/ProfileMetricsGraph/index.js +13 -3
- package/dist/ProfileSelector/index.d.ts.map +1 -1
- package/dist/ProfileSelector/index.js +4 -0
- package/dist/ProfileView/components/ActionButtons/GroupByDropdown.d.ts +1 -0
- package/dist/ProfileView/components/ActionButtons/GroupByDropdown.d.ts.map +1 -1
- package/dist/ProfileView/components/ActionButtons/GroupByDropdown.js +2 -2
- package/dist/ProfileView/components/DashboardItems/index.d.ts +5 -4
- package/dist/ProfileView/components/DashboardItems/index.d.ts.map +1 -1
- package/dist/ProfileView/components/DashboardItems/index.js +4 -3
- package/dist/ProfileView/components/GroupByLabelsDropdown/index.d.ts +2 -1
- package/dist/ProfileView/components/GroupByLabelsDropdown/index.d.ts.map +1 -1
- package/dist/ProfileView/components/GroupByLabelsDropdown/index.js +2 -2
- package/dist/ProfileView/components/Toolbars/MultiLevelDropdown.js +1 -1
- package/dist/ProfileView/components/Toolbars/index.d.ts +2 -0
- package/dist/ProfileView/components/Toolbars/index.d.ts.map +1 -1
- package/dist/ProfileView/components/Toolbars/index.js +4 -2
- package/dist/ProfileView/hooks/useAutoSelectDimension.d.ts +16 -0
- package/dist/ProfileView/hooks/useAutoSelectDimension.d.ts.map +1 -0
- package/dist/ProfileView/hooks/useAutoSelectDimension.js +75 -0
- package/dist/ProfileView/hooks/useVisualizationState.d.ts +2 -0
- package/dist/ProfileView/hooks/useVisualizationState.d.ts.map +1 -1
- package/dist/ProfileView/hooks/useVisualizationState.js +8 -0
- package/dist/ProfileView/index.d.ts +1 -1
- package/dist/ProfileView/index.d.ts.map +1 -1
- package/dist/ProfileView/index.js +7 -4
- package/dist/ProfileView/types/visualization.d.ts +15 -3
- package/dist/ProfileView/types/visualization.d.ts.map +1 -1
- package/dist/ProfileViewWithData.d.ts +2 -1
- package/dist/ProfileViewWithData.d.ts.map +1 -1
- package/dist/ProfileViewWithData.js +41 -29
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/styles.css +1 -1
- package/package.json +8 -7
- package/src/ProfileExplorer/ProfileExplorerSingle.tsx +14 -3
- package/src/{MetricsGraphStrips/AreaGraph → ProfileFlameChart/SamplesStrips/SamplesGraph}/index.tsx +77 -81
- package/src/{MetricsGraphStrips/MetricsGraphStrips.stories.tsx → ProfileFlameChart/SamplesStrips/SamplesStrips.stories.tsx} +7 -6
- package/src/ProfileFlameChart/SamplesStrips/index.tsx +301 -0
- package/src/ProfileFlameChart/index.tsx +305 -0
- package/src/ProfileFlameGraph/index.tsx +0 -1
- package/src/ProfileMetricsGraph/hooks/useQueryRange.ts +18 -26
- package/src/ProfileMetricsGraph/index.tsx +24 -2
- package/src/ProfileSelector/index.tsx +11 -0
- package/src/ProfileView/components/ActionButtons/GroupByDropdown.tsx +3 -0
- package/src/ProfileView/components/DashboardItems/index.tsx +19 -17
- package/src/ProfileView/components/GroupByLabelsDropdown/index.tsx +4 -2
- package/src/ProfileView/components/Toolbars/MultiLevelDropdown.tsx +1 -1
- package/src/ProfileView/components/Toolbars/index.tsx +18 -1
- package/src/ProfileView/hooks/useAutoSelectDimension.ts +90 -0
- package/src/ProfileView/hooks/useVisualizationState.ts +17 -0
- package/src/ProfileView/index.tsx +16 -2
- package/src/ProfileView/types/visualization.ts +17 -3
- package/src/ProfileViewWithData.tsx +80 -37
- package/src/index.tsx +4 -0
- package/dist/MetricsGraphStrips/AreaGraph/Tooltip.d.ts +0 -10
- package/dist/MetricsGraphStrips/AreaGraph/Tooltip.d.ts.map +0 -1
- package/dist/MetricsGraphStrips/AreaGraph/Tooltip.js +0 -44
- package/dist/MetricsGraphStrips/AreaGraph/index.d.ts +0 -21
- package/dist/MetricsGraphStrips/AreaGraph/index.d.ts.map +0 -1
- package/dist/MetricsGraphStrips/MetricsGraphStrips.stories.d.ts.map +0 -1
- package/dist/MetricsGraphStrips/index.d.ts.map +0 -1
- package/dist/MetricsGraphStrips/index.js +0 -70
- package/src/MetricsGraphStrips/AreaGraph/Tooltip.tsx +0 -83
- package/src/MetricsGraphStrips/index.tsx +0 -142
|
@@ -11,20 +11,24 @@
|
|
|
11
11
|
// See the License for the specific language governing permissions and
|
|
12
12
|
// limitations under the License.
|
|
13
13
|
|
|
14
|
-
import {useEffect, useMemo} from 'react';
|
|
15
|
-
|
|
16
14
|
import {RpcError} from '@protobuf-ts/runtime-rpc';
|
|
17
15
|
|
|
18
16
|
import {Duration, QueryRangeResponse, QueryServiceClient, Timestamp} from '@parca/client';
|
|
19
|
-
import {useGrpcMetadata
|
|
20
|
-
import {getStepDuration} from '@parca/utilities';
|
|
17
|
+
import {useGrpcMetadata} from '@parca/components';
|
|
18
|
+
import {getStepDuration, getStepDurationInMilliseconds} from '@parca/utilities';
|
|
21
19
|
|
|
22
20
|
import useGrpcQuery from '../../useGrpcQuery';
|
|
23
21
|
|
|
22
|
+
interface QueryRangeResult {
|
|
23
|
+
response: QueryRangeResponse;
|
|
24
|
+
stepDurationMs: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
24
27
|
interface IQueryRangeState {
|
|
25
28
|
response: QueryRangeResponse | null;
|
|
26
29
|
isLoading: boolean;
|
|
27
30
|
error: RpcError | null;
|
|
31
|
+
stepDurationMs: number;
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
export const getStepCountFromScreenWidth = (pixelsPerPoint: number): number => {
|
|
@@ -43,33 +47,16 @@ export const useQueryRange = (
|
|
|
43
47
|
start: number,
|
|
44
48
|
end: number,
|
|
45
49
|
sumBy: string[],
|
|
50
|
+
stepCount: number,
|
|
46
51
|
skip = false
|
|
47
52
|
): IQueryRangeState => {
|
|
48
53
|
const metadata = useGrpcMetadata();
|
|
49
|
-
const [stepCountStr, setStepCount] = useURLState('step_count');
|
|
50
|
-
|
|
51
|
-
const defaultStepCount = useMemo(() => {
|
|
52
|
-
return getStepCountFromScreenWidth(10);
|
|
53
|
-
}, []);
|
|
54
|
-
|
|
55
|
-
const stepCount = useMemo(() => {
|
|
56
|
-
if (stepCountStr != null) {
|
|
57
|
-
return parseInt(stepCountStr as string, 10);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return defaultStepCount;
|
|
61
|
-
}, [stepCountStr, defaultStepCount]);
|
|
62
|
-
|
|
63
|
-
useEffect(() => {
|
|
64
|
-
if (stepCountStr == null) {
|
|
65
|
-
setStepCount(defaultStepCount.toString());
|
|
66
|
-
}
|
|
67
|
-
}, [stepCountStr, defaultStepCount, setStepCount]);
|
|
68
54
|
|
|
69
|
-
const {data, isLoading, error} = useGrpcQuery<
|
|
55
|
+
const {data, isLoading, error} = useGrpcQuery<QueryRangeResult | undefined>({
|
|
70
56
|
key: ['query-range', queryExpression, start, end, (sumBy ?? []).join(','), stepCount, metadata],
|
|
71
57
|
queryFn: async signal => {
|
|
72
58
|
const stepDuration = getStepDuration(start, end, stepCount);
|
|
59
|
+
const stepDurationMs = getStepDurationInMilliseconds(stepDuration);
|
|
73
60
|
const {response} = await client.queryRange(
|
|
74
61
|
{
|
|
75
62
|
query: queryExpression,
|
|
@@ -81,7 +68,7 @@ export const useQueryRange = (
|
|
|
81
68
|
},
|
|
82
69
|
{meta: metadata, abort: signal}
|
|
83
70
|
);
|
|
84
|
-
return response;
|
|
71
|
+
return {response, stepDurationMs};
|
|
85
72
|
},
|
|
86
73
|
options: {
|
|
87
74
|
retry: false,
|
|
@@ -90,5 +77,10 @@ export const useQueryRange = (
|
|
|
90
77
|
},
|
|
91
78
|
});
|
|
92
79
|
|
|
93
|
-
return {
|
|
80
|
+
return {
|
|
81
|
+
isLoading,
|
|
82
|
+
error: error as RpcError | null,
|
|
83
|
+
response: data?.response ?? null,
|
|
84
|
+
stepDurationMs: data?.stepDurationMs ?? 0,
|
|
85
|
+
};
|
|
94
86
|
};
|
|
@@ -25,8 +25,11 @@ import {
|
|
|
25
25
|
import {
|
|
26
26
|
DateTimeRange,
|
|
27
27
|
MetricsGraphSkeleton,
|
|
28
|
+
NumberParser,
|
|
29
|
+
NumberSerializer,
|
|
28
30
|
TextWithTooltip,
|
|
29
31
|
useParcaContext,
|
|
32
|
+
useURLStateCustom,
|
|
30
33
|
} from '@parca/components';
|
|
31
34
|
import {Query} from '@parca/parser';
|
|
32
35
|
import {TEST_IDS, testId} from '@parca/test-utils';
|
|
@@ -35,7 +38,7 @@ import {capitalizeOnlyFirstLetter, formatDate, timePattern, valueFormatter} from
|
|
|
35
38
|
import {MergedProfileSelection, ProfileSelection} from '..';
|
|
36
39
|
import MetricsGraph, {ContextMenuItemOrSubmenu, Series, SeriesPoint} from '../MetricsGraph';
|
|
37
40
|
import {useMetricsGraphDimensions} from '../MetricsGraph/useMetricsGraphDimensions';
|
|
38
|
-
import {useQueryRange} from './hooks/useQueryRange';
|
|
41
|
+
import {getStepCountFromScreenWidth, useQueryRange} from './hooks/useQueryRange';
|
|
39
42
|
|
|
40
43
|
const createProfileContextMenuItems = (
|
|
41
44
|
addLabelMatcher: (
|
|
@@ -197,11 +200,30 @@ const ProfileMetricsGraph = ({
|
|
|
197
200
|
comparing = false,
|
|
198
201
|
sumBy,
|
|
199
202
|
}: ProfileMetricsGraphProps): JSX.Element => {
|
|
203
|
+
const [rawStepCount] = useURLStateCustom<number>('step_count', {
|
|
204
|
+
defaultValue: String(getStepCountFromScreenWidth(10)),
|
|
205
|
+
parse: NumberParser,
|
|
206
|
+
stringify: NumberSerializer,
|
|
207
|
+
});
|
|
208
|
+
// Clamp step count so the step duration is at least 1 second as we don't have this enforced server-side anymore.
|
|
209
|
+
const stepCount = useMemo(() => {
|
|
210
|
+
const maxForOneSecond = Math.floor((to - from) / 1000);
|
|
211
|
+
return Math.min(rawStepCount, maxForOneSecond);
|
|
212
|
+
}, [rawStepCount, from, to]);
|
|
213
|
+
|
|
200
214
|
const {
|
|
201
215
|
isLoading: metricsGraphLoading,
|
|
202
216
|
response,
|
|
203
217
|
error,
|
|
204
|
-
} = useQueryRange(
|
|
218
|
+
} = useQueryRange(
|
|
219
|
+
queryClient,
|
|
220
|
+
queryExpression,
|
|
221
|
+
from,
|
|
222
|
+
to,
|
|
223
|
+
sumBy,
|
|
224
|
+
stepCount,
|
|
225
|
+
queryExpression === ''
|
|
226
|
+
);
|
|
205
227
|
const {onError, perf, authenticationErrorMessage, isDarkMode, timezone} = useParcaContext();
|
|
206
228
|
const {width, height, margin, heightStyle} = useMetricsGraphDimensions(comparing);
|
|
207
229
|
const [showAllSeriesForResponse, setShowAllSeriesForResponse] = useState<typeof response | null>(
|
|
@@ -153,6 +153,17 @@ const ProfileSelector = ({
|
|
|
153
153
|
DateTimeRange.fromRangeKey(draftSelection.timeSelection, draftSelection.from, draftSelection.to)
|
|
154
154
|
);
|
|
155
155
|
|
|
156
|
+
// Sync local timeRangeSelection when URL state changes externally (e.g., "Switch to 1 minute" button)
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
setTimeRangeSelection(
|
|
159
|
+
DateTimeRange.fromRangeKey(
|
|
160
|
+
querySelection.timeSelection,
|
|
161
|
+
querySelection.from,
|
|
162
|
+
querySelection.to
|
|
163
|
+
)
|
|
164
|
+
);
|
|
165
|
+
}, [querySelection.timeSelection, querySelection.from, querySelection.to]);
|
|
166
|
+
|
|
156
167
|
const [queryExpressionString, setQueryExpressionString] = useState(draftSelection.expression);
|
|
157
168
|
|
|
158
169
|
const [advancedModeForQueryBrowser, setAdvancedModeForQueryBrowser] = useState(
|
|
@@ -21,6 +21,7 @@ interface GroupByControlsProps {
|
|
|
21
21
|
setGroupByLabels: (labels: string[]) => void;
|
|
22
22
|
metadataRefetch?: () => Promise<void>;
|
|
23
23
|
metadataLoading: boolean;
|
|
24
|
+
label?: string;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
const GroupByControls: React.FC<GroupByControlsProps> = ({
|
|
@@ -29,6 +30,7 @@ const GroupByControls: React.FC<GroupByControlsProps> = ({
|
|
|
29
30
|
setGroupByLabels,
|
|
30
31
|
metadataRefetch,
|
|
31
32
|
metadataLoading,
|
|
33
|
+
label,
|
|
32
34
|
}) => {
|
|
33
35
|
return (
|
|
34
36
|
<div className="relative flex" id="h-group-by-controls">
|
|
@@ -38,6 +40,7 @@ const GroupByControls: React.FC<GroupByControlsProps> = ({
|
|
|
38
40
|
setGroupByLabels={setGroupByLabels}
|
|
39
41
|
metadataRefetch={metadataRefetch}
|
|
40
42
|
metadataLoading={metadataLoading}
|
|
43
|
+
label={label}
|
|
41
44
|
/>
|
|
42
45
|
</div>
|
|
43
46
|
);
|
|
@@ -16,6 +16,7 @@ import {Profiler, ProfilerOnRenderCallback} from 'react';
|
|
|
16
16
|
import {QueryServiceClient} from '@parca/client';
|
|
17
17
|
import {ConditionalWrapper} from '@parca/components';
|
|
18
18
|
|
|
19
|
+
import ProfileFlameChart from '../../../ProfileFlameChart';
|
|
19
20
|
import ProfileFlameGraph from '../../../ProfileFlameGraph';
|
|
20
21
|
import {CurrentPathFrame} from '../../../ProfileFlameGraph/FlameGraphArrow/utils';
|
|
21
22
|
import {ProfileSource} from '../../../ProfileSource';
|
|
@@ -24,6 +25,7 @@ import {SourceView} from '../../../SourceView';
|
|
|
24
25
|
import {Table} from '../../../Table';
|
|
25
26
|
import type {
|
|
26
27
|
FlamegraphData,
|
|
28
|
+
SamplesData,
|
|
27
29
|
SandwichData,
|
|
28
30
|
SourceData,
|
|
29
31
|
TopTableData,
|
|
@@ -35,7 +37,7 @@ interface GetDashboardItemProps {
|
|
|
35
37
|
isHalfScreen: boolean;
|
|
36
38
|
dimensions: DOMRect | undefined;
|
|
37
39
|
flamegraphData: FlamegraphData;
|
|
38
|
-
|
|
40
|
+
samplesData?: SamplesData;
|
|
39
41
|
topTableData?: TopTableData;
|
|
40
42
|
sandwichData: SandwichData;
|
|
41
43
|
sourceData?: SourceData;
|
|
@@ -47,7 +49,8 @@ interface GetDashboardItemProps {
|
|
|
47
49
|
perf?: {
|
|
48
50
|
onRender?: ProfilerOnRenderCallback;
|
|
49
51
|
};
|
|
50
|
-
queryClient
|
|
52
|
+
queryClient: QueryServiceClient;
|
|
53
|
+
onSwitchToOneMinute?: () => void;
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
export const getDashboardItem = ({
|
|
@@ -55,7 +58,7 @@ export const getDashboardItem = ({
|
|
|
55
58
|
isHalfScreen,
|
|
56
59
|
dimensions,
|
|
57
60
|
flamegraphData,
|
|
58
|
-
|
|
61
|
+
samplesData,
|
|
59
62
|
topTableData,
|
|
60
63
|
sourceData,
|
|
61
64
|
sandwichData,
|
|
@@ -65,6 +68,8 @@ export const getDashboardItem = ({
|
|
|
65
68
|
curPathArrow,
|
|
66
69
|
setNewCurPathArrow,
|
|
67
70
|
perf,
|
|
71
|
+
queryClient,
|
|
72
|
+
onSwitchToOneMinute,
|
|
68
73
|
}: GetDashboardItemProps): JSX.Element => {
|
|
69
74
|
switch (type) {
|
|
70
75
|
case 'flamegraph':
|
|
@@ -102,16 +107,10 @@ export const getDashboardItem = ({
|
|
|
102
107
|
);
|
|
103
108
|
case 'flamechart':
|
|
104
109
|
return (
|
|
105
|
-
<
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
total={total}
|
|
110
|
-
filtered={filtered}
|
|
111
|
-
profileType={profileSource?.ProfileType()}
|
|
112
|
-
loading={flamechartData.loading}
|
|
113
|
-
error={flamechartData.error}
|
|
114
|
-
isHalfScreen={isHalfScreen}
|
|
110
|
+
<ProfileFlameChart
|
|
111
|
+
samplesData={samplesData}
|
|
112
|
+
queryClient={queryClient}
|
|
113
|
+
profileSource={profileSource}
|
|
115
114
|
width={
|
|
116
115
|
dimensions?.width !== undefined
|
|
117
116
|
? isHalfScreen
|
|
@@ -119,10 +118,13 @@ export const getDashboardItem = ({
|
|
|
119
118
|
: dimensions.width - 16
|
|
120
119
|
: 0
|
|
121
120
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
121
|
+
total={total}
|
|
122
|
+
filtered={filtered}
|
|
123
|
+
profileType={profileSource?.ProfileType()}
|
|
124
|
+
isHalfScreen={isHalfScreen}
|
|
125
|
+
metadataMappingFiles={flamegraphData.metadataMappingFiles}
|
|
126
|
+
metadataLoading={flamegraphData.metadataLoading}
|
|
127
|
+
onSwitchToOneMinute={onSwitchToOneMinute}
|
|
126
128
|
/>
|
|
127
129
|
);
|
|
128
130
|
case 'table':
|
|
@@ -27,6 +27,7 @@ interface Props {
|
|
|
27
27
|
setGroupByLabels: (labels: string[]) => void;
|
|
28
28
|
metadataRefetch?: () => Promise<void>;
|
|
29
29
|
metadataLoading: boolean;
|
|
30
|
+
label?: string;
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
const GroupByLabelsDropdown = ({
|
|
@@ -35,12 +36,13 @@ const GroupByLabelsDropdown = ({
|
|
|
35
36
|
setGroupByLabels,
|
|
36
37
|
metadataRefetch,
|
|
37
38
|
metadataLoading,
|
|
39
|
+
label = 'Group by',
|
|
38
40
|
}: Props): JSX.Element => {
|
|
39
41
|
return (
|
|
40
42
|
<div className="flex flex-col relative" {...testId(TEST_IDS.GROUP_BY_CONTAINER)}>
|
|
41
43
|
<div className="flex items-center justify-between">
|
|
42
44
|
<label className="text-sm" {...testId(TEST_IDS.GROUP_BY_LABEL)}>
|
|
43
|
-
|
|
45
|
+
{label}
|
|
44
46
|
</label>
|
|
45
47
|
</div>
|
|
46
48
|
|
|
@@ -56,7 +58,7 @@ const GroupByLabelsDropdown = ({
|
|
|
56
58
|
refreshTitle="Refresh label names"
|
|
57
59
|
refreshTestId="group-by-refresh-button"
|
|
58
60
|
menuTestId={TEST_IDS.GROUP_BY_SELECT_FLYOUT}
|
|
59
|
-
value={groupBy
|
|
61
|
+
value={(groupBy ?? [])
|
|
60
62
|
.filter(l => l.startsWith(FIELD_LABELS))
|
|
61
63
|
.map(l => ({value: l, label: l.slice(FIELD_LABELS.length + 1)}))}
|
|
62
64
|
onChange={newValue => {
|
|
@@ -412,7 +412,7 @@ const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = ({
|
|
|
412
412
|
<Menu.Items
|
|
413
413
|
className={cx(
|
|
414
414
|
isTableVizOnly ? 'w-64' : 'w-80',
|
|
415
|
-
'absolute z-
|
|
415
|
+
'absolute z-50 mt-2 py-2 bg-white rounded-md shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none border dark:bg-gray-900 dark:border-gray-600',
|
|
416
416
|
shouldOpenLeft ? 'right-0 origin-top-right' : 'left-0 origin-top-left'
|
|
417
417
|
)}
|
|
418
418
|
>
|
|
@@ -47,6 +47,8 @@ export interface VisualisationToolbarProps {
|
|
|
47
47
|
preferencesModal?: boolean;
|
|
48
48
|
profileViewExternalSubActions?: React.ReactNode;
|
|
49
49
|
setGroupByLabels: (labels: string[]) => void;
|
|
50
|
+
flamechartDimension: string[];
|
|
51
|
+
setFlamechartDimension: (labels: string[]) => void;
|
|
50
52
|
showVisualizationSelector?: boolean;
|
|
51
53
|
sandwichFunctionName?: string;
|
|
52
54
|
alignFunctionName: string;
|
|
@@ -135,6 +137,8 @@ export const VisualisationToolbar: FC<VisualisationToolbarProps> = ({
|
|
|
135
137
|
groupBy,
|
|
136
138
|
toggleGroupBy,
|
|
137
139
|
setGroupByLabels,
|
|
140
|
+
flamechartDimension,
|
|
141
|
+
setFlamechartDimension,
|
|
138
142
|
profileType,
|
|
139
143
|
profileSource,
|
|
140
144
|
queryClient,
|
|
@@ -158,6 +162,8 @@ export const VisualisationToolbar: FC<VisualisationToolbarProps> = ({
|
|
|
158
162
|
const isTableVizOnly = dashboardItems?.length === 1 && isTableViz;
|
|
159
163
|
const isGraphViz = dashboardItems?.includes('flamegraph');
|
|
160
164
|
const isGraphVizOnly = dashboardItems?.length === 1 && isGraphViz;
|
|
165
|
+
const isFlamechartViz = dashboardItems?.includes('flamechart');
|
|
166
|
+
const isFlamechartVizOnly = dashboardItems?.length === 1 && isFlamechartViz;
|
|
161
167
|
|
|
162
168
|
const req = profileSource?.QueryRequest();
|
|
163
169
|
if (req !== null && req !== undefined) {
|
|
@@ -184,8 +190,19 @@ export const VisualisationToolbar: FC<VisualisationToolbarProps> = ({
|
|
|
184
190
|
</>
|
|
185
191
|
)}
|
|
186
192
|
|
|
193
|
+
{isFlamechartViz && (
|
|
194
|
+
<GroupByDropdown
|
|
195
|
+
groupBy={flamechartDimension}
|
|
196
|
+
labels={groupByLabels}
|
|
197
|
+
setGroupByLabels={setFlamechartDimension}
|
|
198
|
+
metadataRefetch={metadataRefetch}
|
|
199
|
+
metadataLoading={metadataLoading}
|
|
200
|
+
label="Samples group by"
|
|
201
|
+
/>
|
|
202
|
+
)}
|
|
203
|
+
|
|
187
204
|
<div className="flex mt-5">
|
|
188
|
-
<ProfileFilters />
|
|
205
|
+
{!isFlamechartVizOnly && <ProfileFilters />}
|
|
189
206
|
|
|
190
207
|
{profileViewExternalSubActions != null ? profileViewExternalSubActions : null}
|
|
191
208
|
</div>
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Copyright 2022 The Parca Authors
|
|
2
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
// you may not use this file except in compliance with the License.
|
|
4
|
+
// You may obtain a copy of the License at
|
|
5
|
+
//
|
|
6
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
//
|
|
8
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
// See the License for the specific language governing permissions and
|
|
12
|
+
// limitations under the License.
|
|
13
|
+
|
|
14
|
+
import {useEffect, useRef} from 'react';
|
|
15
|
+
|
|
16
|
+
import {ProfileType} from '@parca/parser';
|
|
17
|
+
|
|
18
|
+
import {wellKnownProfiles} from '../../ProfileTypeSelector';
|
|
19
|
+
|
|
20
|
+
const CPU_PREFERRED_DIMENSIONS = ['cpu', 'cpuid', 'thread', 'thread_id'];
|
|
21
|
+
|
|
22
|
+
const GPU_DIMENSIONS = ['node', 'gpu', 'stream'];
|
|
23
|
+
|
|
24
|
+
const getWellKnownProfileName = (profileType?: ProfileType): string | undefined => {
|
|
25
|
+
if (profileType == null) return undefined;
|
|
26
|
+
const key = profileType.toString();
|
|
27
|
+
return wellKnownProfiles[key]?.name;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const isOnGpuProfile = (profileType?: ProfileType): boolean => {
|
|
31
|
+
const wellKnownName = getWellKnownProfileName(profileType);
|
|
32
|
+
return wellKnownName === 'On-GPU';
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const isOnCpuProfile = (profileType?: ProfileType): boolean => {
|
|
36
|
+
const wellKnownName = getWellKnownProfileName(profileType);
|
|
37
|
+
return wellKnownName === 'On-CPU';
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Auto-selects the best flamechart "group by" dimension on first load.
|
|
42
|
+
*
|
|
43
|
+
* For On-GPU profiles:
|
|
44
|
+
* - Selects all available from: ['node', 'gpu', 'stream']
|
|
45
|
+
*
|
|
46
|
+
* For On-CPU profiles:
|
|
47
|
+
* - Priority: cpu > cpuid > thread > thread_id
|
|
48
|
+
* - Adds 'node' alongside the primary dimension if available
|
|
49
|
+
*
|
|
50
|
+
* For all other profile types:
|
|
51
|
+
* - No auto-selection
|
|
52
|
+
*/
|
|
53
|
+
export const useAutoSelectDimension = (
|
|
54
|
+
metadataLabels: string[] | undefined,
|
|
55
|
+
flamechartDimension: string[] | undefined,
|
|
56
|
+
setFlamechartDimension: (v: string[]) => void,
|
|
57
|
+
profileType?: ProfileType
|
|
58
|
+
): void => {
|
|
59
|
+
const hasAutoSelected = useRef(false);
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (hasAutoSelected.current) return;
|
|
63
|
+
if (metadataLabels == null || metadataLabels.length === 0) return;
|
|
64
|
+
if ((flamechartDimension ?? []).length > 0) {
|
|
65
|
+
hasAutoSelected.current = true;
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (isOnGpuProfile(profileType)) {
|
|
70
|
+
const availableGpuDims = GPU_DIMENSIONS.filter(d => metadataLabels.includes(d));
|
|
71
|
+
if (availableGpuDims.length > 0) {
|
|
72
|
+
setFlamechartDimension(availableGpuDims.map(d => `labels.${d}`));
|
|
73
|
+
hasAutoSelected.current = true;
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (isOnCpuProfile(profileType)) {
|
|
79
|
+
const hasNode = metadataLabels.includes('node');
|
|
80
|
+
for (const name of CPU_PREFERRED_DIMENSIONS) {
|
|
81
|
+
if (metadataLabels.includes(name)) {
|
|
82
|
+
const dims = hasNode ? ['node', name] : [name];
|
|
83
|
+
setFlamechartDimension(dims.map(d => `labels.${d}`));
|
|
84
|
+
hasAutoSelected.current = true;
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}, [metadataLabels, flamechartDimension, setFlamechartDimension, profileType]);
|
|
90
|
+
};
|
|
@@ -42,6 +42,8 @@ export const useVisualizationState = (): {
|
|
|
42
42
|
setGroupBy: (keys: string[]) => void;
|
|
43
43
|
toggleGroupBy: (key: string) => void;
|
|
44
44
|
setGroupByLabels: (labels: string[]) => void;
|
|
45
|
+
flamechartDimension: string[];
|
|
46
|
+
setFlamechartDimension: (labels: string[]) => void;
|
|
45
47
|
sandwichFunctionName: string | undefined;
|
|
46
48
|
setSandwichFunctionName: (sandwichFunctionName: string | undefined) => void;
|
|
47
49
|
resetSandwichFunctionName: () => void;
|
|
@@ -74,6 +76,12 @@ export const useVisualizationState = (): {
|
|
|
74
76
|
const [sandwichFunctionName, setSandwichFunctionName] = useURLState<string | undefined>(
|
|
75
77
|
'sandwich_function_name'
|
|
76
78
|
);
|
|
79
|
+
const [flamechartDimension, setStoreFlamechartDimension] = useURLState<string[]>(
|
|
80
|
+
'flamechart_dimension',
|
|
81
|
+
{
|
|
82
|
+
alwaysReturnArray: true,
|
|
83
|
+
}
|
|
84
|
+
);
|
|
77
85
|
const resetFlameGraphState = useResetFlameGraphState();
|
|
78
86
|
const batchUpdates = useURLStateBatch();
|
|
79
87
|
|
|
@@ -123,6 +131,13 @@ export const useVisualizationState = (): {
|
|
|
123
131
|
[groupBy, setGroupBy, resetFlameGraphState, batchUpdates]
|
|
124
132
|
);
|
|
125
133
|
|
|
134
|
+
const setFlamechartDimension = useCallback(
|
|
135
|
+
(labels: string[]): void => {
|
|
136
|
+
setStoreFlamechartDimension(labels.filter(l => l.startsWith(`${FIELD_LABELS}.`)));
|
|
137
|
+
},
|
|
138
|
+
[setStoreFlamechartDimension]
|
|
139
|
+
);
|
|
140
|
+
|
|
126
141
|
const resetSandwichFunctionName = useCallback((): void => {
|
|
127
142
|
setSandwichFunctionName(undefined);
|
|
128
143
|
}, [setSandwichFunctionName]);
|
|
@@ -153,6 +168,8 @@ export const useVisualizationState = (): {
|
|
|
153
168
|
setGroupBy,
|
|
154
169
|
toggleGroupBy,
|
|
155
170
|
setGroupByLabels,
|
|
171
|
+
flamechartDimension,
|
|
172
|
+
setFlamechartDimension,
|
|
156
173
|
sandwichFunctionName,
|
|
157
174
|
setSandwichFunctionName,
|
|
158
175
|
resetSandwichFunctionName,
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
} from './components/Toolbars';
|
|
28
28
|
import {DashboardProvider} from './context/DashboardContext';
|
|
29
29
|
import {ProfileViewContextProvider} from './context/ProfileViewContext';
|
|
30
|
+
import {useAutoSelectDimension} from './hooks/useAutoSelectDimension';
|
|
30
31
|
import {useProfileMetadata} from './hooks/useProfileMetadata';
|
|
31
32
|
import {useVisualizationState} from './hooks/useVisualizationState';
|
|
32
33
|
import type {ProfileViewProps, VisualizationType} from './types/visualization';
|
|
@@ -35,7 +36,7 @@ export const ProfileView = ({
|
|
|
35
36
|
total,
|
|
36
37
|
filtered,
|
|
37
38
|
flamegraphData,
|
|
38
|
-
|
|
39
|
+
samplesData,
|
|
39
40
|
topTableData,
|
|
40
41
|
sourceData,
|
|
41
42
|
profileSource,
|
|
@@ -45,6 +46,7 @@ export const ProfileView = ({
|
|
|
45
46
|
compare,
|
|
46
47
|
showVisualizationSelector,
|
|
47
48
|
sandwichData,
|
|
49
|
+
onSwitchToOneMinute,
|
|
48
50
|
}: ProfileViewProps): JSX.Element => {
|
|
49
51
|
const {
|
|
50
52
|
timezone,
|
|
@@ -64,12 +66,21 @@ export const ProfileView = ({
|
|
|
64
66
|
groupBy,
|
|
65
67
|
toggleGroupBy,
|
|
66
68
|
setGroupByLabels,
|
|
69
|
+
flamechartDimension,
|
|
70
|
+
setFlamechartDimension,
|
|
67
71
|
sandwichFunctionName,
|
|
68
72
|
resetSandwichFunctionName,
|
|
69
73
|
alignFunctionName,
|
|
70
74
|
setAlignFunctionName,
|
|
71
75
|
} = useVisualizationState();
|
|
72
76
|
|
|
77
|
+
useAutoSelectDimension(
|
|
78
|
+
flamegraphData.metadataLabels,
|
|
79
|
+
flamechartDimension,
|
|
80
|
+
setFlamechartDimension,
|
|
81
|
+
profileSource?.ProfileType()
|
|
82
|
+
);
|
|
83
|
+
|
|
73
84
|
const {colorMappings} = useProfileMetadata({
|
|
74
85
|
flamegraphArrow: flamegraphData.arrow,
|
|
75
86
|
metadataMappingFiles: flamegraphData.metadataMappingFiles,
|
|
@@ -95,7 +106,7 @@ export const ProfileView = ({
|
|
|
95
106
|
isHalfScreen,
|
|
96
107
|
dimensions,
|
|
97
108
|
flamegraphData,
|
|
98
|
-
|
|
109
|
+
samplesData,
|
|
99
110
|
topTableData,
|
|
100
111
|
sourceData,
|
|
101
112
|
profileSource,
|
|
@@ -105,6 +116,7 @@ export const ProfileView = ({
|
|
|
105
116
|
setNewCurPathArrow: setCurPathArrow,
|
|
106
117
|
perf,
|
|
107
118
|
queryClient,
|
|
119
|
+
onSwitchToOneMinute,
|
|
108
120
|
});
|
|
109
121
|
};
|
|
110
122
|
|
|
@@ -153,6 +165,8 @@ export const ProfileView = ({
|
|
|
153
165
|
preferencesModal={preferencesModal}
|
|
154
166
|
profileViewExternalSubActions={profileViewExternalSubActions}
|
|
155
167
|
setGroupByLabels={setGroupByLabels}
|
|
168
|
+
flamechartDimension={flamechartDimension}
|
|
169
|
+
setFlamechartDimension={setFlamechartDimension}
|
|
156
170
|
showVisualizationSelector={showVisualizationSelector}
|
|
157
171
|
sandwichFunctionName={sandwichFunctionName}
|
|
158
172
|
alignFunctionName={alignFunctionName}
|
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
|
|
14
14
|
import {RpcError} from '@protobuf-ts/runtime-rpc';
|
|
15
15
|
|
|
16
|
-
import {FlamegraphArrow, QueryServiceClient, Source, TableArrow} from '@parca/client';
|
|
16
|
+
import {FlamegraphArrow, LabelSet, QueryServiceClient, Source, TableArrow} from '@parca/client';
|
|
17
17
|
|
|
18
|
+
import {DataPoint} from '../../ProfileFlameChart/SamplesStrips/SamplesGraph';
|
|
18
19
|
import {ProfileSource} from '../../ProfileSource';
|
|
19
20
|
|
|
20
21
|
export interface FlamegraphData {
|
|
@@ -49,6 +50,18 @@ export interface SandwichData {
|
|
|
49
50
|
callers: FlamegraphData;
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
export interface SamplesSeries {
|
|
54
|
+
labelset: LabelSet;
|
|
55
|
+
data: DataPoint[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface SamplesData {
|
|
59
|
+
loading: boolean;
|
|
60
|
+
series?: SamplesSeries[];
|
|
61
|
+
error: RpcError | null;
|
|
62
|
+
stepMs?: number;
|
|
63
|
+
}
|
|
64
|
+
|
|
52
65
|
export type VisualizationType =
|
|
53
66
|
| 'flamegraph'
|
|
54
67
|
| 'callgraph'
|
|
@@ -61,14 +74,15 @@ export interface ProfileViewProps {
|
|
|
61
74
|
total: bigint;
|
|
62
75
|
filtered: bigint;
|
|
63
76
|
flamegraphData: FlamegraphData;
|
|
64
|
-
|
|
77
|
+
samplesData?: SamplesData;
|
|
65
78
|
sandwichData: SandwichData;
|
|
66
79
|
topTableData?: TopTableData;
|
|
67
80
|
sourceData?: SourceData;
|
|
68
81
|
profileSource: ProfileSource;
|
|
69
|
-
queryClient
|
|
82
|
+
queryClient: QueryServiceClient;
|
|
70
83
|
compare?: boolean;
|
|
71
84
|
onDownloadPProf: () => void;
|
|
72
85
|
pprofDownloading?: boolean;
|
|
73
86
|
showVisualizationSelector?: boolean;
|
|
87
|
+
onSwitchToOneMinute?: () => void;
|
|
74
88
|
}
|