@parca/profile 0.16.358 → 0.16.360

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.360 (2024-04-25)
7
+
8
+ **Note:** Version bump only for package @parca/profile
9
+
10
+ ## 0.16.359 (2024-04-15)
11
+
12
+ **Note:** Version bump only for package @parca/profile
13
+
6
14
  ## 0.16.358 (2024-04-02)
7
15
 
8
16
  **Note:** Version bump only for package @parca/profile
@@ -2,12 +2,12 @@ import { CallgraphEdge, CallgraphNode } from '@parca/client';
2
2
  export declare const pixelsToInches: (pixels: number) => number;
3
3
  export declare const getCurvePoints: ({ pos, xScale, yScale, source, target, offset, isSelfLoop, }: {
4
4
  pos: string;
5
- xScale?: ((pos: number) => number) | undefined;
6
- yScale?: ((pos: number) => number) | undefined;
7
- source?: number[] | undefined;
8
- target?: number[] | undefined;
9
- isSelfLoop?: boolean | undefined;
10
- offset?: number | undefined;
5
+ xScale?: (pos: number) => number;
6
+ yScale?: (pos: number) => number;
7
+ source?: number[];
8
+ target?: number[];
9
+ isSelfLoop?: boolean;
10
+ offset?: number;
11
11
  }) => number[];
