• 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 {fromNs} from '../../common/time';
16import {
17  TrackController,
18  trackControllerRegistry
19} from '../../controller/track_controller';
20
21import {
22  Config,
23  Data,
24  PROCESS_SCHEDULING_TRACK_KIND,
25  SliceData,
26  SummaryData
27} from './common';
28
29class ProcessSchedulingTrackController extends TrackController<Config, Data> {
30  static readonly kind = PROCESS_SCHEDULING_TRACK_KIND;
31  private busy = false;
32  private setup = false;
33  private numCpus = 0;
34
35  onBoundsChange(start: number, end: number, resolution: number): void {
36    this.update(start, end, resolution);
37  }
38
39  private async update(start: number, end: number, resolution: number):
40      Promise<void> {
41    // TODO: we should really call TraceProcessor.Interrupt() at this point.
42    if (this.busy) return;
43
44    if (!this.config.upid) {
45      return;
46    }
47
48    const startNs = Math.round(start * 1e9);
49    const endNs = Math.round(end * 1e9);
50
51    this.busy = true;
52    if (this.setup === false) {
53      await this.query(
54          `create virtual table ${this.tableName('window')} using window;`);
55      await this.query(`create view ${this.tableName('process')} as
56          select ts, dur, cpu, utid, upid from sched join (
57          select utid, upid from thread
58            where utid != 0 and upid = ${this.config.upid}) using(utid);`);
59      await this.query(`create virtual table ${this.tableName('span')}
60              using span_join(${this.tableName('process')} PARTITIONED cpu,
61                              ${this.tableName('window')});`);
62      this.numCpus = await this.engine.getNumberOfCpus();
63      this.setup = true;
64    }
65
66    const isQuantized = this.shouldSummarize(resolution);
67    // |resolution| is in s/px we want # ns for 20px window:
68    const bucketSizeNs = Math.round(resolution * 10 * 1e9 * 1.5);
69    let windowStartNs = startNs;
70    if (isQuantized) {
71      windowStartNs = Math.floor(windowStartNs / bucketSizeNs) * bucketSizeNs;
72    }
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=${isQuantized ? bucketSizeNs : 0}
79      where rowid = 0;`);
80
81    if (isQuantized) {
82      this.publish(await this.computeSummary(
83          fromNs(windowStartNs), end, resolution, bucketSizeNs));
84    } else {
85      this.publish(
86          await this.computeSlices(fromNs(windowStartNs), end, resolution));
87    }
88    this.busy = false;
89  }
90
91  private async computeSummary(
92      start: number, end: number, resolution: number,
93      bucketSizeNs: number): Promise<SummaryData> {
94    const startNs = Math.round(start * 1e9);
95    const endNs = Math.round(end * 1e9);
96    const numBuckets = Math.ceil((endNs - startNs) / bucketSizeNs);
97
98    // cpu < numCpus improves perfomance a lot since the window table can
99    // avoid generating many rows.
100    const query = `select
101        quantum_ts as bucket,
102        sum(dur)/cast(${bucketSizeNs * this.numCpus} as float) as utilization
103        from ${this.tableName('span')}
104        where upid = ${this.config.upid}
105        and utid != 0
106        and cpu < ${this.numCpus}
107        group by quantum_ts`;
108
109    const rawResult = await this.query(query);
110    const numRows = +rawResult.numRecords;
111
112    const summary: Data = {
113      kind: 'summary',
114      start,
115      end,
116      resolution,
117      bucketSizeSeconds: fromNs(bucketSizeNs),
118      utilizations: new Float64Array(numBuckets),
119    };
120    const cols = rawResult.columns;
121    for (let row = 0; row < numRows; row++) {
122      const bucket = +cols[0].longValues![row];
123      summary.utilizations[bucket] = +cols[1].doubleValues![row];
124    }
125    return summary;
126  }
127
128  private async computeSlices(start: number, end: number, resolution: number):
129      Promise<SliceData> {
130    // TODO(hjd): Remove LIMIT
131    const LIMIT = 10000;
132
133    // cpu < numCpus improves perfomance a lot since the window table can
134    // avoid generating many rows.
135    const query = `select ts,dur,cpu,utid from ${this.tableName('span')}
136        join
137        (select utid from thread where upid = ${this.config.upid})
138        using(utid)
139        where
140        cpu < ${this.numCpus}
141        order by
142        cpu,
143        ts
144        limit ${LIMIT};`;
145    const rawResult = await this.query(query);
146
147    const numRows = +rawResult.numRecords;
148    if (numRows === LIMIT) {
149      console.warn(`More than ${LIMIT} rows returned.`);
150    }
151    const slices: SliceData = {
152      kind: 'slice',
153      start,
154      end,
155      resolution,
156      numCpus: this.numCpus,
157      starts: new Float64Array(numRows),
158      ends: new Float64Array(numRows),
159      cpus: new Uint32Array(numRows),
160      utids: new Uint32Array(numRows),
161    };
162
163    const cols = rawResult.columns;
164    for (let row = 0; row < numRows; row++) {
165      const startSec = fromNs(+cols[0].longValues![row]);
166      slices.starts[row] = startSec;
167      slices.ends[row] = startSec + fromNs(+cols[1].longValues![row]);
168      slices.cpus[row] = +cols[2].longValues![row];
169      slices.utids[row] = +cols[3].longValues![row];
170      slices.end = Math.max(slices.ends[row], slices.end);
171    }
172    return slices;
173  }
174
175  private async query(query: string) {
176    const result = await this.engine.query(query);
177    if (result.error) {
178      console.error(`Query error "${query}": ${result.error}`);
179      throw new Error(`Query error "${query}": ${result.error}`);
180    }
181    return result;
182  }
183
184  onDestroy(): void {
185    if (this.setup) {
186      this.query(`drop table ${this.tableName('window')}`);
187      this.query(`drop table ${this.tableName('span')}`);
188      this.setup = false;
189    }
190  }
191}
192
193trackControllerRegistry.register(ProcessSchedulingTrackController);
194