Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

migrate to being a trace backend #1

Merged
merged 8 commits into from
Sep 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 53 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Catapult [![build](https://github.com/imandra-ai/catapult/actions/workflows/main.yml/badge.svg)](https://github.com/imandra-ai/catapult/actions/workflows/main.yml)

This is a tracing library for OCaml, based on the
This is a collection of tracing _backends_ for
[ocaml-trace](https://github.com/c-cube/ocaml-trace/), ultimately producing
[Catapult/TEF](https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/)
trace format.

Expand All @@ -12,12 +13,46 @@ The traces are `.json` files (or compressed `.json.gz`). They can be viewed in:

## Usage

The core library is `catapult`. It's a small set of probes that can be
inserted in your code, by hand (with meaningful messages if needed).
Instrument your code using [ocaml-trace](https://github.com/c-cube/ocaml-trace/).
In the program's entry point, use one of the Catapult libraries
backend to forward events from [Trace] into the place of your choice.

An example can be found in 'examples/heavy/heavy.ml'

## sqlite

To collect data directly into a local Sqlite database, use something like:

```ocaml
let main () =̵
let@ writer = Catapult_sqlite.Writer.with_ ~file:!db ~sync:!sync () in
Trace.setup_collector (Catapult_sqlite.trace_collector_of_writer writer);

(* do the actual work here *)
```

(assuming this is in scope:
```ocaml
let (let@) = (@@)
```
)


## network client

The library `catapult-client` provides a tracing backend that forwards all events
(messages, traces, metrics) to a network daemon. The daemon is in the
`catapult-daemon` package.

The traces can be listed and retrieved using the `catapult-conv` program that
comes with `catapult-sqlite`.

## Systemd

An example systemd service file can be found in `src/data/catapult-daemon.service`.
An example systemd service file for this daemon can
be found in `src/data/catapult-daemon.service`.

```systemd
[Unit]
Expand All @@ -36,29 +71,30 @@ RestartSec=10
WantedBy=default.target
```

### Example: "basic"
## Example: "basic"

A very stupid example (in `examples/basic/basic.ml`), is:

```ocaml
module Tr = Catapult.Tracing
let (let@) = (@@)
let spf = Printf.sprintf

let rec fake_trace depth =
if depth>=3 then ()
else (
(* the probe is here *)
Tr.with_ "step" @@ fun () ->
let@ _sp = Trace.with_span ~__FILE__ ~__LINE__ "step" in
Thread.delay 0.1;
Printf.printf "fake (depth=%d)\n%!" depth;
fake_trace (depth+1);
Thread.delay 0.2;
Tr.instant "iteration.done" ~args:["depth", `Int depth];
Trace.message "iteration.done" ~data:(fun () -> ["depth", `Int depth]);
)

let () =
(* this just logs into a file. It's not thread safe nor multiprocess-safe. *)
Catapult_file.with_setup @@ fun () ->
(* address of daemon *)
let addr = Catapult_client.addr_of_string_exn "tcp://localhost:1234" in
let@() = Catapult_client.with_ ~addr () in
let n = try int_of_string (Sys.getenv "N") with _ -> 10 in
Printf.printf "run %d iterations\n%!" n;

Expand All @@ -67,14 +103,10 @@ let () =
done
```

If run with the `TRACE=1` environment variable set, this will just produce a
basic trace in the file "trace.json" (otherwise probes will do nothing and keep
a minimal overhead).

Once opened in chrome://tracing, the trace looks like this:
![viewer screenshot](media/viewer1.png)

### Example: "heavy"
## Example: "heavy"

A more heavy example (used to benchmark a bit the tracing), is in `examples/heavy`.

Expand All @@ -87,7 +119,7 @@ $ ./daemon.sh
Then in another terminal:

```
$ ./heavy.sh -n=1 --mode=net -j 2
$ ./heavy.sh -n=1 --mode=net -j 2 --trace-id=mytrace
use net client tcp://127.0.0.1:6981
run 1 iterations
iteration 1
Expand All @@ -96,12 +128,12 @@ run 1 iterations
iteration 1

# list traces
$ catapult-conv
catapult-2022-2-16-16-36-18-pid-3229175.dbo
$ catapult-conv -l
[…]
mytrace.db

# convert last trace into a json.gz file
$ catapult-conv catapult-2022-2-16-16-36-18-pid-3229175.db
# convert last trace into a file (trace.json.gz)
$ catapult-conv mytrace.db

$ ls -lh trace.json.gz
-rw-r--r-- 1 simon simon 374K Feb 16 11:38 trace.json.gz
Expand Down
2 changes: 1 addition & 1 deletion catapult.opam
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ bug-reports: "https://github.com/imandra-ai/catapult/issues"
depends: [
"dune" {>= "2.7"}
"base-threads"
"base-unix"
"trace" {>= "0.3"}
"odoc" {with-doc}
"ocaml" {>= "4.08"}
]
Expand Down
2 changes: 1 addition & 1 deletion dune-project
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
mtime)
(depends
base-threads
base-unix
(trace (>= 0.3))
(odoc :with-doc)
(ocaml (>= "4.08"))))

Expand Down
25 changes: 0 additions & 25 deletions examples/file/basic.ml

This file was deleted.

4 changes: 0 additions & 4 deletions examples/file/dune

This file was deleted.

2 changes: 1 addition & 1 deletion examples/heavy/dune
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
(executable
(name heavy)
(optional)
(libraries threads.posix unix catapult catapult-file catapult-sqlite
(libraries threads.posix trace unix catapult catapult-sqlite
catapult-client))
59 changes: 27 additions & 32 deletions examples/heavy/heavy.ml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module Tr = Catapult.Tracing
open Tr.Syntax
module Tr = Trace

let ( let@ ) = ( @@ )
let spf = Printf.sprintf

let rec fib n =
Expand All @@ -10,30 +10,33 @@ let rec fib n =
fib (n - 1) + fib (n - 2)

let do_work () =
let@ () = Tr.with_ "dowork" in
let@ _sp = Trace.with_span ~__FILE__ ~__LINE__ "dowork" in
for j = 0 to 5_000 do
let n = 15 + (j mod 5) in
let@ () = Tr.with_ ~args:[ "j", `Int j; "fib n", `Int n ] "step" in
let@ _sp =
Trace.with_span ~__FILE__ ~__LINE__
~data:(fun () -> [ "j", `Int j; "fib n", `Int n ])
"step"
in
ignore (Sys.opaque_identity (fib n) : int)
done

let run n =
Printf.printf "run %d iterations\n%!" n;

for i = 1 to n do
let@ () = Tr.with_ "main iter" in
let@ _sp = Trace.with_span ~__FILE__ ~__LINE__ "main iter" in
Printf.printf "iteration %d\n%!" i;
for j = 1 to 4 do
do_work ()
done;
if i mod 3 = 0 then Gc.major ()
done

type mode = Net | File | Db
type mode = Net | Db

let mode_of_str = function
| "net" -> Net
| "file" -> File
| "db" -> Db
| s -> failwith ("unknown mode: " ^ s)

Expand All @@ -45,7 +48,7 @@ let sync_of_str = function

let () =
let n = ref 10 in
let mode = ref File in
let mode = ref Db in
let file = ref "trace.json" in
let addr = ref Catapult_client.default_endpoint in
let j = ref 1 in
Expand All @@ -59,7 +62,7 @@ let () =
"-n", Arg.Set_int n, " number of iterations";
"-o", Arg.Set_string file, " output file";
( "--mode",
Arg.Symbol ([ "net"; "file"; "db" ], fun s -> mode := mode_of_str s),
Arg.Symbol ([ "net"; "db" ], fun s -> mode := mode_of_str s),
" serialization mode" );
"--worker", Arg.Set worker, " act as a worker";
( "--db",
Expand All @@ -83,19 +86,10 @@ let () =
in
Arg.parse opts (fun _ -> ()) "heavy";

if !worker then
Catapult_sqlite.set_multiproc true
else if (not !worker) && !j > 1 then (
Catapult_sqlite.set_multiproc true;

if (not !worker) && !j > 1 then (
(match !mode with
| Net ->
if !trace_id <> "" then Catapult_client.set_trace_id !trace_id;
trace_id := Catapult_client.get_trace_id ()
| Db ->
if !trace_id <> "" then Catapult_sqlite.set_trace_id !trace_id;
trace_id := Catapult_sqlite.get_trace_id ()
| File -> ());
| Net -> ()
| Db -> failwith "cannot use -j with a sqlite backend");

let bin_name = Sys.executable_name in
for _k = 2 to !j do
Expand All @@ -115,16 +109,17 @@ let () =
| Net ->
Printf.printf "use net client %s\n%!"
(Catapult_client.Endpoint_address.to_string !addr);
if !trace_id <> "" then Catapult_client.set_trace_id !trace_id;
Catapult_client.set_endpoint !addr;
Catapult_client.with_setup run
let trace_id =
if !trace_id <> "" then
Some !trace_id
else
None
in
let@ conn = Catapult_client.with_conn ?trace_id ~addr:!addr () in
Trace_core.setup_collector (Catapult_client.trace_collector_of_conn conn);
run ()
| Db ->
Printf.printf "use sqlite backend %s\n%!" !db;
if !trace_id <> "" then Catapult_sqlite.set_trace_id !trace_id;
Catapult_sqlite.set_file !db;
Catapult_sqlite.set_sqlite_sync !sync;
Catapult_sqlite.with_setup run
| File ->
Printf.printf "write to file %S\n%!" !file;
Catapult_file.set_file !file;
Catapult_file.with_setup run
let@ writer = Catapult_sqlite.Writer.with_ ~file:!db ~sync:!sync () in
Trace.setup_collector (Catapult_sqlite.trace_collector_of_writer writer);
run ()
17 changes: 11 additions & 6 deletions src/client/backend.ml
Original file line number Diff line number Diff line change
@@ -1,30 +1,29 @@
open Catapult_utils
module P = Catapult
module Tracing = P.Tracing

type event = Ser.Event.t

module type ARG = sig
val conn : Connections.t
val conn : Connection.t
end

module Make (A : ARG) : P.BACKEND = struct
let conn = A.conn
let teardown () = Connections.close conn
let teardown () = Connection.close conn

let[@inline] opt_map_ f = function
| None -> None
| Some x -> Some (f x)

let conv_arg (key, a) =
let conv_arg (key, (a : [< `Float of float | Trace.user_data ])) =
let open Ser in
let value =
match a with
| `Int x -> Arg_value.Int64 (Int64.of_int x)
| `String s -> Arg_value.String s
| `Float f -> Arg_value.Float64 f
| `Bool b -> Arg_value.Bool b
| `Null -> Arg_value.Void
| `None -> Arg_value.Void
in
{ Arg.key; value }

Expand All @@ -50,10 +49,16 @@ module Make (A : ARG) : P.BACKEND = struct
in
{ Event.id; name; ph; tid; pid; cat; ts_us; args; stack; dur; extra }
in
Connections.send_msg conn ~pid ~now:ts_us ev
Connection.send_msg conn ~pid ~now:ts_us ev

let tick () =
let now = P.Clock.now_us () in
let pid = Unix.getpid () in
Gc_stats.maybe_emit ~now ~pid ()
end

let make (c : Connection.t) : P.backend =
let module M = Make (struct
let conn = c
end) in
(module M)
6 changes: 1 addition & 5 deletions src/client/backend.mli
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
module type ARG = sig
val conn : Connections.t
end

module Make (_ : ARG) : Catapult.BACKEND
val make : Connection.t -> Catapult.backend
Loading
Loading