ui: Move various components into public/lib
- Move slice_details, thread_details, duration.ts, and timestamp.ts
into public/lib as they are used by plugins.
- Make timestamp/duration format settings available on App/Trace.
- Refactored Timestamp/DurationWidget widgets a little.
Note: These sorts of settings (timestamp/duration format) are begging
for a generic settings infrastructure, similar to flags but with more
more options, but this is out of scope for this CL.
Change-Id: If507c083bff017e17948bbde36418eb7332f896c
diff --git a/python/tools/check_imports.py b/python/tools/check_imports.py
index 6f66efe..07b6843 100755
--- a/python/tools/check_imports.py
+++ b/python/tools/check_imports.py
@@ -106,7 +106,6 @@
('/*plugins/*', [
'/frontend/slice_layout',
'/frontend/slice_args',
- '/frontend/slice_details',
'/frontend/checkerboard',
'/common/track_helper',
'/common/track_data',
@@ -134,6 +133,12 @@
'/frontend/tracks/generic_slice_details_tab',
]),
+ # TODO(stevegolton): It's too much effort to change all the callsites of
+ # Timestamp and Duration widgets in order to inject trace into them.
+ ('/public/lib/widgets/*', [
+ '/core/app_impl',
+ ]),
+
# TODO(primiano): controller-related tech debt.
('/frontend/index', '/controller/*'),
('/controller/*', ['/base/*', '/core/*', '/common/*']),
diff --git a/ui/src/core/timeline.ts b/ui/src/core/timeline.ts
index bc8a613..0efc660 100644
--- a/ui/src/core/timeline.ts
+++ b/ui/src/core/timeline.ts
@@ -12,14 +12,19 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {assertTrue} from '../base/logging';
+import {assertTrue, assertUnreachable} from '../base/logging';
import {Time, time, TimeSpan} from '../base/time';
import {HighPrecisionTimeSpan} from '../base/high_precision_time_span';
import {Area} from '../public/selection';
import {raf} from './raf_scheduler';
import {HighPrecisionTime} from '../base/high_precision_time';
-import {Timeline} from '../public/timeline';
-import {timestampFormat, TimestampFormat} from './timestamp_format';
+import {DurationPrecision, Timeline, TimestampFormat} from '../public/timeline';
+import {
+ durationPrecision,
+ setDurationPrecision,
+ setTimestampFormat,
+ timestampFormat,
+} from './timestamp_format';
import {TraceInfo} from '../public/trace_info';
const MIN_DURATION = 10;
@@ -183,7 +188,7 @@
switch (fmt) {
case TimestampFormat.Timecode:
case TimestampFormat.Seconds:
- case TimestampFormat.Milliseoncds:
+ case TimestampFormat.Milliseconds:
case TimestampFormat.Microseconds:
return this.traceInfo.start;
case TimestampFormat.TraceNs:
@@ -194,8 +199,7 @@
case TimestampFormat.TraceTz:
return this.traceInfo.traceTzOffset;
default:
- const x: never = fmt;
- throw new Error(`Unsupported format ${x}`);
+ assertUnreachable(fmt);
}
}
@@ -203,4 +207,20 @@
toDomainTime(ts: time): time {
return Time.sub(ts, this.timestampOffset());
}
+
+ get timestampFormat() {
+ return timestampFormat();
+ }
+
+ set timestampFormat(format: TimestampFormat) {
+ setTimestampFormat(format);
+ }
+
+ get durationPrecision() {
+ return durationPrecision();
+ }
+
+ set durationPrecision(precision: DurationPrecision) {
+ setDurationPrecision(precision);
+ }
}
diff --git a/ui/src/core/timestamp_format.ts b/ui/src/core/timestamp_format.ts
index f4d7d80..25a8ba1 100644
--- a/ui/src/core/timestamp_format.ts
+++ b/ui/src/core/timestamp_format.ts
@@ -13,17 +13,7 @@
// limitations under the License.
import {isEnumValue} from '../base/object_utils';
-
-export enum TimestampFormat {
- Timecode = 'timecode',
- TraceNs = 'traceNs',
- TraceNsLocale = 'traceNsLocale',
- Seconds = 'seconds',
- Milliseoncds = 'milliseconds',
- Microseconds = 'microseconds',
- UTC = 'utc',
- TraceTz = 'traceTz',
-}
+import {DurationPrecision, TimestampFormat} from '../public/timeline';
let timestampFormatCached: TimestampFormat | undefined;
@@ -49,11 +39,6 @@
localStorage.setItem(TIMESTAMP_FORMAT_KEY, format);
}
-export enum DurationPrecision {
- Full = 'full',
- HumanReadable = 'human_readable',
-}
-
let durationFormatCached: DurationPrecision | undefined;
const DURATION_FORMAT_KEY = 'durationFormat';
diff --git a/ui/src/frontend/aggregation_panel.ts b/ui/src/frontend/aggregation_panel.ts
index 3d441ba..2e1d00f 100644
--- a/ui/src/frontend/aggregation_panel.ts
+++ b/ui/src/frontend/aggregation_panel.ts
@@ -20,7 +20,7 @@
isEmptyData,
} from '../public/aggregation';
import {colorForState} from '../public/lib/colorizer';
-import {DurationWidget} from './widgets/duration';
+import {DurationWidget} from '../public/lib/widgets/duration';
import {EmptyState} from '../widgets/empty_state';
import {Anchor} from '../widgets/anchor';
import {Icons} from '../base/semantic_icons';
diff --git a/ui/src/frontend/named_slice_track.ts b/ui/src/frontend/named_slice_track.ts
index 07043e9..6d79af2 100644
--- a/ui/src/frontend/named_slice_track.ts
+++ b/ui/src/frontend/named_slice_track.ts
@@ -26,10 +26,10 @@
SLICE_FLAGS_INSTANT,
} from './base_slice_track';
import {ThreadSliceDetailsPanel} from './thread_slice_details_tab';
-import {renderDuration} from './widgets/duration';
import {TraceImpl} from '../core/trace_impl';
import {assertIsInstance} from '../base/logging';
import {SourceDataset, Dataset} from '../trace_processor/dataset';
+import {formatDuration} from '../public/lib/time_utils';
import {Trace} from '../public/trace';
export const NAMED_ROW = {
@@ -66,7 +66,7 @@
} else if (flags & SLICE_FLAGS_INSTANT) {
duration = 'Instant';
} else {
- duration = renderDuration(dur);
+ duration = formatDuration(this.trace, dur);
}
args.tooltip = [`${title} - [${duration}]`];
}
diff --git a/ui/src/frontend/notes_panel.ts b/ui/src/frontend/notes_panel.ts
index ac5b015..c4d76b7 100644
--- a/ui/src/frontend/notes_panel.ts
+++ b/ui/src/frontend/notes_panel.ts
@@ -23,7 +23,7 @@
import {getMaxMajorTicks, generateTicks, TickType} from './gridline_helper';
import {Size2D} from '../base/geom';
import {Panel} from './panel_container';
-import {Timestamp} from './widgets/timestamp';
+import {Timestamp} from '../public/lib/widgets/timestamp';
import {assertUnreachable} from '../base/logging';
import {DetailsPanel} from '../public/details_panel';
import {TimeScale} from '../base/time_scale';
diff --git a/ui/src/frontend/overview_timeline_panel.ts b/ui/src/frontend/overview_timeline_panel.ts
index 8eb41ec..0d04ce2 100644
--- a/ui/src/frontend/overview_timeline_panel.ts
+++ b/ui/src/frontend/overview_timeline_panel.ts
@@ -15,7 +15,7 @@
import m from 'mithril';
import {Duration, Time, TimeSpan, duration, time} from '../base/time';
import {colorForCpu} from '../public/lib/colorizer';
-import {timestampFormat, TimestampFormat} from '../core/timestamp_format';
+import {timestampFormat} from '../core/timestamp_format';
import {
OVERVIEW_TIMELINE_NON_VISIBLE_COLOR,
TRACK_SHELL_WIDTH,
@@ -39,6 +39,8 @@
import {LONG, NUM} from '../trace_processor/query_result';
import {raf} from '../core/raf_scheduler';
import {getOrCreate} from '../base/utils';
+import {assertUnreachable} from '../base/logging';
+import {TimestampFormat} from '../public/timeline';
const tracesData = new WeakMap<TraceImpl, OverviewDataLoader>();
@@ -299,15 +301,14 @@
case TimestampFormat.Seconds:
ctx.fillText(Time.formatSeconds(time), x, y, minWidth);
break;
- case TimestampFormat.Milliseoncds:
+ case TimestampFormat.Milliseconds:
ctx.fillText(Time.formatMilliseconds(time), x, y, minWidth);
break;
case TimestampFormat.Microseconds:
ctx.fillText(Time.formatMicroseconds(time), x, y, minWidth);
break;
default:
- const z: never = fmt;
- throw new Error(`Invalid timestamp ${z}`);
+ assertUnreachable(fmt);
}
}
diff --git a/ui/src/frontend/pivot_table.ts b/ui/src/frontend/pivot_table.ts
index 925cfed..734d2a0 100644
--- a/ui/src/frontend/pivot_table.ts
+++ b/ui/src/frontend/pivot_table.ts
@@ -36,7 +36,7 @@
} from '../core/pivot_table_query_generator';
import {ReorderableCell, ReorderableCellGroup} from './reorderable_cells';
import {AttributeModalHolder} from './tables/attribute_modal_holder';
-import {DurationWidget} from './widgets/duration';
+import {DurationWidget} from '../public/lib/widgets/duration';
import {getSqlTableDescription} from './widgets/sql/table/sql_table_registry';
import {assertExists, assertFalse} from '../base/logging';
import {Filter, SqlColumn} from './widgets/sql/table/column';
diff --git a/ui/src/frontend/thread_slice_details_tab.ts b/ui/src/frontend/thread_slice_details_tab.ts
index b549971..b872a6d 100644
--- a/ui/src/frontend/thread_slice_details_tab.ts
+++ b/ui/src/frontend/thread_slice_details_tab.ts
@@ -25,14 +25,14 @@
import {Tree} from '../widgets/tree';
import {Flow, FlowPoint} from '../core/flow_types';
import {hasArgs, renderArguments} from './slice_args';
-import {renderDetails} from './slice_details';
+import {renderDetails} from '../public/lib/details/slice_details';
import {getSlice, SliceDetails} from '../trace_processor/sql_utils/slice';
import {
BreakdownByThreadState,
breakDownIntervalByThreadState,
-} from './sql/thread_state';
+} from '../public/lib/details/thread_state';
import {asSliceSqlId} from '../trace_processor/sql_utils/core_types';
-import {DurationWidget} from './widgets/duration';
+import {DurationWidget} from '../public/lib/widgets/duration';
import {SliceRef} from './widgets/slice';
import {BasicTable} from '../widgets/basic_table';
import {getSqlTableDescription} from './widgets/sql/table/sql_table_registry';
diff --git a/ui/src/frontend/time_axis_panel.ts b/ui/src/frontend/time_axis_panel.ts
index 143e402..db7cf9c 100644
--- a/ui/src/frontend/time_axis_panel.ts
+++ b/ui/src/frontend/time_axis_panel.ts
@@ -14,7 +14,7 @@
import m from 'mithril';
import {Time, time, toISODateOnly} from '../base/time';
-import {TimestampFormat, timestampFormat} from '../core/timestamp_format';
+import {timestampFormat} from '../core/timestamp_format';
import {TRACK_SHELL_WIDTH} from './css_constants';
import {
getMaxMajorTicks,
@@ -27,6 +27,8 @@
import {TimeScale} from '../base/time_scale';
import {canvasClip} from '../base/canvas_utils';
import {Trace} from '../public/trace';
+import {assertUnreachable} from '../base/logging';
+import {TimestampFormat} from '../public/timeline';
export class TimeAxisPanel implements Panel {
readonly kind = 'panel';
@@ -58,11 +60,14 @@
private renderOffsetTimestamp(ctx: CanvasRenderingContext2D): void {
const offset = this.trace.timeline.timestampOffset();
- switch (timestampFormat()) {
+ const timestampFormat = this.trace.timeline.timestampFormat;
+ switch (timestampFormat) {
case TimestampFormat.TraceNs:
case TimestampFormat.TraceNsLocale:
break;
case TimestampFormat.Seconds:
+ case TimestampFormat.Milliseconds:
+ case TimestampFormat.Microseconds:
case TimestampFormat.Timecode:
const width = renderTimestamp(ctx, offset, 6, 10, MIN_PX_PER_STEP);
ctx.fillText('+', 6 + width + 2, 10, 6);
@@ -83,6 +88,8 @@
const dateTzStr = toISODateOnly(offsetTzDate);
ctx.fillText(dateTzStr, 6, 10);
break;
+ default:
+ assertUnreachable(timestampFormat);
}
}
@@ -130,7 +137,7 @@
return renderRawTimestamp(ctx, time.toLocaleString(), x, y, minWidth);
case TimestampFormat.Seconds:
return renderRawTimestamp(ctx, Time.formatSeconds(time), x, y, minWidth);
- case TimestampFormat.Milliseoncds:
+ case TimestampFormat.Milliseconds:
return renderRawTimestamp(
ctx,
Time.formatMilliseconds(time),
diff --git a/ui/src/frontend/time_selection_panel.ts b/ui/src/frontend/time_selection_panel.ts
index 5621ff4..599ed5b 100644
--- a/ui/src/frontend/time_selection_panel.ts
+++ b/ui/src/frontend/time_selection_panel.ts
@@ -14,7 +14,7 @@
import m from 'mithril';
import {time, Time} from '../base/time';
-import {timestampFormat, TimestampFormat} from '../core/timestamp_format';
+import {timestampFormat} from '../core/timestamp_format';
import {
BACKGROUND_COLOR,
FOREGROUND_COLOR,
@@ -23,10 +23,12 @@
import {getMaxMajorTicks, generateTicks, TickType} from './gridline_helper';
import {Size2D} from '../base/geom';
import {Panel} from './panel_container';
-import {renderDuration} from './widgets/duration';
import {canvasClip} from '../base/canvas_utils';
import {TimeScale} from '../base/time_scale';
import {TraceImpl} from '../core/trace_impl';
+import {formatDuration} from '../public/lib/time_utils';
+import {TimestampFormat} from '../public/timeline';
+import {assertUnreachable} from '../base/logging';
export interface BBox {
x: number;
@@ -235,7 +237,7 @@
) {
const xLeft = timescale.timeToPx(start);
const xRight = timescale.timeToPx(end);
- const label = renderDuration(end - start);
+ const label = formatDuration(this.trace, end - start);
drawHBar(
ctx,
{
@@ -273,12 +275,11 @@
return time.toLocaleString();
case TimestampFormat.Seconds:
return Time.formatSeconds(time);
- case TimestampFormat.Milliseoncds:
+ case TimestampFormat.Milliseconds:
return Time.formatMilliseconds(time);
case TimestampFormat.Microseconds:
return Time.formatMicroseconds(time);
default:
- const z: never = fmt;
- throw new Error(`Invalid timestamp ${z}`);
+ assertUnreachable(fmt);
}
}
diff --git a/ui/src/frontend/ui_main.ts b/ui/src/frontend/ui_main.ts
index b8d8dda..b28a41a 100644
--- a/ui/src/frontend/ui_main.ts
+++ b/ui/src/frontend/ui_main.ts
@@ -19,10 +19,8 @@
import {assertExists, assertUnreachable} from '../base/logging';
import {undoCommonChatAppReplacements} from '../base/string_utils';
import {
- DurationPrecision,
setDurationPrecision,
setTimestampFormat,
- TimestampFormat,
} from '../core/timestamp_format';
import {raf} from '../core/raf_scheduler';
import {Command} from '../public/command';
@@ -46,6 +44,7 @@
import {NotesEditorTab} from './notes_panel';
import {NotesListEditor} from './notes_list_editor';
import {getTimeSpanOfSelectionOrVisibleWindow} from '../public/utils';
+import {DurationPrecision, TimestampFormat} from '../public/timeline';
const OMNIBOX_INPUT_REF = 'omnibox';
@@ -132,7 +131,7 @@
displayName: 'Realtime (Trace TZ)',
},
{key: TimestampFormat.Seconds, displayName: 'Seconds'},
- {key: TimestampFormat.Milliseoncds, displayName: 'Milliseconds'},
+ {key: TimestampFormat.Milliseconds, displayName: 'Milliseconds'},
{key: TimestampFormat.Microseconds, displayName: 'Microseconds'},
{key: TimestampFormat.TraceNs, displayName: 'Trace nanoseconds'},
{
diff --git a/ui/src/frontend/widgets/duration.ts b/ui/src/frontend/widgets/duration.ts
deleted file mode 100644
index a623406..0000000
--- a/ui/src/frontend/widgets/duration.ts
+++ /dev/null
@@ -1,145 +0,0 @@
-// Copyright (C) 2023 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-import m from 'mithril';
-import {copyToClipboard} from '../../base/clipboard';
-import {Icons} from '../../base/semantic_icons';
-import {Duration, duration} from '../../base/time';
-import {
- DurationPrecision,
- durationPrecision,
- setDurationPrecision,
- TimestampFormat,
- timestampFormat,
-} from '../../core/timestamp_format';
-import {raf} from '../../core/raf_scheduler';
-import {Anchor} from '../../widgets/anchor';
-import {MenuDivider, MenuItem, PopupMenu2} from '../../widgets/menu';
-import {menuItemForFormat} from './timestamp';
-
-interface DurationWidgetAttrs {
- dur: duration;
- extraMenuItems?: m.Child[];
-}
-
-export class DurationWidget implements m.ClassComponent<DurationWidgetAttrs> {
- view({attrs}: m.Vnode<DurationWidgetAttrs>) {
- const {dur} = attrs;
- if (dur === -1n) {
- return '(Did not end)';
- }
- return m(
- PopupMenu2,
- {
- trigger: m(Anchor, renderDuration(dur)),
- },
- m(MenuItem, {
- icon: Icons.Copy,
- label: `Copy raw value`,
- onclick: () => {
- copyToClipboard(dur.toString());
- },
- }),
- m(
- MenuItem,
- {
- label: 'Set time format',
- },
- menuItemForFormat(TimestampFormat.Timecode, 'Timecode'),
- menuItemForFormat(TimestampFormat.UTC, 'Realtime (UTC)'),
- menuItemForFormat(TimestampFormat.TraceTz, 'Realtime (Trace TZ)'),
- menuItemForFormat(TimestampFormat.Seconds, 'Seconds'),
- menuItemForFormat(TimestampFormat.Milliseoncds, 'Milliseconds'),
- menuItemForFormat(TimestampFormat.Microseconds, 'Microseconds'),
- menuItemForFormat(TimestampFormat.TraceNs, 'Raw'),
- menuItemForFormat(
- TimestampFormat.TraceNsLocale,
- 'Raw (with locale-specific formatting)',
- ),
- ),
- m(
- MenuItem,
- {
- label: 'Duration precision',
- disabled: !durationPrecisionHasEffect(),
- title: 'Not configurable with current time format',
- },
- menuItemForPrecision(DurationPrecision.Full, 'Full'),
- menuItemForPrecision(DurationPrecision.HumanReadable, 'Human readable'),
- ),
- attrs.extraMenuItems ? [m(MenuDivider), attrs.extraMenuItems] : null,
- );
- }
-}
-
-function menuItemForPrecision(
- value: DurationPrecision,
- label: string,
-): m.Children {
- return m(MenuItem, {
- label,
- active: value === durationPrecision(),
- onclick: () => {
- setDurationPrecision(value);
- raf.scheduleFullRedraw();
- },
- });
-}
-
-function durationPrecisionHasEffect(): boolean {
- switch (timestampFormat()) {
- case TimestampFormat.Timecode:
- case TimestampFormat.UTC:
- case TimestampFormat.TraceTz:
- return true;
- default:
- return false;
- }
-}
-
-export function renderDuration(dur: duration): string {
- const fmt = timestampFormat();
- switch (fmt) {
- case TimestampFormat.UTC:
- case TimestampFormat.TraceTz:
- case TimestampFormat.Timecode:
- return renderFormattedDuration(dur);
- case TimestampFormat.TraceNs:
- return dur.toString();
- case TimestampFormat.TraceNsLocale:
- return dur.toLocaleString();
- case TimestampFormat.Seconds:
- return Duration.formatSeconds(dur);
- case TimestampFormat.Milliseoncds:
- return Duration.formatMilliseconds(dur);
- case TimestampFormat.Microseconds:
- return Duration.formatMicroseconds(dur);
- default:
- const x: never = fmt;
- throw new Error(`Invalid format ${x}`);
- }
-}
-
-function renderFormattedDuration(dur: duration): string {
- const fmt = durationPrecision();
- switch (fmt) {
- case DurationPrecision.HumanReadable:
- return Duration.humanise(dur);
- case DurationPrecision.Full:
- return Duration.format(dur);
- default:
- const x: never = fmt;
- throw new Error(`Invalid format ${x}`);
- }
-}
diff --git a/ui/src/frontend/widgets/sql/details/details.ts b/ui/src/frontend/widgets/sql/details/details.ts
index 069b723..b05ed41 100644
--- a/ui/src/frontend/widgets/sql/details/details.ts
+++ b/ui/src/frontend/widgets/sql/details/details.ts
@@ -30,8 +30,8 @@
import {SqlRef} from '../../../../widgets/sql_ref';
import {Tree, TreeNode} from '../../../../widgets/tree';
import {hasArgs, renderArguments} from '../../../slice_args';
-import {DurationWidget} from '../../../widgets/duration';
-import {Timestamp as TimestampWidget} from '../../../widgets/timestamp';
+import {DurationWidget} from '../../../../public/lib/widgets/duration';
+import {Timestamp as TimestampWidget} from '../../../../public/lib/widgets/timestamp';
import {sqlIdRegistry} from './sql_ref_renderer_registry';
import {Trace} from '../../../../public/trace';
diff --git a/ui/src/frontend/widgets/sql/table/well_known_columns.ts b/ui/src/frontend/widgets/sql/table/well_known_columns.ts
index 7c1eb98..8ed593e 100644
--- a/ui/src/frontend/widgets/sql/table/well_known_columns.ts
+++ b/ui/src/frontend/widgets/sql/table/well_known_columns.ts
@@ -29,13 +29,13 @@
import {Anchor} from '../../../../widgets/anchor';
import {renderError} from '../../../../widgets/error';
import {MenuDivider, MenuItem, PopupMenu2} from '../../../../widgets/menu';
-import {DurationWidget} from '../../duration';
+import {DurationWidget} from '../../../../public/lib/widgets/duration';
import {processRefMenuItems, showProcessDetailsMenuItem} from '../../process';
import {SchedRef} from '../../sched';
import {SliceRef} from '../../slice';
import {showThreadDetailsMenuItem, threadRefMenuItems} from '../../thread';
import {ThreadStateRef} from '../../thread_state';
-import {Timestamp} from '../../timestamp';
+import {Timestamp} from '../../../../public/lib/widgets/timestamp';
import {
AggregationConfig,
SourceTable,
diff --git a/ui/src/frontend/widgets/timestamp.ts b/ui/src/frontend/widgets/timestamp.ts
deleted file mode 100644
index 7fd44f1..0000000
--- a/ui/src/frontend/widgets/timestamp.ts
+++ /dev/null
@@ -1,145 +0,0 @@
-// Copyright (C) 2023 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-import m from 'mithril';
-import {copyToClipboard} from '../../base/clipboard';
-import {Icons} from '../../base/semantic_icons';
-import {time, Time} from '../../base/time';
-import {
- setTimestampFormat,
- TimestampFormat,
- timestampFormat,
-} from '../../core/timestamp_format';
-import {raf} from '../../core/raf_scheduler';
-import {Anchor} from '../../widgets/anchor';
-import {MenuDivider, MenuItem, PopupMenu2} from '../../widgets/menu';
-import {Trace} from '../../public/trace';
-import {AppImpl} from '../../core/app_impl';
-import {assertExists} from '../../base/logging';
-
-// import {MenuItem, PopupMenu2} from './menu';
-
-interface TimestampAttrs {
- // The timestamp to print, this should be the absolute, raw timestamp as
- // found in trace processor.
- ts: time;
- // Custom text value to show instead of the default HH:MM:SS.mmm uuu nnn
- // formatting.
- display?: m.Children;
- extraMenuItems?: m.Child[];
-}
-
-export class Timestamp implements m.ClassComponent<TimestampAttrs> {
- private readonly trace: Trace;
-
- constructor() {
- // TODO(primiano): the Trace object should be injected into the attrs, but
- // there are too many users of this class and doing so requires a larger
- // refactoring CL. Either that or we should find a different way to plumb
- // the hoverCursorTimestamp.
- this.trace = assertExists(AppImpl.instance.trace);
- }
-
- view({attrs}: m.Vnode<TimestampAttrs>) {
- const {ts} = attrs;
- const timeline = this.trace.timeline;
- return m(
- PopupMenu2,
- {
- trigger: m(
- Anchor,
- {
- onmouseover: () => (timeline.hoverCursorTimestamp = ts),
- onmouseout: () => (timeline.hoverCursorTimestamp = undefined),
- },
- attrs.display ?? renderTimestamp(timeline.toDomainTime(ts)),
- ),
- },
- m(MenuItem, {
- icon: Icons.Copy,
- label: `Copy raw value`,
- onclick: () => {
- copyToClipboard(ts.toString());
- },
- }),
- m(
- MenuItem,
- {
- label: 'Time format',
- },
- menuItemForFormat(TimestampFormat.Timecode, 'Timecode'),
- menuItemForFormat(TimestampFormat.UTC, 'Realtime (UTC)'),
- menuItemForFormat(TimestampFormat.TraceTz, 'Realtime (Trace TZ)'),
- menuItemForFormat(TimestampFormat.Seconds, 'Seconds'),
- menuItemForFormat(TimestampFormat.Milliseoncds, 'Milliseconds'),
- menuItemForFormat(TimestampFormat.Microseconds, 'Microseconds'),
- menuItemForFormat(TimestampFormat.TraceNs, 'Raw'),
- menuItemForFormat(
- TimestampFormat.TraceNsLocale,
- 'Raw (with locale-specific formatting)',
- ),
- ),
- attrs.extraMenuItems ? [m(MenuDivider), attrs.extraMenuItems] : null,
- );
- }
-}
-
-export function menuItemForFormat(
- value: TimestampFormat,
- label: string,
-): m.Children {
- return m(MenuItem, {
- label,
- active: value === timestampFormat(),
- onclick: () => {
- setTimestampFormat(value);
- raf.scheduleFullRedraw();
- },
- });
-}
-
-function renderTimestamp(domainTime: time): m.Children {
- const fmt = timestampFormat();
- switch (fmt) {
- case TimestampFormat.UTC:
- case TimestampFormat.TraceTz:
- case TimestampFormat.Timecode:
- return renderTimecode(domainTime);
- case TimestampFormat.TraceNs:
- return domainTime.toString();
- case TimestampFormat.TraceNsLocale:
- return domainTime.toLocaleString();
- case TimestampFormat.Seconds:
- return Time.formatSeconds(domainTime);
- case TimestampFormat.Milliseoncds:
- return Time.formatMilliseconds(domainTime);
- case TimestampFormat.Microseconds:
- return Time.formatMicroseconds(domainTime);
- default:
- const x: never = fmt;
- throw new Error(`Invalid timestamp ${x}`);
- }
-}
-
-export function renderTimecode(time: time): m.Children {
- const {dhhmmss, millis, micros, nanos} = Time.toTimecode(time);
- return m(
- 'span.pf-timecode',
- m('span.pf-timecode-hms', dhhmmss),
- '.',
- m('span.pf-timecode-millis', millis),
- m('span.pf-timecode-micros', micros),
- m('span.pf-timecode-nanos', nanos),
- );
-}
diff --git a/ui/src/plugins/dev.perfetto.AndroidLog/logs_panel.ts b/ui/src/plugins/dev.perfetto.AndroidLog/logs_panel.ts
index 48714ba..5c79fdd 100644
--- a/ui/src/plugins/dev.perfetto.AndroidLog/logs_panel.ts
+++ b/ui/src/plugins/dev.perfetto.AndroidLog/logs_panel.ts
@@ -15,7 +15,7 @@
import m from 'mithril';
import {time, Time, TimeSpan} from '../../base/time';
import {DetailsShell} from '../../widgets/details_shell';
-import {Timestamp} from '../../frontend/widgets/timestamp';
+import {Timestamp} from '../../public/lib/widgets/timestamp';
import {Engine} from '../../trace_processor/engine';
import {LONG, NUM, NUM_NULL, STR} from '../../trace_processor/query_result';
import {Monitor} from '../../base/monitor';
diff --git a/ui/src/plugins/dev.perfetto.Counter/counter_details_panel.ts b/ui/src/plugins/dev.perfetto.Counter/counter_details_panel.ts
index e0ff568..48649e9 100644
--- a/ui/src/plugins/dev.perfetto.Counter/counter_details_panel.ts
+++ b/ui/src/plugins/dev.perfetto.Counter/counter_details_panel.ts
@@ -27,8 +27,8 @@
import {GridLayout} from '../../widgets/grid_layout';
import {Section} from '../../widgets/section';
import {Tree, TreeNode} from '../../widgets/tree';
-import {Timestamp} from '../../frontend/widgets/timestamp';
-import {DurationWidget} from '../../frontend/widgets/duration';
+import {Timestamp} from '../../public/lib/widgets/timestamp';
+import {DurationWidget} from '../../public/lib/widgets/duration';
import {TrackEventSelection} from '../../public/selection';
import {hasArgs, renderArguments} from '../../frontend/slice_args';
import {asArgSetId} from '../../trace_processor/sql_utils/core_types';
diff --git a/ui/src/plugins/dev.perfetto.CpuProfile/cpu_profile_details_panel.ts b/ui/src/plugins/dev.perfetto.CpuProfile/cpu_profile_details_panel.ts
index 060746e..91c6d2b 100644
--- a/ui/src/plugins/dev.perfetto.CpuProfile/cpu_profile_details_panel.ts
+++ b/ui/src/plugins/dev.perfetto.CpuProfile/cpu_profile_details_panel.ts
@@ -18,7 +18,7 @@
metricsFromTableOrSubquery,
QueryFlamegraph,
} from '../../public/lib/query_flamegraph';
-import {Timestamp} from '../../frontend/widgets/timestamp';
+import {Timestamp} from '../../public/lib/widgets/timestamp';
import {
TrackEventDetailsPanel,
TrackEventDetailsPanelSerializeArgs,
diff --git a/ui/src/plugins/dev.perfetto.CpuSlices/sched_details_tab.ts b/ui/src/plugins/dev.perfetto.CpuSlices/sched_details_tab.ts
index f4b3b93..1aaaf1a 100644
--- a/ui/src/plugins/dev.perfetto.CpuSlices/sched_details_tab.ts
+++ b/ui/src/plugins/dev.perfetto.CpuSlices/sched_details_tab.ts
@@ -19,8 +19,8 @@
import {Section} from '../../widgets/section';
import {SqlRef} from '../../widgets/sql_ref';
import {Tree, TreeNode} from '../../widgets/tree';
-import {DurationWidget} from '../../frontend/widgets/duration';
-import {Timestamp} from '../../frontend/widgets/timestamp';
+import {DurationWidget} from '../../public/lib/widgets/duration';
+import {Timestamp} from '../../public/lib/widgets/timestamp';
import {asSchedSqlId} from '../../trace_processor/sql_utils/core_types';
import {
getSched,
diff --git a/ui/src/plugins/dev.perfetto.Ftrace/ftrace_explorer.ts b/ui/src/plugins/dev.perfetto.Ftrace/ftrace_explorer.ts
index b4036e5..09e45f1 100644
--- a/ui/src/plugins/dev.perfetto.Ftrace/ftrace_explorer.ts
+++ b/ui/src/plugins/dev.perfetto.Ftrace/ftrace_explorer.ts
@@ -22,7 +22,7 @@
PopupMultiSelect,
} from '../../widgets/multiselect';
import {PopupPosition} from '../../widgets/popup';
-import {Timestamp} from '../../frontend/widgets/timestamp';
+import {Timestamp} from '../../public/lib/widgets/timestamp';
import {FtraceFilter, FtraceStat} from './common';
import {Engine} from '../../trace_processor/engine';
import {LONG, NUM, STR, STR_NULL} from '../../trace_processor/query_result';
diff --git a/ui/src/plugins/dev.perfetto.HeapProfile/heap_profile_details_panel.ts b/ui/src/plugins/dev.perfetto.HeapProfile/heap_profile_details_panel.ts
index c3e145e..5b45256 100644
--- a/ui/src/plugins/dev.perfetto.HeapProfile/heap_profile_details_panel.ts
+++ b/ui/src/plugins/dev.perfetto.HeapProfile/heap_profile_details_panel.ts
@@ -21,7 +21,7 @@
metricsFromTableOrSubquery,
} from '../../public/lib/query_flamegraph';
import {convertTraceToPprofAndDownload} from '../../frontend/trace_converter';
-import {Timestamp} from '../../frontend/widgets/timestamp';
+import {Timestamp} from '../../public/lib/widgets/timestamp';
import {
TrackEventDetailsPanel,
TrackEventDetailsPanelSerializeArgs,
diff --git a/ui/src/plugins/dev.perfetto.PerfSamplesProfile/perf_samples_profile_track.ts b/ui/src/plugins/dev.perfetto.PerfSamplesProfile/perf_samples_profile_track.ts
index 06f3244..5157569 100644
--- a/ui/src/plugins/dev.perfetto.PerfSamplesProfile/perf_samples_profile_track.ts
+++ b/ui/src/plugins/dev.perfetto.PerfSamplesProfile/perf_samples_profile_track.ts
@@ -32,7 +32,7 @@
QueryFlamegraph,
} from '../../public/lib/query_flamegraph';
import {DetailsShell} from '../../widgets/details_shell';
-import {Timestamp} from '../../frontend/widgets/timestamp';
+import {Timestamp} from '../../public/lib/widgets/timestamp';
import {time} from '../../base/time';
import {TrackEventDetailsPanel} from '../../public/details_panel';
import {Flamegraph, FLAMEGRAPH_STATE_SCHEMA} from '../../widgets/flamegraph';
diff --git a/ui/src/plugins/dev.perfetto.ThreadState/thread_state_details_panel.ts b/ui/src/plugins/dev.perfetto.ThreadState/thread_state_details_panel.ts
index 0a2287b7..1ba8c3b 100644
--- a/ui/src/plugins/dev.perfetto.ThreadState/thread_state_details_panel.ts
+++ b/ui/src/plugins/dev.perfetto.ThreadState/thread_state_details_panel.ts
@@ -27,8 +27,8 @@
getThreadStateFromConstraints,
ThreadState,
} from '../../trace_processor/sql_utils/thread_state';
-import {DurationWidget, renderDuration} from '../../frontend/widgets/duration';
-import {Timestamp} from '../../frontend/widgets/timestamp';
+import {DurationWidget} from '../../public/lib/widgets/duration';
+import {Timestamp} from '../../public/lib/widgets/timestamp';
import {getProcessName} from '../../trace_processor/sql_utils/process';
import {
getFullThreadName,
@@ -42,6 +42,7 @@
import {goToSchedSlice} from '../../frontend/widgets/sched';
import {TrackEventDetailsPanel} from '../../public/details_panel';
import {Trace} from '../../public/trace';
+import {formatDuration} from '../../public/lib/time_utils';
interface RelatedThreadStates {
prev?: ThreadState;
@@ -225,7 +226,7 @@
});
const nameForNextOrPrev = (threadState: ThreadState) =>
- `${threadState.state} for ${renderDuration(threadState.dur)}`;
+ `${threadState.state} for ${formatDuration(this.trace, threadState.dur)}`;
const renderWaker = (related: RelatedThreadStates) => {
// Could be absent if:
@@ -274,7 +275,7 @@
m(TreeNode, {
left: m(Timestamp, {
ts: state.ts,
- display: `+${renderDuration(state.ts - startTs)}`,
+ display: `+${formatDuration(this.trace, state.ts - startTs)}`,
}),
right: renderRef(state, getFullThreadName(state.thread)),
}),
diff --git a/ui/src/plugins/org.chromium.ChromeCriticalUserInteractions/startup_details_panel.ts b/ui/src/plugins/org.chromium.ChromeCriticalUserInteractions/startup_details_panel.ts
index 0c26623..813e2e6 100644
--- a/ui/src/plugins/org.chromium.ChromeCriticalUserInteractions/startup_details_panel.ts
+++ b/ui/src/plugins/org.chromium.ChromeCriticalUserInteractions/startup_details_panel.ts
@@ -14,8 +14,8 @@
import m from 'mithril';
import {duration, Time, time} from '../../base/time';
-import {DurationWidget} from '../../frontend/widgets/duration';
-import {Timestamp} from '../../frontend/widgets/timestamp';
+import {DurationWidget} from '../../public/lib/widgets/duration';
+import {Timestamp} from '../../public/lib/widgets/timestamp';
import {LONG, NUM, STR, STR_NULL} from '../../trace_processor/query_result';
import {DetailsShell} from '../../widgets/details_shell';
import {GridLayout, GridLayoutColumn} from '../../widgets/grid_layout';
diff --git a/ui/src/plugins/org.chromium.ChromeCriticalUserInteractions/web_content_interaction_details_panel.ts b/ui/src/plugins/org.chromium.ChromeCriticalUserInteractions/web_content_interaction_details_panel.ts
index 25e49aa..58896c2 100644
--- a/ui/src/plugins/org.chromium.ChromeCriticalUserInteractions/web_content_interaction_details_panel.ts
+++ b/ui/src/plugins/org.chromium.ChromeCriticalUserInteractions/web_content_interaction_details_panel.ts
@@ -29,8 +29,8 @@
import m from 'mithril';
import {duration, Time, time} from '../../base/time';
import {asUpid, Upid} from '../../trace_processor/sql_utils/core_types';
-import {DurationWidget} from '../../frontend/widgets/duration';
-import {Timestamp} from '../../frontend/widgets/timestamp';
+import {DurationWidget} from '../../public/lib/widgets/duration';
+import {Timestamp} from '../../public/lib/widgets/timestamp';
import {LONG, NUM, STR} from '../../trace_processor/query_result';
import {DetailsShell} from '../../widgets/details_shell';
import {GridLayout, GridLayoutColumn} from '../../widgets/grid_layout';
diff --git a/ui/src/plugins/org.chromium.ChromeScrollJank/event_latency_details_panel.ts b/ui/src/plugins/org.chromium.ChromeScrollJank/event_latency_details_panel.ts
index cfcdf87..8d803e4 100644
--- a/ui/src/plugins/org.chromium.ChromeScrollJank/event_latency_details_panel.ts
+++ b/ui/src/plugins/org.chromium.ChromeScrollJank/event_latency_details_panel.ts
@@ -15,7 +15,7 @@
import m from 'mithril';
import {Duration, duration, Time, time} from '../../base/time';
import {hasArgs, renderArguments} from '../../frontend/slice_args';
-import {renderDetails} from '../../frontend/slice_details';
+import {renderDetails} from '../../public/lib/details/slice_details';
import {
getDescendantSliceTree,
getSlice,
diff --git a/ui/src/plugins/org.chromium.ChromeScrollJank/index.ts b/ui/src/plugins/org.chromium.ChromeScrollJank/index.ts
index 00415e4..24bd4e2 100644
--- a/ui/src/plugins/org.chromium.ChromeScrollJank/index.ts
+++ b/ui/src/plugins/org.chromium.ChromeScrollJank/index.ts
@@ -48,7 +48,7 @@
INCLUDE PERFETTO MODULE chrome.event_latency;
`);
- const uri = 'perfetto.ChromeScrollJank#toplevelScrolls';
+ const uri = 'org.chromium.ChromeScrollJank#toplevelScrolls';
const title = 'Chrome Scrolls';
ctx.tracks.registerTrack({
@@ -157,7 +157,7 @@
);
await ctx.engine.query(tableDefSql);
- const uri = 'perfetto.ChromeScrollJank#eventLatency';
+ const uri = 'org.chromium.ChromeScrollJank#eventLatency';
const title = 'Chrome Scroll Input Latencies';
ctx.tracks.registerTrack({
@@ -178,7 +178,7 @@
`INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_intervals`,
);
- const uri = 'perfetto.ChromeScrollJank#scrollJankV3';
+ const uri = 'org.chromium.ChromeScrollJank#scrollJankV3';
const title = 'Chrome Scroll Janks';
ctx.tracks.registerTrack({
diff --git a/ui/src/plugins/org.chromium.ChromeScrollJank/scroll_details_panel.ts b/ui/src/plugins/org.chromium.ChromeScrollJank/scroll_details_panel.ts
index eed111f..e9efe97 100644
--- a/ui/src/plugins/org.chromium.ChromeScrollJank/scroll_details_panel.ts
+++ b/ui/src/plugins/org.chromium.ChromeScrollJank/scroll_details_panel.ts
@@ -21,8 +21,8 @@
TableData,
widgetColumn,
} from '../../widgets/table';
-import {DurationWidget} from '../../frontend/widgets/duration';
-import {Timestamp} from '../../frontend/widgets/timestamp';
+import {DurationWidget} from '../../public/lib/widgets/duration';
+import {Timestamp} from '../../public/lib/widgets/timestamp';
import {
LONG,
LONG_NULL,
diff --git a/ui/src/plugins/org.chromium.ChromeScrollJank/scroll_jank_v3_details_panel.ts b/ui/src/plugins/org.chromium.ChromeScrollJank/scroll_jank_v3_details_panel.ts
index fc51eb4..3265e4f 100644
--- a/ui/src/plugins/org.chromium.ChromeScrollJank/scroll_jank_v3_details_panel.ts
+++ b/ui/src/plugins/org.chromium.ChromeScrollJank/scroll_jank_v3_details_panel.ts
@@ -17,8 +17,8 @@
import {exists} from '../../base/utils';
import {getSlice, SliceDetails} from '../../trace_processor/sql_utils/slice';
import {asSliceSqlId} from '../../trace_processor/sql_utils/core_types';
-import {DurationWidget} from '../../frontend/widgets/duration';
-import {Timestamp} from '../../frontend/widgets/timestamp';
+import {DurationWidget} from '../../public/lib/widgets/duration';
+import {Timestamp} from '../../public/lib/widgets/timestamp';
import {Engine} from '../../trace_processor/engine';
import {LONG, NUM, STR} from '../../trace_processor/query_result';
import {DetailsShell} from '../../widgets/details_shell';
diff --git a/ui/src/plugins/org.kernel.SuspendResumeLatency/suspend_resume_details.ts b/ui/src/plugins/org.kernel.SuspendResumeLatency/suspend_resume_details.ts
index 74bc497..d43f699 100644
--- a/ui/src/plugins/org.kernel.SuspendResumeLatency/suspend_resume_details.ts
+++ b/ui/src/plugins/org.kernel.SuspendResumeLatency/suspend_resume_details.ts
@@ -19,8 +19,8 @@
import {GridLayout} from '../../widgets/grid_layout';
import {Section} from '../../widgets/section';
import {Tree, TreeNode} from '../../widgets/tree';
-import {Timestamp} from '../../frontend/widgets/timestamp';
-import {DurationWidget} from '../../frontend/widgets/duration';
+import {Timestamp} from '../../public/lib/widgets/timestamp';
+import {DurationWidget} from '../../public/lib/widgets/duration';
import {Anchor} from '../../widgets/anchor';
import {Engine} from '../../trace_processor/engine';
import {TrackEventDetailsPanel} from '../../public/details_panel';
diff --git a/ui/src/frontend/slice_details.ts b/ui/src/public/lib/details/slice_details.ts
similarity index 79%
rename from ui/src/frontend/slice_details.ts
rename to ui/src/public/lib/details/slice_details.ts
index 784e5b1..4468660 100644
--- a/ui/src/frontend/slice_details.ts
+++ b/ui/src/public/lib/details/slice_details.ts
@@ -13,27 +13,27 @@
// limitations under the License.
import m from 'mithril';
-import {BigintMath} from '../base/bigint_math';
-import {sqliteString} from '../base/string_utils';
-import {exists} from '../base/utils';
-import {SliceDetails} from '../trace_processor/sql_utils/slice';
-import {Anchor} from '../widgets/anchor';
-import {MenuItem, PopupMenu2} from '../widgets/menu';
-import {Section} from '../widgets/section';
-import {SqlRef} from '../widgets/sql_ref';
-import {Tree, TreeNode} from '../widgets/tree';
+import {BigintMath} from '../../../base/bigint_math';
+import {sqliteString} from '../../../base/string_utils';
+import {exists} from '../../../base/utils';
+import {SliceDetails} from '../../../trace_processor/sql_utils/slice';
+import {Anchor} from '../../../widgets/anchor';
+import {MenuItem, PopupMenu2} from '../../../widgets/menu';
+import {Section} from '../../../widgets/section';
+import {SqlRef} from '../../../widgets/sql_ref';
+import {Tree, TreeNode} from '../../../widgets/tree';
import {
BreakdownByThreadState,
BreakdownByThreadStateTreeNode,
-} from './sql/thread_state';
-import {DurationWidget} from './widgets/duration';
-import {renderProcessRef} from './widgets/process';
-import {renderThreadRef} from './widgets/thread';
-import {Timestamp} from './widgets/timestamp';
-import {getSqlTableDescription} from './widgets/sql/table/sql_table_registry';
-import {assertExists} from '../base/logging';
-import {Trace} from '../public/trace';
-import {extensions} from '../public/lib/extensions';
+} from './thread_state';
+import {DurationWidget} from '../widgets/duration';
+import {renderProcessRef} from '../../../frontend/widgets/process';
+import {renderThreadRef} from '../../../frontend/widgets/thread';
+import {Timestamp} from '../widgets/timestamp';
+import {getSqlTableDescription} from '../../../frontend/widgets/sql/table/sql_table_registry';
+import {assertExists} from '../../../base/logging';
+import {Trace} from '../../trace';
+import {extensions} from '../extensions';
// Renders a widget storing all of the generic details for a slice from the
// slice table.
diff --git a/ui/src/frontend/sql/thread_state.ts b/ui/src/public/lib/details/thread_state.ts
similarity index 93%
rename from ui/src/frontend/sql/thread_state.ts
rename to ui/src/public/lib/details/thread_state.ts
index faa464e..6545db0 100644
--- a/ui/src/frontend/sql/thread_state.ts
+++ b/ui/src/public/lib/details/thread_state.ts
@@ -13,16 +13,16 @@
// limitations under the License.
import m from 'mithril';
-import {duration, TimeSpan} from '../../base/time';
-import {Engine} from '../../trace_processor/engine';
+import {duration, TimeSpan} from '../../../base/time';
+import {Engine} from '../../../trace_processor/engine';
import {
LONG,
NUM_NULL,
STR,
STR_NULL,
-} from '../../trace_processor/query_result';
-import {TreeNode} from '../../widgets/tree';
-import {Utid} from '../../trace_processor/sql_utils/core_types';
+} from '../../../trace_processor/query_result';
+import {TreeNode} from '../../../widgets/tree';
+import {Utid} from '../../../trace_processor/sql_utils/core_types';
import {DurationWidget} from '../widgets/duration';
// An individual node of the thread state breakdown tree.
diff --git a/ui/src/public/lib/time_utils.ts b/ui/src/public/lib/time_utils.ts
new file mode 100644
index 0000000..30a3c6a
--- /dev/null
+++ b/ui/src/public/lib/time_utils.ts
@@ -0,0 +1,66 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import m from 'mithril';
+import {Duration, duration, time, Time} from '../../base/time';
+import {Trace} from '../trace';
+import {DurationPrecision, TimestampFormat} from '../timeline';
+
+export function renderTimecode(time: time) {
+ const {dhhmmss, millis, micros, nanos} = Time.toTimecode(time);
+ return m(
+ 'span.pf-timecode',
+ m('span.pf-timecode-hms', dhhmmss),
+ '.',
+ m('span.pf-timecode-millis', millis),
+ m('span.pf-timecode-micros', micros),
+ m('span.pf-timecode-nanos', nanos),
+ );
+}
+
+export function formatDuration(trace: Trace, dur: duration): string {
+ const fmt = trace.timeline.timestampFormat;
+ switch (fmt) {
+ case TimestampFormat.UTC:
+ case TimestampFormat.TraceTz:
+ case TimestampFormat.Timecode:
+ return renderFormattedDuration(trace, dur);
+ case TimestampFormat.TraceNs:
+ return dur.toString();
+ case TimestampFormat.TraceNsLocale:
+ return dur.toLocaleString();
+ case TimestampFormat.Seconds:
+ return Duration.formatSeconds(dur);
+ case TimestampFormat.Milliseconds:
+ return Duration.formatMilliseconds(dur);
+ case TimestampFormat.Microseconds:
+ return Duration.formatMicroseconds(dur);
+ default:
+ const x: never = fmt;
+ throw new Error(`Invalid format ${x}`);
+ }
+}
+
+function renderFormattedDuration(trace: Trace, dur: duration): string {
+ const fmt = trace.timeline.durationPrecision;
+ switch (fmt) {
+ case DurationPrecision.HumanReadable:
+ return Duration.humanise(dur);
+ case DurationPrecision.Full:
+ return Duration.format(dur);
+ default:
+ const x: never = fmt;
+ throw new Error(`Invalid format ${x}`);
+ }
+}
diff --git a/ui/src/public/lib/tracks/sql_table_slice_track_details_tab.ts b/ui/src/public/lib/tracks/sql_table_slice_track_details_tab.ts
index 621d9d3..c5c1935 100644
--- a/ui/src/public/lib/tracks/sql_table_slice_track_details_tab.ts
+++ b/ui/src/public/lib/tracks/sql_table_slice_track_details_tab.ts
@@ -24,8 +24,8 @@
getThreadState,
ThreadState,
} from '../../../trace_processor/sql_utils/thread_state';
-import {DurationWidget} from '../../../frontend/widgets/duration';
-import {Timestamp} from '../../../frontend/widgets/timestamp';
+import {DurationWidget} from '../widgets/duration';
+import {Timestamp} from '../widgets/timestamp';
import {
ColumnType,
durationFromSql,
diff --git a/ui/src/public/lib/widgets/duration.ts b/ui/src/public/lib/widgets/duration.ts
new file mode 100644
index 0000000..409af5d
--- /dev/null
+++ b/ui/src/public/lib/widgets/duration.ts
@@ -0,0 +1,68 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import m from 'mithril';
+import {copyToClipboard} from '../../../base/clipboard';
+import {assertExists} from '../../../base/logging';
+import {Icons} from '../../../base/semantic_icons';
+import {duration} from '../../../base/time';
+import {AppImpl} from '../../../core/app_impl';
+import {Anchor} from '../../../widgets/anchor';
+import {MenuDivider, MenuItem, PopupMenu2} from '../../../widgets/menu';
+import {Trace} from '../../trace';
+import {formatDuration} from '../time_utils';
+import {DurationPrecisionMenuItem} from './duration_precision_menu_items';
+import {TimestampFormatMenuItem} from './timestamp_format_menu';
+
+interface DurationWidgetAttrs {
+ dur: duration;
+ extraMenuItems?: m.Child[];
+}
+
+export class DurationWidget implements m.ClassComponent<DurationWidgetAttrs> {
+ private readonly trace: Trace;
+
+ constructor() {
+ // TODO(primiano): the Trace object should be injected into the attrs, but
+ // there are too many users of this class and doing so requires a larger
+ // refactoring CL. Either that or we should find a different way to plumb
+ // the hoverCursorTimestamp.
+ this.trace = assertExists(AppImpl.instance.trace);
+ }
+
+ view({attrs}: m.Vnode<DurationWidgetAttrs>) {
+ const {dur} = attrs;
+
+ if (dur === -1n) {
+ return '(Did not end)';
+ }
+
+ return m(
+ PopupMenu2,
+ {
+ trigger: m(Anchor, formatDuration(this.trace, dur)),
+ },
+ m(MenuItem, {
+ icon: Icons.Copy,
+ label: `Copy raw value`,
+ onclick: () => {
+ copyToClipboard(dur.toString());
+ },
+ }),
+ m(TimestampFormatMenuItem, {trace: this.trace}),
+ m(DurationPrecisionMenuItem, {trace: this.trace}),
+ attrs.extraMenuItems ? [m(MenuDivider), attrs.extraMenuItems] : null,
+ );
+ }
+}
diff --git a/ui/src/public/lib/widgets/duration_precision_menu_items.ts b/ui/src/public/lib/widgets/duration_precision_menu_items.ts
new file mode 100644
index 0000000..666acba
--- /dev/null
+++ b/ui/src/public/lib/widgets/duration_precision_menu_items.ts
@@ -0,0 +1,61 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import m from 'mithril';
+import {MenuItem} from '../../../widgets/menu';
+import {Trace} from '../../trace';
+import {DurationPrecision, TimestampFormat} from '../../timeline';
+
+interface DurationPrecisionMenuItemAttrs {
+ trace: Trace;
+}
+
+export class DurationPrecisionMenuItem
+ implements m.ClassComponent<DurationPrecisionMenuItemAttrs>
+{
+ view({attrs}: m.Vnode<DurationPrecisionMenuItemAttrs>) {
+ function renderMenuItem(value: DurationPrecision, label: string) {
+ return m(MenuItem, {
+ label,
+ active: value === attrs.trace.timeline.durationPrecision,
+ onclick: () => {
+ attrs.trace.timeline.durationPrecision = value;
+ attrs.trace.scheduleFullRedraw();
+ },
+ });
+ }
+
+ function durationPrecisionHasEffect() {
+ switch (attrs.trace.timeline.timestampFormat) {
+ case TimestampFormat.Timecode:
+ case TimestampFormat.UTC:
+ case TimestampFormat.TraceTz:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ return m(
+ MenuItem,
+ {
+ label: 'Duration precision',
+ disabled: !durationPrecisionHasEffect(),
+ title: 'Not configurable with current time format',
+ },
+ renderMenuItem(DurationPrecision.Full, 'Full'),
+ renderMenuItem(DurationPrecision.HumanReadable, 'Human readable'),
+ );
+ }
+}
diff --git a/ui/src/public/lib/widgets/timestamp.ts b/ui/src/public/lib/widgets/timestamp.ts
new file mode 100644
index 0000000..ee416f8
--- /dev/null
+++ b/ui/src/public/lib/widgets/timestamp.ts
@@ -0,0 +1,100 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import m from 'mithril';
+import {copyToClipboard} from '../../../base/clipboard';
+import {assertExists} from '../../../base/logging';
+import {Icons} from '../../../base/semantic_icons';
+import {time, Time} from '../../../base/time';
+import {AppImpl} from '../../../core/app_impl';
+import {Anchor} from '../../../widgets/anchor';
+import {MenuDivider, MenuItem, PopupMenu2} from '../../../widgets/menu';
+import {Trace} from '../../trace';
+import {TimestampFormatMenuItem} from './timestamp_format_menu';
+import {renderTimecode} from '../time_utils';
+import {TimestampFormat} from '../../timeline';
+
+// import {MenuItem, PopupMenu2} from './menu';
+
+interface TimestampAttrs {
+ // The timestamp to print, this should be the absolute, raw timestamp as
+ // found in trace processor.
+ ts: time;
+ // Custom text value to show instead of the default HH:MM:SS.mmm uuu nnn
+ // formatting.
+ display?: m.Children;
+ extraMenuItems?: m.Child[];
+}
+
+export class Timestamp implements m.ClassComponent<TimestampAttrs> {
+ private readonly trace: Trace;
+
+ constructor() {
+ // TODO(primiano): the Trace object should be injected into the attrs, but
+ // there are too many users of this class and doing so requires a larger
+ // refactoring CL. Either that or we should find a different way to plumb
+ // the hoverCursorTimestamp.
+ this.trace = assertExists(AppImpl.instance.trace);
+ }
+
+ view({attrs}: m.Vnode<TimestampAttrs>) {
+ const {ts} = attrs;
+ const timeline = this.trace.timeline;
+ return m(
+ PopupMenu2,
+ {
+ trigger: m(
+ Anchor,
+ {
+ onmouseover: () => (timeline.hoverCursorTimestamp = ts),
+ onmouseout: () => (timeline.hoverCursorTimestamp = undefined),
+ },
+ attrs.display ?? this.formatTimestamp(timeline.toDomainTime(ts)),
+ ),
+ },
+ m(MenuItem, {
+ icon: Icons.Copy,
+ label: `Copy raw value`,
+ onclick: () => {
+ copyToClipboard(ts.toString());
+ },
+ }),
+ m(TimestampFormatMenuItem),
+ attrs.extraMenuItems ? [m(MenuDivider), attrs.extraMenuItems] : null,
+ );
+ }
+
+ private formatTimestamp(time: time): m.Children {
+ const fmt = this.trace.timeline.timestampFormat;
+ switch (fmt) {
+ case TimestampFormat.UTC:
+ case TimestampFormat.TraceTz:
+ case TimestampFormat.Timecode:
+ return renderTimecode(time);
+ case TimestampFormat.TraceNs:
+ return time.toString();
+ case TimestampFormat.TraceNsLocale:
+ return time.toLocaleString();
+ case TimestampFormat.Seconds:
+ return Time.formatSeconds(time);
+ case TimestampFormat.Milliseconds:
+ return Time.formatMilliseconds(time);
+ case TimestampFormat.Microseconds:
+ return Time.formatMicroseconds(time);
+ default:
+ const x: never = fmt;
+ throw new Error(`Invalid timestamp ${x}`);
+ }
+ }
+}
diff --git a/ui/src/public/lib/widgets/timestamp_format_menu.ts b/ui/src/public/lib/widgets/timestamp_format_menu.ts
new file mode 100644
index 0000000..2b1a351
--- /dev/null
+++ b/ui/src/public/lib/widgets/timestamp_format_menu.ts
@@ -0,0 +1,57 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import m from 'mithril';
+import {MenuItem} from '../../../widgets/menu';
+import {Trace} from '../../trace';
+import {TimestampFormat} from '../../timeline';
+
+interface TimestampFormatMenuItemAttrs {
+ trace: Trace;
+}
+
+export class TimestampFormatMenuItem
+ implements m.ClassComponent<TimestampFormatMenuItemAttrs>
+{
+ view({attrs}: m.Vnode<TimestampFormatMenuItemAttrs>) {
+ function renderMenuItem(value: TimestampFormat, label: string) {
+ return m(MenuItem, {
+ label,
+ active: value === attrs.trace.timeline.timestampFormat,
+ onclick: () => {
+ attrs.trace.timeline.timestampFormat = value;
+ attrs.trace.scheduleFullRedraw();
+ },
+ });
+ }
+
+ return m(
+ MenuItem,
+ {
+ label: 'Time format',
+ },
+ renderMenuItem(TimestampFormat.Timecode, 'Timecode'),
+ renderMenuItem(TimestampFormat.UTC, 'Realtime (UTC)'),
+ renderMenuItem(TimestampFormat.TraceTz, 'Realtime (Trace TZ)'),
+ renderMenuItem(TimestampFormat.Seconds, 'Seconds'),
+ renderMenuItem(TimestampFormat.Milliseconds, 'Milliseconds'),
+ renderMenuItem(TimestampFormat.Microseconds, 'Microseconds'),
+ renderMenuItem(TimestampFormat.TraceNs, 'Raw'),
+ renderMenuItem(
+ TimestampFormat.TraceNsLocale,
+ 'Raw (with locale-specific formatting)',
+ ),
+ );
+ }
+}
diff --git a/ui/src/public/timeline.ts b/ui/src/public/timeline.ts
index ca881e5..8ebad1e 100644
--- a/ui/src/public/timeline.ts
+++ b/ui/src/public/timeline.ts
@@ -15,6 +15,22 @@
import {HighPrecisionTimeSpan} from '../base/high_precision_time_span';
import {time} from '../base/time';
+export enum TimestampFormat {
+ Timecode = 'timecode',
+ TraceNs = 'traceNs',
+ TraceNsLocale = 'traceNsLocale',
+ Seconds = 'seconds',
+ Milliseconds = 'milliseconds',
+ Microseconds = 'microseconds',
+ UTC = 'utc',
+ TraceTz = 'traceTz',
+}
+
+export enum DurationPrecision {
+ Full = 'full',
+ HumanReadable = 'human_readable',
+}
+
export interface Timeline {
// Bring a timestamp into view.
panToTimestamp(ts: time): void;
@@ -39,4 +55,8 @@
// Get a time in the current domain as specified by timestampOffset.
toDomainTime(ts: time): time;
+
+ // These control how timestamps and durations are formatted throughout the UI
+ timestampFormat: TimestampFormat;
+ durationPrecision: DurationPrecision;
}