srctree

Gregory Mullen parent a9e7e0a4 f799d851
track last 30 requests in stats

inlinesplit
src/builtin-html/stats.html added: 108, removed: 38, total 70
@@ -2,6 +2,15 @@
<html>
<head>
<title>Verse Stats</title>
<style>
.verse-stats-contents {
display: block;
width: 100%;
}
.verse-stats-line {
display: block;
}
</style>
</head>
<body>
<div class="verse-stats-header">
@@ -12,7 +21,10 @@
 
<div class="verse-stats-contents">
<For StatsList>
<span class="verse-stats-line"><Uri /> - <Time /> - <Size /> </span>
<span class="verse-stats-line"><Number type="usize" /> : <Uri /> - <Time type="usize" /> - <Size type="usize" /> <Us type="usize" /></span>
</For>
<For StatsListLast>
<span class="verse-stats-line"><Number type="usize" /> : <Uri /> - <Time type="usize" /> - <Size type="usize" /> <Us type="usize" /></span>
</For>
</div>
</body>
 
src/endpoint.zig added: 108, removed: 38, total 70
@@ -72,14 +72,19 @@ pub fn validateEndpoint(EP: anytype) void {
}
}
 
if (@hasDecl(EP, "verse_routes")) {
inline for (EP.verse_routes, 0..) |route, i| {
if (@TypeOf(route) != Router.Match) {
@compileError("the `verse_routes` decl is reserved for a list of pre-constructed " ++
"`Match` targets. " ++ @typeName(route) ++ " at position " ++ i ++ " is invalid.");
}
}
}
// TODO figure out why this causes a dependency loop and reenable
//if (@hasDecl(EP, "verse_routes")) {
// if (@TypeOf(EP.verse_routes) != []Router.Match) {
// @compileError("the `verse_routes` decl is reserved for a list of pre-constructed " ++
// "`Match` targets. expected []Router.Match but found " ++ @typeName(@TypeOf(EP.verse_routes)) ++ ".");
// }
// //inline for (EP.verse_routes, 0..) |route, i| {
// // if (@TypeOf(route) != Router.Match) {
// // @compileError("the `verse_routes` decl is reserved for a list of pre-constructed " ++
// // "`Match` targets. " ++ @typeName(route) ++ " at position " ++ i ++ " is invalid.");
// // }
// //}
//}
 
