• 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 {fromNs} from '../../common/time';
16import {
17  TrackController,
18  trackControllerRegistry
19} from '../../controller/track_controller';
20
21import {
22  Config,
23  Data,
24  PROCESS_SUMMARY_TRACK,
25} from './common';
26
27class ProcessSummaryTrackController extends TrackController<Config, Data> {
28  static readonly kind = PROCESS_SUMMARY_TRACK;
29  private busy = false;
30  private setup = false;
31
32  onBoundsChange(start: number, end: number, resolution: number): void {
33    this.update(start, end, resolution);
34  }
35
36  private async update(start: number, end: number, resolution: number):
37      Promise<void> {
38    // TODO: we should really call TraceProcessor.Interrupt() at this point.
39    if (this.busy) return;
40    this.busy = true;
41
42    const startNs = Math.round(start * 1e9);
43    const endNs = Math.round(end * 1e9);
44
45    if (this.setup === false) {
46      await this.query(
47          `create virtual table ${this.tableName('window')} using window;`);
48
49      let utids = [this.config.utid];
50      if (this.config.upid) {
51        const threadQuery = await this.query(
52            `select utid from thread where upid=${this.config.upid}`);
53        utids = threadQuery.columns[0].longValues! as number[];
54      }
55
56      const processSliceView = this.tableName('process_slice_view');
57      await this.query(
58          `create view ${processSliceView} as ` +
59          // 0 as cpu is a dummy column to perform span join on.
60          `select ts, dur/${utids.length} as dur ` +
61          `from slices where depth = 0 and utid in ` +
62          // TODO(dproy): This query is faster if we write it as x < utid < y.
63          `(${utids.join(',')})`);
64      await this.query(`create virtual table ${this.tableName('span')}
65          using span_join(${processSliceView},
66                          ${this.tableName('window')});`);
67      this.setup = true;
68    }
69
70    // |resolution| is in s/px we want # ns for 10px window:
71    const bucketSizeNs = Math.round(resolution * 10 * 1e9);
72    const windowStartNs = Math.floor(startNs / bucketSizeNs) * bucketSizeNs;
73    const windowDurNs = Math.max(1, endNs - windowStartNs);
74
75    this.query(`update ${this.tableName('window')} set
76      window_start=${windowStartNs},
77      window_dur=${windowDurNs},
78      quantum=${bucketSizeNs}
79      where rowid = 0;`);
80
81    this.publish(await this.computeSummary(
82        fromNs(windowStartNs), end, resolution, bucketSizeNs));
83    this.busy = false;
84  }
85
86  private async computeSummary(
87      start: number, end: number, resolution: number,
88      bucketSizeNs: number): Promise<Data> {
89    const startNs = Math.round(start * 1e9);
90    const endNs = Math.round(end * 1e9);
91    const numBuckets = Math.ceil((endNs - startNs) / bucketSizeNs);
92
93    const query = `select
94      quantum_ts as bucket,
95      sum(dur)/cast(${bucketSizeNs} as float) as utilization
96      from ${this.tableName('span')}
97      group by quantum_ts`;
98
99    const rawResult = await this.query(query);
100    const numRows = +rawResult.numRecords;
101
102    const summary: Data = {
103      start,
104      end,
105      resolution,
106      bucketSizeSeconds: fromNs(bucketSizeNs),
107      utilizations: new Float64Array(numBuckets),
108    };
109    const cols = rawResult.columns;
110    for (let row = 0; row < numRows; row++) {
111      const bucket = +cols[0].longValues![row];
112      summary.utilizations[bucket] = +cols[1].doubleValues![row];
113    }
114    return summary;
115  }
116
117  // TODO(dproy); Dedup with other controllers.
118  private async query(query: string) {
119    const result = await this.engine.query(query);
120    if (result.error) {
121      console.error(`Query error "${query}": ${result.error}`);
122      throw new Error(`Query error "${query}": ${result.error}`);
123    }
124    return result;
125  }
126
127  onDestroy(): void {
128    if (this.setup) {
129      this.query(`drop table ${this.tableName('window')}`);
130      this.query(`drop table ${this.tableName('span')}`);
131      this.setup = false;
132    }
133  }
134}
135
136trackControllerRegistry.register(ProcessSummaryTrackController);
137