• 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 {assertExists} from '../base/logging';
16import {Actions, DeferredAction} from '../common/actions';
17import {AggregateData} from '../common/aggregation_data';
18import {Args, ArgsTree} from '../common/arg_types';
19import {
20  ConversionJobName,
21  ConversionJobStatus
22} from '../common/conversion_jobs';
23import {createEmptyState} from '../common/empty_state';
24import {Engine} from '../common/engine';
25import {MetricResult} from '../common/metric_data';
26import {CurrentSearchResults, SearchSummary} from '../common/search_data';
27import {CallsiteInfo, State} from '../common/state';
28import {fromNs, toNs} from '../common/time';
29
30import {Analytics, initAnalytics} from './analytics';
31import {FrontendLocalState} from './frontend_local_state';
32import {PivotTableHelper} from './pivot_table_helper';
33import {RafScheduler} from './raf_scheduler';
34import {Router} from './router';
35import {ServiceWorkerController} from './service_worker_controller';
36
37type Dispatch = (action: DeferredAction) => void;
38type TrackDataStore = Map<string, {}>;
39type QueryResultsStore = Map<string, {}|undefined>;
40type PivotTableHelperStore = Map<string, PivotTableHelper>;
41type AggregateDataStore = Map<string, AggregateData>;
42type Description = Map<string, string>;
43
44export interface SliceDetails {
45  ts?: number;
46  dur?: number;
47  thread_ts?: number;
48  thread_dur?: number;
49  priority?: number;
50  endState?: string;
51  cpu?: number;
52  id?: number;
53  threadStateId?: number;
54  utid?: number;
55  wakeupTs?: number;
56  wakerUtid?: number;
57  wakerCpu?: number;
58  category?: string;
59  name?: string;
60  tid?: number;
61  threadName?: string;
62  pid?: number;
63  processName?: string;
64  uid?: number;
65  packageName?: string;
66  versionCode?: number;
67  args?: Args;
68  argsTree?: ArgsTree;
69  description?: Description;
70}
71
72export interface FlowPoint {
73  trackId: number;
74
75  sliceName: string;
76  sliceCategory: string;
77  sliceId: number;
78  sliceStartTs: number;
79  sliceEndTs: number;
80  // Thread and process info. Only set in sliceSelected not in areaSelected as
81  // the latter doesn't display per-flow info and it'd be a waste to join
82  // additional tables for undisplayed info in that case. Nothing precludes
83  // adding this in a future iteration however.
84  threadName: string;
85  processName: string;
86
87  depth: number;
88}
89
90export interface Flow {
91  id: number;
92
93  begin: FlowPoint;
94  end: FlowPoint;
95  dur: number;
96
97  category?: string;
98  name?: string;
99}
100
101export interface CounterDetails {
102  startTime?: number;
103  value?: number;
104  delta?: number;
105  duration?: number;
106  name?: string;
107}
108
109export interface ThreadStateDetails {
110  ts?: number;
111  dur?: number;
112  state?: string;
113  utid?: number;
114  cpu?: number;
115  sliceId?: number;
116  blockedFunction?: string;
117}
118
119export interface FlamegraphDetails {
120  type?: string;
121  id?: number;
122  startNs?: number;
123  durNs?: number;
124  pids?: number[];
125  upids?: number[];
126  flamegraph?: CallsiteInfo[];
127  expandedCallsite?: CallsiteInfo;
128  viewingOption?: string;
129  expandedId?: number;
130  // isInAreaSelection is true if a flamegraph is part of the current area
131  // selection.
132  isInAreaSelection?: boolean;
133  // When heap_graph_non_finalized_graph has a count >0, we mark the graph
134  // as incomplete.
135  graphIncomplete?: boolean;
136}
137
138export interface CpuProfileDetails {
139  id?: number;
140  ts?: number;
141  utid?: number;
142  stack?: CallsiteInfo[];
143}
144
145export interface QuantizedLoad {
146  startSec: number;
147  endSec: number;
148  load: number;
149}
150type OverviewStore = Map<string, QuantizedLoad[]>;
151
152export interface ThreadDesc {
153  utid: number;
154  tid: number;
155  threadName: string;
156  pid?: number;
157  procName?: string;
158  cmdline?: string;
159}
160type ThreadMap = Map<number, ThreadDesc>;
161
162function getRoot() {
163  // Works out the root directory where the content should be served from
164  // e.g. `http://origin/v1.2.3/`.
165  const script = document.currentScript as HTMLScriptElement;
166
167  // Needed for DOM tests, that do not have script element.
168  if (script === null) {
169    return '';
170  }
171
172  let root = script.src;
173  root = root.substr(0, root.lastIndexOf('/') + 1);
174  return root;
175}
176
177/**
178 * Global accessors for state/dispatch in the frontend.
179 */
180class Globals {
181  readonly root = getRoot();
182
183  private _testing = false;
184  private _dispatch?: Dispatch = undefined;
185  private _state?: State = undefined;
186  private _frontendLocalState?: FrontendLocalState = undefined;
187  private _rafScheduler?: RafScheduler = undefined;
188  private _serviceWorkerController?: ServiceWorkerController = undefined;
189  private _logging?: Analytics = undefined;
190  private _isInternalUser: boolean|undefined = undefined;
191
192  // TODO(hjd): Unify trackDataStore, queryResults, overviewStore, threads.
193  private _trackDataStore?: TrackDataStore = undefined;
194  private _queryResults?: QueryResultsStore = undefined;
195  private _pivotTableHelper?: PivotTableHelperStore = undefined;
196  private _overviewStore?: OverviewStore = undefined;
197  private _aggregateDataStore?: AggregateDataStore = undefined;
198  private _threadMap?: ThreadMap = undefined;
199  private _sliceDetails?: SliceDetails = undefined;
200  private _threadStateDetails?: ThreadStateDetails = undefined;
201  private _connectedFlows?: Flow[] = undefined;
202  private _selectedFlows?: Flow[] = undefined;
203  private _visibleFlowCategories?: Map<string, boolean> = undefined;
204  private _counterDetails?: CounterDetails = undefined;
205  private _flamegraphDetails?: FlamegraphDetails = undefined;
206  private _cpuProfileDetails?: CpuProfileDetails = undefined;
207  private _numQueriesQueued = 0;
208  private _bufferUsage?: number = undefined;
209  private _recordingLog?: string = undefined;
210  private _traceErrors?: number = undefined;
211  private _metricError?: string = undefined;
212  private _metricResult?: MetricResult = undefined;
213  private _hasFtrace?: boolean = undefined;
214  private _jobStatus?: Map<ConversionJobName, ConversionJobStatus> = undefined;
215  private _router?: Router = undefined;
216
217  // TODO(hjd): Remove once we no longer need to update UUID on redraw.
218  private _publishRedraw?: () => void = undefined;
219
220  private _currentSearchResults: CurrentSearchResults = {
221    sliceIds: new Float64Array(0),
222    tsStarts: new Float64Array(0),
223    utids: new Float64Array(0),
224    trackIds: [],
225    sources: [],
226    totalResults: 0,
227  };
228  searchSummary: SearchSummary = {
229    tsStarts: new Float64Array(0),
230    tsEnds: new Float64Array(0),
231    count: new Uint8Array(0),
232  };
233
234  engines = new Map<string, Engine>();
235
236  initialize(dispatch: Dispatch, router: Router) {
237    this._dispatch = dispatch;
238    this._router = router;
239    this._state = createEmptyState();
240    this._frontendLocalState = new FrontendLocalState();
241    this._rafScheduler = new RafScheduler();
242    this._serviceWorkerController = new ServiceWorkerController();
243    this._testing =
244        self.location && self.location.search.indexOf('testing=1') >= 0;
245    this._logging = initAnalytics();
246
247    // TODO(hjd): Unify trackDataStore, queryResults, overviewStore, threads.
248    this._trackDataStore = new Map<string, {}>();
249    this._queryResults = new Map<string, {}>();
250    this._pivotTableHelper = new Map<string, PivotTableHelper>();
251    this._overviewStore = new Map<string, QuantizedLoad[]>();
252    this._aggregateDataStore = new Map<string, AggregateData>();
253    this._threadMap = new Map<number, ThreadDesc>();
254    this._sliceDetails = {};
255    this._connectedFlows = [];
256    this._selectedFlows = [];
257    this._visibleFlowCategories = new Map<string, boolean>();
258    this._counterDetails = {};
259    this._threadStateDetails = {};
260    this._flamegraphDetails = {};
261    this._cpuProfileDetails = {};
262    this.engines.clear();
263  }
264
265  get router(): Router {
266    return assertExists(this._router);
267  }
268
269  get publishRedraw(): () => void {
270    return this._publishRedraw || (() => {});
271  }
272
273  set publishRedraw(f: () => void) {
274    this._publishRedraw = f;
275  }
276
277  get state(): State {
278    return assertExists(this._state);
279  }
280
281  set state(state: State) {
282    this._state = assertExists(state);
283  }
284
285  get dispatch(): Dispatch {
286    return assertExists(this._dispatch);
287  }
288
289  get frontendLocalState() {
290    return assertExists(this._frontendLocalState);
291  }
292
293  get rafScheduler() {
294    return assertExists(this._rafScheduler);
295  }
296
297  get logging() {
298    return assertExists(this._logging);
299  }
300
301  get serviceWorkerController() {
302    return assertExists(this._serviceWorkerController);
303  }
304
305  // TODO(hjd): Unify trackDataStore, queryResults, overviewStore, threads.
306  get overviewStore(): OverviewStore {
307    return assertExists(this._overviewStore);
308  }
309
310  get trackDataStore(): TrackDataStore {
311    return assertExists(this._trackDataStore);
312  }
313
314  get queryResults(): QueryResultsStore {
315    return assertExists(this._queryResults);
316  }
317
318  get pivotTableHelper(): PivotTableHelperStore {
319    return assertExists(this._pivotTableHelper);
320  }
321
322  get threads() {
323    return assertExists(this._threadMap);
324  }
325
326  get sliceDetails() {
327    return assertExists(this._sliceDetails);
328  }
329
330  set sliceDetails(click: SliceDetails) {
331    this._sliceDetails = assertExists(click);
332  }
333
334  get threadStateDetails() {
335    return assertExists(this._threadStateDetails);
336  }
337
338  set threadStateDetails(click: ThreadStateDetails) {
339    this._threadStateDetails = assertExists(click);
340  }
341
342  get connectedFlows() {
343    return assertExists(this._connectedFlows);
344  }
345
346  set connectedFlows(connectedFlows: Flow[]) {
347    this._connectedFlows = assertExists(connectedFlows);
348  }
349
350  get selectedFlows() {
351    return assertExists(this._selectedFlows);
352  }
353
354  set selectedFlows(selectedFlows: Flow[]) {
355    this._selectedFlows = assertExists(selectedFlows);
356  }
357
358  get visibleFlowCategories() {
359    return assertExists(this._visibleFlowCategories);
360  }
361
362  set visibleFlowCategories(visibleFlowCategories: Map<string, boolean>) {
363    this._visibleFlowCategories = assertExists(visibleFlowCategories);
364  }
365
366  get counterDetails() {
367    return assertExists(this._counterDetails);
368  }
369
370  set counterDetails(click: CounterDetails) {
371    this._counterDetails = assertExists(click);
372  }
373
374  get aggregateDataStore(): AggregateDataStore {
375    return assertExists(this._aggregateDataStore);
376  }
377
378  get flamegraphDetails() {
379    return assertExists(this._flamegraphDetails);
380  }
381
382  set flamegraphDetails(click: FlamegraphDetails) {
383    this._flamegraphDetails = assertExists(click);
384  }
385
386  get traceErrors() {
387    return this._traceErrors;
388  }
389
390  setTraceErrors(arg: number) {
391    this._traceErrors = arg;
392  }
393
394  get metricError() {
395    return this._metricError;
396  }
397
398  setMetricError(arg: string) {
399    this._metricError = arg;
400  }
401
402  get metricResult() {
403    return this._metricResult;
404  }
405
406  setMetricResult(result: MetricResult) {
407    this._metricResult = result;
408  }
409
410  get cpuProfileDetails() {
411    return assertExists(this._cpuProfileDetails);
412  }
413
414  set cpuProfileDetails(click: CpuProfileDetails) {
415    this._cpuProfileDetails = assertExists(click);
416  }
417
418  set numQueuedQueries(value: number) {
419    this._numQueriesQueued = value;
420  }
421
422  get numQueuedQueries() {
423    return this._numQueriesQueued;
424  }
425
426  get bufferUsage() {
427    return this._bufferUsage;
428  }
429
430  get recordingLog() {
431    return this._recordingLog;
432  }
433
434  get currentSearchResults() {
435    return this._currentSearchResults;
436  }
437
438  set currentSearchResults(results: CurrentSearchResults) {
439    this._currentSearchResults = results;
440  }
441
442  get hasFtrace(): boolean {
443    return !!this._hasFtrace;
444  }
445
446  set hasFtrace(value: boolean) {
447    this._hasFtrace = value;
448  }
449
450  getConversionJobStatus(name: ConversionJobName): ConversionJobStatus {
451    return this.getJobStatusMap().get(name) || ConversionJobStatus.NotRunning;
452  }
453
454  setConversionJobStatus(name: ConversionJobName, status: ConversionJobStatus) {
455    const map = this.getJobStatusMap();
456    if (status === ConversionJobStatus.NotRunning) {
457      map.delete(name);
458    } else {
459      map.set(name, status);
460    }
461  }
462
463  private getJobStatusMap(): Map<ConversionJobName, ConversionJobStatus> {
464    if (!this._jobStatus) {
465      this._jobStatus = new Map();
466    }
467    return this._jobStatus;
468  }
469
470  setBufferUsage(bufferUsage: number) {
471    this._bufferUsage = bufferUsage;
472  }
473
474  setTrackData(id: string, data: {}) {
475    this.trackDataStore.set(id, data);
476  }
477
478  setRecordingLog(recordingLog: string) {
479    this._recordingLog = recordingLog;
480  }
481
482  setAggregateData(kind: string, data: AggregateData) {
483    this.aggregateDataStore.set(kind, data);
484  }
485
486  getCurResolution() {
487    // Truncate the resolution to the closest power of 2 (in nanosecond space).
488    // We choose to work in ns space because resolution is consumed be track
489    // controllers for quantization and they rely on resolution to be a power
490    // of 2 in nanosecond form. This is property does not hold if we work in
491    // second space.
492    //
493    // This effectively means the resolution changes approximately every 6 zoom
494    // levels. Logic: each zoom level represents a delta of 0.1 * (visible
495    // window span). Therefore, zooming out by six levels is 1.1^6 ~= 2.
496    // Similarily, zooming in six levels is 0.9^6 ~= 0.5.
497    const pxToSec = this.frontendLocalState.timeScale.deltaPxToDuration(1);
498    // TODO(b/186265930): Remove once fixed:
499    if (!isFinite(pxToSec)) {
500      // Resolution is in pixels per second so 1000 means 1px = 1ms.
501      console.error(`b/186265930: Bad pxToSec suppressed ${pxToSec}`);
502      return fromNs(Math.pow(2, Math.floor(Math.log2(toNs(1000)))));
503    }
504    const pxToNs = Math.max(toNs(pxToSec), 1);
505    const resolution = fromNs(Math.pow(2, Math.floor(Math.log2(pxToNs))));
506    const log2 = Math.log2(toNs(resolution));
507    if (log2 % 1 !== 0) {
508      throw new Error(`Resolution should be a power of two.
509        pxToSec: ${pxToSec},
510        pxToNs: ${pxToNs},
511        resolution: ${resolution},
512        log2: ${Math.log2(toNs(resolution))}`);
513    }
514    return resolution;
515  }
516
517  makeSelection(action: DeferredAction<{}>, tabToOpen = 'current_selection') {
518    // A new selection should cancel the current search selection.
519    globals.dispatch(Actions.setSearchIndex({index: -1}));
520    const tab = action.type === 'deselect' ? undefined : tabToOpen;
521    globals.dispatch(Actions.setCurrentTab({tab}));
522    globals.dispatch(action);
523  }
524
525  resetForTesting() {
526    this._dispatch = undefined;
527    this._state = undefined;
528    this._frontendLocalState = undefined;
529    this._rafScheduler = undefined;
530    this._serviceWorkerController = undefined;
531
532    // TODO(hjd): Unify trackDataStore, queryResults, overviewStore, threads.
533    this._trackDataStore = undefined;
534    this._queryResults = undefined;
535    this._pivotTableHelper = undefined;
536    this._overviewStore = undefined;
537    this._threadMap = undefined;
538    this._sliceDetails = undefined;
539    this._threadStateDetails = undefined;
540    this._aggregateDataStore = undefined;
541    this._numQueriesQueued = 0;
542    this._metricResult = undefined;
543    this._currentSearchResults = {
544      sliceIds: new Float64Array(0),
545      tsStarts: new Float64Array(0),
546      utids: new Float64Array(0),
547      trackIds: [],
548      sources: [],
549      totalResults: 0,
550    };
551  }
552
553  // This variable is set by the is_internal_user.js script if the user is a
554  // googler. This is used to avoid exposing features that are not ready yet
555  // for public consumption. The gated features themselves are not secret.
556  // If a user has been detected as a Googler once, make that sticky in
557  // localStorage, so that we keep treating them as such when they connect over
558  // public networks.
559  get isInternalUser() {
560    if (this._isInternalUser === undefined) {
561      this._isInternalUser = localStorage.getItem('isInternalUser') === '1';
562    }
563    return this._isInternalUser;
564  }
565
566  set isInternalUser(value: boolean) {
567    localStorage.setItem('isInternalUser', value ? '1' : '0');
568    this._isInternalUser = value;
569  }
570
571  get testing() {
572    return this._testing;
573  }
574
575  // Used when switching to the legacy TraceViewer UI.
576  // Most resources are cleaned up by replacing the current |window| object,
577  // however pending RAFs and workers seem to outlive the |window| and need to
578  // be cleaned up explicitly.
579  shutdown() {
580    this._rafScheduler!.shutdown();
581  }
582}
583
584export const globals = new Globals();
585