mirror of
https://github.com/ocaml-tracing/ocaml-trace.git
synced 2026-03-07 18:37:56 -05:00
commit
987b57191c
40 changed files with 1694 additions and 38 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -2,3 +2,5 @@ _opam
|
||||||
_build
|
_build
|
||||||
*.json
|
*.json
|
||||||
*.exe
|
*.exe
|
||||||
|
perf.*
|
||||||
|
*.fxt
|
||||||
|
|
|
||||||
15
README.md
15
README.md
|
|
@ -117,6 +117,21 @@ let f x y z =
|
||||||
raise e
|
raise e
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Alternatively, a name can be provided for the span, which is useful if you want
|
||||||
|
to access it and use functions like `Trace.add_data_to_span`:
|
||||||
|
|
||||||
|
|
||||||
|
```ocaml
|
||||||
|
let%trace f x y z =
|
||||||
|
do_sth x;
|
||||||
|
do_sth y;
|
||||||
|
begin
|
||||||
|
let%trace _sp = "sub-span" in
|
||||||
|
do_sth z;
|
||||||
|
Trace.add_data_to_span _sp ["x", `Int 42]
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
### Dune configuration
|
### Dune configuration
|
||||||
|
|
||||||
In your `library` or `executable` stanza, add: `(preprocess (pps ppx_trace))`.
|
In your `library` or `executable` stanza, add: `(preprocess (pps ppx_trace))`.
|
||||||
|
|
|
||||||
58
bench/bench_fuchsia_write.ml
Normal file
58
bench/bench_fuchsia_write.ml
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
open Trace_fuchsia_write
|
||||||
|
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_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 time_per_iter_ns (samples : B.t list) : float =
|
||||||
|
let n_iters = ref 0L in
|
||||||
|
let time = ref 0. in
|
||||||
|
List.iter
|
||||||
|
(fun (s : B.t) ->
|
||||||
|
n_iters := Int64.add !n_iters s.iters;
|
||||||
|
time := !time +. s.stime +. s.utime)
|
||||||
|
samples;
|
||||||
|
!time *. 1e9 /. Int64.to_float !n_iters
|
||||||
|
|
||||||
|
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 samples = B.throughput1 4 ~name:"encode_1_span" (encode_1_span out) () in
|
||||||
|
B.print_gc samples;
|
||||||
|
|
||||||
|
let [ (_, samples) ] = samples [@@warning "-8"] in
|
||||||
|
|
||||||
|
let iter_per_ns = time_per_iter_ns 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)
|
||||||
|
()
|
||||||
|
in
|
||||||
|
|
||||||
|
let samples = B.throughput1 4 ~name:"encode_3_span" (encode_3_span out) () in
|
||||||
|
B.print_gc samples;
|
||||||
|
()
|
||||||
12
bench/dune
12
bench/dune
|
|
@ -1,4 +1,16 @@
|
||||||
|
|
||||||
(executable
|
(executable
|
||||||
(name trace1)
|
(name trace1)
|
||||||
|
(modules trace1)
|
||||||
(libraries trace.core trace-tef))
|
(libraries trace.core trace-tef))
|
||||||
|
|
||||||
|
(executable
|
||||||
|
(name trace_fx)
|
||||||
|
(modules trace_fx)
|
||||||
|
(preprocess (pps ppx_trace))
|
||||||
|
(libraries trace.core trace-fuchsia))
|
||||||
|
|
||||||
|
(executable
|
||||||
|
(name bench_fuchsia_write)
|
||||||
|
(modules bench_fuchsia_write)
|
||||||
|
(libraries benchmark trace-fuchsia.write))
|
||||||
|
|
|
||||||
50
bench/trace_fx.ml
Normal file
50
bench/trace_fx.ml
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
module Trace = Trace_core
|
||||||
|
|
||||||
|
let ( let@ ) = ( @@ )
|
||||||
|
|
||||||
|
let work ~dom_idx ~n () : unit =
|
||||||
|
Trace_core.set_thread_name (Printf.sprintf "worker%d" dom_idx);
|
||||||
|
for _i = 1 to n do
|
||||||
|
let%trace _sp = "outer" in
|
||||||
|
Trace_core.add_data_to_span _sp [ "i", `Int _i ];
|
||||||
|
for _k = 1 to 10 do
|
||||||
|
let%trace _sp = "inner" in
|
||||||
|
()
|
||||||
|
done;
|
||||||
|
|
||||||
|
(* Thread.delay 1e-6 *)
|
||||||
|
if dom_idx = 0 && _i mod 4096 = 0 then (
|
||||||
|
Trace_core.message "gc stats";
|
||||||
|
let stat = Gc.quick_stat () in
|
||||||
|
Trace_core.counter_float "gc.minor" (8. *. stat.minor_words);
|
||||||
|
Trace_core.counter_float "gc.major" (8. *. stat.major_words)
|
||||||
|
)
|
||||||
|
done
|
||||||
|
|
||||||
|
let main ~n ~j () : unit =
|
||||||
|
let domains =
|
||||||
|
Array.init j (fun dom_idx -> Domain.spawn (fun () -> work ~dom_idx ~n ()))
|
||||||
|
in
|
||||||
|
|
||||||
|
let%trace () = "join" in
|
||||||
|
Array.iter Domain.join domains
|
||||||
|
|
||||||
|
let () =
|
||||||
|
let@ () = Trace_fuchsia.with_setup () in
|
||||||
|
Trace_core.set_process_name "trace_fxt1";
|
||||||
|
Trace_core.set_thread_name "main";
|
||||||
|
|
||||||
|
let%trace () = "main" in
|
||||||
|
|
||||||
|
let n = ref 10_000 in
|
||||||
|
let j = ref 4 in
|
||||||
|
|
||||||
|
let args =
|
||||||
|
[
|
||||||
|
"-n", Arg.Set_int n, " number of iterations";
|
||||||
|
"-j", Arg.Set_int j, " set number of workers";
|
||||||
|
]
|
||||||
|
|> Arg.align
|
||||||
|
in
|
||||||
|
Arg.parse args ignore "bench1";
|
||||||
|
main ~n:!n ~j:!j ()
|
||||||
3
bench_fx.sh
Executable file
3
bench_fx.sh
Executable file
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/sh
|
||||||
|
DUNE_OPTS="--profile=release --display=quiet"
|
||||||
|
exec dune exec $DUNE_OPTS bench/trace_fx.exe -- $@
|
||||||
2
dune
2
dune
|
|
@ -1,4 +1,4 @@
|
||||||
|
|
||||||
(env
|
(env
|
||||||
(_ (flags :standard -strict-sequence -warn-error -a+8+26+27 -w +a-4-40-70)))
|
(_ (flags :standard -strict-sequence -warn-error -a+8+26+27 -w +a-4-40-42-44-70)))
|
||||||
|
|
||||||
|
|
|
||||||
14
dune-project
14
dune-project
|
|
@ -41,9 +41,21 @@
|
||||||
(trace (= :version))
|
(trace (= :version))
|
||||||
(mtime (>= 2.0))
|
(mtime (>= 2.0))
|
||||||
base-unix
|
base-unix
|
||||||
atomic
|
|
||||||
dune)
|
dune)
|
||||||
(tags
|
(tags
|
||||||
(trace tracing catapult)))
|
(trace tracing catapult)))
|
||||||
|
|
||||||
|
(package
|
||||||
|
(name trace-fuchsia)
|
||||||
|
(synopsis "A high-performance backend for trace, emitting a Fuchsia trace into a file")
|
||||||
|
(depends
|
||||||
|
(ocaml (>= 4.08))
|
||||||
|
(trace (= :version))
|
||||||
|
(mtime (>= 2.0))
|
||||||
|
base-bigarray
|
||||||
|
base-unix
|
||||||
|
dune)
|
||||||
|
(tags
|
||||||
|
(trace tracing fuchsia)))
|
||||||
|
|
||||||
; See the complete stanza docs at https://dune.readthedocs.io/en/stable/dune-files.html#dune-project
|
; See the complete stanza docs at https://dune.readthedocs.io/en/stable/dune-files.html#dune-project
|
||||||
|
|
|
||||||
63
src/fuchsia/bg_thread.ml
Normal file
63
src/fuchsia/bg_thread.ml
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
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 -> ()
|
||||||
12
src/fuchsia/common_.ml
Normal file
12
src/fuchsia/common_.ml
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
let on_tracing_error =
|
||||||
|
ref (fun s -> Printf.eprintf "trace-fuchsia error: %s\n%!" s)
|
||||||
|
|
||||||
|
let ( let@ ) = ( @@ )
|
||||||
|
let spf = Printf.sprintf
|
||||||
9
src/fuchsia/dune
Normal file
9
src/fuchsia/dune
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
|
||||||
|
|
||||||
|
(library
|
||||||
|
(name trace_fuchsia)
|
||||||
|
(public_name trace-fuchsia)
|
||||||
|
(synopsis "A high-performance backend for trace, emitting a Fuchsia trace into a file")
|
||||||
|
(libraries trace.core trace.private.util thread-local-storage
|
||||||
|
(re_export trace-fuchsia.write) bigarray
|
||||||
|
mtime mtime.clock.os atomic unix threads))
|
||||||
395
src/fuchsia/fcollector.ml
Normal file
395
src/fuchsia/fcollector.ml
Normal file
|
|
@ -0,0 +1,395 @@
|
||||||
|
open Trace_core
|
||||||
|
open Common_
|
||||||
|
module TLS = Thread_local_storage
|
||||||
|
module Int_map = Map.Make (Int)
|
||||||
|
|
||||||
|
let pid = Unix.getpid ()
|
||||||
|
|
||||||
|
(** 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 = {
|
||||||
|
async_id: int;
|
||||||
|
flavor: [ `Sync | `Async ] option;
|
||||||
|
name: string;
|
||||||
|
mutable data: (string * user_data) list;
|
||||||
|
}
|
||||||
|
|
||||||
|
let key_async_data : async_span_info Meta_map.Key.t = 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 key_thread_local_st : per_thread_state TLS.key =
|
||||||
|
TLS.new_key (fun () ->
|
||||||
|
let tid = Thread.id @@ Thread.self () in
|
||||||
|
{
|
||||||
|
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 ();
|
||||||
|
})
|
||||||
|
|
||||||
|
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 = TLS.get key_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 = TLS.get key_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 = TLS.get key_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 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 async_id =
|
||||||
|
match parent with
|
||||||
|
| Some m -> (Meta_map.find_exn key_async_data m.meta).async_id
|
||||||
|
| None -> A.fetch_and_add st.span_id_gen 1
|
||||||
|
in
|
||||||
|
|
||||||
|
FWrite.Event.Async_begin.encode out ~name ~args:data ~t_ref:tls.thread_ref
|
||||||
|
~time_ns ~async_id ();
|
||||||
|
{
|
||||||
|
span = 0L;
|
||||||
|
meta =
|
||||||
|
Meta_map.(
|
||||||
|
empty |> add key_async_data { async_id; name; flavor; data = [] });
|
||||||
|
}
|
||||||
|
|
||||||
|
let exit_manual_span (es : explicit_span) : unit =
|
||||||
|
let { async_id; 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 ()
|
||||||
|
|
||||||
|
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 ]
|
||||||
|
())
|
||||||
|
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)
|
||||||
3
src/fuchsia/fcollector.mli
Normal file
3
src/fuchsia/fcollector.mli
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
open Trace_core
|
||||||
|
|
||||||
|
val create : out:Bg_thread.out -> unit -> collector
|
||||||
6
src/fuchsia/time.ml
Normal file
6
src/fuchsia/time.ml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
let counter = Mtime_clock.counter ()
|
||||||
|
|
||||||
|
(** Now, in nanoseconds *)
|
||||||
|
let[@inline] now_ns () : int64 =
|
||||||
|
let t = Mtime_clock.count counter in
|
||||||
|
Mtime.Span.to_uint64_ns t
|
||||||
38
src/fuchsia/trace_fuchsia.ml
Normal file
38
src/fuchsia/trace_fuchsia.ml
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
open Common_
|
||||||
|
|
||||||
|
type output =
|
||||||
|
[ `Stdout
|
||||||
|
| `Stderr
|
||||||
|
| `File of string
|
||||||
|
]
|
||||||
|
|
||||||
|
let collector = Fcollector.create
|
||||||
|
|
||||||
|
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) ()
|
||||||
|
| `Env ->
|
||||||
|
(match Sys.getenv_opt "TRACE" with
|
||||||
|
| Some ("1" | "true") ->
|
||||||
|
let path = "trace.fxt" in
|
||||||
|
let c = Fcollector.create ~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
|
||||||
|
Trace_core.setup_collector c
|
||||||
|
| None -> ())
|
||||||
|
|
||||||
|
let with_setup ?out () f =
|
||||||
|
setup ?out ();
|
||||||
|
Fun.protect ~finally:Trace_core.shutdown f
|
||||||
|
|
||||||
|
module Internal_ = struct
|
||||||
|
let on_tracing_error = on_tracing_error
|
||||||
|
end
|
||||||
46
src/fuchsia/trace_fuchsia.mli
Normal file
46
src/fuchsia/trace_fuchsia.mli
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
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. *)
|
||||||
|
|
||||||
|
type output =
|
||||||
|
[ `Stdout
|
||||||
|
| `Stderr
|
||||||
|
| `File of string
|
||||||
|
]
|
||||||
|
(** 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 setup : ?out:[ output | `Env ] -> unit -> unit
|
||||||
|
(** [setup ()] installs the collector depending on [out].
|
||||||
|
|
||||||
|
@param out can take different values:
|
||||||
|
- regular {!output} value to specify where events go
|
||||||
|
- [`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
|
||||||
|
(** [with_setup () f] (optionally) sets a collector up, calls [f()],
|
||||||
|
and makes sure to shutdown before exiting.
|
||||||
|
since 0.2 a () argument was added.
|
||||||
|
*)
|
||||||
|
|
||||||
|
(**/**)
|
||||||
|
|
||||||
|
module Internal_ : sig
|
||||||
|
val on_tracing_error : (string -> unit) ref
|
||||||
|
end
|
||||||
|
|
||||||
|
(**/**)
|
||||||
42
src/fuchsia/write/buf.ml
Normal file
42
src/fuchsia/write/buf.ml
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
open Util
|
||||||
|
|
||||||
|
type t = {
|
||||||
|
buf: bytes;
|
||||||
|
mutable offset: int;
|
||||||
|
}
|
||||||
|
|
||||||
|
let empty : t = { buf = Bytes.empty; offset = 0 }
|
||||||
|
|
||||||
|
let create (n : int) : t =
|
||||||
|
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
|
||||||
|
|
||||||
|
(* see below: we assume little endian *)
|
||||||
|
let () = assert (not Sys.big_endian)
|
||||||
|
|
||||||
|
let[@inline] add_i64 (self : t) (i : int64) : unit =
|
||||||
|
(* NOTE: we use LE, most systems are this way, even though fuchsia
|
||||||
|
says we should use the system's native endianess *)
|
||||||
|
Bytes.set_int64_le self.buf self.offset i;
|
||||||
|
self.offset <- self.offset + 8
|
||||||
|
|
||||||
|
let[@inline] add_string (self : t) (s : string) : unit =
|
||||||
|
let len = String.length s in
|
||||||
|
let missing = missing_to_round len in
|
||||||
|
|
||||||
|
(* bound check *)
|
||||||
|
assert (len + missing + self.offset <= Bytes.length self.buf);
|
||||||
|
Bytes.unsafe_blit_string s 0 self.buf self.offset len;
|
||||||
|
self.offset <- self.offset + len;
|
||||||
|
|
||||||
|
(* add 0-padding *)
|
||||||
|
if missing != 0 then (
|
||||||
|
Bytes.unsafe_fill self.buf self.offset missing '\x00';
|
||||||
|
self.offset <- self.offset + missing
|
||||||
|
)
|
||||||
|
|
||||||
|
let to_string (self : t) : string = Bytes.sub_string self.buf 0 self.offset
|
||||||
58
src/fuchsia/write/buf_pool.ml
Normal file
58
src/fuchsia/write/buf_pool.ml
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
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 *)
|
||||||
9
src/fuchsia/write/dune
Normal file
9
src/fuchsia/write/dune
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
|
||||||
|
(library
|
||||||
|
(name trace_fuchsia_write)
|
||||||
|
(public_name trace-fuchsia.write)
|
||||||
|
(synopsis "Serialization part of trace-fuchsia")
|
||||||
|
(ocamlopt_flags :standard -S
|
||||||
|
;-dlambda
|
||||||
|
)
|
||||||
|
(libraries trace.core threads))
|
||||||
56
src/fuchsia/write/output.ml
Normal file
56
src/fuchsia/write/output.ml
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
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
|
||||||
|
failwith "fuchsia: buffer is too small";
|
||||||
|
buf
|
||||||
|
end
|
||||||
|
|
||||||
|
let[@inline] flush (self : t) : unit = if Buf.size self.buf > 0 then flush_ self
|
||||||
|
|
||||||
|
(** 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
|
||||||
541
src/fuchsia/write/trace_fuchsia_write.ml
Normal file
541
src/fuchsia/write/trace_fuchsia_write.ml
Normal file
|
|
@ -0,0 +1,541 @@
|
||||||
|
(** Write fuchsia events into buffers.
|
||||||
|
|
||||||
|
Reference: https://fuchsia.dev/fuchsia-src/reference/tracing/trace-format *)
|
||||||
|
|
||||||
|
module Util = Util
|
||||||
|
module Buf = Buf
|
||||||
|
module Output = Output
|
||||||
|
module Buf_pool = Buf_pool
|
||||||
|
|
||||||
|
open struct
|
||||||
|
let spf = Printf.sprintf
|
||||||
|
end
|
||||||
|
|
||||||
|
open Util
|
||||||
|
|
||||||
|
type user_data = Trace_core.user_data
|
||||||
|
|
||||||
|
module I64 = struct
|
||||||
|
include Int64
|
||||||
|
|
||||||
|
let ( + ) = add
|
||||||
|
let ( - ) = sub
|
||||||
|
let ( = ) = equal
|
||||||
|
let ( land ) = logand
|
||||||
|
let ( lor ) = logor
|
||||||
|
let lnot = lognot
|
||||||
|
let ( lsl ) = shift_left
|
||||||
|
let ( lsr ) = shift_right_logical
|
||||||
|
let ( asr ) = shift_right
|
||||||
|
end
|
||||||
|
|
||||||
|
module Str_ref = struct
|
||||||
|
type t = int
|
||||||
|
(** 16 bits *)
|
||||||
|
|
||||||
|
let inline (size : int) : t =
|
||||||
|
if size > 32_000 then invalid_arg "fuchsia: max length of strings is 20_000";
|
||||||
|
if size = 0 then
|
||||||
|
0
|
||||||
|
else
|
||||||
|
(1 lsl 15) lor size
|
||||||
|
end
|
||||||
|
|
||||||
|
module Thread_ref = struct
|
||||||
|
type t =
|
||||||
|
| Ref of int
|
||||||
|
| Inline of {
|
||||||
|
pid: int;
|
||||||
|
tid: int;
|
||||||
|
}
|
||||||
|
|
||||||
|
let inline ~pid ~tid : t = Inline { pid; tid }
|
||||||
|
|
||||||
|
let ref x : t =
|
||||||
|
if x = 0 || x > 255 then
|
||||||
|
invalid_arg "fuchsia: thread inline ref must be >0 < 256";
|
||||||
|
Ref x
|
||||||
|
|
||||||
|
let size_word (self : t) : int =
|
||||||
|
match self with
|
||||||
|
| Ref _ -> 0
|
||||||
|
| Inline _ -> 2
|
||||||
|
|
||||||
|
(** 8-bit int for the reference *)
|
||||||
|
let as_i8 (self : t) : int =
|
||||||
|
match self with
|
||||||
|
| Ref i -> i
|
||||||
|
| Inline _ -> 0
|
||||||
|
end
|
||||||
|
|
||||||
|
(** record type = 0 *)
|
||||||
|
module Metadata = struct
|
||||||
|
(** First record in the trace *)
|
||||||
|
module Magic_record = struct
|
||||||
|
let value = 0x0016547846040010L
|
||||||
|
let size_word = 1
|
||||||
|
|
||||||
|
let encode (out : Output.t) =
|
||||||
|
let buf = Output.get_buf out ~available_word:size_word in
|
||||||
|
Buf.add_i64 buf value
|
||||||
|
end
|
||||||
|
|
||||||
|
module Initialization_record = struct
|
||||||
|
let size_word = 2
|
||||||
|
|
||||||
|
(** 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 hd = I64.(1L lor (of_int size_word lsl 4)) in
|
||||||
|
Buf.add_i64 buf hd;
|
||||||
|
Buf.add_i64 buf ticks_per_secs
|
||||||
|
end
|
||||||
|
|
||||||
|
module Provider_info = struct
|
||||||
|
let size_word ~name () = 1 + (round_to_word (String.length name) lsr 3)
|
||||||
|
|
||||||
|
let encode (out : Output.t) ~(id : int) ~name () : unit =
|
||||||
|
let size = size_word ~name () in
|
||||||
|
let buf = Output.get_buf out ~available_word:size in
|
||||||
|
let hd =
|
||||||
|
I64.(
|
||||||
|
(of_int size lsl 4)
|
||||||
|
lor (1L lsl 16)
|
||||||
|
lor (of_int id lsl 20)
|
||||||
|
lor (of_int (Str_ref.inline (String.length name)) lsl 52))
|
||||||
|
in
|
||||||
|
Buf.add_i64 buf hd;
|
||||||
|
Buf.add_string buf name
|
||||||
|
end
|
||||||
|
|
||||||
|
module Provider_section = struct end
|
||||||
|
module Trace_info = struct end
|
||||||
|
end
|
||||||
|
|
||||||
|
module Argument = struct
|
||||||
|
type 'a t = string * ([< user_data | `Kid of int ] as 'a)
|
||||||
|
|
||||||
|
let check_valid _ = ()
|
||||||
|
(* TODO: check string length *)
|
||||||
|
|
||||||
|
let[@inline] is_i32_ (i : int) : bool = Int32.(to_int (of_int i) = i)
|
||||||
|
|
||||||
|
let size_word (self : _ t) =
|
||||||
|
let name, data = self in
|
||||||
|
match data with
|
||||||
|
| `None | `Bool _ -> 1 + (round_to_word (String.length name) lsr 3)
|
||||||
|
| `Int i when is_i32_ i -> 1 + (round_to_word (String.length name) lsr 3)
|
||||||
|
| `Int _ -> (* int64 *) 2 + (round_to_word (String.length name) lsr 3)
|
||||||
|
| `Float _ -> 2 + (round_to_word (String.length name) lsr 3)
|
||||||
|
| `String s ->
|
||||||
|
1
|
||||||
|
+ (round_to_word (String.length s) lsr 3)
|
||||||
|
+ (round_to_word (String.length name) lsr 3)
|
||||||
|
| `Kid _ -> 2 + (round_to_word (String.length name) lsr 3)
|
||||||
|
|
||||||
|
open struct
|
||||||
|
external int_of_bool : bool -> int = "%identity"
|
||||||
|
end
|
||||||
|
|
||||||
|
let encode (buf : Buf.t) (self : _ t) : unit =
|
||||||
|
let name, data = self in
|
||||||
|
let size = size_word self in
|
||||||
|
|
||||||
|
(* part of header with argument name + size *)
|
||||||
|
let hd_arg_size =
|
||||||
|
I64.(
|
||||||
|
(of_int size lsl 4)
|
||||||
|
lor (of_int (Str_ref.inline (String.length name)) lsl 16))
|
||||||
|
in
|
||||||
|
|
||||||
|
match data with
|
||||||
|
| `None ->
|
||||||
|
let hd = hd_arg_size in
|
||||||
|
Buf.add_i64 buf hd;
|
||||||
|
Buf.add_string buf name
|
||||||
|
| `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 ->
|
||||||
|
(* 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 ->
|
||||||
|
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 ->
|
||||||
|
let hd =
|
||||||
|
I64.(
|
||||||
|
6L lor hd_arg_size
|
||||||
|
lor (of_int (Str_ref.inline (String.length s)) lsl 32))
|
||||||
|
in
|
||||||
|
Buf.add_i64 buf hd;
|
||||||
|
Buf.add_string buf name;
|
||||||
|
Buf.add_string buf s
|
||||||
|
| `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 ->
|
||||||
|
(* 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)
|
||||||
|
end
|
||||||
|
|
||||||
|
module Arguments = struct
|
||||||
|
type 'a t = 'a Argument.t list
|
||||||
|
|
||||||
|
let[@inline] len (self : _ t) : int =
|
||||||
|
match self with
|
||||||
|
| [] -> 0
|
||||||
|
| [ _ ] -> 1
|
||||||
|
| _ :: _ :: tl -> 2 + List.length tl
|
||||||
|
|
||||||
|
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) =
|
||||||
|
match self with
|
||||||
|
| [] -> 0
|
||||||
|
| [ a ] -> Argument.size_word a
|
||||||
|
| a :: b :: tl ->
|
||||||
|
List.fold_left
|
||||||
|
(fun n arg -> n + Argument.size_word arg)
|
||||||
|
(Argument.size_word a + Argument.size_word b)
|
||||||
|
tl
|
||||||
|
|
||||||
|
let[@inline] encode (buf : Buf.t) (self : _ t) =
|
||||||
|
let rec aux buf l =
|
||||||
|
match l with
|
||||||
|
| [] -> ()
|
||||||
|
| x :: tl ->
|
||||||
|
Argument.encode buf x;
|
||||||
|
aux buf tl
|
||||||
|
in
|
||||||
|
|
||||||
|
match self with
|
||||||
|
| [] -> ()
|
||||||
|
| [ x ] -> Argument.encode buf x
|
||||||
|
| x :: tl ->
|
||||||
|
Argument.encode buf x;
|
||||||
|
aux buf tl
|
||||||
|
end
|
||||||
|
|
||||||
|
(** record type = 3 *)
|
||||||
|
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 =
|
||||||
|
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 hd = I64.(3L lor (of_int size_word lsl 4) lor (of_int as_ref lsl 16)) in
|
||||||
|
Buf.add_i64 buf hd;
|
||||||
|
Buf.add_i64 buf (I64.of_int pid);
|
||||||
|
Buf.add_i64 buf (I64.of_int tid)
|
||||||
|
end
|
||||||
|
|
||||||
|
(** record type = 4 *)
|
||||||
|
module Event = struct
|
||||||
|
(** type=0 *)
|
||||||
|
module Instant = struct
|
||||||
|
let size_word ~name ~t_ref ~args () : int =
|
||||||
|
1 + Thread_ref.size_word t_ref + 1
|
||||||
|
(* timestamp *) + (round_to_word (String.length name) / 8)
|
||||||
|
+ Arguments.size_word args
|
||||||
|
|
||||||
|
let encode (out : Output.t) ~name ~(t_ref : Thread_ref.t) ~time_ns ~args ()
|
||||||
|
: unit =
|
||||||
|
let size = size_word ~name ~t_ref ~args () in
|
||||||
|
let buf = Output.get_buf out ~available_word:size in
|
||||||
|
|
||||||
|
(* set category = 0 *)
|
||||||
|
let hd =
|
||||||
|
I64.(
|
||||||
|
4L
|
||||||
|
lor (of_int size lsl 4)
|
||||||
|
lor (of_int (Arguments.len args) lsl 20)
|
||||||
|
lor (of_int (Thread_ref.as_i8 t_ref) lsl 24)
|
||||||
|
lor (of_int (Str_ref.inline (String.length name)) lsl 48))
|
||||||
|
in
|
||||||
|
Buf.add_i64 buf hd;
|
||||||
|
Buf.add_i64 buf time_ns;
|
||||||
|
|
||||||
|
(match t_ref with
|
||||||
|
| Thread_ref.Inline { pid; tid } ->
|
||||||
|
Buf.add_i64 buf (I64.of_int pid);
|
||||||
|
Buf.add_i64 buf (I64.of_int tid)
|
||||||
|
| Thread_ref.Ref _ -> ());
|
||||||
|
|
||||||
|
Buf.add_string buf name;
|
||||||
|
Arguments.encode buf args;
|
||||||
|
()
|
||||||
|
end
|
||||||
|
|
||||||
|
(** type=1 *)
|
||||||
|
module Counter = struct
|
||||||
|
let size_word ~name ~t_ref ~args () : int =
|
||||||
|
1 + Thread_ref.size_word t_ref + 1
|
||||||
|
(* timestamp *) + (round_to_word (String.length name) lsr 3)
|
||||||
|
+ Arguments.size_word args + 1 (* counter id *)
|
||||||
|
|
||||||
|
let encode (out : Output.t) ~name ~(t_ref : Thread_ref.t) ~time_ns ~args ()
|
||||||
|
: unit =
|
||||||
|
let size = size_word ~name ~t_ref ~args () in
|
||||||
|
let buf = Output.get_buf out ~available_word:size in
|
||||||
|
|
||||||
|
let hd =
|
||||||
|
I64.(
|
||||||
|
4L
|
||||||
|
lor (of_int size lsl 4)
|
||||||
|
lor (1L lsl 16)
|
||||||
|
lor (of_int (Arguments.len args) lsl 20)
|
||||||
|
lor (of_int (Thread_ref.as_i8 t_ref) lsl 24)
|
||||||
|
lor (of_int (Str_ref.inline (String.length name)) lsl 48))
|
||||||
|
in
|
||||||
|
Buf.add_i64 buf hd;
|
||||||
|
Buf.add_i64 buf time_ns;
|
||||||
|
|
||||||
|
(match t_ref with
|
||||||
|
| Thread_ref.Inline { pid; tid } ->
|
||||||
|
Buf.add_i64 buf (I64.of_int pid);
|
||||||
|
Buf.add_i64 buf (I64.of_int tid)
|
||||||
|
| Thread_ref.Ref _ -> ());
|
||||||
|
|
||||||
|
Buf.add_string buf name;
|
||||||
|
Arguments.encode buf args;
|
||||||
|
(* just use 0 as counter id *)
|
||||||
|
Buf.add_i64 buf 0L;
|
||||||
|
()
|
||||||
|
end
|
||||||
|
|
||||||
|
(** type=2 *)
|
||||||
|
module Duration_begin = struct
|
||||||
|
let size_word ~name ~t_ref ~args () : int =
|
||||||
|
1 + Thread_ref.size_word t_ref + 1
|
||||||
|
(* timestamp *) + (round_to_word (String.length name) lsr 3)
|
||||||
|
+ Arguments.size_word args
|
||||||
|
|
||||||
|
let encode (out : Output.t) ~name ~(t_ref : Thread_ref.t) ~time_ns ~args ()
|
||||||
|
: unit =
|
||||||
|
let size = size_word ~name ~t_ref ~args () in
|
||||||
|
let buf = Output.get_buf out ~available_word:size in
|
||||||
|
|
||||||
|
let hd =
|
||||||
|
I64.(
|
||||||
|
4L
|
||||||
|
lor (of_int size lsl 4)
|
||||||
|
lor (2L lsl 16)
|
||||||
|
lor (of_int (Arguments.len args) lsl 20)
|
||||||
|
lor (of_int (Thread_ref.as_i8 t_ref) lsl 24)
|
||||||
|
lor (of_int (Str_ref.inline (String.length name)) lsl 48))
|
||||||
|
in
|
||||||
|
Buf.add_i64 buf hd;
|
||||||
|
Buf.add_i64 buf time_ns;
|
||||||
|
|
||||||
|
(match t_ref with
|
||||||
|
| Thread_ref.Inline { pid; tid } ->
|
||||||
|
Buf.add_i64 buf (I64.of_int pid);
|
||||||
|
Buf.add_i64 buf (I64.of_int tid)
|
||||||
|
| Thread_ref.Ref _ -> ());
|
||||||
|
|
||||||
|
Buf.add_string buf name;
|
||||||
|
Arguments.encode buf args;
|
||||||
|
()
|
||||||
|
end
|
||||||
|
|
||||||
|
(** type=3 *)
|
||||||
|
module Duration_end = struct
|
||||||
|
let size_word ~name ~t_ref ~args () : int =
|
||||||
|
1 + Thread_ref.size_word t_ref + 1
|
||||||
|
(* timestamp *) + (round_to_word (String.length name) lsr 3)
|
||||||
|
+ Arguments.size_word args
|
||||||
|
|
||||||
|
let encode (out : Output.t) ~name ~(t_ref : Thread_ref.t) ~time_ns ~args ()
|
||||||
|
: unit =
|
||||||
|
let size = size_word ~name ~t_ref ~args () in
|
||||||
|
let buf = Output.get_buf out ~available_word:size in
|
||||||
|
|
||||||
|
let hd =
|
||||||
|
I64.(
|
||||||
|
4L
|
||||||
|
lor (of_int size lsl 4)
|
||||||
|
lor (3L lsl 16)
|
||||||
|
lor (of_int (Arguments.len args) lsl 20)
|
||||||
|
lor (of_int (Thread_ref.as_i8 t_ref) lsl 24)
|
||||||
|
lor (of_int (Str_ref.inline (String.length name)) lsl 48))
|
||||||
|
in
|
||||||
|
Buf.add_i64 buf hd;
|
||||||
|
Buf.add_i64 buf time_ns;
|
||||||
|
|
||||||
|
(match t_ref with
|
||||||
|
| Thread_ref.Inline { pid; tid } ->
|
||||||
|
Buf.add_i64 buf (I64.of_int pid);
|
||||||
|
Buf.add_i64 buf (I64.of_int tid)
|
||||||
|
| Thread_ref.Ref _ -> ());
|
||||||
|
|
||||||
|
Buf.add_string buf name;
|
||||||
|
Arguments.encode buf args;
|
||||||
|
()
|
||||||
|
end
|
||||||
|
|
||||||
|
(** type=4 *)
|
||||||
|
module Duration_complete = struct
|
||||||
|
let size_word ~name ~t_ref ~args () : int =
|
||||||
|
1 + Thread_ref.size_word t_ref + 1
|
||||||
|
(* timestamp *) + (round_to_word (String.length name) lsr 3)
|
||||||
|
+ Arguments.size_word args + 1 (* end timestamp *)
|
||||||
|
|
||||||
|
let encode (out : Output.t) ~name ~(t_ref : Thread_ref.t) ~time_ns
|
||||||
|
~end_time_ns ~args () : unit =
|
||||||
|
let size = size_word ~name ~t_ref ~args () in
|
||||||
|
let buf = Output.get_buf out ~available_word:size in
|
||||||
|
|
||||||
|
(* set category = 0 *)
|
||||||
|
let hd =
|
||||||
|
I64.(
|
||||||
|
4L
|
||||||
|
lor (of_int size lsl 4)
|
||||||
|
lor (4L lsl 16)
|
||||||
|
lor (of_int (Arguments.len args) lsl 20)
|
||||||
|
lor (of_int (Thread_ref.as_i8 t_ref) lsl 24)
|
||||||
|
lor (of_int (Str_ref.inline (String.length name)) lsl 48))
|
||||||
|
in
|
||||||
|
Buf.add_i64 buf hd;
|
||||||
|
Buf.add_i64 buf time_ns;
|
||||||
|
|
||||||
|
(match t_ref with
|
||||||
|
| Thread_ref.Inline { pid; tid } ->
|
||||||
|
Buf.add_i64 buf (I64.of_int pid);
|
||||||
|
Buf.add_i64 buf (I64.of_int tid)
|
||||||
|
| Thread_ref.Ref _ -> ());
|
||||||
|
|
||||||
|
Buf.add_string buf name;
|
||||||
|
Arguments.encode buf args;
|
||||||
|
Buf.add_i64 buf end_time_ns;
|
||||||
|
()
|
||||||
|
end
|
||||||
|
|
||||||
|
(** type=5 *)
|
||||||
|
module Async_begin = struct
|
||||||
|
let size_word ~name ~t_ref ~args () : int =
|
||||||
|
1 + Thread_ref.size_word t_ref + 1
|
||||||
|
(* timestamp *) + (round_to_word (String.length name) lsr 3)
|
||||||
|
+ Arguments.size_word args + 1 (* async id *)
|
||||||
|
|
||||||
|
let encode (out : Output.t) ~name ~(t_ref : Thread_ref.t) ~time_ns
|
||||||
|
~(async_id : int) ~args () : unit =
|
||||||
|
let size = size_word ~name ~t_ref ~args () in
|
||||||
|
let buf = Output.get_buf out ~available_word:size in
|
||||||
|
|
||||||
|
let hd =
|
||||||
|
I64.(
|
||||||
|
4L
|
||||||
|
lor (of_int size lsl 4)
|
||||||
|
lor (5L lsl 16)
|
||||||
|
lor (of_int (Arguments.len args) lsl 20)
|
||||||
|
lor (of_int (Thread_ref.as_i8 t_ref) lsl 24)
|
||||||
|
lor (of_int (Str_ref.inline (String.length name)) lsl 48))
|
||||||
|
in
|
||||||
|
Buf.add_i64 buf hd;
|
||||||
|
Buf.add_i64 buf time_ns;
|
||||||
|
|
||||||
|
(match t_ref with
|
||||||
|
| Thread_ref.Inline { pid; tid } ->
|
||||||
|
Buf.add_i64 buf (I64.of_int pid);
|
||||||
|
Buf.add_i64 buf (I64.of_int tid)
|
||||||
|
| Thread_ref.Ref _ -> ());
|
||||||
|
|
||||||
|
Buf.add_string buf name;
|
||||||
|
Arguments.encode buf args;
|
||||||
|
Buf.add_i64 buf (I64.of_int async_id);
|
||||||
|
()
|
||||||
|
end
|
||||||
|
|
||||||
|
(** type=7 *)
|
||||||
|
module Async_end = struct
|
||||||
|
let size_word ~name ~t_ref ~args () : int =
|
||||||
|
1 + Thread_ref.size_word t_ref + 1
|
||||||
|
(* timestamp *) + (round_to_word (String.length name) lsr 3)
|
||||||
|
+ Arguments.size_word args + 1 (* async id *)
|
||||||
|
|
||||||
|
let encode (out : Output.t) ~name ~(t_ref : Thread_ref.t) ~time_ns
|
||||||
|
~(async_id : int) ~args () : unit =
|
||||||
|
let size = size_word ~name ~t_ref ~args () in
|
||||||
|
let buf = Output.get_buf out ~available_word:size in
|
||||||
|
|
||||||
|
let hd =
|
||||||
|
I64.(
|
||||||
|
4L
|
||||||
|
lor (of_int size lsl 4)
|
||||||
|
lor (7L lsl 16)
|
||||||
|
lor (of_int (Arguments.len args) lsl 20)
|
||||||
|
lor (of_int (Thread_ref.as_i8 t_ref) lsl 24)
|
||||||
|
lor (of_int (Str_ref.inline (String.length name)) lsl 48))
|
||||||
|
in
|
||||||
|
Buf.add_i64 buf hd;
|
||||||
|
Buf.add_i64 buf time_ns;
|
||||||
|
|
||||||
|
(match t_ref with
|
||||||
|
| Thread_ref.Inline { pid; tid } ->
|
||||||
|
Buf.add_i64 buf (I64.of_int pid);
|
||||||
|
Buf.add_i64 buf (I64.of_int tid)
|
||||||
|
| Thread_ref.Ref _ -> ());
|
||||||
|
|
||||||
|
Buf.add_string buf name;
|
||||||
|
Arguments.encode buf args;
|
||||||
|
Buf.add_i64 buf (I64.of_int async_id);
|
||||||
|
()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
(** record type = 7 *)
|
||||||
|
module Kernel_object = struct
|
||||||
|
let size_word ~name ~args () : int =
|
||||||
|
1 + 1
|
||||||
|
+ (round_to_word (String.length name) lsr 3)
|
||||||
|
+ Arguments.size_word args
|
||||||
|
|
||||||
|
(* see:
|
||||||
|
https://cs.opensource.google/fuchsia/fuchsia/+/main:zircon/system/public/zircon/types.h;l=441?q=ZX_OBJ_TYPE&ss=fuchsia%2Ffuchsia
|
||||||
|
*)
|
||||||
|
|
||||||
|
type ty = int
|
||||||
|
|
||||||
|
let ty_process : ty = 1
|
||||||
|
let ty_thread : ty = 2
|
||||||
|
|
||||||
|
let encode (out : Output.t) ~name ~(ty : ty) ~(kid : int) ~args () : unit =
|
||||||
|
let size = size_word ~name ~args () in
|
||||||
|
let buf = Output.get_buf out ~available_word:size in
|
||||||
|
|
||||||
|
let hd =
|
||||||
|
I64.(
|
||||||
|
7L
|
||||||
|
lor (of_int size lsl 4)
|
||||||
|
lor (of_int ty lsl 16)
|
||||||
|
lor (of_int (Arguments.len args) lsl 40)
|
||||||
|
lor (of_int (Str_ref.inline (String.length name)) lsl 24))
|
||||||
|
in
|
||||||
|
Buf.add_i64 buf hd;
|
||||||
|
Buf.add_i64 buf (I64.of_int kid);
|
||||||
|
Buf.add_string buf name;
|
||||||
|
Arguments.encode buf args;
|
||||||
|
()
|
||||||
|
end
|
||||||
5
src/fuchsia/write/util.ml
Normal file
5
src/fuchsia/write/util.ml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
(** How many bytes are missing for [n] to be a multiple of 8 *)
|
||||||
|
let[@inline] missing_to_round (n : int) : int = lnot (n - 1) land 0b111
|
||||||
|
|
||||||
|
(** Round up to a multiple of 8 *)
|
||||||
|
let[@inline] round_to_word (n : int) : int = n + (lnot (n - 1) land 0b111)
|
||||||
|
|
@ -8,19 +8,30 @@ let location_errorf ~loc fmt =
|
||||||
|
|
||||||
(** {2 let expression} *)
|
(** {2 let expression} *)
|
||||||
|
|
||||||
let expand_let ~ctxt (name : string) body =
|
let expand_let ~ctxt (var : [ `Var of label loc | `Unit ]) (name : string) body
|
||||||
|
=
|
||||||
let loc = Expansion_context.Extension.extension_point_loc ctxt in
|
let loc = Expansion_context.Extension.extension_point_loc ctxt in
|
||||||
Ast_builder.Default.(
|
Ast_builder.Default.(
|
||||||
|
let var_pat =
|
||||||
|
match var with
|
||||||
|
| `Var v -> ppat_var ~loc:v.loc v
|
||||||
|
| `Unit -> ppat_var ~loc { loc; txt = "_trace_span" }
|
||||||
|
in
|
||||||
|
let var_exp =
|
||||||
|
match var with
|
||||||
|
| `Var v -> pexp_ident ~loc:v.loc { txt = lident v.txt; loc = v.loc }
|
||||||
|
| `Unit -> [%expr _trace_span]
|
||||||
|
in
|
||||||
[%expr
|
[%expr
|
||||||
let _trace_span =
|
let [%p var_pat] =
|
||||||
Trace_core.enter_span ~__FILE__ ~__LINE__ [%e estring ~loc name]
|
Trace_core.enter_span ~__FILE__ ~__LINE__ [%e estring ~loc name]
|
||||||
in
|
in
|
||||||
try
|
try
|
||||||
let res = [%e body] in
|
let res = [%e body] in
|
||||||
Trace_core.exit_span _trace_span;
|
Trace_core.exit_span [%e var_exp];
|
||||||
res
|
res
|
||||||
with exn ->
|
with exn ->
|
||||||
Trace_core.exit_span _trace_span;
|
Trace_core.exit_span [%e var_exp];
|
||||||
raise exn])
|
raise exn])
|
||||||
|
|
||||||
let extension_let =
|
let extension_let =
|
||||||
|
|
@ -29,7 +40,13 @@ let extension_let =
|
||||||
single_expr_payload
|
single_expr_payload
|
||||||
(pexp_let nonrecursive
|
(pexp_let nonrecursive
|
||||||
(value_binding
|
(value_binding
|
||||||
~pat:(ppat_construct (lident (string "()")) none)
|
~pat:
|
||||||
|
(let pat_var = ppat_var __' |> map ~f:(fun f v -> f (`Var v)) in
|
||||||
|
let pat_unit =
|
||||||
|
as__ @@ ppat_construct (lident (string "()")) none
|
||||||
|
|> map ~f:(fun f _ -> f `Unit)
|
||||||
|
in
|
||||||
|
alt pat_var pat_unit)
|
||||||
~expr:(estring __)
|
~expr:(estring __)
|
||||||
^:: nil)
|
^:: nil)
|
||||||
__))
|
__))
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,4 @@
|
||||||
(name trace_tef)
|
(name trace_tef)
|
||||||
(public_name trace-tef)
|
(public_name trace-tef)
|
||||||
(synopsis "Simple and lightweight tracing using TEF/Catapult format, in-process")
|
(synopsis "Simple and lightweight tracing using TEF/Catapult format, in-process")
|
||||||
(libraries trace.core mtime mtime.clock.os atomic unix threads
|
(libraries trace.core trace.private.util mtime mtime.clock.os unix threads))
|
||||||
(select relax_.ml from
|
|
||||||
(base-domain -> relax_.real.ml)
|
|
||||||
( -> relax_.dummy.ml))))
|
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
let cpu_relax = Domain.cpu_relax
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
open Trace_core
|
open Trace_core
|
||||||
|
open Trace_private_util
|
||||||
module A = Trace_core.Internal_.Atomic_
|
module A = Trace_core.Internal_.Atomic_
|
||||||
|
|
||||||
module Mock_ = struct
|
module Mock_ = struct
|
||||||
|
|
@ -14,7 +15,7 @@ end
|
||||||
let counter = Mtime_clock.counter ()
|
let counter = Mtime_clock.counter ()
|
||||||
|
|
||||||
(** Now, in microseconds *)
|
(** Now, in microseconds *)
|
||||||
let now_us () : float =
|
let[@inline] now_us () : float =
|
||||||
if !Mock_.enabled then
|
if !Mock_.enabled then
|
||||||
Mock_.now_us ()
|
Mock_.now_us ()
|
||||||
else (
|
else (
|
||||||
|
|
@ -22,16 +23,6 @@ let now_us () : float =
|
||||||
Mtime.Span.to_float_ns t /. 1e3
|
Mtime.Span.to_float_ns t /. 1e3
|
||||||
)
|
)
|
||||||
|
|
||||||
let protect ~finally f =
|
|
||||||
try
|
|
||||||
let x = f () in
|
|
||||||
finally ();
|
|
||||||
x
|
|
||||||
with exn ->
|
|
||||||
let bt = Printexc.get_raw_backtrace () in
|
|
||||||
finally ();
|
|
||||||
Printexc.raise_with_backtrace exn bt
|
|
||||||
|
|
||||||
let on_tracing_error = ref (fun s -> Printf.eprintf "trace-tef error: %s\n%!" s)
|
let on_tracing_error = ref (fun s -> Printf.eprintf "trace-tef error: %s\n%!" s)
|
||||||
|
|
||||||
type event =
|
type event =
|
||||||
|
|
@ -144,7 +135,7 @@ module Writer = struct
|
||||||
|
|
||||||
let with_ ~out f =
|
let with_ ~out f =
|
||||||
let writer = create ~out () in
|
let writer = create ~out () in
|
||||||
protect ~finally:(fun () -> close writer) (fun () -> f writer)
|
Fun.protect ~finally:(fun () -> close writer) (fun () -> f writer)
|
||||||
|
|
||||||
let[@inline] flush (self : t) : unit = flush self.oc
|
let[@inline] flush (self : t) : unit = flush self.oc
|
||||||
|
|
||||||
|
|
@ -499,7 +490,7 @@ let setup ?(out = `Env) () =
|
||||||
|
|
||||||
let with_setup ?out () f =
|
let with_setup ?out () f =
|
||||||
setup ?out ();
|
setup ?out ();
|
||||||
protect ~finally:Trace_core.shutdown f
|
Fun.protect ~finally:Trace_core.shutdown f
|
||||||
|
|
||||||
module Internal_ = struct
|
module Internal_ = struct
|
||||||
let mock_all_ () = Mock_.enabled := true
|
let mock_all_ () = Mock_.enabled := true
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
|
module A = Trace_core.Internal_.Atomic_
|
||||||
|
|
||||||
type 'a t = {
|
type 'a t = {
|
||||||
mutex: Mutex.t;
|
mutex: Mutex.t;
|
||||||
cond: Condition.t;
|
cond: Condition.t;
|
||||||
q: 'a Mpsc_bag.t;
|
q: 'a Mpsc_bag.t;
|
||||||
mutable closed: bool;
|
mutable closed: bool;
|
||||||
consumer_waiting: bool Atomic.t;
|
consumer_waiting: bool A.t;
|
||||||
}
|
}
|
||||||
|
|
||||||
exception Closed
|
exception Closed
|
||||||
|
|
@ -14,7 +16,7 @@ let create () : _ t =
|
||||||
cond = Condition.create ();
|
cond = Condition.create ();
|
||||||
q = Mpsc_bag.create ();
|
q = Mpsc_bag.create ();
|
||||||
closed = false;
|
closed = false;
|
||||||
consumer_waiting = Atomic.make false;
|
consumer_waiting = A.make false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let close (self : _ t) =
|
let close (self : _ t) =
|
||||||
|
|
@ -29,7 +31,7 @@ let push (self : _ t) x : unit =
|
||||||
if self.closed then raise Closed;
|
if self.closed then raise Closed;
|
||||||
Mpsc_bag.add self.q x;
|
Mpsc_bag.add self.q x;
|
||||||
if self.closed then raise Closed;
|
if self.closed then raise Closed;
|
||||||
if Atomic.get self.consumer_waiting then (
|
if A.get self.consumer_waiting then (
|
||||||
(* wakeup consumer *)
|
(* wakeup consumer *)
|
||||||
Mutex.lock self.mutex;
|
Mutex.lock self.mutex;
|
||||||
Condition.broadcast self.cond;
|
Condition.broadcast self.cond;
|
||||||
|
|
@ -42,14 +44,14 @@ let rec pop_all (self : 'a t) : 'a list =
|
||||||
| None ->
|
| None ->
|
||||||
if self.closed then raise Closed;
|
if self.closed then raise Closed;
|
||||||
Mutex.lock self.mutex;
|
Mutex.lock self.mutex;
|
||||||
Atomic.set self.consumer_waiting true;
|
A.set self.consumer_waiting true;
|
||||||
(* check again, a producer might have pushed an element since we
|
(* check again, a producer might have pushed an element since we
|
||||||
last checked. However if we still find
|
last checked. However if we still find
|
||||||
nothing, because this comes after [consumer_waiting:=true],
|
nothing, because this comes after [consumer_waiting:=true],
|
||||||
any producer arriving after that will know to wake us up. *)
|
any producer arriving after that will know to wake us up. *)
|
||||||
(match Mpsc_bag.pop_all self.q with
|
(match Mpsc_bag.pop_all self.q with
|
||||||
| Some l ->
|
| Some l ->
|
||||||
Atomic.set self.consumer_waiting false;
|
A.set self.consumer_waiting false;
|
||||||
Mutex.unlock self.mutex;
|
Mutex.unlock self.mutex;
|
||||||
l
|
l
|
||||||
| None ->
|
| None ->
|
||||||
|
|
@ -58,6 +60,6 @@ let rec pop_all (self : 'a t) : 'a list =
|
||||||
raise Closed
|
raise Closed
|
||||||
);
|
);
|
||||||
Condition.wait self.cond self.mutex;
|
Condition.wait self.cond self.mutex;
|
||||||
Atomic.set self.consumer_waiting false;
|
A.set self.consumer_waiting false;
|
||||||
Mutex.unlock self.mutex;
|
Mutex.unlock self.mutex;
|
||||||
pop_all self)
|
pop_all self)
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
let cpu_relax () = ()
|
let cpu_relax () = ()
|
||||||
|
let n_domains () = 1
|
||||||
2
src/util/domain_util.mli
Normal file
2
src/util/domain_util.mli
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
val cpu_relax : unit -> unit
|
||||||
|
val n_domains : unit -> int
|
||||||
2
src/util/domain_util.real.ml
Normal file
2
src/util/domain_util.real.ml
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
let cpu_relax = Domain.cpu_relax
|
||||||
|
let n_domains = Domain.recommended_domain_count
|
||||||
9
src/util/dune
Normal file
9
src/util/dune
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
|
||||||
|
(library
|
||||||
|
(public_name trace.private.util)
|
||||||
|
(synopsis "internal utilities for trace. No guarantees of stability.")
|
||||||
|
(name trace_private_util)
|
||||||
|
(libraries trace.core mtime mtime.clock.os unix threads
|
||||||
|
(select domain_util.ml from
|
||||||
|
(base-domain -> domain_util.real.ml)
|
||||||
|
( -> domain_util.dummy.ml))))
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
type 'a t = { bag: 'a list Atomic.t } [@@unboxed]
|
module A = Trace_core.Internal_.Atomic_
|
||||||
|
|
||||||
|
type 'a t = { bag: 'a list A.t } [@@unboxed]
|
||||||
|
|
||||||
let create () =
|
let create () =
|
||||||
let bag = Atomic.make [] in
|
let bag = A.make [] in
|
||||||
{ bag }
|
{ bag }
|
||||||
|
|
||||||
module Backoff = struct
|
module Backoff = struct
|
||||||
|
|
@ -11,20 +13,20 @@ module Backoff = struct
|
||||||
|
|
||||||
let once (b : t) : t =
|
let once (b : t) : t =
|
||||||
for _i = 1 to b do
|
for _i = 1 to b do
|
||||||
Relax_.cpu_relax ()
|
Domain_util.cpu_relax ()
|
||||||
done;
|
done;
|
||||||
min (b * 2) 256
|
min (b * 2) 256
|
||||||
end
|
end
|
||||||
|
|
||||||
let rec add backoff t x =
|
let rec add backoff t x =
|
||||||
let before = Atomic.get t.bag in
|
let before = A.get t.bag in
|
||||||
let after = x :: before in
|
let after = x :: before in
|
||||||
if not (Atomic.compare_and_set t.bag before after) then
|
if not (A.compare_and_set t.bag before after) then
|
||||||
add (Backoff.once backoff) t x
|
add (Backoff.once backoff) t x
|
||||||
|
|
||||||
let[@inline] add t x = add Backoff.default t x
|
let[@inline] add t x = add Backoff.default t x
|
||||||
|
|
||||||
let[@inline] pop_all t : _ list option =
|
let[@inline] pop_all t : _ list option =
|
||||||
match Atomic.exchange t.bag [] with
|
match A.exchange t.bag [] with
|
||||||
| [] -> None
|
| [] -> None
|
||||||
| l -> Some (List.rev l)
|
| l -> Some (List.rev l)
|
||||||
5
test/fuchsia/write/dune
Normal file
5
test/fuchsia/write/dune
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
|
||||||
|
(tests
|
||||||
|
(names t1 t2)
|
||||||
|
(package trace-fuchsia)
|
||||||
|
(libraries trace-fuchsia.write))
|
||||||
65
test/fuchsia/write/t1.ml
Normal file
65
test/fuchsia/write/t1.ml
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
open Trace_fuchsia_write
|
||||||
|
|
||||||
|
module Str_ = struct
|
||||||
|
open String
|
||||||
|
|
||||||
|
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 * length s) in
|
||||||
|
for i = 0 to length s - 1 do
|
||||||
|
let n = Char.code (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 of_hex_exn (s : string) : string =
|
||||||
|
let n_of_c = function
|
||||||
|
| '0' .. '9' as c -> Char.code c - Char.code '0'
|
||||||
|
| 'a' .. 'f' as c -> 10 + Char.code c - Char.code 'a'
|
||||||
|
| 'A' .. 'F' as c -> 10 + Char.code c - Char.code 'A'
|
||||||
|
| _ -> invalid_arg "string: invalid hex"
|
||||||
|
in
|
||||||
|
if String.length s mod 2 <> 0 then
|
||||||
|
invalid_arg "string: hex sequence must be of even length";
|
||||||
|
let res = Bytes.make (String.length s / 2) '\x00' in
|
||||||
|
for i = 0 to (String.length s / 2) - 1 do
|
||||||
|
let n1 = n_of_c (String.get s (2 * i)) in
|
||||||
|
let n2 = n_of_c (String.get s ((2 * i) + 1)) in
|
||||||
|
let n = (n1 lsl 4) lor n2 in
|
||||||
|
Bytes.set res i (Char.chr n)
|
||||||
|
done;
|
||||||
|
Bytes.unsafe_to_string res
|
||||||
|
end
|
||||||
|
|
||||||
|
let () =
|
||||||
|
let l = List.init 100 (fun i -> 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);
|
||||||
|
()
|
||||||
|
|
||||||
|
let () =
|
||||||
|
let buf = Buf.create 128 in
|
||||||
|
Buf.add_i64 buf 42L;
|
||||||
|
assert (Buf.to_string buf = "\x2a\x00\x00\x00\x00\x00\x00\x00")
|
||||||
|
|
||||||
|
let () =
|
||||||
|
let buf = Buf.create 128 in
|
||||||
|
Buf.add_string buf "";
|
||||||
|
assert (Buf.to_string buf = "")
|
||||||
|
|
||||||
|
let () =
|
||||||
|
let buf = Buf.create 128 in
|
||||||
|
Buf.add_string buf "hello";
|
||||||
|
assert (Buf.to_string buf = "hello\x00\x00\x00")
|
||||||
4
test/fuchsia/write/t2.expected
Normal file
4
test/fuchsia/write/t2.expected
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
first trace
|
||||||
|
100004467854160033000500000000000100000000000000560000000000000054001005000005804e61bc000000000068656c6c6f000000210001802a0000007800000000000000
|
||||||
|
second trace
|
||||||
|
1000044678541600210000000000000000ca9a3b00000000330005000000000001000000000000005600000000000000300011000000b0006f63616d6c2d747261636500000000004400040500000580a0860100000000006f75746572000000404b4c0000000000440004050000058020bf020000000000696e6e657200000020aa440000000000540010050000058087d612000000000068656c6c6f000000210001802a0000007800000000000000
|
||||||
89
test/fuchsia/write/t2.ml
Normal file
89
test/fuchsia/write/t2.ml
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
open Trace_fuchsia_write
|
||||||
|
|
||||||
|
let pf = Printf.printf
|
||||||
|
|
||||||
|
module Str_ = struct
|
||||||
|
open String
|
||||||
|
|
||||||
|
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 * length s) in
|
||||||
|
for i = 0 to length s - 1 do
|
||||||
|
let n = Char.code (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 of_hex_exn (s : string) : string =
|
||||||
|
let n_of_c = function
|
||||||
|
| '0' .. '9' as c -> Char.code c - Char.code '0'
|
||||||
|
| 'a' .. 'f' as c -> 10 + Char.code c - Char.code 'a'
|
||||||
|
| 'A' .. 'F' as c -> 10 + Char.code c - Char.code 'A'
|
||||||
|
| _ -> invalid_arg "string: invalid hex"
|
||||||
|
in
|
||||||
|
if String.length s mod 2 <> 0 then
|
||||||
|
invalid_arg "string: hex sequence must be of even length";
|
||||||
|
let res = Bytes.make (String.length s / 2) '\x00' in
|
||||||
|
for i = 0 to (String.length s / 2) - 1 do
|
||||||
|
let n1 = n_of_c (String.get s (2 * i)) in
|
||||||
|
let n2 = n_of_c (String.get s ((2 * i) + 1)) in
|
||||||
|
let n = (n1 lsl 4) lor n2 in
|
||||||
|
Bytes.set res i (Char.chr n)
|
||||||
|
done;
|
||||||
|
Bytes.unsafe_to_string res
|
||||||
|
end
|
||||||
|
|
||||||
|
let with_buf_output (f : Output.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;
|
||||||
|
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
|
||||||
|
~t_ref:(Thread_ref.Ref 5)
|
||||||
|
~args:[ "x", `Int 42 ]
|
||||||
|
())
|
||||||
|
in
|
||||||
|
pf "%s\n" (Str_.to_hex str)
|
||||||
|
|
||||||
|
let () = pf "second trace\n"
|
||||||
|
|
||||||
|
let () =
|
||||||
|
let str =
|
||||||
|
with_buf_output (fun out ->
|
||||||
|
Metadata.Magic_record.encode out;
|
||||||
|
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"
|
||||||
|
~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 ]
|
||||||
|
())
|
||||||
|
in
|
||||||
|
(let oc = open_out "foo.fxt" in
|
||||||
|
output_string oc str;
|
||||||
|
close_out oc);
|
||||||
|
pf "%s\n" (Str_.to_hex str)
|
||||||
37
trace-fuchsia.opam
Normal file
37
trace-fuchsia.opam
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
# This file is generated by dune, edit dune-project instead
|
||||||
|
opam-version: "2.0"
|
||||||
|
version: "0.5"
|
||||||
|
synopsis:
|
||||||
|
"A high-performance backend for trace, emitting a Fuchsia trace into a file"
|
||||||
|
maintainer: ["Simon Cruanes"]
|
||||||
|
authors: ["Simon Cruanes"]
|
||||||
|
license: "MIT"
|
||||||
|
tags: ["trace" "tracing" "fuchsia"]
|
||||||
|
homepage: "https://github.com/c-cube/ocaml-trace"
|
||||||
|
bug-reports: "https://github.com/c-cube/ocaml-trace/issues"
|
||||||
|
depends: [
|
||||||
|
"ocaml" {>= "4.08"}
|
||||||
|
"trace" {= version}
|
||||||
|
"mtime" {>= "2.0"}
|
||||||
|
"base-bigarray"
|
||||||
|
"base-unix"
|
||||||
|
"dune" {>= "2.9"}
|
||||||
|
"odoc" {with-doc}
|
||||||
|
]
|
||||||
|
build: [
|
||||||
|
["dune" "subst"] {dev}
|
||||||
|
[
|
||||||
|
"dune"
|
||||||
|
"build"
|
||||||
|
"-p"
|
||||||
|
name
|
||||||
|
"-j"
|
||||||
|
jobs
|
||||||
|
"--promote-install-files=false"
|
||||||
|
"@install"
|
||||||
|
"@runtest" {with-test}
|
||||||
|
"@doc" {with-doc}
|
||||||
|
]
|
||||||
|
["dune" "install" "-p" name "--create-install-files" name]
|
||||||
|
]
|
||||||
|
dev-repo: "git+https://github.com/c-cube/ocaml-trace.git"
|
||||||
|
|
@ -14,7 +14,6 @@ depends: [
|
||||||
"trace" {= version}
|
"trace" {= version}
|
||||||
"mtime" {>= "2.0"}
|
"mtime" {>= "2.0"}
|
||||||
"base-unix"
|
"base-unix"
|
||||||
"atomic"
|
|
||||||
"dune" {>= "2.9"}
|
"dune" {>= "2.9"}
|
||||||
"odoc" {with-doc}
|
"odoc" {with-doc}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue