• 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 { HeapDataInterface } from '../../../../../js-heap/HeapDataInterface';
19import { ConstructorComparison, ConstructorItem, ConstructorType } from '../../../../../js-heap/model/UiStruct';
20import '../../../../../base-ui/table/lit-table-column';
21import { TabPaneJsMemoryFilter } from '../TabPaneJsMemoryFilter';
22import '../TabPaneJsMemoryFilter';
23import { HeapSnapshotStruct } from '../../../../database/ui-worker/ProcedureWorkerHeapSnapshot';
24import { LitSelectOption } from '../../../../../base-ui/select/LitSelectOption';
25import { LitSelect } from '../../../../../base-ui/select/LitSelect';
26import { TabPaneComparisonHtml } from './TabPaneComparison.html';
27
28@element('tabpane-comparison')
29export class TabPaneComparison extends BaseElement {
30  private comparisonTableEl: LitTable | undefined | null;
31  private retainerTableEl: LitTable | undefined | null;
32  private comparisonsData: Array<ConstructorComparison> = [];
33  private retainsData: Array<ConstructorItem> = [];
34  private baseFileId: number | undefined | null;
35  private targetFileId: number | undefined | null;
36  private filterEl: TabPaneJsMemoryFilter | undefined | null;
37  private selectEl: LitSelect | undefined | null;
38  private search: HTMLInputElement | undefined | null;
39  private comparisonData: Array<ConstructorItem> = [];
40  private comparisonFilter: Array<ConstructorComparison> = [];
41  private leftArray: Array<ConstructorComparison> = [];
42  private rightArray: Array<ConstructorItem> = [];
43  private rightTheadTable: HTMLDivElement | undefined | null;
44  private leftTheadTable: HTMLDivElement | undefined | null;
45  private comparisonTable: HTMLDivElement | undefined | null;
46  private fileSize: number = 0;
47
48  initElements(): void {
49    this.comparisonTableEl = this.shadowRoot!.querySelector<LitTable>('#tb-comparison') as LitTable;
50    this.retainerTableEl = this.shadowRoot!.querySelector<LitTable>('#tb-retainer') as LitTable;
51    this.filterEl = this.shadowRoot!.querySelector<TabPaneJsMemoryFilter>('#filter');
52    this.selectEl = this.filterEl?.shadowRoot?.querySelector<LitSelect>('lit-select');
53    this.search = this.filterEl?.shadowRoot?.querySelector('#js-memory-filter-input') as HTMLInputElement;
54    this.rightTheadTable = this.retainerTableEl!.shadowRoot?.querySelector('.thead') as HTMLDivElement;
55    this.leftTheadTable = this.comparisonTableEl!.shadowRoot?.querySelector('.thead') as HTMLDivElement;
56    this.comparisonTable = this.comparisonTableEl.shadowRoot?.querySelector('.table') as HTMLDivElement;
57    this.classFilter();
58  }
59
60  initComparison(data: HeapSnapshotStruct, dataListCache: Array<HeapSnapshotStruct>): void {
61    this.clear();
62    this.retainerTableEl!.snapshotDataSource = [];
63    let fileArr: HeapSnapshotStruct[] = [];
64    for (let file of dataListCache) {
65      if (file.id !== data.id) {
66        fileArr.push(file);
67      }
68    }
69    fileArr = fileArr.sort();
70    this.fileSize = data.size;
71    this.initSelect(data.id, fileArr);
72    this.baseFileId = data.id;
73    this.targetFileId = fileArr[0].id;
74    this.updateComparisonData(data.id, fileArr[0].id);
75    new ResizeObserver((): void => {
76      this.comparisonTableEl!.style.height = '100%';
77      this.comparisonTableEl!.reMeauseHeight();
78    }).observe(this.parentElement!);
79  }
80
81  updateComparisonData(baseFileId: number, targetFileId: number): void {
82    this.comparisonsData = HeapDataInterface.getInstance().getClassListForComparison(baseFileId, targetFileId);
83    this.comparisonsData.forEach((dataList): void => {
84      dataList.objectName = dataList.nodeName;
85    });
86    if (this.comparisonsData.length > 0) {
87      this.comparisonData = this.comparisonsData;
88      this.comparisonTableEl!.snapshotDataSource = this.comparisonsData;
89    } else {
90      this.comparisonTableEl!.snapshotDataSource = [];
91    }
92    this.comparisonTableEl!.reMeauseHeight();
93  }
94
95  initSelect(fileId: number, comFileArr: Array<HeapSnapshotStruct>): void {
96    let input = this.selectEl!.shadowRoot?.querySelector('input') as HTMLInputElement;
97    this.selectEl!.innerHTML = '';
98    this.selectEl!.defaultValue = comFileArr[0].name || '';
99    this.selectEl!.placeholder = comFileArr[0].name || '';
100    this.selectEl!.dataSource = comFileArr;
101    let option = new LitSelectOption();
102    option.innerHTML = 'File Name';
103    option.setAttribute('disabled', 'disabled');
104    this.selectEl?.prepend(option);
105    if (comFileArr[0].name) {
106      option.setAttribute('value', comFileArr[0].name);
107    }
108    let selectOption = this.selectEl!.querySelectorAll('lit-select-option');
109    for (const item of selectOption) {
110      item.addEventListener('onSelected', (e): void => {
111        this.comparisonTable!.scrollTop = 0;
112        this.retainerTableEl!.snapshotDataSource = [];
113        for (let f of comFileArr) {
114          if (input.value === f.name) {
115            this.updateComparisonData(fileId, f.id);
116          }
117        }
118        e.stopPropagation();
119      });
120    }
121  }
122
123  sortComprisonByColumn(column: string, sort: number): void {
124    switch (sort) {
125      case 0:
126        if (this.search!.value === '') {
127          this.comparisonTableEl!.snapshotDataSource = this.comparisonsData;
128        } else {
129          this.comparisonTableEl!.snapshotDataSource = this.comparisonFilter;
130        }
131        break;
132      default:
133        if (this.search!.value === '') {
134          this.leftArray = [...this.comparisonsData];
135        } else {
136          this.leftArray = [...this.comparisonFilter];
137        }
138        this.sortComprisonByColumnExtend(column, sort);
139        break;
140    }
141  }
142
143  private sortComprisonByColumnExtend(column: string, sort: number): void {
144    switch (column) {
145      case 'addedCount':
146        this.comparisonTableEl!.snapshotDataSource = this.leftArray.sort((a, b): number => {
147          return sort === 1 ? a.addedCount - b.addedCount : b.addedCount - a.addedCount;
148        });
149        break;
150      case 'removedCount':
151        this.comparisonTableEl!.snapshotDataSource = this.leftArray.sort((a, b): number => {
152          return sort === 1 ? a.removedCount - b.removedCount : b.removedCount - a.removedCount;
153        });
154        break;
155      case 'deltaCount':
156        this.comparisonTableEl!.snapshotDataSource = this.leftArray.sort((a, b): number => {
157          return sort === 1 ? a.deltaCount - b.deltaCount : b.deltaCount - a.deltaCount;
158        });
159        break;
160      case 'objectName':
161        this.comparisonTableEl!.snapshotDataSource = this.leftArray.sort((a, b): number => {
162          return sort === 1
163            ? `${a.objectName}`.localeCompare(`${b.objectName}`)
164            : `${b.objectName}`.localeCompare(`${a.objectName}`);
165        });
166        break;
167      case 'addedSize':
168        this.comparisonTableEl!.snapshotDataSource = this.leftArray.sort((a, b): number => {
169          return sort === 1 ? a.addedSize - b.addedSize : b.addedSize - a.addedSize;
170        });
171        break;
172      case 'removedSize':
173        this.comparisonTableEl!.snapshotDataSource = this.leftArray.sort((a, b): number => {
174          return sort === 1 ? a.removedSize - b.removedSize : b.removedSize - a.removedSize;
175        });
176        break;
177      case 'deltaSize':
178        this.comparisonTableEl!.snapshotDataSource = this.leftArray.sort((a, b): number => {
179          return sort === 1 ? a.deltaSize - b.deltaSize : b.deltaSize - a.deltaSize;
180        });
181        break;
182    }
183  }
184
185  sortRetainerByColumn(column: string, sort: number): void {
186    switch (sort) {
187      case 0:
188        this.retainerTableEl!.snapshotDataSource = this.retainsData;
189        break;
190      default:
191        this.rightArray = [...this.retainsData];
192        switch (column) {
193          case 'distance':
194            this.sortRetainerByDistanceType(sort);
195            break;
196          case 'shallowSize':
197            this.sortRetainerByShallowSizeType(sort);
198            break;
199          case 'retainedSize':
200            this.sortRetainerByRetainedSizeType(sort);
201            break;
202          case 'objectName':
203            this.sortRetainerByObjectNameType(sort);
204            break;
205        }
206        break;
207    }
208  }
209
210  private sortRetainerByObjectNameType(sort: number): void {
211    this.retainerTableEl!.snapshotDataSource = this.rightArray.sort((rightArrA, rightArrB): number => {
212      return sort === 1
213        ? `${rightArrA.objectName}`.localeCompare(`${rightArrB.objectName}`)
214        : `${rightArrB.objectName}`.localeCompare(`${rightArrA.objectName}`);
215    });
216    this.rightArray.forEach((list): void => {
217      let retainsTable = (): void => {
218        const getList = (listArr: Array<ConstructorItem>): void => {
219          listArr.sort((listArrA, listArrB): number => {
220            return sort === 1
221              ? `${listArrA.objectName}`.localeCompare(`${listArrB.objectName}`)
222              : `${listArrB.objectName}`.localeCompare(`${listArrA.objectName}`);
223          });
224          listArr.forEach(function (currentRow): void {
225            if (currentRow.children.length > 0) {
226              getList(currentRow.children);
227            }
228          });
229        };
230        getList(list.children);
231      };
232      retainsTable();
233    });
234    this.retainerTableEl!.snapshotDataSource = this.rightArray;
235  }
236
237  private sortRetainerByRetainedSizeType(sort: number): void {
238    this.retainerTableEl!.snapshotDataSource = this.rightArray.sort((rightArrA, rightArrB): number => {
239      return sort === 1
240        ? rightArrA.retainedSize - rightArrB.retainedSize
241        : rightArrB.retainedSize - rightArrA.retainedSize;
242    });
243    this.rightArray.forEach((list): void => {
244      let retainsTable = (): void => {
245        const getList = (listArr: Array<ConstructorItem>): void => {
246          listArr.sort((listArrA, listArrB): number => {
247            return sort === 1
248              ? listArrA.retainedSize - listArrB.retainedSize
249              : listArrB.retainedSize - listArrA.retainedSize;
250          });
251          listArr.forEach(function (row): void {
252            if (row.children.length > 0) {
253              getList(row.children);
254            }
255          });
256        };
257        getList(list.children);
258      };
259      retainsTable();
260    });
261    this.retainerTableEl!.snapshotDataSource = this.rightArray;
262  }
263
264  private sortRetainerByShallowSizeType(sort: number): void {
265    this.retainerTableEl!.snapshotDataSource = this.rightArray.sort((rightArrA, rightArrB): number => {
266      return sort === 1 ? rightArrA.shallowSize - rightArrB.shallowSize : rightArrB.shallowSize - rightArrA.shallowSize;
267    });
268    this.rightArray.forEach((list): void => {
269      let retainsTable = (): void => {
270        const getList = (listArr: Array<ConstructorItem>): void => {
271          listArr.sort((listArrA, listArrB): number => {
272            return sort === 1
273              ? listArrA.shallowSize - listArrB.shallowSize
274              : listArrB.shallowSize - listArrA.shallowSize;
275          });
276          listArr.forEach(function (rowEl): void {
277            if (rowEl.children.length > 0) {
278              getList(rowEl.children);
279            }
280          });
281        };
282        getList(list.children);
283      };
284      retainsTable();
285    });
286    this.retainerTableEl!.snapshotDataSource = this.rightArray;
287  }
288
289  private sortRetainerByDistanceType(sort: number): void {
290    this.retainerTableEl!.snapshotDataSource = this.rightArray.sort((a, b): number => {
291      return sort === 1 ? a.distance - b.distance : b.distance - a.distance;
292    });
293    this.rightArray.forEach((list): void => {
294      let retainsTable = (): void => {
295        const getList = (currentList: Array<ConstructorItem>): void => {
296          currentList.sort((a, b): number => {
297            return sort === 1 ? a.distance - b.distance : b.distance - a.distance;
298          });
299          currentList.forEach(function (currentRow): void {
300            if (currentRow.children.length > 0) {
301              getList(currentRow.children);
302            }
303          });
304        };
305        getList(list.children);
306      };
307      retainsTable();
308    });
309    this.retainerTableEl!.snapshotDataSource = this.rightArray;
310  }
311
312  classFilter(): void {
313    this.search!.addEventListener('keyup', (): void => {
314      this.comparisonFilter = [];
315      this.comparisonData.forEach((a: unknown) => {
316        // @ts-ignore
317        if (a.objectName.toLowerCase().includes(this.search!.value.toLowerCase())) {
318          // @ts-ignore
319          this.comparisonFilter.push(a);
320        } else {
321        }
322      });
323      this.comparisonTableEl!.snapshotDataSource = this.comparisonFilter;
324      let summaryTable = this.comparisonTableEl!.shadowRoot?.querySelector('.table') as HTMLDivElement;
325      summaryTable.scrollTop = 0;
326      this.comparisonTableEl!.reMeauseHeight();
327    });
328  }
329
330  clear(): void {
331    this.search!.value = '';
332    this.rightTheadTable!.removeAttribute('sort');
333    this.leftTheadTable!.removeAttribute('sort');
334    this.comparisonTable!.scrollTop = 0;
335  }
336
337  connectedCallback(): void {
338    super.connectedCallback();
339    let filterHeight = 0;
340    new ResizeObserver((): void => {
341      let comparisonPanelFilter = this.shadowRoot!.querySelector('#filter') as HTMLElement;
342      if (comparisonPanelFilter.clientHeight > 0) {
343        filterHeight = comparisonPanelFilter.clientHeight;
344      }
345      if (this.parentElement!.clientHeight > filterHeight) {
346        comparisonPanelFilter.style.display = 'flex';
347      } else {
348        comparisonPanelFilter.style.display = 'none';
349      }
350    }).observe(this.parentElement!);
351    this.comparisonTableEl!.addEventListener('icon-click', this.comparisonTblIconClickHandler);
352    this.retainerTableEl!.addEventListener('icon-click', this.retainerTblIconClickHandler);
353    this.comparisonTableEl!.addEventListener('column-click', this.comparisonTblColumnClickHandler);
354    this.retainerTableEl!.addEventListener('column-click', this.retainerTblColumnClickHandler);
355    this.comparisonTableEl!.addEventListener('row-click', this.comparisonTblRowClickHandler);
356    this.retainerTableEl!.addEventListener('row-click', this.retainerTblRowClickHandler);
357  }
358
359  disconnectedCallback(): void {
360    super.disconnectedCallback();
361    this.comparisonTableEl!.removeEventListener('icon-click', this.comparisonTblIconClickHandler);
362    this.retainerTableEl!.removeEventListener('icon-click', this.retainerTblIconClickHandler);
363    this.comparisonTableEl!.removeEventListener('column-click', this.comparisonTblColumnClickHandler);
364    this.retainerTableEl!.removeEventListener('column-click', this.retainerTblColumnClickHandler);
365    this.comparisonTableEl!.removeEventListener('row-click', this.comparisonTblRowClickHandler);
366    this.retainerTableEl!.removeEventListener('row-click', this.retainerTblRowClickHandler);
367  }
368
369  private comparisonTblRowClickHandler = (e: Event): void => {
370    this.rightTheadTable!.removeAttribute('sort');
371    // @ts-ignore
372    let item = e.detail.data as ConstructorItem; // @ts-ignore
373    (item as unknown).isSelected = true;
374    this.retainsData = HeapDataInterface.getInstance().getRetains(item);
375    if (this.retainsData && this.retainsData.length > 0) {
376      this.retainsDataInit();
377      let i = 0;
378      if (this.retainsData[0].distance > 1) {
379        this.retainsData[0].getChildren();
380        this.retainsData[0].expanded = false;
381      }
382      let retainsTable = (): void => {
383        const getList = (list: Array<ConstructorItem>): void => {
384          for (const item of list) {
385            let shallow = `${Math.round((item.shallowSize / this.fileSize) * 100)}%`;
386            let retained = `${Math.round((item.retainedSize / this.fileSize) * 100)}%`;
387            item.shallowPercent = shallow;
388            item.retainedPercent = retained;
389            let nodeId = `${item.nodeName} @${item.id}`;
390            item.objectName = `${item.edgeName}\xa0` + 'in' + `\xa0${nodeId}`;
391            if (item.distance >= 100000000 || item.distance === -5) {
392              // @ts-ignore
393              item.distance = '-';
394            }
395            i++;
396            // @ts-ignore
397            if (i < this.retainsData[0].distance - 1 && list[0].distance !== '-') {
398              list[0].getChildren();
399              list[0].expanded = false;
400              if (item.hasNext) {
401                getList(item.children);
402              }
403            } else {
404              return;
405            }
406          }
407        };
408        getList(this.retainsData[0].children);
409      };
410      retainsTable();
411      this.retainerTableEl!.snapshotDataSource = this.retainsData;
412    } else {
413      this.retainerTableEl!.snapshotDataSource = [];
414    }
415    this.resizeObserverObserve();
416    // @ts-ignore
417    if ((e.detail as unknown).callBack) {
418      // @ts-ignore
419      (e.detail as unknown).callBack(true);
420    }
421  };
422
423  private resizeObserverObserve(): void {
424    new ResizeObserver((): void => {
425      this.retainerTableEl!.style.height = 'calc(100% - 21px)';
426      this.retainerTableEl!.reMeauseHeight();
427    }).observe(this.parentElement!);
428  }
429
430  private retainsDataInit(): void {
431    this.retainsData.forEach((comparisonRetainEl): void => {
432      let shallow = `${Math.round((comparisonRetainEl.shallowSize / this.fileSize) * 100)}%`;
433      let retained = `${Math.round((comparisonRetainEl.retainedSize / this.fileSize) * 100)}%`;
434      comparisonRetainEl.shallowPercent = shallow;
435      comparisonRetainEl.retainedPercent = retained;
436      if (comparisonRetainEl.distance >= 100000000 || comparisonRetainEl.distance === -5) {
437        // @ts-ignore
438        comparisonRetainEl.distance = '-';
439      }
440      let nodeId = `${comparisonRetainEl.nodeName} @${comparisonRetainEl.id}`;
441      comparisonRetainEl.objectName = `${comparisonRetainEl.edgeName}\xa0` + 'in' + `\xa0${nodeId}`;
442    });
443  }
444
445  private retainerTblRowClickHandler = (evt: Event): void => {
446    // @ts-ignore
447    let data = evt.detail.data as ConstructorItem; // @ts-ignore
448    (data as unknown).isSelected = true;
449    // @ts-ignore
450    if ((evt.detail as unknown).callBack) {
451      // @ts-ignore
452      (evt.detail as unknown).callBack(true);
453    }
454  };
455
456  private comparisonTblColumnClickHandler = (e: Event): void => {
457    // @ts-ignore
458    this.sortComprisonByColumn(e.detail.key, e.detail.sort);
459    this.comparisonTableEl!.reMeauseHeight();
460  };
461
462  private retainerTblColumnClickHandler = (e: Event): void => {
463    // @ts-ignore
464    this.sortRetainerByColumn(e.detail.key, e.detail.sort);
465    this.retainerTableEl!.reMeauseHeight();
466  };
467
468  retainerTblIconClickHandler = (e: Event): void => {
469    // @ts-ignore
470    let retainerNext = e.detail.data as ConstructorItem;
471    if (retainerNext) {
472      if (this.retainsData.length > 0) {
473        if (retainerNext.status) {
474          retainerNext.getChildren();
475          let i = 0;
476          let retainsTable = (): void => {
477            const getList = (comList: Array<ConstructorItem>): void => {
478              comList.forEach((row): void => {
479                let shallow = `${Math.round((row.shallowSize / this.fileSize) * 100)}%`;
480                let retained = `${Math.round((row.retainedSize / this.fileSize) * 100)}%`;
481                row.shallowPercent = shallow;
482                row.retainedPercent = retained;
483                let nodeId = `${row.nodeName} @${row.id}`;
484                row.objectName = `${row.edgeName}\xa0` + 'in' + `\xa0${nodeId}`;
485                if (row.distance >= 100000000 || row.distance === -5) {
486                  // @ts-ignore
487                  row.distance = '-';
488                }
489                i++;
490                // @ts-ignore
491                if (i < this.retainsData[0].distance - 1 && comList[0].distance !== '-') {
492                  comList[0].getChildren();
493                  comList[0].expanded = false;
494                  if (row.hasNext) {
495                    getList(row.children);
496                  }
497                } else {
498                  return;
499                }
500              });
501            };
502            getList(retainerNext.children);
503          };
504          retainsTable();
505        } else {
506          retainerNext.status = true;
507        }
508        if (this.rightTheadTable!.hasAttribute('sort')) {
509          this.retainerTableEl!.snapshotDataSource = this.rightArray;
510        } else {
511          this.retainerTableEl!.snapshotDataSource = this.retainsData;
512        }
513      } else {
514        this.retainerTableEl!.snapshotDataSource = [];
515      }
516      this.resizeObserverObserve();
517    }
518  };
519
520  comparisonTblIconClickHandler = (e: Event): void => {
521    // @ts-ignore
522    let clickRow = e.detail.data;
523    if (clickRow.status) {
524      clickRow.targetFileId = this.targetFileId;
525      clickRow.children = HeapDataInterface.getInstance().getNextForComparison(clickRow);
526      if (clickRow.children.length > 0) {
527        for (let item of clickRow.children) {
528          let nodeName = `${item.nodeName} @${item.id}`;
529          item.nodeId = ` @${item.id}`;
530          if (item.isString()) {
531            item.objectName = `"${item.nodeName}"` + ` @${item.id}`;
532          } else {
533            item.objectName = nodeName;
534          }
535          item.deltaCount = '-';
536          item.deltaSize = '-';
537          if (item.edgeName !== '') {
538            item.objectName = `${item.edgeName}\xa0` + '::' + `\xa0${nodeName}`;
539          } else {
540            if (item.fileId === this.baseFileId) {
541              item.addedCount = '•';
542              item.addedSize = item.shallowSize;
543              item.removedCount = '-';
544              item.removedSize = '-';
545            } else if (item.fileId) {
546              item.removedCount = '•';
547              item.removedSize = item.shallowSize;
548              item.addedCount = '-';
549              item.addedSize = '-';
550            }
551          }
552          if (item.type === ConstructorType.FiledType) {
553            item.removedCount = '-';
554            item.removedSize = '-';
555            item.addedCount = '-';
556            item.addedSize = '-';
557          }
558        }
559      } else {
560        this.comparisonTableEl!.snapshotDataSource = [];
561      }
562    } else {
563      clickRow.status = true;
564    }
565    this.comparisonTblIconClickData();
566  };
567
568  private comparisonTblIconClickData(): void {
569    if (this.search!.value !== '') {
570      if (this.leftTheadTable!.hasAttribute('sort')) {
571        this.comparisonTableEl!.snapshotDataSource = this.leftArray;
572      } else {
573        this.comparisonTableEl!.snapshotDataSource = this.comparisonFilter;
574      }
575    } else {
576      if (this.leftTheadTable!.hasAttribute('sort')) {
577        this.comparisonTableEl!.snapshotDataSource = this.leftArray;
578      } else {
579        this.comparisonTableEl!.snapshotDataSource = this.comparisonsData;
580      }
581    }
582    new ResizeObserver((): void => {
583      this.comparisonTableEl!.style.height = '100%';
584      this.comparisonTableEl!.reMeauseHeight();
585    }).observe(this.parentElement!);
586  }
587
588  initHtml(): string {
589    return TabPaneComparisonHtml;
590  }
591}
592