srctree

Gregory Mullen parent 675ceb29 a9e7e0a4
add basic stats page, and example

inlinesplit
build.zig added: 186, removed: 16, total 170
@@ -75,6 +75,7 @@ pub fn build(b: *std.Build) !void {
"request-userdata",
"api",
"websocket",
"stats",
};
inline for (examples) |example| {
const example_exe = b.addExecutable(.{
 
filename was Deleted added: 186, removed: 16, total 170
@@ -0,0 +1,19 @@
//! Quick start example using Verse Endpoints.
const Endpoints = verse.Endpoints(.{
//
verse.stats.Endpoint,
});
 
pub fn main() !void {
var endpoints = Endpoints.init(std.heap.page_allocator);
endpoints.serve(.{
.mode = .{ .http = .{ .port = 8088 } },
.stats = true,
}) catch |err| {
std.log.err("Unable to serve stats err: [{}]", .{err});
@panic("endpoint error");
};
}
 
const std = @import("std");
const verse = @import("verse");
 
filename was Deleted added: 186, removed: 16, total 170
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<title>Verse Stats</title>
</head>
<body>
<div class="verse-stats-header">
<span>Uptime: <Uptime type="usize" /></span>
<span>Count: <Count type="usize" /></span>
<span>Mean Response Time: <MeanRespTime type="usize" /></span>
</div>
 
<div class="verse-stats-contents">
<For StatsList>
<span class="verse-stats-line"><Uri /> - <Time /> - <Size /> </span>
</For>
</div>
</body>
</html>
 
src/http.zig added: 186, removed: 16, total 170
@@ -68,6 +68,8 @@ fn onceThreaded(http: *HTTP, acpt: net.Server.Connection) void {
}
 
pub fn once(http: *HTTP, sconn: net.Server.Connection) !void {
var timer = try std.time.Timer.start();
 
var conn = sconn;
defer conn.stream.close();
 
@@ -85,8 +87,20 @@ pub fn once(http: *HTTP, sconn: net.Server.Connection) !void {
 
var frame = try Frame.init(a, &req, http.auth);
 
errdefer comptime unreachable;
 
const callable = http.router.fallback(&frame, http.router.route);
http.router.builder(&frame, callable);
 
const lap = timer.lap();
const ifc: *Server.Interface = @fieldParentPtr("http", http);
const srvr: *Server = @fieldParentPtr("interface", ifc);
if (srvr.stats) |*stats| {
stats.log(.{
.uri = req.uri,
.us = lap / 1000,
});
}
}
 
fn requestData(a: Allocator, req: *std.http.Server.Request) !Request.Data {
@@ -134,16 +148,21 @@ fn threadFn(server: *HTTP) void {
test HTTP {
const a = std.testing.allocator;
 
var server = try init(a, Router.TestingRouter, .{ .port = 9345 }, .{});
var thread = try std.Thread.spawn(.{}, threadFn, .{&server});
var server: Server = .{
.router = undefined,
.interface = .{ .http = try init(a, Router.TestingRouter, .{ .port = 9345 }, .{}) },
.stats = null,
};
 
var thread = try std.Thread.spawn(.{}, threadFn, .{&server.interface.http});
 
var client = std.http.Client{ .allocator = a };
defer client.deinit();
while (!server.alive) {}
while (!server.interface.http.alive) {}
 
var list = std.ArrayList(u8).init(a);
defer list.clearAndFree();
server.running = false;
server.interface.http.running = false;
const fetch = try client.fetch(.{
.response_storage = .{ .dynamic = &list },
.location = .{ .url = "http://localhost:9345/" },
 
src/server.zig added: 186, removed: 16, total 170
@@ -1,8 +1,12 @@
router: Router,
interface: Interface,
stats: ?Stats,
 
const Server = @This();
 
pub const zWSGI = @import("zwsgi.zig");
pub const Http = @import("http.zig");
 
pub const RunModes = enum {
zwsgi,
http,
@@ -25,6 +29,7 @@ pub const Options = struct {
mode: RunMode = .{ .http = .{} },
auth: Auth.Provider = .invalid,
threads: ?u16 = null,
stats: bool = false,
};
 
pub fn init(a: Allocator, router: Router, opts: Options) !Server {
@@ -35,10 +40,12 @@ pub fn init(a: Allocator, router: Router, opts: Options) !Server {
.http => |h| .{ .http = try Http.init(a, router, h, opts) },
.other => unreachable,
},
.stats = if (opts.stats) .init(opts.threads != null) else null,
};
}
 
pub fn serve(srv: *Server) !void {
if (srv.stats) |_| stats_.active_stats = &srv.stats.?;
switch (srv.interface) {
.zwsgi => |*zw| try zw.serve(),
.http => |*ht| try ht.serve(),
@@ -58,5 +65,5 @@ const Allocator = std.mem.Allocator;
 
const Auth = @import("auth.zig");
const Router = @import("router.zig");
pub const zWSGI = @import("zwsgi.zig");
pub const Http = @import("http.zig");
const stats_ = @import("stats.zig");
const Stats = stats_.Stats;
 
filename was Deleted added: 186, removed: 16, total 170
@@ -0,0 +1,95 @@
pub const Stats = struct {
mutex: ?std.Thread.Mutex,
start_time: i64,
count: usize,
mean: Mean,
 
const Mean = struct {
time: [256]u64 = undefined,
idx: u8 = 0,
 
///
fn mean(m: Mean, count: u8) u64 {
if (count == 0) return 0;
// TODO vectorize
//if (count < m.idx) {
// const sum = @reduce(.Add, m.time[m.idx - count .. m.idx]);
// return sum / count;
//} else {
// var sum = @reduce(.Add, m.time[0..m.idx]);
// sum += @reduce(.Add, m.time[m.time.len - count - m.idx .. m.time.len]);
// return sum / count;
//}
var sum: u128 = 0;
for (0..count) |i| {
sum += m.time[m.idx -| 1 -| i];
}
return @truncate(sum / count);
}
};
 
pub const Data = struct {
uri: []const u8,
us: u64,
};
 
pub fn init(threaded: bool) Stats {
return .{
.mutex = if (threaded) .{} else null,
.start_time = std.time.timestamp(),
.count = 0,
.mean = .{},
};
}
 
pub fn log(stats: *Stats, data: Data) void {
if (stats.mutex) |*mx| mx.lock();
defer if (stats.mutex) |*mx| mx.unlock();
 
stats.count += 1;
stats.mean.time[stats.mean.idx] = data.us;
stats.mean.idx +%= 1;
return;
}
};
 
pub var active_stats: ?*Stats = null;
 
pub const Endpoint = struct {
//const EP = @import("endpoint.zig");
const PageData = @import("template.zig").PageData;
const S = @import("template.zig").Structs;
pub const verse_name = .stats;
 
pub const verse_routes = [_]Router.Match{};
 
const StatsPage = PageData("builtin-html/stats.html");
 
pub fn index(f: *Frame) Router.Error!void {
var data: [30]S.StatsList = @splat(.{ .uri = "null", .time = "never", .size = "unknown" });
var count: usize = 0;
var uptime = std.time.timestamp();
var mean_time: u64 = 0;
 
if (active_stats) |active| {
count = active.count;
uptime -|= active.start_time;
mean_time = active.mean.mean(@truncate(count));
}
 
const page = StatsPage.init(.{
.uptime = @intCast(uptime),
.count = count,
.mean_resp_time = mean_time,
.stats_list = data[0..@min(count, data.len)],
});
return f.sendPage(&page);
}
 
pub fn websocket(_: *Frame) Router.Error!void {}
};
 
const std = @import("std");
const Server = @import("server.zig");
const Frame = @import("frame.zig");
const Router = @import("router.zig");
 
src/verse.zig added: 186, removed: 16, total 170
@@ -3,6 +3,7 @@
pub const auth = @import("auth.zig");
pub const abx = @import("antibiotic.zig");
pub const template = @import("template.zig");
pub const stats = @import("stats.zig");
 
pub const ContentType = @import("content-type.zig");
pub const Frame = @import("frame.zig");
 
src/zwsgi.zig added: 186, removed: 16, total 170
@@ -14,6 +14,7 @@ const zWSGI = @This();
pub const Options = struct {
file: []const u8 = "./zwsgi_file.sock",
chmod: ?std.posix.mode_t = null,
stats: bool = false,
};
 
pub fn init(a: Allocator, router: Router, opts: Options, sopts: Server.Options) zWSGI {
@@ -103,11 +104,11 @@ pub fn once(z: *const zWSGI, acpt: net.Server.Connection) !void {
var frame = try Frame.init(a, &request, z.auth);
 
defer {
const lap = @as(f64, @floatFromInt(timer.lap())) / 1000000.0;
const lap = timer.lap() / 1000;
log.err(
"zWSGI: [{d:.3}] {s} - {s}:{} {s} -- \"{s}\"",
.{
lap,
@as(f64, @floatFromInt(lap)) / 1000.0,
request.remote_addr,
request.method,
@intFromEnum(frame.status orelse .ok),
@@ -115,6 +116,14 @@ pub fn once(z: *const zWSGI, acpt: net.Server.Connection) !void {
if (request.user_agent) |ua| ua.string else "EMPTY",
},
);
const ifc: *const Server.Interface = @fieldParentPtr("zwsgi", z);
const srvr: *Server = @constCast(@fieldParentPtr("interface", ifc));
if (srvr.stats) |*stats| {
stats.log(.{
.uri = request.uri,
.us = lap,
});
}
}
 
const routed_endpoint = z.router.fallback(&frame, z.router.route);