@parca/profile 0.19.113 → 0.19.115

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.
Files changed (86) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/ProfileExplorer/ProfileExplorerSingle.d.ts.map +1 -1
  3. package/dist/ProfileExplorer/ProfileExplorerSingle.js +9 -3
  4. package/dist/ProfileFlameChart/SamplesStrips/SamplesGraph/index.d.ts +31 -0
  5. package/dist/ProfileFlameChart/SamplesStrips/SamplesGraph/index.d.ts.map +1 -0
  6. package/dist/{MetricsGraphStrips/AreaGraph → ProfileFlameChart/SamplesStrips/SamplesGraph}/index.js +32 -60
  7. package/dist/{MetricsGraphStrips/MetricsGraphStrips.stories.d.ts → ProfileFlameChart/SamplesStrips/SamplesStrips.stories.d.ts} +4 -3
  8. package/dist/ProfileFlameChart/SamplesStrips/SamplesStrips.stories.d.ts.map +1 -0
  9. package/dist/{MetricsGraphStrips/MetricsGraphStrips.stories.js → ProfileFlameChart/SamplesStrips/SamplesStrips.stories.js} +5 -4
  10. package/dist/{MetricsGraphStrips → ProfileFlameChart/SamplesStrips}/index.d.ts +5 -4
  11. package/dist/ProfileFlameChart/SamplesStrips/index.d.ts.map +1 -0
  12. package/dist/ProfileFlameChart/SamplesStrips/index.js +145 -0
  13. package/dist/ProfileFlameChart/index.d.ts +20 -0
  14. package/dist/ProfileFlameChart/index.d.ts.map +1 -0
  15. package/dist/ProfileFlameChart/index.js +155 -0
  16. package/dist/ProfileFlameGraph/index.d.ts.map +1 -1
  17. package/dist/ProfileFlameGraph/index.js +0 -1
  18. package/dist/ProfileMetricsGraph/hooks/useQueryRange.d.ts +2 -1
  19. package/dist/ProfileMetricsGraph/hooks/useQueryRange.d.ts.map +1 -1
  20. package/dist/ProfileMetricsGraph/hooks/useQueryRange.js +11 -21
  21. package/dist/ProfileMetricsGraph/index.d.ts.map +1 -1
  22. package/dist/ProfileMetricsGraph/index.js +13 -3
  23. package/dist/ProfileSelector/index.d.ts.map +1 -1
  24. package/dist/ProfileSelector/index.js +4 -0
  25. package/dist/ProfileView/components/ActionButtons/GroupByDropdown.d.ts +1 -0
  26. package/dist/ProfileView/components/ActionButtons/GroupByDropdown.d.ts.map +1 -1
  27. package/dist/ProfileView/components/ActionButtons/GroupByDropdown.js +2 -2
  28. package/dist/ProfileView/components/DashboardItems/index.d.ts +5 -4
  29. package/dist/ProfileView/components/DashboardItems/index.d.ts.map +1 -1
  30. package/dist/ProfileView/components/DashboardItems/index.js +4 -3
  31. package/dist/ProfileView/components/GroupByLabelsDropdown/index.d.ts +2 -1
  32. package/dist/ProfileView/components/GroupByLabelsDropdown/index.d.ts.map +1 -1
  33. package/dist/ProfileView/components/GroupByLabelsDropdown/index.js +2 -2
  34. package/dist/ProfileView/components/Toolbars/MultiLevelDropdown.js +1 -1
  35. package/dist/ProfileView/components/Toolbars/index.d.ts +2 -0
  36. package/dist/ProfileView/components/Toolbars/index.d.ts.map +1 -1
  37. package/dist/ProfileView/components/Toolbars/index.js +4 -2
  38. package/dist/ProfileView/hooks/useAutoSelectDimension.d.ts +16 -0
  39. package/dist/ProfileView/hooks/useAutoSelectDimension.d.ts.map +1 -0
  40. package/dist/ProfileView/hooks/useAutoSelectDimension.js +75 -0
  41. package/dist/ProfileView/hooks/useVisualizationState.d.ts +2 -0
  42. package/dist/ProfileView/hooks/useVisualizationState.d.ts.map +1 -1
  43. package/dist/ProfileView/hooks/useVisualizationState.js +8 -0
  44. package/dist/ProfileView/index.d.ts +1 -1
  45. package/dist/ProfileView/index.d.ts.map +1 -1
  46. package/dist/ProfileView/index.js +7 -4
  47. package/dist/ProfileView/types/visualization.d.ts +15 -3
  48. package/dist/ProfileView/types/visualization.d.ts.map +1 -1
  49. package/dist/ProfileViewWithData.d.ts +2 -1
  50. package/dist/ProfileViewWithData.d.ts.map +1 -1
  51. package/dist/ProfileViewWithData.js +41 -29
  52. package/dist/index.d.ts +1 -0
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +4 -0
  55. package/dist/styles.css +1 -1
  56. package/package.json +8 -7
  57. package/src/ProfileExplorer/ProfileExplorerSingle.tsx +14 -3
  58. package/src/{MetricsGraphStrips/AreaGraph → ProfileFlameChart/SamplesStrips/SamplesGraph}/index.tsx +77 -81
  59. package/src/{MetricsGraphStrips/MetricsGraphStrips.stories.tsx → ProfileFlameChart/SamplesStrips/SamplesStrips.stories.tsx} +7 -6
  60. package/src/ProfileFlameChart/SamplesStrips/index.tsx +317 -0
  61. package/src/ProfileFlameChart/index.tsx +305 -0
  62. package/src/ProfileFlameGraph/index.tsx +0 -1
  63. package/src/ProfileMetricsGraph/hooks/useQueryRange.ts +18 -26
  64. package/src/ProfileMetricsGraph/index.tsx +24 -2
  65. package/src/ProfileSelector/index.tsx +11 -0
  66. package/src/ProfileView/components/ActionButtons/GroupByDropdown.tsx +3 -0
  67. package/src/ProfileView/components/DashboardItems/index.tsx +19 -17
  68. package/src/ProfileView/components/GroupByLabelsDropdown/index.tsx +4 -2
  69. package/src/ProfileView/components/Toolbars/MultiLevelDropdown.tsx +1 -1
  70. package/src/ProfileView/components/Toolbars/index.tsx +18 -1
  71. package/src/ProfileView/hooks/useAutoSelectDimension.ts +90 -0
  72. package/src/ProfileView/hooks/useVisualizationState.ts +17 -0
  73. package/src/ProfileView/index.tsx +16 -2
  74. package/src/ProfileView/types/visualization.ts +17 -3
  75. package/src/ProfileViewWithData.tsx +80 -37
  76. package/src/index.tsx +4 -0
  77. package/dist/MetricsGraphStrips/AreaGraph/Tooltip.d.ts +0 -10
  78. package/dist/MetricsGraphStrips/AreaGraph/Tooltip.d.ts.map +0 -1
  79. package/dist/MetricsGraphStrips/AreaGraph/Tooltip.js +0 -44
  80. package/dist/MetricsGraphStrips/AreaGraph/index.d.ts +0 -21
  81. package/dist/MetricsGraphStrips/AreaGraph/index.d.ts.map +0 -1
  82. package/dist/MetricsGraphStrips/MetricsGraphStrips.stories.d.ts.map +0 -1
  83. package/dist/MetricsGraphStrips/index.d.ts.map +0 -1
  84. package/dist/MetricsGraphStrips/index.js +0 -70
  85. package/src/MetricsGraphStrips/AreaGraph/Tooltip.tsx +0 -83
  86. package/src/MetricsGraphStrips/index.tsx +0 -142
