Last active
November 30, 2021 22:32
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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>"))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The types of parameters will automatically get coerced from String to the type defined on the actual method parameter of requested MBean's operation.
->parse-fn
are supported. So no Maps, POJO objects etc.Example: