@parca/profile 0.19.73 → 0.19.75

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 (75) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/MatchersInput/index.d.ts.map +1 -1
  3. package/dist/MatchersInput/index.js +4 -2
  4. package/dist/ProfileExplorer/ProfileExplorerCompare.d.ts +1 -12
  5. package/dist/ProfileExplorer/ProfileExplorerCompare.d.ts.map +1 -1
  6. package/dist/ProfileExplorer/ProfileExplorerCompare.js +52 -11
  7. package/dist/ProfileExplorer/ProfileExplorerSingle.d.ts +1 -7
  8. package/dist/ProfileExplorer/ProfileExplorerSingle.d.ts.map +1 -1
  9. package/dist/ProfileExplorer/ProfileExplorerSingle.js +4 -2
  10. package/dist/ProfileExplorer/index.d.ts +1 -4
  11. package/dist/ProfileExplorer/index.d.ts.map +1 -1
  12. package/dist/ProfileExplorer/index.js +11 -225
  13. package/dist/ProfileMetricsGraph/index.d.ts +1 -1
  14. package/dist/ProfileMetricsGraph/index.d.ts.map +1 -1
  15. package/dist/ProfileMetricsGraph/index.js +16 -20
  16. package/dist/ProfileSelector/MetricsGraphSection.d.ts +3 -3
  17. package/dist/ProfileSelector/MetricsGraphSection.d.ts.map +1 -1
  18. package/dist/ProfileSelector/MetricsGraphSection.js +10 -6
  19. package/dist/ProfileSelector/index.d.ts +2 -7
  20. package/dist/ProfileSelector/index.d.ts.map +1 -1
  21. package/dist/ProfileSelector/index.js +40 -46
  22. package/dist/ProfileSelector/useAutoQuerySelector.d.ts.map +1 -1
  23. package/dist/ProfileSelector/useAutoQuerySelector.js +19 -4
  24. package/dist/ProfileTypeSelector/index.d.ts.map +1 -1
  25. package/dist/ProfileTypeSelector/index.js +1 -1
  26. package/dist/ProfileView/components/ProfileFilters/filterPresets.d.ts.map +1 -1
  27. package/dist/ProfileView/components/ProfileFilters/filterPresets.js +75 -0
  28. package/dist/ProfileView/components/ViewSelector/index.d.ts.map +1 -1
  29. package/dist/ProfileView/components/ViewSelector/index.js +10 -4
  30. package/dist/ProfileView/hooks/useResetStateOnProfileTypeChange.d.ts.map +1 -1
  31. package/dist/ProfileView/hooks/useResetStateOnProfileTypeChange.js +4 -2
  32. package/dist/ProfileView/hooks/useVisualizationState.d.ts.map +1 -1
  33. package/dist/ProfileView/hooks/useVisualizationState.js +20 -13
  34. package/dist/Table/MoreDropdown.d.ts.map +1 -1
  35. package/dist/Table/MoreDropdown.js +7 -3
  36. package/dist/Table/TableContextMenu.d.ts.map +1 -1
  37. package/dist/Table/TableContextMenu.js +9 -5
  38. package/dist/hooks/useCompareModeMeta.d.ts +10 -0
  39. package/dist/hooks/useCompareModeMeta.d.ts.map +1 -0
  40. package/dist/hooks/useCompareModeMeta.js +113 -0
  41. package/dist/hooks/useQueryState.d.ts +32 -0
  42. package/dist/hooks/useQueryState.d.ts.map +1 -0
  43. package/dist/hooks/useQueryState.js +285 -0
  44. package/dist/hooks/useQueryState.test.d.ts +2 -0
  45. package/dist/hooks/useQueryState.test.d.ts.map +1 -0
  46. package/dist/hooks/useQueryState.test.js +910 -0
  47. package/dist/index.d.ts +4 -5
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +6 -3
  50. package/dist/styles.css +1 -1
  51. package/dist/useSumBy.d.ts +7 -0
  52. package/dist/useSumBy.d.ts.map +1 -1
  53. package/dist/useSumBy.js +31 -6
  54. package/package.json +6 -6
  55. package/src/MatchersInput/index.tsx +4 -2
  56. package/src/ProfileExplorer/ProfileExplorerCompare.tsx +64 -46
  57. package/src/ProfileExplorer/ProfileExplorerSingle.tsx +7 -19
  58. package/src/ProfileExplorer/index.tsx +11 -339
  59. package/src/ProfileMetricsGraph/index.tsx +16 -20
  60. package/src/ProfileSelector/MetricsGraphSection.tsx +14 -10
  61. package/src/ProfileSelector/index.tsx +65 -83
  62. package/src/ProfileSelector/useAutoQuerySelector.ts +23 -5
  63. package/src/ProfileTypeSelector/index.tsx +3 -1
  64. package/src/ProfileView/components/ProfileFilters/filterPresets.ts +75 -0
  65. package/src/ProfileView/components/ViewSelector/index.tsx +9 -4
  66. package/src/ProfileView/hooks/useResetStateOnProfileTypeChange.ts +4 -2
  67. package/src/ProfileView/hooks/useVisualizationState.ts +25 -12
  68. package/src/Table/MoreDropdown.tsx +7 -3
  69. package/src/Table/TableContextMenu.tsx +9 -5
  70. package/src/hooks/useCompareModeMeta.ts +131 -0
  71. package/src/hooks/useQueryState.test.tsx +1202 -0
  72. package/src/hooks/useQueryState.ts +414 -0
  73. package/src/index.tsx +9 -11
  74. package/src/useSumBy.ts +62 -7
  75. package/src/ProfileExplorer/index.test.ts +0 -97
@@ -12,14 +12,15 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
12
12
  // See the License for the specific language governing permissions and
13
13
  // limitations under the License.
14
14
  import cx from 'classnames';
15
+ import { useURLStateBatch } from '@parca/components';
15
16
  import { Query } from '@parca/parser';
16
- import { MergedProfileSelection } from '..';
17
17
  import UtilizationMetricsGraph from '../MetricsGraph/UtilizationMetrics';
18
18
  import AreaChart from '../MetricsGraph/UtilizationMetrics/Throughput';
19
19
  import ProfileMetricsGraph, { ProfileMetricsEmptyState } from '../ProfileMetricsGraph';
20
20
  import { useResetStateOnSeriesChange } from '../ProfileView/hooks/useResetStateOnSeriesChange';
21
- export function MetricsGraphSection({ showMetricsGraph, setDisplayHideMetricsGraphButton, heightStyle, querySelection, profileSelection, comparing, sumBy, defaultSumByLoading, queryClient, queryExpressionString, setTimeRangeSelection, selectQuery, selectProfile, query, setNewQueryExpression, utilizationMetrics, utilizationMetricsLoading, onUtilizationSeriesSelect, }) {
21
+ export function MetricsGraphSection({ showMetricsGraph, setDisplayHideMetricsGraphButton, heightStyle, querySelection, profileSelection, comparing, sumBy, defaultSumByLoading, queryClient, queryExpressionString, setTimeRangeSelection, selectQuery, setProfileSelection, query, setNewQueryExpression, utilizationMetrics, utilizationMetricsLoading, onUtilizationSeriesSelect, }) {
22
22
  const resetStateOnSeriesChange = useResetStateOnSeriesChange();
23
+ const batchUpdates = useURLStateBatch();
23
24
  const handleTimeRangeChange = (range) => {
24
25
  const from = range.getFromMs();
25
26
  const to = range.getToMs();
@@ -62,7 +63,8 @@ export function MetricsGraphSection({ showMetricsGraph, setDisplayHideMetricsGra
62
63
  }
63
64
  if (hasChanged) {
64
65
  // TODO: Change this to store the query object
65
- setNewQueryExpression(newQuery.toString());
66
+ // Pass commit: true to immediately apply the filter when clicking on metrics graph labels
67
+ setNewQueryExpression(newQuery.toString(), true);
66
68
  }
67
69
  };
68
70
  const handlePointClick = (timestamp, labels, queryExpression, duration) => {
@@ -75,8 +77,10 @@ export function MetricsGraphSection({ showMetricsGraph, setDisplayHideMetricsGra
75
77
  });
76
78
  const mergeFrom = timestamp;
77
79
  const mergeTo = query.profileType().delta ? mergeFrom + BigInt(duration) : mergeFrom;
78
- resetStateOnSeriesChange(); // reset some state when a new series is selected
79
- selectProfile(new MergedProfileSelection(mergeFrom, mergeTo, query));
80
+ batchUpdates(() => {
81
+ resetStateOnSeriesChange(); // reset some state when a new series is selected
82
+ setProfileSelection(mergeFrom, mergeTo, query);
83
+ });
80
84
  };
81
85
  const UtilizationGraphToShow = ({ utilizationMetrics, }) => {
82
86
  const throughputMetrics = utilizationMetrics.filter(metric => metric.name === 'gpu_pcie_throughput_transmit_bytes' ||
@@ -99,7 +103,7 @@ export function MetricsGraphSection({ showMetricsGraph, setDisplayHideMetricsGra
99
103
  ?.data ?? [], receiveData: throughputMetrics.find(metric => metric.name === 'gpu_pcie_throughput_receive_bytes')
100
104
  ?.data ?? [], addLabelMatcher: addLabelMatcher, setTimeRange: handleTimeRangeChange, name: throughputMetrics[0].name, humanReadableName: throughputMetrics[0].humanReadableName, from: querySelection.from, to: querySelection.to, utilizationMetricsLoading: utilizationMetricsLoading, selectedSeries: undefined, onSeriesClick: onUtilizationSeriesSelect }))] }));
101
105
  };