@@ -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
- flamechartData,
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
- flamechartData,
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
- flamechartData: FlamegraphData;
77
+ samplesData?: SamplesData;
65
78
  sandwichData: SandwichData;
66
79
  topTableData?: TopTableData;
67
80
  sourceData?: SourceData;
68
81
  profileSource: ProfileSource;
69
- queryClient?: QueryServiceClient;
82
+ queryClient: QueryServiceClient;
70
83
  compare?: boolean;
71
84
  onDownloadPProf: () => void;
72
85
  pprofDownloading?: boolean;
73
86
  showVisualizationSelector?: boolean;
87
+ onSwitchToOneMinute?: () => void;
74
88
  }
@@ -14,14 +14,27 @@
14
14
  import {useEffect, useMemo, useState} from 'react';
15
15
 
16
16
  import {QueryRequest_ReportType, QueryServiceClient} from '@parca/client';
17
- import {useGrpcMetadata, useParcaContext, useURLState} from '@parca/components';
17
+ import {
18
+ NumberParser,
19
+ NumberSerializer,
20
+ useGrpcMetadata,
21
+ useParcaContext,
22
+ useURLState,
23
+ useURLStateCustom,
24
+ } from '@parca/components';
18
25
  import {saveAsBlob} from '@parca/utilities';
