engineyard-visualvm 0.5.3-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/.gitignore +5 -0
  2. data/ChangeLog +22 -0
  3. data/Gemfile +26 -0
  4. data/LICENSE.txt +23 -0
  5. data/README.md +104 -0
  6. data/Rakefile +124 -0
  7. data/Vagrantfile +94 -0
  8. data/bin/ey-visualvm +9 -0
  9. data/cookbooks/apt/README.md +122 -0
  10. data/cookbooks/apt/files/default/apt-cacher +9 -0
  11. data/cookbooks/apt/files/default/apt-cacher.conf +144 -0
  12. data/cookbooks/apt/files/default/apt-proxy-v2.conf +50 -0
  13. data/cookbooks/apt/metadata.json +34 -0
  14. data/cookbooks/apt/metadata.rb +13 -0
  15. data/cookbooks/apt/providers/repository.rb +73 -0
  16. data/cookbooks/apt/recipes/cacher-client.rb +44 -0
  17. data/cookbooks/apt/recipes/cacher.rb +45 -0
  18. data/cookbooks/apt/recipes/default.rb +50 -0
  19. data/cookbooks/apt/resources/repository.rb +30 -0
  20. data/cookbooks/gems/recipes/default.rb +16 -0
  21. data/cookbooks/java/README.md +102 -0
  22. data/cookbooks/java/attributes/default.rb +29 -0
  23. data/cookbooks/java/files/default/java.seed +11 -0
  24. data/cookbooks/java/metadata.json +50 -0
  25. data/cookbooks/java/metadata.rb +16 -0
  26. data/cookbooks/java/recipes/default.rb +21 -0
  27. data/cookbooks/java/recipes/openjdk.rb +39 -0
  28. data/cookbooks/java/recipes/sun.rb +93 -0
  29. data/cookbooks/jruby/attributes/default.rb +2 -0
  30. data/cookbooks/jruby/recipes/default.rb +24 -0
  31. data/cookbooks/server/recipes/default.rb +9 -0
  32. data/cookbooks/server/templates/default/server.sh.erb +27 -0
  33. data/cookbooks/vagrant_main/recipes/default.rb +4 -0
  34. data/engineyard-visualvm-java.gemspec +47 -0
  35. data/engineyard-visualvm.gemspec +43 -0
  36. data/engineyard-visualvm.gemspec.in +35 -0
  37. data/ext/org/jruby/ext/jmx/Agent.java +169 -0
  38. data/ext/org/jruby/ext/jmx/JavaHome.java +7 -0
  39. data/ext/org/jruby/ext/jmx/RMIServerSocketFactoryImpl.java +37 -0
  40. data/lib/engineyard-visualvm.rb +8 -0
  41. data/lib/engineyard-visualvm/agent.jar +0 -0
  42. data/lib/engineyard-visualvm/cli.rb +225 -0
  43. data/lib/engineyard-visualvm/version.rb +11 -0
  44. data/spec/engineyard-visualvm_spec.rb +202 -0
  45. data/spec/spec_helper.rb +49 -0
  46. metadata +157 -0
