diff --git a/dev/src/dev/memory.clj b/dev/src/dev/memory.clj
new file mode 100644
index 0000000000000000000000000000000000000000..fbd44235d73f65447f4cb864eac626b63879b4f6
--- /dev/null
+++ b/dev/src/dev/memory.clj
@@ -0,0 +1,52 @@
+(ns dev.memory
+  (:require
+   [metabase.util.log :as log])
+  (:import
+   (java.lang.management ManagementFactory)))
+
+;; measurement requires reflection as we don't know the concrete class
+;; we can't using `binding` for this variable as it needs to be set at
+;; compile time.
+(set! *warn-on-reflection* false)
+
+(defn- get-thread-allocated-bytes
+  "Measure the cumulative bytes allocated by a thread, ignoring whether its been collected."
+  []
+  (let [mx-bean   (ManagementFactory/getThreadMXBean)
+        ;; `getId`` is deprecated, we will need to update to `threadId` at some point.
+        thread-id (.getId (Thread/currentThread))]
+    ;; this method is only defined for some platform implementations.
+    (assert (.isThreadAllocatedMemorySupported mx-bean))
+    (.getThreadAllocatedBytes mx-bean thread-id)))
+
+(set! *warn-on-reflection* true)
+
+(defn mb-str
+  "Format bytes as megabytes"
+  [bytes]
+  (format "%.3f MB" (/ (double bytes) 1024 1024)))
+
+(defn measure-thread-allocations
+  "Measure the number of bytes allocated when calling the given function."
+  [f]
+  (let [before (get-thread-allocated-bytes)
+        result (f)
+        after  (get-thread-allocated-bytes)]
+    {:result      result
+     :allocations (- after before)}))
+
+(defmacro measuring-thread-allocations
+  "Measure the number of bytes allocated when evaluating the given body."
+  [& body]
+  `(let [m# (measure-thread-allocations #(do ~@body))]
+     (log/warnf "Allocated: %s" (mb-str (:allocations m#)))
+     (:result m#)))
+
+(comment
+  ;; almost correct, at least a constant error
+  (measuring-thread-allocations
+   (byte-array (* 1024 1024)))
+  ;; => 1.028MB
+  ;; => 1.028MB
+  ;; => 1.028MB
+  )