Browse Source

Add ocaml webmachine (#6041)

* feat: add OCaml webmachine

* chore: add OCaml webmachine to travis test suite

* docs(README): fix typo in OCaml webmachine link

* fix(.travis.yml): change OCaml test detection
Robin 4 years ago
parent
commit
f6e3ec7199

+ 1 - 0
.travis.yml

@@ -63,6 +63,7 @@ env:
     - "TESTLANG=Mumps"
     - "TESTLANG=Mumps"
     - "TESTLANG=Nim"
     - "TESTLANG=Nim"
     - 'TESTDIR="Nim/httpbeast Nim/jester Nim/basolato"'
     - 'TESTDIR="Nim/httpbeast Nim/jester Nim/basolato"'
+    - "TESTLANG=OCaml"
     - "TESTLANG=Perl"
     - "TESTLANG=Perl"
     - 'TESTDIR="PHP/php"'
     - 'TESTDIR="PHP/php"'
     - 'TESTDIR="PHP/comet PHP/kumbiaphp PHP/workerman PHP/webman"'
     - 'TESTDIR="PHP/comet PHP/kumbiaphp PHP/workerman PHP/webman"'

+ 33 - 0
frameworks/OCaml/webmachine/README.md

@@ -0,0 +1,33 @@
+# webmachine Benchmarking Test
+
+### Test Type Implementation Source Code
+
+* [JSON](src/tfb.ml#L49-L69)
+* [PLAINTEXT](src/tfb.ml#L49-L69)
+* [DB](src/tfb.ml#L71-L100)
+* [QUERY](src/tfb.ml#L102-L151)
+
+## Important Libraries
+* [Webmachine](https://github.com/inhabitedtype/ocaml-webmachine)
+* [Cohttp](https://github.com/mirage/ocaml-cohttp/issues/328)
+* [Caqti](https://github.com/paurkedal/ocaml-caqti)
+* [Lwt](https://github.com/ocsigen/lwt)
+* [Ezjsonm](https://github.com/mirage/ezjsonm)
+* [Ptime](https://github.com/dbuenzli/ptime)
+
+## Test URLs
+### JSON
+
+http://localhost:8080/json
+
+### PLAINTEXT
+
+http://localhost:8080/plaintext
+
+### DB
+
+http://localhost:8080/db
+
+### QUERY
+
+http://localhost:8080/queries/

+ 47 - 0
frameworks/OCaml/webmachine/benchmark_config.json

@@ -0,0 +1,47 @@
+{
+  "framework": "webmachine",
+  "tests": [{
+      "default": {
+        "json_url": "/json",
+        "plaintext_url": "/plaintext",
+        "db_url": "/db",
+        "query_url": "/queries/",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "postgres",
+        "framework": "webmachine",
+        "language": "OCaml",
+        "flavor": "None",
+        "orm": "Raw",
+        "platform": "cohttp",
+        "webserver": "None",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "webmachine",
+        "notes": "",
+        "versus": "None"
+      },
+      "haproxy": {
+        "json_url": "/json",
+        "plaintext_url": "/plaintext",
+        "db_url": "/db",
+        "query_url": "/queries/",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "postgres",
+        "framework": "webmachine",
+        "language": "OCaml",
+        "flavor": "None",
+        "orm": "Raw",
+        "platform": "cohttp",
+        "webserver": "haproxy",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "webmachine-haproxy",
+        "notes": "",
+        "versus": "None"
+      }
+  }]
+}

+ 59 - 0
frameworks/OCaml/webmachine/haproxy.cfg

@@ -0,0 +1,59 @@
+global
+    chroot      /var/lib/haproxy
+    pidfile     /var/run/haproxy.pid
+    maxconn     32768
+    user        haproxy
+    group       haproxy
+    daemon
+
+defaults
+    mode                    tcp
+    log                     global
+    option                  dontlognull
+    option http-server-close
+    option forwardfor       except 127.0.0.0/8
+    option                  redispatch
+    retries                 3
+    timeout http-request    10s
+    timeout queue           1m
+    timeout connect         10s
+    timeout client          1m
+    timeout server          1m
+    timeout http-keep-alive 10s
+    timeout check           10s
+    maxconn                 3000
+
+frontend main
+    bind *:8080
+    default_backend             tfb
+
+backend tfb
+    balance     roundrobin
+    server  tfb0 127.0.0.1:9000 check
+    server  tfb1 127.0.0.1:9001 check
+    server  tfb2 127.0.0.1:9002 check
+    server  tfb3 127.0.0.1:9003 check
+    server  tfb4 127.0.0.1:9004 check
+    server  tfb5 127.0.0.1:9005 check
+    server  tfb6 127.0.0.1:9006 check
+    server  tfb7 127.0.0.1:9007 check
+    server  tfb8 127.0.0.1:9008 check
+    server  tfb9 127.0.0.1:9009 check
+    server  tfb10 127.0.0.1:9010 check
+    server  tfb11 127.0.0.1:9011 check
+    server  tfb12 127.0.0.1:9012 check
+    server  tfb13 127.0.0.1:9013 check
+    server  tfb14 127.0.0.1:9014 check
+    server  tfb15 127.0.0.1:9015 check
+    server  tfb16 127.0.0.1:9016 check
+    server  tfb17 127.0.0.1:9017 check
+    server  tfb18 127.0.0.1:9018 check
+    server  tfb19 127.0.0.1:9019 check
+    server  tfb20 127.0.0.1:9020 check
+    server  tfb21 127.0.0.1:9021 check
+    server  tfb22 127.0.0.1:9022 check
+    server  tfb23 127.0.0.1:9023 check
+    server  tfb24 127.0.0.1:9024 check
+    server  tfb25 127.0.0.1:9025 check
+    server  tfb26 127.0.0.1:9026 check
+    server  tfb27 127.0.0.1:9027 check

+ 5 - 0
frameworks/OCaml/webmachine/src/.ocamlformat

@@ -0,0 +1,5 @@
+profile = default
+parse-docstrings = true
+wrap-comments = true
+break-cases = fit-or-vertical
+break-infix = fit-or-vertical

+ 7 - 0
frameworks/OCaml/webmachine/src/dune

@@ -0,0 +1,7 @@
+(executable
+ (libraries webmachine lwt cohttp-lwt-unix ptime ptime.clock ptime.clock.os
+            caqti caqti-lwt caqti-driver-postgresql ezjsonm logs)
+ (preprocess (pps lwt_ppx))
+ (modules tfb)
+ (name tfb))
+

+ 23 - 0
frameworks/OCaml/webmachine/src/dune-project

@@ -0,0 +1,23 @@
+(lang dune 2.7)
+(name webmachine-tfb)
+
+(generate_opam_files true)
+
+(source (github TechEmpower/FrameworkBenchmarks))
+(license MIT)
+(authors "Robin Björklin")
+
+(package
+ (name webmachine-tfb)
+ (synopsis "Rudimentary implementation of the Tech Empower Benchmark suite")
+ (depends
+  (dune (>= 2.7.1))
+  (webmachine (>= 0.6.2))
+  (lwt (>= 5.3.0))
+  (caqti (>= 1.2.3))
+  (caqti-lwt (>= 1.2.0))
+  (caqti-driver-postgresql (>= 1.2.4))
+  (cohttp-lwt-unix (>= 2.5.4))
+  (ptime (>= 0.8.5))
+  (ezjsonm (>= 1.2.0))
+  (lwt_ppx (>= 2.0.1))))

+ 188 - 0
frameworks/OCaml/webmachine/src/tfb.ml

@@ -0,0 +1,188 @@
+(* https://github.com/ocsigen/lwt/blob/d7fabaa077389a0035254e66459a6a366c57576e/src/core/lwt_result.ml#L88-L91 *)
+(* >>= is Lwt.Infix equivalent to Lwt.bind:
+   https://ocsigen.org/lwt/5.2.0/api/Lwt#3_Callbacks *)
+(* >|= is Lwt.Infix equivalent to Lwt.map:
+   https://ocsigen.org/lwt/5.2.0/api/Lwt#2_Convenience *)
+open Lwt.Infix
+open Cohttp_lwt_unix
+
+module Wm = struct
+  module Rd = Webmachine.Rd
+
+  module UnixClock = struct
+    let now () = int_of_float (Unix.gettimeofday ())
+  end
+
+  include Webmachine.Make (Cohttp_lwt_unix__Io) (UnixClock)
+end
+
+module World = struct
+  type t = { id : int; randomNumber : int }
+end
+
+let pool =
+  let connection_url =
+    "postgresql://benchmarkdbuser:benchmarkdbpass@tfb-database:5432/hello_world?connect_timeout=15"
+  in
+  match Caqti_lwt.connect_pool ~max_size:10 (Uri.of_string connection_url) with
+  | Ok pool -> pool
+  | Error err ->
+      Printf.eprintf "%s" (Caqti_error.show err);
+      flush stderr;
+      failwith (Caqti_error.show err)
+
+type error = Database_error of string
+
+let or_error m =
+  match%lwt m with
+  | Ok a -> Ok a |> Lwt.return
+  | Error err ->
+      Printf.eprintf "%s" (Caqti_error.show err);
+      flush stderr;
+      Error (Database_error (Caqti_error.show err)) |> Lwt.return
+
+let select_random =
+  Caqti_request.find Caqti_type.int
+    Caqti_type.(tup2 int int)
+    "SELECT id, randomNumber FROM World WHERE id = $1"
+
+class hello =
+  object (self)
+    inherit [Cohttp_lwt.Body.t] Wm.resource
+
+    method! allowed_methods rd = Wm.continue [ `GET ] rd
+
+    method content_types_provided rd =
+      Wm.continue
+        [ ("text/plain", self#to_text); ("application/json", self#to_json) ]
+        rd
+
+    method content_types_accepted rd = Wm.continue [] rd
+
+    method private to_text rd =
+      let text = "Hello, World!" in
+      Wm.continue (`String text) rd
+
+    method private to_json rd =
+      let json = Ezjsonm.value (`O [ ("message", `String "Hello, World!") ]) in
+      Wm.continue (`String (Ezjsonm.value_to_string ~minify:true json)) rd
+  end
+
+class db =
+  object (self)
+    inherit [Cohttp_lwt.Body.t] Wm.resource
+
+    method! allowed_methods rd = Wm.continue [ `GET ] rd
+
+    method content_types_provided rd =
+      Wm.continue [ ("application/json", self#read_db) ] rd
+
+    method content_types_accepted rd = Wm.continue [] rd
+
+    method private read_db rd =
+      let read_db' (module C : Caqti_lwt.CONNECTION) =
+        C.find select_random (Random.int 10000 + 1)
+      in
+      let%lwt id, randomNumber =
+        Caqti_lwt.Pool.use read_db' pool |> or_error >|= function
+        | Ok (x, y) -> (x, y)
+        | Error _ -> failwith "whoops"
+      in
+      let json =
+        Ezjsonm.value
+          (`O
+            [
+              ("id", `Float (float_of_int id));
+              ("randomNumber", `Float (float_of_int randomNumber));
+            ])
+      in
+      Wm.continue (`String (Ezjsonm.value_to_string ~minify:true json)) rd
+  end
+
+class queries =
+  object (self)
+    inherit [Cohttp_lwt.Body.t] Wm.resource
+
+    method private id rd =
+      try
+        let _id = int_of_string (Wm.Rd.lookup_path_info_exn "id" rd) in
+        match _id with
+        | x when x < 1 -> 1
+        | x when x > 500 -> 500
+        | x -> x
+      with
+      | Failure _ -> 1
+      | Not_found -> 1
+
+    method! allowed_methods rd = Wm.continue [ `GET ] rd
+
+    method content_types_provided rd =
+      Wm.continue [ ("application/json", self#read_query) ] rd
+
+    method content_types_accepted rd = Wm.continue [] rd
+
+    method private read_query rd =
+      let query_ids = List.init (self#id rd) (fun _ -> Random.int 10000 + 1) in
+      let read_query' x (module C : Caqti_lwt.CONNECTION) =
+        C.find select_random x
+      in
+      let response =
+        List.map
+          (fun id ->
+            Caqti_lwt.Pool.use (read_query' id) pool |> or_error >|= function
+            | Ok (x, y) -> (x, y)
+            | Error _ -> failwith "whoops")
+          query_ids
+      in
+      let%lwt resolved_response = Lwt.all response in
+      let json =
+        Ezjsonm.list
+          (fun tup ->
+            let id, randomNumber = tup in
+            Ezjsonm.value
+              (`O
+                [
+                  ("id", `Float (float_of_int id));
+                  ("randomNumber", `Float (float_of_int randomNumber));
+                ]))
+          resolved_response
+      in
+      Wm.continue (`String (Ezjsonm.value_to_string ~minify:true json)) rd
+  end
+
+let main () =
+  let port =
+    match Sys.getenv "PORT" with
+    | x -> int_of_string x
+    | exception Not_found -> 8080
+  in
+  let routes =
+    [
+      ("/plaintext", fun () -> new hello);
+      ("/json", fun () -> new hello);
+      ("/db", fun () -> new db);
+      ("/queries", fun () -> new queries);
+      ("/queries/:id", fun () -> new queries);
+    ]
+  in
+  let callback (_ch, _conn) request body =
+    let open Cohttp in
+    (Wm.dispatch' routes ~body ~request >|= function
+     | None -> (`Not_found, Header.init (), `String "Not found", [])
+     | Some result -> result)
+    >>= fun (status, headers, body, _) ->
+    let headers = Header.add headers "Server" "webmachine" in
+    let headers =
+      Header.add headers "Date" (Ptime.to_rfc3339 (Ptime_clock.now ()))
+    in
+    Server.respond ~headers ~body ~status ()
+  in
+
+  let config = Server.make ~callback () in
+  Server.create ~mode:(`TCP (`Port port)) config >|= fun () ->
+  Printf.eprintf "hello_lwt: listening on 0.0.0.0:%d%!" port
+
+let () =
+  (* https://github.com/mirage/ocaml-cohttp/issues/328#issuecomment-222583580 *)
+  Lwt_io.set_default_buffer_size 0x10000;
+  Lwt_main.run (main ())

+ 34 - 0
frameworks/OCaml/webmachine/src/webmachine-tfb.opam

@@ -0,0 +1,34 @@
+# This file is generated by dune, edit dune-project instead
+opam-version: "2.0"
+synopsis: "Rudimentary implementation of the Tech Empower Benchmark suite"
+authors: ["Robin Björklin"]
+license: "MIT"
+homepage: "https://github.com/TechEmpower/FrameworkBenchmarks"
+bug-reports: "https://github.com/TechEmpower/FrameworkBenchmarks/issues"
+depends: [
+  "dune" {>= "2.7" & >= "2.7.1"}
+  "webmachine" {>= "0.6.2"}
+  "caqti" {>= "1.2.3"}
+  "caqti-lwt" {>= "1.2.0"}
+  "caqti-driver-postgresql" {>= "1.2.4"}
+  "cohttp-lwt-unix" {>= "2.5.4"}
+  "ptime" {>= "0.8.5"}
+  "ezjsonm" {>= "1.2.0"}
+  "lwt_ppx" {>= "2.0.1"}
+  "odoc" {with-doc}
+]
+build: [
+  ["dune" "subst"] {dev}
+  [
+    "dune"
+    "build"
+    "-p"
+    name
+    "-j"
+    jobs
+    "@install"
+    "@runtest" {with-test}
+    "@doc" {with-doc}
+  ]
+]
+dev-repo: "git+https://github.com/TechEmpower/FrameworkBenchmarks.git"

+ 10 - 0
frameworks/OCaml/webmachine/start-servers.sh

@@ -0,0 +1,10 @@
+#!/bin/bash
+
+CPU_COUNT=$(nproc)
+P=9000
+END=$(($P+$CPU_COUNT))
+
+while [ $P -lt $END ]; do
+  PORT=$P /webmachine/_build/default/tfb.exe &
+  let P=P+1
+done

+ 27 - 0
frameworks/OCaml/webmachine/webmachine-haproxy.dockerfile

@@ -0,0 +1,27 @@
+FROM fedora:32
+
+#ARG BENCHMARK_ENV=local
+
+ENV VERSION 4.11.1
+#ENV BENCHMARK_ENV $BENCHMARK_ENV
+
+WORKDIR /webmachine
+
+RUN dnf install --assumeyes opam diffutils postgresql-devel
+RUN opam init --disable-sandboxing --auto-setup --compiler ${VERSION}
+# uncomment this line to take better advantage of docker build cache when developing
+#RUN opam install --yes dune webmachine caqti caqti-lwt caqti-driver-postgresql cohttp-lwt-unix ptime ezjsonm lwt_ppx
+
+COPY ./src /webmachine
+# comment the below line while developing to make better use of the docker build cache
+RUN opam install --yes --deps-only ./webmachine-tfb.opam
+
+RUN eval $(opam env) ; dune build --profile release tfb.exe
+
+# try to keep everything above here in sync with webmachine.dockerfile for more efficent use of docker build cache
+RUN dnf install --assumeyes haproxy
+COPY haproxy.cfg /etc/haproxy/haproxy.cfg
+COPY start-servers.sh ./start-servers.sh
+RUN chmod +x /webmachine/start-servers.sh
+
+CMD /webmachine/start-servers.sh ; /usr/sbin/haproxy -Ws -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -q

+ 21 - 0
frameworks/OCaml/webmachine/webmachine.dockerfile

@@ -0,0 +1,21 @@
+FROM fedora:32
+
+#ARG BENCHMARK_ENV=local
+
+ENV VERSION 4.11.1
+#ENV BENCHMARK_ENV $BENCHMARK_ENV
+
+WORKDIR /webmachine
+
+RUN dnf install --assumeyes opam diffutils postgresql-devel
+RUN opam init --disable-sandboxing --auto-setup --compiler ${VERSION}
+# uncomment this line to take better advantage of docker build cache when developing
+#RUN opam install --yes dune webmachine caqti caqti-lwt caqti-driver-postgresql cohttp-lwt-unix ptime ezjsonm lwt_ppx
+
+COPY ./src /webmachine
+# comment the below line while developing to make better use of the docker build cache
+RUN opam install --yes --deps-only ./webmachine-tfb.opam
+
+RUN eval $(opam env) ; dune build --profile release tfb.exe
+
+CMD /webmachine/_build/default/tfb.exe