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 { HiSysEventStruct } from '../../../../database/ui-worker/ProcedureWorkerHiSysEvent'; 19import { ns2x, Rect } from '../../../../database/ui-worker/ProcedureWorkerCommon'; 20import { LitPageTable } from '../../../../../base-ui/table/LitPageTable'; 21import { LitTable } from '../../../../../base-ui/table/lit-table'; 22import { LitSlicerTrack } from '../../../../../base-ui/slicer/lit-slicer'; 23import { TraceRow } from '../../base/TraceRow'; 24import { Flag } from '../../timer-shaft/Flag'; 25import { TraceSheet } from '../../base/TraceSheet'; 26import { SpSystemTrace } from '../../../SpSystemTrace'; 27import { ColorUtils } from '../../base/ColorUtils'; 28import { queryHiSysEventTabData } from '../../../../database/sql/Perf.sql'; 29import { queryRealTime } from '../../../../database/sql/Clock.sql'; 30import { TabPaneHiSysEventsHtml } from './TabPaneHisysEvents.html'; 31 32@element('tab-hisysevents') 33export class TabPaneHisysEvents extends BaseElement { 34 private hisysEventSource: Array<HiSysEventStruct> = []; 35 private filterDataList: HiSysEventStruct[] = []; 36 private hiSysEventTable: LitPageTable | undefined | null; 37 private currentSelection: SelectionParam | undefined; 38 private domainFilterInput: HTMLInputElement | undefined | null; 39 private eventNameFilterInput: HTMLInputElement | undefined | null; 40 private levelFilter: HTMLSelectElement | undefined | null; 41 private contentFilterInput: HTMLInputElement | undefined | null; 42 private domainTag: Set<string> = new Set(); 43 private domainTagDiv: HTMLDivElement | undefined | null; 44 private eventNameTag: Set<string> = new Set(); 45 private eventNameTagDiv: HTMLDivElement | undefined | null; 46 private traceSheetEl: TraceSheet | undefined | null; 47 private spSystemTrace: SpSystemTrace | undefined | null; 48 private detailsTbl: LitTable | null | undefined; 49 private boxDetails: HTMLDivElement | null | undefined; 50 private slicerTrack: LitSlicerTrack | null | undefined; 51 private tableElement: HTMLDivElement | undefined | null; 52 private detailbox: HTMLDivElement | null | undefined; 53 private changeInput: HTMLInputElement | null | undefined; 54 private eventTableTitle: HTMLLabelElement | undefined | null; 55 tableTitleTimeHandle: (() => void) | undefined; 56 private currentDetailList: Array<{ key: string; value: string }> = []; 57 private realTime: number = -1; 58 private bootTime: number = -1; 59 private baseTime: string = ''; 60 61 set data(systemEventParam: SelectionParam) { 62 if (systemEventParam === this.currentSelection) { 63 return; 64 } 65 if (this.hiSysEventTable) { 66 this.hiSysEventTable.recycleDataSource = []; 67 this.filterDataList = []; 68 } 69 if (this.detailsTbl) { 70 this.detailsTbl!.recycleDataSource = []; 71 } 72 this.initTabSheetEl(); 73 queryRealTime().then((result) => { 74 if (result && result.length > 0) { 75 result.forEach((item) => { 76 if (item.name === 'realtime') { 77 this.realTime = item.ts; 78 } else { 79 this.bootTime = item.ts; 80 } 81 }); 82 } 83 queryHiSysEventTabData(systemEventParam.leftNs, systemEventParam.rightNs).then((res) => { 84 this.currentSelection = systemEventParam; 85 systemEventParam.sysAllEventsData = res; 86 this.hiSysEventTable!.recycleDataSource = res; 87 this.hisysEventSource = res; 88 this.updateData(); 89 }); 90 }); 91 } 92 93 queryElements(): void { 94 this.boxDetails = this.shadowRoot?.querySelector<HTMLDivElement>('.box-details'); 95 this.hiSysEventTable = this.shadowRoot?.querySelector<LitPageTable>('#tb-hisysevent'); 96 this.hiSysEventTable!.getItemTextColor = (data): string => { 97 // @ts-ignore 98 return ColorUtils.getHisysEventColor(data.level); 99 }; 100 this.domainTagDiv = this.shadowRoot?.querySelector<HTMLDivElement>('#domainTagFilter'); 101 this.eventNameTagDiv = this.shadowRoot?.querySelector<HTMLDivElement>('#eventNameTagFilter'); 102 this.domainFilterInput = this.shadowRoot?.querySelector<HTMLInputElement>('#domain-filter'); 103 this.eventNameFilterInput = this.shadowRoot?.querySelector<HTMLInputElement>('#event-name-filter'); 104 this.levelFilter = this.shadowRoot?.querySelector<HTMLSelectElement>('#level-filter'); 105 this.spSystemTrace = document 106 .querySelector('body > sp-application') 107 ?.shadowRoot?.querySelector<SpSystemTrace>('#sp-system-trace'); 108 this.traceSheetEl = this.spSystemTrace?.shadowRoot?.querySelector('.trace-sheet'); 109 this.contentFilterInput = this.shadowRoot?.querySelector<HTMLInputElement>('#contents-filter'); 110 this.changeInput = this.shadowRoot?.querySelector<HTMLInputElement>('#contents-change'); 111 this.detailsTbl = this.shadowRoot?.querySelector<LitTable>('#tb-hisysevent-data'); 112 this.slicerTrack = this.shadowRoot?.querySelector<LitSlicerTrack>('lit-slicer-track'); 113 this.detailbox = this.shadowRoot?.querySelector<HTMLDivElement>('.detail-content'); 114 this.tableElement = this.hiSysEventTable?.shadowRoot?.querySelector('.table') as HTMLDivElement; 115 this.eventTableTitle = this.shadowRoot?.querySelector<HTMLLabelElement>('#event-title'); 116 this.tableTitleTimeHandle = this.delayedRefresh(this.refreshEventsTitle); 117 this.initHiSysEventListener(); 118 } 119 120 /** 121 * 按ns去补0 122 * 123 * @param timestamp 124 * @private 125 */ 126 private timestampToNS(timestamp: string): number { 127 return Number(timestamp.toString().padEnd(19, '0')); 128 } 129 130 private initHiSysEventListener(): void { 131 this.hiSysEventTable!.addEventListener('row-click', (event) => { 132 this.changeInput!.value = ''; 133 // @ts-ignore 134 const data = event.detail.data; 135 this.convertData(data); 136 this.hiSysEventTable?.clearAllSelection(); 137 data.isSelected = true; 138 this.hiSysEventTable?.setCurrentSelection(data); 139 this.updateDetail(this.baseTime); 140 }); 141 this.boxDetails!.addEventListener('click', (ev) => { 142 if (ev.target !== this.hiSysEventTable) { 143 this.hiSysEventTable?.clearAllSelection(); 144 this.detailsTbl!.dataSource = []; 145 this.boxDetails!.style.width = '100%'; 146 this.detailbox!.style.display = 'none'; 147 this.slicerTrack!.style.visibility = 'hidden'; 148 this.detailsTbl!.style.paddingLeft = '0px'; 149 } 150 }); 151 this.hiSysEventTable!.addEventListener('row-hover', (e) => { 152 // @ts-ignore 153 let data = e.detail.data; 154 if (data) { 155 this.drawFlag(data.startTs, '#999999'); 156 } 157 }); 158 this.hiSysEventTable!.addEventListener('column-click', (evt) => { 159 // @ts-ignore 160 this.sortByColumn(evt.detail); 161 }); 162 this.tableElement?.addEventListener('mouseout', () => { 163 this.traceSheetEl!.systemLogFlag = undefined; 164 this.spSystemTrace?.refreshCanvas(false); 165 }); 166 } 167 168 initElements(): void { 169 this.queryElements(); 170 this.detailsTbl!.addEventListener('row-hover', (e) => { 171 // @ts-ignore 172 let data = e.detail.data; 173 if (data && data.key && this.realTime >= 0) { 174 if (data.key.endsWith('_TIME') || data.key.endsWith('_LATENCY')) { 175 this.drawFlag(data.value, '#999999'); 176 return; 177 } 178 } 179 this.traceSheetEl!.systemLogFlag = undefined; 180 this.spSystemTrace?.refreshCanvas(false); 181 }); 182 } 183 184 private delayedRefresh(optionFn: Function, dur: number = 50): () => void { 185 let timeOutId: number; 186 return (...args: []): void => { 187 window.clearTimeout(timeOutId); 188 timeOutId = window.setTimeout((): void => { 189 optionFn.apply(this, ...args); 190 }, dur); 191 }; 192 } 193 194 private refreshEventsTitle(): void { 195 let tbl = this.hiSysEventTable?.shadowRoot?.querySelector<HTMLDivElement>('.table'); 196 let height = 0; 197 let firstRowHeight = 27; 198 let tableHeadHeight = 26; 199 if (this.hiSysEventTable && this.hiSysEventTable.currentRecycleList.length > 0) { 200 let startDataIndex = this.hiSysEventTable.startSkip + 1; 201 let endDataIndex = startDataIndex; 202 let crossTopHeight = tbl!.scrollTop % firstRowHeight; 203 let topShowHeight = crossTopHeight === 0 ? 0 : firstRowHeight - crossTopHeight; 204 if (topShowHeight < firstRowHeight * 0.3) { 205 startDataIndex++; 206 } 207 let tableHeight = Number(tbl!.style.height.replace('px', '')) - tableHeadHeight; 208 while (height < tableHeight) { 209 if (firstRowHeight <= 0 || height + firstRowHeight > tableHeight) { 210 break; 211 } 212 height += firstRowHeight; 213 endDataIndex++; 214 } 215 if (tableHeight - height - topShowHeight > firstRowHeight * 0.3) { 216 endDataIndex++; 217 } 218 if (endDataIndex >= this.filterDataList.length) { 219 endDataIndex = this.filterDataList.length; 220 } else { 221 endDataIndex = this.hiSysEventTable.startSkip === 0 ? endDataIndex - 1 : endDataIndex; 222 } 223 this.eventTableTitle!.textContent = `HisysEvents [${this.hiSysEventTable.startSkip === 0 ? 1 : startDataIndex}, 224 ${endDataIndex}] / ${this.filterDataList.length || 0}`; 225 } else { 226 this.eventTableTitle!.textContent = 'HisysEvents [0, 0] / 0'; 227 } 228 } 229 230 initTabSheetEl(): void { 231 this.levelFilter!.selectedIndex = 0; 232 this.domainFilterInput!.value = ''; 233 this.domainTagDiv!.innerHTML = ''; 234 this.domainTag.clear(); 235 this.eventNameFilterInput!.value = ''; 236 this.eventNameTagDiv!.innerHTML = ''; 237 this.eventNameTag.clear(); 238 this.contentFilterInput!.value = ''; 239 this.boxDetails!.style.width = '100%'; 240 this.detailbox!.style.display = 'none'; 241 this.slicerTrack!.style.visibility = 'hidden'; 242 this.detailsTbl!.style.paddingLeft = '0px'; 243 } 244 245 initHtml(): string { 246 return TabPaneHiSysEventsHtml; 247 } 248 249 connectedCallback(): void { 250 this.domainFilterInput?.addEventListener('keyup', this.domainKeyEvent); 251 this.eventNameFilterInput?.addEventListener('keyup', this.eventNameKeyEvent); 252 this.contentFilterInput?.addEventListener('input', this.filterInputEvent); 253 this.levelFilter?.addEventListener('change', this.filterInputEvent); 254 this.domainTagDiv?.addEventListener('click', this.domainDivClickEvent); 255 this.eventNameTagDiv?.addEventListener('click', this.eventNameDivClickEvent); 256 this.changeInput?.addEventListener('input', this.changeInputEvent); 257 new ResizeObserver(() => { 258 this.tableElement!.style.height = `${this.parentElement!.clientHeight - 20 - 35}px`; 259 this.hiSysEventTable?.reMeauseHeight(); 260 this.detailsTbl!.style.height = `${this.parentElement!.clientHeight - 30}px`; 261 this.parentElement!.style.overflow = 'hidden'; 262 this.detailsTbl?.reMeauseHeight(); 263 this.updateData(); 264 this.tableTitleTimeHandle?.(); 265 }).observe(this.parentElement!); 266 let tbl = this.hiSysEventTable?.shadowRoot?.querySelector<HTMLDivElement>('.table'); 267 tbl!.addEventListener('scroll', () => { 268 this.tableTitleTimeHandle?.(); 269 }); 270 } 271 272 disconnectedCallback(): void { 273 super.disconnectedCallback(); 274 this.domainFilterInput?.removeEventListener('keyup', this.domainKeyEvent); 275 this.eventNameFilterInput?.removeEventListener('keyup', this.eventNameKeyEvent); 276 this.contentFilterInput?.removeEventListener('input', this.filterInputEvent); 277 this.levelFilter?.addEventListener('change', this.filterInputEvent); 278 this.changeInput?.addEventListener('input', this.changeInputEvent); 279 this.domainTagDiv?.removeEventListener('click', this.domainDivClickEvent); 280 this.eventNameTagDiv?.removeEventListener('click', this.eventNameDivClickEvent); 281 } 282 283 filterInputEvent = (): void => { 284 this.updateData(); 285 }; 286 287 domainDivClickEvent = (ev: Event): void => { 288 // @ts-ignore 289 let parentNode = ev.target.parentNode; 290 if (parentNode && this.domainTagDiv!.contains(parentNode)) { 291 this.domainTagDiv!.removeChild(parentNode); 292 this.domainTag.delete(parentNode.textContent.trim().toLowerCase()); 293 } 294 this.updateData(); 295 }; 296 297 eventNameDivClickEvent = (ev: Event): void => { 298 // @ts-ignore 299 let parentNode = ev.target.parentNode; 300 if (parentNode && this.eventNameTagDiv!.contains(parentNode)) { 301 this.eventNameTagDiv!.removeChild(parentNode); 302 this.eventNameTag.delete(parentNode.textContent.trim().toLowerCase()); 303 } 304 this.updateData(); 305 }; 306 307 domainKeyEvent = (e: KeyboardEvent): void => { 308 let domainValue = this.domainFilterInput!.value.trim(); 309 if (e.key === 'Enter') { 310 if (domainValue !== '' && !this.domainTag.has(domainValue.toLowerCase()) && this.domainTag.size < 10) { 311 let tagElement = this.buildTag(domainValue); 312 this.domainTag.add(domainValue.toLowerCase()); 313 this.domainTagDiv!.append(tagElement); 314 this.domainFilterInput!.value = ''; 315 } 316 } else if (e.key === 'Backspace') { 317 let index = this.domainTagDiv!.childNodes.length - 1; 318 if (index >= 0 && domainValue === '') { 319 let childNode = this.domainTagDiv!.childNodes[index]; 320 this.domainTagDiv!.removeChild(childNode); 321 this.domainTag.delete(childNode.textContent!.trim().toLowerCase()); 322 } 323 } 324 this.updateData(); 325 }; 326 327 private buildTag(domainValue: string): HTMLDivElement { 328 let tagElement = document.createElement('div'); 329 tagElement.className = 'tagElement'; 330 tagElement.id = domainValue; 331 let tag = document.createElement('div'); 332 tag.className = 'tag'; 333 tag.innerHTML = domainValue; 334 let closeButton = document.createElement('lit-icon'); 335 closeButton.setAttribute('name', 'close-light'); 336 closeButton.style.color = '#FFFFFF'; 337 tagElement.append(tag); 338 tagElement.append(closeButton); 339 return tagElement; 340 } 341 342 eventNameKeyEvent = (e: KeyboardEvent): void => { 343 let eventNameValue = this.eventNameFilterInput!.value.trim(); 344 if (e.key === 'Enter') { 345 if ( 346 eventNameValue !== '' && 347 !this.eventNameTag.has(eventNameValue.toLowerCase()) && 348 this.eventNameTag.size < 10 349 ) { 350 let tagElement = this.buildTag(eventNameValue); 351 this.eventNameTag!.add(eventNameValue.toLowerCase()); 352 this.eventNameTagDiv!.append(tagElement); 353 this.eventNameFilterInput!.value = ''; 354 } 355 } else if (e.key === 'Backspace') { 356 let index = this.eventNameTagDiv!.childNodes.length - 1; 357 if (index >= 0 && eventNameValue === '') { 358 let childNode = this.eventNameTagDiv!.childNodes[index]; 359 this.eventNameTagDiv!.removeChild(childNode); 360 this.eventNameTag.delete(childNode.textContent!.trim().toLowerCase()); 361 } 362 } 363 this.updateData(); 364 }; 365 366 updateData(): void { 367 if (this.hisysEventSource.length > 0) { 368 this.filterDataList = this.hisysEventSource.filter((data) => this.filterData(data)); 369 } 370 if (this.filterDataList.length > 0) { 371 this.hiSysEventTable!.recycleDataSource = this.filterDataList; 372 } else { 373 this.hiSysEventTable!.recycleDataSource = []; 374 } 375 this.refreshEventsTitle(); 376 } 377 378 filterData(data: HiSysEventStruct): boolean { 379 let level = this.levelFilter?.value; 380 let contentsValue = this.contentFilterInput?.value.toLowerCase() || ''; 381 contentsValue = contentsValue.replace(/\s/g, ''); 382 return ( 383 (level === 'ALL' || data.level! === level) && 384 (this.domainTag.size === 0 || this.domainTag.has(data.domain!.toLowerCase())) && 385 (this.eventNameTag.size === 0 || this.eventNameTag.has(data.eventName!.toLowerCase())) && 386 (contentsValue === '' || data.contents!.toLowerCase().replace(/\s/g, '').indexOf(contentsValue) >= 0) 387 ); 388 } 389 390 changeInputEvent = (): void => { 391 const changeValue = this.changeInput!.value; 392 const currentValue = changeValue; 393 if (!/^[0-9]*$/.test(changeValue) || isNaN(Number(currentValue))) { 394 this.changeInput!.value = ''; 395 this.updateDetail(this.baseTime); 396 } else { 397 this.updateDetail(this.changeInput!.value); 398 } 399 }; 400 401 updateDetail(baseTime: string): void { 402 const latencySuffix = '_LATENCY'; 403 let detailList: Array<{ key: string; value: string }> = []; 404 this.currentDetailList.forEach((item) => { 405 const latencyValue = item.key && item.key.endsWith(latencySuffix) ? item.value + Number(baseTime) : item.value; 406 detailList.push({ 407 key: item.key, 408 value: latencyValue, 409 }); 410 }); 411 this.detailsTbl!.recycleDataSource = detailList; 412 } 413 414 convertData = (data: HiSysEventStruct): void => { 415 this.baseTime = ''; 416 this.currentDetailList = [ 417 { 418 key: 'key', 419 value: 'value', 420 }, 421 ]; 422 const content = JSON.parse(data.contents ?? '{}'); 423 if (content && typeof content === 'object') { 424 let isFirstTime = true; 425 let keyList = Object.keys(content); 426 keyList.forEach((key) => { 427 const value: string = content[key]; 428 let contentValue = value; 429 if (key.endsWith('_TIME')) { 430 if (!isNaN(Number(value))) { 431 contentValue = (this.timestampToNS(value) - this.realTime + this.bootTime).toString(); 432 if (this.realTime < 0) { 433 contentValue = value; 434 } 435 if (isFirstTime) { 436 this.baseTime = contentValue; 437 isFirstTime = false; 438 } 439 } 440 if (key === 'INPUT_TIME') { 441 this.baseTime = contentValue; 442 isFirstTime = false; 443 } 444 } 445 this.currentDetailList.push({ 446 key: key, 447 value: contentValue, 448 }); 449 }); 450 } 451 this.changeInput!.value = `${this.baseTime}`; 452 this.detailsTbl!.recycleDataSource = this.currentDetailList; 453 this.slicerTrack!.style.visibility = 'visible'; 454 this.detailsTbl!.style.paddingLeft = '20px'; 455 this.boxDetails!.style.width = '65%'; 456 this.detailbox!.style.display = 'block'; 457 }; 458 459 sortByColumn(framesDetail: { sort: number; key: string }): void { 460 let compare = function (property: string, sort: number, type: string) { 461 return function (eventLeftData: HiSysEventStruct, eventRightData: HiSysEventStruct): number { 462 let firstSortNumber: number = -1; 463 let SecondSortNumber: number = 1; 464 let thirdSortNumber: number = 2; 465 // @ts-ignore 466 let rightEventData = eventRightData[property]; 467 // @ts-ignore 468 let leftEventData = eventLeftData[property]; 469 if (type === 'number') { 470 return sort === thirdSortNumber 471 ? parseFloat(rightEventData) - parseFloat(leftEventData) 472 : parseFloat(leftEventData) - parseFloat(rightEventData); 473 } else { 474 if (rightEventData > leftEventData) { 475 return sort === thirdSortNumber ? SecondSortNumber : firstSortNumber; 476 } else { 477 if (rightEventData === leftEventData) { 478 return 0; 479 } else { 480 return sort === thirdSortNumber ? firstSortNumber : SecondSortNumber; 481 } 482 } 483 } 484 }; 485 }; 486 this.hisysEventSource.sort(compare(framesDetail.key, framesDetail.sort, 'number')); 487 this.hiSysEventTable!.recycleDataSource = this.hisysEventSource; 488 } 489 490 drawFlag(value: number, color: string): void { 491 let pointX: number = ns2x( 492 value || 0, 493 TraceRow.range!.startNS, 494 TraceRow.range!.endNS, 495 TraceRow.range!.totalNS, 496 new Rect(0, 0, TraceRow.FRAME_WIDTH, 0) 497 ); 498 this.traceSheetEl!.systemLogFlag = new Flag(Math.floor(pointX), 0, 0, 0, value!, color, '', true, ''); 499 this.spSystemTrace?.refreshCanvas(false); 500 } 501} 502 503const millisecond = 1000_000; 504