|
| 1 | +(ns clj-async-profiler.flamebin |
| 2 | + "Code responsible for uploading profiling results to flamebin.dev." |
| 3 | + (:require [clj-async-profiler.post-processing :as proc] |
| 4 | + [clj-async-profiler.results :as results] |
| 5 | + [clojure.java.io :as io] |
| 6 | + [clojure.pprint :refer [cl-format]]) |
| 7 | + (:import (java.net HttpURLConnection URL))) |
| 8 | + |
| 9 | +(def flamebin-host (atom "https://flamebin.dev")) |
| 10 | +#_(def flamebin-host (atom "http://localhost:8086")) |
| 11 | + |
| 12 | +(defn- make-upload-url [type public?] |
| 13 | + (format "%s/api/v1/upload-profile?format=dense-edn&type=%s%s" |
| 14 | + @flamebin-host (name type) |
| 15 | + (if public? "&public=true" ""))) |
| 16 | + |
| 17 | +(defn- gzip-dense-profile [dense-profile] |
| 18 | + (let [baos (java.io.ByteArrayOutputStream.)] |
| 19 | + (with-open [w (io/writer (java.util.zip.GZIPOutputStream. baos))] |
| 20 | + (binding [*print-length* nil |
| 21 | + *print-level* nil |
| 22 | + *out* w] |
| 23 | + (pr dense-profile))) |
| 24 | + (.toByteArray baos))) |
| 25 | + |
| 26 | +(defn- report-successful-upload [stacks-file {:keys [location deletion-link read-token]}] |
| 27 | + (cl-format *out* "~%~%Uploaded ~A to Flamebin.~%Share URL: ~A~%Deletion URL: ~A~ |
| 28 | +~@[~%Private uploads don't show on the index page. Private profiles can only be decrypted ~ |
| 29 | +by providing read-token. The server doesn't store read-token for private uploads.~]" |
| 30 | + (str stacks-file) location deletion-link read-token)) |
| 31 | + |
| 32 | +(defn- upload-dense-profile [dense-profile event public?] |
| 33 | + (let [^bytes gzipped (gzip-dense-profile dense-profile) |
| 34 | + url (URL. (make-upload-url event public?)) |
| 35 | + ^HttpURLConnection connection |
| 36 | + (doto ^HttpURLConnection (.openConnection url) |
| 37 | + (.setDoOutput true) |
| 38 | + (.setRequestMethod "POST") |
| 39 | + (.setRequestProperty "Content-Type" "application/edn") |
| 40 | + (.setRequestProperty "Content-Encoding" "gzip") |
| 41 | + (.setRequestProperty "Content-Length" (str (alength gzipped))))] |
| 42 | + (with-open [output-stream (.getOutputStream connection)] |
| 43 | + (io/copy gzipped output-stream)) |
| 44 | + (let [status (.getResponseCode connection) |
| 45 | + msg (.getResponseMessage connection) |
| 46 | + body (slurp (.getInputStream connection))] |
| 47 | + (if (< status 400) |
| 48 | + (let [location (.getHeaderField connection "Location") |
| 49 | + id (.getHeaderField connection "X-Created-ID") |
| 50 | + read-token (.getHeaderField connection "X-Read-Token") |
| 51 | + edit-token (.getHeaderField connection "X-Edit-Token") |
| 52 | + deletion-link (.getHeaderField connection "X-Deletion-Link")] |
| 53 | + {:location location, :id id, :body body, :message msg, |
| 54 | + :read-token read-token, :edit-token edit-token, :deletion-link deletion-link}) |
| 55 | + (throw (ex-info (str "Failed to upload profile: " msg) |
| 56 | + {:status status, :body body})))))) |
| 57 | + |
| 58 | +(defn upload-to-flamebin |
| 59 | + "Generate flamegraph from a collapsed stacks file, identified either by its file |
| 60 | + path or numerical ID, and upload it to flamebin.dev. Options: |
| 61 | +
|
| 62 | + :public? - if true, flamegraph will be displayed on the main page and publicly |
| 63 | + accessible to everyone; otherwise, will requite a token to view." |
| 64 | + [run-id-or-stacks-file options] |
| 65 | + (let [{:keys [public?]} options |
| 66 | + {:keys [stacks-file event]} (results/find-profile run-id-or-stacks-file) |
| 67 | + dense-profile (proc/read-raw-profile-file-to-dense-profile stacks-file) |
| 68 | + {:keys [location] :as resp} (upload-dense-profile dense-profile event public?)] |
| 69 | + (report-successful-upload stacks-file resp) |
| 70 | + location)) |
| 71 | + |
| 72 | +#_(upload-to-flamebin 1 {:public? true}) |
| 73 | +#_(upload-to-flamebin "../flamebin/test/res/normal.txt" {}) |
0 commit comments