1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.ahat;
18 
19 import com.android.ahat.heapdump.AhatHeap;
20 import com.android.ahat.heapdump.AhatSnapshot;
21 import com.android.ahat.heapdump.Diffable;
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 import java.util.List;
25 import java.util.Map;
26 
27 /**
28  * Class for rendering a table that includes sizes of some kind for each heap.
29  */
30 class HeapTable {
31   /**
32    * Configuration for a value column of a heap table.
33    */
34   public interface ValueConfig<T> {
getDescription()35     String getDescription();
render(T element)36     DocString render(T element);
37   }
38 
39   /**
40    * Configuration for the HeapTable.
41    */
42   public interface TableConfig<T> {
getHeapsDescription()43     String getHeapsDescription();
getSize(T element, AhatHeap heap)44     long getSize(T element, AhatHeap heap);
getValueConfigs()45     List<ValueConfig<T>> getValueConfigs();
46   }
47 
sizeString(long size, boolean isPlaceHolder)48   private static DocString sizeString(long size, boolean isPlaceHolder) {
49     DocString string = new DocString();
50     if (isPlaceHolder) {
51       string.append(DocString.removed("del"));
52     } else if (size != 0) {
53       string.appendFormat("%,14d", size);
54     }
55     return string;
56   }
57 
58   /**
59    * Render the table to the given document.
60    * @param query - The page query.
61    * @param id - A unique identifier for the table on the page.
62    */
render(Doc doc, Query query, String id, TableConfig<T> config, AhatSnapshot snapshot, List<T> elements)63   public static <T extends Diffable<T>> void render(Doc doc, Query query, String id,
64       TableConfig<T> config, AhatSnapshot snapshot, List<T> elements) {
65     // Only show the heaps that have non-zero entries.
66     List<AhatHeap> heaps = new ArrayList<AhatHeap>();
67     for (AhatHeap heap : snapshot.getHeaps()) {
68       if (hasNonZeroEntry(heap, config, elements)) {
69         heaps.add(heap);
70       }
71     }
72 
73     List<ValueConfig<T>> values = config.getValueConfigs();
74 
75     // Print the heap and values descriptions.
76     List<Column> subcols = new ArrayList<Column>();
77     for (AhatHeap heap : heaps) {
78       subcols.add(new Column(heap.getName(), Column.Align.RIGHT));
79       subcols.add(new Column("Δ", Column.Align.RIGHT, snapshot.isDiffed()));
80     }
81     boolean showTotal = heaps.size() > 1;
82     subcols.add(new Column("Total", Column.Align.RIGHT, showTotal));
83     subcols.add(new Column("Δ", Column.Align.RIGHT, showTotal && snapshot.isDiffed()));
84     List<Column> cols = new ArrayList<Column>();
85     for (ValueConfig value : values) {
86       cols.add(new Column(value.getDescription()));
87     }
88     doc.table(DocString.text(config.getHeapsDescription()), subcols, cols);
89 
90     // Print the entries up to the selected limit.
91     SubsetSelector<T> selector = new SubsetSelector(query, id, elements);
92     ArrayList<DocString> vals = new ArrayList<DocString>();
93     for (T elem : selector.selected()) {
94       T base = elem.getBaseline();
95       vals.clear();
96       long total = 0;
97       long basetotal = 0;
98       for (AhatHeap heap : heaps) {
99         long size = config.getSize(elem, heap);
100         long basesize = config.getSize(base, heap.getBaseline());
101         total += size;
102         basetotal += basesize;
103         vals.add(sizeString(size, elem.isPlaceHolder()));
104         vals.add(DocString.delta(elem.isPlaceHolder(), base.isPlaceHolder(), size, basesize));
105       }
106       vals.add(sizeString(total, elem.isPlaceHolder()));
107       vals.add(DocString.delta(elem.isPlaceHolder(), base.isPlaceHolder(), total, basetotal));
108 
109       for (ValueConfig<T> value : values) {
110         vals.add(value.render(elem));
111       }
112       doc.row(vals.toArray(new DocString[0]));
113     }
114 
115     // Print a summary of the remaining entries if there are any.
116     List<T> remaining = selector.remaining();
117     if (!remaining.isEmpty()) {
118       Map<AhatHeap, Long> summary = new HashMap<AhatHeap, Long>();
119       Map<AhatHeap, Long> basesummary = new HashMap<AhatHeap, Long>();
120       for (AhatHeap heap : heaps) {
121         summary.put(heap, 0L);
122         basesummary.put(heap, 0L);
123       }
124 
125       for (T elem : remaining) {
126         for (AhatHeap heap : heaps) {
127           long size = config.getSize(elem, heap);
128           summary.put(heap, summary.get(heap) + size);
129 
130           long basesize = config.getSize(elem.getBaseline(), heap.getBaseline());
131           basesummary.put(heap, basesummary.get(heap) + basesize);
132         }
133       }
134 
135       vals.clear();
136       long total = 0;
137       long basetotal = 0;
138       for (AhatHeap heap : heaps) {
139         long size = summary.get(heap);
140         long basesize = basesummary.get(heap);
141         total += size;
142         basetotal += basesize;
143         vals.add(sizeString(size, false));
144         vals.add(DocString.delta(false, false, size, basesize));
145       }
146       vals.add(sizeString(total, false));
147       vals.add(DocString.delta(false, false, total, basetotal));
148 
149       for (ValueConfig<T> value : values) {
150         vals.add(DocString.text("..."));
151       }
152       doc.row(vals.toArray(new DocString[0]));
153     }
154     doc.end();
155     selector.render(doc);
156   }
157 
158   // Returns true if the given heap has a non-zero size entry.
hasNonZeroEntry(AhatHeap heap, TableConfig<T> config, List<T> elements)159   public static <T extends Diffable<T>> boolean hasNonZeroEntry(AhatHeap heap,
160       TableConfig<T> config, List<T> elements) {
161     AhatHeap baseheap = heap.getBaseline();
162     if (heap.getSize() > 0 || baseheap.getSize() > 0) {
163       for (T element : elements) {
164         if (config.getSize(element, heap) > 0 ||
165             config.getSize(element.getBaseline(), baseheap) > 0) {
166           return true;
167         }
168       }
169     }
170     return false;
171   }
172 }
173 
174