diff --git a/README.md b/README.md index 8f0e3ac..fa219e1 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ coroutine pool. The full A–B protocol is the state machine visualised: -![graph](./graph/graph.svg) +![graph](./graph/fsm.svg) This pattern mirrors the **Erlang client-server model**: the server (A) is passive, the client (B) sends messages and waits for replies. diff --git a/build.zig b/build.zig index f62887c..e67f065 100644 --- a/build.zig +++ b/build.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const addGraphFile = @import("polyrole").addGraphFile; pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); @@ -9,18 +10,19 @@ pub fn build(b: *std.Build) void { "backend", "Override the default event loop backend (io_uring, epoll, kqueue, iocp, poll)", ); + //polyrole const polyrole = b.dependency("polyrole", .{ .target = target, .optimize = optimize, - }); + }).module("root"); //zio const zio = b.dependency("zio", .{ .target = target, .optimize = optimize, .backend = backend, - }); + }).module("zio"); const tracy = b.option([]const u8, "tracy", "Enable Tracy integration. Supply path to Tracy source"); const tracy_callstack = b.option(bool, "tracy-callstack", "Include callstack information with Tracy data. Does nothing if -Dtracy is not provided") orelse (tracy != null); @@ -41,19 +43,21 @@ pub fn build(b: *std.Build) void { options.addOption([]const u8, "time", &iso_buf); options.addOption([]const u8, "source_version", git_head); + const root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + .imports = &.{ + .{ .name = "polyrole", .module = polyrole }, + .{ .name = "zio", .module = zio }, + }, + .strip = if (tracy != null) false else if (optimize == .Debug) false else true, + }); + const exe = b.addExecutable(.{ .name = "z3", .use_llvm = true, //arch sframe bug: https://codeberg.org/ziglang/zig/issues/30959 - .root_module = b.createModule(.{ - .root_source_file = b.path("src/main.zig"), - .target = target, - .optimize = optimize, - .imports = &.{ - .{ .name = "polyrole", .module = polyrole.module("root") }, - .{ .name = "zio", .module = zio.module("zio") }, - }, - .strip = if (tracy != null) false else if (optimize == .Debug) false else true, - }), + .root_module = root_module, }); exe.root_module.addOptions("build_options", options); @@ -89,6 +93,35 @@ pub fn build(b: *std.Build) void { if (b.args) |args| { run_cmd.addArgs(args); } + //generte graph + const run_gen_graph = b.step("gen-graph", "Generate graph.dot and graph.svg"); + { + const graph_exe = b.addExecutable(.{ + .name = "generate_graph", + .root_module = b.createModule(.{ .root_source_file = b.path("tools/graph.zig"), .target = target, .optimize = optimize, .imports = &.{ + .{ .name = "polyrole", .module = polyrole }, + .{ .name = "root_module", .module = root_module }, + } }), + .use_llvm = true, + }); + + const run = b.addRunArtifact(graph_exe); + const graph_file = run.captureStdOut(.{}); + + const dot_cmd = b.addSystemCommand(&.{"dot"}); + + dot_cmd.addArg("-Tsvg"); + + dot_cmd.addFileArg(graph_file); + + const graph_svg = dot_cmd.captureStdOut(.{}); + + const install_graph_dot = b.addInstallFileWithDir(graph_file, .{ .custom = "../graph" }, b.fmt("{s}.dot", .{"fsm"})); + const install_graph_svg = b.addInstallFileWithDir(graph_svg, .{ .custom = "../graph" }, b.fmt("{s}.svg", .{"fsm"})); + + run_gen_graph.dependOn(&install_graph_svg.step); + run_gen_graph.dependOn(&install_graph_dot.step); + } const exe_tests = b.addTest(.{ .root_module = exe.root_module, diff --git a/graph/graph.dot b/graph/fsm.dot similarity index 63% rename from graph/graph.dot rename to graph/fsm.dot index 4def53b..8f825e3 100644 --- a/graph/graph.dot +++ b/graph/fsm.dot @@ -7,15 +7,15 @@ digraph fsm_state_graph { } subgraph cluster_fsm_1 { label = "s3: { .client, .server }"; - 0[shape=rect, label=" c [0] Start .client -> { .server }", color = "red"]; - 1[shape=rect, label=" S [1] ServerLookupCredential .server -> { .client }", color = "green"]; - 2[shape=rect, label=" c [2] SigV4 .client -> { .server }", color = "blue"]; - 3[shape=rect, label=" c [3] Route .client -> { .server }", color = "brown"]; - 4[shape=rect, label=" c [4] Send .client -> { .server }", color = "navy"]; - 5[shape=rect, label=" S [5] Success .server -> { .client }", color = "teal"]; - 7[shape=rect, label=" S [7] Error .server -> { .client }", color = "magenta"]; - 8[shape=rect, label=" c [8] Send .client -> { .server }", color = "darkred"]; - 9[shape=rect, label=" S [9] WaitServer .server -> { .client }", color = "darkgreen"]; + 0[shape=rect, label="🅱️[0] Start .client -> { .server }", color = "red"]; + 1[shape=rect, label="🅰️[1] ServerLookupCredential .server -> { .client }", color = "green"]; + 2[shape=rect, label="🅱️[2] SigV4 .client -> { .server }", color = "blue"]; + 3[shape=rect, label="🅱️[3] Route .client -> { .server }", color = "brown"]; + 4[shape=rect, label="🅱️[4] Send .client -> { .server }", color = "navy"]; + 5[shape=rect, label="🅰️[5] Success .server -> { .client }", color = "teal"]; + 7[shape=rect, label="🅰️[7] Error .server -> { .client }", color = "magenta"]; + 8[shape=rect, label="🅱️[8] Send .client -> { .server }", color = "darkred"]; + 9[shape=rect, label="🅰️[9] WaitServer .server -> { .client }", color = "darkgreen"]; } 0 -> 1 [label = "req_credential_and_id", color = "red", fontcolor = "red"]; 0 -> 8 [label = "failed", color = "red", fontcolor = "red"]; diff --git a/graph/fsm.svg b/graph/fsm.svg new file mode 100644 index 0000000..85abacf --- /dev/null +++ b/graph/fsm.svg @@ -0,0 +1,200 @@ + + + + + + +fsm_state_graph + + +cluster_transitions + +State Transitions + + +cluster_fsm_0 + +polyrole_exit + + +cluster_fsm_1 + +s3: { .client, .server } + + + +6 + +[6] Exit + + + +0 + +🅱️[0] Start .client -> { .server } + + + +1 + +🅰️[1] ServerLookupCredential .server -> { .client } + + + +0->1 + + +req_credential_and_id + + + +8 + +🅱️[8] Send .client -> { .server } + + + +0->8 + + +failed + + + +9 + +🅰️[9] WaitServer .server -> { .client } + + + +0->9 + + +get_metrics + + + +2 + +🅱️[2] SigV4 .client -> { .server } + + + +1->2 + + +ok + + + +1->8 + + +no_access_key + + + +3 + +🅱️[3] Route .client -> { .server } + + + +2->3 + + +ok + + + +2->8 + + +failed + + + +4 + +🅱️[4] Send .client -> { .server } + + + +3->4 + + +ok + + + +3->8 + + +failed + + + +5 + +🅰️[5] Success .server -> { .client } + + + +4->5 + + +finish + + + +7 + +🅰️[7] Error .server -> { .client } + + + +4->7 + + +failed + + + +5->6 + + +exit + + + +7->6 + + +exit + + + +8->7 + + +finish + + + +8->7 + + +failed + + + +9->4 + + +notify + + + diff --git a/graph/graph.svg b/graph/graph.svg deleted file mode 100644 index 84b5f4c..0000000 --- a/graph/graph.svg +++ /dev/null @@ -1,200 +0,0 @@ - - - - - - -fsm_state_graph - - -cluster_transitions - -State Transitions - - -cluster_fsm_0 - -polyrole_exit - - -cluster_fsm_1 - -s3: { .client, .server } - - - -6 - -[6] Exit - - - -0 - - c [0] Start .client -> { .server } - - - -1 - - S [1] ServerLookupCredential .server -> { .client } - - - -0->1 - - -req_credential_and_id - - - -8 - - c [8] Send .client -> { .server } - - - -0->8 - - -failed - - - -9 - - S [9] WaitServer .server -> { .client } - - - -0->9 - - -get_metrics - - - -2 - - c [2] SigV4 .client -> { .server } - - - -1->2 - - -ok - - - -1->8 - - -no_access_key - - - -3 - - c [3] Route .client -> { .server } - - - -2->3 - - -ok - - - -2->8 - - -failed - - - -4 - - c [4] Send .client -> { .server } - - - -3->4 - - -ok - - - -3->8 - - -failed - - - -5 - - S [5] Success .server -> { .client } - - - -4->5 - - -finish - - - -7 - - S [7] Error .server -> { .client } - - - -4->7 - - -failed - - - -5->6 - - -exit - - - -7->6 - - -exit - - - -8->7 - - -finish - - - -8->7 - - -failed - - - -9->4 - - -notify - - - diff --git a/src/main.zig b/src/main.zig index 47446b4..f989333 100644 --- a/src/main.zig +++ b/src/main.zig @@ -9,8 +9,8 @@ const polyrole = @import("polyrole"); const s3 = @import("s3.zig"); const zs3 = @import("zs3.zig"); const run = @import("run.zig"); -const Runner = s3.Runner; -const EnterState = s3.Start; +pub const Runner = s3.Runner; +pub const EnterFsmState = s3.Start; const build_options = @import("build_options"); pub fn main(init: std.process.Init) !void { @@ -107,14 +107,6 @@ pub fn main(init: std.process.Init) !void { std.log.warn("Using built-in default credentials (admin:minioadmin:minioadmin) — DO NOT USE IN PRODUCTION", .{}); } - // var dot_file = try Io.Dir.cwd().createFile(io, "graph.dot", .{}); - // var dot_file_wirter = dot_file.writer(io, &.{}); - // var graph = try polyrole.Graph.initWithFsm(gpa, EnterState); - // var emoj_map: std.StringHashMap([]const u8) = .init(gpa); - // try emoj_map.put("client", " c "); - // try emoj_map.put("server", " S "); - // try graph.generateDot(&emoj_map, &dot_file_wirter.interface); - Io.Dir.cwd().createDir(io, data_dir, .default_dir) catch |err| switch (err) { error.PathAlreadyExists => {}, else => return err, @@ -225,7 +217,7 @@ fn accept_loop( try ctx.init(data_dir, tmp_dir, io, stream, gpa); const client_future = try io.concurrent(run.client, .{ - Runner.idFromState(EnterState), + Runner.idFromState(EnterFsmState), msg_channel, ctx, clean_channel, diff --git a/tools/graph.zig b/tools/graph.zig new file mode 100644 index 0000000..71a6641 --- /dev/null +++ b/tools/graph.zig @@ -0,0 +1,25 @@ +const std = @import("std"); +const polyrole = @import("polyrole"); +const root_module = @import("root_module"); +const EnterFsmState = root_module.EnterFsmState; +const Runner = root_module.Runner; + +pub fn main(init: std.process.Init) !void { + { + const io = init.io; + var gpa_instance = std.heap.DebugAllocator(.{}){}; + const gpa = gpa_instance.allocator(); + var graph = try polyrole.Graph.initWithFsm(gpa, EnterFsmState); + defer graph.deinit(); + var stdout_buffer: [1024]u8 = undefined; + var stdout_writer = std.Io.File.stdout().writer(io, &stdout_buffer); + const writer = &stdout_writer.interface; + defer writer.flush() catch @panic("Failed to flush"); + + var emoj_map: std.StringHashMap([]const u8) = .init(gpa); + try emoj_map.put("client", "🅱️"); + try emoj_map.put("server", "🅰️"); + + try graph.generateDot(&emoj_map, writer); + } +}