1// Copyright (C) 2019 The Android Open Source Project
2//
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
15import {CallsiteInfo} from '../common/state';
16
17export const SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY = 'SPACE';
18export const ALLOC_SPACE_MEMORY_ALLOCATED_KEY = 'ALLOC_SPACE';
19export const OBJECTS_ALLOCATED_NOT_FREED_KEY = 'OBJECTS';
20export const OBJECTS_ALLOCATED_KEY = 'ALLOC_OBJECTS';
21
22export const DEFAULT_VIEWING_OPTION = SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY;
23
24export function expandCallsites(
25    data: CallsiteInfo[], clickedCallsiteIndex: number): CallsiteInfo[] {
26  if (clickedCallsiteIndex === -1) return data;
27  const expandedCallsites: CallsiteInfo[] = [];
28  if (clickedCallsiteIndex >= data.length || clickedCallsiteIndex < -1) {
29    return expandedCallsites;
30  }
31  const clickedCallsite = data[clickedCallsiteIndex];
32  expandedCallsites.unshift(clickedCallsite);
33  // Adding parents
34  let parentId = clickedCallsite.parentId;
35  while (parentId > -1) {
36    expandedCallsites.unshift(data[parentId]);
37    parentId = data[parentId].parentId;
38  }
39  // Adding children
40  const parents: number[] = [];
41  parents.push(clickedCallsiteIndex);
42  for (let i = clickedCallsiteIndex + 1; i < data.length; i++) {
43    const element = data[i];
44    if (parents.includes(element.parentId)) {
45      expandedCallsites.push(element);
46      parents.push(element.id);
47    }
48  }
49  return expandedCallsites;
50}
51
52// Merge callsites that have approximately width less than
53// MIN_PIXEL_DISPLAYED. All small callsites in the same depth and with same
54// parent will be merged to one with total size of all merged callsites.
55export function mergeCallsites(data: CallsiteInfo[], minSizeDisplayed: number) {
56  const mergedData: CallsiteInfo[] = [];
57  const mergedCallsites: Map<number, number> = new Map();
58  for (let i = 0; i < data.length; i++) {
59    // When a small callsite is found, it will be merged with other small
60    // callsites of the same depth. So if the current callsite has already been
61    // merged we can skip it.
62    if (mergedCallsites.has(data[i].id)) {
63      continue;
64    }
65    const copiedCallsite = copyCallsite(data[i]);
66    copiedCallsite.parentId =
67        getCallsitesParentHash(copiedCallsite, mergedCallsites);
68
69    let mergedAny = false;
70    // If current callsite is small, find other small callsites with same depth
71    // and parent and merge them into the current one, marking them as merged.
72    if (copiedCallsite.totalSize <= minSizeDisplayed && i + 1 < data.length) {
73      let j = i + 1;
74      let nextCallsite = data[j];
75      while (j < data.length && copiedCallsite.depth === nextCallsite.depth) {
76        if (copiedCallsite.parentId ===
77                getCallsitesParentHash(nextCallsite, mergedCallsites) &&
78            nextCallsite.totalSize <= minSizeDisplayed) {
79          copiedCallsite.totalSize += nextCallsite.totalSize;
80          mergedCallsites.set(nextCallsite.id, copiedCallsite.id);
81          mergedAny = true;
82        }
83        j++;
84        nextCallsite = data[j];
85      }
86      if (mergedAny) {
87        copiedCallsite.name = '[merged]';
88        copiedCallsite.merged = true;
89      }
90    }
91    mergedData.push(copiedCallsite);
92  }
93  return mergedData;
94}
95
96function copyCallsite(callsite: CallsiteInfo): CallsiteInfo {
97  return {
98    id: callsite.id,
99    parentId: callsite.parentId,
100    depth: callsite.depth,
101    name: callsite.name,
102    totalSize: callsite.totalSize,
103    mapping: callsite.mapping,
104    selfSize: callsite.selfSize,
105    merged: callsite.merged,
106    highlighted: callsite.highlighted
107  };
108}
109
110function getCallsitesParentHash(
111    callsite: CallsiteInfo, map: Map<number, number>): number {
112  return map.has(callsite.parentId) ? +map.get(callsite.parentId)! :
113                                      callsite.parentId;
114}
115export function findRootSize(data: CallsiteInfo[]) {
116  let totalSize = 0;
117  let i = 0;
118  while (i < data.length && data[i].depth === 0) {
119    totalSize += data[i].totalSize;
120    i++;
121  }
122  return totalSize;
123}
124