diff --git a/frameworks/OCaml/httpcats/README.md b/frameworks/OCaml/httpcats/README.md new file mode 100755 index 00000000000..51d6cc10416 --- /dev/null +++ b/frameworks/OCaml/httpcats/README.md @@ -0,0 +1,19 @@ +# httpcats Benchmarking Test + +### Test Type Implementation Source Code + +* [JSON](httpaf_unix.ml) +* [PLAINTEXT](httpaf_unix.ml) + +## Important Libraries +The tests were run with: +* [Software](https://github.com/inhabitedtype/httpaf) + +## Test URLs +### JSON + +http://localhost:8080/json + +### PLAINTEXT + +http://localhost:8080/plaintext diff --git a/frameworks/OCaml/httpcats/benchmark_config.json b/frameworks/OCaml/httpcats/benchmark_config.json new file mode 100755 index 00000000000..086277f9a01 --- /dev/null +++ b/frameworks/OCaml/httpcats/benchmark_config.json @@ -0,0 +1,26 @@ +{ + "framework": "httpcats", + "tests": [ + { + "default": { + "json_url": "/json", + "plaintext_url": "/plaintext", + "port": 8080, + "approach": "Realistic", + "classification": "Platform", + "database": "None", + "framework": "None", + "language": "OCaml", + "flavor": "None", + "orm": "None", + "platform": "httpcats", + "webserver": "None", + "os": "Linux", + "database_os": "Linux", + "display_name": "httpcats", + "notes": "", + "versus": "None" + } + } + ] +} diff --git a/frameworks/OCaml/httpcats/config.toml b/frameworks/OCaml/httpcats/config.toml new file mode 100644 index 00000000000..89308198d35 --- /dev/null +++ b/frameworks/OCaml/httpcats/config.toml @@ -0,0 +1,15 @@ +[framework] +name = "httpcats" + +[main] +urls.plaintext = "/plaintext" +urls.json = "/json" +approach = "Realistic" +classification = "Platform" +database = "None" +database_os = "Linux" +os = "Linux" +orm = "None" +platform = "httpcats" +webserver = "None" +versus = "None" diff --git a/frameworks/OCaml/httpcats/dune b/frameworks/OCaml/httpcats/dune new file mode 100644 index 00000000000..fabec05e46e --- /dev/null +++ b/frameworks/OCaml/httpcats/dune @@ -0,0 +1,3 @@ +(executable + (name server) + (libraries httpcats yojson)) diff --git a/frameworks/OCaml/httpcats/dune-project b/frameworks/OCaml/httpcats/dune-project new file mode 100644 index 00000000000..f8af889136b --- /dev/null +++ b/frameworks/OCaml/httpcats/dune-project @@ -0,0 +1 @@ +(lang dune 3.9) diff --git a/frameworks/OCaml/httpcats/httpcats.dockerfile b/frameworks/OCaml/httpcats/httpcats.dockerfile new file mode 100644 index 00000000000..268d4fdf5d9 --- /dev/null +++ b/frameworks/OCaml/httpcats/httpcats.dockerfile @@ -0,0 +1,22 @@ +# -*- mode: dockerfile -*- + +# https://github.com/dinosaure/techempower-ocaml-image +# Use pre-built image with all dependencies for faster test times +FROM dinosaure/techempower-ocaml-image:5.3.0 + +# https://caml.inria.fr/pub/docs/manual-ocaml/libref/Gc.html +# https://linux.die.net/man/1/ocamlrun +# https://blog.janestreet.com/memory-allocator-showdown/ +ENV OCAMLRUNPARAM a=2,o=240 + +COPY . /app + +WORKDIR /app + +RUN \ + eval $(opam env) && \ + dune build --release ./server.exe + +EXPOSE 8080 + +CMD _build/default/server.exe diff --git a/frameworks/OCaml/httpcats/server.ml b/frameworks/OCaml/httpcats/server.ml new file mode 100644 index 00000000000..c93e5b41ea9 --- /dev/null +++ b/frameworks/OCaml/httpcats/server.ml @@ -0,0 +1,101 @@ +(* Dates *) + +let get_date () = Unix.(gettimeofday () |> gmtime) + +let dow = function + | 0 -> "Sun" + | 1 -> "Mon" + | 2 -> "Tue" + | 3 -> "Wed" + | 4 -> "Thu" + | 5 -> "Fri" + | _ -> "Sat" + +let month = function + | 0 -> "Jan" + | 1 -> "Feb" + | 2 -> "Mar" + | 3 -> "Apr" + | 4 -> "May" + | 5 -> "Jun" + | 6 -> "Jul" + | 7 -> "Aug" + | 8 -> "Sep" + | 9 -> "Oct" + | 10 -> "Nov" + | _ -> "Dec" + +let date () = + let d = get_date () in + (* Wed, 17 Apr 2013 12:00:00 GMT *) + Format.sprintf "%s, %02d %s %4d %02d:%02d:%02d GMT" (dow d.tm_wday) d.tm_mday + (month d.tm_mon) (1900 + d.tm_year) d.tm_hour d.tm_min d.tm_sec + +(* HTTP *) + +let _plaintext reqd = + let open H1 in + let payload = "Hello, World!" in + let headers = + Headers.of_rev_list + [ ("content-length", string_of_int (String.length payload)) + ; ("content-type", "text/plain") + ; ("server", "httpcats") + ; ("date", date ()) ] in + let resp = Response.create ~headers `OK in + Reqd.respond_with_string reqd resp payload + +let _json reqd = + let open H1 in + let obj = `Assoc [ ("message", `String "Hello, World!") ] in + let payload = Yojson.to_string obj in + let headers = + Headers.of_rev_list + [ ("content-length", string_of_int (String.length payload)) + ; ("content-type", "application/json") + ; ("server", "httpcats") + ; ("date", date ()) ] in + let resp = Response.create ~headers `OK in + Reqd.respond_with_string reqd resp payload + +let _not_found reqd = + let open H1 in + let moo = "m00." in + let headers = + Headers.of_rev_list + [ "content-length", string_of_int (String.length moo) ] in + let resp = Response.create ~headers `OK in + Reqd.respond_with_string reqd resp moo + +let[@warning "-8"] handler _ + (`V1 reqd : [ `V1 of H1.Reqd.t | `V2 of H2.Reqd.t ]) = + let open H1 in + let request = Reqd.request reqd in + match request.Request.target with + | "/plaintext" -> _plaintext reqd + | "/json" -> _json reqd + | _ -> _not_found reqd + +let localhost_8080 = Unix.(ADDR_INET (inet_addr_any, 8080)) + +let server stop = + Httpcats.Server.clear ~parallel:false ~stop ~backlog:4096 ~handler localhost_8080 + +let () = Sys.set_signal Sys.sigpipe Sys.Signal_ignore + +let run () = + let domains = + Unix.open_process_in "getconf _NPROCESSORS_ONLN" + |> input_line |> int_of_string in + Miou_unix.run ~domains @@ fun () -> + let stop = Httpcats.Server.stop () in + let fn _sigint = Httpcats.Server.switch stop in + ignore (Miou.sys_signal Sys.sigint (Sys.Signal_handle fn)); + let domains = Miou.Domain.available () in + let prm = Miou.async @@ fun () -> server stop in + if domains > 0 then + Miou.parallel server (List.init domains (Fun.const stop)) + |> List.iter (function Ok () -> () | Error exn -> raise exn); + Miou.await_exn prm + +let () = Unix.handle_unix_error run () diff --git a/frameworks/OCaml/httpun/README.md b/frameworks/OCaml/httpun/README.md new file mode 100644 index 00000000000..d2e98cf972e --- /dev/null +++ b/frameworks/OCaml/httpun/README.md @@ -0,0 +1,20 @@ +# httpun Benchmarking Test + +### Test Type Implementation Source Code + +* [JSON](httpun_unix.ml) +* [PLAINTEXT](httpun_unix.ml) + +## Important Libraries +The tests were run with: +* [HTTP library](https://github.com/anmonteiro/httpun) +* [Concurrency library](https://github.com/ocaml-multicore/eio) + +## Test URLs +### JSON + +http://localhost:8080/json + +### PLAINTEXT + +http://localhost:8080/plaintext diff --git a/frameworks/OCaml/httpun/benchmark_config.json b/frameworks/OCaml/httpun/benchmark_config.json new file mode 100755 index 00000000000..2f142b3718d --- /dev/null +++ b/frameworks/OCaml/httpun/benchmark_config.json @@ -0,0 +1,26 @@ +{ + "framework": "httpun", + "tests": [ + { + "default": { + "json_url": "/json", + "plaintext_url": "/plaintext", + "port": 8080, + "approach": "Realistic", + "classification": "Platform", + "database": "None", + "framework": "None", + "language": "OCaml", + "flavor": "None", + "orm": "None", + "platform": "httpun", + "webserver": "None", + "os": "Linux", + "database_os": "Linux", + "display_name": "httpun", + "notes": "", + "versus": "None" + } + } + ] +} diff --git a/frameworks/OCaml/httpun/dune b/frameworks/OCaml/httpun/dune new file mode 100644 index 00000000000..ea953016573 --- /dev/null +++ b/frameworks/OCaml/httpun/dune @@ -0,0 +1,3 @@ +(executable + (name httpun_eio) + (libraries httpun httpun-eio eio eio_main eio_linux yojson unix)) diff --git a/frameworks/OCaml/httpun/dune-project b/frameworks/OCaml/httpun/dune-project new file mode 100644 index 00000000000..f8af889136b --- /dev/null +++ b/frameworks/OCaml/httpun/dune-project @@ -0,0 +1 @@ +(lang dune 3.9) diff --git a/frameworks/OCaml/httpun/httpun.dockerfile b/frameworks/OCaml/httpun/httpun.dockerfile new file mode 100644 index 00000000000..9d0bf780a13 --- /dev/null +++ b/frameworks/OCaml/httpun/httpun.dockerfile @@ -0,0 +1,22 @@ +# -*- mode: dockerfile -*- + +# https://github.com/rbjorklin/techempower-ocaml-image +# Use pre-built image with all dependencies for faster test times +FROM rbjorklin/techempower-ocaml-image:5.3.0-4debebb5 + +# https://caml.inria.fr/pub/docs/manual-ocaml/libref/Gc.html +# https://linux.die.net/man/1/ocamlrun +# https://blog.janestreet.com/memory-allocator-showdown/ +ENV OCAMLRUNPARAM="a=2,o=240" + +COPY . /app + +WORKDIR /app + +RUN \ + eval $(opam env) && \ + dune build --release httpun_eio.exe + +EXPOSE 8080 + +ENTRYPOINT ["_build/default/httpun_eio.exe"] diff --git a/frameworks/OCaml/httpun/httpun_eio.ml b/frameworks/OCaml/httpun/httpun_eio.ml new file mode 100644 index 00000000000..b6cbea56c57 --- /dev/null +++ b/frameworks/OCaml/httpun/httpun_eio.ml @@ -0,0 +1,142 @@ +open Httpun +open Httpun_eio +open Eio.Std + +(* Heavily inspired by the original httpaf implementation in this repo with eio additions from: + * https://github.com/ocaml-multicore/eio/blob/8f7f82d2c12076af8e9b8b365c58ebadaa963b8c/examples/net/server.ml + * https://github.com/ocaml-multicore/eio/blob/8f7f82d2c12076af8e9b8b365c58ebadaa963b8c/examples/net/main.ml + * https://github.com/anmonteiro/httpun/blob/37fdcd8fd09dc851acbf224c4ec1cb8681942f04/examples/lib/httpun_examples.ml#L115-L125 + * https://github.com/anmonteiro/httpun/blob/37fdcd8fd09dc851acbf224c4ec1cb8681942f04/examples/eio/eio_connect_server.ml + *) + +(* Dates *) + +let get_date () = Unix.(gettimeofday () |> gmtime) + +let dow = function + | 0 -> "Sun" + | 1 -> "Mon" + | 2 -> "Tue" + | 3 -> "Wed" + | 4 -> "Thu" + | 5 -> "Fri" + | _ -> "Sat" + +let month = function + | 0 -> "Jan" + | 1 -> "Feb" + | 2 -> "Mar" + | 3 -> "Apr" + | 4 -> "May" + | 5 -> "Jun" + | 6 -> "Jul" + | 7 -> "Aug" + | 8 -> "Sep" + | 9 -> "Oct" + | 10 -> "Nov" + | _ -> "Dec" + +let date () = + let d = get_date () in + (* Wed, 17 Apr 2013 12:00:00 GMT *) + Format.sprintf "%s, %02d %s %4d %02d:%02d:%02d GMT" (dow d.tm_wday) d.tm_mday + (month d.tm_mon) (1900 + d.tm_year) d.tm_hour d.tm_min d.tm_sec + +let memo_date = ref @@ date () + +let refresh_date () = + let f _ = + memo_date := date (); + ignore @@ Unix.alarm 1 + in + (ignore @@ Sys.(signal sigalrm (Signal_handle f))); + f () + +(* HTTP *) + +let request_handler (_ : Eio.Net.Sockaddr.stream) { Gluten.reqd; _ } = + let req = Reqd.request reqd in + match req.target with + | "/json" -> + let obj = `Assoc [ ("message", `String "Hello, World!") ] in + let payload = Yojson.to_string obj in + let headers = + Headers.of_rev_list + [ + ("content-length", string_of_int @@ String.length payload); + ("content-type", "application/json"); + ("server", "httpun"); + ("date", !memo_date); + ] + in + let rsp = Response.create ~headers `OK in + Reqd.respond_with_string reqd rsp payload + | "/plaintext" -> + let payload = "Hello, World!" in + let headers = + Headers.of_rev_list + [ + ("content-length", string_of_int @@ String.length payload); + ("content-type", "text/plain"); + ("server", "httpun"); + ("date", !memo_date); + ] + in + let rsp = Response.create ~headers `OK in + Reqd.respond_with_string reqd rsp payload + | _ -> + let moo = "m00." in + let headers = + Headers.of_list + [ ("content-length", string_of_int @@ String.length moo) ] + in + let rsp = Response.create ~headers `OK in + Reqd.respond_with_string reqd rsp moo + +let error_handler (_ : Eio.Net.Sockaddr.stream) ?request:_ error start_response + = + let response_body = start_response Headers.empty in + (match error with + | `Exn exn -> + Body.Writer.write_string response_body (Printexc.to_string exn); + Body.Writer.write_string response_body "\n" + | #Status.standard as error -> + Body.Writer.write_string response_body + (Status.default_reason_phrase error)); + Body.Writer.close response_body + +let () = + let domain_count = Stdlib.Domain.recommended_domain_count () in + Printf.eprintf "Detected %d cores\n" domain_count; + let ulimit_n = + Unix.open_process_in "ulimit -n" |> input_line |> int_of_string + in + Printf.eprintf "Detected %d max open files\n" ulimit_n; + let somaxconn = + Stdlib.open_in "/proc/sys/net/core/somaxconn" + |> Stdlib.input_line |> Stdlib.int_of_string + in + Printf.eprintf "Detected %d somaxconn\n" somaxconn; + refresh_date (); + let backlog = Stdlib.min ulimit_n somaxconn in + + Eio_main.run @@ fun env -> + Switch.run @@ fun sw -> + (* https://github.com/ocaml-multicore/eio/tree/main?tab=readme-ov-file#executor-pool *) + let dm = Eio.Stdenv.domain_mgr env in + + let addr = `Tcp (Eio.Net.Ipaddr.V4.any, 8080) in + let listening_socket = + Eio.Net.listen ~sw env#net addr ~backlog ~reuse_addr:true + in + + let connection_handler flow addr = + Server.create_connection_handler ~request_handler ~error_handler ~sw addr + flow + in + let run listening_socket = + Eio.Net.run_server ~additional_domains:(dm, domain_count) listening_socket + ~max_connections:backlog connection_handler + ~on_error:(traceln "Error handling connection: %a" Fmt.exn) + in + run listening_socket