if (@hasDecl(EP, "verse_builder")) {
if (@TypeOf(EP.verse_builder) != Router.Builder) {
 
src/frame.zig added: 108, removed: 38, total 70
@@ -39,6 +39,9 @@ status: ?std.http.Status = null,
 
headers_done: bool = false,
 
/// Unstable API; may be altered or removed in the future
server: *const Server,
 
const Frame = @This();
 
pub const Downstream = Request.DownstreamGateway;
@@ -171,7 +174,7 @@ pub fn acceptWebsocket(frame: *Frame) !Websocket {
return Websocket.accept(frame);
}
 
pub fn init(a: Allocator, req: *const Request, auth: Auth.Provider) !Frame {
pub fn init(a: Allocator, srv: *const Server, req: *const Request, auth: Auth.Provider) !Frame {
return .{
.alloc = a,
.request = req,
@@ -181,6 +184,7 @@ pub fn init(a: Allocator, req: *const Request, auth: Auth.Provider) !Frame {
.user = auth.authenticate(&req.headers) catch null,
.cookie_jar = .init(a),
.response_data = ResponseData.init(a),
.server = @ptrCast(srv),
};
}
 
 
src/http.zig added: 108, removed: 38, total 70
@@ -1,5 +1,4 @@
//! Verse HTTP server
 
alloc: Allocator,
router: Router,
auth: Auth.Provider,
@@ -85,7 +84,10 @@ pub fn once(http: *HTTP, sconn: net.Server.Connection) !void {
const reqdata = try requestData(a, &hreq);
const req = try Request.initHttp(a, &hreq, reqdata);
 
var frame = try Frame.init(a, &req, http.auth);
const ifc: *Server.Interface = @fieldParentPtr("http", http);
const srvr: *Server = @fieldParentPtr("interface", ifc);
 
var frame: Frame = try .init(a, srvr, &req, http.auth);
 
errdefer comptime unreachable;
 
@@ -93,8 +95,6 @@ pub fn once(http: *HTTP, sconn: net.Server.Connection) !void {
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,
@@ -149,7 +149,6 @@ test HTTP {
const a = std.testing.allocator;
 
var server: Server = .{
.router = undefined,
.interface = .{ .http = try init(a, Router.TestingRouter, .{ .port = 9345 }, .{}) },
.stats = null,
};
 
src/server.zig added: 108, removed: 38, total 70
@@ -1,4 +1,3 @@
router: Router,
interface: Interface,
stats: ?Stats,
 
@@ -34,7 +33,6 @@ pub const Options = struct {
 
pub fn init(a: Allocator, router: Router, opts: Options) !Server {
return .{
.router = router,
.interface = switch (opts.mode) {
.zwsgi => |z| .{ .zwsgi = zWSGI.init(a, router, z, opts) },
.http => |h| .{ .http = try Http.init(a, router, h, opts) },
@@ -45,7 +43,6 @@ pub fn init(a: Allocator, router: Router, opts: Options) !Server {
}
 
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(),
 
src/stats.zig added: 108, removed: 38, total 70
@@ -3,6 +3,7 @@ pub const Stats = struct {
start_time: i64,
count: usize,
mean: Mean,
rows: [30]Line,
 
const Mean = struct {
time: [256]u64 = undefined,
@@ -28,6 +29,23 @@ pub const Stats = struct {
}
};
 
pub const Line = struct {
number: usize,
size: usize,
time: u64,
uri: Array,
us: usize,
 
const Array = std.BoundedArray(u8, 2048);
pub const empty: Line = .{
.number = 0,
.size = 0,
.time = 0,
.uri = .{},
.us = 0,
};
};
 
pub const Data = struct {
uri: []const u8,
us: u64,
@@ -39,6 +57,7 @@ pub const Stats = struct {
.start_time = std.time.timestamp(),
.count = 0,
.mean = .{},
.rows = @splat(.empty),
};
}
 
@@ -46,42 +65,74 @@ pub const Stats = struct {
if (stats.mutex) |*mx| mx.lock();
defer if (stats.mutex) |*mx| mx.unlock();
 
stats.rows[stats.count % stats.rows.len] = .{
.number = stats.count,
.size = 0,
.time = @intCast(std.time.timestamp()),
.uri = Line.Array.fromSlice(data.uri[0..@min(data.uri.len, 2048)]) catch unreachable,
.us = data.us,
};
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 Router = @import("router.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 const stats = index;
 
pub fn index(f: *Frame) Router.Error!void {
var data: [30]S.StatsList = @splat(.{ .uri = "null", .time = "never", .size = "unknown" });
var data: [30]S.StatsList = @splat(.{
.number = 0,
.size = 0,
.time = 0,
.uri = "null",
.us = 0,
});
var count: usize = 0;
var uptime = std.time.timestamp();
var mean_time: u64 = 0;
 
if (active_stats) |active| {
if (f.server.stats) |active| {
count = active.count;
uptime -|= active.start_time;
mean_time = active.mean.mean(@truncate(count));
for (&data, &active.rows) |*dst, *src| {
dst.* = .{
.number = src.number,
.size = src.size,
.time = src.time,
.uri = src.uri.slice(),
.us = src.us,
};
}
}
 
var first = data[0..@min(count % data.len, data.len)];
var last = data[count % data.len .. @min(count, data.len)];
 
if (count > data.len) {
last = first;
first = data[count % data.len .. @min(count, data.len)];
}
 
const page = StatsPage.init(.{
.uptime = @intCast(uptime),
.count = count,
.mean_resp_time = mean_time,
.stats_list = data[0..@min(count, data.len)],
.stats_list = first,
.stats_list_last = @ptrCast(last),
});
return f.sendPage(&page);
}
@@ -92,4 +143,3 @@ pub const Endpoint = struct {
const std = @import("std");
const Server = @import("server.zig");
const Frame = @import("frame.zig");
const Router = @import("router.zig");
 
src/testing.zig added: 108, removed: 38, total 70
@@ -137,6 +137,7 @@ pub const FrameCtx = struct {
.auth_provider = .invalid,
.response_data = .init(a),
.headers = headers(),
.server = undefined,
},
.buffer = buffer,
};
@@ -213,4 +214,5 @@ const Headers = @import("headers.zig");
const Request = @import("request.zig");
const Frame = @import("frame.zig");
const Router = @import("router.zig");
const Server = @import("server.zig");
const splitUri = Router.splitUri;
 
src/zwsgi.zig added: 108, removed: 38, total 70
@@ -1,6 +1,5 @@
//! Verse zwsgi server
//! Speaks the uwsgi protocol
 
alloc: Allocator,
router: Router,
options: Options,
@@ -101,7 +100,11 @@ pub fn once(z: *const zWSGI, acpt: net.Server.Connection) !void {
var zreq = try zWSGIRequest.init(a, &conn);
const request_data = try requestData(a, &zreq);
const request = try Request.initZWSGI(a, &zreq, request_data);
var frame = try Frame.init(a, &request, z.auth);
 
const ifc: *const Server.Interface = @fieldParentPtr("zwsgi", z);
const srvr: *Server = @constCast(@fieldParentPtr("interface", ifc));
 
var frame: Frame = try .init(a, srvr, &request, z.auth);
 
defer {
const lap = timer.lap() / 1000;
@@ -116,8 +119,6 @@ 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,