@parca/profile 0.19.83 → 0.19.85

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 (83) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/MatchersInput/index.d.ts +2 -34
  3. package/dist/MatchersInput/index.d.ts.map +1 -1
  4. package/dist/MatchersInput/index.js +14 -91
  5. package/dist/MetricsGraph/index.d.ts.map +1 -1
  6. package/dist/MetricsGraph/index.js +13 -1
  7. package/dist/ProfileMetricsGraph/index.d.ts.map +1 -1
  8. package/dist/ProfileMetricsGraph/index.js +6 -29
  9. package/dist/ProfileSelector/MetricsGraphSection.d.ts +8 -10
  10. package/dist/ProfileSelector/MetricsGraphSection.d.ts.map +1 -1
  11. package/dist/ProfileSelector/MetricsGraphSection.js +8 -41
  12. package/dist/ProfileSelector/index.d.ts +1 -29
  13. package/dist/ProfileSelector/index.d.ts.map +1 -1
  14. package/dist/ProfileSelector/index.js +14 -10
  15. package/dist/QueryControls/index.d.ts +46 -0
  16. package/dist/QueryControls/index.d.ts.map +1 -0
  17. package/dist/{ProfileSelector/QueryControls.js → QueryControls/index.js} +16 -13
  18. package/dist/SimpleMatchers/Select.js +1 -1
  19. package/dist/SimpleMatchers/index.d.ts +6 -10
  20. package/dist/SimpleMatchers/index.d.ts.map +1 -1
  21. package/dist/SimpleMatchers/index.js +30 -45
  22. package/dist/ViewMatchers/index.d.ts +0 -9
  23. package/dist/ViewMatchers/index.d.ts.map +1 -1
  24. package/dist/ViewMatchers/index.js +20 -12
  25. package/dist/contexts/LabelsQueryProvider.d.ts +35 -0
  26. package/dist/contexts/LabelsQueryProvider.d.ts.map +1 -0
  27. package/dist/contexts/LabelsQueryProvider.js +70 -0
  28. package/dist/contexts/UnifiedLabelsContext.d.ts +37 -0
  29. package/dist/contexts/UnifiedLabelsContext.d.ts.map +1 -0
  30. package/dist/contexts/UnifiedLabelsContext.js +88 -0
  31. package/dist/contexts/utils.d.ts +10 -0
  32. package/dist/contexts/utils.d.ts.map +1 -0
  33. package/dist/contexts/utils.js +31 -0
  34. package/dist/hooks/useLabels.d.ts +23 -0
  35. package/dist/hooks/useLabels.d.ts.map +1 -0
  36. package/dist/hooks/useLabels.js +75 -0
  37. package/dist/hooks/useQueryState.d.ts +3 -1
  38. package/dist/hooks/useQueryState.d.ts.map +1 -1
  39. package/dist/hooks/useQueryState.js +19 -12
  40. package/dist/index.d.ts +9 -3
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +9 -3
  43. package/dist/styles.css +1 -1
  44. package/dist/useSumBy.js +1 -1
  45. package/package.json +2 -2
  46. package/src/MatchersInput/index.tsx +17 -163
  47. package/src/MetricsGraph/index.tsx +17 -1
  48. package/src/ProfileMetricsGraph/index.tsx +17 -98
  49. package/src/ProfileSelector/MetricsGraphSection.tsx +25 -119
  50. package/src/ProfileSelector/index.tsx +115 -109
  51. package/src/{ProfileSelector/QueryControls.tsx → QueryControls/index.tsx} +76 -84
  52. package/src/SimpleMatchers/Select.tsx +1 -1
  53. package/src/SimpleMatchers/index.tsx +46 -84
  54. package/src/ViewMatchers/index.tsx +22 -30
  55. package/src/contexts/LabelsQueryProvider.tsx +142 -0
  56. package/src/contexts/UnifiedLabelsContext.tsx +155 -0
  57. package/src/contexts/utils.ts +43 -0
  58. package/src/hooks/useLabels.ts +121 -0
  59. package/src/hooks/useQueryState.ts +27 -16
  60. package/src/index.tsx +29 -3
  61. package/src/useSumBy.ts +1 -1
  62. package/dist/MetricsGraph/UtilizationMetrics/Throughput.d.ts +0 -29
  63. package/dist/MetricsGraph/UtilizationMetrics/Throughput.d.ts.map +0 -1
  64. package/dist/MetricsGraph/UtilizationMetrics/Throughput.js +0 -175
  65. package/dist/MetricsGraph/UtilizationMetrics/index.d.ts +0 -28
  66. package/dist/MetricsGraph/UtilizationMetrics/index.d.ts.map +0 -1
  67. package/dist/MetricsGraph/UtilizationMetrics/index.js +0 -186
  68. package/dist/ProfileSelector/QueryControls.d.ts +0 -43
  69. package/dist/ProfileSelector/QueryControls.d.ts.map +0 -1
  70. package/dist/contexts/MatchersInputLabelsContext.d.ts +0 -29
  71. package/dist/contexts/MatchersInputLabelsContext.d.ts.map +0 -1
  72. package/dist/contexts/MatchersInputLabelsContext.js +0 -79
  73. package/dist/contexts/SimpleMatchersLabelContext.d.ts +0 -25
  74. package/dist/contexts/SimpleMatchersLabelContext.d.ts.map +0 -1
  75. package/dist/contexts/SimpleMatchersLabelContext.js +0 -115
  76. package/dist/contexts/UtilizationLabelsContext.d.ts +0 -15
  77. package/dist/contexts/UtilizationLabelsContext.d.ts.map +0 -1
  78. package/dist/contexts/UtilizationLabelsContext.js +0 -25
  79. package/src/MetricsGraph/UtilizationMetrics/Throughput.tsx +0 -405
  80. package/src/MetricsGraph/UtilizationMetrics/index.tsx +0 -426
  81. package/src/contexts/MatchersInputLabelsContext.tsx +0 -141
  82. package/src/contexts/SimpleMatchersLabelContext.tsx +0 -189
  83. package/src/contexts/UtilizationLabelsContext.tsx +0 -45
@@ -0,0 +1,121 @@
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 {LabelsRequest, LabelsResponse, QueryServiceClient, ValuesRequest} from '@parca/client';
15
+ import {useGrpcMetadata} from '@parca/components';
16
+ import {millisToProtoTimestamp, sanitizeLabelValue} from '@parca/utilities';
17
+
18
+ import useGrpcQuery from '../useGrpcQuery';
19
+
20
+ export interface ILabelValuesResult {
21
+ response: string[];
22
+ error?: Error;
23
+ }
24
+
25
+ interface UseLabelValues {
26
+ result: ILabelValuesResult;
27
+ loading: boolean;
28
+ refetch: () => Promise<void>;
29
+ }
30
+
31
+ export interface ILabelNamesResult {
32
+ response?: LabelsResponse;
33
+ error?: Error;
34
+ }
35
+
36
+ interface UseLabelNames {
37
+ result: ILabelNamesResult;
38
+ loading: boolean;
39
+ refetch: () => Promise<void>;
40
+ }
41
+
42
+ export const useLabelNames = (
43
+ client: QueryServiceClient,
44
+ profileType: string,
45
+ start?: number,
46
+ end?: number,
47
+ match?: string[]
48
+ ): UseLabelNames => {
49
+ const metadata = useGrpcMetadata();
50
+
51
+ const {data, isLoading, error, refetch} = useGrpcQuery<LabelsResponse>({
52
+ key: ['labelNames', profileType, match?.join(','), start, end],
53
+ queryFn: async signal => {
54
+ const request: LabelsRequest = {match: match !== undefined ? match : []};
55
+ if (start !== undefined && end !== undefined) {
56
+ request.start = millisToProtoTimestamp(start);
57
+ request.end = millisToProtoTimestamp(end);
58
+ }
59
+ if (profileType !== undefined) {
60
+ request.profileType = profileType;
61
+ }
62
+ const {response} = await client.labels(request, {meta: metadata, abort: signal});
63
+ return response;
64
+ },
65
+ options: {
66
+ enabled: profileType !== undefined && profileType !== '',
67
+ keepPreviousData: false,
68
+ },
69
+ });
70
+
71
+ console.log('Label names query result:', {data, error, isLoading});
72
+
73
+ return {
74
+ result: {response: data, error: error as Error},
75
+ loading: isLoading,
76
+ refetch: async () => {
77
+ await refetch();
78
+ },
79
+ };
80
+ };
81
+
82
+ export const useLabelValues = (
83
+ client: QueryServiceClient,
84
+ labelName: string,
85
+ profileType: string,
86
+ start?: number,
87
+ end?: number
88
+ ): UseLabelValues => {
89
+ const metadata = useGrpcMetadata();
90
+
91
+ const {data, isLoading, error, refetch} = useGrpcQuery<string[]>({
92
+ key: ['labelValues', labelName, profileType, start, end],
93
+ queryFn: async signal => {
94
+ const request: ValuesRequest = {labelName, match: [], profileType};
95
+ if (start !== undefined && end !== undefined) {
96
+ request.start = millisToProtoTimestamp(start);
97
+ request.end = millisToProtoTimestamp(end);
98
+ }
99
+ const {response} = await client.values(request, {meta: metadata, abort: signal});
100
+ return sanitizeLabelValue(response.labelValues);
101
+ },
102
+ options: {
103
+ enabled:
104
+ profileType !== undefined &&
105
+ profileType !== '' &&
106
+ labelName !== undefined &&
107
+ labelName !== '',
108
+ keepPreviousData: false,
109
+ },
110
+ });
111
+
112
+ console.log('Label values query result:', {data, error, isLoading, labelName});
113
+
114
+ return {
115
+ result: {response: data ?? [], error: error as Error},
116
+ loading: isLoading,
117
+ refetch: async () => {
118
+ await refetch();
119
+ },
120
+ };
121
+ };
@@ -38,7 +38,7 @@ interface UseQueryStateReturn {
38
38
  draftSelection: QuerySelection;
39
39
 
40
40
  // Draft setters (update local state only)
41
- setDraftExpression: (expression: string, commit?: boolean) => void;
41
+ setDraftExpression: (expression: string) => void;
42
42
  setDraftTimeRange: (from: number, to: number, timeSelection: string) => void;
43
43
  setDraftSumBy: (sumBy: string[] | undefined) => void;
44
44
  setDraftProfileName: (profileName: string) => void;
@@ -58,6 +58,12 @@ interface UseQueryStateReturn {
58
58
 
59
59
  // Loading state for sumBy computation
60
60
  sumByLoading: boolean;
61
+
62
+ // draft parsed query
63
+ draftParsedQuery: Query | null;
64
+
65
+ // parsed query
66
+ parsedQuery: Query | null;
61
67
  }
62
68
 
63
69
  export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryStateReturn => {
@@ -331,20 +337,6 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
331
337
  ]
332
338
  );
