Author: bpoussin Date: 2013-01-22 18:06:19 +0100 (Tue, 22 Jan 2013) New Revision: 2468 Url: http://nuiton.org/projects/nuiton-utils/repository/revisions/2468 Log: Anomalie #2513: [nuiton-profiling] update javadoc and documentation Evolution #2511: [nuiton-profiling] add automatic CSV file generation when JVM shutdown Evolution #2512: [nuiton-profiling] add mini web server in NuitonTrace to show statistic during application life Added: trunk/nuiton-profiling/src/main/resources/nuiton-js/ trunk/nuiton-profiling/src/main/resources/nuiton-js/wro.properties trunk/nuiton-profiling/src/main/resources/nuiton-js/wro.xml trunk/nuiton-profiling/src/main/resources/org/ trunk/nuiton-profiling/src/main/resources/org/nuiton/ trunk/nuiton-profiling/src/main/resources/org/nuiton/profiling/ trunk/nuiton-profiling/src/main/resources/org/nuiton/profiling/web/ trunk/nuiton-profiling/src/main/resources/org/nuiton/profiling/web/index.html trunk/nuiton-profiling/src/main/resources/org/nuiton/profiling/web/nuiton-profiling.css trunk/nuiton-profiling/src/main/resources/org/nuiton/profiling/web/nuiton-profiling.js Modified: trunk/nuiton-profiling/pom.xml trunk/nuiton-profiling/src/main/java/org/nuiton/profiling/NuitonTrace.java trunk/nuiton-profiling/src/site/apt/index.apt.vm trunk/nuiton-profiling/src/test/java/org/nuiton/profiling/NuitonTraceTest.java Modified: trunk/nuiton-profiling/pom.xml =================================================================== --- trunk/nuiton-profiling/pom.xml 2013-01-13 11:57:03 UTC (rev 2467) +++ trunk/nuiton-profiling/pom.xml 2013-01-22 17:06:19 UTC (rev 2468) @@ -39,6 +39,12 @@ <dependencies> <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>nuiton-utils</artifactId> + <version>${project.version}</version> + </dependency> + + <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </dependency> @@ -108,6 +114,68 @@ <plugins> <plugin> + <groupId>ro.isdc.wro4j</groupId> + <artifactId>wro4j-maven-plugin</artifactId> + <version>1.6.2</version> + <dependencies> + <dependency> + <groupId>org.nuiton.js</groupId> + <artifactId>nuiton-js-wro</artifactId> + <version>0.1-SNAPSHOT</version> + </dependency> + + <dependency> + <groupId>org.nuiton.js</groupId> + <artifactId>nuiton-js-jquery</artifactId> + <version>1.8.3-1-SNAPSHOT</version> + </dependency> + + <dependency> + <groupId>org.nuiton.js</groupId> + <artifactId>nuiton-js-jquery-ui</artifactId> + <version>1.9.2-1-SNAPSHOT</version> + </dependency> + + <dependency> + <groupId>org.nuiton.js</groupId> + <artifactId>nuiton-js-jqplot</artifactId> + <version>1.0.4r1121-1-SNAPSHOT</version> + </dependency> + + <dependency> + <groupId>org.nuiton.js</groupId> + <artifactId>nuiton-js-jqgrid</artifactId> + <version>4.4.1-1-SNAPSHOT</version> + </dependency> + + <dependency> + <groupId>org.nuiton.js</groupId> + <artifactId>nuiton-js-bootstrap</artifactId> + <version>2.2.2-1-SNAPSHOT</version> + </dependency> + </dependencies> + + <executions> + <execution> + <phase>compile</phase> + <goals> + <goal>run</goal> + </goals> + </execution> + </executions> + + <configuration> + <targetGroups>profiling-ui-web</targetGroups> + <minimize>true</minimize> + <wroFile>${basedir}/src/main/resources/nuiton-js/wro.xml</wroFile> + <extraConfigFile>${basedir}/src/main/resources/nuiton-js/wro.properties</extraConfigFile> + <contextFolder>${basedir}/src/main/resources/org/nuiton/profiling/web/</contextFolder> + <destinationFolder>${basedir}/target/classes/org/nuiton/profiling/web/</destinationFolder> + <wroManagerFactory>org.nuiton.js.wro.NuitonJsMavenWroManagerFactory</wroManagerFactory> + </configuration> + </plugin> + + <plugin> <artifactId>maven-dependency-plugin</artifactId> <executions> <execution> Modified: trunk/nuiton-profiling/src/main/java/org/nuiton/profiling/NuitonTrace.java =================================================================== --- trunk/nuiton-profiling/src/main/java/org/nuiton/profiling/NuitonTrace.java 2013-01-13 11:57:03 UTC (rev 2467) +++ trunk/nuiton-profiling/src/main/java/org/nuiton/profiling/NuitonTrace.java 2013-01-22 17:06:19 UTC (rev 2468) @@ -21,20 +21,36 @@ */ package org.nuiton.profiling; -import java.util.Map.Entry; -import org.apache.commons.lang3.time.DurationFormatUtils; - +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; +import com.sun.net.httpserver.spi.HttpServerProvider; +import java.io.ByteArrayOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.io.PrintStream; +import java.io.Writer; import java.lang.reflect.Method; +import java.net.InetSocketAddress; +import java.net.URI; +import java.net.URL; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.Stack; import java.util.TreeSet; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.time.DurationFormatUtils; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterThrowing; @@ -44,48 +60,73 @@ import org.aspectj.lang.reflect.MethodSignature; /** - * TODO poussin 20110824 check documentation: this documentation is for aspectwerkz, there are little - * change for aspectj. see example file aop.xml + * Permet de tracer les appels aux methodes. * - * Permet de tracer les appels aux methodes. + * <h1>Mise en place</h1> + * + * Il faut mettre dans les dependances Maven le nuiton-profiling + * <pre> + $lt;dependency$gt; + $lt;groupId$gt;org.nuiton$lt;/groupId$gt; + $lt;artifactId$gt;nuiton-profiling$lt;/artifactId$gt; + $lt;version$gt;2.7$lt;/version$gt; + $lt;/dependency$gt; + * </pre> + * * <p/> - * Pour l'utiliser il faut définir un fichier XML qui intercepte les methodes - * souhaiter. + * Pour l'utiliser il faut définir un fichier XML aop.xml dans le repertoire + * META-INF ou WEB-INF qui intercepte les methodes souhaiter. * <pre> - * <?xml version="1.0" encoding="UTF-8"?> - * <!DOCTYPE aspectwerkz PUBLIC - * "-//AspectWerkz//DTD//EN" - * "http://aspectwerkz.codehaus.org/dtd/aspectwerkz2.dtd"> - * <aspectwerkz> - * <system id="sample"> - * <aspect class="fr.ifremer.isisfish.aspect.TraceIsis"> - * <pointcut name="executeMethod"> - * execution(* fr.ifremer.isisfish.datastore.ResultStorage.*(..)) - * || execution(* fr.ifremer.isisfish.aspect.Cache.*(..)) - * || execution(* fr.ifremer.isisfish.aspect.Trace.*(..)) - * || execution(* org.nuiton.topia..*(..)) - * || execution(* org.nuiton.math.matrix..*(..)) - * || execution(* fr.ifremer.isisfish.types..*(..)) - * || execution(* org.apache.commons.collections..*(..)) - * </pointcut> - * </aspect> - * </system> - * </aspectwerkz> + * <!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd"$gt; +$lt;aspectj$gt; + $lt;weaver options="-verbose"$gt; + $lt;exclude within="fr.insee..*CGLIB*"/$gt; + $lt;/weaver$gt; + $lt;aspects$gt; + $lt;concrete-aspect name="org.nuiton.profiling.NuitonTraceAppAspect" + extends="org.nuiton.profiling.NuitonTrace"$gt; + $lt;pointcut name="executeMethod" + expression=" + execution(* org.nuiton..*(..)) + || execution(* org.chorem..*(..)) + || execution(* org.apache..*(..)) + || execution(* ognl..*(..))"/$gt; + $lt;/concrete-aspect$gt; + $lt;/aspects$gt; +$lt;/aspectj$gt; * </pre> - * Ensuite il faut lancer la JVM avec deux options + * Ensuite il faut lancer la JVM avec l'option * <pre> - * -javaagent:path/to/aspectwerkz-jdk5-2.0.jar - * -Daspectwerkz.definition.file=path/to/trace-aop.xml + * -javaagent:path/to/aspectjweaver-1.7.1.jar * </pre> - * Il est possible d'utiliser des noms normalisé et - * trouvable dans le classpath a la place de -Daspectwerkz.definition.file - * <li> /aspectwerkz.xml - * <li> META-INF/aop.xml - * <li> WEB-INF/aop.xml - * <br/> - * Ensuite pour afficher les statistiques dans votre programme + * normalement il se trouve dans le repo maven dans le repertoire + * <li>$MAVEN_REPO/org/aspectj/aspectjweaver/1.7.1/aspectjweaver-1.7.1.jar + * + * <h1>Utilisation</h1> + * + * Il y a 3 modes: interactif, non interactif et par programmation + * + * <h2>Mode non interactif</h2> + * + * Il faut mettre le nom d'un fichier dans la variable d'environnnement + * {@link #AUTO_SAVE_FILENAME_OPTION}. Dans ce cas lors de l'arrêt de la JVM + * les statistiques sont ecrites dans ce fichier. + * + * <h2>Mode interactif</h2> + * + * Il est possible de demander a a NuitonTrace lorsqu'il s'initialise de demarrer + * un serveur Web qui permet de voir et recuperer les donnees statistiques. + * Pour cela il faut indique sur quel port le serveur doit attendre via la + * variable d'environnnement {@link #PORT_OPTION} + * + * <h2>Par programmation</h2> + * + * Pour afficher les statistiques dans votre programme * <li> log.info(NuitonTrace.getStatisticsAndClear()); * <li> NuitonTrace.printStatistiqueAndClear(); + * + * <h1>Autre mode de fonctionnement</h1> + * * Il doit être possible, plutot que d'écrire un fichier XML, de sous classer * NuitonTrace en ajoutant par exemple * <p/> @@ -101,16 +142,31 @@ * Pointcut executeMethod; * </pre> * <p/> - * Voir la classe de test {@code org.nuiton.profiling.NuitonTraceTest}. * - * @see http://aspectwerkz.codehaus.org/definition_issues.html - * * @author bpoussin <poussin@codelutin.com> * @author tchemit <chemit@codelutin.com> */ @Aspect public abstract class NuitonTrace { + /** + * constante determinant le nom de la variable d'environnement a lire + * pour connaitre le port a utiliser pour le serveur web + * @since 2.7 + */ // ne doit pas contenir de '.' ou de '-' sinon elle est non utiliable en variable d'env + final static public String PORT_OPTION = "nuitonprofiling_webport"; + /** + * constante determinant le nom de la variable d'environnement a lire + * pour connaitre le nom du fichier de sauvegarde a la sortie de la JVM + * @since 2.7 + */ // ne doit pas contenir de '.' ou de '-' sinon elle est non utiliable en variable d'env + final static public String AUTO_SAVE_FILENAME_OPTION = "nuitonprofiling_autosavefile"; + /** + * Indique si nuiton trace a deja ete initialise ou non + * @since 2.7 + */ + static protected boolean initilized = false; + static private final List<NuitonTrace> instances = new ArrayList<NuitonTrace>(); // poussin 20110824: ne pas mettre de logger, car sinon, ca pose des problemes @@ -287,6 +343,70 @@ return result.toString(); } + /** + * @return les statistiques in json format + * @since 2.7 + */ + static public String getStatisticsJson() { + StringBuilder result = new StringBuilder(); + result.append("{"); + + String separator = ""; + for (NuitonTrace trace : instances) { + for (Method method : trace.statistics.keySet()) { + Statistic stat = trace.getStatistics(method); + Caller<Method> caller = trace.getCallers(method); + long meanTime = stat.timeTotal / stat.call; + result.append(separator); + separator = ","; // separtor est vide seulement la 1ere fois + result.append("'").append(method).append("': {"); + result.append("'method':").append("'").append(method).append("'") + .append(",") + .append("'call':").append(stat.call) + .append(",") + // Time is in nano not millis, we must divide by 1000000 + .append("'min':").append(DurationFormatUtils.formatDuration(stat.timeMin / 1000000, "s'.'S")) + .append(",") + .append("'mean':").append(DurationFormatUtils.formatDuration(meanTime / 1000000, "s'.'S")) + .append(",") + .append("'max':").append(DurationFormatUtils.formatDuration(stat.timeMax / 1000000, "s'.'S")) + .append(",") + .append("'total':").append(DurationFormatUtils.formatDuration(stat.timeTotal / 1000000, "s'.'S")) + .append(",") + .append("'call_nest':").append(stat.callNestMethod) + .append(",") + .append("'total_with_nest':").append(DurationFormatUtils.formatDuration(stat.timeTotalNestMethod / 1000000, "s'.'S")) + .append(",") + .append("'callers': ["); + for (Map.Entry<Method, Integer> c : caller) { + result.append("{") + .append("'caller':").append("'").append(c.getKey()).append("'") + .append(",") + .append("'call':").append(c.getValue()) + .append("},"); + } + result.append("]}").append("\n"); + } + } + result.append("}"); + // Poussin 20110826 on ne vide pas les instances mais les stats +// instances.clear(); + return result.toString(); + } + + /** + * @return clear all statistiques + * @since 2.7 + */ + static public void clearStatistics() { + for (NuitonTrace trace : instances) { + trace.statistics.clear(); + trace.callers.clear(); + } + } + + + /** @return les statistiques */ static public String getStatisticsAndClear() { StringBuilder result = new StringBuilder(); @@ -311,8 +431,6 @@ trace.statistics.clear(); trace.callers.clear(); } - // Poussin 20110826 on ne vide pas les instances mais les stats -// instances.clear(); return result.toString(); } @@ -381,7 +499,7 @@ * appele a elle ce quelqu'un. * */ - protected static class Caller<E> { + protected static class Caller<E> implements Iterable<Map.Entry<E, Integer>> { protected Map<E, Integer> data = new HashMap<E, Integer>(); @@ -406,6 +524,21 @@ */ @Override public String toString() { + + StringBuilder result = new StringBuilder(); + + for (Map.Entry<E, Integer> e : this) { + result.append(e.getValue()).append("=[").append(e.getKey()).append("]").append(","); + } + if (result.length() > 0) { + // on supprime la derniere virgule en trop + result.deleteCharAt(result.length()-1); + } + + return result.toString(); + } + + public Iterator<Entry<E, Integer>> iterator() { Comparator<Map.Entry<E, Integer>> comparator = new Comparator<Map.Entry<E, Integer>>() { public int compare(Entry<E, Integer> o1, Entry<E, Integer> o2) { // on inverse, car on veut le plus grand en 1er @@ -414,25 +547,222 @@ }; Set<Map.Entry<E, Integer>> entries = data.entrySet(); - Set<Map.Entry<E, Integer>> sorted = + Set<Map.Entry<E, Integer>> sorted = new TreeSet<Map.Entry<E, Integer>>(comparator); sorted.addAll(entries); + return sorted.iterator(); + } + } - StringBuilder result = new StringBuilder(); + /** + * Demarre le service web sur le port demande + * @param port un port valide sur lequel attendre + * @since 2.7 + */ + static protected void startWebService(int port) { + try { + HttpServerProvider provider = HttpServerProvider.provider(); + InetSocketAddress inet = new InetSocketAddress(port); + HttpServer server = provider.createHttpServer(inet, 0); - for (Map.Entry<E, Integer> e : sorted) { - result.append(e.getValue()).append("=[").append(e.getKey()).append("]").append(","); + server.createContext("/", new WebHandler()); + server.createContext("/data", new DataHandler()); + server.createContext("/api", new ApiHandler()); + server.start(); + System.out.println("Nuiton profiling Server is listening on " + inet ); + + } catch(Throwable eee) { + System.err.println("Can't start web service on port " + port); + eee.printStackTrace(System.err); + } + } + + /** + * Handler pour le serveur web qui gere le demande de statistique + * @since 2.7 + */ + static class WebHandler implements HttpHandler { + + static final public String filepath = "/org/nuiton/profiling/web/"; + + public void handle(HttpExchange exchange) throws IOException { + String requestMethod = exchange.getRequestMethod(); + if (requestMethod.equalsIgnoreCase("GET")) { + String path = exchange.getRequestURI().getPath(); + String file = StringUtils.substringAfterLast(path, "/"); + URL resource = getClass().getClassLoader().getResource(filepath + file); + if (resource == null) { + exchange.sendResponseHeaders(404, 0); + } else { + String type = "text/" + StringUtils.substringAfterLast(file, "."); + Headers responseHeaders = exchange.getResponseHeaders(); + responseHeaders.set("Content-Type", type); + exchange.sendResponseHeaders(200, 0); + + InputStream in = resource.openStream(); + ByteArrayOutputStream content = new ByteArrayOutputStream(); + IOUtils.copy(in, content); + + OutputStream responseBody = exchange.getResponseBody(); + responseBody.write(content.toByteArray()); + responseBody.close(); + IOUtils.closeQuietly(in); + IOUtils.closeQuietly(content); + } } - if (result.length() > 0) { - // on supprime la derniere virgule en trop - result.deleteCharAt(result.length()-1); + + } + } + + /** + * Handler pour le serveur web qui gere le demande de statistique + * @since 2.7 + */ + static class DataHandler implements HttpHandler { + /** + * Permet de recupere le parametre callback de l'URL, utilise pour le jsonp + * @param exchange + * @return + */ + protected String getCallback(HttpExchange exchange) { + String result = null; + URI uri = exchange.getRequestURI(); + String query = uri.getQuery(); + if (query != null) { + String pairs[] = query.split("[&]"); + + for (String pair : pairs) { + String param[] = pair.split("[=]", 2); + if (param.length == 2 && "callback".equalsIgnoreCase(param[0])) { + result = param[1]; + break; + } + } } + return result; + } - return result.toString(); + public void handle(HttpExchange exchange) throws IOException { + String requestMethod = exchange.getRequestMethod(); + if (requestMethod.equalsIgnoreCase("GET")) { + Headers responseHeaders = exchange.getResponseHeaders(); + responseHeaders.set("Content-Type", "text/javascript"); + exchange.sendResponseHeaders(200, 0); + + String jsoncallback = getCallback(exchange); + String json = NuitonTrace.getStatisticsJson(); + if (jsoncallback != null) { + json = jsoncallback + "(" + json + ")"; + } + + OutputStream responseBody = exchange.getResponseBody(); + responseBody.write(json.getBytes()); + responseBody.close(); + } } + } + /** + * Handler pour le serveur web qui gere l'api: + * <li>clear + * @since 2.7 + */ + static class ApiHandler implements HttpHandler { + public void handle(HttpExchange exchange) throws IOException { + String requestMethod = exchange.getRequestMethod(); + if (requestMethod.equalsIgnoreCase("GET")) { + String path = exchange.getRequestURI().getPath(); + if (StringUtils.endsWithIgnoreCase(path, "clear")) { + Headers responseHeaders = exchange.getResponseHeaders(); + responseHeaders.set("Content-Type", "text/plain"); + exchange.sendResponseHeaders(200, 0); + clearStatistics(); + + OutputStream responseBody = exchange.getResponseBody(); + responseBody.write("ok".getBytes()); + responseBody.close(); + } else { + exchange.sendResponseHeaders(404, 0); + } + } + + } } + /** + * Permet de lire les options de la ligne de commande Java (-D) et si non + * trouver de rechercher dans les variables d'environnement + * @param optionName + * @return la valeur de l'option ou null si non trouve + * @since 2.7 + */ + static protected String getOption(String optionName) { + String result = System.getProperty(optionName); + if (result == null) { + result = System.getenv(optionName); + } + return result; + } + + /** + * Recherche la configuration dans les variables d'environnement et les + * utilise pour mettre en place ce qui est demande par l'utilisateur + * @since 2.7 + */ + static protected void init() { + if (initilized) { + return; + } + initilized = true; + + System.out.println("Init Nuiton Profiling ..."); + String portString = getOption(PORT_OPTION); + System.out.println("NuitonProfiling web port: " + portString); + if (portString != null) { + try { + int port = Integer.parseInt(portString); + if (port > 0) { + startWebService(port); + } + } catch (Exception eee) { + eee.printStackTrace(System.err); + System.err.println("Can't parse port number: "+ portString); + } + } + + final String file = getOption(AUTO_SAVE_FILENAME_OPTION); + System.out.println("NuitonProfiling auto save file: " + file); + if (file != null) { + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + Writer out = null; + try { + String stat = NuitonTrace.getStatisticsCSVAndClear(); + out = new FileWriter(file); + out.write(stat); + } catch (IOException eee) { + eee.printStackTrace(System.err); + System.err.println("Can't write Statistic file: " + file); + } finally { + IOUtils.closeQuietly(out); + } + } + }); + } + } + + /** + * Constructeur static pour initialise nuiton trace + * @since 2.7 + */ + static { + // attention aucune classe utilisee dans le init ne doit utiliser + // des classes surveille par NuitonTrace, sinon y'a un probleme d'init + // l'AOP voulant surveillee une method, mais l'AOP n'est pas encore + // initialise puisqu'on est dans l'init :( + init(); + } } Added: trunk/nuiton-profiling/src/main/resources/nuiton-js/wro.properties =================================================================== --- trunk/nuiton-profiling/src/main/resources/nuiton-js/wro.properties (rev 0) +++ trunk/nuiton-profiling/src/main/resources/nuiton-js/wro.properties 2013-01-22 17:06:19 UTC (rev 2468) @@ -0,0 +1,4 @@ +debug=false +preProcessors=cssDataUri,cssUrlRewriting,cssImport,semicolonAppender,cssMinJawr +postProcessors=cssVariables,jsMin +uriLocators=uri,classpath Added: trunk/nuiton-profiling/src/main/resources/nuiton-js/wro.xml =================================================================== --- trunk/nuiton-profiling/src/main/resources/nuiton-js/wro.xml (rev 0) +++ trunk/nuiton-profiling/src/main/resources/nuiton-js/wro.xml 2013-01-22 17:06:19 UTC (rev 2468) @@ -0,0 +1,29 @@ +<groups xmlns="http://www.isdc.ro/wro"> + + <group name='profiling-ui-web'> + <group-ref>jquery</group-ref> + <group-ref>bootstrap-responsive</group-ref> + <group-ref>jquery-ui</group-ref> + <group-ref>jquery-ui-base</group-ref> + <group-ref>jquery-ui-fr</group-ref> + <group-ref>jqplot</group-ref> + + <!-- plugin jqplot --> + <group-ref>jqplot.dateAxisRenderer</group-ref> + <group-ref>jqplot.canvasAxisTickRenderer</group-ref> + <group-ref>jqplot.canvasTextRenderer</group-ref> + <group-ref>jqplot.categoryAxisRenderer</group-ref> + <group-ref>jqplot.cursor</group-ref> + <group-ref>jqplot.highlighter</group-ref> + <group-ref>jqplot.barRenderer</group-ref> + + <!-- plugin jqgrid --> + <group-ref>jqgrid-fr</group-ref> + <group-ref>jqgrid</group-ref> + + <!-- scpecifique nuiton profiling --> + <css>/nuiton-profiling.css</css> + <js>/nuiton-profiling.js</js> + </group> + +</groups> Added: trunk/nuiton-profiling/src/main/resources/org/nuiton/profiling/web/index.html =================================================================== --- trunk/nuiton-profiling/src/main/resources/org/nuiton/profiling/web/index.html (rev 0) +++ trunk/nuiton-profiling/src/main/resources/org/nuiton/profiling/web/index.html 2013-01-22 17:06:19 UTC (rev 2468) @@ -0,0 +1,54 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE html> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <title>Nuiton Profiling</title> + <meta http-equiv="Content-Type" content="application/xhtml+xml; charset=UTF-8" /> + <meta charset="UTF-8"/> + + <link href="/profiling-ui-web.css" rel="stylesheet" type="text/css"/> + <script type="text/javascript" src="/profiling-ui-web.js"></script> + + </head> + <body> + <div class="navbar navbar-fixed-top"> + <div class="navbar-inner"> + <div class="container"> + <!-- .btn-navbar is used as the toggle for collapsed navbar content --> + <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse"> + <span class="icon-bar"></span> + <span class="icon-bar"></span> + <span class="icon-bar"></span> + </a> + + <a class="brand" href="http://maven-site.nuiton.org/nuiton-utils/nuiton-profiling/index.html"> + Nuiton Profiling + </a> + + <form action="javascript:requestNewData()"> + <input type="text" id="newDatas" placeholder="app url" value="http://[::1]:4488/data"/> + <input id="addStat" type="submit" value="Add"/> + </form> + <input id="clearStat" type="submit" value="Clear"/> + + Datas: <select id="datas"> + <option label="" value=""></option> + </select> + + </div> + </div> + </div> + + <div class="container"> + <div class="well"> + + <span id="download">download</span> + + <table id="treegrid"></table> + <div id="ptreegrid"></div> + + </div> + </div> + </body> +</html> Added: trunk/nuiton-profiling/src/main/resources/org/nuiton/profiling/web/nuiton-profiling.css =================================================================== --- trunk/nuiton-profiling/src/main/resources/org/nuiton/profiling/web/nuiton-profiling.css (rev 0) +++ trunk/nuiton-profiling/src/main/resources/org/nuiton/profiling/web/nuiton-profiling.css 2013-01-22 17:06:19 UTC (rev 2468) @@ -0,0 +1,6 @@ +body { + margin-top: 50px; +} +form { + display: inline; +} Added: trunk/nuiton-profiling/src/main/resources/org/nuiton/profiling/web/nuiton-profiling.js =================================================================== --- trunk/nuiton-profiling/src/main/resources/org/nuiton/profiling/web/nuiton-profiling.js (rev 0) +++ trunk/nuiton-profiling/src/main/resources/org/nuiton/profiling/web/nuiton-profiling.js 2013-01-22 17:06:19 UTC (rev 2468) @@ -0,0 +1,252 @@ +jQuery(document).ready(function($) { + $('#newDatas').val(window.location.origin); + + $('#clearStat').click(function () { + var url = $("#newDatas").val() + "/api/clear"; + $.get(url); + }); + + $('#download').click(function () { + var content = ''; + + var fields = ['method', 'call', 'time', 'timewithnest', 'min', 'mean', 'max']; + content += fields.join(';') + '\n'; + var rows = getGrid().jqGrid().getRowData(); + $.each( rows, function(i, info){ + var sep = ''; + for (var i=0; i<fields.length; i++) { + if (i == 0) { + content += sep + $(info[fields[i]]).text(); + } else { + content += sep + info[fields[i]] + } + sep = ';'; + } + content += '\n'; + }); + + window.URL = window.URL || window.webkitURL; + var a = document.createElement('a'); + var blob = new Blob([content], {'type':'text/csv'}); + a.href = window.URL.createObjectURL(blob); + a.download = 'nuitonprofiling.csv'; + a.click(); + window.URL.revokeObjectURL(a.href); + }); + +$("select#datas").change(function () { + var index = $("select#datas option:selected").val(); + if (index == "") { + loadData({}); + } else { + loadData(datas[index]); + } + }); + + getGrid().jqGrid({ + datatype: processrequest, + mtype: 'POST', + jsonReader : { + root:"rows", + page: "page", + total: "total", + records: "records", + repeatitems: false, + id: "idx" + }, + colNames:["idx", "method","call","time", "timewithnest", "min", "mean", "max"], + colModel:[ + {name:'idx', index:'idx', hidden: true, key:true, firstsortorder:'desc'}, + {name:'method', index:'method', align: "left", formatter:methodFormatter}, + {name:'call', index:'call', width:25, align:"right", sorttype:'float', firstsortorder:'desc'}, + {name:'time', index:'time', width:25, align:"right", formatter:timeFormatter, sorttype:'float', firstsortorder:'desc'}, + {name:'timewithnest', index:'timewithnest', width:25, align:"right", formatter:timeFormatter, sorttype:'float', firstsortorder:'desc'}, + {name:'min', index:'min', width:25, align:"right", sorttype:'float', formatter:timeFormatter, firstsortorder:'desc'}, + {name:'mean', index:'mean', width:25, align:"right", sorttype:'float', formatter:timeFormatter, firstsortorder:'desc'}, + {name:'max', index:'max', width:25 ,align:"right", sorttype:'float', formatter:timeFormatter, firstsortorder:'desc'}, + ], + height:'auto', + autowidth: true, + forceFit: true, + // rownumbers: true, + // treeGrid: true, + sortname: 'time', + sortorder: 'desc', + treeGridModel: 'adjacency', + ExpandColumn : 'method', + caption: "Info" + }); + + getGrid().jqGrid('filterToolbar', {searchOnEnter : false}); + +}); + + +function requestNewData() { + var url = $("#newDatas").val() + "/data"; + var select = $("#datas"); + getNewData(url, select); +} + +function getGrid() { + return $("#treegrid"); +} + +var currentData = {}; +var datas = []; + +/** + * Charge les data comme current data et force le rechargement de la grille + */ +function loadData(data) { + currentData = data; + getGrid().trigger("reloadGrid") +} + +/** + * Recupere les donnees d'une instance de Nuiton Profiling et l'ajoute + * a la liste des donnees disponibles + */ +function getNewData (url, select) { + $.ajax({ + type: "GET", + url: url, + dataType: "jsonp", + success: function(data){ + var date = new Date(); + select.append('<option label="'+date+'" value="'+datas.length+'"/>'); + datas.push(data) + } + }); +} + + + +/** + * Pour les arbres les parametres sont: + * nodeid: le nom du noeud qui a ete ouvert + * n_level: le level du noeud qui a ete ouvert + * parentid: le noeud pere + * + * Les autres parametres: + * _search=false + * nd=1358630099165 + * rows=10000 + * page=1 + * sidx= + * sord=asc + */ +function processrequest(args) { + var p = ""; + for (var f in args) { + p += " "+f+"="+args[f]; + } + console.log("r: '" + p + "'"); + var thegrid = getGrid()[0]; + + var jsonObject = createGridData(currentData, args, args.nodeid, args.n_level, args.sidx, args.sord); + + thegrid.addJSONData(jsonObject); +} + +function checkFilter(filter, row) { + var result = + (!filter.method || row.method.indexOf(filter.method) > 0) && + (!filter.call || row.call - filter.call > 0) && + (!filter.time || row.time - filter.time > 0) && + (!filter.timewithnest || row.timewithnest - filter.timewithnest > 0) && + (!filter.min || row.min - filter.min > 0) && + (!filter.mean || row.mean - filter.mean > 0) && + (!filter.max || row.max - filter.max > 0) + ; + return result; +} + +function createGridData(data, filter, parent, level, sortField, sortOrder) { + if (level === undefined) { + level = 0; + } else { + level++; + } + var rows = []; + if (data) { + $.each( data, function(method, info){ + var row = {}; + row.idx = level + '_' + rows.length; + row.level = level; + row.isLeaf = false; + row.parent = parent; + + row.method = info.method; + row.call = info.call; + row.time = info.total; + row.timewithnest = info.total_with_nest; + row.min = info.min; + row.mean = info.mean; + row.max = info.max; + + if (checkFilter(filter, row)) { + rows.push(row); + } + } ); + } + + sortRow(rows, sortField, sortOrder); + + var result = { + "total": rows.length, + "page": "1", + "records": rows.length, + "rows" : rows + }; + + return result; +} + +function sortRow(rows, field, order) { + if (field == 'method') { + rows.sort(); + } else { + rows.sort(function(a,b) { + var result = a[field] - b[field]; + return result; + }); + } + if (order == 'desc') { + rows.reverse(); + } +} + +function methodFormatter (cellvalue, options, rowObject) { + var shortName = cellvalue.replace(/[^\(]*\.([_\w\$]+\.[_\w\$]+).*/, "$1"); + var result = "<span title='" + cellvalue + "'>" + shortName + "</span>" + return result; +} + +function two(x) {return ((x>9)?"":"0")+x} +function three(x) {return ((x>99)?"":"0")+((x>9)?"":"0")+x} + +function timeFormatter (cellvalue, options, rowObject) { + // return cellvalue; + var ms = cellvalue * 1000; + var sec = Math.floor(ms/1000) + ms = ms % 1000 + t = three(ms) + + var min = Math.floor(sec/60) + sec = sec % 60 + t = two(sec) + "." + t + + var hr = Math.floor(min/60) + min = min % 60 + t = two(min) + ":" + t + + if (hr > 0) { + t = two(hr) + ":" + t + } + + return t; +// var time = new Date(0,0,0,0,0,0,cellvalue*1000); +// var result = time.getHours() + ":" + time.getMinutes() + ":" + time.getSeconds() + "." + time.getMilliseconds(); +// return result; +} Modified: trunk/nuiton-profiling/src/site/apt/index.apt.vm =================================================================== --- trunk/nuiton-profiling/src/site/apt/index.apt.vm 2013-01-13 11:57:03 UTC (rev 2467) +++ trunk/nuiton-profiling/src/site/apt/index.apt.vm 2013-01-22 17:06:19 UTC (rev 2468) @@ -39,9 +39,9 @@ Permet de tracer le temps passé dans les méthodes au cours de l'éxecution. Cette classe utilise l'AOP pour cela. Pour l'utiliser voir sa javadoc. -NuitonTrace pour une application Web lancée avec Maven +Configuration de NuitonTrace - Il faut créer le fichier src/main/webapp/WEB-INF/classes/META-INF/aop.xml + Il faut créer le fichier src/main/resources/META-INF/aop.xml pour indiquer les methodes que vous souhaitez surveiller, il contient par exemple @@ -78,6 +78,40 @@ </dependency> -------------------------------------------------------------------------------- + Vous pouvez ajouter une variable d'environnement pour qu'à la fin de l'exécution + de l'application les statistiques soient automatiquement sauvegardées + +-------------------------------------------------------------------------------- + export nuitonprofiling_autosavefile=/path/to/MonFichierDeStat.csv +-------------------------------------------------------------------------------- + + ou + +-------------------------------------------------------------------------------- + java -Dnuitonprofiling_autosavefile=/path/to/MonFichierDeStat.csv ... +-------------------------------------------------------------------------------- + + Vous pouvez ajouter une variable d'environnement pour qu'un serveur web soit + lancé et vous donne accès au statistique durant l'exécution de l'application + +-------------------------------------------------------------------------------- + export nuitonprofiling_webport=4488 +-------------------------------------------------------------------------------- + + ou + +-------------------------------------------------------------------------------- + java -Dnuitonprofiling_webport=4488 ... +-------------------------------------------------------------------------------- + +..Lors du lancement de la JVM il faut ajouter un agent sur la ligne de commande + +-------------------------------------------------------------------------------- + java -javaagent:\${maven_repo}/org/aspectj/aspectjweaver/${aspectjVersion}/aspectjweaver-${aspectjVersion}.jar ... +-------------------------------------------------------------------------------- + +pour une application Web lancée avec Maven + Il faut ajouter des options Maven -------------------------------------------------------------------------------- @@ -90,21 +124,11 @@ mvn jetty:run -------------------------------------------------------------------------------- - Le plus simple pour sortir les statistiques lorsque vous en avez besoin est de - créer un lien sur votre page web vers une jsp qui contient - --------------------------------------------------------------------------------- - <%@page import="org.nuiton.profiling.NuitonTrace"%> - <%@page contentType="text/csv" pageEncoding="UTF-8"%> - <%=NuitonTrace.getStatisticsCSVAndClear()%> --------------------------------------------------------------------------------- - Il ne vous reste plus qu'a analyser votre application NuitonTrace pour une application Web lancée dans un Tomcat - Procéder de la même façon que pour maven, le seul changement est le nom - de la variable d'environnement qui est + Il faut ajouter des options Tomcat -------------------------------------------------------------------------------- export JAVA_OPTS="$JAVA_OPTS -javaagent:${maven_repo}/org/aspectj/aspectjweaver/${aspectjVersion}/aspectjweaver-${aspectjVersion}.jar" Modified: trunk/nuiton-profiling/src/test/java/org/nuiton/profiling/NuitonTraceTest.java =================================================================== --- trunk/nuiton-profiling/src/test/java/org/nuiton/profiling/NuitonTraceTest.java 2013-01-13 11:57:03 UTC (rev 2467) +++ trunk/nuiton-profiling/src/test/java/org/nuiton/profiling/NuitonTraceTest.java 2013-01-22 17:06:19 UTC (rev 2468) @@ -28,10 +28,11 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; /** - * Launch this test with -javaagent:target/lib/aspectjweaver-1.6.11.jar + * Launch this test with -javaagent:target/lib/aspectjweaver-1.7.1.jar * * @author tchemit <chemit@codelutin.com> * @since 2.3 @@ -72,7 +73,7 @@ // pour pouvoir tester la meme chose avec un simple java -javaagent:... // par exemple: - // java -javaagent:target/lib/aspectjweaver-1.6.11.jar -classpath target/classes:target/lib/aspectjrt-1.6.11.jar:target/lib/aspectjweaver-1.6.11.jar:target/lib/commons-lang-2.6.jar:target/lib/commons-logging-1.1.1.jar:target/lib/junit-4.8.2.jar:target/lib/log4j-1.2.16.jar:target/test-classes org.nuiton.profiling.NuitonTraceTest + // java -javaagent:target/lib/aspectjweaver-1.7.1.jar -classpath target/classes:target/lib/aspectjrt-1.7.1.jar:target/lib/aspectjweaver-1.7.1.jar:target/lib/commons-lang-2.6.jar:target/lib/commons-logging-1.1.1.jar:target/lib/junit-4.8.2.jar:target/lib/log4j-1.2.16.jar:target/test-classes org.nuiton.profiling.NuitonTraceTest public static void main(String... args) { NuitonTraceTest t = new NuitonTraceTest();