1/* 2 * Copyright (C) 2022 Huawei Device Co., Ltd. 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 */ 15 16import { 17 BaseStruct, 18 drawFlagLine, 19 drawLines, 20 drawLoading, 21 drawSelection, 22 PerfRender, 23 RequestMessage, 24} from './ProcedureWorkerCommon.js'; 25import { TraceRow } from '../../component/trace/base/TraceRow.js'; 26 27export class FileSystemRender extends PerfRender { 28 renderMainThread( 29 req: { 30 context: CanvasRenderingContext2D; 31 useCache: boolean; 32 type: string; 33 chartColor: string; 34 }, 35 row: TraceRow<FileSysChartStruct> 36 ) { 37 let list = row.dataList; 38 let filter = row.dataListCache; 39 let groupBy10MS = (TraceRow.range?.scale || 50) > 40_000_000; 40 let isDiskIO: boolean = req.type.includes('disk-io'); 41 if (list && row.dataList2.length == 0) { 42 row.dataList2 = isDiskIO 43 ? FileSysChartStruct.groupBy10MSWithMaxLatency(list) 44 : FileSysChartStruct.groupBy10MSWithCount(list); 45 } 46 fileSysChart( 47 list, 48 row.dataList2, 49 req.type, 50 filter, 51 TraceRow.range?.startNS ?? 0, 52 TraceRow.range?.endNS ?? 0, 53 TraceRow.range?.totalNS ?? 0, 54 row.frame, 55 groupBy10MS, 56 isDiskIO, 57 req.useCache || (TraceRow.range?.refresh ?? false) 58 ); 59 req.context.beginPath(); 60 let find = false; 61 let hoverRect: FileSysChartStruct | undefined = undefined; 62 for (let re of filter) { 63 if (row.isHover && re.frame && row.hoverX >= re.frame.x && row.hoverX <= re.frame.x + re.frame.width) { 64 if (hoverRect == undefined || re.size! > hoverRect.size!) { 65 hoverRect = re; 66 find = true; 67 } 68 } 69 if (re.frame && re.frame!.x > row.hoverX + 3) { 70 break; 71 } 72 } 73 if (hoverRect) { 74 FileSysChartStruct.hoverFileSysStruct = hoverRect; 75 } 76 for (let re of filter) { 77 FileSysChartStruct.draw(req.context, re, req.chartColor); 78 } 79 if (!find && row.isHover) FileSysChartStruct.hoverFileSysStruct = undefined; 80 req.context.closePath(); 81 } 82 83 render(fileSysRequest: RequestMessage, list: Array<any>, filter: Array<any>, dataList2: Array<any>) { 84 let groupBy10MS = fileSysRequest.scale > 20_000_000; 85 let isDiskIO: boolean = fileSysRequest.type!.includes('disk-io'); 86 if (isDiskIO) { 87 groupBy10MS = true; 88 } 89 if (fileSysRequest.lazyRefresh) { 90 fileSysChart( 91 list, 92 dataList2, 93 fileSysRequest.type!, 94 filter, 95 fileSysRequest.startNS, 96 fileSysRequest.endNS, 97 fileSysRequest.totalNS, 98 fileSysRequest.frame, 99 groupBy10MS, 100 isDiskIO, 101 fileSysRequest.useCache || !fileSysRequest.range.refresh 102 ); 103 } else { 104 if (!fileSysRequest.useCache) { 105 fileSysChart( 106 list, 107 dataList2, 108 fileSysRequest.type!, 109 filter, 110 fileSysRequest.startNS, 111 fileSysRequest.endNS, 112 fileSysRequest.totalNS, 113 fileSysRequest.frame, 114 groupBy10MS, 115 isDiskIO, 116 false 117 ); 118 } 119 } 120 let hoverStruct: FileSysChartStruct | undefined; 121 if (fileSysRequest.canvas) { 122 fileSysRequest.context.clearRect(0, 0, fileSysRequest.frame.width, fileSysRequest.frame.height); 123 let arr = filter; 124 if (arr.length > 0 && !fileSysRequest.range.refresh && !fileSysRequest.useCache && fileSysRequest.lazyRefresh) { 125 drawLoading( 126 fileSysRequest.context, 127 fileSysRequest.startNS, 128 fileSysRequest.endNS, 129 fileSysRequest.totalNS, 130 fileSysRequest.frame, 131 arr[0].startNS, 132 arr[arr.length - 1].startNS + arr[arr.length - 1].dur 133 ); 134 } 135 drawLines(fileSysRequest.context, fileSysRequest.xs, fileSysRequest.frame.height, fileSysRequest.lineColor); 136 fileSysRequest.context.stroke(); 137 fileSysRequest.context.beginPath(); 138 if (fileSysRequest.isHover) { 139 let offset = groupBy10MS ? 0 : 3; 140 for (let re of filter) { 141 if ( 142 re.frame && 143 fileSysRequest.hoverX >= re.frame.x - offset && 144 fileSysRequest.hoverX <= re.frame.x + re.frame.width + offset 145 ) { 146 hoverStruct = re; 147 break; 148 } 149 } 150 } 151 for (let re of filter) { 152 FileSysChartStruct.draw(fileSysRequest.context, re, fileSysRequest.chartColor); 153 } 154 drawSelection(fileSysRequest.context, fileSysRequest.params); 155 fileSysRequest.context.closePath(); 156 drawFlagLine( 157 fileSysRequest.context, 158 fileSysRequest.flagMoveInfo, 159 fileSysRequest.flagSelectedInfo, 160 fileSysRequest.startNS, 161 fileSysRequest.endNS, 162 fileSysRequest.totalNS, 163 fileSysRequest.frame, 164 fileSysRequest.slicesTime 165 ); 166 } 167 let msg = { 168 id: fileSysRequest.id, 169 type: fileSysRequest.type, 170 results: fileSysRequest.canvas ? undefined : filter, 171 hover: hoverStruct, 172 }; 173 self.postMessage(msg); 174 } 175} 176 177export function fileSysChart( 178 arr: Array<any>, 179 arr2: Array<any>, 180 type: string, 181 res: Array<any>, 182 startNS: number, 183 endNS: number, 184 totalNS: number, 185 frame: any, 186 groupBy10MS: boolean, 187 isDiskIO: boolean, 188 use: boolean 189) { 190 if (use && res.length > 0) { 191 //&& !groupBy10MS 192 let pns = (endNS - startNS) / frame.width; 193 let y = frame.y; 194 for (let i = 0; i < res.length; i++) { 195 let it = res[i]; 196 if ((it.startNS || 0) + (it.dur || 0) > startNS && (it.startNS || 0) < endNS) { 197 if (!it.frame) { 198 it.frame = {}; 199 it.frame.y = y; 200 } 201 it.frame.height = it.height; 202 FileSysChartStruct.setFrame(it, pns, startNS, endNS, frame); 203 } else { 204 it.frame = null; 205 } 206 } 207 return; 208 } 209 res.length = 0; 210 if (arr) { 211 let list: Array<any> = []; 212 let pns = (endNS - startNS) / frame.width; 213 let y = frame.y; 214 if (groupBy10MS) { 215 list = arr2.filter((it) => (it.startNS || 0) + (it.dur || 0) > startNS && (it.startNS || 0) < endNS); 216 let groups = list 217 .map((it) => { 218 if (!it.frame) { 219 it.frame = {}; 220 it.frame.y = y; 221 } 222 it.frame.height = it.height; 223 FileSysChartStruct.setFrame(it, pns, startNS, endNS, frame); 224 return it; 225 }) 226 .reduce((pre, current, index, arr) => { 227 (pre[`${current.frame.x}`] = pre[`${current.frame.x}`] || []).push(current); 228 return pre; 229 }, {}); 230 Reflect.ownKeys(groups).map((kv) => { 231 res.push(groups[kv][0]); 232 }); 233 } else { 234 let filter = arr.filter((it) => (it.startNS || 0) + (it.dur || 0) > startNS && (it.startNS || 0) < endNS); 235 list = isDiskIO 236 ? FileSysChartStruct.computeHeightNoGroupLatency(filter, totalNS) 237 : FileSysChartStruct.computeHeightNoGroup(filter, totalNS); 238 list.map((it) => { 239 if (!it.frame) { 240 it.frame = {}; 241 it.frame.y = y; 242 } 243 it.frame.height = it.height; 244 FileSysChartStruct.setFrame(it, pns, startNS, endNS, frame); 245 res.push(it); 246 }); 247 } 248 } 249} 250 251export class FileSysChartStruct extends BaseStruct { 252 static hoverFileSysStruct: FileSysChartStruct | undefined; 253 startNS: number | undefined; 254 endNS: number | undefined; 255 dur: number | undefined; 256 size: number | undefined; 257 height: number | undefined; 258 group10Ms: boolean | undefined; 259 260 static draw(ctx: CanvasRenderingContext2D, data: FileSysChartStruct, chartColor: string) { 261 if (data.frame) { 262 ctx.fillStyle = chartColor; 263 ctx.strokeStyle = chartColor; 264 ctx.fillRect(data.frame.x, 40 - (data.height || 0), data.frame.width, data.height || 0); 265 } 266 } 267 268 static setFrame(fileSystemNode: any, pns: number, startNS: number, endNS: number, frame: any) { 269 if ((fileSystemNode.startNS || 0) < startNS) { 270 fileSystemNode.frame.x = 0; 271 } else { 272 fileSystemNode.frame.x = Math.floor(((fileSystemNode.startNS || 0) - startNS) / pns); 273 } 274 if ((fileSystemNode.startNS || 0) + (fileSystemNode.dur || 0) > endNS) { 275 fileSystemNode.frame.width = frame.width - fileSystemNode.frame.x; 276 } else { 277 fileSystemNode.frame.width = Math.ceil( 278 ((fileSystemNode.startNS || 0) + (fileSystemNode.dur || 0) - startNS) / pns - fileSystemNode.frame.x 279 ); 280 } 281 if (fileSystemNode.frame.width < 1) { 282 fileSystemNode.frame.width = 1; 283 } 284 } 285 286 static computeHeightNoGroup(array: Array<any>, totalNS: number): Array<any> { 287 if (array.length > 0) { 288 let time: Array<{ time: number; type: number }> = []; 289 array.map((item) => { 290 time.push({ time: item.startNS, type: 1 }); 291 time.push({ time: item.endNS || totalNS, type: -1 }); 292 }); 293 time = time.sort((a, b) => a.time - b.time); 294 let arr: Array<any> = []; 295 let first = { 296 startNS: time[0].time ?? 0, 297 dur: 0, 298 size: 1, 299 group10Ms: false, 300 height: 1, 301 }; 302 arr.push(first); 303 let max = 2; 304 for (let i = 1, len = time.length; i < len; i++) { 305 let heap = { 306 startNS: time[i].time, 307 dur: 0, 308 size: 0, 309 group10Ms: false, 310 height: 0, 311 }; 312 arr[i - 1].dur = heap.startNS - arr[i - 1].startNS; 313 if (i == len - 1) { 314 heap.dur = totalNS - heap.startNS; 315 } 316 heap.size = arr[i - 1].size + time[i].type; 317 heap.height = Math.floor((heap.size / 6) * 36); 318 max = max > heap.size ? max : heap.size; 319 arr.push(heap); 320 } 321 arr.map((it) => (it.height = Math.floor((it.size / max) * 36))); 322 return arr; 323 } else { 324 return []; 325 } 326 } 327 328 static groupBy10MSWithCount(array: Array<any>): Array<any> { 329 let obj = array 330 .map((it) => { 331 it.timestamp_group = Math.trunc(it.startNS / 1_000_000_0) * 1_000_000_0; 332 return it; 333 }) 334 .reduce((pre, current) => { 335 (pre[current['timestamp_group']] = pre[current['timestamp_group']] || []).push(current); 336 return pre; 337 }, {}); 338 let arr: any[] = []; 339 let max = 1; 340 for (let aKey in obj) { 341 max = obj[aKey].length > max ? obj[aKey].length : max; 342 } 343 for (let aKey in obj) { 344 let ns = parseInt(aKey); 345 let height: number = Math.floor((obj[aKey].length / max) * 36); 346 arr.push({ 347 startNS: ns, 348 dur: 1_000_000_0, 349 group10Ms: true, 350 size: obj[aKey].length, 351 height: height < 1 ? 1 : height, 352 }); 353 } 354 return arr; 355 } 356 357 static computeHeightNoGroupLatency(array: Array<any>, totalNS: number): Array<any> { 358 if (array.length > 0) { 359 let max = 0; 360 let arr: Array<any> = []; 361 for (let io of array) { 362 let ioItem = { 363 startNS: io.startNS, 364 dur: io.endNS > totalNS ? totalNS - io.startNS : io.endNS - io.startNS, 365 size: io.dur, 366 group10Ms: false, 367 height: 0, 368 }; 369 max = max > ioItem.size ? max : ioItem.size; 370 arr.push(ioItem); 371 } 372 arr.map((it) => { 373 let height = Math.floor((it.size / max) * 36); 374 it.height = height < 1 ? 1 : height; 375 }); 376 return arr; 377 } else { 378 return []; 379 } 380 } 381 382 static groupBy10MSWithMaxLatency(array: Array<any>): Array<any> { 383 let obj = array 384 .map((it) => { 385 it.timestamp_group = Math.trunc(it.startNS / 1_000_000_0) * 1_000_000_0; 386 return it; 387 }) 388 .reduce((pre, current) => { 389 if (pre[current['timestamp_group']] == undefined || pre[current['timestamp_group']] == null) { 390 pre[current['timestamp_group']] = []; 391 } 392 if (pre[current['timestamp_group']].length > 0) { 393 let p = pre[current['timestamp_group']][0]; 394 if (p.dur < current.dur) { 395 pre[current['timestamp_group']][0] = current; 396 } 397 } else { 398 pre[current['timestamp_group']][0] = current; 399 } 400 return pre; 401 }, {}); 402 let arr: any[] = []; 403 let max = 1; 404 for (let aKey in obj) { 405 max = obj[aKey][0].dur > max ? obj[aKey][0].dur : max; 406 } 407 for (let aKey in obj) { 408 let ns = parseInt(aKey); 409 let height: number = Math.floor((obj[aKey][0].dur / max) * 36); 410 arr.push({ 411 startNS: ns, 412 dur: 1_000_000_0, 413 group10Ms: true, 414 size: obj[aKey][0].dur, 415 height: height < 1 ? 1 : height, 416 }); 417 } 418 return arr; 419 } 420} 421