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 {assertFalse} from '../../base/logging';
16import {
17  iter,
18  NUM,
19  NUM_NULL,
20  slowlyCountRows,
21  STR_NULL
22} from '../../common/query_iterator';
23import {translateState} from '../../common/thread_state';
24import {fromNs, toNs} from '../../common/time';
25import {
26  TrackController,
27  trackControllerRegistry
28} from '../../controller/track_controller';
29
30import {
31  Config,
32  Data,
33  THREAD_STATE_TRACK_KIND,
34} from './common';
35
36class ThreadStateTrackController extends TrackController<Config, Data> {
37  static readonly kind = THREAD_STATE_TRACK_KIND;
38
39  private maxDurNs = 0;
40
41  async onSetup() {
42    await this.query(`
43      create view ${this.tableName('thread_state')} as
44      select
45        id,
46        ts,
47        dur,
48        cpu,
49        state,
50        io_wait
51      from thread_state
52      where utid = ${this.config.utid} and utid != 0
53    `);
54
55    const rawResult = await this.query(`
56      select max(dur)
57      from ${this.tableName('thread_state')}
58    `);
59    this.maxDurNs = rawResult.columns[0].longValues![0];
60  }
61
62  async onBoundsChange(start: number, end: number, resolution: number):
63      Promise<Data> {
64    const resolutionNs = toNs(resolution);
65    const startNs = toNs(start);
66    const endNs = toNs(end);
67
68    // ns per quantization bucket (i.e. ns per pixel). /2 * 2 is to force it to
69    // be an even number, so we can snap in the middle.
70    const bucketNs =
71        Math.max(Math.round(resolutionNs * this.pxSize() / 2) * 2, 1);
72
73    const query = `
74      select
75        (ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs} as tsq,
76        ts,
77        max(dur) as dur,
78        cast(cpu as integer) as cpu,
79        state,
80        io_wait,
81        id
82      from ${this.tableName('thread_state')}
83      where
84        ts >= ${startNs - this.maxDurNs} and
85        ts <= ${endNs}
86      group by tsq, state, io_wait
87      order by tsq, state, io_wait
88    `;
89
90    const result = await this.query(query);
91    const numRows = slowlyCountRows(result);
92
93    const data: Data = {
94      start,
95      end,
96      resolution,
97      length: numRows,
98      ids: new Float64Array(numRows),
99      starts: new Float64Array(numRows),
100      ends: new Float64Array(numRows),
101      strings: [],
102      state: new Uint16Array(numRows),
103      cpu: new Int8Array(numRows),
104    };
105
106    const stringIndexes =
107        new Map<{shortState: string, ioWait: boolean | undefined}, number>();
108    function internState(shortState: string, ioWait: boolean|undefined) {
109      let idx = stringIndexes.get({shortState, ioWait});
110      if (idx !== undefined) return idx;
111      idx = data.strings.length;
112      data.strings.push(translateState(shortState, ioWait));
113      stringIndexes.set({shortState, ioWait}, idx);
114      return idx;
115    }
116    iter(
117        {
118          'ts': NUM,
119          'dur': NUM,
120          'cpu': NUM_NULL,
121          'state': STR_NULL,
122          'io_wait': NUM_NULL,
123          'id': NUM_NULL,
124        },
125        result);
126    for (let row = 0; row < numRows; row++) {
127      const cols = result.columns;
128      const startNsQ = +cols[0].longValues![row];
129      const startNs = +cols[1].longValues![row];
130      const durNs = +cols[2].longValues![row];
131      const endNs = startNs + durNs;
132
133      let endNsQ = Math.floor((endNs + bucketNs / 2 - 1) / bucketNs) * bucketNs;
134      endNsQ = Math.max(endNsQ, startNsQ + bucketNs);
135
136      const cpu = cols[3].isNulls![row] ? -1 : cols[3].longValues![row];
137      const state = cols[4].stringValues![row];
138      const ioWait =
139          cols[5].isNulls![row] ? undefined : !!cols[5].longValues![row];
140      const id = cols[6].isNulls![row] ? -1 : cols[6].longValues![row];
141
142      // We should never have the end timestamp being the same as the bucket
143      // start.
144      assertFalse(startNsQ === endNsQ);
145
146      data.starts[row] = fromNs(startNsQ);
147      data.ends[row] = fromNs(endNsQ);
148      data.state[row] = internState(state, ioWait);
149      data.ids[row] = id;
150      data.cpu[row] = cpu;
151    }
152    return data;
153  }
154
155  async onDestroy() {
156    await this.query(`drop table if exists ${this.tableName('thread_state')}`);
157  }
158}
159
160trackControllerRegistry.register(ThreadStateTrackController);
161