blob: 78e2e7b2f4dd6fdf637ce4e2ebec2b327237ad46 [file] [log] [blame]
// Copyright (C) 2020 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 * as m from 'mithril';
import {Actions} from '../common/actions';
import {QueryResponse} from '../common/queries';
import {globals} from './globals';
import {createPage} from './pages';
interface StatsSectionAttrs {
title: string;
subTitle: string;
sqlConstraints: string;
cssClass: string;
queryId: string;
}
// Generic class that generate a <section> + <table> from the stats table.
// The caller defines the query constraint, title and styling.
// Used for errors, data losses and debugging sections.
class StatsSection implements m.ClassComponent<StatsSectionAttrs> {
private queryDispatched = false;
view({attrs}: m.CVnode<StatsSectionAttrs>) {
if (!this.queryDispatched) {
this.queryDispatched = true;
globals.dispatch(Actions.executeQuery({
queryId: attrs.queryId,
query: `select name, value, cast(ifnull(idx, '') as text) as idx,
description, severity, source from stats
where ${attrs.sqlConstraints || '1=1'}
order by name, idx`,
}));
}
const resp = globals.queryResults.get(attrs.queryId) as QueryResponse;
if (resp === undefined || resp.totalRowCount === 0) {
return m('');
}
if (resp.error) throw new Error(resp.error);
const tableRows = [];
for (const row of resp.rows) {
const help = [];
if (row.description) {
help.push(m('i.material-icons.contextual-help', 'help_outline'));
}
const idx = row.idx !== '' ? `[${row.idx}]` : '';
tableRows.push(m(
'tr',
m('td.name', {title: row.description}, `${row.name}${idx}`, help),
m('td', `${row.value}`),
m('td', `${row.severity} (${row.source})`),
));
}
return m(
`section${attrs.cssClass}`,
m('h2', attrs.title),
m('h3', attrs.subTitle),
m(
'table',
m('thead',
m('tr', m('td', 'Name'), m('td', 'Value'), m('td', 'Type'))),
m('tbody', tableRows),
),
);
}
}
class MetricErrors implements m.ClassComponent {
view() {
if (!globals.metricError) return;
return m(
`section.errors`,
m('h2', `Metric Errors`),
m('h3', `One or more metrics were not computed successfully:`),
m('div.metric-error', globals.metricError));
}
}
class TraceMetadata implements m.ClassComponent {
private queryDispatched = false;
private readonly QUERY_ID = 'info_metadata';
view() {
if (!this.queryDispatched) {
this.queryDispatched = true;
globals.dispatch(Actions.executeQuery({
queryId: this.QUERY_ID,
query: `with
metadata_with_priorities as (select
name, ifnull(str_value, cast(int_value as text)) as value,
name in (
"trace_size_bytes",
"cr-os-arch",
"cr-os-name",
"cr-os-version",
"cr-physical-memory",
"cr-product-version",
"cr-hardware-class"
) as priority
from metadata
)
select name, value
from metadata_with_priorities
order by priority desc, name`
}));
}
const resp = globals.queryResults.get(this.QUERY_ID) as QueryResponse;
if (resp === undefined || resp.totalRowCount === 0) {
return m('');
}
const tableRows = [];
for (const row of resp.rows) {
tableRows.push(m(
'tr',
m('td.name', `${row.name}`),
m('td', `${row.value}`),
));
}
return m(
'section',
m('h2', 'System info and metadata'),
m(
'table',
m('thead', m('tr', m('td', 'Name'), m('td', 'Value'))),
m('tbody', tableRows),
),
);
}
}
class PackageList implements m.ClassComponent {
private queryDispatched = false;
private readonly QUERY_ID = 'info_package_list';
view() {
if (!this.queryDispatched) {
this.queryDispatched = true;
globals.dispatch(Actions.executeQuery({
queryId: this.QUERY_ID,
query: `select package_name, version_code, debuggable,
profileable_from_shell from package_list`,
}));
}
const resp = globals.queryResults.get(this.QUERY_ID) as QueryResponse;
if (resp === undefined || resp.totalRowCount === 0) {
return m('');
}
const tableRows = [];
for (const row of resp.rows) {
tableRows.push(m(
'tr',
m('td.name', `${row.package_name}`),
m('td', `${row.version_code}`),
m('td',
`${row.debuggable ? 'debuggable' : ''} ${
row.profileable_from_shell ? 'profileable' : ''}`),
));
}
return m(
'section',
m('h2', 'Package list'),
m(
'table',
m('thead',
m('tr',
m('td', 'Name'),
m('td', 'Version code'),
m('td', 'Flags'))),
m('tbody', tableRows),
),
);
}
}
export const TraceInfoPage = createPage({
view() {
return m(
'.trace-info-page',
m(MetricErrors),
m(StatsSection, {
queryId: 'info_errors',
title: 'Import errors',
cssClass: '.errors',
subTitle:
`The following errors have been encountered while importing the
trace. These errors are usually non-fatal but indicate that one
or more tracks might be missing or showing erroneous data.`,
sqlConstraints: `severity = 'error' and value > 0`,
}),
m(StatsSection, {
queryId: 'info_data_losses',
title: 'Data losses',
cssClass: '.errors',
subTitle:
`These counters are collected at trace recording time. The trace
data for one or more data sources was droppped and hence some
track contents will be incomplete.`,
sqlConstraints: `severity = 'data_loss' and value > 0`,
}),
m(TraceMetadata),
m(PackageList),
m(StatsSection, {
queryId: 'info_all',
title: 'Debugging stats',
cssClass: '',
subTitle: `Debugging statistics such as trace buffer usage and metrics
coming from the TraceProcessor importer stages.`,
sqlConstraints: '',
}),
);
}
});