Skip to content

Commit 0083b1c

Browse files
committed
Heap histogram - WIP
Also: * Avoid unhelpful compiler warnings * Capture common code in a macro Note: it would have been nice to alias the closure type, but rust-lang/rfcs#1733 is not yet implemented and macros can't cope (rust-lang/rust#24010).
1 parent dbda939 commit 0083b1c

File tree

6 files changed

+268
-87
lines changed

6 files changed

+268
-87
lines changed

src/agentcontroller/controller.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616

1717
pub struct AgentController<'a> {
18+
#[allow(dead_code)] // TODO: revisit this once port is complete
1819
jvmti: ::env::JvmTiEnv,
1920
heuristic: Box<super::Heuristic + 'a>,
2021
actions: Vec<Box<super::Action>>
@@ -126,9 +127,9 @@ mod tests {
126127
ac.on_oom(dummy_jni_env(), 0);
127128
}
128129

129-
unsafe extern "C" fn test_get_env(vm: *mut ::jvmti::JavaVM,
130-
penv: *mut *mut ::std::os::raw::c_void,
131-
version: ::jvmti::jint)
130+
unsafe extern "C" fn test_get_env(_: *mut ::jvmti::JavaVM,
131+
_: *mut *mut ::std::os::raw::c_void,
132+
_: ::jvmti::jint)
132133
-> ::jvmti::jint {
133134
0
134135
}

src/agentcontroller/heaphistogram.rs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,41 @@
1414
* limitations under the License.
1515
*/
1616

17+
use env::JvmTI;
18+
use std::io::Write;
19+
use std::io::stdout;
20+
use heap::Tagger;
21+
use heap::Tag;
22+
1723
pub struct HeapHistogram {
1824
jvmti: ::env::JvmTiEnv,
1925
}
2026

2127
impl HeapHistogram {
22-
pub fn new(jvmti: ::env::JvmTiEnv) -> Result<Self, ::jvmti::jint> {
28+
pub fn new(mut jvmti: ::env::JvmTiEnv) -> Result<Self, ::jvmti::jint> {
29+
jvmti.enable_object_tagging()?;
2330
Ok(Self {
24-
jvmti: jvmti
31+
jvmti: jvmti,
2532
})
2633
}
34+
35+
pub fn print(&self, writer: &Write) {
36+
let mut tagger = Tagger::new();
37+
// Tag all loaded classes so we can determine each object's class signature during heap traversal.
38+
self.jvmti.tag_loaded_classes(&mut tagger);
39+
40+
// Traverse the live heap and add objects to the heap stats.
41+
self.jvmti.traverse_live_heap(|class_tag: ::jvmti::jlong, size: ::jvmti::jlong| {
42+
if let Some(sig) = tagger.class_signature(class_tag) {
43+
eprintln!("Found object with signature {} and size {}", sig, size);
44+
// heapStats -> recordObject(taggedClass[unmaskedClassTag], size);
45+
}
46+
});
47+
}
2748
}
2849

2950
impl super::Action for HeapHistogram {
30-
fn on_oom(&self, jni_env: ::env::JniEnv, resource_exhaustion_flags: ::jvmti::jint) {
51+
fn on_oom(&self, _: ::env::JniEnv, _: ::jvmti::jint) {
52+
self.print(&stdout());
3153
}
3254
}

src/agentcontroller/kill.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ impl Kill {
3030
}
3131
}
3232

33+
#[cfg(test)]
3334
pub fn setSignal(&mut self, signal: c_int) {
3435
self.signal = signal;
3536
}
@@ -93,7 +94,7 @@ mod tests {
9394
signal::SaFlags::empty(),
9495
signal::SigSet::empty());
9596
unsafe {
96-
signal::sigaction(signal::SIGUSR1, &sig_action);
97+
signal::sigaction(signal::SIGUSR1, &sig_action).unwrap();
9798
}
9899
}
99100
}

src/env.rs

Lines changed: 171 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,26 @@
1515
*/
1616

