vivarium 0.0.1 → 0.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a7b939d2599fb7e7115ef398bcc83830be78d3b8576a05e5c6d7c1adec4003a1
4
- data.tar.gz: 39a158bacb6b706a4b77144b47554f027822263662818a9175b1828109538406
3
+ metadata.gz: 1bc495e3c2b8f9d99a2b1c84706b5e00913cc08831b3103b0460db0d4027c77b
4
+ data.tar.gz: 1ee086243fdc153da5bac6a2bec8d0afe8e644ceac2fef2308b7723b89533338
5
5
  SHA512:
6
- metadata.gz: 6b17e753a8ccaf72cb969e5fdb8b005d2e31fc2aa10f1d33984d93ba2b7c6a1e2d8e28560b39a4cb9f2860f453a498c3c9c974db9b4b316b4162b78b3e3841e6
7
- data.tar.gz: 0bcd982884661f9e93ba27c3dc79cb1a952de8c68f715572022917cad708a771e4f4017af4bf12257f7f71ed38119535d14a5745d06666c580d6486c1d15b820
6
+ metadata.gz: a203dd1cbaeff9fe0f4bf464a01ff49f131a104ca9e1eb14b6c08dd4371f49acf31c493119eee70114cafacbc6aa8a42976a9e6c230b1d828fe442e738c57c54
7
+ data.tar.gz: a31a64e20f2f7d0956626658b5d6f91e0cd5333f97854ad3388c41cc0d3b0b8d233850c3bdaaf60663c9152d6cf35e4034355e37c50fc913687ad05295dffaba
data/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Vivarium
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/vivarium.svg)](https://rubygems.org/gems/vivarium)
4
+
5
+ RubyGems: https://rubygems.org/gems/vivarium
6
+
3
7
  Vivarium is an observation and sandbox helper for Ruby.
4
8
 
5
9
  It combines:
@@ -15,11 +19,13 @@ Implemented in this repository:
15
19
 
16
20
  - BPF LSM hook on `file_open`
17
21
  - Shared pinned maps on bpffs
18
- - `config_targets` (PID -> 0/1)
22
+ - `config_root_targets` (root PID -> 0/1)
23
+ - `config_spawned_targets` (spawned TID -> 0/1)
19
24
  - `event_invoked` (array length 64 with `event_t` records)
20
25
  - `event_write_pos` (cursor for appending into `event_invoked`)
21
26
  - Ruby API `Vivarium.observe do ... end`
22
- - Registers current PID to `config_targets`
27
+ - Registers current PID to `config_root_targets`
28
+ - eBPF tracks spawned descendants into `config_spawned_targets` via `sched_process_fork`
23
29
  - On each `:return` / `:c_return`, drains `event_invoked`
24
30
  - Prints stack trace + events
25
31
  - Clears event slots and cursor
@@ -71,19 +77,34 @@ sudo bundle exec vivariumd
71
77
  require "vivarium"
72
78
 
73
79
  Vivarium.observe do
74
- File.read("/etc/hosts")
80
+ File.read("/etc/passwd")
75
81
  end
76
82
  ```
77
83
 
84
+ You can also start top-level observation without a block (it keeps observing until process exit):
85
+
86
+ ```ruby
87
+ require "vivarium"
88
+
89
+ observer = Vivarium.top_observe
90
+ # or: Vivarium.observe
91
+ # do anything ...
92
+ observer.stop
93
+ ```
94
+
95
+ By default, Vivarium excludes its own internal frames from stack output. Set `VIVARIUM_FILTER_INTERNAL_FRAMES=0` to disable this filter.
96
+
78
97
  You can override pin directory via `VIVARIUM_BPF_PIN_DIR` on both sides:
79
98
 
80
99
  ```bash
81
100
  VIVARIUM_BPF_PIN_DIR=/sys/fs/bpf/vivarium bundle exec vivariumd
82
101
  ```
83
102
 
103
+ Use `Vivarium.bpf_pin_dir = "/sys/fs/bpf/..."` in Ruby code to set it programmatically.
104
+
84
105
  ```ruby
85
- ENV["VIVARIUM_BPF_PIN_DIR"] = "/sys/fs/bpf/vivarium"
86
106
  require "vivarium"
107
+ Vivarium.bpf_pin_dir = "/sys/fs/bpf/vivarium"
87
108
  ```
88
109
 
89
110
  ## Development
@@ -108,6 +129,7 @@ bundle exec vivariumd --pin-dir /sys/fs/bpf/vivarium
108
129
  - Current output format is textual and intended for iteration.
109
130
  - `vivariumd` resolves `struct file::f_path` offset from `/sys/kernel/btf/vmlinux` at startup.
110
131
  - You can override offset manually with `VIVARIUM_FILE_F_PATH_OFFSET` if auto-detection fails.
132
+ - `vivariumd` also prints `bpf_trace_printk` lines (`vivarium: pid=... path=...`) to its own logs.
111
133
 
112
134
  ## Contributing
113
135
 
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Vivarium
6
+ class Logger
7
+ FORMATS = %i[human json].freeze
8
+
9
+ # dest: IO object or file path string
10
+ # format: :human or :json
11
+ # TODO: support flushing in bulk for performance
12
+ def initialize(dest: $stdout, format: :human)
13
+ @format = format.to_sym
14
+ raise ArgumentError, "unknown format: #{@format}; choose from #{FORMATS.join(', ')}" unless FORMATS.include?(@format)
15
+
16
+ if dest.is_a?(String)
17
+ @io = File.open(dest, "a")
18
+ @owned = true
19
+ else
20
+ @io = dest
21
+ @owned = false
22
+ end
23
+ end
24
+
25
+ def log(events, tp, stack)
26
+ case @format
27
+ when :human then log_human(events, tp, stack)
28
+ when :json then log_json(events, tp, stack)
29
+ end
30
+ @io.flush
31
+ end
32
+
33
+ def info(message)
34
+ @io.puts("[vivarium] #{message}")
35
+ @io.flush
36
+ end
37
+
38
+ def close
39
+ @io.close if @owned
40
+ end
41
+
42
+ private
43
+
44
+ def log_human(events, tp, stack)
45
+ @io.puts "[vivarium] #{events.size} event(s) at #{tp.defined_class}##{tp.method_id} (#{tp.event})"
46
+ @io.puts " location: #{tp.path}:#{tp.lineno}"
47
+ events.each do |event|
48
+ @io.puts " pid=#{event.pid} #{event.event_name} payload=#{event.payload.inspect}"
49
+ end
50
+ @io.puts " stack:"
51
+ stack.each do |loc|
52
+ @io.puts " #{loc.path}:#{loc.lineno}:in #{loc.base_label}"
53
+ end
54
+ end
55
+
56
+ def log_json(events, tp, stack)
57
+ entry = {
58
+ at: "#{tp.defined_class}##{tp.method_id}",
59
+ event: tp.event.to_s,
60
+ path: tp.path,
61
+ lineno: tp.lineno,
62
+ events: events.map { |e| { pid: e.pid, event_name: e.event_name, payload: e.payload } },
63
+ stack: stack.map { |loc| "#{loc.path}:#{loc.lineno}:in #{loc.base_label}" }
64
+ }
65
+ @io.puts JSON.generate(entry)
66
+ end
67
+ end
68
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Vivarium
4
- VERSION = "0.0.1"
4
+ VERSION = "0.1.1"
5
5
  end
data/lib/vivarium.rb CHANGED
@@ -6,20 +6,33 @@ require "optparse"
6
6
  require "pathname"
7
7
  require "rbbcc"
8
8
  require_relative "vivarium/version"
9
+ require_relative "vivarium/logger"
9
10
 
10
11
  module Vivarium
11
12
  class Error < StandardError; end
12
13
 
13
14
  PIN_DIR = ENV.fetch("VIVARIUM_BPF_PIN_DIR", "/sys/fs/bpf/vivarium")
14
- CONFIG_TARGETS_PIN = File.join(PIN_DIR, "config_targets")
15
+ CONFIG_ROOT_TARGETS_PIN = File.join(PIN_DIR, "config_root_targets")
16
+ CONFIG_SPAWNED_TARGETS_PIN = File.join(PIN_DIR, "config_spawned_targets")
17
+ CONFIG_TARGETS_PIN = CONFIG_ROOT_TARGETS_PIN
15
18
  EVENT_INVOKED_PIN = File.join(PIN_DIR, "event_invoked")
16
19
  EVENT_WRITE_POS_PIN = File.join(PIN_DIR, "event_write_pos")
17
20
 
18
21
  EVENT_NAME_SIZE = 16
19
- EVENT_PAYLOAD_SIZE = 64
22
+ EVENT_PAYLOAD_SIZE = 256
20
23
  EVENT_STRUCT_SIZE = 4 + EVENT_NAME_SIZE + EVENT_PAYLOAD_SIZE
21
24
  EVENT_CAPACITY = 64
22
25
 
26
+ @bpf_pin_dir = PIN_DIR
27
+
28
+ class << self
29
+ attr_writer :bpf_pin_dir
30
+
31
+ def bpf_pin_dir
32
+ @bpf_pin_dir || PIN_DIR
33
+ end
34
+ end
35
+
23
36
  Event = Struct.new(:pid, :event_name, :payload, keyword_init: true) do
24
37
  def empty?
25
38
  pid.to_i.zero? && event_name.to_s.empty? && payload.to_s.empty?
@@ -30,18 +43,33 @@ module Vivarium
30
43
  bytes = bytes.ljust(EVENT_STRUCT_SIZE, "\x00")
31
44
 
32
45
  pid = bytes[0, 4].unpack1("L<")
33
- event_name = bytes[4, EVENT_NAME_SIZE].delete("\x00")
34
- payload = bytes[4 + EVENT_NAME_SIZE, EVENT_PAYLOAD_SIZE].delete("\x00")
46
+ event_name = c_string(bytes[4, EVENT_NAME_SIZE])
47
+ payload = c_string(bytes[4 + EVENT_NAME_SIZE, EVENT_PAYLOAD_SIZE])
35
48
 
36
49
  new(pid: pid, event_name: event_name, payload: payload)
37
50
  end
51
+
52
+ def self.c_string(bytes)
53
+ str = bytes.to_s.b
54
+ nul = str.index("\x00")
55
+ return str if nul.nil?
56
+
57
+ str[0, nul]
58
+ end
38
59
  end
39
60
 
40
61
  class MapStore
41
- def initialize(pin_dir: PIN_DIR)
62
+ def initialize(pin_dir: Vivarium.bpf_pin_dir)
42
63
  @pin_dir = pin_dir
43
- @config_targets = RbBCC::HashTable.from_pin(
44
- File.join(@pin_dir, "config_targets"),
64
+ @config_root_targets = RbBCC::HashTable.from_pin(
65
+ File.join(@pin_dir, "config_root_targets"),
66
+ "unsigned int",
67
+ "unsigned char",
68
+ keysize: 4,
69
+ leafsize: 1
70
+ )
71
+ @config_spawned_targets = RbBCC::HashTable.from_pin(
72
+ File.join(@pin_dir, "config_spawned_targets"),
45
73
  "unsigned int",
46
74
  "unsigned char",
47
75
  keysize: 4,
@@ -66,11 +94,12 @@ module Vivarium
66
94
  end
67
95
 
68
96
  def register_pid(pid)
69
- @config_targets[pid] = 1
97
+ @config_root_targets[pid] = 1
70
98
  end
71
99
 
72
100
  def unregister_pid(pid)
73
- @config_targets.delete(pid)
101
+ @config_root_targets.delete(pid)
102
+ @config_spawned_targets.clear
74
103
  rescue KeyError
75
104
  nil
76
105
  end
@@ -115,26 +144,63 @@ module Vivarium
115
144
  struct event_t {
116
145
  u32 pid;
117
146
  char event_name[16];
118
- char payload[64];
147
+ char payload[#{EVENT_PAYLOAD_SIZE}];
119
148
  };
120
149
 
121
- BPF_HASH(config_targets, u32, u32, 1024);
122
- BPF_ARRAY(event_invoked, struct event_t, 64);
150
+ BPF_HASH(config_root_targets, u32, u8, 1024);
151
+ BPF_HASH(config_spawned_targets, u32, u8, 8192);
152
+ BPF_ARRAY(event_invoked, struct event_t, 1024);
123
153
  BPF_ARRAY(event_write_pos, u32, 1);
124
154
 
125
- static __always_inline int target_enabled(u32 pid)
155
+ static __always_inline int target_enabled(u32 pid, u32 tid)
126
156
  {
127
- u32 *enabled = config_targets.lookup(&pid);
128
- if (!enabled) {
157
+ u8 *enabled_root = config_root_targets.lookup(&pid);
158
+ if (enabled_root && *enabled_root == 1) {
159
+ return 1;
160
+ }
161
+
162
+ u8 *enabled_spawned = config_spawned_targets.lookup(&tid);
163
+ if (enabled_spawned && *enabled_spawned == 1) {
164
+ return 1;
165
+ }
166
+
167
+ return 0;
168
+ }
169
+
170
+ TRACEPOINT_PROBE(sched, sched_process_fork)
171
+ {
172
+ u32 parent = args->parent_pid;
173
+ u32 child = args->child_pid;
174
+ u8 one = 1;
175
+
176
+ u8 *enabled_root = config_root_targets.lookup(&parent);
177
+ if (enabled_root && *enabled_root == 1) {
178
+ config_spawned_targets.update(&child, &one);
129
179
  return 0;
130
180
  }
131
- return *enabled == 1;
181
+
182
+ u8 *enabled_spawned = config_spawned_targets.lookup(&parent);
183
+ if (enabled_spawned && *enabled_spawned == 1) {
184
+ config_spawned_targets.update(&child, &one);
185
+ }
186
+
187
+ return 0;
188
+ }
189
+
190
+ TRACEPOINT_PROBE(sched, sched_process_exit)
191
+ {
192
+ u32 tid = (u32)bpf_get_current_pid_tgid();
193
+ config_spawned_targets.delete(&tid);
194
+ return 0;
132
195
  }
133
196
 
134
197
  LSM_PROBE(file_open, struct file *file)
135
198
  {
136
- u32 pid = bpf_get_current_pid_tgid() >> 32;
137
- if (!target_enabled(pid)) {
199
+ u64 pid_tgid = bpf_get_current_pid_tgid();
200
+ u32 pid = pid_tgid >> 32;
201
+ u32 tid = (u32)pid_tgid;
202
+ bpf_trace_printk("vivarium: invoked pid=%d\\n", pid);
203
+ if (!target_enabled(pid, tid)) {
138
204
  return 0;
139
205
  }
140
206
 
@@ -144,7 +210,7 @@ module Vivarium
144
210
  return 0;
145
211
  }
146
212
 
147
- u32 idx = *write_pos & 63;
213
+ u32 idx = *write_pos & 1023;
148
214
  __sync_fetch_and_add(write_pos, 1);
149
215
  struct event_t ev = {};
150
216
  int path_ret;
@@ -153,16 +219,20 @@ module Vivarium
153
219
 
154
220
  path_ret = bpf_d_path(&file->f_path, ev.payload, sizeof(ev.payload));
155
221
  if (path_ret < 0) {
156
- __builtin_memcpy(ev.payload, "<path_error>", 13);
222
+ if (ev.payload[0] == 0) {
223
+ __builtin_memcpy(ev.payload, "<path_error>", 13);
224
+ bpf_trace_printk("vivarium: failed to obtain full path. pid=%d path=%s\\n", pid, ev.payload);
225
+ }
157
226
  }
158
227
 
228
+ bpf_trace_printk("vivarium: pid=%d path=%s\\n", pid, ev.payload);
159
229
  event_invoked.update(&idx, &ev);
160
230
 
161
231
  return 0;
162
232
  }
163
233
  CLANG
164
234
 
165
- def initialize(pin_dir: PIN_DIR)
235
+ def initialize(pin_dir: Vivarium.bpf_pin_dir)
166
236
  @pin_dir = pin_dir
167
237
  end
168
238
 
@@ -174,27 +244,37 @@ module Vivarium
174
244
  program = BPF_PROGRAM_TEMPLATE.gsub("__VIVARIUM_F_PATH_OFFSET__", f_path_offset.to_s)
175
245
 
176
246
  bpf = RbBCC::BCC.new(text: program)
247
+ kprint_thread = start_kprint_logger(bpf)
177
248
 
178
- config_targets = bpf["config_targets"]
249
+ config_root_targets = bpf["config_root_targets"]
250
+ config_spawned_targets = bpf["config_spawned_targets"]
179
251
  event_invoked = bpf["event_invoked"]
180
252
  event_write_pos = bpf["event_write_pos"]
181
253
 
182
254
  clear_event_slots(event_invoked)
183
255
  event_write_pos[0] = 0
256
+ config_spawned_targets.clear
184
257
 
185
- pin_map(config_targets, File.join(@pin_dir, "config_targets"))
258
+ pin_map(config_root_targets, File.join(@pin_dir, "config_root_targets"))
259
+ pin_map(config_spawned_targets, File.join(@pin_dir, "config_spawned_targets"))
186
260
  pin_map(event_invoked, File.join(@pin_dir, "event_invoked"))
187
261
  pin_map(event_write_pos, File.join(@pin_dir, "event_write_pos"))
188
262
 
189
263
  puts "[vivariumd] started"
190
264
  puts "[vivariumd] pinned maps in #{@pin_dir}"
191
265
  puts "[vivariumd] watching LSM file_open (f_path offset=#{f_path_offset})"
266
+ puts "[vivariumd] kprint logger enabled"
192
267
 
193
268
  loop do
194
269
  sleep 1
195
270
  end
196
271
  rescue Interrupt
197
272
  puts "\n[vivariumd] stopping"
273
+ ensure
274
+ if kprint_thread
275
+ kprint_thread.kill
276
+ kprint_thread.join(0.2)
277
+ end
198
278
  end
199
279
 
200
280
  private
@@ -218,6 +298,26 @@ module Vivarium
218
298
  end
219
299
  end
220
300
 
301
+ def start_kprint_logger(bpf)
302
+ Thread.new do
303
+ begin
304
+ bpf.trace_fields do |_task, pid, _cpu, _flags, ts, msg|
305
+ line = msg.to_s.strip
306
+ next unless line.start_with?("vivarium:")
307
+
308
+ puts "[vivariumd:kprint #{ts} pid=#{pid}] #{line}"
309
+ end
310
+ rescue IOError, Errno::EINTR
311
+ nil
312
+ rescue StandardError => e
313
+ warn "[vivariumd] kprint stream stopped: #{e.class}: #{e.message}"
314
+ end
315
+ end
316
+ rescue StandardError => e
317
+ warn "[vivariumd] failed to start kprint logger: #{e.class}: #{e.message}"
318
+ nil
319
+ end
320
+
221
321
  def detect_f_path_offset
222
322
  env_offset = ENV["VIVARIUM_FILE_F_PATH_OFFSET"]
223
323
  return Integer(env_offset, 10) if env_offset
@@ -279,36 +379,80 @@ module Vivarium
279
379
  end
280
380
  end
281
381
 
282
- def self.observe(pin_dir: PIN_DIR, out: $stdout)
283
- raise ArgumentError, "block is required" unless block_given?
382
+ class ObservationSession
383
+ def initialize(store:, pid:, tracer:)
384
+ @store = store
385
+ @pid = pid
386
+ @tracer = tracer
387
+ @stopped = false
388
+ end
389
+
390
+ def stop
391
+ return if @stopped
392
+
393
+ @tracer.disable
394
+ @store.unregister_pid(@pid)
395
+ @stopped = true
396
+ end
397
+ end
284
398
 
399
+ def self.observe(pin_dir: bpf_pin_dir, logger: nil, dest: $stdout, format: :human)
400
+ return scoped_observe(pin_dir: pin_dir, logger: logger, dest: dest, format: format) { yield } if block_given?
401
+
402
+ top_observe(pin_dir: pin_dir, logger: logger, dest: dest, format: format)
403
+ end
404
+
405
+ def self.top_observe(pin_dir: bpf_pin_dir, logger: nil, dest: $stdout, format: :human)
406
+ logger ||= Logger.new(dest: dest, format: format)
285
407
  store = MapStore.new(pin_dir: pin_dir)
286
408
  pid = Process.pid
287
409
  store.register_pid(pid)
410
+ logger.info("top-level observing with pid=#{pid}")
288
411
 
289
- tracer = TracePoint.new(:return, :c_return) do |tp|
290
- events = store.drain_events
291
- next if events.empty?
412
+ tracer = build_observe_tracepoint(store, logger)
413
+ tracer.enable
292
414
 
293
- out.puts "[vivarium] #{events.size} event(s) at #{tp.defined_class}##{tp.method_id} (#{tp.event})"
294
- events.each do |event|
295
- out.puts " pid=#{event.pid} #{event.event_name} payload=#{event.payload.inspect}"
296
- end
297
- out.puts " stack:"
298
- caller_locations(0, 12).each do |loc|
299
- out.puts " #{loc.path}:#{loc.lineno}:in #{loc.base_label}"
300
- end
301
- end
415
+ session = ObservationSession.new(store: store, pid: pid, tracer: tracer)
416
+ at_exit { session.stop }
417
+ session
418
+ end
419
+
420
+ def self.scoped_observe(pin_dir:, logger:, dest:, format:)
421
+ logger ||= Logger.new(dest: dest, format: format)
422
+ store = MapStore.new(pin_dir: pin_dir)
423
+ pid = Process.pid
424
+ store.register_pid(pid)
425
+ logger.info("scoped observing with pid=#{pid}")
302
426
 
427
+ tracer = build_observe_tracepoint(store, logger)
303
428
  tracer.enable
429
+
304
430
  yield
305
431
  ensure
306
432
  tracer&.disable
307
433
  store&.unregister_pid(pid)
308
434
  end
309
435
 
436
+ def self.build_observe_tracepoint(store, logger)
437
+ TracePoint.new(:return, :c_return) do |tp|
438
+ events = store.drain_events
439
+ next if events.empty?
440
+
441
+ stack = caller_locations(2, 16)
442
+ stack = stack.reject { |loc| loc.path.to_s.include?("vivarium") } if filter_internal_frames?
443
+ logger.log(events, tp, stack)
444
+ end
445
+ end
446
+
447
+ def self.filter_internal_frames?
448
+ value = ENV["VIVARIUM_FILTER_INTERNAL_FRAMES"]
449
+ return true if value.nil?
450
+
451
+ !%w[0 false off no].include?(value.strip.downcase)
452
+ end
453
+
310
454
  def self.run_daemon!(argv = ARGV)
311
- options = { pin_dir: PIN_DIR }
455
+ options = { pin_dir: bpf_pin_dir }
312
456
  OptionParser.new do |opts|
313
457
  opts.banner = "Usage: vivariumd [--pin-dir PATH]"
314
458
  opts.on("--pin-dir PATH", "Pinned map directory") { |v| options[:pin_dir] = v }
data/sig/vivarium.rbs CHANGED
@@ -21,12 +21,18 @@ module Vivarium
21
21
  end
22
22
 
23
23
  class Daemon
24
- BPF_PROGRAM: String
24
+ BPF_PROGRAM_TEMPLATE: String
25
25
  def initialize: (?pin_dir: String pin_dir) -> void
26
26
  def run: () -> void
27
27
  end
28
28
 
29
+ class ObservationSession
30
+ def stop: () -> void
31
+ end
32
+
29
33
  PIN_DIR: String
34
+ CONFIG_ROOT_TARGETS_PIN: String
35
+ CONFIG_SPAWNED_TARGETS_PIN: String
30
36
  CONFIG_TARGETS_PIN: String
31
37
  EVENT_INVOKED_PIN: String
32
38
  EVENT_WRITE_POS_PIN: String
@@ -36,6 +42,10 @@ module Vivarium
36
42
  EVENT_STRUCT_SIZE: Integer
37
43
  EVENT_CAPACITY: Integer
38
44
 
39
- def self.observe: (?pin_dir: String pin_dir, ?out: untyped out) { () -> untyped } -> untyped
45
+ def self.bpf_pin_dir: () -> String
46
+ def self.bpf_pin_dir=: (String dir) -> String
47
+ def self.observe: (?pin_dir: String pin_dir, ?logger: untyped logger, ?dest: untyped dest, ?format: Symbol format) { () -> untyped } -> untyped
48
+ def self.top_observe: (?pin_dir: String pin_dir, ?logger: untyped logger, ?dest: untyped dest, ?format: Symbol format) -> ObservationSession
49
+ def self.filter_internal_frames?: () -> bool
40
50
  def self.run_daemon!: (?Array[String] argv) -> void
41
51
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vivarium
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Uchio Kondo
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: 0.11.2
18
+ version: 0.11.3
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: 0.11.2
25
+ version: 0.11.3
26
26
  description: Vivarium visualizes low-level events such as file open paths and relates
27
27
  them to Ruby method boundaries by combining RbBCC (eBPF LSM) and TracePoint.
28
28
  email:
@@ -36,6 +36,7 @@ files:
36
36
  - Rakefile
37
37
  - exe/vivariumd
38
38
  - lib/vivarium.rb
39
+ - lib/vivarium/logger.rb
39
40
  - lib/vivarium/version.rb
40
41
  - sig/vivarium.rbs
41
42
  homepage: https://github.com/udzura/vivarium