/* * Copyright 2008-2017 by Emeric Vernat * * This file is part of Java Melody. * * 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 net.bull.javamelody; import java.io.IOException; import java.io.Writer; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Partie du rapport html pour les contextes de requêtes en cours. * @author Emeric Vernat */ class HtmlCounterRequestContextReport extends HtmlAbstractReport { private final List<CounterRequestContext> rootCurrentContexts; private final Map<String, HtmlCounterReport> counterReportsByCounterName; private final Map<Long, ThreadInformations> threadInformationsByThreadId; private final boolean childHitsDisplayed; private final DecimalFormat integerFormat = I18N.createIntegerFormat(); private final long timeOfSnapshot = System.currentTimeMillis(); private final boolean stackTraceEnabled; private final int maxContextsDisplayed; private final boolean systemActionsEnabled = Parameters.isSystemActionsEnabled(); private final HtmlThreadInformationsReport htmlThreadInformationsReport; // classe utilitaire utilisée pour html et pdf static class CounterRequestContextReportHelper { private final List<CounterRequestContext> contexts; private final boolean childHitsDisplayed; private final Map<String, CounterRequest> counterRequestsByRequestName = new HashMap<String, CounterRequest>(); CounterRequestContextReportHelper(List<CounterRequestContext> contexts, boolean childHitsDisplayed) { super(); assert contexts != null; this.contexts = contexts; this.childHitsDisplayed = childHitsDisplayed; } List<int[]> getRequestValues() { final List<int[]> result = new ArrayList<int[]>(); final int contextsSize = contexts.size(); final int[] durationMeans = new int[contextsSize]; final int[] cpuTimes = new int[contextsSize]; final int[] cpuTimesMeans = new int[contextsSize]; int i = 0; for (final CounterRequestContext context : contexts) { final CounterRequest counterRequest = getCounterRequest(context); durationMeans[i] = counterRequest.getMean(); cpuTimesMeans[i] = counterRequest.getCpuTimeMean(); if (cpuTimesMeans[i] >= 0) { cpuTimes[i] = context.getCpuTime(); } else { cpuTimes[i] = -1; } i++; } result.add(durationMeans); result.add(cpuTimes); result.add(cpuTimesMeans); if (childHitsDisplayed) { final int[] totalChildHits = new int[contextsSize]; final int[] childHitsMeans = new int[contextsSize]; final int[] totalChildDurationsSum = new int[contextsSize]; final int[] childDurationsMeans = new int[contextsSize]; i = 0; for (final CounterRequestContext context : contexts) { totalChildHits[i] = getValueOrIgnoreIfNoChildHitForContext(context, context.getTotalChildHits()); final CounterRequest counterRequest = getCounterRequest(context); childHitsMeans[i] = getValueOrIgnoreIfNoChildHitForContext(context, counterRequest.getChildHitsMean()); totalChildDurationsSum[i] = getValueOrIgnoreIfNoChildHitForContext(context, context.getTotalChildDurationsSum()); childDurationsMeans[i] = getValueOrIgnoreIfNoChildHitForContext(context, counterRequest.getChildDurationsMean()); i++; } result.add(totalChildHits); result.add(childHitsMeans); result.add(totalChildDurationsSum); result.add(childDurationsMeans); } return result; } private static int getValueOrIgnoreIfNoChildHitForContext(CounterRequestContext context, int value) { if (context.getParentCounter().getChildCounterName() == null) { // si le compteur parent du contexte n'a pas de compteur fils // (comme le compteur sql et au contraire du compteur http), // alors la valeur de childHits ou childDurations n'a pas de sens // (comme les hits sql fils pour une requête sql) return -1; } return value; } CounterRequest getCounterRequest(CounterRequestContext context) { final String requestName = context.getRequestName(); CounterRequest counterRequest = counterRequestsByRequestName.get(requestName); if (counterRequest == null) { counterRequest = context.getParentCounter().getCounterRequest(context); counterRequestsByRequestName.put(requestName, counterRequest); } return counterRequest; } } HtmlCounterRequestContextReport(List<CounterRequestContext> rootCurrentContexts, Map<String, HtmlCounterReport> counterReportsByCounterName, List<ThreadInformations> threadInformationsList, boolean stackTraceEnabled, int maxContextsDisplayed, Writer writer) { super(writer); assert rootCurrentContexts != null; assert threadInformationsList != null; this.rootCurrentContexts = rootCurrentContexts; if (counterReportsByCounterName == null) { this.counterReportsByCounterName = new HashMap<String, HtmlCounterReport>(); } else { this.counterReportsByCounterName = counterReportsByCounterName; } this.threadInformationsByThreadId = new HashMap<Long, ThreadInformations>( threadInformationsList.size()); for (final ThreadInformations threadInformations : threadInformationsList) { this.threadInformationsByThreadId.put(threadInformations.getId(), threadInformations); } boolean oneRootHasChild = false; for (final CounterRequestContext rootCurrentContext : rootCurrentContexts) { if (rootCurrentContext.hasChildHits()) { oneRootHasChild = true; break; } } this.childHitsDisplayed = oneRootHasChild; this.htmlThreadInformationsReport = new HtmlThreadInformationsReport(threadInformationsList, stackTraceEnabled, writer); this.stackTraceEnabled = stackTraceEnabled; this.maxContextsDisplayed = maxContextsDisplayed; } @Override void toHtml() throws IOException { if (rootCurrentContexts.isEmpty()) { writeln("#Aucune_requete_en_cours#"); return; } writeContexts(Collections.singletonList(rootCurrentContexts.get(0))); writeln("<div align='right' class='noPrint'>"); writeln(getFormattedString("nb_requete_en_cours", integerFormat.format(rootCurrentContexts.size()))); final String separator = "     "; if (isPdfEnabled()) { writeln(separator); write("<a href='?part=currentRequests&format=pdf' title='#afficher_PDF#'>"); write("<img src='?resource=pdf.png' alt='#PDF#'/> #PDF#</a>"); } writeln(separator); if (rootCurrentContexts.size() <= maxContextsDisplayed) { writeln("<a href='?part=currentRequests' title='#Requetes_en_cours#'>"); writeln("<img src='?resource=hourglass.png' alt='#Requetes_en_cours#' width='16' height='16'/>"); writeln("#Voir_dans_une_nouvelle_page#</a>"); writeln(separator); final String counterName = rootCurrentContexts.get(0).getParentCounter().getName(); // PID dans l'id du div pour concaténation de pages et affichage dans serveur de collecte writeShowHideLink("contextDetails" + counterName + PID.getPID(), "#Details#"); writeln(separator); writeln("</div> "); writeln("<div id='contextDetails" + counterName + PID.getPID() + "' style='display: none;'>"); writeContexts(rootCurrentContexts); writeln("</div>"); } else { // le nombre de requêtes en cours dépasse le maximum pour être affiché dans le rapport // principal, donc on affiche seulement un lien vers la page à part writeln("<a href='?part=currentRequests' title='#Requetes_en_cours#'>#Details#</a>"); writeln(separator); writeln("</div> "); } } void writeTitleAndDetails() throws IOException { writeTitle("hourglass.png", getString("Requetes_en_cours")); write("<br/>"); if (rootCurrentContexts.isEmpty()) { writeln("#Aucune_requete_en_cours#"); return; } writeContexts(rootCurrentContexts); writeln("<div align='right'>"); writeln(getFormattedString("nb_requete_en_cours", integerFormat.format(rootCurrentContexts.size()))); writeln("</div>"); } private void writeContexts(List<CounterRequestContext> contexts) throws IOException { boolean displayRemoteUser = false; for (final CounterRequestContext context : contexts) { if (context.getRemoteUser() != null) { displayRemoteUser = true; break; } } final HtmlTable table = new HtmlTable(); table.beginTable(getString("Requetes_en_cours")); write("<th>#Thread#</th>"); if (displayRemoteUser) { write("<th>#Utilisateur#</th>"); } write("<th>#Requete#</th>"); write("<th class='sorttable_numeric'>#Duree_ecoulee#</th><th class='sorttable_numeric'>#Temps_moyen#</th>"); write("<th class='sorttable_numeric'>#Temps_cpu#</th><th class='sorttable_numeric'>#Temps_cpu_moyen#</th>"); // rq : tous ces contextes viennent du même compteur donc peu importe lequel des parentCounter if (childHitsDisplayed) { final String childCounterName = contexts.get(0).getParentCounter() .getChildCounterName(); write("<th class='sorttable_numeric'>" + getFormattedString("hits_fils", childCounterName)); write("</th><th class='sorttable_numeric'>" + getFormattedString("hits_fils_moyens", childCounterName)); write("</th><th class='sorttable_numeric'>" + getFormattedString("temps_fils", childCounterName)); write("</th><th class='sorttable_numeric'>" + getFormattedString("temps_fils_moyen", childCounterName) + "</th>"); } if (stackTraceEnabled) { write("<th>#Methode_executee#</th>"); } if (systemActionsEnabled) { writeln("<th class='noPrint'>#Tuer#</th>"); } for (final CounterRequestContext context : contexts) { table.nextRow(); writeContext(context, displayRemoteUser); } table.endTable(); } private void writeContext(CounterRequestContext rootContext, boolean displayRemoteUser) throws IOException { // attention, cela ne marcherait pas sur le serveur de collecte, à partir du seul threadId // s'il y a plusieurs instances en cluster final ThreadInformations threadInformations = threadInformationsByThreadId .get(rootContext.getThreadId()); write("<td valign='top'>"); final String espace = " "; if (threadInformations == null) { write(espace); // un décalage n'a pas permis de récupérer le thread de ce context } else { htmlThreadInformationsReport.writeThreadWithStackTrace(threadInformations); } if (displayRemoteUser) { write("</td> <td valign='top'>"); if (rootContext.getRemoteUser() == null) { write(espace); } else { write(rootContext.getRemoteUser()); } } final List<CounterRequestContext> contexts = new ArrayList<CounterRequestContext>(3); contexts.add(rootContext); contexts.addAll(rootContext.getChildContexts()); final CounterRequestContextReportHelper counterRequestContextReportHelper = new CounterRequestContextReportHelper( contexts, childHitsDisplayed); write("</td> <td>"); writeRequests(contexts, counterRequestContextReportHelper); write("</td> <td align='right' valign='top'>"); writeDurations(contexts); for (final int[] requestValues : counterRequestContextReportHelper.getRequestValues()) { writeRequestValues(requestValues); } if (stackTraceEnabled) { write("</td> <td valign='top'>"); if (threadInformations == null) { write(espace); // un décalage n'a pas permis de récupérer le thread de ce context } else { htmlThreadInformationsReport.writeExecutedMethod(threadInformations); } } if (threadInformations == null) { write("</td> <td class='noPrint'>"); write(espace); // un décalage n'a pas permis de récupérer le thread de ce context } else { htmlThreadInformationsReport.writeKillThread(threadInformations); } write("</td>"); } private void writeRequests(List<CounterRequestContext> contexts, CounterRequestContextReportHelper counterRequestContextReportHelper) throws IOException { int margin = 0; for (final CounterRequestContext context : contexts) { write("<div style='margin-left: "); write(Integer.toString(margin)); writeln("px;' class='wrappedText'>"); writeRequest(context, counterRequestContextReportHelper); write("</div>"); margin += 10; } } private void writeRequest(CounterRequestContext context, CounterRequestContextReportHelper counterRequestContextReportHelper) throws IOException { final Counter parentCounter = context.getParentCounter(); if (parentCounter.getIconName() != null) { write("<img src='?resource="); write(parentCounter.getIconName()); write("' alt='"); write(parentCounter.getName()); write("' width='16' height='16' /> "); } // la période n'a pas d'importance pour writeRequestGraph final HtmlCounterReport counterReport = getCounterReport(parentCounter, Period.TOUT); final CounterRequest counterRequest = counterRequestContextReportHelper .getCounterRequest(context); counterReport.writeRequestName(counterRequest.getId(), context.getCompleteRequestName(), HtmlCounterReport.isRequestGraphDisplayed(parentCounter), true, false); } private HtmlCounterReport getCounterReport(Counter parentCounter, Period period) { HtmlCounterReport counterReport = counterReportsByCounterName.get(parentCounter.getName()); if (counterReport == null) { counterReport = new HtmlCounterReport(parentCounter, period.getRange(), getWriter()); counterReportsByCounterName.put(parentCounter.getName(), counterReport); } return counterReport; } private void writeDurations(List<CounterRequestContext> contexts) throws IOException { boolean first = true; for (final CounterRequestContext context : contexts) { if (!first) { writeln("<br/>"); } final int duration = context.getDuration(timeOfSnapshot); final Counter parentCounter = context.getParentCounter(); if (parentCounter.getIconName() != null) { // on remet l'icône ici avec la durée en plus de l'icône sur la requête http, façade // ou sql car la requête peut avoir des sauts de ligne (par ex. par manque de place) // et dans ce cas les durées ne sont plus du tout alignées write("<img src='?resource="); write(parentCounter.getIconName()); write("' alt='"); write(parentCounter.getName()); write("' width='16' height='16' /> "); } final HtmlCounterReport counterReport = counterReportsByCounterName .get(parentCounter.getName()); write("<span class='"); write(counterReport.getSlaHtmlClass(duration)); write("'>"); write(integerFormat.format(duration)); write("</span>"); first = false; } } private void writeRequestValues(int[] requestValues) throws IOException { write("</td> <td align='right' valign='top'>"); boolean first = true; for (final int value : requestValues) { if (!first) { writeln("<br/>"); } if (value == -1) { write(" "); } else { write(integerFormat.format(value)); } first = false; } } }