• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 { LitTable } from '../../../../../base-ui/table/lit-table';
18import { SelectionData, SelectionParam } from '../../../../bean/BoxSelection';
19import { SpSystemTrace } from '../../../SpSystemTrace';
20import { TraceRow } from '../../base/TraceRow';
21import { LitSearch } from '../../search/Search';
22import { resizeObserver } from '../SheetUtils';
23import { getTabSlicesAsyncFunc, getTabSlicesAsyncCatFunc, getParentDetail, getFuncChildren } from '../../../../database/sql/Func.sql';
24import { getTabSlices } from '../../../../database/sql/ProcessThread.sql';
25import { FuncStruct } from '../../../../database/ui-worker/ProcedureWorkerFunc';
26import { Utils } from '../../base/Utils';
27
28@element('tabpane-slices')
29export class TabPaneSlices extends BaseElement {
30  private slicesTbl: LitTable | null | undefined;
31  private slicesRange: HTMLLabelElement | null | undefined;
32  private slicesSource: Array<SelectionData> = [];
33  private currentSelectionParam: SelectionParam | undefined;
34  private sliceSearchCount: Element | undefined | null;
35  private isDbClick: boolean = false;
36  set data(slicesParam: SelectionParam) {
37    if (this.currentSelectionParam === slicesParam) {
38      return;
39    } //@ts-ignore
40    this.currentSelectionParam = slicesParam;
41    this.slicesRange!.textContent = `Selected range: ${parseFloat(
42      //@ts-ignore
43      ((slicesParam.rightNs - slicesParam.leftNs) / 1000000.0).toFixed(5)
44    )} ms`;
45
46    //合并SF异步信息,相同pid和tid的name
47    let sfAsyncFuncMap: Map<string, { name: string[]; pid: number, tid: number | undefined }> = new Map();
48    slicesParam.funAsync.forEach((it: { name: string; pid: number, tid: number | undefined }) => {
49      if (sfAsyncFuncMap.has(`${it.pid}-${it.tid}`)) {
50        let item = sfAsyncFuncMap.get(`${it.pid}-${it.tid}`);
51        item?.name.push(it.name);
52      } else {
53        sfAsyncFuncMap.set(`${it.pid}-${it.tid}`, {
54          name: [it.name],
55          pid: it.pid,
56          tid: it.tid
57        });
58      }
59    });
60
61    let filterNameEL: HTMLInputElement | undefined | null =
62      this.shadowRoot?.querySelector<HTMLInputElement>('#filterName');
63    filterNameEL?.addEventListener('keyup', (ev) => {
64      if (ev.key.toLocaleLowerCase() === String.fromCharCode(47)) {
65        ev.stopPropagation();
66      }
67    });
68
69    this.getSliceDb(slicesParam, filterNameEL, sfAsyncFuncMap, slicesParam.funCatAsync);
70  }
71
72  initElements(): void {
73    this.sliceSearchCount = this.shadowRoot?.querySelector<LitTable>('#search-count');
74    this.slicesTbl = this.shadowRoot?.querySelector<LitTable>('#tb-slices');
75    this.slicesRange = this.shadowRoot?.querySelector('#time-range');
76    let slicesInput = this.shadowRoot?.querySelector('#filterName');
77    let spApplication = document.querySelector('body > sp-application');
78    let spSystemTrace = spApplication?.shadowRoot?.querySelector(
79      'div > div.content > sp-system-trace'
80    ) as SpSystemTrace;
81    this.slicesTbl!.addEventListener('column-click', (evt) => {
82      // @ts-ignore
83      this.sortByColumn(evt.detail);
84    });
85    // @ts-ignore
86    let data;
87    this.slicesTbl!.addEventListener('row-click', (evt) => {
88      // @ts-ignore
89      data = evt.detail.data;
90      if (!this.isDbClick) {
91        this.isDbClick = true;
92        FuncStruct.funcSelect = false;
93        // @ts-ignore
94        data && this.orgnazitionData(data, false);
95      }
96    });
97    this.slicesTbl!.addEventListener('contextmenu', () => {
98      FuncStruct.funcSelect = true;
99      // @ts-ignore
100      data && this.orgnazitionData(data);
101    });
102    slicesInput?.addEventListener('input', (e) => {
103      // @ts-ignore
104      this.findName(e.target.value);
105    });
106  }
107
108  getSliceDb(
109    slicesParam: SelectionParam,
110    filterNameEL: HTMLInputElement | undefined | null,
111    sfAsyncFuncMap: Map<string, { name: string[]; pid: number, tid: number | undefined }>,
112    ghAsyncFunc: { threadName: string; pid: number }[]): void {
113    //获取SF异步Func数据
114    let result1 = (): Array<unknown> => {
115      let promises: unknown[] = [];
116      sfAsyncFuncMap.forEach(async (item: { name: string[]; pid: number, tid: number | undefined }) => {
117        let res = await getTabSlicesAsyncFunc(item.name, item.pid, item.tid, slicesParam.leftNs, slicesParam.rightNs);
118        if (res !== undefined && res.length > 0) {
119          promises.push(...res);
120        }
121      });
122      return promises;
123    };
124
125    //获取GH异步Func数据
126    let result2 = (): Array<unknown> => {
127      let promises: unknown[] = [];
128      ghAsyncFunc.forEach(async (item: { pid: number; threadName: string }) => {
129        let res = await getTabSlicesAsyncCatFunc(item.threadName, item.pid, slicesParam.leftNs, slicesParam.rightNs);
130        if (res !== undefined && res.length > 0) {
131          promises.push(...res);
132        }
133      });
134      return promises;
135    };
136
137    //获取同步Func数据
138    let result3 = async (): Promise<unknown> => {
139      let promises: unknown[] = [];
140      let res = await getTabSlices(slicesParam.funTids, slicesParam.processIds, slicesParam.leftNs, slicesParam.rightNs);
141      if (res !== undefined && res.length > 0) {
142        promises.push(...res);
143      }
144      return promises;
145    };
146
147    this.slicesTbl!.loading = true;
148    Promise.all([result1(), result2(), result3()]).then(async res => {
149      let processSlicesResult = (res[0] || []).concat(res[1] || []).concat(res[2] || []);
150      if (processSlicesResult !== null && processSlicesResult.length > 0) {
151        let funcIdArr: Array<number> = [];
152        let minStartTS = Infinity;
153        let maxEndTS = -Infinity;
154        // @ts-ignore
155        let parentDetail: [{
156          startTS: number,
157          endTS: number,
158          depth: number,
159          id: number,
160          name: string
161        }] = await getParentDetail(
162          slicesParam.processIds,
163          slicesParam.funTids,
164          slicesParam.leftNs,
165          slicesParam.rightNs
166        );
167        // @ts-ignore
168        parentDetail.forEach(item => {
169          funcIdArr.push(item.id);
170          if (item.depth === 0) {
171            if (item.startTS < minStartTS) {
172              minStartTS = item.startTS;
173            }
174            if (item.endTS > maxEndTS) {
175              maxEndTS = item.endTS;
176            }
177          }
178        });
179
180        let FuncChildrenList = await getFuncChildren(funcIdArr, slicesParam.processIds, slicesParam.funTids, minStartTS, maxEndTS, false);
181        let childDurMap: Map<number, Map<number, number>> = new Map();
182        FuncChildrenList.forEach((it: unknown) => { // @ts-ignore
183          if (!childDurMap.has(it.parentId)) { // @ts-ignore
184            childDurMap.set(it.parentId, it.duration);
185          } else { // @ts-ignore
186            let dur = childDurMap.get(it.parentId); // @ts-ignore
187            dur += it.duration; // @ts-ignore
188            childDurMap.set(it.parentId, dur!);
189          }
190        });
191        let sumWall = 0.0;
192        let sumOcc = 0;
193        let sumSelf = 0.0;
194        let processSlicesResultMap: Map<string, unknown> = new Map();
195        for (let processSliceItem of processSlicesResult) {
196          //@ts-ignore
197          processSliceItem.selfTime = //@ts-ignore
198          childDurMap.has(processSliceItem.id) ? //@ts-ignore
199          parseFloat(((processSliceItem.wallDuration - childDurMap.get(processSliceItem.id)) / 1000000).toFixed(5)) : //@ts-ignore
200          parseFloat((processSliceItem.wallDuration / 1000000).toFixed(5));
201          //@ts-ignore
202          processSliceItem.name = processSliceItem.name === null ? '' : processSliceItem.name;
203          //@ts-ignore
204          sumWall += processSliceItem.wallDuration;
205          //@ts-ignore
206          sumOcc += processSliceItem.occurrences;
207          //@ts-ignore
208          sumSelf += processSliceItem.selfTime;
209          //@ts-ignore
210          processSliceItem.wallDuration = parseFloat((processSliceItem.wallDuration / 1000000.0).toFixed(5));
211          //@ts-ignore
212          if (processSlicesResultMap.has(processSliceItem.name)) {//@ts-ignore
213            let item = processSlicesResultMap.get(processSliceItem.name);
214            //@ts-ignore
215            item.occurrences = item.occurrences + processSliceItem.occurrences;
216            //@ts-ignore
217            item.wallDuration = parseFloat((item.wallDuration + processSliceItem.wallDuration).toFixed(5));
218          } else {
219            //@ts-ignore
220            processSlicesResultMap.set(processSliceItem.name, {//@ts-ignore
221              ...processSliceItem, //@ts-ignore
222              tabTitle: processSliceItem.name
223            });
224          }
225        }
226        let processSlicesResultsValue = [...processSlicesResultMap.values()];
227        processSlicesResultsValue.forEach(element => {//@ts-ignore
228          element.avgDuration = parseFloat((element.wallDuration / element.occurrences).toFixed(5));
229        });
230        let count = new SelectionData();
231        count.process = ' ';
232        count.wallDuration = parseFloat((sumWall / 1000000.0).toFixed(5));
233        count.occurrences = sumOcc;
234        count.selfTime = parseFloat(sumSelf.toFixed(5));
235        count.tabTitle = 'Summary';
236        //@ts-ignore
237        count.avgDuration = parseFloat((count.wallDuration / count.occurrences).toFixed(5));
238        // @ts-ignore
239        count.allName = processSlicesResultsValue.map((item: unknown) => item.name);
240        processSlicesResultsValue.splice(0, 0, count); //@ts-ignore
241        this.slicesSource = processSlicesResultsValue;
242        this.slicesTbl!.recycleDataSource = processSlicesResultsValue;
243        this.sliceSearchCount!.textContent = this.slicesSource.length - 1 + '';
244        if (filterNameEL && filterNameEL.value.trim() !== '') {
245          this.findName(filterNameEL.value);
246        }
247      } else {
248        this.slicesSource = [];
249        this.slicesTbl!.recycleDataSource = this.slicesSource;
250        this.sliceSearchCount!.textContent = '0';
251      }
252      this.slicesTbl!.loading = false;
253    });
254  }
255  // @ts-ignore
256  async orgnazitionData(data: Object, isDbClick?: false): unknown {
257    // @ts-ignore
258    if (data!.tabTitle === 'Summary') {
259      FuncStruct.funcSelect = true;
260    }
261    let spApplication = document.querySelector('body > sp-application');
262    let spSystemTrace = spApplication?.shadowRoot?.querySelector(
263      'div > div.content > sp-system-trace'
264    ) as SpSystemTrace;
265    let search = spApplication!.shadowRoot?.querySelector('#lit-search') as LitSearch;
266    spSystemTrace?.visibleRows.forEach((it) => {
267      it.highlight = false;
268    });
269    spSystemTrace?.timerShaftEL?.removeTriangle('inverted');
270    // @ts-ignore
271    let asyncFuncArr = spSystemTrace!.seachAsyncFunc(data.name);
272    // @ts-ignore
273    await spSystemTrace!.searchFunction([], asyncFuncArr, data.name).then((mixedResults) => {
274      if (mixedResults && mixedResults.length === 0) {
275        FuncStruct.funcSelect = true;
276        if (!isDbClick) {
277          this.isDbClick = false;
278        }
279        return;
280      }
281      // @ts-ignore
282      search.list = mixedResults.filter((item) => item.funName === data.name); //@ts-ignore
283      const sliceRowList: Array<TraceRow<unknown>> = [];
284      // 框选的slice泳道
285      for (let row of spSystemTrace.rangeSelect.rangeTraceRow!) {
286        if (row.rowType === 'func') {
287          sliceRowList.push(row);
288        }
289        if (row.childrenList) {
290          for (const childrenRow of row.childrenList) {
291            if (childrenRow.rowType === 'func') {
292              sliceRowList.push(childrenRow);
293            }
294          }
295        }
296      }
297      if (sliceRowList.length === 0) {
298        return;
299      }
300      this.slicesTblFreshSearchSelect(search, sliceRowList, data, spSystemTrace);
301      spSystemTrace?.visibleRows.forEach((it) => {
302        it.draw();
303        this.isDbClick = false;
304      });
305    });
306  }
307
308  private slicesTblFreshSearchSelect(
309    search: LitSearch, //@ts-ignore
310    sliceRowList: Array<TraceRow<unknown>>,
311    data: unknown,
312    spSystemTrace: SpSystemTrace
313  ): void {
314    let input = search.shadowRoot?.querySelector('input') as HTMLInputElement;
315    let rangeSelectList: Array<unknown> = []; // 框选范围的数据
316    // search 到的内容与框选泳道的内容取并集
317    for (const searchItem of search.list) {
318      for (const traceRow of sliceRowList) {
319        if (
320          // @ts-ignore
321          Math.max(TraceRow.rangeSelectObject?.startNS!, searchItem.startTime) <=
322          // @ts-ignore
323          Math.min(TraceRow.rangeSelectObject?.endNS!, searchItem.startTime + searchItem.dur) &&
324          !rangeSelectList.includes(searchItem)
325        ) {
326          // 异步调用栈
327          if (traceRow.asyncFuncName) {
328            if (
329              // @ts-ignore
330              `${searchItem.pid}` === `${traceRow.asyncFuncNamePID}` &&
331              traceRow.traceId === Utils.currentSelectTrace
332            ) {
333              rangeSelectList.push(searchItem);
334            }
335          } else {
336            // 线程调用栈
337            // @ts-ignore
338            if (Utils.getDistributedRowId(searchItem.tid) === traceRow.rowId) {
339              rangeSelectList.push(searchItem);
340            }
341          }
342        }
343      }
344    }
345
346    if (rangeSelectList.length === 0) {
347      return;
348    } //@ts-ignore
349    input.value = data.name;
350    //@ts-ignore
351    search.currenSearchValue = data.name;
352    search.list = rangeSelectList;
353    search.total = search.list.length;
354    search.index = spSystemTrace!.showStruct(false, -1, search.list);
355    search.isClearValue = true;
356  }
357
358  connectedCallback(): void {
359    super.connectedCallback();
360    resizeObserver(this.parentElement!, this.slicesTbl!);
361  }
362
363  initHtml(): string {
364    return `
365        <style>
366        .slice-label{
367            height: 20px;
368        }
369        :host{
370            display: flex;
371            padding: 10px 10px;
372            flex-direction: column;
373        }
374        #filterName:focus{
375          outline: none;
376        }
377        </style>
378        <div style="display:flex; justify-content:space-between;">
379        <div style="width: 40%;">
380          <input id="filterName" type="text" style="width:60%;height:18px;border:1px solid #c3c3c3;border-radius:9px" placeholder="Search" value="" />
381          &nbsp;&nbsp;<span style="font-size: 10pt;margin-bottom: 5px">Count:&nbsp;<span id="search-count">0<span></span>
382        </div>
383        <label id="time-range" class="slice-label" style="text-align: end;font-size: 10pt;margin-bottom: 5px">Selected range:0.0 ms</label>
384        </div>
385        <lit-table id="tb-slices" style="height: auto">
386            <lit-table-column class="slices-column" title="Name" width="500px" data-index="name"
387            key="name"  align="flex-start" order>
388            </lit-table-column>
389            <lit-table-column class="slices-column" title="Wall duration(ms)" width="1fr" data-index="wallDuration"
390            key="wallDuration"  align="flex-start" order >
391            </lit-table-column>
392            <lit-table-column class="slices-column" title="Avg Wall duration(ms)" width="1fr" data-index="avgDuration"
393            key="avgDuration"  align="flex-start" order >
394            </lit-table-column>
395            <lit-table-column class="slices-column" title="Occurrences" width="1fr" data-index="occurrences"
396            key="occurrences"  align="flex-start" order tdJump>
397            </lit-table-column>
398            <lit-table-column class="slices-column" title="selfTime(ms)" width="1fr" data-index="selfTime" key="selfTime" align="flex-start" order>
399            </lit-table-column>
400        </lit-table>
401        `;
402  }
403
404  sortByColumn(slicesDetail: unknown): void {
405    // @ts-ignore
406    function compare(property, slicesSort, type) {
407      return function (slicesLeftData: SelectionData, slicesRightData: SelectionData) {
408        if (slicesLeftData.process === ' ' || slicesRightData.process === ' ') {
409          return 0;
410        }
411        if (type === 'number') {
412          // @ts-ignore
413          return slicesSort === 2
414            ? // @ts-ignore
415            parseFloat(slicesRightData[property]) - parseFloat(slicesLeftData[property])
416            : // @ts-ignore
417            parseFloat(slicesLeftData[property]) - parseFloat(slicesRightData[property]);
418        } else {
419          // @ts-ignore
420          if (slicesRightData[property] > slicesLeftData[property]) {
421            return slicesSort === 2 ? 1 : -1;
422          } else {
423            // @ts-ignore
424            if (slicesRightData[property] === slicesLeftData[property]) {
425              return 0;
426            } else {
427              return slicesSort === 2 ? -1 : 1;
428            }
429          }
430        }
431      };
432    }
433    // 拷贝当前表格显示的数据
434    let sortData: Array<SelectionData> = JSON.parse(JSON.stringify(this.slicesTbl!.recycleDataSource));
435    // 取出汇总数据,同时将排序数据去掉汇总数据进行后续排序
436    let headData: SelectionData = sortData.splice(0, 1)[0];
437    //@ts-ignore
438    if (slicesDetail.key === 'name') {
439      //@ts-ignore
440      sortData.sort(compare(slicesDetail.key, slicesDetail.sort, 'string'));
441    } else {
442      //@ts-ignore
443      sortData.sort(compare(slicesDetail.key, slicesDetail.sort, 'number'));
444    }
445    // 排序完成后将汇总数据插入到头部
446    sortData.unshift(headData);
447    this.slicesTbl!.recycleDataSource = sortData;
448    this.sliceSearchCount!.textContent = sortData.length - 1 + '';
449  }
450
451  findName(str: string): void {
452    let searchData: Array<SelectionData> = [];
453    let sumWallDuration: number = 0;
454    let sumOccurrences: number = 0;
455    let nameSet: Array<string> = [];
456    if (str === '') {
457      this.slicesTbl!.recycleDataSource = this.slicesSource;
458      this.sliceSearchCount!.textContent = this.slicesSource.length - 1 + '';
459    } else {
460      this.slicesSource.forEach((item) => {
461        if (item.name.toLowerCase().indexOf(str.toLowerCase()) !== -1) {
462          searchData.push(item);
463          nameSet.push(item.name);
464          sumWallDuration += item.wallDuration;
465          sumOccurrences += item.occurrences;
466        }
467      });
468      let count: SelectionData = new SelectionData();
469      count.process = '';
470      count.name = '';
471      count.allName = nameSet;
472      count.tabTitle = 'Summary';
473      count.wallDuration = Number(sumWallDuration.toFixed(3));
474      count.occurrences = sumOccurrences;
475      searchData.unshift(count);
476      this.slicesTbl!.recycleDataSource = searchData;
477      this.sliceSearchCount!.textContent = searchData.length - 1 + '';
478    }
479  }
480}
481