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