@parca/profile 0.16.122 → 0.16.124

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.124](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.123...@parca/profile@0.16.124) (2023-02-22)
7
+
8
+ **Note:** Version bump only for package @parca/profile
9
+
10
+ ## [0.16.123](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.122...@parca/profile@0.16.123) (2023-02-22)
11
+
12
+ **Note:** Version bump only for package @parca/profile
13
+
6
14
  ## [0.16.122](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.121...@parca/profile@0.16.122) (2023-02-22)
7
15
 
8
16
  **Note:** Version bump only for package @parca/profile
@@ -98,7 +98,7 @@ var MetricsTooltip = function (_a) {
98
98
  }, [x, y, contextElement, update]);
99
99
  var nameLabel = highlighted === null || highlighted === void 0 ? void 0 : highlighted.labels.find(function (e) { return e.name === '__name__'; });
100
100
  var highlightedNameLabel = nameLabel !== undefined ? nameLabel : { name: '', value: '' };
101
- return (_jsx("div", __assign({ ref: setPopperElement, style: styles.popper }, attributes.popper, { className: "z-10" }, { children: _jsx("div", __assign({ className: "flex max-w-md" }, { children: _jsx("div", __assign({ className: "m-auto" }, { children: _jsx("div", __assign({ className: "border-gray-300 dark:border-gray-500 bg-gray-50 dark:bg-gray-900 rounded-lg p-3 shadow-lg opacity-90", style: { borderWidth: 1 } }, { children: _jsx("div", __assign({ className: "flex flex-row" }, { children: _jsxs("div", __assign({ className: "ml-2 mr-6" }, { children: [_jsx("span", __assign({ className: "font-semibold" }, { children: highlightedNameLabel.value })), _jsx("span", __assign({ className: "block text-gray-700 dark:text-gray-300 my-2" }, { children: _jsx("table", __assign({ className: "table-auto" }, { children: _jsxs("tbody", { children: [_jsxs("tr", { children: [_jsx("td", __assign({ className: "w-1/4" }, { children: "Value" })), _jsx("td", __assign({ className: "w-3/4" }, { children: valueFormatter(highlighted.value, sampleUnit, 1) }))] }), _jsxs("tr", { children: [_jsx("td", __assign({ className: "w-1/4" }, { children: "At" })), _jsx("td", __assign({ className: "w-3/4" }, { children: formatDate(highlighted.timestamp, timeFormat) }))] })] }) })) })), _jsx("span", __assign({ className: "block text-gray-500 my-2" }, { children: highlighted.labels
101
+ return (_jsx("div", __assign({ ref: setPopperElement, style: styles.popper }, attributes.popper, { className: "z-10" }, { children: _jsx("div", __assign({ className: "flex max-w-md" }, { children: _jsx("div", __assign({ className: "m-auto" }, { children: _jsx("div", __assign({ className: "border-gray-300 dark:border-gray-500 bg-gray-50 dark:bg-gray-900 rounded-lg p-3 shadow-lg opacity-90", style: { borderWidth: 1 } }, { children: _jsx("div", __assign({ className: "flex flex-row" }, { children: _jsxs("div", __assign({ className: "ml-2 mr-6" }, { children: [_jsx("span", __assign({ className: "font-semibold" }, { children: highlightedNameLabel.value })), _jsx("span", __assign({ className: "block text-gray-700 dark:text-gray-300 my-2" }, { children: _jsx("table", __assign({ className: "table-auto" }, { children: _jsxs("tbody", { children: [_jsxs("tr", { children: [_jsx("td", __assign({ className: "w-1/4" }, { children: "Value" })), _jsx("td", __assign({ className: "w-3/4" }, { children: valueFormatter(highlighted.valuePerSecond, sampleUnit, 5) }))] }), _jsxs("tr", { children: [_jsx("td", __assign({ className: "w-1/4" }, { children: "Total" })), _jsx("td", __assign({ className: "w-3/4" }, { children: valueFormatter(highlighted.value, sampleUnit, 2) }))] }), _jsxs("tr", { children: [_jsx("td", __assign({ className: "w-1/4" }, { children: "At" })), _jsx("td", __assign({ className: "w-3/4" }, { children: formatDate(highlighted.timestamp, timeFormat) }))] })] }) })) })), _jsx("span", __assign({ className: "block text-gray-500 my-2" }, { children: highlighted.labels
102
102
  .filter(function (label) { return label.name !== '__name__'; })
103
103
  .map(function (label) {
104
104
  return (_jsx("button", __assign({ type: "button", className: "inline-block rounded-lg text-gray-700 bg-gray-200 dark:bg-gray-700 dark:text-gray-400 px-2 py-1 text-xs font-bold mr-3", onClick: function () { return onLabelClick(label.name, label.value); } }, { children: _jsx(TextWithTooltip, { text: "".concat(label.name, "=\"").concat(label.value, "\""), maxTextLength: 37, id: "tooltip-".concat(label.name, "-").concat(label.value) }) }), label.name));
@@ -17,6 +17,7 @@ export interface HighlightedSeries {
17
17
  labels: Label[];
18
18
  timestamp: number;
19
19
  value: number;
20
+ valuePerSecond: number;
20
21
  x: number;
21
22
  y: number;
22
23
  }
@@ -68,9 +68,9 @@ export var RawMetricsGraph = function (_a) {
68
68
  agg.push({
69
69
  metric: s.labelset.labels,
70
70
  values: s.samples.reduce(function (agg, d) {
71
- if (d.timestamp !== undefined && d.value !== undefined) {
71
+ if (d.timestamp !== undefined && d.valuePerSecond !== undefined) {
72
72
  var t = (+d.timestamp.seconds * 1e9 + d.timestamp.nanos) / 1e6; // https://github.com/microsoft/TypeScript/issues/5710#issuecomment-157886246
73
- agg.push([t, parseFloat(d.value)]);
73
+ agg.push([t, d.valuePerSecond, parseFloat(d.value)]);
74
74
  }
75
75
  return agg;
76
76
  }, []),
@@ -122,7 +122,8 @@ export var RawMetricsGraph = function (_a) {
122
122
  seriesIndex: closestSeriesIndex,
123
123
  labels: series[closestSeriesIndex].metric,
124
124
  timestamp: point[0],
125
- value: point[1],
125
+ valuePerSecond: point[1],
126
+ value: point[2],
126
127
  x: xScale(point[0]),
127
128
  y: yScale(point[1]),
128
129
  };
@@ -194,14 +195,14 @@ export var RawMetricsGraph = function (_a) {
194
195
  var s = null;
195
196
  var seriesIndex = -1;
196
197
  outer: for (var i = 0; i < series.length; i++) {
197
- var keys = profile.labels.map(function (e) { return e.name; });
198
+ var keys = profile.query.matchers.map(function (e) { return e.key; });
198
199
  var _loop_1 = function (j) {
199
- var labelName = keys[j];
200
- var label = series[i].metric.find(function (e) { return e.name === labelName; });
200
+ var matcherKey = keys[j];
201
+ var label = series[i].metric.find(function (e) { return e.name === matcherKey; });
201
202
  if (label === undefined) {
202
203
  return "continue-outer";
203
204
  }
204
- if (profile.labels[j].value !== label.value) {
205
+ if (profile.query.matchers[j].value !== label.value) {
205
206
  return "continue-outer";
206
207
  }
207
208
  };
@@ -228,7 +229,8 @@ export var RawMetricsGraph = function (_a) {
228
229
  labels: [],
229
230
  seriesIndex: seriesIndex,
230
231
  timestamp: sample[0],
231
- value: sample[1],
232
+ valuePerSecond: sample[1],
233
+ value: sample[2],
232
234
  x: xScale(sample[0]),
233
235
  y: yScale(sample[1]),
234
236
  };
@@ -8,7 +8,6 @@ interface IcicleGraphProps {
8
8
  curPath: string[];
9
9
  setCurPath: (path: string[]) => void;
10
10
  navigateTo?: NavigateFunction;
11
- isTrimmed?: boolean;
12
11
  }
13
12
  export declare const IcicleGraph: import("react").NamedExoticComponent<IcicleGraphProps>;
14
13
  export default IcicleGraph;
@@ -23,30 +23,22 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
23
23
  // See the License for the specific language governing permissions and
24
24
  // limitations under the License.
25
25
  import { memo, useEffect, useMemo, useRef, useState } from 'react';
26
- import cx from 'classnames';
27
26
  import { scaleLinear } from 'd3-scale';
28
- import { Button, useURLState } from '@parca/components';
29
27
  import { selectQueryParam } from '@parca/functions';
30
- import useUserPreference, { USER_PREFERENCES } from '@parca/functions/useUserPreference';
31
28
  import GraphTooltip from '../../GraphTooltip';
32
29
  import ColorStackLegend from './ColorStackLegend';
33
30
  import { IcicleNode, RowHeight } from './IcicleGraphNodes';
34
31
  import useColoredGraph from './useColoredGraph';
35
32
  export var IcicleGraph = memo(function IcicleGraph(_a) {
36
33
  var _b;
37
- var graph = _a.graph, width = _a.width, setCurPath = _a.setCurPath, curPath = _a.curPath, sampleUnit = _a.sampleUnit, navigateTo = _a.navigateTo, _c = _a.isTrimmed, isTrimmed = _c === void 0 ? false : _c;
38
- var _d = useState(), hoveringNode = _d[0], setHoveringNode = _d[1];
39
- var _e = useState(0), height = _e[0], setHeight = _e[1];
34
+ var graph = _a.graph, width = _a.width, setCurPath = _a.setCurPath, curPath = _a.curPath, sampleUnit = _a.sampleUnit, navigateTo = _a.navigateTo;
35
+ var _c = useState(), hoveringNode = _c[0], setHoveringNode = _c[1];
36
+ var _d = useState(0), height = _d[0], setHeight = _d[1];
40
37
  var svg = useRef(null);
41
38
  var ref = useRef(null);
42
- var rawDashboardItems = useURLState({
43
- param: 'dashboard_items',
44
- })[0];
45
- var dashboardItems = rawDashboardItems;
46
39
  var coloredGraph = useColoredGraph(graph);
47
40
  var currentSearchString = (_b = selectQueryParam('search_string')) !== null && _b !== void 0 ? _b : '';
48
41
  var compareMode = selectQueryParam('compare_a') === 'true' && selectQueryParam('compare_b') === 'true';
49
- var colorProfileName = useUserPreference(USER_PREFERENCES.FLAMEGRAPH_COLOR_PROFILE.key)[0];
50
42
  useEffect(function () {
51
43
  if (ref.current != null) {
52
44
  setHeight(ref === null || ref === void 0 ? void 0 : ref.current.getBoundingClientRect().height);
@@ -62,16 +54,6 @@ export var IcicleGraph = memo(function IcicleGraph(_a) {
62
54
  if (coloredGraph.root === undefined || width === undefined) {
63
55
  return _jsx(_Fragment, {});
64
56
  }
65
- var isColorStackLegendVisible = colorProfileName !== 'default';
66
- return (_jsxs("div", __assign({ onMouseLeave: function () { return setHoveringNode(undefined); } }, { children: [_jsx(ColorStackLegend, { navigateTo: navigateTo, compareMode: compareMode }), _jsx(GraphTooltip, { unit: sampleUnit, total: total, hoveringNode: hoveringNode, contextElement: svg.current, strings: coloredGraph.stringTable, mappings: coloredGraph.mapping, locations: coloredGraph.locations, functions: coloredGraph.function }), _jsx("div", __assign({ className: cx('flex justify-start absolute', {
67
- 'top-[-48px]': dashboardItems.length <= 1 && !isTrimmed && !isColorStackLegendVisible,
68
- 'top-[-69px]': dashboardItems.length <= 1 && !isTrimmed && isColorStackLegendVisible,
69
- 'top-[-54px]': dashboardItems.length <= 1 && isTrimmed && isColorStackLegendVisible,
70
- 'top-[-54px] ': dashboardItems.length <= 1 && isTrimmed && !isColorStackLegendVisible,
71
- 'top-[-54px] left-[25px]': dashboardItems.length > 1 && isTrimmed && isColorStackLegendVisible,
72
- 'top-[-54px] left-[25px] ': dashboardItems.length > 1 && isTrimmed && !isColorStackLegendVisible,
73
- 'top-[-70px] left-[25px]': dashboardItems.length > 1 && !isTrimmed && isColorStackLegendVisible,
74
- 'top-[-46px] left-[25px]': dashboardItems.length > 1 && !isTrimmed && !isColorStackLegendVisible,
75
- }) }, { children: _jsx(Button, __assign({ color: "neutral", onClick: function () { return setCurPath([]); }, disabled: curPath.length === 0, className: "w-auto", variant: "neutral" }, { children: "Reset View" })) })), _jsx("svg", __assign({ className: "font-robotoMono", width: width, height: height, preserveAspectRatio: "xMinYMid", ref: svg }, { children: _jsx("g", __assign({ ref: ref }, { children: _jsx("g", __assign({ transform: 'translate(0, 0)' }, { children: _jsx(IcicleNode, { x: 0, y: 0, totalWidth: width, height: RowHeight, setCurPath: setCurPath, setHoveringNode: setHoveringNode, curPath: curPath, data: coloredGraph.root, strings: coloredGraph.stringTable, mappings: coloredGraph.mapping, locations: coloredGraph.locations, functions: coloredGraph.function, total: total, xScale: xScale, path: [], level: 0, isRoot: true, searchString: currentSearchString, compareMode: compareMode }) })) })) }))] })));
57
+ return (_jsxs("div", __assign({ onMouseLeave: function () { return setHoveringNode(undefined); } }, { children: [_jsx(ColorStackLegend, { navigateTo: navigateTo, compareMode: compareMode }), _jsx(GraphTooltip, { unit: sampleUnit, total: total, hoveringNode: hoveringNode, contextElement: svg.current, strings: coloredGraph.stringTable, mappings: coloredGraph.mapping, locations: coloredGraph.locations, functions: coloredGraph.function }), _jsx("svg", __assign({ className: "font-robotoMono", width: width, height: height, preserveAspectRatio: "xMinYMid", ref: svg }, { children: _jsx("g", __assign({ ref: ref }, { children: _jsx("g", __assign({ transform: 'translate(0, 0)' }, { children: _jsx(IcicleNode, { x: 0, y: 0, totalWidth: width, height: RowHeight, setCurPath: setCurPath, setHoveringNode: setHoveringNode, curPath: curPath, data: coloredGraph.root, strings: coloredGraph.stringTable, mappings: coloredGraph.mapping, locations: coloredGraph.locations, functions: coloredGraph.function, total: total, xScale: xScale, path: [], level: 0, isRoot: true, searchString: currentSearchString, compareMode: compareMode }) })) })) }))] })));
76
58
  });
77
59
  export default IcicleGraph;
@@ -10,6 +10,7 @@ interface ProfileIcicleGraphProps {
10
10
  onContainerResize?: ResizeHandler;
11
11
  navigateTo?: NavigateFunction;
12
12
  loading: boolean;
13
+ setActionButtons?: (buttons: JSX.Element) => void;
13
14
  }
14
- declare const ProfileIcicleGraph: ({ graph, curPath, setNewCurPath, sampleUnit, onContainerResize, navigateTo, loading, }: ProfileIcicleGraphProps) => JSX.Element;
15
+ declare const ProfileIcicleGraph: ({ graph, curPath, setNewCurPath, sampleUnit, onContainerResize, navigateTo, loading, setActionButtons, }: ProfileIcicleGraphProps) => JSX.Element;
15
16
  export default ProfileIcicleGraph;
@@ -23,13 +23,14 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
23
23
  // See the License for the specific language governing permissions and
24
24
  // limitations under the License.
25
25
  import { useEffect, useMemo } from 'react';
26
+ import { Button } from '@parca/components';
26
27
  import { useContainerDimensions } from '@parca/dynamicsize';
27
28
  import { selectQueryParam } from '@parca/functions';
28
29
  import DiffLegend from '../components/DiffLegend';
29
- import IcicleGraph from './IcicleGraph';
30
+ import { IcicleGraph } from './IcicleGraph';
30
31
  var numberFormatter = new Intl.NumberFormat('en-US');
31
32
  var ProfileIcicleGraph = function (_a) {
32
- var graph = _a.graph, curPath = _a.curPath, setNewCurPath = _a.setNewCurPath, sampleUnit = _a.sampleUnit, onContainerResize = _a.onContainerResize, navigateTo = _a.navigateTo, loading = _a.loading;
33
+ var graph = _a.graph, curPath = _a.curPath, setNewCurPath = _a.setNewCurPath, sampleUnit = _a.sampleUnit, onContainerResize = _a.onContainerResize, navigateTo = _a.navigateTo, loading = _a.loading, setActionButtons = _a.setActionButtons;
33
34
  var compareMode = selectQueryParam('compare_a') === 'true' && selectQueryParam('compare_b') === 'true';
34
35
  var _b = useContainerDimensions(), ref = _b.ref, dimensions = _b.dimensions;
35
36
  useEffect(function () {
@@ -55,11 +56,17 @@ var ProfileIcicleGraph = function (_a) {
55
56
  numberFormatter.format(untrimmedTotal),
56
57
  ];
57
58
  }, [graph]), isTrimmed = _c[0], _ = _c[1], trimmedPercentage = _c[2], formattedTotal = _c[3], formattedUntrimmedTotal = _c[4];
59
+ useEffect(function () {
60
+ if (setActionButtons === undefined) {
61
+ return;
62
+ }
63
+ setActionButtons(_jsx(_Fragment, { children: _jsx(Button, __assign({ color: "neutral", onClick: function () { return setNewCurPath([]); }, disabled: curPath.length === 0, className: "w-auto", variant: "neutral" }, { children: "Reset View" })) }));
64
+ }, [setNewCurPath, curPath, setActionButtons]);
58
65
  if (graph === undefined)
59
66
  return _jsx("div", { children: "no data..." });
60
67
  var total = graph.total;
61
68
  if (parseFloat(total) === 0 && !loading)
62
69
  return _jsx(_Fragment, { children: "Profile has no samples" });
63
- return (_jsxs("div", __assign({ className: "relative" }, { children: [compareMode && _jsx(DiffLegend, {}), isTrimmed ? (_jsxs("p", __assign({ className: "my-2 text-sm" }, { children: ["Showing ", formattedTotal, "(", trimmedPercentage, "%) out of ", formattedUntrimmedTotal, " samples"] }))) : null, _jsx("div", __assign({ ref: ref }, { children: _jsx(IcicleGraph, { width: dimensions === null || dimensions === void 0 ? void 0 : dimensions.width, graph: graph, curPath: curPath, setCurPath: setNewCurPath, sampleUnit: sampleUnit, navigateTo: navigateTo, isTrimmed: isTrimmed }) }))] })));
70
+ return (_jsxs("div", __assign({ className: "relative" }, { children: [compareMode && _jsx(DiffLegend, {}), isTrimmed ? (_jsxs("p", __assign({ className: "my-2 text-sm" }, { children: ["Showing ", formattedTotal, "(", trimmedPercentage, "%) out of ", formattedUntrimmedTotal, " samples"] }))) : null, _jsx("div", __assign({ ref: ref }, { children: _jsx(IcicleGraph, { width: dimensions === null || dimensions === void 0 ? void 0 : dimensions.width, graph: graph, curPath: curPath, setCurPath: setNewCurPath, sampleUnit: sampleUnit, navigateTo: navigateTo }) }))] })));
64
71
  };
65
72
  export default ProfileIcicleGraph;
@@ -93,6 +93,7 @@ var ProfileSelector = function (_a) {
93
93
  var newValue = value.includes('\\') ? value.replaceAll('\\', '\\\\') : value;
94
94
  var _a = Query.parse(queryExpressionString).setMatcher(key, newValue), newQuery = _a[0], changed = _a[1];
95
95
  if (changed) {
96
+ // TODO: Change this to store the query object
96
97
  setNewQueryExpression(newQuery.toString());
97
98
  }
98
99
  };
@@ -130,12 +131,21 @@ var ProfileSelector = function (_a) {
130
131
  timeSelection: range.getRangeKey(),
131
132
  });
132
133
  }, addLabelMatcher: addLabelMatcher, onPointClick: function (timestamp, labels, queryExpression) {
134
+ // TODO: Pass the query object via click rather than queryExpression
135
+ var query = Query.parse(queryExpression);
136
+ labels.forEach(function (l) {
137
+ var _a = query.setMatcher(l.name, l.value), newQuery = _a[0], updated = _a[1];
138
+ if (updated) {
139
+ query = newQuery;
140
+ }
141
+ });
133
142
  var stepDuration = getStepDuration(querySelection.from, querySelection.to);
134
143
  var stepDurationInMilliseconds = getStepDurationInMilliseconds(stepDuration);
135
- var isDeltaType = Query.parse(queryExpression).profileType().delta;
136
144
  var mergeFrom = timestamp;
137
- var mergeTo = isDeltaType ? mergeFrom + stepDurationInMilliseconds : mergeFrom;
138
- selectProfile(new MergedProfileSelection(mergeFrom, mergeTo, labels, queryExpression));
145
+ var mergeTo = query.profileType().delta
146
+ ? mergeFrom + stepDurationInMilliseconds
147
+ : mergeFrom;
148
+ selectProfile(new MergedProfileSelection(mergeFrom, mergeTo, query));
139
149
  } })) : (_jsx(_Fragment, { children: profileSelection == null && (_jsx("div", __assign({ className: "my-20 text-center" }, { children: _jsx("p", { children: "Run a query, and the result will be displayed here." }) }))) })) })] }));
140
150
  };
141
151
  export default ProfileSelector;
@@ -1,5 +1,5 @@
1
1
  import { Label, ProfileDiffSelection, QueryRequest } from '@parca/client';
2
- import { ProfileType } from '@parca/parser';
2
+ import { ProfileType, Query } from '@parca/parser';
3
3
  export interface ProfileSource {
4
4
  QueryRequest: () => QueryRequest;
5
5
  ProfileType: () => ProfileType;
@@ -30,10 +30,9 @@ export declare function ProfileSelectionFromParams(expression: string | undefine
30
30
  export declare class MergedProfileSelection implements ProfileSelection {
31
31
  mergeFrom: number;
32
32
  mergeTo: number;
33
- query: string;
33
+ query: Query;
34
34
  filterQuery: string | undefined;
35
- labels: Label[];
36
- constructor(mergeFrom: number, mergeTo: number, labels: Label[], query: string, filterQuery?: string);
35
+ constructor(mergeFrom: number, mergeTo: number, query: Query, filterQuery?: string);
37
36
  ProfileName(): string;
38
37
  HistoryParams(): {
39
38
  [key: string]: any;
@@ -55,14 +54,13 @@ export declare class ProfileDiffSource implements ProfileSource {
55
54
  export declare class MergedProfileSource implements ProfileSource {
56
55
  mergeFrom: number;
57
56
  mergeTo: number;
58
- labels: Label[];
59
- query: string;
57
+ query: Query;
60
58
  filterQuery: string | undefined;
61
- constructor(mergeFrom: number, mergeTo: number, labels: Label[], query: string, filterQuery?: string);
59
+ constructor(mergeFrom: number, mergeTo: number, query: Query, filterQuery?: string);
62
60
  DiffSelection(): ProfileDiffSelection;
63
61
  QueryRequest(): QueryRequest;
64
62
  ProfileType(): ProfileType;
65
63
  Describe(): JSX.Element;
66
- stringLabels(): string[];
64
+ stringMatchers(): string[];
67
65
  toString(): string;
68
66
  }
@@ -30,7 +30,9 @@ export function SuffixParams(params, suffix) {
30
30
  }));
31
31
  }
32
32
  export function ParseLabels(labels) {
33
- return labels.map(function (labelString) {
33
+ return labels
34
+ .filter(function (str) { return str !== ''; })
35
+ .map(function (labelString) {
34
36
  var parts = labelString.split('=', 2);
35
37
  return { name: parts[0], value: parts[1] };
36
38
  });
@@ -41,20 +43,27 @@ export function ProfileSelectionFromParams(expression, from, to, mergeFrom, merg
41
43
  mergeFrom !== undefined &&
42
44
  mergeTo !== undefined &&
43
45
  expression !== undefined) {
44
- return new MergedProfileSelection(parseInt(mergeFrom), parseInt(mergeTo), ParseLabels(labels !== null && labels !== void 0 ? labels : ['']), expression, filterQuery);
46
+ // TODO: Refactor parsing the query and adding matchers
47
+ var query_1 = Query.parse(expression);
48
+ ParseLabels(labels !== null && labels !== void 0 ? labels : ['']).forEach(function (l) {
49
+ var _a = query_1.setMatcher(l.name, l.value), newQuery = _a[0], changed = _a[1];
50
+ if (changed) {
51
+ query_1 = newQuery;
52
+ }
53
+ });
54
+ return new MergedProfileSelection(parseInt(mergeFrom), parseInt(mergeTo), query_1, filterQuery);
45
55
  }
46
56
  return null;
47
57
  }
48
58
  var MergedProfileSelection = /** @class */ (function () {
49
- function MergedProfileSelection(mergeFrom, mergeTo, labels, query, filterQuery) {
59
+ function MergedProfileSelection(mergeFrom, mergeTo, query, filterQuery) {
50
60
  this.mergeFrom = mergeFrom;
51
61
  this.mergeTo = mergeTo;
52
62
  this.query = query;
53
63
  this.filterQuery = filterQuery;
54
- this.labels = labels;
55
64
  }
56
65
  MergedProfileSelection.prototype.ProfileName = function () {
57
- return Query.parse(this.query).profileName();
66
+ return this.query.profileName();
58
67
  };
59
68
  MergedProfileSelection.prototype.HistoryParams = function () {
60
69
  return {
@@ -62,14 +71,14 @@ var MergedProfileSelection = /** @class */ (function () {
62
71
  merge_to: this.mergeTo.toString(),
63
72
  query: this.query,
64
73
  profile_name: this.ProfileName(),
65
- labels: this.labels.map(function (label) { return "".concat(label.name, "=").concat(encodeURIComponent(label.value)); }),
74
+ labels: this.query.matchers.map(function (m) { return "".concat(m.key, "=").concat(encodeURIComponent(m.value)); }),
66
75
  };
67
76
  };
68
77
  MergedProfileSelection.prototype.Type = function () {
69
78
  return 'merge';
70
79
  };
71
80
  MergedProfileSelection.prototype.ProfileSource = function () {
72
- return new MergedProfileSource(this.mergeFrom, this.mergeTo, this.labels, this.query, this.filterQuery);
81
+ return new MergedProfileSource(this.mergeFrom, this.mergeTo, this.query, this.filterQuery);
73
82
  };
74
83
  return MergedProfileSelection;
75
84
  }());
@@ -110,10 +119,9 @@ var ProfileDiffSource = /** @class */ (function () {
110
119
  }());
111
120
  export { ProfileDiffSource };
112
121
  var MergedProfileSource = /** @class */ (function () {
113
- function MergedProfileSource(mergeFrom, mergeTo, labels, query, filterQuery) {
122
+ function MergedProfileSource(mergeFrom, mergeTo, query, filterQuery) {
114
123
  this.mergeFrom = mergeFrom;
115
124
  this.mergeTo = mergeTo;
116
- this.labels = labels;
117
125
  this.query = query;
118
126
  this.filterQuery = filterQuery;
119
127
  }
@@ -124,7 +132,7 @@ var MergedProfileSource = /** @class */ (function () {
124
132
  merge: {
125
133
  start: Timestamp.fromDate(new Date(this.mergeFrom)),
126
134
  end: Timestamp.fromDate(new Date(this.mergeTo)),
127
- query: this.query,
135
+ query: this.query.toString(),
128
136
  },
129
137
  },
130
138
  mode: ProfileDiffSelection_Mode.MERGE,
@@ -137,7 +145,7 @@ var MergedProfileSource = /** @class */ (function () {
137
145
  merge: {
138
146
  start: Timestamp.fromDate(new Date(this.mergeFrom)),
139
147
  end: Timestamp.fromDate(new Date(this.mergeTo)),
140
- query: this.query,
148
+ query: this.query.toString(),
141
149
  },
142
150
  },
143
151
  reportType: QueryRequest_ReportType.FLAMEGRAPH_UNSPECIFIED,
@@ -146,18 +154,18 @@ var MergedProfileSource = /** @class */ (function () {
146
154
  };
147
155
  };
148
156
  MergedProfileSource.prototype.ProfileType = function () {
149
- return ProfileType.fromString(Query.parse(this.query).profileName());
157
+ return ProfileType.fromString(Query.parse(this.query.toString()).profileName());
150
158
  };
151
159
  MergedProfileSource.prototype.Describe = function () {
152
- return (_jsxs("a", { children: ["Merge of \"", this.query, "\" from ", formatDate(this.mergeFrom, timeFormat), " to", ' ', formatDate(this.mergeTo, timeFormat)] }));
160
+ return (_jsxs("a", { children: ["Merge of \"", this.query.toString(), "\" from ", formatDate(this.mergeFrom, timeFormat), ' ', "to ", formatDate(this.mergeTo, timeFormat)] }));
153
161
  };
154
- MergedProfileSource.prototype.stringLabels = function () {
155
- return this.labels
156
- .filter(function (label) { return label.name !== '__name__'; })
157
- .map(function (label) { return "".concat(label.name, "=").concat(label.value); });
162
+ MergedProfileSource.prototype.stringMatchers = function () {
163
+ return this.query.matchers
164
+ .filter(function (m) { return m.key !== '__name__'; })
165
+ .map(function (m) { return "".concat(m.key, "=").concat(m.value); });
158
166
  };
159
167
  MergedProfileSource.prototype.toString = function () {
160
- return "merged profiles of query \"".concat(this.query, "\" from ").concat(formatDate(this.mergeFrom, timeFormat), " to ").concat(formatDate(this.mergeTo, timeFormat));
168
+ return "merged profiles of query \"".concat(this.query.toString(), "\" from ").concat(formatDate(this.mergeFrom, timeFormat), " to ").concat(formatDate(this.mergeTo, timeFormat));
161
169
  };
162
170
  return MergedProfileSource;
163
171
  }());
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import type { DraggableProvidedDragHandleProps } from 'react-beautiful-dnd';
3
+ import type { NavigateFunction } from '@parca/functions';
4
+ interface Props {
5
+ dashboardItem: string;
6
+ index: number;
7
+ isMultiPanelView: boolean;
8
+ handleClosePanel: (dashboardItem: string) => void;
9
+ navigateTo: NavigateFunction | undefined;
10
+ dragHandleProps: DraggableProvidedDragHandleProps | null | undefined;
11
+ getDashboardItemByType: (props: {
12
+ type: string;
13
+ isHalfScreen: boolean;
14
+ setActionButtons: (actionButtons: JSX.Element) => void;
15
+ }) => JSX.Element;
16
+ }
17
+ export declare const VisualizationPanel: React.NamedExoticComponent<Props>;
18
+ export {};
@@ -0,0 +1,38 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
+ // Copyright 2022 The Parca Authors
14
+ // Licensed under the Apache License, Version 2.0 (the "License");
15
+ // you may not use this file except in compliance with the License.
16
+ // You may obtain a copy of the License at
17
+ //
18
+ // http://www.apache.org/licenses/LICENSE-2.0
19
+ //
20
+ // Unless required by applicable law or agreed to in writing, software
21
+ // distributed under the License is distributed on an "AS IS" BASIS,
22
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23
+ // See the License for the specific language governing permissions and
24
+ // limitations under the License.
25
+ import React, { useState } from 'react';
26
+ import { Icon } from '@iconify/react';
27
+ import cx from 'classnames';
28
+ import { CloseIcon } from '@parca/icons';
29
+ import ViewSelector from './ViewSelector';
30
+ export var VisualizationPanel = React.memo(function VisualizationPanel(_a) {
31
+ var dashboardItem = _a.dashboardItem, index = _a.index, isMultiPanelView = _a.isMultiPanelView, handleClosePanel = _a.handleClosePanel, navigateTo = _a.navigateTo, dragHandleProps = _a.dragHandleProps, getDashboardItemByType = _a.getDashboardItemByType;
32
+ var _b = useState(_jsx(_Fragment, {})), actionButtons = _b[0], setActionButtons = _b[1];
33
+ return (_jsxs(_Fragment, { children: [_jsxs("div", __assign({ className: "w-full flex justify-end pb-2" }, { children: [_jsxs("div", __assign({ className: "w-full flex justify-between items-center" }, { children: [_jsxs("div", __assign({ className: "flex" }, { children: [_jsx("div", __assign({ className: cx(isMultiPanelView ? 'visible' : 'invisible', 'flex items-center') }, dragHandleProps, { children: _jsx(Icon, { className: "text-xl", icon: "material-symbols:drag-indicator" }) })), _jsx(_Fragment, { children: actionButtons })] })), _jsx(ViewSelector, { defaultValue: dashboardItem, navigateTo: navigateTo, position: index })] })), isMultiPanelView && (_jsx("button", __assign({ type: "button", onClick: function () { return handleClosePanel(dashboardItem); }, className: "pl-2" }, { children: _jsx(CloseIcon, {}) })))] })), getDashboardItemByType({
34
+ type: dashboardItem,
35
+ isHalfScreen: isMultiPanelView,
36
+ setActionButtons: setActionButtons,
37
+ })] }));
38
+ });
@@ -33,14 +33,12 @@ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-run
33
33
  // See the License for the specific language governing permissions and
34
34
  // limitations under the License.
35
35
  import { Profiler, useEffect, useMemo, useState } from 'react';
36
- import { Icon } from '@iconify/react';
37
36
  import cx from 'classnames';
38
37
  import { scaleLinear } from 'd3';
39
38
  import { DragDropContext, Draggable, Droppable, } from 'react-beautiful-dnd';
40
39
  import { Button, Card, ConditionalWrapper, KeyDownProvider, useParcaContext, useURLState, } from '@parca/components';
41
40
  import { useContainerDimensions } from '@parca/dynamicsize';
42
41
  import { getNewSpanColor } from '@parca/functions';
43
- import { CloseIcon } from '@parca/icons';
44
42
  import { selectDarkMode, useAppSelector } from '@parca/store';
45
43
  import { Callgraph } from '../';
46
44
  import ProfileIcicleGraph from '../ProfileIcicleGraph';
@@ -49,6 +47,7 @@ import ProfileShareButton from '../components/ProfileShareButton';
49
47
  import useDelayedLoader from '../useDelayedLoader';
50
48
  import FilterByFunctionButton from './FilterByFunctionButton';
51
49
  import ViewSelector from './ViewSelector';
50
+ import { VisualizationPanel } from './VisualizationPanel';
52
51
  function arrayEquals(a, b) {
53
52
  return (Array.isArray(a) &&
54
53
  Array.isArray(b) &&
@@ -63,6 +62,7 @@ export var ProfileView = function (_a) {
63
62
  param: 'dashboard_items',
64
63
  navigateTo: navigateTo,
65
64
  }), rawDashboardItems = _d[0], setDashboardItems = _d[1];
65
+ var currentSearchString = useURLState({ param: 'search_string' })[0];
66
66
  var dashboardItems = rawDashboardItems;
67
67
  var isDarkMode = useAppSelector(selectDarkMode);
68
68
  var isMultiPanelView = dashboardItems.length > 1;
@@ -97,19 +97,19 @@ export var ProfileView = function (_a) {
97
97
  var minColor = scaleLinear([isDarkMode ? 'black' : 'white', maxColor])(0.3);
98
98
  var colorRange = [minColor, maxColor];
99
99
  var getDashboardItemByType = function (_a) {
100
- var type = _a.type, isHalfScreen = _a.isHalfScreen;
100
+ var type = _a.type, isHalfScreen = _a.isHalfScreen, setActionButtons = _a.setActionButtons;
101
101
  switch (type) {
102
102
  case 'icicle': {
103
103
  return (flamegraphData === null || flamegraphData === void 0 ? void 0 : flamegraphData.data) != null ? (_jsx(ConditionalWrapper, __assign({ condition: (perf === null || perf === void 0 ? void 0 : perf.onRender) != null, WrapperComponent: Profiler, wrapperProps: {
104
104
  id: 'icicleGraph',
105
105
  onRender: perf === null || perf === void 0 ? void 0 : perf.onRender,
106
- } }, { children: _jsx(ProfileIcicleGraph, { curPath: curPath, setNewCurPath: setNewCurPath, graph: flamegraphData.data, sampleUnit: sampleUnit, onContainerResize: onFlamegraphContainerResize, navigateTo: navigateTo, loading: flamegraphData.loading }) }))) : (_jsx(_Fragment, { children: " " }));
106
+ } }, { children: _jsx(ProfileIcicleGraph, { curPath: curPath, setNewCurPath: setNewCurPath, graph: flamegraphData.data, sampleUnit: sampleUnit, onContainerResize: onFlamegraphContainerResize, navigateTo: navigateTo, loading: flamegraphData.loading, setActionButtons: setActionButtons }) }))) : (_jsx(_Fragment, { children: " " }));
107
107
  }
108
108
  case 'callgraph': {
109
109
  return (callgraphData === null || callgraphData === void 0 ? void 0 : callgraphData.data) != null && (dimensions === null || dimensions === void 0 ? void 0 : dimensions.width) !== undefined ? (_jsx(Callgraph, { graph: callgraphData.data, sampleUnit: sampleUnit, width: isHalfScreen ? (dimensions === null || dimensions === void 0 ? void 0 : dimensions.width) / 2 : dimensions === null || dimensions === void 0 ? void 0 : dimensions.width, colorRange: colorRange })) : (_jsx(_Fragment, {}));
110
110
  }
111
111
  case 'table': {
112
- return topTableData != null ? (_jsx(TopTable, { loading: topTableData.loading, data: topTableData.data, sampleUnit: sampleUnit, navigateTo: navigateTo })) : (_jsx(_Fragment, {}));
112
+ return topTableData != null ? (_jsx(TopTable, { loading: topTableData.loading, data: topTableData.data, sampleUnit: sampleUnit, navigateTo: navigateTo, setActionButtons: setActionButtons, currentSearchString: currentSearchString })) : (_jsx(_Fragment, {}));
113
113
  }
114
114
  default: {
115
115
  return _jsx(_Fragment, {});
@@ -135,10 +135,6 @@ export var ProfileView = function (_a) {
135
135
  onDownloadPProf();
136
136
  } }, { children: "Download pprof" }))] })), _jsx(FilterByFunctionButton, { navigateTo: navigateTo })] })), _jsx("div", __assign({ className: "flex ml-auto gap-2" }, { children: _jsx(ViewSelector, { defaultValue: "", navigateTo: navigateTo, position: -1, placeholderText: "Add panel...", primary: true, addView: true, disabled: isMultiPanelView || dashboardItems.length < 1 }) }))] })), isLoaderVisible ? (_jsx(_Fragment, { children: loader })) : (_jsx(DragDropContext, __assign({ onDragEnd: onDragEnd }, { children: _jsx("div", __assign({ className: "w-full", ref: ref }, { children: _jsx(Droppable, __assign({ droppableId: "droppable", direction: "horizontal" }, { children: function (provided) { return (_jsx("div", __assign({ ref: provided.innerRef, className: "flex space-x-4 justify-between w-full" }, provided.droppableProps, { children: dashboardItems.map(function (dashboardItem, index) {
137
137
  return (_jsx(Draggable, __assign({ draggableId: dashboardItem, index: index, isDragDisabled: !isMultiPanelView }, { children: function (provided, snapshot) { return (_createElement("div", __assign({ ref: provided.innerRef }, provided.draggableProps, { key: dashboardItem, className: cx('border dark:bg-gray-700 rounded border-gray-300 dark:border-gray-500 p-3', isMultiPanelView ? 'w-1/2' : 'w-full', snapshot.isDragging ? 'bg-gray-200' : 'bg-white') }),
138
- _jsxs("div", __assign({ className: "w-full flex justify-end pb-2" }, { children: [_jsxs("div", __assign({ className: "w-full flex justify-between" }, { children: [_jsx("div", __assign({ className: cx(isMultiPanelView ? 'visible' : 'invisible', 'flex items-center') }, provided.dragHandleProps, { children: _jsx(Icon, { className: "text-xl", icon: "material-symbols:drag-indicator" }) })), _jsx(ViewSelector, { defaultValue: dashboardItem, navigateTo: navigateTo, position: index })] })), isMultiPanelView && (_jsx("button", __assign({ type: "button", onClick: function () { return handleClosePanel(dashboardItem); }, className: "pl-2" }, { children: _jsx(CloseIcon, {}) })))] })),
139
- getDashboardItemByType({
140
- type: dashboardItem,
141
- isHalfScreen: isMultiPanelView,
142
- }))); } }), dashboardItem));
138
+ _jsx(VisualizationPanel, { handleClosePanel: handleClosePanel, isMultiPanelView: isMultiPanelView, dashboardItem: dashboardItem, getDashboardItemByType: getDashboardItemByType, dragHandleProps: provided.dragHandleProps, navigateTo: navigateTo, index: index }))); } }), dashboardItem));
143
139
  }) }))); } })) })) })))] }) }) })) }));
144
140
  };
@@ -1,3 +1,4 @@
1
+ import React from 'react';
1
2
  import { Top, TopNodeMeta } from '@parca/client';
2
3
  import { type NavigateFunction } from '@parca/functions';
3
4
  interface TopTableProps {
@@ -5,7 +6,9 @@ interface TopTableProps {
5
6
  data?: Top;
6
7
  sampleUnit: string;
7
8
  navigateTo?: NavigateFunction;
9
+ currentSearchString?: string;
10
+ setActionButtons?: (buttons: JSX.Element) => void;
8
11
  }
9
12
  export declare const RowLabel: (meta: TopNodeMeta | undefined) => string;
10
- export declare const TopTable: ({ data: top, sampleUnit: unit, navigateTo, loading, }: TopTableProps) => JSX.Element;
13
+ export declare const TopTable: React.NamedExoticComponent<TopTableProps>;
11
14
  export default TopTable;
@@ -9,7 +9,7 @@ var __assign = (this && this.__assign) || function () {
9
9
  };
10
10
  return __assign.apply(this, arguments);
11
11
  };
12
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
12
+ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
13
13
  // Copyright 2022 The Parca Authors
14
14
  // Licensed under the Apache License, Version 2.0 (the "License");
15
15
  // you may not use this file except in compliance with the License.
@@ -22,7 +22,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
22
22
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23
23
  // See the License for the specific language governing permissions and
24
24
  // limitations under the License.
25
- import { useCallback, useMemo } from 'react';
25
+ import React, { useCallback, useEffect, useMemo } from 'react';
26
26
  import { createColumnHelper } from '@tanstack/react-table';
27
27
  import { Button, Table, useURLState } from '@parca/components';
28
28
  import { getLastItem, isSearchMatch, parseParams, valueFormatter, } from '@parca/functions';
@@ -47,12 +47,11 @@ var addPlusSign = function (num) {
47
47
  }
48
48
  return "+".concat(num);
49
49
  };
50
- export var TopTable = function (_a) {
50
+ export var TopTable = React.memo(function TopTable(_a) {
51
51
  var _b;
52
- var top = _a.data, unit = _a.sampleUnit, navigateTo = _a.navigateTo, loading = _a.loading;
52
+ var top = _a.data, unit = _a.sampleUnit, navigateTo = _a.navigateTo, loading = _a.loading, currentSearchString = _a.currentSearchString, setActionButtons = _a.setActionButtons;
53
53
  var router = parseParams(window.location.search);
54
54
  var rawDashboardItems = useURLState({ param: 'dashboard_items' })[0];
55
- var currentSearchString = useURLState({ param: 'search_string' })[0];
56
55
  var rawcompareMode = useURLState({ param: 'compare_a' })[0];
57
56
  var compareMode = rawcompareMode === undefined ? false : rawcompareMode === 'true';
58
57
  var dashboardItems = rawDashboardItems;
@@ -135,12 +134,18 @@ export var TopTable = function (_a) {
135
134
  navigateTo('/', __assign(__assign({}, router), { search_string: '' }), { replace: true });
136
135
  }
137
136
  }, [navigateTo, router]);
137
+ useEffect(function () {
138
+ if (setActionButtons === undefined) {
139
+ return;
140
+ }
141
+ setActionButtons(dashboardItems.length > 1 ? (_jsx(Button, __assign({ color: "neutral", onClick: clearSelection, className: "w-auto", variant: "neutral", disabled: currentSearchString === undefined || currentSearchString.length === 0 }, { children: "Clear selection" }))) : (_jsx(_Fragment, {})));
142
+ }, [dashboardItems, clearSelection, currentSearchString, setActionButtons]);
138
143
  var initialSorting = useMemo(function () {
139
144
  return [{ id: compareMode ? 'diff' : 'cumulative', desc: true }];
140
145
  }, [compareMode]);
141
146
  var total = top != null ? top.list.length : 0;
142
147
  if (total === 0 && !loading)
143
148
  return _jsx(_Fragment, { children: "Profile has no samples" });
144
- return (_jsxs("div", __assign({ className: "relative" }, { children: [dashboardItems.length > 1 && (_jsx("div", __assign({ className: "left-[25px] top-[-45px] absolute" }, { children: _jsx(Button, __assign({ color: "neutral", onClick: clearSelection, className: "w-auto", variant: "neutral", disabled: currentSearchString === undefined || currentSearchString.length === 0 }, { children: "Clear selection" })) }))), _jsx("div", __assign({ className: "w-full font-robotoMono h-[80vh] overflow-scroll" }, { children: _jsx(Table, { data: (_b = top === null || top === void 0 ? void 0 : top.list) !== null && _b !== void 0 ? _b : [], columns: columns, initialSorting: initialSorting, onRowClick: onRowClick, enableHighlighting: enableHighlighting, shouldHighlightRow: shouldHighlightRow, usePointerCursor: dashboardItems.length > 1 }) }))] })));
145
- };
149
+ return (_jsx("div", __assign({ className: "relative" }, { children: _jsx("div", __assign({ className: "w-full font-robotoMono h-[80vh] overflow-scroll" }, { children: _jsx(Table, { data: (_b = top === null || top === void 0 ? void 0 : top.list) !== null && _b !== void 0 ? _b : [], columns: columns, initialSorting: initialSorting, onRowClick: onRowClick, enableHighlighting: enableHighlighting, shouldHighlightRow: shouldHighlightRow, usePointerCursor: dashboardItems.length > 1 }) })) })));
150
+ });
146
151
  export default TopTable;