Home | History | Annotate | Download | only in process_summary
      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   Data,
     24   PROCESS_SUMMARY_TRACK,
     25 } from './common';
     26 
     27 class ProcessSummaryTrackController extends TrackController<Config, Data> {
     28   static readonly kind = PROCESS_SUMMARY_TRACK;
     29   private busy = false;
     30   private setup = false;
     31 
     32   onBoundsChange(start: number, end: number, resolution: number): void {
     33     this.update(start, end, resolution);
     34   }
     35 
     36   private async update(start: number, end: number, resolution: number):
     37       Promise<void> {
     38     // TODO: we should really call TraceProcessor.Interrupt() at this point.
     39     if (this.busy) return;
     40     this.busy = true;
     41 
     42     const startNs = Math.round(start * 1e9);
     43     const endNs = Math.round(end * 1e9);
     44 
     45     if (this.setup === false) {
     46       await this.query(
     47           `create virtual table ${this.tableName('window')} using window;`);
     48 
     49       let utids = [this.config.utid];
     50       if (this.config.upid) {
     51         const threadQuery = await this.query(
     52             `select utid from thread where upid=${this.config.upid}`);
     53         utids = threadQuery.columns[0].longValues! as number[];
     54       }
     55 
     56       const processSliceView = this.tableName('process_slice_view');
     57       await this.query(
     58           `create view ${processSliceView} as ` +
     59           // 0 as cpu is a dummy column to perform span join on.
     60           `select ts, dur/${utids.length} as dur ` +
     61           `from slices where depth = 0 and utid in ` +
     62           // TODO(dproy): This query is faster if we write it as x < utid < y.
     63           `(${utids.join(',')})`);
     64       await this.query(`create virtual table ${this.tableName('span')}
     65           using span_join(${processSliceView},
     66                           ${this.tableName('window')});`);
     67       this.setup = true;
     68     }
     69 
     70     // |resolution| is in s/px we want # ns for 10px window:
     71     const bucketSizeNs = Math.round(resolution * 10 * 1e9);
     72     const windowStartNs = Math.floor(startNs / bucketSizeNs) * bucketSizeNs;
     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=${bucketSizeNs}
     79       where rowid = 0;`);
     80 
     81     this.publish(await this.computeSummary(
     82         fromNs(windowStartNs), end, resolution, bucketSizeNs));
     83     this.busy = false;
     84   }
     85 
     86   private async computeSummary(
     87       start: number, end: number, resolution: number,
     88       bucketSizeNs: number): Promise<Data> {
     89     const startNs = Math.round(start * 1e9);
     90     const endNs = Math.round(end * 1e9);
     91     const numBuckets = Math.ceil((endNs - startNs) / bucketSizeNs);
     92 
     93     const query = `select
     94       quantum_ts as bucket,
     95       sum(dur)/cast(${bucketSizeNs} as float) as utilization
     96       from ${this.tableName('span')}
     97       group by quantum_ts`;
     98 
     99     const rawResult = await this.query(query);
    100     const numRows = +rawResult.numRecords;
    101 
    102     const summary: Data = {
    103       start,
    104       end,
    105       resolution,
    106       bucketSizeSeconds: fromNs(bucketSizeNs),
    107       utilizations: new Float64Array(numBuckets),
    108     };
    109     const cols = rawResult.columns;
    110     for (let row = 0; row < numRows; row++) {
    111       const bucket = +cols[0].longValues![row];
    112       summary.utilizations[bucket] = +cols[1].doubleValues![row];
    113     }
    114     return summary;
    115   }
    116 
    117   // TODO(dproy); Dedup with other controllers.
    118   private async query(query: string) {
    119     const result = await this.engine.query(query);
    120     if (result.error) {
    121       console.error(`Query error "${query}": ${result.error}`);
    122       throw new Error(`Query error "${query}": ${result.error}`);
    123     }
    124     return result;
    125   }
    126 
    127   onDestroy(): void {
    128     if (this.setup) {
    129       this.query(`drop table ${this.tableName('window')}`);
    130       this.query(`drop table ${this.tableName('span')}`);
    131       this.setup = false;
    132     }
    133   }
    134 }
    135 
    136 trackControllerRegistry.register(ProcessSummaryTrackController);
    137