• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
15import {fromNs} from '../../common/time';
16import {
17  TrackController,
18  trackControllerRegistry
19} from '../../controller/track_controller';
20
21import {
22  Config,
23  COUNTER_TRACK_KIND,
24  Data,
25} from './common';
26
27class CounterTrackController extends TrackController<Config, Data> {
28  static readonly kind = COUNTER_TRACK_KIND;
29  private busy = false;
30  private setup = false;
31  private maximumValueSeen = 0;
32  private minimumValueSeen = 0;
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) {
48      const result = await this.query(`
49      select max(value), min(value) from
50        counters where name = '${this.config.name}'
51        and ref = ${this.config.ref}`);
52      this.maximumValueSeen = +result.columns[0].doubleValues![0];
53      this.minimumValueSeen = +result.columns[1].doubleValues![0];
54      await this.query(
55        `create virtual table ${this.tableName('window')} using window;`);
56
57      await this.query(`create view ${this.tableName('counter_view')} as
58        select ts,
59        lead(ts) over (partition by ref_type order by ts) - ts as dur,
60        value, name, ref
61        from counters
62        where name = '${this.config.name}' and ref = ${this.config.ref};`);
63
64      await this.query(`create virtual table ${this.tableName('span')} using
65        span_join(${this.tableName('counter_view')},
66        ${this.tableName('window')});`);
67      this.setup = true;
68    }
69
70    const isQuantized = this.shouldSummarize(resolution);
71    // |resolution| is in s/px we want # ns for 10px window:
72    const bucketSizeNs = Math.round(resolution * 10 * 1e9);
73    let windowStartNs = startNs;
74    if (isQuantized) {
75      windowStartNs = Math.floor(windowStartNs / bucketSizeNs) * bucketSizeNs;
76    }
77    const windowDurNs = Math.max(1, endNs - windowStartNs);
78
79    this.query(`update ${this.tableName('window')} set
80    window_start=${windowStartNs},
81    window_dur=${windowDurNs},
82    quantum=${isQuantized ? bucketSizeNs : 0}`);
83
84    let query = `select min(ts) as ts,
85      max(value) as value
86      from ${this.tableName('span')}
87      group by quantum_ts;`;
88
89    if (!isQuantized) {
90      // TODO(hjd): Implement window clipping.
91      query = `select ts, value
92        from (select
93          ts,
94          lead(ts) over (partition by ref_type order by ts) as ts_end,
95          value
96          from counters
97          where name = '${this.config.name}' and ref = ${this.config.ref})
98      where ts <= ${endNs} and ${startNs} <= ts_end;`;
99    }
100
101    const rawResult = await this.query(query);
102
103    const numRows = +rawResult.numRecords;
104
105    const data: Data = {
106      start,
107      end,
108      isQuantized,
109      maximumValue: this.maximumValue(),
110      minimumValue: this.minimumValue(),
111      resolution,
112      timestamps: new Float64Array(numRows),
113      values: new Float64Array(numRows),
114    };
115
116    const cols = rawResult.columns;
117    for (let row = 0; row < numRows; row++) {
118      const startSec = fromNs(+cols[0].longValues![row]);
119      const value = +cols[1].doubleValues![row];
120      data.timestamps[row] = startSec;
121      data.values[row] = value;
122    }
123
124    this.publish(data);
125    this.busy = false;
126  }
127
128  private maximumValue() {
129    return Math.max(this.config.maximumValue || 0, this.maximumValueSeen);
130  }
131
132  private minimumValue() {
133    return Math.min(this.config.minimumValue || 0, this.minimumValueSeen);
134  }
135
136  private async query(query: string) {
137    const result = await this.engine.query(query);
138    if (result.error) {
139      console.error(`Query error "${query}": ${result.error}`);
140      throw new Error(`Query error "${query}": ${result.error}`);
141    }
142    return result;
143  }
144}
145
146trackControllerRegistry.register(CounterTrackController);
147