This commit is contained in:
Jeffrey C. Ollie 2023-10-14 21:19:20 -05:00
commit 30bb4a2fd9
Signed by: jeff
GPG key ID: 6F86035A6D97044E
11 changed files with 1071 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
/result*
/zig-cache
/zig-out
/config.json

0
README.md Normal file
View file

74
build.zig Normal file
View file

@ -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);
}

11
build.zig.zon Normal file
View file

@ -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",
},
},
}

13
deps.nix Normal file
View file

@ -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=";
};
}
]

182
flake.lock Normal file
View file

@ -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
}

76
flake.nix Normal file
View file

@ -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
'';
};
};
}
);
}

124
src/iperf3.zig Normal file
View file

@ -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);
}

48
src/loki.zig Normal file
View file

@ -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,
};

229
src/main.zig Normal file
View file

@ -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});
}
}

310
src/test/test-normal.json Normal file
View file

@ -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"
}
}