engineyard-visualvm 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/.gitignore +5 -0
  2. data/Gemfile +16 -0
  3. data/LICENSE.txt +23 -0
  4. data/README.md +102 -0
  5. data/Rakefile +76 -0
  6. data/Vagrantfile +94 -0
  7. data/bin/ey-visualvm +9 -0
  8. data/cookbooks/apt/README.md +122 -0
  9. data/cookbooks/apt/files/default/apt-cacher +9 -0
  10. data/cookbooks/apt/files/default/apt-cacher.conf +144 -0
  11. data/cookbooks/apt/files/default/apt-proxy-v2.conf +50 -0
  12. data/cookbooks/apt/metadata.json +34 -0
  13. data/cookbooks/apt/metadata.rb +13 -0
  14. data/cookbooks/apt/providers/repository.rb +73 -0
  15. data/cookbooks/apt/recipes/cacher-client.rb +44 -0
  16. data/cookbooks/apt/recipes/cacher.rb +45 -0
  17. data/cookbooks/apt/recipes/default.rb +50 -0
  18. data/cookbooks/apt/resources/repository.rb +30 -0
  19. data/cookbooks/gems/recipes/default.rb +16 -0
  20. data/cookbooks/java/README.md +102 -0
  21. data/cookbooks/java/attributes/default.rb +29 -0
  22. data/cookbooks/java/files/default/java.seed +11 -0
  23. data/cookbooks/java/metadata.json +50 -0
  24. data/cookbooks/java/metadata.rb +16 -0
  25. data/cookbooks/java/recipes/default.rb +21 -0
  26. data/cookbooks/java/recipes/openjdk.rb +39 -0
  27. data/cookbooks/java/recipes/sun.rb +93 -0
  28. data/cookbooks/jruby/attributes/default.rb +2 -0
  29. data/cookbooks/jruby/recipes/default.rb +24 -0
  30. data/cookbooks/server/recipes/default.rb +9 -0
  31. data/cookbooks/server/templates/default/server.sh.erb +27 -0
  32. data/cookbooks/vagrant_main/recipes/default.rb +4 -0
  33. data/engineyard-visualvm.gemspec +28 -0
  34. data/ext/org/jruby/ext/jmx/Agent.java +169 -0
  35. data/ext/org/jruby/ext/jmx/JavaHome.java +7 -0
  36. data/ext/org/jruby/ext/jmx/RMIServerSocketFactoryImpl.java +37 -0
  37. data/lib/engineyard-visualvm.rb +8 -0
  38. data/lib/engineyard-visualvm/agent.jar +0 -0
  39. data/lib/engineyard-visualvm/cli.rb +205 -0
  40. data/lib/engineyard-visualvm/version.rb +11 -0
  41. data/spec/engineyard-visualvm_spec.rb +164 -0
  42. data/spec/spec_helper.rb +30 -0
  43. metadata +154 -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,28 @@
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.version = EngineYard::VisualVM::VERSION
8
+ s.authors = ["Nick Sieger"]
9
+ s.email = ["nick@nicksieger.com"]
10
+ s.homepage = "https://github.com/engineyard/engineyard-visualvm"
11
+ s.summary = %q{Client and server helpers for using JMX and VisualVM with EY Cloud.}
12
+ s.description = %q{This provides a Java agent and command-line utility to enable
13
+ JMX in any Java process such that it can be accessed through a firewall,
14
+ and a VisualVM launcher aid to connect to that process through ssh.}
15
+
16
+ s.rubyforge_project = "jruby-extras"
17
+
18
+ s.files = `git ls-files`.split("\n")
19
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
20
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
21
+ s.require_paths = ["lib"]
22
+
23
+ s.add_runtime_dependency "childprocess"
24
+ s.add_runtime_dependency "engineyard"
25
+ s.add_runtime_dependency "jruby-openssl" # engineyard gem uses ssl
26
+ s.add_runtime_dependency "ffi-ncurses" # for highline gem, dep of engineyard
27
+ s.add_development_dependency "rspec"
28
+ 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'
Binary file
@@ -0,0 +1,205 @@
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.port_available?(port)
18
+ begin
19
+ tcps = TCPServer.new("127.0.0.1", port)
20
+ true
21
+ rescue Errno::EADDRINUSE
22
+ false
23
+ ensure
24
+ tcps.close if tcps
25
+ end
26
+ end
27
+
28
+ STARTING_PORT = 5900
29
+
30
+ def self.next_free_port(start = STARTING_PORT)
31
+ (start...start+100).each do |i|
32
+ return i if port_available?(i)
33
+ end
34
+ end
35
+
36
+ def environment
37
+ @environment ||= begin
38
+ fetch_environment(options[:environment], options[:account]).tap {|env|
39
+ @user = env.username
40
+ @host = fetch_public_ip(env)
41
+ }
42
+ rescue EY::Error
43
+ raise if options[:environment]
44
+ nil
45
+ end
46
+ end
47
+
48
+ def user
49
+ @user
50
+ end
51
+
52
+ def ssh?
53
+ environment || (host && user) || options[:ssh] || options[:socks]
54
+ end
55
+
56
+ def socks_proxy?
57
+ options[:socks]
58
+ end
59
+
60
+ def ssh_host
61
+ user ? "#{user}@#{host}" : host
62
+ end
63
+
64
+ def host
65
+ @host ||= begin
66
+ match = /(.*)?@(.*)/.match options[:host]
67
+ if match
68
+ @user = match[1]
69
+ match[2]
70
+ else
71
+ @user = nil
72
+ options[:host]
73
+ end
74
+ end
75
+ end
76
+
77
+ def next_free_port
78
+ Helpers.next_free_port(port)
79
+ end
80
+
81
+ def port
82
+ @port ||= Numeric === options[:port] && options[:port] || STARTING_PORT
83
+ end
84
+
85
+ def jvm_arguments
86
+ tools_jar = find_tools_jar
87
+ args = "-Dorg.jruby.jmx.agent.port=#{next_free_port} -javaagent:#{agent_jar_path}"
88
+ args = "-Dorg.jruby.jmx.agent.hostname=#{host} #{args}" if host != "localhost"
89
+ args = "-Xbootclasspath/a:#{tools_jar} #{args}" if tools_jar
90
+ args
91
+ end
92
+
93
+ def jmx_service_url
94
+ "service:jmx:rmi://#{host}:#{port}/jndi/rmi://#{host}:#{port}/jmxrmi"
95
+ end
96
+
97
+ def find_executable?(exe)
98
+ ENV['PATH'].split(File::PATH_SEPARATOR).detect do |path|
99
+ File.exist?(File.join(path, exe))
100
+ end
101
+ end
102
+
103
+ def agent_jar_path
104
+ File.expand_path('../agent.jar', __FILE__)
105
+ end
106
+
107
+ def find_tools_jar
108
+ java_home = `java -classpath #{agent_jar_path} org.jruby.ext.jmx.agent.JavaHome`
109
+ [File.expand_path('./lib/tools.jar', java_home),
110
+ File.expand_path('../lib/tools.jar', java_home)].detect do |path|
111
+ File.readable?(path)
112
+ end
113
+ end
114
+
115
+ # Return the public IP assigned to an environment (which may or
116
+ # may not be a booted cluster of instances) Displays error and
117
+ # exits if no public IP assigned to the environment
118
+ def fetch_public_ip(environment)
119
+ unless environment.load_balancer_ip_address
120
+ warn "#{environment.account.name}/#{environment.name} has no assigned public IP address."
121
+ end
122
+
123
+ environment.load_balancer_ip_address
124
+ end
125
+ end
126
+
127
+ class CLI < Thor
128
+ include EY::UtilityMethods
129
+ include Helpers
130
+
131
+ class_option :host, :aliases => ["-H"], :default => "localhost",
132
+ :desc => "Host or IP where the JMX agent runs"
133
+ class_option :port, :aliases => ["-p"], :type => :numeric, :default => STARTING_PORT.to_s,
134
+ :desc => "Port where the JMX agent runs"
135
+
136
+ desc "jvmargs", "Print the arguments to be passed to the server JVM"
137
+ def jvmargs
138
+ puts jvm_arguments
139
+ end
140
+
141
+ desc "url", "Print the connection URL for the JMX server process"
142
+ def url
143
+ puts jmx_service_url
144
+ end
145
+
146
+ desc "version", "Show version"
147
+ def version
148
+ puts "ey-visualvm version #{EngineYard::VisualVM::VERSION}"
149
+ end
150
+
151
+ desc "start", "Launch VisualVM to connect to the server.\nUse either the environment/account or host/port options."
152
+ method_option :ssh, :type => :boolean, :desc => "Force VisualVM to connect through an ssh tunnel"
153
+ method_option :socks, :type => :boolean, :desc => "Force VisualVM to connect through a SOCKS proxy"
154
+ method_option :environment, :aliases => ["-e"], :desc => "Environment containing the IP to which to resolve", :type => :string
155
+ method_option :account, :aliases => ["-c"], :desc => "Name of the account where the environment is found"
156
+ def start
157
+ unless find_executable?("jvisualvm")
158
+ warn "Could not find \`jvisualvm\'; do you need to install the JDK?"
159
+ exit 1
160
+ end
161
+
162
+ visualvm_args = []
163
+
164
+ if ssh?
165
+ ssh_dest = ssh_host
166
+
167
+ if socks_proxy?
168
+ proxy_port = next_free_port
169
+ visualvm_args += ["-J-Dnetbeans.system_socks_proxy=localhost:#{proxy_port}", "-J-Djava.net.useSystemProxies=true"]
170
+ @ssh_process = ChildProcess.build("ssh", "-ND", proxy_port.to_s, ssh_dest)
171
+ else
172
+ server_host, server_port = host, port
173
+ @host, @port = "localhost", next_free_port
174
+ @ssh_process = ChildProcess.build("ssh", "-NL", "#{@port}:#{@host}:#{server_port}", "#{ssh_dest}")
175
+ end
176
+
177
+ @ssh_process.start
178
+ end
179
+
180
+ visualvm_args += ["--openjmx", jmx_service_url.to_s]
181
+ visualvm = ChildProcess.build("jvisualvm", *visualvm_args)
182
+ visualvm.start
183
+
184
+ loop do
185
+ visualvm.exited? && break
186
+ sleep 1
187
+ end
188
+
189
+ @ssh_process.stop if @ssh_process
190
+ rescue EY::Error => e
191
+ warn e.message
192
+ exit 2
193
+ end
194
+
195
+ def help(task = nil, *args)
196
+ unless task
197
+ puts "usage: ey-visualvm <task> [options|arguments]"
198
+ puts "Make JMX and VisualVM more accessible to your server-side JVM."
199
+ puts
200
+ end
201
+ super
202
+ end
203
+ end
204
+ end
205
+ end