1// Copyright (C) 2020 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
15
16import * as m from 'mithril';
17
18import {Actions} from '../common/actions';
19import {QueryResponse} from '../common/queries';
20
21import {globals} from './globals';
22import {createPage} from './pages';
23
24
25interface StatsSectionAttrs {
26  title: string;
27  subTitle: string;
28  sqlConstraints: string;
29  cssClass: string;
30  queryId: string;
31}
32
33// Generic class that generate a <section> + <table> from the stats table.
34// The caller defines the query constraint, title and styling.
35// Used for errors, data losses and debugging sections.
36class StatsSection implements m.ClassComponent<StatsSectionAttrs> {
37  private queryDispatched = false;
38
39  view({attrs}: m.CVnode<StatsSectionAttrs>) {
40    if (!this.queryDispatched) {
41      this.queryDispatched = true;
42      globals.dispatch(Actions.executeQuery({
43        engineId: '0',
44        queryId: attrs.queryId,
45        query: `select name, value, cast(ifnull(idx, '') as text) as idx,
46                description, severity, source from stats
47                where ${attrs.sqlConstraints || '1=1'}
48                order by name, idx`,
49      }));
50    }
51
52    const resp = globals.queryResults.get(attrs.queryId) as QueryResponse;
53    if (resp === undefined || resp.totalRowCount === 0) {
54      return m('');
55    }
56    if (resp.error) throw new Error(resp.error);
57
58    const tableRows = [];
59    for (const row of resp.rows) {
60      const help = [];
61      if (row.description) {
62        help.push(m('i.material-icons.contextual-help', 'help_outline'));
63      }
64      const idx = row.idx !== '' ? `[${row.idx}]` : '';
65      tableRows.push(m(
66          'tr',
67          m('td', {title: row.description}, `${row.name}${idx}`, help),
68          m('td', `${row.value}`),
69          m('td', `${row.severity} (${row.source})`),
70          ));
71    }
72
73    return m(
74        `section${attrs.cssClass}`,
75        m('h2', attrs.title),
76        m('h3', attrs.subTitle),
77        m(
78            'table',
79            m('thead',
80              m('tr', m('td', 'Name'), m('td', 'Value'), m('td', 'Type'))),
81            m('tbody', tableRows),
82            ),
83    );
84  }
85}
86
87class MetricErrors implements m.ClassComponent {
88  view() {
89    if (!globals.metricError) return;
90    return m(
91        `section.errors`,
92        m('h2', `Metric Errors`),
93        m('h3', `One or more metrics were not computed successfully:`),
94        m('div.metric-error', globals.metricError));
95  }
96}
97
98class TraceMetadata implements m.ClassComponent {
99  private queryDispatched = false;
100  private readonly QUERY_ID = 'info_metadata';
101
102  view() {
103    if (!this.queryDispatched) {
104      this.queryDispatched = true;
105      globals.dispatch(Actions.executeQuery({
106        engineId: '0',
107        queryId: this.QUERY_ID,
108        query: `select name, ifnull(str_value, cast(int_value as text)) as value
109                from metadata order by name`,
110      }));
111    }
112
113    const resp = globals.queryResults.get(this.QUERY_ID) as QueryResponse;
114    if (resp === undefined || resp.totalRowCount === 0) {
115      return m('');
116    }
117
118    const tableRows = [];
119    for (const row of resp.rows) {
120      tableRows.push(m(
121          'tr',
122          m('td', `${row.name}`),
123          m('td', `${row.value}`),
124          ));
125    }
126
127    return m(
128        'section',
129        m('h2', 'System info and metadata'),
130        m(
131            'table',
132            m('thead', m('tr', m('td', 'Name'), m('td', 'Value'))),
133            m('tbody', tableRows),
134            ),
135    );
136  }
137}
138
139class PackageList implements m.ClassComponent {
140  private queryDispatched = false;
141  private readonly QUERY_ID = 'info_package_list';
142
143  view() {
144    if (!this.queryDispatched) {
145      this.queryDispatched = true;
146      globals.dispatch(Actions.executeQuery({
147        engineId: '0',
148        queryId: this.QUERY_ID,
149        query: `select package_name, version_code, debuggable,
150                profileable_from_shell from package_list`,
151      }));
152    }
153
154    const resp = globals.queryResults.get(this.QUERY_ID) as QueryResponse;
155    if (resp === undefined || resp.totalRowCount === 0) {
156      return m('');
157    }
158
159    const tableRows = [];
160    for (const row of resp.rows) {
161      tableRows.push(m(
162          'tr',
163          m('td', `${row.package_name}`),
164          m('td', `${row.version_code}`),
165          m('td',
166            `${row.debuggable ? 'debuggable' : ''} ${
167                row.profileable_from_shell ? 'profileable' : ''}`),
168          ));
169    }
170
171    return m(
172        'section',
173        m('h2', 'Package list'),
174        m(
175            'table',
176            m('thead',
177              m('tr',
178                m('td', 'Name'),
179                m('td', 'Version code'),
180                m('td', 'Flags'))),
181            m('tbody', tableRows),
182            ),
183    );
184  }
185}
186
187export const TraceInfoPage = createPage({
188  view() {
189    return m(
190        '.trace-info-page',
191        m(MetricErrors),
192        m(StatsSection, {
193          queryId: 'info_errors',
194          title: 'Import errors',
195          cssClass: '.errors',
196          subTitle:
197              `The following errors have been encountered while importing the
198               trace. These errors are usually non-fatal but indicate that one
199               or more tracks might be missing or showing erroneous data.`,
200          sqlConstraints: `severity = 'error' and value > 0`,
201
202        }),
203        m(StatsSection, {
204          queryId: 'info_data_losses',
205          title: 'Data losses',
206          cssClass: '.errors',
207          subTitle:
208              `These counters are collected at trace recording time. The trace
209               data for one or more data sources was droppped and hence some
210               track contents will be incomplete.`,
211          sqlConstraints: `severity = 'data_loss' and value > 0`,
212        }),
213        m(TraceMetadata),
214        m(PackageList),
215        m(StatsSection, {
216          queryId: 'info_all',
217          title: 'Debugging stats',
218          cssClass: '',
219          subTitle: `Debugging statistics such as trace buffer usage and metrics
220                     coming from the TraceProcessor importer stages.`,
221          sqlConstraints: '',
222
223        }),
224    );
225  }
226});
227