rrtrace 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +36 -0
- data/.github/workflows/ci.yml +94 -0
- data/.github/workflows/release.yml +4 -4
- data/Cargo.lock +26 -30
- data/Cargo.toml +8 -4
- data/README.md +77 -16
- data/exe/rrtrace +16 -0
- data/ext/rrtrace/process_manager_posix.h +18 -2
- data/ext/rrtrace/process_manager_windows.h +14 -0
- data/ext/rrtrace/rrtrace.c +153 -44
- data/ext/rrtrace/rrtrace_event.h +6 -15
- data/ext/rrtrace/rrtrace_event_ringbuffer.h +2 -2
- data/ext/rrtrace/shared_memory_posix.h +57 -5
- data/ext/rrtrace/shared_memory_windows.h +38 -3
- data/ext/rrtrace/time_posix.h +29 -0
- data/ext/rrtrace/time_windows.h +33 -0
- data/lib/rrtrace/run.rb +5 -0
- data/lib/rrtrace/version.rb +1 -1
- data/lib/rrtrace.rb +31 -3
- data/libexec/rrtrace +0 -0
- data/mise.toml +5 -2
- data/sig/rrtrace.rbs +3 -0
- data/src/main.rs +114 -32
- data/src/object_scatter.rs +110 -0
- data/src/oneshot_channel.rs +74 -0
- data/src/renderer/vertex_arena.rs +17 -13
- data/src/renderer.rs +268 -37
- data/src/ringbuffer.rs +2 -2
- data/src/shader.wgsl +160 -115
- data/src/trace_state.rs +500 -119
- data/src/universal_notifier.rs +31 -0
- data/taplo.toml +4 -0
- metadata +14 -3
data/src/trace_state.rs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
use crate::ringbuffer::{RRTraceEvent, RRTraceEventType};
|
|
2
2
|
use smallvec::SmallVec;
|
|
3
|
-
use std::
|
|
3
|
+
use std::collections::HashMap;
|
|
4
|
+
use std::collections::hash_map::Entry;
|
|
4
5
|
use std::fmt::Debug;
|
|
6
|
+
use std::{iter, mem};
|
|
5
7
|
|
|
6
8
|
#[repr(C)]
|
|
7
9
|
#[derive(Copy, Default, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
|
|
@@ -12,6 +14,43 @@ pub struct CallBox {
|
|
|
12
14
|
depth: u32,
|
|
13
15
|
}
|
|
14
16
|
|
|
17
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
18
|
+
pub struct ThreadLine {
|
|
19
|
+
start_time: [u32; 2],
|
|
20
|
+
end_time: [u32; 2],
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
impl ThreadLine {
|
|
24
|
+
pub fn start_time(&self) -> [u32; 2] {
|
|
25
|
+
self.start_time
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
pub fn end_time(&self) -> [u32; 2] {
|
|
29
|
+
self.end_time
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
#[derive(Debug, Clone)]
|
|
34
|
+
pub struct ThreadData {
|
|
35
|
+
thread_id: u32,
|
|
36
|
+
call_boxes: Vec<CallBox>,
|
|
37
|
+
thread_line: ThreadLine,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
impl ThreadData {
|
|
41
|
+
pub fn thread_id(&self) -> u32 {
|
|
42
|
+
self.thread_id
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
pub fn call_boxes(&self) -> &[CallBox] {
|
|
46
|
+
&self.call_boxes
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
pub fn thread_line(&self) -> ThreadLine {
|
|
50
|
+
self.thread_line
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
15
54
|
pub const VISIBLE_DURATION: u64 = 1_000_000_000 * 5;
|
|
16
55
|
|
|
17
56
|
pub fn encode_time(time: u64) -> [u32; 2] {
|
|
@@ -21,69 +60,191 @@ pub fn encode_time(time: u64) -> [u32; 2] {
|
|
|
21
60
|
]
|
|
22
61
|
}
|
|
23
62
|
|
|
63
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
64
|
+
enum ThreadId {
|
|
65
|
+
None,
|
|
66
|
+
Initial,
|
|
67
|
+
Id(u32),
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
#[derive(Debug, Clone, Default)]
|
|
71
|
+
struct StackState {
|
|
72
|
+
unmarked_returns: SmallVec<[u64; 2]>,
|
|
73
|
+
stack: SmallVec<[u64; 16]>,
|
|
74
|
+
exited: bool,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
impl StackState {
|
|
78
|
+
fn new() -> StackState {
|
|
79
|
+
StackState::default()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
#[inline(always)]
|
|
83
|
+
fn call(&mut self, method_id: u64) {
|
|
84
|
+
self.stack.push(method_id);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
#[inline(always)]
|
|
88
|
+
fn ret(&mut self, method_id: u64) {
|
|
89
|
+
loop {
|
|
90
|
+
match self.stack.pop() {
|
|
91
|
+
None => {
|
|
92
|
+
self.unmarked_returns.push(method_id);
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
Some(m) if m == method_id => break,
|
|
96
|
+
Some(_) => {}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
#[inline(always)]
|
|
102
|
+
fn exit(&mut self) {
|
|
103
|
+
self.exited = true;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
fn drop_unmarked_returns(&mut self) {
|
|
107
|
+
self.unmarked_returns.clear();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
fn merge_into(&self, other: &mut Self) {
|
|
111
|
+
let additional_push_stack = mem::replace(&mut other.stack, self.stack.clone());
|
|
112
|
+
let unmarked_returns =
|
|
113
|
+
mem::replace(&mut other.unmarked_returns, self.unmarked_returns.clone());
|
|
114
|
+
for method_id in unmarked_returns {
|
|
115
|
+
other.ret(method_id);
|
|
116
|
+
}
|
|
117
|
+
for method_id in additional_push_stack {
|
|
118
|
+
other.call(method_id);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
24
123
|
#[derive(Debug, Clone)]
|
|
25
124
|
pub struct FastTrace {
|
|
26
|
-
thread_stacks:
|
|
27
|
-
|
|
125
|
+
thread_stacks: HashMap<u32, StackState>,
|
|
126
|
+
initial_thread_stack: StackState,
|
|
127
|
+
current_thread: ThreadId,
|
|
28
128
|
in_gc: bool,
|
|
29
129
|
}
|
|
30
130
|
|
|
31
|
-
impl FastTrace {
|
|
32
|
-
|
|
131
|
+
impl Default for FastTrace {
|
|
132
|
+
fn default() -> Self {
|
|
33
133
|
FastTrace {
|
|
34
|
-
thread_stacks:
|
|
35
|
-
|
|
134
|
+
thread_stacks: HashMap::new(),
|
|
135
|
+
initial_thread_stack: StackState::new(),
|
|
136
|
+
current_thread: ThreadId::Id(0),
|
|
36
137
|
in_gc: false,
|
|
37
138
|
}
|
|
38
139
|
}
|
|
39
|
-
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
impl FastTrace {
|
|
143
|
+
pub fn from_events(events: &[RRTraceEvent]) -> FastTrace {
|
|
144
|
+
let mut thread_stacks = HashMap::<u32, StackState>::new();
|
|
145
|
+
let mut initial_thread_stack = StackState::new();
|
|
146
|
+
let mut current_thread = ThreadId::Initial;
|
|
147
|
+
|
|
148
|
+
let mut current_thread_stack = &mut initial_thread_stack;
|
|
40
149
|
for &event in events {
|
|
41
150
|
match event.event_type() {
|
|
42
151
|
RRTraceEventType::Call => {
|
|
43
152
|
let method_id = event.data();
|
|
44
|
-
|
|
45
|
-
.1
|
|
46
|
-
.push(method_id);
|
|
153
|
+
current_thread_stack.call(method_id);
|
|
47
154
|
}
|
|
48
155
|
RRTraceEventType::Return => {
|
|
49
156
|
let method_id = event.data();
|
|
50
|
-
|
|
51
|
-
while stack.pop().is_some_and(|m| m != method_id) {}
|
|
157
|
+
current_thread_stack.ret(method_id);
|
|
52
158
|
}
|
|
53
159
|
RRTraceEventType::ThreadSuspended => {
|
|
54
|
-
|
|
160
|
+
current_thread = ThreadId::None;
|
|
55
161
|
}
|
|
56
162
|
RRTraceEventType::ThreadResume => {
|
|
57
163
|
let thread_id = event.data() as u32;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
.binary_search_by_key(&thread_id, |&(thread_id, _)| thread_id)
|
|
61
|
-
{
|
|
62
|
-
Ok(index) => index,
|
|
63
|
-
Err(index) => {
|
|
64
|
-
self.thread_stacks
|
|
65
|
-
.insert(index, (thread_id, SmallVec::new()));
|
|
66
|
-
index
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
self.current_thread = index as u32;
|
|
164
|
+
current_thread = ThreadId::Id(thread_id);
|
|
165
|
+
current_thread_stack = thread_stacks.entry(thread_id).or_default();
|
|
70
166
|
}
|
|
71
|
-
RRTraceEventType::
|
|
167
|
+
RRTraceEventType::ThreadStart => {
|
|
72
168
|
let thread_id = event.data() as u32;
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
.
|
|
76
|
-
{
|
|
77
|
-
|
|
169
|
+
thread_stacks.insert(thread_id, StackState::new());
|
|
170
|
+
current_thread_stack = if let ThreadId::Id(current_thread_id) = current_thread {
|
|
171
|
+
thread_stacks.entry(current_thread_id).or_default()
|
|
172
|
+
} else {
|
|
173
|
+
&mut initial_thread_stack
|
|
78
174
|
};
|
|
79
175
|
}
|
|
176
|
+
RRTraceEventType::ThreadExit => {
|
|
177
|
+
current_thread_stack.exit();
|
|
178
|
+
}
|
|
80
179
|
RRTraceEventType::GCStart
|
|
81
180
|
| RRTraceEventType::GCEnd
|
|
82
|
-
| RRTraceEventType::ThreadStart
|
|
83
181
|
| RRTraceEventType::ThreadReady => {}
|
|
84
182
|
}
|
|
85
183
|
}
|
|
86
|
-
|
|
184
|
+
let in_gc = events.last().unwrap().event_type() == RRTraceEventType::GCStart;
|
|
185
|
+
FastTrace {
|
|
186
|
+
thread_stacks,
|
|
187
|
+
initial_thread_stack,
|
|
188
|
+
current_thread,
|
|
189
|
+
in_gc,
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
pub fn mark_as_first(&mut self) {
|
|
194
|
+
self.thread_stacks
|
|
195
|
+
.values_mut()
|
|
196
|
+
.chain(iter::once(&mut self.initial_thread_stack))
|
|
197
|
+
.for_each(StackState::drop_unmarked_returns);
|
|
198
|
+
if let ThreadId::Initial = self.current_thread {
|
|
199
|
+
self.current_thread = ThreadId::Id(0);
|
|
200
|
+
}
|
|
201
|
+
let initial_thread_stack = mem::take(&mut self.initial_thread_stack);
|
|
202
|
+
match self.thread_stacks.entry(0) {
|
|
203
|
+
Entry::Occupied(mut entry) => {
|
|
204
|
+
initial_thread_stack.merge_into(entry.get_mut());
|
|
205
|
+
}
|
|
206
|
+
Entry::Vacant(entry) => {
|
|
207
|
+
entry.insert(initial_thread_stack);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
pub fn merge_into(&self, other: &mut Self) {
|
|
213
|
+
match self.current_thread {
|
|
214
|
+
ThreadId::Id(id) => {
|
|
215
|
+
let initial_thread_stack = mem::replace(
|
|
216
|
+
&mut other.initial_thread_stack,
|
|
217
|
+
self.initial_thread_stack.clone(),
|
|
218
|
+
);
|
|
219
|
+
match other.thread_stacks.entry(id) {
|
|
220
|
+
Entry::Occupied(mut entry) => {
|
|
221
|
+
initial_thread_stack.merge_into(entry.get_mut());
|
|
222
|
+
}
|
|
223
|
+
Entry::Vacant(entry) => {
|
|
224
|
+
entry.insert(initial_thread_stack);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
ThreadId::Initial => {
|
|
229
|
+
self.initial_thread_stack
|
|
230
|
+
.merge_into(&mut other.initial_thread_stack);
|
|
231
|
+
}
|
|
232
|
+
ThreadId::None => {}
|
|
233
|
+
}
|
|
234
|
+
if let ThreadId::Initial = other.current_thread {
|
|
235
|
+
other.current_thread = self.current_thread;
|
|
236
|
+
}
|
|
237
|
+
self.thread_stacks.iter().for_each(|(&thread_id, stack)| {
|
|
238
|
+
match other.thread_stacks.entry(thread_id) {
|
|
239
|
+
Entry::Occupied(mut entry) => {
|
|
240
|
+
stack.merge_into(entry.get_mut());
|
|
241
|
+
}
|
|
242
|
+
Entry::Vacant(entry) => {
|
|
243
|
+
entry.insert(stack.clone());
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
other.thread_stacks.retain(|_, stack| !stack.exited);
|
|
87
248
|
}
|
|
88
249
|
}
|
|
89
250
|
|
|
@@ -93,46 +254,117 @@ struct CallStackEntry {
|
|
|
93
254
|
method_id: u64,
|
|
94
255
|
}
|
|
95
256
|
|
|
257
|
+
#[derive(Debug, Clone)]
|
|
258
|
+
struct ThreadTraceState {
|
|
259
|
+
thread_id: u32,
|
|
260
|
+
stack: Vec<CallStackEntry>,
|
|
261
|
+
call_boxes: Vec<CallBox>,
|
|
262
|
+
thread_line: ThreadLine,
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
impl ThreadTraceState {
|
|
266
|
+
fn from_stack(thread_id: u32, stack: &StackState, start_time: u64, end_time: u64) -> Self {
|
|
267
|
+
Self {
|
|
268
|
+
thread_id,
|
|
269
|
+
stack: stack
|
|
270
|
+
.stack
|
|
271
|
+
.iter()
|
|
272
|
+
.map(|&method_id| CallStackEntry {
|
|
273
|
+
method_id,
|
|
274
|
+
vertex_index: usize::MAX,
|
|
275
|
+
})
|
|
276
|
+
.collect(),
|
|
277
|
+
call_boxes: Vec::new(),
|
|
278
|
+
thread_line: ThreadLine {
|
|
279
|
+
start_time: encode_time(start_time),
|
|
280
|
+
end_time: encode_time(end_time),
|
|
281
|
+
},
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
fn new(thread_id: u32, start_time: u64, end_time: u64) -> Self {
|
|
286
|
+
Self {
|
|
287
|
+
thread_id,
|
|
288
|
+
stack: Vec::new(),
|
|
289
|
+
call_boxes: Vec::new(),
|
|
290
|
+
thread_line: ThreadLine {
|
|
291
|
+
start_time: encode_time(start_time),
|
|
292
|
+
end_time: encode_time(end_time),
|
|
293
|
+
},
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
fn into_thread_data(self) -> ThreadData {
|
|
298
|
+
ThreadData {
|
|
299
|
+
thread_id: self.thread_id,
|
|
300
|
+
call_boxes: self.call_boxes,
|
|
301
|
+
thread_line: self.thread_line,
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
fn find_thread_index(call_stack: &[ThreadTraceState], thread_id: u32) -> Result<usize, usize> {
|
|
307
|
+
call_stack.binary_search_by_key(&thread_id, |state| state.thread_id)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
fn get_or_insert_thread_state(
|
|
311
|
+
call_stack: &mut Vec<ThreadTraceState>,
|
|
312
|
+
thread_id: u32,
|
|
313
|
+
start_time: u64,
|
|
314
|
+
end_time: u64,
|
|
315
|
+
) -> usize {
|
|
316
|
+
match find_thread_index(call_stack, thread_id) {
|
|
317
|
+
Ok(index) => index,
|
|
318
|
+
Err(index) => {
|
|
319
|
+
call_stack.insert(
|
|
320
|
+
index,
|
|
321
|
+
ThreadTraceState::new(thread_id, start_time, end_time),
|
|
322
|
+
);
|
|
323
|
+
index
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
96
328
|
pub struct SlowTrace {
|
|
97
|
-
data: Vec<
|
|
329
|
+
data: Vec<ThreadData>,
|
|
98
330
|
max_depth: u32,
|
|
99
331
|
end_time: u64,
|
|
100
332
|
gc_events: Vec<u64>,
|
|
101
333
|
}
|
|
102
334
|
|
|
103
335
|
impl SlowTrace {
|
|
104
|
-
pub fn trace(start_time: u64, fast_trace: FastTrace, events: &[RRTraceEvent]) -> SlowTrace {
|
|
336
|
+
pub fn trace(start_time: u64, fast_trace: &FastTrace, events: &[RRTraceEvent]) -> SlowTrace {
|
|
105
337
|
let end_time = events.last().unwrap().timestamp();
|
|
106
338
|
let mut max_depth = 0;
|
|
107
|
-
let FastTrace {
|
|
108
|
-
thread_stacks,
|
|
339
|
+
let &FastTrace {
|
|
340
|
+
ref thread_stacks,
|
|
341
|
+
initial_thread_stack: _,
|
|
109
342
|
current_thread,
|
|
110
343
|
in_gc,
|
|
111
344
|
} = fast_trace;
|
|
345
|
+
let current_thread = match current_thread {
|
|
346
|
+
ThreadId::None => u32::MAX,
|
|
347
|
+
ThreadId::Initial => unreachable!(),
|
|
348
|
+
ThreadId::Id(id) => id,
|
|
349
|
+
};
|
|
112
350
|
let mut gc_events = Vec::new();
|
|
113
351
|
let mut call_stack = thread_stacks
|
|
114
|
-
.
|
|
115
|
-
.map(|(thread_id, stack)| {
|
|
116
|
-
(
|
|
117
|
-
thread_id,
|
|
118
|
-
stack
|
|
119
|
-
.into_iter()
|
|
120
|
-
.map(|method_id| CallStackEntry {
|
|
121
|
-
method_id,
|
|
122
|
-
vertex_index: usize::MAX,
|
|
123
|
-
})
|
|
124
|
-
.collect::<Vec<_>>(),
|
|
125
|
-
Vec::new(),
|
|
126
|
-
)
|
|
352
|
+
.iter()
|
|
353
|
+
.map(|(&thread_id, stack)| {
|
|
354
|
+
ThreadTraceState::from_stack(thread_id, stack, start_time, end_time)
|
|
127
355
|
})
|
|
128
356
|
.collect::<Vec<_>>();
|
|
129
|
-
|
|
130
|
-
|
|
357
|
+
call_stack.sort_unstable_by_key(|state| state.thread_id);
|
|
358
|
+
if !in_gc && let Ok(index) = find_thread_index(&call_stack, current_thread) {
|
|
359
|
+
let ThreadTraceState {
|
|
360
|
+
stack, call_boxes, ..
|
|
361
|
+
} = &mut call_stack[index];
|
|
362
|
+
call_boxes.reserve(stack.len());
|
|
131
363
|
for (depth, entry) in stack.iter_mut().enumerate() {
|
|
132
|
-
entry.vertex_index =
|
|
364
|
+
entry.vertex_index = call_boxes.len();
|
|
133
365
|
let depth = depth as u32;
|
|
134
366
|
max_depth = max_depth.max(depth);
|
|
135
|
-
|
|
367
|
+
call_boxes.push(CallBox {
|
|
136
368
|
start_time: encode_time(start_time),
|
|
137
369
|
end_time: encode_time(end_time),
|
|
138
370
|
method_id: entry.method_id as u32,
|
|
@@ -140,85 +372,133 @@ impl SlowTrace {
|
|
|
140
372
|
});
|
|
141
373
|
}
|
|
142
374
|
}
|
|
143
|
-
let mut
|
|
144
|
-
let mut null_vec2 = Vec::new();
|
|
145
|
-
let (mut current_stack, mut current_vertices) = call_stack
|
|
146
|
-
.get_mut(current_thread as usize)
|
|
147
|
-
.map_or((&mut null_vec1, &mut null_vec2), |(_, stack, vertices)| {
|
|
148
|
-
(stack, vertices)
|
|
149
|
-
});
|
|
375
|
+
let mut current_thread_id = (current_thread != u32::MAX).then_some(current_thread);
|
|
150
376
|
for event in events {
|
|
151
377
|
match event.event_type() {
|
|
152
378
|
RRTraceEventType::Call => {
|
|
153
|
-
let
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
379
|
+
if let Some(index) = current_thread_id
|
|
380
|
+
.and_then(|thread_id| find_thread_index(&call_stack, thread_id).ok())
|
|
381
|
+
{
|
|
382
|
+
let ThreadTraceState {
|
|
383
|
+
stack: current_stack,
|
|
384
|
+
call_boxes: current_vertices,
|
|
385
|
+
..
|
|
386
|
+
} = &mut call_stack[index];
|
|
387
|
+
let vertex_index = current_vertices.len();
|
|
388
|
+
let depth = current_stack.len() as u32;
|
|
389
|
+
current_stack.push(CallStackEntry {
|
|
390
|
+
vertex_index,
|
|
391
|
+
method_id: event.data(),
|
|
392
|
+
});
|
|
393
|
+
current_vertices.push(CallBox {
|
|
394
|
+
start_time: encode_time(event.timestamp()),
|
|
395
|
+
end_time: encode_time(end_time),
|
|
396
|
+
method_id: event.data() as u32,
|
|
397
|
+
depth,
|
|
398
|
+
});
|
|
399
|
+
max_depth = max_depth.max(depth);
|
|
400
|
+
}
|
|
166
401
|
}
|
|
167
402
|
RRTraceEventType::Return => {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
method_id,
|
|
171
|
-
}) = current_stack.pop()
|
|
403
|
+
if let Some(index) = current_thread_id
|
|
404
|
+
.and_then(|thread_id| find_thread_index(&call_stack, thread_id).ok())
|
|
172
405
|
{
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
406
|
+
let ThreadTraceState {
|
|
407
|
+
stack: current_stack,
|
|
408
|
+
call_boxes: current_vertices,
|
|
409
|
+
..
|
|
410
|
+
} = &mut call_stack[index];
|
|
411
|
+
while let Some(CallStackEntry {
|
|
412
|
+
vertex_index,
|
|
413
|
+
method_id,
|
|
414
|
+
}) = current_stack.pop()
|
|
415
|
+
{
|
|
416
|
+
current_vertices[vertex_index].end_time =
|
|
417
|
+
encode_time(event.timestamp());
|
|
418
|
+
if method_id == event.data() {
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
176
421
|
}
|
|
177
422
|
}
|
|
178
423
|
}
|
|
179
424
|
RRTraceEventType::GCStart => {
|
|
180
425
|
gc_events.push(event.timestamp());
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
426
|
+
if let Some(index) = current_thread_id
|
|
427
|
+
.and_then(|thread_id| find_thread_index(&call_stack, thread_id).ok())
|
|
428
|
+
{
|
|
429
|
+
let ThreadTraceState {
|
|
430
|
+
stack: current_stack,
|
|
431
|
+
call_boxes: current_vertices,
|
|
432
|
+
..
|
|
433
|
+
} = &mut call_stack[index];
|
|
434
|
+
for CallStackEntry { vertex_index, .. } in current_stack.iter_mut() {
|
|
435
|
+
current_vertices[*vertex_index].end_time =
|
|
436
|
+
encode_time(event.timestamp());
|
|
437
|
+
*vertex_index = usize::MAX;
|
|
438
|
+
}
|
|
184
439
|
}
|
|
185
440
|
}
|
|
186
441
|
RRTraceEventType::GCEnd => {
|
|
187
442
|
gc_events.push(event.timestamp());
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
&mut CallStackEntry {
|
|
191
|
-
ref mut vertex_index,
|
|
192
|
-
method_id,
|
|
193
|
-
},
|
|
194
|
-
) in current_stack.iter_mut().enumerate()
|
|
443
|
+
if let Some(index) = current_thread_id
|
|
444
|
+
.and_then(|thread_id| find_thread_index(&call_stack, thread_id).ok())
|
|
195
445
|
{
|
|
196
|
-
let
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
446
|
+
let ThreadTraceState {
|
|
447
|
+
stack: current_stack,
|
|
448
|
+
call_boxes: current_vertices,
|
|
449
|
+
..
|
|
450
|
+
} = &mut call_stack[index];
|
|
451
|
+
for (
|
|
202
452
|
depth,
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
453
|
+
&mut CallStackEntry {
|
|
454
|
+
ref mut vertex_index,
|
|
455
|
+
method_id,
|
|
456
|
+
},
|
|
457
|
+
) in current_stack.iter_mut().enumerate()
|
|
458
|
+
{
|
|
459
|
+
let new_index = current_vertices.len();
|
|
460
|
+
let depth = depth as u32;
|
|
461
|
+
current_vertices.push(CallBox {
|
|
462
|
+
start_time: encode_time(event.timestamp()),
|
|
463
|
+
end_time: encode_time(end_time),
|
|
464
|
+
method_id: method_id as u32,
|
|
465
|
+
depth,
|
|
466
|
+
});
|
|
467
|
+
max_depth = max_depth.max(depth);
|
|
468
|
+
*vertex_index = new_index;
|
|
469
|
+
}
|
|
206
470
|
}
|
|
207
471
|
}
|
|
208
472
|
RRTraceEventType::ThreadSuspended => {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
473
|
+
if let Some(index) = current_thread_id
|
|
474
|
+
.and_then(|thread_id| find_thread_index(&call_stack, thread_id).ok())
|
|
475
|
+
{
|
|
476
|
+
let ThreadTraceState {
|
|
477
|
+
stack: current_stack,
|
|
478
|
+
call_boxes: current_vertices,
|
|
479
|
+
..
|
|
480
|
+
} = &mut call_stack[index];
|
|
481
|
+
for CallStackEntry { vertex_index, .. } in current_stack.iter_mut() {
|
|
482
|
+
current_vertices[*vertex_index].end_time =
|
|
483
|
+
encode_time(event.timestamp());
|
|
484
|
+
*vertex_index = usize::MAX;
|
|
485
|
+
}
|
|
212
486
|
}
|
|
487
|
+
current_thread_id = None;
|
|
213
488
|
}
|
|
214
489
|
RRTraceEventType::ThreadResume => {
|
|
215
490
|
let thread_id = event.data() as u32;
|
|
216
|
-
let index =
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
491
|
+
let index = get_or_insert_thread_state(
|
|
492
|
+
&mut call_stack,
|
|
493
|
+
thread_id,
|
|
494
|
+
start_time,
|
|
495
|
+
end_time,
|
|
496
|
+
);
|
|
497
|
+
let ThreadTraceState {
|
|
498
|
+
stack: new_stack,
|
|
499
|
+
call_boxes: new_vertices,
|
|
500
|
+
..
|
|
501
|
+
} = &mut call_stack[index];
|
|
222
502
|
|
|
223
503
|
for (
|
|
224
504
|
depth,
|
|
@@ -240,25 +520,53 @@ impl SlowTrace {
|
|
|
240
520
|
*vertex_index = new_index;
|
|
241
521
|
}
|
|
242
522
|
|
|
243
|
-
|
|
523
|
+
current_thread_id = Some(thread_id);
|
|
244
524
|
}
|
|
245
|
-
RRTraceEventType::
|
|
246
|
-
|
|
247
|
-
|
|
525
|
+
RRTraceEventType::ThreadStart => {
|
|
526
|
+
let thread_id = event.data() as u32;
|
|
527
|
+
let index = get_or_insert_thread_state(
|
|
528
|
+
&mut call_stack,
|
|
529
|
+
thread_id,
|
|
530
|
+
event.timestamp(),
|
|
531
|
+
end_time,
|
|
532
|
+
);
|
|
533
|
+
call_stack[index].thread_line.start_time = encode_time(event.timestamp());
|
|
534
|
+
}
|
|
535
|
+
RRTraceEventType::ThreadExit => {
|
|
536
|
+
let thread_id = event.data() as u32;
|
|
537
|
+
let index = get_or_insert_thread_state(
|
|
538
|
+
&mut call_stack,
|
|
539
|
+
thread_id,
|
|
540
|
+
start_time,
|
|
541
|
+
event.timestamp(),
|
|
542
|
+
);
|
|
543
|
+
let thread_state = &mut call_stack[index];
|
|
544
|
+
thread_state.thread_line.end_time = encode_time(event.timestamp());
|
|
545
|
+
if current_thread_id == Some(thread_id) {
|
|
546
|
+
for CallStackEntry { vertex_index, .. } in thread_state.stack.iter_mut() {
|
|
547
|
+
thread_state.call_boxes[*vertex_index].end_time =
|
|
548
|
+
encode_time(event.timestamp());
|
|
549
|
+
*vertex_index = usize::MAX;
|
|
550
|
+
}
|
|
551
|
+
current_thread_id = None;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
RRTraceEventType::ThreadReady => {}
|
|
248
555
|
}
|
|
249
556
|
}
|
|
250
557
|
SlowTrace {
|
|
251
|
-
data: call_stack
|
|
558
|
+
data: call_stack
|
|
559
|
+
.into_iter()
|
|
560
|
+
.map(ThreadTraceState::into_thread_data)
|
|
561
|
+
.collect(),
|
|
252
562
|
max_depth,
|
|
253
563
|
end_time,
|
|
254
564
|
gc_events,
|
|
255
565
|
}
|
|
256
566
|
}
|
|
257
567
|
|
|
258
|
-
pub fn data(&self) ->
|
|
259
|
-
self.data
|
|
260
|
-
.iter()
|
|
261
|
-
.map(|&(thread_id, _, ref call_box)| (thread_id, call_box.as_slice()))
|
|
568
|
+
pub fn data(&self) -> &[ThreadData] {
|
|
569
|
+
&self.data
|
|
262
570
|
}
|
|
263
571
|
|
|
264
572
|
pub fn gc_events(&self) -> &[u64] {
|
|
@@ -273,3 +581,76 @@ impl SlowTrace {
|
|
|
273
581
|
self.max_depth
|
|
274
582
|
}
|
|
275
583
|
}
|
|
584
|
+
|
|
585
|
+
#[cfg(test)]
|
|
586
|
+
mod tests {
|
|
587
|
+
use super::*;
|
|
588
|
+
|
|
589
|
+
fn event(event_type: RRTraceEventType, timestamp: u64, data: u64) -> RRTraceEvent {
|
|
590
|
+
let event_bits = match event_type {
|
|
591
|
+
RRTraceEventType::Call => 0x0000000000000000,
|
|
592
|
+
RRTraceEventType::Return => 0x1000000000000000,
|
|
593
|
+
RRTraceEventType::GCStart => 0x2000000000000000,
|
|
594
|
+
RRTraceEventType::GCEnd => 0x3000000000000000,
|
|
595
|
+
RRTraceEventType::ThreadStart => 0x4000000000000000,
|
|
596
|
+
RRTraceEventType::ThreadReady => 0x5000000000000000,
|
|
597
|
+
RRTraceEventType::ThreadSuspended => 0x6000000000000000,
|
|
598
|
+
RRTraceEventType::ThreadResume => 0x7000000000000000,
|
|
599
|
+
RRTraceEventType::ThreadExit => 0x8000000000000000,
|
|
600
|
+
};
|
|
601
|
+
unsafe { mem::transmute([timestamp | event_bits, data]) }
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
#[test]
|
|
605
|
+
fn slow_trace_uses_thread_id_instead_of_vector_index() {
|
|
606
|
+
let fast_trace = FastTrace {
|
|
607
|
+
thread_stacks: HashMap::from([(2, StackState::new())]),
|
|
608
|
+
initial_thread_stack: StackState::new(),
|
|
609
|
+
current_thread: ThreadId::Id(2),
|
|
610
|
+
in_gc: false,
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
let trace = SlowTrace::trace(0, &fast_trace, &[event(RRTraceEventType::Call, 10, 42)]);
|
|
614
|
+
let thread_data = trace
|
|
615
|
+
.data()
|
|
616
|
+
.iter()
|
|
617
|
+
.find(|data| data.thread_id() == 2)
|
|
618
|
+
.unwrap();
|
|
619
|
+
|
|
620
|
+
assert_eq!(thread_data.call_boxes().len(), 1);
|
|
621
|
+
assert_eq!(thread_data.call_boxes()[0].method_id, 42);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
#[test]
|
|
625
|
+
fn thread_start_does_not_switch_current_thread() {
|
|
626
|
+
let fast_trace = FastTrace {
|
|
627
|
+
thread_stacks: HashMap::from([(0, StackState::new())]),
|
|
628
|
+
initial_thread_stack: StackState::new(),
|
|
629
|
+
current_thread: ThreadId::Id(0),
|
|
630
|
+
in_gc: false,
|
|
631
|
+
};
|
|
632
|
+
|
|
633
|
+
let trace = SlowTrace::trace(
|
|
634
|
+
0,
|
|
635
|
+
&fast_trace,
|
|
636
|
+
&[
|
|
637
|
+
event(RRTraceEventType::ThreadStart, 5, 1),
|
|
638
|
+
event(RRTraceEventType::Call, 10, 42),
|
|
639
|
+
],
|
|
640
|
+
);
|
|
641
|
+
let main_thread = trace
|
|
642
|
+
.data()
|
|
643
|
+
.iter()
|
|
644
|
+
.find(|data| data.thread_id() == 0)
|
|
645
|
+
.unwrap();
|
|
646
|
+
let child_thread = trace
|
|
647
|
+
.data()
|
|
648
|
+
.iter()
|
|
649
|
+
.find(|data| data.thread_id() == 1)
|
|
650
|
+
.unwrap();
|
|
651
|
+
|
|
652
|
+
assert_eq!(main_thread.call_boxes().len(), 1);
|
|
653
|
+
assert_eq!(main_thread.call_boxes()[0].method_id, 42);
|
|
654
|
+
assert!(child_thread.call_boxes().is_empty());
|
|
655
|
+
}
|
|
656
|
+
}
|