feat exporter: add self_metrics

This commit is contained in:
Simon Cruanes 2025-12-09 22:00:39 -05:00
parent 2d8939ab0a
commit 239d9d5aec
No known key found for this signature in database
GPG key ID: EBFFF6F283F3A2B4
15 changed files with 72 additions and 9 deletions

View file

@ -22,6 +22,8 @@ let _dummy_start = Mtime.min_stamp
let _empty_state : _ state = { q = []; size = 0; start = _dummy_start } let _empty_state : _ state = { q = []; size = 0; start = _dummy_start }
let[@inline] cur_size (self : _ t) : int = (Atomic.get self.st).size
let make ?(batch = 100) ?high_watermark ?now ?timeout () : _ t = let make ?(batch = 100) ?high_watermark ?now ?timeout () : _ t =
let batch = min batch max_batch_size in let batch = min batch max_batch_size in
let high_watermark = let high_watermark =

View file

@ -53,6 +53,9 @@ val push : 'a t -> 'a list -> [ `Dropped | `Ok ]
val push' : 'a t -> 'a list -> unit val push' : 'a t -> 'a list -> unit
(** Like {!push} but ignores the result *) (** Like {!push} but ignores the result *)
val cur_size : _ t -> int
(** Number of elements in the current batch *)
open Opentelemetry_emitter open Opentelemetry_emitter
val wrap_emitter : 'a t -> 'a Emitter.t -> 'a Emitter.t val wrap_emitter : 'a t -> 'a Emitter.t -> 'a Emitter.t

View file

@ -21,11 +21,15 @@ module Common = struct
This should be as fast and cheap as possible. *) This should be as fast and cheap as possible. *)
num_discarded: unit -> int; (** How many items were discarded? *) num_discarded: unit -> int; (** How many items were discarded? *)
size: unit -> int;
(** Snapshot of how many items are currently in the queue *)
} }
let[@inline] num_discarded self = self.num_discarded () let[@inline] num_discarded self = self.num_discarded ()
let[@inline] closed (self : t) : bool = self.closed () let[@inline] closed (self : t) : bool = self.closed ()
let[@inline] size (self : t) : int = self.size ()
end end
(** Receiving side *) (** Receiving side *)
@ -46,6 +50,8 @@ module Recv = struct
let[@inline] num_discarded self = self.common.num_discarded () let[@inline] num_discarded self = self.common.num_discarded ()
let[@inline] size self = self.common.size ()
let map (type a b) (f : a -> b) (self : a t) : b t = let map (type a b) (f : a -> b) (self : a t) : b t =
{ {
self with self with
@ -78,6 +84,8 @@ module Send = struct
let[@inline] num_discarded self = self.common.num_discarded () let[@inline] num_discarded self = self.common.num_discarded ()
let[@inline] size self = self.common.size ()
let map (type a b) (f : a list -> b list) (self : b t) : a t = let map (type a b) (f : a list -> b list) (self : b t) : a t =
{ {
self with self with

View file

@ -12,6 +12,8 @@ module Q : sig
val close : _ t -> unit val close : _ t -> unit
val size : _ t -> int
val closed : _ t -> bool val closed : _ t -> bool
val try_pop : 'a t -> 'a BQ.pop_result val try_pop : 'a t -> 'a BQ.pop_result
@ -41,6 +43,9 @@ end = struct
a value of type [bool] which OCaml's memory model should guarantee. *) a value of type [bool] which OCaml's memory model should guarantee. *)
let[@inline] closed self = self.closed let[@inline] closed self = self.closed
(* NOTE: race condition here is also benign in absoence of tearing. *)
let[@inline] size self = Queue.length self.q
let close (self : _ t) = let close (self : _ t) =
UM.protect self.mutex @@ fun () -> UM.protect self.mutex @@ fun () ->
if not self.closed then self.closed <- true if not self.closed then self.closed <- true
@ -73,7 +78,8 @@ end = struct
done; done;
let num_discarded = List.length !to_push in let num_discarded = List.length !to_push in
(* Printf.eprintf "bq: pushed %d items\n%!" (List.length xs - num_discarded); *) (* Printf.eprintf "bq: pushed %d items (discarded: %d)\n%!" (List.length xs - num_discarded) num_discarded; *)
Pushed { num_discarded } Pushed { num_discarded }
) )
end end
@ -108,12 +114,13 @@ let to_bounded_queue (self : 'a state) : 'a BQ.t =
let push x = push self x in let push x = push self x in
let on_non_empty = Cb_set.register self.on_non_empty in let on_non_empty = Cb_set.register self.on_non_empty in
let try_pop () = try_pop self in let try_pop () = try_pop self in
let size () = Q.size self.q in
let close () = let close () =
Q.close self.q; Q.close self.q;
(* waiters will want to know *) (* waiters will want to know *)
Cb_set.trigger self.on_non_empty Cb_set.trigger self.on_non_empty
in in
let common = { BQ.Common.closed; num_discarded } in let common = { BQ.Common.closed; num_discarded; size } in
{ {
BQ.send = { push; close; common }; BQ.send = { push; close; common };
recv = { try_pop; on_non_empty; common }; recv = { try_pop; on_non_empty; common };

View file

@ -26,6 +26,7 @@ let add_batching ~(config : Client_config.t) (exp : OTEL.Exporter.t) :
let active = exp.active in let active = exp.active in
let tick = exp.tick in let tick = exp.tick in
let on_tick = exp.on_tick in let on_tick = exp.on_tick in
let self_metrics () = exp.self_metrics () in
let shutdown () = let shutdown () =
let open Opentelemetry_emitter in let open Opentelemetry_emitter in
Emitter.flush_and_close emit_spans; Emitter.flush_and_close emit_spans;
@ -43,4 +44,5 @@ let add_batching ~(config : Client_config.t) (exp : OTEL.Exporter.t) :
on_tick; on_tick;
tick; tick;
shutdown; shutdown;
self_metrics;
} }

View file

@ -30,6 +30,8 @@ let combine_l (es : OTEL.Exporter.t list) : OTEL.Exporter.t =
on_tick = (fun f -> List.iter (fun e -> e.on_tick f) es); on_tick = (fun f -> List.iter (fun e -> e.on_tick f) es);
tick = (fun () -> List.iter tick es); tick = (fun () -> List.iter tick es);
shutdown = (fun () -> shutdown_l es ~trigger); shutdown = (fun () -> shutdown_l es ~trigger);
self_metrics =
(fun () -> List.fold_left (fun acc e -> e.self_metrics () @ acc) [] es);
} }
) )

