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