first
This commit is contained in:
commit
30bb4a2fd9
11 changed files with 1071 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
/result*
|
||||
/zig-cache
|
||||
/zig-out
|
||||
/config.json
|
0
README.md
Normal file
0
README.md
Normal file
74
build.zig
Normal file
74
build.zig
Normal 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
11
build.zig.zon
Normal 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
13
deps.nix
Normal 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
182
flake.lock
Normal 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
76
flake.nix
Normal 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
124
src/iperf3.zig
Normal 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
48
src/loki.zig
Normal 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
229
src/main.zig
Normal 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
310
src/test/test-normal.json
Normal 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"
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue