1--
2-- Copyright 2019 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--     https://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
17SELECT RUN_METRIC('android/process_metadata.sql');
18SELECT RUN_METRIC('android/process_mem.sql');
19
20CREATE VIEW memory_delta AS
21SELECT upid, SUM(size) AS delta
22FROM heap_profile_allocation
23GROUP BY 1;
24
25CREATE VIEW memory_total AS
26SELECT upid, SUM(size) AS total
27FROM heap_profile_allocation
28WHERE size > 0
29GROUP BY 1;
30
31-- Join frames with symbols and mappings to get a textual representation.
32CREATE TABLE symbolized_frame AS
33SELECT
34  frame_id,
35  symbol_name,
36  mapping_name,
37  HASH(symbol_name, mapping_name) frame_hash,
38  HeapProfileCallsites_Frame(
39    'name', symbol_name,
40    'mapping_name', mapping_name
41  ) AS frame_proto
42FROM (
43  SELECT
44    spf.id AS frame_id,
45    COALESCE(
46      (SELECT name FROM stack_profile_symbol symbol
47        WHERE symbol.symbol_set_id = spf.symbol_set_id
48        LIMIT 1),
49      spf.deobfuscated_name,
50      spf.name
51    ) AS symbol_name,
52    spm.name AS mapping_name
53  FROM stack_profile_frame spf
54  JOIN stack_profile_mapping spm
55  ON spf.mapping = spm.id
56);
57
58-- Required to join with callsites
59CREATE UNIQUE INDEX symbolized_frame_idx ON symbolized_frame(frame_id);
60
61-- View that joins callsites with frames. Allocation-agnostic, only used to
62-- generated the hashed callsites.
63CREATE TABLE callsites AS
64SELECT cs.id, cs.parent_id, cs.depth, sf.frame_hash
65FROM stack_profile_callsite cs
66JOIN symbolized_frame sf USING(frame_id);
67
68DROP INDEX symbolized_frame_idx;
69
70-- heapprofd-based callsite ids are based on frame addresses, whereas we want
71-- to group by symbol names.
72-- Create a unique ID for each subtree by traversing from the root.
73-- 1 self_hash can correspond to N callsite_ids (which can then be used to join
74-- with allocs).
75CREATE TABLE hashed_callsites AS
76WITH RECURSIVE callsite_hasher(id, self_hash, parent_hash, frame_hash) AS (
77  SELECT
78    cs.id,
79    cs.frame_hash,
80    -1,
81    cs.frame_hash
82  FROM callsites cs
83  WHERE cs.depth = 0
84  UNION ALL
85  SELECT
86    child.id,
87    HASH(child.frame_hash, parent.self_hash),
88    parent.self_hash,
89    child.frame_hash
90  FROM callsite_hasher parent
91  JOIN callsites child
92  ON parent.id = child.parent_id
93)
94SELECT
95  self_hash,
96  parent_hash,
97  frame_hash,
98  id callsite_id
99FROM callsite_hasher;
100
101DROP TABLE callsites;
102
103CREATE VIEW hashed_callsite_tree AS
104SELECT DISTINCT self_hash, parent_hash, frame_hash
105FROM hashed_callsites;
106
107-- Required to join with allocs
108CREATE INDEX hashed_callsites_id_idx ON hashed_callsites(callsite_id);
109
110-- Computes the allocations for each hash-based callsite.
111CREATE TABLE self_allocs AS
112SELECT
113  hc.self_hash,
114  alloc.upid,
115  SUM(alloc.count) AS delta_count,
116  SUM(CASE WHEN alloc.count > 0 THEN alloc.count ELSE 0 END) AS total_count,
117  SUM(alloc.size) AS delta_bytes,
118  SUM(CASE WHEN alloc.size > 0 THEN alloc.size ELSE 0 END) AS total_bytes
119FROM hashed_callsites hc
120JOIN heap_profile_allocation alloc USING (callsite_id)
121GROUP BY 1, 2;
122
123DROP INDEX hashed_callsites_id_idx;
124
125-- For each allocation (each self_alloc), emit a row for each ancestor and
126-- aggregate them by self_hash.
127CREATE TABLE child_allocs AS
128WITH RECURSIVE parent_traversal(
129  self_hash, parent_hash, upid,
130  delta_count, total_count, delta_bytes, total_bytes) AS (
131  SELECT
132    sa.self_hash,
133    hc.parent_hash,
134    sa.upid,
135    sa.delta_count,
136    sa.total_count,
137    sa.delta_bytes,
138    sa.total_bytes
139  FROM self_allocs sa
140  JOIN hashed_callsite_tree hc ON sa.self_hash = hc.self_hash
141  UNION ALL
142  SELECT
143    parent.self_hash,
144    parent.parent_hash,
145    child.upid,
146    child.delta_count,
147    child.total_count,
148    child.delta_bytes,
149    child.total_bytes
150  FROM parent_traversal child
151  JOIN hashed_callsite_tree parent
152  ON child.parent_hash = parent.self_hash
153)
154SELECT
155  self_hash,
156  upid,
157  SUM(delta_count) AS delta_count,
158  SUM(total_count) AS total_count,
159  SUM(delta_bytes) AS delta_bytes,
160  SUM(total_bytes) AS total_bytes
161FROM parent_traversal
162GROUP BY 1, 2;
163
164CREATE VIEW self_allocs_proto AS
165SELECT
166  self_hash,
167  upid,
168  HeapProfileCallsites_Counters(
169    'delta_count', delta_count, 'total_count', total_count,
170    'delta_bytes', delta_bytes, 'total_bytes', total_bytes
171  ) AS allocs_proto
172FROM self_allocs;
173
174CREATE VIEW child_allocs_proto AS
175SELECT
176  self_hash,
177  upid,
178  HeapProfileCallsites_Counters(
179    'delta_count', delta_count, 'total_count', total_count,
180    'delta_bytes', delta_bytes, 'total_bytes', total_bytes
181  ) AS allocs_proto
182FROM child_allocs;
183
184-- Required to map back to the symbol.
185CREATE INDEX symbolized_frame_hash_idx ON symbolized_frame(frame_hash);
186
187CREATE TABLE process_callsite AS
188SELECT
189  ca.upid,
190  ca.self_hash,
191  tree.parent_hash,
192  frame.frame_proto,
193  sa.allocs_proto AS self_allocs_proto,
194  ca.allocs_proto AS child_allocs_proto
195FROM hashed_callsite_tree tree
196JOIN (SELECT DISTINCT frame_hash, frame_proto FROM symbolized_frame) frame
197  USING (frame_hash)
198JOIN child_allocs_proto ca
199  USING (self_hash)
200LEFT JOIN self_allocs_proto sa
201  USING (self_hash, upid)
202ORDER BY 1, 2;
203
204DROP INDEX symbolized_frame_hash_idx;
205
206CREATE VIEW process_callsite_proto AS
207SELECT
208  upid,
209  RepeatedField(HeapProfileCallsites_Callsite(
210    'hash', self_hash,
211    'parent_hash', parent_hash,
212    'frame', frame_proto,
213    'self_allocs', self_allocs_proto,
214    'child_allocs', child_allocs_proto
215  )) AS repeated_callsite_proto
216FROM process_callsite
217GROUP BY 1;
218
219CREATE VIEW instance_stats_view AS
220SELECT HeapProfileCallsites_InstanceStats(
221    'pid', process.pid,
222    'process_name', process.name,
223    'process', process_metadata.metadata,
224    'callsites', repeated_callsite_proto,
225    'profile_delta_bytes', memory_delta.delta,
226    'profile_total_bytes', memory_total.total,
227    'max_anon_rss_and_swap_bytes', (
228      SELECT CAST(MAX(anon_and_swap_val) AS INT)
229      FROM anon_and_swap_span s WHERE s.upid = process.upid
230    )
231) AS instance_stats_proto
232FROM process_callsite_proto
233JOIN memory_total USING (upid)
234JOIN memory_delta USING (upid)
235JOIN process USING (upid)
236JOIN process_metadata USING (upid);
237
238CREATE VIEW heap_profile_callsites_output AS
239SELECT HeapProfileCallsites(
240  'instance_stats',
241  (SELECT RepeatedField(instance_stats_proto) FROM instance_stats_view)
242);
243