@parca/profile 0.16.401 → 0.16.403

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,14 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [0.16.403](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.402...@parca/profile@0.16.403) (2024-07-05)
7
+
8
+ **Note:** Version bump only for package @parca/profile
9
+
10
+ ## [0.16.402](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.401...@parca/profile@0.16.402) (2024-07-04)
11
+
12
+ **Note:** Version bump only for package @parca/profile
13
+
6
14
  ## [0.16.401](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.400...@parca/profile@0.16.401) (2024-07-03)
7
15
 
8
16
  **Note:** Version bump only for package @parca/profile
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { Card } from '@parca/components';
2
+ import { Card, useURLState } from '@parca/components';
3
3
  import { Query } from '@parca/parser';
4
4
  import { ProfileDiffSource, ProfileViewWithData } from '..';
5
5
  import ProfileSelector from '../ProfileSelector';
@@ -10,6 +10,7 @@ const ProfileExplorerCompare = ({ queryClient, queryA, queryB, profileA, profile
10
10
  const closeProfileB = () => {
11
11
  closeProfile('B');
12
12
  };
13
- return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex justify-between gap-2", children: [_jsx(Card, { className: "mt-2 p-2", children: _jsx(ProfileSelector, { queryClient: queryClient, querySelection: queryA, profileSelection: profileA, selectProfile: selectProfileA, selectQuery: selectQueryA, closeProfile: closeProfileA, enforcedProfileName: '', comparing: true, navigateTo: navigateTo }) }), _jsx(Card, { className: "mt-2 p-2", children: _jsx(ProfileSelector, { queryClient: queryClient, querySelection: queryB, profileSelection: profileB, selectProfile: selectProfileB, selectQuery: selectQueryB, closeProfile: closeProfileB, enforcedProfileName: Query.parse(queryA.expression).profileName(), comparing: true, navigateTo: navigateTo }) })] }), _jsx("div", { className: "grid grid-cols-1", children: profileA != null && profileB != null ? (_jsx("div", { children: _jsx(Card, { className: "mt-2 px-6 py-4", children: _jsx(ProfileViewWithData, { navigateTo: navigateTo, queryClient: queryClient, profileSource: new ProfileDiffSource(profileA.ProfileSource(), profileB.ProfileSource()) }) }) })) : (_jsx("div", { children: _jsx("div", { className: "my-20 text-center", children: _jsx("p", { children: "Select a profile on both sides." }) }) })) })] }));
13
+ const [compareAbsolute] = useURLState({ param: 'compare_absolute', navigateTo });
14
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex justify-between gap-2", children: [_jsx(Card, { className: "mt-2 p-2", children: _jsx(ProfileSelector, { queryClient: queryClient, querySelection: queryA, profileSelection: profileA, selectProfile: selectProfileA, selectQuery: selectQueryA, closeProfile: closeProfileA, enforcedProfileName: '', comparing: true, navigateTo: navigateTo }) }), _jsx(Card, { className: "mt-2 p-2", children: _jsx(ProfileSelector, { queryClient: queryClient, querySelection: queryB, profileSelection: profileB, selectProfile: selectProfileB, selectQuery: selectQueryB, closeProfile: closeProfileB, enforcedProfileName: Query.parse(queryA.expression).profileName(), comparing: true, navigateTo: navigateTo }) })] }), _jsx("div", { className: "grid grid-cols-1", children: profileA != null && profileB != null ? (_jsx("div", { children: _jsx(Card, { className: "mt-2 px-6 py-4", children: _jsx(ProfileViewWithData, { navigateTo: navigateTo, queryClient: queryClient, profileSource: new ProfileDiffSource(profileA.ProfileSource(), profileB.ProfileSource(), '', compareAbsolute === 'true') }) }) })) : (_jsx("div", { children: _jsx("div", { className: "my-20 text-center", children: _jsx("p", { children: "Select a profile on both sides." }) }) })) })] }));
14
15
  };
15
16
  export default ProfileExplorerCompare;
@@ -93,6 +93,15 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({ graph, arrow, to
93
93
  navigateTo,
94
94
  });
95
95
  const isInvert = invertStack === 'true';
96
+ // By default, we want delta profiles (CPU) to be relatively compared.
97
+ // For non-delta profiles, like goroutines or memory, we want the profiles to be compared absolutely.
98
+ const compareAbsoluteDefault = profileType?.delta === false ? 'true' : 'false';
99
+ const [compareAbsolute = compareAbsoluteDefault, setCompareAbsolute] = useURLState({
100
+ param: 'compare_absolute',
101
+ navigateTo,
102
+ withURLUpdate: true,
103
+ });
104
+ const isCompareAbsolute = compareAbsolute === 'true';
96
105
  const [totalFormatted, totalUnfilteredFormatted, isTrimmed, trimmedFormatted, trimmedPercentage, isFiltered, filteredPercentage,] = useMemo(() => {
97
106
  if (graph === undefined && arrow === undefined) {
98
107
  return ['0', '0', false, '0', '0', false, '0', '0'];
@@ -112,7 +121,7 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({ graph, arrow, to
112
121
  ];
113
122
  }, [graph, arrow, filtered, total]);
114
123
  useEffect(() => {
115
- setActionButtons?.(_jsx("div", { className: "flex w-full justify-end gap-2 pb-2", children: _jsxs("div", { className: "ml-2 flex w-full flex-col items-start justify-between gap-2 md:flex-row md:items-end", children: [_jsx(GroupAndSortActionButtons, { navigateTo: navigateTo }), isHalfScreen ? (_jsx(IconButton, { icon: isInvert ? 'ph:sort-ascending' : 'ph:sort-descending', toolTipText: isInvert ? 'Original Call Stack' : 'Invert Call Stack', onClick: () => setInvertStack(isInvert ? '' : 'true'), className: "rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 items-center flex border border-gray-200 dark:border-gray-600 dark:text-white justify-center py-2 px-3 cursor-pointer min-h-[38px]" })) : (_jsxs(Button, { variant: "neutral", className: "gap-2 w-max", onClick: () => setInvertStack(isInvert ? '' : 'true'), children: [isInvert ? 'Original Call Stack' : 'Invert Call Stack', _jsx(Icon, { icon: isInvert ? 'ph:sort-ascending' : 'ph:sort-descending', width: 20 })] })), _jsx(ShowHideLegendButton, { isHalfScreen: isHalfScreen, navigateTo: navigateTo }), isHalfScreen ? (_jsx(IconButton, { icon: "system-uicons:reset", disabled: curPath.length === 0, toolTipText: "Reset View", onClick: () => setNewCurPath([]), className: "rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 items-center flex border border-gray-200 dark:border-gray-600 dark:text-white justify-center py-2 px-3 cursor-pointer min-h-[38px]" })) : (_jsxs(Button, { variant: "neutral", className: "gap-2 w-max", onClick: () => setNewCurPath([]), disabled: curPath.length === 0, children: ["Reset View", _jsx(Icon, { icon: "system-uicons:reset", width: 20 })] }))] }) }));
124
+ setActionButtons?.(_jsx("div", { className: "flex w-full justify-end gap-2 pb-2", children: _jsxs("div", { className: "ml-2 flex w-full flex-col items-start justify-between gap-2 md:flex-row md:items-end", children: [_jsx(GroupAndSortActionButtons, { navigateTo: navigateTo }), isHalfScreen ? (_jsx(IconButton, { icon: isInvert ? 'ph:sort-ascending' : 'ph:sort-descending', toolTipText: isInvert ? 'Original Call Stack' : 'Invert Call Stack', onClick: () => setInvertStack(isInvert ? '' : 'true'), className: "rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 items-center flex border border-gray-200 dark:border-gray-600 dark:text-white justify-center py-2 px-3 cursor-pointer min-h-[38px]" })) : (_jsxs(Button, { variant: "neutral", className: "gap-2 w-max", onClick: () => setInvertStack(isInvert ? '' : 'true'), children: [isInvert ? 'Original Call Stack' : 'Invert Call Stack', _jsx(Icon, { icon: isInvert ? 'ph:sort-ascending' : 'ph:sort-descending', width: 20 })] })), _jsx(ShowHideLegendButton, { isHalfScreen: isHalfScreen, navigateTo: navigateTo }), compareMode && (_jsxs(Button, { variant: "neutral", className: "gap-2 w-max", onClick: () => setCompareAbsolute(isCompareAbsolute ? '' : 'true'), children: [isCompareAbsolute ? 'Compare Relative' : 'Compare Absolute', _jsx(Icon, { icon: isCompareAbsolute ? 'fluent-mdl2:compare' : 'fluent-mdl2:compare-uneven', width: 20 })] })), isHalfScreen ? (_jsx(IconButton, { icon: "system-uicons:reset", disabled: curPath.length === 0, toolTipText: "Reset View", onClick: () => setNewCurPath([]), className: "rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 items-center flex border border-gray-200 dark:border-gray-600 dark:text-white justify-center py-2 px-3 cursor-pointer min-h-[38px]" })) : (_jsxs(Button, { variant: "neutral", className: "gap-2 w-max", onClick: () => setNewCurPath([]), disabled: curPath.length === 0, children: ["Reset View", _jsx(Icon, { icon: "system-uicons:reset", width: 20 })] }))] }) }));
116
125
  }, [
117
126
  navigateTo,
118
127
  isInvert,
@@ -124,6 +133,9 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({ graph, arrow, to
124
133
  loading,
125
134
  isHalfScreen,
126
135
  isLoading,
136
+ compareMode,
137
+ isCompareAbsolute,
138
+ setCompareAbsolute,
127
139
  ]);
128
140
  const loadingState = !loading && (arrow !== undefined || graph !== undefined) && mappings !== undefined;
129
141
  useEffect(() => {
@@ -11,10 +11,10 @@ import { jsx as _jsx } from "react/jsx-runtime";
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, useRef, useState } from 'react';
14
+ import { useEffect, useMemo, useRef, useState } from 'react';
15
15
  import { AnimatePresence, motion } from 'framer-motion';
16
16
  import { Duration, Timestamp } from '@parca/client';
17
- import { MetricsGraphSkeleton, useGrpcMetadata, useParcaContext, } from '@parca/components';
17
+ import { MetricsGraphSkeleton, useGrpcMetadata, useParcaContext, useURLState, } from '@parca/components';
18
18
  import { Query } from '@parca/parser';
19
19
  import { capitalizeOnlyFirstLetter, getStepDuration } from '@parca/utilities';
20
20
  import MetricsGraph from '../MetricsGraph';
@@ -25,6 +25,14 @@ const ErrorContent = ({ errorMessage }) => {
25
25
  export const ProfileMetricsEmptyState = ({ message }) => {
26
26
  return (_jsx("div", { className: "flex h-full w-full flex-col items-center justify-center", children: _jsx("p", { children: message }) }));
27
27
  };
28
+ const getStepCountFromScreenWidth = (pixelsPerPoint) => {
29
+ let width =
30
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
31
+ window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
32
+ // subtract the padding around the graph
33
+ width = width - (20 + 24 + 68) * 2;
34
+ return Math.round(width / pixelsPerPoint);
35
+ };
28
36
  const EMPTY_SUM_BY = [];
29
37
  export const useQueryRange = (client, queryExpression, start, end, timeRange, sumBy = EMPTY_SUM_BY, skip = false) => {
30
38
  const previousQueryParams = useRef({});
@@ -35,6 +43,22 @@ export const useQueryRange = (client, queryExpression, start, end, timeRange, su
35
43
  error: null,
36
44
  });
37
45
  const metadata = useGrpcMetadata();
46
+ const { navigateTo } = useParcaContext();
47
+ const [stepCountStr, setStepCount] = useURLState({ param: 'step_count', navigateTo });
48
+ const defaultStepCount = useMemo(() => {
49
+ return getStepCountFromScreenWidth(10);
50
+ }, []);
51
+ const stepCount = useMemo(() => {
52
+ if (stepCountStr != null) {
53
+ return parseInt(stepCountStr, 10);
54
+ }
55
+ return defaultStepCount;
56
+ }, [stepCountStr, defaultStepCount]);
57
+ useEffect(() => {
58
+ if (stepCountStr == null) {
59
+ setStepCount(defaultStepCount.toString());
60
+ }
61
+ }, [stepCountStr, defaultStepCount, setStepCount]);
38
62
  useEffect(() => {
39
63
  void (async () => {
40
64
  if (skip) {
@@ -50,7 +74,7 @@ export const useQueryRange = (client, queryExpression, start, end, timeRange, su
50
74
  previousQueryParams.current.isRefresh = true;
51
75
  }
52
76
  setLoading(true);
53
- const stepDuration = getStepDuration(start, end);
77
+ const stepDuration = getStepDuration(start, end, stepCount);
54
78
  const call = client.queryRange({
55
79
  query: queryExpression,
56
80
  start: Timestamp.fromDate(new Date(start)),
@@ -75,7 +99,7 @@ export const useQueryRange = (client, queryExpression, start, end, timeRange, su
75
99
  }
76
100
  });
77
101
  })();
78
- }, [client, queryExpression, start, end, metadata, timeRange, sumBy, skip]);
102
+ }, [client, queryExpression, start, end, metadata, timeRange, sumBy, skip, stepCount]);
79
103
  return { ...state, isLoading, isRefreshing: previousQueryParams.current.isRefresh };
80
104
  };
81
105
  const ProfileMetricsGraph = ({ queryClient, queryExpression, profile, from, to, setTimeRange, addLabelMatcher, onPointClick, comparing = false, timeRange, }) => {
@@ -42,7 +42,8 @@ export declare class ProfileDiffSource implements ProfileSource {
42
42
  b: ProfileSource;
43
43
  filterQuery: string | undefined;
44
44
  profileType: ProfileType;
45
- constructor(a: ProfileSource, b: ProfileSource, filterQuery?: string);
45
+ absolute?: boolean;
46
+ constructor(a: ProfileSource, b: ProfileSource, filterQuery?: string, absolute?: boolean);
46
47
  DiffSelection(): ProfileDiffSelection;
47
48
  QueryRequest(): QueryRequest;
48
49
  ProfileType(): ProfileType;
@@ -73,11 +73,12 @@ export class MergedProfileSelection {
73
73
  }
74
74
  }
75
75
  export class ProfileDiffSource {
76
- constructor(a, b, filterQuery) {
76
+ constructor(a, b, filterQuery, absolute) {
77
77
  this.a = a;
78
78
  this.b = b;
79
79
  this.filterQuery = filterQuery;
80
80
  this.profileType = a.ProfileType();
81
+ this.absolute = absolute;
81
82
  }
82
83
  DiffSelection() {
83
84
  throw new Error('Method not implemented.');
@@ -89,9 +90,10 @@ export class ProfileDiffSource {
89
90
  diff: {
90
91
  a: this.a.DiffSelection(),
91
92
  b: this.b.DiffSelection(),
93
+ absolute: this.absolute,
92
94
  },
93
95
  },
94
- reportType: QueryRequest_ReportType.FLAMEGRAPH_UNSPECIFIED,
96
+ reportType: QueryRequest_ReportType.FLAMEGRAPH_ARROW,
95
97
  mode: QueryRequest_Mode.DIFF,
96
98
  filterQuery: this.filterQuery,
97
99
  filter: [],
@@ -143,7 +145,7 @@ export class MergedProfileSource {
143
145
  query: this.query.toString(),
144
146
  },
145
147
  },
146
- reportType: QueryRequest_ReportType.FLAMEGRAPH_UNSPECIFIED,
148
+ reportType: QueryRequest_ReportType.FLAMEGRAPH_ARROW,
147
149
  mode: QueryRequest_Mode.MERGE,
148
150
  filterQuery: this.filterQuery,
149
151
  filter: [],
package/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@parca/profile",
3
- "version": "0.16.401",
3
+ "version": "0.16.403",
4
4
  "description": "Profile viewing libraries",
5
5
  "dependencies": {
6
6
  "@headlessui/react": "^1.7.19",
7
7
  "@iconify/react": "^4.0.0",
8
- "@parca/client": "0.16.119",
9
- "@parca/components": "0.16.289",
8
+ "@parca/client": "0.16.120",
9
+ "@parca/components": "0.16.290",
10
10
  "@parca/dynamicsize": "0.16.65",
11
- "@parca/hooks": "0.0.64",
11
+ "@parca/hooks": "0.0.65",
12
12
  "@parca/icons": "0.16.70",
13
13
  "@parca/parser": "0.16.77",
14
- "@parca/store": "0.16.153",
15
- "@parca/utilities": "0.0.80",
14
+ "@parca/store": "0.16.154",
15
+ "@parca/utilities": "0.0.81",
16
16
  "@popperjs/core": "^2.11.8",
17
17
  "@protobuf-ts/runtime-rpc": "^2.5.0",
18
18
  "@tanstack/react-query": "^4.0.5",
@@ -73,5 +73,5 @@
73
73
  "access": "public",
74
74
  "registry": "https://registry.npmjs.org/"
75
75
  },
76
- "gitHead": "7f74fa47cdf9e7e5c805e4e32596625f47b0fee0"
76
+ "gitHead": "a29408aa67b9e9791fd62789420cbfc7503adb46"
77
77
  }
@@ -12,7 +12,7 @@
12
12
  // limitations under the License.
13
13
 
14
14
  import {QueryServiceClient} from '@parca/client';
15
- import {Card} from '@parca/components';
15
+ import {Card, useURLState} from '@parca/components';
16
16
  import {Query} from '@parca/parser';
17
17
  import type {NavigateFunction} from '@parca/utilities';
18
18
 
@@ -56,6 +56,8 @@ const ProfileExplorerCompare = ({
56
56
  closeProfile('B');
57
57
  };
58
58
 
59
+ const [compareAbsolute] = useURLState({param: 'compare_absolute', navigateTo});
60
+
59
61
  return (
60
62
  <>
61
63
  <div className="flex justify-between gap-2">
@@ -94,7 +96,12 @@ const ProfileExplorerCompare = ({
94
96
  navigateTo={navigateTo}
95
97
  queryClient={queryClient}
96
98
  profileSource={
97
- new ProfileDiffSource(profileA.ProfileSource(), profileB.ProfileSource())
99
+ new ProfileDiffSource(
100
+ profileA.ProfileSource(),
101
+ profileB.ProfileSource(),
102
+ '',
103
+ compareAbsolute === 'true'
104
+ )
98
105
  }
99
106
  />
100
107
  </Card>
@@ -238,9 +238,19 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
238
238
  param: 'invert_call_stack',
239
239
  navigateTo,
240
240
  });
241
-
242
241
  const isInvert = invertStack === 'true';
243
242
 
243
+ // By default, we want delta profiles (CPU) to be relatively compared.
244
+ // For non-delta profiles, like goroutines or memory, we want the profiles to be compared absolutely.
245
+ const compareAbsoluteDefault = profileType?.delta === false ? 'true' : 'false';
246
+
247
+ const [compareAbsolute = compareAbsoluteDefault, setCompareAbsolute] = useURLState({
248
+ param: 'compare_absolute',
249
+ navigateTo,
250
+ withURLUpdate: true,
251
+ });
252
+ const isCompareAbsolute = compareAbsolute === 'true';
253
+
244
254
  const [
245
255
  totalFormatted,
246
256
  totalUnfilteredFormatted,
@@ -294,6 +304,19 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
294
304
  </Button>
295
305
  )}
296
306
  <ShowHideLegendButton isHalfScreen={isHalfScreen} navigateTo={navigateTo} />
307
+ {compareMode && (
308
+ <Button
309
+ variant="neutral"
310
+ className="gap-2 w-max"
311
+ onClick={() => setCompareAbsolute(isCompareAbsolute ? '' : 'true')}
312
+ >
313
+ {isCompareAbsolute ? 'Compare Relative' : 'Compare Absolute'}
314
+ <Icon
315
+ icon={isCompareAbsolute ? 'fluent-mdl2:compare' : 'fluent-mdl2:compare-uneven'}
316
+ width={20}
317
+ />
318
+ </Button>
319
+ )}
297
320
  {isHalfScreen ? (
298
321
  <IconButton
299
322
  icon="system-uicons:reset"
@@ -327,6 +350,9 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
327
350
  loading,
328
351
  isHalfScreen,
329
352
  isLoading,
353
+ compareMode,
354
+ isCompareAbsolute,
355
+ setCompareAbsolute,
330
356
  ]);
331
357
 
332
358
  const loadingState =
@@ -11,7 +11,7 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
- import {useEffect, useRef, useState} from 'react';
14
+ import {useEffect, useMemo, useRef, useState} from 'react';
15
15
 
16
16
  import {RpcError} from '@protobuf-ts/runtime-rpc';
17
17
  import {AnimatePresence, motion} from 'framer-motion';
@@ -22,6 +22,7 @@ import {
22
22
  MetricsGraphSkeleton,
23
23
  useGrpcMetadata,
24
24
  useParcaContext,
25
+ useURLState,
25
26
  } from '@parca/components';
26
27
  import {Query} from '@parca/parser';
27
28
  import {capitalizeOnlyFirstLetter, getStepDuration} from '@parca/utilities';
@@ -80,6 +81,16 @@ export interface IQueryRangeState {
80
81
  error: RpcError | null;
81
82
  }
82
83
 
84
+ const getStepCountFromScreenWidth = (pixelsPerPoint: number): number => {
85
+ let width =
86
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
87
+ window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
88
+
89
+ // subtract the padding around the graph
90
+ width = width - (20 + 24 + 68) * 2;
91
+ return Math.round(width / pixelsPerPoint);
92
+ };
93
+
83
94
  const EMPTY_SUM_BY: string[] = [];
84
95
 
85
96
  export const useQueryRange = (
@@ -103,6 +114,26 @@ export const useQueryRange = (
103
114
  error: null,
104
115
  });
105
116
  const metadata = useGrpcMetadata();
117
+ const {navigateTo} = useParcaContext();
118
+ const [stepCountStr, setStepCount] = useURLState({param: 'step_count', navigateTo});
119
+
120
+ const defaultStepCount = useMemo(() => {
121
+ return getStepCountFromScreenWidth(10);
122
+ }, []);
123
+
124
+ const stepCount = useMemo(() => {
125
+ if (stepCountStr != null) {
126
+ return parseInt(stepCountStr as string, 10);
127
+ }
128
+
129
+ return defaultStepCount;
130
+ }, [stepCountStr, defaultStepCount]);
131
+
132
+ useEffect(() => {
133
+ if (stepCountStr == null) {
134
+ setStepCount(defaultStepCount.toString());
135
+ }
136
+ }, [stepCountStr, defaultStepCount, setStepCount]);
106
137
 
107
138
  useEffect(() => {
108
139
  void (async () => {
@@ -123,7 +154,7 @@ export const useQueryRange = (
123
154
 
124
155
  setLoading(true);
125
156
 
126
- const stepDuration = getStepDuration(start, end);
157
+ const stepDuration = getStepDuration(start, end, stepCount);
127
158
  const call = client.queryRange(
128
159
  {
129
160
  query: queryExpression,
@@ -152,7 +183,7 @@ export const useQueryRange = (
152
183
  }
153
184
  });
154
185
  })();
155
- }, [client, queryExpression, start, end, metadata, timeRange, sumBy, skip]);
186
+ }, [client, queryExpression, start, end, metadata, timeRange, sumBy, skip, stepCount]);
156
187
 
157
188
  return {...state, isLoading, isRefreshing: previousQueryParams.current.isRefresh};
158
189
  };
@@ -136,12 +136,14 @@ export class ProfileDiffSource implements ProfileSource {
136
136
  b: ProfileSource;
137
137
  filterQuery: string | undefined;
138
138
  profileType: ProfileType;
139
+ absolute?: boolean;
139
140
 
140
- constructor(a: ProfileSource, b: ProfileSource, filterQuery?: string) {
141
+ constructor(a: ProfileSource, b: ProfileSource, filterQuery?: string, absolute?: boolean) {
141
142
  this.a = a;
142
143
  this.b = b;
143
144
  this.filterQuery = filterQuery;
144
145
  this.profileType = a.ProfileType();
146
+ this.absolute = absolute;
145
147
  }
146
148
 
147
149
  DiffSelection(): ProfileDiffSelection {
@@ -155,9 +157,10 @@ export class ProfileDiffSource implements ProfileSource {
155
157
  diff: {
156
158
  a: this.a.DiffSelection(),
157
159
  b: this.b.DiffSelection(),
160
+ absolute: this.absolute,
158
161
  },
159
162
  },
160
- reportType: QueryRequest_ReportType.FLAMEGRAPH_UNSPECIFIED,
163
+ reportType: QueryRequest_ReportType.FLAMEGRAPH_ARROW,
161
164
  mode: QueryRequest_Mode.DIFF,
162
165
  filterQuery: this.filterQuery,
163
166
  filter: [],
@@ -227,7 +230,7 @@ export class MergedProfileSource implements ProfileSource {
227
230
  query: this.query.toString(),
228
231
  },
229
232
  },
230
- reportType: QueryRequest_ReportType.FLAMEGRAPH_UNSPECIFIED,
233
+ reportType: QueryRequest_ReportType.FLAMEGRAPH_ARROW,
231
234
  mode: QueryRequest_Mode.MERGE,
232
235
  filterQuery: this.filterQuery,
233
236
  filter: [],