341 lines
10 KiB
OCaml
341 lines
10 KiB
OCaml
open! Core
|
|
|
|
let direct_file_destination ?(buffer_size = 4096 * 16) ~filename () =
|
|
let buf = Iobuf.create ~len:buffer_size in
|
|
let file = Core_unix.openfile ~mode:[ O_CREAT; O_TRUNC; O_RDWR ] filename in
|
|
let written = ref 0 in
|
|
let flush () =
|
|
Iobuf.rewind buf;
|
|
Iobuf.advance buf !written;
|
|
Iobuf.flip_lo buf;
|
|
Iobuf_unix.write buf file;
|
|
written := 0;
|
|
Iobuf.reset buf
|
|
in
|
|
let module Dest = struct
|
|
let next_buf ~ensure_capacity =
|
|
flush ();
|
|
if ensure_capacity > Iobuf.length buf
|
|
then failwith "Not enough buffer space in [direct_file_destination]";
|
|
buf
|
|
;;
|
|
|
|
let wrote_bytes count = written := !written + count
|
|
|
|
let close () =
|
|
flush ();
|
|
Core_unix.close file
|
|
;;
|
|
end
|
|
in
|
|
(module Dest : Writer_intf.Destination)
|
|
;;
|
|
|
|
(* While Zstandard has the best compression, perfetto does not yet understand the format. *)
|
|
let zstd_file_destination ?(buffer_size = 64 * 1024) ~filename () =
|
|
let buf = Iobuf.create ~len:buffer_size in
|
|
let compression_level = 5 in
|
|
(* Ensure the compression buffer is large enough for the worst case of an input of
|
|
[buffer_size]. *)
|
|
let compressed_buf =
|
|
let len =
|
|
buffer_size
|
|
|> Int64.of_int
|
|
|> Zstandard.compression_output_size_bound
|
|
|> Int64.to_int_exn
|
|
in
|
|
Iobuf.create ~len
|
|
in
|
|
let file = Core_unix.openfile ~mode:[ O_CREAT; O_TRUNC; O_CLOEXEC; O_RDWR ] filename in
|
|
let written = ref 0 in
|
|
let compression_context = Zstandard.Compression_context.create () in
|
|
let flush () =
|
|
Iobuf.rewind buf;
|
|
Iobuf.advance buf !written;
|
|
Iobuf.flip_lo buf;
|
|
let input =
|
|
Zstandard.Input.from_bigstring
|
|
~pos:(Iobuf.Expert.lo buf)
|
|
~len:(Iobuf.length buf)
|
|
(Iobuf.Expert.buf buf)
|
|
in
|
|
let output =
|
|
Zstandard.Output.in_buffer
|
|
~pos:(Iobuf.Expert.lo compressed_buf)
|
|
~len:(Iobuf.length compressed_buf)
|
|
(Iobuf.Expert.buf compressed_buf)
|
|
in
|
|
let compressed_length =
|
|
Zstandard.With_explicit_context.compress
|
|
compression_context
|
|
~compression_level
|
|
~input
|
|
~output
|
|
in
|
|
Iobuf.advance compressed_buf compressed_length;
|
|
Iobuf.flip_lo compressed_buf;
|
|
Iobuf_unix.write compressed_buf file;
|
|
written := 0;
|
|
Iobuf.reset buf;
|
|
Iobuf.reset compressed_buf
|
|
in
|
|
let module Dest = struct
|
|
let next_buf ~ensure_capacity =
|
|
flush ();
|
|
if ensure_capacity > Iobuf.length buf
|
|
then failwith "Not enough buffer space in [zstd_file_destination]";
|
|
buf
|
|
;;
|
|
|
|
let wrote_bytes count = written := !written + count
|
|
|
|
let close () =
|
|
flush ();
|
|
Zstandard.Compression_context.free compression_context;
|
|
Core_unix.close file
|
|
;;
|
|
end
|
|
in
|
|
(module Dest : Writer_intf.Destination)
|
|
;;
|
|
|
|
let gzip_file_destination ?(buffer_size = 64 * 1024) ~filename () =
|
|
let buf = Iobuf.create ~len:buffer_size in
|
|
let bytes = Bytes.create buffer_size in
|
|
let file = Core_unix.openfile ~mode:[ O_CREAT; O_TRUNC; O_CLOEXEC; O_RDWR ] filename in
|
|
let out_channel =
|
|
let oc = Core_unix.out_channel_of_descr file in
|
|
(* Consider making the compression level an environment variable for
|
|
experimentation. *)
|
|
Gzip.open_out_chan ~level:6 oc
|
|
in
|
|
let written = ref 0 in
|
|
let flush () =
|
|
Iobuf.rewind buf;
|
|
Iobuf.advance buf !written;
|
|
Iobuf.flip_lo buf;
|
|
Iobuf.Peek.To_bytes.blit
|
|
~src:(Iobuf.read_only buf) ~src_pos:0 ~dst:bytes ~dst_pos:0 ~len:!written;
|
|
Gzip.output out_channel bytes 0 !written;
|
|
written := 0;
|
|
Iobuf.reset buf;
|
|
in
|
|
let module Dest = struct
|
|
let next_buf ~ensure_capacity =
|
|
flush ();
|
|
if ensure_capacity > Iobuf.length buf
|
|
then failwith "Not enough buffer space in [gzip_file_destination]";
|
|
buf
|
|
;;
|
|
|
|
let wrote_bytes count = written := !written + count
|
|
|
|
let close () =
|
|
flush ();
|
|
(* [close_out] also closes the underlying file descr. *)
|
|
Gzip.close_out out_channel
|
|
;;
|
|
end
|
|
in
|
|
(module Dest : Writer_intf.Destination)
|
|
;;
|
|
|
|
let file_destination ?(file_format = Writer_intf.File_format.Uncompressed) ~filename () =
|
|
match file_format with
|
|
| Uncompressed -> direct_file_destination ~filename ()
|
|
| Gzip -> gzip_file_destination ~filename ()
|
|
| Zstandard -> zstd_file_destination ~filename ()
|
|
;;
|
|
|
|
let iobuf_destination buf =
|
|
(* We give out an [Iobuf] with a shared underlying [Bigstring] but different pointers
|
|
so that when this is closed the provided buffer keeps its window, and we can test
|
|
the [Buffer_until_initialized] feature to ignore writes after close.
|
|
|
|
This also ensures our logic works when the window of [buf] is narrower than the
|
|
limits because [sub_shared] leads to a buffer with equal window and limits. *)
|
|
let provided_buf = Iobuf.sub_shared buf in
|
|
let total_written = ref 0 in
|
|
(* Ensure we update the length of the buffer based on [wrote_bytes]
|
|
and thus test that things are calling it correctly. *)
|
|
let set_cur_length () =
|
|
Iobuf.rewind provided_buf;
|
|
Iobuf.advance provided_buf !total_written
|
|
in
|
|
let module Dest = struct
|
|
(* [next_buf] can be called multiple times even without running out of room, for
|
|
example via [Writer.Expert.force_switch_buffers]. But we can just keep giving back
|
|
the same buffer as long as it has room *)
|
|
let next_buf ~ensure_capacity =
|
|
if ensure_capacity > Iobuf.length provided_buf
|
|
then failwith "No more room in [iobuf_destination]";
|
|
provided_buf
|
|
;;
|
|
|
|
let wrote_bytes count =
|
|
total_written := !total_written + count;
|
|
set_cur_length ()
|
|
;;
|
|
|
|
let close () =
|
|
set_cur_length ();
|
|
(* Now that it's closed we set the bounds on the buffer we were originally given to
|
|
match the total length of everything written. *)
|
|
Iobuf.resize buf ~len:!total_written
|
|
;;
|
|
end
|
|
in
|
|
(module Dest : Writer_intf.Destination)
|
|
;;
|
|
|
|
let black_hole_destination ~len ~touch_memory =
|
|
let buf = Iobuf.create ~len in
|
|
if touch_memory then Iobuf.zero buf;
|
|
let module Dest = struct
|
|
let next_buf ~ensure_capacity =
|
|
Iobuf.reset buf;
|
|
if ensure_capacity > Iobuf.length buf
|
|
then failwith "Record too large for [black_hole_destination]";
|
|
buf
|
|
;;
|
|
|
|
let wrote_bytes _count = ()
|
|
let close () = ()
|
|
end
|
|
in
|
|
(module Dest : Writer_intf.Destination)
|
|
;;
|
|
|
|
(* A [Destination] which keeps buffers it gives out in a list and is able to write the
|
|
contents of those buffers to another [Destination]. *)
|
|
module Temp_buffer : sig
|
|
type t =
|
|
{ copy_to : (module Writer_intf.Destination) -> unit
|
|
; dest : (module Writer_intf.Destination)
|
|
}
|
|
|
|
val create : unit -> t
|
|
end = struct
|
|
type t =
|
|
{ copy_to : (module Writer_intf.Destination) -> unit
|
|
; dest : (module Writer_intf.Destination)
|
|
}
|
|
|
|
type internal =
|
|
{ mutable buffers : (read_write, Iobuf.seek, Iobuf.global) Iobuf.t list
|
|
; mutable written_in_cur_buf : int
|
|
}
|
|
|
|
let create () =
|
|
let t = { buffers = []; written_in_cur_buf = 0 } in
|
|
let module Dest = struct
|
|
let next_buf ~ensure_capacity =
|
|
let capacity = Int.max ensure_capacity 1_000 in
|
|
let buf = Iobuf.create ~len:capacity in
|
|
t.buffers <- buf :: t.buffers;
|
|
t.written_in_cur_buf <- 0;
|
|
buf
|
|
;;
|
|
|
|
let wrote_bytes count =
|
|
t.written_in_cur_buf <- t.written_in_cur_buf + count;
|
|
let cur_buf = List.hd_exn t.buffers in
|
|
(* Make sure the lo matches the total bytes written, the Writer probably already
|
|
did this but we don't count on that. *)
|
|
Iobuf.rewind cur_buf;
|
|
Iobuf.advance cur_buf t.written_in_cur_buf
|
|
;;
|
|
|
|
(* We have nowhere to flush to *)
|
|
let close () = ()
|
|
end
|
|
in
|
|
let dest = (module Dest : Writer_intf.Destination) in
|
|
let copy_to (module D : Writer_intf.Destination) =
|
|
let in_order_buffers = List.rev t.buffers in
|
|
let out_buf = ref (D.next_buf ~ensure_capacity:0) in
|
|
List.iter in_order_buffers ~f:(fun in_buf ->
|
|
Iobuf.flip_lo in_buf;
|
|
(* Core.print_s [%sexp (in_buf : (_, _) Iobuf.Window.Hexdump.Pretty.t)]; *)
|
|
let in_buf_len = Iobuf.length in_buf in
|
|
if Iobuf.length !out_buf < in_buf_len
|
|
then out_buf := D.next_buf ~ensure_capacity:in_buf_len;
|
|
Iobuf.Blit_fill.blito ~src:in_buf ~dst:!out_buf ();
|
|
D.wrote_bytes in_buf_len);
|
|
t.buffers <- [];
|
|
t.written_in_cur_buf <- 0
|
|
in
|
|
{ copy_to; dest }
|
|
;;
|
|
end
|
|
|
|
module Buffer_until_initialized = struct
|
|
type state =
|
|
| Buffering_to of Temp_buffer.t
|
|
| Needs_transfer of
|
|
{ src : Temp_buffer.t
|
|
; dst : (module Writer_intf.Destination)
|
|
}
|
|
| Set of (module Writer_intf.Destination)
|
|
|
|
type t = { mutable state : state }
|
|
|
|
let create () =
|
|
let temp_buffer = Temp_buffer.create () in
|
|
{ state = Buffering_to temp_buffer }
|
|
;;
|
|
|
|
let set_destination t destination =
|
|
match t.state with
|
|
| Buffering_to temp_buffer ->
|
|
(* We can't immediately do the transfer because the writer is still using the last
|
|
buffer we gave it, so we need to wait for it to ask for a new buffer. *)
|
|
t.state <- Needs_transfer { src = temp_buffer; dst = destination }
|
|
| Needs_transfer _ | Set _ ->
|
|
failwith "Tried to set Buffer_until_initialized which already had destination"
|
|
;;
|
|
|
|
let to_destination t =
|
|
let module Dest = struct
|
|
let next_buf ~ensure_capacity =
|
|
let (module D) =
|
|
match t.state with
|
|
| Needs_transfer { src; dst } ->
|
|
src.copy_to dst;
|
|
t.state <- Set dst;
|
|
dst
|
|
| Buffering_to temp_buffer -> temp_buffer.dest
|
|
| Set d -> d
|
|
in
|
|
D.next_buf ~ensure_capacity
|
|
;;
|
|
|
|
let wrote_bytes count =
|
|
let (module D) =
|
|
match t.state with
|
|
| Buffering_to temp_buffer -> temp_buffer.dest
|
|
| Needs_transfer { src; dst = _ } -> src.dest
|
|
| Set d -> d
|
|
in
|
|
D.wrote_bytes count
|
|
;;
|
|
|
|
let close () =
|
|
let (module D) =
|
|
match t.state with
|
|
| Needs_transfer { src; dst } ->
|
|
src.copy_to dst;
|
|
dst
|
|
| Buffering_to temp_buffer -> temp_buffer.dest
|
|
| Set d -> d
|
|
in
|
|
D.close ();
|
|
(* Make it so writes after closing will be gracefully ignored. *)
|
|
t.state <- Set (black_hole_destination ~len:1024 ~touch_memory:false)
|
|
;;
|
|
end
|
|
in
|
|
(module Dest : Writer_intf.Destination)
|
|
;;
|
|
end
|