5541c24e24cb95078d6b031202748ca3ba6a9ac3 — Dominic Monroe 1 year, 11 months ago
Add initial version
4 files changed, 142 insertions(+), 0 deletions(-)

A .gitignore
A deps.edn
A src/io/dominic/slow_namespace_clj/core.clj
A  => .gitignore +1 -0
@@ 1,1 @@

A  => README.md +26 -0
@@ 1,26 @@
# slow-namespace-clj

Find which namespaces are slowest in your project.

## Usage

Add an alias to `~/.clojure/deps.edn`

  {:git/url "https://git.sr.ht/~severeoverfl0w/slow-namespace-clj"
   :sha "TODO"}}
 :main-opts ["-m" "io.dominic.slow-namespace-clj.core"]}

Then you can scan for just dependencies of your project, by specifying the
directories from your project

# Main project
$ clojure -A:user/slow-namespace-clj src
# For development
$ clojure -A:dev:user/slow-namespace-clj src dev test

A  => deps.edn +3 -0
@@ 1,3 @@
{:paths ["src"]
 :deps {org.clojure/tools.namespace {:mvn/version "1.0.0"}
        org.clojure/java.classpath {:mvn/version "1.0.0"}}}

A  => src/io/dominic/slow_namespace_clj/core.clj +112 -0
@@ 1,112 @@
(ns io.dominic.slow-namespace-clj.core
  (:require [clojure.tools.namespace.dependency :as dep]
            [clojure.tools.namespace.find :as find]
            [clojure.tools.namespace.parse :as parse]
            [clojure.java.classpath :as cp]
            [clojure.string :as string]))

(defn- recursive-ns-decls
  "Search scan-cp for ns decls, then recursively resolve all dependents using full classpath"
  (let [scan-decls (find/find-ns-decls scan-cp find/clj)
        decl-idx (into {} (map (juxt parse/name-from-ns-decl identity)
                               (find/find-ns-decls (cp/classpath) find/clj)))]
    (loop [decls scan-decls
           final-decls []]
      (if (seq decls)
        (let [[decl & decls] decls]
            (apply conj decls (map decl-idx (parse/deps-from-ns-decl decl)))
            (conj final-decls decl)))

(defn- ns-graph
  ([] (ns-graph (cp/classpath)))
     (fn [graph ns-decl]
         (fn [graph dep]
           (dep/depend graph (parse/name-from-ns-decl ns-decl) dep))
         (parse/deps-from-ns-decl ns-decl)))
     (recursive-ns-decls scan-cp))))

(defmacro ^:private timed
  `(let [start# (. System (nanoTime))]
     (/ (double (- (. System (nanoTime)) start#)) 1000000.0) ))

(defn- prefixes
        #(subs % 0 (or (string/last-index-of % ".") 0))

(def ^:private ^:dynamic *safe* false)

(defn- ns-timing
  (let [timing (atom {})]
    (doseq [ns (dep/topo-sort ns-graph)]
      (binding [*out* *err*]
          (let [ttr (timed (if *safe*
                             (require ns :reload)
                             (require ns)))]
            (swap! timing assoc ns ttr))
          (catch clojure.lang.Compiler$CompilerException _
            (println "Exception while loading" ns))
          (catch ClassNotFoundException _
            (println "Exception while loading" ns))
          (catch java.io.FileNotFoundException _
            (println "Exception while loading" ns)))))

(defn- prefix-timing
  (let [ns-in-group (reduce
                      (fn [grouped ns]
                          (fn [grouped prefix]
                            (update grouped prefix conj ns))
                          (prefixes (str ns))))
                      (keys timing))
        ;; remove groups which are otherwise identical, pick the longest group.
        (vals (reduce-kv
                (fn [x group nss]
                  (update x nss (fn [old new] (max-key count (or old "") new)) group))
    (zipmap deduped-groups
            (map (fn [nss] (apply + (map #(get timing %) nss)))
                 (map ns-in-group deduped-groups)))))

(defn- run
  ([] (run {}))
  ([{:keys [threshold ns-graph]
     :or {threshold 0.1
          ns-graph (ns-graph)}}]
   (let [timing (ns-timing ns-graph)
         prefix-timing (prefix-timing timing)]
     (doseq [[ns ttr] (sort-by val timing)
             :when (> ttr threshold)]
       (println ns ":" ttr "msecs"))
     (println "-- [Groups] --")
     (doseq [[group ttr] (sort-by key prefix-timing)]
       (println group ":" ttr "msecs")))))

(defn -main
  [& dirs]
  (binding [*safe* true]
    (if (seq dirs)
      (run {:ns-graph (ns-graph (map #(java.io.File. %) dirs))})