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