{"id":21834285,"url":"https://github.com/clojure-goes-fast/clj-async-profiler","last_synced_at":"2025-05-15T21:04:33.250Z","repository":{"id":45755463,"uuid":"113863243","full_name":"clojure-goes-fast/clj-async-profiler","owner":"clojure-goes-fast","description":"Embedded high-precision Clojure profiler","archived":false,"fork":false,"pushed_at":"2025-05-07T11:15:09.000Z","size":6167,"stargazers_count":431,"open_issues_count":1,"forks_count":18,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-05-07T11:19:27.060Z","etag":null,"topics":["clojure","flamegraphs","profiling"],"latest_commit_sha":null,"homepage":"http://clojure-goes-fast.com/kb/profiling/clj-async-profiler/","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/clojure-goes-fast.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"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":"2017-12-11T13:37:16.000Z","updated_at":"2025-05-07T11:12:59.000Z","dependencies_parsed_at":"2024-01-29T20:44:11.863Z","dependency_job_id":"7f82de02-26a3-49ab-afa3-38f8cb4db05e","html_url":"https://github.com/clojure-goes-fast/clj-async-profiler","commit_stats":{"total_commits":103,"total_committers":5,"mean_commits":20.6,"dds":0.03883495145631066,"last_synced_commit":"b5a52631d7086e11fa367aa88d0f8bba9c531b92"},"previous_names":[],"tags_count":25,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clojure-goes-fast%2Fclj-async-profiler","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clojure-goes-fast%2Fclj-async-profiler/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clojure-goes-fast%2Fclj-async-profiler/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clojure-goes-fast%2Fclj-async-profiler/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/clojure-goes-fast","download_url":"https://codeload.github.com/clojure-goes-fast/clj-async-profiler/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254422754,"owners_count":22068678,"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","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","flamegraphs","profiling"],"created_at":"2024-11-27T20:09:49.536Z","updated_at":"2025-05-15T21:04:33.230Z","avatar_url":"https://github.com/clojure-goes-fast.png","language":"Clojure","funding_links":[],"categories":["Libraries"],"sub_categories":[],"readme":"# clj-async-profiler [![CircleCI](https://img.shields.io/circleci/build/github/clojure-goes-fast/clj-async-profiler/master.svg)](https://dl.circleci.com/status-badge/redirect/gh/clojure-goes-fast/clj-async-profiler/tree/master) ![](https://img.shields.io/badge/deps-none-teal) [![](https://img.shields.io/clojars/dt/com.clojure-goes-fast/clj-async-profiler?color=teal)](https://clojars.org/com.clojure-goes-fast/clj-async-profiler) ![](https://img.shields.io/github/commit-activity/y/clojure-goes-fast/clj-async-profiler?label=commits\u0026color=blue) [![](https://img.shields.io/badge/-changelog-blue.svg)](CHANGELOG.md)\n\n**clj-async-profiler** is an embedded high-precision performance profiler for\nClojure. No need to install anything on your system — just add a single\ndependency to your project to operate the profiler either programmatically or\nvia a web UI. The profiling overhead is very low, so it can be used even in\nhighly loaded production scenarios.\n\nclj-async-profiler presents the profiling results as an interactive flamegraph.\nYou can navigate the flamegraph, query it, change parameters and adapt the\nresults for easier interpretation.\n\n![](docs/flamegraph-screenshot.png)\n\n\u003cp align = \"center\"\u003e\u003csup\u003e\nExample flamegraph. \u003ca href=\"https://flamebin.dev/Rmxt9P\"\u003eClick\u003c/a\u003e to open the interactive version.\n\u003c/sup\u003e\u003c/p\u003e\n\nTo collect the profiling data, clj-async-profiler utilizes\n[async-profiler](https://github.com/async-profiler/async-profiler) which is\na low overhead sampling profiler for Java. Current version of async-profiler\nthat is used by clj-async-profiler is\n[3.0](https://github.com/async-profiler/async-profiler/releases/tag/v3.0).\n\n## Usage\n\n\u003e [!TIP]\n\u003e Comprehensive usage guide and in-depth documentation are available at [Clojure\nGoes Fast knowledge\nbase](http://clojure-goes-fast.com/kb/profiling/clj-async-profiler/).\n\nclj-async-profiler has the following requirements:\n\n- Linux or MacOS\n- JDK 1.8+. Note that clj-async-profiler requires **JDK**, it won't run on\n  [JRE](https://www.digitalocean.com/community/tutorials/difference-jdk-vs-jre-vs-jvm).\n- Clojure 1.10+\n\nOn Linux, you need to allow async-profiler to use kernel profiling data by\nsetting these two variables ([see\nalso](https://github.com/async-profiler/async-profiler#basic-usage)):\n\n```\nsudo sysctl -w kernel.perf_event_paranoid=1\nsudo sysctl -w kernel.kptr_restrict=0\n```\n\nNext, add `com.clojure-goes-fast/clj-async-profiler` to your dependencies. This\nis the latest version:\n\n[![](https://clojars.org/com.clojure-goes-fast/clj-async-profiler/latest-version.svg)](https://clojars.org/com.clojure-goes-fast/clj-async-profiler)\n\nOn **JDK 11** and later, you must start your application with JVM option\n`-Djdk.attach.allowAttachSelf`, otherwise the profiling agent will not be able\nto dynamically attach to the running process.\n\n- With tools.deps, add `:jvm-opts [\"-Djdk.attach.allowAttachSelf\"]` to an alias\nin `deps.edn` and enable that alias, or add `-J-Djdk.attach.allowAttachSelf`\nexplicitly to your REPL command.\n- With Leiningen, add `:jvm-opts [\"-Djdk.attach.allowAttachSelf\"]` to `project.clj`.\n\n`clj-async-profiler.core` exposes an all-in-one facade for generating profiling\nflame graphs. The most common usage scenario looks like this:\n\n```clojure\n(require '[clj-async-profiler.core :as prof])\n\n;; Profile the following expression:\n(prof/profile (dotimes [i 10000] (reduce + (range i))))\n\n;; The resulting flamegraph will be stored in /tmp/clj-async-profiler/results/\n;; You can view the HTML file directly from there or start a local web UI:\n\n(prof/serve-ui 8080) ; Serve on port 8080\n```\n\nYou can also start and stop the profiler manually with `prof/start` and\n`prof/stop`.\n\nEach profiling command accepts a map of options. See docstrings for each command\nfor the list of supported options, or [Functions and\noptions](http://clojure-goes-fast.com/kb/profiling/clj-async-profiler/basic-usage/#functions-and-options).\n\nOption map for each profiling command can have a `:pid` value. If it is\nprovided, an external JVM process with this PID will be sampled, otherwise the\ncurrent process is targeted.\n\nFor a detailed description of clj-async-profiler's advanced features, see the\ndocumentation pages:\n\n- [Exploring flamegraphs](https://clojure-goes-fast.com/kb/profiling/clj-async-profiler/exploring-flamegraphs/)\n- [Allocation profiling](http://clojure-goes-fast.com/kb/profiling/clj-async-profiler/allocation-profiling/)\n- [Differential flamegraphs](http://clojure-goes-fast.com/kb/profiling/clj-async-profiler/diffgraphs/)\n- [Startup profiling](http://clojure-goes-fast.com/kb/profiling/clj-async-profiler/startup/)\n- [Profiling in production](http://clojure-goes-fast.com/kb/profiling/clj-async-profiler/production/)\n\nAlso check out this video from London Clojurians meetup:\n\n[![Clojure Goes Brrr: a quest for performance](http://img.youtube.com/vi/s3mjVAMNVrA/0.jpg)](http://www.youtube.com/watch?v=s3mjVAMNVrA \"Clojure Goes Brrr: a quest for performance\")\n\n### Sharing flamegraphs\n\nA flamegraph is a fully self-contained HTML file that is saved into\n`/tmp/clj-async-profiler/results/` directory. This file can be sent to somebody\nelse and opened in the browser. It can also be uploaded to any hosting that\nallows serving HTML pages.\n\nclj-async-profiler offers out-of-the-box integration with\n[Flamebin](https://flamebin.dev/) — a website dedicated to storing and sharing\nflamegraphs. You can use it like this:\n\n```clj\n;; Assuming that you have already captured a profile, and its ID=1. ID is the\n;; number that comes after the date in flamegraph's filename. Alternatively, you\n;; can provide the full path to the captured .txt profile.\n\n=\u003e (require '[clj-async-profiler.flamebin :as flamebin])\n=\u003e (flamebin/upload-flamegraph 1 {})\n;; Uploaded /tmp/clj-async-profiler/results/20241120_181404-01-cpu-collapsed.txt to Flamebin.\n;; Share URL: https://flamebin.dev/Y8Gwjh?read-token=xU8wRrZJgXtwkfT0j9\n;; Deletion URL: https://flamebin.dev/api/v1/delete-profile?id=Y8Gwjh\u0026edit-token=...\n;; Private uploads don't show on the index page. Private profiles can only be decrypted by providing read-token. The server doesn't store read-token for private uploads.\n```\n\nThe code above uploads the flamegraph to Flamebin and gives you a link you can\nshare with your colleagues. By default, it creates a **private** Flamegraph, so\na read-token must be provided to view the flamegraph. If you capture a\nflamegraph of an open-source project and plan to share it publicly (e.g.,\nadding it to a bug report/PR), you can opt for a public flamegraph:\n\n```clj\n=\u003e (flamebin/upload-flamegraph 1 {:public? true})\n;; Uploaded /tmp/clj-async-profiler/results/20241120_181404-01-cpu-collapsed.txt to Flamebin.\n;; Share URL: https://flamebin.dev/RibWCt\n;; Deletion URL: https://flamebin.dev/api/v1/delete-profile?id=RibWCt\u0026edit-token=...\n```\n\nNote that the URL is shorter as read-token is no longer present.\n\nIt is also possible to upload diffgraphs using\n`clj-async-profiler.flamebin/upload-diffgraph`. You need to provide two profiles\n(before and after) in the same manner as to `generate-diffgraph`.\n\n### Tuning for better accuracy\n\nFrom [async-profiler\nREADME](https://github.com/async-profiler/async-profiler#restrictionslimitations):\nIt is highly recommended to use `-XX:+UnlockDiagnosticVMOptions\n-XX:+DebugNonSafepoints` JVM flags. Without those flags the profiler will still\nwork correctly but results might be less accurate. Without these options, there\nis a high chance that simple inlined methods will not appear in the profile.\nWhen agent is attached at runtime, CompiledMethodLoad JVMTI event enables debug\ninfo, but only for methods compiled after the event is turned on.\n\nIf you see stackframes like `/usr/lib/.../libjvm.so` in the flamegraph, it means\nthat you have to install JDK debug symbols. E.g., on Ubuntu that would be the\npackage `openjdk-11-dbg`.\n\n### Output directory\n\nBy default, clj-async-profiler writes its output files to\n`/tmp/clj-async-profiler/`. You can change it to a custom directory (e.g., if\nyou run clj-async-profiler in an environment where `/tmp` is not sufficiently\nlarge) by setting Java property `clj-async-profiler.output-dir`:\n\n`clojure -J-Dclj-async-profiler.output-dir=./data ...`\n\n## Troubleshooting\n\n### Error: Can not attach to current VM\n\nMost likely, you are missing `-Djdk.attach.allowAttachSelf` JVM option.\n\n1. Add `-Djdk.attach.allowAttachSelf` to your `deps.edn` or `project.clj` as\n   described in [Usage\n   guide](https://github.com/clojure-goes-fast/clj-async-profiler?tab=readme-ov-file#usage).\n2. Verify that the option is set correctly by running this in the REPL. The\n   command should return `\"\"` (empty string). If it returns `nil`, there is\n   something wrong with how you set the allowAttachSelf option.\n\n    ```clj\n    user=\u003e (System/getProperty \"jdk.attach.allowAttachSelf\")\n    \"\"\n    ```\n\n### WARNING: package jdk.attach not in java.base\n\nYou are trying to run clj-async-profiler with Java JRE. clj-async-profiler can\nonly work under Java **JDK**.\n\n## Platform support\n\nclj-async-profiler ships with the precompiled native libraries that\nasync-profiler itself\n[distributes](https://github.com/async-profiler/async-profiler#download).\nThese include:\n\n- **Linux**: x64, aarch64 (arm64)\n- **MacOS**: x64/aarch64 (universal binary)\n\nTo use clj-async-profiler on other [supported\nplatforms](https://github.com/async-profiler/async-profiler#supported-platforms),\nyou should do the following:\n\n1. Build\n   [async-profiler](https://github.com/async-profiler/async-profiler#building)\n   for the desired platform.\n2. Put the resulting `libasyncProfiler.so` in a place accessible by your JVM\n   process (and which also allows code execution from).\n3. Execute from Clojure:\n\n   ```clj\n   (reset! prof/async-profiler-agent-path \"/path/to/libasyncProfiler.so\")\n   ```\n\n## Development\n\nRegular build tasks are inside [build.clj](build.clj) and invoked as `clojure\n-T:build test`, `clojure -T:build jar`, etc.\n\nWhen starting a REPL, you should add `dev` alias on the list so that\n[virgil](https://github.com/clj-commons/virgil) is loaded. Then, to compile Java\nclasses at the REPL, do:\n\n```clojure\nuser\u003e ((requiring-resolve 'virgil/compile-java) [\"src\"])\n```\n\n## License\n\nclj-async-profiler is distributed under the Eclipse Public License. See\n[ECLIPSE_PUBLIC_LICENSE](docs/ECLIPSE_PUBLIC_LICENSE).\n\nCopyright 2017-2025 Oleksandr Yakushev\n\n---\n\nasync-profiler is distributed under Apache-2.0. See\n[APACHE_PUBLIC_LICENSE](docs/APACHE_PUBLIC_LICENSE) file. The location of the\noriginal repository is\n[https://github.com/async-profiler/async-profiler](https://github.com/async-profiler/async-profiler).\n\nCopyright 2017-2025 Andrei Pangin\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclojure-goes-fast%2Fclj-async-profiler","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fclojure-goes-fast%2Fclj-async-profiler","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclojure-goes-fast%2Fclj-async-profiler/lists"}