Skip to content

Instantly share code, notes, and snippets.

@chenlilyd
Last active December 11, 2022 01:17
Show Gist options
  • Save chenlilyd/42e7995349f7e8db5ccc3f1e7df5491b to your computer and use it in GitHub Desktop.
Save chenlilyd/42e7995349f7e8db5ccc3f1e7df5491b to your computer and use it in GitHub Desktop.
Test concurrent requests to Jetty
(ns garfield.ring-jetty-project
(:gen-class)
(:require
[cheshire.core :refer [parse-string]]
[clj-http.client :as client]
[clojure.java.io :as io]
[clojure.java.shell :refer [sh]]
[clojure.string :as s]
[com.climate.claypoole :as cp]
[ring.adapter.jetty :refer [run-jetty]]
[taoensso.timbre :as log]
[taoensso.timbre.appenders.core :as appenders])
(:import
[java.time ZoneId ZonedDateTime]))
(log/merge-config!
{:appenders {:spit (appenders/spit-appender {:fname "jetty.log"})}})
;; a handler which could take a long time to respond
(defn handler [_req]
(Thread/sleep (if (> (rand-int 100) 95)
(+ 5000 (rand-int 4000)) ;; 5% chance of possible slow response
(rand-int 150)))
{:status 200
:headers {"Content-Type" "text/plain"}
:body "Hello World"})
(defonce server-store (atom nil))
(defn jetty-thread-stats [server]
(let [pool (.getThreadPool server)]
{:threads (.getThreads pool)
:max-threads (.getMaxThreads pool)
:queue-size (.getQueueSize pool)}))
(defn log-middleware [handler]
(fn [{:keys [request-method uri] :as req}]
(let [start (System/currentTimeMillis)]
(try
(handler req)
(finally
(let [time (- (System/currentTimeMillis) start)]
(log/info request-method uri time (jetty-thread-stats @server-store))))))))
(defn -main [& _]
(reset!
server-store
(run-jetty
(->> handler log-middleware)
{:port 4000
:join? false})))
(defn stop! []
(when-let [s @server-store]
(try
(.stop s)
(finally (reset! server-store nil)))))
(comment
;; start server
(-main)
;; Simulate 200 concurrent users
(def unlimited-pool (cp/threadpool 200))
;; Delete existing log file if exists
(io/delete-file (io/file "jetty.log") true)
;; these users send 10k 2000 requests in total, wait for a maximum time of 10 seconds
(time
(do
(def xs (cp/upmap unlimited-pool (partial make-http-request-with-timeout 10000) (range 2000)))
(last xs)))
;; Time taken for the longest running requests
(->> xs (map :time) sort reverse (take 10))
;; => (10011 10011 10011 10010 10009 10008 10005 9981 9957 9951)
;; On the client side, all requests are success except those aborted by the client
(->> xs (map (juxt :http-status :error)) distinct)
;; => ([200 nil] [nil "Read timed out"])
;; On the server side, the longest response time.
;; Notice the max time is less than 9 second.
(with-open [r (io/reader "jetty.log")]
(->>
(line-seq r)
(map (fn [line] ;; extract time from `/request-{id} {time-in-millis}`
(some->> line
(re-seq #"/request-\d+\s+(\d+)")
(first)
(second)
(read-string))))
(filterv identity)
(sort)
(reverse)
(take 10)))
;; => (8983 8965 8939 8917 8868 8859 8837 8832 8832 8800)
;; Check if server handles all the requests, even the client aborted the requests
(with-open [r (io/reader "jetty.log")]
(->>
(line-seq r)
(map (fn [line] ;; extract id from `/request-{id}`
(some->> line
(re-seq #"/request-(\d+)")
(first)
(second)
(read-string))))
(filterv identity)
(sort)
(= (range 2000))))
;; => true
;; stop server
(stop!)
)
@chenlilyd
Copy link
Author

And deps.edn

:deps  {org.clojure/clojure       {:mvn/version "1.11.1"}
         org.clojure/core.async    {:mvn/version "1.3.618"}
         com.taoensso/timbre       {:mvn/version "5.2.1"}
         clj-http/clj-http         {:mvn/version "3.12.3"}
         cheshire/cheshire         {:mvn/version "5.11.0"}
         org.clj-commons/claypoole {:mvn/version "1.2.2"}
         ring/ring                 {:mvn/version "1.9.6"}}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment