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 {duration, Time, time} from '../../base/time'; 16import {colorForFtrace} from '../../components/colorizer'; 17import {LIMIT} from '../../components/tracks/track_data'; 18import {Store, TimelineFetcher} from '../../components/tracks/track_helper'; 19import {checkerboardExcept} from '../../components/checkerboard'; 20import {TrackData} from '../../components/tracks/track_data'; 21import {Engine} from '../../trace_processor/engine'; 22import {Track} from '../../public/track'; 23import {LONG, NUM, STR} from '../../trace_processor/query_result'; 24import {FtraceFilter} from './common'; 25import {Monitor} from '../../base/monitor'; 26import {TrackRenderContext} from '../../public/track'; 27import {SourceDataset, Dataset} from '../../trace_processor/dataset'; 28 29const MARGIN = 2; 30const RECT_HEIGHT = 18; 31const RECT_WIDTH = 8; 32const TRACK_HEIGHT = RECT_HEIGHT + 2 * MARGIN; 33 34interface Data extends TrackData { 35 events: Array<{ 36 timestamp: time; 37 color: string; 38 }>; 39} 40 41export interface Config { 42 cpu?: number; 43} 44 45export class FtraceRawTrack implements Track { 46 private fetcher = new TimelineFetcher(this.onBoundsChange.bind(this)); 47 private engine: Engine; 48 private cpu: number; 49 private store: Store<FtraceFilter>; 50 private readonly monitor: Monitor; 51 52 constructor(engine: Engine, cpu: number, store: Store<FtraceFilter>) { 53 this.engine = engine; 54 this.cpu = cpu; 55 this.store = store; 56 57 this.monitor = new Monitor([() => store.state]); 58 } 59 60 getDataset(): Dataset { 61 return new SourceDataset({ 62 // 'ftrace_event' doesn't have a dur column, but injecting dur=0 (all 63 // ftrace events are effectively 'instant') allows us to participate in 64 // generic slice aggregations 65 src: 'select id, ts, 0 as dur, name, cpu from ftrace_event', 66 schema: { 67 id: NUM, 68 name: STR, 69 ts: LONG, 70 dur: LONG, 71 }, 72 filter: { 73 col: 'cpu', 74 eq: this.cpu, 75 }, 76 }); 77 } 78 79 async onUpdate({ 80 visibleWindow, 81 resolution, 82 }: TrackRenderContext): Promise<void> { 83 this.monitor.ifStateChanged(() => { 84 this.fetcher.invalidate(); 85 }); 86 await this.fetcher.requestData(visibleWindow.toTimeSpan(), resolution); 87 } 88 89 async onDestroy?(): Promise<void> { 90 this.fetcher[Symbol.dispose](); 91 } 92 93 getHeight(): number { 94 return TRACK_HEIGHT; 95 } 96 97 async onBoundsChange( 98 start: time, 99 end: time, 100 resolution: duration, 101 ): Promise<Data> { 102 const excludeList = Array.from(this.store.state.excludeList); 103 const excludeListSql = excludeList.map((s) => `'${s}'`).join(','); 104 const cpuFilter = this.cpu === undefined ? '' : `and cpu = ${this.cpu}`; 105 106 const queryRes = await this.engine.query(` 107 select 108 cast(ts / ${resolution} as integer) * ${resolution} as tsQuant, 109 name 110 from ftrace_event 111 where 112 name not in (${excludeListSql}) and 113 ts >= ${start} and ts <= ${end} ${cpuFilter} 114 group by tsQuant 115 order by tsQuant limit ${LIMIT};`); 116 117 const rowCount = queryRes.numRows(); 118 119 const it = queryRes.iter({tsQuant: LONG, name: STR}); 120 const events = []; 121 for (let row = 0; it.valid(); it.next(), row++) { 122 events.push({ 123 timestamp: Time.fromRaw(it.tsQuant), 124 color: colorForFtrace(it.name).base.cssString, 125 }); 126 } 127 return { 128 start, 129 end, 130 resolution, 131 length: rowCount, 132 events, 133 }; 134 } 135 136 render({ctx, size, timescale}: TrackRenderContext): void { 137 const data = this.fetcher.data; 138 139 if (data === undefined) return; // Can't possibly draw anything. 140 141 const dataStartPx = timescale.timeToPx(data.start); 142 const dataEndPx = timescale.timeToPx(data.end); 143 144 checkerboardExcept( 145 ctx, 146 this.getHeight(), 147 0, 148 size.width, 149 dataStartPx, 150 dataEndPx, 151 ); 152 for (const e of data.events) { 153 ctx.fillStyle = e.color; 154 const xPos = Math.floor(timescale.timeToPx(e.timestamp)); 155 ctx.fillRect(xPos - RECT_WIDTH / 2, MARGIN, RECT_WIDTH, RECT_HEIGHT); 156 } 157 } 158} 159