1// Copyright (C) 2024 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 15import {Time, duration, time} from '../../base/time'; 16import {Engine} from '../../trace_processor/engine'; 17import {Trace} from '../../public/trace'; 18import { 19 LONG, 20 LONG_NULL, 21 NUM, 22 NUM_NULL, 23} from '../../trace_processor/query_result'; 24import {TrackEventDetailsPanel} from '../../public/details_panel'; 25import m from 'mithril'; 26import {DetailsShell} from '../../widgets/details_shell'; 27import {GridLayout} from '../../widgets/grid_layout'; 28import {Section} from '../../widgets/section'; 29import {Tree, TreeNode} from '../../widgets/tree'; 30import {Timestamp} from '../../components/widgets/timestamp'; 31import {DurationWidget} from '../../components/widgets/duration'; 32import {TrackEventSelection} from '../../public/selection'; 33import {hasArgs, renderArguments} from '../../components/details/slice_args'; 34import {asArgSetId} from '../../components/sql_utils/core_types'; 35import {Arg, getArgs} from '../../components/sql_utils/args'; 36 37interface CounterDetails { 38 // The "left" timestamp of the counter sample T(N) 39 ts: time; 40 41 // The delta between this sample and the next one's timestamps T(N+1) - T(N) 42 duration: duration; 43 44 // The value of the counter sample F(N) 45 value: number; 46 47 // The delta between this sample's value and the previous one F(N) - F(N-1) 48 delta: number; 49 50 args?: Arg[]; 51} 52 53export class CounterDetailsPanel implements TrackEventDetailsPanel { 54 private readonly trace: Trace; 55 private readonly engine: Engine; 56 private readonly trackId: number; 57 private readonly rootTable: string; 58 private readonly trackName: string; 59 private counterDetails?: CounterDetails; 60 61 constructor( 62 trace: Trace, 63 trackId: number, 64 trackName: string, 65 rootTable = 'counter', 66 ) { 67 this.trace = trace; 68 this.engine = trace.engine; 69 this.trackId = trackId; 70 this.trackName = trackName; 71 this.rootTable = rootTable; 72 } 73 74 async load({eventId}: TrackEventSelection) { 75 this.counterDetails = await loadCounterDetails( 76 this.engine, 77 this.trackId, 78 eventId, 79 this.rootTable, 80 ); 81 } 82 83 render() { 84 const counterInfo = this.counterDetails; 85 if (counterInfo) { 86 const args = 87 hasArgs(counterInfo.args) && 88 m( 89 Section, 90 {title: 'Arguments'}, 91 m(Tree, renderArguments(this.trace, counterInfo.args)), 92 ); 93 94 return m( 95 DetailsShell, 96 {title: 'Counter', description: `${this.trackName}`}, 97 m( 98 GridLayout, 99 m( 100 Section, 101 {title: 'Properties'}, 102 m( 103 Tree, 104 m(TreeNode, {left: 'Name', right: `${this.trackName}`}), 105 m(TreeNode, { 106 left: 'Start time', 107 right: m(Timestamp, {ts: counterInfo.ts}), 108 }), 109 m(TreeNode, { 110 left: 'Value', 111 right: `${counterInfo.value.toLocaleString()}`, 112 }), 113 m(TreeNode, { 114 left: 'Delta', 115 right: `${counterInfo.delta.toLocaleString()}`, 116 }), 117 m(TreeNode, { 118 left: 'Duration', 119 right: m(DurationWidget, {dur: counterInfo.duration}), 120 }), 121 ), 122 ), 123 args, 124 ), 125 ); 126 } else { 127 return m(DetailsShell, {title: 'Counter', description: 'Loading...'}); 128 } 129 } 130 131 isLoading(): boolean { 132 return this.counterDetails === undefined; 133 } 134} 135 136async function loadCounterDetails( 137 engine: Engine, 138 trackId: number, 139 id: number, 140 rootTable: string, 141): Promise<CounterDetails> { 142 const query = ` 143 WITH CTE AS ( 144 SELECT 145 id, 146 ts as leftTs, 147 value, 148 LAG(value) OVER (ORDER BY ts) AS prevValue, 149 LEAD(ts) OVER (ORDER BY ts) AS rightTs, 150 arg_set_id AS argSetId 151 FROM ${rootTable} 152 WHERE track_id = ${trackId} 153 ) 154 SELECT * FROM CTE WHERE id = ${id} 155 `; 156 157 const counter = await engine.query(query); 158 const row = counter.iter({ 159 value: NUM, 160 prevValue: NUM_NULL, 161 leftTs: LONG, 162 rightTs: LONG_NULL, 163 argSetId: NUM_NULL, 164 }); 165 const value = row.value; 166 const leftTs = Time.fromRaw(row.leftTs); 167 const rightTs = row.rightTs !== null ? Time.fromRaw(row.rightTs) : leftTs; 168 const prevValue = row.prevValue !== null ? row.prevValue : value; 169 170 const delta = value - prevValue; 171 const duration = rightTs - leftTs; 172 const argSetId = row.argSetId; 173 const args = 174 argSetId == null ? undefined : await getArgs(engine, asArgSetId(argSetId)); 175 return {ts: leftTs, value, delta, duration, args}; 176} 177