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 =
1
PACK_FMT =

struct event_t (296B)

"Q<L<L<a16a256Q<"

Class Method Summary collapse

Class Method Details

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

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



46
47
48
49
50
51
52
53
54
55
# File 'lib/vivarium/raw_store.rb', line 46

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:



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/vivarium/raw_store.rb', line 58

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

  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



22
23
24
25
26
27
28
29
# File 'lib/vivarium/raw_store.rb', line 22

def self.pack_record(ev)
  [
    ev.ktime_ns, ev.pid, ev.tid,
    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



31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/vivarium/raw_store.rb', line 31

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<"),
    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