blob: f226fc470e3b33c4aed0ba42aae0f9c4180de035 [file]
// Copyright (C) 2024 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 m from 'mithril';
import {LONG, NUM, STR, STR_NULL} from '../../trace_processor/query_result';
import type {Trace} from '../../public/trace';
import {SliceTrack} from '../../components/tracks/slice_track';
import {SourceDataset} from '../../trace_processor/dataset';
import {makeColorScheme} from '../../components/colorizer';
import {HSLColor} from '../../base/color';
import {Section} from '../../widgets/section';
import {Tree, TreeNode} from '../../widgets/tree';
import {Timestamp} from '../../components/widgets/timestamp';
import {Time} from '../../base/time';
import {DetailsShell} from '../../widgets/details_shell';
import {GridLayout, GridLayoutColumn} from '../../widgets/grid_layout';
import {Spinner} from '../../widgets/spinner';
import type {ColorScheme} from '../../base/color_scheme';
const PRIO_TO_COLOR: Record<number, ColorScheme> = {
2: makeColorScheme(new HSLColor({h: 0, s: 0, l: 60})), // V - grey
3: makeColorScheme(new HSLColor({h: 217, s: 100, l: 60})), // D - blue
4: makeColorScheme(new HSLColor({h: 120, s: 62, l: 55})), // I - green
5: makeColorScheme(new HSLColor({h: 38, s: 100, l: 58})), // W - amber
6: makeColorScheme(new HSLColor({h: 0, s: 86, l: 60})), // E - red
7: makeColorScheme(new HSLColor({h: 268, s: 100, l: 65})), // F - magenta
};
const DEFAULT_PRIO_COLOR = makeColorScheme(new HSLColor({h: 0, s: 0, l: 60}));
const EVT_PX = 6; // Width of an event tick in pixels.
const LOGS_SQL = `
select
id,
ts,
prio,
utid,
tag,
msg,
CASE
WHEN prio <= 3 THEN 0
WHEN prio = 4 THEN 1
WHEN prio = 5 THEN 2
WHEN prio = 6 THEN 3
WHEN prio = 7 THEN 4
ELSE -1
END as depth
from android_logs
order by ts
`;
const LOGS_SCHEMA = {
id: NUM,
ts: LONG,
prio: NUM,
utid: NUM,
depth: NUM,
tag: STR_NULL,
msg: STR_NULL,
};
const PRIO_LABELS: Record<number, string> = {
2: 'Verbose',
3: 'Debug',
4: 'Info',
5: 'Warn',
6: 'Error',
7: 'Fatal',
};
function makeDetailsPanel(
trace: Trace,
row: {
id: number;
ts: bigint;
prio: number;
tag: string | null;
utid: number;
},
) {
// The msg is initially undefined, it'll be filled in when it loads
let msg: string | undefined;
let threadInfo: string | undefined;
// Quickly load the log message
trace.engine
.query(`select msg from android_logs where id = ${row.id}`)
.then((result) => {
msg = result.maybeFirstRow({msg: STR})?.msg;
});
trace.engine
.query(`select tid, name from thread where utid = ${row.utid}`)
.then((result) => {
const r = result.maybeFirstRow({tid: NUM, name: STR_NULL});
threadInfo = r
? r.name
? `${r.name} [${r.tid}]`
: `${r.tid}`
: `utid=${row.utid}`;
});
const prioLabel = PRIO_LABELS[row.prio] ?? `${row.prio}`;
return {
render() {
return m(
DetailsShell,
{title: `Android Log`},
m(
GridLayout,
m(
GridLayoutColumn,
m(
Section,
{title: 'Details'},
m(
Tree,
m(TreeNode, {left: 'ID', right: row.id}),
m(TreeNode, {
left: 'Timestamp',
right: m(Timestamp, {trace, ts: Time.fromRaw(row.ts)}),
}),
m(TreeNode, {left: 'Priority', right: prioLabel}),
m(TreeNode, {left: 'Tag', right: row.tag}),
m(TreeNode, {
left: 'Thread',
right: threadInfo ?? m(Spinner),
}),
m(TreeNode, {
left: 'Message',
right: msg !== undefined ? msg : m(Spinner),
}),
),
),
),
),
);
},
};
}
export function createAndroidLogTrack(trace: Trace, uri: string) {
return SliceTrack.create({
trace,
uri,
dataset: new SourceDataset({src: LOGS_SQL, schema: LOGS_SCHEMA}),
initialMaxDepth: 4,
colorizer: (row) => PRIO_TO_COLOR[row.prio] ?? DEFAULT_PRIO_COLOR,
tooltip: (slice) => [m('', m('b', slice.row.tag)), m('', slice.row.msg)],
// All log events are instant events, render them as a little box rather
// than the default chevron.
instantStyle: {
width: EVT_PX,
render: (ctx, r) => ctx.fillRect(r.x, r.y, r.width, r.height),
},
// Make rows a little more compact.
sliceLayout: {
padding: 2,
sliceHeight: 7,
},
detailsPanel: (row) => makeDetailsPanel(trace, row),
});
}
export function createPerProcessLogTrack(
trace: Trace,
uri: string,
utids: number[],
) {
return SliceTrack.create({
trace,
uri,
dataset: new SourceDataset({
src: LOGS_SQL,
schema: LOGS_SCHEMA,
filter: {col: 'utid', in: utids},
}),
initialMaxDepth: 4,
colorizer: (row) => PRIO_TO_COLOR[row.prio] ?? DEFAULT_PRIO_COLOR,
tooltip: (slice) => [m('', m('b', slice.row.tag)), m('', slice.row.msg)],
instantStyle: {
width: EVT_PX,
render: (ctx, r) => ctx.fillRect(r.x, r.y, r.width, r.height),
},
sliceLayout: {
padding: 2,
sliceHeight: 7,
},
detailsPanel: (row) => makeDetailsPanel(trace, row),
});
}
export function createPerThreadLogTrack(
trace: Trace,
uri: string,
utid: number,
) {
return SliceTrack.create({
trace,
uri,
rootTableName: 'android_logs',
dataset: new SourceDataset({
src: LOGS_SQL,
schema: LOGS_SCHEMA,
filter: {col: 'utid', eq: utid},
}),
initialMaxDepth: 4,
colorizer: (row) => PRIO_TO_COLOR[row.prio] ?? DEFAULT_PRIO_COLOR,
tooltip: (slice) => [m('', m('b', slice.row.tag)), m('', slice.row.msg)],
instantStyle: {
width: EVT_PX,
render: (ctx, r) => ctx.fillRect(r.x, r.y, r.width, r.height),
},
sliceLayout: {
padding: 2,
sliceHeight: 7,
},
detailsPanel: (row) => makeDetailsPanel(trace, row),
});
}