// Copyright (C) 2019 The Android Open Source Project
|
//
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
// you may not use this file except in compliance with the License.
|
// You may obtain a copy of the License at
|
//
|
// http://www.apache.org/licenses/LICENSE-2.0
|
//
|
// Unless required by applicable law or agreed to in writing, software
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// See the License for the specific language governing permissions and
|
// limitations under the License.
|
|
import {Engine} from '../common/engine';
|
import {
|
LogBounds,
|
LogBoundsKey,
|
LogEntries,
|
LogEntriesKey,
|
LogExistsKey
|
} from '../common/logs';
|
import {fromNs, TimeSpan} from '../common/time';
|
|
import {Controller} from './controller';
|
import {App} from './globals';
|
|
async function updateLogBounds(
|
engine: Engine, span: TimeSpan): Promise<LogBounds> {
|
const vizStartNs = Math.floor(span.start * 1e9);
|
const vizEndNs = Math.ceil(span.end * 1e9);
|
|
const countResult = await engine.queryOneRow(`
|
select min(ts), max(ts), count(ts)
|
from android_logs where ts >= ${vizStartNs} and ts <= ${vizEndNs}`);
|
|
const firstRowNs = countResult[0];
|
const lastRowNs = countResult[1];
|
const total = countResult[2];
|
|
const minResult = await engine.queryOneRow(`
|
select max(ts) from android_logs where ts < ${vizStartNs}`);
|
const startNs = minResult[0];
|
|
const maxResult = await engine.queryOneRow(`
|
select min(ts) from android_logs where ts > ${vizEndNs}`);
|
const endNs = maxResult[0];
|
|
const trace = await engine.getTraceTimeBounds();
|
const startTs = startNs ? fromNs(startNs) : trace.start;
|
const endTs = endNs ? fromNs(endNs) : trace.end;
|
const firstRowTs = firstRowNs ? fromNs(firstRowNs) : endTs;
|
const lastRowTs = lastRowNs ? fromNs(lastRowNs) : startTs;
|
return {
|
startTs,
|
endTs,
|
firstRowTs,
|
lastRowTs,
|
total,
|
};
|
}
|
|
async function updateLogEntries(
|
engine: Engine, span: TimeSpan, pagination: Pagination):
|
Promise<LogEntries> {
|
const vizStartNs = Math.floor(span.start * 1e9);
|
const vizEndNs = Math.ceil(span.end * 1e9);
|
const vizSqlBounds = `ts >= ${vizStartNs} and ts <= ${vizEndNs}`;
|
|
const rowsResult =
|
await engine.query(`select ts, prio, tag, msg from android_logs
|
where ${vizSqlBounds}
|
order by ts
|
limit ${pagination.start}, ${pagination.count}`);
|
|
if (!rowsResult.numRecords) {
|
return {
|
offset: pagination.start,
|
timestamps: [],
|
priorities: [],
|
tags: [],
|
messages: [],
|
};
|
}
|
|
const timestamps = rowsResult.columns[0].longValues! as number[];
|
const priorities = rowsResult.columns[1].longValues! as number[];
|
const tags = rowsResult.columns[2].stringValues!;
|
const messages = rowsResult.columns[3].stringValues!;
|
|
return {
|
offset: pagination.start,
|
timestamps,
|
priorities,
|
tags,
|
messages,
|
};
|
}
|
|
class Pagination {
|
private _offset: number;
|
private _count: number;
|
|
constructor(offset: number, count: number) {
|
this._offset = offset;
|
this._count = count;
|
}
|
|
get start() {
|
return this._offset;
|
}
|
|
get count() {
|
return this._count;
|
}
|
|
get end() {
|
return this._offset + this._count;
|
}
|
|
contains(other: Pagination): boolean {
|
return this.start <= other.start && other.end <= this.end;
|
}
|
|
grow(n: number): Pagination {
|
const newStart = Math.max(0, this.start - n / 2);
|
const newCount = this.count + n;
|
return new Pagination(newStart, newCount);
|
}
|
}
|
|
export interface LogsControllerArgs {
|
engine: Engine;
|
app: App;
|
}
|
|
/**
|
* LogsController looks at two parts of the state:
|
* 1. The visible trace window
|
* 2. The requested offset and count the log lines to display
|
* And keeps two bits of published information up to date:
|
* 1. The total number of log messages in visible range
|
* 2. The logs lines that should be displayed
|
*/
|
export class LogsController extends Controller<'main'> {
|
private app: App;
|
private engine: Engine;
|
private span: TimeSpan;
|
private pagination: Pagination;
|
|
constructor(args: LogsControllerArgs) {
|
super('main');
|
this.app = args.app;
|
this.engine = args.engine;
|
this.span = new TimeSpan(0, 10);
|
this.pagination = new Pagination(0, 0);
|
this.publishHasAnyLogs();
|
}
|
|
async publishHasAnyLogs() {
|
const result = await this.engine.queryOneRow(`
|
select count(*) from android_logs
|
`);
|
const exists = result[0] > 0;
|
this.app.publish('TrackData', {
|
id: LogExistsKey,
|
data: {
|
exists,
|
},
|
});
|
}
|
|
run() {
|
const traceTime = this.app.state.frontendLocalState.visibleTraceTime;
|
const newSpan = new TimeSpan(traceTime.startSec, traceTime.endSec);
|
const oldSpan = this.span;
|
|
const pagination = this.app.state.logsPagination;
|
// This can occur when loading old traces.
|
// TODO(hjd): Fix the problem of accessing state from a previous version of
|
// the UI in a general way.
|
if (pagination === undefined) {
|
return;
|
}
|
|
const {offset, count} = pagination;
|
const requestedPagination = new Pagination(offset, count);
|
const oldPagination = this.pagination;
|
|
const needSpanUpdate = !oldSpan.equals(newSpan);
|
const needPaginationUpdate = !oldPagination.contains(requestedPagination);
|
|
// TODO(hjd): We could waste a lot of time queueing useless updates here.
|
// We should avoid enqueuing a request when one is in progress.
|
if (needSpanUpdate) {
|
this.span = newSpan;
|
updateLogBounds(this.engine, newSpan).then(data => {
|
if (!newSpan.equals(this.span)) return;
|
this.app.publish('TrackData', {
|
id: LogBoundsKey,
|
data,
|
});
|
});
|
}
|
|
// TODO(hjd): We could waste a lot of time queueing useless updates here.
|
// We should avoid enqueuing a request when one is in progress.
|
if (needSpanUpdate || needPaginationUpdate) {
|
this.pagination = requestedPagination.grow(100);
|
|
updateLogEntries(this.engine, newSpan, this.pagination).then(data => {
|
if (!this.pagination.contains(requestedPagination)) return;
|
this.app.publish('TrackData', {
|
id: LogEntriesKey,
|
data,
|
});
|
});
|
}
|
|
return [];
|
}
|
}
|