Logging, runtime tracing, and the obs performance profiler for OCaml
0

Configure Feed

Select the types of activity you want to include in your feed.

obs: keep eio out of every instrumented binary

observe depends on observe.producer, which depended on observe.producer.core, which depended on eio. Every CLI that calls Observe.setup therefore linked eio, and with it bigstringaf, cstruct, eio.core, lwt-dllist and optint -- to print log lines. The forwarder never touches Eio: the only users inside producer.core were Ring_eio, an Eio driver the forwarder path never calls, and Clock.of_eio, two clock reads.

Both move to a new observe.producer.core.eio, linked explicitly by the two Eio unikernel examples and the ring tests. producer.core now needs neither eio nor mtime, and obs_guest (observe.producer + probe) no longer drags the Eio core into a guest image. Clock_eio gets its own test module, split out of test_clock.

+86 -40
-6
lib/producer/core/clock.ml
··· 1 1 type t = { unix_ns : unit -> int64; mono_ns : unit -> int64 } 2 2 3 - let of_eio ~clock ~mono = 4 - { 5 - unix_ns = (fun () -> Int64.of_float (Eio.Time.now clock *. 1e9)); 6 - mono_ns = (fun () -> Mtime.to_uint64_ns (Eio.Time.Mono.now mono)); 7 - } 8 - 9 3 let pp ppf t = 10 4 Fmt.pf ppf "clock(unix=%Ldns mono=%Ldns)" (t.unix_ns ()) (t.mono_ns ())
+3 -7
lib/producer/core/clock.mli
··· 2 2 clock read together for the {!Direct} producer's Hello sync sample and to 3 3 stamp its frames. 4 4 5 - A freestanding producer (a solo5 unikernel, with no Unix or [mtime.clock]) 6 - builds one from its Eio environment with {!of_eio}; the host does the same, 7 - so the producer code is identical on both. *) 5 + A value is a pair of closures, one reading wall time and one reading 6 + monotonic time, each in nanoseconds. The caller supplies both, so the type 7 + carries no dependency on any particular time source. *) 8 8 9 9 type t = { unix_ns : unit -> int64; mono_ns : unit -> int64 } 10 - 11 - val of_eio : clock:_ Eio.Time.clock -> mono:_ Eio.Time.Mono.t -> t 12 - (** [of_eio ~clock ~mono] reads wall time from the Eio [clock] and monotonic 13 - time from [mono]. Works on any Eio backend, host or [eio-solo5]. *) 14 10 15 11 val pp : Format.formatter -> t -> unit 16 12 (** [pp ppf t] prints [t]'s current wall and monotonic readings. *)
-2
lib/producer/core/dune
··· 5 5 observe.protocol 6 6 observe.rt 7 7 probe 8 - eio 9 8 runtime_events 10 9 eio.runtime_events 11 - mtime 12 10 fmt))
+8
lib/producer/core/eio/clock_eio.ml
··· 1 + module Clock = Observe_producer_core.Clock 2 + 3 + let v ~clock ~mono = 4 + Clock. 5 + { 6 + unix_ns = (fun () -> Int64.of_float (Eio.Time.now clock *. 1e9)); 7 + mono_ns = (fun () -> Mtime.to_uint64_ns (Eio.Time.Mono.now mono)); 8 + }
+9
lib/producer/core/eio/clock_eio.mli
··· 1 + (** Build a {!Observe_producer_core.Clock.t} from an Eio environment. *) 2 + 3 + val v : 4 + clock:_ Eio.Time.clock -> 5 + mono:_ Eio.Time.Mono.t -> 6 + Observe_producer_core.Clock.t 7 + (** [v ~clock ~mono] reads wall time from the Eio [clock] and monotonic time 8 + from [mono], each in nanoseconds. Works on any Eio backend, host or 9 + [eio-solo5], so a host producer and a solo5 unikernel take the same path. *)
+4
lib/producer/core/eio/dune
··· 1 + (library 2 + (name observe_producer_core_eio) 3 + (public_name observe.producer.core.eio) 4 + (libraries observe.producer.core observe.protocol eio mtime))
+1
lib/producer/core/ring_eio.ml lib/producer/core/eio/ring_eio.ml
··· 1 1 module Frame = Observe_protocol.Frame 2 + module Ring = Observe_producer_core.Ring 2 3 3 4 type stop = unit -> unit 4 5
+1
lib/producer/core/ring_eio.mli lib/producer/core/eio/ring_eio.mli
··· 8 8 ring counterpart of {!Direct_eio}. *) 9 9 10 10 module Frame = Observe_protocol.Frame 11 + module Ring = Observe_producer_core.Ring 11 12 12 13 type stop = unit -> unit 13 14 (** Stops the producer: halts the tick fiber, drains the ring a final time,
+1
test/producer/dune
··· 4 4 observe.protocol 5 5 observe.producer 6 6 observe.producer.core 7 + observe.producer.core.eio 7 8 observe.producer.eio 8 9 observe.rt 9 10 observer_offline
+1
test/producer/test.ml
··· 5 5 Test_forwarder_core.suite; 6 6 Test_forwarder_eio.suite; 7 7 Test_clock.suite; 8 + Test_clock_eio.suite; 8 9 Test_out.suite; 9 10 Test_ring.suite; 10 11 Test_ring_eio.suite;
+1 -22
test/producer/test_clock.ml
··· 9 9 "pp" "clock(unix=42ns mono=7ns)" 10 10 (Fmt.to_to_string Clock.pp c) 11 11 12 - (* A clock built from an Eio env: monotonic advances across a sleep and the wall 13 - clock reads positive. This is the path a host producer and a solo5 unikernel 14 - both take. *) 15 - let test_of_eio () = 16 - Eio_main.run @@ fun env -> 17 - let clock = Eio.Stdenv.clock env in 18 - let mono = Eio.Stdenv.mono_clock env in 19 - let c = Clock.of_eio ~clock ~mono in 20 - let m0 = c.Clock.mono_ns () in 21 - Eio.Time.sleep clock 0.01; 22 - let m1 = c.Clock.mono_ns () in 23 - Alcotest.(check bool) "mono advances" true (Int64.compare m1 m0 > 0); 24 - Alcotest.(check bool) 25 - "wall positive" true 26 - (Int64.compare (c.Clock.unix_ns ()) 0L > 0) 27 - 28 - let suite = 29 - ( "clock", 30 - [ 31 - Alcotest.test_case "constant" `Quick test_constant; 32 - Alcotest.test_case "of_eio" `Quick test_of_eio; 33 - ] ) 12 + let suite = ("clock", [ Alcotest.test_case "constant" `Quick test_constant ])
+48
test/producer/test_clock_eio.ml
··· 1 + module Clock = Observe_producer_core.Clock 2 + module Clock_eio = Observe_producer_core_eio.Clock_eio 3 + 4 + (* [v] wraps the live Eio clocks rather than sampling them once: a second call 5 + to [mono_ns] must see time that passed between the two. A 10ms sleep is 6 + checked against a 5ms floor, since the scheduler only guarantees it slept at 7 + least the requested time and the clock's resolution is coarser than a ns. *) 8 + let test_reads_live_clocks () = 9 + Eio_main.run @@ fun env -> 10 + let clock = Eio.Stdenv.clock env in 11 + let mono = Eio.Stdenv.mono_clock env in 12 + let c = Clock_eio.v ~clock ~mono in 13 + let m0 = c.Clock.mono_ns () in 14 + Eio.Time.sleep clock 0.01; 15 + let m1 = c.Clock.mono_ns () in 16 + let elapsed = Int64.sub m1 m0 in 17 + Alcotest.(check bool) 18 + (Fmt.str "monotonic advanced by at least 5ms (got %Ldns)" elapsed) 19 + true 20 + (Int64.compare elapsed 5_000_000L >= 0) 21 + 22 + (* The two readings come from different clocks and must not be confused: the 23 + wall reading is a Unix epoch stamp, so it tracks [gettimeofday], while the 24 + monotonic reading is an uptime and is far smaller. Getting these backwards 25 + would put a producer's Hello sync sample decades off. *) 26 + let test_wall_is_epoch_mono_is_not () = 27 + Eio_main.run @@ fun env -> 28 + let clock = Eio.Stdenv.clock env in 29 + let mono = Eio.Stdenv.mono_clock env in 30 + let c = Clock_eio.v ~clock ~mono in 31 + let epoch = Int64.of_float (Unix.gettimeofday () *. 1e9) in 32 + let skew = Int64.abs (Int64.sub (c.Clock.unix_ns ()) epoch) in 33 + Alcotest.(check bool) 34 + (Fmt.str "wall is within 1s of gettimeofday (skew %Ldns)" skew) 35 + true 36 + (Int64.compare skew 1_000_000_000L < 0); 37 + (* An uptime cannot plausibly be an epoch stamp: 2020-01-01 in ns. *) 38 + Alcotest.(check bool) 39 + "monotonic is not an epoch stamp" true 40 + (Int64.compare (c.Clock.mono_ns ()) 1_577_836_800_000_000_000L < 0) 41 + 42 + let suite = 43 + ( "clock_eio", 44 + [ 45 + Alcotest.test_case "reads live clocks" `Quick test_reads_live_clocks; 46 + Alcotest.test_case "wall is epoch, mono is not" `Quick 47 + test_wall_is_epoch_mono_is_not; 48 + ] )
+4
test/producer/test_clock_eio.mli
··· 1 + (** Tests for {!Observe_producer_core_eio.Clock_eio}. *) 2 + 3 + val suite : string * unit Alcotest.test_case list 4 + (** Alcotest suite for the Eio-backed producer clock. *)
+4 -2
test/producer/test_ring_eio.ml
··· 1 1 module Ring = Observe_producer_core.Ring 2 - module Ring_eio = Observe_producer_core.Ring_eio 2 + module Ring_eio = Observe_producer_core_eio.Ring_eio 3 3 module Clock = Observe_producer_core.Clock 4 4 module Collector_server = Observer_offline.Collector_server 5 5 module Fxt_adapter = Observer_offline.Fxt_adapter ··· 21 21 let clock = Eio.Stdenv.clock env in 22 22 let mono = Eio.Stdenv.mono_clock env in 23 23 Collector_server.serve ~sw ~net ~mono server ~path; 24 - let t = Ring.v ~clock:(Clock.of_eio ~clock ~mono) () in 24 + let t = 25 + Ring.v ~clock:(Observe_producer_core_eio.Clock_eio.v ~clock ~mono) () 26 + in 25 27 let stop = 26 28 Ring_eio.attach ~sw ~net ~clock ~interval:0.01 t (`Unix path) 27 29 in
+1 -1
test/producer/test_ring_eio.mli
··· 1 - (** Tests for the Eio ring driver {!Observe_producer_core.Ring_eio}. *) 1 + (** Tests for the Eio ring driver {!Observe_producer_core_eio.Ring_eio}. *) 2 2 3 3 val suite : string * unit Alcotest.test_case list 4 4 (** Alcotest suite for the Eio-driven ring producer. *)