12
12
  export declare const jsonToDot: ({ graph, colorRange, }: {
13
13
  graph: {
@@ -31,11 +31,11 @@ export declare const GraphTooltipContent: ({ hoveringNode, unit, total, totalUnf
31
31
  total: bigint;
32
32
  totalUnfiltered: bigint;
33
33
  isFixed: boolean;
34
- strings?: string[] | undefined;
35
- mappings?: Mapping[] | undefined;
36
- locations?: Location[] | undefined;
37
- functions?: ParcaFunction[] | undefined;
38
- type?: string | undefined;
34
+ strings?: string[];
35
+ mappings?: Mapping[];
36
+ locations?: Location[];
37
+ functions?: ParcaFunction[];
38
+ type?: string;
39
39
  }) => JSX.Element;
40
40
  declare const GraphTooltip: ({ x, y, unit, total, totalUnfiltered, hoveringNode: hoveringNodeProp, contextElement, isFixed, virtualContextElement, strings, mappings, locations, functions, type, }: GraphTooltipProps) => JSX.Element;
41
41
  export default GraphTooltip;
@@ -14,9 +14,8 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
14
14
  import { useEffect, useState } from 'react';
15
15
  import { Icon } from '@iconify/react';
16
16
  import { usePopper } from 'react-popper';
17
- import { TextWithTooltip } from '@parca/components';
18
- import { formatDate, valueFormatter } from '@parca/utilities';
19
- import { timeFormat } from '../../';
17
+ import { TextWithTooltip, useParcaContext } from '@parca/components';
18
+ import { formatDate, timePattern, valueFormatter } from '@parca/utilities';
20
19
  const virtualElement = {
21
20
  getBoundingClientRect: () => {
22
21
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
@@ -44,6 +43,7 @@ function generateGetBoundingClientRect(contextElement, x = 0, y = 0) {
44
43
  });
45
44
  }
46
45
  const MetricsTooltip = ({ x, y, highlighted, contextElement, sampleUnit, delta, }) => {
46
+ const { timezone } = useParcaContext();
47
47
  const [popperElement, setPopperElement] = useState(null);
48
48
  const { styles, attributes, ...popperProps } = usePopper(virtualElement, popperElement, {
49
49
  placement: 'auto-start',
@@ -73,7 +73,7 @@ const MetricsTooltip = ({ x, y, highlighted, contextElement, sampleUnit, delta,
73
73
  }, [x, y, contextElement, update]);
74
74
  const nameLabel = highlighted?.labels.find(e => e.name === '__name__');
75
75
  const highlightedNameLabel = nameLabel !== undefined ? nameLabel : { name: '', value: '' };
76
- return (_jsx("div", { ref: setPopperElement, style: styles.popper, ...attributes.popper, className: "z-10", children: _jsx("div", { className: "flex max-w-md", children: _jsx("div", { className: "m-auto", children: _jsx("div", { className: "rounded-lg border-gray-300 bg-gray-50 p-3 opacity-90 shadow-lg dark:border-gray-500 dark:bg-gray-900", style: { borderWidth: 1 }, children: _jsx("div", { className: "flex flex-row", children: _jsxs("div", { className: "ml-2 mr-6", children: [_jsx("span", { className: "font-semibold", children: highlightedNameLabel.value }), _jsx("span", { className: "my-2 block text-gray-700 dark:text-gray-300", children: _jsx("table", { className: "table-auto", children: _jsxs("tbody", { children: [delta ? (_jsxs(_Fragment, { children: [_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Per Second" }), _jsx("td", { className: "w-3/4", children: valueFormatter(highlighted.valuePerSecond, sampleUnit === 'nanoseconds' ? 'CPU Cores' : sampleUnit, 5) })] }), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Total" }), _jsx("td", { className: "w-3/4", children: valueFormatter(highlighted.value, sampleUnit, 2) })] })] })) : (_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Value" }), _jsx("td", { className: "w-3/4", children: valueFormatter(highlighted.valuePerSecond, sampleUnit, 5) })] })), highlighted.duration > 0 && (_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Duration" }), _jsx("td", { className: "w-3/4", children: valueFormatter(highlighted.duration, 'nanoseconds', 2) })] })), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "At" }), _jsx("td", { className: "w-3/4", children: formatDate(highlighted.timestamp, timeFormat) })] })] }) }) }), _jsx("span", { className: "my-2 block text-gray-500", children: highlighted.labels
76
+ return (_jsx("div", { ref: setPopperElement, style: styles.popper, ...attributes.popper, className: "z-10", children: _jsx("div", { className: "flex max-w-md", children: _jsx("div", { className: "m-auto", children: _jsx("div", { className: "rounded-lg border-gray-300 bg-gray-50 p-3 opacity-90 shadow-lg dark:border-gray-500 dark:bg-gray-900", style: { borderWidth: 1 }, children: _jsx("div", { className: "flex flex-row", children: _jsxs("div", { className: "ml-2 mr-6", children: [_jsx("span", { className: "font-semibold", children: highlightedNameLabel.value }), _jsx("span", { className: "my-2 block text-gray-700 dark:text-gray-300", children: _jsx("table", { className: "table-auto", children: _jsxs("tbody", { children: [delta ? (_jsxs(_Fragment, { children: [_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Per Second" }), _jsx("td", { className: "w-3/4", children: valueFormatter(highlighted.valuePerSecond, sampleUnit === 'nanoseconds' ? 'CPU Cores' : sampleUnit, 5) })] }), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Total" }), _jsx("td", { className: "w-3/4", children: valueFormatter(highlighted.value, sampleUnit, 2) })] })] })) : (_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Value" }), _jsx("td", { className: "w-3/4", children: valueFormatter(highlighted.valuePerSecond, sampleUnit, 5) })] })), highlighted.duration > 0 && (_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Duration" }), _jsx("td", { className: "w-3/4", children: valueFormatter(highlighted.duration, 'nanoseconds', 2) })] })), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "At" }), _jsx("td", { className: "w-3/4", children: formatDate(highlighted.timestamp, timePattern(timezone), timezone) })] })] }) }) }), _jsx("span", { className: "my-2 block text-gray-500", children: highlighted.labels
77
77
  .filter((label) => label.name !== '__name__')
78
78
  .map((label) => (_jsx("div", { className: "mr-3 inline-block rounded-lg bg-gray-200 px-2 py-1 text-xs font-bold text-gray-700 dark:bg-gray-700 dark:text-gray-400", children: _jsx(TextWithTooltip, { text: `${label.name}="${label.value}"`, maxTextLength: 37, id: `tooltip-${label.name}-${label.value}` }) }, label.name))) }), _jsxs("div", { className: "flex w-full items-center gap-1 text-xs text-gray-500", children: [_jsx(Icon, { icon: "iconoir:mouse-button-right" }), _jsx("div", { children: "Right click to add labels to query." })] })] }) }) }) }) }) }));
79
79
  };
@@ -16,7 +16,7 @@ import * as d3 from 'd3';
16
16
  import { pointer } from 'd3-selection';
17
17
  import throttle from 'lodash.throttle';
18
18
  import { useContextMenu } from 'react-contexify';
19
- import { DateTimeRange } from '@parca/components';
19
+ import { DateTimeRange, useParcaContext } from '@parca/components';
20
20
  import { formatDate, formatForTimespan, getPrecision, sanitizeHighlightedValues, valueFormatter, } from '@parca/utilities';
21
21
  import MetricsCircle from '../MetricsCircle';
22
22
  import MetricsSeries from '../MetricsSeries';
@@ -37,6 +37,7 @@ export const parseValue = (value) => {
37
37
  const lineStroke = '1px';
38
38
  const lineStrokeHover = '2px';
39
39
  export const RawMetricsGraph = ({ data, from, to, profile, onSampleClick, addLabelMatcher, setTimeRange, width, height = 50, margin = 0, sampleUnit, }) => {
40
+ const { timezone } = useParcaContext();
40
41
  const graph = useRef(null);
41
42
  const [dragging, setDragging] = useState(false);
42
43
  const [hovering, setHovering] = useState(false);
@@ -265,7 +266,7 @@ export const RawMetricsGraph = ({ data, from, to, profile, onSampleClick, addLab
265
266
  transform: `translate(0, ${yScale(d)})`, children: [_jsx("line", { className: "stroke-gray-300 dark:stroke-gray-500", x2: -6 }), _jsx("text", { fill: "currentColor", x: -9, dy: '0.32em', children: valueFormatter(d, yAxisUnit, decimals) })] }, `tick-${i}`), _jsx("g", { children: _jsx("line", { className: "stroke-gray-300 dark:stroke-gray-500", x1: xScale(from), x2: xScale(to), y1: yScale(d), y2: yScale(d) }) }, `grid-${i}`)] }, `${i.toString()}-${d.toString()}`));
266
267
  }), _jsx("line", { className: "stroke-gray-300 dark:stroke-gray-500", x1: 0, x2: 0, y1: 0, y2: height - margin }), _jsx("line", { className: "stroke-gray-300 dark:stroke-gray-500", x1: xScale(to), x2: xScale(to), y1: 0, y2: height - margin }), _jsx("g", { transform: `translate(${-margin}, ${(height - margin) / 2}) rotate(270)`, children: _jsx("text", { fill: "currentColor", dy: "-0.7em", className: "text-sm capitalize", textAnchor: "middle", children: yAxisLabel }) })] }), _jsxs("g", { className: "x axis", fill: "none", fontSize: "10", textAnchor: "middle", transform: `translate(0,${height - margin})`, children: [xScale.ticks(5).map((d, i) => (_jsxs(Fragment, { children: [_jsxs("g", { className: "tick",
267
268
  /* eslint-disable-next-line @typescript-eslint/restrict-template-expressions */
268
- transform: `translate(${xScale(d)}, 0)`, children: [_jsx("line", { y2: 6, className: "stroke-gray-300 dark:stroke-gray-500" }), _jsx("text", { fill: "currentColor", dy: ".71em", y: 9, children: formatDate(d, formatForTimespan(from, to)) })] }, `tick-${i}`), _jsx("g", { children: _jsx("line", { className: "stroke-gray-300 dark:stroke-gray-500", x1: xScale(d), x2: xScale(d), y1: 0, y2: -height + margin }) }, `grid-${i}`)] }, `${i.toString()}-${d.toString()}`))), _jsx("line", { className: "stroke-gray-300 dark:stroke-gray-500", x1: xScale(from), x2: xScale(to), y1: 0, y2: 0 }), _jsx("g", { transform: `translate(${(width - 2.5 * margin) / 2}, ${margin / 2})`, children: _jsx("text", { fill: "currentColor", dy: ".71em", y: 5, className: "text-sm", children: "Time" }) })] }), _jsx("g", { className: "lines fill-transparent", children: series.map((s, i) => (_jsx("g", { className: "line", children: _jsx(MetricsSeries, { data: s, line: l, color: color(i.toString()), strokeWidth: hovering && highlighted != null && i === highlighted.seriesIndex
269
+ transform: `translate(${xScale(d)}, 0)`, children: [_jsx("line", { y2: 6, className: "stroke-gray-300 dark:stroke-gray-500" }), _jsx("text", { fill: "currentColor", dy: ".71em", y: 9, children: formatDate(d, formatForTimespan(from, to), timezone) })] }, `tick-${i}`), _jsx("g", { children: _jsx("line", { className: "stroke-gray-300 dark:stroke-gray-500", x1: xScale(d), x2: xScale(d), y1: 0, y2: -height + margin }) }, `grid-${i}`)] }, `${i.toString()}-${d.toString()}`))), _jsx("line", { className: "stroke-gray-300 dark:stroke-gray-500", x1: xScale(from), x2: xScale(to), y1: 0, y2: 0 }), _jsx("g", { transform: `translate(${(width - 2.5 * margin) / 2}, ${margin / 2})`, children: _jsx("text", { fill: "currentColor", dy: ".71em", y: 5, className: "text-sm", children: "Time" }) })] }), _jsx("g", { className: "lines fill-transparent", children: series.map((s, i) => (_jsx("g", { className: "line", children: _jsx(MetricsSeries, { data: s, line: l, color: color(i.toString()), strokeWidth: hovering && highlighted != null && i === highlighted.seriesIndex
269
270
  ? lineStrokeHover
270
271
  : lineStroke, xScale: xScale, yScale: yScale }) }, i))) }), hovering && highlighted != null && (_jsx("g", { className: "circle-group", ref: metricPointRef, style: { fill: color(highlighted.seriesIndex.toString()) }, children: _jsx(MetricsCircle, { cx: highlighted.x, cy: highlighted.y }) })), selected != null && (_jsx("g", { className: "circle-group", style: selected?.seriesIndex != null
271
272
  ? { fill: color(selected.seriesIndex.toString()) }
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  declare const GroupByDropdown: ({ groupBy, toggleGroupBy, }: {
3
2
  groupBy: string[];
4
3
  toggleGroupBy: (key: string) => void;
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  declare const RuntimeFilterDropdown: ({ showRuntimeRuby, toggleShowRuntimeRuby, showRuntimePython, toggleShowRuntimePython, showInterpretedOnly, toggleShowInterpretedOnly, }: {
3
2
  showRuntimeRuby: boolean;
4
3
  toggleShowRuntimeRuby: () => void;
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  declare const SortBySelect: ({ sortBy, setSortBy, compareMode, }: {
3
2
  sortBy: string;
4
3
  setSortBy: (key: string) => void;
@@ -5,8 +5,7 @@ export interface ProfileSource {
5
5
  QueryRequest: () => QueryRequest;
6
6
  ProfileType: () => ProfileType;
7
7
  DiffSelection: () => ProfileDiffSelection;
8
- Describe: () => JSX.Element;
9
- toString: () => string;
8
+ toString: (timezone?: string) => string;
10
9
  }
11
10
  export interface ProfileSelection {
12
11
  ProfileName: () => string;
@@ -16,7 +15,6 @@ export interface ProfileSelection {
16
15
  ProfileSource: () => ProfileSource;
17
16
  Type: () => string;
18
17
  }
19
- export declare const timeFormat = "yyyy-MM-dd HH:mm:ss '(UTC)'";
20
18
  export declare function ParamsString(params: {
21
19
  [key: string]: string;
22
20
  }): string;
@@ -60,7 +58,6 @@ export declare class MergedProfileSource implements ProfileSource {
60
58
  DiffSelection(): ProfileDiffSelection;
61
59
  QueryRequest(): QueryRequest;
62
60
  ProfileType(): ProfileType;
63
- Describe(): JSX.Element;
64
61
  stringMatchers(): string[];
65
- toString(): string;
62
+ toString(timezone?: string): string;
66
63
  }
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  // Copyright 2022 The Parca Authors
3
3
  // Licensed under the Apache License, Version 2.0 (the "License");
4
4
  // you may not use this file except in compliance with the License.
@@ -14,7 +14,12 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
14
14
  import { ProfileDiffSelection_Mode, QueryRequest_Mode, QueryRequest_ReportType, Timestamp, } from '@parca/client';
15
15
  import { ProfileType, Query } from '@parca/parser';
16
16
  import { formatDate } from '@parca/utilities';
17
- export const timeFormat = "yyyy-MM-dd HH:mm:ss '(UTC)'";
17
+ const timeFormat = (timezone) => {
18
+ if (timezone !== undefined) {
19
+ return 'yyyy-MM-dd HH:mm:ss';
20
+ }
21
+ return "yyyy-MM-dd HH:mm:ss '(UTC)'";
22
+ };
18
23
  export function ParamsString(params) {
19
24
  return Object.keys(params)
20
25
  .map(function (key) {
@@ -153,15 +158,12 @@ export class MergedProfileSource {
153
158
  ProfileType() {
154
159
  return ProfileType.fromString(Query.parse(this.query.toString()).profileName());
155
160
  }
156
- Describe() {
157
- return (_jsxs("a", { children: ["Merge of \"", this.query.toString(), "\" from ", formatDate(this.mergeFrom, timeFormat), ' ', "to ", formatDate(this.mergeTo, timeFormat)] }));
158
- }
159
161
  stringMatchers() {
160
162
  return this.query.matchers
161
163
  .filter((m) => m.key !== '__name__')
162
164
  .map((m) => `${m.key}=${m.value}`);
163
165
  }
164
- toString() {
165
- return `merged profiles of query "${this.query.toString()}" from ${formatDate(this.mergeFrom, timeFormat)} to ${formatDate(this.mergeTo, timeFormat)}`;
166
+ toString(timezone) {
167
+ return `merged profiles of query "${this.query.toString()}" from ${formatDate(this.mergeFrom, timeFormat(timezone), timezone)} to ${formatDate(this.mergeTo, timeFormat(timezone), timezone)}`;
166
168
  }
167
169
  }
@@ -9,7 +9,7 @@ export declare const defaultValue: Props;
9
9
  declare const ProfileViewContext: import("react").Context<Props>;
10
10
  export declare const ProfileViewContextProvider: ({ children, value, }: {
11
11
  children: ReactNode;
12
- value?: Props | undefined;
12
+ value?: Props;
13
13
  }) => JSX.Element;
14
14
  export declare const useProfileViewContext: () => Props;
15
15
  export default ProfileViewContext;
@@ -39,6 +39,7 @@ function arrayEquals(a, b) {
39
39
  a.every((val, index) => val === b[index]));
40
40
  }
41
41
  export const ProfileView = ({ total, filtered, flamegraphData, topTableData, callgraphData, sourceData, sampleUnit, profileSource, queryClient, navigateTo, onDownloadPProf, pprofDownloading, compare, }) => {
42
+ const { timezone } = useParcaContext();
42
43
  const { ref, dimensions } = useContainerDimensions();
43
44
  const [curPath, setCurPath] = useState([]);
44
45
  const [rawDashboardItems = ['icicle'], setDashboardItems] = useURLState({
@@ -144,7 +145,7 @@ export const ProfileView = ({ total, filtered, flamegraphData, topTableData, cal
144
145
  }
145
146
  };
146
147
  // TODO: this is just a placeholder, we need to replace with an actually informative and accurate title (cc @metalmatze)
147
- const profileSourceString = profileSource?.toString();
148
+ const profileSourceString = profileSource?.toString(timezone);
148
149
  const hasProfileSource = profileSource !== undefined && profileSourceString !== '';
149
150
  const headerParts = profileSourceString?.split('"') ?? [];
150
151
  const compareMode = compare === true ||
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  import { type VisibilityState } from '@tanstack/react-table';
3
2
  import { ColumnDef } from '.';
4
3
  declare const ColumnsVisibility: ({ columns, visibility, setVisibility, }: {
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@parca/profile",
3
- "version": "0.16.358",
3
+ "version": "0.16.360",
4
4
  "description": "Profile viewing libraries",
5
5
  "dependencies": {
6
6
  "@parca/client": "^0.16.107",
7
- "@parca/components": "^0.16.264",
7
+ "@parca/components": "^0.16.266",
8
8
  "@parca/dynamicsize": "^0.16.61",
9
- "@parca/hooks": "^0.0.46",
9
+ "@parca/hooks": "^0.0.47",
10
10
  "@parca/parser": "^0.16.69",
11
- "@parca/store": "^0.16.135",
12
- "@parca/utilities": "^0.0.63",
11
+ "@parca/store": "^0.16.136",
12
+ "@parca/utilities": "^0.0.64",
13
13
  "@tanstack/react-query": "^4.0.5",
14
14
  "@types/react-beautiful-dnd": "^13.1.8",
15
15
  "apache-arrow": "^12.0.0",
@@ -17,7 +17,7 @@
17
17
  "d3-scale": "^4.0.2",
18
18
  "d3-selection": "3.0.0",
19
19
  "framer-motion": "^10.18.0",
20
- "graphviz-wasm": "3.0.1",
20
+ "graphviz-wasm": "3.0.2",
21
21
  "react-beautiful-dnd": "^13.1.1",
22
22
  "react-contexify": "^6.0.0",
23
23
  "react-flame-graph": "^1.4.0",
@@ -50,5 +50,5 @@
50
50
  "access": "public",
51
51
  "registry": "https://registry.npmjs.org/"
52
52
  },
53
- "gitHead": "c780c04e3e57f47fe0abb3414496cd6b4331dbac"
53
+ "gitHead": "6430a8027b0edb84336feea5c774e409595f40a6"
54
54
  }
@@ -18,11 +18,10 @@ import type {VirtualElement} from '@popperjs/core';
18
18
  import {usePopper} from 'react-popper';
19
19
 
20
20
  import {Label} from '@parca/client';
21
- import {TextWithTooltip} from '@parca/components';
22
- import {formatDate, valueFormatter} from '@parca/utilities';
21
+ import {TextWithTooltip, useParcaContext} from '@parca/components';
22
+ import {formatDate, timePattern, valueFormatter} from '@parca/utilities';
23
23
 
24
24
  import {HighlightedSeries} from '../';
25
- import {timeFormat} from '../../';
26
25
 
27
26
  interface Props {
28
27
  x: number;
@@ -69,6 +68,8 @@ const MetricsTooltip = ({
69
68
  sampleUnit,
70
69
  delta,
71
70
  }: Props): JSX.Element => {
71
+ const {timezone} = useParcaContext();
72
+
72
73
  const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
73
74
 
74
75
  const {styles, attributes, ...popperProps} = usePopper(virtualElement, popperElement, {
@@ -154,7 +155,13 @@ const MetricsTooltip = ({
154
155
  )}
155
156
  <tr>
156
157
  <td className="w-1/4">At</td>
157
- <td className="w-3/4">{formatDate(highlighted.timestamp, timeFormat)}</td>
158
+ <td className="w-3/4">
159
+ {formatDate(
160
+ highlighted.timestamp,
161
+ timePattern(timezone as string),
162
+ timezone
163
+ )}
164
+ </td>
158
165
  </tr>
159
166
  </tbody>
160
167
  </table>
@@ -19,7 +19,7 @@ import throttle from 'lodash.throttle';
19
19
  import {useContextMenu} from 'react-contexify';
20
20
 
21
21
  import {Label, MetricsSample, MetricsSeries as MetricsSeriesPb} from '@parca/client';
22
- import {DateTimeRange} from '@parca/components';
22
+ import {DateTimeRange, useParcaContext} from '@parca/components';
23
23
  import {
24
24
  formatDate,
25
25
  formatForTimespan,
@@ -132,6 +132,7 @@ export const RawMetricsGraph = ({
132
132
  margin = 0,
133
133
  sampleUnit,
134
134
  }: Props): JSX.Element => {
135
+ const {timezone} = useParcaContext();
135
136
  const graph = useRef(null);
136
137
  const [dragging, setDragging] = useState(false);
137
138
  const [hovering, setHovering] = useState(false);
@@ -537,7 +538,7 @@ export const RawMetricsGraph = ({
537
538
  >
538
539
  <line y2={6} className="stroke-gray-300 dark:stroke-gray-500" />
539
540
  <text fill="currentColor" dy=".71em" y={9}>
540
- {formatDate(d, formatForTimespan(from, to))}
541
+ {formatDate(d, formatForTimespan(from, to), timezone)}
541
542
  </text>
542
543
  </g>
543
544
  <g key={`grid-${i}`}>
@@ -27,8 +27,7 @@ export interface ProfileSource {
27
27
  QueryRequest: () => QueryRequest;
28
28
  ProfileType: () => ProfileType;
29
29
  DiffSelection: () => ProfileDiffSelection;
30
- Describe: () => JSX.Element;
31
- toString: () => string;
30
+ toString: (timezone?: string) => string;
32
31
  }
33
32
 
34
33
  export interface ProfileSelection {
@@ -37,8 +36,13 @@ export interface ProfileSelection {
37
36
  ProfileSource: () => ProfileSource;
38
37
  Type: () => string;
39
38
  }
39
+ const timeFormat = (timezone?: string): string => {
40
+ if (timezone !== undefined) {
41
+ return 'yyyy-MM-dd HH:mm:ss';
42
+ }
40
43
 
41
- export const timeFormat = "yyyy-MM-dd HH:mm:ss '(UTC)'";
44
+ return "yyyy-MM-dd HH:mm:ss '(UTC)'";
45
+ };
42
46
 
43
47
  export function ParamsString(params: {[key: string]: string}): string {
44
48
  return Object.keys(params)
@@ -229,25 +233,17 @@ export class MergedProfileSource implements ProfileSource {
229
233
  return ProfileType.fromString(Query.parse(this.query.toString()).profileName());
230
234
  }
231
235
 
232
- Describe(): JSX.Element {
233
- return (
234
- <a>
235
- Merge of &quot;{this.query.toString()}&quot; from {formatDate(this.mergeFrom, timeFormat)}{' '}
236
- to {formatDate(this.mergeTo, timeFormat)}
237
- </a>
238
- );
239
- }
240
-
241
236
  stringMatchers(): string[] {
242
237
  return this.query.matchers
243
238
  .filter((m: Matcher) => m.key !== '__name__')
244
239
  .map((m: Matcher) => `${m.key}=${m.value}`);
245
240
  }
246
241
 
247
- toString(): string {
242
+ toString(timezone?: string): string {
248
243
  return `merged profiles of query "${this.query.toString()}" from ${formatDate(
249
244
  this.mergeFrom,
250
- timeFormat
251
- )} to ${formatDate(this.mergeTo, timeFormat)}`;
245
+ timeFormat(timezone),
246
+ timezone
247
+ )} to ${formatDate(this.mergeTo, timeFormat(timezone), timezone)}`;
252
248
  }
253
249
  }
@@ -132,6 +132,7 @@ export const ProfileView = ({
132
132
  pprofDownloading,
133
133
  compare,
134
134
  }: ProfileViewProps): JSX.Element => {
135
+ const {timezone} = useParcaContext();
135
136
  const {ref, dimensions} = useContainerDimensions();
136
137
  const [curPath, setCurPath] = useState<string[]>([]);
137
138
  const [rawDashboardItems = ['icicle'], setDashboardItems] = useURLState({
@@ -325,7 +326,7 @@ export const ProfileView = ({
325
326
  };
326
327
 
327
328
  // TODO: this is just a placeholder, we need to replace with an actually informative and accurate title (cc @metalmatze)
328
- const profileSourceString = profileSource?.toString();
329
+ const profileSourceString = profileSource?.toString(timezone);
329
330
  const hasProfileSource = profileSource !== undefined && profileSourceString !== '';
330
331
  const headerParts = profileSourceString?.split('"') ?? [];
331
332