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 { SpSystemTrace } from '../SpSystemTrace';
17import { TraceRow } from '../trace/base/TraceRow';
18
19import { BaseStruct } from '../../bean/BaseStruct';
20import { CounterStruct, SdkCounterRender } from '../../database/ui-worker/ProduceWorkerSdkCounter';
21import { renders } from '../../database/ui-worker/ProcedureWorker';
22import { SdkSliceRender, SdkSliceStruct } from '../../database/ui-worker/ProduceWorkerSdkSlice';
23import { EmptyRender } from '../../database/ui-worker/cpu/ProcedureWorkerCPU';
24import { TabUtil } from '../trace/sheet/sdk/TabUtil';
25import { queryCounterMax, querySdkCount, querySdkCounterData, querySdkSliceData } from '../../database/sql/Sdk.sql';
26import { queryStartTime } from '../../database/sql/SqlLite.sql';
27import { NUM_7 } from '../../bean/NumBean';
28
29export class SpSdkChart {
30  trace: SpSystemTrace;
31  private pluginName = 'dubai-plugin';
32
33  constructor(trace: SpSystemTrace) {
34    this.trace = trace;
35  }
36
37  private parseJsonByCounterType(startTime: number, showType: unknown, configObj: unknown, table: unknown): void {
38    let chartSql = this.createSql(
39      startTime, //@ts-ignore
40      showType.tableName, //@ts-ignore
41      showType.columns,
42      'where counter_id' + ' = $counter_id'
43    ); //@ts-ignore
44    let maxValue = this.createMaxValueSql(showType.tableName, 'where counter_id = $counter_id'); //@ts-ignore
45    let innerTable = showType.inner;
46    let countSql = this.createSql(startTime, innerTable.tableName, innerTable.columns); //@ts-ignore
47    table.push({
48      countSql: countSql,
49      chartSql: chartSql,
50      maxSql: maxValue,
51      type: 'counter', //@ts-ignore
52      name: configObj.disPlayName, //@ts-ignore
53      pluginName: configObj.pluginName,
54    });
55  }
56
57  private parseJsonBySliceType(startTime: number, showType: unknown, configObj: unknown, table: unknown[]): void {
58    let chartSql = this.createSliceSql(
59      startTime, //@ts-ignore
60      showType.tableName, //@ts-ignore
61      showType.columns,
62      'where' + ` slice_id = $column_id and (start_ts - ${startTime}) between $startNS and $endNS;`
63    ); //@ts-ignore
64    let innerTable = showType.inner;
65    let countSql;
66    let countOtherSql = ''; //@ts-ignore
67    if (configObj.pluginName === this.pluginName) {
68      countSql = this.createSql(startTime, innerTable.tableName, innerTable.columns, 'where slice_name like $suffix');
69      countOtherSql = this.createSql(
70        startTime,
71        innerTable.tableName,
72        innerTable.columns,
73        '' +
74          "where slice_name not like '%_cpu' and slice_name not like '%_display' and " +
75          "slice_name not like '%_gpu' and slice_name not like '%_System_idle' and " +
76          "slice_name not like '%_wifi_data' and slice_name not like '%_sensor' and " +
77          "slice_name not like '%_audio' "
78      );
79    } else {
80      countSql = this.createSql(startTime, innerTable.tableName, innerTable.columns);
81    }
82    table.push({
83      countSql: countSql,
84      chartSql: chartSql,
85      type: 'slice', //@ts-ignore
86      name: configObj.disPlayName, //@ts-ignore
87      pluginName: configObj.pluginName,
88      countOtherSql: countOtherSql,
89    });
90  }
91
92  parseJson(startTime: number, map: Map<number, string>): Map<number, unknown> {
93    let tablesMap = new Map();
94    let keys = map.keys();
95    for (let key of keys) {
96      let table: unknown[] = [];
97      let configObj: unknown = map.get(key);
98      if (configObj !== undefined) {
99        //@ts-ignore
100        let configStr = configObj.jsonConfig;
101        let json = JSON.parse(configStr);
102        let tableConfig = json.tableConfig;
103        if (tableConfig !== null) {
104          let showTypes = tableConfig.showType;
105          for (let i = 0; i < showTypes.length; i++) {
106            let showType = showTypes[i];
107            let type = TabUtil.getTableType(showType);
108            if (type === 'counter') {
109              this.parseJsonByCounterType(startTime, showType, configObj, table);
110            } else if (type === 'slice') {
111              this.parseJsonBySliceType(startTime, showType, configObj, table);
112            }
113          }
114          tablesMap.set(key, table);
115        }
116      }
117    }
118    return tablesMap;
119  }
120
121  private createSliceSql(startTime: number, tableName: string, columns: Array<unknown>, where?: string): string {
122    let sliceSelectSql = 'select ';
123    for (let i = 0; i < columns.length; i++) {
124      let column = columns[i]; //@ts-ignore
125      if (column.column === 'start_ts') {
126        //@ts-ignore
127        column.column = `(start_ts - ${startTime}) AS start_ts`;
128      } //@ts-ignore
129      if (column.column === 'end_ts') {
130        //@ts-ignore
131        column.column = `(end_ts - ${startTime}) AS end_ts`;
132      }
133      if (i === columns.length - 1) {
134        //@ts-ignore
135        sliceSelectSql = `${sliceSelectSql + column.column} `;
136      } else {
137        //@ts-ignore
138        sliceSelectSql = `${sliceSelectSql + column.column}, `;
139      }
140    }
141    sliceSelectSql = `${sliceSelectSql}from ${tableName}`;
142    if (where !== undefined) {
143      sliceSelectSql = `${sliceSelectSql} ${where}`;
144    }
145    return sliceSelectSql;
146  }
147
148  private createMaxValueSql(tableName: string, where?: string): string {
149    let selectSql = `select max(value) as max_value from ${tableName}`;
150    if (where !== undefined) {
151      selectSql = `${selectSql} ${where}`;
152    }
153    return selectSql;
154  }
155
156  private createSql(startTime: number, tableName: string, columns: Array<unknown>, where?: string): string {
157    let selectSql = 'select ';
158    for (let i = 0; i < columns.length; i++) {
159      let column = columns[i]; //@ts-ignore
160      if (column.column === 'ts') {
161        //@ts-ignore
162        column.column = `ts - ${startTime} AS ts`;
163      }
164      if (i === columns.length - 1) {
165        //@ts-ignore
166        selectSql = `${selectSql + column.column} `;
167      } else {
168        //@ts-ignore
169        selectSql = `${selectSql + column.column}, `;
170      }
171    }
172    selectSql = `${selectSql}from ${tableName}`;
173    if (where !== undefined) {
174      selectSql = `${selectSql} ${where}`;
175    }
176    return selectSql;
177  }
178
179  async init(): Promise<void> {
180    let configMap = SpSystemTrace.SDK_CONFIG_MAP;
181    if (configMap === undefined) {
182      return;
183    }
184    let res = await queryStartTime();
185    //@ts-ignore
186    let startTime = res[0].start_ts;
187    // @ts-ignore
188    let tablesMap = this.parseJson(startTime, configMap);
189    let tableKeys = tablesMap.keys();
190    for (let componentId of tableKeys) {
191      let table = tablesMap.get(componentId);
192      if (table !== null) {
193        //@ts-ignore
194        let nodeRow = this.initNodeRow(componentId, table[0].name); //@ts-ignore
195        for (let index = 0; index < table.length; index++) {
196          //@ts-ignore
197          let sqlMap = table[index];
198          if (sqlMap.type === 'counter') {
199            let result = await querySdkCount(sqlMap.countSql, componentId);
200            for (let i = 0; i < result.length; i++) {
201              await this.initCounter(nodeRow, i, result[i], sqlMap, componentId);
202            }
203          } else if (sqlMap.type === 'slice' && sqlMap.pluginName === this.pluginName) {
204            let suffixList = ['cpu', 'display', 'gpu', 'System_idle', 'wifi_data', 'sensor', 'audio'];
205            for (let i = 0; i < suffixList.length; i++) {
206              let result = await querySdkCount(sqlMap.countSql, componentId, { $suffix: `%${suffixList[i]}` });
207              if (result.length > 0) {
208                let groupNodeRow = await this.initSecondaryRow(nodeRow, i, suffixList[i]);
209                for (let i = 0; i < result.length; i++) {
210                  await this.initSlice(groupNodeRow, i, result[i], sqlMap, componentId);
211                }
212              }
213            }
214            let result = await querySdkCount(sqlMap.countOtherSql, componentId);
215            if (result.length > 0) {
216              let groupNodeRow = await this.initSecondaryRow(nodeRow, NUM_7, 'other');
217              for (let i = 0; i < result.length; i++) {
218                await this.initSlice(groupNodeRow, i, result[i], sqlMap, componentId);
219              }
220            }
221          } else if (sqlMap.type === 'slice') {
222            let result = await querySdkCount(sqlMap.countSql, componentId, {});
223            for (let i = 0; i < result.length; i++) {
224              await this.initSlice(nodeRow, i, result[i], sqlMap, componentId);
225            }
226          }
227        }
228      }
229    }
230  }
231
232  private initCounterChartRow(
233    componentId: number,
234    expansion: boolean,
235    counterId: string,
236    counterName: string
237  ): TraceRow<CounterStruct> {
238    let traceRow = TraceRow.skeleton<CounterStruct>();
239    traceRow.rowParentId = `Sdk-${componentId}`;
240    traceRow.rowHidden = !expansion;
241    traceRow.rowId = `${counterId}-${componentId}`;
242    traceRow.rowType = TraceRow.ROW_TYPE_SDK_COUNTER;
243    traceRow.folderPaddingLeft = 30;
244    traceRow.favoriteChangeHandler = this.trace.favoriteChangeHandler;
245    traceRow.selectChangeHandler = this.trace.selectChangeHandler;
246    traceRow.style.height = '40px';
247    traceRow.style.width = '100%';
248    traceRow.setAttribute('children', '');
249    traceRow.name = `${counterName}`;
250    return traceRow;
251  }
252
253  private initCounter = async (
254    nodeRow: TraceRow<BaseStruct>,
255    index: number,
256    result: unknown,
257    sqlMap: unknown,
258    componentId: number
259  ): Promise<void> => {
260    //@ts-ignore
261    let traceRow = this.initCounterChartRow(componentId, nodeRow.expansion, result.counter_id, result.counter_name);
262    traceRow.supplier = async (): Promise<CounterStruct[]> => //@ts-ignore
263      querySdkCounterData(sqlMap.chartSql, result.counter_id, componentId);
264    traceRow.focusHandler = (): void => {
265      this.trace?.displayTip(
266        traceRow,
267        CounterStruct.hoverCounterStruct,
268        `<span>${CounterStruct.hoverCounterStruct?.value?.toFixed(2)}</span>`
269      );
270    };
271    traceRow.findHoverStruct = (): void => {
272      CounterStruct.hoverCounterStruct = traceRow.getHoverStruct();
273    }; //@ts-ignore
274    let maxList = await queryCounterMax(sqlMap.maxSql, result.counter_id, componentId);
275    //@ts-ignore
276    let maxCounter = maxList[0].max_value;
277    traceRow.onThreadHandler = (useCache: boolean): void => {
278      let context: CanvasRenderingContext2D;
279      if (traceRow.currentContext) {
280        context = traceRow.currentContext;
281      } else {
282        context = traceRow.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!;
283      }
284      traceRow.canvasSave(context);
285      //@ts-ignore
286      (renders[TraceRow.ROW_TYPE_SDK_COUNTER] as SdkCounterRender).renderMainThread(
287        {
288          context: context,
289          useCache: useCache,
290          type: `sdk-counter-${index}`,
291          maxName: `${maxCounter}`,
292          maxValue: maxCounter,
293        },
294        traceRow
295      );
296      traceRow.canvasRestore(context, this.trace);
297    };
298    nodeRow.addChildTraceRow(traceRow);
299  };
300
301  private initNodeRow = (index: number, name: string): TraceRow<BaseStruct> => {
302    let sdkFolder = TraceRow.skeleton();
303    sdkFolder.rowId = `Sdk-${index}`;
304    sdkFolder.index = index;
305    sdkFolder.rowType = TraceRow.ROW_TYPE_SDK;
306    sdkFolder.rowParentId = '';
307    sdkFolder.style.height = '40px';
308    sdkFolder.folder = true;
309    sdkFolder.name = `${name}`;
310    sdkFolder.favoriteChangeHandler = this.trace.favoriteChangeHandler;
311    sdkFolder.selectChangeHandler = this.trace.selectChangeHandler;
312    sdkFolder.supplier = async (): Promise<BaseStruct[]> => new Promise<[]>((resolve) => resolve([]));
313    sdkFolder.onThreadHandler = (useCache: boolean): void => {
314      sdkFolder.canvasSave(this.trace.canvasPanelCtx!);
315      if (sdkFolder.expansion) {
316        // @ts-ignore
317        this.trace.canvasPanelCtx?.clearRect(0, 0, sdkFolder.frame.width, sdkFolder.frame.height);
318      } else {
319        (renders.empty as EmptyRender).renderMainThread(
320          {
321            context: this.trace.canvasPanelCtx,
322            useCache: useCache,
323            type: '',
324          },
325          // @ts-ignore
326          sdkFolder
327        );
328      }
329      sdkFolder.canvasRestore(this.trace.canvasPanelCtx!, this.trace);
330    };
331    this.trace.rowsEL?.appendChild(sdkFolder);
332    return sdkFolder;
333  };
334
335  private initSecondaryRow = async (
336    nodeRow: TraceRow<BaseStruct>,
337    index: number,
338    name: string
339  ): Promise<TraceRow<BaseStruct>> => {
340    let sdkSecondFolder = TraceRow.skeleton();
341    sdkSecondFolder.rowId = `Sdk-${name}-${index}`;
342    sdkSecondFolder.index = index;
343    sdkSecondFolder.rowType = TraceRow.ROW_TYPE_SDK;
344    sdkSecondFolder.rowParentId = nodeRow.rowId;
345    sdkSecondFolder.rowHidden = !nodeRow.expansion;
346    sdkSecondFolder.style.height = '40px';
347    sdkSecondFolder.folder = true;
348    sdkSecondFolder.folderPaddingLeft = 30;
349    sdkSecondFolder.name = `${name}`;
350    sdkSecondFolder.favoriteChangeHandler = this.trace.favoriteChangeHandler;
351    sdkSecondFolder.selectChangeHandler = this.trace.selectChangeHandler;
352    sdkSecondFolder.supplier = async (): Promise<BaseStruct[]> => new Promise<[]>((resolve) => resolve([]));
353    sdkSecondFolder.onThreadHandler = (useCache: boolean): void => {
354      sdkSecondFolder.canvasSave(this.trace.canvasPanelCtx!);
355      if (sdkSecondFolder.expansion) {
356        // @ts-ignore
357        this.trace.canvasPanelCtx?.clearRect(0, 0, sdkSecondFolder.frame.width, sdkSecondFolder.frame.height);
358      } else {
359        (renders.empty as EmptyRender).renderMainThread(
360          {
361            context: this.trace.canvasPanelCtx,
362            useCache: useCache,
363            type: '',
364          },
365          // @ts-ignore
366          sdkSecondFolder
367        );
368      }
369      sdkSecondFolder.canvasRestore(this.trace.canvasPanelCtx!, this.trace);
370    };
371    this.trace.rowsEL?.appendChild(sdkSecondFolder);
372    return sdkSecondFolder;
373  };
374
375  private initSliceChartRow(
376    expansion: boolean,
377    rowId: string,
378    sliceId: string,
379    sliceName: string,
380    componentId: number
381  ): TraceRow<SdkSliceStruct> {
382    let traceRow = TraceRow.skeleton<SdkSliceStruct>();
383    traceRow.rowType = TraceRow.ROW_TYPE_SDK_SLICE;
384    traceRow.rowHidden = !expansion;
385    traceRow.rowParentId = rowId;
386    traceRow.folderPaddingLeft = 30;
387    traceRow.style.height = '40px';
388    traceRow.style.width = '100%';
389    traceRow.name = `${sliceName}`;
390    traceRow.setAttribute('children', '');
391    traceRow.favoriteChangeHandler = this.trace.favoriteChangeHandler;
392    traceRow.selectChangeHandler = this.trace.selectChangeHandler;
393    traceRow.rowId = `${sliceId}-${componentId}`;
394    return traceRow;
395  }
396
397  private initSlice = async (
398    nodeRow: TraceRow<BaseStruct>,
399    index: number,
400    result: unknown,
401    sqlMap: unknown,
402    componentId: number
403  ): Promise<void> => {
404    let traceRow = this.initSliceChartRow(
405      nodeRow.expansion,
406      nodeRow.rowId!, //@ts-ignore
407      result.slice_id, //@ts-ignore
408      result.slice_name,
409      componentId
410    );
411    traceRow.supplier = async (): Promise<SdkSliceStruct[]> =>
412      querySdkSliceData(
413        //@ts-ignore
414        sqlMap.chartSql, //@ts-ignore
415        result.slice_id,
416        TraceRow.range?.startNS || 0,
417        TraceRow.range?.endNS || 0,
418        componentId
419      );
420    traceRow.focusHandler = (): void => {
421      this.trace?.displayTip(
422        traceRow,
423        SdkSliceStruct.hoverSdkSliceStruct,
424        `<span>${SdkSliceStruct.hoverSdkSliceStruct?.value}</span>`
425      );
426    };
427    traceRow.findHoverStruct = (): void => {
428      SdkSliceStruct.hoverSdkSliceStruct = traceRow.getHoverStruct();
429    };
430    traceRow.onThreadHandler = (useCache: boolean): void => {
431      let context: CanvasRenderingContext2D;
432      if (traceRow.currentContext) {
433        context = traceRow.currentContext;
434      } else {
435        context = traceRow.collect ? this.trace.canvasFavoritePanelCtx! : this.trace.canvasPanelCtx!;
436      }
437      traceRow.canvasSave(context);
438      //@ts-ignore
439      (renders[TraceRow.ROW_TYPE_SDK_SLICE] as SdkSliceRender).renderMainThread(
440        {
441          context: context,
442          useCache: useCache,
443          type: `sdk-slice-${index}`,
444          maxName: '',
445          maxValue: 0,
446        },
447        traceRow
448      );
449      traceRow.canvasRestore(context, this.trace);
450    };
451    nodeRow.addChildTraceRow(traceRow);
452  };
453}
454