~severeoverfl0w/ring-graph

dfed7f898aeab8094942355ee15eb91884088f6d — Dominic Monroe 2 years ago c0b9440
Add initial commit
A .gitignore => .gitignore +1 -0
@@ 0,0 1,1 @@
.cpcache

A README.md => README.md +8 -0
@@ 0,0 1,8 @@
# ring-graph

Create graphs of ring handlers running under different servers.

## Dependencies

* Clojure CLI tools
* wrk

A deps.edn => deps.edn +5 -0
@@ 0,0 1,5 @@
{:paths ["src"]
 :deps {hiccup/hiccup {:mvn/version "2.0.0-alpha2"}
        http-kit/http-kit {:mvn/version "2.4.0"}
        com.hypirion/clj-xchart {:mvn/version "0.2.0"}
        ring/ring-core {:mvn/version "1.8.1"}}}

A ring-heap/deps.edn => ring-heap/deps.edn +9 -0
@@ 0,0 1,9 @@
{:deps {http-kit {:mvn/version "2.3.0"}
        ring {:mvn/version "1.8.1"}
        aleph {:mvn/version "0.4.6"}
        metosin/pohjavirta {:mvn/version "0.0.1-alpha7"}
        vertx-clojure/vertx {:mvn/version "0.0.0-SNAPSHOT"
                             :exclusions [metosin/sieppari metosin/reitit-core io.vertx/vertx-web-client]}
        org.immutant/web {:mvn/version "2.1.10"}
        luminus/ring-undertow-adapter {:mvn/version "1.1.2"}
        org.slf4j/slf4j-nop {:mvn/version "1.7.30"}}}

A ring-heap/src/dominic/aleph.clj => ring-heap/src/dominic/aleph.clj +11 -0
@@ 0,0 1,11 @@
(ns dominic.aleph
  (:require [aleph.http :as http]))

(defn app [req]
  {:status  200
   :headers {"Content-Type" "text/html"}
   :body    "hello HTTP!"})

(defn -main [& args]
  (http/start-server app {:port 8080})
  @(promise))

A ring-heap/src/dominic/httpkit.clj => ring-heap/src/dominic/httpkit.clj +11 -0
@@ 0,0 1,11 @@
(ns dominic.httpkit
  (:require [org.httpkit.server :refer :all]))

(defn app [req]
  {:status  200
   :headers {"Content-Type" "text/html"}
   :body    "hello HTTP!"})

(defn -main [& args]
  (run-server app {:port 8080})
  @(promise))

A ring-heap/src/dominic/immutant.clj => ring-heap/src/dominic/immutant.clj +10 -0
@@ 0,0 1,10 @@
(ns dominic.immutant
  (:require [immutant.web :as srv]))

(defn app [req]
  {:status  200
   :headers {"Content-Type" "text/html"}
   :body    "hello HTTP!"})

(defn -main [& args]
  (srv/run app {:port 8080}))

A ring-heap/src/dominic/jetty.clj => ring-heap/src/dominic/jetty.clj +11 -0
@@ 0,0 1,11 @@
(ns dominic.jetty
  (:require
    [ring.adapter.jetty :refer :all]))

(defn app [req]
  {:status  200
   :headers {"Content-Type" "text/html"}
   :body    "hello HTTP!"})

(defn -main [& args]
  (run-jetty app {:port 8080}))

A ring-heap/src/dominic/pohjavirta.clj => ring-heap/src/dominic/pohjavirta.clj +10 -0
@@ 0,0 1,10 @@
(ns dominic.pohjavirta
  (:require [pohjavirta.server :as server]))

(defn app [req]
  {:status  200
   :headers {"Content-Type" "text/html"}
   :body    "hello HTTP!"})

(defn -main [& args]
  (server/start (server/create app)))

A ring-heap/src/dominic/undertow.clj => ring-heap/src/dominic/undertow.clj +10 -0
@@ 0,0 1,10 @@
(ns dominic.undertow
  (:require [ring.adapter.undertow :refer [run-undertow]]))

(defn app [req]
  {:status  200
   :headers {"Content-Type" "text/html"}
   :body    "hello HTTP!"})

(defn -main [& args]
  (run-undertow app {:port 8080}))

A ring-heap/src/dominic/vertx.clj => ring-heap/src/dominic/vertx.clj +14 -0
@@ 0,0 1,14 @@
(ns dominic.vertx
  (:require [vertx.http :as vh]
            [vertx.core :as vc]))

(defn app [req]
  {:status  200
   :headers {"Content-Type" "text/html"}
   :body    "hello HTTP!"})

(defn -main [& args]
  (let [sys (vc/system)]
    (vh/server sys
               {:handler (vh/handler sys app)
                :port 8080})))

A src/io/dominic/ring_graph/core.clj => src/io/dominic/ring_graph/core.clj +190 -0
@@ 0,0 1,190 @@
(ns io.dominic.ring-graph.core
  (:require
    [clojure.string :as string]
    [clojure.edn :as edn]
    [hiccup2.core :as h]
    hiccup.page
    [com.hypirion.clj-xchart :as c]
    [org.httpkit.server :as srv]
    [clojure.java.shell :as sh]
    [ring.middleware.params :refer [wrap-params]])
  (:import
    [java.lang ProcessBuilder]))

(def state* (agent {}))

