mirror of
https://github.com/ocaml-tracing/ocaml-opentelemetry.git
synced 2026-03-08 20:07:55 -04:00
feat client: generic consumer, notifier, etc.
This commit is contained in:
parent
1a0ba5fc9e
commit
f1437a842f
10 changed files with 278 additions and 1 deletions
206
src/client/generic_http_consumer.ml
Normal file
206
src/client/generic_http_consumer.ml
Normal file
|
|
@ -0,0 +1,206 @@
|
||||||
|
type error = Export_error.t
|
||||||
|
|
||||||
|
(* TODO: emit this in a metric in [tick()] if self tracing is enabled? *)
|
||||||
|
|
||||||
|
(** Number of errors met during export *)
|
||||||
|
let n_errors = Atomic.make 0
|
||||||
|
|
||||||
|
module type IO = Generic_io.S_WITH_CONCURRENCY
|
||||||
|
|
||||||
|
module type HTTPC = sig
|
||||||
|
module IO : IO
|
||||||
|
|
||||||
|
type t
|
||||||
|
|
||||||
|
val create : unit -> t
|
||||||
|
|
||||||
|
val send :
|
||||||
|
t ->
|
||||||
|
url:string ->
|
||||||
|
decode:[ `Dec of Pbrt.Decoder.t -> 'a | `Ret of 'a ] ->
|
||||||
|
string ->
|
||||||
|
('a, error) result IO.t
|
||||||
|
|
||||||
|
val cleanup : t -> unit
|
||||||
|
end
|
||||||
|
|
||||||
|
module Make
|
||||||
|
(IO : IO)
|
||||||
|
(Notifier : Generic_notifier.S with type 'a IO.t = 'a IO.t)
|
||||||
|
(Httpc : HTTPC with type 'a IO.t = 'a IO.t) : sig
|
||||||
|
val consumer :
|
||||||
|
?override_n_workers:int ->
|
||||||
|
ticker_task:float option ->
|
||||||
|
stop:bool Atomic.t ->
|
||||||
|
config:Client_config.t ->
|
||||||
|
unit ->
|
||||||
|
Consumer.any_resource_builder
|
||||||
|
(** Create a consumer.
|
||||||
|
@param stop
|
||||||
|
shared stop variable, set to true to stop this (and maybe other tasks)
|
||||||
|
@param ticker_task
|
||||||
|
controls whether we start a task to call [tick] at the given interval in
|
||||||
|
seconds, or [None] to not start such a task at all. *)
|
||||||
|
end = struct
|
||||||
|
module Proto = Opentelemetry_proto
|
||||||
|
open IO
|
||||||
|
|
||||||
|
type other_config = {
|
||||||
|
override_n_workers: int option;
|
||||||
|
ticker_task: float option;
|
||||||
|
}
|
||||||
|
|
||||||
|
type state = {
|
||||||
|
stop: bool Atomic.t;
|
||||||
|
cleaned: bool Atomic.t; (** True when we cleaned up after closing *)
|
||||||
|
config: Client_config.t;
|
||||||
|
other_config: other_config;
|
||||||
|
q: Any_resource.t Bounded_queue.t;
|
||||||
|
notify: Notifier.t;
|
||||||
|
}
|
||||||
|
|
||||||
|
let shutdown self =
|
||||||
|
Atomic.set self.stop true;
|
||||||
|
if not (Atomic.exchange self.cleaned true) then (
|
||||||
|
Notifier.trigger self.notify;
|
||||||
|
Notifier.delete self.notify
|
||||||
|
)
|
||||||
|
|
||||||
|
let send_http_ (self : state) (httpc : Httpc.t) ~backoff ~url (data : string)
|
||||||
|
: unit IO.t =
|
||||||
|
let* r = Httpc.send httpc ~url ~decode:(`Ret ()) data in
|
||||||
|
match r with
|
||||||
|
| Ok () ->
|
||||||
|
Util_backoff.on_success backoff;
|
||||||
|
IO.return ()
|
||||||
|
| Error `Sysbreak ->
|
||||||
|
Printf.eprintf "ctrl-c captured, stopping\n%!";
|
||||||
|
Atomic.set self.stop true;
|
||||||
|
IO.return ()
|
||||||
|
| Error err ->
|
||||||
|
Atomic.incr n_errors;
|
||||||
|
Export_error.report_err err;
|
||||||
|
(* avoid crazy error loop *)
|
||||||
|
let dur_s = Util_backoff.cur_duration_s backoff in
|
||||||
|
Util_backoff.on_error backoff;
|
||||||
|
IO.sleep_s (dur_s +. Random.float (dur_s /. 10.))
|
||||||
|
|
||||||
|
let send_metrics_http (st : state) client ~encoder ~backoff
|
||||||
|
(l : Proto.Metrics.resource_metrics list) =
|
||||||
|
let msg = Signal.Encode.metrics ~encoder l in
|
||||||
|
send_http_ st client msg ~backoff ~url:st.config.url_metrics
|
||||||
|
|
||||||
|
let send_traces_http st client ~encoder ~backoff
|
||||||
|
(l : Proto.Trace.resource_spans list) =
|
||||||
|
let msg = Signal.Encode.traces ~encoder l in
|
||||||
|
send_http_ st client msg ~backoff ~url:st.config.url_traces
|
||||||
|
|
||||||
|
let send_logs_http st client ~encoder ~backoff
|
||||||
|
(l : Proto.Logs.resource_logs list) =
|
||||||
|
let msg = Signal.Encode.logs ~encoder l in
|
||||||
|
send_http_ st client msg ~backoff ~url:st.config.url_logs
|
||||||
|
|
||||||
|
let tick (self : state) = Notifier.trigger self.notify
|
||||||
|
|
||||||
|
let start_worker (self : state) : unit =
|
||||||
|
let client = Httpc.create () in
|
||||||
|
let encoder = Pbrt.Encoder.create () in
|
||||||
|
let backoff = Util_backoff.create () in
|
||||||
|
|
||||||
|
(* loop on [q] *)
|
||||||
|
let rec loop () : unit IO.t =
|
||||||
|
if Atomic.get self.stop then
|
||||||
|
IO.return ()
|
||||||
|
else
|
||||||
|
let* () =
|
||||||
|
match Bounded_queue.try_pop self.q with
|
||||||
|
| `Closed ->
|
||||||
|
shutdown self;
|
||||||
|
IO.return ()
|
||||||
|
| `Empty -> Notifier.wait self.notify
|
||||||
|
| `Item (R_logs logs) ->
|
||||||
|
send_logs_http self client ~encoder ~backoff logs
|
||||||
|
| `Item (R_metrics ms) ->
|
||||||
|
send_metrics_http self client ~encoder ~backoff ms
|
||||||
|
| `Item (R_spans spans) ->
|
||||||
|
send_traces_http self client ~encoder ~backoff spans
|
||||||
|
in
|
||||||
|
loop ()
|
||||||
|
in
|
||||||
|
|
||||||
|
IO.spawn (fun () ->
|
||||||
|
IO.protect loop ~finally:(fun () ->
|
||||||
|
Httpc.cleanup client;
|
||||||
|
IO.return ()))
|
||||||
|
|
||||||
|
let start_ticker (self : state) ~(interval_s : float) : unit =
|
||||||
|
let rec loop () : unit IO.t =
|
||||||
|
if Atomic.get self.stop then
|
||||||
|
IO.return ()
|
||||||
|
else
|
||||||
|
let* () = IO.sleep_s interval_s in
|
||||||
|
tick self;
|
||||||
|
loop ()
|
||||||
|
in
|
||||||
|
IO.spawn loop
|
||||||
|
|
||||||
|
let default_n_workers = 50
|
||||||
|
|
||||||
|
let create_state ?override_n_workers ~ticker_task ~stop ~config ~q () : state
|
||||||
|
=
|
||||||
|
let other_config = { override_n_workers; ticker_task } in
|
||||||
|
let self =
|
||||||
|
{
|
||||||
|
stop;
|
||||||
|
config;
|
||||||
|
other_config;
|
||||||
|
q;
|
||||||
|
cleaned = Atomic.make false;
|
||||||
|
notify = Notifier.create ();
|
||||||
|
}
|
||||||
|
in
|
||||||
|
|
||||||
|
(* start workers *)
|
||||||
|
let n_workers =
|
||||||
|
min 2
|
||||||
|
(max 500
|
||||||
|
(match
|
||||||
|
( self.other_config.override_n_workers,
|
||||||
|
self.config.http_concurrency_level )
|
||||||
|
with
|
||||||
|
| Some n, _ -> n
|
||||||
|
| None, Some n -> n
|
||||||
|
| None, None -> default_n_workers))
|
||||||
|
in
|
||||||
|
|
||||||
|
for _i = 1 to n_workers do
|
||||||
|
start_worker self
|
||||||
|
done;
|
||||||
|
|
||||||
|
(* start ticker *)
|
||||||
|
(match self.other_config.ticker_task with
|
||||||
|
| None -> ()
|
||||||
|
| Some interval_s -> start_ticker self ~interval_s);
|
||||||
|
|
||||||
|
self
|
||||||
|
|
||||||
|
let to_consumer (self : state) : Any_resource.t Consumer.t =
|
||||||
|
let active () = not (Atomic.get self.stop) in
|
||||||
|
let shutdown ~on_done =
|
||||||
|
shutdown self;
|
||||||
|
on_done ()
|
||||||
|
in
|
||||||
|
let tick () = tick self in
|
||||||
|
{ active; tick; shutdown }
|
||||||
|
|
||||||
|
let consumer ?override_n_workers ~ticker_task ~stop ~config () :
|
||||||
|
Consumer.any_resource_builder =
|
||||||
|
{
|
||||||
|
start_consuming =
|
||||||
|
(fun q ->
|
||||||
|
let st =
|
||||||
|
create_state ?override_n_workers ~ticker_task ~stop ~config ~q ()
|
||||||
|
in
|
||||||
|
to_consumer st);
|
||||||
|
}
|
||||||
|
end
|
||||||
28
src/client/generic_io.ml
Normal file
28
src/client/generic_io.ml
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
(** Generic IO *)
|
||||||
|
module type S = sig
|
||||||
|
type 'a t
|
||||||
|
|
||||||
|
val return : 'a -> 'a t
|
||||||
|
|
||||||
|
val ( let* ) : 'a t -> ('a -> 'b t) -> 'b t
|
||||||
|
|
||||||
|
val protect : finally:(unit -> unit t) -> (unit -> 'a t) -> 'a t
|
||||||
|
end
|
||||||
|
|
||||||
|
module type S_WITH_CONCURRENCY = sig
|
||||||
|
include S
|
||||||
|
|
||||||
|
val sleep_s : float -> unit t
|
||||||
|
|
||||||
|
val spawn : (unit -> unit t) -> unit
|
||||||
|
end
|
||||||
|
|
||||||
|
module Direct_style : S with type 'a t = 'a = struct
|
||||||
|
type 'a t = 'a
|
||||||
|
|
||||||
|
let[@inline] return x = x
|
||||||
|
|
||||||
|
let[@inline] ( let* ) x f = f x
|
||||||
|
|
||||||
|
let protect = Fun.protect
|
||||||
|
end
|
||||||
17
src/client/generic_notifier.ml
Normal file
17
src/client/generic_notifier.ml
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
module type IO = Generic_io.S
|
||||||
|
|
||||||
|
module type S = sig
|
||||||
|
module IO : IO
|
||||||
|
|
||||||
|
type t
|
||||||
|
|
||||||
|
val create : unit -> t
|
||||||
|
|
||||||
|
val delete : t -> unit
|
||||||
|
|
||||||
|
val trigger : t -> unit
|
||||||
|
|
||||||
|
val wait : t -> unit IO.t
|
||||||
|
|
||||||
|
val register_bounded_queue : t -> _ Bounded_queue.t -> unit
|
||||||
|
end
|
||||||
11
src/client/lwt/io_lwt.ml
Normal file
11
src/client/lwt/io_lwt.ml
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
type 'a t = 'a Lwt.t
|
||||||
|
|
||||||
|
let return = Lwt.return
|
||||||
|
|
||||||
|
let ( let* ) = Lwt.Syntax.( let* )
|
||||||
|
|
||||||
|
let sleep_s = Lwt_unix.sleep
|
||||||
|
|
||||||
|
let spawn = Lwt.async
|
||||||
|
|
||||||
|
let[@inline] protect ~finally f = Lwt.finalize f finally
|
||||||
1
src/client/lwt/io_lwt.mli
Normal file
1
src/client/lwt/io_lwt.mli
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
include Generic_io.S_WITH_CONCURRENCY with type 'a t = 'a Lwt.t
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
(** Notification that can be used on the consumer side of a bounded queue *)
|
(** Notification that can be used on the consumer side of a bounded queue *)
|
||||||
|
|
||||||
|
module IO = Io_lwt
|
||||||
|
|
||||||
type t = {
|
type t = {
|
||||||
notified: bool Atomic.t;
|
notified: bool Atomic.t;
|
||||||
cond: unit Lwt_condition.t;
|
cond: unit Lwt_condition.t;
|
||||||
1
src/client/lwt/notifier_lwt.mli
Normal file
1
src/client/lwt/notifier_lwt.mli
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
include Generic_notifier.S with module IO = Io_lwt
|
||||||
8
src/client/notifier_sync.ml
Normal file
8
src/client/notifier_sync.ml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
include Util_thread.MCond
|
||||||
|
module IO = Generic_io.Direct_style
|
||||||
|
|
||||||
|
let delete = ignore
|
||||||
|
|
||||||
|
let trigger = signal
|
||||||
|
|
||||||
|
let register_bounded_queue = wakeup_from_bq
|
||||||
1
src/client/notifier_sync.mli
Normal file
1
src/client/notifier_sync.mli
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
include Generic_notifier.S with type 'a IO.t = 'a
|
||||||
|
|
@ -48,7 +48,9 @@ module Encode = struct
|
||||||
let x = ctor resource in
|
let x = ctor resource in
|
||||||
let@ _sc = Self_trace.with_ ~kind:Span.Span_kind_internal "encode-proto" in
|
let@ _sc = Self_trace.with_ ~kind:Span.Span_kind_internal "encode-proto" in
|
||||||
enc x encoder;
|
enc x encoder;
|
||||||
Pbrt.Encoder.to_string encoder
|
let data = Pbrt.Encoder.to_string encoder in
|
||||||
|
Pbrt.Encoder.reset encoder;
|
||||||
|
data
|
||||||
|
|
||||||
let logs ?encoder resource_logs =
|
let logs ?encoder resource_logs =
|
||||||
resource_to_string ~encoder resource_logs
|
resource_to_string ~encoder resource_logs
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue