@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.
- package/CHANGELOG.md +8 -0
- package/dist/GraphTooltipArrow/Content.d.ts.map +1 -1
- package/dist/GraphTooltipArrow/Content.js +1 -1
- package/dist/MetricsGraph/MetricsContextMenu/index.d.ts +20 -11
- package/dist/MetricsGraph/MetricsContextMenu/index.d.ts.map +1 -1
- package/dist/MetricsGraph/MetricsContextMenu/index.js +16 -20
- package/dist/MetricsGraph/MetricsTooltip/index.d.ts +2 -8
- package/dist/MetricsGraph/MetricsTooltip/index.d.ts.map +1 -1
- package/dist/MetricsGraph/MetricsTooltip/index.js +46 -55
- package/dist/MetricsGraph/UtilizationMetrics/Throughput.d.ts +2 -5
- package/dist/MetricsGraph/UtilizationMetrics/Throughput.d.ts.map +1 -1
- package/dist/MetricsGraph/UtilizationMetrics/Throughput.js +126 -205
- package/dist/MetricsGraph/UtilizationMetrics/index.d.ts +9 -17
- package/dist/MetricsGraph/UtilizationMetrics/index.d.ts.map +1 -1
- package/dist/MetricsGraph/UtilizationMetrics/index.js +149 -208
- package/dist/MetricsGraph/index.d.ts +19 -26
- package/dist/MetricsGraph/index.d.ts.map +1 -1
- package/dist/MetricsGraph/index.js +50 -115
- package/dist/ProfileFlameGraph/index.d.ts.map +1 -1
- package/dist/ProfileFlameGraph/index.js +3 -1
- package/dist/ProfileMetricsGraph/index.d.ts +1 -1
- package/dist/ProfileMetricsGraph/index.d.ts.map +1 -1
- package/dist/ProfileMetricsGraph/index.js +232 -23
- package/dist/ProfileSelector/MetricsGraphSection.d.ts +1 -4
- package/dist/ProfileSelector/MetricsGraphSection.d.ts.map +1 -1
- package/dist/ProfileSelector/MetricsGraphSection.js +8 -4
- package/dist/ProfileSelector/index.d.ts +3 -6
- package/dist/ProfileSelector/index.d.ts.map +1 -1
- package/dist/ProfileSelector/index.js +2 -2
- package/dist/ProfileSource.d.ts +9 -6
- package/dist/ProfileSource.d.ts.map +1 -1
- package/dist/ProfileSource.js +23 -8
- package/dist/ProfileView/components/GroupByLabelsDropdown/index.d.ts.map +1 -1
- package/dist/ProfileView/components/GroupByLabelsDropdown/index.js +5 -1
- package/dist/ProfileView/components/ProfileFilters/index.d.ts.map +1 -1
- package/dist/ProfileView/components/ProfileFilters/index.js +6 -5
- package/dist/styles.css +1 -1
- package/dist/useQuery.js +1 -1
- package/package.json +7 -7
- package/src/GraphTooltipArrow/Content.tsx +2 -4
- package/src/MetricsGraph/MetricsContextMenu/index.tsx +78 -66
- package/src/MetricsGraph/MetricsTooltip/index.tsx +53 -210
- package/src/MetricsGraph/UtilizationMetrics/Throughput.tsx +242 -434
- package/src/MetricsGraph/UtilizationMetrics/index.tsx +312 -448
- package/src/MetricsGraph/index.tsx +99 -185
- package/src/ProfileFlameGraph/index.tsx +3 -1
- package/src/ProfileMetricsGraph/index.tsx +430 -37
- package/src/ProfileSelector/MetricsGraphSection.tsx +12 -8
- package/src/ProfileSelector/index.tsx +5 -5
- package/src/ProfileSource.tsx +34 -17
- package/src/ProfileView/components/GroupByLabelsDropdown/index.tsx +15 -3
- package/src/ProfileView/components/ProfileFilters/index.tsx +23 -3
- package/src/useQuery.tsx +1 -1
package/src/ProfileSource.tsx
CHANGED
|
@@ -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:
|
|
94
|
-
mergeTo:
|
|
90
|
+
mergeFrom: bigint;
|
|
91
|
+
mergeTo: bigint;
|
|
95
92
|
query: Query;
|
|
96
93
|
profileSource: ProfileSource;
|
|
97
94
|
|
|
98
|
-
constructor(mergeFrom:
|
|
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:
|
|
185
|
-
mergeTo:
|
|
197
|
+
mergeFrom: bigint;
|
|
198
|
+
mergeTo: bigint;
|
|
186
199
|
query: Query;
|
|
187
200
|
profileType: ProfileType;
|
|
188
201
|
|
|
189
|
-
constructor(mergeFrom:
|
|
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:
|
|
202
|
-
end:
|
|
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:
|
|
216
|
-
end:
|
|
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 !==
|
|
256
|
+
if (this.mergeFrom !== 0n) {
|
|
244
257
|
timePart = `over ${formatDuration({
|
|
245
|
-
|
|
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"
|
|
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
|
|
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
|
|
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