1717
use std::mem::size_of;
18+
use std::mem::transmute;
1819
use ::std::ptr;
1920
use ::std::ffi::CString;
21+
use ::std::ffi::CStr;
2022
use ::std::sync::Mutex;
2123
use ::jvmti::jvmtiEnv;
2224
use ::jvmti::jrawMonitorID;
25+
use ::heap::Tag;
2326

2427
pub trait JvmTI {
28+
// TODO: rework the following methods not to return values since they only ever return Ok(()) or panic.
2529
fn create_raw_monitor(&mut self, name: String, monitor: &Mutex<RawMonitorId>) -> Result<(), ::jvmti::jint>;
2630
fn raw_monitor_enter(&mut self, monitor: &Mutex<RawMonitorId>) -> Result<(), ::jvmti::jint>;
2731
fn raw_monitor_exit(&mut self, monitor: &Mutex<RawMonitorId>) -> Result<(), ::jvmti::jint>;
2832
fn on_resource_exhausted(&mut self, callback: FnResourceExhausted) -> Result<(), ::jvmti::jint>;
33+
fn enable_object_tagging(&mut self) -> Result<(), ::jvmti::jint>;
34+
fn tag_loaded_classes(&self, tagger: &mut Tag);
35+
36+
// Restriction: traverse_live_heap may be called at most once in the lifetime of a JVM.
37+
fn traverse_live_heap<F>(&self, closure: F) where F: FnMut(::jvmti::jlong, ::jvmti::jlong);
2938
}
3039

3140
pub struct RawMonitorId {
@@ -66,43 +75,37 @@ impl JvmTiEnv {
6675
}
6776
}
6877

