Skip to content

Commit bc18bda

Browse files
stepanchegfacebook-github-bot
authored andcommitted
Move AggregateHeapProfileInfo into a separate file
Summary: Code cleanup. Reviewed By: bobyangyf Differential Revision: D38506985 fbshipit-source-id: 2510ce0ad11a2dd8174f3d71cafaadeea942d8f8
1 parent 0131ba1 commit bc18bda

File tree

5 files changed

+359
-342
lines changed

5 files changed

+359
-342
lines changed

starlark/src/environment/modules.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ use crate::values::docs::DocItem;
4747
use crate::values::docs::DocString;
4848
use crate::values::docs::DocStringKind;
4949
use crate::values::layout::heap::heap_type::HeapKind;
50-
use crate::values::layout::heap::profile::AggregateHeapProfileInfo;
50+
use crate::values::layout::heap::profile::aggregated::AggregateHeapProfileInfo;
5151
use crate::values::Freezer;
5252
use crate::values::FrozenHeap;
5353
use crate::values::FrozenHeapRef;

starlark/src/eval/runtime/profile/heap.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use std::fmt::Debug;
1919

2020
use gazebo::dupe::Dupe;
2121

22-
use crate::values::layout::heap::profile::AggregateHeapProfileInfo;
22+
use crate::values::layout::heap::profile::aggregated::AggregateHeapProfileInfo;
2323
use crate::values::Heap;
2424
use crate::values::Value;
2525

Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
/*
2+
* Copyright 2019 The Starlark in Rust Authors.
3+
* Copyright (c) Facebook, Inc. and its affiliates.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
use std::cell::RefCell;
19+
use std::collections::hash_map;
20+
use std::collections::HashMap;
21+
use std::fmt;
22+
use std::fmt::Debug;
23+
use std::fmt::Formatter;
24+
use std::rc::Rc;
25+
use std::time::Instant;
26+
27+
use either::Either;
28+
use gazebo::dupe::Dupe;
29+
use starlark_map::small_map::SmallMap;
30+
31+
use crate::eval::runtime::profile::flamegraph::FlameGraphWriter;
32+
use crate::eval::runtime::small_duration::SmallDuration;
33+
use crate::values::layout::heap::arena::ArenaVisitor;
34+
use crate::values::layout::heap::heap_type::HeapKind;
35+
use crate::values::layout::heap::profile::alloc_counts::AllocCounts;
36+
use crate::values::layout::heap::profile::by_type::HeapSummary;
37+
use crate::values::layout::heap::profile::string_index::StringId;
38+
use crate::values::layout::heap::profile::string_index::StringIndex;
39+
use crate::values::layout::heap::profile::summary::HeapSummaryByFunction;
40+
use crate::values::layout::heap::repr::AValueOrForward;
41+
use crate::values::layout::pointer::RawPointer;
42+
use crate::values::Heap;
43+
use crate::values::Value;
44+
45+
/// A mapping from function Value to FunctionId, which must be continuous
46+
#[derive(Default)]
47+
struct FunctionIds {
48+
values: HashMap<RawPointer, StringId>,
49+
strings: StringIndex,
50+
}
51+
52+
impl FunctionIds {
53+
fn get_value(&mut self, x: Value) -> StringId {
54+
match self.values.entry(x.ptr_value()) {
55+
hash_map::Entry::Occupied(v) => *v.get(),
56+
hash_map::Entry::Vacant(outer) => {
57+
let function_id = self.strings.index(&x.to_str());
58+
outer.insert(function_id);
59+
function_id
60+
}
61+
}
62+
}
63+
}
64+
65+
/// A stack frame, its caller and the functions it called, and the allocations it made itself.
66+
struct StackFrameData {
67+
callees: SmallMap<StringId, StackFrameBuilder>,
68+
allocs: HeapSummary,
69+
/// Time spent in this frame excluding callees.
70+
/// Double, because enter/exit are recorded twice, in drop and non-drop heaps.
71+
time_x2: SmallDuration,
72+
/// How many times this function was called (with this stack).
73+
/// Double.
74+
calls_x2: u32,
75+
}
76+
77+
#[derive(Clone, Dupe)]
78+
struct StackFrameBuilder(Rc<RefCell<StackFrameData>>);
79+
80+
impl StackFrameBuilder {
81+
fn new() -> Self {
82+
Self(Rc::new(RefCell::new(StackFrameData {
83+
callees: Default::default(),
84+
allocs: Default::default(),
85+
time_x2: SmallDuration::default(),
86+
calls_x2: 0,
87+
})))
88+
}
89+
90+
/// Enter a new stack frame.
91+
fn push(&self, function: StringId) -> Self {
92+
let mut this = self.0.borrow_mut();
93+
94+
let callee = this
95+
.callees
96+
.entry(function)
97+
.or_insert_with(StackFrameBuilder::new);
98+
99+
callee.dupe()
100+
}
101+
102+
fn build(&self) -> StackFrame {
103+
StackFrame {
104+
callees: self
105+
.0
106+
.borrow()
107+
.callees
108+
.iter()
109+
.map(|(f, s)| (*f, s.build()))
110+
.collect(),
111+
allocs: self.0.borrow().allocs.clone(),
112+
time_x2: self.0.borrow().time_x2,
113+
calls_x2: self.0.borrow().calls_x2,
114+
}
115+
}
116+
}
117+
118+
/// An accumulator for stack frames that lets us visit the heap.
119+
pub(crate) struct StackCollector {
120+
/// Timestamp of last call enter or exit.
121+
last_time: Option<Instant>,
122+
ids: FunctionIds,
123+
current: Vec<StackFrameBuilder>,
124+
/// What we are collecting.
125+
/// When unset, we are collecting allocated memory (not retained).
126+
/// When set, must be set to correct heap type (unfrozen or frozen), we are traversing.
127+
retained: Option<HeapKind>,
128+
}
129+
130+
impl StackCollector {
131+
pub(crate) fn new(retained: Option<HeapKind>) -> Self {
132+
Self {
133+
ids: FunctionIds::default(),
134+
current: vec![StackFrameBuilder::new()],
135+
last_time: None,
136+
retained,
137+
}
138+
}
139+
}
140+
141+
impl<'v> ArenaVisitor<'v> for StackCollector {
142+
fn regular_value(&mut self, value: &'v AValueOrForward) {
143+
let value = match (value.unpack(), self.retained) {
144+
(Either::Left(header), None) => unsafe { header.unpack_value(HeapKind::Unfrozen) },
145+
(Either::Right(forward), Some(retained)) => unsafe {
146+
forward.forward_ptr().unpack_value(retained)
147+
},
148+
_ => return,
149+
};
150+
151+
let frame = match self.current.last() {
152+
Some(frame) => frame,
153+
None => return,
154+
};
155+
156+
// Value allocated in this frame, record it!
157+
let typ = value.get_ref().get_type();
158+
let mut frame = frame.0.borrow_mut();
159+
frame.allocs.add(
160+
typ,
161+
AllocCounts {
162+
count: 1,
163+
bytes: value.get_ref().total_memory(),
164+
},
165+
);
166+
}
167+
168+
fn call_enter(&mut self, function: Value<'v>, time: Instant) {
169+
if let Some(last_time) = self.last_time {
170+
self.current.last_mut().unwrap().0.borrow_mut().time_x2 +=
171+
time.saturating_duration_since(last_time);
172+
self.current.last_mut().unwrap().0.borrow_mut().calls_x2 += 1;
173+
}
174+
175+
let frame = match self.current.last() {
176+
Some(frame) => frame,
177+
None => return,
178+
};
179+
180+
// New frame, enter it.
181+
let id = self.ids.get_value(function);
182+
let new_frame = frame.push(id);
183+
self.current.push(new_frame);
184+
185+
self.last_time = Some(time)
186+
}
187+
188+
fn call_exit(&mut self, time: Instant) {
189+
if let Some(last_time) = self.last_time {
190+
self.current.last_mut().unwrap().0.borrow_mut().time_x2 +=
191+
time.saturating_duration_since(last_time);
192+
}
193+
self.current.pop().unwrap();
194+
self.last_time = Some(time);
195+
}
196+
}
197+
198+
/// Aggregated stack frame data.
199+
pub(crate) struct StackFrame {
200+
/// Aggregated callees.
201+
pub(crate) callees: SmallMap<StringId, StackFrame>,
202+
/// Aggregated allocations in this frame, without callees.
203+
pub(crate) allocs: HeapSummary,
204+
/// Time spend in this frame excluding callees.
205+
/// `x2` because enter/exit are recorded twice, in drop and non-drop heaps.
206+
pub(crate) time_x2: SmallDuration,
207+
/// How many times this frame was called with the same callers.
208+
/// `x2` because enter/exit are recorded twice, in drop and non-drop heaps.
209+
pub(crate) calls_x2: u32,
210+
}
211+
212+
impl StackFrame {
213+
/// Write this stack frame's data to a file in flamegraph.pl format.
214+
fn write_flame_graph<'a>(
215+
&self,
216+
file: &mut FlameGraphWriter,
217+
stack: &'_ mut Vec<&'a str>,
218+
ids: &'a StringIndex,
219+
) {
220+
for (k, v) in &self.allocs.summary {
221+
file.write(
222+
stack.iter().copied().chain(std::iter::once(*k)),
223+
v.bytes as u64,
224+
);
225+
}
226+
227+
for (id, frame) in &self.callees {
228+
stack.push(ids.get(*id));
229+
frame.write_flame_graph(file, stack, ids);
230+
stack.pop();
231+
}
232+
}
233+
}
234+
235+
pub(crate) struct AggregateHeapProfileInfo {
236+
pub(crate) strings: StringIndex,
237+
pub(crate) root: StackFrame,
238+
/// String `"TOTALS"`. It is needed in heap summary output.
239+
pub(crate) totals_id: StringId,
240+
/// String `"(root)"`. It is needed in heap summary output.
241+
pub(crate) root_id: StringId,
242+
/// String `""`. It is needed in heap summary output.
243+
pub(crate) blank_id: StringId,
244+
}
245+
246+
impl Debug for AggregateHeapProfileInfo {
247+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
248+
f.debug_struct("AggregateHeapProfileInfo")
249+
.finish_non_exhaustive()
250+
}
251+
}
252+
253+
impl AggregateHeapProfileInfo {
254+
pub(crate) fn collect(heap: &Heap, retained: Option<HeapKind>) -> AggregateHeapProfileInfo {
255+
let mut collector = StackCollector::new(retained);
256+
unsafe {
257+
heap.visit_arena(HeapKind::Unfrozen, &mut collector);
258+
}
259+
assert_eq!(1, collector.current.len());
260+
let totals_id = collector.ids.strings.index("TOTALS");
261+
let root_id = collector.ids.strings.index("(root)");
262+
let blank_id = collector.ids.strings.index("");
263+
AggregateHeapProfileInfo {
264+
strings: collector.ids.strings,
265+
root: collector.current.pop().unwrap().build(),
266+
totals_id,
267+
root_id,
268+
blank_id,
269+
}
270+
}
271+
272+
/// Write this out recursively to a file.
273+
pub(crate) fn gen_flame_graph(&self) -> String {
274+
let mut writer = FlameGraphWriter::new();
275+
self.root
276+
.write_flame_graph(&mut writer, &mut vec![], &self.strings);
277+
writer.finish()
278+
}
279+
280+
pub(crate) fn gen_summary_csv(&self) -> String {
281+
HeapSummaryByFunction::init(self).gen_csv(self)
282+
}
283+
}
284+
285+
#[cfg(test)]
286+
mod tests {
287+
use gazebo::dupe::Dupe;
288+
289+
use crate::const_frozen_string;
290+
use crate::values::layout::heap::heap_type::HeapKind;
291+
use crate::values::layout::heap::profile::aggregated::AggregateHeapProfileInfo;
292+
use crate::values::layout::heap::profile::aggregated::StackFrame;
293+
use crate::values::Freezer;
294+
use crate::values::FrozenHeap;
295+
use crate::values::Heap;
296+
297+
fn total_alloc_count(frame: &StackFrame) -> usize {
298+
frame.allocs.total().count
299+
+ frame
300+
.callees
301+
.values()
302+
.map(|c| total_alloc_count(c.dupe()))
303+
.sum::<usize>()
304+
}
305+
306+
#[test]
307+
fn test_stacks_collect() {
308+
let heap = Heap::new();
309+
heap.record_call_enter(const_frozen_string!("enter").to_value());
310+
heap.alloc_str("xxyy");
311+
heap.alloc_str("zzww");
312+
heap.record_call_exit();
313+
314+
let stacks = AggregateHeapProfileInfo::collect(&heap, None);
315+
assert!(stacks.root.allocs.summary.is_empty());
316+
assert_eq!(1, stacks.root.callees.len());
317+
assert_eq!(2, total_alloc_count(&stacks.root));
318+
}
319+
320+
#[test]
321+
fn test_stacks_collect_retained() {
322+
let heap = Heap::new();
323+
heap.record_call_enter(const_frozen_string!("enter").to_value());
324+
let s0 = heap.alloc_str("xxyy");
325+
let s1 = heap.alloc_str("zzww");
326+
heap.alloc_str("rrtt");
327+
heap.record_call_exit();
328+
329+
let freezer = Freezer::new(FrozenHeap::new());
330+
freezer.freeze(s0.to_value()).unwrap();
331+
freezer.freeze(s1.to_value()).unwrap();
332+
333+
let stacks = AggregateHeapProfileInfo::collect(&heap, Some(HeapKind::Frozen));
334+
assert!(stacks.root.allocs.summary.is_empty());
335+
assert_eq!(1, stacks.root.callees.len());
336+
// 3 allocated, 2 retained.
337+
assert_eq!(
338+
2,
339+
stacks
340+
.root
341+
.callees
342+
.values()
343+
.next()
344+
.unwrap()
345+
.allocs
346+
.summary
347+
.get("string")
348+
.unwrap()
349+
.count
350+
);
351+
assert_eq!(2, total_alloc_count(&stacks.root));
352+
}
353+
}

0 commit comments

Comments
 (0)