/**
* *****************************************************************************
*
* Copyright (c) 2004-2010 Oracle Corporation.
*
* All rights reserved. This program and the accompanying materials are made
* available under the terms of the Eclipse Public License v1.0 which
* accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*
* Kohsuke Kawaguchi, Winston Prakash
*
******************************************************************************
*/
package hudson.util;
import hudson.FilePath;
import hudson.Functions;
import hudson.model.Hudson;
import hudson.remoting.Callable;
import hudson.remoting.DelegatingCallable;
import hudson.remoting.VirtualChannel;
import hudson.security.AccessControlled;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.WebMethod;
import javax.management.JMException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;
import org.eclipse.hudson.script.ScriptSupport;
/**
* Various remoting operations related to diagnostics.
*
* <p> These code are useful wherever {@link VirtualChannel} is used, such as
* master, slaves, Maven JVMs, etc.
*
* @author Kohsuke Kawaguchi
* @since 1.175
*/
public final class RemotingDiagnostics {
public static Map<Object, Object> getSystemProperties(VirtualChannel channel) throws IOException, InterruptedException {
if (channel == null) {
return Collections.<Object, Object>singletonMap("N/A", "N/A");
}
return channel.call(new GetSystemProperties());
}
private static final class GetSystemProperties implements Callable<Map<Object, Object>, RuntimeException> {
public Map<Object, Object> call() {
return new TreeMap<Object, Object>(System.getProperties());
}
private static final long serialVersionUID = 1L;
}
public static Map<String, String> getThreadDump(VirtualChannel channel) throws IOException, InterruptedException {
if (channel == null) {
return Collections.singletonMap("N/A", "N/A");
}
return channel.call(new GetThreadDump());
}
private static final class GetThreadDump implements Callable<Map<String, String>, RuntimeException> {
public Map<String, String> call() {
Map<String, String> r = new LinkedHashMap<String, String>();
try {
ThreadInfo[] data = Functions.getThreadInfos();
Functions.ThreadGroupMap map = Functions.sortThreadsAndGetGroupMap(data);
for (ThreadInfo ti : data) {
r.put(ti.getThreadName(), Functions.dumpThreadInfo(ti, map));
}
} catch (LinkageError _) {
// not in JDK6. fall back to JDK5
r.clear();
for (Map.Entry<Thread, StackTraceElement[]> t : Functions.dumpAllThreads().entrySet()) {
StringBuilder buf = new StringBuilder();
for (StackTraceElement e : t.getValue()) {
buf.append(e).append('\n');
}
r.put(t.getKey().getName(), buf.toString());
}
}
return r;
}
private static final long serialVersionUID = 1L;
}
/**
* Executes script remotely.
*/
public static String executeScript(String script, VirtualChannel channel) throws IOException, InterruptedException {
return channel.call(new Script(script));
}
public static String executeScript(String script, VirtualChannel channel, ScriptSupport scriptSupport) throws IOException, InterruptedException {
return channel.call(new Script(script, scriptSupport));
}
private static final class Script implements DelegatingCallable<String, RuntimeException> {
private final String script;
private transient ClassLoader parentClassLoader;
private ScriptSupport scriptSupport;
private Script(String script) {
this.script = script;
parentClassLoader = getClassLoader();
}
private Script(String script, String scriptType) {
this(script);
if (scriptType != null) {
for (ScriptSupport scriptSupport : ScriptSupport.getAvailableScriptSupports()) {
if (scriptSupport.hasSupport(scriptType)) {
this.scriptSupport = scriptSupport;
}
}
}
}
private Script(String script, ScriptSupport scriptSupport) {
this(script);
this.scriptSupport = scriptSupport;
}
public ClassLoader getClassLoader() {
return Hudson.getInstance().getPluginManager().uberClassLoader;
}
public String call() throws RuntimeException {
if (scriptSupport != null) {
// if we run locally, cl!=null. Otherwise the delegating classloader will be available as context classloader.
if (parentClassLoader == null) {
parentClassLoader = Thread.currentThread().getContextClassLoader();
}
StringWriter out = new StringWriter();
PrintWriter printWriter = new PrintWriter(out);
if (scriptSupport != null) {
scriptSupport.evaluate(parentClassLoader, script, null, printWriter);
return out.toString();
} else {
return "No script support to execute the script. Install script support plugin";
}
}
return "";
}
}
/**
* Obtains the heap dump in an HPROF file.
*/
public static FilePath getHeapDump(VirtualChannel channel) throws IOException, InterruptedException {
return channel.call(new Callable<FilePath, IOException>() {
public FilePath call() throws IOException {
final File hprof = File.createTempFile("hudson-heapdump", "hprof");
hprof.delete();
try {
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
server.invoke(new ObjectName("com.sun.management:type=HotSpotDiagnostic"), "dumpHeap",
new Object[]{hprof.getAbsolutePath(), true}, new String[]{String.class.getName(), boolean.class.getName()});
return new FilePath(hprof);
} catch (JMException e) {
throw new IOException2(e);
}
}
private static final long serialVersionUID = 1L;
});
}
/**
* Heap dump, exposable to URL via Stapler.
*
*/
public static class HeapDump {
private final AccessControlled owner;
private final VirtualChannel channel;
public HeapDump(AccessControlled owner, VirtualChannel channel) {
this.owner = owner;
this.channel = channel;
}
/**
* Obtains the heap dump.
*/
public void doIndex(StaplerResponse rsp) throws IOException {
rsp.sendRedirect("heapdump.hprof");
}
@WebMethod(name = "heapdump.hprof")
public void doHeapDump(StaplerRequest req, StaplerResponse rsp) throws IOException, InterruptedException {
owner.checkPermission(Hudson.ADMINISTER);
rsp.setContentType("application/octet-stream");
FilePath dump = obtain();
try {
dump.copyTo(rsp.getCompressedOutputStream(req));
} finally {
dump.delete();
}
}
public FilePath obtain() throws IOException, InterruptedException {
return RemotingDiagnostics.getHeapDump(channel);
}
}
}