Module: Vivarium::RawStore

Defined in:
lib/vivarium/raw_store.rb

Overview

Reads and writes the vivarium-raw file format: a single JSON metadata line followed by fixed-size (EVENT_STRUCT_SIZE) event_t records. The record layout mirrors the C struct event_t so it round-trips losslessly.

Defined Under Namespace

Classes: FormatError

Constant Summary collapse

FORMAT =
"vivarium-raw"
VERSION =
2
PACK_FMT =

struct event_t (352B)

"Q<L<L<L<L<Q<Q<Q<Q<a16a16a256Q<"

Class Method Summary collapse

Class Method Details

.dump(io, events:, meta:) ⇒ Object

io: a binary-writable IO. meta: session metadata Hash.



57
58
59
60
61
62
63
64
65
66
# File 'lib/vivarium/raw_store.rb', line 57

def self.dump(io, events:, meta:)
  header = meta.merge(
    format: FORMAT, version: VERSION,
    event_struct_size: EVENT_STRUCT_SIZE, event_count: events.size
  )
  io.binmode
  io.write(JSON.generate(header))
  io.write("\n")
  events.each { |ev| io.write(pack_record(ev)) }
end

.load(io) ⇒ Object

Returns { meta: Hash(symbol keys), events: [RawEvent, …] }.

Raises:



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/vivarium/raw_store.rb', line 69

def self.load(io)
  io.binmode
  line = io.gets
  raise FormatError, "empty file" if line.nil?

  begin
    meta = JSON.parse(line, symbolize_names: true)
  rescue JSON::ParserError => e
    raise FormatError, "header is not valid JSON: #{e.message}"
  end
  raise FormatError, "missing JSON object header" unless meta.is_a?(Hash)
  unless meta[:format] == FORMAT
    raise FormatError, "format=#{meta[:format].inspect} (expected #{FORMAT.inspect})"
  end

  size = meta[:event_struct_size]
  if size && size != EVENT_STRUCT_SIZE
    raise FormatError,
          "event_struct_size=#{size} (expected #{EVENT_STRUCT_SIZE}); " \
          "incompatible capture (likely an older vivarium-raw v1 file)"
  end

  events = []
  while (rec = io.read(EVENT_STRUCT_SIZE))
    break if rec.bytesize < EVENT_STRUCT_SIZE

    events << unpack_record(rec)
  end
  { meta: meta, events: events }
end

.pack_record(ev) ⇒ Object



24
25
26
27
28
29
30
31
32
33
# File 'lib/vivarium/raw_store.rb', line 24

def self.pack_record(ev)
  [
    ev.ktime_ns, ev.pid, ev.tid, ev.uid.to_i, ev.gid.to_i,
    ev.trace_hi.to_i, ev.trace_lo.to_i, ev.span_id.to_i, ev.parent_span_id.to_i,
    ev.comm.to_s.b.ljust(EVENT_COMM_SIZE, "\x00")[0, EVENT_COMM_SIZE],
    ev.event_name.to_s.b.ljust(EVENT_NAME_SIZE, "\x00")[0, EVENT_NAME_SIZE],
    ev.payload.to_s.b.ljust(EVENT_PAYLOAD_SIZE, "\x00")[0, EVENT_PAYLOAD_SIZE],
    ev.dropped_since_last
  ].pack(PACK_FMT)
end

.unpack_record(bytes) ⇒ Object



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/vivarium/raw_store.rb', line 35

def self.unpack_record(bytes)
  bytes = bytes.to_s.b
  bytes = bytes.ljust(EVENT_STRUCT_SIZE, "\x00") if bytes.bytesize < EVENT_STRUCT_SIZE

  RawEvent.new(
    ktime_ns:           bytes[EVENT_TS_OFFSET,         EVENT_TS_SIZE].unpack1("Q<"),
    pid:                bytes[EVENT_PID_OFFSET,         4].unpack1("L<"),
    tid:                bytes[EVENT_TID_OFFSET,         4].unpack1("L<"),
    uid:                bytes[EVENT_UID_OFFSET,         4].unpack1("L<"),
    gid:                bytes[EVENT_GID_OFFSET,         4].unpack1("L<"),
    trace_hi:           bytes[EVENT_TRACE_HI_OFFSET,    8].unpack1("Q<"),
    trace_lo:           bytes[EVENT_TRACE_LO_OFFSET,    8].unpack1("Q<"),
    span_id:            bytes[EVENT_SPAN_OFFSET,        8].unpack1("Q<"),
    parent_span_id:     bytes[EVENT_PARENT_SPAN_OFFSET, 8].unpack1("Q<"),
    comm:               Vivarium.c_string(bytes[EVENT_COMM_OFFSET, EVENT_COMM_SIZE]),
    event_name:         Vivarium.c_string(bytes[EVENT_NAME_OFFSET, EVENT_NAME_SIZE]),
    payload:            bytes[EVENT_PAYLOAD_OFFSET,     EVENT_PAYLOAD_SIZE].to_s.b,
    dropped_since_last: bytes[EVENT_DROPPED_OFFSET,     8].unpack1("Q<")
  )
end