add profiler

This commit is contained in:
Rokas Puzonas 2025-02-03 01:02:41 +02:00
parent dc3e66cfdc
commit a87a9c104e
3 changed files with 348 additions and 185 deletions

View File

@ -34,6 +34,11 @@ ui: UI,
channels: std.BoundedArray(Channel, 64) = .{},
ni_daq: NIDaq,
shown_window: enum {
channels,
add_from_device
} = .channels,
pub fn init(allocator: std.mem.Allocator) !App {
return App{
.allocator = allocator,
@ -143,75 +148,7 @@ pub fn appendChannelFromFile(self: *App, file: std.fs.File) !void {
});
}
pub fn tick(self: *App) !void {
rl.beginDrawing();
defer rl.endDrawing();
rl.clearBackground(srcery.black);
if (rl.isKeyPressed(rl.KeyboardKey.key_f3)) {
Platform.toggleConsoleWindow();
}
{
self.ui.begin();
defer self.ui.end();
self.ui.getParent().?.layout_axis = .Y;
{
const toolbar = self.ui.newBoxFromString("Toolbar");
toolbar.flags.insert(.clickable);
toolbar.background = rl.Color.green;
toolbar.layout_axis = .X;
toolbar.size = .{
.x = UI.Size.percent(1, 0),
.y = UI.Size.pixels(32, 1),
};
self.ui.pushParent(toolbar);
defer self.ui.popParent();
{
const box = self.ui.newBoxFromString("Add from file");
box.flags.insert(.clickable);
box.background = rl.Color.red;
box.size = .{
.x = UI.Size.text(2, 1),
.y = UI.Size.percent(1, 1)
};
box.setText("Add from file", .text);
const signal = self.ui.signalFromBox(box);
if (signal.clicked()) {
if (Platform.openFilePicker()) |file| {
defer file.close();
// TODO: Handle error
self.appendChannelFromFile(file) catch @panic("Failed to append channel from file");
} else |err| {
// TODO: Show error message to user;
log.err("Failed to pick file: {}", .{ err });
}
}
}
{
const box = self.ui.newBoxFromString("Add from device");
box.flags.insert(.clickable);
box.background = rl.Color.lime;
box.size = .{
.x = UI.Size.text(2, 1),
.y = UI.Size.percent(1, 1)
};
box.setText("Add from device", .text);
const signal = self.ui.signalFromBox(box);
if (signal.clicked()) {
std.debug.print("click two!\n", .{});
}
}
}
{
fn showChannelsWindow(self: *App) void {
const scroll_area = self.ui.pushScrollbar(self.ui.newKeyFromString("Channels"));
scroll_area.layout_axis = .Y;
defer self.ui.popScrollbar();
@ -342,6 +279,90 @@ pub fn tick(self: *App) !void {
}
}
}
fn showAddFromDeviceWindow(self: *App) !void {
const names = try self.ni_daq.listDeviceNames();
_ = names;
}
fn showToolbar(self: *App) void {
const toolbar = self.ui.newBoxFromString("Toolbar");
toolbar.flags.insert(.clickable);
toolbar.background = rl.Color.green;
toolbar.layout_axis = .X;
toolbar.size = .{
.x = UI.Size.percent(1, 0),
.y = UI.Size.pixels(32, 1),
};
self.ui.pushParent(toolbar);
defer self.ui.popParent();
{
const box = self.ui.newBoxFromString("Add from file");
box.flags.insert(.clickable);
box.background = rl.Color.red;
box.size = .{
.x = UI.Size.text(2, 1),
.y = UI.Size.percent(1, 1)
};
box.setText("Add from file", .text);
const signal = self.ui.signalFromBox(box);
if (signal.clicked()) {
if (Platform.openFilePicker()) |file| {
defer file.close();
// TODO: Handle error
self.appendChannelFromFile(file) catch @panic("Failed to append channel from file");
} else |err| {
// TODO: Show error message to user;
log.err("Failed to pick file: {}", .{ err });
}
}
}
{
const box = self.ui.newBoxFromString("Add from device");
box.flags.insert(.clickable);
box.background = rl.Color.lime;
box.size = .{
.x = UI.Size.text(2, 1),
.y = UI.Size.percent(1, 1)
};
box.setText("Add from device", .text);
const signal = self.ui.signalFromBox(box);
if (signal.clicked()) {
if (self.shown_window == .add_from_device) {
self.shown_window = .channels;
} else {
self.shown_window = .add_from_device;
}
}
}
}
pub fn tick(self: *App) !void {
rl.clearBackground(srcery.black);
if (rl.isKeyPressed(rl.KeyboardKey.key_f3)) {
Platform.toggleConsoleWindow();
}
{
self.ui.begin();
defer self.ui.end();
const root_box = self.ui.getParent().?;
root_box.layout_axis = .Y;
self.showToolbar();
if (self.shown_window == .channels) {
self.showChannelsWindow();
} else if (self.shown_window == .add_from_device) {
try self.showAddFromDeviceWindow();
}
}
self.ui.draw();

View File

@ -3,12 +3,14 @@ const rl = @import("raylib");
const builtin = @import("builtin");
const Application = @import("./app.zig");
const Assets = @import("./assets.zig");
const Profiler = @import("./profiler.zig");
const raylib_h = @cImport({
@cInclude("stdio.h");
@cInclude("raylib.h");
});
const log = std.log;
const profiler_enabled = builtin.mode == .Debug;
// TODO: Maybe move this to a config.zig or options.zig file.
// Have all of the contstants in a single file.
@ -134,6 +136,9 @@ pub fn main() !void {
rl.setWindowMinSize(256, 256);
rl.setWindowIcon(icon_image);
const target_fps = 60;
rl.setTargetFPS(target_fps);
if (builtin.mode != .Debug) {
rl.setExitKey(.key_null);
}
@ -158,8 +163,35 @@ pub fn main() !void {
}
}
var profiler: ?Profiler = null;
defer if (profiler) |p| p.deinit();
var profiler_shown = false;
if (profiler_enabled) {
const font_face = Assets.font(.text);
profiler = try Profiler.init(allocator, 10 * target_fps, @divFloor(std.time.ns_per_s, target_fps), font_face);
}
while (!rl.windowShouldClose()) {
rl.beginDrawing();
defer rl.endDrawing();
if (profiler) |*p| {
p.start();
}
try app.tick();
if (profiler) |*p| {
p.stop();
if (rl.isKeyPressed(.key_p) and rl.isKeyDown(.key_left_control)) {
profiler_shown = !profiler_shown;
}
if (profiler_shown) {
try p.showResults();
}
}
}
}

110
src/profiler.zig Normal file
View File

@ -0,0 +1,110 @@
const std = @import("std");
const rl = @import("raylib");
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const FontFace = @import("./font-face.zig");
const srcery = @import("./srcery.zig");
const rect_utils = @import("./rect-utils.zig");
allocator: Allocator,
history: []u128,
history_size: usize,
history_head: usize,
started_at: i128,
ns_budget: u128,
font_face: FontFace,
pub fn init(allocator: Allocator, data_points: usize, ns_budget: u128, font_face: FontFace) !@This() {
return @This(){
.allocator = allocator,
.history = try allocator.alloc(u128, data_points),
.history_size = 0,
.history_head = 0,
.started_at = 0,
.ns_budget = ns_budget,
.font_face = font_face,
};
}
pub fn deinit(self: @This()) void {
self.allocator.free(self.history);
}
pub fn start(self: *@This()) void {
self.started_at = std.time.nanoTimestamp();
}
pub fn stop(self: *@This()) void {
const stopped_at = std.time.nanoTimestamp();
assert(stopped_at >= self.started_at);
const duration: u128 = @intCast(stopped_at - self.started_at);
if (self.history_size < self.history.len) {
self.history[self.history_size] = duration;
self.history_size += 1;
} else {
self.history_head = @mod(self.history_head + 1, self.history.len);
self.history[self.history_head] = duration;
}
}
pub fn showResults(self: *const @This()) !void {
const screen_width: f32 = @floatFromInt(rl.getScreenWidth());
const screen_height: f32 = @floatFromInt(rl.getScreenHeight());
const profile_height = 200;
const profile_box = rl.Rectangle.init(0, screen_height - profile_height, screen_width, profile_height);
const color = srcery.bright_white;
rl.drawRectangleLinesEx(profile_box, 1, color);
const ns_budget: f32 = @floatFromInt(self.ns_budget);
const measurement_width = profile_box.width / @as(f32, @floatFromInt(self.history.len));
for (0..self.history_size) |i| {
const measurement = self.history[@mod(self.history_head + i, self.history.len)];
const measurement_height = @as(f32, @floatFromInt(measurement)) / ns_budget * profile_box.height;
rl.drawRectangleV(
.{
.x = profile_box.x + measurement_width * @as(f32, @floatFromInt(i + self.history.len - self.history_size)),
.y = profile_box.y + profile_box.height - measurement_height
},
.{ .x = measurement_width, .y = measurement_height },
color
);
}
var min_time_taken = self.history[0];
var max_time_taken = self.history[0];
var sum_time_taken: u128 = 0;
for (self.history[0..self.history_size]) |measurement| {
min_time_taken = @min(min_time_taken, measurement);
max_time_taken = @max(max_time_taken, measurement);
sum_time_taken += measurement;
}
const avg_time_taken = @as(f32, @floatFromInt(sum_time_taken)) / @as(f32, @floatFromInt(self.history_size));
const content_rect = rect_utils.shrink(profile_box, 10);
var layout_offset: f32 = 0;
const allocator = self.allocator;
const font_size = self.font_face.getSize();
const labels = .{
.{ "Min", @as(f32, @floatFromInt(min_time_taken)) },
.{ "Max", @as(f32, @floatFromInt(max_time_taken)) },
.{ "Avg", avg_time_taken }
};
inline for (labels) |label| {
const label_name = label[0];
const time_taken = label[1];
const min_time_str = try std.fmt.allocPrintZ(allocator, "{s}: {d:10.0}us ({d:.3}%)", .{ label_name, time_taken / std.time.ns_per_us, time_taken / ns_budget * 100 });
defer allocator.free(min_time_str);
self.font_face.drawText(min_time_str, .{ .x = content_rect.x, .y = content_rect.y + layout_offset }, color);
layout_offset += font_size;
}
}