• 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 {
17  LogBounds,
18  LogBoundsKey,
19  LogEntries,
20  LogEntriesKey,
21  LogExistsKey
22} from '../common/logs';
23import {NUM, STR} from '../common/query_result';
24import {fromNs, TimeSpan, toNsCeil, toNsFloor} from '../common/time';
25import {publishTrackData} from '../frontend/publish';
26
27import {Controller} from './controller';
28import {App} from './globals';
29
30async function updateLogBounds(
31    engine: Engine, span: TimeSpan): Promise<LogBounds> {
32  const vizStartNs = toNsFloor(span.start);
33  const vizEndNs = toNsCeil(span.end);
34
35  const countResult = await engine.query(`
36     select
37      ifnull(min(ts), 0) as minTs,
38      ifnull(max(ts), 0) as maxTs,
39      count(ts) as countTs
40     from android_logs where ts >= ${vizStartNs} and ts <= ${vizEndNs}`);
41
42  const countRow = countResult.firstRow({minTs: NUM, maxTs: NUM, countTs: NUM});
43
44  const firstRowNs = countRow.minTs;
45  const lastRowNs = countRow.maxTs;
46  const total = countRow.countTs;
47
48  const minResult = await engine.query(`
49     select ifnull(max(ts), 0) as maxTs from android_logs where ts < ${
50      vizStartNs}`);
51  const startNs = minResult.firstRow({maxTs: NUM}).maxTs;
52
53  const maxResult = await engine.query(`
54     select ifnull(min(ts), 0) as minTs from android_logs where ts > ${
55      vizEndNs}`);
56  const endNs = maxResult.firstRow({minTs: NUM}).minTs;
57
58  const trace = await engine.getTraceTimeBounds();
59  const startTs = startNs ? fromNs(startNs) : trace.start;
60  const endTs = endNs ? fromNs(endNs) : trace.end;
61  const firstRowTs = firstRowNs ? fromNs(firstRowNs) : endTs;
62  const lastRowTs = lastRowNs ? fromNs(lastRowNs) : startTs;
63  return {
64    startTs,
65    endTs,
66    firstRowTs,
67    lastRowTs,
68    total,
69  };
70}
71
72async function updateLogEntries(
73    engine: Engine, span: TimeSpan, pagination: Pagination):
74    Promise<LogEntries> {
75  const vizStartNs = toNsFloor(span.start);
76  const vizEndNs = toNsCeil(span.end);
77  const vizSqlBounds = `ts >= ${vizStartNs} and ts <= ${vizEndNs}`;
78
79  const rowsResult = await engine.query(`
80        select
81          ts,
82          prio,
83          ifnull(tag, '[NULL]') as tag,
84          ifnull(msg, '[NULL]') as msg
85        from android_logs
86        where ${vizSqlBounds}
87        order by ts
88        limit ${pagination.start}, ${pagination.count}
89    `);
90
91  const timestamps = [];
92  const priorities = [];
93  const tags = [];
94  const messages = [];
95
96  const it = rowsResult.iter({ts: NUM, prio: NUM, tag: STR, msg: STR});
97  for (; it.valid(); it.next()) {
98    timestamps.push(it.ts);
99    priorities.push(it.prio);
100    tags.push(it.tag);
101    messages.push(it.msg);
102  }
103
104  return {
105    offset: pagination.start,
106    timestamps,
107    priorities,
108    tags,
109    messages,
110  };
111}
112
113class Pagination {
114  private _offset: number;
115  private _count: number;
116
117  constructor(offset: number, count: number) {
118    this._offset = offset;
119    this._count = count;
120  }
121
122  get start() {
123    return this._offset;
124  }
125
126  get count() {
127    return this._count;
128  }
129
130  get end() {
131    return this._offset + this._count;
132  }
133
134  contains(other: Pagination): boolean {
135    return this.start <= other.start && other.end <= this.end;
136  }
137
138  grow(n: number): Pagination {
139    const newStart = Math.max(0, this.start - n / 2);
140    const newCount = this.count + n;
141    return new Pagination(newStart, newCount);
142  }
143}
144
145export interface LogsControllerArgs {
146  engine: Engine;
147  app: App;
148}
149
150/**
151 * LogsController looks at two parts of the state:
152 * 1. The visible trace window
153 * 2. The requested offset and count the log lines to display
154 * And keeps two bits of published information up to date:
155 * 1. The total number of log messages in visible range
156 * 2. The logs lines that should be displayed
157 */
158export class LogsController extends Controller<'main'> {
159  private app: App;
160  private engine: Engine;
161  private span: TimeSpan;
162  private pagination: Pagination;
163  private hasLogs = false;
164
165  constructor(args: LogsControllerArgs) {
166    super('main');
167    this.app = args.app;
168    this.engine = args.engine;
169    this.span = new TimeSpan(0, 10);
170    this.pagination = new Pagination(0, 0);
171    this.hasAnyLogs().then(exists => {
172      this.hasLogs = exists;
173      publishTrackData({
174        id: LogExistsKey,
175        data: {
176          exists,
177        },
178      });
179    });
180  }
181
182  async hasAnyLogs() {
183    const result = await this.engine.query(`
184      select count(*) as cnt from android_logs
185    `);
186    return result.firstRow({cnt: NUM}).cnt > 0;
187  }
188
189  run() {
190    if (!this.hasLogs) return;
191
192    const traceTime = this.app.state.frontendLocalState.visibleState;
193    const newSpan = new TimeSpan(traceTime.startSec, traceTime.endSec);
194    const oldSpan = this.span;
195
196    const pagination = this.app.state.logsPagination;
197    // This can occur when loading old traces.
198    // TODO(hjd): Fix the problem of accessing state from a previous version of
199    // the UI in a general way.
200    if (pagination === undefined) {
201      return;
202    }
203
204    const {offset, count} = pagination;
205    const requestedPagination = new Pagination(offset, count);
206    const oldPagination = this.pagination;
207
208    const needSpanUpdate = !oldSpan.equals(newSpan);
209    const needPaginationUpdate = !oldPagination.contains(requestedPagination);
210
211    // TODO(hjd): We could waste a lot of time queueing useless updates here.
212    // We should avoid enqueuing a request when one is in progress.
213    if (needSpanUpdate) {
214      this.span = newSpan;
215      updateLogBounds(this.engine, newSpan).then(data => {
216        if (!newSpan.equals(this.span)) return;
217        publishTrackData({
218          id: LogBoundsKey,
219          data,
220        });
221      });
222    }
223
224    // TODO(hjd): We could waste a lot of time queueing useless updates here.
225    // We should avoid enqueuing a request when one is in progress.
226    if (needSpanUpdate || needPaginationUpdate) {
227      this.pagination = requestedPagination.grow(100);
228
229      updateLogEntries(this.engine, newSpan, this.pagination).then(data => {
230        if (!this.pagination.contains(requestedPagination)) return;
231        publishTrackData({
232          id: LogEntriesKey,
233          data,
234        });
235      });
236    }
237
238    return [];
239  }
240}
241