├── example ├── boot.properties ├── README.md ├── build.boot ├── deps.edn ├── project.clj ├── bench │ └── example │ │ └── core_bench.clj └── src │ └── example │ └── core.clj ├── .gitignore ├── libra-runner ├── deps.edn └── src │ └── libra │ └── runner.clj ├── lein-libra ├── project.clj └── src │ └── leiningen │ └── libra.clj ├── libra ├── src │ └── libra │ │ ├── criterium.clj │ │ └── bench.clj ├── project.clj └── test │ └── libra │ └── bench_test.clj ├── boot-libra ├── build.boot └── src │ └── libra │ └── boot.clj ├── .circleci └── config.yml ├── LICENSE └── README.md /example/boot.properties: -------------------------------------------------------------------------------- 1 | BOOT_CLOJURE_NAME=org.clojure/clojure 2 | BOOT_CLOJURE_VERSION=1.8.0 3 | BOOT_VERSION=2.7.2 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | classes/ 3 | checkouts/ 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | .lein-* 9 | .cpcache 10 | .nrepl-port 11 | -------------------------------------------------------------------------------- /libra-runner/deps.edn: -------------------------------------------------------------------------------- 1 | {:deps {org.clojure/clojure {:mvn/version "1.8.0"} 2 | org.clojure/tools.cli {:mvn/version "0.3.7"} 3 | org.clojure/tools.namespace {:mvn/version "0.2.11"}}} 4 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Example project of Libra 2 | 3 | To run benchmark, 4 | 5 | with Clojure CLI: 6 | 7 | ```console 8 | $ clj -Alibra 9 | ``` 10 | 11 | with Leiningen: 12 | 13 | ```console 14 | $ lein libra 15 | ``` 16 | 17 | with Boot: 18 | 19 | ```console 20 | $ boot benchmarking libra 21 | ``` 22 | -------------------------------------------------------------------------------- /lein-libra/project.clj: -------------------------------------------------------------------------------- 1 | (defproject net.totakke/lein-libra "0.1.2" 2 | :description "Libra Leiningen plugin" 3 | :url "https://github.com/totakke/libra" 4 | :scm {:dir ".."} 5 | :license {:name "The MIT License" 6 | :url "https://opensource.org/licenses/MIT"} 7 | :eval-in-leiningen true 8 | :dependencies [[org.clojure/tools.namespace "0.2.11"] 9 | [org.clojure/tools.reader "1.1.3.1"]]) 10 | -------------------------------------------------------------------------------- /libra/src/libra/criterium.clj: -------------------------------------------------------------------------------- 1 | (ns libra.criterium 2 | (:require [criterium.core :as c])) 3 | 4 | (defmacro bench 5 | [expr] 6 | `(let [result# (c/benchmark ~expr {})] 7 | {:time (* (first (:mean result#)) 1e9) 8 | :sd (* (first (:sample-variance result#)) 1e9)})) 9 | 10 | (defmacro quick-bench 11 | [expr] 12 | `(let [result# (c/quick-benchmark ~expr {})] 13 | {:time (* (first (:mean result#)) 1e9) 14 | :sd (* (first (:sample-variance result#)) 1e9)})) 15 | -------------------------------------------------------------------------------- /example/build.boot: -------------------------------------------------------------------------------- 1 | (set-env! 2 | :source-paths #{"src"} 3 | :dependencies '[[org.clojure/clojure "1.8.0"] 4 | [criterium "0.4.4" :scope "test"] 5 | [net.totakke/libra "0.1.1" :scope "test"] 6 | [net.totakke/boot-libra "0.1.0" :scope "test"]]) 7 | 8 | (require '[libra.boot :refer [libra]]) 9 | 10 | (deftask benchmarking 11 | "Profile setup for running benchmarks." 12 | [] 13 | (set-env! :source-paths #(conj % "bench")) 14 | identity) 15 | -------------------------------------------------------------------------------- /example/deps.edn: -------------------------------------------------------------------------------- 1 | {:deps {criterium {:mvn/version "0.4.4"} 2 | net.totakke/libra {:mvn/version "0.1.1"}} 3 | :aliases {:libra {:extra-paths ["bench"] 4 | :extra-deps {net.totakke/libra-runner {:git/url "https://github.com/totakke/libra" 5 | :sha "6d2da78300438cb212b58d17675fc9e5d405ad49" 6 | :deps/root "libra-runner"}} 7 | :main-opts ["-m" "libra.runner"]}}} 8 | -------------------------------------------------------------------------------- /example/project.clj: -------------------------------------------------------------------------------- 1 | (defproject example "0.1.0-SNAPSHOT" 2 | :description "Example of Libra" 3 | :url "https://github.com/totakke/libra" 4 | :dependencies [[org.clojure/clojure "1.8.0"]] 5 | :plugins [[net.totakke/lein-libra "0.1.2"]] 6 | :profiles {:dev {:dependencies [[criterium "0.4.4"] 7 | [net.totakke/libra "0.1.1"]]}} 8 | :libra {:bench-paths ["bench"] ; default "bench" 9 | :bench-selectors {:default (complement :slow) 10 | :slow :slow}}) 11 | -------------------------------------------------------------------------------- /boot-libra/build.boot: -------------------------------------------------------------------------------- 1 | (set-env! 2 | :resource-paths #{"src"} 3 | :dependencies '[[org.clojure/clojure "1.8.0" :scope "provided"] 4 | [org.clojure/tools.namespace "0.2.11"]]) 5 | 6 | (def +version+ "0.1.1-SNAPSHOT") 7 | 8 | (task-options! 9 | pom {:project 'net.totakke/boot-libra 10 | :version +version+ 11 | :description "Libra Boot task" 12 | :url "https://github.com/totakke/libra" 13 | :scm {:url "https://github.com/totakke/libra"} 14 | :license {"The MIT License" "https://opensource.org/licenses/MIT"}}) 15 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | working_directory: ~/libra 5 | docker: 6 | - image: circleci/clojure:lein-2.8.1 7 | environment: 8 | LEIN_ROOT: nbd 9 | JVM_OPTS: -Xmx3200m 10 | steps: 11 | - checkout 12 | - restore_cache: 13 | key: libra-{{ checksum "libra/project.clj" }} 14 | - run: cd libra && lein with-profile +dev:+1.7:+1.9:+1.10 deps 15 | - save_cache: 16 | paths: 17 | - ~/.m2 18 | - ~/.lein 19 | key: libra-{{ checksum "libra/project.clj" }} 20 | - run: cd libra && lein with-profile +dev:+1.7:+1.9:+1.10 test 21 | -------------------------------------------------------------------------------- /example/bench/example/core_bench.clj: -------------------------------------------------------------------------------- 1 | (ns example.core-bench 2 | (:require [libra.bench :refer :all] 3 | [libra.criterium :as c] 4 | [example.core :refer :all])) 5 | 6 | (defbench primes-with-trial-div-bench 7 | (is (dur 10 (doall (primes-with-trial-div 100000))))) 8 | 9 | (defbench primes-with-eratosthenes-bench 10 | (is (dur 10 (doall (primes-with-eratosthenes 100000))))) 11 | 12 | (defbench ^:slow primes-with-trial-div-bench-precise 13 | (is (c/bench (doall (primes-with-trial-div 100000))))) 14 | 15 | (defbench ^:slow primes-with-eratosthenes-bench-precise 16 | (is (c/bench (doall (primes-with-eratosthenes 100000))))) 17 | -------------------------------------------------------------------------------- /libra/project.clj: -------------------------------------------------------------------------------- 1 | (defproject net.totakke/libra "0.1.2-SNAPSHOT" 2 | :description "Simple benchmarking framework for Clojure" 3 | :url "https://github.com/totakke/libra" 4 | :scm {:dir ".."} 5 | :license {:name "The MIT License" 6 | :url "https://opensource.org/licenses/MIT"} 7 | :dependencies [[org.clojure/clojure "1.8.0" :scope "provided"]] 8 | :profiles {:dev {:dependencies [[org.clojure/test.check "0.9.0"] 9 | [criterium "0.4.4"]]} 10 | :1.7 {:dependencies [[org.clojure/clojure "1.7.0"]]} 11 | :1.9 {:dependencies [[org.clojure/clojure "1.9.0"]]} 12 | :1.10 {:dependencies [[org.clojure/clojure "1.10.0-alpha6"]]}}) 13 | -------------------------------------------------------------------------------- /example/src/example/core.clj: -------------------------------------------------------------------------------- 1 | (ns example.core) 2 | 3 | ;; Trial division 4 | 5 | (defn prime-with-trial-div? 6 | [n] 7 | (cond 8 | (< n 2) false 9 | (= n 2) true 10 | (zero? (mod n 2)) false 11 | :else (->> (range 3 n 2) 12 | (take-while #(<= % (/ n %))) 13 | (every? #(pos? (mod n %)))))) 14 | 15 | (defn primes-with-trial-div 16 | [n] 17 | (filter prime-with-trial-div? (range (inc n)))) 18 | 19 | ;; Sieve of Eratosthenes 20 | 21 | (defn primes-with-eratosthenes 22 | [n] 23 | (let [m (Math/sqrt n)] 24 | (loop [[f & r :as xs] (range 2 n) 25 | primes []] 26 | (if (<= f m) 27 | (recur (remove #(zero? (mod % f)) r) (conj primes f)) 28 | (concat primes xs))))) 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2018 Toshiki Takeuchi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /boot-libra/src/libra/boot.clj: -------------------------------------------------------------------------------- 1 | (ns libra.boot 2 | {:boot/export-tasks true} 3 | (:require [clojure.java.io :as io] 4 | [clojure.tools.namespace :as tns] 5 | [boot.core :as core :refer [deftask]] 6 | [boot.pod :as pod])) 7 | 8 | (def ^:private pod-deps '[]) 9 | 10 | (defn- init [fresh-pod] 11 | (pod/require-in fresh-pod '[libra.bench])) 12 | 13 | (deftask libra 14 | "Measure the project's benchmarks." 15 | [n namespaces NAMESPACE #{sym} "The set of namespace symbols to run benchmarks in."] 16 | (let [updated-env (update-in (core/get-env) [:dependencies] into pod-deps) 17 | worker-pods (pod/pod-pool updated-env :init init)] 18 | (core/cleanup (worker-pods :shutdown)) 19 | (core/with-pre-wrap fileset 20 | (let [worker-pod (worker-pods :refresh) 21 | namespaces (or (seq namespaces) 22 | (->> (:source-paths (core/get-env)) 23 | (map io/file) 24 | (mapcat tns/find-namespaces-in-dir) 25 | distinct)) 26 | ns-sym (gensym "namespaces")] 27 | (if (seq namespaces) 28 | (pod/with-eval-in worker-pod 29 | (let [~ns-sym '~namespaces] 30 | (when (seq ~ns-sym) 31 | (apply require :reload ~ns-sym)) 32 | (apply libra.bench/run-benches ~ns-sym))))) 33 | (core/commit! fileset)))) 34 | -------------------------------------------------------------------------------- /libra/test/libra/bench_test.clj: -------------------------------------------------------------------------------- 1 | (ns libra.bench-test 2 | (:require [clojure.test :refer :all] 3 | [clojure.test.check :as tc] 4 | [clojure.test.check.clojure-test :refer [defspec]] 5 | [clojure.test.check.generators :as gen] 6 | [clojure.test.check.properties :as prop] 7 | [libra.bench :as b])) 8 | 9 | (deftest format-time-test 10 | (are [x s] (= (#'b/format-time x) s) 11 | 12.0 "12.000000 ns" 12 | 12312.0 "12.312000 µs" 13 | 12312312.0 "12.312312 ms" 14 | 12312312312.0 "12.312312 sec" 15 | nil "n/a")) 16 | 17 | (defspec format-time-ns-test 18 | 100 19 | (prop/for-all [x (gen/double* {:min 0 :max (- 1e3 0.1) :infinite? false :NaN? false})] 20 | (re-matches #"-?\d{1,3}\.\d{6} ns" (#'b/format-time x)))) 21 | 22 | (defspec format-time-micros-test 23 | 100 24 | (prop/for-all [x (gen/double* {:min 1e3 :max (- 1e6 0.1) :infinite? false :NaN? false})] 25 | (re-matches #"\d{1,3}\.\d{6} µs" (#'b/format-time x)))) 26 | 27 | (defspec format-time-ms-test 28 | 100 29 | (prop/for-all [x (gen/double* {:min 1e6 :max (- 1e9 0.1) :infinite? false :NaN? false})] 30 | (re-matches #"\d{1,3}\.\d{6} ms" (#'b/format-time x)))) 31 | 32 | (defspec format-time-sec-test 33 | 100 34 | (prop/for-all [x (gen/double* {:min 1e9 :infinite? false :NaN? false})] 35 | (re-matches #"\d+\.\d{6} sec" (#'b/format-time x)))) 36 | 37 | (deftest mean-test 38 | (are [xs x] (= (#'b/mean xs) x) 39 | [1 1 1 1 1] 1 40 | [1 2 3 4 5] 3)) 41 | 42 | (defspec mean-range-test 43 | 100 44 | (prop/for-all [v (-> (gen/double* {:min -1e30 :max 1e30 :infinite? false :NaN? false}) 45 | gen/vector 46 | gen/not-empty)] 47 | (<= (apply min v) (#'b/mean v) (apply max v)))) 48 | 49 | (deftest variance-test 50 | (are [xs x] (= (#'b/variance xs) x) 51 | [1 1 1 1 1] 0.0 52 | [1 2 3 4 5] 2.5)) 53 | 54 | (defspec variance-range-test 55 | 100 56 | (prop/for-all [v (gen/vector (gen/double* {:min -1e30 :max 1e30 :infinite? false :NaN? false}) 57 | 2 100)] 58 | (<= 0.0 (#'b/variance v)))) 59 | -------------------------------------------------------------------------------- /libra-runner/src/libra/runner.clj: -------------------------------------------------------------------------------- 1 | (ns libra.runner 2 | (:require [clojure.java.io :as io] 3 | [clojure.string :as string] 4 | [clojure.tools.cli :as cli] 5 | [clojure.tools.namespace :as tns] 6 | [libra.bench :as libra])) 7 | 8 | (defn- bench 9 | [options] 10 | (let [dirs (or (:dir options) 11 | #{"bench"}) 12 | namespaces (or (seq (:namespace options)) 13 | (->> dirs 14 | (map io/file) 15 | (mapcat tns/find-namespaces-in-dir) 16 | distinct)) 17 | inclusion (if (:include options) 18 | (apply some-fn (:include options)) 19 | (constantly true)) 20 | exclusion (if (:exclude options) 21 | (complement (apply some-fn (:exclude options))) 22 | (constantly true))] 23 | (when (seq namespaces) 24 | (apply require :reload namespaces)) 25 | (binding [libra/*selector* (every-pred inclusion exclusion)] 26 | (apply libra/run-benches namespaces)))) 27 | 28 | (defn- parse-kw 29 | [s] 30 | (if (.startsWith s ":") 31 | (read-string s) 32 | (keyword s))) 33 | 34 | (defn- acc-opts 35 | [m k v] 36 | (update-in m [k] (fnil conj #{}) v)) 37 | 38 | (def cli-options 39 | [["-d" "--dir DIR" "Name of the directory containing benchmarks, default \"bench\"." 40 | :assoc-fn acc-opts] 41 | ["-n" "--namespace SYMBOL" "Symbol indicating a specific namespace to run benchmarks." 42 | :parse-fn symbol 43 | :assoc-fn acc-opts] 44 | ["-i" "--include KEYWORD" "Run only benchmarks that have this metadata keyword." 45 | :parse-fn parse-kw 46 | :assoc-fn acc-opts] 47 | ["-e" "--exclude KEYWORD" "Exclude benchmarks with this metadata keyword." 48 | :parse-fn parse-kw 49 | :assoc-fn acc-opts] 50 | ["-h" "--help"]]) 51 | 52 | (defn- help 53 | [summary] 54 | (println "Usage: clj -m" (namespace `usage) "[]\n") 55 | (println summary)) 56 | 57 | (defn- exit 58 | [status message] 59 | (println message) 60 | (System/exit status)) 61 | 62 | (defn -main 63 | "Entry point for the libra runner" 64 | [& args] 65 | (let [{:keys [options errors summary]} (cli/parse-opts args cli-options)] 66 | (cond 67 | errors (exit 1 (string/join \newline errors)) 68 | (:help options) (help summary) 69 | :else (bench options)))) 70 | -------------------------------------------------------------------------------- /libra/src/libra/bench.clj: -------------------------------------------------------------------------------- 1 | (ns libra.bench 2 | (:require [clojure.template :as temp])) 3 | 4 | (defn- scaled-time 5 | [nanos] 6 | (condp > nanos 7 | 1e3 [nanos "ns"] 8 | 1e6 [(double (/ nanos 1e3)) "µs"] 9 | 1e9 [(double (/ nanos 1e6)) "ms"] 10 | [(double (/ nanos 1e9)) "sec"])) 11 | 12 | (defn- format-time 13 | [nanos] 14 | (if (nil? nanos) 15 | "n/a" 16 | (let [[scaled unit] (scaled-time nanos)] 17 | (format "%f %s" scaled unit)))) 18 | 19 | (defn- filename 20 | [s] 21 | (second (re-find #"([\w\.]+)$" s))) 22 | 23 | (defn report 24 | [m] 25 | (newline) 26 | (when (:message m) 27 | (println (str " " (:message m)))) 28 | (println (format " time: %s, sd: %s" (format-time (:time m)) (format-time (:sd m))))) 29 | 30 | (defn bench-var 31 | [v] 32 | (let [m (meta v)] 33 | (when-let [b (:bench m)] 34 | (newline) 35 | (println (str (:name m) " (" (filename (:file m)) ":" (:line m) ")")) 36 | (b)))) 37 | 38 | (def ^:dynamic *selector* (constantly true)) 39 | 40 | (defn bench-ns 41 | [ns] 42 | (let [ns-obj (the-ns ns) 43 | vs (->> (ns-interns ns-obj) 44 | vals 45 | (sort-by (comp (juxt :file :line) meta)) 46 | (filter (fn [v] 47 | (let [m (meta v)] 48 | (and (:bench m) (*selector* m))))))] 49 | (when (seq vs) 50 | (newline) 51 | (println "Measuring" (str ns-obj)) 52 | (doseq [v vs] 53 | (bench-var v))))) 54 | 55 | (defmacro is 56 | ([expr] `(is ~expr nil)) 57 | ([expr msg] `(report (assoc ~expr :message ~msg)))) 58 | 59 | (defmacro are 60 | [argv expr & args] 61 | (letfn [(message [x] 62 | (let [s (str x)] 63 | (if (> (count s) 80) (subs s 0 80) s)))] 64 | (if (or (and (empty? argv) (empty? args)) 65 | (and (pos? (count argv)) 66 | (pos? (count args)) 67 | (zero? (mod (count args) (count argv))))) 68 | `(temp/do-template ~argv (is ~expr (~message ~argv)) ~@args) 69 | (throw (IllegalArgumentException. "#args does not match argv"))))) 70 | 71 | (defmacro ^:deprecated measure 72 | ([expr] `(is ~expr)) 73 | ([expr msg] `(is ~expr ~msg))) 74 | 75 | (defmacro defbench 76 | [name & body] 77 | `(def ~(vary-meta name assoc :bench `(fn [] ~@body)) 78 | (fn [] (bench-var (var ~name))))) 79 | 80 | (defn run-benches 81 | ([] (run-benches *ns*)) 82 | ([& namespaces] 83 | (doseq [ns namespaces] 84 | (bench-ns ns)))) 85 | 86 | (defn- mean 87 | [xs] 88 | (/ (reduce + xs) (count xs))) 89 | 90 | (defn- variance 91 | [xs] 92 | (/ (->> (map #(- % (mean xs)) xs) 93 | (map #(Math/pow % 2)) 94 | (reduce +)) 95 | (dec (count xs)))) 96 | 97 | (defn- sd 98 | [xs] 99 | (Math/sqrt (variance xs))) 100 | 101 | (defn dur* 102 | ([f] 103 | (let [start (System/nanoTime)] 104 | (f) 105 | {:time (double (- (System/nanoTime) start)), :sd nil})) 106 | ([n f] 107 | {:pre [(pos? n)]} 108 | (if (= n 1) 109 | (dur* f) 110 | (let [ts (map :time (repeatedly n #(dur* f)))] 111 | {:time (mean ts), :sd (sd ts)})))) 112 | 113 | (defmacro dur 114 | ([expr] `(dur 1 ~expr)) 115 | ([n expr] `(dur* ~n (fn [] ~expr)))) 116 | -------------------------------------------------------------------------------- /lein-libra/src/leiningen/libra.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.libra 2 | (:require [clojure.java.io :as io] 3 | [clojure.string :as string] 4 | [clojure.tools.namespace :as tns] 5 | [clojure.tools.reader.edn :as edn] 6 | [leiningen.core.eval :as eval] 7 | [leiningen.core.project :as project])) 8 | 9 | (defn- split-args 10 | [args] 11 | (let [[nses selectors] (split-with (complement keyword?) args)] 12 | (loop [acc {} [selector & selectors] selectors] 13 | (if (seq selectors) 14 | (let [[args next] (split-with (complement keyword?) selectors)] 15 | (recur (assoc acc selector args) next)) 16 | (if selector 17 | (assoc acc selector ()) 18 | acc))))) 19 | 20 | (defn- parse-only 21 | [xs] 22 | (->> (map str xs) 23 | (map (fn [s] 24 | (let [[ns* v] (string/split s #"/")] 25 | [ns* v]))) 26 | (group-by first) 27 | (map (fn [[k v]] 28 | [(edn/read-string k) 29 | (->> (keep second v) 30 | distinct 31 | (map #(str k "/" %)) 32 | (map edn/read-string) 33 | seq)])) 34 | (into {}))) 35 | 36 | (defn- parse-args 37 | [args project] 38 | (let [given-selectors (split-args args) 39 | project-selectors (merge {:all '(constantly true) 40 | :only '(constantly true) 41 | :default '(constantly true)} 42 | (-> project :libra :bench-selectors)) 43 | selectors (->> given-selectors 44 | (keep (fn [[k v]] 45 | (if-let [selector (k project-selectors)] 46 | [k (if (= k :only) 47 | (parse-only v) 48 | selector)]))) 49 | (into {}))] 50 | (if (seq selectors) 51 | selectors 52 | (select-keys project-selectors [:default])))) 53 | 54 | (defn- select-namespaces 55 | [namespaces selectors] 56 | (let [selector-nses (-> (:only selectors) keys set)] 57 | (if (seq selector-nses) 58 | (filter selector-nses namespaces) 59 | namespaces))) 60 | 61 | (defn benchmarking-form 62 | [namespaces selectors] 63 | (let [namespaces (select-namespaces namespaces selectors)] 64 | (if (seq namespaces) 65 | (let [ns-sym (gensym "namespaces")] 66 | `(let [~ns-sym '~namespaces] 67 | (when (seq ~ns-sym) 68 | (apply require :reload ~ns-sym)) 69 | (doseq [ns# ~ns-sym] 70 | (if-let [only# (:only '~selectors)] 71 | (if-let [vs# (get only# ns#)] 72 | (doseq [v# vs#] (libra.bench/bench-var (resolve v#))) 73 | (libra.bench/bench-ns ns#)) 74 | (binding [libra.bench/*selector* (->> (vals '~selectors) 75 | (map eval) 76 | (apply every-pred))] 77 | (libra.bench/bench-ns ns#))))))))) 78 | 79 | (defn libra 80 | "Measure the project's benchmarks. 81 | 82 | A default :only bench-selector is available to run select benchmarks. For 83 | example, `lein libra :only example.foo-bench` only runs benchmarks in the 84 | specified namespace." 85 | [project & args] 86 | (let [libra-profile (merge {:bench-paths ["bench"]} (:libra project)) 87 | project (project/merge-profiles project [{:source-paths (:bench-paths libra-profile)}]) 88 | selectors (parse-args (map edn/read-string args) project) 89 | _ (eval/prep project) 90 | namespaces (->> (:bench-paths libra-profile) 91 | (mapcat (comp tns/find-namespaces-in-dir io/file)) 92 | distinct) 93 | form (benchmarking-form namespaces selectors)] 94 | (eval/eval-in-project project form '(require 'libra.bench)))) 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Libra 2 | 3 | [![Clojars Project](https://img.shields.io/clojars/v/net.totakke/libra.svg)](https://clojars.org/net.totakke/libra) 4 | [![CircleCI](https://circleci.com/gh/totakke/libra.svg?style=svg)](https://circleci.com/gh/totakke/libra) 5 | 6 | Benchmarking framework for Clojure 7 | 8 | ## Installation 9 | 10 | ### Core 11 | 12 | Leiningen/Boot: 13 | 14 | ```clojure 15 | [net.totakke/libra "0.1.1"] 16 | ``` 17 | 18 | Clojure CLI: 19 | 20 | ```clojure 21 | net.totakke/libra {:mvn/version "0.1.1"} 22 | ``` 23 | 24 | ### Tools 25 | 26 | Leiningen plugin: 27 | 28 | ```clojure 29 | :plugins [[net.totakke/lein-libra "0.1.2"]] 30 | ``` 31 | 32 | Boot task: 33 | 34 | ```clojure 35 | [net.totakke/boot-libra "0.1.0" :scope "test"] 36 | ``` 37 | 38 | CLI runner: 39 | 40 | ```clojure 41 | net.totakke/libra-runner {:git/url "https://github.com/totakke/libra" 42 | :sha "6d2da78300438cb212b58d17675fc9e5d405ad49" 43 | :deps/root "libra-runner"} 44 | ``` 45 | 46 | ## Getting started 47 | 48 | Libra provides clojure.test-like functions and macros for benchmarking. For 49 | example, `defbench` defines a benchmark and `run-benches` measures defined 50 | benchmarks in the namespace. 51 | 52 | ```clojure 53 | (require '[libra.bench :refer :all]) 54 | 55 | (defn slow-inc [n] 56 | (Thread/sleep 10) 57 | (inc n)) 58 | 59 | (defbench slow-inc-bench 60 | (is (dur 10 (slow-inc 100)))) 61 | 62 | (run-benches) 63 | ;; Measuring user 64 | ;; 65 | ;; slow-inc-bench (:xx) 66 | ;; 67 | ;; time: 11.725818 ms, sd: 1.073600 ms 68 | ;;=> nil 69 | ``` 70 | 71 | ## Basics 72 | 73 | Basic usage is writing benchmarks in a separate directory (e.g. `bench`) from 74 | `src` and running them with command-line. See [example project](https://github.com/totakke/libra/tree/master/example) 75 | and try running benchmark. 76 | 77 | The project consists of the following files. 78 | 79 | ``` 80 | example/ 81 | ├── project.clj or build.boot or deps.edn 82 | ├── src/ 83 | │ └── example/ 84 | │ └── core.clj 85 | └── bench/ 86 | └── example/ 87 | └── core_bench.clj 88 | ``` 89 | 90 | Locate your awesome codes in `src/example/core.clj` as usual, and write 91 | benchmarking programs in `bench/example/core_bench.clj`. 92 | 93 | ```clojure 94 | (ns example.core-bench 95 | (:require [libra.bench :refer :all] 96 | [example.core :refer :all])) 97 | 98 | (defbench primes-with-trial-div-bench 99 | (is (dur 10 (doall (primes-with-trial-div 100000))))) 100 | 101 | (defbench primes-with-eratosthenes-bench 102 | (is (dur 10 (doall (primes-with-eratosthenes 100000))))) 103 | ``` 104 | 105 | ### With Leiningen 106 | 107 | To run the benchmark with Leiningen, 108 | 109 | ```console 110 | $ lein libra 111 | ``` 112 | 113 | lein-libra looks benchmark files in `bench` directory by default. You can change 114 | this by placing the following in `project.clj`: 115 | 116 | ```clojure 117 | :libra {:bench-paths ["path/to/bench"]} 118 | ``` 119 | 120 | You can supply predicates to determine whether to run a benchmark or not, which 121 | takes `defbench` metadata as argument: 122 | 123 | ```clojure 124 | :libra {:bench-selectors {:default (complement :slow) 125 | :slow :slow}} 126 | ``` 127 | 128 | ### With Boot 129 | 130 | To run the benchmark with Boot, 131 | 132 | ```console 133 | $ lein benchmarking libra 134 | ``` 135 | 136 | boot-libra provides `libra` task. Benchmark directory needs to be included in 137 | the classpath, so that you should add a profile task for benchmarking: 138 | 139 | ```clojure 140 | (require '[libra.boot :refer [libra]]) 141 | 142 | (deftask benchmarking [] 143 | (set-env! :source-paths #(conj % "bench")) 144 | identity) 145 | ``` 146 | 147 | ### With Clojure CLI 148 | 149 | Include a dependency on libra-runner in `deps.edn`. 150 | 151 | ```clojure 152 | :aliases {:libra {:extra-paths ["bench"] 153 | :extra-deps {net.totakke/libra-runner {:git/url "https://github.com/totakke/libra" 154 | :sha "" 155 | :deps/root "libra-runner"}} 156 | :main-opts ["-m" "libra.runner"]}} 157 | ``` 158 | 159 | Then, invoke `libra` alias with CLI. 160 | 161 | ```console 162 | $ clj -Alibra 163 | ``` 164 | 165 | You may supply additional options: 166 | 167 | ``` 168 | -d, --dir DIR Name of the directory containing benchmarks, default "bench". 169 | -n, --namespace SYMBOL Symbol indicating a specific namespace to run benchmarks. 170 | -i, --include KEYWORD Run only benchmarks that have this metadata keyword. 171 | -e, --exclude KEYWORD Exclude benchmarks with this metadata keyword. 172 | ``` 173 | 174 | ## Criterium integration 175 | 176 | Libra can be used with a famous benchmarking library, [Criterium](https://github.com/hugoduncan/criterium/). 177 | `libra.criterium` provides wrapper macros of Criterium. 178 | 179 | ```clojure 180 | (require '[libra.criterium :as c]) 181 | 182 | (defbench primes-with-eratosthenes-bench 183 | (is (c/quick-bench (doall (primes-with-eratosthenes 100000))))) 184 | ``` 185 | 186 | ## License 187 | 188 | Copyright © 2017-2019 Toshiki Takeuchi 189 | 190 | Distributed under the MIT License. 191 | --------------------------------------------------------------------------------