From ef50b578f1fd5d659cd6940cfb4c282d74fda2de Mon Sep 17 00:00:00 2001 From: Simon Cruanes Date: Fri, 2 May 2025 08:56:25 -0400 Subject: [PATCH 01/15] refactor(subscriber): timestamps are int64ns now --- src/subscriber/callbacks.ml | 22 ++++++++++----------- src/subscriber/time_.dummy.ml | 2 +- src/subscriber/time_.mli | 2 +- src/subscriber/time_.mtime.ml | 4 ++-- src/subscriber/time_.unix.ml | 4 ++-- src/subscriber/trace_subscriber.ml | 21 +++++++++++--------- src/subscriber/trace_subscriber.mli | 10 +++++++--- src/tef-tldrs/trace_tef_tldrs.ml | 2 +- src/tef/trace_tef.ml | 30 +++++++++++++++-------------- 9 files changed, 53 insertions(+), 44 deletions(-) diff --git a/src/subscriber/callbacks.ml b/src/subscriber/callbacks.ml index 5d2c759..95926ea 100644 --- a/src/subscriber/callbacks.ml +++ b/src/subscriber/callbacks.ml @@ -29,16 +29,16 @@ module type S = sig type st (** Type of the state passed to every callback. *) - val on_init : st -> time_ns:float -> unit + val on_init : st -> time_ns:int64 -> unit (** Called when the subscriber is initialized in a collector *) - val on_shutdown : st -> time_ns:float -> unit + val on_shutdown : st -> time_ns:int64 -> unit (** Called when the collector is shutdown *) - val on_name_thread : st -> time_ns:float -> tid:int -> name:string -> unit + val on_name_thread : st -> time_ns:int64 -> tid:int -> name:string -> unit (** Current thread is being named *) - val on_name_process : st -> time_ns:float -> tid:int -> name:string -> unit + val on_name_process : st -> time_ns:int64 -> tid:int -> name:string -> unit (** Current process is being named *) val on_enter_span : @@ -46,7 +46,7 @@ module type S = sig __FUNCTION__:string option -> __FILE__:string -> __LINE__:int -> - time_ns:float -> + time_ns:int64 -> tid:int -> data:(string * user_data) list -> name:string -> @@ -54,7 +54,7 @@ module type S = sig unit (** Enter a regular (sync) span *) - val on_exit_span : st -> time_ns:float -> tid:int -> span -> unit + val on_exit_span : st -> time_ns:int64 -> tid:int -> span -> unit (** Exit a span. This and [on_enter_span] must follow strict stack discipline *) @@ -63,7 +63,7 @@ module type S = sig val on_message : st -> - time_ns:float -> + time_ns:int64 -> tid:int -> span:span option -> data:(string * user_data) list -> @@ -73,7 +73,7 @@ module type S = sig val on_counter : st -> - time_ns:float -> + time_ns:int64 -> tid:int -> data:(string * user_data) list -> name:string -> @@ -86,7 +86,7 @@ module type S = sig __FUNCTION__:string option -> __FILE__:string -> __LINE__:int -> - time_ns:float -> + time_ns:int64 -> tid:int -> parent:span option -> data:(string * user_data) list -> @@ -99,7 +99,7 @@ module type S = sig val on_exit_manual_span : st -> - time_ns:float -> + time_ns:int64 -> tid:int -> name:string -> data:(string * user_data) list -> @@ -110,7 +110,7 @@ module type S = sig (** Exit a manual span *) val on_extension_event : - st -> time_ns:float -> tid:int -> extension_event -> unit + st -> time_ns:int64 -> tid:int -> extension_event -> unit (** Extension event @since 0.8 *) end diff --git a/src/subscriber/time_.dummy.ml b/src/subscriber/time_.dummy.ml index c727752..29ce8e8 100644 --- a/src/subscriber/time_.dummy.ml +++ b/src/subscriber/time_.dummy.ml @@ -1 +1 @@ -let[@inline] get_time_ns () : float = 0. +let[@inline] get_time_ns () : int64 = 0L diff --git a/src/subscriber/time_.mli b/src/subscriber/time_.mli index ee1a9f4..5b29ca0 100644 --- a/src/subscriber/time_.mli +++ b/src/subscriber/time_.mli @@ -1 +1 @@ -val get_time_ns : unit -> float +val get_time_ns : unit -> int64 diff --git a/src/subscriber/time_.mtime.ml b/src/subscriber/time_.mtime.ml index 6b512c8..baa3f86 100644 --- a/src/subscriber/time_.mtime.ml +++ b/src/subscriber/time_.mtime.ml @@ -1,3 +1,3 @@ -let[@inline] get_time_ns () : float = +let[@inline] get_time_ns () : int64 = let t = Mtime_clock.now () in - Int64.to_float (Mtime.to_uint64_ns t) + Mtime.to_uint64_ns t diff --git a/src/subscriber/time_.unix.ml b/src/subscriber/time_.unix.ml index b2683ff..f7411c1 100644 --- a/src/subscriber/time_.unix.ml +++ b/src/subscriber/time_.unix.ml @@ -1,3 +1,3 @@ -let[@inline] get_time_ns () : float = +let[@inline] get_time_ns () : int64 = let t = Unix.gettimeofday () in - t *. 1e9 + Int64.of_float (t *. 1e9) diff --git a/src/subscriber/trace_subscriber.ml b/src/subscriber/trace_subscriber.ml index b287d69..d419587 100644 --- a/src/subscriber/trace_subscriber.ml +++ b/src/subscriber/trace_subscriber.ml @@ -6,19 +6,22 @@ include Types type t = Subscriber.t module Private_ = struct - let get_now_ns_ = ref None - let get_tid_ = ref None + let mock = ref false + let get_now_ns_ = ref Time_.get_time_ns + let get_tid_ = ref Thread_.get_tid (** Now, in nanoseconds *) - let[@inline] now_ns () : float = - match !get_now_ns_ with - | Some f -> f () - | None -> Time_.get_time_ns () + let[@inline] now_ns () : int64 = + if !mock then + !get_now_ns_ () + else + Time_.get_time_ns () let[@inline] tid_ () : int = - match !get_tid_ with - | Some f -> f () - | None -> Thread_.get_tid () + if !mock then + !get_tid_ () + else + Thread_.get_tid () end open struct diff --git a/src/subscriber/trace_subscriber.mli b/src/subscriber/trace_subscriber.mli index 387b152..a3f2325 100644 --- a/src/subscriber/trace_subscriber.mli +++ b/src/subscriber/trace_subscriber.mli @@ -31,13 +31,17 @@ val collector : t -> Trace_core.collector (**/**) module Private_ : sig - val get_now_ns_ : (unit -> float) option ref + val mock : bool ref + (** Global mock flag. If enable, all timestamps, tid, etc should be faked. *) + + val get_now_ns_ : (unit -> int64) ref (** The callback used to get the current timestamp *) - val get_tid_ : (unit -> int) option ref + val get_tid_ : (unit -> int) ref (** The callback used to get the current thread's id *) - val now_ns : unit -> float + val now_ns : unit -> int64 + (** Get the current timestamp, or a mock version *) end (**/**) diff --git a/src/tef-tldrs/trace_tef_tldrs.ml b/src/tef-tldrs/trace_tef_tldrs.ml index 7d3eb2f..47584a4 100644 --- a/src/tef-tldrs/trace_tef_tldrs.ml +++ b/src/tef-tldrs/trace_tef_tldrs.ml @@ -22,7 +22,7 @@ let get_unix_socket () = type as_client = { trace_id: string; - socket: string; + socket: string; (** Unix socket address *) emit_tef_at_exit: string option; (** For parent, ask daemon to emit traces here *) } diff --git a/src/tef/trace_tef.ml b/src/tef/trace_tef.ml index a24f5a1..d9dd4f8 100644 --- a/src/tef/trace_tef.ml +++ b/src/tef/trace_tef.ml @@ -6,6 +6,9 @@ module A = Trace_core.Internal_.Atomic_ let on_tracing_error = ref (fun s -> Printf.eprintf "trace-tef error: %s\n%!" s) +let[@inline] time_us_of_time_ns (t : int64) : float = + Int64.div t 1_000L |> Int64.to_float + let[@inline] int64_of_trace_id_ (id : Trace_core.trace_id) : int64 = if id == Trace_core.Collector.dummy_trace_id then 0L @@ -13,14 +16,13 @@ let[@inline] int64_of_trace_id_ (id : Trace_core.trace_id) : int64 = Bytes.get_int64_le (Bytes.unsafe_of_string id) 0 module Mock_ = struct - let enabled = ref false let now = ref 0 (* used to mock timing *) - let get_now_ns () : float = + let get_now_ns () : int64 = let x = !now in incr now; - float_of_int x *. 1000. + Int64.(mul (of_int x) 1000L) let get_tid_ () : int = 3 end @@ -63,7 +65,7 @@ module Writer = struct | `Output oc -> oc, false in let pid = - if !Mock_.enabled then + if !Sub.Private_.mock then 2 else Unix.getpid () @@ -300,7 +302,7 @@ let bg_thread ~mode ~out (events : Event.t B_queue.t) : unit = (* write a message about us closing *) Writer.emit_instant_event ~name:"tef-worker.exit" ~tid:(Thread.id @@ Thread.self ()) - ~ts:(Sub.Private_.now_ns () *. 1e-3) + ~ts:(time_us_of_time_ns @@ Sub.Private_.now_ns ()) ~args:[] writer; (* warn if app didn't close all spans *) @@ -354,12 +356,12 @@ let subscriber_ ~finally ~out ~(mode : [ `Single | `Jsonl ]) () : Sub.t = let[@inline] on_enter_span (self : st) ~__FUNCTION__:fun_name ~__FILE__:_ ~__LINE__:_ ~time_ns ~tid ~data ~name span : unit = - let time_us = time_ns *. 1e-3 in + let time_us = time_us_of_time_ns @@ time_ns in B_queue.push self.events @@ E_define_span { tid; name; time_us; id = span; fun_name; data } let on_exit_span (self : st) ~time_ns ~tid:_ span : unit = - let time_us = time_ns *. 1e-3 in + let time_us = time_us_of_time_ns @@ time_ns in B_queue.push self.events @@ E_exit_span { id = span; time_us } let on_add_data (self : st) ~data span = @@ -367,24 +369,24 @@ let subscriber_ ~finally ~out ~(mode : [ `Single | `Jsonl ]) () : Sub.t = B_queue.push self.events @@ E_add_data { id = span; data } let on_message (self : st) ~time_ns ~tid ~span:_ ~data msg : unit = - let time_us = time_ns *. 1e-3 in + let time_us = time_us_of_time_ns @@ time_ns in B_queue.push self.events @@ E_message { tid; time_us; msg; data } let on_counter (self : st) ~time_ns ~tid ~data:_ ~name f : unit = - let time_us = time_ns *. 1e-3 in + let time_us = time_us_of_time_ns @@ time_ns in B_queue.push self.events @@ E_counter { name; n = f; time_us; tid } let on_enter_manual_span (self : st) ~__FUNCTION__:fun_name ~__FILE__:_ ~__LINE__:_ ~time_ns ~tid ~parent:_ ~data ~name ~flavor ~trace_id _span : unit = - let time_us = time_ns *. 1e-3 in + let time_us = time_us_of_time_ns @@ time_ns in B_queue.push self.events @@ E_enter_manual_span { id = trace_id; time_us; tid; data; name; fun_name; flavor } let on_exit_manual_span (self : st) ~time_ns ~tid ~name ~data ~flavor ~trace_id (_ : span) : unit = - let time_us = time_ns *. 1e-3 in + let time_us = time_us_of_time_ns @@ time_ns in B_queue.push self.events @@ E_exit_manual_span { tid; id = trace_id; name; time_us; data; flavor } @@ -438,9 +440,9 @@ let with_setup ?out () f = module Private_ = struct let mock_all_ () = - Mock_.enabled := true; - Sub.Private_.get_now_ns_ := Some Mock_.get_now_ns; - Sub.Private_.get_tid_ := Some Mock_.get_tid_; + Sub.Private_.mock := true; + Sub.Private_.get_now_ns_ := Mock_.get_now_ns; + Sub.Private_.get_tid_ := Mock_.get_tid_; () let on_tracing_error = on_tracing_error From 7405e3ae1bf9c5ff5fd9879b5a7cdc3e6a06b9ff Mon Sep 17 00:00:00 2001 From: Simon Cruanes Date: Fri, 2 May 2025 08:56:48 -0400 Subject: [PATCH 02/15] wip: port fuchsia to subscriber infra --- src/fuchsia/fcollector.ml | 20 ++++++++++++++++++++ src/fuchsia/fcollector.mli | 8 ++++++++ src/fuchsia/trace_fuchsia.ml | 1 + src/fuchsia/trace_fuchsia.mli | 3 +++ 4 files changed, 32 insertions(+) diff --git a/src/fuchsia/fcollector.ml b/src/fuchsia/fcollector.ml index c2fc367..35c7925 100644 --- a/src/fuchsia/fcollector.ml +++ b/src/fuchsia/fcollector.ml @@ -5,6 +5,19 @@ module Int_map = Map.Make (Int) let pid = Unix.getpid () +module Mock_ = struct + let enabled = ref false + let now = ref 0 + + (* used to mock timing *) + let get_now_ns () : float = + let x = !now in + incr now; + float_of_int x *. 1000. + + let get_tid_ () : int = 3 +end + (** Thread-local stack of span info *) module Span_info_stack : sig type t @@ -408,3 +421,10 @@ let create ~out () : collector = () in (module Coll) + +module Internal_ = struct + let mock_all_ () = + Mock_.enabled := true; + Sub.Private_.get_now_ns_ := Some Mock_.get_now_ns; + Sub.Private_.get_tid_ := Some Mock_.get_tid_ +end diff --git a/src/fuchsia/fcollector.mli b/src/fuchsia/fcollector.mli index 780b3f1..2ba03e9 100644 --- a/src/fuchsia/fcollector.mli +++ b/src/fuchsia/fcollector.mli @@ -1,3 +1,11 @@ open Trace_core val create : out:Bg_thread.out -> unit -> collector + +(**/**) + +module Internal_ : sig + val mock_all_ : unit -> unit +end + +(**/**) diff --git a/src/fuchsia/trace_fuchsia.ml b/src/fuchsia/trace_fuchsia.ml index 102a744..f6b2b14 100644 --- a/src/fuchsia/trace_fuchsia.ml +++ b/src/fuchsia/trace_fuchsia.ml @@ -34,5 +34,6 @@ let with_setup ?out () f = Fun.protect ~finally:Trace_core.shutdown f module Internal_ = struct + let mock_all_ = Fcollector.Internal_.mock_all_ let on_tracing_error = on_tracing_error end diff --git a/src/fuchsia/trace_fuchsia.mli b/src/fuchsia/trace_fuchsia.mli index eaf1847..d28111a 100644 --- a/src/fuchsia/trace_fuchsia.mli +++ b/src/fuchsia/trace_fuchsia.mli @@ -45,6 +45,9 @@ val with_setup : ?out:[ output | `Env ] -> unit -> (unit -> 'a) -> 'a module Internal_ : sig val on_tracing_error : (string -> unit) ref + + val mock_all_ : unit -> unit + (** use fake, deterministic timestamps, TID, PID *) end (**/**) From 7cc16bc0b8fab2ff4b797d9221b70e4f192179e7 Mon Sep 17 00:00:00 2001 From: Simon Cruanes Date: Fri, 2 May 2025 08:57:08 -0400 Subject: [PATCH 03/15] wip: test for fuchsia --- test/fuchsia/dune | 5 +++++ test/fuchsia/t1.ml | 48 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 test/fuchsia/dune create mode 100644 test/fuchsia/t1.ml diff --git a/test/fuchsia/dune b/test/fuchsia/dune new file mode 100644 index 0000000..087400b --- /dev/null +++ b/test/fuchsia/dune @@ -0,0 +1,5 @@ +(test + (name t1) + (package trace-fuchsia) + (modules t1) + (libraries trace trace-fuchsia)) diff --git a/test/fuchsia/t1.ml b/test/fuchsia/t1.ml new file mode 100644 index 0000000..83e55f3 --- /dev/null +++ b/test/fuchsia/t1.ml @@ -0,0 +1,48 @@ +let run () = + Trace.set_process_name "main"; + Trace.set_thread_name "t1"; + + let n = ref 0 in + + for _i = 1 to 50 do + Trace.with_span ~__FILE__ ~__LINE__ "outer.loop" @@ fun _sp -> + let pseudo_async_sp = + Trace.enter_manual_span ~parent:None ~__FILE__ ~__LINE__ "fake_sleep" + in + + for _j = 2 to 5 do + incr n; + Trace.with_span ~__FILE__ ~__LINE__ "inner.loop" @@ fun _sp -> + Trace.messagef (fun k -> k "hello %d %d" _i _j); + Trace.message "world"; + Trace.counter_int "n" !n; + + Trace.add_data_to_span _sp [ "i", `Int _i ]; + + if _j = 2 then ( + Trace.add_data_to_span _sp [ "j", `Int _j ]; + let _sp = + Trace.enter_manual_span + ~parent:(Some (Trace.ctx_of_span pseudo_async_sp)) + ~flavor: + (if _i mod 3 = 0 then + `Sync + else + `Async) + ~__FILE__ ~__LINE__ "sub-sleep" + in + + (* fake micro sleep *) + Thread.delay 0.005; + Trace.exit_manual_span _sp + ) else if _j = 3 then ( + (* pretend some task finished. Note that this is not well scoped wrt other spans. *) + Trace.add_data_to_manual_span pseudo_async_sp [ "slept", `Bool true ]; + Trace.exit_manual_span pseudo_async_sp + ) + done + done + +let () = + Trace_tef.Private_.mock_all_ (); + Trace_tef.with_setup ~out:`Stdout () @@ fun () -> run () From 76703461eab929232e227768179e6cc11b070ba5 Mon Sep 17 00:00:00 2001 From: Simon Cruanes Date: Wed, 7 May 2025 11:08:49 -0400 Subject: [PATCH 04/15] feat(trace.subscriber): add `Span_tbl`, and a depopt on picos_aux --- .github/workflows/main.yml | 3 +++ dune-project | 1 + src/subscriber/callbacks.ml | 11 ++++++++- src/subscriber/dune | 7 ++++++ src/subscriber/span_tbl.ml | 1 + src/subscriber/span_tbl.mli | 21 ++++++++++++++++ src/subscriber/tbl_.basic.ml | 13 ++++++++++ src/subscriber/tbl_.mli | 7 ++++++ src/subscriber/tbl_.picos.ml | 18 ++++++++++++++ src/subscriber/tbl_.thread.ml | 38 +++++++++++++++++++++++++++++ src/subscriber/trace_subscriber.ml | 1 + src/subscriber/trace_subscriber.mli | 4 +++ trace.opam | 1 + 13 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 src/subscriber/span_tbl.ml create mode 100644 src/subscriber/span_tbl.mli create mode 100644 src/subscriber/tbl_.basic.ml create mode 100644 src/subscriber/tbl_.mli create mode 100644 src/subscriber/tbl_.picos.ml create mode 100644 src/subscriber/tbl_.thread.ml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 009de5a..3f894c3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -51,6 +51,9 @@ jobs: - run: opam install hmap - run: opam exec -- dune build '@install' -p trace,trace-tef,trace-fuchsia + - run: opam install picos_aux + - run: opam exec -- dune build '@install' -p trace,trace-tef,trace-fuchsia + - run: opam install mtime - run: opam exec -- dune build '@install' -p trace,trace-tef,trace-fuchsia diff --git a/dune-project b/dune-project index 3e8fcbf..0ef4777 100644 --- a/dune-project +++ b/dune-project @@ -28,6 +28,7 @@ (depopts hmap unix + (picos_aux (>= 0.6)) (mtime (>= 2.0))) (tags diff --git a/src/subscriber/callbacks.ml b/src/subscriber/callbacks.ml index 95926ea..3dd1b70 100644 --- a/src/subscriber/callbacks.ml +++ b/src/subscriber/callbacks.ml @@ -121,7 +121,16 @@ type 'st t = (module S with type st = 'st) (** Dummy callbacks. It can be useful to reuse some of these functions in a real subscriber that doesn't want to handle {b all} events, but only some of - them. *) + them. + + To write a subscriber that only supports some callbacks, this can be handy: + {[ + module My_callbacks = struct + type st = my_own_state + include Callbacks.Dummy + let on_counter (st:st) ~time_ns ~tid ~data ~name v : unit = ... + end + ]} *) module Dummy = struct let on_init _ ~time_ns:_ = () let on_shutdown _ ~time_ns:_ = () diff --git a/src/subscriber/dune b/src/subscriber/dune index 478132a..580214d 100644 --- a/src/subscriber/dune +++ b/src/subscriber/dune @@ -1,6 +1,7 @@ (library (name trace_subscriber) (public_name trace.subscriber) + (private_modules time_ thread_ tbl_) (libraries (re_export trace.core) (select @@ -8,6 +9,12 @@ from (threads -> thread_.real.ml) (-> thread_.dummy.ml)) + (select + tbl_.ml + from + (picos_aux.htbl -> tbl_.picos.ml) + (threads -> tbl_.thread.ml) + (-> tbl_.basic.ml)) (select time_.ml from diff --git a/src/subscriber/span_tbl.ml b/src/subscriber/span_tbl.ml new file mode 100644 index 0000000..e5113cc --- /dev/null +++ b/src/subscriber/span_tbl.ml @@ -0,0 +1 @@ +include Tbl_ diff --git a/src/subscriber/span_tbl.mli b/src/subscriber/span_tbl.mli new file mode 100644 index 0000000..32be058 --- /dev/null +++ b/src/subscriber/span_tbl.mli @@ -0,0 +1,21 @@ +(** A table that can be used to remember information about spans. + + This is convenient when we want to rememner information from a span begin, + when dealing with the corresponding span end. + + {b NOTE}: this is thread safe when threads are enabled. *) + +open Trace_core + +type 'v t + +val create : unit -> 'v t +val add : 'v t -> span -> 'v -> unit + +val find_exn : 'v t -> span -> 'v +(** @raise Not_found if information isn't found *) + +val remove : _ t -> span -> unit +(** Remove the span if present *) + +val to_list : 'v t -> (span * 'v) list diff --git a/src/subscriber/tbl_.basic.ml b/src/subscriber/tbl_.basic.ml new file mode 100644 index 0000000..e182d8a --- /dev/null +++ b/src/subscriber/tbl_.basic.ml @@ -0,0 +1,13 @@ +module T = Hashtbl.Make (struct + include Int64 + + let hash = Hashtbl.hash +end) + +type 'v t = 'v T.t + +let create () : _ t = T.create 32 +let find_exn = T.find +let remove = T.remove +let add = T.replace +let to_list self : _ list = T.fold (fun k v l -> (k, v) :: l) self [] diff --git a/src/subscriber/tbl_.mli b/src/subscriber/tbl_.mli new file mode 100644 index 0000000..78e2443 --- /dev/null +++ b/src/subscriber/tbl_.mli @@ -0,0 +1,7 @@ +type 'v t + +val create : unit -> 'v t +val add : 'v t -> int64 -> 'v -> unit +val find_exn : 'v t -> int64 -> 'v +val remove : _ t -> int64 -> unit +val to_list : 'v t -> (int64 * 'v) list diff --git a/src/subscriber/tbl_.picos.ml b/src/subscriber/tbl_.picos.ml new file mode 100644 index 0000000..36dba69 --- /dev/null +++ b/src/subscriber/tbl_.picos.ml @@ -0,0 +1,18 @@ +module H = Picos_aux_htbl + +module Key = struct + include Int64 + + let hash = Hashtbl.hash +end + +type 'v t = (int64, 'v) H.t + +let create () : _ t = H.create ~hashed_type:(module Key) () +let find_exn = H.find_exn +let[@inline] remove self k = ignore (H.try_remove self k : bool) + +let[@inline] add self k v = + if not (H.try_add self k v) then ignore (H.try_set self k v) + +let[@inline] to_list self = H.to_seq self |> List.of_seq diff --git a/src/subscriber/tbl_.thread.ml b/src/subscriber/tbl_.thread.ml new file mode 100644 index 0000000..54517f2 --- /dev/null +++ b/src/subscriber/tbl_.thread.ml @@ -0,0 +1,38 @@ +module T = Hashtbl.Make (struct + include Int64 + + let hash = Hashtbl.hash +end) + +type 'v t = { + tbl: 'v T.t; + lock: Mutex.t; +} + +let create () : _ t = { tbl = T.create 32; lock = Mutex.create () } + +let find_exn self k = + Mutex.lock self.lock; + try + let v = T.find self.tbl k in + Mutex.unlock self.lock; + v + with e -> + Mutex.unlock self.lock; + raise e + +let remove self k = + Mutex.lock self.lock; + T.remove self.tbl k; + Mutex.unlock self.lock + +let add self k v = + Mutex.lock self.lock; + T.replace self.tbl k v; + Mutex.unlock self.lock + +let to_list self : _ list = + Mutex.lock self.lock; + let l = T.fold (fun k v l -> (k, v) :: l) self.tbl [] in + Mutex.unlock self.lock; + l diff --git a/src/subscriber/trace_subscriber.ml b/src/subscriber/trace_subscriber.ml index d419587..eb35150 100644 --- a/src/subscriber/trace_subscriber.ml +++ b/src/subscriber/trace_subscriber.ml @@ -1,6 +1,7 @@ open Trace_core module Callbacks = Callbacks module Subscriber = Subscriber +module Span_tbl = Span_tbl include Types type t = Subscriber.t diff --git a/src/subscriber/trace_subscriber.mli b/src/subscriber/trace_subscriber.mli index a3f2325..5b535bf 100644 --- a/src/subscriber/trace_subscriber.mli +++ b/src/subscriber/trace_subscriber.mli @@ -4,10 +4,14 @@ trace event. It also defines a collector that needs to be installed for the subscriber(s) to be called. + Thanks to {!Subscriber.tee_l} it's possible to combine multiple subscribers + into a single collector. + @since 0.8 *) module Callbacks = Callbacks module Subscriber = Subscriber +module Span_tbl = Span_tbl include module type of struct include Types diff --git a/trace.opam b/trace.opam index 83bc324..8813330 100644 --- a/trace.opam +++ b/trace.opam @@ -17,6 +17,7 @@ depends: [ depopts: [ "hmap" "unix" + "picos_aux" {>= "0.6"} "mtime" {>= "2.0"} ] build: [ From 005626a2cd398d293dd76f916c822c530979eb0a Mon Sep 17 00:00:00 2001 From: Simon Cruanes Date: Wed, 7 May 2025 11:10:15 -0400 Subject: [PATCH 05/15] feat: add trace.event, useful for background threads send these events into a queue and process them somewhere else. --- src/event/dune | 7 +++++ src/event/event.ml | 63 +++++++++++++++++++++++++++++++++++++++++ src/event/subscriber.ml | 53 ++++++++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+) create mode 100644 src/event/dune create mode 100644 src/event/event.ml create mode 100644 src/event/subscriber.ml diff --git a/src/event/dune b/src/event/dune new file mode 100644 index 0000000..051a6f0 --- /dev/null +++ b/src/event/dune @@ -0,0 +1,7 @@ + +(library + (name trace_event) + (public_name trace.event) + (synopsis "Turns subscriber callbacks into an event type") + (libraries + (re_export trace.core) (re_export trace.subscriber))) diff --git a/src/event/event.ml b/src/event/event.ml new file mode 100644 index 0000000..7ccb4d2 --- /dev/null +++ b/src/event/event.ml @@ -0,0 +1,63 @@ +open Trace_core +module Sub = Trace_subscriber + +(** An event with TEF/fuchsia semantics *) +type t = + | E_tick + | E_init of { time_ns: int64 } + | E_shutdown of { time_ns: int64 } + | E_message of { + tid: int; + msg: string; + time_ns: int64; + data: (string * Sub.user_data) list; + } + | E_define_span of { + tid: int; + name: string; + time_ns: int64; + id: span; + fun_name: string option; + data: (string * Sub.user_data) list; + } + | E_exit_span of { + id: span; + time_ns: int64; + } + | E_add_data of { + id: span; + data: (string * Sub.user_data) list; + } + | E_enter_manual_span of { + tid: int; + name: string; + time_ns: int64; + id: trace_id; + flavor: Sub.flavor option; + fun_name: string option; + data: (string * Sub.user_data) list; + } + | E_exit_manual_span of { + tid: int; + name: string; + time_ns: int64; + flavor: Sub.flavor option; + data: (string * Sub.user_data) list; + id: trace_id; + } + | E_counter of { + name: string; + tid: int; + time_ns: int64; + n: float; + } + | E_name_process of { name: string } + | E_name_thread of { + tid: int; + name: string; + } + | E_extension_event of { + tid: int; + time_ns: int64; + ext: Trace_core.extension_event; + } diff --git a/src/event/subscriber.ml b/src/event/subscriber.ml new file mode 100644 index 0000000..11edab7 --- /dev/null +++ b/src/event/subscriber.ml @@ -0,0 +1,53 @@ +open Trace_core +open Event + +type event_consumer = { on_event: Event.t -> unit } [@@unboxed] +(** Callback for events *) + +module Callbacks : Sub.Callbacks.S with type st = event_consumer = struct + type st = event_consumer + + let on_init (self : st) ~time_ns = self.on_event (E_init { time_ns }) + let on_shutdown (self : st) ~time_ns = self.on_event (E_shutdown { time_ns }) + + let on_name_process (self : st) ~time_ns:_ ~tid:_ ~name : unit = + self.on_event @@ E_name_process { name } + + let on_name_thread (self : st) ~time_ns:_ ~tid ~name : unit = + self.on_event @@ E_name_thread { tid; name } + + let[@inline] on_enter_span (self : st) ~__FUNCTION__:fun_name ~__FILE__:_ + ~__LINE__:_ ~time_ns ~tid ~data ~name span : unit = + self.on_event + @@ E_define_span { tid; name; time_ns; id = span; fun_name; data } + + let on_exit_span (self : st) ~time_ns ~tid:_ span : unit = + self.on_event @@ E_exit_span { id = span; time_ns } + + let on_add_data (self : st) ~data span = + if data <> [] then self.on_event @@ E_add_data { id = span; data } + + let on_message (self : st) ~time_ns ~tid ~span:_ ~data msg : unit = + self.on_event @@ E_message { tid; time_ns; msg; data } + + let on_counter (self : st) ~time_ns ~tid ~data:_ ~name f : unit = + self.on_event @@ E_counter { name; n = f; time_ns; tid } + + let on_enter_manual_span (self : st) ~__FUNCTION__:fun_name ~__FILE__:_ + ~__LINE__:_ ~time_ns ~tid ~parent:_ ~data ~name ~flavor ~trace_id _span : + unit = + self.on_event + @@ E_enter_manual_span + { id = trace_id; time_ns; tid; data; name; fun_name; flavor } + + let on_exit_manual_span (self : st) ~time_ns ~tid ~name ~data ~flavor + ~trace_id (_ : span) : unit = + self.on_event + @@ E_exit_manual_span { tid; id = trace_id; name; time_ns; data; flavor } + + let on_extension_event (self : st) ~time_ns ~tid ext : unit = + self.on_event @@ E_extension_event { tid; time_ns; ext } +end + +let subscriber (consumer : event_consumer) : Sub.t = + Sub.Subscriber.Sub { st = consumer; callbacks = (module Callbacks) } From 4454975a616d610a23dd790f2a7c8faf275a71fd Mon Sep 17 00:00:00 2001 From: Simon Cruanes Date: Wed, 7 May 2025 13:06:55 -0400 Subject: [PATCH 06/15] feat util: remove b_queue, add Rpool to be used in various buffer pools. --- src/util/b_queue.ml | 65 ---------------------------------------- src/util/b_queue.mli | 18 ----------- src/util/mpsc_bag.ml | 32 -------------------- src/util/mpsc_bag.mli | 11 ------- src/util/rpool.ml | 69 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 69 insertions(+), 126 deletions(-) delete mode 100644 src/util/b_queue.ml delete mode 100644 src/util/b_queue.mli delete mode 100644 src/util/mpsc_bag.ml delete mode 100644 src/util/mpsc_bag.mli create mode 100644 src/util/rpool.ml diff --git a/src/util/b_queue.ml b/src/util/b_queue.ml deleted file mode 100644 index f5ee5f3..0000000 --- a/src/util/b_queue.ml +++ /dev/null @@ -1,65 +0,0 @@ -module A = Trace_core.Internal_.Atomic_ - -type 'a t = { - mutex: Mutex.t; - cond: Condition.t; - q: 'a Mpsc_bag.t; - mutable closed: bool; - consumer_waiting: bool A.t; -} - -exception Closed - -let create () : _ t = - { - mutex = Mutex.create (); - cond = Condition.create (); - q = Mpsc_bag.create (); - closed = false; - consumer_waiting = A.make false; - } - -let close (self : _ t) = - Mutex.lock self.mutex; - if not self.closed then ( - self.closed <- true; - Condition.broadcast self.cond (* awake waiters so they fail *) - ); - Mutex.unlock self.mutex - -let push (self : _ t) x : unit = - if self.closed then raise Closed; - Mpsc_bag.add self.q x; - if self.closed then raise Closed; - if A.get self.consumer_waiting then ( - (* wakeup consumer *) - Mutex.lock self.mutex; - Condition.broadcast self.cond; - Mutex.unlock self.mutex - ) - -let rec pop_all (self : 'a t) : 'a list = - match Mpsc_bag.pop_all self.q with - | Some l -> l - | None -> - if self.closed then raise Closed; - Mutex.lock self.mutex; - A.set self.consumer_waiting true; - (* check again, a producer might have pushed an element since we - last checked. However if we still find - nothing, because this comes after [consumer_waiting:=true], - any producer arriving after that will know to wake us up. *) - (match Mpsc_bag.pop_all self.q with - | Some l -> - A.set self.consumer_waiting false; - Mutex.unlock self.mutex; - l - | None -> - if self.closed then ( - Mutex.unlock self.mutex; - raise Closed - ); - Condition.wait self.cond self.mutex; - A.set self.consumer_waiting false; - Mutex.unlock self.mutex; - pop_all self) diff --git a/src/util/b_queue.mli b/src/util/b_queue.mli deleted file mode 100644 index 1fb8f5c..0000000 --- a/src/util/b_queue.mli +++ /dev/null @@ -1,18 +0,0 @@ -(** Basic Blocking Queue *) - -type 'a t - -val create : unit -> _ t - -exception Closed - -val push : 'a t -> 'a -> unit -(** [push q x] pushes [x] into [q], and returns [()]. - @raise Closed if [close q] was previously called.*) - -val pop_all : 'a t -> 'a list -(** [pop_all bq] returns all items presently in [bq], in the same order, and - clears [bq]. It blocks if no element is in [bq]. *) - -val close : _ t -> unit -(** Close the queue, meaning there won't be any more [push] allowed. *) diff --git a/src/util/mpsc_bag.ml b/src/util/mpsc_bag.ml deleted file mode 100644 index 02aeadf..0000000 --- a/src/util/mpsc_bag.ml +++ /dev/null @@ -1,32 +0,0 @@ -module A = Trace_core.Internal_.Atomic_ - -type 'a t = { bag: 'a list A.t } [@@unboxed] - -let create () = - let bag = A.make [] in - { bag } - -module Backoff = struct - type t = int - - let default = 2 - - let once (b : t) : t = - for _i = 1 to b do - Domain_util.cpu_relax () - done; - min (b * 2) 256 -end - -let rec add backoff t x = - let before = A.get t.bag in - let after = x :: before in - if not (A.compare_and_set t.bag before after) then - add (Backoff.once backoff) t x - -let[@inline] add t x = add Backoff.default t x - -let[@inline] pop_all t : _ list option = - match A.exchange t.bag [] with - | [] -> None - | l -> Some (List.rev l) diff --git a/src/util/mpsc_bag.mli b/src/util/mpsc_bag.mli deleted file mode 100644 index edb5dba..0000000 --- a/src/util/mpsc_bag.mli +++ /dev/null @@ -1,11 +0,0 @@ -(** A multi-producer, single-consumer bag *) - -type 'a t - -val create : unit -> 'a t - -val add : 'a t -> 'a -> unit -(** [add q x] adds [x] in the bag. *) - -val pop_all : 'a t -> 'a list option -(** Return all current items in the insertion order. *) diff --git a/src/util/rpool.ml b/src/util/rpool.ml new file mode 100644 index 0000000..2a5446c --- /dev/null +++ b/src/util/rpool.ml @@ -0,0 +1,69 @@ +(** A resource pool (for buffers) *) + +open struct + module A = Trace_core.Internal_.Atomic_ +end + +module List_with_len = struct + type +'a t = + | Nil + | Cons of int * 'a * 'a t + + let empty : _ t = Nil + + let[@inline] len = function + | Nil -> 0 + | Cons (i, _, _) -> i + + let[@inline] cons x self = Cons (len self + 1, x, self) +end + +type 'a t = { + max_size: int; + create: unit -> 'a; + clear: 'a -> unit; + cached: 'a List_with_len.t A.t; +} + +let create ~max_size ~create ~clear () : _ t = + { max_size; create; clear; cached = A.make List_with_len.empty } + +let alloc (type a) (self : a t) : a = + let module M = struct + exception Found of a + end in + try + while + match A.get self.cached with + | Nil -> false + | Cons (_, x, tl) as old -> + if A.compare_and_set self.cached old tl then + raise_notrace (M.Found x) + else + true + do + () + done; + self.create () + with M.Found x -> x + +let recycle (self : 'a t) (x : 'a) : unit = + self.clear x; + while + match A.get self.cached with + | Cons (i, _, _) when i >= self.max_size -> false (* drop buf *) + | old -> not (A.compare_and_set self.cached old (List_with_len.cons x old)) + do + () + done + +let with_ (self : 'a t) f = + let x = alloc self in + try + let res = f x in + recycle self x; + res + with e -> + let bt = Printexc.get_raw_backtrace () in + recycle self x; + Printexc.raise_with_backtrace e bt From 81096e0d3cac2d0ee6f5f944ebd30aa1b3afeaa6 Mon Sep 17 00:00:00 2001 From: Simon Cruanes Date: Wed, 7 May 2025 13:07:22 -0400 Subject: [PATCH 07/15] refactor TEF: split into exporter,writer,subscriber code is a lot cleaner now. --- src/tef/common_.ml | 4 + src/tef/emit_tef.ml | 0 src/tef/event.ml | 56 ------ src/tef/exporter.ml | 85 ++++++++ src/tef/subscriber.ml | 173 ++++++++++++++++ src/tef/subscriber.mli | 28 +++ src/tef/trace_tef.ml | 438 ++++++----------------------------------- src/tef/trace_tef.mli | 4 + src/tef/writer.ml | 97 +++++++++ src/tef/writer.mli | 54 +++++ 10 files changed, 501 insertions(+), 438 deletions(-) create mode 100644 src/tef/common_.ml create mode 100644 src/tef/emit_tef.ml delete mode 100644 src/tef/event.ml create mode 100644 src/tef/exporter.ml create mode 100644 src/tef/subscriber.ml create mode 100644 src/tef/subscriber.mli create mode 100644 src/tef/writer.ml create mode 100644 src/tef/writer.mli diff --git a/src/tef/common_.ml b/src/tef/common_.ml new file mode 100644 index 0000000..114d81c --- /dev/null +++ b/src/tef/common_.ml @@ -0,0 +1,4 @@ +module Sub = Trace_subscriber +module A = Trace_core.Internal_.Atomic_ + +let ( let@ ) = ( @@ ) diff --git a/src/tef/emit_tef.ml b/src/tef/emit_tef.ml new file mode 100644 index 0000000..e69de29 diff --git a/src/tef/event.ml b/src/tef/event.ml deleted file mode 100644 index 7e5c0fc..0000000 --- a/src/tef/event.ml +++ /dev/null @@ -1,56 +0,0 @@ -open Trace_core -module Sub = Trace_subscriber - -(** An event, specialized for TEF *) -type t = - | E_tick - | E_message of { - tid: int; - msg: string; - time_us: float; - data: (string * Sub.user_data) list; - } - | E_define_span of { - tid: int; - name: string; - time_us: float; - id: span; - fun_name: string option; - data: (string * Sub.user_data) list; - } - | E_exit_span of { - id: span; - time_us: float; - } - | E_add_data of { - id: span; - data: (string * Sub.user_data) list; - } - | E_enter_manual_span of { - tid: int; - name: string; - time_us: float; - id: trace_id; - flavor: Sub.flavor option; - fun_name: string option; - data: (string * Sub.user_data) list; - } - | E_exit_manual_span of { - tid: int; - name: string; - time_us: float; - flavor: Sub.flavor option; - data: (string * Sub.user_data) list; - id: trace_id; - } - | E_counter of { - name: string; - tid: int; - time_us: float; - n: float; - } - | E_name_process of { name: string } - | E_name_thread of { - tid: int; - name: string; - } diff --git a/src/tef/exporter.ml b/src/tef/exporter.ml new file mode 100644 index 0000000..561d80d --- /dev/null +++ b/src/tef/exporter.ml @@ -0,0 +1,85 @@ +(** An exporter, takes JSON objects and writes them somewhere *) + +open Common_ + +type t = { + on_json: Buffer.t -> unit; + (** Takes a buffer and writes it somewhere. The buffer is only valid + during this call and must not be stored. *) + flush: unit -> unit; (** Force write *) + close: unit -> unit; (** Close underlying resources *) +} +(** An exporter, takes JSON objects and writes them somewhere. + + This should be thread-safe if used in a threaded environment. *) + +open struct + let with_lock lock f = + Mutex.lock lock; + try + let res = f () in + Mutex.unlock lock; + res + with e -> + let bt = Printexc.get_raw_backtrace () in + Mutex.unlock lock; + Printexc.raise_with_backtrace e bt +end + +(** Export to the channel + @param jsonl + if true, export as a JSON object per line, otherwise export as a single + big JSON array. + @param close_channel if true, closing the exporter will close the channel *) +let of_out_channel ~close_channel ~jsonl oc : t = + let lock = Mutex.create () in + let first = ref true in + let closed = ref false in + let flush () = + let@ () = with_lock lock in + flush oc + in + let close () = + let@ () = with_lock lock in + if not !closed then ( + closed := true; + if not jsonl then output_char oc ']'; + if close_channel then close_out_noerr oc + ) + in + let on_json buf = + let@ () = with_lock lock in + if not jsonl then + if !first then ( + if not jsonl then output_char oc '['; + first := false + ) else + output_string oc ",\n"; + Buffer.output_buffer oc buf; + if jsonl then output_char oc '\n' + in + { flush; close; on_json } + +let of_buffer ~jsonl (buf : Buffer.t) : t = + let lock = Mutex.create () in + let first = ref true in + let closed = ref false in + let close () = + let@ () = with_lock lock in + if not !closed then ( + closed := true; + if not jsonl then Buffer.add_char buf ']' + ) + in + let on_json json = + let@ () = with_lock lock in + if not jsonl then + if !first then ( + if not jsonl then Buffer.add_char buf '['; + first := false + ) else + Buffer.add_string buf ",\n"; + Buffer.add_buffer buf json; + if jsonl then Buffer.add_char buf '\n' + in + { flush = ignore; close; on_json } diff --git a/src/tef/subscriber.ml b/src/tef/subscriber.ml new file mode 100644 index 0000000..d3e4b54 --- /dev/null +++ b/src/tef/subscriber.ml @@ -0,0 +1,173 @@ +open Common_ +open Trace_core +open Trace_private_util +module Span_tbl = Sub.Span_tbl + +module Buf_pool = struct + type t = Buffer.t Rpool.t + + let create ?(max_size = 32) ?(buf_size = 256) () : t = + Rpool.create ~max_size ~clear:Buffer.reset + ~create:(fun () -> Buffer.create buf_size) + () +end + +open struct + let[@inline] time_us_of_time_ns (t : int64) : float = + Int64.div t 1_000L |> Int64.to_float + + let[@inline] int64_of_trace_id_ (id : Trace_core.trace_id) : int64 = + if id == Trace_core.Collector.dummy_trace_id then + 0L + else + Bytes.get_int64_le (Bytes.unsafe_of_string id) 0 +end + +let on_tracing_error = ref (fun s -> Printf.eprintf "trace-tef error: %s\n%!" s) + +type span_info = { + tid: int; + name: string; + start_us: float; + mutable data: (string * Sub.user_data) list; + (* NOTE: thread safety: this is supposed to only be modified by the thread +that's running this (synchronous, stack-abiding) span. *) +} +(** Information we store about a span begin event, to emit a complete event when + we meet the corresponding span end event *) + +type t = { + active: bool A.t; + pid: int; + spans: span_info Span_tbl.t; + buf_pool: Buf_pool.t; + exporter: Exporter.t; +} +(** Subscriber state *) + +open struct + let print_non_closed_spans_warning spans = + let module Str_set = Set.Make (String) in + let spans = Span_tbl.to_list spans in + if spans <> [] then ( + !on_tracing_error + @@ Printf.sprintf "trace-tef: warning: %d spans were not closed\n" + (List.length spans); + let names = + List.fold_left + (fun set (_, span) -> Str_set.add span.name set) + Str_set.empty spans + in + Str_set.iter + (fun name -> + !on_tracing_error @@ Printf.sprintf " span %S was not closed\n" name) + names; + flush stderr + ) +end + +let close (self : t) : unit = + if A.exchange self.active false then ( + print_non_closed_spans_warning self.spans; + self.exporter.close () + ) + +let[@inline] active self = A.get self.active +let[@inline] flush (self : t) : unit = self.exporter.flush () + +let create ?(buf_pool = Buf_pool.create ()) ~pid ~exporter () : t = + { active = A.make true; exporter; buf_pool; pid; spans = Span_tbl.create () } + +module Callbacks = struct + type st = t + + let on_init _ ~time_ns:_ = () + let on_shutdown (self : st) ~time_ns:_ = close self + + let on_name_process (self : st) ~time_ns:_ ~tid:_ ~name : unit = + let@ buf = Rpool.with_ self.buf_pool in + Writer.emit_name_process ~pid:self.pid ~name buf; + self.exporter.on_json buf + + let on_name_thread (self : st) ~time_ns:_ ~tid ~name : unit = + let@ buf = Rpool.with_ self.buf_pool in + Writer.emit_name_thread buf ~pid:self.pid ~tid ~name; + self.exporter.on_json buf + + (* add function name, if provided, to the metadata *) + let add_fun_name_ fun_name data : _ list = + match fun_name with + | None -> data + | Some f -> ("function", Sub.U_string f) :: data + + let[@inline] on_enter_span (self : st) ~__FUNCTION__:fun_name ~__FILE__:_ + ~__LINE__:_ ~time_ns ~tid ~data ~name span : unit = + let time_us = time_us_of_time_ns @@ time_ns in + let data = add_fun_name_ fun_name data in + let info = { tid; name; start_us = time_us; data } in + (* save the span so we find it at exit *) + Span_tbl.add self.spans span info + + let on_exit_span (self : st) ~time_ns ~tid:_ span : unit = + let time_us = time_us_of_time_ns @@ time_ns in + + match Span_tbl.find_exn self.spans span with + | exception Not_found -> + !on_tracing_error (Printf.sprintf "cannot find span %Ld" span) + | { tid; name; start_us; data } -> + Span_tbl.remove self.spans span; + let@ buf = Rpool.with_ self.buf_pool in + Writer.emit_duration_event buf ~pid:self.pid ~tid ~name ~start:start_us + ~end_:time_us ~args:data; + + self.exporter.on_json buf + + let on_add_data (self : st) ~data span = + if data <> [] then ( + try + let info = Span_tbl.find_exn self.spans span in + info.data <- List.rev_append data info.data + with Not_found -> + !on_tracing_error (Printf.sprintf "cannot find span %Ld" span) + ) + + let on_message (self : st) ~time_ns ~tid ~span:_ ~data msg : unit = + let time_us = time_us_of_time_ns @@ time_ns in + let@ buf = Rpool.with_ self.buf_pool in + Writer.emit_instant_event buf ~pid:self.pid ~tid ~name:msg ~ts:time_us + ~args:data; + self.exporter.on_json buf + + let on_counter (self : st) ~time_ns ~tid ~data:_ ~name n : unit = + let time_us = time_us_of_time_ns @@ time_ns in + let@ buf = Rpool.with_ self.buf_pool in + Writer.emit_counter buf ~pid:self.pid ~name ~tid ~ts:time_us n; + self.exporter.on_json buf + + let on_enter_manual_span (self : st) ~__FUNCTION__:fun_name ~__FILE__:_ + ~__LINE__:_ ~time_ns ~tid ~parent:_ ~data ~name ~flavor ~trace_id _span : + unit = + let time_us = time_us_of_time_ns @@ time_ns in + + let data = add_fun_name_ fun_name data in + let@ buf = Rpool.with_ self.buf_pool in + Writer.emit_manual_begin buf ~pid:self.pid ~tid ~name + ~id:(int64_of_trace_id_ trace_id) + ~ts:time_us ~args:data ~flavor; + self.exporter.on_json buf + + let on_exit_manual_span (self : st) ~time_ns ~tid ~name ~data ~flavor + ~trace_id (_ : span) : unit = + let time_us = time_us_of_time_ns @@ time_ns in + + let@ buf = Rpool.with_ self.buf_pool in + Writer.emit_manual_end buf ~pid:self.pid ~tid ~name + ~id:(int64_of_trace_id_ trace_id) + ~ts:time_us ~flavor ~args:data; + self.exporter.on_json buf + + let on_extension_event _ ~time_ns:_ ~tid:_ _ev = () +end + +let subscriber (self : t) : Sub.t = + Sub.Subscriber.Sub { st = self; callbacks = (module Callbacks) } diff --git a/src/tef/subscriber.mli b/src/tef/subscriber.mli new file mode 100644 index 0000000..9f2f235 --- /dev/null +++ b/src/tef/subscriber.mli @@ -0,0 +1,28 @@ +open Common_ + +module Buf_pool : sig + type t + + val create : ?max_size:int -> ?buf_size:int -> unit -> t +end + +type t +(** Main subscriber state. *) + +val create : ?buf_pool:Buf_pool.t -> pid:int -> exporter:Exporter.t -> unit -> t +(** Create a subscriber state. *) + +val flush : t -> unit +val close : t -> unit +val active : t -> bool + +module Callbacks : Sub.Callbacks.S with type st = t + +val subscriber : t -> Sub.t +(** Subscriber that writes json into this writer *) + +(**/**) + +val on_tracing_error : (string -> unit) ref + +(**/**) diff --git a/src/tef/trace_tef.ml b/src/tef/trace_tef.ml index d9dd4f8..e29488f 100644 --- a/src/tef/trace_tef.ml +++ b/src/tef/trace_tef.ml @@ -1,216 +1,7 @@ open Trace_core -open Trace_private_util -open Event -module Sub = Trace_subscriber -module A = Trace_core.Internal_.Atomic_ - -let on_tracing_error = ref (fun s -> Printf.eprintf "trace-tef error: %s\n%!" s) - -let[@inline] time_us_of_time_ns (t : int64) : float = - Int64.div t 1_000L |> Int64.to_float - -let[@inline] int64_of_trace_id_ (id : Trace_core.trace_id) : int64 = - if id == Trace_core.Collector.dummy_trace_id then - 0L - else - Bytes.get_int64_le (Bytes.unsafe_of_string id) 0 - -module Mock_ = struct - let now = ref 0 - - (* used to mock timing *) - let get_now_ns () : int64 = - let x = !now in - incr now; - Int64.(mul (of_int x) 1000L) - - let get_tid_ () : int = 3 -end - -module Span_tbl = Hashtbl.Make (struct - include Int64 - - let hash : t -> int = Hashtbl.hash -end) - -type span_info = { - tid: int; - name: string; - start_us: float; - mutable data: (string * Sub.user_data) list; -} - -(** Writer: knows how to write entries to a file in TEF format *) -module Writer = struct - type t = { - oc: out_channel; - jsonl: bool; (** JSONL mode, one json event per line *) - mutable first: bool; (** first event? useful in json mode *) - buf: Buffer.t; (** Buffer to write into *) - must_close: bool; (** Do we have to close the underlying channel [oc]? *) - pid: int; - } - (** A writer to a [out_channel]. It writes JSON entries in an array and closes - the array at the end. *) - - let create ~(mode : [ `Single | `Jsonl ]) ~out () : t = - let jsonl = mode = `Jsonl in - let oc, must_close = - match out with - | `Stdout -> stdout, false - | `Stderr -> stderr, false - | `File path -> open_out path, true - | `File_append path -> - open_out_gen [ Open_creat; Open_wronly; Open_append ] 0o644 path, true - | `Output oc -> oc, false - in - let pid = - if !Sub.Private_.mock then - 2 - else - Unix.getpid () - in - if not jsonl then output_char oc '['; - { oc; jsonl; first = true; pid; must_close; buf = Buffer.create 2_048 } - - let close (self : t) : unit = - if self.jsonl then - output_char self.oc '\n' - else - output_char self.oc ']'; - flush self.oc; - if self.must_close then close_out self.oc - - let with_ ~mode ~out f = - let writer = create ~mode ~out () in - Fun.protect ~finally:(fun () -> close writer) (fun () -> f writer) - - let[@inline] flush (self : t) : unit = flush self.oc - - (** Emit "," if we need, and get the buffer ready *) - let emit_sep_and_start_ (self : t) = - Buffer.reset self.buf; - if self.jsonl then - Buffer.add_char self.buf '\n' - else if self.first then - self.first <- false - else - Buffer.add_string self.buf ",\n" - - let char = Buffer.add_char - let raw_string = Buffer.add_string - - let str_val (buf : Buffer.t) (s : string) = - char buf '"'; - let encode_char c = - match c with - | '"' -> raw_string buf {|\"|} - | '\\' -> raw_string buf {|\\|} - | '\n' -> raw_string buf {|\n|} - | '\b' -> raw_string buf {|\b|} - | '\r' -> raw_string buf {|\r|} - | '\t' -> raw_string buf {|\t|} - | _ when Char.code c <= 0x1f -> - raw_string buf {|\u00|}; - Printf.bprintf buf "%02x" (Char.code c) - | c -> char buf c - in - String.iter encode_char s; - char buf '"' - - let pp_user_data_ (out : Buffer.t) : Sub.user_data -> unit = function - | U_none -> raw_string out "null" - | U_int i -> Printf.bprintf out "%d" i - | U_bool b -> Printf.bprintf out "%b" b - | U_string s -> str_val out s - | U_float f -> Printf.bprintf out "%g" f - - (* emit args, if not empty. [ppv] is used to print values. *) - let emit_args_o_ ppv (out : Buffer.t) args : unit = - if args <> [] then ( - Printf.bprintf out {json|,"args": {|json}; - List.iteri - (fun i (n, value) -> - if i > 0 then raw_string out ","; - Printf.bprintf out {json|"%s":%a|json} n ppv value) - args; - char out '}' - ) - - let emit_duration_event ~tid ~name ~start ~end_ ~args (self : t) : unit = - let dur = end_ -. start in - let ts = start in - - emit_sep_and_start_ self; - - Printf.bprintf self.buf - {json|{"pid":%d,"cat":"","tid": %d,"dur": %.2f,"ts": %.2f,"name":%a,"ph":"X"%a}|json} - self.pid tid dur ts str_val name - (emit_args_o_ pp_user_data_) - args; - Buffer.output_buffer self.oc self.buf - - let emit_manual_begin ~tid ~name ~(id : trace_id) ~ts ~args - ~(flavor : Sub.flavor option) (self : t) : unit = - emit_sep_and_start_ self; - Printf.bprintf self.buf - {json|{"pid":%d,"cat":"trace","id":%Ld,"tid": %d,"ts": %.2f,"name":%a,"ph":"%c"%a}|json} - self.pid (int64_of_trace_id_ id) tid ts str_val name - (match flavor with - | None | Some Async -> 'b' - | Some Sync -> 'B') - (emit_args_o_ pp_user_data_) - args; - Buffer.output_buffer self.oc self.buf - - let emit_manual_end ~tid ~name ~(id : trace_id) ~ts - ~(flavor : Sub.flavor option) ~args (self : t) : unit = - emit_sep_and_start_ self; - Printf.bprintf self.buf - {json|{"pid":%d,"cat":"trace","id":%Ld,"tid": %d,"ts": %.2f,"name":%a,"ph":"%c"%a}|json} - self.pid (int64_of_trace_id_ id) tid ts str_val name - (match flavor with - | None | Some Async -> 'e' - | Some Sync -> 'E') - (emit_args_o_ pp_user_data_) - args; - Buffer.output_buffer self.oc self.buf - - let emit_instant_event ~tid ~name ~ts ~args (self : t) : unit = - emit_sep_and_start_ self; - Printf.bprintf self.buf - {json|{"pid":%d,"cat":"","tid": %d,"ts": %.2f,"name":%a,"ph":"I"%a}|json} - self.pid tid ts str_val name - (emit_args_o_ pp_user_data_) - args; - Buffer.output_buffer self.oc self.buf - - let emit_name_thread ~tid ~name (self : t) : unit = - emit_sep_and_start_ self; - Printf.bprintf self.buf - {json|{"pid":%d,"tid": %d,"name":"thread_name","ph":"M"%a}|json} self.pid - tid - (emit_args_o_ pp_user_data_) - [ "name", U_string name ]; - Buffer.output_buffer self.oc self.buf - - let emit_name_process ~name (self : t) : unit = - emit_sep_and_start_ self; - Printf.bprintf self.buf - {json|{"pid":%d,"name":"process_name","ph":"M"%a}|json} self.pid - (emit_args_o_ pp_user_data_) - [ "name", U_string name ]; - Buffer.output_buffer self.oc self.buf - - let emit_counter ~name ~tid ~ts (self : t) f : unit = - emit_sep_and_start_ self; - Printf.bprintf self.buf - {json|{"pid":%d,"tid":%d,"ts":%.2f,"name":"c","ph":"C"%a}|json} self.pid - tid ts - (emit_args_o_ pp_user_data_) - [ name, U_float f ]; - Buffer.output_buffer self.oc self.buf -end +module Subscriber = Subscriber +module Exporter = Exporter +module Writer = Writer let block_signals () = try @@ -228,97 +19,14 @@ let block_signals () = : _ list) with _ -> () -let print_non_closed_spans_warning spans = - let module Str_set = Set.Make (String) in - Printf.eprintf "trace-tef: warning: %d spans were not closed\n" - (Span_tbl.length spans); - let names = ref Str_set.empty in - Span_tbl.iter (fun _ span -> names := Str_set.add span.name !names) spans; - Str_set.iter - (fun name -> Printf.eprintf " span %S was not closed\n" name) - !names; - flush stderr - -(** Background thread, takes events from the queue, puts them in context using - local state, and writes fully resolved TEF events to [out]. *) -let bg_thread ~mode ~out (events : Event.t B_queue.t) : unit = - block_signals (); - - (* open a writer to [out] *) - Writer.with_ ~mode ~out @@ fun writer -> - (* local state, to keep track of span information and implicit stack context *) - let spans : span_info Span_tbl.t = Span_tbl.create 32 in - - (* add function name, if provided, to the metadata *) - let add_fun_name_ fun_name data : _ list = - match fun_name with - | None -> data - | Some f -> ("function", Sub.U_string f) :: data - in - - (* how to deal with an event *) - let handle_ev (ev : Event.t) : unit = - match ev with - | E_tick -> Writer.flush writer - | E_message { tid; msg; time_us; data } -> - Writer.emit_instant_event ~tid ~name:msg ~ts:time_us ~args:data writer - | E_define_span { tid; name; id; time_us; fun_name; data } -> - let data = add_fun_name_ fun_name data in - let info = { tid; name; start_us = time_us; data } in - (* save the span so we find it at exit *) - Span_tbl.add spans id info - | E_exit_span { id; time_us = stop_us } -> - (match Span_tbl.find_opt spans id with - | None -> !on_tracing_error (Printf.sprintf "cannot find span %Ld" id) - | Some { tid; name; start_us; data } -> - Span_tbl.remove spans id; - Writer.emit_duration_event ~tid ~name ~start:start_us ~end_:stop_us - ~args:data writer) - | E_add_data { id; data } -> - (match Span_tbl.find_opt spans id with - | None -> !on_tracing_error (Printf.sprintf "cannot find span %Ld" id) - | Some info -> info.data <- List.rev_append data info.data) - | E_enter_manual_span { tid; time_us; name; id; data; fun_name; flavor } -> - let data = add_fun_name_ fun_name data in - Writer.emit_manual_begin ~tid ~name ~id ~ts:time_us ~args:data ~flavor - writer - | E_exit_manual_span { tid; time_us; name; id; flavor; data } -> - Writer.emit_manual_end ~tid ~name ~id ~ts:time_us ~flavor ~args:data - writer - | E_counter { tid; name; time_us; n } -> - Writer.emit_counter ~name ~tid ~ts:time_us writer n - | E_name_process { name } -> Writer.emit_name_process ~name writer - | E_name_thread { tid; name } -> Writer.emit_name_thread ~tid ~name writer - in - - try - while true do - (* get all the events in the incoming blocking queue, in - one single critical section. *) - let local = B_queue.pop_all events in - List.iter handle_ev local - done - with B_queue.Closed -> - (* write a message about us closing *) - Writer.emit_instant_event ~name:"tef-worker.exit" - ~tid:(Thread.id @@ Thread.self ()) - ~ts:(time_us_of_time_ns @@ Sub.Private_.now_ns ()) - ~args:[] writer; - - (* warn if app didn't close all spans *) - if Span_tbl.length spans > 0 then print_non_closed_spans_warning spans; - () - (** Thread that simply regularly "ticks", sending events to the background thread so it has a chance to write to the file *) -let tick_thread events : unit = +let tick_thread (sub : Subscriber.t) : unit = block_signals (); - try - while true do - Thread.delay 0.5; - B_queue.push events E_tick - done - with B_queue.Closed -> () + while Subscriber.active sub do + Thread.delay 0.5; + Subscriber.flush sub + done type output = [ `Stdout @@ -326,91 +34,45 @@ type output = | `File of string ] -module Internal_st = struct - type t = { - active: bool A.t; - events: Event.t B_queue.t; - t_write: Thread.t; - } -end - -let subscriber_ ~finally ~out ~(mode : [ `Single | `Jsonl ]) () : Sub.t = - let module M : Sub.Callbacks.S with type st = Internal_st.t = struct - type st = Internal_st.t - - let on_init _ ~time_ns:_ = () - - let on_shutdown (self : st) ~time_ns:_ = - if A.exchange self.active false then ( - B_queue.close self.events; - (* wait for writer thread to be done. The writer thread will exit - after processing remaining events because the queue is now closed *) - Thread.join self.t_write - ) - - let on_name_process (self : st) ~time_ns:_ ~tid:_ ~name : unit = - B_queue.push self.events @@ E_name_process { name } - - let on_name_thread (self : st) ~time_ns:_ ~tid ~name : unit = - B_queue.push self.events @@ E_name_thread { tid; name } - - let[@inline] on_enter_span (self : st) ~__FUNCTION__:fun_name ~__FILE__:_ - ~__LINE__:_ ~time_ns ~tid ~data ~name span : unit = - let time_us = time_us_of_time_ns @@ time_ns in - B_queue.push self.events - @@ E_define_span { tid; name; time_us; id = span; fun_name; data } - - let on_exit_span (self : st) ~time_ns ~tid:_ span : unit = - let time_us = time_us_of_time_ns @@ time_ns in - B_queue.push self.events @@ E_exit_span { id = span; time_us } - - let on_add_data (self : st) ~data span = - if data <> [] then - B_queue.push self.events @@ E_add_data { id = span; data } - - let on_message (self : st) ~time_ns ~tid ~span:_ ~data msg : unit = - let time_us = time_us_of_time_ns @@ time_ns in - B_queue.push self.events @@ E_message { tid; time_us; msg; data } - - let on_counter (self : st) ~time_ns ~tid ~data:_ ~name f : unit = - let time_us = time_us_of_time_ns @@ time_ns in - B_queue.push self.events @@ E_counter { name; n = f; time_us; tid } - - let on_enter_manual_span (self : st) ~__FUNCTION__:fun_name ~__FILE__:_ - ~__LINE__:_ ~time_ns ~tid ~parent:_ ~data ~name ~flavor ~trace_id _span - : unit = - let time_us = time_us_of_time_ns @@ time_ns in - B_queue.push self.events - @@ E_enter_manual_span - { id = trace_id; time_us; tid; data; name; fun_name; flavor } - - let on_exit_manual_span (self : st) ~time_ns ~tid ~name ~data ~flavor - ~trace_id (_ : span) : unit = - let time_us = time_us_of_time_ns @@ time_ns in - B_queue.push self.events - @@ E_exit_manual_span { tid; id = trace_id; name; time_us; data; flavor } - - let on_extension_event _ ~time_ns:_ ~tid:_ _ev = () - end in - let events = B_queue.create () in - let t_write = - Thread.create - (fun () -> Fun.protect ~finally @@ fun () -> bg_thread ~mode ~out events) - () +let subscriber_ ~finally ~out ~(mode : [ `Single | `Jsonl ]) () : + Trace_subscriber.t = + let jsonl = mode = `Jsonl in + let oc, must_close = + match out with + | `Stdout -> stdout, false + | `Stderr -> stderr, false + | `File path -> open_out path, true + | `File_append path -> + open_out_gen [ Open_creat; Open_wronly; Open_append ] 0o644 path, true + | `Output oc -> oc, false + in + let pid = + if !Trace_subscriber.Private_.mock then + 2 + else + Unix.getpid () in - (* ticker thread, regularly sends a message to the writer thread. - no need to join it. *) - let _t_tick : Thread.t = Thread.create (fun () -> tick_thread events) () in - let st : Internal_st.t = { active = A.make true; events; t_write } in - Sub.Subscriber.Sub { st; callbacks = (module M) } + let exporter = Exporter.of_out_channel oc ~jsonl ~close_channel:must_close in + let exporter = + { + exporter with + close = + (fun () -> + exporter.close (); + finally ()); + } + in + let sub = Subscriber.create ~pid ~exporter () in + let _t_tick : Thread.t = Thread.create tick_thread sub in + Subscriber.subscriber sub let collector_ ~(finally : unit -> unit) ~(mode : [ `Single | `Jsonl ]) ~out () : collector = let sub = subscriber_ ~finally ~mode ~out () in - Sub.collector sub + Trace_subscriber.collector sub -let[@inline] subscriber ~out () : Sub.t = +let[@inline] subscriber ~out () : Trace_subscriber.t = subscriber_ ~finally:ignore ~mode:`Single ~out () let[@inline] collector ~out () : collector = @@ -438,14 +100,26 @@ let with_setup ?out () f = setup ?out (); Fun.protect ~finally:Trace_core.shutdown f +module Mock_ = struct + let now = ref 0 + + (* used to mock timing *) + let get_now_ns () : int64 = + let x = !now in + incr now; + Int64.(mul (of_int x) 1000L) + + let get_tid_ () : int = 3 +end + module Private_ = struct let mock_all_ () = - Sub.Private_.mock := true; - Sub.Private_.get_now_ns_ := Mock_.get_now_ns; - Sub.Private_.get_tid_ := Mock_.get_tid_; + Trace_subscriber.Private_.mock := true; + Trace_subscriber.Private_.get_now_ns_ := Mock_.get_now_ns; + Trace_subscriber.Private_.get_tid_ := Mock_.get_tid_; () - let on_tracing_error = on_tracing_error + let on_tracing_error = Subscriber.on_tracing_error let subscriber_jsonl ~finally ~out () = subscriber_ ~finally ~mode:`Jsonl ~out () diff --git a/src/tef/trace_tef.mli b/src/tef/trace_tef.mli index d5741fd..9cf8dd1 100644 --- a/src/tef/trace_tef.mli +++ b/src/tef/trace_tef.mli @@ -1,3 +1,7 @@ +module Subscriber = Subscriber +module Exporter = Exporter +module Writer = Writer + type output = [ `Stdout | `Stderr diff --git a/src/tef/writer.ml b/src/tef/writer.ml new file mode 100644 index 0000000..9865988 --- /dev/null +++ b/src/tef/writer.ml @@ -0,0 +1,97 @@ +open Common_ + +let char = Buffer.add_char +let raw_string = Buffer.add_string + +let str_val (buf : Buffer.t) (s : string) = + char buf '"'; + let encode_char c = + match c with + | '"' -> raw_string buf {|\"|} + | '\\' -> raw_string buf {|\\|} + | '\n' -> raw_string buf {|\n|} + | '\b' -> raw_string buf {|\b|} + | '\r' -> raw_string buf {|\r|} + | '\t' -> raw_string buf {|\t|} + | _ when Char.code c <= 0x1f -> + raw_string buf {|\u00|}; + Printf.bprintf buf "%02x" (Char.code c) + | c -> char buf c + in + String.iter encode_char s; + char buf '"' + +let pp_user_data_ (out : Buffer.t) : Sub.user_data -> unit = function + | U_none -> raw_string out "null" + | U_int i -> Printf.bprintf out "%d" i + | U_bool b -> Printf.bprintf out "%b" b + | U_string s -> str_val out s + | U_float f -> Printf.bprintf out "%g" f + +(* emit args, if not empty. [ppv] is used to print values. *) +let emit_args_o_ ppv (out : Buffer.t) args : unit = + if args <> [] then ( + Printf.bprintf out {json|,"args": {|json}; + List.iteri + (fun i (n, value) -> + if i > 0 then raw_string out ","; + Printf.bprintf out {json|"%s":%a|json} n ppv value) + args; + char out '}' + ) + +let emit_duration_event ~pid ~tid ~name ~start ~end_ ~args buf : unit = + let dur = end_ -. start in + let ts = start in + + Printf.bprintf buf + {json|{"pid":%d,"cat":"","tid": %d,"dur": %.2f,"ts": %.2f,"name":%a,"ph":"X"%a}|json} + pid tid dur ts str_val name + (emit_args_o_ pp_user_data_) + args + +let emit_manual_begin ~pid ~tid ~name ~(id : int64) ~ts ~args + ~(flavor : Sub.flavor option) buf : unit = + Printf.bprintf buf + {json|{"pid":%d,"cat":"trace","id":%Ld,"tid": %d,"ts": %.2f,"name":%a,"ph":"%c"%a}|json} + pid id tid ts str_val name + (match flavor with + | None | Some Async -> 'b' + | Some Sync -> 'B') + (emit_args_o_ pp_user_data_) + args + +let emit_manual_end ~pid ~tid ~name ~(id : int64) ~ts + ~(flavor : Sub.flavor option) ~args buf : unit = + Printf.bprintf buf + {json|{"pid":%d,"cat":"trace","id":%Ld,"tid": %d,"ts": %.2f,"name":%a,"ph":"%c"%a}|json} + pid id tid ts str_val name + (match flavor with + | None | Some Async -> 'e' + | Some Sync -> 'E') + (emit_args_o_ pp_user_data_) + args + +let emit_instant_event ~pid ~tid ~name ~ts ~args buf : unit = + Printf.bprintf buf + {json|{"pid":%d,"cat":"","tid": %d,"ts": %.2f,"name":%a,"ph":"I"%a}|json} + pid tid ts str_val name + (emit_args_o_ pp_user_data_) + args + +let emit_name_thread ~pid ~tid ~name buf : unit = + Printf.bprintf buf + {json|{"pid":%d,"tid": %d,"name":"thread_name","ph":"M"%a}|json} pid tid + (emit_args_o_ pp_user_data_) + [ "name", U_string name ] + +let emit_name_process ~pid ~name buf : unit = + Printf.bprintf buf {json|{"pid":%d,"name":"process_name","ph":"M"%a}|json} pid + (emit_args_o_ pp_user_data_) + [ "name", U_string name ] + +let emit_counter ~pid ~tid ~name ~ts buf f : unit = + Printf.bprintf buf + {json|{"pid":%d,"tid":%d,"ts":%.2f,"name":"c","ph":"C"%a}|json} pid tid ts + (emit_args_o_ pp_user_data_) + [ name, U_float f ] diff --git a/src/tef/writer.mli b/src/tef/writer.mli new file mode 100644 index 0000000..d1563a7 --- /dev/null +++ b/src/tef/writer.mli @@ -0,0 +1,54 @@ +(** Write JSON events to a buffer. + + This is the part of the code that knows how to emit TEF-compliant JSON from + raw event data. *) + +open Common_ +open Trace_core + +val emit_duration_event : + pid:int -> + tid:int -> + name:string -> + start:float -> + end_:float -> + args:(string * Sub.user_data) list -> + Buffer.t -> + unit + +val emit_manual_begin : + pid:int -> + tid:int -> + name:string -> + id:span -> + ts:float -> + args:(string * Sub.user_data) list -> + flavor:Sub.flavor option -> + Buffer.t -> + unit + +val emit_manual_end : + pid:int -> + tid:int -> + name:string -> + id:span -> + ts:float -> + flavor:Sub.flavor option -> + args:(string * Sub.user_data) list -> + Buffer.t -> + unit + +val emit_instant_event : + pid:int -> + tid:int -> + name:string -> + ts:float -> + args:(string * Sub.user_data) list -> + Buffer.t -> + unit + +val emit_name_thread : pid:int -> tid:int -> name:string -> Buffer.t -> unit +val emit_name_process : pid:int -> name:string -> Buffer.t -> unit + +val emit_counter : + pid:int -> tid:int -> name:string -> ts:float -> Buffer.t -> float -> unit From a4779227fa825383075780dff2a7625140cd6ef7 Mon Sep 17 00:00:00 2001 From: Simon Cruanes Date: Wed, 7 May 2025 13:15:37 -0400 Subject: [PATCH 08/15] add .mli for rpool --- src/util/rpool.mli | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/util/rpool.mli diff --git a/src/util/rpool.mli b/src/util/rpool.mli new file mode 100644 index 0000000..5998658 --- /dev/null +++ b/src/util/rpool.mli @@ -0,0 +1,10 @@ +(** A resource pool (for buffers) *) + +type 'a t + +val create : + max_size:int -> create:(unit -> 'a) -> clear:('a -> unit) -> unit -> 'a t + +val alloc : 'a t -> 'a +val recycle : 'a t -> 'a -> unit +val with_ : 'a t -> ('a -> 'b) -> 'b From 190f70d7c95cbb803e64450dd9e40c42576acd78 Mon Sep 17 00:00:00 2001 From: Simon Cruanes Date: Wed, 7 May 2025 15:33:34 -0400 Subject: [PATCH 09/15] feat fuchsia: full revamp of the library, modularized - separate exporter, writer, subscriber - use the subscriber span tbl to keep track of context - use a `Buf_chain.t` to keep multiple buffers in use, and keep a set of ready buffers - batch write the ready buffers and then recycle them --- src/fuchsia/bg_thread.ml | 62 --- src/fuchsia/{write => }/buf.ml | 2 + src/fuchsia/buf_chain.ml | 140 ++++++ src/fuchsia/buf_pool.ml | 23 + src/fuchsia/common_.ml | 20 +- src/fuchsia/dune | 2 +- src/fuchsia/exporter.ml | 61 +++ src/fuchsia/fcollector.ml | 430 ------------------ src/fuchsia/fcollector.mli | 11 - src/fuchsia/lock.ml | 28 ++ src/fuchsia/lock.mli | 10 + src/fuchsia/subscriber.ml | 173 +++++++ src/fuchsia/subscriber.mli | 20 + src/fuchsia/trace_fuchsia.ml | 66 ++- src/fuchsia/trace_fuchsia.mli | 29 +- src/fuchsia/{write => }/util.ml | 0 src/fuchsia/write/buf_pool.ml | 58 --- src/fuchsia/write/dune | 9 - src/fuchsia/write/output.ml | 65 --- .../trace_fuchsia_write.ml => writer.ml} | 133 +++--- 20 files changed, 613 insertions(+), 729 deletions(-) delete mode 100644 src/fuchsia/bg_thread.ml rename src/fuchsia/{write => }/buf.ml (93%) create mode 100644 src/fuchsia/buf_chain.ml create mode 100644 src/fuchsia/buf_pool.ml create mode 100644 src/fuchsia/exporter.ml delete mode 100644 src/fuchsia/fcollector.ml delete mode 100644 src/fuchsia/fcollector.mli create mode 100644 src/fuchsia/lock.ml create mode 100644 src/fuchsia/lock.mli create mode 100644 src/fuchsia/subscriber.ml create mode 100644 src/fuchsia/subscriber.mli rename src/fuchsia/{write => }/util.ml (100%) delete mode 100644 src/fuchsia/write/buf_pool.ml delete mode 100644 src/fuchsia/write/dune delete mode 100644 src/fuchsia/write/output.ml rename src/fuchsia/{write/trace_fuchsia_write.ml => writer.ml} (81%) diff --git a/src/fuchsia/bg_thread.ml b/src/fuchsia/bg_thread.ml deleted file mode 100644 index 631a1db..0000000 --- a/src/fuchsia/bg_thread.ml +++ /dev/null @@ -1,62 +0,0 @@ -open Common_ - -type out = - [ `Stdout - | `Stderr - | `File of string - ] - -type event = - | E_write_buf of Buf.t - | E_tick - -type state = { - buf_pool: Buf_pool.t; - oc: out_channel; - events: event B_queue.t; -} - -let with_out_ (out : out) f = - let oc, must_close = - match out with - | `Stdout -> stdout, false - | `Stderr -> stderr, false - | `File path -> open_out path, true - in - - if must_close then ( - let finally () = close_out_noerr oc in - Fun.protect ~finally (fun () -> f oc) - ) else - f oc - -let handle_ev (self : state) (ev : event) : unit = - match ev with - | E_tick -> flush self.oc - | E_write_buf buf -> - output self.oc buf.buf 0 buf.offset; - Buf_pool.recycle self.buf_pool buf - -let bg_loop (self : state) : unit = - let continue = ref true in - - while !continue do - match B_queue.pop_all self.events with - | exception B_queue.Closed -> continue := false - | evs -> List.iter (handle_ev self) evs - done - -let bg_thread ~buf_pool ~out ~(events : event B_queue.t) () : unit = - let@ oc = with_out_ out in - let st = { oc; buf_pool; events } in - bg_loop st - -(** Thread that simply regularly "ticks", sending events to the background - thread so it has a chance to write to the file, and call [f()] *) -let tick_thread events : unit = - try - while true do - Thread.delay 0.5; - B_queue.push events E_tick - done - with B_queue.Closed -> () diff --git a/src/fuchsia/write/buf.ml b/src/fuchsia/buf.ml similarity index 93% rename from src/fuchsia/write/buf.ml rename to src/fuchsia/buf.ml index b2aae8a..a1d46c5 100644 --- a/src/fuchsia/write/buf.ml +++ b/src/fuchsia/buf.ml @@ -8,12 +8,14 @@ type t = { let empty : t = { buf = Bytes.empty; offset = 0 } let create (n : int) : t = + (* multiple of 8-bytes size *) let buf = Bytes.create (round_to_word n) in { buf; offset = 0 } let[@inline] clear self = self.offset <- 0 let[@inline] available self = Bytes.length self.buf - self.offset let[@inline] size self = self.offset +let[@inline] is_empty self = self.offset = 0 (* see below: we assume little endian *) let () = assert (not Sys.big_endian) diff --git a/src/fuchsia/buf_chain.ml b/src/fuchsia/buf_chain.ml new file mode 100644 index 0000000..77616e5 --- /dev/null +++ b/src/fuchsia/buf_chain.ml @@ -0,0 +1,140 @@ +(** A set of buffers in use, and a set of ready buffers *) + +open Common_ + +(** Buffers in use *) +type buffers = + | B_one of { mutable buf: Buf.t } + | B_many of Buf.t Lock.t array + (** mask(thread id) -> buffer. This reduces contention *) + +type t = { + bufs: buffers; + has_ready: bool A.t; + ready: Buf.t Queue.t Lock.t; + (** Buffers that are full (enough) and must be written *) + buf_pool: Buf_pool.t; +} +(** A set of buffers, some of which are ready to be written *) + +open struct + let shard_log = 4 + let shard = 1 lsl shard_log + let shard_mask = shard - 1 +end + +let create ~(sharded : bool) ~(buf_pool : Buf_pool.t) () : t = + let bufs = + if sharded then ( + let bufs = + Array.init shard (fun _ -> Lock.create @@ Buf_pool.alloc buf_pool) + in + B_many bufs + ) else + B_one { buf = Buf_pool.alloc buf_pool } + in + { + bufs; + buf_pool; + has_ready = A.make false; + ready = Lock.create @@ Queue.create (); + } + +open struct + let put_in_ready (self : t) buf : unit = + if Buf.size buf > 0 then ( + let@ q = Lock.with_ self.ready in + Atomic.set self.has_ready true; + Queue.push buf q + ) + + let assert_available buf ~available = + if Buf.available buf < available then ( + let msg = + Printf.sprintf + "fuchsia: buffer is too small (available: %d bytes, needed: %d bytes)" + (Buf.available buf) available + in + failwith msg + ) +end + +(** Move all non-empty buffers to [ready] *) +let ready_all_non_empty (self : t) : unit = + let@ q = Lock.with_ self.ready in + match self.bufs with + | B_one r -> + if not (Buf.is_empty r.buf) then ( + Queue.push r.buf q; + A.set self.has_ready true; + r.buf <- Buf.empty + ) + | B_many bufs -> + Array.iter + (fun buf -> + Lock.update buf (fun buf -> + if Buf.size buf > 0 then ( + Queue.push buf q; + A.set self.has_ready true; + Buf.empty + ) else + buf)) + bufs + +let[@inline] has_ready self : bool = A.get self.has_ready + +(** Get access to ready buffers, then clean them up automatically *) +let pop_ready (self : t) ~(f : Buf.t Queue.t -> 'a) : 'a = + let@ q = Lock.with_ self.ready in + let res = f q in + + (* clear queue *) + Queue.iter (Buf_pool.recycle self.buf_pool) q; + Queue.clear q; + A.set self.has_ready false; + res + +(** Maximum size available, in words, for a single message *) +let[@inline] max_size_word (_self : t) : int = fuchsia_buf_size lsr 3 + +(** Obtain a buffer with at least [available_word] 64-bit words *) +let with_buf (self : t) ~(available_word : int) (f : Buf.t -> 'a) : 'a = + let available = available_word lsl 3 in + match self.bufs with + | B_one r -> + if Buf.available r.buf < available_word then ( + put_in_ready self r.buf; + r.buf <- Buf_pool.alloc self.buf_pool + ); + assert_available r.buf ~available; + f r.buf + | B_many bufs -> + let tid = Thread.(id (self ())) in + let masked_tid = tid land shard_mask in + let buf_lock = bufs.(masked_tid) in + let@ buf = Lock.with_ buf_lock in + let buf = + if Buf.available buf < available then ( + put_in_ready self buf; + let new_buf = Buf_pool.alloc self.buf_pool in + assert_available new_buf ~available; + Lock.set_while_locked buf_lock new_buf; + new_buf + ) else + buf + in + f buf + +(** Dispose of resources (here, recycle buffers) *) +let dispose (self : t) : unit = + match self.bufs with + | B_one r -> + Buf_pool.recycle self.buf_pool r.buf; + r.buf <- Buf.empty + | B_many bufs -> + Array.iter + (fun buf_lock -> + let@ buf = Lock.with_ buf_lock in + Buf_pool.recycle self.buf_pool buf; + Lock.set_while_locked buf_lock Buf.empty) + bufs diff --git a/src/fuchsia/buf_pool.ml b/src/fuchsia/buf_pool.ml new file mode 100644 index 0000000..6ea615d --- /dev/null +++ b/src/fuchsia/buf_pool.ml @@ -0,0 +1,23 @@ +open Common_ +open Trace_private_util + +type t = Buf.t Rpool.t + +let create ?(max_size = 64) () : t = + Rpool.create ~max_size ~clear:Buf.clear + ~create:(fun () -> Buf.create fuchsia_buf_size) + () + +let alloc = Rpool.alloc +let[@inline] recycle self buf = if buf != Buf.empty then Rpool.recycle self buf + +let with_ (self : t) f = + let x = alloc self in + try + let res = f x in + recycle self x; + res + with e -> + let bt = Printexc.get_raw_backtrace () in + recycle self x; + Printexc.raise_with_backtrace e bt diff --git a/src/fuchsia/common_.ml b/src/fuchsia/common_.ml index 14b78bf..3a335a3 100644 --- a/src/fuchsia/common_.ml +++ b/src/fuchsia/common_.ml @@ -1,12 +1,22 @@ module A = Trace_core.Internal_.Atomic_ -module FWrite = Trace_fuchsia_write -module B_queue = Trace_private_util.B_queue -module Buf = FWrite.Buf -module Buf_pool = FWrite.Buf_pool -module Output = FWrite.Output +module Sub = Trace_subscriber let on_tracing_error = ref (fun s -> Printf.eprintf "trace-fuchsia error: %s\n%!" s) let ( let@ ) = ( @@ ) let spf = Printf.sprintf + +let with_lock lock f = + Mutex.lock lock; + try + let res = f () in + Mutex.unlock lock; + res + with e -> + let bt = Printexc.get_raw_backtrace () in + Mutex.unlock lock; + Printexc.raise_with_backtrace e bt + +(** Buffer size we use. *) +let fuchsia_buf_size = 1 lsl 16 diff --git a/src/fuchsia/dune b/src/fuchsia/dune index 8e4f0f4..4ef6048 100644 --- a/src/fuchsia/dune +++ b/src/fuchsia/dune @@ -6,8 +6,8 @@ (libraries trace.core trace.private.util + trace.subscriber thread-local-storage - (re_export trace-fuchsia.write) bigarray mtime mtime.clock.os diff --git a/src/fuchsia/exporter.ml b/src/fuchsia/exporter.ml new file mode 100644 index 0000000..dc3b4e3 --- /dev/null +++ b/src/fuchsia/exporter.ml @@ -0,0 +1,61 @@ +(** An exporter, takes buffers with fuchsia events, and writes them somewhere *) + +open Common_ + +type t = { + write_bufs: Buf.t Queue.t -> unit; + (** Takes buffers and writes them somewhere. The buffers are only valid + during this call and must not be stored. The queue must not be + modified. *) + flush: unit -> unit; (** Force write *) + close: unit -> unit; (** Close underlying resources *) +} +(** An exporter, takes buffers and writes them somewhere. This should be + thread-safe if used in a threaded environment. *) + +open struct + let with_lock lock f = + Mutex.lock lock; + try + let res = f () in + Mutex.unlock lock; + res + with e -> + let bt = Printexc.get_raw_backtrace () in + Mutex.unlock lock; + Printexc.raise_with_backtrace e bt +end + +(** Export to the channel + @param close_channel if true, closing the exporter will close the channel *) +let of_out_channel ~close_channel oc : t = + let lock = Mutex.create () in + let closed = ref false in + let flush () = + let@ () = with_lock lock in + flush oc + in + let close () = + let@ () = with_lock lock in + if not !closed then ( + closed := true; + if close_channel then close_out_noerr oc + ) + in + let write_bufs bufs = + if not (Queue.is_empty bufs) then + let@ () = with_lock lock in + Queue.iter (fun (buf : Buf.t) -> output oc buf.buf 0 buf.offset) bufs + in + { flush; close; write_bufs } + +let of_buffer (buffer : Buffer.t) : t = + let buffer = Lock.create buffer in + let write_bufs bufs = + if not (Queue.is_empty bufs) then + let@ buffer = Lock.with_ buffer in + Queue.iter + (fun (buf : Buf.t) -> Buffer.add_subbytes buffer buf.buf 0 buf.offset) + bufs + in + { flush = ignore; close = ignore; write_bufs } diff --git a/src/fuchsia/fcollector.ml b/src/fuchsia/fcollector.ml deleted file mode 100644 index 35c7925..0000000 --- a/src/fuchsia/fcollector.ml +++ /dev/null @@ -1,430 +0,0 @@ -open Trace_core -open Common_ -module TLS = Thread_local_storage -module Int_map = Map.Make (Int) - -let pid = Unix.getpid () - -module Mock_ = struct - let enabled = ref false - let now = ref 0 - - (* used to mock timing *) - let get_now_ns () : float = - let x = !now in - incr now; - float_of_int x *. 1000. - - let get_tid_ () : int = 3 -end - -(** Thread-local stack of span info *) -module Span_info_stack : sig - type t - - val create : unit -> t - - val push : - t -> - span -> - name:string -> - start_time_ns:int64 -> - data:(string * user_data) list -> - unit - - val pop : t -> int64 * string * int64 * (string * user_data) list - val find_ : t -> span -> int option - val add_data : t -> int -> (string * user_data) list -> unit -end = struct - module BA = Bigarray - module BA1 = Bigarray.Array1 - - type int64arr = (int64, BA.int64_elt, BA.c_layout) BA1.t - - type t = { - mutable len: int; - mutable span: int64arr; - mutable start_time_ns: int64arr; - mutable name: string array; - mutable data: (string * user_data) list array; - } - - let init_size_ = 1 - - let create () : t = - { - len = 0; - span = BA1.create BA.Int64 BA.C_layout init_size_; - start_time_ns = BA1.create BA.Int64 BA.C_layout init_size_; - name = Array.make init_size_ ""; - data = Array.make init_size_ []; - } - - let[@inline] cap self = Array.length self.name - - let grow_ (self : t) : unit = - let new_cap = 2 * cap self in - let new_span = BA1.create BA.Int64 BA.C_layout new_cap in - BA1.blit self.span (BA1.sub new_span 0 self.len); - let new_startime_ns = BA1.create BA.Int64 BA.C_layout new_cap in - BA1.blit self.start_time_ns (BA1.sub new_startime_ns 0 self.len); - let new_name = Array.make new_cap "" in - Array.blit self.name 0 new_name 0 self.len; - let new_data = Array.make new_cap [] in - Array.blit self.data 0 new_data 0 self.len; - self.span <- new_span; - self.start_time_ns <- new_startime_ns; - self.name <- new_name; - self.data <- new_data - - let push (self : t) (span : int64) ~name ~start_time_ns ~data = - if cap self = self.len then grow_ self; - BA1.set self.span self.len span; - BA1.set self.start_time_ns self.len start_time_ns; - Array.set self.name self.len name; - Array.set self.data self.len data; - self.len <- self.len + 1 - - let pop (self : t) = - assert (self.len > 0); - self.len <- self.len - 1; - - let span = BA1.get self.span self.len in - let name = self.name.(self.len) in - let start_time_ns = BA1.get self.start_time_ns self.len in - let data = self.data.(self.len) in - - (* avoid holding onto old values *) - Array.set self.name self.len ""; - Array.set self.data self.len []; - - span, name, start_time_ns, data - - let[@inline] add_data self i d : unit = - assert (i < self.len); - self.data.(i) <- List.rev_append d self.data.(i) - - exception Found of int - - let[@inline] find_ (self : t) span : _ option = - try - for i = self.len - 1 downto 0 do - if Int64.equal (BA1.get self.span i) span then raise_notrace (Found i) - done; - - None - with Found i -> Some i -end - -type async_span_info = { - flavor: [ `Sync | `Async ] option; - name: string; - mutable data: (string * user_data) list; -} - -let key_async_data : async_span_info Meta_map.key = Meta_map.Key.create () - -open struct - let state_id_ = A.make 0 - - (* re-raise exception with its backtrace *) - external reraise : exn -> 'a = "%reraise" -end - -type per_thread_state = { - tid: int; - state_id: int; (** ID of the current collector state *) - local_span_id_gen: int A.t; (** Used for thread-local spans *) - mutable thread_ref: FWrite.Thread_ref.t; - mutable out: Output.t option; - spans: Span_info_stack.t; (** In-flight spans *) -} - -type state = { - active: bool A.t; - events: Bg_thread.event B_queue.t; - span_id_gen: int A.t; (** Used for async spans *) - bg_thread: Thread.t; - buf_pool: Buf_pool.t; - next_thread_ref: int A.t; (** in [0x01 .. 0xff], to allocate thread refs *) - per_thread: per_thread_state Int_map.t A.t array; - (** the state keeps tabs on thread-local state, so it can flush writers at - the end. This is a tid-sharded array of maps. *) -} - -let[@inline] mk_trace_id (self : state) : trace_id = - let n = A.fetch_and_add self.span_id_gen 1 in - let b = Bytes.create 8 in - Bytes.set_int64_le b 0 (Int64.of_int n); - Bytes.unsafe_to_string b - -let key_thread_local_st : per_thread_state TLS.t = TLS.create () - -let[@inline never] mk_thread_local_st () = - let tid = Thread.id @@ Thread.self () in - let st = - { - tid; - state_id = A.get state_id_; - thread_ref = FWrite.Thread_ref.inline ~pid ~tid; - local_span_id_gen = A.make 0; - out = None; - spans = Span_info_stack.create (); - } - in - TLS.set key_thread_local_st st; - st - -let[@inline] get_thread_local_st () = - match TLS.get_opt key_thread_local_st with - | Some k -> k - | None -> mk_thread_local_st () - -let out_of_st (st : state) : Output.t = - FWrite.Output.create () ~buf_pool:st.buf_pool ~send_buf:(fun buf -> - try B_queue.push st.events (E_write_buf buf) with B_queue.Closed -> ()) - -module C - (St : sig - val st : state - end) - () = -struct - open St - - let state_id = 1 + A.fetch_and_add state_id_ 1 - - (** prepare the thread's state *) - let[@inline never] update_or_init_local_state (self : per_thread_state) : unit - = - (* get an output *) - let out = out_of_st st in - self.out <- Some out; - - (* try to allocate a thread ref for current thread *) - let th_ref = A.fetch_and_add st.next_thread_ref 1 in - if th_ref <= 0xff then ( - self.thread_ref <- FWrite.Thread_ref.ref th_ref; - FWrite.Thread_record.encode out ~as_ref:th_ref ~tid:self.tid ~pid () - ); - - (* add to [st]'s list of threads *) - let shard_of_per_thread = st.per_thread.(self.tid land 0b1111) in - while - let old = A.get shard_of_per_thread in - not - (A.compare_and_set shard_of_per_thread old - (Int_map.add self.tid self old)) - do - () - done; - - let on_exit _ = - while - let old = A.get shard_of_per_thread in - not - (A.compare_and_set shard_of_per_thread old - (Int_map.remove self.tid old)) - do - () - done; - Option.iter Output.flush self.out - in - - (* after thread exits, flush output and remove from global list *) - Gc.finalise on_exit (Thread.self ()); - () - - (** Obtain the output for the current thread *) - let[@inline] get_thread_output () : Output.t * per_thread_state = - let tls = get_thread_local_st () in - if tls.state_id != state_id || tls.out == None then - update_or_init_local_state tls; - let out = - match tls.out with - | None -> assert false - | Some o -> o - in - out, tls - - let close_per_thread (tls : per_thread_state) = - Option.iter Output.flush tls.out - - (** flush all outputs *) - let flush_all_outputs_ () = - Array.iter - (fun shard -> - let tls_l = A.get shard in - Int_map.iter (fun _tid tls -> close_per_thread tls) tls_l) - st.per_thread - - let shutdown () = - if A.exchange st.active false then ( - flush_all_outputs_ (); - - B_queue.close st.events; - (* wait for writer thread to be done. The writer thread will exit - after processing remaining events because the queue is now closed *) - Thread.join st.bg_thread - ) - - let enter_span ~__FUNCTION__:_ ~__FILE__:_ ~__LINE__:_ ~data name : span = - let tls = get_thread_local_st () in - let span = Int64.of_int (A.fetch_and_add tls.local_span_id_gen 1) in - let time_ns = Time.now_ns () in - Span_info_stack.push tls.spans span ~name ~data ~start_time_ns:time_ns; - span - - let exit_span span : unit = - let out, tls = get_thread_output () in - let end_time_ns = Time.now_ns () in - - let span', name, start_time_ns, data = Span_info_stack.pop tls.spans in - if span <> span' then - !on_tracing_error - (spf "span mismatch: top is %Ld, expected %Ld" span' span) - else - FWrite.Event.Duration_complete.encode out ~name ~t_ref:tls.thread_ref - ~time_ns:start_time_ns ~end_time_ns ~args:data () - - let with_span ~__FUNCTION__:_ ~__FILE__:_ ~__LINE__:_ ~data name f = - let out, tls = get_thread_output () in - let time_ns = Time.now_ns () in - let span = Int64.of_int (A.fetch_and_add tls.local_span_id_gen 1) in - Span_info_stack.push tls.spans span ~start_time_ns:time_ns ~data ~name; - - let[@inline] exit () : unit = - let end_time_ns = Time.now_ns () in - - let _span', _, _, data = Span_info_stack.pop tls.spans in - assert (span = _span'); - FWrite.Event.Duration_complete.encode out ~name ~time_ns ~end_time_ns - ~t_ref:tls.thread_ref ~args:data () - in - - try - let x = f span in - exit (); - x - with exn -> - exit (); - reraise exn - - let add_data_to_span span data = - let tls = get_thread_local_st () in - match Span_info_stack.find_ tls.spans span with - | None -> !on_tracing_error (spf "unknown span %Ld" span) - | Some idx -> Span_info_stack.add_data tls.spans idx data - - let enter_manual_span ~(parent : explicit_span_ctx option) ~flavor - ~__FUNCTION__:_ ~__FILE__:_ ~__LINE__:_ ~data name : explicit_span = - let out, tls = get_thread_output () in - let time_ns = Time.now_ns () in - - (* get the id, or make a new one *) - let trace_id = - match parent with - | Some m -> m.trace_id - | None -> mk_trace_id st - in - - FWrite.Event.Async_begin.encode out ~name ~args:data ~t_ref:tls.thread_ref - ~time_ns ~async_id:trace_id (); - { - span = 0L; - trace_id; - meta = Meta_map.(empty |> add key_async_data { name; flavor; data = [] }); - } - - let exit_manual_span (es : explicit_span) : unit = - let { name; data; flavor = _ } = Meta_map.find_exn key_async_data es.meta in - let out, tls = get_thread_output () in - let time_ns = Time.now_ns () in - - FWrite.Event.Async_end.encode out ~name ~t_ref:tls.thread_ref ~time_ns - ~args:data ~async_id:es.trace_id () - - let add_data_to_manual_span (es : explicit_span) data = - let m = Meta_map.find_exn key_async_data es.meta in - m.data <- List.rev_append data m.data - - let message ?span:_ ~data msg : unit = - let out, tls = get_thread_output () in - let time_ns = Time.now_ns () in - FWrite.Event.Instant.encode out ~name:msg ~time_ns ~t_ref:tls.thread_ref - ~args:data () - - let counter_float ~data name f = - let out, tls = get_thread_output () in - let time_ns = Time.now_ns () in - FWrite.Event.Counter.encode out ~name:"c" ~time_ns ~t_ref:tls.thread_ref - ~args:((name, `Float f) :: data) - () - - let counter_int ~data name i = - let out, tls = get_thread_output () in - let time_ns = Time.now_ns () in - FWrite.Event.Counter.encode out ~name:"c" ~time_ns ~t_ref:tls.thread_ref - ~args:((name, `Int i) :: data) - () - - let name_process name : unit = - let out, _tls = get_thread_output () in - FWrite.Kernel_object.(encode out ~name ~ty:ty_process ~kid:pid ~args:[] ()) - - let name_thread name : unit = - let out, tls = get_thread_output () in - FWrite.Kernel_object.( - encode out ~name ~ty:ty_thread ~kid:tls.tid - ~args:[ "process", `Kid pid ] - ()) - - let extension_event _ = () -end - -let create ~out () : collector = - let buf_pool = Buf_pool.create () in - let events = B_queue.create () in - - let bg_thread = - Thread.create (Bg_thread.bg_thread ~buf_pool ~out ~events) () - in - - let st = - { - active = A.make true; - buf_pool; - bg_thread; - events; - span_id_gen = A.make 0; - next_thread_ref = A.make 1; - per_thread = Array.init 16 (fun _ -> A.make Int_map.empty); - } - in - - let _tick_thread = Thread.create (fun () -> Bg_thread.tick_thread events) in - - (* write header *) - let out = out_of_st st in - FWrite.Metadata.Magic_record.encode out; - FWrite.Metadata.Initialization_record.( - encode out ~ticks_per_secs:default_ticks_per_sec ()); - FWrite.Metadata.Provider_info.encode out ~id:0 ~name:"ocaml-trace" (); - Output.flush out; - Output.dispose out; - - let module Coll = - C - (struct - let st = st - end) - () - in - (module Coll) - -module Internal_ = struct - let mock_all_ () = - Mock_.enabled := true; - Sub.Private_.get_now_ns_ := Some Mock_.get_now_ns; - Sub.Private_.get_tid_ := Some Mock_.get_tid_ -end diff --git a/src/fuchsia/fcollector.mli b/src/fuchsia/fcollector.mli deleted file mode 100644 index 2ba03e9..0000000 --- a/src/fuchsia/fcollector.mli +++ /dev/null @@ -1,11 +0,0 @@ -open Trace_core - -val create : out:Bg_thread.out -> unit -> collector - -(**/**) - -module Internal_ : sig - val mock_all_ : unit -> unit -end - -(**/**) diff --git a/src/fuchsia/lock.ml b/src/fuchsia/lock.ml new file mode 100644 index 0000000..fe2c1f0 --- /dev/null +++ b/src/fuchsia/lock.ml @@ -0,0 +1,28 @@ +type 'a t = { + mutex: Mutex.t; + mutable content: 'a; +} + +let create content : _ t = { mutex = Mutex.create (); content } + +let with_ (self : _ t) f = + Mutex.lock self.mutex; + try + let x = f self.content in + Mutex.unlock self.mutex; + x + with e -> + let bt = Printexc.get_raw_backtrace () in + Mutex.unlock self.mutex; + Printexc.raise_with_backtrace e bt + +let[@inline] update self f = with_ self (fun x -> self.content <- f x) + +let[@inline] update_map l f = + with_ l (fun x -> + let x', y = f x in + l.content <- x'; + y) + +let[@inline] set_while_locked (self : 'a t) (x : 'a) = self.content <- x + diff --git a/src/fuchsia/lock.mli b/src/fuchsia/lock.mli new file mode 100644 index 0000000..7a4e77b --- /dev/null +++ b/src/fuchsia/lock.mli @@ -0,0 +1,10 @@ +type 'a t +(** A value protected by a mutex *) + +val create : 'a -> 'a t +val with_ : 'a t -> ('a -> 'b) -> 'b +val update : 'a t -> ('a -> 'a) -> unit +val update_map : 'a t -> ('a -> 'a * 'b) -> 'b + +val set_while_locked : 'a t -> 'a -> unit +(** Change the value while inside [with_] or similar. *) diff --git a/src/fuchsia/subscriber.ml b/src/fuchsia/subscriber.ml new file mode 100644 index 0000000..fac5178 --- /dev/null +++ b/src/fuchsia/subscriber.ml @@ -0,0 +1,173 @@ +open Common_ +open Trace_core +module Span_tbl = Trace_subscriber.Span_tbl + +let on_tracing_error = on_tracing_error + +type span_info = { + tid: int; + name: string; + start_ns: int64; + mutable data: (string * Sub.user_data) list; + (* NOTE: thread safety: this is supposed to only be modified by the thread + that's running this (synchronous, stack-abiding) span. *) +} +(** Information we store about a span begin event, to emit a complete event when + we meet the corresponding span end event *) + +type t = { + active: bool A.t; + pid: int; + spans: span_info Span_tbl.t; + buf_chain: Buf_chain.t; + exporter: Exporter.t; +} +(** Subscriber state *) + +open struct + (** Write the buffers that are ready *) + let[@inline] write_ready_ (self : t) = + if Buf_chain.has_ready self.buf_chain then + Buf_chain.pop_ready self.buf_chain ~f:self.exporter.write_bufs + + let print_non_closed_spans_warning spans = + let module Str_set = Set.Make (String) in + let spans = Span_tbl.to_list spans in + if spans <> [] then ( + !on_tracing_error + @@ Printf.sprintf "trace-tef: warning: %d spans were not closed\n" + (List.length spans); + let names = + List.fold_left + (fun set (_, span) -> Str_set.add span.name set) + Str_set.empty spans + in + Str_set.iter + (fun name -> + !on_tracing_error @@ Printf.sprintf " span %S was not closed\n" name) + names; + flush stderr + ) +end + +let close (self : t) : unit = + if A.exchange self.active false then ( + Buf_chain.ready_all_non_empty self.buf_chain; + write_ready_ self; + self.exporter.close (); + + print_non_closed_spans_warning self.spans + ) + +let[@inline] active self = A.get self.active + +let flush (self : t) : unit = + Buf_chain.ready_all_non_empty self.buf_chain; + write_ready_ self; + self.exporter.flush () + +let create ?(buf_pool = Buf_pool.create ()) ~pid ~exporter () : t = + let buf_chain = Buf_chain.create ~sharded:true ~buf_pool () in + { active = A.make true; buf_chain; exporter; pid; spans = Span_tbl.create () } + +module Callbacks = struct + type st = t + + let on_init (self : st) ~time_ns:_ = + Writer.Metadata.Magic_record.encode self.buf_chain; + Writer.Metadata.Initialization_record.( + encode self.buf_chain ~ticks_per_secs:default_ticks_per_sec ()); + Writer.Metadata.Provider_info.encode self.buf_chain ~id:0 + ~name:"ocaml-trace" (); + (* make sure we write these immediately so they're not out of order *) + Buf_chain.ready_all_non_empty self.buf_chain; + + write_ready_ self + + let on_shutdown (self : st) ~time_ns:_ = close self + + let on_name_process (self : st) ~time_ns:_ ~tid:_ ~name : unit = + Writer.Kernel_object.( + encode self.buf_chain ~name ~ty:ty_process ~kid:self.pid ~args:[] ()); + write_ready_ self + + let on_name_thread (self : st) ~time_ns:_ ~tid ~name : unit = + Writer.Kernel_object.( + encode self.buf_chain ~name ~ty:ty_thread ~kid:tid + ~args:[ "process", A_kid (Int64.of_int self.pid) ] + ()); + write_ready_ self + + (* add function name, if provided, to the metadata *) + let add_fun_name_ fun_name data : _ list = + match fun_name with + | None -> data + | Some f -> ("function", Sub.U_string f) :: data + + let[@inline] on_enter_span (self : st) ~__FUNCTION__:fun_name ~__FILE__:_ + ~__LINE__:_ ~time_ns ~tid ~data ~name span : unit = + let data = add_fun_name_ fun_name data in + let info = { tid; name; start_ns = time_ns; data } in + (* save the span so we find it at exit *) + Span_tbl.add self.spans span info + + let on_exit_span (self : st) ~time_ns ~tid:_ span : unit = + match Span_tbl.find_exn self.spans span with + | exception Not_found -> + !on_tracing_error (Printf.sprintf "cannot find span %Ld" span) + | { tid; name; start_ns; data } -> + Span_tbl.remove self.spans span; + Writer.( + Event.Duration_complete.encode self.buf_chain ~name + ~t_ref:(Thread_ref.inline ~pid:self.pid ~tid) + ~time_ns:start_ns ~end_time_ns:time_ns ~args:(args_of_user_data data) + ()); + write_ready_ self + + let on_add_data (self : st) ~data span = + if data <> [] then ( + try + let info = Span_tbl.find_exn self.spans span in + info.data <- List.rev_append data info.data + with Not_found -> + !on_tracing_error (Printf.sprintf "cannot find span %Ld" span) + ) + + let on_message (self : st) ~time_ns ~tid ~span:_ ~data msg : unit = + Writer.( + Event.Instant.encode self.buf_chain + ~t_ref:(Thread_ref.inline ~pid:self.pid ~tid) + ~name:msg ~time_ns ~args:(args_of_user_data data) ()); + write_ready_ self + + let on_counter (self : st) ~time_ns ~tid ~data ~name n : unit = + Writer.( + Event.Counter.encode self.buf_chain + ~t_ref:(Thread_ref.inline ~pid:self.pid ~tid) + ~name ~time_ns + ~args:((name, A_float n) :: args_of_user_data data) + ()); + write_ready_ self + + let on_enter_manual_span (self : st) ~__FUNCTION__:_ ~__FILE__:_ ~__LINE__:_ + ~time_ns ~tid ~parent:_ ~data ~name ~flavor:_ ~trace_id _span : unit = + Writer.( + Event.Async_begin.encode self.buf_chain ~name + ~args:(args_of_user_data data) + ~t_ref:(Thread_ref.inline ~pid:self.pid ~tid) + ~time_ns ~async_id:trace_id ()); + write_ready_ self + + let on_exit_manual_span (self : st) ~time_ns ~tid ~name ~data ~flavor:_ + ~trace_id (_ : span) : unit = + Writer.( + Event.Async_end.encode self.buf_chain ~name ~args:(args_of_user_data data) + ~t_ref:(Thread_ref.inline ~pid:self.pid ~tid) + ~time_ns ~async_id:trace_id ()); + write_ready_ self + + let on_extension_event _ ~time_ns:_ ~tid:_ _ev = () +end + +let subscriber (self : t) : Sub.t = + Sub.Subscriber.Sub { st = self; callbacks = (module Callbacks) } diff --git a/src/fuchsia/subscriber.mli b/src/fuchsia/subscriber.mli new file mode 100644 index 0000000..66318f5 --- /dev/null +++ b/src/fuchsia/subscriber.mli @@ -0,0 +1,20 @@ +type t +(** Main subscriber state. *) + +val create : ?buf_pool:Buf_pool.t -> pid:int -> exporter:Exporter.t -> unit -> t +(** Create a subscriber state. *) + +val flush : t -> unit +val close : t -> unit +val active : t -> bool + +module Callbacks : Trace_subscriber.Callbacks.S with type st = t + +val subscriber : t -> Trace_subscriber.t +(** Subscriber that writes json into this writer *) + +(**/**) + +val on_tracing_error : (string -> unit) ref + +(**/**) diff --git a/src/fuchsia/trace_fuchsia.ml b/src/fuchsia/trace_fuchsia.ml index f6b2b14..6529192 100644 --- a/src/fuchsia/trace_fuchsia.ml +++ b/src/fuchsia/trace_fuchsia.ml @@ -1,31 +1,50 @@ open Common_ +module Buf = Buf +module Buf_chain = Buf_chain +module Buf_pool = Buf_pool +module Exporter = Exporter +module Subscriber = Subscriber +module Writer = Writer type output = - [ `Stdout - | `Stderr - | `File of string + [ `File of string + | `Exporter of Exporter.t ] -let collector = Fcollector.create +let get_out_ (out : [< output ]) : Exporter.t = + match out with + | `File path -> + let oc = open_out path in + Exporter.of_out_channel ~close_channel:true oc + | `Exporter e -> e + +let subscriber ~out () : Sub.t = + let exporter = get_out_ out in + let pid = + if !Trace_subscriber.Private_.mock then + 2 + else + Unix.getpid () + in + let sub = Subscriber.create ~pid ~exporter () in + Subscriber.subscriber sub + +let collector ~out () = Sub.collector @@ subscriber ~out () let setup ?(out = `Env) () = match out with - | `Stderr -> Trace_core.setup_collector @@ Fcollector.create ~out:`Stderr () - | `Stdout -> Trace_core.setup_collector @@ Fcollector.create ~out:`Stdout () - | `File path -> - Trace_core.setup_collector @@ Fcollector.create ~out:(`File path) () + | `File path -> Trace_core.setup_collector @@ collector ~out:(`File path) () + | `Exporter _ as out -> + let sub = subscriber ~out () in + Trace_core.setup_collector @@ Sub.collector sub | `Env -> (match Sys.getenv_opt "TRACE" with | Some ("1" | "true") -> let path = "trace.fxt" in - let c = Fcollector.create ~out:(`File path) () in + let c = collector ~out:(`File path) () in Trace_core.setup_collector c - | Some "stdout" -> - Trace_core.setup_collector @@ Fcollector.create ~out:`Stdout () - | Some "stderr" -> - Trace_core.setup_collector @@ Fcollector.create ~out:`Stderr () | Some path -> - let c = Fcollector.create ~out:(`File path) () in + let c = collector ~out:(`File path) () in Trace_core.setup_collector c | None -> ()) @@ -33,7 +52,24 @@ let with_setup ?out () f = setup ?out (); Fun.protect ~finally:Trace_core.shutdown f +module Mock_ = struct + let now = ref 0 + + (* used to mock timing *) + let get_now_ns () : int64 = + let x = !now in + incr now; + Int64.(mul (of_int x) 1000L) + + let get_tid_ () : int = 3 +end + module Internal_ = struct - let mock_all_ = Fcollector.Internal_.mock_all_ + let mock_all_ () = + Sub.Private_.mock := true; + Sub.Private_.get_now_ns_ := Mock_.get_now_ns; + Sub.Private_.get_tid_ := Mock_.get_tid_; + () + let on_tracing_error = on_tracing_error end diff --git a/src/fuchsia/trace_fuchsia.mli b/src/fuchsia/trace_fuchsia.mli index d28111a..74905dc 100644 --- a/src/fuchsia/trace_fuchsia.mli +++ b/src/fuchsia/trace_fuchsia.mli @@ -6,22 +6,23 @@ trace format}. This reduces the tracing overhead compared to [trace-tef], at the expense of simplicity. *) -val collector : - out:[ `File of string | `Stderr | `Stdout ] -> unit -> Trace_core.collector -(** Make a collector that writes into the given output. See {!setup} for more - details. *) +module Buf = Buf +module Buf_chain = Buf_chain +module Buf_pool = Buf_pool +module Exporter = Exporter +module Subscriber = Subscriber +module Writer = Writer type output = - [ `Stdout - | `Stderr - | `File of string + [ `File of string + | `Exporter of Exporter.t ] -(** Output for tracing. - - [`Stdout] will enable tracing and print events on stdout - - [`Stderr] will enable tracing and print events on stderr - - [`File "foo"] will enable tracing and print events into file named "foo" -*) +val subscriber : out:[< output ] -> unit -> Trace_subscriber.t + +val collector : out:[< output ] -> unit -> Trace_core.collector +(** Make a collector that writes into the given output. See {!setup} for more + details. *) val setup : ?out:[ output | `Env ] -> unit -> unit (** [setup ()] installs the collector depending on [out]. @@ -32,12 +33,10 @@ val setup : ?out:[ output | `Env ] -> unit -> unit - [`Env] will enable tracing if the environment variable "TRACE" is set. - If it's set to "1", then the file is "trace.fxt". - - If it's set to "stdout", then logging happens on stdout (since 0.2) - - If it's set to "stderr", then logging happens on stdout (since 0.2) - Otherwise, if it's set to a non empty string, the value is taken to be the file path into which to write. *) -val with_setup : ?out:[ output | `Env ] -> unit -> (unit -> 'a) -> 'a +val with_setup : ?out:[< output | `Env > `Env ] -> unit -> (unit -> 'a) -> 'a (** [with_setup () f] (optionally) sets a collector up, calls [f()], and makes sure to shutdown before exiting. *) diff --git a/src/fuchsia/write/util.ml b/src/fuchsia/util.ml similarity index 100% rename from src/fuchsia/write/util.ml rename to src/fuchsia/util.ml diff --git a/src/fuchsia/write/buf_pool.ml b/src/fuchsia/write/buf_pool.ml deleted file mode 100644 index 961a2d3..0000000 --- a/src/fuchsia/write/buf_pool.ml +++ /dev/null @@ -1,58 +0,0 @@ -open struct - module A = Trace_core.Internal_.Atomic_ - - exception Got_buf of Buf.t -end - -module List_with_len = struct - type +'a t = - | Nil - | Cons of int * 'a * 'a t - - let empty : _ t = Nil - - let[@inline] len = function - | Nil -> 0 - | Cons (i, _, _) -> i - - let[@inline] cons x self = Cons (len self + 1, x, self) -end - -type t = { - max_len: int; - buf_size: int; - bufs: Buf.t List_with_len.t A.t; -} - -let create ?(max_len = 64) ?(buf_size = 1 lsl 16) () : t = - let buf_size = min (1 lsl 22) (max buf_size (1 lsl 15)) in - { max_len; buf_size; bufs = A.make List_with_len.empty } - -let alloc (self : t) : Buf.t = - try - while - match A.get self.bufs with - | Nil -> false - | Cons (_, buf, tl) as old -> - if A.compare_and_set self.bufs old tl then - raise (Got_buf buf) - else - false - do - () - done; - Buf.create self.buf_size - with Got_buf b -> b - -let recycle (self : t) (buf : Buf.t) : unit = - Buf.clear buf; - try - while - match A.get self.bufs with - | Cons (i, _, _) when i >= self.max_len -> raise Exit - | old -> - not (A.compare_and_set self.bufs old (List_with_len.cons buf old)) - do - () - done - with Exit -> () (* do not recycle *) diff --git a/src/fuchsia/write/dune b/src/fuchsia/write/dune deleted file mode 100644 index 88acba8..0000000 --- a/src/fuchsia/write/dune +++ /dev/null @@ -1,9 +0,0 @@ -(library - (name trace_fuchsia_write) - (public_name trace-fuchsia.write) - (synopsis "Serialization part of trace-fuchsia") - (ocamlopt_flags - :standard - ;-dlambda - ) - (libraries trace.core threads)) diff --git a/src/fuchsia/write/output.ml b/src/fuchsia/write/output.ml deleted file mode 100644 index a2f79d4..0000000 --- a/src/fuchsia/write/output.ml +++ /dev/null @@ -1,65 +0,0 @@ -type t = { - mutable buf: Buf.t; - mutable send_buf: Buf.t -> unit; - buf_pool: Buf_pool.t; -} - -let create ~(buf_pool : Buf_pool.t) ~send_buf () : t = - let buf_size = buf_pool.buf_size in - let buf = Buf.create buf_size in - { buf; send_buf; buf_pool } - -open struct - (* NOTE: there is a potential race condition if an output is - flushed from the main thread upon closing, while - the local thread is blissfully writing new records to it - as we're winding down the collector. This is trying to reduce - the likelyhood of a race happening. *) - let[@poll error] replace_buf_ (self : t) (new_buf : Buf.t) : Buf.t = - let old_buf = self.buf in - self.buf <- new_buf; - old_buf - - let flush_ (self : t) : unit = - let new_buf = Buf_pool.alloc self.buf_pool in - let old_buf = replace_buf_ self new_buf in - self.send_buf old_buf - - let[@inline never] cycle_buf (self : t) ~available : Buf.t = - flush_ self; - let buf = self.buf in - - if Buf.available buf < available then ( - let msg = - Printf.sprintf - "fuchsia: buffer is too small (available: %d bytes, needed: %d bytes)" - (Buf.available buf) available - in - failwith msg - ); - buf -end - -let[@inline] flush (self : t) : unit = if Buf.size self.buf > 0 then flush_ self - -(** Maximum size available, in words, for a single message *) -let[@inline] max_size_word (self : t) : int = self.buf_pool.buf_size lsr 3 - -(** Obtain a buffer with at least [available] bytes *) -let[@inline] get_buf (self : t) ~(available_word : int) : Buf.t = - let available = available_word lsl 3 in - if Buf.available self.buf >= available then - self.buf - else - cycle_buf self ~available - -let into_buffer ~buf_pool (buffer : Buffer.t) : t = - let send_buf (buf : Buf.t) = - Buffer.add_subbytes buffer buf.buf 0 buf.offset - in - create ~buf_pool ~send_buf () - -let dispose (self : t) : unit = - flush_ self; - Buf_pool.recycle self.buf_pool self.buf; - self.buf <- Buf.empty diff --git a/src/fuchsia/write/trace_fuchsia_write.ml b/src/fuchsia/writer.ml similarity index 81% rename from src/fuchsia/write/trace_fuchsia_write.ml rename to src/fuchsia/writer.ml index 7cf9bf7..47d2646 100644 --- a/src/fuchsia/write/trace_fuchsia_write.ml +++ b/src/fuchsia/writer.ml @@ -2,14 +2,10 @@ Reference: https://fuchsia.dev/fuchsia-src/reference/tracing/trace-format *) +open Common_ module Util = Util -module Buf = Buf -module Output = Output -module Buf_pool = Buf_pool open struct - let spf = Printf.sprintf - let[@inline] int64_of_trace_id_ (id : Trace_core.trace_id) : int64 = if id == Trace_core.Collector.dummy_trace_id then 0L @@ -19,7 +15,27 @@ end open Util -type user_data = Trace_core.user_data +type user_data = Sub.user_data = + | U_bool of bool + | U_float of float + | U_int of int + | U_none + | U_string of string + +type arg = + | A_bool of bool + | A_float of float + | A_int of int + | A_none + | A_string of string + | A_kid of int64 + +(* NOTE: only works because [user_data] is a prefix of [arg] and is immutable *) +let arg_of_user_data : user_data -> arg = Obj.magic + +(* NOTE: only works because [user_data] is a prefix of [arg] and is immutable *) +let args_of_user_data : (string * user_data) list -> (string * arg) list = + Obj.magic module I64 = struct include Int64 @@ -111,8 +127,8 @@ module Metadata = struct let value = 0x0016547846040010L let size_word = 1 - let encode (out : Output.t) = - let buf = Output.get_buf out ~available_word:size_word in + let encode (bufs : Buf_chain.t) = + let@ buf = Buf_chain.with_buf bufs ~available_word:size_word in Buf.add_i64 buf value end @@ -122,8 +138,8 @@ module Metadata = struct (** Default: 1 tick = 1 ns *) let default_ticks_per_sec = 1_000_000_000L - let encode (out : Output.t) ~ticks_per_secs () : unit = - let buf = Output.get_buf out ~available_word:size_word in + let encode (bufs : Buf_chain.t) ~ticks_per_secs () : unit = + let@ buf = Buf_chain.with_buf bufs ~available_word:size_word in let hd = I64.(1L lor (of_int size_word lsl 4)) in Buf.add_i64 buf hd; Buf.add_i64 buf ticks_per_secs @@ -132,10 +148,10 @@ module Metadata = struct module Provider_info = struct let size_word ~name () = 1 + str_len_word name - let encode (out : Output.t) ~(id : int) ~name () : unit = + let encode (bufs : Buf_chain.t) ~(id : int) ~name () : unit = let name = truncate_string name in let size = size_word ~name () in - let buf = Output.get_buf out ~available_word:size in + let@ buf = Buf_chain.with_buf bufs ~available_word:size in let hd = I64.( (of_int size lsl 4) @@ -152,29 +168,29 @@ module Metadata = struct end module Argument = struct - type 'a t = string * ([< user_data | `Kid of int ] as 'a) + type t = string * arg - let check_valid_ : _ t -> unit = function - | _, `String s -> assert (String.length s < max_str_len) + let check_valid_ : t -> unit = function + | _, A_string s -> assert (String.length s < max_str_len) | _ -> () let[@inline] is_i32_ (i : int) : bool = Int32.(to_int (of_int i) = i) - let size_word (self : _ t) = + let size_word (self : t) = let name, data = self in match data with - | `None | `Bool _ -> 1 + str_len_word name - | `Int i when is_i32_ i -> 1 + str_len_word name - | `Int _ -> (* int64 *) 2 + str_len_word name - | `Float _ -> 2 + str_len_word name - | `String s -> 1 + str_len_word_maybe_too_big s + str_len_word name - | `Kid _ -> 2 + str_len_word name + | A_none | A_bool _ -> 1 + str_len_word name + | A_int i when is_i32_ i -> 1 + str_len_word name + | A_int _ -> (* int64 *) 2 + str_len_word name + | A_float _ -> 2 + str_len_word name + | A_string s -> 1 + str_len_word_maybe_too_big s + str_len_word name + | A_kid _ -> 2 + str_len_word name open struct external int_of_bool : bool -> int = "%identity" end - let encode (buf : Buf.t) (self : _ t) : unit = + let encode (buf : Buf.t) (self : t) : unit = let name, data = self in let name = truncate_string name in let size = size_word self in @@ -187,26 +203,26 @@ module Argument = struct in match data with - | `None -> + | A_none -> let hd = hd_arg_size in Buf.add_i64 buf hd; Buf.add_string buf name - | `Int i when is_i32_ i -> + | A_int i when is_i32_ i -> let hd = I64.(1L lor hd_arg_size lor (of_int i lsl 32)) in Buf.add_i64 buf hd; Buf.add_string buf name - | `Int i -> + | A_int i -> (* int64 *) let hd = I64.(3L lor hd_arg_size) in Buf.add_i64 buf hd; Buf.add_string buf name; Buf.add_i64 buf (I64.of_int i) - | `Float f -> + | A_float f -> let hd = I64.(5L lor hd_arg_size) in Buf.add_i64 buf hd; Buf.add_string buf name; Buf.add_i64 buf (I64.bits_of_float f) - | `String s -> + | A_string s -> let s = truncate_string s in let hd = I64.( @@ -216,35 +232,35 @@ module Argument = struct Buf.add_i64 buf hd; Buf.add_string buf name; Buf.add_string buf s - | `Bool b -> + | A_bool b -> let hd = I64.(9L lor hd_arg_size lor (of_int (int_of_bool b) lsl 16)) in Buf.add_i64 buf hd; Buf.add_string buf name - | `Kid kid -> + | A_kid kid -> (* int64 *) let hd = I64.(8L lor hd_arg_size) in Buf.add_i64 buf hd; Buf.add_string buf name; - Buf.add_i64 buf (I64.of_int kid) + Buf.add_i64 buf kid end module Arguments = struct - type 'a t = 'a Argument.t list + type t = Argument.t list - let[@inline] len (self : _ t) : int = + let[@inline] len (self : t) : int = match self with | [] -> 0 | [ _ ] -> 1 | _ :: _ :: tl -> 2 + List.length tl - let check_valid (self : _ t) = + let check_valid (self : t) = let len = len self in if len > 15 then invalid_arg (spf "fuchsia: can have at most 15 args, got %d" len); List.iter Argument.check_valid_ self; () - let[@inline] size_word (self : _ t) = + let[@inline] size_word (self : t) = match self with | [] -> 0 | [ a ] -> Argument.size_word a @@ -254,7 +270,7 @@ module Arguments = struct (Argument.size_word a + Argument.size_word b) tl - let[@inline] encode (buf : Buf.t) (self : _ t) = + let[@inline] encode (buf : Buf.t) (self : t) = let rec aux buf l = match l with | [] -> () @@ -276,11 +292,11 @@ module Thread_record = struct let size_word : int = 3 (** Record that [Thread_ref.ref as_ref] represents the pair [pid, tid] *) - let encode (out : Output.t) ~as_ref ~pid ~tid () : unit = + let encode (bufs : Buf_chain.t) ~as_ref ~pid ~tid () : unit = if as_ref <= 0 || as_ref > 255 then invalid_arg "fuchsia: thread_record: invalid ref"; - let buf = Output.get_buf out ~available_word:size_word in + let@ buf = Buf_chain.with_buf bufs ~available_word:size_word in let hd = I64.(3L lor (of_int size_word lsl 4) lor (of_int as_ref lsl 16)) in Buf.add_i64 buf hd; @@ -296,11 +312,11 @@ module Event = struct 1 + Thread_ref.size_word t_ref + 1 (* timestamp *) + str_len_word name + Arguments.size_word args - let encode (out : Output.t) ~name ~(t_ref : Thread_ref.t) ~time_ns ~args () - : unit = + let encode (bufs : Buf_chain.t) ~name ~(t_ref : Thread_ref.t) ~time_ns ~args + () : unit = let name = truncate_string name in let size = size_word ~name ~t_ref ~args () in - let buf = Output.get_buf out ~available_word:size in + let@ buf = Buf_chain.with_buf bufs ~available_word:size in (* set category = 0 *) let hd = @@ -331,11 +347,11 @@ module Event = struct 1 + Thread_ref.size_word t_ref + 1 (* timestamp *) + str_len_word name + Arguments.size_word args + 1 (* counter id *) - let encode (out : Output.t) ~name ~(t_ref : Thread_ref.t) ~time_ns ~args () - : unit = + let encode (bufs : Buf_chain.t) ~name ~(t_ref : Thread_ref.t) ~time_ns ~args + () : unit = let name = truncate_string name in let size = size_word ~name ~t_ref ~args () in - let buf = Output.get_buf out ~available_word:size in + let@ buf = Buf_chain.with_buf bufs ~available_word:size in let hd = I64.( @@ -368,11 +384,11 @@ module Event = struct 1 + Thread_ref.size_word t_ref + 1 (* timestamp *) + str_len_word name + Arguments.size_word args - let encode (out : Output.t) ~name ~(t_ref : Thread_ref.t) ~time_ns ~args () - : unit = + let encode (bufs : Buf_chain.t) ~name ~(t_ref : Thread_ref.t) ~time_ns ~args + () : unit = let name = truncate_string name in let size = size_word ~name ~t_ref ~args () in - let buf = Output.get_buf out ~available_word:size in + let@ buf = Buf_chain.with_buf bufs ~available_word:size in let hd = I64.( @@ -403,11 +419,11 @@ module Event = struct 1 + Thread_ref.size_word t_ref + 1 (* timestamp *) + str_len_word name + Arguments.size_word args - let encode (out : Output.t) ~name ~(t_ref : Thread_ref.t) ~time_ns ~args () - : unit = + let encode (bufs : Buf_chain.t) ~name ~(t_ref : Thread_ref.t) ~time_ns ~args + () : unit = let name = truncate_string name in let size = size_word ~name ~t_ref ~args () in - let buf = Output.get_buf out ~available_word:size in + let@ buf = Buf_chain.with_buf bufs ~available_word:size in let hd = I64.( @@ -438,11 +454,11 @@ module Event = struct 1 + Thread_ref.size_word t_ref + 1 (* timestamp *) + str_len_word name + Arguments.size_word args + 1 (* end timestamp *) - let encode (out : Output.t) ~name ~(t_ref : Thread_ref.t) ~time_ns + let encode (bufs : Buf_chain.t) ~name ~(t_ref : Thread_ref.t) ~time_ns ~end_time_ns ~args () : unit = let name = truncate_string name in let size = size_word ~name ~t_ref ~args () in - let buf = Output.get_buf out ~available_word:size in + let@ buf = Buf_chain.with_buf bufs ~available_word:size in (* set category = 0 *) let hd = @@ -475,11 +491,11 @@ module Event = struct 1 + Thread_ref.size_word t_ref + 1 (* timestamp *) + str_len_word name + Arguments.size_word args + 1 (* async id *) - let encode (out : Output.t) ~name ~(t_ref : Thread_ref.t) ~time_ns + let encode (bufs : Buf_chain.t) ~name ~(t_ref : Thread_ref.t) ~time_ns ~(async_id : Trace_core.trace_id) ~args () : unit = let name = truncate_string name in let size = size_word ~name ~t_ref ~args () in - let buf = Output.get_buf out ~available_word:size in + let@ buf = Buf_chain.with_buf bufs ~available_word:size in let hd = I64.( @@ -511,11 +527,11 @@ module Event = struct 1 + Thread_ref.size_word t_ref + 1 (* timestamp *) + str_len_word name + Arguments.size_word args + 1 (* async id *) - let encode (out : Output.t) ~name ~(t_ref : Thread_ref.t) ~time_ns + let encode (bufs : Buf_chain.t) ~name ~(t_ref : Thread_ref.t) ~time_ns ~(async_id : Trace_core.trace_id) ~args () : unit = let name = truncate_string name in let size = size_word ~name ~t_ref ~args () in - let buf = Output.get_buf out ~available_word:size in + let@ buf = Buf_chain.with_buf bufs ~available_word:size in let hd = I64.( @@ -556,10 +572,11 @@ module Kernel_object = struct let ty_process : ty = 1 let ty_thread : ty = 2 - let encode (out : Output.t) ~name ~(ty : ty) ~(kid : int) ~args () : unit = + let encode (bufs : Buf_chain.t) ~name ~(ty : ty) ~(kid : int) ~args () : unit + = let name = truncate_string name in let size = size_word ~name ~args () in - let buf = Output.get_buf out ~available_word:size in + let@ buf = Buf_chain.with_buf bufs ~available_word:size in let hd = I64.( From 7acc1b930f6ff085ede56dac7f070b7dba4f18bf Mon Sep 17 00:00:00 2001 From: Simon Cruanes Date: Wed, 7 May 2025 15:35:11 -0400 Subject: [PATCH 10/15] detail --- src/util/rpool.ml | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/util/rpool.ml b/src/util/rpool.ml index 2a5446c..dbaf605 100644 --- a/src/util/rpool.ml +++ b/src/util/rpool.ml @@ -1,5 +1,3 @@ -(** A resource pool (for buffers) *) - open struct module A = Trace_core.Internal_.Atomic_ end From 86e65d20469016ecd06f9226c76f8e1ec4e4c09b Mon Sep 17 00:00:00 2001 From: Simon Cruanes Date: Wed, 7 May 2025 15:35:17 -0400 Subject: [PATCH 11/15] test: update and improve fuchsia tests --- test/fuchsia/t1.expected | 1 + test/fuchsia/t1.ml | 31 +++++++++++++++++++++++++++-- test/fuchsia/write/dune | 2 +- test/fuchsia/write/t1.ml | 12 ++++++------ test/fuchsia/write/t2.ml | 42 ++++++++++++++++++++++------------------ test/t1.expected | 3 +-- test/t2.expected | 3 +-- 7 files changed, 62 insertions(+), 32 deletions(-) create mode 100644 test/fuchsia/t1.expected diff --git a/test/fuchsia/t1.expected b/test/fuchsia/t1.expected new file mode 100644 index 0000000..ac5f6e1 --- /dev/null +++ b/test/fuchsia/t1.expected @@ -0,0 +1 @@ +1000044678541600210000000000000000ca9a3b0000000030000100000020006f63616d6c2d74726163650000000000370001048000000002000000000000006d61696e00000000670002028001000003000000000000007431000000000000380007800000000070726f636573730002000000000000007400050000000a80a00f0000000000000200000000000000030000000000000066616b655f736c6565700000000000000000000000000000640000000000098070170000000000000200000000000000030000000000000068656c6c6f20312032000000000000005400000000000580581b00000000000002000000000000000300000000000000776f726c640000009400110000000180401f000000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000f03f000000000000000074000500000009802823000000000000020000000000000003000000000000007375622d736c65657000000000000000000000000000000074000700000009801027000000000000020000000000000003000000000000007375622d736c656570000000000000000000000000000000b400240000000a80881300000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a0000000000000021000180010000006900000000000000f82a0000000000006400000000000980c8320000000000000200000000000000030000000000000068656c6c6f20312033000000000000005400000000000580b03600000000000002000000000000000300000000000000776f726c640000009400110000000180983a000000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000004000000000000000009400170000000a80803e0000000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c65707400000000000000000000009400140000000a80e02e00000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018001000000690000000000000068420000000000006400000000000980384a0000000000000200000000000000030000000000000068656c6c6f20312034000000000000005400000000000580204e00000000000002000000000000000300000000000000776f726c6400000094001100000001800852000000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000084000000000000000009400140000000a80504600000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180010000006900000000000000f0550000000000006400000000000980c05d0000000000000200000000000000030000000000000068656c6c6f20312035000000000000005400000000000580a86100000000000002000000000000000300000000000000776f726c6400000094001100000001809065000000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000104000000000000000009400140000000a80d85900000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018001000000690000000000000078690000000000007400040000000a80b80b000000000000020000000000000003000000000000006f757465722e6c6f6f70000000000000606d0000000000007400050000000a8030750000000000000200000000000000030000000000000066616b655f736c65657000000000000001000000000000006400000000000980007d0000000000000200000000000000030000000000000068656c6c6f20322032000000000000005400000000000580e88000000000000002000000000000000300000000000000776f726c640000009400110000000180d084000000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000144000000000000000007400050000000980b888000000000000020000000000000003000000000000007375622d736c6565700000000000000001000000000000007400070000000980a08c000000000000020000000000000003000000000000007375622d736c656570000000000000000100000000000000b400240000000a80187900000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a00000000000000210001800200000069000000000000008890000000000000640000000000098058980000000000000200000000000000030000000000000068656c6c6f20322033000000000000005400000000000580409c00000000000002000000000000000300000000000000776f726c64000000940011000000018028a0000000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000184000000000000000009400170000000a8010a40000000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c65707400000001000000000000009400140000000a80709400000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006900000000000000f8a70000000000006400000000000980c8af0000000000000200000000000000030000000000000068656c6c6f20322034000000000000005400000000000580b0b300000000000002000000000000000300000000000000776f726c64000000940011000000018098b7000000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000001c4000000000000000009400140000000a80e0ab00000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018002000000690000000000000080bb000000000000640000000000098050c30000000000000200000000000000030000000000000068656c6c6f2032203500000000000000540000000000058038c700000000000002000000000000000300000000000000776f726c64000000940011000000018020cb000000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000204000000000000000009400140000000a8068bf00000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018002000000690000000000000008cf0000000000007400040000000a804871000000000000020000000000000003000000000000006f757465722e6c6f6f70000000000000f0d20000000000007400050000000a80c0da0000000000000200000000000000030000000000000066616b655f736c6565700000000000000200000000000000640000000000098090e20000000000000200000000000000030000000000000068656c6c6f2033203200000000000000540000000000058078e600000000000002000000000000000300000000000000776f726c64000000940011000000018060ea000000000000020000000000000003000000000000006e0000000000000035000180000000006e0000000000000000000000000022400000000000000000740005000000098048ee000000000000020000000000000003000000000000007375622d736c656570000000000000000200000000000000740007000000098030f2000000000000020000000000000003000000000000007375622d736c656570000000000000000200000000000000b400240000000a80a8de00000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a000000000000002100018003000000690000000000000018f60000000000006400000000000980e8fd0000000000000200000000000000030000000000000068656c6c6f20332033000000000000005400000000000580d00101000000000002000000000000000300000000000000776f726c640000009400110000000180b805010000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000244000000000000000009400170000000a80a0090100000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c65707400000002000000000000009400140000000a8000fa00000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180030000006900000000000000880d010000000000640000000000098058150100000000000200000000000000030000000000000068656c6c6f20332034000000000000005400000000000580401901000000000002000000000000000300000000000000776f726c640000009400110000000180281d010000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000264000000000000000009400140000000a80701101000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018003000000690000000000000010210100000000006400000000000980e0280100000000000200000000000000030000000000000068656c6c6f20332035000000000000005400000000000580c82c01000000000002000000000000000300000000000000776f726c640000009400110000000180b030010000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000284000000000000000009400140000000a80f82401000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018003000000690000000000000098340100000000007400040000000a80d8d6000000000000020000000000000003000000000000006f757465722e6c6f6f7000000000000080380100000000007400050000000a8050400100000000000200000000000000030000000000000066616b655f736c6565700000000000000300000000000000640000000000098020480100000000000200000000000000030000000000000068656c6c6f20342032000000000000005400000000000580084c01000000000002000000000000000300000000000000776f726c640000009400110000000180f04f010000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000002a4000000000000000007400050000000980d853010000000000020000000000000003000000000000007375622d736c6565700000000000000003000000000000007400070000000980c057010000000000020000000000000003000000000000007375622d736c656570000000000000000300000000000000b400240000000a80384401000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a0000000000000021000180040000006900000000000000a85b010000000000640000000000098078630100000000000200000000000000030000000000000068656c6c6f20342033000000000000005400000000000580606701000000000002000000000000000300000000000000776f726c640000009400110000000180486b010000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000002c4000000000000000009400170000000a80306f0100000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c65707400000003000000000000009400140000000a80905f01000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018004000000690000000000000018730100000000006400000000000980e87a0100000000000200000000000000030000000000000068656c6c6f20342034000000000000005400000000000580d07e01000000000002000000000000000300000000000000776f726c640000009400110000000180b882010000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000002e4000000000000000009400140000000a80007701000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180040000006900000000000000a0860100000000006400000000000980708e0100000000000200000000000000030000000000000068656c6c6f20342035000000000000005400000000000580589201000000000002000000000000000300000000000000776f726c6400000094001100000001804096010000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000304000000000000000009400140000000a80888a01000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180040000006900000000000000289a0100000000007400040000000a80683c010000000000020000000000000003000000000000006f757465722e6c6f6f70000000000000109e0100000000007400050000000a80e0a50100000000000200000000000000030000000000000066616b655f736c65657000000000000004000000000000006400000000000980b0ad0100000000000200000000000000030000000000000068656c6c6f2035203200000000000000540000000000058098b101000000000002000000000000000300000000000000776f726c64000000940011000000018080b5010000000000020000000000000003000000000000006e0000000000000035000180000000006e0000000000000000000000000031400000000000000000740005000000098068b9010000000000020000000000000003000000000000007375622d736c656570000000000000000400000000000000740007000000098050bd010000000000020000000000000003000000000000007375622d736c656570000000000000000400000000000000b400240000000a80c8a901000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a000000000000002100018005000000690000000000000038c1010000000000640000000000098008c90100000000000200000000000000030000000000000068656c6c6f20352033000000000000005400000000000580f0cc01000000000002000000000000000300000000000000776f726c640000009400110000000180d8d0010000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000324000000000000000009400170000000a80c0d40100000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c65707400000004000000000000009400140000000a8020c501000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180050000006900000000000000a8d8010000000000640000000000098078e00100000000000200000000000000030000000000000068656c6c6f2035203400000000000000540000000000058060e401000000000002000000000000000300000000000000776f726c64000000940011000000018048e8010000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000334000000000000000009400140000000a8090dc01000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018005000000690000000000000030ec010000000000640000000000098000f40100000000000200000000000000030000000000000068656c6c6f20352035000000000000005400000000000580e8f701000000000002000000000000000300000000000000776f726c640000009400110000000180d0fb010000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000344000000000000000009400140000000a8018f001000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180050000006900000000000000b8ff0100000000007400040000000a80f8a1010000000000020000000000000003000000000000006f757465722e6c6f6f70000000000000a0030200000000007400050000000a80700b0200000000000200000000000000030000000000000066616b655f736c6565700000000000000500000000000000640000000000098040130200000000000200000000000000030000000000000068656c6c6f20362032000000000000005400000000000580281702000000000002000000000000000300000000000000776f726c640000009400110000000180101b020000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000354000000000000000007400050000000980f81e020000000000020000000000000003000000000000007375622d736c6565700000000000000005000000000000007400070000000980e022020000000000020000000000000003000000000000007375622d736c656570000000000000000500000000000000b400240000000a80580f02000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a0000000000000021000180060000006900000000000000c8260200000000006400000000000980982e0200000000000200000000000000030000000000000068656c6c6f20362033000000000000005400000000000580803202000000000002000000000000000300000000000000776f726c6400000094001100000001806836020000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000364000000000000000009400170000000a80503a0200000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c65707400000005000000000000009400140000000a80b02a02000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180060000006900000000000000383e020000000000640000000000098008460200000000000200000000000000030000000000000068656c6c6f20362034000000000000005400000000000580f04902000000000002000000000000000300000000000000776f726c640000009400110000000180d84d020000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000374000000000000000009400140000000a80204202000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180060000006900000000000000c051020000000000640000000000098090590200000000000200000000000000030000000000000068656c6c6f20362035000000000000005400000000000580785d02000000000002000000000000000300000000000000776f726c6400000094001100000001806061020000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000384000000000000000009400140000000a80a85502000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018006000000690000000000000048650200000000007400040000000a808807020000000000020000000000000003000000000000006f757465722e6c6f6f7000000000000030690200000000007400050000000a8000710200000000000200000000000000030000000000000066616b655f736c65657000000000000006000000000000006400000000000980d0780200000000000200000000000000030000000000000068656c6c6f20372032000000000000005400000000000580b87c02000000000002000000000000000300000000000000776f726c640000009400110000000180a080020000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000003940000000000000000074000500000009808884020000000000020000000000000003000000000000007375622d736c65657000000000000000060000000000000074000700000009807088020000000000020000000000000003000000000000007375622d736c656570000000000000000600000000000000b400240000000a80e87402000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a0000000000000021000180070000006900000000000000588c020000000000640000000000098028940200000000000200000000000000030000000000000068656c6c6f20372033000000000000005400000000000580109802000000000002000000000000000300000000000000776f726c640000009400110000000180f89b020000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000003a4000000000000000009400170000000a80e09f0200000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c65707400000006000000000000009400140000000a80409002000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180070000006900000000000000c8a3020000000000640000000000098098ab0200000000000200000000000000030000000000000068656c6c6f2037203400000000000000540000000000058080af02000000000002000000000000000300000000000000776f726c64000000940011000000018068b3020000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000003b4000000000000000009400140000000a80b0a702000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018007000000690000000000000050b7020000000000640000000000098020bf0200000000000200000000000000030000000000000068656c6c6f2037203500000000000000540000000000058008c302000000000002000000000000000300000000000000776f726c640000009400110000000180f0c6020000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000003c4000000000000000009400140000000a8038bb02000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180070000006900000000000000d8ca0200000000007400040000000a80186d020000000000020000000000000003000000000000006f757465722e6c6f6f70000000000000c0ce0200000000007400050000000a8090d60200000000000200000000000000030000000000000066616b655f736c6565700000000000000700000000000000640000000000098060de0200000000000200000000000000030000000000000068656c6c6f2038203200000000000000540000000000058048e202000000000002000000000000000300000000000000776f726c64000000940011000000018030e6020000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000003d400000000000000000740005000000098018ea020000000000020000000000000003000000000000007375622d736c656570000000000000000700000000000000740007000000098000ee020000000000020000000000000003000000000000007375622d736c656570000000000000000700000000000000b400240000000a8078da02000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a0000000000000021000180080000006900000000000000e8f10200000000006400000000000980b8f90200000000000200000000000000030000000000000068656c6c6f20382033000000000000005400000000000580a0fd02000000000002000000000000000300000000000000776f726c6400000094001100000001808801030000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000003e4000000000000000009400170000000a8070050300000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c65707400000007000000000000009400140000000a80d0f502000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001800800000069000000000000005809030000000000640000000000098028110300000000000200000000000000030000000000000068656c6c6f20382034000000000000005400000000000580101503000000000002000000000000000300000000000000776f726c640000009400110000000180f818030000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000003f4000000000000000009400140000000a80400d03000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180080000006900000000000000e01c0300000000006400000000000980b0240300000000000200000000000000030000000000000068656c6c6f20382035000000000000005400000000000580982803000000000002000000000000000300000000000000776f726c640000009400110000000180802c030000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000404000000000000000009400140000000a80c82003000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018008000000690000000000000068300300000000007400040000000a80a8d2020000000000020000000000000003000000000000006f757465722e6c6f6f7000000000000050340300000000007400050000000a80203c0300000000000200000000000000030000000000000066616b655f736c65657000000000000008000000000000006400000000000980f0430300000000000200000000000000030000000000000068656c6c6f20392032000000000000005400000000000580d84703000000000002000000000000000300000000000000776f726c640000009400110000000180c04b030000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000080404000000000000000007400050000000980a84f030000000000020000000000000003000000000000007375622d736c65657000000000000000080000000000000074000700000009809053030000000000020000000000000003000000000000007375622d736c656570000000000000000800000000000000b400240000000a80084003000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a000000000000002100018009000000690000000000000078570300000000006400000000000980485f0300000000000200000000000000030000000000000068656c6c6f20392033000000000000005400000000000580306303000000000002000000000000000300000000000000776f726c6400000094001100000001801867030000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000414000000000000000009400170000000a80006b0300000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c65707400000008000000000000009400140000000a80605b03000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180090000006900000000000000e86e0300000000006400000000000980b8760300000000000200000000000000030000000000000068656c6c6f20392034000000000000005400000000000580a07a03000000000002000000000000000300000000000000776f726c640000009400110000000180887e030000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000080414000000000000000009400140000000a80d07203000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018009000000690000000000000070820300000000006400000000000980408a0300000000000200000000000000030000000000000068656c6c6f20392035000000000000005400000000000580288e03000000000002000000000000000300000000000000776f726c6400000094001100000001801092030000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000424000000000000000009400140000000a80588603000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180090000006900000000000000f8950300000000007400040000000a803838030000000000020000000000000003000000000000006f757465722e6c6f6f70000000000000e0990300000000007400050000000a80b0a10300000000000200000000000000030000000000000066616b655f736c65657000000000000009000000000000006400000000000a8080a90300000000000200000000000000030000000000000068656c6c6f2031302032000000000000540000000000058068ad03000000000002000000000000000300000000000000776f726c64000000940011000000018050b1030000000000020000000000000003000000000000006e0000000000000035000180000000006e0000000000000000000000008042400000000000000000740005000000098038b5030000000000020000000000000003000000000000007375622d736c656570000000000000000900000000000000740007000000098020b9030000000000020000000000000003000000000000007375622d736c656570000000000000000900000000000000b400240000000a8098a503000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a00000000000000210001800a000000690000000000000008bd0300000000006400000000000a80d8c40300000000000200000000000000030000000000000068656c6c6f20313020330000000000005400000000000580c0c803000000000002000000000000000300000000000000776f726c640000009400110000000180a8cc030000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000434000000000000000009400170000000a8090d00300000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c65707400000009000000000000009400140000000a80f0c003000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001800a000000690000000000000078d40300000000006400000000000a8048dc0300000000000200000000000000030000000000000068656c6c6f2031302034000000000000540000000000058030e003000000000002000000000000000300000000000000776f726c64000000940011000000018018e4030000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000080434000000000000000009400140000000a8060d803000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001800a000000690000000000000000e80300000000006400000000000a80d0ef0300000000000200000000000000030000000000000068656c6c6f20313020350000000000005400000000000580b8f303000000000002000000000000000300000000000000776f726c640000009400110000000180a0f7030000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000444000000000000000009400140000000a80e8eb03000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001800a000000690000000000000088fb0300000000007400040000000a80c89d030000000000020000000000000003000000000000006f757465722e6c6f6f7000000000000070ff0300000000007400050000000a8040070400000000000200000000000000030000000000000066616b655f736c6565700000000000000a000000000000006400000000000a80100f0400000000000200000000000000030000000000000068656c6c6f20313120320000000000005400000000000580f81204000000000002000000000000000300000000000000776f726c640000009400110000000180e016040000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000080444000000000000000007400050000000980c81a040000000000020000000000000003000000000000007375622d736c656570000000000000000a000000000000007400070000000980b01e040000000000020000000000000003000000000000007375622d736c656570000000000000000a00000000000000b400240000000a80280b04000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a00000000000000210001800b000000690000000000000098220400000000006400000000000a80682a0400000000000200000000000000030000000000000068656c6c6f20313120330000000000005400000000000580502e04000000000002000000000000000300000000000000776f726c6400000094001100000001803832040000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000454000000000000000009400170000000a8020360400000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c6570740000000a000000000000009400140000000a80802604000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001800b0000006900000000000000083a0400000000006400000000000a80d8410400000000000200000000000000030000000000000068656c6c6f20313120340000000000005400000000000580c04504000000000002000000000000000300000000000000776f726c640000009400110000000180a849040000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000080454000000000000000009400140000000a80f03d04000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001800b0000006900000000000000904d0400000000006400000000000a8060550400000000000200000000000000030000000000000068656c6c6f20313120350000000000005400000000000580485904000000000002000000000000000300000000000000776f726c640000009400110000000180305d040000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000464000000000000000009400140000000a80785104000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001800b000000690000000000000018610400000000007400040000000a805803040000000000020000000000000003000000000000006f757465722e6c6f6f7000000000000000650400000000007400050000000a80d06c0400000000000200000000000000030000000000000066616b655f736c6565700000000000000b000000000000006400000000000a80a0740400000000000200000000000000030000000000000068656c6c6f20313220320000000000005400000000000580887804000000000002000000000000000300000000000000776f726c640000009400110000000180707c040000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000804640000000000000000074000500000009805880040000000000020000000000000003000000000000007375622d736c656570000000000000000b0000000000000074000700000009804084040000000000020000000000000003000000000000007375622d736c656570000000000000000b00000000000000b400240000000a80b87004000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a00000000000000210001800c000000690000000000000028880400000000006400000000000a80f88f0400000000000200000000000000030000000000000068656c6c6f20313220330000000000005400000000000580e09304000000000002000000000000000300000000000000776f726c640000009400110000000180c897040000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000474000000000000000009400170000000a80b09b0400000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c6570740000000b000000000000009400140000000a80108c04000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001800c0000006900000000000000989f0400000000006400000000000a8068a70400000000000200000000000000030000000000000068656c6c6f2031322034000000000000540000000000058050ab04000000000002000000000000000300000000000000776f726c64000000940011000000018038af040000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000080474000000000000000009400140000000a8080a304000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001800c000000690000000000000020b30400000000006400000000000a80f0ba0400000000000200000000000000030000000000000068656c6c6f20313220350000000000005400000000000580d8be04000000000002000000000000000300000000000000776f726c640000009400110000000180c0c2040000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000484000000000000000009400140000000a8008b704000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001800c0000006900000000000000a8c60400000000007400040000000a80e868040000000000020000000000000003000000000000006f757465722e6c6f6f7000000000000090ca0400000000007400050000000a8060d20400000000000200000000000000030000000000000066616b655f736c6565700000000000000c000000000000006400000000000a8030da0400000000000200000000000000030000000000000068656c6c6f2031332032000000000000540000000000058018de04000000000002000000000000000300000000000000776f726c64000000940011000000018000e2040000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000080484000000000000000007400050000000980e8e5040000000000020000000000000003000000000000007375622d736c656570000000000000000c000000000000007400070000000980d0e9040000000000020000000000000003000000000000007375622d736c656570000000000000000c00000000000000b400240000000a8048d604000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a00000000000000210001800d0000006900000000000000b8ed0400000000006400000000000a8088f50400000000000200000000000000030000000000000068656c6c6f2031332033000000000000540000000000058070f904000000000002000000000000000300000000000000776f726c64000000940011000000018058fd040000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000494000000000000000009400170000000a8040010500000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c6570740000000c000000000000009400140000000a80a0f104000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001800d000000690000000000000028050500000000006400000000000a80f80c0500000000000200000000000000030000000000000068656c6c6f20313320340000000000005400000000000580e01005000000000002000000000000000300000000000000776f726c640000009400110000000180c814050000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000080494000000000000000009400140000000a80100905000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001800d0000006900000000000000b0180500000000006400000000000a8080200500000000000200000000000000030000000000000068656c6c6f20313320350000000000005400000000000580682405000000000002000000000000000300000000000000776f726c6400000094001100000001805028050000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000004a4000000000000000009400140000000a80981c05000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001800d0000006900000000000000382c0500000000007400040000000a8078ce040000000000020000000000000003000000000000006f757465722e6c6f6f7000000000000020300500000000007400050000000a80f0370500000000000200000000000000030000000000000066616b655f736c6565700000000000000d000000000000006400000000000a80c03f0500000000000200000000000000030000000000000068656c6c6f20313420320000000000005400000000000580a84305000000000002000000000000000300000000000000776f726c6400000094001100000001809047050000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000804a4000000000000000007400050000000980784b050000000000020000000000000003000000000000007375622d736c656570000000000000000d000000000000007400070000000980604f050000000000020000000000000003000000000000007375622d736c656570000000000000000d00000000000000b400240000000a80d83b05000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a00000000000000210001800e000000690000000000000048530500000000006400000000000a80185b0500000000000200000000000000030000000000000068656c6c6f20313420330000000000005400000000000580005f05000000000002000000000000000300000000000000776f726c640000009400110000000180e862050000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000004b4000000000000000009400170000000a80d0660500000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c6570740000000d000000000000009400140000000a80305705000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001800e0000006900000000000000b86a0500000000006400000000000a8088720500000000000200000000000000030000000000000068656c6c6f20313420340000000000005400000000000580707605000000000002000000000000000300000000000000776f726c640000009400110000000180587a050000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000804b4000000000000000009400140000000a80a06e05000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001800e0000006900000000000000407e0500000000006400000000000a8010860500000000000200000000000000030000000000000068656c6c6f20313420350000000000005400000000000580f88905000000000002000000000000000300000000000000776f726c640000009400110000000180e08d050000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000004c4000000000000000009400140000000a80288205000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001800e0000006900000000000000c8910500000000007400040000000a800834050000000000020000000000000003000000000000006f757465722e6c6f6f70000000000000b0950500000000007400050000000a80809d0500000000000200000000000000030000000000000066616b655f736c6565700000000000000e000000000000006400000000000a8050a50500000000000200000000000000030000000000000068656c6c6f2031352032000000000000540000000000058038a905000000000002000000000000000300000000000000776f726c64000000940011000000018020ad050000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000804c400000000000000000740005000000098008b1050000000000020000000000000003000000000000007375622d736c656570000000000000000e000000000000007400070000000980f0b4050000000000020000000000000003000000000000007375622d736c656570000000000000000e00000000000000b400240000000a8068a105000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a00000000000000210001800f0000006900000000000000d8b80500000000006400000000000a80a8c00500000000000200000000000000030000000000000068656c6c6f2031352033000000000000540000000000058090c405000000000002000000000000000300000000000000776f726c64000000940011000000018078c8050000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000004d4000000000000000009400170000000a8060cc0500000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c6570740000000e000000000000009400140000000a80c0bc05000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001800f000000690000000000000048d00500000000006400000000000a8018d80500000000000200000000000000030000000000000068656c6c6f2031352034000000000000540000000000058000dc05000000000002000000000000000300000000000000776f726c640000009400110000000180e8df050000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000804d4000000000000000009400140000000a8030d405000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001800f0000006900000000000000d0e30500000000006400000000000a80a0eb0500000000000200000000000000030000000000000068656c6c6f2031352035000000000000540000000000058088ef05000000000002000000000000000300000000000000776f726c64000000940011000000018070f3050000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000004e4000000000000000009400140000000a80b8e705000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001800f000000690000000000000058f70500000000007400040000000a809899050000000000020000000000000003000000000000006f757465722e6c6f6f7000000000000040fb0500000000007400050000000a8010030600000000000200000000000000030000000000000066616b655f736c6565700000000000000f000000000000006400000000000a80e00a0600000000000200000000000000030000000000000068656c6c6f20313620320000000000005400000000000580c80e06000000000002000000000000000300000000000000776f726c640000009400110000000180b012060000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000804e40000000000000000074000500000009809816060000000000020000000000000003000000000000007375622d736c656570000000000000000f000000000000007400070000000980801a060000000000020000000000000003000000000000007375622d736c656570000000000000000f00000000000000b400240000000a80f80606000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a0000000000000021000180100000006900000000000000681e0600000000006400000000000a8038260600000000000200000000000000030000000000000068656c6c6f20313620330000000000005400000000000580202a06000000000002000000000000000300000000000000776f726c640000009400110000000180082e060000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000004f4000000000000000009400170000000a80f0310600000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c6570740000000f000000000000009400140000000a80502206000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180100000006900000000000000d8350600000000006400000000000a80a83d0600000000000200000000000000030000000000000068656c6c6f20313620340000000000005400000000000580904106000000000002000000000000000300000000000000776f726c6400000094001100000001807845060000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000804f4000000000000000009400140000000a80c03906000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018010000000690000000000000060490600000000006400000000000a8030510600000000000200000000000000030000000000000068656c6c6f20313620350000000000005400000000000580185506000000000002000000000000000300000000000000776f726c6400000094001100000001800059060000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000504000000000000000009400140000000a80484d06000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180100000006900000000000000e85c0600000000007400040000000a8028ff050000000000020000000000000003000000000000006f757465722e6c6f6f70000000000000d0600600000000007400050000000a80a0680600000000000200000000000000030000000000000066616b655f736c65657000000000000010000000000000006400000000000a8070700600000000000200000000000000030000000000000068656c6c6f20313720320000000000005400000000000580587406000000000002000000000000000300000000000000776f726c6400000094001100000001804078060000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000040504000000000000000007400050000000980287c060000000000020000000000000003000000000000007375622d736c65657000000000000000100000000000000074000700000009801080060000000000020000000000000003000000000000007375622d736c656570000000000000001000000000000000b400240000000a80886c06000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a0000000000000021000180110000006900000000000000f8830600000000006400000000000a80c88b0600000000000200000000000000030000000000000068656c6c6f20313720330000000000005400000000000580b08f06000000000002000000000000000300000000000000776f726c6400000094001100000001809893060000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000080504000000000000000009400170000000a8080970600000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c65707400000010000000000000009400140000000a80e08706000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180110000006900000000000000689b0600000000006400000000000a8038a30600000000000200000000000000030000000000000068656c6c6f2031372034000000000000540000000000058020a706000000000002000000000000000300000000000000776f726c64000000940011000000018008ab060000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000c0504000000000000000009400140000000a80509f06000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180110000006900000000000000f0ae0600000000006400000000000a80c0b60600000000000200000000000000030000000000000068656c6c6f20313720350000000000005400000000000580a8ba06000000000002000000000000000300000000000000776f726c64000000940011000000018090be060000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000514000000000000000009400140000000a80d8b206000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018011000000690000000000000078c20600000000007400040000000a80b864060000000000020000000000000003000000000000006f757465722e6c6f6f7000000000000060c60600000000007400050000000a8030ce0600000000000200000000000000030000000000000066616b655f736c65657000000000000011000000000000006400000000000a8000d60600000000000200000000000000030000000000000068656c6c6f20313820320000000000005400000000000580e8d906000000000002000000000000000300000000000000776f726c640000009400110000000180d0dd060000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000040514000000000000000007400050000000980b8e1060000000000020000000000000003000000000000007375622d736c6565700000000000000011000000000000007400070000000980a0e5060000000000020000000000000003000000000000007375622d736c656570000000000000001100000000000000b400240000000a8018d206000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a000000000000002100018012000000690000000000000088e90600000000006400000000000a8058f10600000000000200000000000000030000000000000068656c6c6f2031382033000000000000540000000000058040f506000000000002000000000000000300000000000000776f726c64000000940011000000018028f9060000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000080514000000000000000009400170000000a8010fd0600000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c65707400000011000000000000009400140000000a8070ed06000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180120000006900000000000000f8000700000000006400000000000a80c8080700000000000200000000000000030000000000000068656c6c6f20313820340000000000005400000000000580b00c07000000000002000000000000000300000000000000776f726c6400000094001100000001809810070000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000c0514000000000000000009400140000000a80e00407000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018012000000690000000000000080140700000000006400000000000a80501c0700000000000200000000000000030000000000000068656c6c6f20313820350000000000005400000000000580382007000000000002000000000000000300000000000000776f726c6400000094001100000001802024070000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000524000000000000000009400140000000a80681807000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018012000000690000000000000008280700000000007400040000000a8048ca060000000000020000000000000003000000000000006f757465722e6c6f6f70000000000000f02b0700000000007400050000000a80c0330700000000000200000000000000030000000000000066616b655f736c65657000000000000012000000000000006400000000000a80903b0700000000000200000000000000030000000000000068656c6c6f20313920320000000000005400000000000580783f07000000000002000000000000000300000000000000776f726c6400000094001100000001806043070000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000405240000000000000000074000500000009804847070000000000020000000000000003000000000000007375622d736c6565700000000000000012000000000000007400070000000980304b070000000000020000000000000003000000000000007375622d736c656570000000000000001200000000000000b400240000000a80a83707000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a0000000000000021000180130000006900000000000000184f0700000000006400000000000a80e8560700000000000200000000000000030000000000000068656c6c6f20313920330000000000005400000000000580d05a07000000000002000000000000000300000000000000776f726c640000009400110000000180b85e070000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000080524000000000000000009400170000000a80a0620700000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c65707400000012000000000000009400140000000a80005307000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018013000000690000000000000088660700000000006400000000000a80586e0700000000000200000000000000030000000000000068656c6c6f20313920340000000000005400000000000580407207000000000002000000000000000300000000000000776f726c6400000094001100000001802876070000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000c0524000000000000000009400140000000a80706a07000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180130000006900000000000000107a0700000000006400000000000a80e0810700000000000200000000000000030000000000000068656c6c6f20313920350000000000005400000000000580c88507000000000002000000000000000300000000000000776f726c640000009400110000000180b089070000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000534000000000000000009400140000000a80f87d07000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180130000006900000000000000988d0700000000007400040000000a80d82f070000000000020000000000000003000000000000006f757465722e6c6f6f7000000000000080910700000000007400050000000a8050990700000000000200000000000000030000000000000066616b655f736c65657000000000000013000000000000006400000000000a8020a10700000000000200000000000000030000000000000068656c6c6f2032302032000000000000540000000000058008a507000000000002000000000000000300000000000000776f726c640000009400110000000180f0a8070000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000040534000000000000000007400050000000980d8ac070000000000020000000000000003000000000000007375622d736c6565700000000000000013000000000000007400070000000980c0b0070000000000020000000000000003000000000000007375622d736c656570000000000000001300000000000000b400240000000a80389d07000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a0000000000000021000180140000006900000000000000a8b40700000000006400000000000a8078bc0700000000000200000000000000030000000000000068656c6c6f2032302033000000000000540000000000058060c007000000000002000000000000000300000000000000776f726c64000000940011000000018048c4070000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000080534000000000000000009400170000000a8030c80700000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c65707400000013000000000000009400140000000a8090b807000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018014000000690000000000000018cc0700000000006400000000000a80e8d30700000000000200000000000000030000000000000068656c6c6f20323020340000000000005400000000000580d0d707000000000002000000000000000300000000000000776f726c640000009400110000000180b8db070000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000c0534000000000000000009400140000000a8000d007000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180140000006900000000000000a0df0700000000006400000000000a8070e70700000000000200000000000000030000000000000068656c6c6f2032302035000000000000540000000000058058eb07000000000002000000000000000300000000000000776f726c64000000940011000000018040ef070000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000544000000000000000009400140000000a8088e307000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018014000000690000000000000028f30700000000007400040000000a806895070000000000020000000000000003000000000000006f757465722e6c6f6f7000000000000010f70700000000007400050000000a80e0fe0700000000000200000000000000030000000000000066616b655f736c65657000000000000014000000000000006400000000000a80b0060800000000000200000000000000030000000000000068656c6c6f20323120320000000000005400000000000580980a08000000000002000000000000000300000000000000776f726c640000009400110000000180800e080000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000405440000000000000000074000500000009806812080000000000020000000000000003000000000000007375622d736c65657000000000000000140000000000000074000700000009805016080000000000020000000000000003000000000000007375622d736c656570000000000000001400000000000000b400240000000a80c80208000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a0000000000000021000180150000006900000000000000381a0800000000006400000000000a8008220800000000000200000000000000030000000000000068656c6c6f20323120330000000000005400000000000580f02508000000000002000000000000000300000000000000776f726c640000009400110000000180d829080000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000080544000000000000000009400170000000a80c02d0800000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c65707400000014000000000000009400140000000a80201e08000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180150000006900000000000000a8310800000000006400000000000a8078390800000000000200000000000000030000000000000068656c6c6f20323120340000000000005400000000000580603d08000000000002000000000000000300000000000000776f726c6400000094001100000001804841080000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000c0544000000000000000009400140000000a80903508000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018015000000690000000000000030450800000000006400000000000a80004d0800000000000200000000000000030000000000000068656c6c6f20323120350000000000005400000000000580e85008000000000002000000000000000300000000000000776f726c640000009400110000000180d054080000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000554000000000000000009400140000000a80184908000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180150000006900000000000000b8580800000000007400040000000a80f8fa070000000000020000000000000003000000000000006f757465722e6c6f6f70000000000000a05c0800000000007400050000000a8070640800000000000200000000000000030000000000000066616b655f736c65657000000000000015000000000000006400000000000a80406c0800000000000200000000000000030000000000000068656c6c6f20323220320000000000005400000000000580287008000000000002000000000000000300000000000000776f726c6400000094001100000001801074080000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000040554000000000000000007400050000000980f877080000000000020000000000000003000000000000007375622d736c6565700000000000000015000000000000007400070000000980e07b080000000000020000000000000003000000000000007375622d736c656570000000000000001500000000000000b400240000000a80586808000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a0000000000000021000180160000006900000000000000c87f0800000000006400000000000a8098870800000000000200000000000000030000000000000068656c6c6f20323220330000000000005400000000000580808b08000000000002000000000000000300000000000000776f726c640000009400110000000180688f080000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000080554000000000000000009400170000000a8050930800000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c65707400000015000000000000009400140000000a80b08308000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018016000000690000000000000038970800000000006400000000000a80089f0800000000000200000000000000030000000000000068656c6c6f20323220340000000000005400000000000580f0a208000000000002000000000000000300000000000000776f726c640000009400110000000180d8a6080000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000c0554000000000000000009400140000000a80209b08000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180160000006900000000000000c0aa0800000000006400000000000a8090b20800000000000200000000000000030000000000000068656c6c6f2032322035000000000000540000000000058078b608000000000002000000000000000300000000000000776f726c64000000940011000000018060ba080000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000564000000000000000009400140000000a80a8ae08000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018016000000690000000000000048be0800000000007400040000000a808860080000000000020000000000000003000000000000006f757465722e6c6f6f7000000000000030c20800000000007400050000000a8000ca0800000000000200000000000000030000000000000066616b655f736c65657000000000000016000000000000006400000000000a80d0d10800000000000200000000000000030000000000000068656c6c6f20323320320000000000005400000000000580b8d508000000000002000000000000000300000000000000776f726c640000009400110000000180a0d9080000000000020000000000000003000000000000006e0000000000000035000180000000006e0000000000000000000000004056400000000000000000740005000000098088dd080000000000020000000000000003000000000000007375622d736c656570000000000000001600000000000000740007000000098070e1080000000000020000000000000003000000000000007375622d736c656570000000000000001600000000000000b400240000000a80e8cd08000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a000000000000002100018017000000690000000000000058e50800000000006400000000000a8028ed0800000000000200000000000000030000000000000068656c6c6f2032332033000000000000540000000000058010f108000000000002000000000000000300000000000000776f726c640000009400110000000180f8f4080000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000080564000000000000000009400170000000a80e0f80800000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c65707400000016000000000000009400140000000a8040e908000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180170000006900000000000000c8fc0800000000006400000000000a8098040900000000000200000000000000030000000000000068656c6c6f20323320340000000000005400000000000580800809000000000002000000000000000300000000000000776f726c640000009400110000000180680c090000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000c0564000000000000000009400140000000a80b00009000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018017000000690000000000000050100900000000006400000000000a8020180900000000000200000000000000030000000000000068656c6c6f20323320350000000000005400000000000580081c09000000000002000000000000000300000000000000776f726c640000009400110000000180f01f090000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000574000000000000000009400140000000a80381409000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180170000006900000000000000d8230900000000007400040000000a8018c6080000000000020000000000000003000000000000006f757465722e6c6f6f70000000000000c0270900000000007400050000000a80902f0900000000000200000000000000030000000000000066616b655f736c65657000000000000017000000000000006400000000000a8060370900000000000200000000000000030000000000000068656c6c6f20323420320000000000005400000000000580483b09000000000002000000000000000300000000000000776f726c640000009400110000000180303f090000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000405740000000000000000074000500000009801843090000000000020000000000000003000000000000007375622d736c65657000000000000000170000000000000074000700000009800047090000000000020000000000000003000000000000007375622d736c656570000000000000001700000000000000b400240000000a80783309000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a0000000000000021000180180000006900000000000000e84a0900000000006400000000000a80b8520900000000000200000000000000030000000000000068656c6c6f20323420330000000000005400000000000580a05609000000000002000000000000000300000000000000776f726c640000009400110000000180885a090000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000080574000000000000000009400170000000a80705e0900000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c65707400000017000000000000009400140000000a80d04e09000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018018000000690000000000000058620900000000006400000000000a80286a0900000000000200000000000000030000000000000068656c6c6f20323420340000000000005400000000000580106e09000000000002000000000000000300000000000000776f726c640000009400110000000180f871090000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000c0574000000000000000009400140000000a80406609000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180180000006900000000000000e0750900000000006400000000000a80b07d0900000000000200000000000000030000000000000068656c6c6f20323420350000000000005400000000000580988109000000000002000000000000000300000000000000776f726c6400000094001100000001808085090000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000584000000000000000009400140000000a80c87909000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018018000000690000000000000068890900000000007400040000000a80a82b090000000000020000000000000003000000000000006f757465722e6c6f6f70000000000000508d0900000000007400050000000a8020950900000000000200000000000000030000000000000066616b655f736c65657000000000000018000000000000006400000000000a80f09c0900000000000200000000000000030000000000000068656c6c6f20323520320000000000005400000000000580d8a009000000000002000000000000000300000000000000776f726c640000009400110000000180c0a4090000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000040584000000000000000007400050000000980a8a8090000000000020000000000000003000000000000007375622d736c656570000000000000001800000000000000740007000000098090ac090000000000020000000000000003000000000000007375622d736c656570000000000000001800000000000000b400240000000a80089909000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a000000000000002100018019000000690000000000000078b00900000000006400000000000a8048b80900000000000200000000000000030000000000000068656c6c6f2032352033000000000000540000000000058030bc09000000000002000000000000000300000000000000776f726c64000000940011000000018018c0090000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000080584000000000000000009400170000000a8000c40900000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c65707400000018000000000000009400140000000a8060b409000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180190000006900000000000000e8c70900000000006400000000000a80b8cf0900000000000200000000000000030000000000000068656c6c6f20323520340000000000005400000000000580a0d309000000000002000000000000000300000000000000776f726c64000000940011000000018088d7090000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000c0584000000000000000009400140000000a80d0cb09000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018019000000690000000000000070db0900000000006400000000000a8040e30900000000000200000000000000030000000000000068656c6c6f2032352035000000000000540000000000058028e709000000000002000000000000000300000000000000776f726c64000000940011000000018010eb090000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000594000000000000000009400140000000a8058df09000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180190000006900000000000000f8ee0900000000007400040000000a803891090000000000020000000000000003000000000000006f757465722e6c6f6f70000000000000e0f20900000000007400050000000a80b0fa0900000000000200000000000000030000000000000066616b655f736c65657000000000000019000000000000006400000000000a8080020a00000000000200000000000000030000000000000068656c6c6f2032362032000000000000540000000000058068060a000000000002000000000000000300000000000000776f726c640000009400110000000180500a0a0000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000040594000000000000000007400050000000980380e0a0000000000020000000000000003000000000000007375622d736c656570000000000000001900000000000000740007000000098020120a0000000000020000000000000003000000000000007375622d736c656570000000000000001900000000000000b400240000000a8098fe09000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a00000000000000210001801a000000690000000000000008160a00000000006400000000000a80d81d0a00000000000200000000000000030000000000000068656c6c6f20323620330000000000005400000000000580c0210a000000000002000000000000000300000000000000776f726c640000009400110000000180a8250a0000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000080594000000000000000009400170000000a8090290a00000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c65707400000019000000000000009400140000000a80f0190a000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001801a0000006900000000000000782d0a00000000006400000000000a8048350a00000000000200000000000000030000000000000068656c6c6f2032362034000000000000540000000000058030390a000000000002000000000000000300000000000000776f726c640000009400110000000180183d0a0000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000c0594000000000000000009400140000000a8060310a000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001801a000000690000000000000000410a00000000006400000000000a80d0480a00000000000200000000000000030000000000000068656c6c6f20323620350000000000005400000000000580b84c0a000000000002000000000000000300000000000000776f726c640000009400110000000180a0500a0000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000005a4000000000000000009400140000000a80e8440a000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001801a000000690000000000000088540a00000000007400040000000a80c8f6090000000000020000000000000003000000000000006f757465722e6c6f6f7000000000000070580a00000000007400050000000a8040600a00000000000200000000000000030000000000000066616b655f736c6565700000000000001a000000000000006400000000000a8010680a00000000000200000000000000030000000000000068656c6c6f20323720320000000000005400000000000580f86b0a000000000002000000000000000300000000000000776f726c640000009400110000000180e06f0a0000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000405a4000000000000000007400050000000980c8730a0000000000020000000000000003000000000000007375622d736c656570000000000000001a000000000000007400070000000980b0770a0000000000020000000000000003000000000000007375622d736c656570000000000000001a00000000000000b400240000000a8028640a000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a00000000000000210001801b0000006900000000000000987b0a00000000006400000000000a8068830a00000000000200000000000000030000000000000068656c6c6f2032372033000000000000540000000000058050870a000000000002000000000000000300000000000000776f726c640000009400110000000180388b0a0000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000805a4000000000000000009400170000000a80208f0a00000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c6570740000001a000000000000009400140000000a80807f0a000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001801b000000690000000000000008930a00000000006400000000000a80d89a0a00000000000200000000000000030000000000000068656c6c6f20323720340000000000005400000000000580c09e0a000000000002000000000000000300000000000000776f726c640000009400110000000180a8a20a0000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000c05a4000000000000000009400140000000a80f0960a000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001801b000000690000000000000090a60a00000000006400000000000a8060ae0a00000000000200000000000000030000000000000068656c6c6f2032372035000000000000540000000000058048b20a000000000002000000000000000300000000000000776f726c64000000940011000000018030b60a0000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000005b4000000000000000009400140000000a8078aa0a000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001801b000000690000000000000018ba0a00000000007400040000000a80585c0a0000000000020000000000000003000000000000006f757465722e6c6f6f7000000000000000be0a00000000007400050000000a80d0c50a00000000000200000000000000030000000000000066616b655f736c6565700000000000001b000000000000006400000000000a80a0cd0a00000000000200000000000000030000000000000068656c6c6f2032382032000000000000540000000000058088d10a000000000002000000000000000300000000000000776f726c64000000940011000000018070d50a0000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000405b400000000000000000740005000000098058d90a0000000000020000000000000003000000000000007375622d736c656570000000000000001b00000000000000740007000000098040dd0a0000000000020000000000000003000000000000007375622d736c656570000000000000001b00000000000000b400240000000a80b8c90a000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a00000000000000210001801c000000690000000000000028e10a00000000006400000000000a80f8e80a00000000000200000000000000030000000000000068656c6c6f20323820330000000000005400000000000580e0ec0a000000000002000000000000000300000000000000776f726c640000009400110000000180c8f00a0000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000805b4000000000000000009400170000000a80b0f40a00000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c6570740000001b000000000000009400140000000a8010e50a000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001801c000000690000000000000098f80a00000000006400000000000a8068000b00000000000200000000000000030000000000000068656c6c6f2032382034000000000000540000000000058050040b000000000002000000000000000300000000000000776f726c64000000940011000000018038080b0000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000c05b4000000000000000009400140000000a8080fc0a000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001801c0000006900000000000000200c0b00000000006400000000000a80f0130b00000000000200000000000000030000000000000068656c6c6f20323820350000000000005400000000000580d8170b000000000002000000000000000300000000000000776f726c640000009400110000000180c01b0b0000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000005c4000000000000000009400140000000a8008100b000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001801c0000006900000000000000a81f0b00000000007400040000000a80e8c10a0000000000020000000000000003000000000000006f757465722e6c6f6f7000000000000090230b00000000007400050000000a80602b0b00000000000200000000000000030000000000000066616b655f736c6565700000000000001c000000000000006400000000000a8030330b00000000000200000000000000030000000000000068656c6c6f2032392032000000000000540000000000058018370b000000000002000000000000000300000000000000776f726c640000009400110000000180003b0b0000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000405c4000000000000000007400050000000980e83e0b0000000000020000000000000003000000000000007375622d736c656570000000000000001c000000000000007400070000000980d0420b0000000000020000000000000003000000000000007375622d736c656570000000000000001c00000000000000b400240000000a80482f0b000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a00000000000000210001801d0000006900000000000000b8460b00000000006400000000000a80884e0b00000000000200000000000000030000000000000068656c6c6f2032392033000000000000540000000000058070520b000000000002000000000000000300000000000000776f726c64000000940011000000018058560b0000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000805c4000000000000000009400170000000a80405a0b00000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c6570740000001c000000000000009400140000000a80a04a0b000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001801d0000006900000000000000285e0b00000000006400000000000a80f8650b00000000000200000000000000030000000000000068656c6c6f20323920340000000000005400000000000580e0690b000000000002000000000000000300000000000000776f726c640000009400110000000180c86d0b0000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000c05c4000000000000000009400140000000a8010620b000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001801d0000006900000000000000b0710b00000000006400000000000a8080790b00000000000200000000000000030000000000000068656c6c6f20323920350000000000005400000000000580687d0b000000000002000000000000000300000000000000776f726c64000000940011000000018050810b0000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000005d4000000000000000009400140000000a8098750b000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001801d000000690000000000000038850b00000000007400040000000a8078270b0000000000020000000000000003000000000000006f757465722e6c6f6f7000000000000020890b00000000007400050000000a80f0900b00000000000200000000000000030000000000000066616b655f736c6565700000000000001d000000000000006400000000000a80c0980b00000000000200000000000000030000000000000068656c6c6f20333020320000000000005400000000000580a89c0b000000000002000000000000000300000000000000776f726c64000000940011000000018090a00b0000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000405d400000000000000000740005000000098078a40b0000000000020000000000000003000000000000007375622d736c656570000000000000001d00000000000000740007000000098060a80b0000000000020000000000000003000000000000007375622d736c656570000000000000001d00000000000000b400240000000a80d8940b000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a00000000000000210001801e000000690000000000000048ac0b00000000006400000000000a8018b40b00000000000200000000000000030000000000000068656c6c6f2033302033000000000000540000000000058000b80b000000000002000000000000000300000000000000776f726c640000009400110000000180e8bb0b0000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000805d4000000000000000009400170000000a80d0bf0b00000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c6570740000001d000000000000009400140000000a8030b00b000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001801e0000006900000000000000b8c30b00000000006400000000000a8088cb0b00000000000200000000000000030000000000000068656c6c6f2033302034000000000000540000000000058070cf0b000000000002000000000000000300000000000000776f726c64000000940011000000018058d30b0000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000c05d4000000000000000009400140000000a80a0c70b000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001801e000000690000000000000040d70b00000000006400000000000a8010df0b00000000000200000000000000030000000000000068656c6c6f20333020350000000000005400000000000580f8e20b000000000002000000000000000300000000000000776f726c640000009400110000000180e0e60b0000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000005e4000000000000000009400140000000a8028db0b000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001801e0000006900000000000000c8ea0b00000000007400040000000a80088d0b0000000000020000000000000003000000000000006f757465722e6c6f6f70000000000000b0ee0b00000000007400050000000a8080f60b00000000000200000000000000030000000000000066616b655f736c6565700000000000001e000000000000006400000000000a8050fe0b00000000000200000000000000030000000000000068656c6c6f2033312032000000000000540000000000058038020c000000000002000000000000000300000000000000776f726c64000000940011000000018020060c0000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000405e4000000000000000007400050000000980080a0c0000000000020000000000000003000000000000007375622d736c656570000000000000001e000000000000007400070000000980f00d0c0000000000020000000000000003000000000000007375622d736c656570000000000000001e00000000000000b400240000000a8068fa0b000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a00000000000000210001801f0000006900000000000000d8110c00000000006400000000000a80a8190c00000000000200000000000000030000000000000068656c6c6f20333120330000000000005400000000000580901d0c000000000002000000000000000300000000000000776f726c64000000940011000000018078210c0000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000805e4000000000000000009400170000000a8060250c00000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c6570740000001e000000000000009400140000000a80c0150c000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001801f000000690000000000000048290c00000000006400000000000a8018310c00000000000200000000000000030000000000000068656c6c6f2033312034000000000000540000000000058000350c000000000002000000000000000300000000000000776f726c640000009400110000000180e8380c0000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000c05e4000000000000000009400140000000a80302d0c000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001801f0000006900000000000000d03c0c00000000006400000000000a80a0440c00000000000200000000000000030000000000000068656c6c6f2033312035000000000000540000000000058088480c000000000002000000000000000300000000000000776f726c640000009400110000000180704c0c0000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000005f4000000000000000009400140000000a80b8400c000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001801f000000690000000000000058500c00000000007400040000000a8098f20b0000000000020000000000000003000000000000006f757465722e6c6f6f7000000000000040540c00000000007400050000000a80105c0c00000000000200000000000000030000000000000066616b655f736c6565700000000000001f000000000000006400000000000a80e0630c00000000000200000000000000030000000000000068656c6c6f20333220320000000000005400000000000580c8670c000000000002000000000000000300000000000000776f726c640000009400110000000180b06b0c0000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000405f4000000000000000007400050000000980986f0c0000000000020000000000000003000000000000007375622d736c656570000000000000001f00000000000000740007000000098080730c0000000000020000000000000003000000000000007375622d736c656570000000000000001f00000000000000b400240000000a80f85f0c000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a000000000000002100018020000000690000000000000068770c00000000006400000000000a80387f0c00000000000200000000000000030000000000000068656c6c6f2033322033000000000000540000000000058020830c000000000002000000000000000300000000000000776f726c64000000940011000000018008870c0000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000805f4000000000000000009400170000000a80f08a0c00000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c6570740000001f000000000000009400140000000a80507b0c000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180200000006900000000000000d88e0c00000000006400000000000a80a8960c00000000000200000000000000030000000000000068656c6c6f20333220340000000000005400000000000580909a0c000000000002000000000000000300000000000000776f726c640000009400110000000180789e0c0000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000c05f4000000000000000009400140000000a80c0920c000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018020000000690000000000000060a20c00000000006400000000000a8030aa0c00000000000200000000000000030000000000000068656c6c6f2033322035000000000000540000000000058018ae0c000000000002000000000000000300000000000000776f726c64000000940011000000018000b20c0000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000604000000000000000009400140000000a8048a60c000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180200000006900000000000000e8b50c00000000007400040000000a8028580c0000000000020000000000000003000000000000006f757465722e6c6f6f70000000000000d0b90c00000000007400050000000a80a0c10c00000000000200000000000000030000000000000066616b655f736c65657000000000000020000000000000006400000000000a8070c90c00000000000200000000000000030000000000000068656c6c6f2033332032000000000000540000000000058058cd0c000000000002000000000000000300000000000000776f726c64000000940011000000018040d10c0000000000020000000000000003000000000000006e0000000000000035000180000000006e0000000000000000000000002060400000000000000000740005000000098028d50c0000000000020000000000000003000000000000007375622d736c656570000000000000002000000000000000740007000000098010d90c0000000000020000000000000003000000000000007375622d736c656570000000000000002000000000000000b400240000000a8088c50c000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a0000000000000021000180210000006900000000000000f8dc0c00000000006400000000000a80c8e40c00000000000200000000000000030000000000000068656c6c6f20333320330000000000005400000000000580b0e80c000000000002000000000000000300000000000000776f726c64000000940011000000018098ec0c0000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000040604000000000000000009400170000000a8080f00c00000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c65707400000020000000000000009400140000000a80e0e00c000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018021000000690000000000000068f40c00000000006400000000000a8038fc0c00000000000200000000000000030000000000000068656c6c6f2033332034000000000000540000000000058020000d000000000002000000000000000300000000000000776f726c64000000940011000000018008040d0000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000060604000000000000000009400140000000a8050f80c000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180210000006900000000000000f0070d00000000006400000000000a80c00f0d00000000000200000000000000030000000000000068656c6c6f20333320350000000000005400000000000580a8130d000000000002000000000000000300000000000000776f726c64000000940011000000018090170d0000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000080604000000000000000009400140000000a80d80b0d000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180210000006900000000000000781b0d00000000007400040000000a80b8bd0c0000000000020000000000000003000000000000006f757465722e6c6f6f70000000000000601f0d00000000007400050000000a8030270d00000000000200000000000000030000000000000066616b655f736c65657000000000000021000000000000006400000000000a80002f0d00000000000200000000000000030000000000000068656c6c6f20333420320000000000005400000000000580e8320d000000000002000000000000000300000000000000776f726c640000009400110000000180d0360d0000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000a0604000000000000000007400050000000980b83a0d0000000000020000000000000003000000000000007375622d736c6565700000000000000021000000000000007400070000000980a03e0d0000000000020000000000000003000000000000007375622d736c656570000000000000002100000000000000b400240000000a80182b0d000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a000000000000002100018022000000690000000000000088420d00000000006400000000000a80584a0d00000000000200000000000000030000000000000068656c6c6f20333420330000000000005400000000000580404e0d000000000002000000000000000300000000000000776f726c64000000940011000000018028520d0000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000c0604000000000000000009400170000000a8010560d00000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c65707400000021000000000000009400140000000a8070460d000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180220000006900000000000000f8590d00000000006400000000000a80c8610d00000000000200000000000000030000000000000068656c6c6f20333420340000000000005400000000000580b0650d000000000002000000000000000300000000000000776f726c64000000940011000000018098690d0000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000e0604000000000000000009400140000000a80e05d0d000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180220000006900000000000000806d0d00000000006400000000000a8050750d00000000000200000000000000030000000000000068656c6c6f2033342035000000000000540000000000058038790d000000000002000000000000000300000000000000776f726c640000009400110000000180207d0d0000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000614000000000000000009400140000000a8068710d000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018022000000690000000000000008810d00000000007400040000000a8048230d0000000000020000000000000003000000000000006f757465722e6c6f6f70000000000000f0840d00000000007400050000000a80c08c0d00000000000200000000000000030000000000000066616b655f736c65657000000000000022000000000000006400000000000a8090940d00000000000200000000000000030000000000000068656c6c6f2033352032000000000000540000000000058078980d000000000002000000000000000300000000000000776f726c640000009400110000000180609c0d0000000000020000000000000003000000000000006e0000000000000035000180000000006e0000000000000000000000002061400000000000000000740005000000098048a00d0000000000020000000000000003000000000000007375622d736c656570000000000000002200000000000000740007000000098030a40d0000000000020000000000000003000000000000007375622d736c656570000000000000002200000000000000b400240000000a80a8900d000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a000000000000002100018023000000690000000000000018a80d00000000006400000000000a80e8af0d00000000000200000000000000030000000000000068656c6c6f20333520330000000000005400000000000580d0b30d000000000002000000000000000300000000000000776f726c640000009400110000000180b8b70d0000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000040614000000000000000009400170000000a80a0bb0d00000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c65707400000022000000000000009400140000000a8000ac0d000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018023000000690000000000000088bf0d00000000006400000000000a8058c70d00000000000200000000000000030000000000000068656c6c6f2033352034000000000000540000000000058040cb0d000000000002000000000000000300000000000000776f726c64000000940011000000018028cf0d0000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000060614000000000000000009400140000000a8070c30d000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018023000000690000000000000010d30d00000000006400000000000a80e0da0d00000000000200000000000000030000000000000068656c6c6f20333520350000000000005400000000000580c8de0d000000000002000000000000000300000000000000776f726c640000009400110000000180b0e20d0000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000080614000000000000000009400140000000a80f8d60d000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018023000000690000000000000098e60d00000000007400040000000a80d8880d0000000000020000000000000003000000000000006f757465722e6c6f6f7000000000000080ea0d00000000007400050000000a8050f20d00000000000200000000000000030000000000000066616b655f736c65657000000000000023000000000000006400000000000a8020fa0d00000000000200000000000000030000000000000068656c6c6f2033362032000000000000540000000000058008fe0d000000000002000000000000000300000000000000776f726c640000009400110000000180f0010e0000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000a0614000000000000000007400050000000980d8050e0000000000020000000000000003000000000000007375622d736c6565700000000000000023000000000000007400070000000980c0090e0000000000020000000000000003000000000000007375622d736c656570000000000000002300000000000000b400240000000a8038f60d000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a0000000000000021000180240000006900000000000000a80d0e00000000006400000000000a8078150e00000000000200000000000000030000000000000068656c6c6f2033362033000000000000540000000000058060190e000000000002000000000000000300000000000000776f726c640000009400110000000180481d0e0000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000c0614000000000000000009400170000000a8030210e00000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c65707400000023000000000000009400140000000a8090110e000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018024000000690000000000000018250e00000000006400000000000a80e82c0e00000000000200000000000000030000000000000068656c6c6f20333620340000000000005400000000000580d0300e000000000002000000000000000300000000000000776f726c640000009400110000000180b8340e0000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000e0614000000000000000009400140000000a8000290e000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180240000006900000000000000a0380e00000000006400000000000a8070400e00000000000200000000000000030000000000000068656c6c6f2033362035000000000000540000000000058058440e000000000002000000000000000300000000000000776f726c64000000940011000000018040480e0000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000624000000000000000009400140000000a80883c0e000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180240000006900000000000000284c0e00000000007400040000000a8068ee0d0000000000020000000000000003000000000000006f757465722e6c6f6f7000000000000010500e00000000007400050000000a80e0570e00000000000200000000000000030000000000000066616b655f736c65657000000000000024000000000000006400000000000a80b05f0e00000000000200000000000000030000000000000068656c6c6f2033372032000000000000540000000000058098630e000000000002000000000000000300000000000000776f726c64000000940011000000018080670e0000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000020624000000000000000007400050000000980686b0e0000000000020000000000000003000000000000007375622d736c6565700000000000000024000000000000007400070000000980506f0e0000000000020000000000000003000000000000007375622d736c656570000000000000002400000000000000b400240000000a80c85b0e000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a000000000000002100018025000000690000000000000038730e00000000006400000000000a80087b0e00000000000200000000000000030000000000000068656c6c6f20333720330000000000005400000000000580f07e0e000000000002000000000000000300000000000000776f726c640000009400110000000180d8820e0000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000040624000000000000000009400170000000a80c0860e00000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c65707400000024000000000000009400140000000a8020770e000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180250000006900000000000000a88a0e00000000006400000000000a8078920e00000000000200000000000000030000000000000068656c6c6f2033372034000000000000540000000000058060960e000000000002000000000000000300000000000000776f726c640000009400110000000180489a0e0000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000060624000000000000000009400140000000a80908e0e000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180250000006900000000000000309e0e00000000006400000000000a8000a60e00000000000200000000000000030000000000000068656c6c6f20333720350000000000005400000000000580e8a90e000000000002000000000000000300000000000000776f726c640000009400110000000180d0ad0e0000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000080624000000000000000009400140000000a8018a20e000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180250000006900000000000000b8b10e00000000007400040000000a80f8530e0000000000020000000000000003000000000000006f757465722e6c6f6f70000000000000a0b50e00000000007400050000000a8070bd0e00000000000200000000000000030000000000000066616b655f736c65657000000000000025000000000000006400000000000a8040c50e00000000000200000000000000030000000000000068656c6c6f2033382032000000000000540000000000058028c90e000000000002000000000000000300000000000000776f726c64000000940011000000018010cd0e0000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000a0624000000000000000007400050000000980f8d00e0000000000020000000000000003000000000000007375622d736c6565700000000000000025000000000000007400070000000980e0d40e0000000000020000000000000003000000000000007375622d736c656570000000000000002500000000000000b400240000000a8058c10e000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a0000000000000021000180260000006900000000000000c8d80e00000000006400000000000a8098e00e00000000000200000000000000030000000000000068656c6c6f2033382033000000000000540000000000058080e40e000000000002000000000000000300000000000000776f726c64000000940011000000018068e80e0000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000c0624000000000000000009400170000000a8050ec0e00000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c65707400000025000000000000009400140000000a80b0dc0e000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018026000000690000000000000038f00e00000000006400000000000a8008f80e00000000000200000000000000030000000000000068656c6c6f20333820340000000000005400000000000580f0fb0e000000000002000000000000000300000000000000776f726c640000009400110000000180d8ff0e0000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000e0624000000000000000009400140000000a8020f40e000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180260000006900000000000000c0030f00000000006400000000000a80900b0f00000000000200000000000000030000000000000068656c6c6f20333820350000000000005400000000000580780f0f000000000002000000000000000300000000000000776f726c64000000940011000000018060130f0000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000634000000000000000009400140000000a80a8070f000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018026000000690000000000000048170f00000000007400040000000a8088b90e0000000000020000000000000003000000000000006f757465722e6c6f6f70000000000000301b0f00000000007400050000000a8000230f00000000000200000000000000030000000000000066616b655f736c65657000000000000026000000000000006400000000000a80d02a0f00000000000200000000000000030000000000000068656c6c6f20333920320000000000005400000000000580b82e0f000000000002000000000000000300000000000000776f726c640000009400110000000180a0320f0000000000020000000000000003000000000000006e0000000000000035000180000000006e0000000000000000000000002063400000000000000000740005000000098088360f0000000000020000000000000003000000000000007375622d736c6565700000000000000026000000000000007400070000000980703a0f0000000000020000000000000003000000000000007375622d736c656570000000000000002600000000000000b400240000000a80e8260f000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a0000000000000021000180270000006900000000000000583e0f00000000006400000000000a8028460f00000000000200000000000000030000000000000068656c6c6f20333920330000000000005400000000000580104a0f000000000002000000000000000300000000000000776f726c640000009400110000000180f84d0f0000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000040634000000000000000009400170000000a80e0510f00000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c65707400000026000000000000009400140000000a8040420f000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180270000006900000000000000c8550f00000000006400000000000a80985d0f00000000000200000000000000030000000000000068656c6c6f2033392034000000000000540000000000058080610f000000000002000000000000000300000000000000776f726c64000000940011000000018068650f0000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000060634000000000000000009400140000000a80b0590f000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018027000000690000000000000050690f00000000006400000000000a8020710f00000000000200000000000000030000000000000068656c6c6f2033392035000000000000540000000000058008750f000000000002000000000000000300000000000000776f726c640000009400110000000180f0780f0000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000080634000000000000000009400140000000a80386d0f000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180270000006900000000000000d87c0f00000000007400040000000a80181f0f0000000000020000000000000003000000000000006f757465722e6c6f6f70000000000000c0800f00000000007400050000000a8090880f00000000000200000000000000030000000000000066616b655f736c65657000000000000027000000000000006400000000000a8060900f00000000000200000000000000030000000000000068656c6c6f2034302032000000000000540000000000058048940f000000000002000000000000000300000000000000776f726c64000000940011000000018030980f0000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000a0634000000000000000007400050000000980189c0f0000000000020000000000000003000000000000007375622d736c656570000000000000002700000000000000740007000000098000a00f0000000000020000000000000003000000000000007375622d736c656570000000000000002700000000000000b400240000000a80788c0f000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a0000000000000021000180280000006900000000000000e8a30f00000000006400000000000a80b8ab0f00000000000200000000000000030000000000000068656c6c6f20343020330000000000005400000000000580a0af0f000000000002000000000000000300000000000000776f726c64000000940011000000018088b30f0000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000c0634000000000000000009400170000000a8070b70f00000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c65707400000027000000000000009400140000000a80d0a70f000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018028000000690000000000000058bb0f00000000006400000000000a8028c30f00000000000200000000000000030000000000000068656c6c6f2034302034000000000000540000000000058010c70f000000000002000000000000000300000000000000776f726c640000009400110000000180f8ca0f0000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000e0634000000000000000009400140000000a8040bf0f000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180280000006900000000000000e0ce0f00000000006400000000000a80b0d60f00000000000200000000000000030000000000000068656c6c6f2034302035000000000000540000000000058098da0f000000000002000000000000000300000000000000776f726c64000000940011000000018080de0f0000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000644000000000000000009400140000000a80c8d20f000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018028000000690000000000000068e20f00000000007400040000000a80a8840f0000000000020000000000000003000000000000006f757465722e6c6f6f7000000000000050e60f00000000007400050000000a8020ee0f00000000000200000000000000030000000000000066616b655f736c65657000000000000028000000000000006400000000000a80f0f50f00000000000200000000000000030000000000000068656c6c6f20343120320000000000005400000000000580d8f90f000000000002000000000000000300000000000000776f726c640000009400110000000180c0fd0f0000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000020644000000000000000007400050000000980a801100000000000020000000000000003000000000000007375622d736c65657000000000000000280000000000000074000700000009809005100000000000020000000000000003000000000000007375622d736c656570000000000000002800000000000000b400240000000a8008f20f000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a000000000000002100018029000000690000000000000078091000000000006400000000000a8048111000000000000200000000000000030000000000000068656c6c6f20343120330000000000005400000000000580301510000000000002000000000000000300000000000000776f726c6400000094001100000001801819100000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000040644000000000000000009400170000000a80001d1000000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c65707400000028000000000000009400140000000a80600d10000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180290000006900000000000000e8201000000000006400000000000a80b8281000000000000200000000000000030000000000000068656c6c6f20343120340000000000005400000000000580a02c10000000000002000000000000000300000000000000776f726c6400000094001100000001808830100000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000060644000000000000000009400140000000a80d02410000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018029000000690000000000000070341000000000006400000000000a80403c1000000000000200000000000000030000000000000068656c6c6f20343120350000000000005400000000000580284010000000000002000000000000000300000000000000776f726c6400000094001100000001801044100000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000080644000000000000000009400140000000a80583810000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180290000006900000000000000f8471000000000007400040000000a8038ea0f0000000000020000000000000003000000000000006f757465722e6c6f6f70000000000000e04b1000000000007400050000000a80b0531000000000000200000000000000030000000000000066616b655f736c65657000000000000029000000000000006400000000000a80805b1000000000000200000000000000030000000000000068656c6c6f20343220320000000000005400000000000580685f10000000000002000000000000000300000000000000776f726c6400000094001100000001805063100000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000a06440000000000000000074000500000009803867100000000000020000000000000003000000000000007375622d736c6565700000000000000029000000000000007400070000000980206b100000000000020000000000000003000000000000007375622d736c656570000000000000002900000000000000b400240000000a80985710000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a00000000000000210001802a0000006900000000000000086f1000000000006400000000000a80d8761000000000000200000000000000030000000000000068656c6c6f20343220330000000000005400000000000580c07a10000000000002000000000000000300000000000000776f726c640000009400110000000180a87e100000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000c0644000000000000000009400170000000a8090821000000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c65707400000029000000000000009400140000000a80f07210000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001802a000000690000000000000078861000000000006400000000000a80488e1000000000000200000000000000030000000000000068656c6c6f20343220340000000000005400000000000580309210000000000002000000000000000300000000000000776f726c6400000094001100000001801896100000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000e0644000000000000000009400140000000a80608a10000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001802a0000006900000000000000009a1000000000006400000000000a80d0a11000000000000200000000000000030000000000000068656c6c6f20343220350000000000005400000000000580b8a510000000000002000000000000000300000000000000776f726c640000009400110000000180a0a9100000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000654000000000000000009400140000000a80e89d10000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001802a000000690000000000000088ad1000000000007400040000000a80c84f100000000000020000000000000003000000000000006f757465722e6c6f6f7000000000000070b11000000000007400050000000a8040b91000000000000200000000000000030000000000000066616b655f736c6565700000000000002a000000000000006400000000000a8010c11000000000000200000000000000030000000000000068656c6c6f20343320320000000000005400000000000580f8c410000000000002000000000000000300000000000000776f726c640000009400110000000180e0c8100000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000020654000000000000000007400050000000980c8cc100000000000020000000000000003000000000000007375622d736c656570000000000000002a000000000000007400070000000980b0d0100000000000020000000000000003000000000000007375622d736c656570000000000000002a00000000000000b400240000000a8028bd10000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a00000000000000210001802b000000690000000000000098d41000000000006400000000000a8068dc1000000000000200000000000000030000000000000068656c6c6f2034332033000000000000540000000000058050e010000000000002000000000000000300000000000000776f726c64000000940011000000018038e4100000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000040654000000000000000009400170000000a8020e81000000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c6570740000002a000000000000009400140000000a8080d810000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001802b000000690000000000000008ec1000000000006400000000000a80d8f31000000000000200000000000000030000000000000068656c6c6f20343320340000000000005400000000000580c0f710000000000002000000000000000300000000000000776f726c640000009400110000000180a8fb100000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000060654000000000000000009400140000000a80f0ef10000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001802b000000690000000000000090ff1000000000006400000000000a8060071100000000000200000000000000030000000000000068656c6c6f20343320350000000000005400000000000580480b11000000000002000000000000000300000000000000776f726c640000009400110000000180300f110000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000080654000000000000000009400140000000a80780311000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001802b000000690000000000000018131100000000007400040000000a8058b5100000000000020000000000000003000000000000006f757465722e6c6f6f7000000000000000171100000000007400050000000a80d01e1100000000000200000000000000030000000000000066616b655f736c6565700000000000002b000000000000006400000000000a80a0261100000000000200000000000000030000000000000068656c6c6f20343420320000000000005400000000000580882a11000000000002000000000000000300000000000000776f726c640000009400110000000180702e110000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000a06540000000000000000074000500000009805832110000000000020000000000000003000000000000007375622d736c656570000000000000002b0000000000000074000700000009804036110000000000020000000000000003000000000000007375622d736c656570000000000000002b00000000000000b400240000000a80b82211000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a00000000000000210001802c0000006900000000000000283a1100000000006400000000000a80f8411100000000000200000000000000030000000000000068656c6c6f20343420330000000000005400000000000580e04511000000000002000000000000000300000000000000776f726c640000009400110000000180c849110000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000c0654000000000000000009400170000000a80b04d1100000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c6570740000002b000000000000009400140000000a80103e11000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001802c000000690000000000000098511100000000006400000000000a8068591100000000000200000000000000030000000000000068656c6c6f20343420340000000000005400000000000580505d11000000000002000000000000000300000000000000776f726c6400000094001100000001803861110000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000e0654000000000000000009400140000000a80805511000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001802c000000690000000000000020651100000000006400000000000a80f06c1100000000000200000000000000030000000000000068656c6c6f20343420350000000000005400000000000580d87011000000000002000000000000000300000000000000776f726c640000009400110000000180c074110000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000664000000000000000009400140000000a80086911000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001802c0000006900000000000000a8781100000000007400040000000a80e81a110000000000020000000000000003000000000000006f757465722e6c6f6f70000000000000907c1100000000007400050000000a8060841100000000000200000000000000030000000000000066616b655f736c6565700000000000002c000000000000006400000000000a80308c1100000000000200000000000000030000000000000068656c6c6f20343520320000000000005400000000000580189011000000000002000000000000000300000000000000776f726c6400000094001100000001800094110000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000020664000000000000000007400050000000980e897110000000000020000000000000003000000000000007375622d736c656570000000000000002c000000000000007400070000000980d09b110000000000020000000000000003000000000000007375622d736c656570000000000000002c00000000000000b400240000000a80488811000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a00000000000000210001802d0000006900000000000000b89f1100000000006400000000000a8088a71100000000000200000000000000030000000000000068656c6c6f2034352033000000000000540000000000058070ab11000000000002000000000000000300000000000000776f726c64000000940011000000018058af110000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000040664000000000000000009400170000000a8040b31100000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c6570740000002c000000000000009400140000000a80a0a311000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001802d000000690000000000000028b71100000000006400000000000a80f8be1100000000000200000000000000030000000000000068656c6c6f20343520340000000000005400000000000580e0c211000000000002000000000000000300000000000000776f726c640000009400110000000180c8c6110000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000060664000000000000000009400140000000a8010bb11000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001802d0000006900000000000000b0ca1100000000006400000000000a8080d21100000000000200000000000000030000000000000068656c6c6f2034352035000000000000540000000000058068d611000000000002000000000000000300000000000000776f726c64000000940011000000018050da110000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000080664000000000000000009400140000000a8098ce11000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001802d000000690000000000000038de1100000000007400040000000a807880110000000000020000000000000003000000000000006f757465722e6c6f6f7000000000000020e21100000000007400050000000a80f0e91100000000000200000000000000030000000000000066616b655f736c6565700000000000002d000000000000006400000000000a80c0f11100000000000200000000000000030000000000000068656c6c6f20343620320000000000005400000000000580a8f511000000000002000000000000000300000000000000776f726c64000000940011000000018090f9110000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000a066400000000000000000740005000000098078fd110000000000020000000000000003000000000000007375622d736c656570000000000000002d0000000000000074000700000009806001120000000000020000000000000003000000000000007375622d736c656570000000000000002d00000000000000b400240000000a80d8ed11000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a00000000000000210001802e000000690000000000000048051200000000006400000000000a80180d1200000000000200000000000000030000000000000068656c6c6f20343620330000000000005400000000000580001112000000000002000000000000000300000000000000776f726c640000009400110000000180e814120000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000c0664000000000000000009400170000000a80d0181200000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c6570740000002d000000000000009400140000000a80300912000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001802e0000006900000000000000b81c1200000000006400000000000a8088241200000000000200000000000000030000000000000068656c6c6f20343620340000000000005400000000000580702812000000000002000000000000000300000000000000776f726c640000009400110000000180582c120000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000e0664000000000000000009400140000000a80a02012000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001802e000000690000000000000040301200000000006400000000000a8010381200000000000200000000000000030000000000000068656c6c6f20343620350000000000005400000000000580f83b12000000000002000000000000000300000000000000776f726c640000009400110000000180e03f120000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000674000000000000000009400140000000a80283412000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001802e0000006900000000000000c8431200000000007400040000000a8008e6110000000000020000000000000003000000000000006f757465722e6c6f6f70000000000000b0471200000000007400050000000a80804f1200000000000200000000000000030000000000000066616b655f736c6565700000000000002e000000000000006400000000000a8050571200000000000200000000000000030000000000000068656c6c6f20343720320000000000005400000000000580385b12000000000002000000000000000300000000000000776f726c640000009400110000000180205f120000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000206740000000000000000074000500000009800863120000000000020000000000000003000000000000007375622d736c656570000000000000002e000000000000007400070000000980f066120000000000020000000000000003000000000000007375622d736c656570000000000000002e00000000000000b400240000000a80685312000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a00000000000000210001802f0000006900000000000000d86a1200000000006400000000000a80a8721200000000000200000000000000030000000000000068656c6c6f20343720330000000000005400000000000580907612000000000002000000000000000300000000000000776f726c640000009400110000000180787a120000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000040674000000000000000009400170000000a80607e1200000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c6570740000002e000000000000009400140000000a80c06e12000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001802f000000690000000000000048821200000000006400000000000a80188a1200000000000200000000000000030000000000000068656c6c6f20343720340000000000005400000000000580008e12000000000002000000000000000300000000000000776f726c640000009400110000000180e891120000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000060674000000000000000009400140000000a80308612000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001802f0000006900000000000000d0951200000000006400000000000a80a09d1200000000000200000000000000030000000000000068656c6c6f2034372035000000000000540000000000058088a112000000000002000000000000000300000000000000776f726c64000000940011000000018070a5120000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000080674000000000000000009400140000000a80b89912000000000002000000000000000300000000000000696e6e65722e6c6f6f70000000000000210001802f000000690000000000000058a91200000000007400040000000a80984b120000000000020000000000000003000000000000006f757465722e6c6f6f7000000000000040ad1200000000007400050000000a8010b51200000000000200000000000000030000000000000066616b655f736c6565700000000000002f000000000000006400000000000a80e0bc1200000000000200000000000000030000000000000068656c6c6f20343820320000000000005400000000000580c8c012000000000002000000000000000300000000000000776f726c640000009400110000000180b0c4120000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000a067400000000000000000740005000000098098c8120000000000020000000000000003000000000000007375622d736c656570000000000000002f00000000000000740007000000098080cc120000000000020000000000000003000000000000007375622d736c656570000000000000002f00000000000000b400240000000a80f8b812000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a000000000000002100018030000000690000000000000068d01200000000006400000000000a8038d81200000000000200000000000000030000000000000068656c6c6f2034382033000000000000540000000000058020dc12000000000002000000000000000300000000000000776f726c64000000940011000000018008e0120000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000c0674000000000000000009400170000000a80f0e31200000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c6570740000002f000000000000009400140000000a8050d412000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180300000006900000000000000d8e71200000000006400000000000a80a8ef1200000000000200000000000000030000000000000068656c6c6f2034382034000000000000540000000000058090f312000000000002000000000000000300000000000000776f726c64000000940011000000018078f7120000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000e0674000000000000000009400140000000a80c0eb12000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018030000000690000000000000060fb1200000000006400000000000a8030031300000000000200000000000000030000000000000068656c6c6f20343820350000000000005400000000000580180713000000000002000000000000000300000000000000776f726c640000009400110000000180000b130000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000684000000000000000009400140000000a8048ff12000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180300000006900000000000000e80e1300000000007400040000000a8028b1120000000000020000000000000003000000000000006f757465722e6c6f6f70000000000000d0121300000000007400050000000a80a01a1300000000000200000000000000030000000000000066616b655f736c65657000000000000030000000000000006400000000000a8070221300000000000200000000000000030000000000000068656c6c6f20343920320000000000005400000000000580582613000000000002000000000000000300000000000000776f726c640000009400110000000180402a130000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000020684000000000000000007400050000000980282e130000000000020000000000000003000000000000007375622d736c65657000000000000000300000000000000074000700000009801032130000000000020000000000000003000000000000007375622d736c656570000000000000003000000000000000b400240000000a80881e13000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a0000000000000021000180310000006900000000000000f8351300000000006400000000000a80c83d1300000000000200000000000000030000000000000068656c6c6f20343920330000000000005400000000000580b04113000000000002000000000000000300000000000000776f726c6400000094001100000001809845130000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000040684000000000000000009400170000000a8080491300000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c65707400000030000000000000009400140000000a80e03913000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180310000006900000000000000684d1300000000006400000000000a8038551300000000000200000000000000030000000000000068656c6c6f20343920340000000000005400000000000580205913000000000002000000000000000300000000000000776f726c640000009400110000000180085d130000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000060684000000000000000009400140000000a80505113000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180310000006900000000000000f0601300000000006400000000000a80c0681300000000000200000000000000030000000000000068656c6c6f20343920350000000000005400000000000580a86c13000000000002000000000000000300000000000000776f726c6400000094001100000001809070130000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000080684000000000000000009400140000000a80d86413000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018031000000690000000000000078741300000000007400040000000a80b816130000000000020000000000000003000000000000006f757465722e6c6f6f7000000000000060781300000000007400050000000a8030801300000000000200000000000000030000000000000066616b655f736c65657000000000000031000000000000006400000000000a8000881300000000000200000000000000030000000000000068656c6c6f20353020320000000000005400000000000580e88b13000000000002000000000000000300000000000000776f726c640000009400110000000180d08f130000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000a0684000000000000000007400050000000980b893130000000000020000000000000003000000000000007375622d736c6565700000000000000031000000000000007400070000000980a097130000000000020000000000000003000000000000007375622d736c656570000000000000003100000000000000b400240000000a80188413000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180020000006a0000000000000021000180320000006900000000000000889b1300000000006400000000000a8058a31300000000000200000000000000030000000000000068656c6c6f2035302033000000000000540000000000058040a713000000000002000000000000000300000000000000776f726c64000000940011000000018028ab130000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000c0684000000000000000009400170000000a8010af1300000000000200000000000000030000000000000066616b655f736c6565700000000000002900058000000000736c65707400000031000000000000009400140000000a80709f13000000000002000000000000000300000000000000696e6e65722e6c6f6f7000000000000021000180320000006900000000000000f8b21300000000006400000000000a80c8ba1300000000000200000000000000030000000000000068656c6c6f20353020340000000000005400000000000580b0be13000000000002000000000000000300000000000000776f726c64000000940011000000018098c2130000000000020000000000000003000000000000006e0000000000000035000180000000006e000000000000000000000000e0684000000000000000009400140000000a80e0b613000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018032000000690000000000000080c61300000000006400000000000a8050ce1300000000000200000000000000030000000000000068656c6c6f2035302035000000000000540000000000058038d213000000000002000000000000000300000000000000776f726c64000000940011000000018020d6130000000000020000000000000003000000000000006e0000000000000035000180000000006e00000000000000000000000000694000000000000000009400140000000a8068ca13000000000002000000000000000300000000000000696e6e65722e6c6f6f700000000000002100018032000000690000000000000008da1300000000007400040000000a80487c130000000000020000000000000003000000000000006f757465722e6c6f6f70000000000000f0dd130000000000 diff --git a/test/fuchsia/t1.ml b/test/fuchsia/t1.ml index 83e55f3..5fd6b9e 100644 --- a/test/fuchsia/t1.ml +++ b/test/fuchsia/t1.ml @@ -43,6 +43,33 @@ let run () = done done +let to_hex (s : string) : string = + let i_to_hex (i : int) = + if i < 10 then + Char.chr (i + Char.code '0') + else + Char.chr (i - 10 + Char.code 'a') + in + + let res = Bytes.create (2 * String.length s) in + for i = 0 to String.length s - 1 do + let n = Char.code (String.get s i) in + Bytes.set res (2 * i) (i_to_hex ((n land 0xf0) lsr 4)); + Bytes.set res ((2 * i) + 1) (i_to_hex (n land 0x0f)) + done; + Bytes.unsafe_to_string res + let () = - Trace_tef.Private_.mock_all_ (); - Trace_tef.with_setup ~out:`Stdout () @@ fun () -> run () + Trace_fuchsia.Internal_.mock_all_ (); + let buf = Buffer.create 32 in + let exporter = Trace_fuchsia.Exporter.of_buffer buf in + Trace_fuchsia.with_setup ~out:(`Exporter exporter) () run; + exporter.close (); + + let data = Buffer.contents buf in + (let oc = open_out_bin "t1.fxt" in + output_string oc data; + close_out_noerr oc); + + print_endline (to_hex data); + flush stdout diff --git a/test/fuchsia/write/dune b/test/fuchsia/write/dune index b277755..7204788 100644 --- a/test/fuchsia/write/dune +++ b/test/fuchsia/write/dune @@ -1,4 +1,4 @@ (tests (names t1 t2) (package trace-fuchsia) - (libraries trace-fuchsia.write)) + (libraries trace-fuchsia)) diff --git a/test/fuchsia/write/t1.ml b/test/fuchsia/write/t1.ml index 8b59e85..46780be 100644 --- a/test/fuchsia/write/t1.ml +++ b/test/fuchsia/write/t1.ml @@ -1,4 +1,4 @@ -open Trace_fuchsia_write +open Trace_fuchsia module Str_ = struct open String @@ -39,14 +39,14 @@ module Str_ = struct end let () = - let l = List.init 100 (fun i -> Util.round_to_word i) in + let l = List.init 100 (fun i -> Writer.Util.round_to_word i) in assert (List.for_all (fun x -> x mod 8 = 0) l) let () = - assert (Str_ref.inline 0 = 0b0000_0000_0000_0000); - assert (Str_ref.inline 1 = 0b1000_0000_0000_0001); - assert (Str_ref.inline 6 = 0b1000_0000_0000_0110); - assert (Str_ref.inline 31999 = 0b1111_1100_1111_1111); + assert (Writer.Str_ref.inline 0 = 0b0000_0000_0000_0000); + assert (Writer.Str_ref.inline 1 = 0b1000_0000_0000_0001); + assert (Writer.Str_ref.inline 6 = 0b1000_0000_0000_0110); + assert (Writer.Str_ref.inline 31999 = 0b1111_1100_1111_1111); () let () = diff --git a/test/fuchsia/write/t2.ml b/test/fuchsia/write/t2.ml index 0dcc5e9..77cb07b 100644 --- a/test/fuchsia/write/t2.ml +++ b/test/fuchsia/write/t2.ml @@ -1,4 +1,5 @@ -open Trace_fuchsia_write +open Trace_fuchsia +open Trace_fuchsia.Writer let pf = Printf.printf @@ -40,24 +41,27 @@ module Str_ = struct Bytes.unsafe_to_string res end -let with_buf_output (f : Output.t -> unit) : string = +let with_buf_chain (f : Buf_chain.t -> unit) : string = let buf_pool = Buf_pool.create () in let buffer = Buffer.create 32 in - let out = Output.into_buffer ~buf_pool buffer in - f out; - Output.flush out; + let buf_chain = Buf_chain.create ~sharded:true ~buf_pool () in + f buf_chain; + + Buf_chain.ready_all_non_empty buf_chain; + let exp = Exporter.of_buffer buffer in + Buf_chain.pop_ready buf_chain ~f:exp.write_bufs; Buffer.contents buffer let () = pf "first trace\n" let () = let str = - with_buf_output (fun out -> - Metadata.Magic_record.encode out; - Thread_record.encode out ~as_ref:5 ~pid:1 ~tid:86 (); - Event.Instant.encode out ~name:"hello" ~time_ns:1234_5678L + with_buf_chain (fun bufs -> + Metadata.Magic_record.encode bufs; + Thread_record.encode bufs ~as_ref:5 ~pid:1 ~tid:86 (); + Event.Instant.encode bufs ~name:"hello" ~time_ns:1234_5678L ~t_ref:(Thread_ref.Ref 5) - ~args:[ "x", `Int 42 ] + ~args:[ "x", A_int 42 ] ()) in pf "%s\n" (Str_.to_hex str) @@ -66,21 +70,21 @@ let () = pf "second trace\n" let () = let str = - with_buf_output (fun out -> - Metadata.Magic_record.encode out; + with_buf_chain (fun bufs -> + Metadata.Magic_record.encode bufs; Metadata.Initialization_record.( - encode out ~ticks_per_secs:default_ticks_per_sec ()); - Thread_record.encode out ~as_ref:5 ~pid:1 ~tid:86 (); - Metadata.Provider_info.encode out ~id:1 ~name:"ocaml-trace" (); - Event.Duration_complete.encode out ~name:"outer" + encode bufs ~ticks_per_secs:default_ticks_per_sec ()); + Thread_record.encode bufs ~as_ref:5 ~pid:1 ~tid:86 (); + Metadata.Provider_info.encode bufs ~id:1 ~name:"ocaml-trace" (); + Event.Duration_complete.encode bufs ~name:"outer" ~t_ref:(Thread_ref.Ref 5) ~time_ns:100_000L ~end_time_ns:5_000_000L ~args:[] (); - Event.Duration_complete.encode out ~name:"inner" + Event.Duration_complete.encode bufs ~name:"inner" ~t_ref:(Thread_ref.Ref 5) ~time_ns:180_000L ~end_time_ns:4_500_000L ~args:[] (); - Event.Instant.encode out ~name:"hello" ~time_ns:1_234_567L + Event.Instant.encode bufs ~name:"hello" ~time_ns:1_234_567L ~t_ref:(Thread_ref.Ref 5) - ~args:[ "x", `Int 42 ] + ~args:[ "x", A_int 42 ] ()) in (let oc = open_out "foo.fxt" in diff --git a/test/t1.expected b/test/t1.expected index 9ffcb82..1c95f6e 100644 --- a/test/t1.expected +++ b/test/t1.expected @@ -1049,5 +1049,4 @@ {"pid":2,"cat":"","tid": 3,"ts": 1299.00,"name":"world","ph":"I"}, {"pid":2,"tid":3,"ts":1300.00,"name":"c","ph":"C","args": {"n":200}}, {"pid":2,"cat":"","tid": 3,"dur": 4.00,"ts": 1297.00,"name":"inner.loop","ph":"X","args": {"i":50}}, -{"pid":2,"cat":"","tid": 3,"dur": 25.00,"ts": 1277.00,"name":"outer.loop","ph":"X"}, -{"pid":2,"cat":"","tid": 1,"ts": 1304.00,"name":"tef-worker.exit","ph":"I"}] \ No newline at end of file +{"pid":2,"cat":"","tid": 3,"dur": 25.00,"ts": 1277.00,"name":"outer.loop","ph":"X"}] \ No newline at end of file diff --git a/test/t2.expected b/test/t2.expected index 5d8c042..692da75 100644 --- a/test/t2.expected +++ b/test/t2.expected @@ -929,5 +929,4 @@ {"pid":2,"cat":"","tid": 3,"dur": 217.00,"ts": 1642.00,"name":"Dune__exe__T2.fib2","ph":"X"}, {"pid":2,"cat":"","tid": 3,"dur": 353.00,"ts": 1507.00,"name":"Dune__exe__T2.fib2","ph":"X"}, {"pid":2,"cat":"","tid": 3,"dur": 573.00,"ts": 1288.00,"name":"Dune__exe__T2.fib2","ph":"X"}, -{"pid":2,"cat":"","tid": 3,"dur": 929.00,"ts": 933.00,"name":"Dune__exe__T2.fib2","ph":"X"}, -{"pid":2,"cat":"","tid": 1,"ts": 1864.00,"name":"tef-worker.exit","ph":"I"}] \ No newline at end of file +{"pid":2,"cat":"","tid": 3,"dur": 929.00,"ts": 933.00,"name":"Dune__exe__T2.fib2","ph":"X"}] \ No newline at end of file From d7f0aff4067170c2dc5067a3d33d883c7196b0e0 Mon Sep 17 00:00:00 2001 From: Simon Cruanes Date: Wed, 7 May 2025 15:44:39 -0400 Subject: [PATCH 12/15] cleaner tracing errors --- src/fuchsia/subscriber.ml | 5 ++--- src/tef/subscriber.ml | 12 +++++++----- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/fuchsia/subscriber.ml b/src/fuchsia/subscriber.ml index fac5178..acf9c83 100644 --- a/src/fuchsia/subscriber.ml +++ b/src/fuchsia/subscriber.ml @@ -35,8 +35,7 @@ open struct let spans = Span_tbl.to_list spans in if spans <> [] then ( !on_tracing_error - @@ Printf.sprintf "trace-tef: warning: %d spans were not closed\n" - (List.length spans); + @@ Printf.sprintf "warning: %d spans were not closed" (List.length spans); let names = List.fold_left (fun set (_, span) -> Str_set.add span.name set) @@ -44,7 +43,7 @@ open struct in Str_set.iter (fun name -> - !on_tracing_error @@ Printf.sprintf " span %S was not closed\n" name) + !on_tracing_error @@ Printf.sprintf " span %S was not closed" name) names; flush stderr ) diff --git a/src/tef/subscriber.ml b/src/tef/subscriber.ml index d3e4b54..bc7b8f6 100644 --- a/src/tef/subscriber.ml +++ b/src/tef/subscriber.ml @@ -23,7 +23,7 @@ open struct Bytes.get_int64_le (Bytes.unsafe_of_string id) 0 end -let on_tracing_error = ref (fun s -> Printf.eprintf "trace-tef error: %s\n%!" s) +let on_tracing_error = ref (fun s -> Printf.eprintf "%s\n%!" s) type span_info = { tid: int; @@ -51,7 +51,7 @@ open struct let spans = Span_tbl.to_list spans in if spans <> [] then ( !on_tracing_error - @@ Printf.sprintf "trace-tef: warning: %d spans were not closed\n" + @@ Printf.sprintf "trace-tef: warning: %d spans were not closed" (List.length spans); let names = List.fold_left @@ -60,7 +60,7 @@ open struct in Str_set.iter (fun name -> - !on_tracing_error @@ Printf.sprintf " span %S was not closed\n" name) + !on_tracing_error @@ Printf.sprintf " span %S was not closed" name) names; flush stderr ) @@ -113,7 +113,8 @@ module Callbacks = struct match Span_tbl.find_exn self.spans span with | exception Not_found -> - !on_tracing_error (Printf.sprintf "cannot find span %Ld" span) + !on_tracing_error + (Printf.sprintf "trace-tef: error: cannot find span %Ld" span) | { tid; name; start_us; data } -> Span_tbl.remove self.spans span; let@ buf = Rpool.with_ self.buf_pool in @@ -128,7 +129,8 @@ module Callbacks = struct let info = Span_tbl.find_exn self.spans span in info.data <- List.rev_append data info.data with Not_found -> - !on_tracing_error (Printf.sprintf "cannot find span %Ld" span) + !on_tracing_error + (Printf.sprintf "trace-tef: error: cannot find span %Ld" span) ) let on_message (self : st) ~time_ns ~tid ~span:_ ~data msg : unit = From c3bd2f92a885b428e724804c3474c82d95f38b73 Mon Sep 17 00:00:00 2001 From: Simon Cruanes Date: Wed, 7 May 2025 20:37:09 -0400 Subject: [PATCH 13/15] fix bench --- bench/bench_fuchsia_write.ml | 67 ++++++++++++++++++++---------------- bench/dune | 2 +- 2 files changed, 39 insertions(+), 30 deletions(-) diff --git a/bench/bench_fuchsia_write.ml b/bench/bench_fuchsia_write.ml index a62149e..07ca16a 100644 --- a/bench/bench_fuchsia_write.ml +++ b/bench/bench_fuchsia_write.ml @@ -1,23 +1,34 @@ -open Trace_fuchsia_write +open Trace_fuchsia +open Trace_fuchsia.Writer module B = Benchmark let pf = Printf.printf -let encode_1_span (out : Output.t) () = - Event.Duration_complete.encode out ~name:"span" ~t_ref:(Thread_ref.Ref 5) - ~time_ns:100_000L ~end_time_ns:5_000_000L ~args:[] () +let encode_1000_span (bufs : Buf_chain.t) () = + for _i = 1 to 1000 do + Event.Duration_complete.encode bufs ~name:"span" ~t_ref:(Thread_ref.Ref 5) + ~time_ns:100_000L ~end_time_ns:5_000_000L ~args:[] () + done; + Buf_chain.ready_all_non_empty bufs; + Buf_chain.pop_ready bufs ~f:ignore; + () -let encode_3_span (out : Output.t) () = - Event.Duration_complete.encode out ~name:"outer" ~t_ref:(Thread_ref.Ref 5) - ~time_ns:100_000L ~end_time_ns:5_000_000L ~args:[] (); - Event.Duration_complete.encode out ~name:"inner" ~t_ref:(Thread_ref.Ref 5) - ~time_ns:180_000L ~end_time_ns:4_500_000L ~args:[] (); - Event.Instant.encode out ~name:"hello" ~time_ns:1_234_567L - ~t_ref:(Thread_ref.Ref 5) - ~args:[ "x", `Int 42 ] - () +let encode_300_span (bufs : Buf_chain.t) () = + for _i = 1 to 100 do + Event.Duration_complete.encode bufs ~name:"outer" ~t_ref:(Thread_ref.Ref 5) + ~time_ns:100_000L ~end_time_ns:5_000_000L ~args:[] (); + Event.Duration_complete.encode bufs ~name:"inner" ~t_ref:(Thread_ref.Ref 5) + ~time_ns:180_000L ~end_time_ns:4_500_000L ~args:[] (); + Event.Instant.encode bufs ~name:"hello" ~time_ns:1_234_567L + ~t_ref:(Thread_ref.Ref 5) + ~args:[ "x", A_int 42 ] + () + done; + Buf_chain.ready_all_non_empty bufs; + Buf_chain.pop_ready bufs ~f:ignore; + () -let time_per_iter_ns (samples : B.t list) : float = +let time_per_iter_ns n_per_iter (samples : B.t list) : float = let n_iters = ref 0L in let time = ref 0. in List.iter @@ -25,34 +36,32 @@ let time_per_iter_ns (samples : B.t list) : float = n_iters := Int64.add !n_iters s.iters; time := !time +. s.stime +. s.utime) samples; - !time *. 1e9 /. Int64.to_float !n_iters + !time *. 1e9 /. (Int64.to_float !n_iters *. float n_per_iter) let () = let buf_pool = Buf_pool.create () in - let out = - Output.create ~buf_pool - ~send_buf:(fun buf -> Buf_pool.recycle buf_pool buf) - () - in + let bufs = Buf_chain.create ~sharded:false ~buf_pool () in - let samples = B.throughput1 4 ~name:"encode_1_span" (encode_1_span out) () in + let samples = + B.throughput1 4 ~name:"encode_1000_span" (encode_1000_span bufs) () + in B.print_gc samples; let [ (_, samples) ] = samples [@@warning "-8"] in - - let iter_per_ns = time_per_iter_ns samples in + let iter_per_ns = time_per_iter_ns 1000 samples in pf "%.3f ns/iter\n" iter_per_ns; () let () = let buf_pool = Buf_pool.create () in - let out = - Output.create ~buf_pool - ~send_buf:(fun buf -> Buf_pool.recycle buf_pool buf) - () + let bufs = Buf_chain.create ~sharded:false ~buf_pool () in + let samples = + B.throughput1 4 ~name:"encode_300_span" (encode_300_span bufs) () in - - let samples = B.throughput1 4 ~name:"encode_3_span" (encode_3_span out) () in B.print_gc samples; + + let [ (_, samples) ] = samples [@@warning "-8"] in + let iter_per_ns = time_per_iter_ns 300 samples in + pf "%.3f ns/iter\n" iter_per_ns; () diff --git a/bench/dune b/bench/dune index 31c0547..78c837b 100644 --- a/bench/dune +++ b/bench/dune @@ -20,4 +20,4 @@ (executable (name bench_fuchsia_write) (modules bench_fuchsia_write) - (libraries benchmark trace-fuchsia.write)) + (libraries benchmark trace-fuchsia)) From 4098e88c688f37b6d7714e8c61b0e7a2364031e6 Mon Sep 17 00:00:00 2001 From: Simon Cruanes Date: Wed, 7 May 2025 21:52:25 -0400 Subject: [PATCH 14/15] CI --- .github/workflows/main.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3f894c3..a06c6fa 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -52,7 +52,9 @@ jobs: - run: opam exec -- dune build '@install' -p trace,trace-tef,trace-fuchsia - run: opam install picos_aux + if: matrix.ocaml-compiler != '4.08.x' && matrix.ocaml-compiler != '4.12.x' - run: opam exec -- dune build '@install' -p trace,trace-tef,trace-fuchsia + if: matrix.ocaml-compiler != '4.08.x' && matrix.ocaml-compiler != '4.12.x' - run: opam install mtime - run: opam exec -- dune build '@install' -p trace,trace-tef,trace-fuchsia From d1759fea8989ae67e4ff599741c4a360ee82932a Mon Sep 17 00:00:00 2001 From: Simon Cruanes Date: Wed, 7 May 2025 22:32:29 -0400 Subject: [PATCH 15/15] fix for 4.08 --- src/fuchsia/buf_chain.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fuchsia/buf_chain.ml b/src/fuchsia/buf_chain.ml index 77616e5..f31d81b 100644 --- a/src/fuchsia/buf_chain.ml +++ b/src/fuchsia/buf_chain.ml @@ -44,7 +44,7 @@ open struct let put_in_ready (self : t) buf : unit = if Buf.size buf > 0 then ( let@ q = Lock.with_ self.ready in - Atomic.set self.has_ready true; + A.set self.has_ready true; Queue.push buf q )