• 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  CPU_FREQ_TRACK_KIND,
24  Data,
25} from './common';
26
27class CpuFreqTrackController extends TrackController<Config, Data> {
28  static readonly kind = CPU_FREQ_TRACK_KIND;
29  private busy = false;
30  private setup = false;
31  private maximumValueSeen = 0;
32
33  onBoundsChange(start: number, end: number, resolution: number): void {
34    this.update(start, end, resolution);
35  }
36
37  private async update(start: number, end: number, resolution: number):
38      Promise<void> {
39    // TODO: we should really call TraceProcessor.Interrupt() at this point.
40    if (this.busy) return;
41
42    const startNs = Math.round(start * 1e9);
43    const endNs = Math.round(end * 1e9);
44
45    this.busy = true;
46    if (!this.setup) {
47      const result = await this.query(`
48      select max(value) from
49        counters where name = 'cpufreq'
50        and ref = ${this.config.cpu}`);
51      this.maximumValueSeen = +result.columns[0].doubleValues![0];
52
53      await this.query(
54        `create virtual table ${this.tableName('window')} using window;`);
55
56      await this.query(`create view ${this.tableName('freq')}
57          as select
58            ts,
59            lead(ts) over (order by ts) - ts as dur,
60            ref as cpu,
61            name as freq_name,
62            value as freq_value
63          from counters
64          where name = 'cpufreq'
65            and ref = ${this.config.cpu}
66            and ref_type = 'cpu';
67      `);
68
69      await this.query(`create view ${this.tableName('idle')}
70        as select
71          ts,
72          lead(ts) over (order by ts) - ts as dur,
73          ref as cpu,
74          name as idle_name,
75          value as idle_value
76        from counters
77        where name = 'cpuidle'
78          and ref = ${this.config.cpu}
79          and ref_type = 'cpu';
80      `);
81
82      await this.query(`create virtual table ${this.tableName('freq_idle')}
83              using span_join(${this.tableName('freq')} PARTITIONED cpu,
84                              ${this.tableName('idle')} PARTITIONED cpu);`);
85
86      await this.query(`create virtual table ${this.tableName('span_activity')}
87      using span_join(${this.tableName('freq_idle')} PARTITIONED cpu,
88                      ${this.tableName('window')});`);
89
90      // TODO(taylori): Move the idle value processing to the TP.
91      await this.query(`create view ${this.tableName('activity')}
92      as select
93        ts,
94        dur,
95        quantum_ts,
96        cpu,
97        case idle_value
98          when 4294967295 then -1
99          else idle_value
100        end as idle,
101        freq_value as freq
102        from ${this.tableName('span_activity')};
103      `);
104
105      this.setup = true;
106    }
107
108    const isQuantized = this.shouldSummarize(resolution);
109    // |resolution| is in s/px we want # ns for 10px window:
110    const bucketSizeNs = Math.round(resolution * 10 * 1e9);
111    let windowStartNs = startNs;
112    if (isQuantized) {
113      windowStartNs = Math.floor(windowStartNs / bucketSizeNs) * bucketSizeNs;
114    }
115    const windowDurNs = Math.max(1, endNs - windowStartNs);
116
117    this.query(`update ${this.tableName('window')} set
118      window_start = ${startNs},
119      window_dur = ${windowDurNs},
120      quantum = ${isQuantized ? bucketSizeNs : 0}`);
121
122    // Cast as double to avoid problem where values are sometimes
123    // doubles, sometimes longs.
124    let query = `select ts, dur, cast(idle as DOUBLE), freq
125      from ${this.tableName('activity')}`;
126
127    if (isQuantized) {
128      query = `select
129        min(ts) as ts,
130        sum(dur) as dur,
131        case
132          when min(idle) = -1 then cast(-1 as DOUBLE)
133          else cast(0 as DOUBLE)
134        end as idle,
135        sum(weighted_freq)/sum(dur) as freq_avg,
136        quantum_ts
137        from (
138          select
139          ts,
140          dur,
141          quantum_ts,
142          freq*dur as weighted_freq,
143          idle
144          from ${this.tableName('activity')})
145        group by quantum_ts`;
146    }
147
148    const freqResult = await this.query(query);
149
150    const numRows = +freqResult.numRecords;
151    const data: Data = {
152      start,
153      end,
154      maximumValue: this.maximumValue(),
155      resolution,
156      isQuantized,
157      tsStarts: new Float64Array(numRows),
158      tsEnds: new Float64Array(numRows),
159      idles: new Int8Array(numRows),
160      freqKHz: new Uint32Array(numRows),
161    };
162
163    const cols = freqResult.columns;
164    for (let row = 0; row < numRows; row++) {
165      const startSec = fromNs(+cols[0].longValues![row]);
166      data.tsStarts[row] = startSec;
167      data.tsEnds[row] = startSec + fromNs(+cols[1].longValues![row]);
168      data.idles[row] = +cols[2].doubleValues![row];
169      data.freqKHz[row] = +cols[3].doubleValues![row];
170    }
171
172    this.publish(data);
173    this.busy = false;
174  }
175
176  private maximumValue() {
177    return Math.max(this.config.maximumValue || 0, this.maximumValueSeen);
178  }
179
180  private async query(query: string) {
181    const result = await this.engine.query(query);
182    if (result.error) {
183      console.error(`Query error "${query}": ${result.error}`);
184      throw new Error(`Query error "${query}": ${result.error}`);
185    }
186    return result;
187  }
188}
189
190
191trackControllerRegistry.register(CpuFreqTrackController);
192