View file

@ -22,6 +22,7 @@ let debug ?(out = Format.err_formatter) () : OTEL.Exporter.t =
List.iter (Format.fprintf out "METRIC: %a@." Metrics.pp_metric) m); List.iter (Format.fprintf out "METRIC: %a@." Metrics.pp_metric) m);
on_tick = Cb_set.register ticker; on_tick = Cb_set.register ticker;
tick = (fun () -> Cb_set.trigger ticker); tick = (fun () -> Cb_set.trigger ticker);
self_metrics = (fun () -> []);
shutdown = shutdown =
(fun () -> (fun () ->
Format.fprintf out "CLEANUP@."; Format.fprintf out "CLEANUP@.";

View file

@ -47,6 +47,20 @@ let create ~(q : OTEL.Any_signal_l.t Bounded_queue.t)
let tick () = Cb_set.trigger tick_set in let tick () = Cb_set.trigger tick_set in
let on_tick f = Cb_set.register tick_set f in let on_tick f = Cb_set.register tick_set f in
let self_metrics () : _ list =
let now = OTEL.Timestamp_ns.now_unix_ns () in
let m_size =
OTEL.Metrics.gauge ~name:"otel-ocaml.exporter-queue.size"
[ OTEL.Metrics.int ~now (Bounded_queue.Recv.size q.recv) ]
in
let m_discarded =
OTEL.Metrics.sum ~is_monotonic:true
~name:"otel-ocaml.exporter-queue.discarded"
[ OTEL.Metrics.int ~now (Bounded_queue.Recv.num_discarded q.recv) ]
in
m_size :: m_discarded :: Consumer.self_metrics consumer
in
let shutdown () = let shutdown () =
if Aswitch.is_on active && not (Atomic.exchange shutdown_started true) then ( if Aswitch.is_on active && not (Atomic.exchange shutdown_started true) then (
(* flush all emitters *) (* flush all emitters *)
@ -68,4 +82,13 @@ let create ~(q : OTEL.Any_signal_l.t Bounded_queue.t)
Aswitch.on_turn_off (Consumer.active consumer) shutdown; Aswitch.on_turn_off (Consumer.active consumer) shutdown;
let active () = active in let active () = active in
{ active; emit_logs; emit_metrics; emit_spans; tick; on_tick; shutdown } {
active;
emit_logs;
emit_metrics;
emit_spans;
self_metrics;
tick;
on_tick;
shutdown;
}

View file

@ -64,6 +64,7 @@ let stdout : OTEL.Exporter.t =
let emit_logs = mk_emitter pp_log in let emit_logs = mk_emitter pp_log in
let emit_metrics = mk_emitter pp_metric in let emit_metrics = mk_emitter pp_metric in
let self_metrics () = [] in
let shutdown () = let shutdown () =
Emitter.flush_and_close emit_spans; Emitter.flush_and_close emit_spans;
Emitter.flush_and_close emit_logs; Emitter.flush_and_close emit_logs;
@ -77,6 +78,7 @@ let stdout : OTEL.Exporter.t =
emit_logs; emit_logs;
emit_metrics; emit_metrics;
on_tick = Cb_set.register ticker; on_tick = Cb_set.register ticker;
self_metrics;
tick; tick;
shutdown; shutdown;
} }

View file

@ -194,12 +194,6 @@ end = struct
[ [
sum ~name:"otel-ocaml.export.errors" ~is_monotonic:true sum ~name:"otel-ocaml.export.errors" ~is_monotonic:true
[ int ~now:(Mtime.to_uint64_ns now) (Atomic.get n_errors) ]; [ int ~now:(Mtime.to_uint64_ns now) (Atomic.get n_errors) ];
sum ~name:"otel-ocaml.export.discarded-by-bounded-queue"
~is_monotonic:true
[
int ~now:(Mtime.to_uint64_ns now)
(Bounded_queue.Recv.num_discarded self.q);
];
] ]
let to_consumer (self : state) : Consumer.t = let to_consumer (self : state) : Consumer.t =

View file

@ -24,6 +24,7 @@ type t = {
responsible for sending remaining batches, flushing sockets, etc. To responsible for sending remaining batches, flushing sockets, etc. To
know when shutdown is complete, register callbacks on [active]. know when shutdown is complete, register callbacks on [active].
@since 0.12 *) @since 0.12 *)
self_metrics: unit -> Metrics.t list; (** metrics about the exporter itself *)
} }
(** Main exporter interface. *) (** Main exporter interface. *)
@ -39,6 +40,7 @@ let dummy () : t =
on_tick = Cb_set.register ticker; on_tick = Cb_set.register ticker;
tick = (fun () -> Cb_set.trigger ticker); tick = (fun () -> Cb_set.trigger ticker);
shutdown = (fun () -> Aswitch.turn_off trigger); shutdown = (fun () -> Aswitch.turn_off trigger);
self_metrics = (fun () -> []);
} }
let[@inline] send_trace (self : t) (l : Proto.Trace.span list) = let[@inline] send_trace (self : t) (l : Proto.Trace.span list) =
@ -73,3 +75,5 @@ let on_stop self f = Aswitch.on_turn_off (self.active ()) f
let[@inline] shutdown (self : t) : unit = self.shutdown () let[@inline] shutdown (self : t) : unit = self.shutdown ()
let (cleanup [@deprecated "use shutdown instead"]) = shutdown let (cleanup [@deprecated "use shutdown instead"]) = shutdown
let[@inline] self_metrics (self : t) : _ list = self.self_metrics ()

