| // 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 m from 'mithril'; |
| |
| import {QueryResponse, runQuery} from '../common/queries'; |
| import {raf} from '../core/raf_scheduler'; |
| import {Engine} from '../trace_processor/engine'; |
| |
| import {globals} from './globals'; |
| import {createPage} from './pages'; |
| |
| interface StatsSectionAttrs { |
| title: string; |
| subTitle: string; |
| sqlConstraints: string; |
| cssClass: string; |
| queryId: string; |
| } |
| |
| function getEngine(name: string): Engine | undefined { |
| const currentEngine = globals.getCurrentEngine(); |
| if (currentEngine === undefined) return undefined; |
| const engineId = currentEngine.id; |
| return globals.engines.get(engineId)?.getProxy(name); |
| } |
| |
| // 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 queryResponse?: QueryResponse; |
| |
| constructor({attrs}: m.CVnode<StatsSectionAttrs>) { |
| const engine = getEngine('StatsSection'); |
| if (engine === undefined) { |
| return; |
| } |
| const 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`; |
| runQuery(query, engine).then((resp: QueryResponse) => { |
| this.queryResponse = resp; |
| raf.scheduleFullRedraw(); |
| }); |
| } |
| |
| view({attrs}: m.CVnode<StatsSectionAttrs>) { |
| const resp = this.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 = []; |
| // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions |
| 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 queryResponse?: QueryResponse; |
| |
| constructor() { |
| const engine = getEngine('StatsSection'); |
| if (engine === undefined) { |
| return; |
| } |
| const 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`; |
| runQuery(query, engine).then((resp: QueryResponse) => { |
| this.queryResponse = resp; |
| raf.scheduleFullRedraw(); |
| }); |
| } |
| |
| view() { |
| const resp = this.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 AndroidGameInterventionList implements m.ClassComponent { |
| private queryResponse?: QueryResponse; |
| |
| constructor() { |
| const engine = getEngine('StatsSection'); |
| if (engine === undefined) { |
| return; |
| } |
| const query = `select |
| package_name, |
| uid, |
| current_mode, |
| standard_mode_supported, |
| standard_mode_downscale, |
| standard_mode_use_angle, |
| standard_mode_fps, |
| perf_mode_supported, |
| perf_mode_downscale, |
| perf_mode_use_angle, |
| perf_mode_fps, |
| battery_mode_supported, |
| battery_mode_downscale, |
| battery_mode_use_angle, |
| battery_mode_fps |
| from android_game_intervention_list`; |
| runQuery(query, engine).then((resp: QueryResponse) => { |
| this.queryResponse = resp; |
| raf.scheduleFullRedraw(); |
| }); |
| } |
| |
| view() { |
| const resp = this.queryResponse; |
| if (resp === undefined || resp.totalRowCount === 0) { |
| return m(''); |
| } |
| |
| const tableRows = []; |
| let standardInterventions = ''; |
| let perfInterventions = ''; |
| let batteryInterventions = ''; |
| for (const row of resp.rows) { |
| // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions |
| if (row.standard_mode_supported) { |
| standardInterventions = `angle=${row.standard_mode_use_angle},downscale=${row.standard_mode_downscale},fps=${row.standard_mode_fps}`; |
| } else { |
| standardInterventions = 'Not supported'; |
| } |
| |
| // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions |
| if (row.perf_mode_supported) { |
| perfInterventions = `angle=${row.perf_mode_use_angle},downscale=${row.perf_mode_downscale},fps=${row.perf_mode_fps}`; |
| } else { |
| perfInterventions = 'Not supported'; |
| } |
| |
| // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions |
| if (row.battery_mode_supported) { |
| batteryInterventions = `angle=${row.battery_mode_use_angle},downscale=${row.battery_mode_downscale},fps=${row.battery_mode_fps}`; |
| } else { |
| batteryInterventions = 'Not supported'; |
| } |
| // Game mode numbers are defined in |
| // https://cs.android.com/android/platform/superproject/+/main:frameworks/base/core/java/android/app/GameManager.java;l=68 |
| if (row.current_mode === 1) { |
| row.current_mode = 'Standard'; |
| } else if (row.current_mode === 2) { |
| row.current_mode = 'Performance'; |
| } else if (row.current_mode === 3) { |
| row.current_mode = 'Battery'; |
| } |
| tableRows.push( |
| m( |
| 'tr', |
| m('td.name', `${row.package_name}`), |
| m('td', `${row.current_mode}`), |
| m('td', standardInterventions), |
| m('td', perfInterventions), |
| m('td', batteryInterventions), |
| ), |
| ); |
| } |
| |
| return m( |
| 'section', |
| m('h2', 'Game Intervention List'), |
| m( |
| 'table', |
| m( |
| 'thead', |
| m( |
| 'tr', |
| m('td', 'Name'), |
| m('td', 'Current mode'), |
| m('td', 'Standard mode interventions'), |
| m('td', 'Performance mode interventions'), |
| m('td', 'Battery mode interventions'), |
| ), |
| ), |
| m('tbody', tableRows), |
| ), |
| ); |
| } |
| } |
| |
| class PackageList implements m.ClassComponent { |
| private queryResponse?: QueryResponse; |
| |
| constructor() { |
| const engine = getEngine('StatsSection'); |
| if (engine === undefined) { |
| return; |
| } |
| const query = `select package_name, version_code, debuggable, |
| profileable_from_shell from package_list`; |
| runQuery(query, engine).then((resp: QueryResponse) => { |
| this.queryResponse = resp; |
| raf.scheduleFullRedraw(); |
| }); |
| } |
| |
| view() { |
| const resp = this.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}`), |
| /* eslint-disable @typescript-eslint/strict-boolean-expressions */ |
| m( |
| 'td', |
| `${row.debuggable ? 'debuggable' : ''} ${ |
| row.profileable_from_shell ? 'profileable' : '' |
| }`, |
| ), |
| /* eslint-enable */ |
| ), |
| ); |
| } |
| |
| 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 dropped and hence some |
| track contents will be incomplete.`, |
| sqlConstraints: `severity = 'data_loss' and value > 0`, |
| }), |
| m(TraceMetadata), |
| m(PackageList), |
| m(AndroidGameInterventionList), |
| 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: '', |
| }), |
| ); |
| }, |
| }); |