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 {LIMIT, TrackData} from '../../components/tracks/track_data'; 17import {TimelineFetcher} from '../../components/tracks/track_helper'; 18import {checkerboardExcept} from '../../components/checkerboard'; 19import {Engine} from '../../trace_processor/engine'; 20import {LONG, NUM} from '../../trace_processor/query_result'; 21import {Track} from '../../public/track'; 22import {TrackRenderContext} from '../../public/track'; 23 24export interface Data extends TrackData { 25 // Total number of log events within [start, end], before any quantization. 26 numEvents: number; 27 28 // Below: data quantized by resolution and aggregated by event priority. 29 timestamps: BigInt64Array; 30 31 // Each Uint8 value has the i-th bit is set if there is at least one log 32 // event at the i-th priority level at the corresponding time in |timestamps|. 33 priorities: Uint8Array; 34} 35 36const LEVELS: LevelCfg[] = [ 37 {color: 'hsl(122, 39%, 49%)', prios: [0, 1, 2, 3]}, // Up to DEBUG: Green. 38 {color: 'hsl(0, 0%, 70%)', prios: [4]}, // 4 (INFO) -> Gray. 39 {color: 'hsl(45, 100%, 51%)', prios: [5]}, // 5 (WARN) -> Amber. 40 {color: 'hsl(4, 90%, 58%)', prios: [6]}, // 6 (ERROR) -> Red. 41 {color: 'hsl(291, 64%, 42%)', prios: [7]}, // 7 (FATAL) -> Purple 42]; 43 44const MARGIN_TOP = 2; 45const RECT_HEIGHT = 35; 46const EVT_PX = 2; // Width of an event tick in pixels. 47 48interface LevelCfg { 49 color: string; 50 prios: number[]; 51} 52 53export class AndroidLogTrack implements Track { 54 private fetcher = new TimelineFetcher<Data>(this.onBoundsChange.bind(this)); 55 56 constructor(private engine: Engine) {} 57 58 async onUpdate({ 59 visibleWindow, 60 resolution, 61 }: TrackRenderContext): Promise<void> { 62 await this.fetcher.requestData(visibleWindow.toTimeSpan(), resolution); 63 } 64 65 async onDestroy(): Promise<void> { 66 this.fetcher[Symbol.dispose](); 67 } 68 69 getHeight(): number { 70 return 40; 71 } 72 73 async onBoundsChange( 74 start: time, 75 end: time, 76 resolution: duration, 77 ): Promise<Data> { 78 const queryRes = await this.engine.query(` 79 select 80 cast(ts / ${resolution} as integer) * ${resolution} as tsQuant, 81 prio, 82 count(prio) as numEvents 83 from android_logs 84 where ts >= ${start} and ts <= ${end} 85 group by tsQuant, prio 86 order by tsQuant, prio limit ${LIMIT};`); 87 88 const rowCount = queryRes.numRows(); 89 const result = { 90 start, 91 end, 92 resolution, 93 length: rowCount, 94 numEvents: 0, 95 timestamps: new BigInt64Array(rowCount), 96 priorities: new Uint8Array(rowCount), 97 }; 98 99 const it = queryRes.iter({tsQuant: LONG, prio: NUM, numEvents: NUM}); 100 for (let row = 0; it.valid(); it.next(), row++) { 101 result.timestamps[row] = it.tsQuant; 102 const prio = Math.min(it.prio, 7); 103 result.priorities[row] |= 1 << prio; 104 result.numEvents += it.numEvents; 105 } 106 return result; 107 } 108 109 render({ctx, size, timescale}: TrackRenderContext): void { 110 const data = this.fetcher.data; 111 112 if (data === undefined) return; // Can't possibly draw anything. 113 114 const dataStartPx = timescale.timeToPx(data.start); 115 const dataEndPx = timescale.timeToPx(data.end); 116 117 checkerboardExcept( 118 ctx, 119 this.getHeight(), 120 0, 121 size.width, 122 dataStartPx, 123 dataEndPx, 124 ); 125 126 const quantWidth = Math.max( 127 EVT_PX, 128 timescale.durationToPx(data.resolution), 129 ); 130 const blockH = RECT_HEIGHT / LEVELS.length; 131 for (let i = 0; i < data.timestamps.length; i++) { 132 for (let lev = 0; lev < LEVELS.length; lev++) { 133 let hasEventsForCurColor = false; 134 for (const prio of LEVELS[lev].prios) { 135 if (data.priorities[i] & (1 << prio)) hasEventsForCurColor = true; 136 } 137 if (!hasEventsForCurColor) continue; 138 ctx.fillStyle = LEVELS[lev].color; 139 const timestamp = Time.fromRaw(data.timestamps[i]); 140 const px = Math.floor(timescale.timeToPx(timestamp)); 141 ctx.fillRect(px, MARGIN_TOP + blockH * lev, quantWidth, blockH); 142 } // for(lev) 143 } // for (timestamps) 144 } 145} 146