Skip to content

Instantly share code, notes, and snippets.

@brdloush
Last active November 30, 2021 22:32
Show Gist options
  • Save brdloush/5bb334180aec0bd47550643b655578cd to your computer and use it in GitHub Desktop.
Save brdloush/5bb334180aec0bd47550643b655578cd to your computer and use it in GitHub Desktop.
Clojure CLI utility for simple execution of operations via JMX of local java process
#!/bin/sh
#_(
DEPS='
{:deps {org.clojure/java.jmx {:mvn/version,"1.0.0"}}}
'
exec clojure $OPTS -Sdeps "$DEPS" -M "$0" "$@"
)
(ns user
(:require [clojure.java.jmx :as jmx]
[clojure.string :as str])
(:import [javax.management.remote JMXServiceURL]))
(defn find-mbean-by-name [s]
(->> (jmx/mbean-names "*:*")
(map str)
(filter #(str/includes? % (str "name=" s)))
first))
(defmacro with-local-pid-connection
[pid & body]
`(let [vm# (com.sun.tools.attach.VirtualMachine/attach (str ~pid))
url# (.startLocalManagementAgent vm#)]
(with-open [connector# (javax.management.remote.JMXConnectorFactory/connect
(JMXServiceURL. url#)
{})]
(binding [jmx/*connection* (.getMBeanServerConnection connector#)]
~@body))))
(defn add-system-classpath
"Add an url path to the system class loader"
[url-string]
(let [field (aget (.getDeclaredFields java.net.URLClassLoader) 0)]
(.setAccessible field true)
(let [ucp (.get field (ClassLoader/getSystemClassLoader))]
(.addURL ucp (java.net.URL. url-string)))))
(try (Class/forName "com.sun.tools.attach.VirtualMachine")
(catch ClassNotFoundException e
(add-system-classpath (format "file:///%s/../lib/tools.jar" (System/getProperty "java.home")))
(Class/forName "com.sun.tools.attach.VirtualMachine")))
(defn ->parse-fn [kw]
(case kw
:int #(Integer/parseInt %)
:short #(Short/valueOf %)
:long #(Long/parseLong %)
:byte #(Byte/valueOf %)
:float #(Float/valueOf %)
:double #(Double/valueOf %)
:char #(Character/valueOf %)
:boolean #(Boolean/valueOf %)
identity))
(let [[pid jmx-bean-name operation & op-params] *command-line-args*]
(if (and pid jmx-bean-name operation)
(try
(println (format "Connecting to JMX of process PID=%s.." pid))
(with-local-pid-connection pid
(if-let [jmx-mbean (find-mbean-by-name jmx-bean-name)]
(do (println (format "Triggering jmx bean=%s, operation=%s, args=%s" jmx-mbean operation op-params))
(if-let [op-params-parse-fns
(some->> (.getOperations (jmx/mbean-info jmx-mbean))
(filter #(= operation (.getName %)))
first
.getSignature
(mapv #(-> % .getType keyword ->parse-fn)))]
(let [params
(->> (interleave op-params op-params-parse-fns)
(partition 2)
(mapv (fn [[param parse-fn]] (parse-fn param))))
result (apply (partial jmx/invoke jmx-mbean (keyword operation)) params)]
(println (format "Execution finished. Result: %s" result))
result)
(println (format "No operation '%s' found" operation))))
(println (format "JMX bean %s not found" jmx-bean-name))))
(catch Throwable t
(println (format "ERROR: %s (%s)" (-> t .getClass .getSimpleName) (or (.getMessage t) t)))))
(println "Usage: triggerJmx.clj <pid> <mbean-name> <operation-name> <param1> <param2> ... <paramX>")))
@brdloush
Copy link
Author

The types of parameters will automatically get coerced from String to the type defined on the actual method parameter of requested MBean's operation.

  • known limitation #1: overloaded methods will not work properly (1st method will always be looked up)
  • known limitation #2: only types defined in ->parse-fn are supported. So no Maps, POJO objects etc.

Example:

PID=`jps | grep MyApplication | awk '{print $1}'`
triggerJmx $PID someMBean someOperation param1 param2  

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment