engineyard-visualvm 0.5.0

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.
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