333
339
 
334
- // Draft setters (update local state only, or commit directly if specified)
335
- const setDraftExpressionCallback = useCallback(
336
- (newExpression: string, commit = false) => {
337
- if (commit) {
338
- // Commit with the new expression, which will also update merge params and selection
339
- commitDraft(undefined, newExpression);
340
- } else {
341
- // Only update draft state
342
- setDraftExpression(newExpression);
343
- }
344
- },
345
- [commitDraft]
346
- );
347
-
348
340
  const setDraftTimeRange = useCallback(
349
341
  (newFrom: number, newTo: number, newTimeSelection: string) => {
350
342
  setDraftFrom(newFrom.toString());
@@ -390,6 +382,22 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
390
382
  [batchUpdates, setSelectionParam, setMergeFromState, setMergeToState]
391
383
  );
392
384
 
385
+ const draftParsedQuery = useMemo(() => {
386
+ try {
387
+ return Query.parse(draftSelection.expression ?? '');
388
+ } catch {
389
+ return Query.parse('');
390
+ }
391
+ }, [draftSelection.expression]);
392
+
393
+ const parsedQuery = useMemo(() => {
394
+ try {
395
+ return Query.parse(querySelection.expression ?? '');
396
+ } catch {
397
+ return Query.parse('');
398
+ }
399
+ }, [querySelection.expression]);
400
+
393
401
  return {
394
402
  // Current committed state
395
403
  querySelection,
@@ -398,7 +406,7 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
398
406
  draftSelection,
399
407
 
400
408
  // Draft setters
401
- setDraftExpression: setDraftExpressionCallback,
409
+ setDraftExpression,
402
410
  setDraftTimeRange,
403
411
  setDraftSumBy: setDraftSumByCallback,
404
412
  setDraftProfileName,
@@ -414,5 +422,8 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
414
422
 
415
423
  // Loading state
416
424
  sumByLoading: sumBySelectionLoading,
425
+
426
+ draftParsedQuery,
427
+ parsedQuery,
417
428
  };
418
429
  };
