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} from '../../base/time'; 16import { 17 BaseCounterTrack, 18 CounterOptions, 19} from '../../components/tracks/base_counter_track'; 20import {TrackEventDetails} from '../../public/selection'; 21import {Trace} from '../../public/trace'; 22import {TrackMouseEvent} from '../../public/track'; 23import {LONG, LONG_NULL, NUM} from '../../trace_processor/query_result'; 24import {CounterDetailsPanel} from './counter_details_panel'; 25 26export class TraceProcessorCounterTrack extends BaseCounterTrack { 27 constructor( 28 trace: Trace, 29 uri: string, 30 options: Partial<CounterOptions>, 31 private readonly trackId: number, 32 private readonly trackName: string, 33 private readonly rootTable: string = 'counter', 34 ) { 35 super(trace, uri, options); 36 } 37 38 getSqlSource() { 39 return ` 40 select 41 id, 42 ts, 43 value 44 from ${this.rootTable} 45 where track_id = ${this.trackId} 46 `; 47 } 48 49 onMouseClick({x, timescale}: TrackMouseEvent): boolean { 50 const time = timescale.pxToHpTime(x).toTime('floor'); 51 52 const query = ` 53 select 54 id 55 from ${this.rootTable} 56 where 57 track_id = ${this.trackId} 58 and ts < ${time} 59 order by ts DESC 60 limit 1 61 `; 62 63 this.engine.query(query).then((result) => { 64 const it = result.iter({ 65 id: NUM, 66 }); 67 if (!it.valid()) { 68 return; 69 } 70 const id = it.id; 71 this.trace.selection.selectTrackEvent(this.uri, id); 72 }); 73 74 return true; 75 } 76 77 // We must define this here instead of in `BaseCounterTrack` because 78 // `BaseCounterTrack` does not require the query to have an id column. Here, 79 // however, we make the assumption that `rootTable` has an id column, as we 80 // need it ot make selections in `onMouseClick` above. Whether or not we 81 // SHOULD assume `rootTable` has an id column is another matter... 82 async getSelectionDetails(id: number): Promise<TrackEventDetails> { 83 const query = ` 84 WITH 85 CTE AS ( 86 SELECT 87 id, 88 ts as leftTs, 89 LEAD(ts) OVER (ORDER BY ts) AS rightTs 90 FROM ${this.rootTable} 91 ) 92 SELECT * FROM CTE WHERE id = ${id} 93 `; 94 95 const counter = await this.engine.query(query); 96 const row = counter.iter({ 97 leftTs: LONG, 98 rightTs: LONG_NULL, 99 }); 100 const leftTs = Time.fromRaw(row.leftTs); 101 const rightTs = row.rightTs !== null ? Time.fromRaw(row.rightTs) : leftTs; 102 const duration = rightTs - leftTs; 103 return {ts: leftTs, dur: duration}; 104 } 105 106 detailsPanel() { 107 return new CounterDetailsPanel(this.trace, this.trackId, this.trackName); 108 } 109} 110