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
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require "http/2"
|
|
5
|
+
rescue LoadError
|
|
6
|
+
# http/2 gem is optional; without it we can still parse frame headers but not HPACK-decompress HEADERS.
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
module Vivarium
|
|
10
|
+
# Decodes payloads captured from OpenSSL `SSL_write` into a human-readable
|
|
11
|
+
# one-liner. Auto-detects HTTP/1.x request/response lines and HTTP/2 binary
|
|
12
|
+
# frames; HPACK-decompresses HEADERS / CONTINUATION when the `http-2` gem
|
|
13
|
+
# is available, otherwise reports frame types only.
|
|
14
|
+
#
|
|
15
|
+
# HPACK decompressor state is kept per pid. This is sufficient for the
|
|
16
|
+
# common "one HTTPS connection per process" case; with multiple concurrent
|
|
17
|
+
# TLS connections per pid the HPACK table can diverge and decoding may
|
|
18
|
+
# fail — in that case the decompressor for that pid is reset on the next
|
|
19
|
+
# decode error.
|
|
20
|
+
class HttpDecoder
|
|
21
|
+
HTTP1_METHODS = %w[GET POST PUT PATCH DELETE HEAD OPTIONS TRACE CONNECT].freeze
|
|
22
|
+
HTTP2_PREFACE = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".b
|
|
23
|
+
H2_FRAME_HEADER_SIZE = 9
|
|
24
|
+
H2_FLAG_END_HEADERS = 0x04
|
|
25
|
+
H2_FLAG_PADDED = 0x08
|
|
26
|
+
H2_FLAG_PRIORITY = 0x20
|
|
27
|
+
FRAME_TYPE_NAMES = {
|
|
28
|
+
0x0 => "DATA",
|
|
29
|
+
0x1 => "HEADERS",
|
|
30
|
+
0x2 => "PRIORITY",
|
|
31
|
+
0x3 => "RST_STREAM",
|
|
32
|
+
0x4 => "SETTINGS",
|
|
33
|
+
0x5 => "PUSH_PROMISE",
|
|
34
|
+
0x6 => "PING",
|
|
35
|
+
0x7 => "GOAWAY",
|
|
36
|
+
0x8 => "WINDOW_UPDATE",
|
|
37
|
+
0x9 => "CONTINUATION"
|
|
38
|
+
}.freeze
|
|
39
|
+
|
|
40
|
+
def initialize
|
|
41
|
+
@hpack_available = load_http2_gem
|
|
42
|
+
@decompressors = {}
|
|
43
|
+
@continuation = {}
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def hpack_available?
|
|
47
|
+
@hpack_available
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def render(pid:, data:, data_len:)
|
|
51
|
+
data = data.to_s.b
|
|
52
|
+
data_len = data_len.to_i
|
|
53
|
+
|
|
54
|
+
return "data_len=0" if data_len <= 0
|
|
55
|
+
return "len=#{data_len} <no-capture>" if data.empty?
|
|
56
|
+
|
|
57
|
+
if (summary = http1_summary(data))
|
|
58
|
+
kind, line = summary
|
|
59
|
+
return "http/1.x #{kind}: #{line}#{truncation_note(data, data_len)}"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
rest = data
|
|
63
|
+
preface_note = ""
|
|
64
|
+
if rest.start_with?(HTTP2_PREFACE)
|
|
65
|
+
preface_note = "preface "
|
|
66
|
+
rest = rest.byteslice(HTTP2_PREFACE.bytesize..) || "".b
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
frames = parse_h2_frames(rest)
|
|
70
|
+
if frames.empty?
|
|
71
|
+
if !preface_note.empty?
|
|
72
|
+
return "h2 preface only#{truncation_note(data, data_len)}"
|
|
73
|
+
end
|
|
74
|
+
return "binary len=#{data_len}#{truncation_note(data, data_len)}"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
rendered = frames.map { |f| render_h2_frame(pid, f) }.join(" | ")
|
|
78
|
+
"h2 #{preface_note}#{rendered}#{truncation_note(data, data_len)}"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def truncation_note(data, data_len)
|
|
84
|
+
return "" if data.bytesize >= data_len
|
|
85
|
+
|
|
86
|
+
" (captured #{data.bytesize}/#{data_len}B)"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def load_http2_gem
|
|
90
|
+
require "http/2"
|
|
91
|
+
true
|
|
92
|
+
rescue LoadError
|
|
93
|
+
false
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def http1_summary(data)
|
|
97
|
+
head = data.byteslice(0, 512).to_s
|
|
98
|
+
first_line = head.split("\r\n", 2).first
|
|
99
|
+
return nil if first_line.nil? || first_line.empty?
|
|
100
|
+
|
|
101
|
+
first_line = first_line.dup.force_encoding(Encoding::UTF_8)
|
|
102
|
+
return nil unless first_line.valid_encoding?
|
|
103
|
+
|
|
104
|
+
if HTTP1_METHODS.any? { |m| first_line.start_with?("#{m} ") }
|
|
105
|
+
return ["request", first_line]
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
if first_line.start_with?("HTTP/1.1 ") || first_line.start_with?("HTTP/1.0 ")
|
|
109
|
+
return ["response", first_line]
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
nil
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# @return [Array<Array(Integer, Integer, Integer, String, Boolean)>]
|
|
116
|
+
# each entry: [frame_type, flags, stream_id, frame_payload, truncated?]
|
|
117
|
+
def parse_h2_frames(payload)
|
|
118
|
+
frames = []
|
|
119
|
+
i = 0
|
|
120
|
+
total = payload.bytesize
|
|
121
|
+
|
|
122
|
+
while i + H2_FRAME_HEADER_SIZE <= total
|
|
123
|
+
length = (payload.getbyte(i) << 16) |
|
|
124
|
+
(payload.getbyte(i + 1) << 8) |
|
|
125
|
+
payload.getbyte(i + 2)
|
|
126
|
+
frame_type = payload.getbyte(i + 3)
|
|
127
|
+
flags = payload.getbyte(i + 4)
|
|
128
|
+
stream_id = payload.byteslice(i + 5, 4).unpack1("N") & 0x7fff_ffff
|
|
129
|
+
i += H2_FRAME_HEADER_SIZE
|
|
130
|
+
|
|
131
|
+
if i + length > total
|
|
132
|
+
remaining = payload.byteslice(i, total - i) || "".b
|
|
133
|
+
frames << [frame_type, flags, stream_id, remaining, true]
|
|
134
|
+
break
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
frame_payload = payload.byteslice(i, length) || "".b
|
|
138
|
+
i += length
|
|
139
|
+
frames << [frame_type, flags, stream_id, frame_payload, false]
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Heuristic: if the very first "frame" doesn't look like a valid HTTP/2
|
|
143
|
+
# frame, refuse the whole parse so we fall back to "binary".
|
|
144
|
+
first_type = frames.first && frames.first[0]
|
|
145
|
+
return [] if first_type && !FRAME_TYPE_NAMES.key?(first_type)
|
|
146
|
+
|
|
147
|
+
frames
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def render_h2_frame(pid, frame)
|
|
151
|
+
frame_type, flags, stream_id, frame_payload, truncated = frame
|
|
152
|
+
frame_name = FRAME_TYPE_NAMES.fetch(frame_type, "TYPE0x#{format('%02x', frame_type)}")
|
|
153
|
+
header = "#{frame_name} stream=#{stream_id} flags=0x#{format('%02x', flags)} len=#{frame_payload.bytesize}#{truncated ? '*' : ''}"
|
|
154
|
+
|
|
155
|
+
case frame_type
|
|
156
|
+
when 0x1 # HEADERS
|
|
157
|
+
fragment = headers_fragment(flags, frame_payload)
|
|
158
|
+
return "#{header} <bad_payload>" if fragment.nil?
|
|
159
|
+
|
|
160
|
+
if (flags & H2_FLAG_END_HEADERS) != 0
|
|
161
|
+
pseudo = decode_hpack(pid, fragment)
|
|
162
|
+
"#{header}#{format_pseudo(pseudo)}"
|
|
163
|
+
else
|
|
164
|
+
@continuation[[pid, stream_id]] = fragment.dup
|
|
165
|
+
"#{header} <collecting>"
|
|
166
|
+
end
|
|
167
|
+
when 0x9 # CONTINUATION
|
|
168
|
+
key = [pid, stream_id]
|
|
169
|
+
unless @continuation.key?(key)
|
|
170
|
+
return "#{header} <orphan>"
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
@continuation[key] << frame_payload
|
|
174
|
+
if (flags & H2_FLAG_END_HEADERS) == 0
|
|
175
|
+
"#{header} <collecting>"
|
|
176
|
+
else
|
|
177
|
+
buf = @continuation.delete(key)
|
|
178
|
+
pseudo = decode_hpack(pid, buf)
|
|
179
|
+
"#{header}#{format_pseudo(pseudo)}"
|
|
180
|
+
end
|
|
181
|
+
else
|
|
182
|
+
header
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def headers_fragment(flags, frame_payload)
|
|
187
|
+
start_idx = 0
|
|
188
|
+
end_idx = frame_payload.bytesize
|
|
189
|
+
|
|
190
|
+
if (flags & H2_FLAG_PADDED) != 0
|
|
191
|
+
return nil if end_idx.zero?
|
|
192
|
+
|
|
193
|
+
pad_len = frame_payload.getbyte(0)
|
|
194
|
+
start_idx += 1
|
|
195
|
+
end_idx = [start_idx, end_idx - pad_len].max
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
if (flags & H2_FLAG_PRIORITY) != 0
|
|
199
|
+
return nil if start_idx + 5 > end_idx
|
|
200
|
+
|
|
201
|
+
start_idx += 5
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
frame_payload.byteslice(start_idx, end_idx - start_idx) || "".b
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def decompressor_for(pid)
|
|
208
|
+
return nil unless @hpack_available
|
|
209
|
+
|
|
210
|
+
@decompressors[pid] ||= HTTP2::Header::Decompressor.new
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def decode_hpack(pid, header_block)
|
|
214
|
+
dec = decompressor_for(pid)
|
|
215
|
+
return { ":error" => "hpack-unavailable" } unless dec
|
|
216
|
+
|
|
217
|
+
pairs = dec.decode(header_block.b)
|
|
218
|
+
pairs.each_with_object({}) { |(k, v), h| h[k] = v }
|
|
219
|
+
rescue StandardError => e
|
|
220
|
+
@decompressors.delete(pid)
|
|
221
|
+
{ ":error" => "#{e.class}: #{e.message}" }
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def format_pseudo(pseudo)
|
|
225
|
+
return " <error: #{pseudo[':error']}>" if pseudo.key?(":error")
|
|
226
|
+
|
|
227
|
+
parts = []
|
|
228
|
+
parts << ":method=#{pseudo[':method']}" if pseudo[':method']
|
|
229
|
+
parts << ":path=#{pseudo[':path']}" if pseudo[':path']
|
|
230
|
+
parts << ":authority=#{pseudo[':authority']}" if pseudo[':authority']
|
|
231
|
+
parts << ":status=#{pseudo[':status']}" if pseudo[':status']
|
|
232
|
+
return "" if parts.empty?
|
|
233
|
+
|
|
234
|
+
" #{parts.join(' ')}"
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "set"
|
|
4
|
+
require_relative "http_decoder"
|
|
4
5
|
|
|
5
6
|
module Vivarium
|
|
6
7
|
class TreeRenderer
|
|
7
8
|
SPAN_EVENT_NAMES = %w[span_start span_stop].to_set.freeze
|
|
8
9
|
FORK_EVENT_NAME = "proc_fork"
|
|
9
10
|
EXEC_EVENT_NAME = "proc_exec"
|
|
11
|
+
SSL_WRITE_EVENT_NAME = "ssl_write"
|
|
10
12
|
|
|
11
13
|
LSM_EVENT_NAMES = %w[
|
|
12
14
|
path_open sock_connect odd_socket
|
|
@@ -19,6 +21,9 @@ module Vivarium
|
|
|
19
21
|
dns_req proc_exec file_getdents proc_fork
|
|
20
22
|
].to_set.freeze
|
|
21
23
|
|
|
24
|
+
UPROBE_EVENT_NAMES = %w[ssl_write].to_set.freeze
|
|
25
|
+
DL_EVENT_NAMES = %w[dlopen mmap_exec].to_set.freeze
|
|
26
|
+
|
|
22
27
|
SYNTHETIC_SPAN_NAME = "<no-span>"
|
|
23
28
|
UNRESOLVED_METHOD_PREFIX = "<method_id="
|
|
24
29
|
|
|
@@ -49,7 +54,7 @@ module Vivarium
|
|
|
49
54
|
|
|
50
55
|
def initialize(events:, method_table:, observer_pid:, main_tid:,
|
|
51
56
|
session_start_iso:, session_start_ktime:,
|
|
52
|
-
session_stop_iso:, session_stop_ktime:, dest:)
|
|
57
|
+
session_stop_iso:, session_stop_ktime:, filter: nil, dest:)
|
|
53
58
|
@events = events
|
|
54
59
|
@method_table = method_table
|
|
55
60
|
@observer_pid = observer_pid
|
|
@@ -58,6 +63,7 @@ module Vivarium
|
|
|
58
63
|
@session_start_ktime = session_start_ktime
|
|
59
64
|
@session_stop_iso = session_stop_iso
|
|
60
65
|
@session_stop_ktime = session_stop_ktime
|
|
66
|
+
@display_filter = Vivarium::DisplayFilter.compile(filter)
|
|
61
67
|
@dest = dest
|
|
62
68
|
|
|
63
69
|
@pid_comm = { observer_pid => "ruby" }
|
|
@@ -265,6 +271,7 @@ module Vivarium
|
|
|
265
271
|
@dest.puts "[PROC pid=#{@observer_pid} comm=#{@pid_comm[@observer_pid] || 'ruby'}]"
|
|
266
272
|
children = spans.reject { |s| s.synthetic && s.events.empty? }
|
|
267
273
|
.reject { |s| @child_span_set.include?(s) }
|
|
274
|
+
.select { |s| span_visible?(s) }
|
|
268
275
|
children.each_with_index do |span, idx|
|
|
269
276
|
print_span(span, prefix: "", is_last: idx == children.size - 1)
|
|
270
277
|
end
|
|
@@ -318,8 +325,17 @@ module Vivarium
|
|
|
318
325
|
def build_span_children(span)
|
|
319
326
|
proc_node_by_pid = {}
|
|
320
327
|
root_children = []
|
|
328
|
+
prev_event_ktime = span.start_ktime
|
|
321
329
|
|
|
322
330
|
span.events.each do |ev|
|
|
331
|
+
target_text = nil
|
|
332
|
+
if @display_filter.needs_payload?
|
|
333
|
+
target_text = render_target(ev)
|
|
334
|
+
next unless event_visible?(ev, span, target_text)
|
|
335
|
+
else
|
|
336
|
+
next unless event_visible?(ev, span)
|
|
337
|
+
end
|
|
338
|
+
|
|
323
339
|
if ev.event_name == FORK_EVENT_NAME
|
|
324
340
|
child_pid = read_proc_fork_child_pid(ev.payload)
|
|
325
341
|
child_node = ProcNode.new(
|
|
@@ -333,28 +349,28 @@ module Vivarium
|
|
|
333
349
|
ev_node = EventNode.new(
|
|
334
350
|
kind: kind_for(ev),
|
|
335
351
|
name: ev.event_name,
|
|
336
|
-
target: render_target(ev),
|
|
352
|
+
target: target_text || render_target(ev),
|
|
337
353
|
offset_ns: ev.ktime_ns - span.start_ktime,
|
|
338
354
|
child_proc: child_node
|
|
339
355
|
)
|
|
340
356
|
|
|
341
357
|
parent_container = container_for_pid(ev.pid, span, proc_node_by_pid, root_children)
|
|
358
|
+
maybe_inject_drop_node(parent_container, ev, span, prev_event_ktime)
|
|
342
359
|
append_event(parent_container, ev_node)
|
|
343
360
|
else
|
|
344
361
|
ev_node = EventNode.new(
|
|
345
362
|
kind: kind_for(ev),
|
|
346
363
|
name: ev.event_name,
|
|
347
|
-
target: render_target(ev),
|
|
364
|
+
target: target_text || render_target(ev),
|
|
348
365
|
offset_ns: ev.ktime_ns - span.start_ktime,
|
|
349
366
|
child_proc: nil
|
|
350
367
|
)
|
|
351
368
|
|
|
352
|
-
if ev.pid == @observer_pid && ev.tid == span.tid
|
|
353
|
-
|
|
369
|
+
container = if ev.pid == @observer_pid && ev.tid == span.tid
|
|
370
|
+
root_children
|
|
354
371
|
elsif (node = proc_node_by_pid[ev.pid])
|
|
355
|
-
|
|
372
|
+
node.children
|
|
356
373
|
else
|
|
357
|
-
# event from a descendant pid we haven't materialized — synthesize a stub PROC node
|
|
358
374
|
stub = ProcNode.new(
|
|
359
375
|
pid: ev.pid,
|
|
360
376
|
comm: @pid_comm[ev.pid] || "?",
|
|
@@ -362,14 +378,19 @@ module Vivarium
|
|
|
362
378
|
children: []
|
|
363
379
|
)
|
|
364
380
|
proc_node_by_pid[ev.pid] = stub
|
|
365
|
-
append_event(stub.children, ev_node)
|
|
366
381
|
root_children << stub
|
|
382
|
+
stub.children
|
|
367
383
|
end
|
|
368
384
|
|
|
385
|
+
maybe_inject_drop_node(container, ev, span, prev_event_ktime)
|
|
386
|
+
append_event(container, ev_node)
|
|
387
|
+
|
|
369
388
|
if ev.event_name == EXEC_EVENT_NAME && (node = proc_node_by_pid[ev.pid])
|
|
370
389
|
node.comm = @pid_comm[ev.pid] || node.comm
|
|
371
390
|
end
|
|
372
391
|
end
|
|
392
|
+
|
|
393
|
+
prev_event_ktime = ev.ktime_ns
|
|
373
394
|
end
|
|
374
395
|
|
|
375
396
|
# Interleave child spans by start time among the event/proc nodes
|
|
@@ -415,9 +436,30 @@ module Vivarium
|
|
|
415
436
|
container << ev_node
|
|
416
437
|
end
|
|
417
438
|
|
|
439
|
+
def maybe_inject_drop_node(container, ev, span, prev_event_ktime = nil)
|
|
440
|
+
n = ev.dropped_since_last.to_i
|
|
441
|
+
return if n.zero?
|
|
442
|
+
|
|
443
|
+
# Show the start of the drop window (= time of last good event).
|
|
444
|
+
# The end is implicitly ev.ktime_ns (shown on the following event line).
|
|
445
|
+
drop_start_ns = prev_event_ktime ? (prev_event_ktime - span.start_ktime) : nil
|
|
446
|
+
|
|
447
|
+
container << EventNode.new(
|
|
448
|
+
kind: "DROP",
|
|
449
|
+
name: "dropped_events",
|
|
450
|
+
target: "#{n} event(s) lost (ringbuf overflow)",
|
|
451
|
+
offset_ns: drop_start_ns,
|
|
452
|
+
child_proc: nil
|
|
453
|
+
)
|
|
454
|
+
end
|
|
455
|
+
|
|
418
456
|
def print_nodes(nodes, prefix)
|
|
419
|
-
nodes.
|
|
420
|
-
|
|
457
|
+
visible_nodes = nodes.select do |node|
|
|
458
|
+
!node.is_a?(Span) || span_visible?(node)
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
visible_nodes.each_with_index do |node, idx|
|
|
462
|
+
is_last = idx == visible_nodes.size - 1
|
|
421
463
|
case node
|
|
422
464
|
when EventNode
|
|
423
465
|
print_event_node(node, prefix: prefix, is_last: is_last)
|
|
@@ -429,6 +471,21 @@ module Vivarium
|
|
|
429
471
|
end
|
|
430
472
|
end
|
|
431
473
|
|
|
474
|
+
def span_visible?(span)
|
|
475
|
+
@display_filter.allow_span_name?(span_display_name(span))
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
def event_visible?(ev, span, target_text = nil)
|
|
479
|
+
@display_filter.allow_event?(
|
|
480
|
+
event_name: ev.event_name,
|
|
481
|
+
severity: Vivarium.event_severity(ev.event_name),
|
|
482
|
+
span_name: span_display_name(span),
|
|
483
|
+
payload: target_text,
|
|
484
|
+
pid: ev.pid,
|
|
485
|
+
tid: ev.tid
|
|
486
|
+
)
|
|
487
|
+
end
|
|
488
|
+
|
|
432
489
|
def print_event_node(node, prefix:, is_last:)
|
|
433
490
|
marker = is_last && node.child_proc.nil? ? "└─ " : "├─ "
|
|
434
491
|
marker = "└─ " if is_last && node.child_proc.nil?
|
|
@@ -457,6 +514,8 @@ module Vivarium
|
|
|
457
514
|
def kind_for(ev)
|
|
458
515
|
return "EXCP" if ev.event_name == "span_raise"
|
|
459
516
|
return "USDT" if SPAN_EVENT_NAMES.include?(ev.event_name)
|
|
517
|
+
return "SSL" if ev.event_name == SSL_WRITE_EVENT_NAME
|
|
518
|
+
return "DL" if DL_EVENT_NAMES.include?(ev.event_name)
|
|
460
519
|
return "LSM" if LSM_EVENT_NAMES.include?(ev.event_name)
|
|
461
520
|
return "TP" if TP_EVENT_NAMES.include?(ev.event_name)
|
|
462
521
|
|
|
@@ -465,12 +524,28 @@ module Vivarium
|
|
|
465
524
|
|
|
466
525
|
def render_target(ev)
|
|
467
526
|
return render_raise_target(ev) if ev.event_name == "span_raise"
|
|
527
|
+
return render_ssl_write_target(ev) if ev.event_name == SSL_WRITE_EVENT_NAME
|
|
468
528
|
|
|
469
529
|
text = Vivarium.render_event_payload(ev).to_s
|
|
470
530
|
text = text.gsub(/\s+/, " ").strip
|
|
471
531
|
text.empty? ? "-" : text
|
|
472
532
|
end
|
|
473
533
|
|
|
534
|
+
def render_ssl_write_target(ev)
|
|
535
|
+
decoded = Vivarium.decode_ssl_write_payload(ev.payload)
|
|
536
|
+
http_decoder.render(
|
|
537
|
+
pid: ev.pid,
|
|
538
|
+
data: decoded[:data],
|
|
539
|
+
data_len: decoded[:data_len]
|
|
540
|
+
)
|
|
541
|
+
rescue StandardError => e
|
|
542
|
+
"ssl_write <decode-error: #{e.class}: #{e.message}>"
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
def http_decoder
|
|
546
|
+
@http_decoder ||= Vivarium::HttpDecoder.new
|
|
547
|
+
end
|
|
548
|
+
|
|
474
549
|
def render_raise_target(ev)
|
|
475
550
|
bytes = ev.payload.to_s.b
|
|
476
551
|
return "-" if bytes.bytesize < 8
|
data/lib/vivarium/version.rb
CHANGED