View file

@ -45,6 +45,8 @@ type flags = Proto.Logs.log_record_flags =
let pp_flags = Proto.Logs.pp_log_record_flags let pp_flags = Proto.Logs.pp_log_record_flags
let pp = Proto.Logs.pp_log_record
(** Make a single log entry *) (** Make a single log entry *)
let make ?time ?(observed_time_unix_nano = Timestamp_ns.now_unix_ns ()) let make ?time ?(observed_time_unix_nano = Timestamp_ns.now_unix_ns ())
?severity ?log_level ?flags ?trace_id ?span_id ?(attrs = []) ?severity ?log_level ?flags ?trace_id ?span_id ?(attrs = [])

View file

@ -13,6 +13,8 @@ type t = Metrics.metric
distribution. It is composed of one or more data points that have precise distribution. It is composed of one or more data points that have precise
values and time stamps. Each distinct metric should have a distinct name. *) values and time stamps. Each distinct metric should have a distinct name. *)
let pp = Proto.Metrics.pp_metric
open struct open struct
let _program_start = Timestamp_ns.now_unix_ns () let _program_start = Timestamp_ns.now_unix_ns ()
end end

View file

@ -28,6 +28,8 @@ let[@inline] trace_id self = Trace_id.of_bytes self.trace_id
let[@inline] is_not_dummy self = Span_id.is_valid (id self) let[@inline] is_not_dummy self = Span_id.is_valid (id self)
let pp = Proto.Trace.pp_span
let default_kind = ref Proto.Trace.Span_kind_unspecified let default_kind = ref Proto.Trace.Span_kind_unspecified
let make ?(kind = !default_kind) ?trace_state ?(attrs = []) ?(events = []) let make ?(kind = !default_kind) ?trace_state ?(attrs = []) ?(events = [])

View file

@ -92,6 +92,11 @@ let dynamic_forward_to_main_exporter : Exporter.t =
| None -> () | None -> ()
| Some exp -> exp.tick () | Some exp -> exp.tick ()
in in
let self_metrics () =
match get () with
| None -> []
| Some exp -> exp.self_metrics ()
in
let shutdown () = () in let shutdown () = () in
{ {
Exporter.active; Exporter.active;
@ -101,8 +106,12 @@ let dynamic_forward_to_main_exporter : Exporter.t =
on_tick; on_tick;
tick; tick;
shutdown; shutdown;
self_metrics;
} }
let self_metrics () : Metrics.t list =
dynamic_forward_to_main_exporter.self_metrics ()
(** Set the global exporter *) (** Set the global exporter *)
let set (exp : t) : unit = let set (exp : t) : unit =
(* sanity check! this specific exporter would just call itself, leading to (* sanity check! this specific exporter would just call itself, leading to