87c8f2d7创建于 2022年4月19日历史提交
open! Core
module TW = Tracing_zero.Writer

module Thread = struct
  type t =
    { pid : int
    ; tid : int
    ; mutable id : TW.Thread_id.t option
    }
end

type t =
  { writer : TW.t
  ; interned_strings : TW.String_id.t String.Table.t
  ; counter_ids : int String.Table.t
  ; thread_slots : Thread.t Int.Table.t
  ; base_time : Time_ns.t
  ; mutable next_thread_slot : int
  ; mutable flow_id_counter : int
  ; mutable counter_id_counter : int
  ; mutable koid_counter : int
  }

let create ~base_time writer =
  let base_time =
    match base_time with
    | None -> Time_ns.epoch
    | Some base_time ->
      let tick_translation = { TW.Tick_translation.epoch_ns with base_time } in
      TW.write_tick_initialization writer tick_translation;
      base_time
  in
  { writer
  ; interned_strings = String.Table.create ()
  ; counter_ids = String.Table.create ()
  ; thread_slots = Int.Table.create ()
  ; base_time
  ; next_thread_slot = 0
  ; flow_id_counter = 0
  ; counter_id_counter = 0
  ; koid_counter = 0
  }
;;

let create_for_file ~base_time ~filename =
  let writer = TW.create_for_file ~filename () in
  create ~base_time writer
;;

module Expert = struct
  let create = create
end

let close t = TW.close t.writer

let translate_time t time =
  assert (Time_ns.(time >= t.base_time));
  Time_ns.diff time t.base_time
;;

let intern_string_cached t s =
  Hashtbl.find_or_add t.interned_strings s ~default:(fun () ->
    TW.intern_string t.writer s)
;;

let span_to_ticks span = Time_ns.Span.to_int_ns span

let allocate_pid t ~name =
  t.koid_counter <- t.koid_counter + 1;
  TW.set_process_name t.writer ~pid:t.koid_counter ~name:(intern_string_cached t name);
  t.koid_counter
;;

let allocate_thread t ~pid ~name =
  t.koid_counter <- t.koid_counter + 1;
  let tid = t.koid_counter in
  TW.set_thread_name t.writer ~pid ~tid ~name:(intern_string_cached t name);
  { Thread.pid; tid; id = None }
;;

module Arg = Trace_intf.Event_arg

module Baked_args = struct
  type baked_value =
    | String of TW.String_id.t
    | Int32 of int
    | Int63 of int
    | Int64 of int64
    | Pointer of int64
    | Float of float

  type t = (TW.String_id.t * baked_value) list

  let bake temp_slot trace (v : Arg.value) : baked_value =
    match v with
    | Interned s -> String (intern_string_cached trace s)
    | String s ->
      incr temp_slot;
      String (TW.set_temp_string_slot trace.writer ~slot:!temp_slot s)
    | Int i -> if Util.int_fits_in_int32 i then Int32 i else Int63 i
    | Int64 i ->
      if Util.int64_fits_in_int32 i then Int32 (Int64.to_int_trunc i) else Int64 i
    | Pointer p -> Pointer p
    | Float f -> Float f
  ;;

  let create trace (args : Arg.t list) : t =
    let temp_slot = ref 0 in
    List.map args ~f:(fun (name, v) ->
      intern_string_cached trace name, bake temp_slot trace v)
  ;;

  let types t =
    let strings = ref 0 in
    let int32s = ref 0 in
    let int64s = ref 0 in
    let floats = ref 0 in
    List.iter t ~f:(fun (_, v) ->
      match v with
      | String _ -> incr strings
      | Int32 _ -> incr int32s
      | Int63 _ | Int64 _ | Pointer _ -> incr int64s
      | Float _ -> incr floats);
    TW.Arg_types.create
      ~int32s:!int32s
      ~int64s:!int64s
      ~floats:!floats
      ~strings:!strings
      ()
  ;;

  let write (t : t) w =
    List.iter t ~f:(function
      | name, String s -> TW.Write_arg.string w ~name s
      | name, Int32 i -> TW.Write_arg.int32 w ~name i
      | name, Int63 i -> TW.Write_arg.int63 w ~name i
      | name, Int64 i -> TW.Write_arg.int64 w ~name i
      | name, Pointer p -> TW.Write_arg.pointer w ~name p
      | name, Float f -> TW.Write_arg.float w ~name f)
  ;;
