1// Copyright (C) 2018 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 {cropText} from '../../common/canvas_utils'; 16import {TrackState} from '../../common/state'; 17import {checkerboardExcept} from '../../frontend/checkerboard'; 18import {globals} from '../../frontend/globals'; 19import {Track} from '../../frontend/track'; 20import {trackRegistry} from '../../frontend/track_registry'; 21 22import {Config, Data, SLICE_TRACK_KIND} from './common'; 23 24const SLICE_HEIGHT = 20; 25const TRACK_PADDING = 5; 26 27function hash(s: string): number { 28 let hash = 0x811c9dc5 & 0xfffffff; 29 for (let i = 0; i < s.length; i++) { 30 hash ^= s.charCodeAt(i); 31 hash = (hash * 16777619) & 0xffffffff; 32 } 33 return hash & 0xff; 34} 35 36class ChromeSliceTrack extends Track<Config, Data> { 37 static readonly kind = SLICE_TRACK_KIND; 38 static create(trackState: TrackState): ChromeSliceTrack { 39 return new ChromeSliceTrack(trackState); 40 } 41 42 private hoveredTitleId = -1; 43 44 constructor(trackState: TrackState) { 45 super(trackState); 46 } 47 48 renderCanvas(ctx: CanvasRenderingContext2D): void { 49 // TODO: fonts and colors should come from the CSS and not hardcoded here. 50 51 const {timeScale, visibleWindowTime} = globals.frontendLocalState; 52 const data = this.data(); 53 54 // If there aren't enough cached slices data in |data| request more to 55 // the controller. 56 const inRange = data !== undefined && 57 (visibleWindowTime.start >= data.start && 58 visibleWindowTime.end <= data.end); 59 if (!inRange || data === undefined || 60 data.resolution > globals.getCurResolution()) { 61 globals.requestTrackData(this.trackState.id); 62 if (data === undefined) return; // Can't possibly draw anything. 63 } 64 65 // If the cached trace slices don't fully cover the visible time range, 66 // show a gray rectangle with a "Loading..." label. 67 checkerboardExcept( 68 ctx, 69 timeScale.timeToPx(visibleWindowTime.start), 70 timeScale.timeToPx(visibleWindowTime.end), 71 timeScale.timeToPx(data.start), 72 timeScale.timeToPx(data.end), ); 73 74 ctx.font = '12px Google Sans'; 75 ctx.textAlign = 'center'; 76 77 // measuretext is expensive so we only use it once. 78 const charWidth = ctx.measureText('ACBDLqsdfg').width / 10; 79 const pxEnd = timeScale.timeToPx(visibleWindowTime.end); 80 81 for (let i = 0; i < data.starts.length; i++) { 82 const tStart = data.starts[i]; 83 const tEnd = data.ends[i]; 84 const depth = data.depths[i]; 85 const cat = data.strings[data.categories[i]]; 86 const titleId = data.titles[i]; 87 const title = data.strings[titleId]; 88 if (tEnd <= visibleWindowTime.start || tStart >= visibleWindowTime.end) { 89 continue; 90 } 91 const rectXStart = Math.max(timeScale.timeToPx(tStart), 0); 92 const rectXEnd = Math.min(timeScale.timeToPx(tEnd), pxEnd); 93 const rectWidth = rectXEnd - rectXStart; 94 const rectYStart = TRACK_PADDING + depth * SLICE_HEIGHT; 95 96 const hovered = titleId === this.hoveredTitleId; 97 const hue = hash(cat); 98 const saturation = Math.min(20 + depth * 10, 70); 99 ctx.fillStyle = `hsl(${hue}, ${saturation}%, ${hovered ? 30 : 65}%)`; 100 ctx.fillRect(rectXStart, rectYStart, rectWidth, SLICE_HEIGHT); 101 102 ctx.fillStyle = 'white'; 103 const displayText = cropText(title, charWidth, rectWidth); 104 const rectXCenter = rectXStart + rectWidth / 2; 105 ctx.textBaseline = "middle"; 106 ctx.fillText(displayText, rectXCenter, rectYStart + SLICE_HEIGHT / 2); 107 } 108 } 109 110 onMouseMove({x, y}: {x: number, y: number}) { 111 const data = this.data(); 112 this.hoveredTitleId = -1; 113 if (data === undefined) return; 114 const {timeScale} = globals.frontendLocalState; 115 if (y < TRACK_PADDING) return; 116 const t = timeScale.pxToTime(x); 117 const depth = Math.floor(y / SLICE_HEIGHT); 118 for (let i = 0; i < data.starts.length; i++) { 119 const tStart = data.starts[i]; 120 const tEnd = data.ends[i]; 121 const titleId = data.titles[i]; 122 if (tStart <= t && t <= tEnd && depth === data.depths[i]) { 123 this.hoveredTitleId = titleId; 124 break; 125 } 126 } 127 } 128 129 onMouseOut() { 130 this.hoveredTitleId = -1; 131 } 132 133 getHeight() { 134 return SLICE_HEIGHT * (this.config.maxDepth + 1) + 2 * TRACK_PADDING; 135 } 136} 137 138trackRegistry.register(ChromeSliceTrack); 139