@parca/profile 0.19.81 → 0.19.82

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 +4 -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 +2 -9
  10. package/dist/ProfileSelector/MetricsGraphSection.d.ts.map +1 -1
  11. package/dist/ProfileSelector/MetricsGraphSection.js +3 -38
  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 +12 -9
  15. package/dist/QueryControls/index.d.ts +42 -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 +2 -11
  20. package/dist/SimpleMatchers/index.d.ts.map +1 -1
  21. package/dist/SimpleMatchers/index.js +34 -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 +2 -0
  38. package/dist/hooks/useQueryState.d.ts.map +1 -1
  39. package/dist/hooks/useQueryState.js +18 -0
  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 +14 -115
  50. package/src/ProfileSelector/index.tsx +106 -109
  51. package/src/{ProfileSelector/QueryControls.tsx → QueryControls/index.tsx} +66 -84
  52. package/src/SimpleMatchers/Select.tsx +1 -1
  53. package/src/SimpleMatchers/index.tsx +46 -85
  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 +25 -0
  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
+ };
@@ -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 => {
@@ -390,6 +396,22 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
390
396
  [batchUpdates, setSelectionParam, setMergeFromState, setMergeToState]
391
397
  );
392
398
 
399
+ const draftParsedQuery = useMemo(() => {
400
+ try {
401
+ return Query.parse(draftSelection.expression ?? '');
402
+ } catch {
403
+ return Query.parse('');
404
+ }
405
+ }, [draftSelection.expression]);
406
+
407
+ const parsedQuery = useMemo(() => {
408
+ try {
409
+ return Query.parse(querySelection.expression ?? '');
410
+ } catch {
411
+ return Query.parse('');
412
+ }
413
+ }, [querySelection.expression]);
414
+
393
415
  return {
394
416
  // Current committed state
395
417
  querySelection,
@@ -414,5 +436,8 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
414
436
 
415
437
  // Loading state
416
438
  sumByLoading: sumBySelectionLoading,
439
+
440
+ draftParsedQuery,
441
+ parsedQuery,
417
442
  };
