Introduce Gerrit Inspector: interactive Jython shell
Adds "-s" option to com.google.gerrit.pgm.Daemon to start an interactive Jython shell for inspection and troubleshooting of live data of the Gerrit instance. Gerrit Inspector is an interactive scriptable environment to inspect and modify internal state of the system. This environment is available on the system console after the system starts. Leaving the Inspector will shutdown the Gerrit instance. The environment allows interactive work as well as running of Python scripts for troubleshooting. Accessing Java Virtual Machine objects and calling Java methods is possible. Gerrit Inspect can be started by adding -s option to the command used to launch the daemon: java -jar buck-out/gen/gerrit.war daemon -d ../test_site -s For more information on this facility please see: Documentation/dev-inspector.txt Implementation remark: Jython needs to examine available .jar files on startup. It does so currently by reading contents of the WAR archive unpacked by GerritLauncher in the temporary directory. Jython is able to store this information persistently, but it does not currently work because we unpack WAR file every time on startup. Currently no attempt is made to introspect Guice bindings. Change-Id: I47ffc8383fd50bc6ec12ba31edb6d7d614e97bd5
This commit is contained in:
committed by
Shawn Pearce
parent
aebbe03e4f
commit
ed612fb44a
@@ -33,6 +33,7 @@ import com.google.gerrit.pgm.http.jetty.GetUserFilter;
|
||||
import com.google.gerrit.pgm.http.jetty.JettyEnv;
|
||||
import com.google.gerrit.pgm.http.jetty.JettyModule;
|
||||
import com.google.gerrit.pgm.http.jetty.ProjectQoSFilter;
|
||||
import com.google.gerrit.pgm.shell.JythonShell;
|
||||
import com.google.gerrit.pgm.util.ErrorLogFile;
|
||||
import com.google.gerrit.pgm.util.GarbageCollectionLogFile;
|
||||
import com.google.gerrit.pgm.util.LogFileCompressor;
|
||||
@@ -57,6 +58,7 @@ import com.google.gerrit.server.mail.SmtpEmailSender;
|
||||
import com.google.gerrit.server.patch.IntraLineWorkerPool;
|
||||
import com.google.gerrit.server.plugins.PluginGuiceEnvironment;
|
||||
import com.google.gerrit.server.plugins.PluginRestApiModule;
|
||||
import com.google.gerrit.server.schema.DataSourceProvider;
|
||||
import com.google.gerrit.server.schema.SchemaVersionCheck;
|
||||
import com.google.gerrit.server.ssh.NoSshKeyCache;
|
||||
import com.google.gerrit.server.ssh.NoSshModule;
|
||||
@@ -109,6 +111,9 @@ public class Daemon extends SiteProgram {
|
||||
@Option(name = "--console-log", usage = "Log to console (not $site_path/logs)")
|
||||
private boolean consoleLog;
|
||||
|
||||
@Option(name = "-s", usage = "Start interactive shell")
|
||||
private boolean inspector;
|
||||
|
||||
@Option(name = "--run-id", usage = "Cookie to store in $site_path/logs/gerrit.run")
|
||||
private String runId;
|
||||
|
||||
@@ -224,7 +229,15 @@ public class Daemon extends SiteProgram {
|
||||
serverStarted.run();
|
||||
}
|
||||
|
||||
RuntimeShutdown.waitFor();
|
||||
if (inspector) {
|
||||
JythonShell shell = new JythonShell();
|
||||
shell.set("m", manager);
|
||||
shell.set("ds", dbInjector.getInstance(DataSourceProvider.class));
|
||||
shell.set("schk", dbInjector.getInstance(SchemaVersionCheck.class));
|
||||
shell.run();
|
||||
} else {
|
||||
RuntimeShutdown.waitFor();
|
||||
}
|
||||
return 0;
|
||||
} catch (Throwable err) {
|
||||
log.error("Unable to start daemon", err);
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
// Copyright (C) 2013 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.pgm.shell;
|
||||
|
||||
import com.google.gerrit.launcher.GerritLauncher;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Properties;
|
||||
|
||||
public class JythonShell {
|
||||
private static final Logger log = LoggerFactory.getLogger(JythonShell.class);
|
||||
private static final String STARTUP_RESOURCE = "com/google/gerrit/pgm/Startup.py";
|
||||
private static final String STARTUP_FILE = "Startup.py";
|
||||
|
||||
private Class<?> console;
|
||||
private Class<?> pyObject;
|
||||
private Class<?> pySystemState;
|
||||
private Object shell;
|
||||
private ArrayList <String> injectedVariables;
|
||||
|
||||
public JythonShell() {
|
||||
Properties env = new Properties();
|
||||
// Let us inspect private class members
|
||||
env.setProperty("python.security.respectJavaAccessibility", "false");
|
||||
|
||||
File home = GerritLauncher.getHomeDirectory();
|
||||
if (home != null) {
|
||||
env.setProperty("python.cachedir", new File(home, "jythoncache").getPath());
|
||||
}
|
||||
|
||||
// For package introspection and "import com.google" to work,
|
||||
// Jython needs to inspect actual .jar files (not just classloader)
|
||||
StringBuilder classPath = new StringBuilder();
|
||||
final ClassLoader cl = getClass().getClassLoader();
|
||||
if (cl instanceof java.net.URLClassLoader) {
|
||||
URLClassLoader ucl = (URLClassLoader) cl;
|
||||
for (URL u : ucl.getURLs()) {
|
||||
if ("file".equals(u.getProtocol())) {
|
||||
if (classPath.length() > 0) {
|
||||
classPath.append(java.io.File.pathSeparatorChar);
|
||||
}
|
||||
classPath.append(u.getFile());
|
||||
}
|
||||
}
|
||||
}
|
||||
env.setProperty("java.class.path", classPath.toString());
|
||||
|
||||
console = findClass("org.python.util.InteractiveConsole");
|
||||
pyObject = findClass("org.python.core.PyObject");
|
||||
pySystemState = findClass("org.python.core.PySystemState");
|
||||
|
||||
runMethod(pySystemState, pySystemState, "initialize",
|
||||
new Class[] { Properties.class, Properties.class },
|
||||
new Object[] { null, env }
|
||||
);
|
||||
|
||||
try {
|
||||
shell = console.newInstance();
|
||||
log.info("Jython shell instance created.");
|
||||
} catch (InstantiationException e) {
|
||||
throw noInterpreter(e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw noInterpreter(e);
|
||||
}
|
||||
injectedVariables = new ArrayList<String>();
|
||||
set("Shell", this);
|
||||
}
|
||||
|
||||
protected Object runMethod0(Class<?> klazz, Object instance,
|
||||
String name, Class<?>[] sig, Object[] args)
|
||||
throws InvocationTargetException {
|
||||
try {
|
||||
Method m;
|
||||
m = klazz.getMethod(name, sig);
|
||||
return m.invoke(instance, args);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw cannotStart(e);
|
||||
} catch (SecurityException e) {
|
||||
throw cannotStart(e);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw cannotStart(e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw cannotStart(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected Object runMethod(Class<?> klazz, Object instance,
|
||||
String name, Class<?>[] sig, Object[] args) {
|
||||
try {
|
||||
return runMethod0(klazz, instance, name, sig, args);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw cannotStart(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected Object runInterpreter(String name, Class<?>[] sig, Object[] args) {
|
||||
return runMethod(console, shell, name, sig, args);
|
||||
}
|
||||
|
||||
protected String getDefaultBanner() {
|
||||
return (String)runInterpreter("getDefaultBanner",
|
||||
new Class[] { }, new Object[] { });
|
||||
}
|
||||
|
||||
protected void printInjectedVariable(String id) {
|
||||
runInterpreter("exec",
|
||||
new Class[] { String.class },
|
||||
new Object[] { "print '\"%s\" is \"%s\"' % (\"" + id + "\", " + id + ")" }
|
||||
);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
for (String key : injectedVariables) {
|
||||
printInjectedVariable(key);
|
||||
}
|
||||
reload();
|
||||
runInterpreter("interact",
|
||||
new Class[] { String.class, pyObject },
|
||||
new Object[] { getDefaultBanner() +
|
||||
" running for Gerrit " + com.google.gerrit.common.Version.getVersion(),
|
||||
null });
|
||||
}
|
||||
|
||||
public void set(String key, Object content) {
|
||||
runInterpreter("set",
|
||||
new Class[] { String.class, Object.class },
|
||||
new Object[] { key, content }
|
||||
);
|
||||
injectedVariables.add(key);
|
||||
}
|
||||
|
||||
private static Class<?> findClass(String klazzname) {
|
||||
try {
|
||||
return Class.forName(klazzname);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw noShell("Class " + klazzname + " not found", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
execResource(STARTUP_RESOURCE);
|
||||
execFile(GerritLauncher.getHomeDirectory(), STARTUP_FILE);
|
||||
}
|
||||
|
||||
protected void execResource(final String p) {
|
||||
InputStream in = JythonShell.class.getClassLoader().getResourceAsStream(p);
|
||||
if (in != null) {
|
||||
execStream(in, "resource " + p);
|
||||
} else {
|
||||
log.error("Cannot load resource " + p);
|
||||
}
|
||||
}
|
||||
|
||||
protected void execFile(final File parent, final String p) {
|
||||
try {
|
||||
File script = new File(parent, p);
|
||||
if (script.canExecute()) {
|
||||
runMethod0(console, shell, "execfile",
|
||||
new Class[] { String.class },
|
||||
new Object[] { script.getAbsolutePath() }
|
||||
);
|
||||
} else {
|
||||
log.info("User initialization file "
|
||||
+ script.getAbsolutePath()
|
||||
+ " is not found or not executable");
|
||||
}
|
||||
} catch (InvocationTargetException e) {
|
||||
log.error("Exception occured while loading file " + p + " : ", e);
|
||||
} catch (SecurityException e) {
|
||||
log.error("SecurityException occured while loading file " + p + " : ", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void execStream(final InputStream in, final String p) {
|
||||
try {
|
||||
runMethod0(console, shell, "execfile",
|
||||
new Class[] { InputStream.class, String.class },
|
||||
new Object[] { in, p }
|
||||
);
|
||||
} catch (InvocationTargetException e) {
|
||||
log.error("Exception occured while loading " + p + " : ", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static UnsupportedOperationException noShell(final String m, Throwable why) {
|
||||
final String prefix = "Cannot create Jython shell: ";
|
||||
final String postfix = "\n (You might need to install jython.jar in the lib directory)";
|
||||
return new UnsupportedOperationException(prefix + m + postfix, why);
|
||||
}
|
||||
|
||||
private static UnsupportedOperationException noInterpreter(Throwable why) {
|
||||
final String msg = "Cannot create Python interpreter";
|
||||
return noShell(msg, why);
|
||||
}
|
||||
|
||||
private static UnsupportedOperationException cannotStart(Throwable why) {
|
||||
final String msg = "Cannot start Jython shell";
|
||||
return new UnsupportedOperationException(msg, why);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user