@parca/profile 0.19.44 → 0.19.46

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 (53) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/GraphTooltipArrow/Content.d.ts.map +1 -1
  3. package/dist/GraphTooltipArrow/Content.js +1 -1
  4. package/dist/MetricsGraph/MetricsContextMenu/index.d.ts +20 -11
  5. package/dist/MetricsGraph/MetricsContextMenu/index.d.ts.map +1 -1
  6. package/dist/MetricsGraph/MetricsContextMenu/index.js +16 -20
  7. package/dist/MetricsGraph/MetricsTooltip/index.d.ts +2 -8
  8. package/dist/MetricsGraph/MetricsTooltip/index.d.ts.map +1 -1
  9. package/dist/MetricsGraph/MetricsTooltip/index.js +46 -55
  10. package/dist/MetricsGraph/UtilizationMetrics/Throughput.d.ts +2 -5
  11. package/dist/MetricsGraph/UtilizationMetrics/Throughput.d.ts.map +1 -1
  12. package/dist/MetricsGraph/UtilizationMetrics/Throughput.js +126 -205
  13. package/dist/MetricsGraph/UtilizationMetrics/index.d.ts +9 -17
  14. package/dist/MetricsGraph/UtilizationMetrics/index.d.ts.map +1 -1
  15. package/dist/MetricsGraph/UtilizationMetrics/index.js +149 -208
  16. package/dist/MetricsGraph/index.d.ts +19 -26
  17. package/dist/MetricsGraph/index.d.ts.map +1 -1
  18. package/dist/MetricsGraph/index.js +50 -115
  19. package/dist/ProfileFlameGraph/index.d.ts.map +1 -1
  20. package/dist/ProfileFlameGraph/index.js +3 -1
  21. package/dist/ProfileMetricsGraph/index.d.ts +1 -1
  22. package/dist/ProfileMetricsGraph/index.d.ts.map +1 -1
  23. package/dist/ProfileMetricsGraph/index.js +232 -23
  24. package/dist/ProfileSelector/MetricsGraphSection.d.ts +1 -4
  25. package/dist/ProfileSelector/MetricsGraphSection.d.ts.map +1 -1
  26. package/dist/ProfileSelector/MetricsGraphSection.js +8 -4
  27. package/dist/ProfileSelector/index.d.ts +3 -6
  28. package/dist/ProfileSelector/index.d.ts.map +1 -1
  29. package/dist/ProfileSelector/index.js +2 -2
  30. package/dist/ProfileSource.d.ts +9 -6
  31. package/dist/ProfileSource.d.ts.map +1 -1
  32. package/dist/ProfileSource.js +23 -8
  33. package/dist/ProfileView/components/GroupByLabelsDropdown/index.d.ts.map +1 -1
  34. package/dist/ProfileView/components/GroupByLabelsDropdown/index.js +5 -1
  35. package/dist/ProfileView/components/ProfileFilters/index.d.ts.map +1 -1
  36. package/dist/ProfileView/components/ProfileFilters/index.js +6 -5
  37. package/dist/styles.css +1 -1
  38. package/dist/useQuery.js +1 -1
  39. package/package.json +7 -7
  40. package/src/GraphTooltipArrow/Content.tsx +2 -4
  41. package/src/MetricsGraph/MetricsContextMenu/index.tsx +78 -66
  42. package/src/MetricsGraph/MetricsTooltip/index.tsx +53 -210
  43. package/src/MetricsGraph/UtilizationMetrics/Throughput.tsx +242 -434
  44. package/src/MetricsGraph/UtilizationMetrics/index.tsx +312 -448
  45. package/src/MetricsGraph/index.tsx +99 -185
  46. package/src/ProfileFlameGraph/index.tsx +3 -1
  47. package/src/ProfileMetricsGraph/index.tsx +430 -37
  48. package/src/ProfileSelector/MetricsGraphSection.tsx +12 -8
  49. package/src/ProfileSelector/index.tsx +5 -5
  50. package/src/ProfileSource.tsx +34 -17
  51. package/src/ProfileView/components/GroupByLabelsDropdown/index.tsx +15 -3
  52. package/src/ProfileView/components/ProfileFilters/index.tsx +23 -3
  53. package/src/useQuery.tsx +1 -1