package/src/index.tsx CHANGED
@@ -13,11 +13,21 @@
13
13
 
14
14
  import type {ParamPreferences} from '@parca/components';
15
15
 
16
- import {useLabelNames} from './MatchersInput';
16
+ import MetricsGraph, {type ContextMenuItemOrSubmenu, type Series} from './MetricsGraph';
17
17
  import ProfileExplorer from './ProfileExplorer';
18
18
  import ProfileTypeSelector from './ProfileTypeSelector';
19
- import SelectWithRefresh from './SelectWithRefresh';
19
+ import {SelectWithRefresh} from './SelectWithRefresh';
20
20
  import CustomSelect from './SimpleMatchers/Select';
21
+ import {
22
+ LabelsQueryProvider,
23
+ useLabelsQueryProvider,
24
+ type LabelsQueryProviderContextType,
25
+ } from './contexts/LabelsQueryProvider';
26
+ import {UnifiedLabelsProvider, useUnifiedLabels} from './contexts/UnifiedLabelsContext';
27
+ import {useLabelNames} from './hooks/useLabels';
28
+ import {useQueryState} from './hooks/useQueryState';
29
+
30
+ export {useMetricsGraphDimensions} from './MetricsGraph/useMetricsGraphDimensions';
21
31
 
22
32
  export * from './ProfileFlameGraph';
23
33
  export * from './ProfileSource';
@@ -32,6 +42,7 @@ export * from './ProfileTypeSelector';
32
42
  export * from './SourceView';
33
43
  export * from './ProfileMetricsGraph';
34
44
  export * from './useSumBy';
45
+ export {QueryControls} from './QueryControls';
35
46
 
36
47
  export {default as ProfileFilters} from './ProfileView/components/ProfileFilters';
37
48
  export {useProfileFiltersUrlState} from './ProfileView/components/ProfileFilters/useProfileFiltersUrlState';
@@ -43,4 +54,19 @@ export const DEFAULT_PROFILE_EXPLORER_PARAM_VALUES: ParamPreferences = {
43
54
  },
44
55
  };
45
56
 
46
- export {ProfileExplorer, ProfileTypeSelector, CustomSelect, SelectWithRefresh, useLabelNames};
57
+ export {
58
+ ProfileExplorer,
59
+ ProfileTypeSelector,
60
+ CustomSelect,
61
+ SelectWithRefresh,
62
+ useLabelNames,
63
+ MetricsGraph,
64
+ type ContextMenuItemOrSubmenu,
65
+ type Series,
66
+ LabelsQueryProvider,
67
+ useLabelsQueryProvider,
68
+ UnifiedLabelsProvider,
69
+ useUnifiedLabels,
70
+ useQueryState,
71
+ type LabelsQueryProviderContextType,
72
+ };
package/src/useSumBy.ts CHANGED
@@ -17,7 +17,7 @@ import {QueryServiceClient} from '@parca/client';
17
17
  import {DateTimeRange} from '@parca/components';
