• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2018 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import {BigintMath} from '../base/bigint_math';
16import {assertExists} from '../base/logging';
17import {Actions, DeferredAction} from '../common/actions';
18import {AggregateData} from '../common/aggregation_data';
19import {Args, ArgsTree} from '../common/arg_types';
20import {
21  ConversionJobName,
22  ConversionJobStatus,
23} from '../common/conversion_jobs';
24import {createEmptyState} from '../common/empty_state';
25import {Engine} from '../common/engine';
26import {
27  HighPrecisionTime,
28  HighPrecisionTimeSpan,
29} from '../common/high_precision_time';
30import {MetricResult} from '../common/metric_data';
31import {CurrentSearchResults, SearchSummary} from '../common/search_data';
32import {CallsiteInfo, EngineConfig, ProfileType, State} from '../common/state';
33import {Span, tpTimeFromSeconds} from '../common/time';
34import {
35  TPDuration,
36  TPTime,
37  TPTimeSpan,
38} from '../common/time';
39
40import {Analytics, initAnalytics} from './analytics';
41import {BottomTabList} from './bottom_tab';
42import {FrontendLocalState} from './frontend_local_state';
43import {RafScheduler} from './raf_scheduler';
44import {Router} from './router';
45import {ServiceWorkerController} from './service_worker_controller';
46import {PxSpan, TimeScale} from './time_scale';
47
48type Dispatch = (action: DeferredAction) => void;
49type TrackDataStore = Map<string, {}>;
50type QueryResultsStore = Map<string, {}|undefined>;
51type AggregateDataStore = Map<string, AggregateData>;
52type Description = Map<string, string>;
53
54export interface SliceDetails {
55  ts?: TPTime;
56  absTime?: string;
57  dur?: TPDuration;
58  threadTs?: TPTime;
59  threadDur?: TPDuration;
60  priority?: number;
61  endState?: string|null;
62  cpu?: number;
63  id?: number;
64  threadStateId?: number;
65  utid?: number;
66  wakeupTs?: TPTime;
67  wakerUtid?: number;
68  wakerCpu?: number;
69  category?: string;
70  name?: string;
71  tid?: number;
72  threadName?: string;
73  pid?: number;
74  processName?: string;
75  uid?: number;
76  packageName?: string;
77  versionCode?: number;
78  args?: Args;
79  argsTree?: ArgsTree;
80  description?: Description;
81}
82
83export interface FlowPoint {
84  trackId: number;
85
86  sliceName: string;
87  sliceCategory: string;
88  sliceId: number;
89  sliceStartTs: number;
90  sliceEndTs: number;
91  // Thread and process info. Only set in sliceSelected not in areaSelected as
92  // the latter doesn't display per-flow info and it'd be a waste to join
93  // additional tables for undisplayed info in that case. Nothing precludes
94  // adding this in a future iteration however.
95  threadName: string;
96  processName: string;
97
98  depth: number;
99
100  // TODO(altimin): Ideally we should have a generic mechanism for allowing to
101  // customise the name here, but for now we are hardcording a few
102  // Chrome-specific bits in the query here.
103  sliceChromeCustomName?: string;
104}
105
106export interface Flow {
107  id: number;
108
109  begin: FlowPoint;
110  end: FlowPoint;
111  dur: number;
112
113  category?: string;
114  name?: string;
115}
116
117export interface CounterDetails {
118  startTime?: TPTime;
119  value?: number;
120  delta?: number;
121  duration?: TPDuration;
122  name?: string;
123}
124
125export interface ThreadStateDetails {
126  ts?: TPTime;
127  dur?: TPDuration;
128}
129
130export interface FlamegraphDetails {
131  type?: ProfileType;
132  id?: number;
133  start?: TPTime;
134  dur?: TPDuration;
135  pids?: number[];
136  upids?: number[];
137  flamegraph?: CallsiteInfo[];
138  expandedCallsite?: CallsiteInfo;
139  viewingOption?: string;
140  expandedId?: number;
141  // isInAreaSelection is true if a flamegraph is part of the current area
142  // selection.
143  isInAreaSelection?: boolean;
144  // When heap_graph_non_finalized_graph has a count >0, we mark the graph
145  // as incomplete.
146  graphIncomplete?: boolean;
147}
148
149export interface CpuProfileDetails {
150  id?: number;
151  ts?: number;
152  utid?: number;
153  stack?: CallsiteInfo[];
154}
155
156export interface QuantizedLoad {
157  start: TPTime;
158  end: TPTime;
159  load: number;
160}
161type OverviewStore = Map<string, QuantizedLoad[]>;
162
163export interface ThreadDesc {
164  utid: number;
165  tid: number;
166  threadName: string;
167  pid?: number;
168  procName?: string;
169  cmdline?: string;
170}
171type ThreadMap = Map<number, ThreadDesc>;
172
173export interface FtraceEvent {
174  id: number;
175  ts: TPTime;
176  name: string;
177  cpu: number;
178  thread: string|null;
179  process: string|null;
180  args: string;
181}
182
183export interface FtracePanelData {
184  events: FtraceEvent[];
185  offset: number;
186  numEvents: number;  // Number of events in the visible window
187}
188
189export interface FtraceStat {
190  name: string;
191  count: number;
192}
193
194function getRoot() {
195  // Works out the root directory where the content should be served from
196  // e.g. `http://origin/v1.2.3/`.
197  const script = document.currentScript as HTMLScriptElement;
198
199  // Needed for DOM tests, that do not have script element.
200  if (script === null) {
201    return '';
202  }
203
204  let root = script.src;
205  root = root.substr(0, root.lastIndexOf('/') + 1);
206  return root;
207}
208
209/**
210 * Global accessors for state/dispatch in the frontend.
211 */
212class Globals {
213  readonly root = getRoot();
214
215  bottomTabList?: BottomTabList = undefined;
216
217  private _testing = false;
218  private _dispatch?: Dispatch = undefined;
219  private _state?: State = undefined;
220  private _frontendLocalState?: FrontendLocalState = undefined;
221  private _rafScheduler?: RafScheduler = undefined;
222  private _serviceWorkerController?: ServiceWorkerController = undefined;
223  private _logging?: Analytics = undefined;
224  private _isInternalUser: boolean|undefined = undefined;
225
226  // TODO(hjd): Unify trackDataStore, queryResults, overviewStore, threads.
227  private _trackDataStore?: TrackDataStore = undefined;
228  private _queryResults?: QueryResultsStore = undefined;
229  private _overviewStore?: OverviewStore = undefined;
230  private _aggregateDataStore?: AggregateDataStore = undefined;
231  private _threadMap?: ThreadMap = undefined;
232  private _sliceDetails?: SliceDetails = undefined;
233  private _threadStateDetails?: ThreadStateDetails = undefined;
234  private _connectedFlows?: Flow[] = undefined;
235  private _selectedFlows?: Flow[] = undefined;
236  private _visibleFlowCategories?: Map<string, boolean> = undefined;
237  private _counterDetails?: CounterDetails = undefined;
238  private _flamegraphDetails?: FlamegraphDetails = undefined;
239  private _cpuProfileDetails?: CpuProfileDetails = undefined;
240  private _numQueriesQueued = 0;
241  private _bufferUsage?: number = undefined;
242  private _recordingLog?: string = undefined;
243  private _traceErrors?: number = undefined;
244  private _metricError?: string = undefined;
245  private _metricResult?: MetricResult = undefined;
246  private _jobStatus?: Map<ConversionJobName, ConversionJobStatus> = undefined;
247  private _router?: Router = undefined;
248  private _embeddedMode?: boolean = undefined;
249  private _hideSidebar?: boolean = undefined;
250  private _ftraceCounters?: FtraceStat[] = undefined;
251  private _ftracePanelData?: FtracePanelData = undefined;
252
253  // TODO(hjd): Remove once we no longer need to update UUID on redraw.
254  private _publishRedraw?: () => void = undefined;
255
256  private _currentSearchResults: CurrentSearchResults = {
257    sliceIds: new Float64Array(0),
258    tsStarts: new Float64Array(0),
259    utids: new Float64Array(0),
260    trackIds: [],
261    sources: [],
262    totalResults: 0,
263  };
264  searchSummary: SearchSummary = {
265    tsStarts: new Float64Array(0),
266    tsEnds: new Float64Array(0),
267    count: new Uint8Array(0),
268  };
269
270  engines = new Map<string, Engine>();
271
272  initialize(dispatch: Dispatch, router: Router) {
273    this._dispatch = dispatch;
274    this._router = router;
275    this._state = createEmptyState();
276    this._frontendLocalState = new FrontendLocalState();
277    this._rafScheduler = new RafScheduler();
278    this._serviceWorkerController = new ServiceWorkerController();
279    this._testing =
280        self.location && self.location.search.indexOf('testing=1') >= 0;
281    this._logging = initAnalytics();
282
283    // TODO(hjd): Unify trackDataStore, queryResults, overviewStore, threads.
284    this._trackDataStore = new Map<string, {}>();
285    this._queryResults = new Map<string, {}>();
286    this._overviewStore = new Map<string, QuantizedLoad[]>();
287    this._aggregateDataStore = new Map<string, AggregateData>();
288    this._threadMap = new Map<number, ThreadDesc>();
289    this._sliceDetails = {};
290    this._connectedFlows = [];
291    this._selectedFlows = [];
292    this._visibleFlowCategories = new Map<string, boolean>();
293    this._counterDetails = {};
294    this._threadStateDetails = {};
295    this._flamegraphDetails = {};
296    this._cpuProfileDetails = {};
297    this.engines.clear();
298  }
299
300  get router(): Router {
301    return assertExists(this._router);
302  }
303
304  get publishRedraw(): () => void {
305    return this._publishRedraw || (() => {});
306  }
307
308  set publishRedraw(f: () => void) {
309    this._publishRedraw = f;
310  }
311
312  get state(): State {
313    return assertExists(this._state);
314  }
315
316  set state(state: State) {
317    this._state = assertExists(state);
318  }
319
320  get dispatch(): Dispatch {
321    return assertExists(this._dispatch);
322  }
323
324  dispatchMultiple(actions: DeferredAction[]): void {
325    const dispatch = this.dispatch;
326    for (const action of actions) {
327      dispatch(action);
328    }
329  }
330
331  get frontendLocalState() {
332    return assertExists(this._frontendLocalState);
333  }
334
335  get rafScheduler() {
336    return assertExists(this._rafScheduler);
337  }
338
339  get logging() {
340    return assertExists(this._logging);
341  }
342
343  get serviceWorkerController() {
344    return assertExists(this._serviceWorkerController);
345  }
346
347  // TODO(hjd): Unify trackDataStore, queryResults, overviewStore, threads.
348  get overviewStore(): OverviewStore {
349    return assertExists(this._overviewStore);
350  }
351
352  get trackDataStore(): TrackDataStore {
353    return assertExists(this._trackDataStore);
354  }
355
356  get queryResults(): QueryResultsStore {
357    return assertExists(this._queryResults);
358  }
359
360  get threads() {
361    return assertExists(this._threadMap);
362  }
363
364  get sliceDetails() {
365    return assertExists(this._sliceDetails);
366  }
367
368  set sliceDetails(click: SliceDetails) {
369    this._sliceDetails = assertExists(click);
370  }
371
372  get threadStateDetails() {
373    return assertExists(this._threadStateDetails);
374  }
375
376  set threadStateDetails(click: ThreadStateDetails) {
377    this._threadStateDetails = assertExists(click);
378  }
379
380  get connectedFlows() {
381    return assertExists(this._connectedFlows);
382  }
383
384  set connectedFlows(connectedFlows: Flow[]) {
385    this._connectedFlows = assertExists(connectedFlows);
386  }
387
388  get selectedFlows() {
389    return assertExists(this._selectedFlows);
390  }
391
392  set selectedFlows(selectedFlows: Flow[]) {
393    this._selectedFlows = assertExists(selectedFlows);
394  }
395
396  get visibleFlowCategories() {
397    return assertExists(this._visibleFlowCategories);
398  }
399
400  set visibleFlowCategories(visibleFlowCategories: Map<string, boolean>) {
401    this._visibleFlowCategories = assertExists(visibleFlowCategories);
402  }
403
404  get counterDetails() {
405    return assertExists(this._counterDetails);
406  }
407
408  set counterDetails(click: CounterDetails) {
409    this._counterDetails = assertExists(click);
410  }
411
412  get aggregateDataStore(): AggregateDataStore {
413    return assertExists(this._aggregateDataStore);
414  }
415
416  get flamegraphDetails() {
417    return assertExists(this._flamegraphDetails);
418  }
419
420  set flamegraphDetails(click: FlamegraphDetails) {
421    this._flamegraphDetails = assertExists(click);
422  }
423
424  get traceErrors() {
425    return this._traceErrors;
426  }
427
428  setTraceErrors(arg: number) {
429    this._traceErrors = arg;
430  }
431
432  get metricError() {
433    return this._metricError;
434  }
435
436  setMetricError(arg: string) {
437    this._metricError = arg;
438  }
439
440  get metricResult() {
441    return this._metricResult;
442  }
443
444  setMetricResult(result: MetricResult) {
445    this._metricResult = result;
446  }
447
448  get cpuProfileDetails() {
449    return assertExists(this._cpuProfileDetails);
450  }
451
452  set cpuProfileDetails(click: CpuProfileDetails) {
453    this._cpuProfileDetails = assertExists(click);
454  }
455
456  set numQueuedQueries(value: number) {
457    this._numQueriesQueued = value;
458  }
459
460  get numQueuedQueries() {
461    return this._numQueriesQueued;
462  }
463
464  get bufferUsage() {
465    return this._bufferUsage;
466  }
467
468  get recordingLog() {
469    return this._recordingLog;
470  }
471
472  get currentSearchResults() {
473    return this._currentSearchResults;
474  }
475
476  set currentSearchResults(results: CurrentSearchResults) {
477    this._currentSearchResults = results;
478  }
479
480  get hasFtrace(): boolean {
481    return Boolean(this._ftraceCounters && this._ftraceCounters.length > 0);
482  }
483
484  get ftraceCounters(): FtraceStat[]|undefined {
485    return this._ftraceCounters;
486  }
487
488  set ftraceCounters(value: FtraceStat[]|undefined) {
489    this._ftraceCounters = value;
490  }
491
492  getConversionJobStatus(name: ConversionJobName): ConversionJobStatus {
493    return this.getJobStatusMap().get(name) || ConversionJobStatus.NotRunning;
494  }
495
496  setConversionJobStatus(name: ConversionJobName, status: ConversionJobStatus) {
497    const map = this.getJobStatusMap();
498    if (status === ConversionJobStatus.NotRunning) {
499      map.delete(name);
500    } else {
501      map.set(name, status);
502    }
503  }
504
505  private getJobStatusMap(): Map<ConversionJobName, ConversionJobStatus> {
506    if (!this._jobStatus) {
507      this._jobStatus = new Map();
508    }
509    return this._jobStatus;
510  }
511
512  get embeddedMode(): boolean {
513    return !!this._embeddedMode;
514  }
515
516  set embeddedMode(value: boolean) {
517    this._embeddedMode = value;
518  }
519
520  get hideSidebar(): boolean {
521    return !!this._hideSidebar;
522  }
523
524  set hideSidebar(value: boolean) {
525    this._hideSidebar = value;
526  }
527
528  setBufferUsage(bufferUsage: number) {
529    this._bufferUsage = bufferUsage;
530  }
531
532  setTrackData(id: string, data: {}) {
533    this.trackDataStore.set(id, data);
534  }
535
536  setRecordingLog(recordingLog: string) {
537    this._recordingLog = recordingLog;
538  }
539
540  setAggregateData(kind: string, data: AggregateData) {
541    this.aggregateDataStore.set(kind, data);
542  }
543
544  getCurResolution(): TPDuration {
545    // Truncate the resolution to the closest power of 2 (in nanosecond space).
546    // We choose to work in ns space because resolution is consumed be track
547    // controllers for quantization and they rely on resolution to be a power
548    // of 2 in nanosecond form. This is property does not hold if we work in
549    // second space.
550    //
551    // This effectively means the resolution changes approximately every 6 zoom
552    // levels. Logic: each zoom level represents a delta of 0.1 * (visible
553    // window span). Therefore, zooming out by six levels is 1.1^6 ~= 2.
554    // Similarily, zooming in six levels is 0.9^6 ~= 0.5.
555    const timeScale = this.frontendLocalState.visibleTimeScale;
556    // TODO(b/186265930): Remove once fixed:
557    if (timeScale.pxSpan.delta === 0) {
558      console.error(`b/186265930: Bad pxToSec suppressed`);
559      return BigintMath.bitFloor(tpTimeFromSeconds(1000));
560    }
561
562    const timePerPx = HighPrecisionTime.max(
563        timeScale.pxDeltaToDuration(1), new HighPrecisionTime(1n));
564
565    const resolutionBig = BigintMath.bitFloor(timePerPx.toTPTime());
566    return resolutionBig;
567  }
568
569  getCurrentEngine(): EngineConfig|undefined {
570    return this.state.engine;
571  }
572
573  get ftracePanelData(): FtracePanelData|undefined {
574    return this._ftracePanelData;
575  }
576
577  set ftracePanelData(data: FtracePanelData|undefined) {
578    this._ftracePanelData = data;
579  }
580
581  makeSelection(action: DeferredAction<{}>, tabToOpen = 'current_selection') {
582    // A new selection should cancel the current search selection.
583    globals.dispatch(Actions.setSearchIndex({index: -1}));
584    const tab = action.type === 'deselect' ? undefined : tabToOpen;
585    globals.dispatch(Actions.setCurrentTab({tab}));
586    globals.dispatch(action);
587  }
588
589  resetForTesting() {
590    this._dispatch = undefined;
591    this._state = undefined;
592    this._frontendLocalState = undefined;
593    this._rafScheduler = undefined;
594    this._serviceWorkerController = undefined;
595
596    // TODO(hjd): Unify trackDataStore, queryResults, overviewStore, threads.
597    this._trackDataStore = undefined;
598    this._queryResults = undefined;
599    this._overviewStore = undefined;
600    this._threadMap = undefined;
601    this._sliceDetails = undefined;
602    this._threadStateDetails = undefined;
603    this._aggregateDataStore = undefined;
604    this._numQueriesQueued = 0;
605    this._metricResult = undefined;
606    this._currentSearchResults = {
607      sliceIds: new Float64Array(0),
608      tsStarts: new Float64Array(0),
609      utids: new Float64Array(0),
610      trackIds: [],
611      sources: [],
612      totalResults: 0,
613    };
614  }
615
616  // This variable is set by the is_internal_user.js script if the user is a
617  // googler. This is used to avoid exposing features that are not ready yet
618  // for public consumption. The gated features themselves are not secret.
619  // If a user has been detected as a Googler once, make that sticky in
620  // localStorage, so that we keep treating them as such when they connect over
621  // public networks.
622  get isInternalUser() {
623    if (this._isInternalUser === undefined) {
624      this._isInternalUser = localStorage.getItem('isInternalUser') === '1';
625    }
626    return this._isInternalUser;
627  }
628
629  set isInternalUser(value: boolean) {
630    localStorage.setItem('isInternalUser', value ? '1' : '0');
631    this._isInternalUser = value;
632  }
633
634  get testing() {
635    return this._testing;
636  }
637
638  // Used when switching to the legacy TraceViewer UI.
639  // Most resources are cleaned up by replacing the current |window| object,
640  // however pending RAFs and workers seem to outlive the |window| and need to
641  // be cleaned up explicitly.
642  shutdown() {
643    this._rafScheduler!.shutdown();
644  }
645
646  // Get a timescale that covers the entire trace
647  getTraceTimeScale(pxSpan: PxSpan): TimeScale {
648    const {start, end} = this.state.traceTime;
649    const traceTime = HighPrecisionTimeSpan.fromTpTime(start, end);
650    return new TimeScale(traceTime.start, traceTime.duration.nanos, pxSpan);
651  }
652
653  // Get the trace time bounds
654  stateTraceTime(): Span<HighPrecisionTime> {
655    const {start, end} = this.state.traceTime;
656    return HighPrecisionTimeSpan.fromTpTime(start, end);
657  }
658
659  stateTraceTimeTP(): Span<TPTime> {
660    const {start, end} = this.state.traceTime;
661    return new TPTimeSpan(start, end);
662  }
663
664  // Get the state version of the visible time bounds
665  stateVisibleTime(): Span<TPTime> {
666    const {start, end} = this.state.frontendLocalState.visibleState;
667    return new TPTimeSpan(start, end);
668  }
669}
670
671export const globals = new Globals();
672