418
443
  };
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"}
@@ -1,186 +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 createUtilizationContextMenuItems = (addLabelMatcher, originalData) => {
25
- return [
26
- {
27
- id: 'focus-on-single-series',
28
- label: 'Focus only on this series',
29
- icon: 'ph:star',
30
- onClick: (closestPoint, _series) => {
31
- if (closestPoint != null &&
32
- originalData.length > 0 &&
33
- originalData[closestPoint.seriesIndex] != null) {
34
- const originalSeriesData = originalData[closestPoint.seriesIndex];
35
- if (originalSeriesData.labelset?.labels != null) {
36
- const labels = originalSeriesData.labelset.labels.filter(label => label.name !== '__name__');
37
- const labelsToAdd = labels.map(label => ({
38
- key: label.name,
39
- value: label.value,
40
- }));
41
- addLabelMatcher(labelsToAdd);
42
- }
43
- }
44
- },
45
- },
46
- {
47
- id: 'add-to-query',
48
- label: 'Add to query',
49
- icon: 'material-symbols:add',
50
- createDynamicItems: (closestPoint, _series) => {
51
- if (closestPoint == null ||
52
- originalData.length === 0 ||
53
- originalData[closestPoint.seriesIndex] == null) {
54
- return [
55
- {
56
- id: 'no-labels-available',
57
- label: 'No labels available',
58
- icon: 'ph:warning',
59
- disabled: () => true,
60
- onClick: () => { }, // No-op for disabled item
61
- },
62
- ];
63
- }
64
- const originalSeriesData = originalData[closestPoint.seriesIndex];
65
- if (originalSeriesData.labelset?.labels == null) {
66
- return [
67
- {
68
- id: 'no-labels-available',
69
- label: 'No labels available',
70
- icon: 'ph:warning',
71
- disabled: () => true,
72
- onClick: () => { }, // No-op for disabled item
73
- },
74
- ];
75
- }
76
- const labels = originalSeriesData.labelset.labels.filter(label => label.name !== '__name__');
77
- return labels.map(label => ({
78
- id: `add-label-${label.name}`,
79
- 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}"` })),
80
- onClick: () => {
81
- addLabelMatcher({
82
- key: label.name,
83
- value: label.value,
84
- });
85
- },
86
- }));
87
- },
88
- },
89
- ];
90
- };
91
- const transformMetricSeriesToSeries = (data) => {
92
- return data.map(metricSeries => {
93
- if (metricSeries.labelset != null) {
94
- const labels = metricSeries.labelset.labels ?? [];
95
- const sortedLabels = labels.sort((a, b) => a.name.localeCompare(b.name));
96
- const id = sortedLabels.map(label => `${label.name}=${label.value}`).join(',');
97
- return {
98
- id: id !== '' ? id : 'default',
99
- values: metricSeries.samples.map((sample) => [
100
- sample.timestamp,
101
- sample.value,
102
- ]),
103
- };
104
- }
105
- return {
106
- id: 'default',
107
- values: [],
108
- };
109
- });
110
- };
111
- const _getYAxisUnit = (name) => {
112
- switch (name) {
113
- case 'gpu_power_watt':
114
- return 'watts';
115
- case 'gpu_temperature_celsius':
116
- return 'celsius';
117
- case 'gpu_clock_hertz':
118
- return 'hertz';
119
- default:
120
- return 'percent';
121
- }
122
- };
123
- const RawUtilizationMetrics = ({ data, originalData, setTimeRange, width, height, margin, humanReadableName, from, to, yAxisUnit, contextMenuItems, onSeriesClick, }) => {
124
- const { timezone } = useParcaContext();
125
- return (_jsx(MetricsGraph, { data: data.map((val, idx) => ({
126
- ...val,
127
- highlighted: originalData?.[idx]?.isSelected ?? false,
128
- })), from: from, to: to, setTimeRange: setTimeRange, onSampleClick: closestPoint => {
129
- if (onSeriesClick != null) {
130
- onSeriesClick(closestPoint.seriesIndex);
131
- }
132
- }, yAxisLabel: humanReadableName, yAxisUnit: yAxisUnit, width: width, height: height, margin: margin, contextMenuItems: contextMenuItems, renderTooltipContent: (seriesIndex, pointIndex) => {
133
- if (originalData?.[seriesIndex]?.samples?.[pointIndex] != null) {
134
- const originalSeriesData = originalData[seriesIndex];
135
- const originalPoint = originalData[seriesIndex].samples[pointIndex];
136
- const labels = originalSeriesData.labelset?.labels ?? [];
137
- const nameLabel = labels.find(e => e.name === '__name__');
138
- const highlightedNameLabel = nameLabel ?? { name: '', value: '' };
139
- // Calculate attributes maps for utilization metrics
140
- const attributesMap = labels
141
- .filter(label => label.name.startsWith('attributes.') &&
142
- !label.name.startsWith('attributes_resource.'))
143
- .reduce((acc, label) => {
144
- const key = label.name.replace('attributes.', '');
145
- acc[key] = label.value;
146
- return acc;
147
- }, {});
148
- const attributesResourceMap = labels
149
- .filter(label => label.name.startsWith('attributes_resource.'))
150
- .reduce((acc, label) => {
151
- const key = label.name.replace('attributes_resource.', '');
152
- acc[key] = label.value;
153
- return acc;
154
- }, {});
155
- 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: [_jsx("td", { className: "w-1/4", children: "Value" }), _jsx("td", { className: "w-3/4", children: valueFormatter(originalPoint.value, yAxisUnit, 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) })] })] }) }) }), _jsxs("span", { className: "my-2 block text-gray-500", children: [Object.keys(attributesResourceMap).length > 0 ? (_jsx("span", { className: "text-sm font-bold text-gray-700 dark:text-white", children: "Resource Attributes" })) : null, _jsx("span", { className: "my-2 block text-gray-500", children: Object.keys(attributesResourceMap).map(name => (_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(name)}="${attributesResourceMap[name] ?? ''}"`, maxTextLength: 48, id: `tooltip-${name}-${attributesResourceMap[name] ?? ''}` }) }, 'resourceattribute-' +
156
- seriesIndex.toString() +
157
- '-' +
158
- pointIndex.toString() +
159
- '-' +
160
- name))) }), Object.keys(attributesMap).length > 0 ? (_jsx("span", { className: "text-sm font-bold text-gray-700 dark:text-white", children: "Attributes" })) : null, _jsx("span", { className: "my-2 block text-gray-500", children: Object.keys(attributesMap).map(name => (_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(name)}="${attributesMap[name] ?? ''}"`, maxTextLength: 48, id: `tooltip-${name}-${attributesMap[name] ?? ''}` }) }, 'attribute-' +
161
- seriesIndex.toString() +
162
- '-' +
163
- pointIndex.toString() +
164
- '-' +
165
- name))) }), labels
166
- .filter(label => label.name !== '__name__' && !label.name.startsWith('attributes'))
167
- .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: `tooltip-${label.name}` }) }, 'attribute-' +
168
- seriesIndex.toString() +
169
- '-' +
170
- pointIndex.toString() +
171
- '-label-' +
172
- 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." })] })] }) }));
173
- }
174
- return null;
175
- } }));
176
- };
177
- const UtilizationMetrics = ({ data, setTimeRange, utilizationMetricsLoading, humanReadableName, from, to, yAxisUnit, addLabelMatcher, onSeriesClick, onSelectedSeriesChange: _onSelectedSeriesChange, }) => {
178
- const { isDarkMode } = useParcaContext();
179
- const { width, height, margin, heightStyle } = useMetricsGraphDimensions(false, true);
180
- const transformedData = useMemo(() => transformMetricSeriesToSeries(data), [data]);
181
- const contextMenuItems = useMemo(() => {
182
- return addLabelMatcher != null ? createUtilizationContextMenuItems(addLabelMatcher, data) : [];
183
- }, [addLabelMatcher, data]);
184
- 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(RawUtilizationMetrics, { data: transformedData, originalData: data, setTimeRange: setTimeRange, width: width, height: height, margin: margin, humanReadableName: humanReadableName, from: from, to: to, yAxisUnit: yAxisUnit, contextMenuItems: contextMenuItems, onSeriesClick: onSeriesClick })) }, "utilization-metrics-graph-loaded") }));
185
- };
186
- export default UtilizationMetrics;