69-
impl JvmTI for JvmTiEnv {
70-
fn create_raw_monitor(&mut self, name: String, monitor: &Mutex<RawMonitorId>) -> Result<(), ::jvmti::jint> {
78+
macro_rules! jvmtifn (
79+
($r:expr, $f:ident, $($arg:tt)*) => { {
7180
let rc;
81+
#[allow(unused_unsafe)] // suppress warning if used inside unsafe block
7282
unsafe {
73-
let create_raw_monitor_fn = (**self.jvmti).CreateRawMonitor.unwrap();
74-
rc = create_raw_monitor_fn(self.jvmti, CString::new(name).unwrap().into_raw(), monitor.lock().unwrap().id);
83+
let fnc = (**$r).$f.unwrap();
84+
rc = fnc($r, $($arg)*);
7585
}
7686
if rc != ::jvmti::jvmtiError::JVMTI_ERROR_NONE {
77-
eprintln!("ERROR: CreateRawMonitor failed: {:?}", rc);
78-
return Err(::jvmti::JNI_ERR);
87+
eprintln!("ERROR: JVMTI {} failed: {:?}", stringify!($f), rc);
88+
panic!(::jvmti::JNI_ERR);
7989
}
90+
} }
91+
);
92+
93+
// Pick a suitable object tag mask greater than tags used to tag classes.
94+
const TAG_VISITED_MASK: ::jvmti::jlong = 1 << 31;
95+
96+
impl JvmTI for JvmTiEnv {
97+
fn create_raw_monitor(&mut self, name: String, monitor: &Mutex<RawMonitorId>) -> Result<(), ::jvmti::jint> {
98+
jvmtifn!(self.jvmti, CreateRawMonitor, CString::new(name).unwrap().into_raw(), monitor.lock().unwrap().id);
8099
Ok(())
81100
}
82101

83102
fn raw_monitor_enter(&mut self, monitor: &Mutex<RawMonitorId>) -> Result<(), ::jvmti::jint> {
84-
let rc;
85-
unsafe {
86-
let raw_monitor_enter_fn = (**self.jvmti).RawMonitorEnter.unwrap();
87-
rc = raw_monitor_enter_fn(self.jvmti, *monitor.lock().unwrap().id);
88-
}
89-
if rc != ::jvmti::jvmtiError::JVMTI_ERROR_NONE {
90-
eprintln!("ERROR: RawMonitorEnter failed: {:?}", rc);
91-
return Err(::jvmti::JNI_ERR);
92-
}
103+
jvmtifn!(self.jvmti, RawMonitorEnter, *monitor.lock().unwrap().id);
93104
Ok(())
94105
}
95106

96107
fn raw_monitor_exit(&mut self, monitor: &Mutex<RawMonitorId>) -> Result<(), ::jvmti::jint> {
97-
let rc;
98-
unsafe {
99-
let raw_monitor_exit_fn = (**self.jvmti).RawMonitorExit.unwrap();
100-
rc = raw_monitor_exit_fn(self.jvmti, *monitor.lock().unwrap().id);
101-
}
102-
if rc != ::jvmti::jvmtiError::JVMTI_ERROR_NONE {
103-
eprintln!("ERROR: RawMonitorExit failed: {:?}", rc);
104-
return Err(::jvmti::JNI_ERR);
105-
}
108+
jvmtifn!(self.jvmti, RawMonitorExit, *monitor.lock().unwrap().id);
106109
Ok(())
107110
}
108111

@@ -111,65 +114,125 @@ impl JvmTI for JvmTiEnv {
111114
EVENT_CALLBACKS.resource_exhausted = Some(callback);
112115
}
113116

114-
let rc;
115-
unsafe {
116-
let set_event_callbacks_fn = (**self.jvmti).SetEventCallbacks.unwrap();
117-
let callbacks = ::jvmti::jvmtiEventCallbacks {
118-
VMInit: None,
119-
VMDeath: None,
120-
ThreadStart: None,
121-
ThreadEnd: None,
122-
ClassFileLoadHook: None,
123-
ClassLoad: None,
124-
ClassPrepare: None,
125-
VMStart: None,
126-
Exception: None,
127-
ExceptionCatch: None,
128-
SingleStep: None,
129-
FramePop: None,
130-
Breakpoint: None,
131-
FieldAccess: None,
132-
FieldModification: None,
133-
MethodEntry: None,
134-
MethodExit: None,
135-
NativeMethodBind: None,
136-
CompiledMethodLoad: None,
137-
CompiledMethodUnload: None,
138-
DynamicCodeGenerated: None,
139-
DataDumpRequest: None,
140-
reserved72: None,
141-
MonitorWait: None,
142-
MonitorWaited: None,
143-
MonitorContendedEnter: None,
144-
MonitorContendedEntered: None,
145-
reserved77: None,
146-
reserved78: None,
147-
reserved79: None,
148-
ResourceExhausted: Some(resource_exhausted),
149-
GarbageCollectionStart: None,
150-
GarbageCollectionFinish: None,
151-
ObjectFree: None,
152-
VMObjectAlloc: None
153-
};
154-
rc = set_event_callbacks_fn(self.jvmti, &callbacks, size_of::<::jvmti::jvmtiEventCallbacks>() as i32);
155-
}
156-
if rc != ::jvmti::jvmtiError::JVMTI_ERROR_NONE {
157-
eprintln!("ERROR: SetEventCallbacks failed: {:?}", rc);
158-
return Err(::jvmti::JNI_ERR);
159-
}
117+
let callbacks = ::jvmti::jvmtiEventCallbacks {
118+
VMInit: None,
119+
VMDeath: None,
120+
ThreadStart: None,
121+
ThreadEnd: None,
122+
ClassFileLoadHook: None,
123+
ClassLoad: None,
124+
ClassPrepare: None,
125+
VMStart: None,
126+
Exception: None,
127+
ExceptionCatch: None,
128+
SingleStep: None,
129+
FramePop: None,
130+
Breakpoint: None,
131+
FieldAccess: None,
132+
FieldModification: None,
133+
MethodEntry: None,
134+
MethodExit: None,
135+
NativeMethodBind: None,
136+
CompiledMethodLoad: None,
137+
CompiledMethodUnload: None,
138+
DynamicCodeGenerated: None,
139+
DataDumpRequest: None,
140+
reserved72: None,
141+
MonitorWait: None,
142+
MonitorWaited: None,
143+
MonitorContendedEnter: None,
144+
MonitorContendedEntered: None,
145+
reserved77: None,
146+
reserved78: None,
147+
reserved79: None,
148+
ResourceExhausted: Some(resource_exhausted),
149+
GarbageCollectionStart: None,
150+
GarbageCollectionFinish: None,
151+
ObjectFree: None,
152+
VMObjectAlloc: None
153+
};
154+
jvmtifn!(self.jvmti, SetEventCallbacks, &callbacks, size_of::<::jvmti::jvmtiEventCallbacks>() as i32);
160155

161-
let rc;
162-
unsafe {
163-
let set_event_notification_mode_fn = (**self.jvmti).SetEventNotificationMode.unwrap();
164-
rc = set_event_notification_mode_fn(self.jvmti, ::jvmti::jvmtiEventMode::JVMTI_ENABLE, ::jvmti::jvmtiEvent::JVMTI_EVENT_RESOURCE_EXHAUSTED, ::std::ptr::null_mut());
165-
}
166-
if rc != ::jvmti::jvmtiError::JVMTI_ERROR_NONE {
167-
eprintln!("ERROR: SetEventNotificationMode failed: {:?}", rc);
168-
return Err(::jvmti::JNI_ERR);
169-
}
156+
jvmtifn!(self.jvmti, SetEventNotificationMode, ::jvmti::jvmtiEventMode::JVMTI_ENABLE, ::jvmti::jvmtiEvent::JVMTI_EVENT_RESOURCE_EXHAUSTED, ::std::ptr::null_mut());
157+
158+
Ok(())
159+
}
160+
161+
fn enable_object_tagging(&mut self) -> Result<(), ::jvmti::jint> {
162+
let mut capabilities = ::jvmti::jvmtiCapabilities {
163+
_bitfield_1: [0; 4],
164+
_bitfield_2: [0; 2],
165+
_bitfield_3: [0; 2],
166+
_bitfield_4: [0; 2],
167+
__bindgen_align: [],
168+
// FIXME: seems dangeous to reference a field with this name. Same may be true of other fields in this struct.
169+
};
170+
171+
jvmtifn!(self.jvmti, GetCapabilities, &mut capabilities);
172+
173+
capabilities.set_can_tag_objects(1);
174+
175+
jvmtifn!(self.jvmti, AddCapabilities, &capabilities);
170176

171177
Ok(())
172178
}
179+
180+
fn tag_loaded_classes(&self, tagger: &mut Tag) {
181+
let mut class_count = 0;
182+
let mut class_ptr = ::std::ptr::null_mut();
183+
jvmtifn!(self.jvmti, GetLoadedClasses, &mut class_count, &mut class_ptr);
184+
185+
while class_count > 0 {
186+
let mut sig_ptr = ::std::ptr::null_mut();
187+
jvmtifn!(self.jvmti, GetClassSignature, *class_ptr, &mut sig_ptr, ::std::ptr::null_mut());
188+
unsafe {
189+
let cstr = CStr::from_ptr(sig_ptr); // sig_ptr is deallocated later
190+
let tag = tagger.class_tag(&cstr.to_str().unwrap().to_string());
191+
jvmtifn!(self.jvmti, SetTag, *class_ptr, tag);
192+
}
193+
jvmtifn!(self.jvmti, Deallocate, sig_ptr as *mut u8);
194+
195+
class_count -= 1;
196+
unsafe { class_ptr = class_ptr.offset(1); }
197+
}
198+
}
199+
200+
fn traverse_live_heap<F>(&self, mut closure: F)
201+
where F: FnMut(::jvmti::jlong, ::jvmti::jlong) {
202+
let callbacks = ::jvmti::jvmtiHeapCallbacks {
203+
heap_iteration_callback: None,
204+
heap_reference_callback: Some(heapReferenceCallback),
205+
primitive_field_callback: None,
206+
array_primitive_value_callback: None,
207+
string_primitive_value_callback: None,
208+
reserved5: None,
209+
reserved6: None,
210+
reserved7: None,
211+
reserved8: None,
212+
reserved9: None,
213+
reserved10: None,
214+
reserved11: None,
215+
reserved12: None,
216+
reserved13: None,
217+
reserved14: None,
218+
reserved15: None,
219+
};
220+
// Pass closure to the callback as a thin pointer pointing to a fat pointer pointing to the closure.
221+
// See: https://stackoverflow.com/questions/38995701/how-do-i-pass-closures-through-raw-pointers-as-arguments-to-c-functions
222+
let mut closure_ptr: &mut FnMut(::jvmti::jlong, ::jvmti::jlong) = &mut closure;
223+
let closure_ptr_ptr = unsafe { transmute(&mut closure_ptr) };
224+
225+
// Need to pass the traversal state into FollowReferences and pick it up in the callback, which may be called multiple times
226+
jvmtifn!(self.jvmti, FollowReferences, 0, ::std::ptr::null_mut(), ::std::ptr::null_mut(), &callbacks, closure_ptr_ptr);
227+
// jvmtiHeapCallbacks callbacks = {};
228+
// callbacks.heap_reference_callback = &heapRefCallback;
229+
//
230+
// jvmtiError err = jvmti -> FollowReferences(0, NULL, NULL, &callbacks, this);
231+
// if (err != JVMTI_ERROR_NONE) {
232+
// std::cerr << "ERROR: FollowReferences failed: " << err << std::endl;
233+
// throw new std::runtime_error("FollowReferences failed");
234+
// }
235+
}
173236
}
174237

175238
#[allow(unused_variables)]
@@ -198,13 +261,41 @@ pub static mut EVENT_CALLBACKS: EventCallbacks = EventCallbacks {
198261
resource_exhausted: None
199262
};
200263

264+
#[allow(unused_variables)]
265+
unsafe extern "C" fn heapReferenceCallback(reference_kind: ::jvmti::jvmtiHeapReferenceKind,
266+
reference_info: *const ::jvmti::jvmtiHeapReferenceInfo,
267+
class_tag: ::jvmti::jlong,
268+
referrer_class_tag: ::jvmti::jlong,
269+
size: ::jvmti::jlong,
270+
tag_ptr: *mut ::jvmti::jlong,
271+
referrer_tag_ptr: *mut ::jvmti::jlong,
272+
length: ::jvmti::jint,
273+
user_data: *mut ::std::os::raw::c_void)
274+
-> ::jvmti::jint {
275+
if *tag_ptr & TAG_VISITED_MASK == TAG_VISITED_MASK {
276+
return 0;
277+
}
278+
279+
// For each object encountered, tag it so we can avoid visiting it again
280+
// noting that traverse_live_heap is called at most once in the lifetime of a JVM
281+
*tag_ptr |= TAG_VISITED_MASK;
282+
283+
// Add the object to the heap stats along with its class signature.
284+
let unmaskedClassTag = class_tag & !TAG_VISITED_MASK;
285+
let closure: &mut &mut FnMut(::jvmti::jlong, ::jvmti::jlong) -> ::jvmti::jint = transmute(user_data);
286+
closure(unmaskedClassTag, size);
287+
288+
::jvmti::JVMTI_VISIT_OBJECTS as ::jvmti::jint
289+
}
290+
201291
#[derive(Clone, Copy)]
202292
pub struct JniEnv {
293+
#[allow(dead_code)] // TODO: revisit this once port is complete
203294
jni: *mut ::jvmti::JNIEnv
204295
}
205296

206297
impl JniEnv {
207298
pub fn new(jni_env: *mut ::jvmti::JNIEnv) -> JniEnv {
208-
JniEnv {jni: jni_env}
299+
JniEnv { jni: jni_env }
209300
}
210301
}

0 commit comments

Comments
 (0)