diff --git a/ui/src/common/actions.ts b/ui/src/common/actions.ts index 92cad97336..4cebecb05c 100644 --- a/ui/src/common/actions.ts +++ b/ui/src/common/actions.ts @@ -15,7 +15,7 @@ import {Draft} from 'immer'; import {assertExists, assertTrue} from '../base/logging'; -import {duration, time} from '../base/time'; +import {duration, Time, time} from '../base/time'; import {RecordConfig} from '../controller/record_config_types'; import { GenericSliceDetailsTabConfig, @@ -63,7 +63,6 @@ import { State, Status, ThreadTrackSortKey, - TraceTime, TrackSortKey, UtidToTrackSortKey, VisibleState, @@ -474,10 +473,6 @@ export const StateActions = { state.permalink = {}; }, - setTraceTime(state: StateDraft, args: TraceTime): void { - state.traceTime = args; - }, - updateStatus(state: StateDraft, args: Status): void { if (statusTraceEvent) { traceEventEnd(statusTraceEvent); @@ -673,7 +668,7 @@ export const StateActions = { }; this.openFlamegraph(state, { type: args.type, - start: state.traceTime.start as time, // TODO(stevegolton): Avoid type assertion here. + start: Time.ZERO, end: args.ts, upids: [args.upid], viewingOption: defaultViewingOption(args.type), diff --git a/ui/src/common/empty_state.ts b/ui/src/common/empty_state.ts index f866914934..e3ed2b75b3 100644 --- a/ui/src/common/empty_state.ts +++ b/ui/src/common/empty_state.ts @@ -22,12 +22,7 @@ import { } from '../frontend/record_config'; import {SqlTables} from '../frontend/sql_table/well_known_tables'; -import { - defaultTraceTime, - NonSerializableState, - State, - STATE_VERSION, -} from './state'; +import {NonSerializableState, State, STATE_VERSION} from './state'; const AUTOLOAD_STARTED_CONFIG_FLAG = featureFlags.register({ id: 'autoloadStartedConfig', @@ -92,7 +87,6 @@ export function createEmptyState(): State { version: STATE_VERSION, nextId: '-1', newEngineMode: 'USE_HTTP_RPC_IF_AVAILABLE', - traceTime: {...defaultTraceTime}, tracks: {}, utidToThreadSortKey: {}, aggregatePreferences: {}, @@ -112,7 +106,8 @@ export function createEmptyState(): State { frontendLocalState: { visibleState: { - ...defaultTraceTime, + start: Time.ZERO, + end: Time.ZERO, lastUpdate: 0, resolution: 0n, }, diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts index d0af73b613..7ca7a7417b 100644 --- a/ui/src/common/state.ts +++ b/ui/src/common/state.ts @@ -13,7 +13,7 @@ // limitations under the License. import {BigintMath} from '../base/bigint_math'; -import {duration, Time, time} from '../base/time'; +import {duration, time} from '../base/time'; import {RecordConfig} from '../controller/record_config_types'; import { Aggregation, @@ -150,7 +150,8 @@ export const MAX_TIME = 180; // 51. Changed structure of FlamegraphState.expandedCallsiteByViewingOption. // 52. Update track group state - don't make the summary track the first track. // 53. Remove android log state. -export const STATE_VERSION = 53; +// 54. Remove traceTime. +export const STATE_VERSION = 54; export const SCROLLING_TRACK_GROUP = 'ScrollingTracks'; @@ -315,11 +316,6 @@ export interface PermalinkConfig { isRecordingConfig?: boolean; // this permalink request is for a recording config only } -export interface TraceTime { - start: time; - end: time; -} - export interface FrontendLocalState { visibleState: VisibleState; } @@ -479,7 +475,6 @@ export interface State { */ newEngineMode: NewEngineMode; engine?: EngineConfig; - traceTime: TraceTime; traceUuid?: string; trackGroups: ObjectById; tracks: ObjectByKey; @@ -558,11 +553,6 @@ export interface State { plugins: {[key: string]: any}; } -export const defaultTraceTime = { - start: Time.ZERO, - end: Time.fromSeconds(10), -}; - export declare type RecordMode = | 'STOP_WHEN_FULL' | 'RING_BUFFER' diff --git a/ui/src/controller/selection_controller.ts b/ui/src/controller/selection_controller.ts index dc905ea858..1b6dd0fe12 100644 --- a/ui/src/controller/selection_controller.ts +++ b/ui/src/controller/selection_controller.ts @@ -441,7 +441,7 @@ export class SelectionController extends Controller<'main'> { IFNULL(value, 0) as value FROM counter WHERE ts < ${ts} and track_id = ${trackId}`); const previousValue = previous.firstRow({value: NUM}).value; - const endTs = rightTs !== -1n ? rightTs : globals.state.traceTime.end; + const endTs = rightTs !== -1n ? rightTs : globals.traceTime.end; const delta = value - previousValue; const duration = endTs - ts; const trackKey = globals.trackManager.trackKeyByTrackId.get(trackId); diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts index c1f0d6e74e..315b64d75b 100644 --- a/ui/src/controller/trace_controller.ts +++ b/ui/src/controller/trace_controller.ts @@ -26,21 +26,22 @@ import { isMetatracingEnabled, } from '../common/metatracing'; import {pluginManager} from '../common/plugins'; +import {EngineMode, PendingDeeplinkState, ProfileType} from '../common/state'; +import {featureFlags, Flag, PERF_SAMPLE_FLAG} from '../core/feature_flags'; import { defaultTraceTime, - EngineMode, - PendingDeeplinkState, - ProfileType, -} from '../common/state'; -import {featureFlags, Flag, PERF_SAMPLE_FLAG} from '../core/feature_flags'; -import {globals, QuantizedLoad, ThreadDesc} from '../frontend/globals'; + globals, + QuantizedLoad, + ThreadDesc, + TraceTime, +} from '../frontend/globals'; import { clearOverviewData, publishHasFtrace, publishMetricError, publishOverviewData, - publishRealtimeOffset, publishThreads, + publishTraceDetails, } from '../frontend/publish'; import {addQueryResultsTab} from '../frontend/query_result_tab'; import {Router} from '../frontend/router'; @@ -450,13 +451,8 @@ export class TraceController extends Controller { // traceUuid will be '' if the trace is not cacheable (URL or RPC). const traceUuid = await this.cacheCurrentTrace(); - const traceTime = await this.engine.getTraceTimeBounds(); - const start = traceTime.start; - const end = traceTime.end; - const traceTimeState = { - start, - end, - }; + const traceDetails = await getTraceTimeDetails(this.engine); + publishTraceDetails(traceDetails); const shownJsonWarning = window.localStorage.getItem(SHOWN_JSON_WARNING_KEY) !== null; @@ -485,12 +481,11 @@ export class TraceController extends Controller { const actions: DeferredAction[] = [ Actions.setOmnibox(emptyOmniboxState), Actions.setTraceUuid({traceUuid}), - Actions.setTraceTime(traceTimeState), ]; const visibleTimeSpan = await computeVisibleTime( - traceTime.start, - traceTime.end, + traceDetails.start, + traceDetails.end, isJsonTrace, this.engine, ); @@ -530,7 +525,9 @@ export class TraceController extends Controller { this.decideTabs(); await this.listThreads(); - await this.loadTimelineOverview(traceTime); + await this.loadTimelineOverview( + new TimeSpan(traceDetails.start, traceDetails.end), + ); { // Check if we have any ftrace events at all @@ -544,82 +541,12 @@ export class TraceController extends Controller { publishHasFtrace(res.numRows() > 0); } - { - // Find the first REALTIME or REALTIME_COARSE clock snapshot. - // Prioritize REALTIME over REALTIME_COARSE. - const query = `select - ts, - clock_value as clockValue, - clock_name as clockName - from clock_snapshot - where - snapshot_id = 0 AND - clock_name in ('REALTIME', 'REALTIME_COARSE') - `; - const result = await assertExists(this.engine).query(query); - const it = result.iter({ - ts: LONG, - clockValue: LONG, - clockName: STR, - }); - - let snapshot = { - clockName: '', - ts: Time.ZERO, - clockValue: Time.ZERO, - }; - - // Find the most suitable snapshot - for (let row = 0; it.valid(); it.next(), row++) { - if (it.clockName === 'REALTIME') { - snapshot = { - clockName: it.clockName, - ts: Time.fromRaw(it.ts), - clockValue: Time.fromRaw(it.clockValue), - }; - break; - } else if (it.clockName === 'REALTIME_COARSE') { - if (snapshot.clockName !== 'REALTIME') { - snapshot = { - clockName: it.clockName, - ts: Time.fromRaw(it.ts), - clockValue: Time.fromRaw(it.clockValue), - }; - } - } - } - - // The max() is so the query returns NULL if the tz info doesn't exist. - const queryTz = `select max(int_value) as tzOffMin from metadata - where name = 'timezone_off_mins'`; - const resTz = await assertExists(this.engine).query(queryTz); - const tzOffMin = resTz.firstRow({tzOffMin: NUM_NULL}).tzOffMin ?? 0; - - // This is the offset between the unix epoch and ts in the ts domain. - // I.e. the value of ts at the time of the unix epoch - usually some large - // negative value. - const realtimeOffset = Time.sub(snapshot.ts, snapshot.clockValue); - - // Find the previous closest midnight from the trace start time. - const utcOffset = Time.getLatestMidnight( - globals.state.traceTime.start, - realtimeOffset, - ); - - const traceTzOffset = Time.getLatestMidnight( - globals.state.traceTime.start, - Time.sub(realtimeOffset, Time.fromSeconds(tzOffMin * 60)), - ); - - publishRealtimeOffset(realtimeOffset, utcOffset, traceTzOffset); - } - globals.dispatch(Actions.sortThreadTracks({})); globals.dispatch(Actions.maybeExpandOnlyTrackGroup({})); await this.selectFirstHeapProfile(); if (PERF_SAMPLE_FLAG.get()) { - await this.selectPerfSample(); + await this.selectPerfSample(traceDetails); } const pendingDeeplink = globals.state.pendingDeeplink; @@ -663,7 +590,7 @@ export class TraceController extends Controller { return engineMode; } - private async selectPerfSample() { + private async selectPerfSample(traceTime: {start: time; end: time}) { const query = `select upid from perf_sample join thread using (utid) @@ -673,8 +600,8 @@ export class TraceController extends Controller { if (profile.numRows() !== 1) return; const row = profile.firstRow({upid: NUM}); const upid = row.upid; - const leftTs = globals.state.traceTime.start; - const rightTs = globals.state.traceTime.end; + const leftTs = traceTime.start; + const rightTs = traceTime.end; globals.dispatch( Actions.selectPerfSamples({ id: 0, @@ -1217,3 +1144,77 @@ async function computeVisibleTime( } return HighPrecisionTimeSpan.fromTime(visibleStart, visibleEnd); } + +async function getTraceTimeDetails(engine: Engine): Promise { + const traceTime = await engine.getTraceTimeBounds(); + + // Find the first REALTIME or REALTIME_COARSE clock snapshot. + // Prioritize REALTIME over REALTIME_COARSE. + const query = `select + ts, + clock_value as clockValue, + clock_name as clockName + from clock_snapshot + where + snapshot_id = 0 AND + clock_name in ('REALTIME', 'REALTIME_COARSE') + `; + const result = await engine.query(query); + const it = result.iter({ + ts: LONG, + clockValue: LONG, + clockName: STR, + }); + + let snapshot = { + clockName: '', + ts: Time.ZERO, + clockValue: Time.ZERO, + }; + + // Find the most suitable snapshot + for (let row = 0; it.valid(); it.next(), row++) { + if (it.clockName === 'REALTIME') { + snapshot = { + clockName: it.clockName, + ts: Time.fromRaw(it.ts), + clockValue: Time.fromRaw(it.clockValue), + }; + break; + } else if (it.clockName === 'REALTIME_COARSE') { + if (snapshot.clockName !== 'REALTIME') { + snapshot = { + clockName: it.clockName, + ts: Time.fromRaw(it.ts), + clockValue: Time.fromRaw(it.clockValue), + }; + } + } + } + + // The max() is so the query returns NULL if the tz info doesn't exist. + const queryTz = `select max(int_value) as tzOffMin from metadata + where name = 'timezone_off_mins'`; + const resTz = await assertExists(engine).query(queryTz); + const tzOffMin = resTz.firstRow({tzOffMin: NUM_NULL}).tzOffMin ?? 0; + + // This is the offset between the unix epoch and ts in the ts domain. + // I.e. the value of ts at the time of the unix epoch - usually some large + // negative value. + const realtimeOffset = Time.sub(snapshot.ts, snapshot.clockValue); + + // Find the previous closest midnight from the trace start time. + const utcOffset = Time.getLatestMidnight(traceTime.start, realtimeOffset); + + const traceTzOffset = Time.getLatestMidnight( + traceTime.start, + Time.sub(realtimeOffset, Time.fromSeconds(tzOffMin * 60)), + ); + + return { + ...traceTime, + realtimeOffset, + utcOffset, + traceTzOffset, + }; +} diff --git a/ui/src/frontend/app.ts b/ui/src/frontend/app.ts index 92301b715c..0cb3fc98ad 100644 --- a/ui/src/frontend/app.ts +++ b/ui/src/frontend/app.ts @@ -620,8 +620,8 @@ export class App implements m.ClassComponent { if (selection !== null && selection.kind === 'AREA') { const area = globals.state.areas[selection.areaId]; const coversEntireTimeRange = - globals.state.traceTime.start === area.start && - globals.state.traceTime.end === area.end; + globals.traceTime.start === area.start && + globals.traceTime.end === area.end; if (!coversEntireTimeRange) { // If the current selection is an area which does not cover the // entire time range, preserve the list of selected tracks and @@ -636,7 +636,7 @@ export class App implements m.ClassComponent { // If the current selection is not an area, select all. tracksToSelect = Object.keys(globals.state.tracks); } - const {start, end} = globals.state.traceTime; + const {start, end} = globals.traceTime; globals.dispatch( Actions.selectArea({ area: { diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts index 50b2286cbd..d41cebd1b8 100644 --- a/ui/src/frontend/globals.ts +++ b/ui/src/frontend/globals.ts @@ -221,6 +221,32 @@ export interface LegacySelectionArgs { pendingScrollId: number | undefined; } +export interface TraceTime { + readonly start: time; + readonly end: time; + + // This is the ts value at the time of the Unix epoch. + // Normally some large negative value, because the unix epoch is normally in + // the past compared to ts=0. + readonly realtimeOffset: time; + + // This is the timestamp that we should use for our offset when in UTC mode. + // Usually the most recent UTC midnight compared to the trace start time. + readonly utcOffset: time; + + // Trace TZ is like UTC but keeps into account also the timezone_off_mins + // recorded into the trace, to show timestamps in the device local time. + readonly traceTzOffset: time; +} + +export const defaultTraceTime: TraceTime = { + start: Time.ZERO, + end: Time.fromSeconds(10), + realtimeOffset: Time.ZERO, + utcOffset: Time.ZERO, + traceTzOffset: Time.ZERO, +}; + /** * Global accessors for state/dispatch in the frontend. */ @@ -260,9 +286,6 @@ class Globals { private _embeddedMode?: boolean = undefined; private _hideSidebar?: boolean = undefined; private _cmdManager = new CommandManager(); - private _realtimeOffset = Time.ZERO; - private _utcOffset = Time.ZERO; - private _traceTzOffset = Time.ZERO; private _tabManager = new TabManager(); private _trackManager = new TrackManager(this._store); private _selectionManager = new SelectionManager(this._store); @@ -273,6 +296,8 @@ class Globals { newVersionAvailable = false; showPanningHint = false; + traceTime = defaultTraceTime; + // TODO(hjd): Remove once we no longer need to update UUID on redraw. private _publishRedraw?: () => void = undefined; @@ -691,19 +716,19 @@ class Globals { // Get a timescale that covers the entire trace getTraceTimeScale(pxSpan: PxSpan): TimeScale { - const {start, end} = this.state.traceTime; + const {start, end} = this.traceTime; const traceTime = HighPrecisionTimeSpan.fromTime(start, end); return TimeScale.fromHPTimeSpan(traceTime, pxSpan); } // Get the trace time bounds stateTraceTime(): Span { - const {start, end} = this.state.traceTime; + const {start, end} = this.traceTime; return HighPrecisionTimeSpan.fromTime(start, end); } stateTraceTimeTP(): Span { - const {start, end} = this.state.traceTime; + const {start, end} = this.traceTime; return new TimeSpan(start, end); } @@ -723,37 +748,6 @@ class Globals { return assertExists(this._cmdManager); } - // This is the ts value at the time of the Unix epoch. - // Normally some large negative value, because the unix epoch is normally in - // the past compared to ts=0. - get realtimeOffset(): time { - return this._realtimeOffset; - } - - set realtimeOffset(time: time) { - this._realtimeOffset = time; - } - - // This is the timestamp that we should use for our offset when in UTC mode. - // Usually the most recent UTC midnight compared to the trace start time. - get utcOffset(): time { - return this._utcOffset; - } - - set utcOffset(offset: time) { - this._utcOffset = offset; - } - - // Trace TZ is like UTC but keeps into account also the timezone_off_mins - // recorded into the trace, to show timestamps in the device local time. - get traceTzOffset(): time { - return this._traceTzOffset; - } - - set traceTzOffset(offset: time) { - this._traceTzOffset = offset; - } - get tabManager() { return this._tabManager; } @@ -768,14 +762,14 @@ class Globals { switch (fmt) { case TimestampFormat.Timecode: case TimestampFormat.Seconds: - return this.state.traceTime.start; + return this.traceTime.start; case TimestampFormat.Raw: case TimestampFormat.RawLocale: return Time.ZERO; case TimestampFormat.UTC: - return this.utcOffset; + return this.traceTime.utcOffset; case TimestampFormat.TraceTz: - return this.traceTzOffset; + return this.traceTime.traceTzOffset; default: const x: never = fmt; throw new Error(`Unsupported format ${x}`); diff --git a/ui/src/frontend/publish.ts b/ui/src/frontend/publish.ts index f8c36cbe34..bfb34cae19 100644 --- a/ui/src/frontend/publish.ts +++ b/ui/src/frontend/publish.ts @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -import {time} from '../base/time'; import {Actions} from '../common/actions'; import {AggregateData} from '../common/aggregation_data'; import {ConversionJobStatusUpdate} from '../common/conversion_jobs'; @@ -32,6 +31,7 @@ import { SliceDetails, ThreadDesc, ThreadStateDetails, + TraceTime, } from './globals'; import {findCurrentSelection} from './keyboard_event_handler'; @@ -96,14 +96,8 @@ export function publishHasFtrace(value: boolean): void { globals.publishRedraw(); } -export function publishRealtimeOffset( - offset: time, - utcOffset: time, - traceTzOffset: time, -) { - globals.realtimeOffset = offset; - globals.utcOffset = utcOffset; - globals.traceTzOffset = traceTzOffset; +export function publishTraceDetails(details: TraceTime): void { + globals.traceTime = details; globals.publishRedraw(); } diff --git a/ui/src/frontend/time_axis_panel.ts b/ui/src/frontend/time_axis_panel.ts index d20341f754..af1f3dfcc5 100644 --- a/ui/src/frontend/time_axis_panel.ts +++ b/ui/src/frontend/time_axis_panel.ts @@ -57,16 +57,16 @@ export class TimeAxisPanel implements Panel { break; case TimestampFormat.UTC: const offsetDate = Time.toDate( - globals.utcOffset, - globals.realtimeOffset, + globals.traceTime.utcOffset, + globals.traceTime.realtimeOffset, ); const dateStr = toISODateOnly(offsetDate); ctx.fillText(`UTC ${dateStr}`, 6, 10); break; case TimestampFormat.TraceTz: const offsetTzDate = Time.toDate( - globals.traceTzOffset, - globals.realtimeOffset, + globals.traceTime.traceTzOffset, + globals.traceTime.realtimeOffset, ); const dateTzStr = toISODateOnly(offsetTzDate); ctx.fillText(dateTzStr, 6, 10); diff --git a/ui/src/frontend/viewer_page.ts b/ui/src/frontend/viewer_page.ts index b599dc71fe..b67f2e5f4c 100644 --- a/ui/src/frontend/viewer_page.ts +++ b/ui/src/frontend/viewer_page.ts @@ -128,7 +128,7 @@ class TraceViewer implements m.ClassComponent { currentY: number, editing: boolean, ) => { - const traceTime = globals.state.traceTime; + const traceTime = globals.traceTime; const {visibleTimeScale} = timeline; this.keepCurrentSelection = true; if (editing) {