102
- return (_jsxs("div", { className: cx('relative', { 'py-4': !showMetricsGraph }), children: [setDisplayHideMetricsGraphButton != null ? (_jsxs("button", { onClick: () => setDisplayHideMetricsGraphButton(!showMetricsGraph), className: cx('hidden px-3 py-1 text-sm font-medium text-gray-700 dark:text-gray-200 bg-gray-100 rounded-md hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:bg-gray-900 z-[5]', showMetricsGraph && 'absolute right-0 bottom-3 !flex', !showMetricsGraph && 'relative !flex ml-auto'), children: [showMetricsGraph ? 'Hide' : 'Show', " Metrics Graph"] })) : null, showMetricsGraph && (_jsx(_Fragment, { children: _jsx("div", { style: { height: heightStyle }, children: querySelection.expression !== '' &&
106
+ return (_jsxs("div", { className: cx('relative', { 'py-4': !showMetricsGraph }), children: [setDisplayHideMetricsGraphButton != null ? (_jsxs("button", { onClick: () => setDisplayHideMetricsGraphButton(!showMetricsGraph), className: cx('hidden px-3 py-1 text-sm font-medium text-gray-700 dark:text-gray-200 bg-gray-100 rounded-md hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:bg-gray-900 z-[5]', showMetricsGraph && 'absolute right-0 bottom-3 !flex', !showMetricsGraph && 'relative !flex ml-auto'), children: [showMetricsGraph ? 'Hide' : 'Show', " Metrics Graph"] })) : null, showMetricsGraph && (_jsx(_Fragment, { children: _jsx("div", { style: { height: heightStyle }, children: (querySelection.expression !== '' || defaultSumByLoading) &&
103
107
  querySelection.from !== undefined &&
104
108
  querySelection.to !== undefined ? (_jsx(_Fragment, { children: utilizationMetrics !== undefined ? (_jsx(UtilizationGraphToShow, { utilizationMetrics: utilizationMetrics })) : (_jsx(_Fragment, { children: _jsx(ProfileMetricsGraph, { queryClient: queryClient, queryExpression: querySelection.expression, from: querySelection.from, to: querySelection.to, profile: profileSelection, comparing: comparing, sumBy: querySelection.sumBy ?? sumBy ?? [], sumByLoading: defaultSumByLoading, setTimeRange: handleTimeRangeChange, addLabelMatcher: addLabelMatcher, onPointClick: handlePointClick }) })) })) : (profileSelection === null && (_jsx("div", { className: "p-2", children: _jsx(ProfileMetricsEmptyState, { message: "Please select a profile type and click 'Search' to begin." }) }))) }) }))] }));
105
109
  }
@@ -2,7 +2,6 @@ import { Dispatch, SetStateAction } from 'react';
2
2
  import { RpcError } from '@protobuf-ts/runtime-rpc';
3
3
  import { ProfileTypesResponse, QueryServiceClient } from '@parca/client';
4
4
  import { type NavigateFunction } from '@parca/utilities';
5
- import { ProfileSelection } from '..';
6
5
  export interface QuerySelection {
7
6
  expression: string;
8
7
  from: number;
@@ -40,16 +39,12 @@ export interface UtilizationLabels {
40
39
  }
41
40
  interface ProfileSelectorProps extends ProfileSelectorFeatures {
42
41
  queryClient: QueryServiceClient;
43
- querySelection: QuerySelection;
44
- selectProfile: (source: ProfileSelection) => void;
45
- selectQuery: (query: QuerySelection) => void;
46
42
  closeProfile: () => void;
47
43
  enforcedProfileName: string;
48
- profileSelection: ProfileSelection | null;
49
44
  comparing: boolean;
50
45
  navigateTo: NavigateFunction;
51
46
  setDisplayHideMetricsGraphButton?: Dispatch<SetStateAction<boolean>>;
52
- suffix?: string;
47
+ suffix?: '_a' | '_b';
53
48
  utilizationMetrics?: Array<{
54
49
  name: string;
55
50
  humanReadableName: string;
@@ -65,6 +60,6 @@ export interface IProfileTypesResult {
65
60
  error?: RpcError;
66
61
  }
67
62
  export declare const useProfileTypes: (client: QueryServiceClient, start?: number, end?: number) => IProfileTypesResult;
68
- declare const ProfileSelector: ({ queryClient, querySelection, selectProfile, selectQuery, closeProfile, enforcedProfileName, profileSelection, comparing, navigateTo, showMetricsGraph, showSumBySelector, showProfileTypeSelector, disableExplorativeQuerying, setDisplayHideMetricsGraphButton, utilizationMetrics, utilizationMetricsLoading, utilizationLabels, onUtilizationSeriesSelect, }: ProfileSelectorProps) => JSX.Element;
63
+ declare const ProfileSelector: ({ queryClient, closeProfile, enforcedProfileName, comparing, navigateTo, showMetricsGraph, showSumBySelector, showProfileTypeSelector, disableExplorativeQuerying, setDisplayHideMetricsGraphButton, suffix, utilizationMetrics, utilizationMetricsLoading, utilizationLabels, onUtilizationSeriesSelect, }: ProfileSelectorProps) => JSX.Element;
69
64
  export default ProfileSelector;
70
65
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ProfileSelector/index.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ProfileSelector/index.tsx"],"names":[],"mappings":"AAaA,OAAO,EAAC,QAAQ,EAAE,cAAc,EAAoD,MAAM,OAAO,CAAC;AAElG,OAAO,EAAC,QAAQ,EAAC,MAAM,0BAA0B,CAAC;AAElD,OAAO,EAAsB,oBAAoB,EAAE,kBAAkB,EAAC,MAAM,eAAe,CAAC;AAW5F,OAAO,EAAyB,KAAK,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AAW/E,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,uBAAuB;IAC/B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,2BAA2B,CAAC,EAAE,OAAO,CAAC;CACvC;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE;QACR,MAAM,EAAE,KAAK,CAAC;YACZ,IAAI,EAAE,MAAM,CAAC;YACb,KAAK,EAAE,MAAM,CAAC;SACf,CAAC,CAAC;KACJ,CAAC;IACF,OAAO,EAAE,KAAK,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;KACf,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,iBAAiB;IAChC,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;IACjC,2BAA2B,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACjE,sBAAsB,CAAC,EAAE,MAAM,EAAE,CAAC;IAClC,4BAA4B,CAAC,EAAE,OAAO,CAAC;CACxC;AAED,UAAU,oBAAqB,SAAQ,uBAAuB;IAC5D,WAAW,EAAE,kBAAkB,CAAC;IAChC,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,gCAAgC,CAAC,EAAE,QAAQ,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACrE,MAAM,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACrB,kBAAkB,CAAC,EAAE,KAAK,CAAC;QACzB,IAAI,EAAE,MAAM,CAAC;QACb,iBAAiB,EAAE,MAAM,CAAC;QAC1B,IAAI,EAAE,kBAAkB,EAAE,CAAC;KAC5B,CAAC,CAAC;IACH,yBAAyB,CAAC,EAAE,OAAO,CAAC;IACpC,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,yBAAyB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3D;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,oBAAoB,CAAC;IAC5B,KAAK,CAAC,EAAE,QAAQ,CAAC;CAClB;AAED,eAAO,MAAM,eAAe,GAC1B,QAAQ,kBAAkB,EAC1B,QAAQ,MAAM,EACd,MAAM,MAAM,KACX,mBAsBF,CAAC;AAEF,QAAA,MAAM,eAAe,GAAI,6SAgBtB,oBAAoB,KAAG,GAAG,CAAC,OAwN7B,CAAC;AAEF,eAAe,eAAe,CAAC"}
@@ -11,7 +11,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
11
11
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  // See the License for the specific language governing permissions and
13
13
  // limitations under the License.
14
- import { useEffect, useMemo, useRef, useState } from 'react';
14
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
15
15
  import { DateTimeRange, IconButton, useGrpcMetadata, useParcaContext, useURLState, } from '@parca/components';
16
16
  import { CloseIcon } from '@parca/icons';
17
17
  import { Query } from '@parca/parser';
@@ -20,8 +20,8 @@ import { millisToProtoTimestamp } from '@parca/utilities';
20
20
  import { useLabelNames } from '../MatchersInput/index';
21
21
  import { useMetricsGraphDimensions } from '../MetricsGraph/useMetricsGraphDimensions';
22
22
  import { UtilizationLabelsProvider } from '../contexts/UtilizationLabelsContext';
23
+ import { useQueryState } from '../hooks/useQueryState';
23
24
  import useGrpcQuery from '../useGrpcQuery';
24
- import { useDefaultSumBy, useSumBySelection } from '../useSumBy';
25
25
  import { MetricsGraphSection } from './MetricsGraphSection';
26
26
  import { QueryControls } from './QueryControls';
27
27
  import { useAutoQuerySelector } from './useAutoQuerySelector';
@@ -45,36 +45,31 @@ export const useProfileTypes = (client, start, end) => {
45
45
  });
46
46
  return { loading: isLoading, data, error: error };
47
47
  };
48
- const ProfileSelector = ({ queryClient, querySelection, selectProfile, selectQuery, closeProfile, enforcedProfileName, profileSelection, comparing, navigateTo, showMetricsGraph = true, showSumBySelector = true, showProfileTypeSelector = true, disableExplorativeQuerying = false, setDisplayHideMetricsGraphButton, utilizationMetrics, utilizationMetricsLoading, utilizationLabels, onUtilizationSeriesSelect, }) => {
48
+ const ProfileSelector = ({ queryClient, closeProfile, enforcedProfileName, comparing, navigateTo, showMetricsGraph = true, showSumBySelector = true, showProfileTypeSelector = true, disableExplorativeQuerying = false, setDisplayHideMetricsGraphButton, suffix, utilizationMetrics, utilizationMetricsLoading, utilizationLabels, onUtilizationSeriesSelect, }) => {
49
49
  const { heightStyle } = useMetricsGraphDimensions(comparing, utilizationMetrics != null);
50
50
  const { viewComponent } = useParcaContext();
51
51
  const [queryBrowserMode, setQueryBrowserMode] = useURLState('query_browser_mode');
52
- const [timeRangeSelection, setTimeRangeSelection] = useState(DateTimeRange.fromRangeKey(querySelection.timeSelection, querySelection.from, querySelection.to));
53
- const [queryExpressionString, setQueryExpressionString] = useState(querySelection.expression);
52
+ // Use the new useQueryState hook - reads directly from URL params
53
+ const { querySelection, draftSelection, setDraftExpression, setDraftTimeRange, setDraftSumBy, setDraftProfileName, setDraftMatchers, commitDraft, profileSelection, setProfileSelection, sumByLoading, } = useQueryState({ suffix });
54
+ // Use draft state for local state instead of committed state
55
+ const [timeRangeSelection, setTimeRangeSelection] = useState(DateTimeRange.fromRangeKey(draftSelection.timeSelection, draftSelection.from, draftSelection.to));
56
+ const [queryExpressionString, setQueryExpressionString] = useState(draftSelection.expression);
54
57
  const [advancedModeForQueryBrowser, setAdvancedModeForQueryBrowser] = useState(queryBrowserMode === 'advanced');
58
+ // Handler to update draft when time range changes
59
+ const handleTimeRangeChange = useCallback((range) => {
60
+ setTimeRangeSelection(range);
61
+ setDraftTimeRange(range.getFromMs(), range.getToMs(), range.getRangeKey());
62
+ }, [setDraftTimeRange]);
55
63
  const profileType = useMemo(() => {
56
64
  return Query.parse(queryExpressionString).profileType();
57
65
  }, [queryExpressionString]);
58
- const selectedProfileType = useMemo(() => {
59
- return Query.parse(querySelection.expression).profileType();
60
- }, [querySelection.expression]);
61
66
  const from = timeRangeSelection.getFromMs();
62
67
  const to = timeRangeSelection.getToMs();
63
68
  const { loading: profileTypesLoading, data: profileTypesData, error, } = useProfileTypes(queryClient, from, to);
64
- const { loading: labelNamesLoading, result, refetch, } = useLabelNames(queryClient, profileType.toString(), from, to);
65
- const { loading: selectedLabelNamesLoading, result: selectedLabelNamesResult } = useLabelNames(queryClient, selectedProfileType.toString(), from, to);
69
+ const { result, refetch } = useLabelNames(queryClient, profileType.toString(), from, to);
66
70
  const labels = useMemo(() => {
67
71
  return result.response?.labelNames === undefined ? [] : result.response.labelNames;
68
72
  }, [result]);
69
- const selectedLabels = useMemo(() => {
70
- return selectedLabelNamesResult.response?.labelNames === undefined
71
- ? []
72
- : selectedLabelNamesResult.response.labelNames;
73
- }, [selectedLabelNamesResult]);
74
- const [sumBySelection, setUserSumBySelection, { isLoading: sumBySelectionLoading }] = useSumBySelection(profileType, labelNamesLoading, labels, {
75
- defaultValue: querySelection.sumBy,
76
- });
77
- const { defaultSumBy, isLoading: defaultSumByLoading } = useDefaultSumBy(selectedProfileType, selectedLabelNamesLoading, selectedLabels);
78
73
  useEffect(() => {
79
74
  if (enforcedProfileName !== '') {
80
75
  const [q, changed] = Query.parse(querySelection.expression).setProfileName(enforcedProfileName);
@@ -92,37 +87,36 @@ const ProfileSelector = ({ queryClient, querySelection, selectProfile, selectQue
92
87
  };
93
88
  const query = enforcedProfileName !== '' ? enforcedProfileNameQuery() : Query.parse(queryExpressionString);
94
89
  const selectedProfileName = query.profileName();
95
- const setNewQueryExpression = (expr, updateTs = false) => {
96
- const query = enforcedProfileName !== '' ? enforcedProfileNameQuery() : Query.parse(expr);
97
- const delta = query.profileType().delta;
98
- const from = timeRangeSelection.getFromMs(updateTs);
99
- const to = timeRangeSelection.getToMs(updateTs);
100
- const mergeParams = delta
101
- ? {
102
- mergeFrom: (BigInt(from) * 1000000n).toString(),
103
- mergeTo: (BigInt(to) * 1000000n).toString(),
104
- }
105
- : {};
106
- selectQuery({
107
- expression: expr,
108
- from,
109
- to,
110
- timeSelection: timeRangeSelection.getRangeKey(),
111
- sumBy: sumBySelection,
112
- ...mergeParams,
113
- });
114
- };
115
90
  const setQueryExpression = (updateTs = false) => {
116
- setNewQueryExpression(query.toString(), updateTs);
91
+ // When updateTs is true, re-evaluate the time range to current values
92
+ if (updateTs) {
93
+ // Force re-evaluation of time range (important for relative ranges like "last 15 minutes")
94
+ const currentFrom = timeRangeSelection.getFromMs(true);
95
+ const currentTo = timeRangeSelection.getToMs(true);
96
+ const currentRangeKey = timeRangeSelection.getRangeKey();
97
+ // Commit with refreshed time range
98
+ commitDraft({
99
+ from: currentFrom,
100
+ to: currentTo,
101
+ timeSelection: currentRangeKey,
102
+ });
103
+ }
104
+ else {
105
+ // Commit the draft with existing values
106
+ commitDraft();
107
+ }
117
108
  };
118
109
  const setMatchersString = (matchers) => {
119
- const newExpressionString = `${selectedProfileName}{${matchers}}`;
120
- setQueryExpressionString(newExpressionString);
110
+ // Update draft state only
111
+ setDraftMatchers(matchers);
112
+ setQueryExpressionString(`${selectedProfileName}{${matchers}}`);
121
113
  };
122
114
  const setProfileName = (profileName) => {
123
115
  if (profileName === undefined) {
124
116
  return;
125
117
  }
118
+ // Update draft state only
119
+ setDraftProfileName(profileName);
126
120
  const [newQuery, changed] = query.setProfileName(profileName);
127
121
  if (changed) {
128
122
  const q = newQuery.toString();
@@ -139,17 +133,17 @@ const ProfileSelector = ({ queryClient, querySelection, selectProfile, selectQue
139
133
  profileTypesData,
140
134
  setProfileName,
141
135
  setQueryExpression,
142
- querySelection: { ...querySelection, sumBy: sumBySelection },
136
+ querySelection,
143
137
  navigateTo,
144
- loading: sumBySelectionLoading,
138
+ loading: sumByLoading,
145
139
  });
146
140
  const searchDisabled = queryExpressionString === undefined ||
147
141
  queryExpressionString === '' ||
148
142
  queryExpressionString === '{}';
149
143
  const queryBrowserRef = useRef(null);
150
144
  const sumByRef = useRef(null);
151
- return (_jsx(UtilizationLabelsProvider, { value: { ...utilizationLabels }, children: _jsxs(_Fragment, { children: [_jsxs("div", { className: "mb-2 flex", children: [_jsx(QueryControls, { showProfileTypeSelector: showProfileTypeSelector, showSumBySelector: showSumBySelector, disableExplorativeQuerying: disableExplorativeQuerying, profileTypesData: profileTypesData, profileTypesLoading: profileTypesLoading, selectedProfileName: selectedProfileName, setProfileName: setProfileName, setMatchersString: setMatchersString, setQueryExpression: setQueryExpression, query: query, queryBrowserRef: queryBrowserRef, timeRangeSelection: timeRangeSelection, setTimeRangeSelection: setTimeRangeSelection, searchDisabled: searchDisabled, queryBrowserMode: queryBrowserMode, setQueryBrowserMode: setQueryBrowserMode, advancedModeForQueryBrowser: advancedModeForQueryBrowser, setAdvancedModeForQueryBrowser: setAdvancedModeForQueryBrowser, queryClient: queryClient, sumByRef: sumByRef, labels: labels, sumBySelection: sumBySelection ?? [], sumBySelectionLoading: sumBySelectionLoading, setUserSumBySelection: setUserSumBySelection, profileType: profileType, profileTypesError: error, viewComponent: viewComponent, refreshLabelNames: refetch }), comparing && (_jsx("div", { children: _jsx(IconButton, { onClick: () => closeProfile(), icon: _jsx(CloseIcon, {}), ...testId(TEST_IDS.COMPARE_CLOSE_BUTTON) }) }))] }), _jsx(MetricsGraphSection, { showMetricsGraph: showMetricsGraph, setDisplayHideMetricsGraphButton: setDisplayHideMetricsGraphButton, heightStyle: utilizationMetrics !== undefined && utilizationMetrics?.length > 0
145
+ return (_jsx(UtilizationLabelsProvider, { value: { ...utilizationLabels }, children: _jsxs(_Fragment, { children: [_jsxs("div", { className: "mb-2 flex", children: [_jsx(QueryControls, { showProfileTypeSelector: showProfileTypeSelector, showSumBySelector: showSumBySelector, disableExplorativeQuerying: disableExplorativeQuerying, profileTypesData: profileTypesData, profileTypesLoading: profileTypesLoading, selectedProfileName: selectedProfileName, setProfileName: setProfileName, setMatchersString: setMatchersString, setQueryExpression: setQueryExpression, query: query, queryBrowserRef: queryBrowserRef, timeRangeSelection: timeRangeSelection, setTimeRangeSelection: handleTimeRangeChange, searchDisabled: searchDisabled, queryBrowserMode: queryBrowserMode, setQueryBrowserMode: setQueryBrowserMode, advancedModeForQueryBrowser: advancedModeForQueryBrowser, setAdvancedModeForQueryBrowser: setAdvancedModeForQueryBrowser, queryClient: queryClient, sumByRef: sumByRef, labels: labels, sumBySelection: draftSelection.sumBy ?? [], sumBySelectionLoading: sumByLoading, setUserSumBySelection: setDraftSumBy, profileType: profileType, profileTypesError: error, viewComponent: viewComponent, refreshLabelNames: refetch }), comparing && (_jsx("div", { children: _jsx(IconButton, { onClick: () => closeProfile(), icon: _jsx(CloseIcon, {}), ...testId(TEST_IDS.COMPARE_CLOSE_BUTTON) }) }))] }), _jsx(MetricsGraphSection, { showMetricsGraph: showMetricsGraph, setDisplayHideMetricsGraphButton: setDisplayHideMetricsGraphButton, heightStyle: utilizationMetrics !== undefined && utilizationMetrics?.length > 0
152
146
  ? 'auto'
153
- : heightStyle, querySelection: querySelection, profileSelection: profileSelection, comparing: comparing, sumBy: querySelection.sumBy ?? defaultSumBy ?? [], defaultSumByLoading: defaultSumByLoading, queryClient: queryClient, queryExpressionString: queryExpressionString, setTimeRangeSelection: setTimeRangeSelection, selectQuery: selectQuery, selectProfile: selectProfile, query: query, setQueryExpression: setQueryExpression, setNewQueryExpression: setNewQueryExpression, utilizationMetrics: utilizationMetrics, utilizationMetricsLoading: utilizationMetricsLoading, onUtilizationSeriesSelect: onUtilizationSeriesSelect })] }) }));
147
+ : heightStyle, querySelection: querySelection, profileSelection: profileSelection, comparing: comparing, sumBy: querySelection.sumBy ?? [], defaultSumByLoading: sumByLoading, queryClient: queryClient, queryExpressionString: queryExpressionString, setTimeRangeSelection: handleTimeRangeChange, selectQuery: commitDraft, setProfileSelection: setProfileSelection, query: query, setQueryExpression: setQueryExpression, setNewQueryExpression: setDraftExpression, utilizationMetrics: utilizationMetrics, utilizationMetricsLoading: utilizationMetricsLoading, onUtilizationSeriesSelect: onUtilizationSeriesSelect })] }) }));
154
148
  };
155
149
  export default ProfileSelector;
@@ -1 +1 @@
1
- {"version":3,"file":"useAutoQuerySelector.d.ts","sourceRoot":"","sources":["../../src/ProfileSelector/useAutoQuerySelector.ts"],"names":[],"mappings":"AAeA,OAAO,EAAC,oBAAoB,EAAC,MAAM,eAAe,CAAC;AAEnD,OAAO,EAAC,KAAK,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AAGvD,OAAO,EAAC,cAAc,EAAC,MAAM,oBAAoB,CAAC;AAGlD,UAAU,KAAK;IACb,mBAAmB,EAAE,MAAM,CAAC;IAC5B,gBAAgB,EAAE,oBAAoB,GAAG,SAAS,CAAC;IACnD,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,kBAAkB,EAAE,MAAM,IAAI,CAAC;IAC/B,cAAc,EAAE,cAAc,CAAC;IAC/B,UAAU,EAAE,gBAAgB,CAAC;IAC7B,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,eAAO,MAAM,oBAAoB,GAAI,qHAQlC,KAAK,KAAG,IAsIV,CAAC"}
1
+ {"version":3,"file":"useAutoQuerySelector.d.ts","sourceRoot":"","sources":["../../src/ProfileSelector/useAutoQuerySelector.ts"],"names":[],"mappings":"AAeA,OAAO,EAAC,oBAAoB,EAAC,MAAM,eAAe,CAAC;AAEnD,OAAO,EAAC,KAAK,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AAGvD,OAAO,EAAC,cAAc,EAAC,MAAM,oBAAoB,CAAC;AAGlD,UAAU,KAAK;IACb,mBAAmB,EAAE,MAAM,CAAC;IAC5B,gBAAgB,EAAE,oBAAoB,GAAG,SAAS,CAAC;IACnD,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,kBAAkB,EAAE,MAAM,IAAI,CAAC;IAC/B,cAAc,EAAE,cAAc,CAAC;IAC/B,UAAU,EAAE,gBAAgB,CAAC;IAC7B,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,eAAO,MAAM,oBAAoB,GAAI,qHAQlC,KAAK,KAAG,IAwJV,CAAC"}
@@ -10,7 +10,7 @@
10
10
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
- import { useEffect } from 'react';
13
+ import { useEffect, useRef } from 'react';
14
14
  import { selectAutoQuery, setAutoQuery, useAppDispatch, useAppSelector } from '@parca/store';
15
15
  import { ProfileSelectionFromParams, SuffixParams } from '..';
16
16
  import { constructProfileName } from '../ProfileTypeSelector';
@@ -18,13 +18,27 @@ export const useAutoQuerySelector = ({ selectedProfileName, profileTypesData, se
18
18
  const autoQuery = useAppSelector(selectAutoQuery);
19
19
  const dispatch = useAppDispatch();
20
20
  const queryParams = new URLSearchParams(location.search);
21
- const comparing = queryParams.get('comparing') === 'true';
21
+ const compareA = queryParams.get('compare_a');
22
+ const compareB = queryParams.get('compare_b');
23
+ const comparing = compareA === 'true' || compareB === 'true';
22
24
  const expressionA = queryParams.get('expression_a');
25
+ const expressionB = queryParams.get('expression_b');
26
+ // Track if we've already set up compare mode to prevent infinite loops
27
+ const hasSetupCompareMode = useRef(false);
23
28
  useEffect(() => {
24
29
  if (loading) {
25
30
  return;
26
31
  }
27
- if (comparing && expressionA !== null && expressionA !== undefined) {
32
+ // Only run this effect if:
33
+ // 1. We're in compare mode
34
+ // 2. expressionA exists
35
+ // 3. expressionB doesn't exist yet (meaning we need to set it up)
36
+ // 4. We haven't already set it up in this session
37
+ if (comparing &&
38
+ expressionA !== null &&
39
+ expressionA !== undefined &&
40
+ expressionB === null &&
41
+ !hasSetupCompareMode.current) {
28
42
  if (querySelection.expression === undefined) {
29
43
  return;
30
44
  }
@@ -59,13 +73,14 @@ export const useAutoQuerySelector = ({ selectedProfileName, profileTypesData, se
59
73
  ...compareQuery,
60
74
  };
61
75
  }
76
+ hasSetupCompareMode.current = true;
62
77
  void navigateTo('/', {
63
78
  ...compareQuery,
64
79
  search_string: '',
65
80
  dashboard_items: ['flamegraph'],
66
81
  });
67
82
  }
68
- }, [comparing, querySelection, navigateTo, expressionA, dispatch, loading]);
83
+ }, [comparing, querySelection, navigateTo, expressionA, expressionB, dispatch, loading]);
69
84
  // Effect to load some initial data on load when is no selection
70
85
  useEffect(() => {
71
86
  void (async () => {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ProfileTypeSelector/index.tsx"],"names":[],"mappings":"AAeA,OAAO,EAAC,QAAQ,EAAC,MAAM,0BAA0B,CAAC;AAElD,OAAO,EAAC,WAAW,EAAE,oBAAoB,EAAC,MAAM,eAAe,CAAC;AAChE,OAAO,EAAS,KAAK,aAAa,EAAC,MAAM,mBAAmB,CAAC;AAG7D,UAAU,gBAAgB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,iBAAiB;IACzB,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,CAAC;CACjC;AAED,eAAO,MAAM,iBAAiB,EAAE,iBAiF/B,CAAC;AAEF,wBAAgB,gCAAgC,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAU3F;AAED,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,8BAA8B,EAAE,OAAO,GACtC,aAAa,CAiBf;AAED,eAAO,MAAM,oBAAoB,GAAI,MAAM,WAAW,KAAG,MAIxD,CAAC;AAEF,eAAO,MAAM,yBAAyB,GAAI,OAAO,WAAW,EAAE,KAAG,MAAM,EAItE,CAAC;AAEF,UAAU,KAAK;IACb,gBAAgB,CAAC,EAAE,oBAAoB,CAAC;IACxC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC5B,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,8BAA8B,CAAC,EAAE,OAAO,CAAC;IACzC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IACjD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,QAAA,MAAM,mBAAmB,GAAI,2GAQ1B,KAAK,KAAG,GAAG,CAAC,OA0Bd,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ProfileTypeSelector/index.tsx"],"names":[],"mappings":"AAeA,OAAO,EAAC,QAAQ,EAAC,MAAM,0BAA0B,CAAC;AAElD,OAAO,EAAC,WAAW,EAAE,oBAAoB,EAAC,MAAM,eAAe,CAAC;AAChE,OAAO,EAAS,KAAK,aAAa,EAAC,MAAM,mBAAmB,CAAC;AAG7D,UAAU,gBAAgB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,iBAAiB;IACzB,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,CAAC;CACjC;AAED,eAAO,MAAM,iBAAiB,EAAE,iBAiF/B,CAAC;AAEF,wBAAgB,gCAAgC,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAU3F;AAED,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,8BAA8B,EAAE,OAAO,GACtC,aAAa,CAiBf;AAED,eAAO,MAAM,oBAAoB,GAAI,MAAM,WAAW,KAAG,MAIxD,CAAC;AAEF,eAAO,MAAM,yBAAyB,GAAI,OAAO,WAAW,EAAE,KAAG,MAAM,EAItE,CAAC;AAEF,UAAU,KAAK;IACb,gBAAgB,CAAC,EAAE,oBAAoB,CAAC;IACxC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC5B,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,8BAA8B,CAAC,EAAE,OAAO,CAAC;IACzC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IACjD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,QAAA,MAAM,mBAAmB,GAAI,2GAQ1B,KAAK,KAAG,GAAG,CAAC,OA4Bd,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
@@ -135,7 +135,7 @@ const ProfileTypeSelector = ({ profileTypesData, loading = false, error, selecte
135
135
  ? normalizeProfileTypesData(profileTypesData.types)
136
136
  : [];
137
137
  }, [profileTypesData, error]);
138
- const profileLabels = profileNames.map(name => ({
138
+ const profileLabels = (profileNames.length > 0 ? profileNames : selectedKey != null ? [selectedKey] : []).map(name => ({
139
139
  key: name,
140
140
  element: profileSelectElement(name, flexibleKnownProfilesDetection),
141
141
  }));
@@ -1 +1 @@
1
- {"version":3,"file":"filterPresets.d.ts","sourceRoot":"","sources":["../../../../src/ProfileView/components/ProfileFilters/filterPresets.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAEvD,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC,CAAC;IAC1C,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;CAChC;AAED,eAAO,MAAM,aAAa,EAAE,YAAY,EAkEvC,CAAC;AAIF,eAAO,MAAM,WAAW,GAAI,KAAK,MAAM,KAAG,OAEzC,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,KAAG,YAAY,GAAG,SAE3D,CAAC;AAEF,eAAO,MAAM,wBAAwB,GAAI,cAAc,MAAM,KAAG,YAAY,EAO3E,CAAC"}
1
+ {"version":3,"file":"filterPresets.d.ts","sourceRoot":"","sources":["../../../../src/ProfileView/components/ProfileFilters/filterPresets.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAEvD,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC,CAAC;IAC1C,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;CAChC;AAED,eAAO,MAAM,aAAa,EAAE,YAAY,EA6IvC,CAAC;AAIF,eAAO,MAAM,WAAW,GAAI,KAAK,MAAM,KAAG,OAEzC,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,KAAG,YAAY,GAAG,SAE3D,CAAC;AAEF,eAAO,MAAM,wBAAwB,GAAI,cAAc,MAAM,KAAG,YAAY,EAO3E,CAAC"}
@@ -76,6 +76,81 @@ export const filterPresets = [
76
76
  },
77
77
  ],
78
78
  },
79
+ {
80
+ key: 'hide_cuda_internals',
81
+ name: 'Hide CUDA Internals',
82
+ description: 'Excludes CUDA and NVIDIA GPU driver internal functions from the profile',
83
+ filters: [
84
+ {
85
+ type: 'frame',
86
+ field: 'binary',
87
+ matchType: 'not_contains',
88
+ value: 'libcudnn_engines_precompiled.so',
89
+ },
90
+ {
91
+ type: 'frame',
92
+ field: 'binary',
93
+ matchType: 'not_contains',
94
+ value: 'libcupti.so',
95
+ },
96
+ {
97
+ type: 'frame',
98
+ field: 'binary',
99
+ matchType: 'not_contains',
100
+ value: 'libparcgpcupti.so',
101
+ },
102
+ {
103
+ type: 'frame',
104
+ field: 'binary',
105
+ matchType: 'not_contains',
106
+ value: 'libcudart.so',
107
+ },
108
+ {
109
+ type: 'frame',
110
+ field: 'binary',
111
+ matchType: 'not_contains',
112
+ value: 'libcuda.so',
113
+ },
114
+ ],
115
+ },
116
+ {
117
+ key: 'hide_python_internals',
118
+ name: 'Hide Python Internals',
119
+ description: 'Excludes Python interpreter internal functions from the profile',
120
+ filters: [
121
+ {
122
+ type: 'frame',
123
+ field: 'binary',
124
+ matchType: 'not_contains',
125
+ value: 'python3',
126
+ },
127
+ {
128
+ type: 'frame',
129
+ field: 'function_name',
130
+ matchType: 'not_equal',
131
+ value: '<interpreter trampoline>',
132
+ },
133
+ {
134
+ type: 'frame',
135
+ field: 'function_name',
136
+ matchType: 'not_equal',
137
+ value: '<module>',
138
+ },
139
+ ],
140
+ },
141
+ {
142
+ key: 'hide_libc',
143
+ name: 'Hide libc',
144
+ description: 'Excludes C standard library functions from the profile',
145
+ filters: [
146
+ {
147
+ type: 'frame',
148
+ field: 'binary',
149
+ matchType: 'not_contains',
150
+ value: 'libc.so',
151
+ },
152
+ ],
153
+ },
79
154
  ];
80
155
  const presetKeys = new Set(filterPresets.map(preset => preset.key));
81
156
  export const isPresetKey = (key) => {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/ProfileView/components/ViewSelector/index.tsx"],"names":[],"mappings":"AAiBA,OAAO,EAAC,aAAa,EAAC,MAAM,wBAAwB,CAAC;AAGrD,UAAU,KAAK;IACb,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED,QAAA,MAAM,YAAY,GAAI,mBAAiB,KAAK,KAAG,GAAG,CAAC,OA0JlD,CAAC;AAEF,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/ProfileView/components/ViewSelector/index.tsx"],"names":[],"mappings":"AAiBA,OAAO,EAAC,aAAa,EAAC,MAAM,wBAAwB,CAAC;AAGrD,UAAU,KAAK;IACb,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED,QAAA,MAAM,YAAY,GAAI,mBAAiB,KAAK,KAAG,GAAG,CAAC,OA+JlD,CAAC;AAEF,eAAe,YAAY,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useParcaContext, useURLState } from '@parca/components';
2
+ import { useParcaContext, useURLState, useURLStateBatch } from '@parca/components';
3
3
  import Dropdown from './Dropdown';
4
4
  const ViewSelector = ({ profileSource }) => {
5
5
  const [dashboardItems = ['flamegraph'], setDashboardItems] = useURLState('dashboard_items', {
@@ -7,6 +7,7 @@ const ViewSelector = ({ profileSource }) => {
7
7
  });
8
8
  const [, setSandwichFunctionName] = useURLState('sandwich_function_name');
9
9
  const { enableSourcesView, enableSandwichView } = useParcaContext();
10
+ const batchUpdates = useURLStateBatch();
10
11
  const allItems = [
11
12
  {
12
13
  key: 'flamegraph',
@@ -60,10 +61,15 @@ const ViewSelector = ({ profileSource }) => {
60
61
  }
61
62
  else {
62
63
  const newDashboardItems = dashboardItems.filter(v => v !== item.key);
63
- setDashboardItems(newDashboardItems);
64
- // Reset sandwich function name when removing sandwich panel
64
+ // Batch updates when removing sandwich panel to combine both URL changes
65
65
  if (item.key === 'sandwich') {
66
- setSandwichFunctionName(undefined);
66
+ batchUpdates(() => {
67
+ setDashboardItems(newDashboardItems);
68
+ setSandwichFunctionName(undefined);
69
+ });
70
+ }
71
+ else {
72
+ setDashboardItems(newDashboardItems);
67
73
  }
68
74
  }
69
75
  },
@@ -1 +1 @@
1
- {"version":3,"file":"useResetStateOnProfileTypeChange.d.ts","sourceRoot":"","sources":["../../../src/ProfileView/hooks/useResetStateOnProfileTypeChange.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,gCAAgC,QAAO,CAAC,MAAM,IAAI,CAqB9D,CAAC"}
1
+ {"version":3,"file":"useResetStateOnProfileTypeChange.d.ts","sourceRoot":"","sources":["../../../src/ProfileView/hooks/useResetStateOnProfileTypeChange.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,gCAAgC,QAAO,CAAC,MAAM,IAAI,CAuB9D,CAAC"}
@@ -10,15 +10,17 @@
10
10
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
- import { useURLState } from '@parca/components';
13
+ import { useURLState, useURLStateBatch } from '@parca/components';
14
14
  import { useProfileFilters } from '../components/ProfileFilters/useProfileFilters';
15
15
  export const useResetStateOnProfileTypeChange = () => {
16
16
  const [groupBy, setGroupBy] = useURLState('group_by');
17
17
  const [curPath, setCurPath] = useURLState('cur_path');
18
18
  const { resetFilters } = useProfileFilters();
19
19
  const [sandwichFunctionName, setSandwichFunctionName] = useURLState('sandwich_function_name');
20
+ const batchUpdates = useURLStateBatch();
20
21
  return () => {
21
- setTimeout(() => {
22
+ // Batch all URL state resets into a single navigation
23
+ batchUpdates(() => {
22
24
  if (groupBy !== undefined) {
23
25
  setGroupBy(undefined);
24
26
  }
@@ -1 +1 @@
1
- {"version":3,"file":"useVisualizationState.d.ts","sourceRoot":"","sources":["../../../src/ProfileView/hooks/useVisualizationState.ts"],"names":[],"mappings":"AAyBA,OAAO,EAAC,gBAAgB,EAAC,MAAM,+CAA+C,CAAC;AAG/E,eAAO,MAAM,qBAAqB,QAAO;IACvC,YAAY,EAAE,gBAAgB,EAAE,CAAC;IACjC,eAAe,EAAE,CAAC,IAAI,EAAE,gBAAgB,EAAE,KAAK,IAAI,CAAC;IACpD,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACrC,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,gBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC7C,oBAAoB,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,uBAAuB,EAAE,CAAC,oBAAoB,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IAC5E,yBAAyB,EAAE,MAAM,IAAI,CAAC;IACtC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,oBAAoB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CA0G/C,CAAC"}
1
+ {"version":3,"file":"useVisualizationState.d.ts","sourceRoot":"","sources":["../../../src/ProfileView/hooks/useVisualizationState.ts"],"names":[],"mappings":"AA+BA,OAAO,EAAC,gBAAgB,EAAC,MAAM,+CAA+C,CAAC;AAG/E,eAAO,MAAM,qBAAqB,QAAO;IACvC,YAAY,EAAE,gBAAgB,EAAE,CAAC;IACjC,eAAe,EAAE,CAAC,IAAI,EAAE,gBAAgB,EAAE,KAAK,IAAI,CAAC;IACpD,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACrC,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,gBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC7C,oBAAoB,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,uBAAuB,EAAE,CAAC,oBAAoB,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IAC5E,yBAAyB,EAAE,MAAM,IAAI,CAAC;IACtC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,oBAAoB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAiH/C,CAAC"}
@@ -11,7 +11,7 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
  import { useCallback, useMemo } from 'react';
14
- import { JSONParser, JSONSerializer, useURLState, useURLStateCustom } from '@parca/components';
14
+ import { JSONParser, JSONSerializer, useURLState, useURLStateBatch, useURLStateCustom, } from '@parca/components';
15
15
  import { USER_PREFERENCES, useUserPreference } from '@parca/hooks';
16
16
  import { FIELD_FUNCTION_FILE_NAME, FIELD_FUNCTION_NAME, FIELD_LABELS, FIELD_LOCATION_ADDRESS, FIELD_MAPPING_FILE, } from '../../ProfileFlameGraph/FlameGraphArrow';
17
17
  import { useResetFlameGraphState } from './useResetFlameGraphState';
@@ -36,6 +36,7 @@ export const useVisualizationState = () => {
36
36
  });
37
37
  const [sandwichFunctionName, setSandwichFunctionName] = useURLState('sandwich_function_name');
38
38
  const resetFlameGraphState = useResetFlameGraphState();
39
+ const batchUpdates = useURLStateBatch();
39
40
  const levelsOfProfiling = useMemo(() => [
40
41
  FIELD_FUNCTION_NAME,
41
42
  FIELD_FUNCTION_FILE_NAME,
@@ -46,19 +47,25 @@ export const useVisualizationState = () => {
46
47
  setStoreGroupBy(keys);
47
48
  }, [setStoreGroupBy]);
48
49
  const toggleGroupBy = useCallback((key) => {
49
- if (groupBy.includes(key)) {
50
- setGroupBy(groupBy.filter(v => v !== key)); // remove
51
- }
52
- else {
53
- const filteredGroupBy = groupBy.filter(item => !levelsOfProfiling.includes(item));
54
- setGroupBy([...filteredGroupBy, key]); // add
55
- }
56
- resetFlameGraphState();
57
- }, [groupBy, setGroupBy, levelsOfProfiling, resetFlameGraphState]);
50
+ // Batch updates to combine setGroupBy + resetFlameGraphState into single URL navigation
51
+ batchUpdates(() => {
52
+ if (groupBy.includes(key)) {
53
+ setGroupBy(groupBy.filter(v => v !== key)); // remove
54
+ }
55
+ else {
56
+ const filteredGroupBy = groupBy.filter(item => !levelsOfProfiling.includes(item));
57
+ setGroupBy([...filteredGroupBy, key]); // add
58
+ }
59
+ resetFlameGraphState();
60
+ });
61
+ }, [groupBy, setGroupBy, levelsOfProfiling, resetFlameGraphState, batchUpdates]);
58
62
  const setGroupByLabels = useCallback((labels) => {
59
- setGroupBy(groupBy.filter(l => !l.startsWith(`${FIELD_LABELS}.`)).concat(labels));
60
- resetFlameGraphState();
61
- }, [groupBy, setGroupBy, resetFlameGraphState]);
63
+ // Batch updates to combine setGroupBy + resetFlameGraphState into single URL navigation
64
+ batchUpdates(() => {
65
+ setGroupBy(groupBy.filter(l => !l.startsWith(`${FIELD_LABELS}.`)).concat(labels));
66
+ resetFlameGraphState();
67
+ });
68
+ }, [groupBy, setGroupBy, resetFlameGraphState, batchUpdates]);
62
69
  const resetSandwichFunctionName = useCallback(() => {
63
70
  setSandwichFunctionName(undefined);
64
71
  }, [setSandwichFunctionName]);
@@ -1 +1 @@
1
- {"version":3,"file":"MoreDropdown.d.ts","sourceRoot":"","sources":["../../src/Table/MoreDropdown.tsx"],"names":[],"mappings":"AAkBA,QAAA,MAAM,YAAY,GAAI,kBAAgB;IAAC,YAAY,EAAE,MAAM,CAAA;CAAC,KAAG,KAAK,CAAC,GAAG,CAAC,OAAO,GAAG,IA4DlF,CAAC;AAEF,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"MoreDropdown.d.ts","sourceRoot":"","sources":["../../src/Table/MoreDropdown.tsx"],"names":[],"mappings":"AAkBA,QAAA,MAAM,YAAY,GAAI,kBAAgB;IAAC,YAAY,EAAE,MAAM,CAAA;CAAC,KAAG,KAAK,CAAC,GAAG,CAAC,OAAO,GAAG,IAgElF,CAAC;AAEF,eAAe,YAAY,CAAC"}
@@ -13,16 +13,20 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
13
13
  // limitations under the License.
14
14
  import { Menu } from '@headlessui/react';
15
15
  import { Icon } from '@iconify/react';
16
- import { useParcaContext, useURLState } from '@parca/components';
16
+ import { useParcaContext, useURLState, useURLStateBatch } from '@parca/components';
17
17
  const MoreDropdown = ({ functionName }) => {
18
18
  const [_, setSandwichFunctionName] = useURLState('sandwich_function_name');
19
19
  const [dashboardItems, setDashboardItems] = useURLState('dashboard_items', {
20
20
  alwaysReturnArray: true,
21
21
  });
22
22
  const { enableSandwichView } = useParcaContext();
23
+ const batchUpdates = useURLStateBatch();
23
24
  const onSandwichViewSelect = () => {
24
- setSandwichFunctionName(functionName.trim());
25
- setDashboardItems([...dashboardItems, 'sandwich']);
25
+ // Batch updates to combine setSandwichFunctionName + setDashboardItems into single URL navigation
26
+ batchUpdates(() => {
27
+ setSandwichFunctionName(functionName.trim());
28
+ setDashboardItems([...dashboardItems, 'sandwich']);
29
+ });
26
30
  };
27
31
  const menuItems = [];
28
32
  if (enableSandwichView === true) {
@@ -1 +1 @@
1
- {"version":3,"file":"TableContextMenu.d.ts","sourceRoot":"","sources":["../../src/Table/TableContextMenu.tsx"],"names":[],"mappings":"AAiBA,OAAO,yCAAyC,CAAC;AAKjD,OAAO,EAAC,KAAK,GAAG,EAAC,MAAM,GAAG,CAAC;AAG3B,OAAO,EAAC,KAAK,UAAU,EAAC,MAAM,mBAAmB,CAAC;AAElD,UAAU,qBAAqB;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,GAAG,GAAG,IAAI,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;CAChD;AAED,QAAA,MAAM,gBAAgB,GAAI,kEAOvB,qBAAqB,KAAG,KAAK,CAAC,GAAG,CAAC,OA+LpC,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
1
+ {"version":3,"file":"TableContextMenu.d.ts","sourceRoot":"","sources":["../../src/Table/TableContextMenu.tsx"],"names":[],"mappings":"AAiBA,OAAO,yCAAyC,CAAC;AAKjD,OAAO,EAAC,KAAK,GAAG,EAAC,MAAM,GAAG,CAAC;AAG3B,OAAO,EAAC,KAAK,UAAU,EAAC,MAAM,mBAAmB,CAAC;AAElD,UAAU,qBAAqB;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,GAAG,GAAG,IAAI,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;CAChD;AAED,QAAA,MAAM,gBAAgB,GAAI,kEAOvB,qBAAqB,KAAG,KAAK,CAAC,GAAG,CAAC,OAmMpC,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
@@ -15,7 +15,7 @@ import { Icon } from '@iconify/react';
15
15
  import cx from 'classnames';
16
16
  import { Item, Menu, Submenu } from 'react-contexify';
17
17
  import 'react-contexify/dist/ReactContexify.css';
18
- import { useParcaContext, useURLState } from '@parca/components';
18
+ import { useParcaContext, useURLState, useURLStateBatch } from '@parca/components';
19
19
  import { valueFormatter } from '@parca/utilities';
20
20
  import { getTextForCumulative } from '../ProfileFlameGraph/FlameGraphArrow/utils';
21
21
  import { truncateString } from '../utils';
@@ -25,12 +25,16 @@ const TableContextMenu = ({ menuId, row, unit, total, totalUnfiltered, columnVis
25
25
  alwaysReturnArray: true,
26
26
  });
27
27
  const { enableSandwichView, isDarkMode } = useParcaContext();
28
+ const batchUpdates = useURLStateBatch();
28
29
  const onSandwichViewSelect = () => {
29
30
  if (row?.name != null && row.name.length > 0) {
30
- setSandwichFunctionName(row.name.trim());
31
- if (!dashboardItems.includes('sandwich')) {
32
- setDashboardItems([...dashboardItems, 'sandwich']);
33
- }
31
+ // Batch updates to combine setSandwichFunctionName + setDashboardItems into single URL navigation
32
+ batchUpdates(() => {
33
+ setSandwichFunctionName(row.name.trim());
34
+ if (!dashboardItems.includes('sandwich')) {
35
+ setDashboardItems([...dashboardItems, 'sandwich']);
36
+ }
37
+ });
34
38
  }
35
39
  };
36
40
  const handleCopyItem = (text) => {