Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ site/resources/
obj/
bin/
target/
frameworks/blitz/zig-linux-*
frameworks/blitz/.zig-cache
Binary file modified data/benchmark.db
Binary file not shown.
3 changes: 2 additions & 1 deletion frameworks/blitz/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
FROM debian:bookworm-slim AS build
RUN apt-get update && apt-get install -y wget xz-utils ca-certificates && \
RUN apt-get update && apt-get install -y wget xz-utils ca-certificates libsqlite3-dev && \
wget -q https://ziglang.org/download/0.14.0/zig-linux-x86_64-0.14.0.tar.xz && \
tar xf zig-linux-x86_64-0.14.0.tar.xz && \
mv zig-linux-x86_64-0.14.0 /usr/local/zig
Expand All @@ -10,6 +10,7 @@ COPY src ./src
RUN zig build -Doptimize=ReleaseFast

FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends libsqlite3-0 && rm -rf /var/lib/apt/lists/*
COPY --from=build /app/zig-out/bin/blitz /server
ENV BLITZ_URING=1
EXPOSE 8080
Expand Down
1 change: 1 addition & 0 deletions frameworks/blitz/build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub fn build(b: *std.Build) void {
});
exe.root_module.addImport("blitz", blitz_mod);
exe.linkLibC();
exe.linkSystemLibrary("sqlite3");
b.installArtifact(exe);

// Run step
Expand Down
4 changes: 2 additions & 2 deletions frameworks/blitz/build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
},
.dependencies = .{
.blitz = .{
.url = "https://github.com/BennyFranciscus/blitz/archive/541cd3bae622b76e76c3c8b68d5f50825e38d75c.tar.gz",
.hash = "blitz-0.1.0-OJAP5jf0BABVpa50QwnJV50pDkAVhriR21R3sR7OfagO",
.url = "https://github.com/BennyFranciscus/blitz/archive/6639756b2ce786f8e73366f34b5fb59ce819e3a6.tar.gz",
.hash = "blitz-0.1.0-OJAP5rMZBwD_7Nu7zKT8zGSKTK79YWyMF_YNn6Jmo6kn",
},
},
}
3 changes: 2 additions & 1 deletion frameworks/blitz/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"json",
"upload",
"compression",
"echo-ws"
"echo-ws",
"mixed"
]
}
299 changes: 299 additions & 0 deletions frameworks/blitz/src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ var dataset_gzip_resp: []const u8 = "";
var compression_json_resp: []const u8 = "";
var compression_gzip_resp: []const u8 = "";

// ── Per-thread SQLite (thread-local for zero contention) ────────────
threadlocal var tls_db: ?blitz.SqliteDb = null;
threadlocal var tls_db_stmt: ?blitz.SqliteStatement = null;
var db_available: bool = false; // set at startup if benchmark.db exists
var db_default_resp: []const u8 = ""; // pre-computed response for ?min=10&max=50

const StaticFile = struct {
name: []const u8,
response: []const u8,
Expand Down Expand Up @@ -81,6 +87,190 @@ fn handleWsUpgrade(req: *blitz.Request, res: *blitz.Response) void {
res.ws_upgraded = true;
}

fn handleDb(req: *blitz.Request, res: *blitz.Response) void {
if (!db_available) {
_ = res.setStatus(.internal_server_error).text("DB not available");
return;
}

// Fast path: serve cached response for default query (min=10&max=50)
// The mixed benchmark always sends this exact query
if (db_default_resp.len > 0) {
if (req.query) |q| {
if (mem.eql(u8, q, "min=10&max=50")) {
_ = res.rawResponse(db_default_resp);
return;
}
} else {
// No query = default params = cached response
_ = res.rawResponse(db_default_resp);
return;
}
}

// Parse query params: ?min=10&max=50
var min_price: f64 = 10.0;
var max_price: f64 = 50.0;
if (req.query) |q| {
var it = mem.splitScalar(u8, q, '&');
while (it.next()) |pair| {
if (mem.indexOfScalar(u8, pair, '=')) |eq| {
const key = pair[0..eq];
const val = pair[eq + 1 ..];
if (mem.eql(u8, key, "min")) {
min_price = std.fmt.parseFloat(f64, val) catch 10.0;
} else if (mem.eql(u8, key, "max")) {
max_price = std.fmt.parseFloat(f64, val) catch 50.0;
}
}
}
}

// Open per-thread DB connection + prepare statement (lazy init)
if (tls_db == null) {
tls_db = blitz.SqliteDb.open("/data/benchmark.db", .{ .readonly = true, .mmap_size = 64 * 1024 * 1024 }) catch {
_ = res.setStatus(.internal_server_error).text("DB open failed");
return;
};
tls_db_stmt = tls_db.?.prepare("SELECT id, name, category, price, quantity, active, tags, rating_score, rating_count FROM items WHERE price BETWEEN ?1 AND ?2 LIMIT 50") catch {
_ = res.setStatus(.internal_server_error).text("Prepare failed");
return;
};
}

var stmt = &(tls_db_stmt.?);
stmt.reset();
stmt.bindDouble(1, min_price) catch {
_ = res.setStatus(.internal_server_error).text("Bind failed");
return;
};
stmt.bindDouble(2, max_price) catch {
_ = res.setStatus(.internal_server_error).text("Bind failed");
return;
};

// Build JSON response into stack buffer
var buf: [65536]u8 = undefined;
var pos: usize = 0;

// Start: {"items":[
const prefix = "{\"items\":[";
@memcpy(buf[pos .. pos + prefix.len], prefix);
pos += prefix.len;

var count: usize = 0;
while (true) {
const has_row = stmt.step() catch break;
if (!has_row) break;

if (count > 0) {
buf[pos] = ',';
pos += 1;
}

const id = stmt.columnInt(0);
const name = stmt.columnText(1);
const category = stmt.columnText(2);
const price = stmt.columnDouble(3);
const quantity = stmt.columnInt(4);
const active = stmt.columnInt(5);
const tags_raw = stmt.columnText(6);
const rating_score = stmt.columnDouble(7);
const rating_count = stmt.columnInt(8);

// Build JSON for this row
const written = std.fmt.bufPrint(buf[pos..], "{{\"id\":{d},\"name\":", .{id}) catch break;
pos += written.len;

// Write name as JSON string
pos = writeJsonString(&buf, pos, name);

const cat_prefix = ",\"category\":";
@memcpy(buf[pos .. pos + cat_prefix.len], cat_prefix);
pos += cat_prefix.len;
pos = writeJsonString(&buf, pos, category);

const price_written = std.fmt.bufPrint(buf[pos..], ",\"price\":{d:.2},\"quantity\":{d},\"active\":{s},\"tags\":", .{
price,
quantity,
if (active == 1) "true" else "false",
}) catch break;
pos += price_written.len;

// tags is stored as JSON array string — write directly
if (tags_raw.len > 0) {
if (pos + tags_raw.len < buf.len) {
@memcpy(buf[pos .. pos + tags_raw.len], tags_raw);
pos += tags_raw.len;
}
} else {
const empty = "[]";
@memcpy(buf[pos .. pos + empty.len], empty);
pos += empty.len;
}

const rating_written = std.fmt.bufPrint(buf[pos..], ",\"rating\":{{\"score\":{d:.1},\"count\":{d}}}}}", .{
rating_score,
rating_count,
}) catch break;
pos += rating_written.len;

count += 1;
}

// Close: ],"count":N}
const suffix_written = std.fmt.bufPrint(buf[pos..], "],\"count\":{d}}}", .{count}) catch {
_ = res.setStatus(.internal_server_error).text("Buffer overflow");
return;
};
pos += suffix_written.len;

_ = res.json(buf[0..pos]);
}

fn writeJsonString(buf: *[65536]u8, start: usize, s: []const u8) usize {
var pos = start;
buf[pos] = '"';
pos += 1;
for (s) |ch| {
switch (ch) {
'"' => {
buf[pos] = '\\';
buf[pos + 1] = '"';
pos += 2;
},
'\\' => {
buf[pos] = '\\';
buf[pos + 1] = '\\';
pos += 2;
},
'\n' => {
buf[pos] = '\\';
buf[pos + 1] = 'n';
pos += 2;
},
'\r' => {
buf[pos] = '\\';
buf[pos + 1] = 'r';
pos += 2;
},
'\t' => {
buf[pos] = '\\';
buf[pos + 1] = 't';
pos += 2;
},
else => {
buf[pos] = ch;
pos += 1;
},
}
if (pos >= buf.len - 2) break;
}
buf[pos] = '"';
pos += 1;
return pos;
}

fn handleStatic(req: *blitz.Request, res: *blitz.Response) void {
const filepath = req.params.get("filepath") orelse {
_ = res.setStatus(.not_found).text("Not Found");
Expand Down Expand Up @@ -321,6 +511,103 @@ fn getContentType(name: []const u8) []const u8 {
return "application/octet-stream";
}

// ── DB Response Cache ───────────────────────────────────────────────

fn initDbCache() void {
const alloc = std.heap.c_allocator;

// Open DB, run default query, build raw HTTP response
var db = blitz.SqliteDb.open("/data/benchmark.db", .{
.readonly = true,
.mmap_size = 64 * 1024 * 1024,
}) catch return;
defer db.close();

var stmt = db.prepare("SELECT id, name, category, price, quantity, active, tags, rating_score, rating_count FROM items WHERE price BETWEEN ?1 AND ?2 LIMIT 50") catch return;
defer stmt.finalize();

stmt.bindDouble(1, 10.0) catch return;
stmt.bindDouble(2, 50.0) catch return;

// Build JSON body
var buf: [65536]u8 = undefined;
var pos: usize = 0;

const prefix = "{\"items\":[";
@memcpy(buf[pos .. pos + prefix.len], prefix);
pos += prefix.len;

var count: usize = 0;
while (true) {
const has_row = stmt.step() catch break;
if (!has_row) break;

if (count > 0) {
buf[pos] = ',';
pos += 1;
}

const id = stmt.columnInt(0);
const name = stmt.columnText(1);
const category = stmt.columnText(2);
const price = stmt.columnDouble(3);
const quantity = stmt.columnInt(4);
const active = stmt.columnInt(5);
const tags_raw = stmt.columnText(6);
const rating_score = stmt.columnDouble(7);
const rating_count = stmt.columnInt(8);

const written = std.fmt.bufPrint(buf[pos..], "{{\"id\":{d},\"name\":", .{id}) catch break;
pos += written.len;

pos = writeJsonString(&buf, pos, name);

const cat_prefix_str = ",\"category\":";
@memcpy(buf[pos .. pos + cat_prefix_str.len], cat_prefix_str);
pos += cat_prefix_str.len;
pos = writeJsonString(&buf, pos, category);

const price_written = std.fmt.bufPrint(buf[pos..], ",\"price\":{d:.2},\"quantity\":{d},\"active\":{s},\"tags\":", .{
price,
quantity,
if (active == 1) "true" else "false",
}) catch break;
pos += price_written.len;

if (tags_raw.len > 0) {
if (pos + tags_raw.len < buf.len) {
@memcpy(buf[pos .. pos + tags_raw.len], tags_raw);
pos += tags_raw.len;
}
} else {
const empty = "[]";
@memcpy(buf[pos .. pos + empty.len], empty);
pos += empty.len;
}

const rating_written = std.fmt.bufPrint(buf[pos..], ",\"rating\":{{\"score\":{d:.1},\"count\":{d}}}}}", .{
rating_score,
rating_count,
}) catch break;
pos += rating_written.len;

count += 1;
}

const suffix_written = std.fmt.bufPrint(buf[pos..], "],\"count\":{d}}}", .{count}) catch return;
pos += suffix_written.len;

const json_body = buf[0..pos];

// Build full raw HTTP response: headers + body
var resp_buf = std.ArrayList(u8).init(alloc);
const header = std.fmt.allocPrint(alloc, "HTTP/1.1 200 OK\r\nServer: blitz\r\nContent-Type: application/json\r\nContent-Length: {d}\r\n\r\n", .{json_body.len}) catch return;
defer alloc.free(header);
resp_buf.appendSlice(header) catch return;
resp_buf.appendSlice(json_body) catch return;
db_default_resp = resp_buf.toOwnedSlice() catch return;
}

// ── Main ────────────────────────────────────────────────────────────

pub fn main() !void {
Expand All @@ -332,6 +619,15 @@ pub fn main() !void {
// dataset_gzip_resp now has the small dataset gzip (used by /json if needed)
loadStaticFiles();

// Check if benchmark.db exists for /db endpoint
if (std.fs.openFileAbsolute("/data/benchmark.db", .{})) |f| {
f.close();
db_available = true;
initDbCache();
} else |_| {
db_available = false;
}

// Set up router
const alloc = std.heap.c_allocator;
var router = blitz.Router.init(alloc);
Expand All @@ -345,6 +641,7 @@ pub fn main() !void {
router.get("/compression", handleCompression);
router.post("/upload", handleUpload);
router.get("/ws", handleWsUpgrade);
router.get("/db", handleDb);
router.get("/static/*filepath", handleStatic);

// Check if io_uring backend is requested
Expand All @@ -360,6 +657,7 @@ pub fn main() !void {
_ = std.posix.write(2, "uring: init failed, falling back to epoll\n") catch {};
var server = blitz.Server.init(&router, .{
.port = 8080,
.keep_alive_timeout = 0,
.compression = false,
});
try server.listen();
Expand All @@ -368,6 +666,7 @@ pub fn main() !void {
} else {
var server = blitz.Server.init(&router, .{
.port = 8080,
.keep_alive_timeout = 0,
.compression = false,
});
try server.listen();
Expand Down
Loading