diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index eba351b..65ff292 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -32,6 +32,11 @@ jobs: dune-cache: true allow-prerelease-opam: true + # check that trace compiles with no optional deps + - run: opam install -t trace --deps-only + - run: opam exec -- dune build '@install' -p trace + + # install all packages - run: opam install -t trace trace-tef trace-fuchsia --deps-only - run: opam install ppx_trace --deps-only # no tests if: matrix.ocaml-compiler != '4.08.x' diff --git a/README.md b/README.md index b10a258..99e943b 100644 --- a/README.md +++ b/README.md @@ -150,9 +150,18 @@ Concrete tracing or observability formats such as: * [x] backend for [tldrs](https://github.com/imandra-ai/tldrs), a small rust daemon that aggregates TEF traces from multiple processes/clients into a single `.jsonl` file - * [ ] richer bindings with [ocaml-catapult](https://github.com/imandra-ai/catapult), - with multi-process backends, etc. + * [x] [tldrs](https://github.com/imandra-ai/tldrs), to collect TEF traces from multiple processes in a clean way. + This requires the rust `tldrs` program to be in path. + * ~~[ ] richer bindings with [ocaml-catapult](https://github.com/imandra-ai/catapult), + with multi-process backends, etc.~~ (subsumed by tldrs) - [x] Tracy (see [ocaml-tracy](https://github.com/imandra-ai/ocaml-tracy), more specifically `tracy-client.trace`) - [x] Opentelemetry (see [ocaml-opentelemetry](https://github.com/imandra-ai/ocaml-opentelemetry/), in `opentelemetry.trace`) - [ ] landmarks? - [ ] Logs (only for messages, obviously) + +## Subscribers + +The library `trace.subscriber` defines composable _subscribers_, which are sets of callbacks +that consume tracing events. +Multiple subscribers can be aggregated together (with events being dispatched to all of them) +and be installed as a normal _collector_. diff --git a/src/subscriber/callbacks.ml b/src/subscriber/callbacks.ml index 3ff7647..112bf51 100644 --- a/src/subscriber/callbacks.ml +++ b/src/subscriber/callbacks.ml @@ -1,3 +1,23 @@ +(** Callbacks used for subscribers. + + Each subscriber defines a set of callbacks, for each possible + tracing event. These callbacks take a custom state that is paired + with the callbacks in {!Subscriber.t}. + + To use a default implementation for some callbacks, use: + + {[ + module My_callbacks = struct + type st = … + + include Trace_subscriber.Callbacks.Dummy + + let on_init (state:st) ~time_ns : unit = … + + (* other customize callbacks *) + end ]} +*) + open Trace_core open Types @@ -119,7 +139,7 @@ module Dummy = struct () end -(** Dummy callbacks, do nothing. *) +(** Dummy callbacks, ignores all events. *) let dummy (type st) () : st t = let module M = struct type nonrec st = st diff --git a/src/subscriber/subscriber.ml b/src/subscriber/subscriber.ml index 0246984..46c100c 100644 --- a/src/subscriber/subscriber.ml +++ b/src/subscriber/subscriber.ml @@ -1,9 +1,11 @@ +(** Trace subscribers *) + (** A trace subscriber. It pairs a set of callbacks with the state they need (which can contain a file handle, - a socket, config, etc.). + a socket to write events to, config, etc.). The design goal for this is that it should be possible to avoid allocations - when the trace collector calls the callbacks. *) + whenever the trace collector invokes the callbacks. *) type t = | Sub : { st: 'st; @@ -102,3 +104,12 @@ end let tee (s1 : t) (s2 : t) : t = let st = s1, s2 in Sub { st; callbacks = (module Tee_cb) } + +(** Tee multiple subscribers, ie return a subscriber that forwards + to all the subscribers in [subs]. *) +let rec tee_l (subs : t list) : t = + match subs with + | [] -> dummy + | [ s ] -> s + | [ s1; s2 ] -> tee s1 s2 + | s1 :: s2 :: tl -> tee (tee s1 s2) (tee_l tl) diff --git a/src/subscriber/trace_subscriber.ml b/src/subscriber/trace_subscriber.ml index bbe4626..fe13910 100644 --- a/src/subscriber/trace_subscriber.ml +++ b/src/subscriber/trace_subscriber.ml @@ -13,9 +13,7 @@ module Private_ = struct let[@inline] now_ns () : float = match !get_now_ns_ with | Some f -> f () - | None -> - let t = Mtime_clock.now () in - Int64.to_float (Mtime.to_uint64_ns t) + | None -> Time_.get_time_ns () let[@inline] tid_ () : int = match !get_tid_ with diff --git a/src/subscriber/trace_subscriber.mli b/src/subscriber/trace_subscriber.mli index bba7982..a20daf0 100644 --- a/src/subscriber/trace_subscriber.mli +++ b/src/subscriber/trace_subscriber.mli @@ -15,13 +15,17 @@ include module type of struct include Types end +(** {2 Main API} *) + type t = Subscriber.t val collector : t -> Trace_core.collector +(** A collector that calls the subscriber's callbacks. + + It uses [mtime] (if available) to obtain timestamps. *) (**/**) -(**/*) module Private_ : sig val get_now_ns_ : (unit -> float) option ref (** The callback used to get the current timestamp *) @@ -31,3 +35,5 @@ module Private_ : sig val now_ns : unit -> float end + +(**/**) diff --git a/src/subscriber/types.ml b/src/subscriber/types.ml index e507f46..dc48841 100644 --- a/src/subscriber/types.ml +++ b/src/subscriber/types.ml @@ -1,10 +1,13 @@ +(** Some core types for subscribers. *) + type user_data = | U_bool of bool | U_float of float | U_int of int | U_none | U_string of string + (** A non polymorphic-variant version of {!Trace_core.user_data} *) type flavor = | Sync - | Async + | Async (** A non polymorphic-variant version of {!Trace_core.flavor} *)