mirror of
https://github.com/mikkelam/fast-cli.git
synced 2025-12-20 05:44:05 +00:00
163 lines
5.1 KiB
Zig
163 lines
5.1 KiB
Zig
const std = @import("std");
|
|
const http = std.http;
|
|
|
|
pub const HttpLatencyTester = struct {
|
|
allocator: std.mem.Allocator,
|
|
|
|
const Self = @This();
|
|
|
|
pub fn init(allocator: std.mem.Allocator) Self {
|
|
return Self{
|
|
.allocator = allocator,
|
|
};
|
|
}
|
|
|
|
pub fn deinit(self: *Self) void {
|
|
_ = self;
|
|
}
|
|
|
|
/// Measure latency to multiple URLs using HEAD requests
|
|
/// Returns median latency in milliseconds, or null if all requests failed
|
|
pub fn measureLatency(self: *Self, urls: []const []const u8) !?f64 {
|
|
if (urls.len == 0) return null;
|
|
|
|
var latencies = std.ArrayList(f64).init(self.allocator);
|
|
defer latencies.deinit();
|
|
|
|
// Test each URL
|
|
for (urls) |url| {
|
|
if (self.measureSingleUrl(url)) |latency_ms| {
|
|
try latencies.append(latency_ms);
|
|
} else |_| {
|
|
// Ignore errors, continue with other URLs
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (latencies.items.len == 0) return null;
|
|
|
|
// Return median latency
|
|
return self.calculateMedian(latencies.items);
|
|
}
|
|
|
|
/// Measure latency to a single URL using connection reuse method
|
|
/// First request establishes HTTPS connection, second request measures pure RTT
|
|
fn measureSingleUrl(self: *Self, url: []const u8) !f64 {
|
|
var client = http.Client{ .allocator = self.allocator };
|
|
defer client.deinit();
|
|
|
|
// Parse URL
|
|
const uri = try std.Uri.parse(url);
|
|
|
|
// First request: Establish HTTPS connection (ignore timing)
|
|
{
|
|
const server_header_buffer = try self.allocator.alloc(u8, 4096);
|
|
defer self.allocator.free(server_header_buffer);
|
|
|
|
var req = try client.open(.HEAD, uri, .{
|
|
.server_header_buffer = server_header_buffer,
|
|
});
|
|
defer req.deinit();
|
|
|
|
try req.send();
|
|
try req.finish();
|
|
try req.wait();
|
|
}
|
|
|
|
// Second request: Reuse connection and measure pure HTTP RTT
|
|
const start_time = std.time.nanoTimestamp();
|
|
|
|
{
|
|
const server_header_buffer = try self.allocator.alloc(u8, 4096);
|
|
defer self.allocator.free(server_header_buffer);
|
|
|
|
var req = try client.open(.HEAD, uri, .{
|
|
.server_header_buffer = server_header_buffer,
|
|
});
|
|
defer req.deinit();
|
|
|
|
try req.send();
|
|
try req.finish();
|
|
try req.wait();
|
|
}
|
|
|
|
const end_time = std.time.nanoTimestamp();
|
|
|
|
// Convert to milliseconds
|
|
const latency_ns = end_time - start_time;
|
|
const latency_ms = @as(f64, @floatFromInt(latency_ns)) / std.time.ns_per_ms;
|
|
|
|
return latency_ms;
|
|
}
|
|
|
|
/// Calculate median from array of latencies
|
|
fn calculateMedian(self: *Self, latencies: []f64) f64 {
|
|
_ = self;
|
|
|
|
if (latencies.len == 0) return 0;
|
|
if (latencies.len == 1) return latencies[0];
|
|
|
|
// Sort latencies
|
|
std.mem.sort(f64, latencies, {}, std.sort.asc(f64));
|
|
|
|
const mid = latencies.len / 2;
|
|
if (latencies.len % 2 == 0) {
|
|
// Even number of elements - average of two middle values
|
|
return (latencies[mid - 1] + latencies[mid]) / 2.0;
|
|
} else {
|
|
// Odd number of elements - middle value
|
|
return latencies[mid];
|
|
}
|
|
}
|
|
};
|
|
|
|
const testing = std.testing;
|
|
|
|
test "HttpLatencyTester init/deinit" {
|
|
var tester = HttpLatencyTester.init(testing.allocator);
|
|
defer tester.deinit();
|
|
|
|
// Test with empty URLs
|
|
const result = try tester.measureLatency(&[_][]const u8{});
|
|
try testing.expect(result == null);
|
|
}
|
|
|
|
test "calculateMedian" {
|
|
var tester = HttpLatencyTester.init(testing.allocator);
|
|
defer tester.deinit();
|
|
|
|
// Test odd number of elements
|
|
var latencies_odd = [_]f64{ 10.0, 20.0, 30.0 };
|
|
const median_odd = tester.calculateMedian(&latencies_odd);
|
|
try testing.expectEqual(@as(f64, 20.0), median_odd);
|
|
|
|
// Test even number of elements
|
|
var latencies_even = [_]f64{ 10.0, 20.0, 30.0, 40.0 };
|
|
const median_even = tester.calculateMedian(&latencies_even);
|
|
try testing.expectEqual(@as(f64, 25.0), median_even);
|
|
|
|
// Test single element
|
|
var latencies_single = [_]f64{15.0};
|
|
const median_single = tester.calculateMedian(&latencies_single);
|
|
try testing.expectEqual(@as(f64, 15.0), median_single);
|
|
}
|
|
|
|
test "HttpLatencyTester integration with example.com" {
|
|
var tester = HttpLatencyTester.init(testing.allocator);
|
|
defer tester.deinit();
|
|
|
|
// Test with real HTTP endpoint
|
|
const urls = [_][]const u8{"https://example.com"};
|
|
const result = tester.measureLatency(&urls) catch |err| {
|
|
// Allow network errors in CI environments
|
|
std.log.warn("Network error in integration test (expected in CI): {}", .{err});
|
|
return;
|
|
};
|
|
|
|
if (result) |latency_ms| {
|
|
// Reasonable latency bounds (1ms to 5000ms)
|
|
try testing.expect(latency_ms >= 1.0);
|
|
try testing.expect(latency_ms <= 5000.0);
|
|
std.log.info("example.com latency: {d:.1}ms", .{latency_ms});
|
|
}
|
|
}
|