19
26
 
20
27
  import {validateFlameChartQuery} from './ProfileFlameGraph';
21
- import {FIELD_FUNCTION_NAME} from './ProfileFlameGraph/FlameGraphArrow';
28
+ import {FIELD_FUNCTION_NAME, FIELD_LABELS} from './ProfileFlameGraph/FlameGraphArrow';
29
+ import {boundsFromProfileSource} from './ProfileFlameGraph/FlameGraphArrow/utils';
30
+ import {
31
+ getStepCountFromScreenWidth,
32
+ useQueryRange,
33
+ } from './ProfileMetricsGraph/hooks/useQueryRange';
22
34
  import {MergedProfileSource, ProfileSource} from './ProfileSource';
23
35
  import {ProfileView} from './ProfileView';
24
36
  import {useProfileFilters} from './ProfileView/components/ProfileFilters/useProfileFilters';
37
+ import type {SamplesSeries} from './ProfileView/types/visualization';
25
38
  import {useQuery} from './useQuery';
26
39
  import {downloadPprof} from './utils';
27
40
 
@@ -30,12 +43,14 @@ interface ProfileViewWithDataProps {
30
43
  profileSource: ProfileSource;
31
44
  compare?: boolean;
32
45
  showVisualizationSelector?: boolean;
46
+ onSwitchToOneMinute?: () => void;
33
47
  }
34
48
 
35
49
  export const ProfileViewWithData = ({
36
50
  queryClient,
37
51
  profileSource,
38
52
  showVisualizationSelector,
53
+ onSwitchToOneMinute,
39
54
  }: ProfileViewWithDataProps): JSX.Element => {
40
55
  const metadata = useGrpcMetadata();
41
56
  const [dashboardItems, setDashboardItems] = useURLState<string[]>('dashboard_items', {
@@ -48,6 +63,9 @@ export const ProfileViewWithData = ({
48
63
  alwaysReturnArray: true,
49
64
  });
50
65
  const [sandwichFunctionName] = useURLState<string | undefined>('sandwich_function_name');
66
+ const [flamechartDimension] = useURLState<string[]>('flamechart_dimension', {
67
+ alwaysReturnArray: true,
68
+ });
51
69
 
52
70
  const [invertStack] = useURLState('invert_call_stack');
53
71
  const invertCallStack = invertStack === 'true';
@@ -96,21 +114,66 @@ export const ProfileViewWithData = ({
96
114
  protoFilters,
97
115
  });
98
116
 
99
- const {
100
- isLoading: flamechartLoading,
101
- response: flamechartResponse,
102
- error: flamechartError,
103
- } = useQuery(queryClient, profileSource, QueryRequest_ReportType.FLAMECHART, {
104
- skip: !(
105
- dashboardItems.includes('flamechart') &&
106
- validateFlameChartQuery(profileSource as MergedProfileSource).isValid
107
- ),
108
- nodeTrimThreshold,
109
- groupBy,
110
- invertCallStack,
111
- protoFilters,
117
+ const samplesEnabled = !!(
118
+ dashboardItems.includes('flamechart') &&
119
+ validateFlameChartQuery(profileSource as MergedProfileSource).isValid &&
120
+ (flamechartDimension ?? []).length > 0
121
+ );
122
+
123
+ const [samplesFromMs, samplesToMs] = useMemo(() => {
124
+ const bounds = boundsFromProfileSource(profileSource);
125
+ return [Number(bounds[0] / 1_000_000n), Number(bounds[1] / 1_000_000n)];
126
+ }, [profileSource]);
127
+
128
+ const samplesSumBy = useMemo(
129
+ () =>
130
+ (flamechartDimension ?? []).map(f =>
131
+ f.startsWith('labels.') ? f.slice('labels.'.length) : f
132
+ ),
133
+ [flamechartDimension]
134
+ );
135
+
136
+ // Samples step count: 2px per data point for finer granularity in strips
137
+ const [samplesStepCount] = useURLStateCustom<number>('samples_step_count', {
138
+ defaultValue: String(getStepCountFromScreenWidth(2)),
139
+ parse: NumberParser,
140
+ stringify: NumberSerializer,
112
141
  });
113
142
 
143
+ const {
144
+ isLoading: samplesLoading,
145
+ response: samplesRangeResponse,
146
+ error: samplesError,
147
+ stepDurationMs: samplesStepMs,
148
+ } = useQueryRange(
149
+ queryClient,
150
+ samplesEnabled ? (profileSource as MergedProfileSource).query.toString() : '',
151
+ samplesFromMs,
152
+ samplesToMs,
153
+ samplesSumBy,
154
+ samplesStepCount,
155
+ !samplesEnabled
156
+ );
157
+
158
+ // Map QueryRange response to SamplesData
159
+ // TODO (manoj): Check if we can skip this mapping and adapt the CPUSampleStrips to work directly with the QueryRange response format.
160
+ const samplesData = useMemo(() => {
161
+ if (samplesLoading) return {loading: true, error: null};
162
+ if (samplesError != null) return {loading: false, error: samplesError};
163
+ if (samplesRangeResponse?.series?.length == null) return {loading: false, error: null};
164
+
165
+ const series: SamplesSeries[] = samplesRangeResponse.series.map(ms => ({
166
+ labelset: ms.labelset ?? {labels: []},
167
+ data: ms.samples.map(s => ({
168
+ timestamp: Number(s.timestamp!.seconds) * 1000 + Math.floor(s.timestamp!.nanos / 1_000_000),
169
+ value: Number(s.value),
170
+ sampleCount: Number(s.count),
171
+ })),
172
+ }));
173
+
174
+ return {loading: false, series, error: null, stepMs: samplesStepMs};
175
+ }, [samplesLoading, samplesRangeResponse, samplesError, samplesStepMs]);
176
+
114
177
  const {
115
178
  isLoading: profileMetadataLoading,
116
179
  response: profileMetadataResponse,
@@ -227,9 +290,6 @@ export const ProfileViewWithData = ({
227
290
  } else if (sourceResponse !== null) {
228
291
  total = BigInt(sourceResponse.total);
229
292
  filtered = BigInt(sourceResponse.filtered);
230
- } else if (flamechartResponse !== null) {
231
- total = BigInt(flamechartResponse.total);
232
- filtered = BigInt(flamechartResponse.filtered);
233
293
  } else if (callersFlamegraphResponse !== null) {
234
294
  total = BigInt(callersFlamegraphResponse.total);
235
295
  filtered = BigInt(callersFlamegraphResponse.filtered);
@@ -262,25 +322,6 @@ export const ProfileViewWithData = ({
262
322
  metadataLoading: profileMetadataLoading,
263
323
  metadataRefetch,
264
324
  }}
265
- flamechartData={{
266
- loading: flamechartLoading && profileMetadataLoading,
267
- arrow:
268
- flamechartResponse?.report.oneofKind === 'flamegraphArrow'
269
- ? flamechartResponse?.report?.flamegraphArrow
270
- : undefined,
271
- total: BigInt(flamechartResponse?.total ?? '0'),
272
- filtered: BigInt(flamechartResponse?.filtered ?? '0'),
273
- error: flamechartError,
274
- metadataMappingFiles:
275
- profileMetadataResponse?.report.oneofKind === 'profileMetadata'
276
- ? profileMetadataResponse?.report?.profileMetadata?.mappingFiles
277
- : undefined,
278
- metadataLabels:
279
- profileMetadataResponse?.report.oneofKind === 'profileMetadata'
280
- ? profileMetadataResponse?.report?.profileMetadata?.labels
281
- : undefined,
282
- metadataLoading: profileMetadataLoading,
283
- }}
284
325
  topTableData={{
285
326
  loading: tableLoading,
286
327
  arrow:
@@ -333,11 +374,13 @@ export const ProfileViewWithData = ({
333
374
  metadataLoading: profileMetadataLoading,
334
375
  },
335
376
  }}
377
+ samplesData={samplesData}
336
378
  profileSource={profileSource}
337
379
  queryClient={queryClient}
338
380
  onDownloadPProf={() => void downloadPProfClick()}
339
381
  pprofDownloading={pprofDownloading}
340
382
  showVisualizationSelector={showVisualizationSelector}
383
+ onSwitchToOneMinute={onSwitchToOneMinute}
341
384
  />
342
385
  );
343
386
  };
package/src/index.tsx CHANGED
@@ -29,6 +29,7 @@ import {useQueryState} from './hooks/useQueryState';
29
29
 
30
30
  export {useMetricsGraphDimensions} from './MetricsGraph/useMetricsGraphDimensions';
31
31
 
32
+ export * from './ProfileFlameChart';
32
33
  export * from './ProfileFlameGraph';
33
34
  export * from './ProfileSource';
34
35
  export {
@@ -57,6 +58,9 @@ export const DEFAULT_PROFILE_EXPLORER_PARAM_VALUES: ParamPreferences = {
57
58
  group_by: {
58
59
  splitOnCommas: true,
59
60
  },
61
+ flamechart_dimension: {
62
+ splitOnCommas: true,
63
+ },
60
64
  };
61
65
 
62
66
  export {useProfileTypes} from './ProfileSelector';
@@ -1,10 +0,0 @@
1
- interface TooltipProps {
2
- x: number;
3
- y: number;
4
- timestamp: Date | number;
5
- value: number;
6
- containerWidth: number;
7
- }
8
- export declare function Tooltip({ x, y, timestamp, value, containerWidth }: TooltipProps): JSX.Element;
9
- export {};
10
- //# sourceMappingURL=Tooltip.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Tooltip.d.ts","sourceRoot":"","sources":["../../../src/MetricsGraphStrips/AreaGraph/Tooltip.tsx"],"names":[],"mappings":"AAkBA,UAAU,YAAY;IACpB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,SAAS,EAAE,IAAI,GAAG,MAAM,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,OAAO,CAAC,EAAC,CAAC,EAAE,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,cAAc,EAAC,EAAE,YAAY,GAAG,GAAG,CAAC,OAAO,CAwD3F"}
@@ -1,44 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // Copyright 2022 The Parca Authors
3
- // Licensed under the Apache License, Version 2.0 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // http://www.apache.org/licenses/LICENSE-2.0
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
- import { useEffect, useRef, useState } from 'react';
15
- import { useParcaContext } from '@parca/components';
16
- import { formatDateTimeDownToMS, valueFormatter } from '@parca/utilities';
17
- export function Tooltip({ x, y, timestamp, value, containerWidth }) {
18
- const { timezone } = useParcaContext();
19
- const tooltipRef = useRef(null);
20
- const [tooltipPosition, setTooltipPosition] = useState({ x, y });
21
- const baseOffset = {
22
- x: 16,
23
- y: -8,
24
- };
25
- useEffect(() => {
26
- if (tooltipRef.current != null) {
27
- const tooltipWidth = tooltipRef.current.offsetWidth;
28
- let newX = x + baseOffset.x;
29
- let newY = y + baseOffset.y;
30
- if (newX + tooltipWidth > containerWidth) {
31
- newX = x - tooltipWidth - baseOffset.x;
32
- }
33
- if (newY < 0) {
34
- newY = y + Math.abs(baseOffset.y);
35
- }
36
- setTooltipPosition({ x: newX, y: newY });
37
- }
38
- }, [x, y, containerWidth, baseOffset.x, baseOffset.y]);
39
- return (_jsx("div", { ref: tooltipRef, className: "absolute bg-white dark:bg-gray-800 rounded-md shadow-lg border border-gray-200 dark:border-gray-700 p-2 text-sm z-50", style: {
40
- left: `${tooltipPosition.x}px`,
41
- top: `${tooltipPosition.y}px`,
42
- pointerEvents: 'none',
43
- }, children: _jsxs("div", { className: "flex flex-col gap-1", children: [_jsxs("div", { className: "flex gap-1 items-center", children: [_jsx("div", { children: "Timestamp:" }), _jsx("div", { className: "text-gray-600 dark:text-gray-300", children: formatDateTimeDownToMS(timestamp, timezone) })] }), _jsxs("div", { className: "flex gap-1 items-center", children: [_jsx("div", { children: "Value:" }), _jsx("div", { className: "text-gray-600 dark:text-gray-300", children: valueFormatter(value, 'nanoseconds', 2) })] })] }) }));
44
- }
@@ -1,21 +0,0 @@
1
- import { NumberDuo } from '../../utils';
2
- export interface DataPoint {
3
- timestamp: number;
4
- value: number;
5
- }
6
- interface Props {
7
- width: number;
8
- height: number;
9
- marginLeft?: number;
10
- marginRight?: number;
11
- marginTop?: number;
12
- marginBottom?: number;
13
- fill?: string;
14
- data: DataPoint[];
15
- selectionBounds?: NumberDuo | undefined;
16
- setSelectionBounds: (newBounds: NumberDuo | undefined) => void;
17
- valueBounds: NumberDuo;
18
- }
19
- export declare const AreaGraph: ({ data, height, width, marginLeft, marginRight, marginBottom, marginTop, fill, selectionBounds, setSelectionBounds, valueBounds, }: Props) => JSX.Element;
20
- export {};
21
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/MetricsGraphStrips/AreaGraph/index.tsx"],"names":[],"mappings":"AAmBA,OAAO,EAAC,SAAS,EAAC,MAAM,aAAa,CAAC;AAGtC,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,UAAU,KAAK;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,EAAE,CAAC;IAClB,eAAe,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IACxC,kBAAkB,EAAE,CAAC,SAAS,EAAE,SAAS,GAAG,SAAS,KAAK,IAAI,CAAC;IAC/D,WAAW,EAAE,SAAS,CAAC;CACxB;AAwJD,eAAO,MAAM,SAAS,GAAI,oIAYvB,KAAK,KAAG,GAAG,CAAC,OAoJd,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"MetricsGraphStrips.stories.d.ts","sourceRoot":"","sources":["../../src/MetricsGraphStrips/MetricsGraphStrips.stories.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAAC,IAAI,EAAC,MAAM,kBAAkB,CAAC;AAEtC,OAAO,EAAC,SAAS,EAAC,MAAM,UAAU,CAAC;AACnC,OAAO,EAAC,SAAS,EAAC,MAAM,aAAa,CAAC;AAqBtC,QAAA,MAAM,IAAI,EAAE,IAGX,CAAC;AACF,eAAe,IAAI,CAAC;AAEpB,eAAO,MAAM,cAAc;;;;;;;;;;;;;qCAKM,MAAM,UAAU,SAAS,KAAG,IAAI;;;mBAK9B,GAAG,KAAG,GAAG,CAAC,OAAO;CAUnD,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/MetricsGraphStrips/index.tsx"],"names":[],"mappings":"AAoBA,OAAO,EAAC,QAAQ,EAAC,MAAM,eAAe,CAAC;AAGvC,OAAO,EAAC,SAAS,EAAC,MAAM,UAAU,CAAC;AACnC,OAAO,EAAY,SAAS,EAAC,MAAM,aAAa,CAAC;AAEjD,UAAU,KAAK;IACb,IAAI,EAAE,QAAQ,EAAE,CAAC;IACjB,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC;IACpB,iBAAiB,CAAC,EAAE;QAClB,MAAM,EAAE,QAAQ,CAAC;QACjB,MAAM,EAAE,SAAS,CAAC;KACnB,CAAC;IACF,mBAAmB,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,GAAG,SAAS,KAAK,IAAI,CAAC;IAC/E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,SAAS,CAAC;CACnB;AAED,eAAO,MAAM,gBAAgB,GAAI,WAAW,QAAQ,KAAG,MAmBtD,CAAC;AAaF,eAAO,MAAM,kBAAkB,GAAI,wEAOhC,KAAK,KAAG,GAAG,CAAC,OAgEd,CAAC"}
@@ -1,70 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // Copyright 2022 The Parca Authors
3
- // Licensed under the Apache License, Version 2.0 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // http://www.apache.org/licenses/LICENSE-2.0
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
- import { useState } from 'react';
15
- import { Icon } from '@iconify/react';
16
- import cx from 'classnames';
17
- import * as d3 from 'd3';
18
- import isEqual from 'fast-deep-equal';
19
- import { TimelineGuide } from '../TimelineGuide';
20
- import { AreaGraph } from './AreaGraph';
21
- export const labelSetToString = (labelSet) => {
22
- if (labelSet === undefined) {
23
- return '{}';
24
- }
25
- let str = '{';
26
- let isFirst = true;
27
- for (const label of labelSet.labels) {
28
- if (!isFirst) {
29
- str += ', ';
30
- isFirst = false;
31
- }
32
- str += `${label.name}: ${label.value}`;
33
- }
34
- str += '}';
35
- return str;
36
- };
37
- const STRIP_HEIGHT = 24;
38
- const getTimelineGuideHeight = (cpus, collapsedIndices) => {
39
- return ((STRIP_HEIGHT + 4) * (cpus.length - collapsedIndices.length) +
40
- 20 * collapsedIndices.length +
41
- 24 -
42
- 6);
43
- };
44
- export const MetricsGraphStrips = ({ cpus, data, selectedTimeframe, onSelectedTimeframe, width, bounds, }) => {
45
- const [collapsedIndices, setCollapsedIndices] = useState([]);
46
- const color = d3.scaleOrdinal(d3.schemeObservable10);
47
- const valueBounds = d3.extent(data.flatMap(d => d.map(p => p.value)));
48
- return (_jsxs("div", { className: "flex flex-col gap-1 relative my-0 ml-[70px]", style: { width: width ?? '100%' }, children: [_jsx(TimelineGuide, { bounds: [BigInt(0), BigInt(bounds[1] - bounds[0])], width: width ?? 1468, height: getTimelineGuideHeight(cpus, collapsedIndices), margin: 1 }), cpus.map((cpu, i) => {
49
- const isCollapsed = collapsedIndices.includes(i);
50
- const isSelected = isEqual(cpu, selectedTimeframe?.labels);
51
- const labelStr = labelSetToString(cpu);
52
- return (_jsxs("div", { className: cx('min-h-5', {
53
- relative: !isSelected,
54
- 'sticky z-30 bg-white dark:bg-black bg-opacity-75': isSelected,
55
- }), style: { width: width ?? 1468, top: isSelected ? 302 : undefined }, children: [_jsxs("div", { className: "text-xs absolute top-0 left-0 flex gap-[2px] items-center bg-white/50 dark:bg-black/50 px-1 rounded-sm cursor-pointer", style: {
56
- zIndex: 15,
57
- }, onClick: () => {
58
- const newCollapsedIndices = [...collapsedIndices];
59
- if (collapsedIndices.includes(i)) {
60
- newCollapsedIndices.splice(newCollapsedIndices.indexOf(i), 1);
61
- }
62
- else {
63
- newCollapsedIndices.push(i);
64
- }
65
- setCollapsedIndices(newCollapsedIndices);
66
- }, children: [_jsx(Icon, { icon: isCollapsed ? 'bxs:right-arrow' : 'bxs:down-arrow' }), labelStr] }), !isCollapsed ? (_jsx(AreaGraph, { data: data[i], height: STRIP_HEIGHT, width: width ?? 1468, fill: color(labelStr), selectionBounds: isSelected ? selectedTimeframe?.bounds : undefined, setSelectionBounds: bounds => {
67
- onSelectedTimeframe(cpu, bounds);
68
- }, valueBounds: valueBounds })) : null] }, labelStr));
69
- })] }));
70
- };