1// Copyright (c) 2021 Huawei Device Co., Ltd.
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6//     http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14import { TraficEnum } from './utils/QueryEnum';
15
16interface SenderParam {
17  params: {
18    frame: { width: number };
19    drawType: number;
20    endNS: number;
21    startNS: number;
22    eventType: number;
23    ipid: number;
24    processes: number[];
25    totalNS: number;
26    trafic: TraficEnum;
27    recordEndNS: number;
28    recordStartNS: number;
29    model: string;
30    isCache: boolean;
31  };
32  id: string;
33  action: string;
34}
35
36interface NativeMemoryCacheType {
37  maxSize: number;
38  minSize: number;
39  maxDensity: number;
40  minDensity: number;
41  dataList: Array<NativeMemoryChartDataType>;
42}
43
44class NativeMemoryChartDataType {
45  startTime: number = 0;
46  dur: number = 0;
47  heapSize: number = 0;
48  density: number = 0;
49}
50
51class NMData {
52  callchainId: number = 0;
53  startTs: number = 0;
54  startTime: number = 0;
55  applyCount: number = 0;
56  applySize: number = 0;
57  releaseCount: number = 0;
58  releaseSize: number = 0;
59  heapSize: number = 0;
60  eventType: number = 0;
61  type: number = 0;
62  ipid: number = 0;
63}
64
65const dataCache: {
66  normalCache: Map<string, NativeMemoryCacheType>;
67  statisticsCache: Map<string, NativeMemoryCacheType>;
68} = {
69  normalCache: new Map<string, NativeMemoryCacheType>(),
70  statisticsCache: new Map<string, NativeMemoryCacheType>(),
71};
72
73let tempSize: number = 0;
74let tempDensity: number = 0;
75
76function nativeMemoryChartDataCacheSql(model: string, startNS: number, endNS: number): string {
77  if (model === 'native_hook') {
78    return `select * from (
79                select 
80                    h.start_ts - ${startNS} as startTime,
81                    h.heap_size as heapSize,
82                    (case when h.event_type = 'AllocEvent' then 0 else 1 end) as eventType,
83                    ipid
84                from native_hook h
85                where h.start_ts between ${startNS} and ${endNS}
86                    and (h.event_type = 'AllocEvent' or h.event_type = 'MmapEvent')
87                union all
88                select 
89                    h.end_ts - ${startNS} as startTime,
90                    h.heap_size as heapSize,
91                    (case when h.event_type = 'AllocEvent' then 2 else 3 end) as eventType,
92                    ipid
93                from native_hook h
94                where 
95                  h.start_ts between ${startNS} and ${endNS}
96                  and h.end_ts between ${startNS} and ${endNS}
97                  and (h.event_type = 'AllocEvent' or h.event_type = 'MmapEvent')
98            )
99            order by startTime;`;
100  } else {
101    return `select callchain_id    as callchainId,
102                ts - ${startNS}    as startTs,
103                apply_count        as applyCount,
104                apply_size         as applySize,
105                release_count      as releaseCount,
106                release_size       as releaseSize,
107                ipid,
108                type               
109            from native_hook_statistic
110            where ts between ${startNS} and ${endNS};
111    `;
112  }
113}
114
115function normalChartDataHandler(data: Array<NMData>, key: string, totalNS: number): void {
116  let nmFilterLen = data.length;
117  let nmFilterLevel = getFilterLevel(nmFilterLen);
118  tempSize = 0;
119  tempDensity = 0;
120  data.map((ne: NMData, index: number): void =>
121    mergeNormalChartData(ne, nmFilterLevel, index === nmFilterLen - 1, key)
122  );
123  let cache = dataCache.normalCache.get(key);
124  if (cache && cache.dataList.length > 0) {
125    cache.dataList[cache.dataList.length - 1].dur = totalNS - cache.dataList[cache.dataList.length - 1].startTime!;
126  }
127}
128
129function mergeNormalChartData(ne: NMData, filterLevel: number, finish: boolean, key: string): void {
130  let item: NativeMemoryChartDataType = {
131    startTime: ne.startTime,
132    density: 0,
133    heapSize: 0,
134    dur: 0,
135  };
136  if (!dataCache.normalCache.has(key)) {
137    if (ne.eventType === 0 || ne.eventType === 1) {
138      item.density = 1;
139      item.heapSize = ne.heapSize;
140    } else {
141      item.density = -1;
142      item.heapSize = 0 - ne.heapSize;
143    }
144    dataCache.normalCache.set(key, {
145      maxSize: item.heapSize,
146      minSize: item.heapSize,
147      maxDensity: item.density,
148      minDensity: item.density,
149      dataList: [item],
150    });
151  } else {
152    mergeData(item, ne, filterLevel, finish, key);
153  }
154}
155
156function mergeData(
157  item: NativeMemoryChartDataType,
158  ne: NMData,
159  filterLevel: number,
160  finish: boolean,
161  key: string
162): void {
163  let data = dataCache.normalCache.get(key);
164  if (data) {
165    let last = data.dataList[data.dataList.length - 1];
166    last.dur = item.startTime! - last.startTime!;
167    if (last.dur > filterLevel || finish) {
168      if (ne.eventType === 0 || ne.eventType === 1) {
169        item.density = last.density! + tempDensity + 1;
170        item.heapSize = last.heapSize! + tempSize + ne.heapSize;
171      } else {
172        item.density = last.density! + tempDensity - 1;
173        item.heapSize = last.heapSize! + tempSize - ne.heapSize;
174      }
175      tempDensity = 0;
176      tempSize = 0;
177      data.maxDensity = Math.max(item.density, data.maxDensity);
178      data.minDensity = Math.min(item.density, data.minDensity);
179      data.maxSize = Math.max(item.heapSize, data.maxSize);
180      data.minSize = Math.min(item.heapSize, data.minSize);
181      data.dataList.push(item);
182    } else {
183      if (ne.eventType === 0 || ne.eventType === 1) {
184        tempDensity += 1;
185        tempSize += ne.heapSize;
186      } else {
187        tempDensity -= 1;
188        tempSize -= ne.heapSize;
189      }
190    }
191  }
192}
193
194function statisticChartHandler(arr: Array<NMData>, key: string, timeArr: number[]): void {
195  let callGroupMap: Map<number, NMData[]> = new Map<number, NMData[]>();
196  let obj: Map<number, NativeMemoryChartDataType> = new Map<number, NativeMemoryChartDataType>();
197  for (let hook of arr) {
198    if (obj.has(hook.startTs)) {
199      let data = obj.get(hook.startTs)!;
200      data.startTime = hook.startTs;
201      data.dur = 0;
202      if (callGroupMap.has(hook.callchainId)) {
203        let calls = callGroupMap.get(hook.callchainId);
204        let last = calls![calls!.length - 1];
205        data.heapSize += hook.applySize - last.applySize - (hook.releaseSize - last.releaseSize);
206        data.density += hook.applyCount - last.applyCount - (hook.releaseCount - last.releaseCount);
207        calls!.push(hook);
208      } else {
209        data.heapSize += hook.applySize - hook.releaseSize;
210        data.density += hook.applyCount - hook.releaseCount;
211        callGroupMap.set(hook.callchainId, [hook]);
212      }
213    } else {
214      let data: NativeMemoryChartDataType = new NativeMemoryChartDataType();
215      data.startTime = hook.startTs;
216      data.dur = 0;
217      if (callGroupMap.has(hook.callchainId)) {
218        let calls = callGroupMap.get(hook.callchainId);
219        let last = calls![calls!.length - 1];
220        data.heapSize = hook.applySize - last.applySize - (hook.releaseSize - last.releaseSize);
221        data.density = hook.applyCount - last.applyCount - (hook.releaseCount - last.releaseCount);
222        calls!.push(hook);
223      } else {
224        data.heapSize = hook.applySize - hook.releaseSize;
225        data.density = hook.applyCount - hook.releaseCount;
226        callGroupMap.set(hook.callchainId, [hook]);
227      }
228      obj.set(hook.startTs, data);
229    }
230  }
231  saveStatisticsCacheMapValue(key, obj, timeArr);
232}
233
234function saveStatisticsCacheMapValue(
235  key: string,
236  obj: Map<number, NativeMemoryChartDataType>,
237  timeArr: number[]
238): void {
239  let source = Array.from(obj.values());
240  let arr: NativeMemoryChartDataType[] = [];
241  let cache = {
242    maxSize: 0,
243    minSize: 0,
244    maxDensity: 0,
245    minDensity: 0,
246    dataList: arr,
247  };
248  for (let i = 0, len = source.length; i < len; i++) {
249    let startTsIndex = timeArr.findIndex((time) => source[i].startTime === time);
250    let realStartTs = startTsIndex > 0 ? timeArr[startTsIndex - 1] : 0;
251    let item = {
252      startTime: realStartTs,
253      heapSize: i > 0 ? source[i].heapSize + arr[i - 1].heapSize : source[i].heapSize,
254      density: i > 0 ? source[i].density + arr[i - 1].density : source[i].density,
255      dur: source[i].startTime - realStartTs,
256    };
257    arr.push(item);
258    cache.maxSize = Math.max(cache.maxSize, item.heapSize);
259    cache.maxDensity = Math.max(cache.maxDensity, item.density);
260    cache.minSize = Math.min(cache.minSize, item.heapSize);
261    cache.minDensity = Math.min(cache.minDensity, item.density);
262  }
263  source.length = 0;
264  dataCache.statisticsCache.set(key, cache);
265}
266
267function cacheSumRowData(ipid: number, key: string, timeArr: number[]): void {
268  let ah = dataCache.statisticsCache.get(`${ipid}-1`)?.dataList;
269  let mmap = dataCache.statisticsCache.get(`${ipid}-2`)?.dataList;
270  let arr: NativeMemoryChartDataType[] = [];
271  let sumCache = {
272    maxSize: 0,
273    minSize: 0,
274    maxDensity: 0,
275    minDensity: 0,
276    dataList: arr,
277  };
278  timeArr.unshift(0);
279  timeArr.forEach((time, index) => {
280    let item = {
281      startTime: time,
282      heapSize: 0,
283      density: 0,
284      dur: 0,
285    };
286    let ahItem = ah?.find((it) => it.startTime === time);
287    let mmapItem = mmap?.find((it) => it.startTime === time);
288    if (ahItem) {
289      item.heapSize += ahItem.heapSize;
290      item.density += ahItem.density;
291    }
292    if (mmapItem) {
293      item.heapSize += mmapItem.heapSize;
294      item.density += mmapItem.density;
295    }
296    item.dur = ahItem?.dur || mmapItem?.dur || 0;
297    arr.push(item);
298    sumCache.maxSize = Math.max(sumCache.maxSize, item.heapSize);
299    sumCache.maxDensity = Math.max(sumCache.maxDensity, item.density);
300    sumCache.minSize = Math.min(sumCache.minSize, item.heapSize);
301    sumCache.minDensity = Math.min(sumCache.minDensity, item.density);
302  });
303  dataCache.statisticsCache.set(key, sumCache);
304}
305
306function cacheNativeMemoryChartData(model: string, totalNS: number, processes: number[], data: Array<NMData>): void {
307  processes.forEach((ipid) => {
308    let processData = data.filter((ne) => ne.ipid === ipid);
309    if (model === 'native_hook') {
310      //正常模式
311      normalChartDataHandler(processData, `${ipid}-0`, totalNS);
312      normalChartDataHandler(
313        processData.filter((ne) => ne.eventType === 0 || ne.eventType === 2),
314        `${ipid}-1`,
315        totalNS
316      );
317      normalChartDataHandler(
318        processData.filter((ne) => ne.eventType === 1 || ne.eventType === 3),
319        `${ipid}-2`,
320        totalNS
321      );
322    } else {
323      //统计模式
324      let timeSet: Set<number> = new Set<number>();
325      let alData: NMData[] = [];
326      let mmapData: NMData[] = [];
327      processData.forEach((ne) => {
328        timeSet.add(ne.startTs);
329        if (ne.type === 0) {
330          alData.push(ne);
331        } else {
332          mmapData.push(ne);
333        }
334      });
335      let timeArr = Array.from(timeSet).sort((a, b) => a - b);
336      statisticChartHandler(alData, `${ipid}-1`, timeArr);
337      statisticChartHandler(mmapData, `${ipid}-2`, timeArr);
338      cacheSumRowData(ipid, `${ipid}-0`, timeArr);
339      timeArr.length = 0;
340      timeSet.clear();
341    }
342    processData.length = 0;
343  });
344}
345
346function getFilterLevel(len: number): number {
347  if (len > 300_0000) {
348    return 50_0000;
349  } else if (len > 200_0000) {
350    return 30_0000;
351  } else if (len > 100_0000) {
352    return 10_0000;
353  } else if (len > 50_0000) {
354    return 5_0000;
355  } else if (len > 30_0000) {
356    return 2_0000;
357  } else if (len > 15_0000) {
358    return 1_0000;
359  } else {
360    return 0;
361  }
362}
363
364export function nativeMemoryCacheClear(): void {
365  dataCache.normalCache.clear();
366  dataCache.statisticsCache.clear();
367}
368
369export function nativeMemoryDataHandler(data: SenderParam, proc: Function): void {
370  if (data.params.isCache) {
371    dataCache.normalCache.clear();
372    dataCache.statisticsCache.clear();
373    let arr: NMData[] = [];
374    let res: Array<
375      | {
376          nativeMemoryNormal: NMData;
377          nativeMemoryStatistic: NMData;
378        }
379      | NMData
380    > = proc(nativeMemoryChartDataCacheSql(data.params.model, data.params.recordStartNS, data.params.recordEndNS));
381    if (data.params.trafic === TraficEnum.ProtoBuffer) {
382      arr = (
383        res as Array<{
384          nativeMemoryNormal: NMData;
385          nativeMemoryStatistic: NMData;
386        }>
387      ).map((item) => {
388        let nm = new NMData();
389        if (data.params.model === 'native_hook') {
390          Object.assign(nm, item.nativeMemoryNormal);
391        } else {
392          Object.assign(nm, item.nativeMemoryStatistic);
393        }
394        return nm;
395      });
396    } else {
397      arr = res as Array<NMData>;
398    }
399    cacheNativeMemoryChartData(data.params.model, data.params.totalNS, data.params.processes, arr);
400    res.length = 0;
401    (self as unknown as Worker).postMessage(
402      {
403        id: data.id,
404        action: data.action,
405        results: 'ok',
406        len: 0,
407      },
408      []
409    );
410  } else {
411    arrayBufferCallback(data, true);
412  }
413}
414
415function arrayBufferCallback(data: SenderParam, transfer: boolean): void {
416  let cacheKey = `${data.params.ipid}-${data.params.eventType}`;
417  let dataFilter = filterNativeMemoryChartData(
418    data.params.model,
419    data.params.startNS,
420    data.params.endNS,
421    data.params.totalNS,
422    data.params.drawType,
423    data.params.frame,
424    cacheKey
425  );
426  let len = dataFilter.startTime.length;
427  let startTime = new Float64Array(len);
428  let dur = new Float64Array(len);
429  let density = new Int32Array(len);
430  let heapSize = new Float64Array(len);
431  for (let i = 0; i < len; i++) {
432    startTime[i] = dataFilter.startTime[i];
433    dur[i] = dataFilter.dur[i];
434    heapSize[i] = dataFilter.heapSize[i];
435    density[i] = dataFilter.density[i];
436  }
437  let cacheSource = data.params.model === 'native_hook' ? dataCache.normalCache : dataCache.statisticsCache;
438  let cache = cacheSource.get(cacheKey);
439  (self as unknown as Worker).postMessage(
440    {
441      id: data.id,
442      action: data.action,
443      results: transfer
444        ? {
445            startTime: startTime.buffer,
446            dur: dur.buffer,
447            density: density.buffer,
448            heapSize: heapSize.buffer,
449            maxSize: cache!.maxSize,
450            minSize: cache!.minSize,
451            maxDensity: cache!.maxDensity,
452            minDensity: cache!.minDensity,
453          }
454        : {},
455      len: len,
456    },
457    transfer ? [startTime.buffer, dur.buffer, density.buffer, heapSize.buffer] : []
458  );
459}
460
461export function filterNativeMemoryChartData(
462  model: string,
463  startNS: number,
464  endNS: number,
465  totalNS: number,
466  drawType: number,
467  frame: { width: number },
468  key: string
469): NativeMemoryDataSource {
470  let dataSource = new NativeMemoryDataSource();
471  let cache = model === 'native_hook' ? dataCache.normalCache.get(key) : dataCache.statisticsCache.get(key);
472  if (cache !== undefined) {
473    let data: Map<string, number> = new Map<string, number>();
474    cache!.dataList.reduce((pre, current, index) => {
475      if (current.dur > 0 && current.startTime + current.dur >= startNS && current.startTime <= endNS) {
476        if (dur2Width(current.startTime, current.dur, startNS, endNS || totalNS, frame) >= 1) {
477          //计算绘制宽度 大于 1px,则加入绘制列表
478          dataSource.startTime.push(current.startTime);
479          dataSource.dur.push(current.dur);
480          dataSource.density.push(current.density);
481          dataSource.heapSize.push(current.heapSize);
482        } else {
483          let x = 0;
484          if (current.startTime > startNS && current.startTime < endNS) {
485            x = Math.trunc(ns2x(current.startTime, startNS, endNS, totalNS, frame));
486          } else {
487            x = 0;
488          }
489          let key = `${x}`;
490          let preIndex = pre.get(key);
491          if (preIndex !== undefined) {
492            if (drawType === 0) {
493              pre.set(key, cache!.dataList[preIndex].heapSize > cache!.dataList[index].heapSize ? preIndex : index);
494            } else {
495              pre.set(key, cache!.dataList[preIndex].density > cache!.dataList[index].density ? preIndex : index);
496            }
497          } else {
498            pre.set(key, index);
499          }
500        }
501      }
502      return pre;
503    }, data);
504    setDataSource(data, dataSource, cache);
505  }
506  return dataSource;
507}
508
509function setDataSource(
510  data: Map<string, number>,
511  dataSource: NativeMemoryDataSource,
512  cache: NativeMemoryCacheType
513): void {
514  Array.from(data.values()).forEach((idx) => {
515    dataSource.startTime.push(cache!.dataList[idx].startTime);
516    dataSource.dur.push(cache!.dataList[idx].dur);
517    dataSource.density.push(cache!.dataList[idx].density);
518    dataSource.heapSize.push(cache!.dataList[idx].heapSize);
519  });
520}
521
522function ns2x(ns: number, startNS: number, endNS: number, duration: number, rect: { width: number }): number {
523  if (endNS === 0) {
524    endNS = duration;
525  }
526  let xSizeNM: number = ((ns - startNS) * rect.width) / (endNS - startNS);
527  if (xSizeNM < 0) {
528    xSizeNM = 0;
529  } else if (xSizeNM > rect.width) {
530    xSizeNM = rect.width;
531  }
532  return xSizeNM;
533}
534
535function dur2Width(startTime: number, dur: number, startNS: number, endNS: number, rect: { width: number }): number {
536  let realDur = startTime + dur - Math.max(startTime, startNS);
537  return Math.trunc((realDur * rect.width) / (endNS - startNS));
538}
539
540class NativeMemoryDataSource {
541  startTime: Array<number>;
542  dur: Array<number>;
543  heapSize: Array<number>;
544  density: Array<number>;
545  constructor() {
546    this.startTime = [];
547    this.dur = [];
548    this.heapSize = [];
549    this.density = [];
550  }
551}
552