Compare commits

...

9 Commits

Author SHA1 Message Date
Thomas Leonard
f5232a0c17
Merge pull request #692 from talex5/stat-bench
Stat benchmark: report cleanup time and optimise
2024-02-14 16:09:45 +00:00
Thomas Leonard
db8672b737 Optimise stat benchmark
- Instead of creating thousands of fibers and having them fight over a
  semaphore, limit the number of fibers created. This also makes the
  traces easier to view.

- Remove the items in parallel.

- Fill the files with zero bytes instead of asking the OS for secure
  random data, since that's slow and isn't useful for the test.
2024-02-14 12:13:07 +00:00
Thomas Leonard
250affff67 Stat benchmark: report cleanup time too 2024-02-14 11:40:13 +00:00
Thomas Leonard
43bd3b852e
Merge pull request #691 from talex5/uring-eperm
Handle EPERM when trying to initialise uring
2024-02-14 10:01:52 +00:00
Thomas Leonard
7b781b56b6 Handle EPERM when trying to initialise uring
This can happen when using a Docker container.
2024-02-14 09:42:49 +00:00
Thomas Leonard
82c47a25b2
Merge pull request #690 from talex5/fd-bits
Minor file-descriptor improvements
2024-02-14 09:40:05 +00:00
Thomas Leonard
91ca880220 Add test for Path.open_dir 2024-02-14 09:16:35 +00:00
Thomas Leonard
2f6a5790ba Add Eio_unix.Fd.is_open convenience function 2024-02-13 16:35:43 +00:00
Thomas Leonard
2c005eddaf Enable O_PATH on Linux
Requires `-D_GNU_SOURCE` for some reason.
2024-02-13 16:35:43 +00:00
7 changed files with 81 additions and 29 deletions

View File

@ -38,25 +38,19 @@ module Bench_dir = struct
| File { name; size; perm } ->
Fmt.pf ppf "file %s (0o%o) %Lu" name perm size
let make random fs t =
let limit = Eio.Semaphore.make 32 in (* Prevent FD exhaustion *)
let rec aux fs = function
let make fs t =
let rec aux iter fs = function
| Dir { name; perm; children } ->
let dir = fs / name in
Eio.Semaphore.acquire limit;
Path.mkdir ~perm dir;
Eio.Semaphore.release limit;
Fiber.List.iter (aux dir) children
iter (aux List.iter dir) children
| File { name; size; perm } ->
Eio.Semaphore.acquire limit;
let buf = Cstruct.create (Int64.to_int size) in
Eio.Flow.read_exact random buf;
Path.with_open_out ~create:(`If_missing perm) (fs / name) (fun oc ->
Eio.Flow.write oc [ buf ]
);
Eio.Semaphore.release limit
)
in
aux fs t
aux Fiber.List.iter fs t
end
let with_tmp_dir ~fs prefix suffix fn =
@ -66,30 +60,27 @@ let with_tmp_dir ~fs prefix suffix fn =
fn dir
let bench_stat root =
let limit = Eio.Semaphore.make 32 in (* Prevent FD exhaustion *)
let rec aux dir =
Eio.Semaphore.acquire limit;
let rec aux level dir =
let { Eio.File.Stat.kind; perm; size; _ } = Path.stat ~follow:false dir in
match kind with
| `Directory ->
let items = Path.read_dir dir in
Eio.Semaphore.release limit;
let children = items |> Fiber.List.map (fun f -> aux (dir / f)) in
let map = if level > 3 then List.map else Fiber.List.map ?max_fibers:None in
let children = items |> map (fun f -> aux (level + 1) (dir / f)) in
let name = Path.native_exn dir |> Filename.basename in
Bench_dir.Dir { name; perm; children }
| `Regular_file ->
Eio.Semaphore.release limit;
let name = Path.native_exn dir |> Filename.basename in
File { name; perm; size = Optint.Int63.to_int64 size }
| _ -> assert false
in
aux root
aux 1 root
let file name = Bench_dir.File { name; perm = 0o644; size = 128L }
let dir name children = Bench_dir.Dir { name; perm = 0o700; children }
let random_bench_dir ~n ~levels =
if levels < 0 then invalid_arg "Levels should be > 0";
if levels < 1 then invalid_arg "Levels should be >= 1";
let rec loop root = function
| 1 -> (
match root with
@ -109,16 +100,16 @@ let random_bench_dir ~n ~levels =
in
loop (dir "root" []) levels
let run_bench ~n ~levels ~random ~root ~clock =
let run_bench ~n ~levels ~root ~clock =
let dir = random_bench_dir ~levels ~n |> Bench_dir.sort in
traceln "Going to create %i files and directories" (Bench_dir.size dir);
let create_time =
let t0 = Eio.Time.now clock in
Bench_dir.make random root dir;
Bench_dir.make root dir;
let t1 = Eio.Time.now clock in
t1 -. t0
in
traceln "Created %i files and directories in %.2f s" (Bench_dir.size dir) create_time;
traceln "Created in %.2f s" create_time;
let bench () =
Gc.full_major ();
let stat0 = Gc.stat () in
@ -136,16 +127,26 @@ let run_bench ~n ~levels ~random ~root ~clock =
| _ -> failwith "Stat not the same as the spec"
in
let time, minor, major = bench () in
traceln "Statted in %.2f s" time;
let remove_time =
let t0 = Eio.Time.now clock in
let root = root / "root" in
Eio.Path.read_dir root |> Fiber.List.iter (fun item -> Eio.Path.rmtree (root / item));
Eio.Path.rmdir root;
let t1 = Eio.Time.now clock in
t1 -. t0
in
traceln "Removed in %.2f s" remove_time;
[
Metric.create "create-time" (`Float (1e3 *. create_time)) "ms" (Fmt.str "Time to create %i files and directories" (Bench_dir.size dir));
Metric.create "stat-time" (`Float (1e3 *. time)) "ms" (Fmt.str "Time to stat %i files and directories" (Bench_dir.size dir));
Metric.create "stat-minor" (`Float (1e-3 *. minor)) "kwords" (Fmt.str "Minor words allocated to stat %i files and directories" (Bench_dir.size dir));
Metric.create "stat-major" (`Float (1e-3 *. major)) "kwords" (Fmt.str "Major words allocated %i files and directories" (Bench_dir.size dir))
Metric.create "stat-major" (`Float (1e-3 *. major)) "kwords" (Fmt.str "Major words allocated %i files and directories" (Bench_dir.size dir));
Metric.create "remove-time" (`Float (1e3 *. remove_time)) "ms" "Time to remove everything";
]
let run env =
let fs = Eio.Stdenv.fs env in
let random = Eio.Stdenv.secure_random env in
let clock = Eio.Stdenv.clock env in
with_tmp_dir ~fs "eio-bench-" "-stat" @@ fun root ->
run_bench ~n:20 ~levels:4 ~root ~random ~clock
run_bench ~n:20 ~levels:4 ~root ~clock

View File

@ -1,3 +1,5 @@
open Eio.Std
let benchmarks = [
"Promise", Bench_promise.run;
"Cancel", Bench_cancel.run;
@ -22,6 +24,7 @@ let usage_error () =
let () =
Eio_main.run @@ fun env ->
traceln "Using %s backend" env#backend_id;
let benchmarks =
match Array.to_list Sys.argv with
| [_] -> benchmarks
@ -35,7 +38,7 @@ let () =
| _ -> usage_error ()
in
let run (name, fn) =
Eio.traceln "Running %s..." name;
traceln "Running %s..." name;
let metrics = fn env in
`Assoc [
"name", `String name;

View File

@ -77,6 +77,8 @@ let is_seekable t =
t.seekable <- if seekable then Yes else No;
seekable
let is_open t = Rcfd.is_open t.fd
let rec use_exn_list op xs k =
match xs with
| [] -> k []

View File

@ -55,6 +55,12 @@ val remove : t -> Unix.file_descr option
Returns [None] if [t] is closed by another fiber first. *)
val is_open : t -> bool
(** [is_open t] returns [true] until [t] has been marked as closing, after which it returns [false].
This is mostly useful inside the callback of {!use}, to test whether
another fiber has started closing [t] (in which case you may decide to stop early). *)
(** {2 Flags} *)
val is_blocking : t -> bool

View File

@ -535,7 +535,8 @@ let with_eventfd fn =
let with_sched ?(fallback=no_fallback) config fn =
let { queue_depth; n_blocks; block_size; polling_timeout } = config in
match Uring.create ~queue_depth ?polling_timeout () with
| exception Unix.Unix_error(Unix.ENOSYS, _, _) -> fallback (`Msg "io_uring is not available on this system")
| exception Unix.Unix_error(ENOSYS, _, _) -> fallback (`Msg "io_uring is not available on this system")
| exception Unix.Unix_error(EPERM, _, _) -> fallback (`Msg "io_uring is not available (permission denied)")
| uring ->
let probe = Uring.get_probe uring in
if not (Uring.op_supported probe Uring.Op.shutdown) then (

View File

@ -8,7 +8,7 @@ let optional_flags = [
let () =
C.main ~name:"discover" (fun c ->
let c_flags = ["-D_LARGEFILE64_SOURCE"; "-D_XOPEN_SOURCE=700"; "-D_DARWIN_C_SOURCE"] in
let c_flags = ["-D_LARGEFILE64_SOURCE"; "-D_XOPEN_SOURCE=700"; "-D_DARWIN_C_SOURCE"; "-D_GNU_SOURCE"] in
let includes = ["sys/types.h"; "sys/stat.h"; "fcntl.h"] in
let extra_flags, missing_defs =
C.C_define.import c ~c_flags ~includes

View File

@ -519,6 +519,45 @@ Create a sandbox, write a file with it, then read it from outside:
- : unit = ()
```
```ocaml
# run ~clear:["foo"] @@ fun env ->
let fs = env#fs in
let cwd = env#cwd in
Path.mkdirs (cwd / "foo/bar") ~perm:0o700;
let test ?(succeeds=true) path =
Eio.Exn.Backend.show := succeeds;
try
Switch.run @@ fun sw ->
let _ : _ Path.t = Path.open_dir ~sw path in
traceln "open_dir %a -> OK" Path.pp path
with ex ->
traceln "@[<h>%a@]" Eio.Exn.pp ex
in
let reject = test ~succeeds:false in
test (cwd / "foo/bar");
reject (cwd / "..");
test (cwd / ".");
reject (cwd / "/");
test (cwd / "foo/bar/..");
test (fs / "foo/bar");
Unix.symlink ".." "foo/up";
test (cwd / "foo/up/foo/bar");
Unix.symlink "/" "foo/root";
reject (cwd / "foo/root/..");
+open_dir <cwd:foo/bar> -> OK
+Eio.Io Fs Permission_denied _, opening directory <cwd:..>
+open_dir <cwd:.> -> OK
+Eio.Io Fs Permission_denied _, opening directory <cwd:/>
+open_dir <cwd:foo/bar/..> -> OK
+open_dir <fs:foo/bar> -> OK
+open_dir <cwd:foo/up/foo/bar> -> OK
+Eio.Io Fs Permission_denied _, opening directory <cwd:foo/root/..>
- : unit = ()
# Eio.Exn.Backend.show := false
- : unit = ()
```
# Unconfined FS access
We create a directory and chdir into it.
@ -684,7 +723,7 @@ let try_rename t =
Confined:
```ocaml
# run ~clear:["tmp"; "dir"] @@ fun env -> try_rename env#cwd;;
# run ~clear:["tmp"; "dir"; "foo"] @@ fun env -> try_rename env#cwd;;
+mkdir <cwd:tmp> -> ok
+rename <cwd:tmp> to <cwd:dir> -> ok
+write <cwd:foo> -> ok