• 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, toNs} from '../../common/time';
16import {LIMIT} from '../../common/track_data';
17
18import {
19  TrackController,
20  trackControllerRegistry
21} from '../../controller/track_controller';
22
23import {
24  Config,
25  CPU_SLICE_TRACK_KIND,
26  Data,
27  SliceData,
28  SummaryData
29} from './common';
30
31class CpuSliceTrackController extends TrackController<Config, Data> {
32  static readonly kind = CPU_SLICE_TRACK_KIND;
33  private setup = false;
34
35  async onBoundsChange(start: number, end: number, resolution: number):
36      Promise<Data> {
37    const startNs = toNs(start);
38    const endNs = toNs(end);
39
40    if (this.setup === false) {
41      await this.query(
42          `create virtual table ${this.tableName('window')} using window;`);
43      await this.query(`create virtual table ${this.tableName('span')}
44              using span_join(sched PARTITIONED cpu,
45                              ${this.tableName('window')});`);
46      this.setup = true;
47    }
48
49    const isQuantized = this.shouldSummarize(resolution);
50    // |resolution| is in s/px we want # ns for 10px window:
51    const bucketSizeNs = Math.round(resolution * 10 * 1e9);
52    let windowStartNs = startNs;
53    if (isQuantized) {
54      windowStartNs = Math.floor(windowStartNs / bucketSizeNs) * bucketSizeNs;
55    }
56    const windowDurNs = Math.max(1, endNs - windowStartNs);
57
58    this.query(`update ${this.tableName('window')} set
59      window_start=${windowStartNs},
60      window_dur=${windowDurNs},
61      quantum=${isQuantized ? bucketSizeNs : 0}
62      where rowid = 0;`);
63
64    if (isQuantized) {
65      return this.computeSummary(
66          fromNs(windowStartNs), end, resolution, bucketSizeNs);
67    } else {
68      return this.computeSlices(fromNs(windowStartNs), end, resolution);
69    }
70  }
71
72  private async computeSummary(
73      start: number, end: number, resolution: number,
74      bucketSizeNs: number): Promise<SummaryData> {
75    const startNs = toNs(start);
76    const endNs = toNs(end);
77    const numBuckets = Math.ceil((endNs - startNs) / bucketSizeNs);
78
79    const query = `select
80        quantum_ts as bucket,
81        sum(dur)/cast(${bucketSizeNs} as float) as utilization
82        from ${this.tableName('span')}
83        where cpu = ${this.config.cpu}
84        and utid != 0
85        group by quantum_ts limit ${LIMIT}`;
86
87    const rawResult = await this.query(query);
88    const numRows = +rawResult.numRecords;
89
90    const summary: Data = {
91      kind: 'summary',
92      start,
93      end,
94      resolution,
95      length: numRows,
96      bucketSizeSeconds: fromNs(bucketSizeNs),
97      utilizations: new Float64Array(numBuckets),
98    };
99    const cols = rawResult.columns;
100    for (let row = 0; row < numRows; row++) {
101      const bucket = +cols[0].longValues![row];
102      summary.utilizations[bucket] = +cols[1].doubleValues![row];
103    }
104    return summary;
105  }
106
107  private async computeSlices(start: number, end: number, resolution: number):
108      Promise<SliceData> {
109    // TODO(hjd): Remove LIMIT
110    const LIMIT = 10000;
111
112    const query = `select ts,dur,utid,id from ${this.tableName('span')}
113        where cpu = ${this.config.cpu}
114        and utid != 0
115        limit ${LIMIT};`;
116    const rawResult = await this.query(query);
117
118    const numRows = +rawResult.numRecords;
119    const slices: SliceData = {
120      kind: 'slice',
121      start,
122      end,
123      resolution,
124      length: numRows,
125      ids: new Float64Array(numRows),
126      starts: new Float64Array(numRows),
127      ends: new Float64Array(numRows),
128      utids: new Uint32Array(numRows),
129    };
130
131    const cols = rawResult.columns;
132    for (let row = 0; row < numRows; row++) {
133      const startSec = fromNs(+cols[0].longValues![row]);
134      slices.starts[row] = startSec;
135      slices.ends[row] = startSec + fromNs(+cols[1].longValues![row]);
136      slices.utids[row] = +cols[2].longValues![row];
137      slices.ids[row] = +cols[3].longValues![row];
138    }
139    if (numRows === LIMIT) {
140      slices.end = slices.ends[slices.ends.length - 1];
141    }
142    return slices;
143  }
144
145
146  onDestroy(): void {
147    if (this.setup) {
148      this.query(`drop table ${this.tableName('window')}`);
149      this.query(`drop table ${this.tableName('span')}`);
150      this.setup = false;
151    }
152  }
153}
154
155trackControllerRegistry.register(CpuSliceTrackController);
156