(ns dumpfm.millstone (:use clojure.contrib.def clojure.contrib.seq-utils clojure-http.client)) (def *spec*) (def *cookies*) (def *results*) (def printer (agent nil)) (defn log [& args] (send printer (fn [_] (apply println args)))) (defmacro with-timing [e] `(let [s# (System/nanoTime) r# ~e f# (System/nanoTime)] [(float (/ (- f# s#) 1e6)) r#])) (defn do-base-request [server path method cookies params] (let [method (.toUpperCase method) url (str server path)] (if (= method "GET") (request (add-query-params url params) method nil cookies) (request url method nil cookies params)))) (defnk do-setup-request! [path :params nil :method "GET"] (let [res (do-base-request (:server *spec*) path method *cookies* params)] (if (:cookies res) (set! *cookies* (merge *cookies* (:cookies res)))) res)) (defn- success? [resp] (= (:code resp) 200)) (defnk do-request! [path :label nil :params nil :method "GET"] (let [[ms resp] (with-timing (try (do-base-request (:server *spec*) path method *cookies* params) (catch Exception e (log (format "Exception for %s - %s" path (.getMessage e))) {:code "?" :msg "EXCEPTION"}))) result {:path path :label (or label path) :ms ms :error (if (success? resp) nil [(:code resp) (:msg resp)])}] (if (= (rand-int 100) 1) (log (format "%s - %.02fms" path ms))) (dosync (commute *results* conj result)) resp)) (defn build-client! [spec client-id] (binding [*cookies* {}] (if (:setup-func spec) ((:setup-func spec))) {:client-id client-id :cookies *cookies*})) (defn join [s lst] (apply str (interpose s lst))) (defn sum [nums] (reduce + nums)) (defn avg [nums] (float (/ (sum nums) (count nums)))) (defn print-run-results [spec results elapsed-ms] (log (format "\nResults\n--------\n%s requests in %.02fs (%.02f r/s)\n%s clients" (count results) (/ elapsed-ms 1000) (/ (count results) (/ elapsed-ms 1000)) (:clients spec))) (let [avg-in-req (/ (sum (map :ms results)) (:clients spec))] (log (format "\nTime spent in requests (per worker): %.02fs (%.02f%%)" (/ avg-in-req 1000) (* (/ avg-in-req elapsed-ms) 100)))) (doseq [[label rs] (group-by :label results)] (let [nums (map :ms rs) errors (filter identity (map :error rs))] (log (format "\n%s:\n#: %s\nmin: %.02f ms\navg: %.02f ms\nmax: %.02f ms" label (count rs) (apply min nums) (avg nums) (apply max nums))) (if-let [threshhold (:max-latency spec)] (let [timeouts (count (filter #(> % threshhold) (map :ms rs)))] (if (not (zero? timeouts)) (log (format "timeouts: %s (> %sms)" timeouts threshhold))))) (if (not (empty? errors)) (log (format "errors: %s (%s)" (count errors) (join " " (sort (set errors)))))))) (log "")) (defn build-func-list [routes] (flatten (for [[n f] routes] (repeat n f)))) (def num-updates 10) (defn update-markers [reqs num] (set (map #(int (* % (/ reqs num))) (range 1 (inc num))))) (defn grind! [spec] (binding [*spec* spec] (let [clients (doall (for [id (range (:clients spec))] (build-client! spec id))) reqs (:requests spec) funcs (build-func-list (:funcs spec)) results (ref []) counter (atom 0) update-on (update-markers reqs num-updates) threads (doall (for [c clients] (Thread. (fn [] (binding [*spec* spec *cookies* (:cookies c) *results* results] (loop [] (let [c (swap! counter inc)] (if (<= c reqs) (do ((rand-elt funcs)) (if (contains? update-on c) (log (format "Completed %s requests" c))) (recur))))))))))] (log "Finished setup") (let [[elapsed _] (with-timing (do (doseq [t threads] (.start t)) (doseq [t threads] (.join t))))] (print-run-results spec @results elapsed)))))