(defn results-graph
  [state]
  (c/category-chart
    (:requests state)
    {:width 640
     :height 500
     :render-style :bar
     :y-axis {:decimal-pattern "## total reqs"}
     :x-axis {:order (sort-by #(Integer/parseInt (subs % 0 (dec (count %)))) (distinct (mapcat keys (vals (:requests state)))))}}))

(comment (c/spit (results-graph @state*) "/tmp/reqs.png"))

(def state @state*)

(defn body
  [state]
  (str
    (h/html
      (hiccup.page/doctype :html5)
      [:html
       [:head
        [:title "Ring Graph"]
        (when (:running? state)
          [:meta {:http-equiv "refresh" :content "5"}])]
       [:body
        [:h1 (if (:running? state) "Job Running" "Configure job")]
        [:form {:method "post"}
         [:div
          [:label "Heap Sizes"
           [:textarea {:name "values"
                       :disabled (:running? state)
                       :rows 5
                       :cols 33}
             (pr-str (get-in state [:params :heap-sizes] ["128M" "40M" "30M" "20M" "10M" "9M" "8M" "7M"]))]]]
         [:div
          [:label "wrk threads"
           [:input {:type "text"
                    :value (get-in state [:params :threads] "2")
                    :disabled (:running? state)
                    :name "threads"}]]]
         [:div
          [:label "wrk duration"
           [:input {:type "text"
                    :value (get-in state [:params :duration] "10")
                    :disabled (:running? state)
                    :name "duration"}]]]
         [:button {:type "submit"
                   :disabled (:running? state)} "Submit"]] 

        [:h2 "Requests Chart"]
        (when (seq (:results state))
          (h/raw (String. (c/to-bytes (results-graph state) :svg))))

        [:h2 "Results ("
         (count (:results state))
         "/" (count (:matrix state)) ")"]
        [:pre (for [[k v] (:results state)]
                (str k "\n" v "\n\n"))]
        #_[:div {:style "display:flex;"}
         [:pre {:style "flex: 1 1 0"}]
         [:pre {:style "flex: 1 1 0"}]]]])))

(defn wait-port-open
  []
  (let [start (System/currentTimeMillis)]
    (while
      (try (.close (java.net.Socket. "localhost" 8080)) false
           (catch java.net.SocketException _
             true))
      (when (>= (- (System/currentTimeMillis) start ) 20000)
        (throw (ex-info "Timeout waiting for port to open" {})))
      (Thread/sleep 1000))))

(defn schedule-job
  [{:keys [heap-sizes duration worker-threads]
    :or {duration "10"
         worker-threads "2"}}]
  (doto
    (Thread.
      (fn []
        (try
          (let [matrix (for [arg heap-sizes
                             main ["dominic.aleph"
                                   "dominic.jetty"
                                   "dominic.vertx"
                                   "dominic.pohjavirta"
                                   "dominic.httpkit"
                                   "dominic.immutant"
                                   "dominic.undertow"]]
                         [arg main])]
            (send-off state* assoc :matrix matrix)
            (doseq [[arg main] matrix]
              (println arg main)
              (try
                (let [srvp (-> (ProcessBuilder.
                                 ["clojure" (str "-J-Xmx" arg) "-m" main])
                               (.directory (java.io.File. "ring-heap"))
                               (.inheritIO)
                               (.start))
                      _ (wait-port-open)
                      wrk (sh/sh "wrk"
                                 "-s" "wrk.lua"
                                 "-d" duration
                                 "-t" worker-threads
                                 "http://localhost:8080")]
                  ;; TODO: try/finally
                  (.destroy srvp)
                  (.waitFor srvp)
                  (send-off state*
                            (fn [old-state]
                              (let [new-state (assoc-in old-state
                                                        [:results [arg main]]
                                                        (str
                                                          "server:\n"
                                                          ;; (slurp (.getErrorStream srvp))
                                                          ;; (slurp (.getInputStream srvp))
                                                          "-----\nwrk:\n"
                                                          (:err wrk)
                                                          (:out wrk)))
                                    wrk-lines (string/split-lines (:out wrk))]
                                (if (some #{"------"} wrk-lines)
                                  (assoc-in new-state
                                            [:requests main arg]
                                            (Long/parseLong (last wrk-lines)))
                                  new-state)))))
                (catch Exception e
                  (send-off state*
                            (fn [old-state]
                              (-> old-state
                                  (assoc-in [:results [arg main]] (str (.getMessage e)))
                                  (assoc-in [:requests main arg] -1))))))))
          (finally
            (send-off state* assoc :running? false :done? true)))))
    (.start)))

(defn app
  [req]
  (when (= :post (:request-method req))
    (let [args {:heap-sizes (edn/read-string (get-in req [:form-params "values"]))
                :duration (get-in req [:form-params "duration"])
                :threads (get-in req [:form-params "threads"])}]
      (send-off
        state*
        (fn [old-state]
          (if-not (:running? old-state)
            (do
              (schedule-job args)
              {:running? true
               :params args})
            old-state))))
    (await state*))
  {:status 200
   :headers {"Content-Type" "text/html"}
   :body (body @state*)})

(defn deps-missing?
  []
  (try
    (sh/sh "wrk")
    false
    (catch java.io.IOException _
      true)))

(comment
  (srv/run-server (wrap-params #'app) {:port 8000})
  )

(defn -main
 [& _args]
 (if-not (deps-missing?)
   (srv/run-server app {:port 8000})
   (do
     (binding [*out* *err*]
       (println "Dependencies missing.  Is wrk installed?"))
     (System/exit 1))))

A wrk.lua => wrk.lua +4 -0
@@ 0,0 1,4 @@
done = function(summary, latency, requests)
    io.write("------\n")
    io.write(string.format("%d\n", summary.requests))
end