Normalize timestamp printing. Add new function formatTime() for formatting timestamps. Add dropdown menu to Timestamp component. Use formatTime() or m(Timestamp, ...) for timestamps in panels. Change-Id: Ic17d242a32be25f97bfb0094e4374827ce5a3676
diff --git a/ui/src/frontend/chrome_slice_panel.ts b/ui/src/frontend/chrome_slice_panel.ts index d1dfcc7..93597f0 100644 --- a/ui/src/frontend/chrome_slice_panel.ts +++ b/ui/src/frontend/chrome_slice_panel.ts
@@ -32,12 +32,14 @@ import {runQueryInNewTab} from './query_result_tab'; import {verticalScrollToTrack} from './scroll_helper'; import {Icons} from './semantic_icons'; +import {asTPTimestamp} from './sql_types'; import {Button} from './widgets/button'; import {DetailsShell} from './widgets/details_shell'; import {Column, GridLayout} from './widgets/grid_layout'; import {MenuItem, PopupMenu2} from './widgets/menu'; import {Section} from './widgets/section'; import {SqlRef} from './widgets/sql_ref'; +import {Timestamp} from './widgets/timestamp'; import {Tree, TreeNode} from './widgets/tree'; import {exists} from './widgets/utils'; @@ -360,7 +362,7 @@ }), m(TreeNode, { left: 'Start time', - right: tpTimeToCode(slice.ts - globals.state.traceTime.start), + right: m(Timestamp, {ts: asTPTimestamp(slice.ts)}), }), exists(slice.absTime) && m(TreeNode, {left: 'Absolute Time', right: slice.absTime}),
diff --git a/ui/src/frontend/counter_panel.ts b/ui/src/frontend/counter_panel.ts index cbeb653..e1710a8 100644 --- a/ui/src/frontend/counter_panel.ts +++ b/ui/src/frontend/counter_panel.ts
@@ -17,9 +17,11 @@ import {tpTimeToCode} from '../common/time'; import {globals} from './globals'; +import {asTPTimestamp} from './sql_types'; import {DetailsShell} from './widgets/details_shell'; import {GridLayout} from './widgets/grid_layout'; import {Section} from './widgets/section'; +import {Timestamp} from './widgets/timestamp'; import {Tree, TreeNode} from './widgets/tree'; export class CounterDetailsPanel implements m.ClassComponent { @@ -40,10 +42,9 @@ m(TreeNode, {left: 'Name', right: `${counterInfo.name}`}), m(TreeNode, { left: 'Start time', - right: `${ - tpTimeToCode( - counterInfo.startTime - - globals.state.traceTime.start)}`, + right: + m(Timestamp, + {ts: asTPTimestamp(counterInfo.startTime)}), }), m(TreeNode, { left: 'Value',
diff --git a/ui/src/frontend/ftrace_panel.ts b/ui/src/frontend/ftrace_panel.ts index fdeef5c..02b3d12 100644 --- a/ui/src/frontend/ftrace_panel.ts +++ b/ui/src/frontend/ftrace_panel.ts
@@ -18,10 +18,11 @@ import {assertExists} from '../base/logging'; import {Actions} from '../common/actions'; import {colorForString} from '../common/colorizer'; -import {formatTPTime, TPTime} from '../common/time'; +import {TPTime} from '../common/time'; import {globals} from './globals'; import {Panel} from './panel'; +import {asTPTimestamp} from './sql_types'; import {DetailsShell} from './widgets/details_shell'; import { MultiSelect, @@ -29,6 +30,7 @@ Option as MultiSelectOption, } from './widgets/multiselect'; import {PopupPosition} from './widgets/popup'; +import {Timestamp} from './widgets/timestamp'; const ROW_H = 20; const PAGE_SIZE = 250; @@ -187,7 +189,7 @@ for (let i = 0; i < events.length; i++) { const {ts, name, cpu, process, args} = events[i]; - const timestamp = formatTPTime(ts - globals.state.traceTime.start); + const timestamp = m(Timestamp, {ts: asTPTimestamp(ts), minimal: true}); const rank = i + offset;
diff --git a/ui/src/frontend/logs_panel.ts b/ui/src/frontend/logs_panel.ts index 8c50589..030763e 100644 --- a/ui/src/frontend/logs_panel.ts +++ b/ui/src/frontend/logs_panel.ts
@@ -23,12 +23,14 @@ LogEntries, LogEntriesKey, } from '../common/logs'; -import {formatTPTime, TPTime} from '../common/time'; +import {TPTime} from '../common/time'; import {SELECTED_LOG_ROWS_COLOR} from './css_constants'; import {globals} from './globals'; import {LOG_PRIORITIES, LogsFilters} from './logs_filters'; import {Panel} from './panel'; +import {asTPTimestamp} from './sql_types'; +import {Timestamp} from './widgets/timestamp'; const ROW_H = 20; @@ -154,7 +156,7 @@ 'onmouseover': this.onRowOver.bind(this, ts), 'onmouseout': this.onRowOut.bind(this), }, - m('.cell', formatTPTime(ts - globals.state.traceTime.start)), + m('.cell', m(Timestamp, {ts: asTPTimestamp(ts), minimal: true})), m('.cell', priorityLetter || '?'), m('.cell', tags[i]), hasProcessNames ? m('.cell.with-process', processNames[i]) :
diff --git a/ui/src/frontend/semantic_icons.ts b/ui/src/frontend/semantic_icons.ts index 1ee0b61..e30eb99 100644 --- a/ui/src/frontend/semantic_icons.ts +++ b/ui/src/frontend/semantic_icons.ts
@@ -13,8 +13,9 @@ // limitations under the License. export class Icons { - static readonly ExternalLink = 'open_in_new'; // Could be undefined - static readonly UpdateSelection = 'call_made'; // Could be 'open_in_new' - static readonly ChangeViewport = 'query_stats'; // Could be 'search' - static readonly ContextMenu = 'arrow_drop_down'; + static readonly ExternalLink = 'open_in_new'; // Could be undefined + static readonly UpdateSelection = 'call_made'; // Could be 'open_in_new' + static readonly ChangeViewport = 'query_stats'; // Could be 'search' + static readonly ContextMenu = 'arrow_drop_down'; // Could be 'more_vert' + static readonly Copy = 'content_copy'; }
diff --git a/ui/src/frontend/slice_details_panel.ts b/ui/src/frontend/slice_details_panel.ts index 1b50462..eaab6ad 100644 --- a/ui/src/frontend/slice_details_panel.ts +++ b/ui/src/frontend/slice_details_panel.ts
@@ -16,16 +16,18 @@ import {Actions} from '../common/actions'; import {translateState} from '../common/thread_state'; -import {tpTimeToCode} from '../common/time'; +import {formatTime, tpTimeToCode} from '../common/time'; import {Anchor} from './anchor'; import {globals, SliceDetails, ThreadDesc} from './globals'; import {scrollToTrackAndTs} from './scroll_helper'; import {SlicePanel} from './slice_panel'; +import {asTPTimestamp} from './sql_types'; import {DetailsShell} from './widgets/details_shell'; import {GridLayout} from './widgets/grid_layout'; import {Section} from './widgets/section'; import {SqlRef} from './widgets/sql_ref'; +import {Timestamp} from './widgets/timestamp'; import {Tree, TreeNode} from './widgets/tree'; export class SliceDetailsPanel extends SlicePanel { @@ -82,8 +84,7 @@ if (!threadInfo) { return null; } - const timestamp = - tpTimeToCode(sliceInfo.wakeupTs! - globals.state.traceTime.start); + const timestamp = formatTime(sliceInfo.wakeupTs!); return m( '.slice-details-wakeup-text', m('', `Wakeup @ ${timestamp} on CPU ${sliceInfo.wakerCpu} by`), @@ -161,7 +162,7 @@ }), m(TreeNode, { left: 'Start time', - right: tpTimeToCode(sliceInfo.ts - globals.state.traceTime.start), + right: m(Timestamp, {ts: asTPTimestamp(sliceInfo.ts)}), }), m(TreeNode, { left: 'Duration',
diff --git a/ui/src/frontend/thread_state.ts b/ui/src/frontend/thread_state.ts index 0bc4082..1de576c 100644 --- a/ui/src/frontend/thread_state.ts +++ b/ui/src/frontend/thread_state.ts
@@ -19,12 +19,9 @@ import { TPDuration, TPTime, - tpTimeToCode, } from '../common/time'; -import {copyToClipboard} from './clipboard'; import {globals} from './globals'; -import {menuItem} from './popup_menu'; import {scrollToTrackAndTs} from './scroll_helper'; import { asUtid, @@ -37,12 +34,9 @@ SQLConstraints, } from './sql_utils'; import { - getProcessName, getThreadInfo, - getThreadName, ThreadInfo, } from './thread_and_process_info'; -import {dict, Dict, maybeValue, Value, value} from './value'; // Representation of a single thread state object, corresponding to // a row for the |thread_slice| table. @@ -152,56 +146,3 @@ globals.makeSelection(Actions.selectSlice({id, trackId})); scrollToTrackAndTs(trackId, ts); } - -function stateToValue( - state: string, cpu: number|undefined, id: SchedSqlId|undefined, ts: TPTime): - Value|null { - if (!state) { - return null; - } - if (id === undefined || cpu === undefined) { - return value(state); - } - return value(`${state} on CPU ${cpu}`, { - rightButton: { - action: () => { - goToSchedSlice(cpu, id, ts); - }, - hoverText: 'Go to CPU slice', - }, - }); -} - -export function threadStateToDict(state: ThreadState): Dict { - const result: {[name: string]: Value|null} = {}; - - result['Start time'] = - value(tpTimeToCode(state.ts - globals.state.traceTime.start)); - result['Duration'] = value(tpTimeToCode(state.dur)); - result['State'] = - stateToValue(state.state, state.cpu, state.schedSqlId, state.ts); - result['Blocked function'] = maybeValue(state.blockedFunction); - const process = state?.thread?.process; - result['Process'] = maybeValue(process ? getProcessName(process) : undefined); - const thread = state?.thread; - result['Thread'] = maybeValue(thread ? getThreadName(thread) : undefined); - if (state.wakerThread) { - const process = state.wakerThread.process; - result['Waker'] = dict({ - 'Process': maybeValue(process ? getProcessName(process) : undefined), - 'Thread': maybeValue(getThreadName(state.wakerThread)), - }); - } - result['SQL id'] = value(`thread_state[${state.threadStateSqlId}]`, { - contextMenu: [ - menuItem( - 'Copy SQL query', - () => { - copyToClipboard(`select * from thread_state where id=${ - state.threadStateSqlId}`); - }), - ], - }); - - return dict(result); -}
diff --git a/ui/src/frontend/thread_state_tab.ts b/ui/src/frontend/thread_state_tab.ts index 3547939..b30b540 100644 --- a/ui/src/frontend/thread_state_tab.ts +++ b/ui/src/frontend/thread_state_tab.ts
@@ -19,7 +19,7 @@ import {Anchor} from './anchor'; import {BottomTab, bottomTabRegistry, NewBottomTabArgs} from './bottom_tab'; import {globals} from './globals'; -import {SchedSqlId, ThreadStateSqlId} from './sql_types'; +import {asTPTimestamp, SchedSqlId, ThreadStateSqlId} from './sql_types'; import { getProcessName, getThreadName, @@ -30,6 +30,7 @@ import {GridLayout} from './widgets/grid_layout'; import {Section} from './widgets/section'; import {SqlRef} from './widgets/sql_ref'; +import {Timestamp} from './widgets/timestamp'; import {Tree, TreeNode} from './widgets/tree'; interface ThreadStateTabConfig { @@ -95,7 +96,7 @@ Tree, m(TreeNode, { left: 'Start time', - right: tpTimeToCode(state.ts - globals.state.traceTime.start), + right: m(Timestamp, {ts: asTPTimestamp(state.ts)}), }), m(TreeNode, { left: 'Duration',
diff --git a/ui/src/frontend/widgets/timestamp.ts b/ui/src/frontend/widgets/timestamp.ts index d8cc841..374f8e3 100644 --- a/ui/src/frontend/widgets/timestamp.ts +++ b/ui/src/frontend/widgets/timestamp.ts
@@ -14,15 +14,37 @@ import m from 'mithril'; -import {tpTimeToCode} from '../../common/time'; -import {toTraceTime, TPTimestamp} from '../sql_types'; +import {formatTime} from '../../common/time'; +import {Anchor} from '../anchor'; +import {copyToClipboard} from '../clipboard'; +import {Icons} from '../semantic_icons'; +import {TPTimestamp} from '../sql_types'; + +import {MenuItem, PopupMenu2} from './menu'; interface TimestampAttrs { + // The timestamp to print, this should be the absolute, raw timestamp as + // found in trace processor. ts: TPTimestamp; + minimal?: boolean; } export class Timestamp implements m.ClassComponent<TimestampAttrs> { - view(vnode: m.Vnode<TimestampAttrs>) { - return tpTimeToCode(toTraceTime(vnode.attrs.ts)); + view({attrs}: m.Vnode<TimestampAttrs>) { + const {ts, minimal = false} = attrs; + return m( + PopupMenu2, + { + trigger: + m(Anchor, {icon: Icons.ContextMenu}, formatTime(ts, minimal)), + }, + m(MenuItem, { + icon: Icons.Copy, + label: 'Copy raw timestamp', + onclick: () => { + copyToClipboard(ts.toString()); + }, + }), + ); } }