Home | History | Annotate | Download | only in chrome_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 {Config, Data, SLICE_TRACK_KIND} from './common';
     22 
     23 class ChromeSliceTrackController extends TrackController<Config, Data> {
     24   static readonly kind = SLICE_TRACK_KIND;
     25   private busy = false;
     26   private setup = false;
     27 
     28   onBoundsChange(start: number, end: number, resolution: number): void {
     29     this.update(start, end, resolution);
     30   }
     31 
     32   private async update(start: number, end: number, resolution: number) {
     33     // TODO: we should really call TraceProcessor.Interrupt() at this point.
     34     if (this.busy) return;
     35 
     36     const startNs = Math.round(start * 1e9);
     37     const endNs = Math.round(end * 1e9);
     38     // Ns in 1px width. We want all slices smaller than 1px to be grouped.
     39     const minNs = Math.round(resolution * 1e9);
     40     const LIMIT = 10000;
     41     this.busy = true;
     42 
     43     if (!this.setup) {
     44       await this.query(
     45           `create virtual table ${this.tableName('window')} using window;`);
     46 
     47       await this.query(
     48           `create view ${this.tableName('small')} as ` +
     49           `select ts,dur,depth,cat,name from slices ` +
     50           `where utid = ${this.config.utid} ` +
     51           `and ts >= ${startNs} - dur ` +
     52           `and ts <= ${endNs} ` +
     53           `and dur < ${minNs} ` +
     54           `order by ts ` +
     55           `limit ${LIMIT};`);
     56 
     57       await this.query(`create virtual table ${this.tableName('span')} using
     58       span_join(${this.tableName('small')},
     59       ${this.tableName('window')});`);
     60 
     61       this.setup = true;
     62     }
     63 
     64     const windowDurNs = Math.max(1, endNs - startNs);
     65 
     66     this.query(`update ${this.tableName('window')} set
     67     window_start=${startNs},
     68     window_dur=${windowDurNs},
     69     quantum=${minNs}`);
     70 
     71     await this.query(`drop view if exists ${this.tableName('small')}`);
     72     await this.query(`drop view if exists ${this.tableName('big')}`);
     73     await this.query(`drop view if exists ${this.tableName('summary')}`);
     74 
     75     await this.query(
     76         `create view ${this.tableName('small')} as ` +
     77         `select ts,dur,depth,cat,name from slices ` +
     78         `where utid = ${this.config.utid} ` +
     79         `and ts >= ${startNs} - dur ` +
     80         `and ts <= ${endNs} ` +
     81         `and dur < ${minNs} ` +
     82         `order by ts `);
     83 
     84     await this.query(
     85         `create view ${this.tableName('big')} as ` +
     86         `select ts,dur,depth,cat,name from slices ` +
     87         `where utid = ${this.config.utid} ` +
     88         `and ts >= ${startNs} - dur ` +
     89         `and ts <= ${endNs} ` +
     90         `and dur >= ${minNs} ` +
     91         `order by ts `);
     92 
     93     await this.query(`create view ${this.tableName('summary')} as select
     94       min(ts) as ts,
     95       ${minNs} as dur,
     96       depth,
     97       cat,
     98       'Busy' as name
     99       from ${this.tableName('span')}
    100       group by cat, depth, quantum_ts
    101       limit ${LIMIT};`);
    102 
    103     const query = `select * from ${this.tableName('summary')} UNION ` +
    104         `select * from ${this.tableName('big')} order by ts`;
    105 
    106     const rawResult = await this.query(query);
    107     this.busy = false;
    108 
    109     if (rawResult.error) {
    110       throw new Error(`Query error "${query}": ${rawResult.error}`);
    111     }
    112 
    113     const numRows = +rawResult.numRecords;
    114 
    115     const slices: Data = {
    116       start,
    117       end,
    118       resolution,
    119       strings: [],
    120       starts: new Float64Array(numRows),
    121       ends: new Float64Array(numRows),
    122       depths: new Uint16Array(numRows),
    123       titles: new Uint16Array(numRows),
    124       categories: new Uint16Array(numRows),
    125     };
    126 
    127     const stringIndexes = new Map<string, number>();
    128     function internString(str: string) {
    129       let idx = stringIndexes.get(str);
    130       if (idx !== undefined) return idx;
    131       idx = slices.strings.length;
    132       slices.strings.push(str);
    133       stringIndexes.set(str, idx);
    134       return idx;
    135     }
    136 
    137     for (let row = 0; row < numRows; row++) {
    138       const cols = rawResult.columns;
    139       const startSec = fromNs(+cols[0].longValues![row]);
    140       slices.starts[row] = startSec;
    141       slices.ends[row] = startSec + fromNs(+cols[1].longValues![row]);
    142       slices.depths[row] = +cols[2].longValues![row];
    143       slices.categories[row] = internString(cols[3].stringValues![row]);
    144       slices.titles[row] = internString(cols[4].stringValues![row]);
    145     }
    146     if (numRows === LIMIT) {
    147       slices.end = slices.ends[slices.ends.length - 1];
    148     }
    149     this.publish(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 
    162 
    163 trackControllerRegistry.register(ChromeSliceTrackController);
    164