{"id":32191789,"url":"https://github.com/withjak/inspector","last_synced_at":"2025-10-22T01:50:50.611Z","repository":{"id":199935685,"uuid":"704048216","full_name":"withjak/inspector","owner":"withjak","description":"See what your functions are doing.","archived":false,"fork":false,"pushed_at":"2024-09-12T11:07:17.000Z","size":420,"stargazers_count":22,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-10-22T01:49:54.994Z","etag":null,"topics":["clojure","debugger","hierarchy","omnipresent","profiling","trace"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/withjak.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-10-12T12:36:09.000Z","updated_at":"2025-04-07T13:33:38.000Z","dependencies_parsed_at":null,"dependency_job_id":"0c47087c-e8a0-4f9e-8bde-3d0bd276daf9","html_url":"https://github.com/withjak/inspector","commit_stats":null,"previous_names":["withjak/inspector"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/withjak/inspector","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/withjak%2Finspector","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/withjak%2Finspector/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/withjak%2Finspector/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/withjak%2Finspector/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/withjak","download_url":"https://codeload.github.com/withjak/inspector/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/withjak%2Finspector/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":280365582,"owners_count":26318385,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-21T02:00:06.614Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clojure","debugger","hierarchy","omnipresent","profiling","trace"],"created_at":"2025-10-22T01:50:47.520Z","updated_at":"2025-10-22T01:50:50.602Z","avatar_url":"https://github.com/withjak.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# inspector\n\n[![Clojars Project](https://img.shields.io/clojars/v/org.clojars.akshay/inspector.svg)](https://clojars.org/org.clojars.akshay/inspector)\n[![Clojars Project](https://img.shields.io/clojars/v/org.clojars.akshay/inspector.svg?include_prereleases)](https://clojars.org/org.clojars.akshay/inspector)\n\n**Inspector** is a tool for profiling, debugging, tracing, and visualizing function call hierarchies in Clojure applications. It provides insights into who is calling whom, with what arguments, what was returned, execution time, and more.\n\n# Table of Contents\n- [Add dependency](#Add-dependency)\n- [Features](#Features)\n- [Basic Usage](#Basic-Usage)\n  - [Setup](#Setup)\n  - [Normal mode](#Normal-mode)\n  - [Omnipresent mode](#Omnipresent-mode)\n  - [Important Notes](#Important-Notes)\n- [Detailed Usage](#Detailed-Usage)\n  - [Normal Mode: Output](#Normal-Mode-Output)\n  - [Normal Mode: Raw Data](#Normal-Mode-Raw-Data)\n  - [Omnipresent Mode: REPL](#Omnipresent-Mode-REPL)\n- [Tracking Specific Functions or Namespaces](#Tracking-Specific-Functions-or-Namespaces)\n- [Skipping Function Tracking](#Skipping-Function-Tracking)\n- [Middleware](#Middleware)\n\n## Add dependency\nAdd the following dependency to your project:\n### Leiningen\n```clojure\n[org.clojars.akshay/inspector \"1.1.3\"]\n```\n\n### Clojure CLI/deps.edn\n```clojure\norg.clojars.akshay/inspector {:mvn/version \"1.1.3\"}\n```\n\n## Features\n- **Minimal API**: `get-vars`, `iprint`, `ispit`, `stream-raw`.\n- **Fine-grained control**: Track specific functions and namespaces.\n- **Low performance overhead**.\n- **Multiple Modes**:\n  - **Normal Mode**: Get human-readable output for specific function calls. (`iprint`, `ispit`)\n  - **Omnipresent Mode**: Continuously capture function calls across all threads. (`stream-raw`)\n- **Middleware**: Inject custom code before and after tracked functions executions.\n- **Detailed Insights for Each Function Call**:\n  - `:fn-name`: Namespace-qualified function name.\n  - `:time`:    Execution time (in nanoseconds).\n  - `:fn-args`: Arguments passed.\n  - `:fn-rv`:   Return value.\n  - `:e`:       Errors (if any).\n  - `:id`:      Unique ID for the function call.\n  - `:tid`:     Thread ID.\n  - `:c-id`:    Caller’s ID.\n  - `:c-tid`:   Caller’s thread ID.\n  - `:c-chain`: Call chain (vector of function ids).\n  - `:uuid`:    All function calls resulting from a top-level function invocation have same uuid.\n\n\n## Basic Usage\n\n### Setup\nStart by requiring the necessary namespace:\n```clojure\n(require '[inspector.inspector :as i])\n```\nNext, define the functions you want to track using get-vars:\n```clojure\n(def tracked-vars (i/get-vars #\"project-prefix.*\"))\n```\n\n### Normal mode\nTo print function calls in a readable format, use:\n```clojure\n(i/iprint tracked-vars #(my-fn arg1 arg2 argn))\n```\nOr, write the output to a file:\n```clojure\n(i/ispit \"/tmp/hierarchy.log\" tracked-vars #(my-fn arg1 arg2 argn))\n```\nExample output from `inspector.test.inspector-test`:\n```roomsql\nTime: Tue Jan 23 16:28:30 IST 2024\nГ-- inspector.test.inspector-test/parallel (1) \u003c-- arguments\n|  Г-- inspector.test.inspector-test/simple (0)\n|  |  Г-- inspector.test.inspector-test/simplest (0)\n|  |  L-- 0\n|  L-- 0\n|  Г-- inspector.test.inspector-test/simple (1)\n|  |  Г-- inspector.test.inspector-test/simplest (1)\n|  |  L-- 1\n|  L-- 1\nL-- [0 1] \u003c-- return value\n```\n\n### Omnipresent mode\nTo capture data continuously:\n```clojure\n(defn ^:i-skip export \n  [{:keys [:fn-name :fn-args :fn-rv :e :time :id :tid :c-id :c-tid :c-chain :uuid]} :as record]\n  ;; Handle the captured data (e.g., log it, send to a database, etc.)\n  (clojure.tools.logging/info (dissoc record :fn-args :fn-rv)))\n  \n;; export will be called every time a function execution completes\n;; place it somewhere near the top of -main function\n(i/stream-raw tracked-vars export)\n```\n\n### Important Notes\n- **Normal Mode** (`iprint`, `ispit`): Use for targeted debugging of specific top level function.\n- **Omnipresent Mode** (`stream-raw`): Use for continuous data collection. When running **via repl** in a remote environment (staging/production), restore the environment as described in [Omnipresent Mode: REPL](#Omnipresent-Mode-REPL).\n\n\n## Detailed Usage\n### Normal Mode: Output\nCustomize the output of `iprint` and `ispit` using options.\n```clojure \n(i/iprint tracked-vars #(my-fn arg1 arg2) {:start [:time :fn-args]})\n```\nOutput:\n```roomsql\nГ-- fn-name time fn-args\n|  Г-- fn-name time fn-args\n|  |  Г-- fn-name time fn-args\n|  |  L-- fn-rv\n|  L-- fn-rv\n|  Г-- fn-name time fn-args\n|  L-- fn-rv\nL-- fn-rv\n```\n\nAnother example\n```clojure \n(i/iprint tracked-vars #(my-fn arg1 arg2) {:expanded-view? false \n                                           :start [:time :fn-rv]})\n```\nOutput:\n```roomsql\n--\u003e fn-name time fn-rv\n   --\u003e fn-name time fn-rv\n      --\u003e fn-name time fn-rv\n   --\u003e fn-name time fn-rv\n```\nYou can further tweak the output by providing different options to control indentation, markers, and more.\nCheck `i/parse-opts` to see all possible options.\n\n### Normal Mode: Raw Data\nGet raw data for advanced processing:\n```clojure\n; rv is return value of (my-fn arg1 arg2 argn)\n(let [{:keys [e rv records]} (i/export-raw tracked-vars #(my-fn arg1 arg2 argn)] \n  records)\n```\n\nExample output from `inspector.test.capture-test`:\n```clojure\n[{:c-chain [1 2] :id 4 :c-id 2   :fn-name \"inspector.test.capture-test/simplest\" :fn-args (0) :tid 30 :c-tid 30  :uuid #uuid \"4c3bf13a-7899-4202-ade6-cfa0dfc3955e\" :time 6584   :fn-rv 0}\n {:c-chain [1]   :id 2 :c-id 1   :fn-name \"inspector.test.capture-test/simple\"   :fn-args (0) :tid 30 :c-tid 34  :uuid #uuid \"4c3bf13a-7899-4202-ade6-cfa0dfc3955e\" :time 49583  :fn-rv 0}\n {:c-chain [1 3] :id 5 :c-id 3   :fn-name \"inspector.test.capture-test/simplest\" :fn-args (1) :tid 29 :c-tid 29  :uuid #uuid \"4c3bf13a-7899-4202-ade6-cfa0dfc3955e\" :time 1625   :fn-rv 1}\n {:c-chain [1]   :id 3 :c-id 1   :fn-name \"inspector.test.capture-test/simple\"   :fn-args (1) :tid 29 :c-tid 34  :uuid #uuid \"4c3bf13a-7899-4202-ade6-cfa0dfc3955e\" :time 42625  :fn-rv 1}\n {:c-chain []    :id 1 :c-id nil :fn-name \"inspector.test.capture-test/parallel\" :fn-args (1) :tid 34 :c-tid nil :uuid #uuid \"4c3bf13a-7899-4202-ade6-cfa0dfc3955e\" :time 431833 :fn-rv [0 1]}]\n```\n\n### Omnipresent Mode: REPL\nIf you're tracking function calls in a remote environment via REPL by using `stream-raw`, make sure to restore the original function definitions once done:\n```clojure\n(inspector.track/un-track tracked-vars)\n```\n\n## Tracking Specific Functions or Namespaces\nUse `get-vars` (which returns a set) to collect vars from specific namespaces. Then pass them to `iprint`, `ispit`, or `stream-raw` to start tracking them.\n\n```clojure\n(i/get-vars #\"project-prefix.*\")                ; set of all functions from all namespaces.\n\n(i/get-vars #\"project-prefix.c\")                ; set of all functions from project-prefix.c namespace\n\n(clojure.set/difference                         ; set of all functions except those defined in project-prefix.c namespace\n  (i/get-vars #\"project-prefix.*\")  \n  (i/get-vars #\"project-prefix.c\"))\n\n(set/difference                                 ; set of all functions except function project-prefix.c/c-2\n  (i/get-vars #\"project-prefix.*\") \n  #{#'dummy.c/c-2})\n```\n**Note**: \u003cbr\u003e\nIf the function call sequence is `a -\u003e b -\u003e c` and only `a` and `c` are being tracked, you'll still receive information showing `a -\u003e c`.\n\n## Skipping Function Tracking\nTo skip tracking a specific function, you can either remove its var from tracked-vars or add :`i-skip` metadata:\n```clojure\n(defn ^:i-skip foo\n  [args]\n  ...)\n```\n\n## Middleware\nYou can use middleware to run custom code before and after the execution of every tracked function.\n\n```clojure \n(defn nano-\u003ems-middleware\n  \"Converts execution time from nanoseconds to milliseconds.\"\n  [handler]\n  (fn [{:keys [fn-args fn-meta fn-rv e time id tid c-id c-tid c-chain uuid] :as state}]\n    (let [new-state (handler state)]\n      (update new-state :time nano-\u003ems))))\n```\nTo wrap tracked functions with your custom middleware, check out:\n- `stream-raw` : for omnipresent mode.\n- `export-raw` : for normal mode.\n\n## License\n\nThis program and the accompanying materials are made available under the\nterms of the Eclipse Public License 2.0 which is available at\nhttp://www.eclipse.org/legal/epl-2.0.\n\nThis Source Code may also be made available under the following Secondary\nLicenses when the conditions for such availability set forth in the Eclipse\nPublic License, v. 2.0 are satisfied: GNU General Public License as published by\nthe Free Software Foundation, either version 2 of the License, or (at your\noption) any later version, with the GNU Classpath Exception which is available\nat https://www.gnu.org/software/classpath/license.html.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwithjak%2Finspector","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwithjak%2Finspector","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwithjak%2Finspector/lists"}