Skip to documentation
SLOP

tiny.profiling.report.serve

Reference tiny.profiling report serve

Defined in report.

API (6)

Actions

Public operations.

Types and contracts

Public types and contracts.

No direct callersNo direct callsreportserve
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Called byCallsNo direct callsreport.serveruntest; no linksrc.profiling.report.servetest: serve regenerates and serves th...report.serve.Stateinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate; no linksrc.profiling.report.serve.Staterefreshreport.serveruntest; no linksrc.profiling.report.servetest: serve regenerates and serves th...private; no linksrc.profiling.report.servenewestArtifactreport.sitegeneratereport.serve.Stateregenerate
Static calls · unresolved targets: 0 · external targets: 4.
Called byCallsprivate; no linksrc.profiling.report.serve.Staterefreshprivate; no linkfun.sdfii.src.backend.routerdeinitprivate sourcelib.machine.src.checkpoint.canon.digestpagereport.serve.Stateinitreport.serve.Stateregeneratereport.serverun
Static calls · unresolved targets: 0 · external targets: 9.

Source: src/profiling/report/root.zig:7

zig
pub const serve = @import("serve.zig");

Source: src/profiling/report/serve.zig

zig
const std = @import("std");const alloc = @import("alloc");const http = @import("http");const pretty_usage = @import("pretty_usage");const sys = @import("sys");const site = @import("root.zig").site;const max_page_bytes = 512 * 1024 * 1024;pub const Options = struct {    site: site.Options = .{},    address: []const u8 = "127.0.0.1",    port: u16 = 8787,};pub const Generated = struct {    runs: usize,    pages: usize,};pub const State = struct {    allocator: std.mem.Allocator,    options: Options,    output_dir: []const u8 = "",    newest_seen: i128 = 0,    mutex: std.atomic.Mutex = .unlocked,    pub fn init(allocator: std.mem.Allocator, options: Options) State {        return .{ .allocator = allocator, .options = options };    }    pub fn regenerate(self: *State) !Generated {        var scratch_state = std.heap.ArenaAllocator.init(self.allocator);        defer scratch_state.deinit();        const result = try site.generate(scratch_state.allocator(), self.options.site);        const output_dir = try self.allocator.dupe(u8, result.output_dir);        if (self.output_dir.len != 0) self.allocator.free(self.output_dir);        self.output_dir = output_dir;        self.newest_seen = newestArtifact(self.allocator, self.options.site.profiling_dir) orelse 0;        return .{ .runs = result.runs, .pages = result.pages };    }    fn refresh(self: *State) void {        const newest = newestArtifact(self.allocator, self.options.site.profiling_dir) orelse return;        if (newest <= self.newest_seen) return;        const result = self.regenerate() catch |err| {            pretty_usage.Terminal.stderr(self.allocator, .{}).writeTextFmt(                "profile web: regeneration failed, serving the previous site: {s}\n",                .{@errorName(err)},            ) catch {};            return;        };        pretty_usage.Terminal.stderr(self.allocator, .{}).writeTextFmt(            "profile web: regenerated {d} page(s) for {d} run(s)\n",            .{ result.pages, result.runs },        ) catch {};    }    fn lock(self: *State) void {        while (!self.mutex.tryLock()) sys.thread.yield();    }    fn unlock(self: *State) void {        self.mutex.unlock();    }};fn newestArtifact(allocator: std.mem.Allocator, profiling_dir: []const u8) ?i128 {    var scratch_state = std.heap.ArenaAllocator.init(allocator);    defer scratch_state.deinit();    const scratch = scratch_state.allocator();    const dir_stat = sys.fs.statFile(profiling_dir) catch return null;    var newest: i128 = dir_stat.mtime.nanoseconds;    const listing = sys.fs.listDirAlloc(scratch, profiling_dir) catch return newest;    for (listing) |item| {        if (item.kind != .directory) continue;        const manifest = std.fs.path.join(scratch, &.{ item.path, "manifest.json" }) catch continue;        const stat = sys.fs.statFile(manifest) catch continue;        newest = @max(newest, @as(i128, stat.mtime.nanoseconds));    }    return newest;}pub fn run(options: Options) !void {    const allocator = alloc.workerAllocator();    var state = State.init(allocator, options);    const first = try state.regenerate();    var router = http.Router(*State).init(allocator, &state);    defer router.deinit();    router.notFound(serveFile);    const server = try http.Server.init(allocator, .{        .address = options.address,        .port = options.port,    });    defer server.deinit();    const port = try sys.net.socketPort(server.listener.?);    server.setHandler(&router, handleConnection);    try pretty_usage.Terminal.stdout(allocator, .{}).writeTextFmt(        "profile web: {d} run(s), {d} page(s) in {s}\nserving http://{s}:{d}/ (Ctrl-C to stop)\n",        .{ first.runs, first.pages, state.output_dir, options.address, port },    );    try server.listen();}fn handleConnection(router: *http.Router(*State), conn: *http.Connection) void {    while (router.handleRequest(conn) catch false) {}}fn releaseFileBody(state: *State, body: []const u8) void {    state.allocator.free(body);}fn serveFile(state: *State, req: *http.Request, res: *http.Response) !void {    state.lock();    defer state.unlock();    const target = req.pathOnly();    if (std.mem.eql(u8, target, "/") or std.mem.eql(u8, target, "/index.html")) state.refresh();    const relative = resolveRelative(state.allocator, target) catch {        res.status = 400;        res.status_text = "Bad Request";        res.body = "Bad Request";        return;    };    defer state.allocator.free(relative);    const full = try std.fs.path.join(state.allocator, &.{ state.output_dir, relative });    defer state.allocator.free(full);    const body = sys.fs.readFileAlloc(state.allocator, full, max_page_bytes) catch {        res.status = 404;        res.status_text = "Not Found";        res.body = "Not Found";        try res.setHeader("Content-Type", "text/plain");        return;    };    res.setBodyWithRelease(body, state, releaseFileBody);    try res.setHeader("Content-Type", contentType(relative));    try res.setHeader("Cache-Control", "no-store");}fn resolveRelative(allocator: std.mem.Allocator, target: []const u8) ![]const u8 {    var trimmed = std.mem.trimStart(u8, target, "/");    if (trimmed.len == 0) trimmed = "index.html";    var parts = std.mem.splitScalar(u8, trimmed, '/');    while (parts.next()) |part| {        if (std.mem.eql(u8, part, "..") or std.mem.eql(u8, part, ".")) return error.PathEscapes;    }    if (trimmed[trimmed.len - 1] == '/') {        return try std.fmt.allocPrint(allocator, "{s}index.html", .{trimmed});    }    return try allocator.dupe(u8, trimmed);}fn contentType(path: []const u8) []const u8 {    const extension = std.fs.path.extension(path);    if (std.mem.eql(u8, extension, ".html")) return "text/html; charset=utf-8";    if (std.mem.eql(u8, extension, ".css")) return "text/css";    if (std.mem.eql(u8, extension, ".js")) return "text/javascript";    if (std.mem.eql(u8, extension, ".svg")) return "image/svg+xml";    if (std.mem.eql(u8, extension, ".json")) return "application/json";    if (std.mem.eql(u8, extension, ".txt")) return "text/plain; charset=utf-8";    if (std.mem.eql(u8, extension, ".png")) return "image/png";    return "application/octet-stream";}test "serve resolves paths and rejects escapes" {    var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena_state.deinit();    const allocator = arena_state.allocator();    try std.testing.expectEqualStrings("index.html", try resolveRelative(allocator, "/"));    try std.testing.expectEqualStrings("index.html", try resolveRelative(allocator, ""));    try std.testing.expectEqualStrings("runs/a/index.html", try resolveRelative(allocator, "/runs/a/"));    try std.testing.expectEqualStrings("runs/a/workloads/w.html", try resolveRelative(allocator, "/runs/a/workloads/w.html"));    try std.testing.expectError(error.PathEscapes, resolveRelative(allocator, "/../etc/passwd"));    try std.testing.expectError(error.PathEscapes, resolveRelative(allocator, "/runs/../../x"));}test "serve names content types by extension" {    try std.testing.expectEqualStrings("text/html; charset=utf-8", contentType("index.html"));    try std.testing.expectEqualStrings("text/css", contentType("profile.css"));    try std.testing.expectEqualStrings("application/json", contentType("analysis.json"));    try std.testing.expectEqualStrings("application/octet-stream", contentType("perf.data"));}fn test_listen(server: *http.Server) void {    server.listen() catch {};}test "serve regenerates and serves the site over http" {    const model = @import("root.zig").model;    var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena_state.deinit();    const allocator = arena_state.allocator();    const profiling_dir = ".zig-cache/profile-web-serve-test";    const output_dir = ".zig-cache/profile-web-serve-test-out";    defer sys.fs.deleteTree(profiling_dir) catch {};    defer sys.fs.deleteTree(output_dir) catch {};    try model.writeTestFixture(allocator, profiling_dir);    var state = State.init(allocator, .{        .site = .{ .profiling_dir = profiling_dir, .output_dir = output_dir },    });    const generated = try state.regenerate();    try std.testing.expectEqual(@as(usize, 9), generated.pages);    var router = http.Router(*State).init(allocator, &state);    defer router.deinit();    router.notFound(serveFile);    const server = try http.Server.init(std.testing.allocator, .{ .port = 0, .num_workers = 1 });    defer {        server.stop();        server.deinit();    }    const port = try sys.net.socketPort(server.listener.?);    server.setHandler(&router, handleConnection);    const listen_thread = try sys.thread.spawn(test_listen, .{server});    defer {        server.stop();        listen_thread.join();    }    sys.time.sleepMilliseconds(10);    var client = http.Client.init(std.testing.allocator);    defer client.deinit();    var client_operation_storage = try http.ClientOperationStorage.init(std.testing.allocator, .{        .operation_count = 1,        .host_bytes_per_operation = http.default_client_operation_host_bytes,        .target_bytes_per_operation = http.default_client_operation_target_bytes,        .plain_read_bytes_per_operation = http.default_client_operation_plain_read_bytes,        .plain_write_bytes_per_operation = http.default_client_operation_plain_write_bytes,    });    defer client_operation_storage.deinit(std.testing.allocator);    client_operation_storage.activate();    const operation_scratch = try client_operation_storage.operation(0);    var client_response_storage = try http.ClientResponseStorage.init(std.testing.allocator, .{        .response_count = 1,        .header_count_per_response = 16,        .head_bytes_per_response = 4096,        .body_bytes_per_response = 1024 * 1024,    });    defer client_response_storage.deinit(std.testing.allocator);    client_response_storage.activate();    const response_scratch = try client_response_storage.response(0);    var url_buffer: [128]u8 = undefined;    const index_response = try client.get(        operation_scratch,        response_scratch,        try std.fmt.bufPrint(&url_buffer, "http://127.0.0.1:{d}/", .{port}),        &.{},    );    try std.testing.expectEqual(@as(u16, 200), index_response.status);    try std.testing.expect(std.mem.indexOf(u8, index_response.body, "tiny profiling") != null);    const flame_response = try client.get(        operation_scratch,        response_scratch,        try std.fmt.bufPrint(            &url_buffer,            "http://127.0.0.1:{d}/runs/run-200-bbb/workloads/gpalloc.allocator.flame.html",            .{port},        ),        &.{},    );    try std.testing.expectEqual(@as(u16, 200), flame_response.status);    try std.testing.expect(std.mem.indexOf(u8, flame_response.body, "flame-cpu") != null);    const missing_response = try client.get(        operation_scratch,        response_scratch,        try std.fmt.bufPrint(&url_buffer, "http://127.0.0.1:{d}/nope.html", .{port}),        &.{},    );    try std.testing.expectEqual(@as(u16, 404), missing_response.status);    const escape_response = try client.get(        operation_scratch,        response_scratch,        try std.fmt.bufPrint(&url_buffer, "http://127.0.0.1:{d}/../build.zig", .{port}),        &.{},    );    try std.testing.expect(escape_response.status == 400 or escape_response.status == 404);}

Audit

Definitions7
Public names7
Members10
Version26.7.0
Revisiondaab053ee433