end

(* This type is duplicated here mostly because if it was deduplicated with an intf it then
   it would take a lot of hopping to figure out what arguments you need to pass to the
   writer functions. *)
type 'a event_writer =
  t
  -> args:Arg.t list
  -> thread:Thread.t
  -> category:string
  -> name:string
  -> time:Time_ns.Span.t
  -> 'a

let id_for_thread t thread =
  match thread.Thread.id with
  | Some id -> id
  | None ->
    let slot = t.next_thread_slot in
    t.next_thread_slot <- (t.next_thread_slot + 1) % 255;
    let id = TW.set_thread_slot t.writer ~slot ~pid:thread.pid ~tid:thread.tid in
    thread.id <- Some id;
    Hashtbl.update t.thread_slots slot ~f:(fun old ->
      Option.iter old ~f:(fun kicked_thread -> kicked_thread.id <- None);
      thread);
    id
;;

let writer_adapter raw_writer complete_fn t ~args ~thread ~category ~name ~time =
  let thread_id = id_for_thread t thread in
  let baked_args = Baked_args.create t args in
  let writer =
    raw_writer
      t.writer
      ~arg_types:(Baked_args.types baked_args)
      ~thread:thread_id
      ~category:(intern_string_cached t category)
      ~name:(intern_string_cached t name)
      ~ticks:(span_to_ticks time)
  in
  let write_args () = Baked_args.write baked_args t.writer in
  complete_fn write_args writer
;;

let write_instant = writer_adapter TW.write_instant (fun write_args () -> write_args ())

let write_counter t ~args ~thread ~category ~name ~time =
  List.iter args ~f:(fun (_, v) ->
    match v with
    | Trace_intf.Event_arg.Int _ | Int64 _ | Pointer _ | Float _ -> ()
    | Interned _ | String _ -> failwith "counter events only accept numeric arguments.");
  (* Unlike the other types of IDs where we expose allocation to the user, counter IDs
     both don't have a separate creation step and we have a name to automatically
     associate them with. In fact Perfetto completely ignores these IDs and just uses the
     name to associate counters, so doing the interning ourselves by name means that any
     tool which does look at counter IDs will match Perfetto. *)
  let counter_id =
    Hashtbl.find_or_add t.counter_ids name ~default:(fun () ->
      t.counter_id_counter <- t.counter_id_counter + 1;
      t.counter_id_counter)
  in
  let handler write_args writer =
    writer ~counter_id;
    write_args ()
  in
  writer_adapter TW.write_counter handler t ~args ~thread ~category ~name ~time
;;

let write_duration_begin =
  writer_adapter TW.write_duration_begin (fun write_args () -> write_args ())
;;

let write_duration_end =
  writer_adapter TW.write_duration_end (fun write_args () -> write_args ())
;;

let write_duration_complete =
  writer_adapter TW.write_duration_complete (fun write_args writer ~time_end ->
    writer ~ticks_end:(span_to_ticks time_end);
    write_args ())
;;

let write_duration_instant t ~args ~thread ~category ~name ~time =
  write_duration_complete t ~args ~thread ~category ~name ~time ~time_end:time
;;

let create_flow t =
  t.flow_id_counter <- t.flow_id_counter + 1;
  Flow.create ~flow_id:t.flow_id_counter
;;

let write_flow_step t flow ~thread ~time =
  let thread = id_for_thread t thread in
  Flow.write_step flow t.writer ~thread ~ticks:(span_to_ticks time)
;;

let finish_flow t flow = Flow.finish flow t.writer