@@ -0,0 +1,9 @@
1
+ template "#{ENV['HOME']}/server.sh" do
2
+ source "server.sh.erb"
3
+ mode "0755"
4
+ end
5
+
6
+ execute "start server" do
7
+ cwd ENV['HOME']
8
+ command "./server.sh"
9
+ end
@@ -0,0 +1,27 @@
1
+ #!/bin/bash
2
+
3
+ export PATH=/usr/local/bin:$PATH
4
+ export JAVA_OPTS=$(jruby -S ey-visualvm jvmargs)
5
+
6
+ start() {
7
+ /sbin/start-stop-daemon --start --verbose --background --make-pidfile \
8
+ --pidfile server.pid --exec /usr/local/bin/jruby --chdir $PWD \
9
+ -- -e sleep
10
+ }
11
+
12
+ stop() {
13
+ /sbin/start-stop-daemon --stop --quiet --pidfile server.pid
14
+ }
15
+
16
+ case $1 in
17
+ start)
18
+ start
19
+ ;;
20
+ stop)
21
+ stop
22
+ ;;
23
+ *)
24
+ start
25
+ ;;
26
+ esac
27
+
@@ -0,0 +1,4 @@
1
+ require_recipe 'apt'
2
+ require_recipe 'jruby'
3
+ require_recipe 'gems'
4
+ require_recipe 'server'
@@ -0,0 +1,47 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{engineyard-visualvm}
5
+ s.version = "0.5.3"
6
+ s.platform = %q{java}
7
+
8
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
9
+ s.authors = [%q{Nick Sieger}]
10
+ s.date = %q{2011-12-21}
11
+ s.description = %q{This provides a Java agent and command-line utility to enable
12
+ JMX in any Java process such that it can be accessed through a firewall,
13
+ and a VisualVM launcher aid to connect to that process through ssh.}
14
+ s.email = [%q{nick@nicksieger.com}]
15
+ s.executables = [%q{ey-visualvm}]
16
+ s.files = [%q{.gitignore}, %q{ChangeLog}, %q{Gemfile}, %q{LICENSE.txt}, %q{README.md}, %q{Rakefile}, %q{Vagrantfile}, %q{bin/ey-visualvm}, %q{cookbooks/apt/README.md}, %q{cookbooks/apt/files/default/apt-cacher}, %q{cookbooks/apt/files/default/apt-cacher.conf}, %q{cookbooks/apt/files/default/apt-proxy-v2.conf}, %q{cookbooks/apt/metadata.json}, %q{cookbooks/apt/metadata.rb}, %q{cookbooks/apt/providers/repository.rb}, %q{cookbooks/apt/recipes/cacher-client.rb}, %q{cookbooks/apt/recipes/cacher.rb}, %q{cookbooks/apt/recipes/default.rb}, %q{cookbooks/apt/resources/repository.rb}, %q{cookbooks/gems/recipes/default.rb}, %q{cookbooks/java/README.md}, %q{cookbooks/java/attributes/default.rb}, %q{cookbooks/java/files/default/java.seed}, %q{cookbooks/java/metadata.json}, %q{cookbooks/java/metadata.rb}, %q{cookbooks/java/recipes/default.rb}, %q{cookbooks/java/recipes/openjdk.rb}, %q{cookbooks/java/recipes/sun.rb}, %q{cookbooks/jruby/attributes/default.rb}, %q{cookbooks/jruby/recipes/default.rb}, %q{cookbooks/server/recipes/default.rb}, %q{cookbooks/server/templates/default/server.sh.erb}, %q{cookbooks/vagrant_main/recipes/default.rb}, %q{engineyard-visualvm-java.gemspec}, %q{engineyard-visualvm.gemspec}, %q{engineyard-visualvm.gemspec.in}, %q{ext/org/jruby/ext/jmx/Agent.java}, %q{ext/org/jruby/ext/jmx/JavaHome.java}, %q{ext/org/jruby/ext/jmx/RMIServerSocketFactoryImpl.java}, %q{lib/engineyard-visualvm.rb}, %q{lib/engineyard-visualvm/agent.jar}, %q{lib/engineyard-visualvm/cli.rb}, %q{lib/engineyard-visualvm/version.rb}, %q{spec/engineyard-visualvm_spec.rb}, %q{spec/spec_helper.rb}]
17
+ s.homepage = %q{https://github.com/engineyard/engineyard-visualvm}
18
+ s.require_paths = [%q{lib}]
19
+ s.rubyforge_project = %q{jruby-extras}
20
+ s.rubygems_version = %q{1.8.9}
21
+ s.summary = %q{Client and server helpers for using JMX and VisualVM with EY Cloud.}
22
+ s.test_files = [%q{spec/engineyard-visualvm_spec.rb}, %q{spec/spec_helper.rb}]
23
+
24
+ if s.respond_to? :specification_version then
25
+ s.specification_version = 3
26
+
27
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
28
+ s.add_runtime_dependency(%q<childprocess>, [">= 0"])
29
+ s.add_runtime_dependency(%q<engineyard>, [">= 0"])
30
+ s.add_runtime_dependency(%q<jruby-openssl>, [">= 0"])
31
+ s.add_runtime_dependency(%q<ffi-ncurses>, [">= 0"])
32
+ s.add_development_dependency(%q<rspec>, [">= 0"])
33
+ else
34
+ s.add_dependency(%q<childprocess>, [">= 0"])
35
+ s.add_dependency(%q<engineyard>, [">= 0"])
36
+ s.add_dependency(%q<jruby-openssl>, [">= 0"])
37
+ s.add_dependency(%q<ffi-ncurses>, [">= 0"])
38
+ s.add_dependency(%q<rspec>, [">= 0"])
39
+ end
40
+ else
41
+ s.add_dependency(%q<childprocess>, [">= 0"])
42
+ s.add_dependency(%q<engineyard>, [">= 0"])
43
+ s.add_dependency(%q<jruby-openssl>, [">= 0"])
44
+ s.add_dependency(%q<ffi-ncurses>, [">= 0"])
45
+ s.add_dependency(%q<rspec>, [">= 0"])
46
+ end
47
+ end
@@ -0,0 +1,43 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{engineyard-visualvm}
5
+ s.version = "0.5.3"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = [%q{Nick Sieger}]
9
+ s.date = %q{2011-12-21}
10
+ s.description = %q{This provides a Java agent and command-line utility to enable
11
+ JMX in any Java process such that it can be accessed through a firewall,
12
+ and a VisualVM launcher aid to connect to that process through ssh.}
13
+ s.email = [%q{nick@nicksieger.com}]
14
+ s.executables = [%q{ey-visualvm}]
15
+ s.files = [%q{.gitignore}, %q{ChangeLog}, %q{Gemfile}, %q{LICENSE.txt}, %q{README.md}, %q{Rakefile}, %q{Vagrantfile}, %q{bin/ey-visualvm}, %q{cookbooks/apt/README.md}, %q{cookbooks/apt/files/default/apt-cacher}, %q{cookbooks/apt/files/default/apt-cacher.conf}, %q{cookbooks/apt/files/default/apt-proxy-v2.conf}, %q{cookbooks/apt/metadata.json}, %q{cookbooks/apt/metadata.rb}, %q{cookbooks/apt/providers/repository.rb}, %q{cookbooks/apt/recipes/cacher-client.rb}, %q{cookbooks/apt/recipes/cacher.rb}, %q{cookbooks/apt/recipes/default.rb}, %q{cookbooks/apt/resources/repository.rb}, %q{cookbooks/gems/recipes/default.rb}, %q{cookbooks/java/README.md}, %q{cookbooks/java/attributes/default.rb}, %q{cookbooks/java/files/default/java.seed}, %q{cookbooks/java/metadata.json}, %q{cookbooks/java/metadata.rb}, %q{cookbooks/java/recipes/default.rb}, %q{cookbooks/java/recipes/openjdk.rb}, %q{cookbooks/java/recipes/sun.rb}, %q{cookbooks/jruby/attributes/default.rb}, %q{cookbooks/jruby/recipes/default.rb}, %q{cookbooks/server/recipes/default.rb}, %q{cookbooks/server/templates/default/server.sh.erb}, %q{cookbooks/vagrant_main/recipes/default.rb}, %q{engineyard-visualvm-java.gemspec}, %q{engineyard-visualvm.gemspec}, %q{engineyard-visualvm.gemspec.in}, %q{ext/org/jruby/ext/jmx/Agent.java}, %q{ext/org/jruby/ext/jmx/JavaHome.java}, %q{ext/org/jruby/ext/jmx/RMIServerSocketFactoryImpl.java}, %q{lib/engineyard-visualvm.rb}, %q{lib/engineyard-visualvm/agent.jar}, %q{lib/engineyard-visualvm/cli.rb}, %q{lib/engineyard-visualvm/version.rb}, %q{spec/engineyard-visualvm_spec.rb}, %q{spec/spec_helper.rb}]
16
+ s.homepage = %q{https://github.com/engineyard/engineyard-visualvm}
17
+ s.require_paths = [%q{lib}]
18
+ s.rubyforge_project = %q{jruby-extras}
19
+ s.rubygems_version = %q{1.8.9}
20
+ s.summary = %q{Client and server helpers for using JMX and VisualVM with EY Cloud.}
21
+ s.test_files = [%q{spec/engineyard-visualvm_spec.rb}, %q{spec/spec_helper.rb}]
22
+
23
+ if s.respond_to? :specification_version then
24
+ s.specification_version = 3
25
+
26
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
27
+ s.add_runtime_dependency(%q<childprocess>, [">= 0"])
28
+ s.add_runtime_dependency(%q<engineyard>, [">= 0"])
29
+ s.add_runtime_dependency(%q<ffi-ncurses>, [">= 0"])
30
+ s.add_development_dependency(%q<rspec>, [">= 0"])
31
+ else
32
+ s.add_dependency(%q<childprocess>, [">= 0"])
33
+ s.add_dependency(%q<engineyard>, [">= 0"])
34
+ s.add_dependency(%q<ffi-ncurses>, [">= 0"])
35
+ s.add_dependency(%q<rspec>, [">= 0"])
36
+ end
37
+ else
38
+ s.add_dependency(%q<childprocess>, [">= 0"])
39
+ s.add_dependency(%q<engineyard>, [">= 0"])
40
+ s.add_dependency(%q<ffi-ncurses>, [">= 0"])
41
+ s.add_dependency(%q<rspec>, [">= 0"])
42
+ end
43
+ end
@@ -0,0 +1,35 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "engineyard-visualvm/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "engineyard-visualvm"
7
+ s.platform = "java" if defined?(use_jruby)
8
+ s.version = EngineYard::VisualVM::VERSION
9
+ s.authors = ["Nick Sieger"]
10
+ s.email = ["nick@nicksieger.com"]
11
+ s.homepage = "https://github.com/engineyard/engineyard-visualvm"
12
+ s.summary = %q{Client and server helpers for using JMX and VisualVM with EY Cloud.}
13
+ s.description = %q{This provides a Java agent and command-line utility to enable
14
+ JMX in any Java process such that it can be accessed through a firewall,
15
+ and a VisualVM launcher aid to connect to that process through ssh.}
16
+
17
+ s.rubyforge_project = "jruby-extras"
18
+
19
+ s.files = `git ls-files`.split("\n")
20
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
21
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
22
+ s.require_paths = ["lib"]
23
+
24
+ s.add_runtime_dependency "childprocess"
25
+ s.add_runtime_dependency "engineyard"
26
+ if defined?(use_jruby)
27
+ s.add_runtime_dependency "jruby-openssl" # engineyard gem uses ssl
28
+ end
29
+ s.add_runtime_dependency "ffi-ncurses" # for highline gem, dep of engineyard
30
+ s.add_development_dependency "rspec"
31
+ end
32
+
33
+ # Local Variables:
34
+ # mode: ruby
35
+ # End:
@@ -0,0 +1,169 @@
1
+ /*
2
+ * Copyright (c) 2011 Engine Yard, Inc.
3
+ * See the file LICENSE.txt included with the distribution for
4
+ * software license details.
5
+ *
6
+ * Based on CustomAgent.java found at the following URLs:
7
+ * https://blogs.oracle.com/jmxetc/entry/connecting_through_firewall_using_jmx
8
+ * https://blogs.oracle.com/jmxetc/entry/more_on_premain_and_jmx
9
+ */
10
+ /*
11
+ * CustomAgent.java
12
+ *
13
+ * Copyright 2007, 2011 Sun Microsystems, Inc. All Rights Reserved.
14
+ *
15
+ * Redistribution and use in source and binary forms, with or without
16
+ * modification, are permitted provided that the following conditions
17
+ * are met:
18
+ *
19
+ * - Redistributions of source code must retain the above copyright
20
+ * notice, this list of conditions and the following disclaimer.
21
+ *
22
+ * - Redistributions in binary form must reproduce the above copyright
23
+ * notice, this list of conditions and the following disclaimer in the
24
+ * documentation and/or other materials provided with the distribution.
25
+ *
26
+ * - Neither the name of Sun Microsystems nor the names of its
27
+ * contributors may be used to endorse or promote products derived
28
+ * from this software without specific prior written permission.
29
+ *
30
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
31
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
32
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
33
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
35
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
36
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
37
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
38
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
39
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
40
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
41
+ *
42
+ * Created on Jul 25, 2007, 11:42:49 AM
43
+ *
44
+ */
45
+
46
+ package org.jruby.ext.jmx;
47
+
48
+ import java.io.IOException;
49
+ import java.lang.management.ManagementFactory;
50
+ import java.rmi.Remote;
51
+ import java.rmi.registry.LocateRegistry;
52
+ import java.rmi.server.RMIServerSocketFactory;
53
+ import java.rmi.server.UnicastRemoteObject;
54
+ import java.util.HashMap;
55
+ import javax.management.MBeanServer;
56
+ import javax.management.remote.JMXConnectorServer;
57
+ import javax.management.remote.JMXConnectorServerFactory;
58
+ import javax.management.remote.JMXServiceURL;
59
+ import javax.management.remote.rmi.RMIConnectorServer;
60
+
61
+ import sun.jvmstat.monitor.remote.RemoteHost;
62
+ import sun.tools.jstatd.RemoteHostImpl;
63
+ import java.rmi.Naming;
64
+
65
+ public class Agent {
66
+
67
+ private static Thread cleaner;
68
+
69
+ private Agent() { }
70
+
71
+ public static void premain(String agentArgs) throws IOException {
72
+
73
+ // Ensure cryptographically strong random number generator used
74
+ // to choose the object number - see java.rmi.server.ObjID
75
+ //
76
+ System.setProperty("java.rmi.server.randomIDs", "true");
77
+
78
+ // Ensure JRuby JMX beans are available in all runtimes
79
+ System.setProperty("jruby.management.enabled", "true");
80
+
81
+ final int port = Integer.parseInt(System.getProperty("org.jruby.jmx.agent.port", "5900"));
82
+ final String hostname = System.getProperty("org.jruby.jmx.agent.hostname", "localhost");
83
+
84
+ // Make sure our RMI server knows which host we're binding to
85
+ System.setProperty("java.rmi.server.hostname", hostname);
86
+ System.setProperty("java.rmi.server.disableHttp", "true");
87
+
88
+ final RMIServerSocketFactory factory = new RMIServerSocketFactoryImpl(hostname);
89
+
90
+ LocateRegistry.createRegistry(port, null, factory);
91
+ MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
92
+ HashMap<String,Object> env = new HashMap<String,Object>();
93
+ env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, factory);
94
+
95
+ // Create an RMI connector server.
96
+ //
97
+ // As specified in the JMXServiceURL the RMIServer stub will be
98
+ // registered in the RMI registry running in the local host with the
99
+ // name "jmxrmi". This is the same name the out-of-the-box
100
+ // management agent uses to register the RMIServer stub too.
101
+ //
102
+ // The port specified in "service:jmx:rmi://"+hostname+":"+port
103
+ // is the second port, where RMI connection objects will be exported.
104
+ // Here we use the same port as that we choose for the RMI registry.
105
+ // The port for the RMI registry is specified in the second part
106
+ // of the URL, in "rmi://"+hostname+":"+port
107
+ //
108
+ JMXConnectorServer cs = new RMIConnectorServer(makeJMXServiceURL(hostname, port), env, mbs);
109
+
110
+ try {
111
+ // Create and register Jstatd remote host
112
+ Remote remoteHost = (Remote) Class.forName("sun.tools.jstatd.RemoteHostImpl").newInstance();
113
+ UnicastRemoteObject.exportObject(remoteHost, port, null, factory);
114
+ Naming.rebind("//"+hostname+":"+port+"/JStatRemoteHost", remoteHost);
115
+ } catch (Throwable e) {
116
+ System.err.println("Unable to start jstatd monitor: " + e.toString());
117
+ }
118
+
119
+ cs.start();
120
+ cleaner = new CleanThread(cs);
121
+ cleaner.start();
122
+ }
123
+
124
+ public static JMXServiceURL makeJMXServiceURL(String hostname, int port) throws IOException {
125
+ return new JMXServiceURL("service:jmx:rmi://"+hostname+
126
+ ":"+port+"/jndi/rmi://"+hostname+":"+port+"/jmxrmi");
127
+ }
128
+
129
+ public static class CleanThread extends Thread {
130
+ private final JMXConnectorServer cs;
131
+ public CleanThread(JMXConnectorServer cs) {
132
+ super("JMX Agent Cleaner");
133
+ this.cs = cs;
134
+ setDaemon(true);
135
+ }
136
+ public void run() {
137
+ boolean loop = true;
138
+ try {
139
+ while (loop) {
140
+ final Thread[] all = new Thread[Thread.activeCount()+100];
141
+ final int count = Thread.enumerate(all);
142
+ loop = false;
143
+ for (int i=0;i<count;i++) {
144
+ final Thread t = all[i];
145
+ // daemon: skip it.
146
+ if (t.isDaemon()) continue;
147
+ // RMI Reaper: skip it.
148
+ if (t.getName().startsWith("RMI Reaper")) continue;
149
+ if (t.getName().startsWith("DestroyJavaVM")) continue;
150
+ // Non daemon, non RMI Reaper: join it, break the for
151
+ // loop, continue in the while loop (loop=true)
152
+ loop = true;
153
+ try {
154
+ t.join();
155
+ } catch (Exception ex) {
156
+ }
157
+ break;
158
+ }
159
+ }
160
+ } catch (Exception ex) {
161
+ } finally {
162
+ try {
163
+ cs.stop();
164
+ } catch (Exception ex) {
165
+ }
166
+ }
167
+ }
168
+ }
169
+ }
@@ -0,0 +1,7 @@
1
+ package org.jruby.ext.jmx.agent;
2
+
3
+ public class JavaHome {
4
+ public static void main(String[] args) {
5
+ System.out.println(System.getProperty("java.home"));
6
+ }
7
+ }
@@ -0,0 +1,37 @@
1
+ /*
2
+ * Copyright (c) 2011 Engine Yard, Inc.
3
+ * See the file LICENSE.txt included with the distribution for
4
+ * software license details.
5
+ *
6
+ *
7
+ * Cribbed from http://vafer.org/blog/20061010091658/
8
+ */
9
+
10
+ package org.jruby.ext.jmx;
11
+
12
+ import java.io.IOException;
13
+ import java.net.InetAddress;
14
+ import java.net.ServerSocket;
15
+ import java.net.UnknownHostException;
16
+ import java.rmi.server.RMIServerSocketFactory;
17
+ import javax.net.ServerSocketFactory;
18
+
19
+ public class RMIServerSocketFactoryImpl implements RMIServerSocketFactory {
20
+ private final InetAddress localAddress;
21
+
22
+ public RMIServerSocketFactoryImpl(final String address) throws UnknownHostException {
23
+ localAddress = InetAddress.getByName(address);
24
+ }
25
+
26
+ public ServerSocket createServerSocket(final int port) throws IOException {
27
+ return ServerSocketFactory.getDefault().createServerSocket(port, 0, localAddress);
28
+ }
29
+
30
+ public boolean equals(Object obj) {
31
+ return obj != null && obj.getClass().equals(getClass());
32
+ }
33
+
34
+ public int hashCode() {
35
+ return RMIServerSocketFactoryImpl.class.hashCode();
36
+ }
37
+ }
@@ -0,0 +1,8 @@
1
+ #--
2
+ # Copyright (c) 2011 Engine Yard, Inc.
3
+ # See the file LICENSE.txt included with the distribution for
4
+ # software license details.
5
+ #++
6
+
7
+ require 'engineyard-visualvm/version'
8
+ require 'engineyard-visualvm/cli'
@@ -0,0 +1,225 @@
1
+ #--
2
+ # Copyright (c) 2011 Engine Yard, Inc.
3
+ # See the file LICENSE.txt included with the distribution for
4
+ # software license details.
5
+ #++
6
+
7
+ require "thor"
8
+ require "childprocess"
9
+ require "socket"
10
+ require "engineyard"
11
+ require "engineyard/cli"
12
+ require "engineyard/thor"
13
+
14
+ module EngineYard
15
+ module VisualVM
16
+ module Helpers
17
+ def self.included(base)
18
+ unless base.instance_methods.map(&:to_s).include?("options")
19
+ base.class_eval do
20
+ def options
21
+ @_opts ||= { :host => "localhost" }
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ def self.port_available?(port)
28
+ begin
29
+ tcps = TCPServer.new("127.0.0.1", port)
30
+ true
31
+ rescue Errno::EADDRINUSE
32
+ false
33
+ ensure
34
+ tcps.close if tcps
35
+ end
36
+ end
37
+
38
+ STARTING_PORT = 5900
39
+
40
+ def self.next_free_port(start = STARTING_PORT)
41
+ (start...start+100).each do |i|
42
+ return i if port_available?(i)
43
+ end
44
+ end
45
+
46
+ def environment
47
+ @environment ||= begin
48
+ fetch_environment(options[:environment], options[:account]).tap {|env|
49
+ @user = env.username
50
+ @host = fetch_public_ip(env)
51
+ }
52
+ rescue EY::Error
53
+ raise if options[:environment]
54
+ nil
55
+ end
56
+ end
57
+
58
+ def user
59
+ @user
60
+ end
61
+
62
+ def ssh?
63
+ environment || (host && user) || options[:ssh] || options[:socks]
64
+ end
65
+
66
+ def socks_proxy?
67
+ options[:socks]
68
+ end
69
+
70
+ def ssh_host
71
+ user ? "#{user}@#{host}" : host
72
+ end
73
+
74
+ def host
75
+ @host ||= begin
76
+ match = /(.*)?@(.*)/.match options[:host]
77
+ if match
78
+ @user = match[1]
79
+ match[2]
80
+ else
81
+ @user = nil
82
+ options[:host]
83
+ end
84
+ end
85
+ end
86
+
87
+ def next_free_port
88
+ Helpers.next_free_port(port)
89
+ end
90
+
91
+ def port
92
+ @port ||= Numeric === options[:port] && options[:port] || STARTING_PORT
93
+ end
94
+
95
+ def jvm_arguments
96
+ tools_jar = find_tools_jar
97
+ args = "-Dorg.jruby.jmx.agent.port=#{next_free_port} -javaagent:#{agent_jar_path}"
98
+ args = "-Dorg.jruby.jmx.agent.hostname=#{host} #{args}" if host != "localhost"
99
+ args = "-Xbootclasspath/a:#{tools_jar} #{args}" if tools_jar
100
+ args
101
+ end
102
+
103
+ def jmx_service_url
104
+ "service:jmx:rmi://#{host}:#{port}/jndi/rmi://#{host}:#{port}/jmxrmi"
105
+ end
106
+
107
+ def find_executable?(exe)
108
+ ENV['PATH'].split(File::PATH_SEPARATOR).detect do |path|
109
+ File.exist?(File.join(path, exe))
110
+ end
111
+ end
112
+
113
+ def agent_jar_path
114
+ File.expand_path('../agent.jar', __FILE__)
115
+ end
116
+
117
+ def find_tools_jar
118
+ java_home = `java -classpath #{agent_jar_path} org.jruby.ext.jmx.agent.JavaHome`
119
+ [File.expand_path('./lib/tools.jar', java_home),
120
+ File.expand_path('../lib/tools.jar', java_home)].detect do |path|
121
+ File.readable?(path)
122
+ end
123
+ end
124
+
125
+ # Return the public IP or instance hostname assigned to an
126
+ # environment (which may or may not be a booted cluster of
127
+ # instances) Displays error and exits if no public IP assigned
128
+ # to the environment
129
+ def fetch_public_ip(environment)
130
+ if environment.load_balancer_ip_address
131
+ return environment.load_balancer_ip_address
132
+ end
133
+
134
+ if environment.instances.length == 1 && environment.instances.first.public_hostname
135
+ return environment.instances.first.public_hostname
136
+ end
137
+
138
+ warn "#{environment.account.name}/#{environment.name} has no assigned public IP address or hostname."
139
+ nil
140
+ end
141
+ end
142
+
143
+ class CLI < Thor
144
+ include EY::UtilityMethods
145
+ include Helpers
146
+
147
+ class_option :host, :aliases => ["-H"], :default => "localhost",
148
+ :desc => "Host or IP where the JMX agent runs"
149
+ class_option :port, :aliases => ["-p"], :type => :numeric, :default => STARTING_PORT.to_s,
150
+ :desc => "Port where the JMX agent runs"
151
+
152
+ desc "jvmargs", "Print the arguments to be passed to the server JVM"
153
+ def jvmargs
154
+ puts jvm_arguments
155
+ end
156
+
157
+ desc "url", "Print the connection URL for the JMX server process"
158
+ def url
159
+ puts jmx_service_url
160
+ end
161
+
162
+ desc "version", "Show version"
163
+ def version
164
+ puts "ey-visualvm version #{EngineYard::VisualVM::VERSION}"
165
+ end
166
+
167
+ desc "start", "Launch VisualVM to connect to the server.\nUse either the environment/account or host/port options."
168
+ method_option :ssh, :type => :boolean, :desc => "Force VisualVM to connect through an ssh tunnel"
169
+ method_option :socks, :type => :boolean, :desc => "Force VisualVM to connect through a SOCKS proxy"
170
+ method_option :environment, :aliases => ["-e"], :desc => "Environment containing the IP to which to resolve", :type => :string
171
+ method_option :account, :aliases => ["-c"], :desc => "Name of the account where the environment is found"
172
+ def start
173
+ unless find_executable?("jvisualvm")
174
+ warn "Could not find \`jvisualvm\'; do you need to install the JDK?"
175
+ exit 1
176
+ end
177
+
178
+ visualvm_args = []
179
+
180
+ if ssh?
181
+ ssh_dest = ssh_host
182
+ unless system "ssh #{ssh_dest} true"
183
+ warn "Error establishing ssh connection; make sure you can `ssh #{ssh_dest}'."
184
+ exit 3
185
+ end
186
+
187
+ if socks_proxy?
188
+ proxy_port = next_free_port
189
+ visualvm_args += ["-J-Dnetbeans.system_socks_proxy=localhost:#{proxy_port}", "-J-Djava.net.useSystemProxies=true"]
190
+ @ssh_process = ChildProcess.build("ssh", "-ND", proxy_port.to_s, ssh_dest)
191
+ else
192
+ server_host, server_port = host, port
193
+ @host, @port = "localhost", next_free_port
194
+ @ssh_process = ChildProcess.build("ssh", "-NL", "#{@port}:#{@host}:#{server_port}", ssh_dest)
195
+ end
196
+
197
+ @ssh_process.start
198
+ end
199
+
200
+ visualvm_args += ["--openjmx", jmx_service_url.to_s]
201
+ visualvm = ChildProcess.build("jvisualvm", *visualvm_args)
202
+ visualvm.start
203
+
204
+ loop do
205
+ visualvm.exited? && break
206
+ sleep 1
207
+ end
208
+
209
+ @ssh_process.stop if @ssh_process
210
+ rescue EY::Error => e
211
+ warn e.message
212
+ exit 2
213
+ end
214
+
215
+ def help(task = nil, *args)
216
+ unless task
217
+ puts "usage: ey-visualvm <task> [options|arguments]"
218
+ puts "Make JMX and VisualVM more accessible to your server-side JVM."
219
+ puts
220
+ end
221
+ super
222
+ end
223
+ end
224
+ end
225
+ end