xref: /aosp_15_r20/external/perfetto/ui/src/plugins/dev.perfetto.AndroidLog/logs_track.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
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