• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 NativeMemoryCacheType {
17  maxSize: number;
18  minSize: number;
19  maxDensity: number;
20  minDensity: number;
21  dataList: Array<NativeMemoryChartDataType>;
22}
23
24interface NativeMemoryChartDataType {
25  startTime: number;
26  dur: number;
27  heapSize: number;
28  density: number;
29}
30
31const dataCache: {
32  normalCache: Map<string, NativeMemoryCacheType>;
33  statisticsCache: Map<string, NativeMemoryCacheType>;
34} = {
35  normalCache: new Map<string, NativeMemoryCacheType>(),
36  statisticsCache: new Map<string, NativeMemoryCacheType>(),
37};
38
39let tempSize: number = 0;
40let tempDensity: number = 0;
41
42function nativeMemoryChartDataCacheSql(model: string, startNS: number, endNS: number): string {
43  if (model === 'native_hook') {
44    return `select * from (
45                select
46                    h.start_ts - ${startNS} as startTime,
47                    h.heap_size as heapSize,
48                    (case when h.event_type = 'AllocEvent' then 0 else 1 end) as eventType,
49                    ipid
50                from native_hook h
51                where h.start_ts between ${startNS} and ${endNS}
52                    and (h.event_type = 'AllocEvent' or h.event_type = 'MmapEvent')
53                union all
54                select
55                    h.end_ts - ${startNS} as startTime,
56                    h.heap_size as heapSize,
57                    (case when h.event_type = 'AllocEvent' then 2 else 3 end) as eventType,
58                    ipid
59                from native_hook h
60                where
61                  h.start_ts between ${startNS} and ${endNS}
62                  and h.end_ts between ${startNS} and ${endNS}
63                  and (h.event_type = 'AllocEvent' or h.event_type = 'MmapEvent')
64            )
65            order by startTime;`;
66  } else {
67    return `select callchain_id    as callchainId,
68                ts - ${startNS}    as startTs,
69                apply_count        as applyCount,
70                apply_size         as applySize,
71                release_count      as releaseCount,
72                release_size       as releaseSize,
73                ipid,
74                type
75            from native_hook_statistic
76            where ts between ${startNS} and ${endNS};
77    `;
78  }
79}
80
81function normalChartDataHandler(data: Array<any>, key: string, totalNS: number): void {
82  let nmFilterLen = data.length;
83  let nmFilterLevel = getFilterLevel(nmFilterLen);
84  tempSize = 0;
85  tempDensity = 0;
86  data.map((ne: any, index: number): void => mergeNormalChartData(ne, nmFilterLevel, index === nmFilterLen - 1, key));
87  let cache = dataCache.normalCache.get(key);
88  if (cache && cache.dataList.length > 0) {
89    cache.dataList[cache.dataList.length - 1].dur = totalNS - cache.dataList[cache.dataList.length - 1].startTime!;
90  }
91}
92
93function mergeNormalChartData(ne: any, filterLevel: number, finish: boolean, key: string): void {
94  let item = {
95    startTime: ne.startTime,
96    density: 0,
97    heapSize: 0,
98    dur: 0,
99  };
100  if (!dataCache.normalCache.has(key)) {
101    if (ne.eventType === 0 || ne.eventType === 1) {
102      item.density = 1;
103      item.heapSize = ne.heapSize;
104    } else {
105      item.density = -1;
106      item.heapSize = 0 - ne.heapSize;
107    }
108    dataCache.normalCache.set(key, {
109      maxSize: item.heapSize,
110      minSize: item.heapSize,
111      maxDensity: item.density,
112      minDensity: item.density,
113      dataList: [item],
114    });
115  } else {
116    mergeData(item, ne, filterLevel, finish, key);
117  }
118}
119
120function mergeData(item: any, ne: any, filterLevel: number, finish: boolean, key: string): void {
121  let data = dataCache.normalCache.get(key);
122  if (data) {
123    let last = data.dataList[data.dataList.length - 1];
124    last.dur = item.startTime! - last.startTime!;
125    if (last.dur > filterLevel || finish) {
126      if (ne.eventType === 0 || ne.eventType === 1) {
127        item.density = last.density! + tempDensity + 1;
128        item.heapSize = last.heapSize! + tempSize + ne.heapSize;
129      } else {
130        item.density = last.density! + tempDensity - 1;
131        item.heapSize = last.heapSize! + tempSize - ne.heapSize;
132      }
133      tempDensity = 0;
134      tempSize = 0;
135      data.maxDensity = Math.max(item.density, data.maxDensity);
136      data.minDensity = Math.min(item.density, data.minDensity);
137      data.maxSize = Math.max(item.heapSize, data.maxSize);
138      data.minSize = Math.min(item.heapSize, data.minSize);
139      data.dataList.push(item);
140    } else {
141      if (ne.eventType === 0 || ne.eventType === 1) {
142        tempDensity += 1;
143        tempSize += ne.heapSize;
144      } else {
145        tempDensity -= 1;
146        tempSize -= ne.heapSize;
147      }
148    }
149  }
150}
151
152function statisticChartHandler(arr: Array<any>, key: string, totalNS: number): void {
153  let callGroupMap: Map<number, any[]> = new Map<number, any[]>();
154  let obj: any = {};
155  for (let hook of arr) {
156    if (obj[hook.startTs]) {
157      let data = obj[hook.startTs];
158      data.startTime = hook.startTs;
159      data.dur = 0;
160      if (callGroupMap.has(hook.callchainId)) {
161        let calls = callGroupMap.get(hook.callchainId);
162        let last = calls![calls!.length - 1];
163        data.heapSize += hook.applySize - last.applySize - (hook.releaseSize - last.releaseSize);
164        data.density += hook.applyCount - last.applyCount - (hook.releaseCount - last.releaseCount);
165        calls!.push(hook);
166      } else {
167        data.heapSize += hook.applySize - hook.releaseSize;
168        data.density += hook.applyCount - hook.releaseCount;
169        callGroupMap.set(hook.callchainId, [hook]);
170      }
171    } else {
172      let data: any = {};
173      data.startTime = hook.startTs;
174      data.dur = 0;
175      if (callGroupMap.has(hook.callchainId)) {
176        let calls = callGroupMap.get(hook.callchainId);
177        let last = calls![calls!.length - 1];
178        data.heapSize = hook.applySize - last.applySize - (hook.releaseSize - last.releaseSize);
179        data.density = hook.applyCount - last.applyCount - (hook.releaseCount - last.releaseCount);
180        calls!.push(hook);
181      } else {
182        data.heapSize = hook.applySize - hook.releaseSize;
183        data.density = hook.applyCount - hook.releaseCount;
184        callGroupMap.set(hook.callchainId, [hook]);
185      }
186      obj[hook.startTs] = data;
187    }
188  }
189  let cache = setStatisticsCacheMapValue(obj, totalNS);
190  dataCache.statisticsCache.set(key, cache);
191}
192
193function setStatisticsCacheMapValue(obj: any, totalNS: number): any {
194  let source = Object.values(obj) as {
195    startTime: number;
196    heapSize: number;
197    density: number;
198    dur: number;
199  }[];
200  let cache = {
201    maxSize: 0,
202    minSize: 0,
203    maxDensity: 0,
204    minDensity: 0,
205    dataList: source,
206  };
207  for (let i = 0, len = source.length; i < len; i++) {
208    if (i === len - 1) {
209      source[i].dur = totalNS - source[i].startTime;
210    } else {
211      source[i + 1].heapSize = source[i].heapSize + source[i + 1].heapSize;
212      source[i + 1].density = source[i].density + source[i + 1].density;
213      source[i].dur = source[i + 1].startTime - source[i].startTime;
214    }
215    cache.maxSize = Math.max(cache.maxSize, source[i].heapSize);
216    cache.maxDensity = Math.max(cache.maxDensity, source[i].density);
217    cache.minSize = Math.min(cache.minSize, source[i].heapSize);
218    cache.minDensity = Math.min(cache.minDensity, source[i].density);
219  }
220  return cache;
221}
222
223function cacheNativeMemoryChartData(model: string, totalNS: number, processes: number[], data: Array<any>): void {
224  processes.forEach(ipid => {
225    let processData = data.filter(ne => ne.ipid === ipid);
226    if (model === 'native_hook') {
227      //正常模式
228      normalChartDataHandler(processData, `${ipid}-0`, totalNS);
229      normalChartDataHandler(
230        processData.filter(ne => ne.eventType === 0 || ne.eventType === 2),
231        `${ipid}-1`,
232        totalNS
233      );
234      normalChartDataHandler(
235        processData.filter(ne => ne.eventType === 1 || ne.eventType === 3),
236        `${ipid}-2`,
237        totalNS
238      );
239    } else {
240      //统计模式
241      statisticChartHandler(processData, `${ipid}-0`, totalNS);
242      statisticChartHandler(
243        processData.filter(ne => ne.type === 0),
244        `${ipid}-1`,
245        totalNS
246      );
247      statisticChartHandler(
248        processData.filter(ne => ne.type > 0),
249        `${ipid}-2`,
250        totalNS
251      );
252    }
253    processData.length = 0;
254  });
255}
256
257function getFilterLevel(len: number): number {
258  if (len > 300_0000) {
259    return 50_0000;
260  } else if (len > 200_0000) {
261    return 30_0000;
262  } else if (len > 100_0000) {
263    return 10_0000;
264  } else if (len > 50_0000) {
265    return 5_0000;
266  } else if (len > 30_0000) {
267    return 2_0000;
268  } else if (len > 15_0000) {
269    return 1_0000;
270  } else {
271    return 0;
272  }
273}
274
275export function nativeMemoryCacheClear() {
276  dataCache.normalCache.clear();
277  dataCache.statisticsCache.clear();
278}
279
280export function nativeMemoryDataHandler(data: any, proc: Function): void {
281  if (data.params.isCache) {
282    dataCache.normalCache.clear();
283    dataCache.statisticsCache.clear();
284    let res: Array<any> = proc(
285      nativeMemoryChartDataCacheSql(data.params.model, data.params.recordStartNS, data.params.recordEndNS)
286    );
287    if (data.params.trafic === TraficEnum.ProtoBuffer) {
288      res = res.map((item) => {
289        if (data.params.model === 'native_hook') {
290          return {
291            startTime: item.nativeMemoryNormal.startTime || 0,
292            heapSize: item.nativeMemoryNormal.heapSize || 0,
293            eventType: item.nativeMemoryNormal.eventType || 0,
294            ipid: item.nativeMemoryNormal.ipid || 0,
295          };
296        } else {
297          return {
298            callchainId: item.nativeMemoryStatistic.callchainId || 0,
299            startTs: item.nativeMemoryStatistic.startTs || 0,
300            applyCount: item.nativeMemoryStatistic.applyCount || 0,
301            applySize: item.nativeMemoryStatistic.applySize || 0,
302            releaseCount: item.nativeMemoryStatistic.releaseCount || 0,
303            releaseSize: item.nativeMemoryStatistic.releaseSize || 0,
304            ipid: item.nativeMemoryStatistic.ipid || 0,
305            type: item.nativeMemoryStatistic.type || 0,
306          };
307        }
308      });
309    }
310    cacheNativeMemoryChartData(data.params.model, data.params.totalNS, data.params.processes, res);
311    res.length = 0;
312    (self as unknown as Worker).postMessage(
313      {
314        id: data.id,
315        action: data.action,
316        results: 'ok',
317        len: 0,
318      },
319      []
320    );
321  } else {
322    arrayBufferCallback(data, true);
323  }
324}
325
326function arrayBufferCallback(data: any, transfer: boolean): void {
327  let cacheKey = `${data.params.ipid}-${data.params.eventType}`;
328  let dataFilter = filterNativeMemoryChartData(
329    data.params.model,
330    data.params.startNS,
331    data.params.endNS,
332    data.params.totalNS,
333    data.params.drawType,
334    data.params.frame,
335    cacheKey
336  );
337  let len = dataFilter.startTime.length;
338  let startTime = new Float64Array(len);
339  let dur = new Float64Array(len);
340  let density = new Int32Array(len);
341  let heapSize = new Int32Array(len);
342  for (let i = 0; i < len; i++) {
343    startTime[i] = dataFilter.startTime[i];
344    dur[i] = dataFilter.dur[i];
345    heapSize[i] = dataFilter.heapSize[i];
346    density[i] = dataFilter.density[i];
347  }
348  let cacheSource = data.params.model === 'native_hook' ? dataCache.normalCache : dataCache.statisticsCache;
349  let cache = cacheSource.get(cacheKey);
350  (self as unknown as Worker).postMessage(
351    {
352      id: data.id,
353      action: data.action,
354      results: transfer
355        ? {
356            startTime: startTime.buffer,
357            dur: dur.buffer,
358            density: density.buffer,
359            heapSize: heapSize.buffer,
360            maxSize: cache!.maxSize,
361            minSize: cache!.minSize,
362            maxDensity: cache!.maxDensity,
363            minDensity: cache!.minDensity,
364          }
365        : {},
366      len: len,
367    },
368    transfer ? [startTime.buffer, dur.buffer, density.buffer, heapSize.buffer] : []
369  );
370}
371
372export function filterNativeMemoryChartData(
373  model: string,
374  startNS: number,
375  endNS: number,
376  totalNS: number,
377  drawType: number,
378  frame: any,
379  key: string
380): NativeMemoryDataSource {
381  let dataSource = new NativeMemoryDataSource();
382  let cache = model === 'native_hook' ? dataCache.normalCache.get(key) : dataCache.statisticsCache.get(key);
383  if (cache !== undefined) {
384    let data: any = {};
385    cache!.dataList.reduce((pre, current, index) => {
386      if (current.dur > 0 && current.startTime + current.dur >= startNS && current.startTime <= endNS) {
387        if (dur2Width(current.startTime, current.dur, startNS, endNS || totalNS, frame) >= 1) {
388          //计算绘制宽度 大于 1px,则加入绘制列表
389          dataSource.startTime.push(current.startTime);
390          dataSource.dur.push(current.dur);
391          dataSource.density.push(current.density);
392          dataSource.heapSize.push(current.heapSize);
393        } else {
394          let x = 0;
395          if (current.startTime > startNS && current.startTime < endNS) {
396            x = Math.trunc(ns2x(current.startTime, startNS, endNS, totalNS, frame));
397          } else {
398            x = 0;
399          }
400          let key = `${x}`;
401          let preIndex = pre[key];
402          if (preIndex !== undefined) {
403            if (drawType === 0) {
404              pre[key] = cache!.dataList[preIndex].heapSize > cache!.dataList[index].heapSize ? preIndex : index;
405            } else {
406              pre[key] = cache!.dataList[preIndex].density > cache!.dataList[index].density ? preIndex : index;
407            }
408          } else {
409            pre[key] = index;
410          }
411        }
412      }
413      return pre;
414    }, data);
415    setDataSource(data, dataSource, cache);
416  }
417  return dataSource;
418}
419
420function setDataSource(data: any, dataSource: NativeMemoryDataSource, cache: any) {
421  Reflect.ownKeys(data).map((kv: string | symbol): void => {
422    let index = data[kv as string] as number;
423    dataSource.startTime.push(cache!.dataList[index].startTime);
424    dataSource.dur.push(cache!.dataList[index].dur);
425    dataSource.density.push(cache!.dataList[index].density);
426    dataSource.heapSize.push(cache!.dataList[index].heapSize);
427  });
428}
429
430function ns2x(ns: number, startNS: number, endNS: number, duration: number, rect: any): number {
431  if (endNS === 0) {
432    endNS = duration;
433  }
434  let xSizeNM: number = ((ns - startNS) * rect.width) / (endNS - startNS);
435  if (xSizeNM < 0) {
436    xSizeNM = 0;
437  } else if (xSizeNM > rect.width) {
438    xSizeNM = rect.width;
439  }
440  return xSizeNM;
441}
442
443function dur2Width(startTime: number, dur: number, startNS: number, endNS: number, rect: any): number {
444  let realDur = startTime + dur - Math.max(startTime, startNS);
445  return Math.trunc((realDur * rect.width) / (endNS - startNS));
446}
447
448class NativeMemoryDataSource {
449  startTime: Array<number>;
450  dur: Array<number>;
451  heapSize: Array<number>;
452  density: Array<number>;
453  constructor() {
454    this.startTime = [];
455    this.dur = [];
456    this.heapSize = [];
457    this.density = [];
458  }
459}
460