• 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 {
16  AggregateData,
17  Column,
18  ColumnDef,
19  ThreadStateExtra,
20} from '../../common/aggregation_data';
21import {Engine} from '../../common/engine';
22import {slowlyCountRows} from '../../common/query_iterator';
23import {Area, Sorting} from '../../common/state';
24import {Controller} from '../controller';
25import {globals} from '../globals';
26
27export interface AggregationControllerArgs {
28  engine: Engine;
29  kind: string;
30}
31
32export abstract class AggregationController extends Controller<'main'> {
33  readonly kind: string;
34  private previousArea?: Area;
35  private previousSorting?: Sorting;
36  private requestingData = false;
37  private queuedRequest = false;
38
39  abstract async createAggregateView(engine: Engine, area: Area):
40      Promise<boolean>;
41
42  abstract async getExtra(engine: Engine, area: Area):
43      Promise<ThreadStateExtra|void>;
44
45  abstract getTabName(): string;
46  abstract getDefaultSorting(): Sorting;
47  abstract getColumnDefinitions(): ColumnDef[];
48
49  constructor(private args: AggregationControllerArgs) {
50    super('main');
51    this.kind = this.args.kind;
52  }
53
54  run() {
55    const selection = globals.state.currentSelection;
56    if (selection === null || selection.kind !== 'AREA') {
57      globals.publish('AggregateData', {
58        data: {
59          tabName: this.getTabName(),
60          columns: [],
61          strings: [],
62          columnSums: [],
63        },
64        kind: this.args.kind
65      });
66      return;
67    }
68    const selectedArea = globals.state.areas[selection.areaId];
69    const aggregatePreferences =
70        globals.state.aggregatePreferences[this.args.kind];
71
72    const areaChanged = this.previousArea !== selectedArea;
73    const sortingChanged = aggregatePreferences &&
74        this.previousSorting !== aggregatePreferences.sorting;
75    if (!areaChanged && !sortingChanged) return;
76
77    if (this.requestingData) {
78      this.queuedRequest = true;
79    } else {
80      this.requestingData = true;
81      if (sortingChanged) this.previousSorting = aggregatePreferences.sorting;
82      if (areaChanged) this.previousArea = Object.assign({}, selectedArea);
83      this.getAggregateData(selectedArea, areaChanged)
84          .then(
85              data => globals.publish(
86                  'AggregateData', {data, kind: this.args.kind}))
87          .finally(() => {
88            this.requestingData = false;
89            if (this.queuedRequest) {
90              this.queuedRequest = false;
91              this.run();
92            }
93          });
94    }
95  }
96
97  async getAggregateData(area: Area, areaChanged: boolean):
98      Promise<AggregateData> {
99    if (areaChanged) {
100      const viewExists = await this.createAggregateView(this.args.engine, area);
101      if (!viewExists) {
102        return {
103          tabName: this.getTabName(),
104          columns: [],
105          strings: [],
106          columnSums: [],
107        };
108      }
109    }
110
111    const defs = this.getColumnDefinitions();
112    const colIds = defs.map(col => col.columnId);
113    const pref = globals.state.aggregatePreferences[this.kind];
114    let sorting = `${this.getDefaultSorting().column} ${
115        this.getDefaultSorting().direction}`;
116    if (pref && pref.sorting) {
117      sorting = `${pref.sorting.column} ${pref.sorting.direction}`;
118    }
119    const query = `select ${colIds} from ${this.kind} order by ${sorting}`;
120    const result = await this.args.engine.query(query);
121
122    const numRows = slowlyCountRows(result);
123    const columns = defs.map(def => this.columnFromColumnDef(def, numRows));
124    const columnSums = await Promise.all(defs.map(def => this.getSum(def)));
125    const extraData = await this.getExtra(this.args.engine, area);
126    const extra = extraData ? extraData : undefined;
127    const data: AggregateData =
128        {tabName: this.getTabName(), columns, columnSums, strings: [], extra};
129
130    const stringIndexes = new Map<string, number>();
131    function internString(str: string) {
132      let idx = stringIndexes.get(str);
133      if (idx !== undefined) return idx;
134      idx = data.strings.length;
135      data.strings.push(str);
136      stringIndexes.set(str, idx);
137      return idx;
138    }
139
140    for (let row = 0; row < numRows; row++) {
141      const cols = result.columns;
142      for (let col = 0; col < result.columns.length; col++) {
143        if (cols[col].stringValues && cols[col].stringValues!.length > 0) {
144          data.columns[col].data[row] =
145              internString(cols[col].stringValues![row]);
146        } else if (cols[col].longValues && cols[col].longValues!.length > 0) {
147          data.columns[col].data[row] = cols[col].longValues![row];
148        } else if (
149            cols[col].doubleValues && cols[col].doubleValues!.length > 0) {
150          data.columns[col].data[row] = cols[col].doubleValues![row];
151        }
152      }
153    }
154    return data;
155  }
156
157  async getSum(def: ColumnDef): Promise<string> {
158    if (!def.sum) return '';
159    const result = await this.args.engine.queryOneRow(
160        `select sum(${def.columnId}) from ${this.kind}`);
161    let sum = result[0];
162    if (def.kind === 'TIMESTAMP_NS') {
163      sum = sum / 1e6;
164    }
165    return `${sum}`;
166  }
167
168  columnFromColumnDef(def: ColumnDef, numRows: number): Column {
169    // TODO(hjd): The Column type should be based on the
170    // ColumnDef type or vice versa to avoid this cast.
171    return {
172      title: def.title,
173      kind: def.kind,
174      data: new def.columnConstructor(numRows),
175      columnId: def.columnId,
176    } as Column;
177  }
178}
179