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