Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

initial commit

  • Loading branch information...
commit d34e9992d6a64a62bfb930978b78155bbfab25d1 0 parents
@stuarthalloway stuarthalloway authored
90 README.md
@@ -0,0 +1,90 @@
+# java.jmx
+
+Produce and consume JMX beans from Clojure.
+
+## Setup
+
+Requires Clojure 1.3 alpha or later.
+
+ (require '[clojure.java.jmx :as jmx])
+
+## Usage
+
+What beans do I have?
+
+ (jmx/mbean-names \"*:*\")
+ -> #<HashSet [java.lang:type=MemoryPool,name=CMS Old Gen,
+ java.lang:type=Memory, ...]
+
+What attributes does a bean have?
+
+ (jmx/attribute-names \"java.lang:type=Memory\")
+ -> (:Verbose :ObjectPendingFinalizationCount
+ :HeapMemoryUsage :NonHeapMemoryUsage)
+
+What is the value of an attribute?
+
+ (jmx/read \"java.lang:type=Memory\" :ObjectPendingFinalizationCount)
+ -> 0
+
+Give me all the attributes:
+
+ (jmx/mbean \"java.lang:type=Memory\")
+ -> {:NonHeapMemoryUsage
+ {:used 16674024, :max 138412032, :init 24317952, :committed 24317952},
+ :HeapMemoryUsage
+ {:used 18619064, :max 85393408, :init 0, :committed 83230720},
+ :ObjectPendingFinalizationCount 0,
+ :Verbose false}
+
+Find an invoke an operation:
+
+ (jmx/operation-names \"java.lang:type=Memory\")
+ -> (:gc)
+ (jmx/invoke \"java.lang:type=Memory\" :gc)
+ -> nil
+
+Conneting to another process? Just run *any* of the above code
+inside a with-connection:
+
+ (jmx/with-connection {:host \"localhost\", :port 3000}
+ (jmx/mbean \"java.lang:type=Memory\"))
+ -> {:ObjectPendingFinalizationCount 0,
+ :HeapMemoryUsage ... etc.}
+
+Serve your own beans. Drop a Clojure ref into create-bean
+to expose read-only attributes for every key/value pair
+in the ref:
+
+ (jmx/register-mbean
+ (create-bean
+ (ref {:string-attribute \"a-string\"}))
+ \"my.namespace:name=Value\")"}
+
+### "Installation"
+
+java.jmx is available in Maven central. Add it to your Maven project's `pom.xml`:
+
+ <dependency>
+ <groupId>org.clojure</groupId>
+ <artifactId>java.jmx</artifactId>
+ <version>0.1.0</version>
+ </dependency>
+
+or your leiningen project.clj:
+
+ [org.clojure/java.jmx "0.1.0"]
+
+### Building
+
+0. Clone the repo
+1. Make sure you have maven installed
+2. Run the maven build: `mvn package` will produce a JAR file in the
+target directory, and run all tests with the most recently-released build
+of Clojure (currently 1.3.0 alpha n).
+
+## License
+
+Copyright © Stuart Halloway
+
+Licensed under the EPL. (See the file epl.html.)
261 epl.html
@@ -0,0 +1,261 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
+<title>Eclipse Public License - Version 1.0</title>
+<style type="text/css">
+ body {
+ size: 8.5in 11.0in;
+ margin: 0.25in 0.5in 0.25in 0.5in;
+ tab-interval: 0.5in;
+ }
+ p {
+ margin-left: auto;
+ margin-top: 0.5em;
+ margin-bottom: 0.5em;
+ }
+ p.list {
+ margin-left: 0.5in;
+ margin-top: 0.05em;
+ margin-bottom: 0.05em;
+ }
+ </style>
+
+</head>
+
+<body lang="EN-US">
+
+<h2>Eclipse Public License - v 1.0</h2>
+
+<p>THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE
+PUBLIC LICENSE (&quot;AGREEMENT&quot;). ANY USE, REPRODUCTION OR
+DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS
+AGREEMENT.</p>
+
+<p><b>1. DEFINITIONS</b></p>
+
+<p>&quot;Contribution&quot; means:</p>
+
+<p class="list">a) in the case of the initial Contributor, the initial
+code and documentation distributed under this Agreement, and</p>
+<p class="list">b) in the case of each subsequent Contributor:</p>
+<p class="list">i) changes to the Program, and</p>
+<p class="list">ii) additions to the Program;</p>
+<p class="list">where such changes and/or additions to the Program
+originate from and are distributed by that particular Contributor. A
+Contribution 'originates' from a Contributor if it was added to the
+Program by such Contributor itself or anyone acting on such
+Contributor's behalf. Contributions do not include additions to the
+Program which: (i) are separate modules of software distributed in
+conjunction with the Program under their own license agreement, and (ii)
+are not derivative works of the Program.</p>
+
+<p>&quot;Contributor&quot; means any person or entity that distributes
+the Program.</p>
+
+<p>&quot;Licensed Patents&quot; mean patent claims licensable by a
+Contributor which are necessarily infringed by the use or sale of its
+Contribution alone or when combined with the Program.</p>
+
+<p>&quot;Program&quot; means the Contributions distributed in accordance
+with this Agreement.</p>
+
+<p>&quot;Recipient&quot; means anyone who receives the Program under
+this Agreement, including all Contributors.</p>
+
+<p><b>2. GRANT OF RIGHTS</b></p>
+
+<p class="list">a) Subject to the terms of this Agreement, each
+Contributor hereby grants Recipient a non-exclusive, worldwide,
+royalty-free copyright license to reproduce, prepare derivative works
+of, publicly display, publicly perform, distribute and sublicense the
+Contribution of such Contributor, if any, and such derivative works, in
+source code and object code form.</p>
+
+<p class="list">b) Subject to the terms of this Agreement, each
+Contributor hereby grants Recipient a non-exclusive, worldwide,
+royalty-free patent license under Licensed Patents to make, use, sell,
+offer to sell, import and otherwise transfer the Contribution of such
+Contributor, if any, in source code and object code form. This patent
+license shall apply to the combination of the Contribution and the
+Program if, at the time the Contribution is added by the Contributor,
+such addition of the Contribution causes such combination to be covered
+by the Licensed Patents. The patent license shall not apply to any other
+combinations which include the Contribution. No hardware per se is
+licensed hereunder.</p>
+
+<p class="list">c) Recipient understands that although each Contributor
+grants the licenses to its Contributions set forth herein, no assurances
+are provided by any Contributor that the Program does not infringe the
+patent or other intellectual property rights of any other entity. Each
+Contributor disclaims any liability to Recipient for claims brought by
+any other entity based on infringement of intellectual property rights
+or otherwise. As a condition to exercising the rights and licenses
+granted hereunder, each Recipient hereby assumes sole responsibility to
+secure any other intellectual property rights needed, if any. For
+example, if a third party patent license is required to allow Recipient
+to distribute the Program, it is Recipient's responsibility to acquire
+that license before distributing the Program.</p>
+
+<p class="list">d) Each Contributor represents that to its knowledge it
+has sufficient copyright rights in its Contribution, if any, to grant
+the copyright license set forth in this Agreement.</p>
+
+<p><b>3. REQUIREMENTS</b></p>
+
+<p>A Contributor may choose to distribute the Program in object code
+form under its own license agreement, provided that:</p>
+
+<p class="list">a) it complies with the terms and conditions of this
+Agreement; and</p>
+
+<p class="list">b) its license agreement:</p>
+
+<p class="list">i) effectively disclaims on behalf of all Contributors
+all warranties and conditions, express and implied, including warranties
+or conditions of title and non-infringement, and implied warranties or
+conditions of merchantability and fitness for a particular purpose;</p>
+
+<p class="list">ii) effectively excludes on behalf of all Contributors
+all liability for damages, including direct, indirect, special,
+incidental and consequential damages, such as lost profits;</p>
+
+<p class="list">iii) states that any provisions which differ from this
+Agreement are offered by that Contributor alone and not by any other
+party; and</p>
+
+<p class="list">iv) states that source code for the Program is available
+from such Contributor, and informs licensees how to obtain it in a
+reasonable manner on or through a medium customarily used for software
+exchange.</p>
+
+<p>When the Program is made available in source code form:</p>
+
+<p class="list">a) it must be made available under this Agreement; and</p>
+
+<p class="list">b) a copy of this Agreement must be included with each
+copy of the Program.</p>
+
+<p>Contributors may not remove or alter any copyright notices contained
+within the Program.</p>
+
+<p>Each Contributor must identify itself as the originator of its
+Contribution, if any, in a manner that reasonably allows subsequent
+Recipients to identify the originator of the Contribution.</p>
+
+<p><b>4. COMMERCIAL DISTRIBUTION</b></p>
+
+<p>Commercial distributors of software may accept certain
+responsibilities with respect to end users, business partners and the
+like. While this license is intended to facilitate the commercial use of
+the Program, the Contributor who includes the Program in a commercial
+product offering should do so in a manner which does not create
+potential liability for other Contributors. Therefore, if a Contributor
+includes the Program in a commercial product offering, such Contributor
+(&quot;Commercial Contributor&quot;) hereby agrees to defend and
+indemnify every other Contributor (&quot;Indemnified Contributor&quot;)
+against any losses, damages and costs (collectively &quot;Losses&quot;)
+arising from claims, lawsuits and other legal actions brought by a third
+party against the Indemnified Contributor to the extent caused by the
+acts or omissions of such Commercial Contributor in connection with its
+distribution of the Program in a commercial product offering. The
+obligations in this section do not apply to any claims or Losses
+relating to any actual or alleged intellectual property infringement. In
+order to qualify, an Indemnified Contributor must: a) promptly notify
+the Commercial Contributor in writing of such claim, and b) allow the
+Commercial Contributor to control, and cooperate with the Commercial
+Contributor in, the defense and any related settlement negotiations. The
+Indemnified Contributor may participate in any such claim at its own
+expense.</p>
+
+<p>For example, a Contributor might include the Program in a commercial
+product offering, Product X. That Contributor is then a Commercial
+Contributor. If that Commercial Contributor then makes performance
+claims, or offers warranties related to Product X, those performance
+claims and warranties are such Commercial Contributor's responsibility
+alone. Under this section, the Commercial Contributor would have to
+defend claims against the other Contributors related to those
+performance claims and warranties, and if a court requires any other
+Contributor to pay any damages as a result, the Commercial Contributor
+must pay those damages.</p>
+
+<p><b>5. NO WARRANTY</b></p>
+
+<p>EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS
+PROVIDED ON AN &quot;AS IS&quot; BASIS, WITHOUT WARRANTIES OR CONDITIONS
+OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION,
+ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY
+OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely
+responsible for determining the appropriateness of using and
+distributing the Program and assumes all risks associated with its
+exercise of rights under this Agreement , including but not limited to
+the risks and costs of program errors, compliance with applicable laws,
+damage to or loss of data, programs or equipment, and unavailability or
+interruption of operations.</p>
+
+<p><b>6. DISCLAIMER OF LIABILITY</b></p>
+
+<p>EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT
+NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING
+WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR
+DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.</p>
+
+<p><b>7. GENERAL</b></p>
+
+<p>If any provision of this Agreement is invalid or unenforceable under
+applicable law, it shall not affect the validity or enforceability of
+the remainder of the terms of this Agreement, and without further action
+by the parties hereto, such provision shall be reformed to the minimum
+extent necessary to make such provision valid and enforceable.</p>
+
+<p>If Recipient institutes patent litigation against any entity
+(including a cross-claim or counterclaim in a lawsuit) alleging that the
+Program itself (excluding combinations of the Program with other
+software or hardware) infringes such Recipient's patent(s), then such
+Recipient's rights granted under Section 2(b) shall terminate as of the
+date such litigation is filed.</p>
+
+<p>All Recipient's rights under this Agreement shall terminate if it
+fails to comply with any of the material terms or conditions of this
+Agreement and does not cure such failure in a reasonable period of time
+after becoming aware of such noncompliance. If all Recipient's rights
+under this Agreement terminate, Recipient agrees to cease use and
+distribution of the Program as soon as reasonably practicable. However,
+Recipient's obligations under this Agreement and any licenses granted by
+Recipient relating to the Program shall continue and survive.</p>
+
+<p>Everyone is permitted to copy and distribute copies of this
+Agreement, but in order to avoid inconsistency the Agreement is
+copyrighted and may only be modified in the following manner. The
+Agreement Steward reserves the right to publish new versions (including
+revisions) of this Agreement from time to time. No one other than the
+Agreement Steward has the right to modify this Agreement. The Eclipse
+Foundation is the initial Agreement Steward. The Eclipse Foundation may
+assign the responsibility to serve as the Agreement Steward to a
+suitable separate entity. Each new version of the Agreement will be
+given a distinguishing version number. The Program (including
+Contributions) may always be distributed subject to the version of the
+Agreement under which it was received. In addition, after a new version
+of the Agreement is published, Contributor may elect to distribute the
+Program (including its Contributions) under the new version. Except as
+expressly stated in Sections 2(a) and 2(b) above, Recipient receives no
+rights or licenses to the intellectual property of any Contributor under
+this Agreement, whether expressly, by implication, estoppel or
+otherwise. All rights in the Program not expressly granted under this
+Agreement are reserved.</p>
+
+<p>This Agreement is governed by the laws of the State of New York and
+the intellectual property laws of the United States of America. No party
+to this Agreement will bring a legal action under this Agreement more
+than one year after the cause of action arose. Each party waives its
+rights to a jury trial in any resulting litigation.</p>
+
+</body>
+
+</html>
24 pom.xml
@@ -0,0 +1,24 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>tools.logging</artifactId>
+ <version>0.0.9-SNAPSHOT</version>
+ <name>${artifactId}</name>
+
+ <parent>
+ <groupId>org.clojure</groupId>
+ <artifactId>pom.contrib</artifactId>
+ <version>0.0.20</version>
+ </parent>
+
+ <developers>
+ <developer>
+ <name>Stuart Halloway</name>
+ </developer>
+ </developers>
+
+ <scm>
+ <connection>scm:git:git@github.com:clojure/java.jmx.git</connection>
+ <developerConnection>scm:git:git@github.com:clojure/java.jmx.git</developerConnection>
+ <url>git@github.com:clojure/java.jmx.git</url>
+ </scm>
+</project>
295 src/main/clojure/clojure/java/jmx.clj
@@ -0,0 +1,295 @@
+;; Copyright (c) Stuart Halloway. All rights reserved. The use
+;; and distribution terms for this software are covered by the Eclipse
+;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
+;; which can be found in the file epl-v10.html at the root of this
+;; distribution. By using this software in any fashion, you are
+;; agreeing to be bound by the terms of this license. You must not
+;; remove this notice, or any other, from this software.
+
+
+(ns ^{:author "Stuart Halloway"
+ :doc "JMX support for Clojure
+
+ Usage
+ (require '[clojure.java.jmx :as jmx])
+
+ What beans do I have?
+
+ (jmx/mbean-names \"*:*\")
+ -> #<HashSet [java.lang:type=MemoryPool,name=CMS Old Gen,
+ java.lang:type=Memory, ...]
+
+ What attributes does a bean have?
+
+ (jmx/attribute-names \"java.lang:type=Memory\")
+ -> (:Verbose :ObjectPendingFinalizationCount
+ :HeapMemoryUsage :NonHeapMemoryUsage)
+
+ What is the value of an attribute?
+
+ (jmx/read \"java.lang:type=Memory\" :ObjectPendingFinalizationCount)
+ -> 0
+
+ Can't I just have *all* the attributes in a Clojure map?
+
+ (jmx/mbean \"java.lang:type=Memory\")
+ -> {:NonHeapMemoryUsage
+ {:used 16674024, :max 138412032, :init 24317952, :committed 24317952},
+ :HeapMemoryUsage
+ {:used 18619064, :max 85393408, :init 0, :committed 83230720},
+ :ObjectPendingFinalizationCount 0,
+ :Verbose false}
+
+ Can I find and invoke an operation?
+
+ (jmx/operation-names \"java.lang:type=Memory\")
+ -> (:gc)
+ (jmx/invoke \"java.lang:type=Memory\" :gc)
+ -> nil
+
+ What about some other process? Just run *any* of the above code
+ inside a with-connection:
+
+ (jmx/with-connection {:host \"localhost\", :port 3000}
+ (jmx/mbean \"java.lang:type=Memory\"))
+ -> {:ObjectPendingFinalizationCount 0,
+ :HeapMemoryUsage ... etc.}
+
+ Can I serve my own beans? Sure, just drop a Clojure ref
+ into an instance of clojure.java.jmx.Bean, and the bean
+ will expose read-only attributes for every key/value pair
+ in the ref:
+
+ (jmx/register-mbean
+ (create-bean
+ (ref {:string-attribute \"a-string\"}))
+ \"my.namespace:name=Value\")"}
+ clojure.java.jmx
+ (:refer-clojure :exclude [read])
+ (:use [clojure.walk :only [postwalk]])
+ (:import [clojure.lang Associative]
+ java.lang.management.ManagementFactory
+ [javax.management Attribute AttributeList DynamicMBean MBeanInfo
+ ObjectName RuntimeMBeanException MBeanAttributeInfo]
+ [javax.management.remote JMXConnectorFactory JMXServiceURL]))
+
+(def ^:dynamic *connection*
+ "The connection to be used for JMX ops. Defaults to the local process."
+ (ManagementFactory/getPlatformMBeanServer))
+
+(declare jmx->clj)
+
+(defn jmx-url
+ "Build a JMX URL from options."
+ ([] (jmx-url {}))
+ ([overrides]
+ (let [opts (merge {:host "localhost", :port "3000", :jndi-path "jmxrmi"} overrides)]
+ (format "service:jmx:rmi:///jndi/rmi://%s:%s/%s" (opts :host) (opts :port) (opts :jndi-path)))))
+
+(defmulti as-object-name
+ "Interpret an object as a JMX ObjectName."
+ { :arglists '([string-or-name]) }
+ class)
+(defmethod as-object-name String [n] (ObjectName. n))
+(defmethod as-object-name ObjectName [n] n)
+
+(defn composite-data->map [cd]
+ (into {}
+ (map (fn [attr] [(keyword attr) (jmx->clj (.get cd attr))])
+ (.. cd getCompositeType keySet))))
+
+(defn maybe-keywordize
+ "Convert a string key to a keyword, leaving other types alone. Used to
+ simplify keys in the tabular data API."
+ [s]
+ (if (string? s) (keyword s) s))
+
+(defn maybe-atomize
+ "Convert a list of length 1 into its contents, leaving other things alone.
+ Used to simplify keys in the tabular data API."
+ [k]
+ (if (and (instance? java.util.List k)
+ (= 1 (count k)))
+ (first k)
+ k))
+
+(def simplify-tabular-data-key
+ (comp maybe-keywordize maybe-atomize))
+
+(defn tabular-data->map [td]
+ (into {}
+ ; the need for into-array here was a surprise, and may not
+ ; work for all examples. Are keys always arrays?
+ (map (fn [k]
+ [(simplify-tabular-data-key k) (jmx->clj (.get td (into-array k)))])
+ (.keySet td))))
+
+(defmulti jmx->clj
+ "Coerce JMX data structures into Clojure data.
+ Handles CompositeData, TabularData, maps, and atoms."
+ { :argslists '([jmx-data-structure]) }
+ (fn [x]
+ (cond
+ (instance? javax.management.openmbean.CompositeData x) :composite
+ (instance? javax.management.openmbean.TabularData x) :tabular
+ (instance? clojure.lang.Associative x) :map
+ :default :default)))
+(defmethod jmx->clj :composite [c] (composite-data->map c))
+(defmethod jmx->clj :tabular [t] (tabular-data->map t))
+(defmethod jmx->clj :map [m] (into {} (zipmap (keys m) (map jmx->clj (vals m)))))
+(defmethod jmx->clj :default [obj] obj)
+
+(def guess-attribute-map
+ {"java.lang.Integer" "int"
+ "java.lang.Boolean" "boolean"
+ "java.lang.Long" "long"
+ })
+
+(defn guess-attribute-typename
+ "Guess the attribute typename for MBeanAttributeInfo based on the attribute value."
+ [value]
+ (let [classname (.getName (class value))]
+ (get guess-attribute-map classname classname)))
+
+(defn build-attribute-info
+ "Construct an MBeanAttributeInfo. Normally called with a key/value pair from a Clojure map."
+ ([attr-name attr-value]
+ (build-attribute-info
+ (name attr-name)
+ (guess-attribute-typename attr-value)
+ (name attr-name) true false false))
+ ([name type desc readable? writable? is?] (MBeanAttributeInfo. name type desc readable? writable? is? )))
+
+(defn map->attribute-infos
+ "Construct an MBeanAttributeInfo[] from a Clojure associative."
+ [attr-map]
+ (into-array (map (fn [[attr-name value]] (build-attribute-info attr-name value))
+ attr-map)))
+
+(defmacro with-connection
+ "Execute body with JMX connection specified by opts. opts can also
+ include an optional :environment key which is passed as the
+ environment arg to JMXConnectorFactory/connect."
+ [opts & body]
+ `(let [opts# ~opts
+ env# (get opts# :environment {})
+ opts# (dissoc opts# :environment)]
+ (with-open [connector# (javax.management.remote.JMXConnectorFactory/connect
+ (JMXServiceURL. (jmx-url opts#)) env#)]
+ (binding [*connection* (.getMBeanServerConnection connector#)]
+ ~@body))))
+
+(defn mbean-info [n]
+ (.getMBeanInfo *connection* (as-object-name n)))
+
+(defn raw-read
+ "Read an mbean property. Returns low-level Java object model for
+ composites, tabulars, etc. Most callers should use read."
+ [n attr]
+ (.getAttribute *connection* (as-object-name n) (name attr)))
+
+(def read
+ "Read an mbean property."
+ (comp jmx->clj raw-read))
+
+(defn read-supported
+ "Calls read to read an mbean property, *returning* unsupported
+ operation exceptions instead of throwing them. Used to keep mbean
+ from blowing up. Note: There is no good exception that aggregates
+ unsupported operations, hence the overly-general catch block."
+ [n attr]
+ (try
+ (read n attr)
+ (catch Exception e
+ e)))
+
+(defn write! [n attr value]
+ (.setAttribute
+ *connection*
+ (as-object-name n)
+ (Attribute. (name attr) value)))
+
+(defn attribute-info
+ "Get the MBeanAttributeInfo for an attribute."
+ [object-name attr-name]
+ (filter #(= (name attr-name) (.getName %))
+ (.getAttributes (mbean-info object-name))))
+
+(defn readable?
+ "Is attribute readable?"
+ [n attr]
+ (.isReadable (mbean-info n)))
+
+(defn operations
+ "All oeprations available on an MBean."
+ [n]
+ (.getOperations (mbean-info n)))
+
+(defn operation
+ "The MBeanOperationInfo for operation op on mbean n. Used by invoke."
+ [n op]
+ (first (filter #(= (-> % .getName keyword) op) (operations n))))
+
+(defn op-param-types
+ "The parameter types (as class name strings) for operation op on n.
+ Used for invoke."
+ [n op]
+ (map #(-> % .getType) (.getSignature (operation n op))))
+
+(defn register-mbean [mbean mbean-name]
+ (.registerMBean *connection* mbean (as-object-name mbean-name)))
+
+(defn mbean-names
+ "Finds all MBeans matching a name on the current *connection*."
+ [n]
+ (.queryNames *connection* (as-object-name n) nil))
+
+(defn attribute-names
+ "All attribute names available on an MBean."
+ [n]
+ (doall (map #(-> % .getName keyword)
+ (.getAttributes (mbean-info n)))))
+
+(defn operation-names
+ "All operation names available on an MBean."
+ [n]
+ (doall (map #(-> % .getName keyword) (operations n))))
+
+(defn invoke [n op & args]
+ (if ( seq args)
+ (.invoke *connection* (as-object-name n) (name op)
+ (into-array args)
+ (into-array String (op-param-types n op)))
+ (.invoke *connection* (as-object-name n) (name op)
+ nil nil)))
+
+(defn mbean
+ "Like clojure.core/bean, but for JMX beans. Returns a read-only map of
+ a JMX bean's attributes. If an attribute it not supported, value is
+ set to the exception thrown."
+ [n]
+ (into {} (map (fn [attr-name] [(keyword attr-name) (read-supported n attr-name)])
+ (attribute-names n))))
+
+(deftype Bean [state-ref]
+ DynamicMBean
+ (getMBeanInfo [_]
+ (MBeanInfo. (.. _ getClass getName) ; class name
+ "Clojure Dynamic MBean" ; description
+ (map->attribute-infos @state-ref) ; attributes
+ nil ; constructors
+ nil ; operations
+ nil))
+ (getAttribute [_ attr]
+ (@state-ref (keyword attr)))
+ (getAttributes [_ attrs]
+ (let [result (AttributeList.)]
+ (doseq [attr attrs]
+ (.add result (.getAttribute _ attr)))
+ result)))
+
+(defn create-bean
+ "Expose a reference as a JMX bean. state-ref should be a Clojure
+ reference (ref, atom, agent) containing a map."
+ [state-ref]
+ (Bean. state-ref))
178 src/test/clojure/clojure/java/test_jmx.clj
@@ -0,0 +1,178 @@
+;; Tests for JMX support for Clojure
+
+;; by Stuart Halloway
+
+;; Copyright (c) Stuart Halloway. All rights reserved. The use
+;; and distribution terms for this software are covered by the Eclipse
+;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
+;; which can be found in the file epl-v10.html at the root of this
+;; distribution. By using this software in any fashion, you are
+;; agreeing to be bound by the terms of this license. You must not
+;; remove this notice, or any other, from this software.
+
+(ns clojure.java.test-jmx
+ (:import javax.management.openmbean.CompositeDataSupport
+ [javax.management MBeanAttributeInfo AttributeList]
+ [java.util.logging LogManager Logger])
+ (:use clojure.test)
+ (:require [clojure.java [jmx :as jmx]]))
+
+
+(defn =set [a b]
+ (= (set a) (set b)))
+
+(defn seq-contains-all?
+ "Does container contain every item in containee?
+ Not fast. Testing use only"
+ [container containee]
+ (let [container (set container)]
+ (every? #(contains? container %) containee)))
+
+(deftest finding-mbeans
+ (testing "as-object-name"
+ (are [cname object-name]
+ (= cname (.getCanonicalName object-name))
+ "java.lang:type=Memory" (jmx/as-object-name "java.lang:type=Memory")))
+ (testing "mbean-names"
+ (are [cnames object-name]
+ (= cnames (map #(.getCanonicalName %) object-name))
+ ["java.lang:type=Memory"] (jmx/mbean-names "java.lang:type=Memory"))))
+
+; These actual beans may differ on different JVM platforms.
+; Tested April 2010 to work on Sun and IBM JDKs.
+(deftest testing-actual-beans
+ (testing "reflecting on capabilities"
+ (are [attr-list mbean-name]
+ (seq-contains-all? (jmx/attribute-names mbean-name) attr-list)
+ [:Verbose :ObjectPendingFinalizationCount :HeapMemoryUsage :NonHeapMemoryUsage] "java.lang:type=Memory")
+ (are [op-list mbean-name]
+ (seq-contains-all? (jmx/operation-names mbean-name) op-list)
+ [:gc] "java.lang:type=Memory"))
+ (testing "mbean-from-oname"
+ (are [key-names oname]
+ (seq-contains-all? (keys (jmx/mbean oname)) key-names)
+ [:Verbose :ObjectPendingFinalizationCount :HeapMemoryUsage :NonHeapMemoryUsage] "java.lang:type=Memory")))
+
+(deftest raw-reading-attributes
+ (let [mem "java.lang:type=Memory"
+ log "java.util.logging:type=Logging"]
+ (testing "simple scalar attributes"
+ (are [a b] (= a b)
+ false (jmx/raw-read mem :Verbose))
+ (are [type attr] (instance? type attr)
+ Number (jmx/raw-read mem :ObjectPendingFinalizationCount)))))
+
+(deftest reading-attributes
+ (testing "simple scalar attributes"
+ (are [type attr] (instance? type attr)
+ Number (jmx/read "java.lang:type=Memory" :ObjectPendingFinalizationCount)))
+ (testing "composite attributes"
+ (are [ks attr] (=set ks (keys attr))
+ [:used :max :init :committed] (jmx/read "java.lang:type=Memory" :HeapMemoryUsage)))
+ (testing "tabular attributes"
+ (is (map? (jmx/read "java.lang:type=Runtime" :SystemProperties)))))
+
+(deftest writing-attributes
+ (let [mem "java.lang:type=Memory"]
+ (jmx/write! mem :Verbose true)
+ ;; need boolean cast to fix Boolean object identity error
+ (is (true? (boolean (jmx/raw-read mem :Verbose))))
+ (jmx/write! mem :Verbose false)))
+
+(deftest test-invoke-operations
+ (testing "without arguments"
+ (jmx/invoke "java.lang:type=Memory" :gc))
+ (testing "with arguments"
+ (.addLogger (LogManager/getLogManager) (Logger/getLogger "clojure.java.test_jmx"))
+ (jmx/invoke "java.util.logging:type=Logging" :setLoggerLevel "clojure.java.test_jmx" "WARNING")))
+
+(deftest test-jmx->clj
+ (testing "it works recursively on maps"
+ (let [some-map {:foo (jmx/raw-read "java.lang:type=Memory" :HeapMemoryUsage)}]
+ (is (map? (:foo (jmx/jmx->clj some-map))))))
+ (testing "it leaves everything else untouched"
+ (is (= "foo" (jmx/jmx->clj "foo")))))
+
+
+(deftest test-composite-data->map
+ (let [data (jmx/raw-read "java.lang:type=Memory" :HeapMemoryUsage)
+ prox (jmx/composite-data->map data)]
+ (testing "returns a map with keyword keys"
+ (is (= (set [:committed :init :max :used]) (set (keys prox)))))))
+
+(deftest test-tabular-data->map
+ (let [raw-props (jmx/raw-read "java.lang:type=Runtime" :SystemProperties)
+ props (jmx/tabular-data->map raw-props)]
+ (are [k] (contains? props k)
+ :java.class.path
+ :path.separator)))
+
+(deftest test-creating-attribute-infos
+ (let [infos (jmx/map->attribute-infos [[:a 1] [:b 2]])
+ info (first infos)]
+ (testing "generates the right class"
+ (is (= (class (into-array MBeanAttributeInfo [])) (class infos))))
+ (testing "generates the right instance data"
+ (are [result expr] (= result expr)
+ "a" (.getName info)
+ "a" (.getDescription info)))))
+
+(deftest various-beans-are-readable
+ (testing "that all java.lang beans can be read without error"
+ (doseq [mb (jmx/mbean-names "*:*")]
+ (is (map? (jmx/mbean mb)) mb))))
+
+(deftest test-jmx-url
+ (testing "creates default url"
+ (is (= "service:jmx:rmi:///jndi/rmi://localhost:3000/jmxrmi"
+ (jmx/jmx-url))))
+ (testing "creates custom url"
+ (is (= "service:jmx:rmi:///jndi/rmi://example.com:4000/jmxrmi"
+ (jmx/jmx-url {:host "example.com" :port 4000}))))
+ (testing "creates custom jndi path"
+ (is (= "service:jmx:rmi:///jndi/rmi://example.com:4000/jmxconnector"
+ (jmx/jmx-url {:host "example.com" :port 4000 :jndi-path "jmxconnector"})))))
+
+;; ----------------------------------------------------------------------
+;; tests for clojure.java.jmx.Bean.
+
+(deftest dynamic-mbean-from-compiled-class
+ (let [mbean-name "clojure.java.test_jmx:name=Foo"]
+ (jmx/register-mbean
+ (jmx/create-bean
+ (ref {:string-attribute "a-string"}))
+ mbean-name)
+ (are [result expr] (= result expr)
+ "a-string" (jmx/read mbean-name :string-attribute)
+ {:string-attribute "a-string"} (jmx/mbean mbean-name)
+ )))
+
+(deftest test-getAttribute
+ (doseq [reftype [ref atom agent]]
+ (let [state (reftype {:a 1 :b 2})
+ bean (jmx/create-bean state)]
+ (testing (str "accessing values from a " (class state))
+ (are [result expr] (= result expr)
+ 1 (.getAttribute bean "a"))))))
+
+(deftest test-bean-info
+ (let [state (ref {:a 1 :b 2})
+ bean (jmx/create-bean state)
+ info (.getMBeanInfo bean)]
+ (testing "accessing info"
+ (are [result expr] (= result expr)
+ "clojure.java.jmx.Bean" (.getClassName info)))))
+
+(deftest test-getAttributes
+ (let [bean (jmx/create-bean (ref {:r 5 :d 4}))
+ atts (.getAttributes bean (into-array ["r" "d"]))]
+ (are [x y] (= x y)
+ AttributeList (class atts)
+ [5 4] (seq atts))))
+
+(deftest test-guess-attribute-typename
+ (are [x y] (= x (jmx/guess-attribute-typename y))
+; "long" 10
+ "boolean" false
+ "java.lang.String" "foo"
+ "long" (Long/valueOf (long 10))))
Please sign in to comment.
Something went wrong with that request. Please try again.