From 30bb4a2fd9c4b3fa00a8df101626047e51e12527 Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Sat, 14 Oct 2023 21:19:20 -0500 Subject: [PATCH] first --- .gitignore | 4 + README.md | 0 build.zig | 74 +++++++++ build.zig.zon | 11 ++ deps.nix | 13 ++ flake.lock | 182 ++++++++++++++++++++++ flake.nix | 76 ++++++++++ src/iperf3.zig | 124 +++++++++++++++ src/loki.zig | 48 ++++++ src/main.zig | 229 ++++++++++++++++++++++++++++ src/test/test-normal.json | 310 ++++++++++++++++++++++++++++++++++++++ 11 files changed, 1071 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 build.zig create mode 100644 build.zig.zon create mode 100644 deps.nix create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 src/iperf3.zig create mode 100644 src/loki.zig create mode 100644 src/main.zig create mode 100644 src/test/test-normal.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4500c5f --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/result* +/zig-cache +/zig-out +/config.json diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..c047c42 --- /dev/null +++ b/build.zig @@ -0,0 +1,74 @@ +const std = @import("std"); + +// Although this function looks imperative, note that its job is to +// declaratively construct a build graph that will be executed by an external +// runner. +pub fn build(b: *std.Build) void { + // Standard target options allows the person running `zig build` to choose + // what target to build for. Here we do not override the defaults, which + // means any target is allowed, and the default is native. Other options + // for restricting supported target set are available. + const target = b.standardTargetOptions(.{}); + + // Standard optimization options allow the person running `zig build` to select + // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not + // set a preferred release mode, allowing the user to decide how to optimize. + const optimize = b.standardOptimizeOption(.{}); + + const exe = b.addExecutable(.{ + .name = "iperf-watcher", + // In this case the main source file is merely a path, however, in more + // complicated build scripts, this could be a generated file. + .root_source_file = .{ .path = "src/main.zig" }, + .target = target, + .optimize = optimize, + }); + + const datetime = b.dependency("datetime", .{}); + exe.addModule("datetime", datetime.module("datetime")); + // exe.linkLibrary(datetime.artifact("datetime")); + + // This declares intent for the executable to be installed into the + // standard location when the user invokes the "install" step (the default + // step when running `zig build`). + b.installArtifact(exe); + + // This *creates* a Run step in the build graph, to be executed when another + // step is evaluated that depends on it. The next line below will establish + // such a dependency. + const run_cmd = b.addRunArtifact(exe); + + // By making the run step depend on the install step, it will be run from the + // installation directory rather than directly from within the cache directory. + // This is not necessary, however, if the application depends on other installed + // files, this ensures they will be present and in the expected location. + run_cmd.step.dependOn(b.getInstallStep()); + + // This allows the user to pass arguments to the application in the build + // command itself, like this: `zig build run -- arg1 arg2 etc` + if (b.args) |args| { + run_cmd.addArgs(args); + } + + // This creates a build step. It will be visible in the `zig build --help` menu, + // and can be selected like this: `zig build run` + // This will evaluate the `run` step rather than the default, which is "install". + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); + + // Creates a step for unit testing. This only builds the test executable + // but does not run it. + const unit_tests = b.addTest(.{ + .root_source_file = .{ .path = "src/main.zig" }, + .target = target, + .optimize = optimize, + }); + + const run_unit_tests = b.addRunArtifact(unit_tests); + + // Similar to creating the run step earlier, this exposes a `test` step to + // the `zig build --help` menu, providing a way for the user to request + // running the unit tests. + const test_step = b.step("test", "Run unit tests"); + test_step.dependOn(&run_unit_tests.step); +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..badef1f --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,11 @@ +.{ + .name = "iperf-watcher", + .version = "0.0.1", + .paths = .{""}, + .dependencies = .{ + .datetime = .{ + .url = "https://git.ocjtech.us/jeff/zig-datetime/archive/15fb4297884e4875833a1a0cdb8394c5269d11f9.tar.gz", + .hash = "12202e0deaf429245daffbc7dcaf810e31123e18f7d511f6ced4e54fad994242f670", + }, + }, +} diff --git a/deps.nix b/deps.nix new file mode 100644 index 0000000..e26bb58 --- /dev/null +++ b/deps.nix @@ -0,0 +1,13 @@ +# generated by zon2nix (https://github.com/nix-community/zon2nix) + +{ linkFarm, fetchzip }: + +linkFarm "zig-packages" [ + { + name = "12202e0deaf429245daffbc7dcaf810e31123e18f7d511f6ced4e54fad994242f670"; + path = fetchzip { + url = "https://git.ocjtech.us/jeff/zig-datetime/archive/15fb4297884e4875833a1a0cdb8394c5269d11f9.tar.gz"; + hash = "sha256-IWQLSYM2HLO3DMaiCwbbBpnWFojDJhnCRyuHCkngDpc="; + }; + } +] diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..9142f07 --- /dev/null +++ b/flake.lock @@ -0,0 +1,182 @@ +{ + "nodes": { + "bash": { + "locked": { + "lastModified": 1697126158, + "narHash": "sha256-XoRmgs8U78oVMVzk4riJpkmXaX1Pk2Ya/wYMmTYt2mA=", + "ref": "refs/heads/main", + "rev": "443dc212854202ddf2bb3bf29ad6d6c1f8829ff6", + "revCount": 11, + "type": "git", + "url": "https://git.ocjtech.us/jeff/nixos-bash-prompt-builder.git" + }, + "original": { + "type": "git", + "url": "https://git.ocjtech.us/jeff/nixos-bash-prompt-builder.git" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1694529238, + "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1689068808, + "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "make-shell": { + "locked": { + "lastModified": 1634940815, + "narHash": "sha256-P69OmveboXzS+es1vQGS4bt+ckwbeIExqxfGLjGuJqA=", + "owner": "ursi", + "repo": "nix-make-shell", + "rev": "8add91681170924e4d0591b22f294aee3f5516f9", + "type": "github" + }, + "original": { + "owner": "ursi", + "repo": "nix-make-shell", + "type": "github" + } + }, + "nix-github-actions": { + "inputs": { + "nixpkgs": [ + "poetry2nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1688870561, + "narHash": "sha256-4UYkifnPEw1nAzqqPOTL2MvWtm3sNGw1UTYTalkTcGY=", + "owner": "nix-community", + "repo": "nix-github-actions", + "rev": "165b1650b753316aa7f1787f3005a8d2da0f5301", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nix-github-actions", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1697059129, + "narHash": "sha256-9NJcFF9CEYPvHJ5ckE8kvINvI84SZZ87PvqMbH6pro0=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "5e4c2ada4fcd54b99d56d7bd62f384511a7e2593", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-unstable", + "type": "indirect" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1697059129, + "narHash": "sha256-9NJcFF9CEYPvHJ5ckE8kvINvI84SZZ87PvqMbH6pro0=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "5e4c2ada4fcd54b99d56d7bd62f384511a7e2593", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "poetry2nix": { + "inputs": { + "flake-utils": "flake-utils_2", + "nix-github-actions": "nix-github-actions", + "nixpkgs": "nixpkgs_2" + }, + "locked": { + "lastModified": 1696512612, + "narHash": "sha256-p6niqag7b4XEHvzWgG0X/xjoW/ZXbAxW8ggd8yReT3Y=", + "owner": "nix-community", + "repo": "poetry2nix", + "rev": "e23218d1599e3369dfc878757e58974017e0ecc8", + "type": "github" + }, + "original": { + "id": "poetry2nix", + "type": "indirect" + } + }, + "root": { + "inputs": { + "bash": "bash", + "flake-utils": "flake-utils", + "make-shell": "make-shell", + "nixpkgs": "nixpkgs", + "poetry2nix": "poetry2nix" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..acc8df1 --- /dev/null +++ b/flake.nix @@ -0,0 +1,76 @@ +{ + description = "iperf watcher"; + + inputs = { + nixpkgs = { + url = "nixpkgs/nixos-unstable"; + }; + flake-utils = { + url = "github:numtide/flake-utils"; + }; + bash = { + url = "git+https://git.ocjtech.us/jeff/nixos-bash-prompt-builder.git"; + }; + make-shell = { + url = "github:ursi/nix-make-shell"; + }; + }; + + outputs = { self, nixpkgs, poetry2nix, flake-utils, bash, ... }@inputs: + flake-utils.lib.eachDefaultSystem + (system: + let + pkgs = import nixpkgs { + inherit system; + }; + in + { + devShells.default = + let + project = "iperf-watcher"; + prompt = ( + bash.build_ps1_prompt + bash.ansi_normal_blue + "${project} - ${bash.username}@${bash.hostname_short}: ${bash.current_working_directory}" + "${project}:${bash.current_working_directory}" + ); + make-shell = import inputs.make-shell { + inherit system; + pkgs = pkgs; + }; + in + make-shell { + packages = [ + # python + # pkgs.poetry + pkgs.iperf3 + pkgs.zon2nix + pkgs.zig_0_11 + pkgs.zls + ]; + env = { + POETRY_VIRTUALENVS_IN_PROJECT = "true"; + POETRY_VIRTUALENVS_OPTIONS_SYSTEM_SITE_PACKAGES = "true"; + PS1 = prompt; + PYTHON_KEYRING_BACKEND = "keyring.backends.null.Keyring"; + IPERF3 = "${pkgs.iperf3}/bin/iperf3"; + }; + setup = '' + export PATH=$(pwd)/.venv/bin:$PATH + ''; + }; + packages = { + iperf-watcher = pkgs.stdenv.mkDerivation { + name = "iperf-watcher"; + src = ./.; + nativeBuildInputs = [ + pkgs.zig_0_11.hook + ]; + postPatch = '' + ln -s ${pkgs.callPackage ./deps.nix { }} $ZIG_GLOBAL_CACHE_DIR/p + ''; + }; + }; + } + ); +} diff --git a/src/iperf3.zig b/src/iperf3.zig new file mode 100644 index 0000000..d53068d --- /dev/null +++ b/src/iperf3.zig @@ -0,0 +1,124 @@ +const std = @import("std"); + +pub const IPerfReturn = struct { + start: struct { + connected: []struct { + socket: u64, + local_host: []const u8, + local_port: u16, + remote_host: []const u8, + remote_port: u16, + }, + version: []const u8, + system_info: []const u8, + sock_bufsize: ?u64 = null, + sndbuf_actual: ?u64 = null, + rcvbuf_actual: ?u64 = null, + timestamp: ?struct { + time: []const u8, + timesecs: u64, + } = null, + accepted_connection: ?struct { + host: []const u8, + port: u16, + } = null, + cookie: ?[]const u8 = null, + tcp_mss_default: ?u64 = null, + target_bitrate: ?u64 = null, + fq_rate: ?u64 = null, + test_start: ?struct { + protocol: []const u8, + num_streams: u64, + blksize: u64, + omit: u64, + duration: u64, + bytes: u64, + blocks: u64, + reverse: u64, + tos: u64, + target_bitrate: u64, + bidir: u64, + fqrate: u64, + }, + }, + intervals: []struct { + streams: []struct { + socket: u64, + start: f128, + end: f128, + seconds: f128, + bytes: u64, + bits_per_second: f128, + omitted: bool, + sender: bool, + }, + sum: struct { + start: f128, + end: f128, + seconds: f128, + bytes: u64, + bits_per_second: f128, + omitted: bool, + sender: bool, + }, + }, + end: struct { + streams: ?[]struct { + sender: struct { + socket: u64, + start: f128, + end: f128, + seconds: f128, + bytes: u64, + bits_per_second: f128, + sender: bool, + }, + receiver: struct { + socket: u64, + start: f128, + end: f128, + seconds: f128, + bytes: u64, + bits_per_second: f128, + sender: bool, + }, + } = null, + sum_sent: ?struct { + start: f128, + end: f128, + seconds: f128, + bytes: u64, + bits_per_second: f128, + sender: bool, + } = null, + sum_received: ?struct { + start: f128, + end: f128, + seconds: f128, + bytes: u64, + bits_per_second: f128, + sender: bool, + } = null, + cpu_utilization_percent: ?struct { + host_total: f128, + host_user: f128, + host_system: f128, + remote_total: f128, + remote_user: f128, + remote_system: f128, + } = null, + receiver_tcp_congestion: ?[]const u8 = null, + }, + @"error": ?[]const u8 = null, +}; + +test "test-normal" { + const input = @embedFile("test/test-normal.json"); + const result = try std.json.parseFromSlice(IPerfReturn, std.testing.allocator, input, .{}); + defer result.deinit(); + // std.debug.print("{}\n", .{result.value}); + // const test_output = try std.fs.cwd().createFile("test.json", .{}); + // try std.json.stringify(result.value, .{}, test_output.writer()); + // test_output.close(); + try std.testing.expect(result.value.@"error" == null); +} diff --git a/src/loki.zig b/src/loki.zig new file mode 100644 index 0000000..57193ff --- /dev/null +++ b/src/loki.zig @@ -0,0 +1,48 @@ +const std = @import("std"); + +pub const LogLevel = enum { + critical, + @"error", + warning, + info, + debug, + trace, + unknown, +}; + +pub const LokiLabels = struct { + job: []const u8, + server: []const u8, + level: LogLevel = .info, +}; + +pub const LokiValue = struct { + ts: i128, + line: []const u8, + + const Self = @This(); + + pub fn jsonStringify(self: Self, jws: anytype) !void { + var tsb: [32]u8 = undefined; + const tsb_size = std.fmt.formatIntBuf( + &tsb, + self.ts, + 10, + .lower, + .{}, + ); + try jws.beginArray(); + try jws.write(tsb[0..tsb_size]); + try jws.write(self.line); + try jws.endArray(); + } +}; + +pub const LokiStream = struct { + stream: LokiLabels, + values: []const LokiValue, +}; + +pub const LokiStreams = struct { + streams: []const LokiStream, +}; diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..08061f8 --- /dev/null +++ b/src/main.zig @@ -0,0 +1,229 @@ +const std = @import("std"); +const datetime = @import("datetime"); + +const iperf3 = @import("iperf3.zig"); +const loki = @import("loki.zig"); + +pub const std_options = struct { + pub const log_level = .debug; + pub const logFn = myLogFn; +}; + +pub fn myLogFn( + comptime level: std.log.Level, + comptime scope: @TypeOf(.EnumLiteral), + comptime format: []const u8, + args: anytype, +) void { + // const scope_prefix = "(" ++ switch (scope) { + // .iperf3, std.log.default_log_scope => @tagName(scope), + // else => if (@intFromEnum(level) <= @intFromEnum(std.log.Level.debug)) + // @tagName(scope) + // else + // return, + // } ++ "): "; + const scope_prefix = "(" ++ @tagName(scope) ++ "): "; + const prefix = " [" ++ comptime level.asText() ++ "] " ++ scope_prefix; + + // Print the message to stderr, silently ignoring any errors + std.debug.getStderrMutex().lock(); + defer std.debug.getStderrMutex().unlock(); + const stderr = std.io.getStdErr().writer(); + const now = datetime.Instant.now().asDateTime(); + nosuspend now.format("YYYY-MM-DDTHH:mm:ss.SSSSSSSSS", .{}, stderr) catch return; + nosuspend stderr.writeAll(prefix) catch return; + nosuspend stderr.print(format, args) catch return; + if (format[format.len - 1] != '\n') nosuspend stderr.writeAll("\n") catch return; +} + +const Config = struct { + loki: struct { + url: []const u8, + username: []const u8, + password: ?[]const u8 = null, + password_file: ?[]const u8 = null, + }, + iperf3: struct { + path: ?[]const u8 = null, + port: ?u16 = null, + }, + + pub fn deinit(self: @This(), allocator: std.mem.Allocator) void { + allocator.free(self.loki.url); + allocator.free(self.loki.username); + if (self.loki.password) |password| allocator.free(password); + if (self.loki.password_file) |password_file| allocator.free(password_file); + if (self.iperf3.path) |path| allocator.free(path); + } +}; + +pub fn ConfigWrapper(comptime T: type) type { + return struct { + arena: *std.heap.ArenaAllocator, + value: T, + + pub fn deinit(self: @This()) void { + const allocator = self.arena.child_allocator; + self.arena.deinit(); + allocator.destroy(self.arena); + } + }; +} + +fn readConfig(allocator: std.mem.Allocator, path: []const u8) !ConfigWrapper(Config) { + var config = ConfigWrapper(Config){ + .arena = try allocator.create(std.heap.ArenaAllocator), + .value = undefined, + }; + config.arena.child_allocator = allocator; + const data = try std.fs.cwd().readFileAlloc(config.arena.child_allocator, path, 1024); + config.value = try std.json.parseFromSliceLeaky(Config, config.arena.child_allocator, data, .{}); + return config; +} + +pub fn main() !void { + const iperf3_log = std.log.scoped(.iperf3); + + const hostname = blk: { + var buffer: [std.os.HOST_NAME_MAX]u8 = undefined; + const name = try std.os.gethostname(&buffer); + iperf3_log.debug("{s}", .{name}); + break :blk name; + }; + + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + const allocator = gpa.allocator(); + const config = try readConfig(allocator, "config.json"); + + const b64 = std.base64.standard.Encoder; + + var auth_buf: [256]u8 = undefined; + const auth = try std.fmt.bufPrint( + &auth_buf, + "{s}:{s}", + .{ + config.value.loki.username, + config.value.loki.password.?, + }, + ); + var auth_encoded_buf: [b64.calcSize(auth_buf.len)]u8 = undefined; + var auth_encoded = b64.encode(&auth_encoded_buf, auth); + var auth_header_buf: [256]u8 = undefined; + var auth_header = try std.fmt.bufPrint(&auth_header_buf, "Basic {s}", .{auth_encoded}); + + const uri = try std.Uri.parse(config.value.loki.url); + + // var headers = std.http.Headers{ .allocator = allocator }; + // try headers.append("Authorization", auth_header); + // try headers.append("Content-Type", "application/json"); + + var client = std.http.Client{ .allocator = allocator }; + defer client.deinit(); + + // const stderr_file = std.io.getStdErr().writer(); + // var stderr_bw = std.io.bufferedWriter(stderr_file); + // const stderr = stderr_bw.writer(); + // _ = stderr; + + var port_buf: [16]u8 = undefined; + var port: []u8 = undefined; + if (config.value.iperf3.port) |p| { + port = try std.fmt.bufPrint(&port_buf, "{d}", .{p}); + } else { + port = try std.fmt.bufPrint(&port_buf, "{d}", .{5201}); + } + + while (true) { + iperf3_log.info("waiting for connection", .{}); + // try stderr_bw.flush(); + var c = std.process.Child.init( + &[_][]const u8{ + if (config.value.iperf3.path) |path| path else "iperf3", + "--server", + "--port", + port, + "--json", + "--one-off", + }, + allocator, + ); + c.stdin_behavior = .Ignore; + c.stdout_behavior = .Pipe; + c.stderr_behavior = .Ignore; + try c.spawn(); + + var reader = c.stdout.?.reader(); + var token_reader = std.json.reader(allocator, reader); + + var obj = try std.json.parseFromTokenSource( + iperf3.IPerfReturn, + allocator, + &token_reader, + .{}, + ); + defer obj.deinit(); + + var line = std.ArrayList(u8).init(allocator); + try std.json.stringify( + obj.value, + .{ + .emit_null_optional_fields = false, + }, + line.writer(), + ); + defer line.deinit(); + + const streams = loki.LokiStreams{ + .streams = &[_]loki.LokiStream{ + .{ + .stream = .{ + .job = "iperf3", + .server = hostname, + .level = if (obj.value.@"error" == null) .info else .@"error", + }, + .values = &[_]loki.LokiValue{ + .{ + .ts = std.time.nanoTimestamp(), + .line = line.items, + }, + }, + }, + }, + }; + + var data = std.ArrayList(u8).init(allocator); + try std.json.stringify( + streams, + .{ + .emit_null_optional_fields = false, + }, + data.writer(), + ); + defer data.deinit(); + + // try stderr.writeAll(data.items); + // try stderr.writeAll("\n"); + // try stderr_bw.flush(); + + var content_length_buf: [16]u8 = undefined; + var content_length = try std.fmt.bufPrint( + &content_length_buf, + "{d}", + .{data.items.len}, + ); + + var headers = std.http.Headers{ .allocator = allocator }; + try headers.append("Authorization", auth_header); + try headers.append("Accept", "application/json"); + try headers.append("Content-Type", "application/json"); + try headers.append("Content-Length", content_length); + + var req = try client.request(.POST, uri, headers, .{}); + try req.start(); + try req.writeAll(data.items); + try req.finish(); + try req.wait(); + + iperf3_log.info("{}\n", .{req.response.status}); + } +} diff --git a/src/test/test-normal.json b/src/test/test-normal.json new file mode 100644 index 0000000..6cba297 --- /dev/null +++ b/src/test/test-normal.json @@ -0,0 +1,310 @@ +{ + "start": { + "connected": [{ + "socket": 5, + "local_host": "127.0.0.1", + "local_port": 5201, + "remote_host": "127.0.0.1", + "remote_port": 58332 + }], + "version": "iperf 3.14", + "system_info": "Linux localhost 6.5.5 #1-NixOS SMP PREEMPT_DYNAMIC Sat Sep 23 09:14:39 UTC 2023 x86_64", + "sock_bufsize": 0, + "sndbuf_actual": 16384, + "rcvbuf_actual": 131072, + "timestamp": { + "time": "Sat, 14 Oct 2023 04:54:02 GMT", + "timesecs": 1697259242 + }, + "accepted_connection": { + "host": "127.0.0.1", + "port": 58328 + }, + "cookie": "jy5fh44dohyv7imf4ouzjm4sx5ssdffs5teh", + "tcp_mss_default": 0, + "target_bitrate": 0, + "fq_rate": 0, + "test_start": { + "protocol": "TCP", + "num_streams": 1, + "blksize": 131072, + "omit": 0, + "duration": 10, + "bytes": 0, + "blocks": 0, + "reverse": 0, + "tos": 0, + "target_bitrate": 0, + "bidir": 0, + "fqrate": 0 + } + }, + "intervals": [{ + "streams": [{ + "socket": 5, + "start": 0, + "end": 1.000003, + "seconds": 1.0000029802322388, + "bytes": 6765608907, + "bits_per_second": 54124709951.794487, + "omitted": false, + "sender": false + }], + "sum": { + "start": 0, + "end": 1.000003, + "seconds": 1.0000029802322388, + "bytes": 6765608907, + "bits_per_second": 54124709951.794487, + "omitted": false, + "sender": false + } + }, { + "streams": [{ + "socket": 5, + "start": 1.000003, + "end": 2.000009, + "seconds": 1.0000059604644775, + "bytes": 7257980928, + "bits_per_second": 58063501338.562828, + "omitted": false, + "sender": false + }], + "sum": { + "start": 1.000003, + "end": 2.000009, + "seconds": 1.0000059604644775, + "bytes": 7257980928, + "bits_per_second": 58063501338.562828, + "omitted": false, + "sender": false + } + }, { + "streams": [{ + "socket": 5, + "start": 2.000009, + "end": 3.000008, + "seconds": 0.99999898672103882, + "bytes": 7245135872, + "bits_per_second": 57961145706.8095, + "omitted": false, + "sender": false + }], + "sum": { + "start": 2.000009, + "end": 3.000008, + "seconds": 0.99999898672103882, + "bytes": 7245135872, + "bits_per_second": 57961145706.8095, + "omitted": false, + "sender": false + } + }, { + "streams": [{ + "socket": 5, + "start": 3.000008, + "end": 4.000009, + "seconds": 1.0000009536743164, + "bytes": 7196770304, + "bits_per_second": 57574107525.052361, + "omitted": false, + "sender": false + }], + "sum": { + "start": 3.000008, + "end": 4.000009, + "seconds": 1.0000009536743164, + "bytes": 7196770304, + "bits_per_second": 57574107525.052361, + "omitted": false, + "sender": false + } + }, { + "streams": [{ + "socket": 5, + "start": 4.000009, + "end": 5.000013, + "seconds": 1.0000040531158447, + "bytes": 6916538368, + "bits_per_second": 55332082676.658981, + "omitted": false, + "sender": false + }], + "sum": { + "start": 4.000009, + "end": 5.000013, + "seconds": 1.0000040531158447, + "bytes": 6916538368, + "bits_per_second": 55332082676.658981, + "omitted": false, + "sender": false + } + }, { + "streams": [{ + "socket": 5, + "start": 5.000013, + "end": 6.000005, + "seconds": 0.9999920129776, + "bytes": 7191789568, + "bits_per_second": 57534776075.545288, + "omitted": false, + "sender": false + }], + "sum": { + "start": 5.000013, + "end": 6.000005, + "seconds": 0.9999920129776, + "bytes": 7191789568, + "bits_per_second": 57534776075.545288, + "omitted": false, + "sender": false + } + }, { + "streams": [{ + "socket": 5, + "start": 6.000005, + "end": 7.000006, + "seconds": 1.0000009536743164, + "bytes": 7165444096, + "bits_per_second": 57323498100.052132, + "omitted": false, + "sender": false + }], + "sum": { + "start": 6.000005, + "end": 7.000006, + "seconds": 1.0000009536743164, + "bytes": 7165444096, + "bits_per_second": 57323498100.052132, + "omitted": false, + "sender": false + } + }, { + "streams": [{ + "socket": 5, + "start": 7.000006, + "end": 8.000009, + "seconds": 1.0000029802322388, + "bytes": 7299137536, + "bits_per_second": 58392926263.518631, + "omitted": false, + "sender": false + }], + "sum": { + "start": 7.000006, + "end": 8.000009, + "seconds": 1.0000029802322388, + "bytes": 7299137536, + "bits_per_second": 58392926263.518631, + "omitted": false, + "sender": false + } + }, { + "streams": [{ + "socket": 5, + "start": 8.000009, + "end": 9.00002, + "seconds": 1.0000109672546387, + "bytes": 7292846080, + "bits_per_second": 58342128787.017433, + "omitted": false, + "sender": false + }], + "sum": { + "start": 8.000009, + "end": 9.00002, + "seconds": 1.0000109672546387, + "bytes": 7292846080, + "bits_per_second": 58342128787.017433, + "omitted": false, + "sender": false + } + }, { + "streams": [{ + "socket": 5, + "start": 9.00002, + "end": 10.000013, + "seconds": 0.99999302625656128, + "bytes": 7296516096, + "bits_per_second": 58372535843.088837, + "omitted": false, + "sender": false + }], + "sum": { + "start": 9.00002, + "end": 10.000013, + "seconds": 0.99999302625656128, + "bytes": 7296516096, + "bits_per_second": 58372535843.088837, + "omitted": false, + "sender": false + } + }, { + "streams": [{ + "socket": 5, + "start": 10.000013, + "end": 10.00009, + "seconds": 7.6999996963422745e-05, + "bytes": 458805, + "bits_per_second": 47668053827.89257, + "omitted": false, + "sender": false + }], + "sum": { + "start": 10.000013, + "end": 10.00009, + "seconds": 7.6999996963422745e-05, + "bytes": 458805, + "bits_per_second": 47668053827.89257, + "omitted": false, + "sender": false + } + }], + "end": { + "streams": [{ + "sender": { + "socket": 5, + "start": 0, + "end": 10.00009, + "seconds": 10.00009, + "bytes": 0, + "bits_per_second": 0, + "sender": false + }, + "receiver": { + "socket": 5, + "start": 0, + "end": 10.00009, + "seconds": 10.00009, + "bytes": 71628226560, + "bits_per_second": 57302065529.410233, + "sender": false + } + }], + "sum_sent": { + "start": 0, + "end": 10.00009, + "seconds": 10.00009, + "bytes": 0, + "bits_per_second": 0, + "sender": false + }, + "sum_received": { + "start": 0, + "end": 10.00009, + "seconds": 10.00009, + "bytes": 71628226560, + "bits_per_second": 57302065529.410233, + "sender": false + }, + "cpu_utilization_percent": { + "host_total": 88.797154269026464, + "host_user": 3.33468598620294, + "host_system": 85.4624582829255, + "remote_total": 0, + "remote_user": 0, + "remote_system": 0 + }, + "receiver_tcp_congestion": "cubic" + } +}