tobias-jmx 0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+