@parca/profile 0.19.41 → 0.19.42
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 +4 -0
- package/dist/ProfileView/components/ProfileFilters/useProfileFilters.d.ts +1 -0
- package/dist/ProfileView/components/ProfileFilters/useProfileFilters.d.ts.map +1 -1
- package/dist/ProfileView/components/ProfileFilters/useProfileFilters.js +88 -20
- package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.d.ts.map +1 -1
- package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.js +23 -3
- package/dist/ViewMatchers/index.d.ts.map +1 -1
- package/dist/ViewMatchers/index.js +3 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/package.json +2 -2
- package/src/ProfileView/components/ProfileFilters/useProfileFilters.ts +105 -23
- package/src/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.ts +32 -3
- package/src/ViewMatchers/index.tsx +4 -0
- package/src/index.tsx +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
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.19.42](https://github.com/parca-dev/parca/compare/@parca/profile@0.19.41...@parca/profile@0.19.42) (2025-08-25)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @parca/profile
|
|
9
|
+
|
|
6
10
|
## [0.19.41](https://github.com/parca-dev/parca/compare/@parca/profile@0.19.40...@parca/profile@0.19.41) (2025-08-25)
|
|
7
11
|
|
|
8
12
|
**Note:** Version bump only for package @parca/profile
|
|
@@ -6,6 +6,7 @@ export interface ProfileFilter {
|
|
|
6
6
|
matchType?: 'equal' | 'not_equal' | 'contains' | 'not_contains';
|
|
7
7
|
value: string;
|
|
8
8
|
}
|
|
9
|
+
export declare const convertFromProtoFilters: (protoFilters: Filter[]) => ProfileFilter[];
|
|
9
10
|
export declare const convertToProtoFilters: (profileFilters: ProfileFilter[]) => Filter[];
|
|
10
11
|
export declare const useProfileFilters: () => {
|
|
11
12
|
localFilters: ProfileFilter[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useProfileFilters.d.ts","sourceRoot":"","sources":["../../../../src/ProfileView/components/ProfileFilters/useProfileFilters.ts"],"names":[],"mappings":"AAeA,OAAO,EAAC,KAAK,MAAM,
|
|
1
|
+
{"version":3,"file":"useProfileFilters.d.ts","sourceRoot":"","sources":["../../../../src/ProfileView/components/ProfileFilters/useProfileFilters.ts"],"names":[],"mappings":"AAeA,OAAO,EAAC,KAAK,MAAM,EAA6C,MAAM,eAAe,CAAC;AAKtF,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC;IAClC,KAAK,CAAC,EAAE,eAAe,GAAG,QAAQ,GAAG,aAAa,GAAG,UAAU,GAAG,SAAS,GAAG,aAAa,CAAC;IAC5F,SAAS,CAAC,EAAE,OAAO,GAAG,WAAW,GAAG,UAAU,GAAG,cAAc,CAAC;IAChE,KAAK,EAAE,MAAM,CAAC;CACf;AAqBD,eAAO,MAAM,uBAAuB,GAAI,cAAc,MAAM,EAAE,KAAG,aAAa,EA+E7E,CAAC;AAGF,eAAO,MAAM,qBAAqB,GAAI,gBAAgB,aAAa,EAAE,KAAG,MAAM,EAmF7E,CAAC;AAEF,eAAO,MAAM,iBAAiB,QAAO;IACnC,YAAY,EAAE,aAAa,EAAE,CAAC;IAC9B,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,aAAa,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,mBAAmB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAClD,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,KAAK,IAAI,CAAC;IACpE,YAAY,EAAE,MAAM,IAAI,CAAC;CAsL1B,CAAC"}
|
|
@@ -13,6 +13,91 @@
|
|
|
13
13
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
14
14
|
import { getPresetByKey, isPresetKey } from './filterPresets';
|
|
15
15
|
import { useProfileFiltersUrlState } from './useProfileFiltersUrlState';
|
|
16
|
+
const createStringCondition = (matchType, value) => ({
|
|
17
|
+
condition: matchType === 'equal'
|
|
18
|
+
? { oneofKind: 'equal', equal: value }
|
|
19
|
+
: matchType === 'not_equal'
|
|
20
|
+
? { oneofKind: 'notEqual', notEqual: value }
|
|
21
|
+
: matchType === 'contains'
|
|
22
|
+
? { oneofKind: 'contains', contains: value }
|
|
23
|
+
: { oneofKind: 'notContains', notContains: value },
|
|
24
|
+
});
|
|
25
|
+
const createNumberCondition = (matchType, value) => ({
|
|
26
|
+
condition: matchType === 'equal'
|
|
27
|
+
? { oneofKind: 'equal', equal: value }
|
|
28
|
+
: { oneofKind: 'notEqual', notEqual: value },
|
|
29
|
+
});
|
|
30
|
+
// Convert protobuf Filter[] back to ProfileFilter[] format for editing
|
|
31
|
+
export const convertFromProtoFilters = (protoFilters) => {
|
|
32
|
+
const profileFilters = [];
|
|
33
|
+
for (const [index, protoFilter] of protoFilters.entries()) {
|
|
34
|
+
if (protoFilter?.filter == null)
|
|
35
|
+
continue;
|
|
36
|
+
const filter = protoFilter.filter;
|
|
37
|
+
let type;
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
39
|
+
let criteria;
|
|
40
|
+
if (filter.oneofKind === 'stackFilter' &&
|
|
41
|
+
filter.stackFilter?.filter?.oneofKind === 'criteria') {
|
|
42
|
+
type = 'stack';
|
|
43
|
+
criteria = filter.stackFilter.filter.criteria;
|
|
44
|
+
}
|
|
45
|
+
else if (filter.oneofKind === 'frameFilter' &&
|
|
46
|
+
filter.frameFilter?.filter?.oneofKind === 'criteria') {
|
|
47
|
+
type = 'frame';
|
|
48
|
+
criteria = filter.frameFilter.filter.criteria;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
for (const [fieldName, condition] of Object.entries(criteria)) {
|
|
54
|
+
if (condition === undefined || typeof condition !== 'object')
|
|
55
|
+
continue;
|
|
56
|
+
const conditionObj = condition;
|
|
57
|
+
if (conditionObj.condition?.oneofKind === undefined)
|
|
58
|
+
continue;
|
|
59
|
+
let matchType;
|
|
60
|
+
let value;
|
|
61
|
+
switch (conditionObj.condition.oneofKind) {
|
|
62
|
+
case 'equal':
|
|
63
|
+
matchType = 'equal';
|
|
64
|
+
value = String(conditionObj.condition.equal);
|
|
65
|
+
break;
|
|
66
|
+
case 'notEqual':
|
|
67
|
+
matchType = 'not_equal';
|
|
68
|
+
value = String(conditionObj.condition.notEqual);
|
|
69
|
+
break;
|
|
70
|
+
case 'contains':
|
|
71
|
+
matchType = 'contains';
|
|
72
|
+
value = conditionObj.condition.contains ?? '';
|
|
73
|
+
break;
|
|
74
|
+
case 'notContains':
|
|
75
|
+
matchType = 'not_contains';
|
|
76
|
+
value = conditionObj.condition.notContains ?? '';
|
|
77
|
+
break;
|
|
78
|
+
default:
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const fieldMap = {
|
|
82
|
+
functionName: 'function_name',
|
|
83
|
+
binary: 'binary',
|
|
84
|
+
systemName: 'system_name',
|
|
85
|
+
filename: 'filename',
|
|
86
|
+
address: 'address',
|
|
87
|
+
lineNumber: 'line_number',
|
|
88
|
+
};
|
|
89
|
+
const field = fieldMap[fieldName] ?? fieldName;
|
|
90
|
+
profileFilters.push({
|
|
91
|
+
id: `parsed-${index}-${fieldName}`,
|
|
92
|
+
type: type,
|
|
93
|
+
field: field,
|
|
94
|
+
matchType: matchType,
|
|
95
|
+
value,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return profileFilters;
|
|
100
|
+
};
|
|
16
101
|
// Convert ProfileFilter[] to protobuf Filter[] matching the expected structure
|
|
17
102
|
export const convertToProtoFilters = (profileFilters) => {
|
|
18
103
|
// First, expand any preset filters to their constituent filters
|
|
@@ -41,26 +126,9 @@ export const convertToProtoFilters = (profileFilters) => {
|
|
|
41
126
|
.map(f => {
|
|
42
127
|
// Build the condition based on field type
|
|
43
128
|
const isNumberField = f.field === 'address' || f.field === 'line_number';
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
condition = {
|
|
48
|
-
condition: f.matchType === 'equal'
|
|
49
|
-
? { oneofKind: 'equal', equal: numValue }
|
|
50
|
-
: { oneofKind: 'notEqual', notEqual: numValue },
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
condition = {
|
|
55
|
-
condition: f.matchType === 'equal'
|
|
56
|
-
? { oneofKind: 'equal', equal: f.value }
|
|
57
|
-
: f.matchType === 'not_equal'
|
|
58
|
-
? { oneofKind: 'notEqual', notEqual: f.value }
|
|
59
|
-
: f.matchType === 'contains'
|
|
60
|
-
? { oneofKind: 'contains', contains: f.value }
|
|
61
|
-
: { oneofKind: 'notContains', notContains: f.value },
|
|
62
|
-
};
|
|
63
|
-
}
|
|
129
|
+
const condition = isNumberField
|
|
130
|
+
? createNumberCondition(f.matchType, BigInt(f.value))
|
|
131
|
+
: createStringCondition(f.matchType, f.value);
|
|
64
132
|
// Create FilterCriteria
|
|
65
133
|
const criteria = {};
|
|
66
134
|
switch (f.field) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useProfileFiltersUrlState.d.ts","sourceRoot":"","sources":["../../../../src/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useProfileFiltersUrlState.d.ts","sourceRoot":"","sources":["../../../../src/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.ts"],"names":[],"mappings":"AAeA,OAAO,EAAoB,KAAK,sBAAsB,EAAC,MAAM,mBAAmB,CAAC;AAIjF,OAAO,EAAC,KAAK,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAoEvD,eAAO,MAAM,oBAAoB,GAAI,SAAS,MAAM,KAAG,aAAa,EAgDnE,CAAC;AAEF,eAAO,MAAM,yBAAyB,QAAO;IAC3C,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,iBAAiB,EAAE,sBAAsB,CAAC,aAAa,EAAE,CAAC,CAAC;CAwB5D,CAAC"}
|
|
@@ -10,6 +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 { useMemo } from 'react';
|
|
13
14
|
import { useURLStateCustom } from '@parca/components';
|
|
14
15
|
import { safeDecode } from '@parca/utilities';
|
|
15
16
|
import { isPresetKey } from './filterPresets';
|
|
@@ -58,6 +59,16 @@ const encodeProfileFilters = (filters) => {
|
|
|
58
59
|
})
|
|
59
60
|
.join(',');
|
|
60
61
|
};
|
|
62
|
+
const generateFilterId = (filter, index) => {
|
|
63
|
+
const parts = [
|
|
64
|
+
filter.type ?? '',
|
|
65
|
+
filter.field ?? '',
|
|
66
|
+
filter.matchType ?? '',
|
|
67
|
+
filter.value,
|
|
68
|
+
index.toString(),
|
|
69
|
+
];
|
|
70
|
+
return `filter-${parts.join('-').replace(/[^a-zA-Z0-9-]/g, '_')}`;
|
|
71
|
+
};
|
|
61
72
|
// Decode filters from compact string format
|
|
62
73
|
export const decodeProfileFilters = (encoded) => {
|
|
63
74
|
if (encoded === '' || encoded === undefined)
|
|
@@ -71,8 +82,9 @@ export const decodeProfileFilters = (encoded) => {
|
|
|
71
82
|
if (parts[0] === 'p' && parts.length >= 3) {
|
|
72
83
|
const presetKey = parts[1];
|
|
73
84
|
const value = parts.slice(2).join(':'); // Handle values with colons
|
|
85
|
+
const filterData = { type: presetKey, value };
|
|
74
86
|
return {
|
|
75
|
-
id:
|
|
87
|
+
id: generateFilterId(filterData, index),
|
|
76
88
|
type: presetKey,
|
|
77
89
|
value,
|
|
78
90
|
};
|
|
@@ -81,7 +93,12 @@ export const decodeProfileFilters = (encoded) => {
|
|
|
81
93
|
const [type, field, match, ...valueParts] = parts;
|
|
82
94
|
const value = valueParts.join(':'); // Handle values with colons
|
|
83
95
|
const decodedFilter = {
|
|
84
|
-
id:
|
|
96
|
+
id: generateFilterId({
|
|
97
|
+
type: TYPE_MAP_REVERSE[type],
|
|
98
|
+
field: FIELD_MAP_REVERSE[field],
|
|
99
|
+
matchType: MATCH_MAP_REVERSE[match],
|
|
100
|
+
value,
|
|
101
|
+
}, index),
|
|
85
102
|
type: TYPE_MAP_REVERSE[type],
|
|
86
103
|
field: FIELD_MAP_REVERSE[field],
|
|
87
104
|
matchType: MATCH_MAP_REVERSE[match],
|
|
@@ -105,8 +122,11 @@ export const useProfileFiltersUrlState = () => {
|
|
|
105
122
|
},
|
|
106
123
|
defaultValue: [],
|
|
107
124
|
});
|
|
125
|
+
const memoizedAppliedFilters = useMemo(() => {
|
|
126
|
+
return appliedFilters ?? [];
|
|
127
|
+
}, [appliedFilters]);
|
|
108
128
|
return {
|
|
109
|
-
appliedFilters:
|
|
129
|
+
appliedFilters: memoizedAppliedFilters,
|
|
110
130
|
setAppliedFilters,
|
|
111
131
|
};
|
|
112
132
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ViewMatchers/index.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAiD,MAAM,OAAO,CAAC;AAKtE,OAAO,EAAC,kBAAkB,EAAC,MAAM,eAAe,CAAC;AAEjD,OAAO,EAAC,KAAK,EAAC,MAAM,eAAe,CAAC;AAMpC,UAAU,KAAK;IACb,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,YAAY,EAAE,KAAK,CAAC;IACpB,WAAW,EAAE,kBAAkB,CAAC;IAChC,iBAAiB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,QAAA,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ViewMatchers/index.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAiD,MAAM,OAAO,CAAC;AAKtE,OAAO,EAAC,kBAAkB,EAAC,MAAM,eAAe,CAAC;AAEjD,OAAO,EAAC,KAAK,EAAC,MAAM,eAAe,CAAC;AAMpC,UAAU,KAAK;IACb,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,YAAY,EAAE,KAAK,CAAC;IACpB,WAAW,EAAE,kBAAkB,CAAC;IAChC,iBAAiB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,QAAA,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,CAyKjC,CAAC;AAEF,eAAe,YAAY,CAAC"}
|
|
@@ -44,6 +44,9 @@ const ViewMatchers = ({ labelNames, profileType, queryClient, runQuery, setMatch
|
|
|
44
44
|
useEffect(() => {
|
|
45
45
|
runQueryRef.current = runQuery;
|
|
46
46
|
}, [runQuery]);
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
selectionsRef.current = initialSelections;
|
|
49
|
+
}, [initialSelections]);
|
|
47
50
|
const fetchLabelValues = useCallback(async (labelName) => {
|
|
48
51
|
try {
|
|
49
52
|
const response = await queryClient.values({
|
package/dist/index.d.ts
CHANGED
|
@@ -4,13 +4,14 @@ import ProfileTypeSelector from './ProfileTypeSelector';
|
|
|
4
4
|
import CustomSelect from './SimpleMatchers/Select';
|
|
5
5
|
export * from './ProfileFlameGraph';
|
|
6
6
|
export * from './ProfileSource';
|
|
7
|
-
export { convertToProtoFilters } from './ProfileView/components/ProfileFilters/useProfileFilters';
|
|
7
|
+
export { convertToProtoFilters, convertFromProtoFilters, } from './ProfileView/components/ProfileFilters/useProfileFilters';
|
|
8
8
|
export * from './ProfileView';
|
|
9
9
|
export * from './ProfileViewWithData';
|
|
10
10
|
export * from './utils';
|
|
11
11
|
export * from './ProfileTypeSelector';
|
|
12
12
|
export * from './SourceView';
|
|
13
13
|
export * from './ProfileMetricsGraph';
|
|
14
|
+
export * from './useSumBy';
|
|
14
15
|
export { default as ProfileFilters } from './ProfileView/components/ProfileFilters';
|
|
15
16
|
export { useProfileFiltersUrlState } from './ProfileView/components/ProfileFilters/useProfileFiltersUrlState';
|
|
16
17
|
export declare const DEFAULT_PROFILE_EXPLORER_PARAM_VALUES: {
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAaA,OAAO,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAC;AAC9C,OAAO,eAAe,EAAE,EAAC,sBAAsB,EAAC,MAAM,mBAAmB,CAAC;AAC1E,OAAO,mBAAmB,MAAM,uBAAuB,CAAC;AACxD,OAAO,YAAY,MAAM,yBAAyB,CAAC;AAEnD,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAaA,OAAO,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAC;AAC9C,OAAO,eAAe,EAAE,EAAC,sBAAsB,EAAC,MAAM,mBAAmB,CAAC;AAC1E,OAAO,mBAAmB,MAAM,uBAAuB,CAAC;AACxD,OAAO,YAAY,MAAM,yBAAyB,CAAC;AAEnD,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,OAAO,EACL,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,2DAA2D,CAAC;AACnE,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,SAAS,CAAC;AACxB,cAAc,uBAAuB,CAAC;AACtC,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC;AACtC,cAAc,YAAY,CAAC;AAE3B,OAAO,EAAC,OAAO,IAAI,cAAc,EAAC,MAAM,yCAAyC,CAAC;AAClF,OAAO,EAAC,yBAAyB,EAAC,MAAM,mEAAmE,CAAC;AAE5G,eAAO,MAAM,qCAAqC;;CAEjD,CAAC;AAEF,OAAO,EAAC,eAAe,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,YAAY,EAAE,aAAa,EAAC,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -16,13 +16,14 @@ import ProfileTypeSelector from './ProfileTypeSelector';
|
|
|
16
16
|
import CustomSelect from './SimpleMatchers/Select';
|
|
17
17
|
export * from './ProfileFlameGraph';
|
|
18
18
|
export * from './ProfileSource';
|
|
19
|
-
export { convertToProtoFilters } from './ProfileView/components/ProfileFilters/useProfileFilters';
|
|
19
|
+
export { convertToProtoFilters, convertFromProtoFilters, } from './ProfileView/components/ProfileFilters/useProfileFilters';
|
|
20
20
|
export * from './ProfileView';
|
|
21
21
|
export * from './ProfileViewWithData';
|
|
22
22
|
export * from './utils';
|
|
23
23
|
export * from './ProfileTypeSelector';
|
|
24
24
|
export * from './SourceView';
|
|
25
25
|
export * from './ProfileMetricsGraph';
|
|
26
|
+
export * from './useSumBy';
|
|
26
27
|
export { default as ProfileFilters } from './ProfileView/components/ProfileFilters';
|
|
27
28
|
export { useProfileFiltersUrlState } from './ProfileView/components/ProfileFilters/useProfileFiltersUrlState';
|
|
28
29
|
export const DEFAULT_PROFILE_EXPLORER_PARAM_VALUES = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parca/profile",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.42",
|
|
4
4
|
"description": "Profile viewing libraries",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@floating-ui/react": "^0.27.12",
|
|
@@ -79,5 +79,5 @@
|
|
|
79
79
|
"access": "public",
|
|
80
80
|
"registry": "https://registry.npmjs.org/"
|
|
81
81
|
},
|
|
82
|
-
"gitHead": "
|
|
82
|
+
"gitHead": "3d789485ed269766c55c137aee80bad8c68dd351"
|
|
83
83
|
}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import {useCallback, useEffect, useMemo, useRef, useState} from 'react';
|
|
15
15
|
|
|
16
|
-
import {type Filter} from '@parca/client';
|
|
16
|
+
import {type Filter, type NumberCondition, type StringCondition} from '@parca/client';
|
|
17
17
|
|
|
18
18
|
import {getPresetByKey, isPresetKey} from './filterPresets';
|
|
19
19
|
import {useProfileFiltersUrlState} from './useProfileFiltersUrlState';
|
|
@@ -26,6 +26,106 @@ export interface ProfileFilter {
|
|
|
26
26
|
value: string;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
const createStringCondition = (matchType: string, value: string): StringCondition => ({
|
|
30
|
+
condition:
|
|
31
|
+
matchType === 'equal'
|
|
32
|
+
? {oneofKind: 'equal' as const, equal: value}
|
|
33
|
+
: matchType === 'not_equal'
|
|
34
|
+
? {oneofKind: 'notEqual' as const, notEqual: value}
|
|
35
|
+
: matchType === 'contains'
|
|
36
|
+
? {oneofKind: 'contains' as const, contains: value}
|
|
37
|
+
: {oneofKind: 'notContains' as const, notContains: value},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const createNumberCondition = (matchType: string, value: bigint): NumberCondition => ({
|
|
41
|
+
condition:
|
|
42
|
+
matchType === 'equal'
|
|
43
|
+
? {oneofKind: 'equal' as const, equal: value}
|
|
44
|
+
: {oneofKind: 'notEqual' as const, notEqual: value},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Convert protobuf Filter[] back to ProfileFilter[] format for editing
|
|
48
|
+
export const convertFromProtoFilters = (protoFilters: Filter[]): ProfileFilter[] => {
|
|
49
|
+
const profileFilters: ProfileFilter[] = [];
|
|
50
|
+
|
|
51
|
+
for (const [index, protoFilter] of protoFilters.entries()) {
|
|
52
|
+
if (protoFilter?.filter == null) continue;
|
|
53
|
+
|
|
54
|
+
const filter = protoFilter.filter;
|
|
55
|
+
let type: string;
|
|
56
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
57
|
+
let criteria: Record<string, any>;
|
|
58
|
+
|
|
59
|
+
if (
|
|
60
|
+
filter.oneofKind === 'stackFilter' &&
|
|
61
|
+
filter.stackFilter?.filter?.oneofKind === 'criteria'
|
|
62
|
+
) {
|
|
63
|
+
type = 'stack';
|
|
64
|
+
criteria = filter.stackFilter.filter.criteria;
|
|
65
|
+
} else if (
|
|
66
|
+
filter.oneofKind === 'frameFilter' &&
|
|
67
|
+
filter.frameFilter?.filter?.oneofKind === 'criteria'
|
|
68
|
+
) {
|
|
69
|
+
type = 'frame';
|
|
70
|
+
criteria = filter.frameFilter.filter.criteria;
|
|
71
|
+
} else {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
for (const [fieldName, condition] of Object.entries(criteria)) {
|
|
76
|
+
if (condition === undefined || typeof condition !== 'object') continue;
|
|
77
|
+
|
|
78
|
+
const conditionObj = condition;
|
|
79
|
+
if (conditionObj.condition?.oneofKind === undefined) continue;
|
|
80
|
+
|
|
81
|
+
let matchType: string;
|
|
82
|
+
let value: string;
|
|
83
|
+
|
|
84
|
+
switch (conditionObj.condition.oneofKind) {
|
|
85
|
+
case 'equal':
|
|
86
|
+
matchType = 'equal';
|
|
87
|
+
value = String(conditionObj.condition.equal);
|
|
88
|
+
break;
|
|
89
|
+
case 'notEqual':
|
|
90
|
+
matchType = 'not_equal';
|
|
91
|
+
value = String(conditionObj.condition.notEqual);
|
|
92
|
+
break;
|
|
93
|
+
case 'contains':
|
|
94
|
+
matchType = 'contains';
|
|
95
|
+
value = conditionObj.condition.contains ?? '';
|
|
96
|
+
break;
|
|
97
|
+
case 'notContains':
|
|
98
|
+
matchType = 'not_contains';
|
|
99
|
+
value = conditionObj.condition.notContains ?? '';
|
|
100
|
+
break;
|
|
101
|
+
default:
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const fieldMap: Record<string, string> = {
|
|
106
|
+
functionName: 'function_name',
|
|
107
|
+
binary: 'binary',
|
|
108
|
+
systemName: 'system_name',
|
|
109
|
+
filename: 'filename',
|
|
110
|
+
address: 'address',
|
|
111
|
+
lineNumber: 'line_number',
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const field = fieldMap[fieldName] ?? fieldName;
|
|
115
|
+
|
|
116
|
+
profileFilters.push({
|
|
117
|
+
id: `parsed-${index}-${fieldName}`,
|
|
118
|
+
type: type as ProfileFilter['type'],
|
|
119
|
+
field: field as ProfileFilter['field'],
|
|
120
|
+
matchType: matchType as ProfileFilter['matchType'],
|
|
121
|
+
value,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return profileFilters;
|
|
127
|
+
};
|
|
128
|
+
|
|
29
129
|
// Convert ProfileFilter[] to protobuf Filter[] matching the expected structure
|
|
30
130
|
export const convertToProtoFilters = (profileFilters: ProfileFilter[]): Filter[] => {
|
|
31
131
|
// First, expand any preset filters to their constituent filters
|
|
@@ -56,30 +156,12 @@ export const convertToProtoFilters = (profileFilters: ProfileFilter[]): Filter[]
|
|
|
56
156
|
// Build the condition based on field type
|
|
57
157
|
const isNumberField = f.field === 'address' || f.field === 'line_number';
|
|
58
158
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
condition = {
|
|
63
|
-
condition:
|
|
64
|
-
f.matchType === 'equal'
|
|
65
|
-
? {oneofKind: 'equal' as const, equal: numValue}
|
|
66
|
-
: {oneofKind: 'notEqual' as const, notEqual: numValue},
|
|
67
|
-
};
|
|
68
|
-
} else {
|
|
69
|
-
condition = {
|
|
70
|
-
condition:
|
|
71
|
-
f.matchType === 'equal'
|
|
72
|
-
? {oneofKind: 'equal' as const, equal: f.value}
|
|
73
|
-
: f.matchType === 'not_equal'
|
|
74
|
-
? {oneofKind: 'notEqual' as const, notEqual: f.value}
|
|
75
|
-
: f.matchType === 'contains'
|
|
76
|
-
? {oneofKind: 'contains' as const, contains: f.value}
|
|
77
|
-
: {oneofKind: 'notContains' as const, notContains: f.value},
|
|
78
|
-
};
|
|
79
|
-
}
|
|
159
|
+
const condition: StringCondition | NumberCondition = isNumberField
|
|
160
|
+
? createNumberCondition(f.matchType as string, BigInt(f.value))
|
|
161
|
+
: createStringCondition(f.matchType as string, f.value);
|
|
80
162
|
|
|
81
163
|
// Create FilterCriteria
|
|
82
|
-
const criteria:
|
|
164
|
+
const criteria: Record<string, StringCondition | NumberCondition> = {};
|
|
83
165
|
switch (f.field) {
|
|
84
166
|
case 'function_name':
|
|
85
167
|
criteria.functionName = condition;
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
// See the License for the specific language governing permissions and
|
|
12
12
|
// limitations under the License.
|
|
13
13
|
|
|
14
|
+
import {useMemo} from 'react';
|
|
15
|
+
|
|
14
16
|
import {useURLStateCustom, type ParamValueSetterCustom} from '@parca/components';
|
|
15
17
|
import {safeDecode} from '@parca/utilities';
|
|
16
18
|
|
|
@@ -68,6 +70,20 @@ const encodeProfileFilters = (filters: ProfileFilter[]): string => {
|
|
|
68
70
|
.join(',');
|
|
69
71
|
};
|
|
70
72
|
|
|
73
|
+
const generateFilterId = (
|
|
74
|
+
filter: {type?: string; field?: string; matchType?: string; value: string},
|
|
75
|
+
index: number
|
|
76
|
+
): string => {
|
|
77
|
+
const parts = [
|
|
78
|
+
filter.type ?? '',
|
|
79
|
+
filter.field ?? '',
|
|
80
|
+
filter.matchType ?? '',
|
|
81
|
+
filter.value,
|
|
82
|
+
index.toString(),
|
|
83
|
+
];
|
|
84
|
+
return `filter-${parts.join('-').replace(/[^a-zA-Z0-9-]/g, '_')}`;
|
|
85
|
+
};
|
|
86
|
+
|
|
71
87
|
// Decode filters from compact string format
|
|
72
88
|
export const decodeProfileFilters = (encoded: string): ProfileFilter[] => {
|
|
73
89
|
if (encoded === '' || encoded === undefined) return [];
|
|
@@ -84,8 +100,9 @@ export const decodeProfileFilters = (encoded: string): ProfileFilter[] => {
|
|
|
84
100
|
const presetKey = parts[1];
|
|
85
101
|
const value = parts.slice(2).join(':'); // Handle values with colons
|
|
86
102
|
|
|
103
|
+
const filterData = {type: presetKey, value};
|
|
87
104
|
return {
|
|
88
|
-
id:
|
|
105
|
+
id: generateFilterId(filterData, index),
|
|
89
106
|
type: presetKey,
|
|
90
107
|
value,
|
|
91
108
|
};
|
|
@@ -96,7 +113,15 @@ export const decodeProfileFilters = (encoded: string): ProfileFilter[] => {
|
|
|
96
113
|
const value = valueParts.join(':'); // Handle values with colons
|
|
97
114
|
|
|
98
115
|
const decodedFilter = {
|
|
99
|
-
id:
|
|
116
|
+
id: generateFilterId(
|
|
117
|
+
{
|
|
118
|
+
type: TYPE_MAP_REVERSE[type],
|
|
119
|
+
field: FIELD_MAP_REVERSE[field],
|
|
120
|
+
matchType: MATCH_MAP_REVERSE[match],
|
|
121
|
+
value,
|
|
122
|
+
},
|
|
123
|
+
index
|
|
124
|
+
),
|
|
100
125
|
type: TYPE_MAP_REVERSE[type] as ProfileFilter['type'],
|
|
101
126
|
field: FIELD_MAP_REVERSE[field] as ProfileFilter['field'],
|
|
102
127
|
matchType: MATCH_MAP_REVERSE[match] as ProfileFilter['matchType'],
|
|
@@ -128,8 +153,12 @@ export const useProfileFiltersUrlState = (): {
|
|
|
128
153
|
}
|
|
129
154
|
);
|
|
130
155
|
|
|
156
|
+
const memoizedAppliedFilters = useMemo(() => {
|
|
157
|
+
return appliedFilters ?? [];
|
|
158
|
+
}, [appliedFilters]);
|
|
159
|
+
|
|
131
160
|
return {
|
|
132
|
-
appliedFilters:
|
|
161
|
+
appliedFilters: memoizedAppliedFilters,
|
|
133
162
|
setAppliedFilters,
|
|
134
163
|
};
|
|
135
164
|
};
|
|
@@ -78,6 +78,10 @@ const ViewMatchers: React.FC<Props> = ({
|
|
|
78
78
|
runQueryRef.current = runQuery;
|
|
79
79
|
}, [runQuery]);
|
|
80
80
|
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
selectionsRef.current = initialSelections;
|
|
83
|
+
}, [initialSelections]);
|
|
84
|
+
|
|
81
85
|
const fetchLabelValues = useCallback(
|
|
82
86
|
async (labelName: string): Promise<string[]> => {
|
|
83
87
|
try {
|
package/src/index.tsx
CHANGED
|
@@ -18,13 +18,17 @@ import CustomSelect from './SimpleMatchers/Select';
|
|
|
18
18
|
|
|
19
19
|
export * from './ProfileFlameGraph';
|
|
20
20
|
export * from './ProfileSource';
|
|
21
|
-
export {
|
|
21
|
+
export {
|
|
22
|
+
convertToProtoFilters,
|
|
23
|
+
convertFromProtoFilters,
|
|
24
|
+
} from './ProfileView/components/ProfileFilters/useProfileFilters';
|
|
22
25
|
export * from './ProfileView';
|
|
23
26
|
export * from './ProfileViewWithData';
|
|
24
27
|
export * from './utils';
|
|
25
28
|
export * from './ProfileTypeSelector';
|
|
26
29
|
export * from './SourceView';
|
|
27
30
|
export * from './ProfileMetricsGraph';
|
|
31
|
+
export * from './useSumBy';
|
|
28
32
|
|
|
29
33
|
export {default as ProfileFilters} from './ProfileView/components/ProfileFilters';
|
|
30
34
|
export {useProfileFiltersUrlState} from './ProfileView/components/ProfileFilters/useProfileFiltersUrlState';
|