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 {searchEq, searchRange, searchSegment} from '../../base/binary_search'; 16import {assertTrue} from '../../base/logging'; 17import {colorForThread} from '../../common/colorizer'; 18import {TrackState} from '../../common/state'; 19import {checkerboardExcept} from '../../frontend/checkerboard'; 20import {globals} from '../../frontend/globals'; 21import {Track} from '../../frontend/track'; 22import {trackRegistry} from '../../frontend/track_registry'; 23 24import { 25 Config, 26 Data, 27 PROCESS_SCHEDULING_TRACK_KIND, 28} from './common'; 29 30const MARGIN_TOP = 5; 31const RECT_HEIGHT = 30; 32const TRACK_HEIGHT = MARGIN_TOP * 2 + RECT_HEIGHT; 33 34class ProcessSchedulingTrack extends Track<Config, Data> { 35 static readonly kind = PROCESS_SCHEDULING_TRACK_KIND; 36 static create(trackState: TrackState): ProcessSchedulingTrack { 37 return new ProcessSchedulingTrack(trackState); 38 } 39 40 private mouseXpos?: number; 41 private utidHoveredInThisTrack = -1; 42 43 constructor(trackState: TrackState) { 44 super(trackState); 45 } 46 47 getHeight(): number { 48 return TRACK_HEIGHT; 49 } 50 51 renderCanvas(ctx: CanvasRenderingContext2D): void { 52 // TODO: fonts and colors should come from the CSS and not hardcoded here. 53 const {timeScale, visibleWindowTime} = globals.frontendLocalState; 54 const data = this.data(); 55 56 if (data === undefined) return; // Can't possibly draw anything. 57 58 // If the cached trace slices don't fully cover the visible time range, 59 // show a gray rectangle with a "Loading..." label. 60 checkerboardExcept( 61 ctx, 62 this.getHeight(), 63 timeScale.timeToPx(visibleWindowTime.start), 64 timeScale.timeToPx(visibleWindowTime.end), 65 timeScale.timeToPx(data.start), 66 timeScale.timeToPx(data.end)); 67 68 assertTrue(data.starts.length === data.ends.length); 69 assertTrue(data.starts.length === data.utids.length); 70 71 const rawStartIdx = 72 data.ends.findIndex(end => end >= visibleWindowTime.start); 73 const startIdx = rawStartIdx === -1 ? data.starts.length : rawStartIdx; 74 75 const [, rawEndIdx] = searchSegment(data.starts, visibleWindowTime.end); 76 const endIdx = rawEndIdx === -1 ? data.starts.length : rawEndIdx; 77 78 const cpuTrackHeight = Math.floor(RECT_HEIGHT / data.maxCpu); 79 80 for (let i = startIdx; i < endIdx; i++) { 81 const tStart = data.starts[i]; 82 const tEnd = data.ends[i]; 83 const utid = data.utids[i]; 84 const cpu = data.cpus[i]; 85 86 const rectStart = timeScale.timeToPx(tStart); 87 const rectEnd = timeScale.timeToPx(tEnd); 88 const rectWidth = rectEnd - rectStart; 89 if (rectWidth < 0.3) continue; 90 91 const threadInfo = globals.threads.get(utid); 92 const pid = (threadInfo ? threadInfo.pid : -1) || -1; 93 94 const isHovering = globals.frontendLocalState.hoveredUtid !== -1; 95 const isThreadHovered = globals.frontendLocalState.hoveredUtid === utid; 96 const isProcessHovered = globals.frontendLocalState.hoveredPid === pid; 97 const color = colorForThread(threadInfo); 98 if (isHovering && !isThreadHovered) { 99 if (!isProcessHovered) { 100 color.l = 90; 101 color.s = 0; 102 } else { 103 color.l = Math.min(color.l + 30, 80); 104 color.s -= 20; 105 } 106 } else { 107 color.l = Math.min(color.l + 10, 60); 108 color.s -= 20; 109 } 110 ctx.fillStyle = `hsl(${color.h}, ${color.s}%, ${color.l}%)`; 111 const y = MARGIN_TOP + cpuTrackHeight * cpu + cpu; 112 ctx.fillRect(rectStart, y, rectEnd - rectStart, cpuTrackHeight); 113 } 114 115 const hoveredThread = globals.threads.get(this.utidHoveredInThisTrack); 116 if (hoveredThread !== undefined && this.mouseXpos !== undefined) { 117 const tidText = `T: ${hoveredThread.threadName} [${hoveredThread.tid}]`; 118 if (hoveredThread.pid) { 119 const pidText = `P: ${hoveredThread.procName} [${hoveredThread.pid}]`; 120 this.drawTrackHoverTooltip(ctx, this.mouseXpos, pidText, tidText); 121 } else { 122 this.drawTrackHoverTooltip(ctx, this.mouseXpos, tidText); 123 } 124 } 125 } 126 127 128 onMouseMove({x, y}: {x: number, y: number}) { 129 const data = this.data(); 130 this.mouseXpos = x; 131 if (data === undefined) return; 132 if (y < MARGIN_TOP || y > MARGIN_TOP + RECT_HEIGHT) { 133 this.utidHoveredInThisTrack = -1; 134 globals.frontendLocalState.setHoveredUtidAndPid(-1, -1); 135 return; 136 } 137 138 const cpuTrackHeight = Math.floor(RECT_HEIGHT / data.maxCpu); 139 const cpu = Math.floor((y - MARGIN_TOP) / (cpuTrackHeight + 1)); 140 const {timeScale} = globals.frontendLocalState; 141 const t = timeScale.pxToTime(x); 142 143 const [i, j] = searchRange(data.starts, t, searchEq(data.cpus, cpu)); 144 if (i === j || i >= data.starts.length || t > data.ends[i]) { 145 this.utidHoveredInThisTrack = -1; 146 globals.frontendLocalState.setHoveredUtidAndPid(-1, -1); 147 return; 148 } 149 150 const utid = data.utids[i]; 151 this.utidHoveredInThisTrack = utid; 152 const threadInfo = globals.threads.get(utid); 153 const pid = threadInfo ? (threadInfo.pid ? threadInfo.pid : -1) : -1; 154 globals.frontendLocalState.setHoveredUtidAndPid(utid, pid); 155 } 156 157 onMouseOut() { 158 this.utidHoveredInThisTrack = -1; 159 globals.frontendLocalState.setHoveredUtidAndPid(-1, -1); 160 this.mouseXpos = 0; 161 } 162} 163 164trackRegistry.register(ProcessSchedulingTrack); 165