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