atproto relay implementation in zig zlay.waow.tech

add malloc arena metrics to prometheus endpoint

mallinfo2() exposes in-use vs free-but-held-by-malloc bytes,
letting us distinguish real leaks from glibc fragmentation.
also adds VmHWM and RssAnon from /proc/self/status.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

+43 -12
+43 -12
src/broadcaster.zig
··· 628 628 return fbs.getWritten(); 629 629 } 630 630 631 + const malloc_h = @cImport(@cInclude("malloc.h")); 632 + 631 633 fn appendProcMetrics(w: anytype) void { 632 634 // RSS from /proc/self/statm (field[1] * page_size) 633 635 if (std.fs.openFileAbsolute("/proc/self/statm", .{})) |f| { ··· 650 652 } 651 653 } else |_| {} 652 654 653 - // thread count from /proc/self/status 655 + // thread count + memory detail from /proc/self/status 654 656 if (std.fs.openFileAbsolute("/proc/self/status", .{})) |f| { 655 657 defer f.close(); 656 658 var status_buf: [4096]u8 = undefined; 657 659 const n = f.readAll(&status_buf) catch 0; 658 660 if (n > 0) { 659 661 const content = status_buf[0..n]; 660 - if (std.mem.indexOf(u8, content, "Threads:")) |pos| { 661 - const rest = content[pos + "Threads:".len ..]; 662 - const trimmed = std.mem.trimLeft(u8, rest, " \t"); 663 - const end = std.mem.indexOfScalar(u8, trimmed, '\n') orelse trimmed.len; 664 - if (std.fmt.parseInt(u64, trimmed[0..end], 10)) |threads| { 665 - std.fmt.format(w, 666 - \\# TYPE relay_threads_total gauge 667 - \\relay_threads_total {d} 668 - \\ 669 - , .{threads}) catch {}; 670 - } else |_| {} 662 + const fields = .{ 663 + .{ "Threads:", "relay_threads_total" }, 664 + .{ "VmHWM:", "relay_vm_hwm_kb" }, 665 + .{ "RssAnon:", "relay_rss_anon_kb" }, 666 + }; 667 + inline for (fields) |entry| { 668 + if (std.mem.indexOf(u8, content, entry[0])) |pos| { 669 + const rest = content[pos + entry[0].len ..]; 670 + const trimmed = std.mem.trimLeft(u8, rest, " \t"); 671 + const end = std.mem.indexOfScalar(u8, trimmed, ' ') orelse 672 + (std.mem.indexOfScalar(u8, trimmed, '\n') orelse trimmed.len); 673 + if (std.fmt.parseInt(u64, trimmed[0..end], 10)) |val| { 674 + std.fmt.format(w, 675 + \\# TYPE {s} gauge 676 + \\{s} {d} 677 + \\ 678 + , .{ entry[1], entry[1], val }) catch {}; 679 + } else |_| {} 680 + } 671 681 } 672 682 } 673 683 } else |_| {} 684 + 685 + // glibc malloc arena stats — distinguishes in-use heap from fragmentation 686 + const mi = malloc_h.mallinfo2(); 687 + std.fmt.format(w, 688 + \\# TYPE relay_malloc_arena_bytes gauge 689 + \\# HELP relay_malloc_arena_bytes total bytes claimed from OS by malloc 690 + \\relay_malloc_arena_bytes {d} 691 + \\ 692 + \\# TYPE relay_malloc_in_use_bytes gauge 693 + \\# HELP relay_malloc_in_use_bytes bytes actively allocated (in-use heap) 694 + \\relay_malloc_in_use_bytes {d} 695 + \\ 696 + \\# TYPE relay_malloc_free_bytes gauge 697 + \\# HELP relay_malloc_free_bytes free bytes held by malloc (fragmentation) 698 + \\relay_malloc_free_bytes {d} 699 + \\ 700 + \\# TYPE relay_malloc_mmap_bytes gauge 701 + \\# HELP relay_malloc_mmap_bytes bytes allocated via mmap (large blocks) 702 + \\relay_malloc_mmap_bytes {d} 703 + \\ 704 + , .{ mi.arena, mi.uordblks, mi.fordblks, mi.hblkhd }) catch {}; 674 705 } 675 706 676 707 const posix_vfs = @cImport(@cInclude("sys/statvfs.h"));