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 { BaseElement, element } from '../../../../../base-ui/BaseElement'; 17import { SelectionParam } from '../../../../bean/BoxSelection'; 18import { TraceRow } from '../../base/TraceRow'; 19import { TraceSheet } from '../../base/TraceSheet'; 20import { Flag } from '../../timer-shaft/Flag'; 21import { SpSystemTrace } from '../../../SpSystemTrace'; 22import { ns2Timestamp, ns2x, Rect } from '../../../../database/ui-worker/ProcedureWorkerCommon'; 23import { ColorUtils } from '../../base/ColorUtils'; 24import { LitPageTable } from '../../../../../base-ui/table/LitPageTable'; 25import { LitProgressBar } from '../../../../../base-ui/progress-bar/LitProgressBar'; 26import { TabPaneHangHtml } from './TabPaneHang.html'; 27import { HangStruct } from '../../../../database/ui-worker/ProcedureWorkerHang'; 28import { queryAllHangs } from '../../../../database/sql/Hang.sql'; 29import { HangType, SpHangChart } from '../../../chart/SpHangChart'; 30import { getTimeString } from '../TabPaneCurrentSelection'; 31 32/// Hangs 框选Tab页1 33@element('tab-hang') 34export class TabPaneHang extends BaseElement { 35 // Elements 36 private spSystemTrace: SpSystemTrace | undefined | null; 37 private traceSheetEl: TraceSheet | undefined | null; 38 private levelFilterInput: HTMLSelectElement | undefined | null; 39 private searchFilterInput: HTMLInputElement | undefined | null; 40 private processFilter: HTMLInputElement | undefined | null; 41 private hangTableTitle: HTMLDivElement | undefined | null; 42 private hangTbl: LitPageTable | undefined | null; 43 44 private tableTimeHandle: (() => void) | undefined; 45 private tableTitleTimeHandle: (() => void) | undefined; 46 private systemHangSource: HangStructInPane[] = []; 47 private filterData: HangStructInPane[] = []; 48 49 private optionLevel: string[] = ['Instant', 'Circumstantial', 'Micro', 'Severe']; 50 private allowTag: Set<string> = new Set(); 51 private progressEL: LitProgressBar | null | undefined; 52 private timeOutId: number | undefined; 53 54 /// 框选时段范围时触发 55 set data(selectionParam: SelectionParam) { 56 if (this.hangTbl) { 57 this.hangTbl.recycleDataSource = []; 58 this.filterData = []; 59 } 60 window.clearTimeout(this.timeOutId); 61 queryAllHangs().then((ret) => { 62 const filter = new Set([...selectionParam.hangMapData.keys()].map(key => key.split(' ').at(-1))); 63 ret = ret.filter(struct => ( 64 filter.has(`${struct.pid ?? 0}`) && 65 ((struct.startTime ?? 0) <= selectionParam.rightNs) && 66 (selectionParam.leftNs <= ((struct.startTime ?? 0) + (struct.dur ?? 0))) 67 )); 68 69 if (ret.length === 0) { 70 this.progressEL!.loading = false; 71 } 72 this.systemHangSource = ret.map(HangStructInPane.new); 73 this.refreshTable(); 74 }); 75 } 76 77 init(): void { 78 this.levelFilterInput = this.shadowRoot?.querySelector<HTMLSelectElement>('#level-filter'); 79 this.hangTableTitle = this.shadowRoot?.querySelector<HTMLDivElement>('#hang-title'); 80 this.searchFilterInput = this.shadowRoot?.querySelector<HTMLInputElement>('#search-filter'); 81 this.processFilter = this.shadowRoot?.querySelector<HTMLInputElement>('#process-filter'); 82 this.spSystemTrace = document.querySelector('body > sp-application')?.shadowRoot?.querySelector<SpSystemTrace>('#sp-system-trace'); 83 this.tableTimeHandle = this.delayedRefresh(this.refreshTable); 84 this.tableTitleTimeHandle = this.delayedRefresh(this.refreshHangsTitle); 85 this.hangTbl = this.shadowRoot?.querySelector<LitPageTable>('#tb-hang'); 86 this.progressEL = this.shadowRoot?.querySelector('.progress') as LitProgressBar; 87 this.hangTbl!.getItemTextColor = (data): string => { 88 const hangData = data as HangStructInPane; 89 return ColorUtils.getHangColor(hangData.type as HangType); 90 }; 91 this.hangTbl!.itemTextHandleMap.set('startTime', (startTs) => { 92 // @ts-ignore 93 return ns2Timestamp(startTs); 94 }); 95 this.hangTbl!.addEventListener('row-hover', (e): void => { 96 // @ts-ignore 97 let data = e.detail.data as HangStructInPane; 98 if (data) { 99 let pointX: number = ns2x( 100 data.startTime || 0, 101 TraceRow.range!.startNS, 102 TraceRow.range!.endNS, 103 TraceRow.range!.totalNS, 104 new Rect(0, 0, TraceRow.FRAME_WIDTH, 0), 105 ); 106 this.traceSheetEl!.systemLogFlag = new Flag( 107 Math.floor(pointX), 0, 0, 0, data.startTime, '#999999', '', true, '', 108 ); 109 this.spSystemTrace?.refreshCanvas(false); 110 } 111 }); 112 let tbl = this.hangTbl?.shadowRoot?.querySelector<HTMLDivElement>('.table'); 113 tbl!.addEventListener('scroll', () => { 114 this.tableTitleTimeHandle?.(); 115 }); 116 this.hangTbl!.addEventListener('column-click', (evt) => { 117 // @ts-ignore 118 this.sortKey = evt.detail.key; 119 // @ts-ignore 120 this.sortType = evt.detail.sort; 121 // @ts-ignore 122 this.sortByColumn(evt.detail.key, evt.detail.sort); 123 this.refreshHangTab(); 124 }); 125 } 126 127 initElements(): void { 128 this.init(); 129 this.searchFilterInput!.oninput = (): void => { 130 this.tableTimeHandle?.(); 131 }; 132 this.processFilter!.oninput = (): void => { 133 this.tableTimeHandle?.(); 134 }; 135 this.levelFilterInput!.onchange = (): void => { 136 this.tableTimeHandle?.(); 137 }; 138 } 139 140 connectedCallback(): void { 141 super.connectedCallback(); 142 new ResizeObserver((): void => { 143 this.parentElement!.style.overflow = 'hidden'; 144 if (this.hangTbl) { 145 // @ts-ignore 146 this.hangTbl.shadowRoot.querySelector('.table').style.height = 147 this.parentElement!.clientHeight - 20 - 45 + 'px'; 148 } 149 if (this.filterData.length > 0) { 150 this.refreshTable(); 151 this.tableTitleTimeHandle?.(); 152 } 153 }).observe(this.parentElement!); 154 } 155 156 disconnectedCallback(): void { 157 super.disconnectedCallback(); 158 } 159 160 initHtml(): string { 161 return TabPaneHangHtml; 162 } 163 164 refreshHangTab(): void { 165 let tbl = this.hangTbl?.shadowRoot?.querySelector<HTMLDivElement>('.table'); 166 let height = 0; 167 if (tbl) { 168 const trs = tbl.querySelectorAll<HTMLElement>('.tr'); 169 trs.forEach((trEl: HTMLElement, index: number): void => { 170 if (index === 0) { 171 let frontTotalRowSize = Math.round((tbl!.scrollTop / trEl.clientHeight) * 100) / 100; 172 if (frontTotalRowSize.toString().indexOf('.') >= 0) { 173 let rowCount = frontTotalRowSize.toString().split('.'); 174 height += trEl.clientHeight - (Number(rowCount[1]) / 100) * trEl.clientHeight; 175 } 176 } 177 let allTdEl = trEl.querySelectorAll<HTMLElement>('.td'); 178 allTdEl[0].style.color = '#3D88C7'; 179 allTdEl[0].style.textDecoration = 'underline'; 180 allTdEl[0].style.textDecorationColor = '#3D88C7'; 181 }); 182 } 183 } 184 185 refreshHangsTitle(): void { 186 let tbl = this.hangTbl?.shadowRoot?.querySelector<HTMLDivElement>('.table'); 187 let height = 0; 188 let firstRowHeight = 27; 189 let tableHeadHeight = 26; 190 this.refreshHangTab(); 191 if (this.hangTbl && this.hangTbl.currentRecycleList.length > 0) { 192 let startDataIndex = this.hangTbl.startSkip + 1; 193 let endDataIndex = startDataIndex; 194 let crossTopHeight = tbl!.scrollTop % firstRowHeight; 195 let topShowHeight = crossTopHeight === 0 ? 0 : firstRowHeight - crossTopHeight; 196 if (topShowHeight < firstRowHeight * 0.3) { 197 startDataIndex++; 198 } 199 let tableHeight = Number(tbl!.style.height.replace('px', '')) - tableHeadHeight; 200 while (height < tableHeight) { 201 if (firstRowHeight <= 0 || height + firstRowHeight > tableHeight) { 202 break; 203 } 204 height += firstRowHeight; 205 endDataIndex++; 206 } 207 if (tableHeight - height - topShowHeight > firstRowHeight * 0.3) { 208 endDataIndex++; 209 } 210 if (endDataIndex >= this.filterData.length) { 211 endDataIndex = this.filterData.length; 212 } else { 213 endDataIndex = this.hangTbl.startSkip === 0 ? endDataIndex - 1 : endDataIndex; 214 } 215 this.hangTableTitle!.textContent = `Hangs [${this.hangTbl.startSkip === 0 ? 1 : startDataIndex}, 216 ${endDataIndex}] / ${this.filterData.length || 0}`; 217 } else { 218 this.hangTableTitle!.textContent = 'Hangs [0, 0] / 0'; 219 } 220 if (this.hangTbl!.recycleDataSource.length > 0) { 221 this.progressEL!.loading = false; 222 } 223 } 224 225 initTabSheetEl(traceSheet: TraceSheet): void { 226 this.traceSheetEl = traceSheet; 227 this.levelFilterInput!.selectedIndex = 0; 228 this.allowTag.clear(); 229 this.processFilter!.value = ''; 230 this.searchFilterInput!.value = ''; 231 } 232 233 private updateFilterData(): void { 234 if (this.systemHangSource?.length > 0) { 235 this.filterData = this.systemHangSource.filter((data) => this.isFilterHang(data)); 236 } 237 if (this.hangTbl) { 238 // @ts-ignore 239 this.hangTbl.shadowRoot.querySelector('.table').style.height = this.parentElement.clientHeight - 20 - 45 + 'px'; 240 } 241 if (this.filterData.length > 0) { 242 this.hangTbl!.recycleDataSource = this.filterData; 243 } else { 244 this.hangTbl!.recycleDataSource = []; 245 } 246 this.refreshHangsTitle(); 247 } 248 249 private isFilterHang(data: HangStructInPane): boolean { 250 let type = this.levelFilterInput?.selectedIndex ?? 0; 251 let search = this.searchFilterInput?.value.toLocaleLowerCase() ?? ''; 252 let process = this.processFilter?.value.toLocaleLowerCase() ?? ''; 253 return ( 254 (type === 0 || this.optionLevel.indexOf(data.type) >= type) && 255 (search === '' || data.caller.toLocaleLowerCase().indexOf(search) >= 0) && 256 (process === '' || data.pname.toLocaleLowerCase().indexOf(process) >= 0) 257 ); 258 } 259 260 private refreshTable(): void { 261 if (this.traceSheetEl) { 262 this.traceSheetEl.systemLogFlag = undefined; 263 this.spSystemTrace?.refreshCanvas(false); 264 this.updateFilterData(); 265 } 266 } 267 268 private delayedRefresh(optionFn: Function, dur: number = tableTimeOut): () => void { 269 return (...args: []): void => { 270 window.clearTimeout(this.timeOutId); 271 this.timeOutId = window.setTimeout((): void => { 272 optionFn.apply(this, ...args); 273 }, dur); 274 }; 275 } 276 277 sortByColumn(key: string, type: number): void { 278 if (type === 0) { 279 this.hangTbl!.recycleDataSource = this.filterData; 280 } else { 281 let arr = Array.from(this.filterData); 282 arr.sort((a, b): number => { 283 if (key === 'startTime') { 284 if (type === 1) { 285 // @ts-ignore 286 return a.startTime - b.startTime; 287 } else { 288 // @ts-ignore 289 return b.startTime - a.startTime; 290 } 291 } else if (key === 'durStr') { 292 if (type === 1) { 293 // @ts-ignore 294 return a.dur - b.dur; 295 } else { 296 // @ts-ignore 297 return b.dur - a.dur; 298 } 299 } else if (key === 'type') { 300 if (type === 1) { 301 // @ts-ignore 302 return a[key].localeCompare(b[key]); 303 } else { 304 // @ts-ignore 305 return b[key].localeCompare(a[key]); 306 } 307 } else if (key === 'pname') { 308 if (type === 1) { 309 // @ts-ignore 310 return a[key].localeCompare(b[key]); 311 } else { 312 // @ts-ignore 313 return b[key].localeCompare(a[key]); 314 } 315 } else if (key === 'sendEventTid') { 316 if (type === 1) { 317 // @ts-ignore 318 return Number(a.sendEventTid) - Number(b.sendEventTid); 319 } else { 320 // @ts-ignore 321 return Number(b.sendEventTid) - Number(a.sendEventTid); 322 } 323 } else if (key === 'sendTime') { 324 if (type === 1) { 325 // @ts-ignore 326 return Number(a.sendTime) - Number(b.sendTime); 327 } else { 328 // @ts-ignore 329 return Number(b.sendTime) - Number(a.sendTime); 330 } 331 } else if (key === 'expectHandleTime') { 332 if (type === 1) { 333 // @ts-ignore 334 return Number(a.expectHandleTime) - Number(b.expectHandleTime); 335 } else { 336 // @ts-ignore 337 return Number(b.expectHandleTime) - Number(a.expectHandleTime); 338 } 339 } else if (key === 'taskNameId') { 340 if (type === 1) { 341 // @ts-ignore 342 return a[key].localeCompare(b[key]); 343 } else { 344 // @ts-ignore 345 return b[key].localeCompare(a[key]); 346 } 347 } else if (key === 'prio') { 348 if (type === 1) { 349 // @ts-ignore 350 return a.prio - b.prio; 351 } else { 352 // @ts-ignore 353 return b.prio - a.prio; 354 } 355 } else if (key === 'caller') { 356 if (type === 1) { 357 // @ts-ignore 358 return a[key].localeCompare(b[key]); 359 } else { 360 // @ts-ignore 361 return b[key].localeCompare(a[key]); 362 } 363 } else { 364 return 0; 365 } 366 367 }); 368 369 this.hangTbl!.recycleDataSource = arr; 370 } 371 } 372} 373 374let defaultIndex: number = 1; 375let tableTimeOut: number = 50; 376 377export class HangStructInPane { 378 startTime: number = 0; 379 dur: number = 0; 380 durStr: string = '0'; 381 pname: string = 'Process'; 382 type: string; 383 sendEventTid: string; 384 sendTime: string; 385 expectHandleTime: string; 386 taskNameId: string; 387 prio: string; 388 caller: string; 389 390 constructor(parent: HangStruct) { 391 this.startTime = parent.startTime ?? this.startTime; 392 this.dur = parent.dur ?? 0; 393 this.durStr = getTimeString(parent.dur ?? 0); 394 this.pname = `${parent.pname ?? this.pname} ${parent.pid ?? ''}`.trim(); 395 this.type = SpHangChart.calculateHangType(parent.dur ?? 0); 396 [this.sendEventTid, this.sendTime, this.expectHandleTime, this.taskNameId, this.prio, this.caller] = (parent.content ?? ',0,0,,,').split(',').map(i => i.trim()); 397 this.sendEventTid = this.sendEventTid.split(':').at(-1)!; 398 } 399 400 static new(parent: HangStruct): HangStructInPane { 401 return new HangStructInPane(parent); 402 } 403}