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