• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2019 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 * as m from 'mithril';
16
17import {assertExists} from '../base/logging';
18import {Actions} from '../common/actions';
19import {
20  LogBounds,
21  LogBoundsKey,
22  LogEntries,
23  LogEntriesKey
24} from '../common/logs';
25import {formatTimestamp} from '../common/time';
26import {TimeSpan} from '../common/time';
27
28import {globals} from './globals';
29import {Panel} from './panel';
30
31const ROW_H = 20;
32
33const PRIO_TO_LETTER = ['-', '-', 'V', 'D', 'I', 'W', 'E', 'F'];
34
35export class LogPanel extends Panel<{}> {
36  private scrollContainer?: HTMLElement;
37  private bounds?: LogBounds;
38  private entries?: LogEntries;
39
40  private visibleRowOffset = 0;
41  private visibleRowCount = 0;
42
43  recomputeVisibleRowsAndUpdate() {
44    const scrollContainer = assertExists(this.scrollContainer);
45
46    const prevOffset = this.visibleRowOffset;
47    const prevCount = this.visibleRowCount;
48    this.visibleRowOffset = Math.floor(scrollContainer.scrollTop / ROW_H);
49    this.visibleRowCount = Math.ceil(scrollContainer.clientHeight / ROW_H);
50
51    if (this.visibleRowOffset !== prevOffset ||
52        this.visibleRowCount !== prevCount)
53       {
54        globals.dispatch(Actions.updateLogsPagination({
55          offset: this.visibleRowOffset,
56          count: this.visibleRowCount,
57        }));
58      }
59  }
60
61  oncreate({dom}: m.CVnodeDOM) {
62    this.scrollContainer = assertExists(
63        dom.parentElement!.parentElement!.parentElement as HTMLElement);
64    this.scrollContainer.addEventListener(
65        'scroll', this.onScroll.bind(this), {passive: true});
66    this.bounds = globals.trackDataStore.get(LogBoundsKey) as LogBounds;
67    this.entries = globals.trackDataStore.get(LogEntriesKey) as LogEntries;
68    this.recomputeVisibleRowsAndUpdate();
69  }
70
71  onupdate(_: m.CVnodeDOM) {
72    this.bounds = globals.trackDataStore.get(LogBoundsKey) as LogBounds;
73    this.entries = globals.trackDataStore.get(LogEntriesKey) as LogEntries;
74    this.recomputeVisibleRowsAndUpdate();
75  }
76
77  onScroll() {
78    if (this.scrollContainer === undefined) return;
79    this.recomputeVisibleRowsAndUpdate();
80    globals.rafScheduler.scheduleFullRedraw();
81  }
82
83  onRowOver(ts: number) {
84    globals.dispatch(Actions.setHoveredLogsTimestamp({ts}));
85  }
86
87  onRowOut() {
88    globals.dispatch(Actions.setHoveredLogsTimestamp({ts: -1}));
89  }
90
91  private totalRows():
92      {isStale: boolean, total: number, offset: number, count: number} {
93    if (!this.bounds) {
94      return {isStale: false, total: 0, offset: 0, count: 0};
95    }
96    const {total, startTs, endTs, firstRowTs, lastRowTs} = this.bounds;
97    const vis = globals.frontendLocalState.visibleWindowTime;
98    const leftSpan = new TimeSpan(startTs, firstRowTs);
99    const rightSpan = new TimeSpan(lastRowTs, endTs);
100
101    const isStaleLeft = !leftSpan.isInBounds(vis.start);
102    const isStaleRight = !rightSpan.isInBounds(vis.end);
103    const isStale = isStaleLeft || isStaleRight;
104    const offset = Math.min(this.visibleRowOffset, total);
105    const visCount = Math.min(total, this.visibleRowCount);
106    return {isStale, total, count: visCount, offset};
107  }
108
109  view(_: m.CVnode<{}>) {
110    const {isStale, total, offset, count} = this.totalRows();
111
112    const rows: m.Children = [];
113    if (this.entries) {
114      const offset = this.entries.offset;
115      const timestamps = this.entries.timestamps;
116      const priorities = this.entries.priorities;
117      const tags = this.entries.tags;
118      const messages = this.entries.messages;
119      for (let i = 0; i < this.entries.timestamps.length; i++) {
120        const priorityLetter = PRIO_TO_LETTER[priorities[i]];
121        const ts = timestamps[i];
122        const prioClass = priorityLetter || '';
123        rows.push(
124            m(`.row.${prioClass}`,
125              {
126                'class': isStale ? 'stale' : '',
127                style: {top: `${(offset + i) * ROW_H}px`},
128                onmouseover: this.onRowOver.bind(this, ts / 1e9),
129                onmouseout: this.onRowOut.bind(this),
130              },
131              m('.cell',
132                formatTimestamp(ts / 1e9 - globals.state.traceTime.startSec)),
133              m('.cell', priorityLetter || '?'),
134              m('.cell', tags[i]),
135              m('.cell', messages[i]),
136              m('br')));
137      }
138    }
139
140    return m(
141        '.log-panel',
142        m('header',
143          {
144            'class': isStale ? 'stale' : '',
145          },
146          `Logs rows [${offset}, ${offset + count}] / ${total}`),
147        m('.rows', {style: {height: `${total * ROW_H}px`}}, rows));
148  }
149
150  renderCanvas() {}
151}
152