• 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 {Engine} from '../common/engine';
16import {CurrentSearchResults, SearchSummary} from '../common/search_data';
17import {TimeSpan} from '../common/time';
18
19import {Controller} from './controller';
20import {App} from './globals';
21
22export interface SearchControllerArgs {
23  engine: Engine;
24  app: App;
25}
26
27
28export class SearchController extends Controller<'main'> {
29  private engine: Engine;
30  private app: App;
31  private previousSpan: TimeSpan;
32  private previousResolution: number;
33  private previousSearch: string;
34  private updateInProgress: boolean;
35  private setupInProgress: boolean;
36
37  constructor(args: SearchControllerArgs) {
38    super('main');
39    this.engine = args.engine;
40    this.app = args.app;
41    this.previousSpan = new TimeSpan(0, 1);
42    this.previousSearch = '';
43    this.updateInProgress = false;
44    this.setupInProgress = true;
45    this.previousResolution = 1;
46    this.setup().finally(() => {
47      this.setupInProgress = false;
48      this.run();
49    });
50  }
51
52  private async setup() {
53    await this.query(`create virtual table search_summary_window
54      using window;`);
55    await this.query(`create virtual table search_summary_sched_span using
56      span_join(sched PARTITIONED cpu, search_summary_window);`);
57    await this.query(`create virtual table search_summary_slice_span using
58      span_join(slice PARTITIONED track_id, search_summary_window);`);
59  }
60
61  run() {
62    if (this.setupInProgress || this.updateInProgress) {
63      return;
64    }
65
66    const visibleState = this.app.state.frontendLocalState.visibleState;
67    const omniboxState = this.app.state.frontendLocalState.omniboxState;
68    if (visibleState === undefined || omniboxState === undefined ||
69        omniboxState.mode === 'COMMAND') {
70      return;
71    }
72    const newSpan = new TimeSpan(visibleState.startSec, visibleState.endSec);
73    const newSearch = omniboxState.omnibox;
74    const newResolution = visibleState.resolution;
75    if (this.previousSpan.contains(newSpan) &&
76        this.previousResolution === newResolution &&
77        newSearch === this.previousSearch) {
78      return;
79    }
80    this.previousSpan = new TimeSpan(
81        Math.max(newSpan.start - newSpan.duration, 0),
82        newSpan.end + newSpan.duration);
83    this.previousResolution = newResolution;
84    this.previousSearch = newSearch;
85    if (newSearch === '' || newSearch.length < 4) {
86      this.app.publish('Search', {
87        tsStarts: new Float64Array(0),
88        tsEnds: new Float64Array(0),
89        count: new Uint8Array(0),
90      });
91      this.app.publish('SearchResult', {
92        sliceIds: new Float64Array(0),
93        tsStarts: new Float64Array(0),
94        utids: new Float64Array(0),
95        sources: [],
96        trackIds: [],
97        totalResults: 0,
98      });
99      return;
100    }
101
102    const startNs = Math.round(newSpan.start * 1e9);
103    const endNs = Math.round(newSpan.end * 1e9);
104    this.updateInProgress = true;
105    const computeSummary =
106        this.update(newSearch, startNs, endNs, newResolution).then(summary => {
107          this.app.publish('Search', summary);
108        });
109
110    const computeResults =
111        this.specificSearch(newSearch).then(searchResults => {
112          this.app.publish('SearchResult', searchResults);
113        });
114
115    Promise.all([computeSummary, computeResults])
116        .finally(() => {
117          this.updateInProgress = false;
118          this.run();
119        });
120  }
121
122  onDestroy() {}
123
124  private async update(
125      search: string, startNs: number, endNs: number,
126      resolution: number): Promise<SearchSummary> {
127    const quantumNs = Math.round(resolution * 10 * 1e9);
128
129    startNs = Math.floor(startNs / quantumNs) * quantumNs;
130
131    await this.query(`update search_summary_window set
132      window_start=${startNs},
133      window_dur=${endNs - startNs},
134      quantum=${quantumNs}
135      where rowid = 0;`);
136
137    const rawUtidResult = await this.query(`select utid from thread join process
138      using(upid) where thread.name like "%${search}%" or process.name like "%${
139        search}%"`);
140
141    const utids = [...rawUtidResult.columns[0].longValues!];
142
143    const cpus = await this.engine.getCpus();
144    const maxCpu = Math.max(...cpus, -1);
145
146    const rawResult = await this.query(`
147        select
148          (quantum_ts * ${quantumNs} + ${startNs})/1e9 as tsStart,
149          ((quantum_ts+1) * ${quantumNs} + ${startNs})/1e9 as tsEnd,
150          min(count(*), 255) as count
151          from (
152              select
153              quantum_ts
154              from search_summary_sched_span
155              where utid in (${utids.join(',')}) and cpu <= ${maxCpu}
156            union all
157              select
158              quantum_ts
159              from search_summary_slice_span
160              where name like '%${search}%'
161          )
162          group by quantum_ts
163          order by quantum_ts;`);
164
165    const numRows = +rawResult.numRecords;
166    const summary = {
167      tsStarts: new Float64Array(numRows),
168      tsEnds: new Float64Array(numRows),
169      count: new Uint8Array(numRows)
170    };
171
172    const columns = rawResult.columns;
173    for (let row = 0; row < numRows; row++) {
174      summary.tsStarts[row] = +columns[0].doubleValues![row];
175      summary.tsEnds[row] = +columns[1].doubleValues![row];
176      summary.count[row] = +columns[2].longValues![row];
177    }
178    return summary;
179  }
180
181  private async specificSearch(search: string) {
182    // TODO(hjd): we should avoid recomputing this every time. This will be
183    // easier once the track table has entries for all the tracks.
184    const cpuToTrackId = new Map();
185    const engineTrackIdToTrackId = new Map();
186    for (const track of Object.values(this.app.state.tracks)) {
187      if (track.kind === 'CpuSliceTrack') {
188        cpuToTrackId.set((track.config as {cpu: number}).cpu, track.id);
189        continue;
190      }
191      if (track.kind === 'ChromeSliceTrack' ||
192          track.kind === 'AsyncSliceTrack') {
193        engineTrackIdToTrackId.set(
194            (track.config as {trackId: number}).trackId, track.id);
195        continue;
196      }
197    }
198
199    const rawUtidResult = await this.query(`select utid from thread join process
200    using(upid) where thread.name like "%${search}%" or process.name like "%${
201        search}%"`);
202    const utids = [...rawUtidResult.columns[0].longValues!];
203
204    const rawResult = await this.query(`
205    select
206      id as slice_id,
207      ts,
208      'cpu' as source,
209      cpu as source_id,
210      utid
211    from sched where utid in (${utids.join(',')})
212    union
213    select
214      slice_id,
215      ts,
216      'track' as source,
217      track_id as source_id,
218      0 as utid
219      from slice
220      inner join track on slice.track_id = track.id
221      and slice.name like '%${search}%'
222    order by ts`);
223
224    const numRows = +rawResult.numRecords;
225
226    const searchResults: CurrentSearchResults = {
227      sliceIds: new Float64Array(numRows),
228      tsStarts: new Float64Array(numRows),
229      utids: new Float64Array(numRows),
230      trackIds: [],
231      sources: [],
232      totalResults: +numRows,
233    };
234
235    const columns = rawResult.columns;
236    for (let row = 0; row < numRows; row++) {
237      const source = columns[2].stringValues![row];
238      const sourceId = +columns[3].longValues![row];
239      let trackId = undefined;
240      if (source === 'cpu') {
241        trackId = cpuToTrackId.get(sourceId);
242      } else if (source === 'track') {
243        trackId = engineTrackIdToTrackId.get(sourceId);
244      }
245
246      if (trackId === undefined) {
247        searchResults.totalResults--;
248        continue;
249      }
250
251      searchResults.trackIds.push(trackId);
252      searchResults.sources.push(source);
253      searchResults.sliceIds[row] = +columns[0].longValues![row];
254      searchResults.tsStarts[row] = +columns[1].longValues![row];
255      searchResults.utids[row] = +columns[4].longValues![row];
256    }
257    return searchResults;
258  }
259
260
261  private async query(query: string) {
262    const result = await this.engine.query(query);
263    return result;
264  }
265}
266