tobias-jmx 0.8

Sign up to get free protection for your applications and to get access to all the features.
data/.gemtest ADDED
File without changes
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Thomas E Enebo <enebo@acm.org>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Manifest.txt ADDED
@@ -0,0 +1,14 @@
1
+ Manifest.txt
2
+ Rakefile
3
+ README.md
4
+ LICENSE.txt
5
+ lib/jmx
6
+ lib/jmx.rb
7
+ lib/rmi.rb
8
+ lib/jmx/dynamic_mbean.rb
9
+ lib/jmx/server.rb
10
+ lib/jmx/version.rb
11
+ samples/memory.rb
12
+ test/jmx_attribute_test.rb
13
+ test/jmx_client_test.rb
14
+ test/jmx_server_test.rb
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # JMX
2
+
3
+ ## DESCRIPTION:
4
+
5
+ JMX is a library which allows you to access JMX MBeans as a client or create
6
+ your own MBeans as a Ruby class.
7
+
8
+ ## FEATURES/PROBLEMS:
9
+
10
+ * Use '-J-Dcom.sun.management.jmxremote' to make jruby process accessible from a jruby command-line
11
+
12
+ ## SYNOPSIS:
13
+
14
+ Connect to same JVM as client script and look at Memory MBean
15
+
16
+ require 'jmx'
17
+
18
+ client = JMX.simple_connect(:port => 9999)
19
+
20
+ memory = client["java.lang:type=Memory"]
21
+ puts memory.attributes
22
+
23
+ ## REQUIREMENTS:
24
+
25
+ * JRuby
26
+
27
+ ## INSTALL:
28
+
29
+ * jruby -S gem install jmx
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ MANIFEST = FileList["Manifest.txt", "Rakefile", "README.md", "LICENSE.txt", "lib/**/*", "samples/*","test/**/*"]
2
+
3
+ file "Manifest.txt" => :manifest
4
+ task :manifest do
5
+ File.open("Manifest.txt", "w") {|f| MANIFEST.each {|n| f << "#{n}\n"} }
6
+ end
7
+ Rake::Task['manifest'].invoke # Always regen manifest, so Hoe has up-to-date list of files
8
+
9
+ $LOAD_PATH << 'lib'
10
+ require 'jmx/version'
11
+ begin
12
+ require 'hoe'
13
+ Hoe.new("tobias-jmx", JMX::VERSION) do |p|
14
+ p.rubyforge_name = "jruby-extras"
15
+ p.url = "http://github.com/enebo/jmxjr"
16
+ p.author = "Thomas Enebo & Jay McGaffigan"
17
+ p.email = "tom.enebo@gmail.com"
18
+ p.summary = "Package for interacting/creating Java Management Extensions"
19
+ p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
20
+ p.description = "Install this gem and require 'jmx' to load the library."
21
+ end.spec.dependencies.delete_if { |dep| dep.name == "hoe" }
22
+ rescue LoadError
23
+ puts "You need Hoe installed to be able to package this gem"
24
+ rescue => e
25
+ p e.backtrace
26
+ puts "ignoring error while loading hoe: #{e.to_s}"
27
+ end
data/lib/jmx.rb ADDED
@@ -0,0 +1,218 @@
1
+ include Java
2
+
3
+ require 'rmi'
4
+ require 'jmx/dynamic_mbean'
5
+ require 'jmx/server'
6
+
7
+ java_import java.util.ArrayList
8
+ java_import javax.management.Attribute
9
+ java_import javax.management.MBeanInfo
10
+ java_import javax.management.DynamicMBean
11
+
12
+ module JMX
13
+ java_import javax.management.ObjectName
14
+ class ObjectName
15
+ def [](key)
16
+ get_key_property(key.to_s)
17
+ end
18
+
19
+ def info(server)
20
+ server.getMBeanInfo(self)
21
+ end
22
+ end
23
+ end
24
+
25
+ module javax::management::openmbean::CompositeData
26
+ include Enumerable
27
+
28
+ def [](key)
29
+ get(key.to_s)
30
+ end
31
+
32
+ def method_missing(name, *args)
33
+ self[name]
34
+ end
35
+
36
+ def each
37
+ get_composite_type.key_set.each { |key| yield key }
38
+ end
39
+
40
+ def each_pair
41
+ get_composite_type.key_set.each { |key| yield key, get(key) }
42
+ end
43
+ end
44
+
45
+ module JMX
46
+ ##
47
+ # Connect to a MBeanServer
48
+ # opts can contain several values
49
+ # :host - The hostname where the server resides (def: localhost)
50
+ # :port - Which port the server resides at (def: 8686)
51
+ # :url_path - path part of JMXServerURL (def: /jmxrmi)
52
+ # :user - User to connect as (optional)
53
+ # :password - Password for user (optional)
54
+ def self.connect(opts = {})
55
+ host = opts[:host] || 'localhost'
56
+ port = opts[:port] || 8686
57
+ url_path = opts[:url_path] || "/jmxrmi"
58
+ url = "service:jmx:rmi:///jndi/rmi://#{host}:#{port}#{url_path}"
59
+
60
+ if opts[:user]
61
+ JMX::MBeanServer.new url, opts[:user], opts[:password]
62
+ else
63
+ JMX::MBeanServer.new url
64
+ end
65
+ end
66
+
67
+ ##
68
+ # sad little simple server setup so you can connect up to it.
69
+ #
70
+ def self.simple_server(opts = {})
71
+ port = opts[:port] || 8686
72
+ url_path = opts[:url_path] || "/jmxrmi"
73
+ url = "service:jmx:rmi:///jndi/rmi://localhost:#{port}#{url_path}"
74
+ $registry = RMIRegistry.new port
75
+ @connector = JMX::MBeanServerConnector.new(url, JMX::MBeanServer.new).start
76
+ end
77
+
78
+ # Holder for beans created from retrieval (namespace protection [tm]).
79
+ # This also gives MBeans nicer names when inspected
80
+ module MBeans
81
+ ##
82
+ # Create modules in this namespace for each package in the Java fully
83
+ # qualified name and return the deepest module along with the Java class
84
+ # name back to the caller.
85
+ def self.parent_for(java_class_fqn)
86
+ java_class_fqn.split(".").inject(MBeans) do |parent, segment|
87
+ # Note: We are boned if java class name is lower cased
88
+ return [parent, segment] if segment =~ /^[A-Z]/
89
+
90
+ segment.capitalize!
91
+ unless parent.const_defined? segment
92
+ parent.const_set segment, Module.new
93
+ else
94
+ parent.const_get segment
95
+ end
96
+ end
97
+
98
+ end
99
+ end
100
+
101
+ # Create a Ruby proxy based on the MBean represented by the object_name
102
+ class MBeanProxy
103
+ # Generate a friendly Ruby proxy for the MBean represented by object_name
104
+ def self.generate(server, object_name)
105
+ parent, class_name = MBeans.parent_for object_name.info(server).class_name
106
+
107
+ if parent.const_defined? class_name
108
+ proxy = parent.const_get(class_name)
109
+ else
110
+ proxy = Class.new MBeanProxy
111
+ parent.const_set class_name, proxy
112
+ end
113
+
114
+ proxy.new(server, object_name)
115
+ end
116
+
117
+ def initialize(server, object_name)
118
+ @server, @object_name = server, object_name
119
+ @info = @server.getMBeanInfo(@object_name)
120
+
121
+ define_attributes
122
+ define_operations
123
+ end
124
+
125
+ def attributes
126
+ @attributes ||= @info.attributes.inject([]) { |s,attr| s << attr.name }
127
+ end
128
+
129
+ def operations
130
+ @operations ||= @info.operations.inject([]) { |s,op| s << op.name }
131
+ end
132
+
133
+ # Get MBean attribute specified by name. If it is just a plain attribute then
134
+ # unwrap the attribute and just return the value.
135
+ def [](name)
136
+ attribute = @server.getAttribute(@object_name, name.to_s)
137
+ return attribute.value if attribute.kind_of? javax.management.Attribute
138
+ attribute
139
+ end
140
+
141
+ # Set MBean attribute specified by name to value
142
+ def []=(name, value)
143
+ @server.setAttribute @object_name, javax.management.Attribute.new(name.to_s, value)
144
+ end
145
+
146
+ def add_notification_listener(filter=nil, handback=nil, &listener)
147
+ @server.addNotificationListener @object_name, listener, filter, handback
148
+ end
149
+
150
+ def remove_notification_listener(listener)
151
+ @server.removeNotificationListener @object_name, listener
152
+ end
153
+
154
+ private
155
+
156
+ def safe_define_method(method_name, &block)
157
+ method_name = "mbean_#{method_name}" if respond_to?(method_name) || method_name == 'initialize'
158
+ self.class.__send__(:define_method, method_name, &block)
159
+ end
160
+
161
+ # Define ruby friendly methods for attributes. For odd attribute names or names
162
+ # that you want to call with the actual attribute name you can call aref/aset
163
+ def define_attributes
164
+ @info.attributes.each do |attr|
165
+ rname = underscore(attr.name)
166
+ safe_define_method(rname) { self[attr.name] } if attr.readable?
167
+ safe_define_method(rname + "=") {|v| self[attr.name] = v } if attr.writable?
168
+ end
169
+ end
170
+
171
+ def define_operations
172
+ @info.operations.each do |op|
173
+ safe_define_method(op.name) do |*args|
174
+ jargs, jtypes = java_args(op.signature, args)
175
+ @server.invoke @object_name, op.name, jargs, jtypes
176
+ end
177
+ end
178
+ end
179
+
180
+ # Given the signature and the parameters supplied do these signatures match.
181
+ # Repackage these parameters as Java objects in a primitive object array.
182
+ def java_args(signature, params)
183
+ return nil if params.nil?
184
+
185
+ jtypes = []
186
+ jargs = []
187
+ params.each_with_index do |param, i|
188
+ type = signature[i].get_type
189
+ jtypes << type
190
+ required_type = JavaClass.for_name(type)
191
+
192
+ java_arg = param.to_java(:object)
193
+
194
+ if (param.kind_of? Array)
195
+ java_arg = param.inject(ArrayList.new) {|l, element| l << element }
196
+ end
197
+
198
+ jargs << java_arg
199
+
200
+ arg_type = java_arg.java_class
201
+
202
+ raise TypeError.new("parameter #{signature[i].name} expected to be #{required_type}, but was #{arg_type}") if !required_type.assignable_from? arg_type
203
+ end
204
+ [jargs.to_java, jtypes.to_java(:string)]
205
+ end
206
+
207
+ # Convert a collection of java objects to their Java class name equivalents
208
+ def java_types(params)
209
+ return nil if params.nil?
210
+
211
+ params.map {|e| e.class.java_class.name }.to_java(:string)
212
+ end
213
+
214
+ def underscore(string)
215
+ string.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,270 @@
1
+ module JMX
2
+ java_import javax.management.MBeanParameterInfo
3
+ java_import javax.management.MBeanOperationInfo
4
+ java_import javax.management.MBeanAttributeInfo
5
+ java_import javax.management.MBeanInfo
6
+
7
+ # Module that is used to bridge java to ruby and ruby to java types.
8
+ module JavaTypeAware
9
+ # Current list of types we understand If it's not in this list we are
10
+ # assuming that we are going to convert to a java.object
11
+ SIMPLE_TYPES = {
12
+ :int => ['java.lang.Integer', lambda {|param| param.to_i}],
13
+ :list => ['java.util.List', lambda {|param| param.to_a}],
14
+ :long => ['java.lang.Long', lambda {|param| param.to_i}],
15
+ :float => ['java.lang.Float', lambda {|param| param.to_f}],
16
+ :map => ['java.util.Map', lambda {|param| param}],
17
+ :set => ['java.util.Set', lambda {|param| param}],
18
+ :string => ['java.lang.String', lambda {|param| param.to_s}],
19
+ :void => ['java.lang.Void', lambda {|param| nil}]
20
+ }
21
+
22
+ def to_java_type(type_name)
23
+ SIMPLE_TYPES[type_name][0] || type_name
24
+ end
25
+
26
+ #TODO: I'm not sure this is strictly needed, but funky things can happen if you
27
+ # are expecting your attributes (from the ruby side) to be ruby types and they are java types.
28
+ def to_ruby(type_name)
29
+ SIMPLE_TYPES[type_name][1] || lambda {|param| param}
30
+ end
31
+
32
+ module_function :to_java_type, :to_ruby
33
+ end
34
+
35
+ class Parameter
36
+ include JavaTypeAware
37
+
38
+ def initialize(type, name, description)
39
+ @type, @name, @description = type, name, description
40
+ end
41
+
42
+ def to_jmx
43
+ MBeanParameterInfo.new @name.to_s, to_java_type(@type), @description
44
+ end
45
+ end
46
+
47
+ class Operation < Struct.new(:description, :parameters, :return_type, :name, :impact)
48
+ include JavaTypeAware
49
+
50
+ def initialize(description)
51
+ super
52
+ self.parameters, self.impact, self.description = [], MBeanOperationInfo::UNKNOWN, description
53
+ end
54
+
55
+ def to_jmx
56
+ java_parameters = parameters.map { |parameter| parameter.to_jmx }
57
+ MBeanOperationInfo.new name.to_s, description, java_parameters.to_java(javax.management.MBeanParameterInfo), to_java_type(return_type), impact
58
+ end
59
+ end
60
+
61
+ class Attribute < Struct.new(:name, :type, :description, :is_reader, :is_writer, :is_iser)
62
+ include JavaTypeAware
63
+
64
+ def initialize(name, type, description, is_rdr, is_wrtr)
65
+ super
66
+ self.description, self.type, self.name = description, type, name
67
+ self.is_reader,self.is_writer, self.is_iser = is_rdr, is_wrtr, false
68
+ end
69
+
70
+ def to_jmx
71
+ MBeanAttributeInfo.new(name.to_s, to_java_type(type), description, is_reader, is_writer, is_iser)
72
+ end
73
+ end
74
+ end
75
+
76
+ # The Ruby-Java JMX utilities work throughout the DynamicMBean concept. Creators of Ruby based MBeans must inherit this
77
+ # class (<tt>RubyDynamicMBean</tt>) in their own bean classes and then register them with a JMX mbean server.
78
+ # Here is an example:
79
+ # class MyMBean < RuybDynamicMBean
80
+ # rw_attribute :status, :string, "Status information for this process"
81
+ #
82
+ # operation "Shutdown this process"
83
+ # parameter :string, "user_name", "Name of user requesting shutdown"
84
+ # returns :string
85
+ # def shutdown(user_name)
86
+ # "shutdown requests more time"
87
+ # end
88
+ # end
89
+ # Once you have defined your bean class you can start declaring attributes and operations.
90
+ # Attributes come in three flavors: read, write, and read write. Simmilar to the <tt>attr*</tt>
91
+ # helpers, there are helpers that are used to create management attributes. Use +r_attribute+,
92
+ # +w_attribute+, and +rw_attribute+ to declare attributes, and the +operation+, +returns+,
93
+ # and +parameter+ helpers to define a management operation.
94
+ # Creating attributes with the *_attribute convention ALSO creates ruby accessors
95
+ # (it invokes the attr_accessor/attr_reader/attr_writer ruby helpers) to create ruby methods
96
+ # like: user_name= and username. So in your ruby code you can treat the attributes
97
+ # as "regular" ruby accessors
98
+ class RubyDynamicMBean
99
+ java_import javax.management.AttributeList
100
+ java_import javax.management.MBeanOperationInfo
101
+ java_import javax.management.MBeanAttributeInfo
102
+ java_import javax.management.MBeanInfo
103
+ include JMX::JavaTypeAware
104
+
105
+ def self.inherited(cls)
106
+ cls.send(:include, javax.management.DynamicMBean)
107
+ end
108
+
109
+ # TODO: preserve any original method_added?
110
+ # TODO: Error handling here when it all goes wrong?
111
+ def self.method_added(name) #:nodoc:
112
+ return if metadata[:op].nil?
113
+ metadata[:op].name = name
114
+ operations << metadata[:op].to_jmx
115
+ metadata[:op] = nil
116
+ end
117
+
118
+ # All attributes in this class
119
+ def self.attributes
120
+ metadata[:attrs] ||= []
121
+ end
122
+
123
+ # All attributes up the inheritance chain
124
+ def self.all_attributes
125
+ ancestors.inject([]) do |sum, clazz|
126
+ sum.concat(clazz.attributes) if clazz.respond_to? :attributes
127
+ sum
128
+ end
129
+ end
130
+
131
+ # All operations up the inheritance chain
132
+ def self.all_operations
133
+ ancestors.inject([]) do |sum, clazz|
134
+ sum.concat(clazz.operations) if clazz.respond_to? :operations
135
+ sum
136
+ end
137
+ end
138
+
139
+ # All operations in this class
140
+ def self.operations
141
+ metadata[:ops] ||= []
142
+ end
143
+
144
+ def self.define_getter(name, type, reader)
145
+ # FIXME: Our to_java_type needs to do something saner
146
+ java_type = begin; to_java_type(type); rescue; nil; end
147
+ value_proc = java_type ? proc { |value| java_type.new value } : proc { |value| Java.ruby_to_java value }
148
+
149
+ reader = name.to_s unless reader
150
+ attr_reader reader unless instance_methods.include?(reader)
151
+ define_method("jmx_get_#{name.downcase}") do
152
+ javax.management.Attribute.new name, value_proc.call(__send__(reader))
153
+ end
154
+ end
155
+
156
+ def self.define_setter(name, type, writer)
157
+ value_converter = JMX::JavaTypeAware.to_ruby(type)
158
+
159
+ writer = name.to_s + '=' unless writer
160
+ attr_writer name.to_s unless instance_methods.include?(writer)
161
+ define_method("jmx_set_#{name.downcase}") do |value|
162
+ __send__ writer, value_converter.call(value)
163
+ end
164
+ end
165
+
166
+ # the <tt>rw_attribute</tt> method is used to declare a JMX read write attribute. See the +JavaSimpleTypes+
167
+ # module for more information about acceptable types usage:
168
+ # rw_attribute :attribute_name, :string, "Description displayed in a JMX console"
169
+ def self.rw_attribute(name, type, description, reader=nil, writer=nil)
170
+ name = name.to_s
171
+ attributes << JMX::Attribute.new(name, type, description, true, true).to_jmx
172
+ define_getter name, type, reader
173
+ define_setter name, type, writer
174
+ end
175
+
176
+ # the <tt>r_attribute</tt> method is used to declare a JMX read only attribute. See the +JavaSimpleTypes+
177
+ # module for more information about acceptable types usage:
178
+ # r_attribute :attribute_name, :string, "Description displayed in a JMX console"
179
+ def self.r_attribute(name, type, description, reader=nil)
180
+ name = name.to_s
181
+ attributes << JMX::Attribute.new(name, type, description, true, false).to_jmx
182
+ define_getter name, type, reader
183
+ end
184
+
185
+ # the <tt>w_attribute</tt> method is used to declare a JMX write only attribute. See the +JavaSimpleTypes+
186
+ # module for more information about acceptable types usage:
187
+ # w_attribute :attribute_name, :string, "Description displayed in a JMX console"
188
+ def self.w_attribute(name, type, description, writer=nil)
189
+ name = name.to_s
190
+ attributes << JMX::Attribute.new(name, type, description, false, true).to_jmx
191
+ define_setter name, type, writer
192
+ end
193
+
194
+ # Use the operation method to declare the start of an operation
195
+ # It takes as an argument the description for the operation
196
+ # operation "Used to start the service"
197
+ # def start
198
+ # end
199
+ #--
200
+ # Last operation wins if more than one
201
+ #++
202
+ def self.operation(description)
203
+ # Wait to error check until method_added so we can know method name
204
+ metadata[:op] = JMX::Operation.new description
205
+ end
206
+
207
+ # Used to declare a parameter (you can declare more than one in succession) that
208
+ # is associated with the currently declared operation.
209
+ # operation "Used to update the name of a service"
210
+ # parameter :string, "name", "Set the new name of the service"
211
+ # def start
212
+ # end
213
+ def self.parameter(type, name=nil, description=nil)
214
+ metadata[:op].parameters << JMX::Parameter.new(type, name, description)
215
+ end
216
+
217
+ # Used to declare the return type of the operation
218
+ # operation "Used to update the name of a service"
219
+ # parameter :string, "name", "Set the new name of the service"
220
+ # returns :void
221
+ # def set_name
222
+ # end
223
+ def self.returns(type)
224
+ metadata[:op].return_type = type
225
+ end
226
+
227
+ # Thread local storage for the derived bean
228
+ def self.metadata
229
+ @@metadata ||= {}
230
+ @@metadata[object_id] ||= {}
231
+ end
232
+
233
+ # when creating a dynamic MBean we need to provide it with a name and a description.
234
+ def initialize(name, description)
235
+ operations = self.class.all_operations.to_java MBeanOperationInfo
236
+ attributes = self.class.all_attributes.to_java MBeanAttributeInfo
237
+ @info = MBeanInfo.new name, description, attributes, nil, operations, nil
238
+ end
239
+
240
+ # Retrieve the value of the requested attribute (where attribute is a javax.management.Attribute class)
241
+ def getAttribute(attribute)
242
+ __send__("jmx_get_" + attribute.downcase)
243
+ end
244
+
245
+ def getAttributes(attributes)
246
+ attributes.inject(AttributeList.new) { |attrs, attribute| attrs.add(getAttribute(attribute)); attrs }
247
+ end
248
+
249
+ def getMBeanInfo
250
+ @info
251
+ end
252
+
253
+ def invoke(actionName, params=nil, signature=nil)
254
+ __send__(actionName, *params)
255
+ end
256
+
257
+ def setAttribute(attribute)
258
+ __send__("jmx_set_#{attribute.name.downcase}", attribute.value)
259
+ end
260
+
261
+ def setAttributes(attributes)
262
+ attributes.each { |attribute| setAttribute attribute}
263
+ end
264
+
265
+ def toString
266
+ "#@info.class_name: #@info.description"
267
+ end
268
+ alias_method :to_s, :toString
269
+ alias_method :inspect, :toString
270
+ end
data/lib/jmx/server.rb ADDED
@@ -0,0 +1,131 @@
1
+ module JMX
2
+ # The MBeanServer represents a connection to an MBean server
3
+ # rather than an actual MBean server. Depending upon how
4
+ # this object is constructed you can either talk to the
5
+ # PlatformMBeanServer or any "remote" MBean server.
6
+ #--
7
+ # Represents both MBeanServer and MBeanServerConnection
8
+ #++
9
+ class MBeanServer
10
+ java_import javax.management.Attribute
11
+ java_import javax.management.MBeanServerFactory
12
+ java_import javax.management.remote.JMXConnectorFactory
13
+ java_import javax.management.remote.JMXServiceURL
14
+
15
+ attr_accessor :server
16
+ @@classes = {}
17
+
18
+ # when creatinga new MBeanServer you can optionally specify a location, username, and password
19
+ # if specify these values (or at least the location) the MBeanServer instance will connect to
20
+ # an existing (and remote ) MBean server and register the mbeans there.
21
+ # otherwise the server will connect to to the local Platform MBean Server.
22
+ def initialize(location=nil, username=nil, password=nil)
23
+ if (location)
24
+ env = username ?
25
+ {"jmx.remote.credentials" => [username, password].to_java(:string)} :
26
+ nil
27
+ url = JMXServiceURL.new location
28
+ @server = JMXConnectorFactory.connect(url, env).getMBeanServerConnection
29
+ else
30
+ @server = java.lang.management.ManagementFactory.getPlatformMBeanServer
31
+ #@server = MBeanServerFactory.createMBeanServer
32
+ end
33
+ end
34
+
35
+ def [](object_name)
36
+ name = make_object_name object_name
37
+
38
+ unless @server.isRegistered(name)
39
+ raise NoSuchBeanError.new("No name: #{object_name}")
40
+ end
41
+
42
+ #### TODO: Why?
43
+ @server.getObjectInstance name
44
+ MBeanProxy.generate(@server, name)
45
+ end
46
+
47
+ def []=(class_name, object_name)
48
+ name = make_object_name object_name
49
+
50
+ @server.createMBean class_name, name, nil, nil
51
+
52
+ MBeanProxy.generate(@server, name)
53
+ end
54
+
55
+ def default_domain
56
+ @server.getDefaultDomain
57
+ end
58
+
59
+ def domains
60
+ @server.domains
61
+ end
62
+
63
+ def mbean_count
64
+ @server.getMBeanCount
65
+ end
66
+
67
+ def query_names(name=nil, query=nil)
68
+ object_name = name.nil? ? nil : make_object_name(name)
69
+
70
+ @server.query_names(object_name, query)
71
+ end
72
+
73
+ def unregister_mbean(object_name)
74
+ name = make_object_name object_name
75
+ @server.unregisterMBean(name)
76
+
77
+ end
78
+
79
+ def register_mbean(object, object_name)
80
+ name = make_object_name object_name
81
+ @server.registerMBean(object, name)
82
+ MBeanProxy.generate(@server, name)
83
+ end
84
+
85
+ def self.find(agent_id=nil)
86
+ MBeanServerFactory.findMBeanServer(agent_id)
87
+ end
88
+
89
+ private
90
+
91
+ def make_object_name(object_name)
92
+ return object_name if object_name.kind_of? ObjectName
93
+
94
+ ObjectName.new object_name
95
+ rescue
96
+ raise ArgumentError.new("Invalid ObjectName #{$!.message}")
97
+ end
98
+ end
99
+
100
+ class NoSuchBeanError < RuntimeError
101
+ end
102
+
103
+ class MBeanServerConnector
104
+ java_import javax.management.remote.JMXServiceURL
105
+ java_import javax.management.remote.JMXConnectorServerFactory
106
+
107
+ def initialize(location, server)
108
+ @url = JMXServiceURL.new location
109
+ @server = JMXConnectorServerFactory.newJMXConnectorServer @url, nil, server.server
110
+
111
+ if block_given?
112
+ start
113
+ yield
114
+ stop
115
+ end
116
+ end
117
+
118
+ def active?
119
+ @server.isActive
120
+ end
121
+
122
+ def start
123
+ @server.start
124
+ self
125
+ end
126
+
127
+ def stop
128
+ @server.stop if active?
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,3 @@
1
+ module JMX
2
+ VERSION = "0.8"
3
+ end
data/lib/rmi.rb ADDED
@@ -0,0 +1,21 @@
1
+ include Java
2
+
3
+ java_import java.rmi.registry.LocateRegistry
4
+ java_import java.rmi.registry.Registry
5
+ java_import java.rmi.server.UnicastRemoteObject
6
+
7
+ class RMIRegistry
8
+ def initialize(port = Registry::REGISTRY_PORT)
9
+ start(port)
10
+ end
11
+
12
+ def start(port)
13
+ @registry = LocateRegistry.createRegistry port
14
+
15
+ end
16
+
17
+ def stop
18
+ UnicastRemoteObject.unexportObject @registry, true
19
+ end
20
+ end
21
+
data/samples/memory.rb ADDED
@@ -0,0 +1,29 @@
1
+ require 'jmx'
2
+
3
+ def in_mb(value)
4
+ format "%0.2f Mb" % (value.to_f / (1024 * 1024))
5
+ end
6
+
7
+ server = JMX.simple_server
8
+ client = JMX.connect
9
+ memory = client["java.lang:type=Memory"]
10
+
11
+ Thread.new do
12
+ puts "Enter 'gc' to garbage collect or anything else to quit"
13
+ while (command = gets.chomp)
14
+ break if command != "gc"
15
+ memory.gc
16
+ end
17
+
18
+ server.stop
19
+ exit 0
20
+ end
21
+
22
+ while (true)
23
+ heap = in_mb(memory.heap_memory_usage.used)
24
+ non_heap = in_mb(memory.non_heap_memory_usage.used)
25
+
26
+ puts "Heap: #{heap}, Non-Heap: #{non_heap}"
27
+ sleep(2)
28
+ end
29
+
@@ -0,0 +1,82 @@
1
+ $:.unshift File.join(File.dirname(__FILE__),'..','lib')
2
+
3
+ require 'test/unit'
4
+ require 'jmx'
5
+
6
+ # These tests are for verifying that at a Ruby-level (on server-side) it is still possible
7
+ # to interact with dynamic mbeans as you expect. *_attributes are backed by ordinary
8
+ # Ruby instance variables of the same name.
9
+
10
+ class MyAttributeDynamicBean < RubyDynamicMBean
11
+ rw_attribute :name, :string, "My sample attribute"
12
+ r_attribute :number_read_only, :int, "My sample integer based attribute that is read only"
13
+ w_attribute :number_write_only, :int, "My sample integer based attribute that is write only"
14
+
15
+ # Give us a way to change the attribute for testing
16
+ def set_number_read_only(value)
17
+ @number_read_only = value
18
+ end
19
+
20
+ def fetch_number_write_only
21
+ @number_write_only
22
+ end
23
+
24
+ def my_reader
25
+ 42
26
+ end
27
+ end
28
+
29
+
30
+ class JMXAttributeTest < Test::Unit::TestCase
31
+
32
+ def setup
33
+ @madb = MyAttributeDynamicBean.new("test.MyTestBean","Mwahahahahahah")
34
+ end
35
+
36
+ #make sure we didn't break anything from a ruby perspective
37
+ def test_can_create_bean_and_access_accessor_type_methods
38
+ @madb.set_number_read_only 4
39
+ assert_nil(@madb.name)
40
+ @madb.name = "Name"
41
+ assert_equal("Name", @madb.name)
42
+ assert_equal(4, @madb.number_read_only)
43
+ @madb.number_write_only = 4
44
+ assert_equal(4, @madb.fetch_number_write_only)
45
+ assert_raise(NoMethodError) { @madb.number_write_only }
46
+ end
47
+
48
+ def test_get_attributes_via_dynamicmbeaninterface
49
+ @madb.set_number_read_only 4
50
+ @madb.name = "Name"
51
+
52
+ assert_equal(@madb.name, @madb.getAttribute("name").get_value.to_s)
53
+ assert_equal(@madb.number_read_only, @madb.getAttribute("number_read_only").get_value)
54
+ atts = ["name", "number_read_only"]
55
+ retrieved = @madb.getAttributes(atts)
56
+ assert_equal(2, retrieved.length)
57
+ #TODO: assertion comparing the types in teh array to java types
58
+ end
59
+
60
+ def test_set_attributes_via_dynamicbeaninterface
61
+ @madb.name = "blue"
62
+ red = java.lang.String.new("red")
63
+ attribute = javax.management.Attribute.new("name", red)
64
+ @madb.setAttribute(attribute)
65
+
66
+ assert_equal("String", @madb.name.class.to_s )
67
+ assert_equal("red", @madb.name)
68
+ end
69
+
70
+ def test_set_multiple_attributes_via_dynamicbeaninterface
71
+ @madb.name = "blue"
72
+ three = java.lang.Integer.new(3)
73
+ red = java.lang.String.new("red")
74
+ attribute1 = javax.management.Attribute.new("name", red)
75
+ attribute2 = javax.management.Attribute.new("number_write_only", three)
76
+
77
+ @madb.setAttributes([attribute1, attribute2])
78
+ assert_equal("red", @madb.name)
79
+ assert_equal(3, @madb.fetch_number_write_only)
80
+ end
81
+ end
82
+
@@ -0,0 +1,86 @@
1
+ $:.unshift File.join(File.dirname(__FILE__),'..','lib')
2
+
3
+ require 'test/unit'
4
+ require 'rmi'
5
+ require 'jmx'
6
+
7
+ PORT = 9999
8
+ $registry = RMIRegistry.new PORT
9
+
10
+ class JMXConnectorClientTest < Test::Unit::TestCase
11
+ URL = "service:jmx:rmi:///jndi/rmi://localhost:#{PORT}/jmxrmi"
12
+
13
+ def setup
14
+ @connector = JMX::MBeanServerConnector.new(URL, JMX::MBeanServer.new)
15
+ @connector.start
16
+ @client = JMX::connect(:port => PORT)
17
+ end
18
+
19
+ def teardown
20
+ @connector.stop
21
+ end
22
+
23
+ def test_invalid_mbean_name
24
+ assert_raises(ArgumentError) { @client["::::::"] }
25
+ end
26
+
27
+ def test_get_mbean
28
+ memory = @client["java.lang:type=Memory"]
29
+
30
+ assert_not_nil memory, "Could not acquire memory mbean"
31
+
32
+ # Attr form
33
+ heap = memory[:HeapMemoryUsage]
34
+ assert_not_nil heap
35
+ assert(heap[:used] > 0, "No heap used? Impossible!")
36
+
37
+ # underscored form
38
+ heap = memory.heap_memory_usage
39
+ assert_not_nil heap
40
+ assert(heap.used > 0, "No heap used? Impossible!")
41
+ end
42
+
43
+ def test_set_mbean
44
+ memory = @client["java.lang:type=Memory"]
45
+ original_verbose = memory.verbose
46
+ memory.verbose = !original_verbose
47
+ assert(memory.verbose != original_verbose, "Could not change verbose")
48
+
49
+ memory[:Verbose] = original_verbose
50
+ assert(memory[:Verbose] == original_verbose, "Could not change back verbose")
51
+ end
52
+
53
+ def test_attributes
54
+ memory = @client["java.lang:type=Memory"]
55
+ assert(memory.attributes.include?("HeapMemoryUsage"), "HeapMemoryUsage not found")
56
+ end
57
+
58
+ def test_operations
59
+ memory = @client["java.lang:type=Memory"]
60
+ assert(memory.operations.include?("gc"), "gc not found")
61
+ end
62
+
63
+
64
+ def test_simple_operation
65
+ memory = @client["java.lang:type=Memory"]
66
+
67
+ heap1 = memory[:HeapMemoryUsage][:used]
68
+ memory.gc
69
+ heap2 = memory[:HeapMemoryUsage][:used]
70
+
71
+ assert(heap1.to_i >= heap2.to_i, "GC did not collect")
72
+ end
73
+
74
+ def test_query_names
75
+ names = @client.query_names("java.lang:type=MemoryPool,*")
76
+ assert(names.size > 0, "No memory pools. Impossible!")
77
+
78
+ a_memory_pool_bean = @client[names.to_array[0]]
79
+ assert_not_nil a_memory_pool_bean, "Name must resolve to something"
80
+
81
+ usage = a_memory_pool_bean[:Usage]
82
+ assert_not_nil usage, "Memory pools have usage"
83
+
84
+ assert_not_nil usage[:used], "Some memory is used"
85
+ end
86
+ end
@@ -0,0 +1,129 @@
1
+
2
+ $:.unshift File.join(File.dirname(__FILE__),'..','lib')
3
+
4
+ require 'test/unit'
5
+ require 'rmi'
6
+ require 'jmx'
7
+
8
+ class MyDynamicMBean < RubyDynamicMBean
9
+ rw_attribute :name, :string, "My sample attribute"
10
+ r_attribute :explicit_reader, :int, "Sample int with writer", :my_reader
11
+ w_attribute :explicit_writer, :int, "Sample int with writer", :my_writer
12
+ rw_attribute :explicit_both, :int, "Sample int with writer", :my_read, :my_write
13
+
14
+ operation "Doubles a value"
15
+ parameter :int, "a", "Value to double"
16
+ returns :int
17
+ def double(a)
18
+ a + a
19
+ end
20
+
21
+ operation "Doubles a string"
22
+ parameter :string, "a", "Value to double"
23
+ returns :string
24
+ def string_double(a)
25
+ a + a
26
+ end
27
+
28
+ operation "Give me foo"
29
+ returns :string
30
+ def foo
31
+ "foo"
32
+ end
33
+
34
+ operation "Concatentates a list"
35
+ parameter :list, "list", "List to concatenate"
36
+ returns :string
37
+ def concat(list)
38
+ list.inject("") { |memo, element| memo << element.to_s }
39
+ end
40
+
41
+ def my_reader
42
+ 42
43
+ end
44
+
45
+ def my_writer(value)
46
+ @name = value.to_s
47
+ end
48
+
49
+ def my_read
50
+ @frogger
51
+ end
52
+
53
+ def my_write(value)
54
+ @frogger = value
55
+ end
56
+ end
57
+
58
+ class MyExtendedDynamicMBean < MyDynamicMBean
59
+ operation "Triples a value"
60
+ parameter :int, "a", "Value to triple"
61
+ returns :int
62
+ def triple(a)
63
+ a + a + a
64
+ end
65
+ end
66
+
67
+ class JMXServerTest < Test::Unit::TestCase
68
+ PORT = 9999
69
+ URL = "service:jmx:rmi:///jndi/rmi://localhost:#{PORT}/jmxrmi"
70
+
71
+ def setup
72
+ @registry = RMIRegistry.new PORT
73
+ @server = JMX::MBeanServer.new
74
+ @connector = JMX::MBeanServerConnector.new(URL, @server)
75
+ @connector.start
76
+ @client = JMX::connect(:port => PORT)
77
+ reg_mbean
78
+ end
79
+
80
+ def reg_mbean
81
+ dyna = MyDynamicMBean.new("domain.MySuperBean", "Heh")
82
+ @domain = @server.default_domain
83
+ @server.register_mbean dyna, "#{@domain}:type=MyDynamicMBean"
84
+ @bean = @client["#{@domain}:type=MyDynamicMBean"]
85
+ end
86
+
87
+ def unreg_mbean
88
+ @server.unregister_mbean "#{@domain}:type=MyDynamicMBean"
89
+ end
90
+
91
+ def teardown
92
+ @connector.stop
93
+ @registry.stop
94
+ unreg_mbean
95
+ end
96
+
97
+
98
+ def test_ruby_mbean
99
+ assert_equal("foo", @bean.foo)
100
+ assert_equal(6, @bean.double(3))
101
+ assert_raise(TypeError) { puts @bean.double("HEH") }
102
+ assert_equal("hehheh", @bean.string_double("heh"))
103
+ assert_equal("123", @bean.concat([1,2,3]))
104
+ end
105
+
106
+ def test_ruby_mbean_attribtues
107
+ assert_nil(@bean.name)
108
+ @bean.name = "Name"
109
+ assert_equal("Name", @bean.name)
110
+
111
+ assert_equal(42, @bean.explicit_reader)
112
+ @bean.explicit_writer = 69
113
+ # explicit_writer changes attribute name as a side-effect
114
+ assert_equal("69", @bean.name)
115
+
116
+ @bean.explicit_both = 1
117
+ assert_equal(1, @bean.explicit_both)
118
+ end
119
+
120
+ def test_extended_mbean
121
+ dyna = MyExtendedDynamicMBean.new("domain.MySuperBean", "Heh")
122
+ @server.register_mbean dyna, "#{@domain}:type=MyExtendedDynamicMBean"
123
+ @bean = @client["#{@domain}:type=MyExtendedDynamicMBean"]
124
+
125
+ assert_equal(12, @bean.triple(4))
126
+
127
+ @server.unregister_mbean "#{@domain}:type=MyExtendedDynamicMBean"
128
+ end
129
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tobias-jmx
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: "0.8"
6
+ platform: ruby
7
+ authors:
8
+ - Thomas Enebo & Jay McGaffigan
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-04-29 00:00:00 -04:00
14
+ default_executable:
15
+ dependencies: []
16
+
17
+ description: Install this gem and require 'jmx' to load the library.
18
+ email: tom.enebo@gmail.com
19
+ executables: []
20
+
21
+ extensions: []
22
+
23
+ extra_rdoc_files:
24
+ - Manifest.txt
25
+ - LICENSE.txt
26
+ files:
27
+ - Manifest.txt
28
+ - Rakefile
29
+ - README.md
30
+ - LICENSE.txt
31
+ - lib/jmx.rb
32
+ - lib/rmi.rb
33
+ - lib/jmx/dynamic_mbean.rb
34
+ - lib/jmx/server.rb
35
+ - lib/jmx/version.rb
36
+ - samples/memory.rb
37
+ - test/jmx_attribute_test.rb
38
+ - test/jmx_client_test.rb
39
+ - test/jmx_server_test.rb
40
+ - .gemtest
41
+ has_rdoc: true
42
+ homepage: http://github.com/enebo/jmxjr
43
+ licenses: []
44
+
45
+ post_install_message:
46
+ rdoc_options:
47
+ - --main
48
+ - README.txt
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: "0"
63
+ requirements: []
64
+
65
+ rubyforge_project: jruby-extras
66
+ rubygems_version: 1.5.1
67
+ signing_key:
68
+ specification_version: 3
69
+ summary: Package for interacting/creating Java Management Extensions
70
+ test_files: []
71
+