/* * eXist Open Source Native XML Database * Copyright (C) 2001-06 The eXist Project * http://exist-db.org * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * $Id$ */ package org.exist.xquery; import java.util.Stack; import org.apache.log4j.Logger; import org.exist.xquery.parser.XQueryAST; import org.exist.xquery.value.Sequence; import org.exist.storage.BrokerPool; /** * XQuery profiling output. Profiling information is written to a * logger. The profiler can be enabled/disabled and configured * via an XQuery pragma or "declare option" expression. Example: * * <pre>declare option exist:profiling "enabled=yes verbosity=10 logger=profiler";</pre> * * @author wolf * */ public class Profiler { /** value for Verbosity property: basic profiling : just elapsed time */ public static int TIME = 1; /** value for Verbosity property: For optimizations */ public static int OPTIMIZATIONS = 2; /** For computations that will trigger further optimizations */ public static int OPTIMIZATION_FLAGS = 3; /** Indicates the dependencies of the expression */ public static int DEPENDENCIES = 4; /** An abstract level for viewing the expression's context sequence/item */ public static int START_SEQUENCES = 4; /** Just returns the number of items in the sequence */ public static int ITEM_COUNT = 5; /** For a truncated string representation of the context sequence (TODO) */ public static int SEQUENCE_PREVIEW = 6; /** For a full representation of the context sequence (TODO) */ public static int SEQUENCE_DUMP = 8; public static String CONFIG_PROPERTY_TRACELOG = "xquery.profiling.tracelog"; /** * The logger where all output goes. */ private Logger log = Logger.getLogger("xquery.profiling"); private Stack stack = new Stack(); private final StringBuilder buf = new StringBuilder(64); private boolean enabled = false; private boolean logEnabled = false; private int verbosity = 0; private PerformanceStats stats; private long queryStart = 0; private BrokerPool pool; public Profiler(BrokerPool pool) { this.pool = pool; this.stats = new PerformanceStats(pool); } /** * Configure the profiler from an XQuery pragma. * Parameters are: * * <ul> * <li><strong>enabled</strong>: yes|no.</li> * <li><strong>logger</strong>: name of the logger to use.</li> * <li><strong>verbosity</strong>: integer value > 0. 1 does only output function calls.</li> * </ul> * @param pragma */ public final void configure(Option pragma) { String options[] = pragma.tokenizeContents(); String params[]; for (int i = 0; i < options.length; i++) { params = Option.parseKeyValuePair(options[i]); if (params != null) { if (params[0].equals("trace")) { stats.setEnabled(true); } else if (params[0].equals("tracelog")) { logEnabled = params[1].equals("yes"); } else if (params[0].equals("logger")) { log = Logger.getLogger(params[1]); } else if (params[0].equals("enabled")) { enabled = params[1].equals("yes"); } else if ("verbosity".equals(params[0])) { try { verbosity = Integer.parseInt(params[1]); } catch (NumberFormatException e) { log.warn( "invalid value for verbosity: " + "should be an integer between 0 and " + SEQUENCE_DUMP ); } } } } if (verbosity == 0) enabled=false; } /** * Is profiling enabled? * * @return True if profiling is enabled */ public final boolean isEnabled() { return enabled; } public final boolean isLogEnabled() { Boolean globalProp = (Boolean) pool.getConfiguration().getProperty(CONFIG_PROPERTY_TRACELOG); return logEnabled || (globalProp != null && globalProp.booleanValue()); } public final void setLogEnabled(boolean enabled) { logEnabled = enabled; } public final boolean traceFunctions() { return stats.isEnabled() || isLogEnabled(); } /** * @return the verbosity of the profiler. */ public final int verbosity() { return verbosity; } public final void traceQueryStart() { queryStart = System.currentTimeMillis(); } public final void traceQueryEnd(XQueryContext context) { stats.recordQuery(context.getSourceKey(), (System.currentTimeMillis() - queryStart)); } public final void traceFunctionStart(Function function) { if (isLogEnabled()) { log.trace(String.format("ENTER %-25s", function.getSignature().getName())); } } public final void traceFunctionEnd(Function function, long elapsed) { if (stats.isEnabled()) { String source; if (function instanceof InternalFunctionCall) source = ((InternalFunctionCall) function).getFunction().getClass().getName(); else source = function.getContext().getSourceKey(); stats.recordFunctionCall(function.getSignature().getName(), source, elapsed); } if (isLogEnabled()) { log.trace(String.format("EXIT %-25s %10d ms", function.getSignature().getName(), elapsed)); } } public final void traceIndexUsage(XQueryContext context, String indexType, Expression expression, int mode, long elapsed) { stats.recordIndexUse(expression, indexType, context.getSourceKey(), mode, elapsed); } private void save() { if (pool != null) { pool.getPerformanceStats().merge(stats); } } /** * Called by an expression to indicate the start of an operation. * The profiler registers the start time. * * @param expr the expression. */ public final void start(Expression expr) { start(expr, null); } /** * Called by an expression to indicate the start of an operation. * The profiler registers the start time. * * @param expr the expression. * @param message if not null, contains an optional message to print in the log. */ public final void start(Expression expr, String message) { if (!enabled) return; if (stack.size() == 0) { log.debug("QUERY START"); } buf.setLength(0); for (int i = 0; i < stack.size(); i++) buf.append('\t'); ProfiledExpr e = new ProfiledExpr(expr); stack.push(e); buf.append("START\t"); printPosition(e.expr); buf.append(expr.toString()); log.debug(buf.toString()); if (message != null && !"".equals(message)) { buf.setLength(0); for (int i = 0; i < stack.size(); i++) buf.append('\t'); buf.append("MSG\t"); buf.append(message); buf.append("\t"); printPosition(e.expr); buf.append(expr.toString()); log.debug(buf.toString()); } } /** * Called by an expression to indicate the end of an operation. * The profiler computes the elapsed time. * * @param expr the expression. * @param message required: a message to be printed to the log. */ public final void end(Expression expr, String message, Sequence result) { if (!enabled) return; try { ProfiledExpr e = (ProfiledExpr) stack.pop(); if (e.expr != expr) { log.warn("Error: the object passed to end() does not correspond to the expression on top of the stack."); stack.clear(); return; } long elapsed = System.currentTimeMillis() - e.start; if (message != null && !"".equals(message)) { buf.setLength(0); for (int i = 0; i < stack.size(); i++) buf.append('\t'); buf.append("MSG\t"); buf.append(message); buf.append("\t"); printPosition(e.expr); buf.append(expr.toString()); log.debug(buf.toString()); } if (verbosity > START_SEQUENCES) { buf.setLength(0); for (int i = 0; i < stack.size(); i++) buf.append('\t'); buf.append("RESULT\t"); /* if (verbosity >= SEQUENCE_DUMP) buf.append(result.toString()); else if (verbosity >= SEQUENCE_PREVIEW) buf.append(sequencePreview(result)); else*/ if (verbosity >= ITEM_COUNT) buf.append(result.getItemCount() + " item(s)"); buf.append("\t"); printPosition(e.expr); buf.append(expr.toString()); log.debug(buf.toString()); } if (verbosity >= TIME) { buf.setLength(0); for (int i = 0; i < stack.size(); i++) buf.append('\t'); buf.append("TIME\t"); buf.append(elapsed + " ms"); buf.append("\t"); printPosition(e.expr); buf.append(expr.toString()); log.debug(buf.toString()); } buf.setLength(0); for (int i = 0; i < stack.size(); i++) buf.append('\t'); buf.append("END\t"); printPosition(e.expr); buf.append(expr.toString()); log.debug(buf.toString()); if (stack.size() == 0) { log.debug("QUERY END"); } } catch (RuntimeException e) { log.debug("Profiler: could not pop from expression stack - " + expr + " - "+ message + ". Error : "+ e.getMessage()); } } /** * Print out a single profiling message for the given * expression object. * * * @param level * @param title * @param sequence * @param expr */ public final void message(Expression expr, int level, String title, Sequence sequence) { if (!enabled) return; if (level > verbosity) return; buf.setLength(0); for (int i = 0; i < stack.size() - 1; i++) buf.append('\t'); if (title != null && !"".equals(title)) buf.append(title); else buf.append("MSG"); buf.append("\t"); /* if (verbosity >= SEQUENCE_DUMP) buf.append(sequence.toString()); else if (verbosity >= SEQUENCE_PREVIEW) buf.append(sequencePreview(sequence)); else */ if (verbosity >= ITEM_COUNT) buf.append(sequence.getItemCount() + " item(s)"); buf.append("\t"); buf.append(expr.toString()); log.debug(buf.toString()); } public final void message(Expression expr, int level, String title, String message) { if (!enabled) return; if (level > verbosity) return; buf.setLength(0); for (int i = 0; i < stack.size() - 1; i++) buf.append('\t'); if (title != null && !"".equals(title)) buf.append(title); else buf.append("MSG"); if (message != null && !"".equals(message)) { buf.append("\t"); buf.append(message); } buf.append("\t"); printPosition(expr); buf.append(expr.toString()); log.debug(buf.toString()); } public void reset() { if (stack.size() > 0) log.debug("QUERY RESET"); stack.clear(); if (stats.isEnabled() && stats.hasData()) { save(); stats.reset(); } } private void printPosition(Expression expr) { if (expr.getLine() > -1) { buf.append('['); buf.append(expr.getLine()); buf.append(','); buf.append(expr.getColumn()); buf.append("]\t"); } else buf.append("\t"); } //TODO : find a way to preview "abstract" sequences // never used locally private String sequencePreview(Sequence sequence) { StringBuilder truncation = new StringBuilder(); if (sequence.isEmpty()) truncation.append(sequence.toString()); else if (sequence.hasOne()) { truncation.append("("); if (sequence.itemAt(0).toString().length() > 20) truncation.append(sequence.itemAt(0).toString().substring(0, 20)).append("... "); else truncation.append(sequence.itemAt(0).toString()); truncation.append(")"); } else { truncation.append("("); if (sequence.itemAt(0).toString().length() > 20) truncation.append(sequence.itemAt(0).toString().substring(0, 20)).append("... "); else truncation.append(sequence.itemAt(0).toString()); truncation.append(", ... )"); } return truncation.toString(); } private final static class ProfiledExpr { long start; Expression expr; private ProfiledExpr(Expression expression) { this.expr = expression; this.start = System.currentTimeMillis(); } } public void setEnabled(boolean enabled) { this.enabled = enabled; } public void setVerbosity(int verbosity) { this.verbosity = verbosity; } }