• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {featureFlags} from '../core/feature_flags';
16import {ProfileType} from './state';
17
18export enum FlamegraphViewingOption {
19  SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY = 'SPACE',
20  ALLOC_SPACE_MEMORY_ALLOCATED_KEY = 'ALLOC_SPACE',
21  OBJECTS_ALLOCATED_NOT_FREED_KEY = 'OBJECTS',
22  OBJECTS_ALLOCATED_KEY = 'ALLOC_OBJECTS',
23  PERF_SAMPLES_KEY = 'PERF_SAMPLES',
24  DOMINATOR_TREE_OBJ_SIZE_KEY = 'DOMINATED_OBJ_SIZE',
25  DOMINATOR_TREE_OBJ_COUNT_KEY = 'DOMINATED_OBJ_COUNT',
26}
27
28interface ViewingOption {
29  option: FlamegraphViewingOption;
30  name: string;
31}
32
33export interface CallsiteInfo {
34  id: number;
35  parentId: number;
36  depth: number;
37  name?: string;
38  totalSize: number;
39  selfSize: number;
40  mapping: string;
41  merged: boolean;
42  highlighted: boolean;
43  location?: string;
44}
45
46const SHOW_HEAP_GRAPH_DOMINATOR_TREE_FLAG = featureFlags.register({
47  id: 'showHeapGraphDominatorTree',
48  name: 'Show heap graph dominator tree',
49  description: 'Show dominated size and objects tabs in Java heap graph view.',
50  defaultValue: true,
51});
52
53export function viewingOptions(profileType: ProfileType): Array<ViewingOption> {
54  switch (profileType) {
55    case ProfileType.PERF_SAMPLE:
56      return [
57        {
58          option: FlamegraphViewingOption.PERF_SAMPLES_KEY,
59          name: 'Samples',
60        },
61      ];
62    case ProfileType.JAVA_HEAP_GRAPH:
63      return [
64        {
65          option: FlamegraphViewingOption.SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY,
66          name: 'Size',
67        },
68        {
69          option: FlamegraphViewingOption.OBJECTS_ALLOCATED_NOT_FREED_KEY,
70          name: 'Objects',
71        },
72      ].concat(
73        SHOW_HEAP_GRAPH_DOMINATOR_TREE_FLAG.get()
74          ? [
75              {
76                option: FlamegraphViewingOption.DOMINATOR_TREE_OBJ_SIZE_KEY,
77                name: 'Dominated size',
78              },
79              {
80                option: FlamegraphViewingOption.DOMINATOR_TREE_OBJ_COUNT_KEY,
81                name: 'Dominated objects',
82              },
83            ]
84          : [],
85      );
86    case ProfileType.HEAP_PROFILE:
87      return [
88        {
89          option: FlamegraphViewingOption.SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY,
90          name: 'Unreleased size',
91        },
92        {
93          option: FlamegraphViewingOption.OBJECTS_ALLOCATED_NOT_FREED_KEY,
94          name: 'Unreleased count',
95        },
96        {
97          option: FlamegraphViewingOption.ALLOC_SPACE_MEMORY_ALLOCATED_KEY,
98          name: 'Total size',
99        },
100        {
101          option: FlamegraphViewingOption.OBJECTS_ALLOCATED_KEY,
102          name: 'Total count',
103        },
104      ];
105    case ProfileType.NATIVE_HEAP_PROFILE:
106      return [
107        {
108          option: FlamegraphViewingOption.SPACE_MEMORY_ALLOCATED_NOT_FREED_KEY,
109          name: 'Unreleased malloc size',
110        },
111        {
112          option: FlamegraphViewingOption.OBJECTS_ALLOCATED_NOT_FREED_KEY,
113          name: 'Unreleased malloc count',
114        },
115        {
116          option: FlamegraphViewingOption.ALLOC_SPACE_MEMORY_ALLOCATED_KEY,
117          name: 'Total malloc size',
118        },
119        {
120          option: FlamegraphViewingOption.OBJECTS_ALLOCATED_KEY,
121          name: 'Total malloc count',
122        },
123      ];
124    case ProfileType.JAVA_HEAP_SAMPLES:
125      return [
126        {
127          option: FlamegraphViewingOption.ALLOC_SPACE_MEMORY_ALLOCATED_KEY,
128          name: 'Total allocation size',
129        },
130        {
131          option: FlamegraphViewingOption.OBJECTS_ALLOCATED_KEY,
132          name: 'Total allocation count',
133        },
134      ];
135    case ProfileType.MIXED_HEAP_PROFILE:
136      return [
137        {
138          option: FlamegraphViewingOption.ALLOC_SPACE_MEMORY_ALLOCATED_KEY,
139          name: 'Total allocation size (malloc + java)',
140        },
141        {
142          option: FlamegraphViewingOption.OBJECTS_ALLOCATED_KEY,
143          name: 'Total allocation count (malloc + java)',
144        },
145      ];
146    default:
147      const exhaustiveCheck: never = profileType;
148      throw new Error(`Unhandled case: ${exhaustiveCheck}`);
149  }
150}
151
152export function defaultViewingOption(
153  profileType: ProfileType,
154): FlamegraphViewingOption {
155  return viewingOptions(profileType)[0].option;
156}
157
158export function expandCallsites(
159  data: ReadonlyArray<CallsiteInfo>,
160  clickedCallsiteIndex: number,
161): ReadonlyArray<CallsiteInfo> {
162  if (clickedCallsiteIndex === -1) return data;
163  const expandedCallsites: CallsiteInfo[] = [];
164  if (clickedCallsiteIndex >= data.length || clickedCallsiteIndex < -1) {
165    return expandedCallsites;
166  }
167  const clickedCallsite = data[clickedCallsiteIndex];
168  expandedCallsites.unshift(clickedCallsite);
169  // Adding parents
170  let parentId = clickedCallsite.parentId;
171  while (parentId > -1) {
172    expandedCallsites.unshift(data[parentId]);
173    parentId = data[parentId].parentId;
174  }
175  // Adding children
176  const parents: number[] = [];
177  parents.push(clickedCallsiteIndex);
178  for (let i = clickedCallsiteIndex + 1; i < data.length; i++) {
179    const element = data[i];
180    if (parents.includes(element.parentId)) {
181      expandedCallsites.push(element);
182      parents.push(element.id);
183    }
184  }
185  return expandedCallsites;
186}
187
188// Merge callsites that have approximately width less than
189// MIN_PIXEL_DISPLAYED. All small callsites in the same depth and with same
190// parent will be merged to one with total size of all merged callsites.
191export function mergeCallsites(
192  data: ReadonlyArray<CallsiteInfo>,
193  minSizeDisplayed: number,
194) {
195  const mergedData: CallsiteInfo[] = [];
196  const mergedCallsites: Map<number, number> = new Map();
197  for (let i = 0; i < data.length; i++) {
198    // When a small callsite is found, it will be merged with other small
199    // callsites of the same depth. So if the current callsite has already been
200    // merged we can skip it.
201    if (mergedCallsites.has(data[i].id)) {
202      continue;
203    }
204    const copiedCallsite = copyCallsite(data[i]);
205    copiedCallsite.parentId = getCallsitesParentHash(
206      copiedCallsite,
207      mergedCallsites,
208    );
209
210    let mergedAny = false;
211    // If current callsite is small, find other small callsites with same depth
212    // and parent and merge them into the current one, marking them as merged.
213    if (copiedCallsite.totalSize <= minSizeDisplayed && i + 1 < data.length) {
214      let j = i + 1;
215      let nextCallsite = data[j];
216      while (j < data.length && copiedCallsite.depth === nextCallsite.depth) {
217        if (
218          copiedCallsite.parentId ===
219            getCallsitesParentHash(nextCallsite, mergedCallsites) &&
220          nextCallsite.totalSize <= minSizeDisplayed
221        ) {
222          copiedCallsite.totalSize += nextCallsite.totalSize;
223          mergedCallsites.set(nextCallsite.id, copiedCallsite.id);
224          mergedAny = true;
225        }
226        j++;
227        nextCallsite = data[j];
228      }
229      if (mergedAny) {
230        copiedCallsite.name = '[merged]';
231        copiedCallsite.merged = true;
232      }
233    }
234    mergedData.push(copiedCallsite);
235  }
236  return mergedData;
237}
238
239function copyCallsite(callsite: CallsiteInfo): CallsiteInfo {
240  return {
241    id: callsite.id,
242    parentId: callsite.parentId,
243    depth: callsite.depth,
244    name: callsite.name,
245    totalSize: callsite.totalSize,
246    mapping: callsite.mapping,
247    selfSize: callsite.selfSize,
248    merged: callsite.merged,
249    highlighted: callsite.highlighted,
250    location: callsite.location,
251  };
252}
253
254function getCallsitesParentHash(
255  callsite: CallsiteInfo,
256  map: Map<number, number>,
257): number {
258  return map.has(callsite.parentId)
259    ? +map.get(callsite.parentId)!
260    : callsite.parentId;
261}
262export function findRootSize(data: ReadonlyArray<CallsiteInfo>) {
263  let totalSize = 0;
264  let i = 0;
265  while (i < data.length && data[i].depth === 0) {
266    totalSize += data[i].totalSize;
267    i++;
268  }
269  return totalSize;
270}
271