@@ -27,6 +27,7 @@ export interface ProfileSource {
27
27
  ProfileType: () => ProfileType;
28
28
  DiffSelection: () => ProfileDiffSelection;
29
29
  toString: (timezone?: string) => string;
30
+ toKey: () => string;
30
31
  }
31
32
 
32
33
  export interface ProfileSelection {
@@ -79,23 +80,19 @@ export function ProfileSelectionFromParams(
79
80
  return null;
80
81
  }
81
82
 
82
- return new MergedProfileSelection(
83
- parseInt(mergeFrom),
84
- parseInt(mergeTo),
85
- Query.parse(selection)
86
- );
83
+ return new MergedProfileSelection(BigInt(mergeFrom), BigInt(mergeTo), Query.parse(selection));
87
84
  }
88
85
 
89
86
  return null;
90
87
  }
91
88
 
92
89
  export class MergedProfileSelection implements ProfileSelection {
93
- mergeFrom: number;
94
- mergeTo: number;
90
+ mergeFrom: bigint;
91
+ mergeTo: bigint;
95
92
  query: Query;
96
93
  profileSource: ProfileSource;
97
94
 
98
- constructor(mergeFrom: number, mergeTo: number, query: Query) {
95
+ constructor(mergeFrom: bigint, mergeTo: bigint, query: Query) {
99
96
  this.mergeFrom = mergeFrom;
100
97
  this.mergeTo = mergeTo;
101
98
  this.query = query;
@@ -178,15 +175,31 @@ export class ProfileDiffSource implements ProfileSource {
178
175
 
179
176
  return `${this.a.toString()} compared with ${this.b.toString()}`;
180
177
  }
178
+
179
+ toKey(): string {
180
+ return `${this.a.toKey()}-${this.b.toKey()}`;
181
+ }
182
+ }
183
+
184
+ function nanosToTimestamp(nanos: bigint): Timestamp {
185
+ const NANOS_PER_SECOND = 1_000_000_000n;
186
+
187
+ const seconds = nanos / NANOS_PER_SECOND;
188
+ const remainingNanos = nanos % NANOS_PER_SECOND;
189
+
190
+ return {
191
+ seconds,
192
+ nanos: Number(remainingNanos), // Safe since remainingNanos < 1e9
193
+ };
181
194
  }
182
195
 
183
196
  export class MergedProfileSource implements ProfileSource {
184
- mergeFrom: number;
185
- mergeTo: number;
197
+ mergeFrom: bigint;
198
+ mergeTo: bigint;
186
199
  query: Query;
187
200
  profileType: ProfileType;
188
201
 
189
- constructor(mergeFrom: number, mergeTo: number, query: Query) {
202
+ constructor(mergeFrom: bigint, mergeTo: bigint, query: Query) {
190
203
  this.mergeFrom = mergeFrom;
191
204
  this.mergeTo = mergeTo;
192
205
  this.query = query;
@@ -198,8 +211,8 @@ export class MergedProfileSource implements ProfileSource {
198
211
  options: {
199
212
  oneofKind: 'merge',
200
213
  merge: {
201
- start: Timestamp.fromDate(new Date(this.mergeFrom)),
202
- end: Timestamp.fromDate(new Date(this.mergeTo)),
214
+ start: nanosToTimestamp(this.mergeFrom),
215
+ end: nanosToTimestamp(this.mergeTo),
203
216
  query: this.query.toString(),
204
217
  },
205
218
  },
@@ -212,8 +225,8 @@ export class MergedProfileSource implements ProfileSource {
212
225
  options: {
213
226
  oneofKind: 'merge',
214
227
  merge: {
215
- start: Timestamp.fromDate(new Date(this.mergeFrom)),
216
- end: Timestamp.fromDate(new Date(this.mergeTo)),
228
+ start: nanosToTimestamp(this.mergeFrom),
229
+ end: nanosToTimestamp(this.mergeTo),
217
230
  query: this.query.toString(),
218
231
  },
219
232
  },
@@ -240,9 +253,9 @@ export class MergedProfileSource implements ProfileSource {
240
253
  }
241
254
 
242
255
  let timePart = '';
243
- if (this.mergeFrom !== 0) {
256
+ if (this.mergeFrom !== 0n) {
244
257
  timePart = `over ${formatDuration({
245
- milliseconds: this.mergeTo - this.mergeFrom,
258
+ nanos: Number(this.mergeTo - this.mergeFrom),
246
259
  })} from ${formatDate(this.mergeFrom, timeFormat(timezone), timezone)} to ${formatDate(
247
260
  this.mergeTo,
248
261
  timeFormat(timezone),
@@ -252,4 +265,8 @@ export class MergedProfileSource implements ProfileSource {
252
265
 
253
266
  return `Merged profiles${queryPart}${timePart}`;
254
267
  }
268
+
269
+ toKey(): string {
270
+ return `${this.mergeFrom.toString()}-${this.mergeTo.toString()}-${this.query.toString()}`;
271
+ }
255
272
  }
@@ -13,6 +13,8 @@
13
13
 
14
14
  import Select from 'react-select';
15
15
 
16
+ import {testId} from '@parca/test-utils';
17
+
16
18
  import {FIELD_LABELS} from '../../../ProfileFlameGraph/FlameGraphArrow';
17
19
 
18
20
  interface LabelOption {
@@ -28,13 +30,14 @@ interface Props {
28
30
 
29
31
  const GroupByLabelsDropdown = ({labels, groupBy, setGroupByLabels}: Props): JSX.Element => {
30
32
  return (
31
- <div className="flex flex-col">
33
+ <div className="flex flex-col" {...testId('GROUP_BY_CONTAINER')}>
32
34
  <div className="flex items-center justify-between">
33
- <label className="text-sm">Group by</label>
35
+ <label className="text-sm" {...testId('GROUP_BY_LABEL')}>
36
+ Group by
37
+ </label>
34
38
  </div>
35
39
 
36
40
  <Select<LabelOption, true>
37
- id="h-group-by-labels-selector"
38
41
  isMulti
39
42
  defaultMenuIsOpen={false}
40
43
  defaultValue={undefined}
@@ -42,6 +45,15 @@ const GroupByLabelsDropdown = ({labels, groupBy, setGroupByLabels}: Props): JSX.
42
45
  options={labels.map(label => ({label, value: `${FIELD_LABELS}.${label}`}))}
43
46
  className="parca-select-container text-sm rounded-md bg-white"
44
47
  classNamePrefix="parca-select"
48
+ menuPortalTarget={document.body}
49
+ components={{
50
+ // eslint-disable-next-line react/prop-types
51
+ MenuList: ({children, innerProps}) => (
52
+ <div {...testId('GROUP_BY_SELECT_FLYOUT')} {...innerProps}>
53
+ {children}
54
+ </div>
55
+ ),
56
+ }}
45
57
  value={groupBy
46
58
  .filter(l => l.startsWith(FIELD_LABELS))
47
59
  .map(l => ({value: l, label: l.slice(FIELD_LABELS.length + 1)}))}
@@ -17,6 +17,7 @@ import {Icon} from '@iconify/react';
17
17
  import cx from 'classnames';
18
18
 
19
19
  import {Button, Input, Select, type SelectItem} from '@parca/components';
20
+ import {testId} from '@parca/test-utils';
20
21
 
21
22
  import {useProfileViewContext} from '../../context/ProfileViewContext';
22
23
  import {getPresetByKey, getPresetsForProfileType, isPresetKey} from './filterPresets';
@@ -204,7 +205,7 @@ const ProfileFilters = ({readOnly = false}: ProfileFiltersProps = {}): JSX.Eleme
204
205
  const filtersToRender = localFilters.length > 0 ? localFilters : appliedFilters ?? [];
205
206
 
206
207
  return (
207
- <div className="flex gap-2 w-full items-start">
208
+ <div className="flex gap-2 w-full items-start" {...testId('PROFILE_FILTERS_CONTAINER')}>
208
209
  <div className="flex-1 flex flex-wrap gap-2">
209
210
  {filtersToRender.map(filter => {
210
211
  const isNumberField = filter.field === 'address' || filter.field === 'line_number';
@@ -218,6 +219,8 @@ const ProfileFilters = ({readOnly = false}: ProfileFiltersProps = {}): JSX.Eleme
218
219
  selectedKey={filter.type}
219
220
  placeholder="Select Filter"
220
221
  disabled={readOnly}
222
+ {...testId('FILTER_TYPE_SELECT')}
223
+ flyoutTestId="filter-type-select-flyout"
221
224
  onSelection={key => {
222
225
  // Check if this is a preset selection
223
226
  if (isPresetKey(key)) {
@@ -266,6 +269,8 @@ const ProfileFilters = ({readOnly = false}: ProfileFiltersProps = {}): JSX.Eleme
266
269
  items={fieldItems}
267
270
  selectedKey={filter.field ?? ''}
268
271
  disabled={readOnly}
272
+ {...testId('FILTER_FIELD_SELECT')}
273
+ flyoutTestId="filter-field-select-flyout"
269
274
  onSelection={key => {
270
275
  const newField = key as ProfileFilter['field'];
271
276
  const isNewFieldNumber = newField === 'address' || newField === 'line_number';
@@ -292,6 +297,8 @@ const ProfileFilters = ({readOnly = false}: ProfileFiltersProps = {}): JSX.Eleme
292
297
  items={matchTypeItems}
293
298
  selectedKey={filter.matchType ?? ''}
294
299
  disabled={readOnly}
300
+ {...testId('FILTER_MATCH_TYPE_SELECT')}
301
+ flyoutTestId="filter-match-type-select-flyout"
295
302
  onSelection={key =>
296
303
  updateFilter(filter.id, {matchType: key as ProfileFilter['matchType']})
297
304
  }
@@ -309,6 +316,7 @@ const ProfileFilters = ({readOnly = false}: ProfileFiltersProps = {}): JSX.Eleme
309
316
  onChange={e => updateFilter(filter.id, {value: e.target.value})}
310
317
  onKeyDown={handleKeyDown}
311
318
  className="rounded-none w-36 text-sm focus:outline-1"
319
+ {...testId('FILTER_VALUE_INPUT')}
312
320
  />
313
321
  </>
314
322
  )}
@@ -316,6 +324,7 @@ const ProfileFilters = ({readOnly = false}: ProfileFiltersProps = {}): JSX.Eleme
316
324
  {!readOnly && (
317
325
  <Button
318
326
  variant="neutral"
327
+ {...testId('FILTER_REMOVE_BUTTON')}
319
328
  onClick={() => {
320
329
  // If we're displaying local filters and this is the last one, reset everything
321
330
  if (localFilters.length > 0 && localFilters.length === 1) {
@@ -345,13 +354,23 @@ const ProfileFilters = ({readOnly = false}: ProfileFiltersProps = {}): JSX.Eleme
345
354
  })}
346
355
 
347
356
  {!readOnly && localFilters.length > 0 && (
348
- <Button variant="neutral" onClick={addFilter} className="p-3 h-[38px]">
357
+ <Button
358
+ variant="neutral"
359
+ onClick={addFilter}
360
+ className="p-3 h-[38px]"
361
+ {...testId('ADD_FILTER_BUTTON')}
362
+ >
349
363
  <Icon icon="mdi:filter-plus-outline" className="h-4 w-4" />
350
364
  </Button>
351
365
  )}
352
366
 
353
367
  {!readOnly && localFilters.length === 0 && (appliedFilters?.length ?? 0) === 0 && (
354
- <Button variant="neutral" onClick={addFilter} className="flex items-center gap-2">
368
+ <Button
369
+ variant="neutral"
370
+ onClick={addFilter}
371
+ className="flex items-center gap-2"
372
+ {...testId('ADD_FILTER_BUTTON')}
373
+ >
355
374
  <Icon icon="mdi:filter-outline" className="h-4 w-4" />
356
375
  <span>Filter</span>
357
376
  </Button>
@@ -364,6 +383,7 @@ const ProfileFilters = ({readOnly = false}: ProfileFiltersProps = {}): JSX.Eleme
364
383
  onClick={onApplyFilters}
365
384
  disabled={!hasUnsavedChanges || !localFilters.some(isFilterComplete)}
366
385
  className={cx('flex items-center gap-2 sticky top-0')}
386
+ {...testId('APPLY_FILTERS_BUTTON')}
367
387
  >
368
388
  <span>Apply</span>
369
389
  </Button>
package/src/useQuery.tsx CHANGED
@@ -55,7 +55,7 @@ export const useQuery = (
55
55
  const {data, isLoading, error} = useGrpcQuery<QueryResponse | undefined>({
56
56
  key: [
57
57
  'query',
58
- profileSource,
58
+ profileSource.toKey(),
59
59
  reportType,
60
60
  options?.nodeTrimThreshold,
61
61
  options?.groupBy,