vivarium 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +14 -10
- data/examples/dlopen_demo.rb +50 -0
- data/examples/drop_demo.rb +78 -0
- data/examples/execve_demo.rb +4 -1
- data/examples/file_operation_demo.rb +4 -1
- data/examples/network_client_demo.rb +5 -1
- data/examples/privilege_event_demo.rb +5 -1
- data/examples/raise_demo.rb +5 -1
- data/examples/signal_kill_demo.rb +5 -1
- data/examples/ssl_write_demo.rb +37 -0
- data/lib/vivarium/correlator.rb +13 -8
- data/lib/vivarium/display_filter.rb +158 -0
- data/lib/vivarium/http_decoder.rb +237 -0
- data/lib/vivarium/tree_renderer.rb +85 -10
- data/lib/vivarium/version.rb +1 -1
- data/lib/vivarium.rb +275 -11
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a0e7e80cef690342f163fde6c05fed6859c3ef81aa794fe532c87b00fbe11cd5
|
|
4
|
+
data.tar.gz: 4f2de93c9795fe93ecb0d8291f86a117ac0f595c8c12e2c7b41caabaae7c3273
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9a116f2c50b978a368a05cca351411f9724686746820085b651038d060c8452a8546170cb6e6ac885ab8a721aa20bcf42c5746639d32cbfea0ba8a9452b9687e
|
|
7
|
+
data.tar.gz: fb5b4b5307682fa84b77dcfaa2165a71a5e16deef8d7b7d21d6b3267e82b5a12ce1f73dee946193605e039a247d59613a9c2dffca15ef96ddb502a17c9ce742d
|
data/README.md
CHANGED
|
@@ -21,6 +21,7 @@ Implemented in this repository:
|
|
|
21
21
|
- BPF LSM hooks on `inode_symlink`, `inode_link`, `inode_rename`, `path_chmod`
|
|
22
22
|
- BPF tracepoint on `sys_enter_getdents64`
|
|
23
23
|
- BPF tracepoint on `sys_enter_execve` (captures executable path and first few argv entries as `proc_exec`)
|
|
24
|
+
- BPF tracepoint on `sched_process_fork` (tracks descendants and emits `proc_fork`)
|
|
24
25
|
- BPF LSM hooks for suspicious behavior checks:
|
|
25
26
|
- `ptrace_access_check` (emits `ptrace_check`)
|
|
26
27
|
- `sb_mount` (emits `sb_mount`)
|
|
@@ -32,17 +33,18 @@ Implemented in this repository:
|
|
|
32
33
|
- BPF LSM hook on `socket_create` (flags unusual socket creation as `odd_socket`)
|
|
33
34
|
- BPF LSM hook on `socket_connect` (captures destination family/address/port as `sock_connect`)
|
|
34
35
|
- BPF tracepoints on `sys_enter_sendmsg`, `sys_enter_sendto`, `sys_enter_sendmmsg` (capture UDP/53 DNS QNAME raw bytes as `dns_req`)
|
|
36
|
+
- USDT probes for method span boundaries and exceptions (`span_start`, `span_stop`, `span_raise`)
|
|
37
|
+
- OpenSSL `SSL_write` uprobe event (`ssl_write`)
|
|
35
38
|
- Shared pinned maps on bpffs
|
|
36
39
|
- `config_root_targets` (root PID -> 0/1)
|
|
37
40
|
- `config_spawned_targets` (spawned TID -> 0/1)
|
|
38
|
-
- `
|
|
39
|
-
- `event_write_pos` (cursor for appending into `event_invoked`)
|
|
41
|
+
- `events` (`BPF_RINGBUF_OUTPUT`, shared by system events and span events)
|
|
40
42
|
- Ruby API `Vivarium.observe do ... end`
|
|
41
43
|
- Registers current PID to `config_root_targets`
|
|
42
44
|
- eBPF tracks spawned descendants into `config_spawned_targets` via `sched_process_fork`
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
-
|
|
45
|
+
- `TracePoint` emits span probes on allowlisted call/return and emits `span_raise` on Ruby `:raise`
|
|
46
|
+
- Correlator thread consumes ringbuf events and joins them to spans by `tid`/time window
|
|
47
|
+
- Renders a process tree once at session end
|
|
46
48
|
- Unregisters PID on block exit
|
|
47
49
|
|
|
48
50
|
`event_t` currently:
|
|
@@ -51,6 +53,7 @@ Implemented in this repository:
|
|
|
51
53
|
struct event_t {
|
|
52
54
|
u64 ktime_ns;
|
|
53
55
|
u32 pid;
|
|
56
|
+
u32 tid;
|
|
54
57
|
char event_name[16];
|
|
55
58
|
char payload[256];
|
|
56
59
|
};
|
|
@@ -147,7 +150,7 @@ observer = Vivarium.top_observe
|
|
|
147
150
|
observer.stop
|
|
148
151
|
```
|
|
149
152
|
|
|
150
|
-
|
|
153
|
+
`Vivarium.observe` / `Vivarium.top_observe` produce one process-tree report at session end (block exit, `observer.stop`, or process exit).
|
|
151
154
|
|
|
152
155
|
You can override pin directory via `VIVARIUM_BPF_PIN_DIR` on both sides:
|
|
153
156
|
|
|
@@ -179,17 +182,18 @@ bundle exec vivariumd --pin-dir /sys/fs/bpf/vivarium
|
|
|
179
182
|
## Notes
|
|
180
183
|
|
|
181
184
|
- Thread/Ractor-awareness is not yet implemented.
|
|
182
|
-
-
|
|
185
|
+
- Current transport is ring buffer (`events`) pinned under bpffs.
|
|
186
|
+
- Ring buffer is single-consumer by nature; v1 supports a single observer per host.
|
|
183
187
|
- `payload` is 256 bytes in `event_t`; some event types intentionally use smaller structured slices inside that buffer.
|
|
184
188
|
- `proc_exec` currently stores the executable path plus up to 3 argv entries in 4 fixed 64-byte slots to keep the BPF verifier happy.
|
|
189
|
+
- `span_raise` is emitted on Ruby `:raise` and rendered as `EXCP` lines within the enclosing span.
|
|
190
|
+
- Events that do not match any real span are grouped into synthetic `<no-span>` spans.
|
|
185
191
|
- Each event is tagged with severity metadata: `high` for `setid_change`, `capable_check`, `bprm_creds`, `task_kill`, `ptrace_check`, `sb_mount`, and `kernel_read_file`; others are `medium` by default.
|
|
186
|
-
- In `human` format output, `high` severity events are rendered in red.
|
|
187
192
|
- `capable_check` is intentionally filtered to high-risk capabilities to reduce noise from extremely frequent `capable` hook calls.
|
|
188
|
-
-
|
|
193
|
+
- Output format is textual process tree with a session header and per-span relative timing.
|
|
189
194
|
- `vivariumd` resolves `struct file::f_path` offset from `/sys/kernel/btf/vmlinux` at startup.
|
|
190
195
|
- `vivariumd` also resolves `struct dentry::d_name` offset from `/sys/kernel/btf/vmlinux` at startup.
|
|
191
196
|
- You can override offsets manually with `VIVARIUM_FILE_F_PATH_OFFSET` and `VIVARIUM_DENTRY_D_NAME_OFFSET` if auto-detection fails.
|
|
192
|
-
- `vivariumd` also prints `bpf_trace_printk` lines (`vivarium: pid=... path=...`) to its own logs.
|
|
193
197
|
|
|
194
198
|
## Contributing
|
|
195
199
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "fiddle"
|
|
5
|
+
require "vivarium"
|
|
6
|
+
|
|
7
|
+
FILTER = {
|
|
8
|
+
include_events: %w[dlopen mmap_exec]
|
|
9
|
+
}.freeze
|
|
10
|
+
|
|
11
|
+
# Usage:
|
|
12
|
+
# 1) In another shell (root): sudo bundle exec vivariumd
|
|
13
|
+
# (dlopen uprobe is attached automatically when libc is found)
|
|
14
|
+
# 2) Run this script: bundle exec ruby examples/dlopen_demo.rb
|
|
15
|
+
#
|
|
16
|
+
# Expected output: "DL dlopen" and "DL mmap_exec" events for each
|
|
17
|
+
# library loaded via Fiddle.dlopen.
|
|
18
|
+
#
|
|
19
|
+
# You can disable the dlopen uprobe with `sudo vivariumd --no-dlopen-trace`
|
|
20
|
+
# or point at a specific libc with `sudo vivariumd --libc /lib/x86_64-linux-gnu/libc.so.6`.
|
|
21
|
+
|
|
22
|
+
Vivarium.observe(filter: FILTER) do
|
|
23
|
+
# libm: math functions — almost universally available
|
|
24
|
+
begin
|
|
25
|
+
libm = Fiddle.dlopen("libm.so.6")
|
|
26
|
+
sin_fn = Fiddle::Function.new(libm["sin"], [Fiddle::TYPE_DOUBLE], Fiddle::TYPE_DOUBLE)
|
|
27
|
+
puts "[dlopen_demo] sin(PI/4) = #{sin_fn.call(Math::PI / 4).round(6)}"
|
|
28
|
+
libm.close
|
|
29
|
+
rescue Fiddle::DLError => e
|
|
30
|
+
warn "[dlopen_demo] libm: #{e.message}"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# libsqlite3: a common library that may not be loaded at startup
|
|
34
|
+
begin
|
|
35
|
+
libsqlite3 = Fiddle.dlopen("libsqlite3.so.0")
|
|
36
|
+
puts "[dlopen_demo] libsqlite3 loaded: version = #{Fiddle::Function.new(libsqlite3["sqlite3_libversion"], [], Fiddle::TYPE_VOIDP).call}"
|
|
37
|
+
libsqlite3.close
|
|
38
|
+
rescue Fiddle::DLError => e
|
|
39
|
+
warn "[dlopen_demo] libsqlite3: #{e.message}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Spawn a child process that also calls dlopen — its events should
|
|
43
|
+
# appear under a PROC node in the tree (descendant PID tracking).
|
|
44
|
+
# Unbundle so Bundler does not spawn anything.
|
|
45
|
+
Bundler.with_unbundled_env do
|
|
46
|
+
system("ruby -e 'require \"fiddle\"; Fiddle.dlopen(\"libm.so.6\").close'")
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
puts "[dlopen_demo] done"
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Standalone demo: shows what DROP warning nodes look like in the TreeRenderer
|
|
5
|
+
# output. Does NOT require BPF or vivariumd — constructs RawEvent objects
|
|
6
|
+
# directly and feeds them to TreeRenderer.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# ruby examples/drop_demo.rb
|
|
10
|
+
|
|
11
|
+
$LOAD_PATH.unshift File.join(__dir__, "..", "lib")
|
|
12
|
+
|
|
13
|
+
require "vivarium"
|
|
14
|
+
require "vivarium/correlator"
|
|
15
|
+
require "vivarium/tree_renderer"
|
|
16
|
+
require "vivarium/display_filter"
|
|
17
|
+
require "vivarium_usdt"
|
|
18
|
+
|
|
19
|
+
t0 = 1_000_000_000 # base ktime_ns
|
|
20
|
+
pid = Process.pid
|
|
21
|
+
tid = Process.pid
|
|
22
|
+
method_id = 0x0001_0001
|
|
23
|
+
|
|
24
|
+
# span_start payload: method_id (8B) + file_id (8B) + lineno (8B)
|
|
25
|
+
span_start_payload = [method_id, 0, 10].pack("q<q<q<")
|
|
26
|
+
.ljust(Vivarium::EVENT_PAYLOAD_SIZE, "\x00")
|
|
27
|
+
|
|
28
|
+
events = [
|
|
29
|
+
Vivarium::Correlator::RawEvent.new(
|
|
30
|
+
ktime_ns: t0,
|
|
31
|
+
pid: pid, tid: tid,
|
|
32
|
+
event_name: "span_start",
|
|
33
|
+
payload: span_start_payload,
|
|
34
|
+
dropped_since_last: 0
|
|
35
|
+
),
|
|
36
|
+
# This event carries drop info: 5 events were lost before it arrived
|
|
37
|
+
Vivarium::Correlator::RawEvent.new(
|
|
38
|
+
ktime_ns: t0 + 10_000_000,
|
|
39
|
+
pid: pid, tid: tid,
|
|
40
|
+
event_name: "path_open",
|
|
41
|
+
payload: "/etc/passwd\x00".b.ljust(Vivarium::EVENT_PAYLOAD_SIZE, "\x00"),
|
|
42
|
+
dropped_since_last: 5
|
|
43
|
+
),
|
|
44
|
+
Vivarium::Correlator::RawEvent.new(
|
|
45
|
+
ktime_ns: t0 + 20_000_000,
|
|
46
|
+
pid: pid, tid: tid,
|
|
47
|
+
event_name: "dns_req",
|
|
48
|
+
payload: "\x06google\x03com\x00".b.ljust(Vivarium::EVENT_PAYLOAD_SIZE, "\x00"),
|
|
49
|
+
dropped_since_last: 0
|
|
50
|
+
),
|
|
51
|
+
# Another burst: 12 events dropped just before this sock_connect
|
|
52
|
+
Vivarium::Correlator::RawEvent.new(
|
|
53
|
+
ktime_ns: t0 + 25_000_000,
|
|
54
|
+
pid: pid, tid: tid,
|
|
55
|
+
event_name: "sock_connect",
|
|
56
|
+
payload: [2, 443, 0x7f000001, 0].pack("S<nNN").ljust(Vivarium::EVENT_PAYLOAD_SIZE, "\x00"),
|
|
57
|
+
dropped_since_last: 12
|
|
58
|
+
),
|
|
59
|
+
Vivarium::Correlator::RawEvent.new(
|
|
60
|
+
ktime_ns: t0 + 30_000_000,
|
|
61
|
+
pid: pid, tid: tid,
|
|
62
|
+
event_name: "span_stop",
|
|
63
|
+
payload: "\x00" * Vivarium::EVENT_PAYLOAD_SIZE,
|
|
64
|
+
dropped_since_last: 0
|
|
65
|
+
),
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
Vivarium::TreeRenderer.new(
|
|
69
|
+
events: events,
|
|
70
|
+
method_table: { method_id => "MyClass#my_method" },
|
|
71
|
+
observer_pid: pid,
|
|
72
|
+
main_tid: tid,
|
|
73
|
+
session_start_iso: "2026-06-02T00:00:00.000Z",
|
|
74
|
+
session_start_ktime: t0,
|
|
75
|
+
session_stop_iso: "2026-06-02T00:00:00.030Z",
|
|
76
|
+
session_stop_ktime: t0 + 30_000_000,
|
|
77
|
+
dest: $stdout
|
|
78
|
+
).render
|
data/examples/execve_demo.rb
CHANGED
|
@@ -9,6 +9,9 @@ require "vivarium"
|
|
|
9
9
|
# 2) Run this script: bundle exec ruby examples/execve_demo.rb
|
|
10
10
|
|
|
11
11
|
TMP_PREFIX = "vivarium-exec-demo"
|
|
12
|
+
FILTER = {
|
|
13
|
+
include_events: %w[proc_exec]
|
|
14
|
+
}.freeze
|
|
12
15
|
|
|
13
16
|
def try_step(title)
|
|
14
17
|
puts "[exec-demo] #{title}"
|
|
@@ -20,7 +23,7 @@ end
|
|
|
20
23
|
Dir.mktmpdir(TMP_PREFIX, "/tmp") do |dir|
|
|
21
24
|
output_path = File.join(dir, "execve-demo.out")
|
|
22
25
|
|
|
23
|
-
Vivarium.observe do
|
|
26
|
+
Vivarium.observe(filter: FILTER) do
|
|
24
27
|
try_step("system echo with multiple args") do
|
|
25
28
|
system("/bin/echo", "hello", "from", "vivarium", out: File::NULL)
|
|
26
29
|
end
|
|
@@ -10,6 +10,9 @@ require "vivarium"
|
|
|
10
10
|
# 2) Run this script: bundle exec ruby examples/file_operation_demo.rb
|
|
11
11
|
|
|
12
12
|
TMP_PREFIX = "vivarium-file-demo"
|
|
13
|
+
FILTER = {
|
|
14
|
+
include_events: %w[path_open file_symlink file_hardlink file_rename file_chmod file_getdents]
|
|
15
|
+
}.freeze
|
|
13
16
|
|
|
14
17
|
def try_step(title)
|
|
15
18
|
puts "[file-demo] #{title}"
|
|
@@ -24,7 +27,7 @@ Dir.mktmpdir(TMP_PREFIX, "/tmp") do |dir|
|
|
|
24
27
|
hardlink_path = File.join(dir, "hardlink.txt")
|
|
25
28
|
symlink_path = File.join(dir, "symlink.txt")
|
|
26
29
|
|
|
27
|
-
Vivarium.observe do
|
|
30
|
+
Vivarium.observe(filter: FILTER) do
|
|
28
31
|
try_step("create source file") do
|
|
29
32
|
File.write(source_path, "vivarium sample\n")
|
|
30
33
|
File.read(source_path)
|
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
require "socket"
|
|
5
5
|
require "vivarium"
|
|
6
6
|
|
|
7
|
+
FILTER = {
|
|
8
|
+
include_events: %w[sock_connect dns_req odd_socket]
|
|
9
|
+
}.freeze
|
|
10
|
+
|
|
7
11
|
# Usage:
|
|
8
12
|
# 1) In another shell (root): sudo bundle exec vivariumd
|
|
9
13
|
# 2) Run this script: bundle exec ruby examples/network_client_demo.rb
|
|
@@ -15,7 +19,7 @@ rescue StandardError => e
|
|
|
15
19
|
puts "[client] #{title} failed: #{e.class}: #{e.message}"
|
|
16
20
|
end
|
|
17
21
|
|
|
18
|
-
Vivarium.observe do
|
|
22
|
+
Vivarium.observe(filter: FILTER) do
|
|
19
23
|
# Likely emits sock_connect and dns_req via resolver traffic.
|
|
20
24
|
try_step("system: DNS lookup") do
|
|
21
25
|
system("getent hosts example.com >/dev/null 2>&1 || true")
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
|
|
4
4
|
require "vivarium"
|
|
5
5
|
|
|
6
|
+
FILTER = {
|
|
7
|
+
include_events: %w[setid_change capable_check bprm_creds]
|
|
8
|
+
}.freeze
|
|
9
|
+
|
|
6
10
|
# Usage:
|
|
7
11
|
# 1) In another shell (root): sudo bundle exec vivariumd
|
|
8
12
|
# 2) Run this script: bundle exec ruby examples/privilege_event_demo.rb
|
|
@@ -14,7 +18,7 @@ rescue StandardError => e
|
|
|
14
18
|
puts "[priv-demo] #{title} failed: #{e.class}: #{e.message}"
|
|
15
19
|
end
|
|
16
20
|
|
|
17
|
-
Vivarium.observe do
|
|
21
|
+
Vivarium.observe(filter: FILTER) do
|
|
18
22
|
try_step("attempt setuid(0)") do
|
|
19
23
|
Process::UID.change_privilege(0)
|
|
20
24
|
end
|
data/examples/raise_demo.rb
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
|
|
4
4
|
require "vivarium"
|
|
5
5
|
|
|
6
|
+
FILTER = {
|
|
7
|
+
include_events: %w[span_raise]
|
|
8
|
+
}.freeze
|
|
9
|
+
|
|
6
10
|
def try_step(title)
|
|
7
11
|
puts "[priv-demo] #{title}"
|
|
8
12
|
yield
|
|
@@ -10,7 +14,7 @@ rescue StandardError => e
|
|
|
10
14
|
puts "[priv-demo] #{title} failed: #{e.class}: #{e.message}"
|
|
11
15
|
end
|
|
12
16
|
|
|
13
|
-
Vivarium.observe do
|
|
17
|
+
Vivarium.observe(filter: FILTER) do
|
|
14
18
|
try_step("raise in main") do
|
|
15
19
|
raise "error in main"
|
|
16
20
|
end
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
|
|
4
4
|
require "vivarium"
|
|
5
5
|
|
|
6
|
+
FILTER = {
|
|
7
|
+
include_events: %w[task_kill]
|
|
8
|
+
}.freeze
|
|
9
|
+
|
|
6
10
|
# Usage:
|
|
7
11
|
# 1) In another shell (root): sudo bundle exec vivariumd
|
|
8
12
|
# 2) Run this script: bundle exec ruby examples/signal_kill_demo.rb
|
|
@@ -16,7 +20,7 @@ end
|
|
|
16
20
|
|
|
17
21
|
child_pid = nil
|
|
18
22
|
|
|
19
|
-
Vivarium.observe do
|
|
23
|
+
Vivarium.observe(filter: FILTER) do
|
|
20
24
|
try_step("fork child process") do
|
|
21
25
|
child_pid = fork do
|
|
22
26
|
trap("TERM") { exit!(0) }
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "net/http"
|
|
5
|
+
require "uri"
|
|
6
|
+
require "vivarium"
|
|
7
|
+
|
|
8
|
+
FILTER = {
|
|
9
|
+
include_events: %w[ssl_write]
|
|
10
|
+
}.freeze
|
|
11
|
+
|
|
12
|
+
# Usage:
|
|
13
|
+
# 1) In another shell (root): sudo bundle exec vivariumd
|
|
14
|
+
# (SSL_write uprobe is attached automatically when libssl is found)
|
|
15
|
+
# 2) Run this script: bundle exec ruby examples/ssl_write_demo.rb
|
|
16
|
+
#
|
|
17
|
+
# You can disable the SSL_write uprobe with `sudo vivariumd --no-ssl-trace`
|
|
18
|
+
# or point at a specific library with `sudo vivariumd --libssl /path/to/libssl.so.3`.
|
|
19
|
+
|
|
20
|
+
Vivarium.observe(filter: FILTER) do
|
|
21
|
+
# Net::HTTP uses libssl's SSL_write under the hood. With HTTP/1.1 the
|
|
22
|
+
# request line should appear verbatim in the SSL event payload.
|
|
23
|
+
begin
|
|
24
|
+
uri = URI("https://udzura.jp/")
|
|
25
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
|
|
26
|
+
http.get(uri.request_uri)
|
|
27
|
+
end
|
|
28
|
+
rescue StandardError => e
|
|
29
|
+
warn "[ssl_demo] Net::HTTP failed: #{e.class}: #{e.message}"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# `curl --http2` should produce HTTP/2 traffic; HEADERS frames will be
|
|
33
|
+
# HPACK-decoded if the `http-2` gem is installed on the observer side.
|
|
34
|
+
system("curl -sS --http2 -o /dev/null https://nghttp2.org/ 2>/dev/null")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
puts "[ssl_demo] done"
|
data/lib/vivarium/correlator.rb
CHANGED
|
@@ -6,7 +6,7 @@ require "time"
|
|
|
6
6
|
module Vivarium
|
|
7
7
|
class Correlator
|
|
8
8
|
RawEvent = Struct.new(
|
|
9
|
-
:ktime_ns, :pid, :tid, :event_name, :payload,
|
|
9
|
+
:ktime_ns, :pid, :tid, :event_name, :payload, :dropped_since_last,
|
|
10
10
|
keyword_init: true
|
|
11
11
|
)
|
|
12
12
|
|
|
@@ -17,16 +17,18 @@ module Vivarium
|
|
|
17
17
|
u32 tid;
|
|
18
18
|
char event_name[16];
|
|
19
19
|
char payload[256];
|
|
20
|
+
u64 dropped_since_last;
|
|
20
21
|
};
|
|
21
22
|
C
|
|
22
23
|
|
|
23
24
|
POLL_TIMEOUT_MS = 200
|
|
24
25
|
|
|
25
|
-
def initialize(pin_dir:, observer_pid:, main_tid:, method_id_queue:, dest: $stdout)
|
|
26
|
+
def initialize(pin_dir:, observer_pid:, main_tid:, method_id_queue:, filter: nil, dest: $stdout)
|
|
26
27
|
@pin_dir = pin_dir
|
|
27
28
|
@observer_pid = observer_pid
|
|
28
29
|
@main_tid = main_tid
|
|
29
30
|
@method_id_queue = method_id_queue
|
|
31
|
+
@filter = filter
|
|
30
32
|
@dest = dest
|
|
31
33
|
|
|
32
34
|
@events = []
|
|
@@ -79,6 +81,7 @@ module Vivarium
|
|
|
79
81
|
session_start_ktime: @session_start_ktime,
|
|
80
82
|
session_stop_iso: @session_stop_iso,
|
|
81
83
|
session_stop_ktime: @session_stop_ktime,
|
|
84
|
+
filter: @filter,
|
|
82
85
|
dest: @dest
|
|
83
86
|
).render
|
|
84
87
|
end
|
|
@@ -102,11 +105,12 @@ module Vivarium
|
|
|
102
105
|
bytes = data[0, size].to_s.b
|
|
103
106
|
bytes = bytes.ljust(Vivarium::EVENT_STRUCT_SIZE, "\x00") if bytes.bytesize < Vivarium::EVENT_STRUCT_SIZE
|
|
104
107
|
|
|
105
|
-
ktime_ns
|
|
106
|
-
pid
|
|
107
|
-
tid
|
|
108
|
-
event_name
|
|
109
|
-
payload
|
|
108
|
+
ktime_ns = bytes[Vivarium::EVENT_TS_OFFSET, Vivarium::EVENT_TS_SIZE].unpack1("Q<")
|
|
109
|
+
pid = bytes[Vivarium::EVENT_PID_OFFSET, 4].unpack1("L<")
|
|
110
|
+
tid = bytes[Vivarium::EVENT_TID_OFFSET, 4].unpack1("L<")
|
|
111
|
+
event_name = Vivarium.c_string(bytes[Vivarium::EVENT_NAME_OFFSET, Vivarium::EVENT_NAME_SIZE])
|
|
112
|
+
payload = bytes[Vivarium::EVENT_PAYLOAD_OFFSET, Vivarium::EVENT_PAYLOAD_SIZE].to_s.b
|
|
113
|
+
dropped_since_last = bytes[Vivarium::EVENT_DROPPED_OFFSET, 8].unpack1("Q<")
|
|
110
114
|
|
|
111
115
|
@events_mutex.synchronize do
|
|
112
116
|
@events << RawEvent.new(
|
|
@@ -114,7 +118,8 @@ module Vivarium
|
|
|
114
118
|
pid: pid,
|
|
115
119
|
tid: tid,
|
|
116
120
|
event_name: event_name,
|
|
117
|
-
payload: payload
|
|
121
|
+
payload: payload,
|
|
122
|
+
dropped_since_last: dropped_since_last
|
|
118
123
|
)
|
|
119
124
|
end
|
|
120
125
|
rescue StandardError => e
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "set"
|
|
4
|
+
|
|
5
|
+
module Vivarium
|
|
6
|
+
class DisplayFilter
|
|
7
|
+
attr_reader :include_events, :exclude_events, :include_severities, :include_pids, :include_tids
|
|
8
|
+
|
|
9
|
+
def self.compile(raw)
|
|
10
|
+
return new if raw.nil?
|
|
11
|
+
return raw if raw.is_a?(self)
|
|
12
|
+
|
|
13
|
+
unless raw.respond_to?(:to_h)
|
|
14
|
+
raise ArgumentError, "filter must be a Hash-compatible object"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
new(raw.to_h)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def initialize(raw = {})
|
|
21
|
+
@raw = symbolize_keys(raw || {})
|
|
22
|
+
|
|
23
|
+
@include_events = normalize_string_set(fetch_key(:include_events, :event_names, :events))
|
|
24
|
+
@exclude_events = normalize_string_set(fetch_key(:exclude_events))
|
|
25
|
+
@include_severities = normalize_string_set(fetch_key(:include_severities, :severities, :severity))
|
|
26
|
+
@include_pids = normalize_integer_set(fetch_key(:include_pids, :pids, :pid))
|
|
27
|
+
@include_tids = normalize_integer_set(fetch_key(:include_tids, :tids, :tid))
|
|
28
|
+
|
|
29
|
+
@include_span_names = normalize_string_set(fetch_key(:include_span_names, :span_names))
|
|
30
|
+
@span_pattern = normalize_pattern(fetch_key(:span, :span_pattern))
|
|
31
|
+
|
|
32
|
+
payload_value = fetch_key(:payload)
|
|
33
|
+
@payload_pattern = normalize_pattern(fetch_key(:payload_pattern))
|
|
34
|
+
@payload_patterns_by_event = {}
|
|
35
|
+
if payload_value.is_a?(Hash)
|
|
36
|
+
@payload_patterns_by_event = normalize_payload_map(payload_value)
|
|
37
|
+
else
|
|
38
|
+
@payload_pattern ||= normalize_pattern(payload_value)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def enabled?
|
|
43
|
+
!@raw.empty?
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def needs_payload?
|
|
47
|
+
!@payload_pattern.nil? || !@payload_patterns_by_event.empty?
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def allow_span_name?(span_name)
|
|
51
|
+
return true if @include_span_names.empty? && @span_pattern.nil?
|
|
52
|
+
|
|
53
|
+
name = span_name.to_s
|
|
54
|
+
return true if @include_span_names.include?(name)
|
|
55
|
+
return true if @span_pattern && @span_pattern.match?(name)
|
|
56
|
+
|
|
57
|
+
false
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def allow_event?(event_name:, severity:, span_name:, payload: nil, pid: nil, tid: nil)
|
|
61
|
+
return false unless allow_span_name?(span_name)
|
|
62
|
+
|
|
63
|
+
name = event_name.to_s
|
|
64
|
+
sev = severity.to_s
|
|
65
|
+
|
|
66
|
+
return false if @exclude_events.include?(name)
|
|
67
|
+
return false if !@include_events.empty? && !@include_events.include?(name)
|
|
68
|
+
return false if !@include_severities.empty? && !@include_severities.include?(sev)
|
|
69
|
+
return false if !@include_pids.empty? && !@include_pids.include?(pid.to_i)
|
|
70
|
+
return false if !@include_tids.empty? && !@include_tids.include?(tid.to_i)
|
|
71
|
+
|
|
72
|
+
payload_pattern = @payload_patterns_by_event[name] || @payload_pattern
|
|
73
|
+
if payload_pattern
|
|
74
|
+
return false if payload.nil?
|
|
75
|
+
return false unless payload_pattern.match?(payload.to_s)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
true
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def fetch_key(*keys)
|
|
84
|
+
keys.each do |key|
|
|
85
|
+
return @raw[key] if @raw.key?(key)
|
|
86
|
+
end
|
|
87
|
+
nil
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def symbolize_keys(hash)
|
|
91
|
+
hash.each_with_object({}) do |(k, v), out|
|
|
92
|
+
out[k.respond_to?(:to_sym) ? k.to_sym : k] = v
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def normalize_string_set(value)
|
|
97
|
+
arr = case value
|
|
98
|
+
when nil
|
|
99
|
+
[]
|
|
100
|
+
when Array
|
|
101
|
+
value
|
|
102
|
+
else
|
|
103
|
+
[value]
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
arr.each_with_object(Set.new) do |item, set|
|
|
107
|
+
str = item.to_s.strip
|
|
108
|
+
set << str unless str.empty?
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def normalize_integer_set(value)
|
|
113
|
+
arr = case value
|
|
114
|
+
when nil
|
|
115
|
+
[]
|
|
116
|
+
when Array
|
|
117
|
+
value
|
|
118
|
+
else
|
|
119
|
+
[value]
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
arr.each_with_object(Set.new) do |item, set|
|
|
123
|
+
begin
|
|
124
|
+
set << Integer(item)
|
|
125
|
+
rescue ArgumentError, TypeError
|
|
126
|
+
nil
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def normalize_pattern(value)
|
|
132
|
+
case value
|
|
133
|
+
when nil
|
|
134
|
+
nil
|
|
135
|
+
when Regexp
|
|
136
|
+
value
|
|
137
|
+
when String
|
|
138
|
+
return nil if value.empty?
|
|
139
|
+
|
|
140
|
+
Regexp.new(Regexp.escape(value))
|
|
141
|
+
else
|
|
142
|
+
nil
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def normalize_payload_map(raw_map)
|
|
147
|
+
raw_map.each_with_object({}) do |(event_name, pattern), out|
|
|
148
|
+
key = event_name.to_s.strip
|
|
149
|
+
next if key.empty?
|
|
150
|
+
|
|
151
|
+
normalized = normalize_pattern(pattern)
|
|
152
|
+
next unless normalized
|
|
153
|
+
|
|
154
|
+
out[key] = normalized
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|