rrtrace 0.1.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 +7 -0
- data/.github/workflows/release.yml +137 -0
- data/Cargo.lock +2477 -0
- data/Cargo.toml +23 -0
- data/LICENSE.txt +21 -0
- data/README.md +39 -0
- data/Rakefile +16 -0
- data/ext/rrtrace/extconf.rb +16 -0
- data/ext/rrtrace/process_manager_posix.h +25 -0
- data/ext/rrtrace/process_manager_windows.h +40 -0
- data/ext/rrtrace/rrtrace.c +192 -0
- data/ext/rrtrace/rrtrace.h +8 -0
- data/ext/rrtrace/rrtrace_event.h +111 -0
- data/ext/rrtrace/rrtrace_event_ringbuffer.h +45 -0
- data/ext/rrtrace/rust_build_helper.rb +41 -0
- data/ext/rrtrace/shared_memory_posix.h +26 -0
- data/ext/rrtrace/shared_memory_windows.h +34 -0
- data/lib/rrtrace/version.rb +5 -0
- data/lib/rrtrace.rb +12 -0
- data/libexec/rrtrace +0 -0
- data/mise.toml +8 -0
- data/sig/rrtrace.rbs +4 -0
- data/src/main.rs +197 -0
- data/src/renderer/vertex_arena.rs +305 -0
- data/src/renderer.rs +751 -0
- data/src/ringbuffer.rs +134 -0
- data/src/shader.wgsl +115 -0
- data/src/shm_unix.rs +47 -0
- data/src/shm_windows.rs +44 -0
- data/src/trace_state.rs +275 -0
- metadata +86 -0
data/src/ringbuffer.rs
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
use std::sync::atomic::{self, AtomicU64};
|
|
2
|
+
|
|
3
|
+
#[repr(C)]
|
|
4
|
+
#[derive(Copy, Clone, Default)]
|
|
5
|
+
pub struct RRTraceEvent {
|
|
6
|
+
timestamp_and_event_type: u64,
|
|
7
|
+
data: u64,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const EVENT_TYPE_MASK: u64 = 0xF000000000000000;
|
|
11
|
+
|
|
12
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
13
|
+
pub enum RRTraceEventType {
|
|
14
|
+
Call,
|
|
15
|
+
Return,
|
|
16
|
+
GCStart,
|
|
17
|
+
GCEnd,
|
|
18
|
+
ThreadStart,
|
|
19
|
+
ThreadReady,
|
|
20
|
+
ThreadSuspended,
|
|
21
|
+
ThreadResume,
|
|
22
|
+
ThreadExit,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
impl RRTraceEvent {
|
|
26
|
+
pub fn timestamp(&self) -> u64 {
|
|
27
|
+
self.timestamp_and_event_type & !EVENT_TYPE_MASK
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
pub fn event_type(&self) -> RRTraceEventType {
|
|
31
|
+
match self.timestamp_and_event_type & EVENT_TYPE_MASK {
|
|
32
|
+
0x0000000000000000 => RRTraceEventType::Call,
|
|
33
|
+
0x1000000000000000 => RRTraceEventType::Return,
|
|
34
|
+
0x2000000000000000 => RRTraceEventType::GCStart,
|
|
35
|
+
0x3000000000000000 => RRTraceEventType::GCEnd,
|
|
36
|
+
0x4000000000000000 => RRTraceEventType::ThreadStart,
|
|
37
|
+
0x5000000000000000 => RRTraceEventType::ThreadReady,
|
|
38
|
+
0x6000000000000000 => RRTraceEventType::ThreadSuspended,
|
|
39
|
+
0x7000000000000000 => RRTraceEventType::ThreadResume,
|
|
40
|
+
0x8000000000000000 => RRTraceEventType::ThreadExit,
|
|
41
|
+
_ => unreachable!(),
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
pub fn data(&self) -> u64 {
|
|
46
|
+
self.data
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
pub const SIZE: usize = 65_536;
|
|
51
|
+
pub const MASK: usize = SIZE - 1;
|
|
52
|
+
|
|
53
|
+
#[repr(C, align(64))]
|
|
54
|
+
struct RRTraceEventRingBufferWriter {
|
|
55
|
+
write_index: AtomicU64,
|
|
56
|
+
read_index_cache: u64,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
#[repr(C, align(64))]
|
|
60
|
+
struct RRTraceEventRingBufferReader {
|
|
61
|
+
read_index: AtomicU64,
|
|
62
|
+
write_index_cache: u64,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
#[repr(C)]
|
|
66
|
+
pub struct RRTraceEventRingBuffer {
|
|
67
|
+
buffer: [RRTraceEvent; SIZE],
|
|
68
|
+
writer: RRTraceEventRingBufferWriter,
|
|
69
|
+
reader: RRTraceEventRingBufferReader,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
impl RRTraceEventRingBuffer {
|
|
73
|
+
unsafe fn read(this: *mut Self, buffer: &mut [RRTraceEvent]) -> usize {
|
|
74
|
+
unsafe {
|
|
75
|
+
let read_index = (*this).reader.read_index.load(atomic::Ordering::Acquire);
|
|
76
|
+
let write_index = (*this).reader.write_index_cache;
|
|
77
|
+
let available = (write_index - read_index) as usize;
|
|
78
|
+
let available = if available == 0 {
|
|
79
|
+
(*this).reader.write_index_cache =
|
|
80
|
+
(*this).writer.write_index.load(atomic::Ordering::Acquire);
|
|
81
|
+
let write_index = (*this).reader.write_index_cache;
|
|
82
|
+
(write_index - read_index) as usize
|
|
83
|
+
} else {
|
|
84
|
+
available
|
|
85
|
+
};
|
|
86
|
+
let read_len = available.min(buffer.len());
|
|
87
|
+
let buffer = &mut buffer[..read_len];
|
|
88
|
+
|
|
89
|
+
let first_part_len = (&(*this).buffer)[read_index as usize & MASK..].len();
|
|
90
|
+
if read_len <= first_part_len {
|
|
91
|
+
buffer
|
|
92
|
+
.copy_from_slice(&(&(*this).buffer)[read_index as usize & MASK..][..read_len]);
|
|
93
|
+
} else {
|
|
94
|
+
buffer[..first_part_len]
|
|
95
|
+
.copy_from_slice(&(&(*this).buffer)[read_index as usize & MASK..]);
|
|
96
|
+
buffer[first_part_len..]
|
|
97
|
+
.copy_from_slice(&(&(*this).buffer)[0..read_len - first_part_len]);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
(*this)
|
|
101
|
+
.reader
|
|
102
|
+
.read_index
|
|
103
|
+
.store(read_index + read_len as u64, atomic::Ordering::Release);
|
|
104
|
+
read_len
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
pub struct EventRingBuffer {
|
|
110
|
+
ringbuffer: *mut RRTraceEventRingBuffer,
|
|
111
|
+
drop: Option<Box<dyn FnOnce()>>,
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
impl EventRingBuffer {
|
|
115
|
+
pub unsafe fn new(
|
|
116
|
+
ringbuffer: *mut RRTraceEventRingBuffer,
|
|
117
|
+
drop: impl FnOnce() + 'static,
|
|
118
|
+
) -> Self {
|
|
119
|
+
EventRingBuffer {
|
|
120
|
+
ringbuffer,
|
|
121
|
+
drop: Some(Box::new(drop)),
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
pub fn read(&mut self, buffer: &mut [RRTraceEvent]) -> usize {
|
|
126
|
+
unsafe { RRTraceEventRingBuffer::read(self.ringbuffer, buffer) }
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
impl Drop for EventRingBuffer {
|
|
131
|
+
fn drop(&mut self) {
|
|
132
|
+
self.drop.take().unwrap()();
|
|
133
|
+
}
|
|
134
|
+
}
|
data/src/shader.wgsl
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
struct Vertex {
|
|
2
|
+
@location(0) position: vec3<f32>,
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
struct CallBox {
|
|
6
|
+
@location(1) start_time: vec2<u32>,
|
|
7
|
+
@location(2) end_time: vec2<u32>,
|
|
8
|
+
@location(3) method_id: u32,
|
|
9
|
+
@location(4) depth: u32,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
struct GCBox {
|
|
13
|
+
@location(1) time: vec2<u32>,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
struct CameraUniform {
|
|
17
|
+
view_proj: mat4x4<f32>,
|
|
18
|
+
base_time: vec2<u32>, // x: lo, y: hi
|
|
19
|
+
max_depth: u32,
|
|
20
|
+
num_threads: u32,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
struct ThreadInfo {
|
|
24
|
+
lane_id: u32,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@group(0) @binding(0)
|
|
28
|
+
var<uniform> camera: CameraUniform;
|
|
29
|
+
|
|
30
|
+
@group(0) @binding(1)
|
|
31
|
+
var<uniform> thread_info: ThreadInfo;
|
|
32
|
+
|
|
33
|
+
struct VertexOutput {
|
|
34
|
+
@builtin(position) clip_position: vec4<f32>,
|
|
35
|
+
@location(0) color: vec4<f32>,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
fn sub64(a: vec2<u32>, b: vec2<u32>) -> vec2<u32> {
|
|
39
|
+
if (a.x < b.x) {
|
|
40
|
+
let lo = a.x + 0x80000000u - b.x;
|
|
41
|
+
let hi = a.y - 1 - b.y;
|
|
42
|
+
return vec2<u32>(lo, hi);
|
|
43
|
+
} else {
|
|
44
|
+
let lo = a.x - b.x;
|
|
45
|
+
let hi = a.y - b.y;
|
|
46
|
+
return vec2<u32>(lo, hi);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
fn u64tof32(v: vec2<u32>) -> f32 {
|
|
51
|
+
return f32(v.y) * 2147483648.0 + f32(v.x);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
fn get_color(method_id: u32) -> vec4<f32> {
|
|
55
|
+
let m = method_id;
|
|
56
|
+
let r = f32((m * 123u) % 255u) / 255.0;
|
|
57
|
+
let g = f32((m * 456u) % 255u) / 255.0;
|
|
58
|
+
let b = f32((m * 789u) % 255u) / 255.0;
|
|
59
|
+
return vec4<f32>(r, g, b, 1.0);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@vertex
|
|
63
|
+
fn vs_main(
|
|
64
|
+
v: Vertex,
|
|
65
|
+
call: CallBox,
|
|
66
|
+
) -> VertexOutput {
|
|
67
|
+
var end_time: vec2<u32>;
|
|
68
|
+
if (call.end_time.y == 0xffffffffu) {
|
|
69
|
+
end_time = vec2<u32>(0, 0);
|
|
70
|
+
} else {
|
|
71
|
+
end_time = sub64(camera.base_time, call.end_time);
|
|
72
|
+
}
|
|
73
|
+
let start_time = sub64(camera.base_time, call.start_time);
|
|
74
|
+
|
|
75
|
+
let x = select(start_time, end_time, v.position.x > 0.5);
|
|
76
|
+
|
|
77
|
+
let world_pos = vec3<f32>(
|
|
78
|
+
u64tof32(x) / 500000000.0,
|
|
79
|
+
(f32(call.depth) + v.position.y) / f32(camera.max_depth),
|
|
80
|
+
(f32(thread_info.lane_id) + v.position.z) / f32(camera.num_threads),
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
var out: VertexOutput;
|
|
84
|
+
out.color = get_color(call.method_id);
|
|
85
|
+
out.clip_position = camera.view_proj * vec4<f32>(world_pos, 1.0);
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@fragment
|
|
90
|
+
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
|
91
|
+
return in.color;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
struct GCVertex {
|
|
95
|
+
@location(0) position: vec2<f32>,
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@vertex
|
|
99
|
+
fn vs_gc(
|
|
100
|
+
v: GCVertex,
|
|
101
|
+
gc: GCBox,
|
|
102
|
+
) -> VertexOutput {
|
|
103
|
+
let time = sub64(camera.base_time, gc.time);
|
|
104
|
+
|
|
105
|
+
let world_pos = vec3<f32>(
|
|
106
|
+
u64tof32(time) / 500000000.0,
|
|
107
|
+
v.position.x,
|
|
108
|
+
v.position.y,
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
var out: VertexOutput;
|
|
112
|
+
out.color = vec4<f32>(1.0, 0.5, 0.0, 0.1);
|
|
113
|
+
out.clip_position = camera.view_proj * vec4<f32>(world_pos, 1.0);
|
|
114
|
+
return out;
|
|
115
|
+
}
|
data/src/shm_unix.rs
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
use std::ffi::CString;
|
|
2
|
+
|
|
3
|
+
pub struct SharedMemory {
|
|
4
|
+
ptr: *mut u8,
|
|
5
|
+
name: CString,
|
|
6
|
+
size: usize,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
impl SharedMemory {
|
|
10
|
+
pub unsafe fn open(name: CString, size: usize) -> SharedMemory {
|
|
11
|
+
let fd = unsafe { libc::shm_open(name.as_ptr(), libc::O_RDWR, 0) };
|
|
12
|
+
if fd < 0 {
|
|
13
|
+
panic!("shm_open failed");
|
|
14
|
+
}
|
|
15
|
+
let memory = unsafe {
|
|
16
|
+
libc::mmap(
|
|
17
|
+
std::ptr::null_mut(),
|
|
18
|
+
size,
|
|
19
|
+
libc::PROT_READ | libc::PROT_WRITE,
|
|
20
|
+
libc::MAP_SHARED,
|
|
21
|
+
fd,
|
|
22
|
+
0,
|
|
23
|
+
)
|
|
24
|
+
};
|
|
25
|
+
if memory == libc::MAP_FAILED {
|
|
26
|
+
panic!("mmap failed");
|
|
27
|
+
}
|
|
28
|
+
SharedMemory {
|
|
29
|
+
ptr: memory as *mut u8,
|
|
30
|
+
name,
|
|
31
|
+
size,
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
pub fn as_ptr<T>(&self) -> *mut T {
|
|
36
|
+
self.ptr.cast()
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
impl Drop for SharedMemory {
|
|
41
|
+
fn drop(&mut self) {
|
|
42
|
+
unsafe {
|
|
43
|
+
libc::munmap(self.ptr as *mut libc::c_void, self.size);
|
|
44
|
+
libc::shm_unlink(self.name.as_ptr());
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
data/src/shm_windows.rs
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
use std::ffi::CString;
|
|
2
|
+
use windows_sys::Win32::Foundation::{CloseHandle, GetLastError, HANDLE};
|
|
3
|
+
use windows_sys::Win32::System::Memory::{
|
|
4
|
+
FILE_MAP_ALL_ACCESS, MEMORY_MAPPED_VIEW_ADDRESS, MapViewOfFile, OpenFileMappingA,
|
|
5
|
+
UnmapViewOfFile,
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
pub struct SharedMemory {
|
|
9
|
+
ptr: MEMORY_MAPPED_VIEW_ADDRESS,
|
|
10
|
+
handle: HANDLE,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
impl SharedMemory {
|
|
14
|
+
pub unsafe fn open(name: CString, size: usize) -> SharedMemory {
|
|
15
|
+
let handle =
|
|
16
|
+
unsafe { OpenFileMappingA(FILE_MAP_ALL_ACCESS, 0, name.as_ptr() as *const u8) };
|
|
17
|
+
if handle.is_null() {
|
|
18
|
+
panic!("OpenFileMappingA failed with error {}", unsafe {
|
|
19
|
+
GetLastError()
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let ptr = unsafe { MapViewOfFile(handle, FILE_MAP_ALL_ACCESS, 0, 0, size) };
|
|
24
|
+
if ptr.Value.is_null() {
|
|
25
|
+
unsafe { CloseHandle(handle) };
|
|
26
|
+
panic!("MapViewOfFile failed");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
SharedMemory { ptr, handle }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
pub fn as_ptr<T>(&self) -> *mut T {
|
|
33
|
+
self.ptr.Value.cast()
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
impl Drop for SharedMemory {
|
|
38
|
+
fn drop(&mut self) {
|
|
39
|
+
unsafe {
|
|
40
|
+
UnmapViewOfFile(self.ptr);
|
|
41
|
+
CloseHandle(self.handle);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
data/src/trace_state.rs
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
use crate::ringbuffer::{RRTraceEvent, RRTraceEventType};
|
|
2
|
+
use smallvec::SmallVec;
|
|
3
|
+
use std::convert;
|
|
4
|
+
use std::fmt::Debug;
|
|
5
|
+
|
|
6
|
+
#[repr(C)]
|
|
7
|
+
#[derive(Copy, Default, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
|
|
8
|
+
pub struct CallBox {
|
|
9
|
+
start_time: [u32; 2],
|
|
10
|
+
end_time: [u32; 2],
|
|
11
|
+
method_id: u32,
|
|
12
|
+
depth: u32,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
pub const VISIBLE_DURATION: u64 = 1_000_000_000 * 5;
|
|
16
|
+
|
|
17
|
+
pub fn encode_time(time: u64) -> [u32; 2] {
|
|
18
|
+
[
|
|
19
|
+
(time & 0x7fffffff) as u32,
|
|
20
|
+
((time >> 31) & 0xffffffff) as u32,
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
#[derive(Debug, Clone)]
|
|
25
|
+
pub struct FastTrace {
|
|
26
|
+
thread_stacks: SmallVec<[(u32, SmallVec<[u64; 4]>); 1]>,
|
|
27
|
+
current_thread: u32,
|
|
28
|
+
in_gc: bool,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
impl FastTrace {
|
|
32
|
+
pub fn new() -> FastTrace {
|
|
33
|
+
FastTrace {
|
|
34
|
+
thread_stacks: smallvec::smallvec![(0, SmallVec::new())],
|
|
35
|
+
current_thread: 0,
|
|
36
|
+
in_gc: false,
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
pub fn process_events(&mut self, events: &[RRTraceEvent]) {
|
|
40
|
+
for &event in events {
|
|
41
|
+
match event.event_type() {
|
|
42
|
+
RRTraceEventType::Call => {
|
|
43
|
+
let method_id = event.data();
|
|
44
|
+
self.thread_stacks[self.current_thread as usize]
|
|
45
|
+
.1
|
|
46
|
+
.push(method_id);
|
|
47
|
+
}
|
|
48
|
+
RRTraceEventType::Return => {
|
|
49
|
+
let method_id = event.data();
|
|
50
|
+
let stack = &mut self.thread_stacks[self.current_thread as usize].1;
|
|
51
|
+
while stack.pop().is_some_and(|m| m != method_id) {}
|
|
52
|
+
}
|
|
53
|
+
RRTraceEventType::ThreadSuspended => {
|
|
54
|
+
self.current_thread = u32::MAX;
|
|
55
|
+
}
|
|
56
|
+
RRTraceEventType::ThreadResume => {
|
|
57
|
+
let thread_id = event.data() as u32;
|
|
58
|
+
let index = match self
|
|
59
|
+
.thread_stacks
|
|
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;
|
|
70
|
+
}
|
|
71
|
+
RRTraceEventType::ThreadExit => {
|
|
72
|
+
let thread_id = event.data() as u32;
|
|
73
|
+
if let Ok(index) = self
|
|
74
|
+
.thread_stacks
|
|
75
|
+
.binary_search_by_key(&thread_id, |&(thread_id, _)| thread_id)
|
|
76
|
+
{
|
|
77
|
+
self.thread_stacks.remove(index);
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
RRTraceEventType::GCStart
|
|
81
|
+
| RRTraceEventType::GCEnd
|
|
82
|
+
| RRTraceEventType::ThreadStart
|
|
83
|
+
| RRTraceEventType::ThreadReady => {}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
self.in_gc = events.last().unwrap().event_type() == RRTraceEventType::GCStart;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
#[derive(Debug, Clone)]
|
|
91
|
+
struct CallStackEntry {
|
|
92
|
+
vertex_index: usize,
|
|
93
|
+
method_id: u64,
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
pub struct SlowTrace {
|
|
97
|
+
data: Vec<(u32, Vec<CallStackEntry>, Vec<CallBox>)>,
|
|
98
|
+
max_depth: u32,
|
|
99
|
+
end_time: u64,
|
|
100
|
+
gc_events: Vec<u64>,
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
impl SlowTrace {
|
|
104
|
+
pub fn trace(start_time: u64, fast_trace: FastTrace, events: &[RRTraceEvent]) -> SlowTrace {
|
|
105
|
+
let end_time = events.last().unwrap().timestamp();
|
|
106
|
+
let mut max_depth = 0;
|
|
107
|
+
let FastTrace {
|
|
108
|
+
thread_stacks,
|
|
109
|
+
current_thread,
|
|
110
|
+
in_gc,
|
|
111
|
+
} = fast_trace;
|
|
112
|
+
let mut gc_events = Vec::new();
|
|
113
|
+
let mut call_stack = thread_stacks
|
|
114
|
+
.into_iter()
|
|
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
|
+
)
|
|
127
|
+
})
|
|
128
|
+
.collect::<Vec<_>>();
|
|
129
|
+
if !in_gc && let Some((_, stack, vertices)) = call_stack.get_mut(current_thread as usize) {
|
|
130
|
+
vertices.reserve(stack.len());
|
|
131
|
+
for (depth, entry) in stack.iter_mut().enumerate() {
|
|
132
|
+
entry.vertex_index = vertices.len();
|
|
133
|
+
let depth = depth as u32;
|
|
134
|
+
max_depth = max_depth.max(depth);
|
|
135
|
+
vertices.push(CallBox {
|
|
136
|
+
start_time: encode_time(start_time),
|
|
137
|
+
end_time: encode_time(end_time),
|
|
138
|
+
method_id: entry.method_id as u32,
|
|
139
|
+
depth,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
let mut null_vec1 = Vec::new();
|
|
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
|
+
});
|
|
150
|
+
for event in events {
|
|
151
|
+
match event.event_type() {
|
|
152
|
+
RRTraceEventType::Call => {
|
|
153
|
+
let vertex_index = current_vertices.len();
|
|
154
|
+
let depth = current_stack.len() as u32;
|
|
155
|
+
current_stack.push(CallStackEntry {
|
|
156
|
+
vertex_index,
|
|
157
|
+
method_id: event.data(),
|
|
158
|
+
});
|
|
159
|
+
current_vertices.push(CallBox {
|
|
160
|
+
start_time: encode_time(event.timestamp()),
|
|
161
|
+
end_time: encode_time(end_time),
|
|
162
|
+
method_id: event.data() as u32,
|
|
163
|
+
depth,
|
|
164
|
+
});
|
|
165
|
+
max_depth = max_depth.max(depth);
|
|
166
|
+
}
|
|
167
|
+
RRTraceEventType::Return => {
|
|
168
|
+
while let Some(CallStackEntry {
|
|
169
|
+
vertex_index,
|
|
170
|
+
method_id,
|
|
171
|
+
}) = current_stack.pop()
|
|
172
|
+
{
|
|
173
|
+
current_vertices[vertex_index].end_time = encode_time(event.timestamp());
|
|
174
|
+
if method_id == event.data() {
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
RRTraceEventType::GCStart => {
|
|
180
|
+
gc_events.push(event.timestamp());
|
|
181
|
+
for CallStackEntry { vertex_index, .. } in current_stack.iter_mut() {
|
|
182
|
+
current_vertices[*vertex_index].end_time = encode_time(event.timestamp());
|
|
183
|
+
*vertex_index = usize::MAX;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
RRTraceEventType::GCEnd => {
|
|
187
|
+
gc_events.push(event.timestamp());
|
|
188
|
+
for (
|
|
189
|
+
depth,
|
|
190
|
+
&mut CallStackEntry {
|
|
191
|
+
ref mut vertex_index,
|
|
192
|
+
method_id,
|
|
193
|
+
},
|
|
194
|
+
) in current_stack.iter_mut().enumerate()
|
|
195
|
+
{
|
|
196
|
+
let new_index = current_vertices.len();
|
|
197
|
+
let depth = depth as u32;
|
|
198
|
+
current_vertices.push(CallBox {
|
|
199
|
+
start_time: encode_time(event.timestamp()),
|
|
200
|
+
end_time: encode_time(end_time),
|
|
201
|
+
method_id: method_id as u32,
|
|
202
|
+
depth,
|
|
203
|
+
});
|
|
204
|
+
max_depth = max_depth.max(depth);
|
|
205
|
+
*vertex_index = new_index;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
RRTraceEventType::ThreadSuspended => {
|
|
209
|
+
for CallStackEntry { vertex_index, .. } in current_stack.iter_mut() {
|
|
210
|
+
current_vertices[*vertex_index].end_time = encode_time(event.timestamp());
|
|
211
|
+
*vertex_index = usize::MAX;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
RRTraceEventType::ThreadResume => {
|
|
215
|
+
let thread_id = event.data() as u32;
|
|
216
|
+
let index = call_stack.binary_search_by_key(&thread_id, |&(tid, _, _)| tid);
|
|
217
|
+
if let Err(i) = index {
|
|
218
|
+
call_stack.insert(i, (thread_id, Vec::new(), Vec::new()));
|
|
219
|
+
}
|
|
220
|
+
let (_, new_stack, new_vertices) =
|
|
221
|
+
&mut call_stack[index.unwrap_or_else(convert::identity)];
|
|
222
|
+
|
|
223
|
+
for (
|
|
224
|
+
depth,
|
|
225
|
+
&mut CallStackEntry {
|
|
226
|
+
ref mut vertex_index,
|
|
227
|
+
method_id,
|
|
228
|
+
},
|
|
229
|
+
) in new_stack.iter_mut().enumerate()
|
|
230
|
+
{
|
|
231
|
+
let new_index = new_vertices.len();
|
|
232
|
+
let depth = depth as u32;
|
|
233
|
+
new_vertices.push(CallBox {
|
|
234
|
+
start_time: encode_time(event.timestamp()),
|
|
235
|
+
end_time: encode_time(end_time),
|
|
236
|
+
method_id: method_id as u32,
|
|
237
|
+
depth,
|
|
238
|
+
});
|
|
239
|
+
max_depth = max_depth.max(depth);
|
|
240
|
+
*vertex_index = new_index;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
(current_stack, current_vertices) = (new_stack, new_vertices);
|
|
244
|
+
}
|
|
245
|
+
RRTraceEventType::ThreadExit
|
|
246
|
+
| RRTraceEventType::ThreadStart
|
|
247
|
+
| RRTraceEventType::ThreadReady => {}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
SlowTrace {
|
|
251
|
+
data: call_stack,
|
|
252
|
+
max_depth,
|
|
253
|
+
end_time,
|
|
254
|
+
gc_events,
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
pub fn data(&self) -> impl Iterator<Item = (u32, &[CallBox])> {
|
|
259
|
+
self.data
|
|
260
|
+
.iter()
|
|
261
|
+
.map(|&(thread_id, _, ref call_box)| (thread_id, call_box.as_slice()))
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
pub fn gc_events(&self) -> &[u64] {
|
|
265
|
+
&self.gc_events
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
pub fn end_time(&self) -> u64 {
|
|
269
|
+
self.end_time
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
pub fn max_depth(&self) -> u32 {
|
|
273
|
+
self.max_depth
|
|
274
|
+
}
|
|
275
|
+
}
|
metadata
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rrtrace
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- White-Green
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: rake-compiler
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :development
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
description: Rrtrace is a profiling tool that captures Ruby method calls and visualizes
|
|
27
|
+
them using a high-performance Rust renderer.
|
|
28
|
+
email:
|
|
29
|
+
- 43771790+White-Green@users.noreply.github.com
|
|
30
|
+
executables: []
|
|
31
|
+
extensions:
|
|
32
|
+
- ext/rrtrace/extconf.rb
|
|
33
|
+
extra_rdoc_files: []
|
|
34
|
+
files:
|
|
35
|
+
- ".github/workflows/release.yml"
|
|
36
|
+
- Cargo.lock
|
|
37
|
+
- Cargo.toml
|
|
38
|
+
- LICENSE.txt
|
|
39
|
+
- README.md
|
|
40
|
+
- Rakefile
|
|
41
|
+
- ext/rrtrace/extconf.rb
|
|
42
|
+
- ext/rrtrace/process_manager_posix.h
|
|
43
|
+
- ext/rrtrace/process_manager_windows.h
|
|
44
|
+
- ext/rrtrace/rrtrace.c
|
|
45
|
+
- ext/rrtrace/rrtrace.h
|
|
46
|
+
- ext/rrtrace/rrtrace_event.h
|
|
47
|
+
- ext/rrtrace/rrtrace_event_ringbuffer.h
|
|
48
|
+
- ext/rrtrace/rust_build_helper.rb
|
|
49
|
+
- ext/rrtrace/shared_memory_posix.h
|
|
50
|
+
- ext/rrtrace/shared_memory_windows.h
|
|
51
|
+
- lib/rrtrace.rb
|
|
52
|
+
- lib/rrtrace/version.rb
|
|
53
|
+
- libexec/rrtrace
|
|
54
|
+
- mise.toml
|
|
55
|
+
- sig/rrtrace.rbs
|
|
56
|
+
- src/main.rs
|
|
57
|
+
- src/renderer.rs
|
|
58
|
+
- src/renderer/vertex_arena.rs
|
|
59
|
+
- src/ringbuffer.rs
|
|
60
|
+
- src/shader.wgsl
|
|
61
|
+
- src/shm_unix.rs
|
|
62
|
+
- src/shm_windows.rs
|
|
63
|
+
- src/trace_state.rs
|
|
64
|
+
homepage: https://github.com/White-Green/rrtrace
|
|
65
|
+
licenses:
|
|
66
|
+
- MIT
|
|
67
|
+
metadata:
|
|
68
|
+
homepage_uri: https://github.com/White-Green/rrtrace
|
|
69
|
+
rdoc_options: []
|
|
70
|
+
require_paths:
|
|
71
|
+
- lib
|
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
73
|
+
requirements:
|
|
74
|
+
- - ">="
|
|
75
|
+
- !ruby/object:Gem::Version
|
|
76
|
+
version: 3.2.0
|
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0'
|
|
82
|
+
requirements: []
|
|
83
|
+
rubygems_version: 4.0.3
|
|
84
|
+
specification_version: 4
|
|
85
|
+
summary: A Ruby trace tool with Rust-based visualizer
|
|
86
|
+
test_files: []
|