ludic/packages/ludic.telemetry/tests/ring_test.ludic
Orkuncakilkaya d1bea7f10e fix(ludic.telemetry): nothing made per event - properties and the line written into kept buffers, the queue a ring of bytes made at the start
An event was a tree of values encoded into a new string, a dropped line was never given back, and
every batch and save joined the queue into another. Now: telemetry_props / telemetry_str / _int /
_bool write the properties as JSON into a buffer the state keeps; the line is written beside it,
its time worked out from the clock's seconds (TelemetryWorld.clock_s, was stamp); its bytes go
into one ring (ring_bytes, at most queue_max lines), the oldest overwritten past either; a batch
and the file are written into a third kept buffer and go as bytes (TelemetryTransport.send takes
the bytes and their length; Http.body_bytes, Fs.write_bytes). The facts are pooled.
tests/ring_test: ten thousand events past the cap in lines and in bytes, 0 bytes of heap; the line
exact JSON, escaped, 2000-02-29 right; the batch puts the id in; the file round-trips.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 15:15:12 +03:00

93 lines
4.3 KiB
Text

# ring_test.ludic - an event written once into a kept line and the queue a ring of bytes: ten thousand
# events, most past the cap in lines and in bytes, make nothing; the line is exact JSON with its time
# worked out from the seconds; the batch puts the id in; the file round-trips with its marks
import "ludic.telemetry"
import "ludic.base"
program RingTest {
numbers float
state RingTestState {
now: int = 1790337600
body: string = ""
}
function fk_now() -> int { return 0 }
function fk_on() -> bool { return true }
function fk_clock(ring_test_st: RingTestState) -> int { return ring_test_st.now }
function fk_send(url: string, body: []byte, n: int) -> int { return -1 }
function fk_poll(h: int) -> int { return -1 }
function fk_drop(h: int) -> void { }
bind TelemetryWorld { can_send: fn fk_on, enabled: fn fk_on, now_ms: fn fk_now, clock_s: fn fk_clock }
bind TelemetryTransport { send: fn fk_send, poll: fn fk_poll, drop: fn fk_drop }
function qfile() -> string { return Os.temp_dir() + "/ludic-telemetry-ring-queue.txt" }
function start(telemetry_st: mut TelemetryState, max: int, bytes: int) -> void {
telemetry_reset(telemetry_st)
if Fs.exists(qfile()) { Fs.remove(qfile()) }
Fs.write_text(Os.temp_dir() + "/ludic-telemetry-ring-id.json", "{\"id\":\"p-9\"}")
let c = new TelemetryConfig
c.host = "http://fake"
c.key = "k-9"
c.lib = "ring"
c.queue_file = qfile()
c.id_file = Os.temp_dir() + "/ludic-telemetry-ring-id.json"
c.queue_max = max
c.ring_bytes = bytes
c.batch = 3
telemetry_config(telemetry_st, c)
telemetry_start(telemetry_st)
}
function one(telemetry_st: mut TelemetryState, k: int) -> void {
let p = telemetry_props(telemetry_st)
telemetry_int(p, "n", k)
telemetry_str(p, "what", "a fish")
telemetry_bool(p, "dev", true)
telemetry_event(telemetry_st, "caught", p)
}
test "ten thousand events, past the cap in lines and in bytes, make nothing" (telemetry_st: mut TelemetryState) {
start(telemetry_st, 5000, 262144)
for k in 0 .. 200 { one(telemetry_st, k) }
let heap = Os.heap_bytes()
for k in 0 .. 10000 { one(telemetry_st, k) }
let grew = Os.heap_bytes() - heap
print(`ring: 10000 events, {telemetry_queued(telemetry_st)} held, heap {grew} bytes`)
expect(grew == long(0))
expect(Text.contains(telemetry_line(telemetry_st, telemetry_queued(telemetry_st) - 1), "\"n\":9999"))
start(telemetry_st, 40, 1048576)
let heap2 = Os.heap_bytes()
for k in 0 .. 10000 { one(telemetry_st, k) }
expect(Os.heap_bytes() - heap2 == long(0))
expect_eq(telemetry_queued(telemetry_st), 40)
expect(Text.contains(telemetry_line(telemetry_st, 0), "\"n\":9960"))
}
test "a line is exact JSON, escaped, its time from the seconds" (telemetry_st: mut TelemetryState, ring_test_st: mut RingTestState) {
start(telemetry_st, 50, 65536)
let p = telemetry_props(telemetry_st)
telemetry_str(p, "say", "a \"quote\"\\ and\na line")
telemetry_event(telemetry_st, "said", p)
ring_test_st.now = 951868799
telemetry_event(telemetry_st, "leap", null)
let a = telemetry_line(telemetry_st, 0)
expect(Text.contains(a, "\"properties\":{\"say\":\"a \\\"quote\\\"\\\\ and\\u000aa line\",\"$session_id\":"))
expect(Text.contains(a, "\"timestamp\":\"2026-09-25T12:00:00Z\"}"))
expect(Json.parse(a) != null)
expect(Text.contains(telemetry_line(telemetry_st, 1), "\"timestamp\":\"2000-02-29T23:59:59Z\""))
expect(Text.contains(telemetry_line(telemetry_st, 1), "\"properties\":{\"$session_id\""))
}
test "the batch puts the id in, and the file round-trips with its marks" (telemetry_st: mut TelemetryState) {
start(telemetry_st, 50, 65536)
for k in 0 .. 4 { one(telemetry_st, k) }
let body = telemetry_batch_body(telemetry_st, 3)
expect(Text.starts_with(body, "{\"api_key\":\"k-9\",\"batch\":[{"))
expect(Text.contains(body, "\"distinct_id\":\"p-9\""))
expect(not Text.contains(body, "\"n\":3"))
expect(Json.parse(body) != null)
telemetry_save(telemetry_st)
telemetry_reset(telemetry_st)
telemetry_start(telemetry_st)
expect_eq(telemetry_queued(telemetry_st), 4)
expect(Text.contains(telemetry_batch_body(telemetry_st, 4), "\"n\":3,"))
expect(not Text.contains(telemetry_batch_body(telemetry_st, 4), "TELEMETRY_PLAYER"))
}
}