| // Copyright (C) 2019 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| import * as m from 'mithril'; |
| |
| import {assertExists} from '../base/logging'; |
| import {Actions} from '../common/actions'; |
| import { |
| LogBounds, |
| LogBoundsKey, |
| LogEntries, |
| LogEntriesKey |
| } from '../common/logs'; |
| import {formatTimestamp} from '../common/time'; |
| import {TimeSpan} from '../common/time'; |
| |
| import {globals} from './globals'; |
| import {Panel} from './panel'; |
| |
| const ROW_H = 20; |
| |
| const PRIO_TO_LETTER = ['-', '-', 'V', 'D', 'I', 'W', 'E', 'F']; |
| |
| export class LogPanel extends Panel<{}> { |
| private scrollContainer?: HTMLElement; |
| private bounds?: LogBounds; |
| private entries?: LogEntries; |
| |
| private visibleRowOffset = 0; |
| private visibleRowCount = 0; |
| |
| recomputeVisibleRowsAndUpdate() { |
| const scrollContainer = assertExists(this.scrollContainer); |
| |
| const prevOffset = this.visibleRowOffset; |
| const prevCount = this.visibleRowCount; |
| this.visibleRowOffset = Math.floor(scrollContainer.scrollTop / ROW_H); |
| this.visibleRowCount = Math.ceil(scrollContainer.clientHeight / ROW_H); |
| |
| if (this.visibleRowOffset !== prevOffset || |
| this.visibleRowCount !== prevCount) |
| { |
| globals.dispatch(Actions.updateLogsPagination({ |
| offset: this.visibleRowOffset, |
| count: this.visibleRowCount, |
| })); |
| } |
| } |
| |
| oncreate({dom}: m.CVnodeDOM) { |
| this.scrollContainer = assertExists( |
| dom.parentElement!.parentElement!.parentElement as HTMLElement); |
| this.scrollContainer.addEventListener( |
| 'scroll', this.onScroll.bind(this), {passive: true}); |
| this.bounds = globals.trackDataStore.get(LogBoundsKey) as LogBounds; |
| this.entries = globals.trackDataStore.get(LogEntriesKey) as LogEntries; |
| this.recomputeVisibleRowsAndUpdate(); |
| } |
| |
| onupdate(_: m.CVnodeDOM) { |
| this.bounds = globals.trackDataStore.get(LogBoundsKey) as LogBounds; |
| this.entries = globals.trackDataStore.get(LogEntriesKey) as LogEntries; |
| this.recomputeVisibleRowsAndUpdate(); |
| } |
| |
| onScroll() { |
| if (this.scrollContainer === undefined) return; |
| this.recomputeVisibleRowsAndUpdate(); |
| globals.rafScheduler.scheduleFullRedraw(); |
| } |
| |
| onRowOver(ts: number) { |
| globals.dispatch(Actions.setHoveredLogsTimestamp({ts})); |
| } |
| |
| onRowOut() { |
| globals.dispatch(Actions.setHoveredLogsTimestamp({ts: -1})); |
| } |
| |
| private totalRows(): |
| {isStale: boolean, total: number, offset: number, count: number} { |
| if (!this.bounds) { |
| return {isStale: false, total: 0, offset: 0, count: 0}; |
| } |
| const {total, startTs, endTs, firstRowTs, lastRowTs} = this.bounds; |
| const vis = globals.frontendLocalState.visibleWindowTime; |
| const leftSpan = new TimeSpan(startTs, firstRowTs); |
| const rightSpan = new TimeSpan(lastRowTs, endTs); |
| |
| const isStaleLeft = !leftSpan.isInBounds(vis.start); |
| const isStaleRight = !rightSpan.isInBounds(vis.end); |
| const isStale = isStaleLeft || isStaleRight; |
| const offset = Math.min(this.visibleRowOffset, total); |
| const visCount = Math.min(total, this.visibleRowCount); |
| return {isStale, total, count: visCount, offset}; |
| } |
| |
| view(_: m.CVnode<{}>) { |
| const {isStale, total, offset, count} = this.totalRows(); |
| |
| const rows: m.Children = []; |
| if (this.entries) { |
| const offset = this.entries.offset; |
| const timestamps = this.entries.timestamps; |
| const priorities = this.entries.priorities; |
| const tags = this.entries.tags; |
| const messages = this.entries.messages; |
| for (let i = 0; i < this.entries.timestamps.length; i++) { |
| const priorityLetter = PRIO_TO_LETTER[priorities[i]]; |
| const ts = timestamps[i]; |
| const prioClass = priorityLetter || ''; |
| rows.push( |
| m(`.row.${prioClass}`, |
| { |
| 'class': isStale ? 'stale' : '', |
| style: {top: `${(offset + i) * ROW_H}px`}, |
| onmouseover: this.onRowOver.bind(this, ts / 1e9), |
| onmouseout: this.onRowOut.bind(this), |
| }, |
| m('.cell', |
| formatTimestamp(ts / 1e9 - globals.state.traceTime.startSec)), |
| m('.cell', priorityLetter || '?'), |
| m('.cell', tags[i]), |
| m('.cell', messages[i]), |
| m('br'))); |
| } |
| } |
| |
| return m( |
| '.log-panel', |
| m('header', |
| { |
| 'class': isStale ? 'stale' : '', |
| }, |
| `Logs rows [${offset}, ${offset + count}] / ${total}`), |
| m('.rows', {style: {height: `${total * ROW_H}px`}}, rows)); |
| } |
| |
| renderCanvas() {} |
| } |