better already

This commit is contained in:
2025-12-11 06:40:15 +01:00
parent 4930d86e23
commit 06b8cac896
3 changed files with 362 additions and 66 deletions

View File

@@ -2,6 +2,7 @@ use minifb::{Key, Window, WindowOptions};
use std::thread; use std::thread;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::sync::atomic::{AtomicUsize, Ordering};
use rand::Rng; use rand::Rng;
use teleprof::instrument; use teleprof::instrument;
@@ -9,6 +10,8 @@ const WIDTH: usize = 800;
const HEIGHT: usize = 600; const HEIGHT: usize = 600;
const BALL_RADIUS: usize = 20; const BALL_RADIUS: usize = 20;
static COLOR_PICKER_COUNTER: AtomicUsize = AtomicUsize::new(0);
struct Ball { struct Ball {
x: f32, x: f32,
y: f32, y: f32,
@@ -94,8 +97,9 @@ fn main_frame(
// If we hit a wall, spawn a thread to pick a new color // If we hit a wall, spawn a thread to pick a new color
if hit_wall { if hit_wall {
let ball_clone = Arc::clone(ball); let ball_clone = Arc::clone(ball);
let id = COLOR_PICKER_COUNTER.fetch_add(1, Ordering::Relaxed);
thread::spawn(move || { thread::spawn(move || {
teleprof::set_thread_name("ColorPicker"); teleprof::set_thread_name(format!("ColorPicker-{}", id));
pick_new_color(ball_clone); pick_new_color(ball_clone);
}); });
} }

View File

@@ -5,6 +5,9 @@ use teleprof::instrument;
fn main() { fn main() {
// Start the telemetry window // Start the telemetry window
teleprof::start(); teleprof::start();
// Name the main thread
teleprof::set_thread_name("Main");
println!("Teleprof demo running..."); println!("Teleprof demo running...");
println!("Press Space in the profiler window to pause/unpause"); println!("Press Space in the profiler window to pause/unpause");
@@ -44,6 +47,7 @@ fn physics_update() {
// Spawn some worker threads // Spawn some worker threads
let handles: Vec<_> = (0..3).map(|i| { let handles: Vec<_> = (0..3).map(|i| {
thread::spawn(move || { thread::spawn(move || {
teleprof::set_thread_name(format!("Physics-{}", i));
physics_worker(i); physics_worker(i);
}) })
}).collect(); }).collect();

View File

@@ -144,15 +144,14 @@ mod window {
const MAX_EVENTS: usize = 1_000_000; const MAX_EVENTS: usize = 1_000_000;
// Monokai palette // Monokai palette
const COLORS: [u32; 8] = [ const COLORS: [u32; 7] = [
0x75715E, // Gray
0xF92672, // Pink 0xF92672, // Pink
0xA6E22E, // Green 0xA6E22E, // Green
0xFD971F, // Orange 0xFD971F, // Orange
0x66D9EF, // Cyan 0x66D9EF, // Cyan
0xAE81FF, // Purple 0xAE81FF, // Purple
0xE6DB74, // Yellow 0xE6DB74, // Yellow
0xF8F8F2, // White
0x75715E, // Gray
]; ];
const BG_COLOR: u32 = 0x272822; const BG_COLOR: u32 = 0x272822;
@@ -230,6 +229,10 @@ mod window {
timeline_time_offset: f64, timeline_time_offset: f64,
timeline_time_scale: f64, timeline_time_scale: f64,
// Vertical scroll
icicle_scroll_y: f32,
timeline_scroll_y: f32,
// Mouse state // Mouse state
mouse_down: bool, mouse_down: bool,
last_mouse_x: f32, last_mouse_x: f32,
@@ -238,10 +241,25 @@ mod window {
mouse_x: f32, mouse_x: f32,
mouse_y: f32, mouse_y: f32,
// Box selection for zoom
selecting: bool,
selection_start_x: f32,
selection_end_x: f32,
// Hover state
hovered_span: Option<HoveredSpan>,
// Pause state // Pause state
paused: bool, paused: bool,
pause_guard: Option<std::sync::MutexGuard<'static, ()>>, pause_guard: Option<std::sync::MutexGuard<'static, ()>>,
} }
struct HoveredSpan {
name: &'static str,
duration_us: f64,
thread_id: u64,
start_time: f64,
}
impl ViewState { impl ViewState {
fn new() -> Self { fn new() -> Self {
@@ -250,12 +268,18 @@ mod window {
icicle_time_scale: 100.0, icicle_time_scale: 100.0,
timeline_time_offset: 0.0, timeline_time_offset: 0.0,
timeline_time_scale: 100.0, timeline_time_scale: 100.0,
icicle_scroll_y: 0.0,
timeline_scroll_y: 0.0,
mouse_down: false, mouse_down: false,
last_mouse_x: 0.0, last_mouse_x: 0.0,
last_mouse_y: 0.0, last_mouse_y: 0.0,
mouse_visible: false, mouse_visible: false,
mouse_x: 0.0, mouse_x: 0.0,
mouse_y: 0.0, mouse_y: 0.0,
selecting: false,
selection_start_x: 0.0,
selection_end_x: 0.0,
hovered_span: None,
paused: false, paused: false,
pause_guard: None, pause_guard: None,
} }
@@ -291,8 +315,16 @@ mod window {
let (width, height) = window.get_size(); let (width, height) = window.get_size();
// Collect spans for this frame
let spans: Vec<_> = buffer.iter().collect();
let earliest = if !spans.is_empty() {
spans.iter().map(|s| s.start).min().unwrap()
} else {
Instant::now()
};
// Handle input // Handle input
handle_input(&mut window, &mut view, width, height); handle_input(&mut window, &mut view, &buffer, earliest, width, height);
// Resize framebuffer if needed // Resize framebuffer if needed
if framebuffer.len() != width * height { if framebuffer.len() != width * height {
@@ -310,7 +342,7 @@ mod window {
Ok(()) Ok(())
} }
fn handle_input(window: &mut Window, view: &mut ViewState, _width: usize, height: usize) { fn handle_input(window: &mut Window, view: &mut ViewState, buffer: &RingBuffer, earliest: Instant, _width: usize, height: usize) {
// Pause/unpause // Pause/unpause
if window.is_key_pressed(Key::Space, minifb::KeyRepeat::No) { if window.is_key_pressed(Key::Space, minifb::KeyRepeat::No) {
view.paused = !view.paused; view.paused = !view.paused;
@@ -328,47 +360,128 @@ mod window {
view.mouse_y = my; view.mouse_y = my;
let icicle_height = height / 2; let icicle_height = height / 2;
let _is_icicle = my < icicle_height as f32; let is_icicle = my < icicle_height as f32;
// Handle mouse wheel (zoom) // Check if shift is pressed (for timeline zoom)
let shift_pressed = window.is_key_down(Key::LeftShift) || window.is_key_down(Key::RightShift);
// Handle mouse wheel
if let Some((_, scroll_y)) = window.get_scroll_wheel() { if let Some((_, scroll_y)) = window.get_scroll_wheel() {
if scroll_y != 0.0 { if scroll_y != 0.0 {
let zoom_factor = if scroll_y > 0.0 { 1.2 } else { 1.0 / 1.2 }; if shift_pressed && !is_icicle {
// Shift + scroll in timeline = zoom timeline only
// Synchronize both views - zoom changes scale for both let zoom_factor = if scroll_y > 0.0 { 1.2 } else { 1.0 / 1.2 };
let old_scale = view.icicle_time_scale; let old_scale = view.timeline_time_scale;
let new_scale = old_scale * zoom_factor; let new_scale = old_scale * zoom_factor;
let mouse_time = view.timeline_time_offset + (mx as f64 / old_scale);
// Calculate mouse time in world space (same for both views since synchronized)
let mouse_time = view.icicle_time_offset + (mx as f64 / old_scale); view.timeline_time_scale = new_scale;
view.timeline_time_offset = mouse_time - (mx as f64 / new_scale);
// Update scale and adjust offset to keep mouse position stable
view.icicle_time_scale = new_scale; // Also update icicle to match
view.timeline_time_scale = new_scale; view.icicle_time_scale = new_scale;
view.icicle_time_offset = view.timeline_time_offset;
let new_offset = mouse_time - (mx as f64 / new_scale); } else if !shift_pressed && scroll_y.abs() > 0.5 {
view.icicle_time_offset = new_offset; // Regular scroll = zoom both views (horizontal)
view.timeline_time_offset = new_offset; let zoom_factor = if scroll_y > 0.0 { 1.2 } else { 1.0 / 1.2 };
let old_scale = view.icicle_time_scale;
let new_scale = old_scale * zoom_factor;
let mouse_time = view.icicle_time_offset + (mx as f64 / old_scale);
view.icicle_time_scale = new_scale;
view.timeline_time_scale = new_scale;
let new_offset = mouse_time - (mx as f64 / new_scale);
view.icicle_time_offset = new_offset;
view.timeline_time_offset = new_offset;
} else {
// Vertical scroll
let scroll_amount = scroll_y * 20.0;
if is_icicle {
view.icicle_scroll_y = (view.icicle_scroll_y - scroll_amount).max(0.0);
} else {
view.timeline_scroll_y = (view.timeline_scroll_y - scroll_amount).max(0.0);
}
}
} }
} }
// Handle mouse drag (pan) // Handle mouse buttons
let mouse_down = window.get_mouse_down(MouseButton::Left); let left_down = window.get_mouse_down(MouseButton::Left);
let right_down = window.get_mouse_down(MouseButton::Right);
if mouse_down && view.mouse_down { // Right click = pan
if right_down && view.mouse_down {
let dx = mx - view.last_mouse_x; let dx = mx - view.last_mouse_x;
// Pan both views together (synchronized)
let delta = dx as f64 / view.icicle_time_scale; let delta = dx as f64 / view.icicle_time_scale;
view.icicle_time_offset -= delta; view.icicle_time_offset -= delta;
view.timeline_time_offset -= delta; view.timeline_time_offset -= delta;
} }
view.mouse_down = mouse_down; // Left click = box selection for zoom
if left_down && !view.selecting && !view.mouse_down {
view.selecting = true;
view.selection_start_x = mx;
view.selection_end_x = mx;
} else if left_down && view.selecting {
view.selection_end_x = mx;
} else if !left_down && view.selecting {
// Complete selection - zoom to selected region
let x1 = view.selection_start_x.min(view.selection_end_x);
let x2 = view.selection_start_x.max(view.selection_end_x);
if (x2 - x1) > 5.0 { // Minimum selection size
let time1 = view.icicle_time_offset + (x1 as f64 / view.icicle_time_scale);
let time2 = view.icicle_time_offset + (x2 as f64 / view.icicle_time_scale);
let time_range = time2 - time1;
// Calculate new scale to fit selection in view
let new_scale = window.get_size().0 as f64 / time_range;
view.icicle_time_scale = new_scale;
view.timeline_time_scale = new_scale;
view.icicle_time_offset = time1;
view.timeline_time_offset = time1;
}
view.selecting = false;
}
// Update hover detection
update_hover(view, buffer, earliest, mx, my, icicle_height);
view.mouse_down = left_down || right_down;
view.last_mouse_x = mx; view.last_mouse_x = mx;
view.last_mouse_y = my; view.last_mouse_y = my;
} else { } else {
view.mouse_visible = false; view.mouse_visible = false;
view.hovered_span = None;
}
}
fn update_hover(view: &mut ViewState, buffer: &RingBuffer, earliest: Instant, mx: f32, my: f32, icicle_height: usize) {
view.hovered_span = None;
if my >= icicle_height as f32 {
return; // Only hover in icicle view for now
}
let mouse_time = view.icicle_time_offset + (mx as f64 / view.icicle_time_scale);
// Find span under cursor
for span in buffer.iter() {
let start_time = (span.start - earliest).as_secs_f64();
let end_time = (span.end - earliest).as_secs_f64();
if mouse_time >= start_time && mouse_time <= end_time {
let duration_us = (end_time - start_time) * 1_000_000.0;
view.hovered_span = Some(HoveredSpan {
name: span.name,
duration_us,
thread_id: span.thread_id,
start_time,
});
break; // Take first match (could be improved to find best match by y-position)
}
} }
} }
@@ -412,31 +525,189 @@ mod window {
font, font,
); );
// Draw cursor // Draw timestamp axis at the bottom of icicle view
if view.mouse_visible { draw_timestamp_axis(framebuffer, width, icicle_height - 20, view, earliest, font);
let cursor_x = view.mouse_x as usize;
// Draw selection box if selecting
if view.selecting {
let x1 = view.selection_start_x.min(view.selection_end_x) as usize;
let x2 = view.selection_start_x.max(view.selection_end_x) as usize;
let selection_color = 0x4080FF; // Semi-transparent blue
// Draw vertical line
for y in 0..height { for y in 0..height {
if cursor_x < width { for x in x1..x2.min(width) {
let idx = y * width + cursor_x; let idx = y * width + x;
if idx < framebuffer.len() { if idx < framebuffer.len() {
// Make cursor semi-transparent by blending
let bg = framebuffer[idx]; let bg = framebuffer[idx];
let bg_r = ((bg >> 16) & 0xFF) as u32; let bg_r = ((bg >> 16) & 0xFF) as f32;
let bg_g = ((bg >> 8) & 0xFF) as u32; let bg_g = ((bg >> 8) & 0xFF) as f32;
let bg_b = (bg & 0xFF) as u32; let bg_b = (bg & 0xFF) as f32;
let alpha = 0.5; let sel_r = ((selection_color >> 16) & 0xFF) as f32;
let r = (bg_r as f32 * (1.0 - alpha) + 255.0 * alpha) as u32; let sel_g = ((selection_color >> 8) & 0xFF) as f32;
let g = (bg_g as f32 * (1.0 - alpha) + 255.0 * alpha) as u32; let sel_b = (selection_color & 0xFF) as f32;
let b = (bg_b as f32 * (1.0 - alpha) + 255.0 * alpha) as u32;
let alpha = 0.3;
let r = (bg_r * (1.0 - alpha) + sel_r * alpha) as u32;
let g = (bg_g * (1.0 - alpha) + sel_g * alpha) as u32;
let b = (bg_b * (1.0 - alpha) + sel_b * alpha) as u32;
framebuffer[idx] = (r << 16) | (g << 8) | b; framebuffer[idx] = (r << 16) | (g << 8) | b;
} }
} }
} }
} }
// Draw crosshair cursor
if view.mouse_visible && !view.selecting {
let cursor_x = view.mouse_x as usize;
let cursor_y = view.mouse_y as usize;
// Vertical line
for y in 0..height {
if cursor_x < width {
let idx = y * width + cursor_x;
if idx < framebuffer.len() {
framebuffer[idx] = blend_cursor(framebuffer[idx]);
}
}
}
// Horizontal line
if cursor_y < height {
for x in 0..width {
let idx = cursor_y * width + x;
if idx < framebuffer.len() {
framebuffer[idx] = blend_cursor(framebuffer[idx]);
}
}
}
}
// Draw hover tooltip
if let Some(ref hover) = view.hovered_span {
draw_tooltip(framebuffer, width, height, view, hover, font);
}
}
fn blend_cursor(bg: u32) -> u32 {
let bg_r = ((bg >> 16) & 0xFF) as f32;
let bg_g = ((bg >> 8) & 0xFF) as f32;
let bg_b = (bg & 0xFF) as f32;
let alpha = 0.6;
let r = (bg_r * (1.0 - alpha) + 255.0 * alpha) as u32;
let g = (bg_g * (1.0 - alpha) + 255.0 * alpha) as u32;
let b = (bg_b * (1.0 - alpha) + 255.0 * alpha) as u32;
(r << 16) | (g << 8) | b
}
fn draw_tooltip(
framebuffer: &mut [u32],
width: usize,
height: usize,
view: &ViewState,
hover: &HoveredSpan,
font: &fontdue::Font,
) {
let tooltip_w = 280;
let tooltip_h = 80;
let padding = 8;
// Position tooltip near cursor but keep it on screen
let tooltip_x = (view.mouse_x as usize + 20).min(width.saturating_sub(tooltip_w + 10));
let tooltip_y = (view.mouse_y as usize + 20).min(height.saturating_sub(tooltip_h + 10));
// Draw tooltip background
for dy in 0..tooltip_h {
for dx in 0..tooltip_w {
let x = tooltip_x + dx;
let y = tooltip_y + dy;
if x < width && y < height {
let idx = y * width + x;
if idx < framebuffer.len() {
// Dark background with border
let color = if dx == 0 || dy == 0 || dx == tooltip_w - 1 || dy == tooltip_h - 1 {
0x808080 // Border
} else {
0x1E1E1E // Background
};
framebuffer[idx] = color;
}
}
}
}
// Draw text
let text_x = tooltip_x + padding;
let text_y = tooltip_y + padding;
draw_text(framebuffer, width, text_x, text_y, hover.name, font, 14.0, 0xFFFFFF);
draw_text(framebuffer, width, text_x, text_y + 20,
&format!("Duration: {:.2} μs", hover.duration_us), font, 12.0, 0xCCCCCC);
draw_text(framebuffer, width, text_x, text_y + 38,
&format!("Thread: {}", hover.thread_id), font, 12.0, 0xCCCCCC);
draw_text(framebuffer, width, text_x, text_y + 56,
&format!("Start: {:.6} s", hover.start_time), font, 12.0, 0xCCCCCC);
}
fn draw_timestamp_axis(
framebuffer: &mut [u32],
width: usize,
y: usize,
view: &ViewState,
_earliest: Instant,
font: &fontdue::Font,
) {
// Draw background bar
for x in 0..width {
let idx = y * width + x;
if idx < framebuffer.len() {
framebuffer[idx] = 0x1E1E1E;
}
}
// Calculate time markers
let visible_duration = width as f64 / view.icicle_time_scale;
let time_step = calculate_time_step(visible_duration);
let start_time = view.icicle_time_offset;
let first_marker = (start_time / time_step).ceil() * time_step;
let mut time = first_marker;
while time < start_time + visible_duration {
let x = ((time - view.icicle_time_offset) * view.icicle_time_scale) as i32;
if x >= 0 && x < width as i32 {
// Draw tick mark
for dy in 0..6 {
let idx = (y + dy) * width + x as usize;
if idx < framebuffer.len() {
framebuffer[idx] = 0x808080;
}
}
// Draw time label
let label = format!("{:.3}s", time);
draw_text(framebuffer, width, (x + 2).max(0) as usize, y + 6, &label, font, 10.0, 0xCCCCCC);
}
time += time_step;
}
}
fn calculate_time_step(visible_duration: f64) -> f64 {
// Choose appropriate time step based on visible duration
let steps = [0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1.0, 2.0, 5.0, 10.0];
for &step in &steps {
if visible_duration / step < 20.0 {
return step;
}
}
10.0
} }
fn render_icicle( fn render_icicle(
@@ -460,23 +731,32 @@ mod window {
} }
} }
// Sort roots by start time
roots.sort_by_key(|s| s.start);
// Render each root and its children recursively // Render each root and its children recursively
let row_height = 24; let row_height = 24;
let mut y_offset = 0; let mut y_offset = view.icicle_scroll_y as usize;
for root in roots { for root in roots {
y_offset = render_icicle_span( // Only render if in view
framebuffer, if y_offset < height {
width, y_offset = render_icicle_span(
height, framebuffer,
root, width,
&children, height,
earliest, root,
y_offset, &children,
row_height, earliest,
view, y_offset,
font, row_height,
); view,
font,
0, // depth
);
} else {
break;
}
} }
} }
@@ -491,6 +771,7 @@ mod window {
row_height: usize, row_height: usize,
view: &ViewState, view: &ViewState,
font: &fontdue::Font, font: &fontdue::Font,
depth: usize,
) -> usize { ) -> usize {
if y + row_height > height { if y + row_height > height {
return y; return y;
@@ -506,36 +787,43 @@ mod window {
// Only render if visible // Only render if visible
if x2 > 0 && x1 < width as i32 { if x2 > 0 && x1 < width as i32 {
let color = get_color_for_name(span.name); let color = get_color_for_name(span.name);
let bar_width = (x2 - x1).max(1) as usize;
fill_rect(framebuffer, width, x1.max(0) as usize, y, (x2 - x1).max(1) as usize, row_height - 2, color); fill_rect(framebuffer, width, x1.max(0) as usize, y, bar_width, row_height - 2, color);
// Render text if there's enough space // Render text if there's enough space, otherwise render to the right
let text_width = (x2 - x1) as usize; if bar_width > 40 {
if text_width > 40 {
draw_text(framebuffer, width, x1.max(0) as usize + 4, y + 4, span.name, font, 14.0, TEXT_COLOR); draw_text(framebuffer, width, x1.max(0) as usize + 4, y + 4, span.name, font, 14.0, TEXT_COLOR);
} else if x2 >= 0 && x2 < width as i32 {
// Draw text to the right of the bar
draw_text(framebuffer, width, x2.max(0) as usize + 4, y + 4, span.name, font, 14.0, TEXT_COLOR);
} }
} }
// Render children // Render children at next depth level
let mut next_y = y + row_height; let mut child_y = y + row_height;
if let Some(child_spans) = children.get(&span.span_id) { if let Some(child_spans) = children.get(&span.span_id) {
for child in child_spans { for child in child_spans {
next_y = render_icicle_span( if child_y >= height {
break;
}
child_y = render_icicle_span(
framebuffer, framebuffer,
width, width,
height, height,
child, child,
children, children,
earliest, earliest,
next_y, child_y,
row_height, row_height,
view, view,
font, font,
depth + 1,
); );
} }
} }
next_y child_y
} }
fn render_timeline( fn render_timeline(