18
18
  import {ProfileType} from '@parca/parser';
19
19
 
20
- import {useLabelNames} from './MatchersInput/index';
20
+ import {useLabelNames} from './hooks/useLabels';
21
21
 
22
22
  export const DEFAULT_EMPTY_SUM_BY: string[] = [];
23
23
 
@@ -1,29 +0,0 @@
1
- import { DateTimeRange } from '@parca/components';
2
- import { type UtilizationMetrics as MetricSeries } from '../../ProfileSelector';
3
- interface CommonProps {
4
- transmitData: MetricSeries[];
5
- receiveData: MetricSeries[];
6
- addLabelMatcher: (labels: {
7
- key: string;
8
- value: string;
9
- } | Array<{
10
- key: string;
11
- value: string;
12
- }>) => void;
13
- setTimeRange: (range: DateTimeRange) => void;
14
- name: string;
15
- humanReadableName: string;
16
- from: number;
17
- to: number;
18
- selectedSeries?: Array<{
19
- key: string;
20
- value: string;
21
- }>;
22
- onSeriesClick?: (name: string, seriesIndex: number) => void;
23
- }
24
- type Props = CommonProps & {
25
- utilizationMetricsLoading?: boolean;
26
- };
27
- declare const AreaChart: ({ transmitData, receiveData, addLabelMatcher, setTimeRange, utilizationMetricsLoading, name, humanReadableName, from, to, selectedSeries, onSeriesClick, }: Props) => JSX.Element;
28
- export default AreaChart;
29
- //# sourceMappingURL=Throughput.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Throughput.d.ts","sourceRoot":"","sources":["../../../src/MetricsGraph/UtilizationMetrics/Throughput.tsx"],"names":[],"mappings":"AAkBA,OAAO,EACL,aAAa,EAId,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAC,KAAK,kBAAkB,IAAI,YAAY,EAAC,MAAM,uBAAuB,CAAC;AAiB9E,UAAU,WAAW;IACnB,YAAY,EAAE,YAAY,EAAE,CAAC;IAC7B,WAAW,EAAE,YAAY,EAAE,CAAC;IAC5B,eAAe,EAAE,CACf,MAAM,EAAE;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAC,GAAG,KAAK,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAC,CAAC,KACvE,IAAI,CAAC;IACV,YAAY,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB,EAAE,MAAM,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,cAAc,CAAC,EAAE,KAAK,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAC,CAAC,CAAC;IACrD,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7D;AAUD,KAAK,KAAK,GAAG,WAAW,GAAG;IACzB,yBAAyB,CAAC,EAAE,OAAO,CAAC;CACrC,CAAC;AAoRF,QAAA,MAAM,SAAS,GAAI,4JAYhB,KAAK,KAAG,GAAG,CAAC,OA8Cd,CAAC;AAEF,eAAe,SAAS,CAAC"}
@@ -1,175 +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 { useMemo } from 'react';
15
- import { Icon } from '@iconify/react';
16
- import { AnimatePresence, motion } from 'framer-motion';
17
- import { MetricsGraphSkeleton, TextWithTooltip, useParcaContext, } from '@parca/components';
18
- import { formatDate, timePattern, valueFormatter } from '@parca/utilities';
19
- import MetricsGraph from '../index';
20
- import { useMetricsGraphDimensions } from '../useMetricsGraphDimensions';
21
- const transformUtilizationLabels = (label) => {
22
- return label.replace('attributes.', '').replace('attributes_resource.', '');
23
- };
24
- const createThroughputContextMenuItems = (addLabelMatcher, transmitData, receiveData) => {
25
- const allData = [...transmitData, ...receiveData];
26
- return [
27
- {
28
- id: 'focus-on-single-series',
29
- label: 'Focus only on this series',
30
- icon: 'ph:star',
31
- onClick: (closestPoint, _series) => {
32
- if (closestPoint != null &&
33
- allData.length > 0 &&
34
- allData[closestPoint.seriesIndex] != null) {
35
- const originalSeriesData = allData[closestPoint.seriesIndex];
36
- if (originalSeriesData.labelset?.labels != null) {
37
- const labels = originalSeriesData.labelset.labels.filter(label => label.name !== '__name__');
38
- const labelsToAdd = labels.map(label => ({
39
- key: label.name,
40
- value: label.value,
41
- }));
42
- addLabelMatcher(labelsToAdd);
43
- }
44
- }
45
- },
46
- },
47
- {
48
- id: 'add-to-query',
49
- label: 'Add to query',
50
- icon: 'material-symbols:add',
51
- createDynamicItems: (closestPoint, _series) => {
52
- if (closestPoint == null ||
53
- allData.length === 0 ||
54
- allData[closestPoint.seriesIndex] == null) {
55
- return [
56
- {
57
- id: 'no-labels-available',
58
- label: 'No labels available',
59
- icon: 'ph:warning',
60
- disabled: () => true,
61
- onClick: () => { }, // No-op for disabled item
62
- },
63
- ];
64
- }
65
- const originalSeriesData = allData[closestPoint.seriesIndex];
66
- if (originalSeriesData.labelset?.labels == null) {
67
- return [
68
- {
69
- id: 'no-labels-available',
70
- label: 'No labels available',
71
- icon: 'ph:warning',
72
- disabled: () => true,
73
- onClick: () => { }, // No-op for disabled item
74
- },
75
- ];
76
- }
77
- const labels = originalSeriesData.labelset.labels.filter(label => label.name !== '__name__');
78
- return labels.map(label => ({
79
- id: `add-label-${label.name}`,
80
- label: (_jsx("div", { className: "mr-3 inline-block rounded-lg bg-gray-200 px-2 py-1 text-xs font-bold text-gray-700 dark:bg-gray-700 dark:text-gray-300", children: `${transformUtilizationLabels(label.name)}="${label.value}"` })),
81
- onClick: () => {
82
- addLabelMatcher({
83
- key: label.name,
84
- value: label.value,
85
- });
86
- },
87
- }));
88
- },
89
- },
90
- ];
91
- };
92
- function transformToSeries(data, isReceive = false) {
93
- const series = data.reduce(function (agg, s) {
94
- if (s.labelset !== undefined) {
95
- const metric = s.labelset.labels.sort((a, b) => a.name.localeCompare(b.name));
96
- agg.push({
97
- metric,
98
- values: s.samples.reduce(function (agg, d) {
99
- if (d.timestamp !== undefined && d.value !== undefined) {
100
- // Multiply receive values by -1 to display below zero
101
- const value = isReceive ? -1 * d.value : d.value;
102
- agg.push([d.timestamp, value]);
103
- }
104
- return agg;
105
- }, []),
106
- labelset: metric.map(m => `${m.name}=${m.value}`).join(','),
107
- isReceive,
108
- isSelected: s.isSelected,
109
- });
110
- }
111
- return agg;
112
- }, []);
113
- // Sort values by timestamp for each series
114
- return series.map(series => ({
115
- ...series,
116
- values: series.values.sort((a, b) => a[0] - b[0]),
117
- }));
118
- }
119
- function transformNetworkSeriesToSeries(transmitData, receiveData) {
120
- const transmitSeries = transformToSeries(transmitData);
121
- const receiveSeries = transformToSeries(receiveData, true);
122
- const allSeries = [...transmitSeries, ...receiveSeries];
123
- return allSeries.map(networkSeries => {
124
- const labels = networkSeries.metric ?? [];
125
- const sortedLabels = labels
126
- .filter(label => label.name !== '__name__')
127
- .sort((a, b) => a.name.localeCompare(b.name));
128
- const labelString = sortedLabels.map(label => `${label.name}=${label.value}`).join(',');
129
- const id = (networkSeries.isReceive === true ? 'receive-' : 'transmit-') +
130
- (labelString !== '' ? labelString : 'default');
131
- return {
132
- id,
133
- values: networkSeries.values.map(([timestamp, value]) => [
134
- timestamp,
135
- value,
136
- ]),
137
- highlighted: networkSeries.isSelected ?? false,
138
- };
139
- });
140
- }
141
- const RawAreaChart = ({ transmitData, receiveData, transformedData, addLabelMatcher: _addLabelMatcher, setTimeRange, width, height, margin, humanReadableName, from, to, selectedSeries: _selectedSeries, onSeriesClick, contextMenuItems, }) => {
142
- const { timezone } = useParcaContext();
143
- // Compute original series data for rich tooltip
144
- const allOriginalData = useMemo(() => [...transmitData, ...receiveData], [transmitData, receiveData]);
145
- return (_jsx(MetricsGraph, { data: transformedData, from: from, to: to, setTimeRange: setTimeRange, onSampleClick: closestPoint => {
146
- if (onSeriesClick != null) {
147
- onSeriesClick(humanReadableName, closestPoint.seriesIndex);
148
- }
149
- }, yAxisLabel: humanReadableName, yAxisUnit: "bytes_per_second", width: width, height: height, margin: margin, contextMenuItems: contextMenuItems, renderTooltipContent: (seriesIndex, pointIndex) => {
150
- if (allOriginalData?.[seriesIndex]?.samples?.[pointIndex] != null) {
151
- const originalSeriesData = allOriginalData[seriesIndex];
152
- const originalPoint = allOriginalData[seriesIndex].samples[pointIndex];
153
- const labels = originalSeriesData.labelset?.labels ?? [];
154
- const nameLabel = labels.find(e => e.name === '__name__');
155
- const highlightedNameLabel = nameLabel ?? { name: '', value: '' };
156
- // Determine if this is receive data (negative values)
157
- const isReceive = seriesIndex >= transmitData.length;
158
- const valuePrefix = isReceive ? 'Receive ' : 'Transmit ';
159
- return (_jsx("div", { className: "flex flex-row", children: _jsxs("div", { className: "ml-2 mr-6", children: [_jsx("span", { className: "font-semibold", children: highlightedNameLabel.value }), _jsx("span", { className: "my-2 block text-gray-700 dark:text-gray-300", children: _jsx("table", { className: "table-auto", children: _jsxs("tbody", { children: [_jsxs("tr", { children: [_jsxs("td", { className: "w-1/4", children: [valuePrefix, "Value"] }), _jsx("td", { className: "w-3/4", children: valueFormatter(Math.abs(originalPoint.value), 'bytes_per_second', 2) })] }), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "At" }), _jsx("td", { className: "w-3/4", children: formatDate(new Date(originalPoint.timestamp), timePattern(timezone), timezone) })] })] }) }) }), _jsx("span", { className: "my-2 block text-gray-500", children: labels
160
- .filter(label => label.name !== '__name__')
161
- .map(label => (_jsx("div", { className: "mr-3 inline-block rounded-lg bg-gray-200 px-2 py-1 text-xs font-bold text-gray-700 dark:bg-gray-700 dark:text-gray-400", children: _jsx(TextWithTooltip, { text: `${transformUtilizationLabels(label.name)}="${label.value}"`, maxTextLength: 37, id: `${seriesIndex.toString()}-${pointIndex.toString()}-tooltip-${label.name}` }) }, `${seriesIndex.toString()}-${pointIndex.toString()}-${label.name}`))) }), _jsxs("div", { className: "flex w-full items-center gap-1 text-xs text-gray-500", children: [_jsx(Icon, { icon: "iconoir:mouse-button-right" }), _jsx("div", { children: "Right click to add labels to query." })] })] }) }));
162
- }
163
- return null;
164
- } }));
165
- };
166
- const AreaChart = ({ transmitData, receiveData, addLabelMatcher, setTimeRange, utilizationMetricsLoading, name, humanReadableName, from, to, selectedSeries, onSeriesClick, }) => {
167
- const { isDarkMode } = useParcaContext();
168
- const { width, height, margin, heightStyle } = useMetricsGraphDimensions(false, true);
169
- const transformedData = useMemo(() => transformNetworkSeriesToSeries(transmitData, receiveData), [transmitData, receiveData]);
170
- const contextMenuItems = useMemo(() => {
171
- return createThroughputContextMenuItems(addLabelMatcher, transmitData, receiveData);
172
- }, [addLabelMatcher, transmitData, receiveData]);
173
- return (_jsx(AnimatePresence, { children: _jsx(motion.div, { className: "w-full relative", initial: false, animate: { display: 'block', opacity: 1 }, transition: { duration: 0.5 }, children: utilizationMetricsLoading === true ? (_jsx(MetricsGraphSkeleton, { heightStyle: heightStyle, isDarkMode: isDarkMode, isMini: true })) : (_jsx(RawAreaChart, { transmitData: transmitData, receiveData: receiveData, transformedData: transformedData, addLabelMatcher: addLabelMatcher, setTimeRange: setTimeRange, width: width, height: height, margin: margin, name: name, humanReadableName: humanReadableName, from: from, to: to, selectedSeries: selectedSeries, onSeriesClick: onSeriesClick, contextMenuItems: contextMenuItems })) }, "area-chart-graph-loaded") }));
174
- };
175
- export default AreaChart;
@@ -1,28 +0,0 @@
1
- import { DateTimeRange } from '@parca/components';
2
- import { type UtilizationMetrics as MetricSeries } from '../../ProfileSelector';
3
- interface CommonProps {
4
- setTimeRange: (range: DateTimeRange) => void;
5
- humanReadableName: string;
6
- from: number;
7
- to: number;
8
- onSeriesClick?: (seriesIndex: number) => void;
9
- }
10
- type Props = CommonProps & {
11
- data: MetricSeries[];
12
- yAxisUnit: string;
13
- utilizationMetricsLoading?: boolean;
14
- addLabelMatcher?: (labels: {
15
- key: string;
16
- value: string;
17
- } | Array<{
18
- key: string;
19
- value: string;
20
- }>) => void;
21
- onSelectedSeriesChange?: (series: Array<{
22
- key: string;
23
- value: string;
24
- }>) => void;
25
- };
26
- declare const UtilizationMetrics: ({ data, setTimeRange, utilizationMetricsLoading, humanReadableName, from, to, yAxisUnit, addLabelMatcher, onSeriesClick, onSelectedSeriesChange: _onSelectedSeriesChange, }: Props) => JSX.Element;
27
- export default UtilizationMetrics;
28
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/MetricsGraph/UtilizationMetrics/index.tsx"],"names":[],"mappings":"AAkBA,OAAO,EACL,aAAa,EAId,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAC,KAAK,kBAAkB,IAAI,YAAY,EAAC,MAAM,uBAAuB,CAAC;AAI9E,UAAU,WAAW;IACnB,YAAY,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC7C,iBAAiB,EAAE,MAAM,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/C;AAYD,KAAK,KAAK,GAAG,WAAW,GAAG;IACzB,IAAI,EAAE,YAAY,EAAE,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,yBAAyB,CAAC,EAAE,OAAO,CAAC;IACpC,eAAe,CAAC,EAAE,CAChB,MAAM,EAAE;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAC,GAAG,KAAK,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAC,CAAC,KACvE,IAAI,CAAC;IACV,sBAAsB,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAC,CAAC,KAAK,IAAI,CAAC;CAChF,CAAC;AA4TF,QAAA,MAAM,kBAAkB,GAAI,6KAWzB,KAAK,KAAG,GAAG,CAAC,OAwCd,CAAC;AAEF,eAAe,kBAAkB,CAAC"}