-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
326 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# This file is machine-generated - editing it directly is not advised | ||
|
||
[[ArgParse]] | ||
deps = ["Logging", "TextWrap"] | ||
git-tree-sha1 = "4a8f4df432fd8e8a96a142c53f9432b9022a92e6" | ||
uuid = "c7e460c6-2fb9-53a9-8c5b-16f535851c63" | ||
version = "1.1.1" | ||
|
||
[[Dates]] | ||
deps = ["Printf"] | ||
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" | ||
|
||
[[JSON]] | ||
deps = ["Dates", "Mmap", "Parsers", "Unicode"] | ||
git-tree-sha1 = "81690084b6198a2e1da36fcfda16eeca9f9f24e4" | ||
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" | ||
version = "0.21.1" | ||
|
||
[[Logging]] | ||
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" | ||
|
||
[[Mmap]] | ||
uuid = "a63ad114-7e13-5084-954f-fe012c677804" | ||
|
||
[[Parsers]] | ||
deps = ["Dates"] | ||
git-tree-sha1 = "50c9a9ed8c714945e01cd53a21007ed3865ed714" | ||
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" | ||
version = "1.0.15" | ||
|
||
[[Printf]] | ||
deps = ["Unicode"] | ||
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" | ||
|
||
[[TextWrap]] | ||
git-tree-sha1 = "9250ef9b01b66667380cf3275b3f7488d0e25faf" | ||
uuid = "b718987f-49a8-5099-9789-dcd902bef87d" | ||
version = "1.0.1" | ||
|
||
[[Unicode]] | ||
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
[deps] | ||
ArgParse = "c7e460c6-2fb9-53a9-8c5b-16f535851c63" | ||
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
# VizTrace | ||
**Trace visualization tool for UnetStack JSON trace files** | ||
|
||
UnetStack 3.3 introduced the event logging framework and JSON trace file format that contains detailed information for automated analysis of network traces. To illustrate the power of the event logging framework, we have built a simple viztrace tool to automatically draw sequence diagrams from the JSON trace files. The tool is written in Julia, and will require a working installation of [Julia](https://julialang.org/downloads/) on your machine to run. | ||
|
||
To illustrate the power of the tool, let us simulate a simple [2-node network](https://unetstack.net/handbook/unet-handbook_getting_started.html) and make a range measurment from node A to B. On node A: | ||
|
||
``` | ||
> range host('B') | ||
999.99976 | ||
``` | ||
|
||
If you look in the `logs` folder in the simulator, you'll find a `trace.nam` file. We can analyze it using the `viztrace` tool: | ||
|
||
```sh | ||
$ julia --project viztrace.jl trace.json | ||
Specify a trace: | ||
1: 1617881734525 [B] AddressAllocReq ⟦ node → arp ⟧ (1 events) | ||
2: 1617881734525 [A] AddressAllocReq ⟦ node → arp ⟧ (1 events) | ||
3: 1617881734531 [A] AddressResolutionReq ⟦ websh → arp ⟧ (1 events) | ||
4: 1617881734595 [A] RangeReq ⟦ websh → ranging ⟧ (23 events) | ||
``` | ||
|
||
So the tool tells us that there are 4 event traces in the `trace.json` file. The first 2 traces are related to address allocations on nodes B and A. The third trace is an address resolution for node B, when we called `host('B')`. The final trace is the actual ranging event, consisting of 23 individual sub-events. Let's explore that in more detail: | ||
|
||
```sh | ||
$ julia --project viztrace.jl -t 4 trace.json > event4.mmd | ||
``` | ||
|
||
This generates a [mermaid](https://mermaid-js.github.io/) sequence diagram for all the events in trace 4: | ||
|
||
``` | ||
sequenceDiagram | ||
participant websh_A as websh/A | ||
participant ranging_A as ranging/A | ||
participant mac_A as mac/A | ||
participant phy_A as phy/A | ||
participant phy_B as phy/B | ||
participant ranging_B as ranging/B | ||
websh_A->>ranging_A: RangeReq | ||
ranging_A-->>websh_A: AGREE | ||
ranging_A->>mac_A: ReservationReq | ||
mac_A->>ranging_A: ReservationRsp | ||
mac_A->>ranging_A: ReservationStatusNtf | ||
ranging_A->>phy_A: ClearReq | ||
phy_A-->>ranging_A: AGREE | ||
ranging_A->>phy_A: TxFrameReq | ||
phy_A-->>ranging_A: AGREE | ||
phy_A->>ranging_A: TxFrameNtf | ||
phy_A->>phy_B: HalfDuplexModem$TX | ||
phy_B->>ranging_B: RxFrameNtf | ||
ranging_B->>phy_B: ClearReq | ||
phy_B-->>ranging_B: AGREE | ||
ranging_B->>phy_B: TxFrameReq | ||
phy_B-->>ranging_B: AGREE | ||
phy_B->>ranging_B: TxFrameNtf | ||
phy_B->>phy_A: HalfDuplexModem$TX | ||
phy_A->>ranging_A: RxFrameNtf | ||
ranging_A->>websh_A: RangeNtf | ||
ranging_A->>mac_A: ReservationCancelReq | ||
mac_A-->>ranging_A: AGREE | ||
mac_A->>ranging_A: ReservationStatusNtf | ||
``` | ||
|
||
We can easily convert this to a nice sequence diagram using the [mermaid command-line interface](https://github.com/mermaid-js/mermaid-cli) or the [mermaid online live editor](https://mermaid-js.github.io/mermaid-live-editor): | ||
|
||
![](https://mermaid.ink/img/eyJjb2RlIjoic2VxdWVuY2VEaWFncmFtXG4gIHBhcnRpY2lwYW50IHdlYnNoX0EgYXMgd2Vic2gvQVxuICBwYXJ0aWNpcGFudCByYW5naW5nX0EgYXMgcmFuZ2luZy9BXG4gIHBhcnRpY2lwYW50IG1hY19BIGFzIG1hYy9BXG4gIHBhcnRpY2lwYW50IHBoeV9BIGFzIHBoeS9BXG4gIHBhcnRpY2lwYW50IHBoeV9CIGFzIHBoeS9CXG4gIHBhcnRpY2lwYW50IHJhbmdpbmdfQiBhcyByYW5naW5nL0JcbiAgd2Vic2hfQS0-PnJhbmdpbmdfQTogUmFuZ2VSZXFcbiAgcmFuZ2luZ19BLS0-PndlYnNoX0E6IEFHUkVFXG4gIHJhbmdpbmdfQS0-Pm1hY19BOiBSZXNlcnZhdGlvblJlcVxuICBtYWNfQS0-PnJhbmdpbmdfQTogUmVzZXJ2YXRpb25Sc3BcbiAgbWFjX0EtPj5yYW5naW5nX0E6IFJlc2VydmF0aW9uU3RhdHVzTnRmXG4gIHJhbmdpbmdfQS0-PnBoeV9BOiBDbGVhclJlcVxuICBwaHlfQS0tPj5yYW5naW5nX0E6IEFHUkVFXG4gIHJhbmdpbmdfQS0-PnBoeV9BOiBUeEZyYW1lUmVxXG4gIHBoeV9BLS0-PnJhbmdpbmdfQTogQUdSRUVcbiAgcGh5X0EtPj5yYW5naW5nX0E6IFR4RnJhbWVOdGZcbiAgcGh5X0EtPj5waHlfQjogSGFsZkR1cGxleE1vZGVtJFRYXG4gIHBoeV9CLT4-cmFuZ2luZ19COiBSeEZyYW1lTnRmXG4gIHJhbmdpbmdfQi0-PnBoeV9COiBDbGVhclJlcVxuICBwaHlfQi0tPj5yYW5naW5nX0I6IEFHUkVFXG4gIHJhbmdpbmdfQi0-PnBoeV9COiBUeEZyYW1lUmVxXG4gIHBoeV9CLS0-PnJhbmdpbmdfQjogQUdSRUVcbiAgcGh5X0ItPj5yYW5naW5nX0I6IFR4RnJhbWVOdGZcbiAgcGh5X0ItPj5waHlfQTogSGFsZkR1cGxleE1vZGVtJFRYXG4gIHBoeV9BLT4-cmFuZ2luZ19BOiBSeEZyYW1lTnRmXG4gIHJhbmdpbmdfQS0-PndlYnNoX0E6IFJhbmdlTnRmXG4gIHJhbmdpbmdfQS0-Pm1hY19BOiBSZXNlcnZhdGlvbkNhbmNlbFJlcVxuICBtYWNfQS0tPj5yYW5naW5nX0E6IEFHUkVFXG4gIG1hY19BLT4-cmFuZ2luZ19BOiBSZXNlcnZhdGlvblN0YXR1c050ZlxuIiwibWVybWFpZCI6e30sInVwZGF0ZUVkaXRvciI6ZmFsc2V9) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
import Pkg | ||
Pkg.instantiate() | ||
|
||
using ArgParse | ||
import JSON | ||
|
||
### analysis functions | ||
|
||
function groups(data; init=Tuple{String,Any}[]) | ||
if "events" ∈ keys(data) | ||
events = data["events"] | ||
n = length(init) | ||
for e ∈ events | ||
groups(e; init) | ||
end | ||
length(init) == n && push!(init, (data["group"], events)) | ||
end | ||
init | ||
end | ||
|
||
function findtrace(cache, ev) | ||
if "threadID" ∈ keys(ev) | ||
id = ev["threadID"] | ||
id ∈ keys(cache) && return cache[id] | ||
end | ||
if "stimulus" ∈ keys(ev) | ||
id = ev["stimulus"]["messageID"] | ||
id ∈ keys(cache) && return cache[id] | ||
end | ||
0 | ||
end | ||
|
||
function addtocache!(cache, ev, trace) | ||
if "threadID" ∈ keys(ev) | ||
id = ev["threadID"] | ||
cache[id] = trace | ||
end | ||
if "stimulus" ∈ keys(ev) | ||
id = ev["stimulus"]["messageID"] | ||
cache[id] = trace | ||
end | ||
if "response" ∈ keys(ev) | ||
id = ev["response"]["messageID"] | ||
cache[id] = trace | ||
end | ||
end | ||
|
||
function splitcomponent(s) | ||
m = match(r"^(?<name>.*)::(?<clazz>[^/]*)/?(?<node>.*)$", s) | ||
m === nothing && return "", "", "" | ||
m[:name], m[:clazz], m[:node] | ||
end | ||
|
||
function prettymessage(msg) | ||
clazz = msg["clazz"] | ||
p = findlast('.', clazz) | ||
p === nothing || (clazz = clazz[p+1:end]) | ||
sender = "sender" ∈ keys(msg) ? msg["sender"] : "" | ||
recipient = "recipient" ∈ keys(msg) ? msg["recipient"] : "" | ||
"$clazz ⟦ $sender → $recipient ⟧" | ||
end | ||
|
||
function analyze(events) | ||
traces = Tuple{Int64,String,Vector{Any}}[] | ||
cache = Dict{String,Int64}() | ||
for ev ∈ events | ||
trace = findtrace(cache, ev) | ||
if trace == 0 | ||
cname, cclazz, cnode = splitcomponent(ev["component"]) | ||
cnode != "" && (cnode = "[$cnode] ") | ||
desc = cnode * cname | ||
if "stimulus" ∈ keys(ev) | ||
desc = cnode * prettymessage(ev["stimulus"]) | ||
elseif "response" ∈ keys(ev) | ||
desc = cnode * prettymessage(ev["response"]) | ||
end | ||
push!(traces, (ev["time"], desc, [ev])) | ||
trace = length(traces) | ||
else | ||
push!(traces[trace][3], ev) | ||
end | ||
addtocache!(cache, ev, trace) | ||
end | ||
traces | ||
end | ||
|
||
function capture!(actors, msgs, origin, node, agent, t, msg, stimulus) | ||
"recipient" ∉ keys(msg) && return | ||
recipient = msg["recipient"] | ||
if startswith(recipient, '#') | ||
stimulus || return | ||
recipient = agent | ||
end | ||
onode = node | ||
okey = msg["messageID"] * "→" * recipient | ||
if okey ∈ keys(origin) | ||
n = origin[okey] | ||
onode == n && return | ||
onode = n | ||
end | ||
stimulus && "sender" ∉ keys(msg) && return | ||
sender = "sender" ∈ keys(msg) ? msg["sender"] : agent | ||
if node != "" | ||
sender = sender * "/" * onode | ||
recipient = recipient * "/" * node | ||
end | ||
clazz = msg["clazz"] | ||
if clazz == "org.arl.fjage.Message" | ||
clazz = msg["performative"] | ||
else | ||
p = findlast('.', clazz) | ||
p === nothing || (clazz = clazz[p+1:end]) | ||
end | ||
origin[okey] = node | ||
sender == recipient && return | ||
push!(actors, (onode, sender)) | ||
push!(actors, (node, recipient)) | ||
push!(msgs, (t, clazz, sender, recipient)) | ||
end | ||
|
||
function sequence(events) | ||
actors = Tuple{String,String}[] | ||
msgs = Tuple{Int64,String,String,String}[] | ||
origin = Dict{String,String}() | ||
for ev ∈ events | ||
cname, cclazz, cnode = splitcomponent(ev["component"]) | ||
"stimulus" ∈ keys(ev) && capture!(actors, msgs, origin, cnode, cname, ev["time"], ev["stimulus"], true) | ||
"response" ∈ keys(ev) && capture!(actors, msgs, origin, cnode, cname, ev["time"], ev["response"], false) | ||
end | ||
unique!(sort!(actors; by = x -> x[1])) | ||
actors, msgs | ||
end | ||
|
||
function mermaid(actors, msgs) | ||
println("sequenceDiagram") | ||
for a ∈ actors | ||
id = replace(a[2], '/' => '_') | ||
println(" participant $id as $(a[2])") | ||
end | ||
for m ∈ msgs | ||
id1 = replace(m[3], '/' => '_') | ||
id2 = replace(m[4], '/' => '_') | ||
print(" $id1") | ||
m[2] == "AGREE" && print('-') | ||
println("->>$id2: $(m[2])") | ||
end | ||
end | ||
|
||
### main | ||
|
||
aps = ArgParseSettings() | ||
@add_arg_table! aps begin | ||
"--group", "-g" | ||
help = "event group number" | ||
arg_type = Int | ||
default = 0 | ||
"--trace", "-t" | ||
help = "trace number" | ||
arg_type = Int | ||
default = 0 | ||
"filename" | ||
help = "JSON trace file" | ||
default = "trace.json" | ||
end | ||
args = parse_args(ARGS, aps) | ||
|
||
if !isfile(args["filename"]) | ||
println(args["filename"], " not found") | ||
exit(1) | ||
end | ||
|
||
grps = groups(JSON.parsefile(args["filename"])) | ||
|
||
if length(grps) < 1 | ||
println("no traces found") | ||
exit(1) | ||
end | ||
|
||
g = args["group"] | ||
length(grps) == 1 && g == 0 && (g = 1) | ||
|
||
if g == 0 | ||
println("Specify an event group:") | ||
for (i, g1) ∈ enumerate(grps) | ||
println(i, ": ", g1[1], " (", length(g1[2]), " events)") | ||
end | ||
exit(1) | ||
end | ||
|
||
if g < 1 || length(grps) < g | ||
println("invalid event group number") | ||
exit(1) | ||
end | ||
|
||
traces = analyze(grps[g][2]) | ||
|
||
t = args["trace"] | ||
length(traces) == 1 && t == 0 && (t = 1) | ||
|
||
if t == 0 | ||
println("Specify a trace:") | ||
for (i, t1) ∈ enumerate(traces) | ||
println(i, ": ", t1[1], " ", t1[2], " (", length(t1[3]), " events)") | ||
end | ||
exit(1) | ||
end | ||
|
||
if t < 1 || length(traces) < t | ||
println("invalid trace number") | ||
exit(1) | ||
end | ||
|
||
mermaid(sequence(traces[t][3])...) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
#define _BSD_SOURCE | ||
#define _DEFAULT_SOURCE | ||
#include <stdlib.h> | ||
#include <errno.h> | ||
#include "pthreadwindows.h" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters