package com.anjlab.gae; import java.util.List; import java.util.concurrent.Future; import java.util.regex.Pattern; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.apphosting.api.ApiProxy; import com.google.apphosting.api.ApiProxy.ApiConfig; import com.google.apphosting.api.ApiProxy.ApiProxyException; import com.google.apphosting.api.ApiProxy.Delegate; import com.google.apphosting.api.ApiProxy.Environment; import com.google.apphosting.api.ApiProxy.LogRecord; public class ProfilingDelegate implements Delegate<Environment> { private static final Logger logger = LoggerFactory.getLogger(ProfilingDelegate.class); private final Delegate<Environment> parent; private final String[] excludePackages; private final boolean logTraces; public ProfilingDelegate(Delegate<Environment> parent, boolean logTraces, String... excludePackages) { this.parent = parent; this.excludePackages = excludePackages; this.logTraces = logTraces; } @Override public void log(Environment env, LogRecord logRec) { parent.log(env, logRec); } @Override public byte[] makeSyncCall(Environment env, String pkg, String method, byte[] request) throws ApiProxyException { long start = System.currentTimeMillis(); byte[] result = parent.makeSyncCall(env, pkg, method, request); StringBuilder builder = logTraces ? buildStackTrace(excludePackages) : null; logger.info("GAE/S {}.{}: ->{} ms<-\n{}", new Object[] { pkg, method, System.currentTimeMillis() - start, builder }); return result; } /** * * @param appPackage * Only classes from this package would be included in trace. * @return */ private static StringBuilder buildStackTrace(String... excludePackages) { StackTraceElement[] traces = Thread.currentThread().getStackTrace(); StringBuilder builder = new StringBuilder(); int length = traces.length; StackTraceElement traceElement; String className; // for (int i = 3; i < length; i++) { // traceElement = traces[i]; // className = traceElement.getClassName(); // if (className.startsWith(appPackage)) { // if (builder.length() > 0) { // builder.append('\n'); // } // builder.append(".."); // builder.append(className.substring(className.lastIndexOf('.'))); // builder.append('.'); // builder.append(traceElement.getMethodName()); // builder.append(':'); // builder.append(traceElement.getLineNumber()); // } // } if (builder.length() == 0) { nextTrace: for (int i = 3; i < length; i++) { traceElement = traces[i]; className = traceElement.getClassName(); for (String pkg : excludePackages) { if (Pattern.matches(pkg, className)) { continue nextTrace; } } if (builder.length() > 0) { builder.append('\n'); } builder.append(className); builder.append('.'); builder.append(traceElement.getMethodName()); builder.append(':'); builder.append(traceElement.getLineNumber()); } } return builder; } @Override public Future<byte[]> makeAsyncCall(Environment env, String pkg, String method, byte[] request, ApiConfig config) { long start = System.currentTimeMillis(); Future<byte[]> result = parent.makeAsyncCall(env, pkg, method, request, config); StringBuilder builder = logTraces ? buildStackTrace(excludePackages) : null; logger.info("GAE/A {}.{}: ->{} ms<-\n{}", new Object[] { pkg, method, System.currentTimeMillis() - start, builder }); return result; } @Override public void flushLogs(Environment env) { parent.flushLogs(env); } @Override public List<Thread> getRequestThreads(Environment env) { return parent.getRequestThreads(env); } @SuppressWarnings("unchecked") public static void register() { // Note: Comment this off to profile Google API requests ApiProxy.setDelegate(new ProfilingDelegate(ApiProxy.getDelegate(), false, "org.mortbay.*", "com.google.apphosting.utils.*", "com.google.tracing.*", "org.apache.tapestry5.*", "^\\$[^\\.]*$")); } }