1 /*
2  * Copyright 2008-2019 by Emeric Vernat
3  *
4  *     This file is part of Java Melody.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */

18 package net.bull.javamelody.internal.web.html;
19
20 import java.io.IOException;
21 import java.io.Writer;
22 import java.text.DecimalFormat;
23 import java.util.ArrayList;
24 import java.util.Collections;
25 import java.util.HashMap;
26 import java.util.List;
27 import java.util.Map;
28
29 import net.bull.javamelody.internal.common.I18N;
30 import net.bull.javamelody.internal.common.Parameters;
31 import net.bull.javamelody.internal.model.Counter;
32 import net.bull.javamelody.internal.model.CounterRequest;
33 import net.bull.javamelody.internal.model.CounterRequestContext;
34 import net.bull.javamelody.internal.model.PID;
35 import net.bull.javamelody.internal.model.Period;
36 import net.bull.javamelody.internal.model.ThreadInformations;
37
38 /**
39  * Partie du rapport html pour les contextes de requêtes en cours.
40  * @author Emeric Vernat
41  */

42 public class HtmlCounterRequestContextReport extends HtmlAbstractReport {
43     private final List<CounterRequestContext> rootCurrentContexts;
44     private final Map<String, HtmlCounterReport> counterReportsByCounterName;
45     private final Map<Long, ThreadInformations> threadInformationsByThreadId;
46     private final boolean childHitsDisplayed;
47     private final DecimalFormat integerFormat = I18N.createIntegerFormat();
48     private final long timeOfSnapshot = System.currentTimeMillis();
49     private final boolean stackTraceEnabled;
50     private final int maxContextsDisplayed;
51     private final boolean systemActionsEnabled = Parameters.isSystemActionsEnabled();
52     private final HtmlThreadInformationsReport htmlThreadInformationsReport;
53
54     /**
55      * Helper class used for html and pdf.
56      * @author Emeric Vernat
57      */

58     public static class CounterRequestContextReportHelper {
59         private final List<CounterRequestContext> contexts;
60         private final boolean childHitsDisplayed;
61         private final Map<String, CounterRequest> counterRequestsByRequestName = new HashMap<>();
62
63         public CounterRequestContextReportHelper(List<CounterRequestContext> contexts,
64                 boolean childHitsDisplayed) {
65             super();
66             assert contexts != null;
67             this.contexts = contexts;
68             this.childHitsDisplayed = childHitsDisplayed;
69         }
70
71         public List<int[]> getRequestValues() {
72             final List<int[]> result = new ArrayList<>();
73             final int contextsSize = contexts.size();
74             final int[] durationMeans = new int[contextsSize];
75             final int[] cpuTimes = new int[contextsSize];
76             final int[] cpuTimesMeans = new int[contextsSize];
77             //            final int[] allocatedKBytes = new int[contextsSize];
78             //            final int[] allocatedKBytesMean = new int[contextsSize];
79             int i = 0;
80             for (final CounterRequestContext context : contexts) {
81                 final CounterRequest counterRequest = getCounterRequest(context);
82                 durationMeans[i] = counterRequest.getMean();
83                 cpuTimesMeans[i] = counterRequest.getCpuTimeMean();
84                 if (cpuTimesMeans[i] >= 0) {
85                     cpuTimes[i] = context.getCpuTime();
86                 } else {
87                     cpuTimes[i] = -1;
88                 }
89                 //                allocatedKBytesMean[i] = counterRequest.getAllocatedKBytesMean();
90                 //                if (allocatedKBytesMean[i] >= 0) {
91                 //                    allocatedKBytes[i] = context.getAllocatedKBytes();
92                 //                } else {
93                 //                    allocatedKBytes[i] = -1;
94                 //                }
95                 i++;
96             }
97             result.add(durationMeans);
98             result.add(cpuTimes);
99             result.add(cpuTimesMeans);
100             //            result.add(allocatedKBytes);
101             //            result.add(allocatedKBytesMean);
102             if (childHitsDisplayed) {
103                 final int[] totalChildHits = new int[contextsSize];
104                 final int[] childHitsMeans = new int[contextsSize];
105                 final int[] totalChildDurationsSum = new int[contextsSize];
106                 final int[] childDurationsMeans = new int[contextsSize];
107                 i = 0;
108                 for (final CounterRequestContext context : contexts) {
109                     totalChildHits[i] = getValueOrIgnoreIfNoChildHitForContext(context,
110                             context.getTotalChildHits());
111                     final CounterRequest counterRequest = getCounterRequest(context);
112                     childHitsMeans[i] = getValueOrIgnoreIfNoChildHitForContext(context,
113                             counterRequest.getChildHitsMean());
114                     totalChildDurationsSum[i] = getValueOrIgnoreIfNoChildHitForContext(context,
115                             context.getTotalChildDurationsSum());
116                     childDurationsMeans[i] = getValueOrIgnoreIfNoChildHitForContext(context,
117                             counterRequest.getChildDurationsMean());
118                     i++;
119                 }
120                 result.add(totalChildHits);
121                 result.add(childHitsMeans);
122                 result.add(totalChildDurationsSum);
123                 result.add(childDurationsMeans);
124             }
125             return result;
126         }
127
128         private static int getValueOrIgnoreIfNoChildHitForContext(CounterRequestContext context,
129                 int value) {
130             if (context.getParentCounter().getChildCounterName() == null) {
131                 // si le compteur parent du contexte n'a pas de compteur fils
132                 // (comme le compteur sql et au contraire du compteur http),
133                 // alors la valeur de childHits ou childDurations n'a pas de sens
134                 // (comme les hits sql fils pour une requête sql)
135                 return -1;
136             }
137             return value;
138         }
139
140         CounterRequest getCounterRequest(CounterRequestContext context) {
141             final String requestName = context.getRequestName();
142             CounterRequest counterRequest = counterRequestsByRequestName.get(requestName);
143             if (counterRequest == null) {
144                 counterRequest = context.getParentCounter().getCounterRequest(context);
145                 counterRequestsByRequestName.put(requestName, counterRequest);
146             }
147             return counterRequest;
148         }
149     }
150
151     HtmlCounterRequestContextReport(List<CounterRequestContext> rootCurrentContexts,
152             Map<String, HtmlCounterReport> counterReportsByCounterName,
153             List<ThreadInformations> threadInformationsList, boolean stackTraceEnabled,
154             int maxContextsDisplayed, Writer writer) {
155         super(writer);
156         assert rootCurrentContexts != null;
157         assert threadInformationsList != null;
158
159         this.rootCurrentContexts = rootCurrentContexts;
160         if (counterReportsByCounterName == null) {
161             this.counterReportsByCounterName = new HashMap<>();
162         } else {
163             this.counterReportsByCounterName = counterReportsByCounterName;
164         }
165         this.threadInformationsByThreadId = new HashMap<>(threadInformationsList.size());
166         for (final ThreadInformations threadInformations : threadInformationsList) {
167             this.threadInformationsByThreadId.put(threadInformations.getId(), threadInformations);
168         }
169         boolean oneRootHasChild = false;
170         for (final CounterRequestContext rootCurrentContext : rootCurrentContexts) {
171             if (rootCurrentContext.hasChildHits()) {
172                 oneRootHasChild = true;
173                 break;
174             }
175         }
176         this.childHitsDisplayed = oneRootHasChild;
177         this.htmlThreadInformationsReport = new HtmlThreadInformationsReport(threadInformationsList,
178                 stackTraceEnabled, writer);
179         this.stackTraceEnabled = stackTraceEnabled;
180         this.maxContextsDisplayed = maxContextsDisplayed;
181     }
182
183     @Override
184     void toHtml() throws IOException {
185         if (rootCurrentContexts.isEmpty()) {
186             writeln("#Aucune_requete_en_cours#");
187             return;
188         }
189         writeContexts(Collections.singletonList(rootCurrentContexts.get(0)));
190         writeln("<div align='right' class='noPrint'>");
191         writeln(getFormattedString("nb_requete_en_cours",
192                 integerFormat.format(rootCurrentContexts.size())));
193         final String separator = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
194         if (isPdfEnabled()) {
195             writeln(separator);
196             write("<a href='?part=currentRequests&amp;format=pdf' title='#afficher_PDF#'>");
197             write("<img src='?resource=pdf.png' alt='#PDF#'/> #PDF#</a>");
198         }
199         writeln(separator);
200         if (rootCurrentContexts.size() <= maxContextsDisplayed) {
201             writeln("<a href='?part=currentRequests' title='#Requetes_en_cours#'>");
202             writeln("<img src='?resource=hourglass.png' alt='#Requetes_en_cours#' width='16' height='16'/>");
203             writeln("#Voir_dans_une_nouvelle_page#</a>");
204             writeln(separator);
205
206             final String counterName = rootCurrentContexts.get(0).getParentCounter().getName();
207             // PID dans l'id du div pour concaténation de pages et affichage dans serveur de collecte
208             writeShowHideLink("contextDetails" + counterName + PID.getPID(), "#Details#");
209             writeln(separator);
210             writeln("</div> ");
211             writeln("<div id='contextDetails" + counterName + PID.getPID()
212                     + "' class='displayNone'>");
213             writeContexts(rootCurrentContexts);
214             writeln("</div>");
215         } else {
216             // le nombre de requêtes en cours dépasse le maximum pour être affiché dans le rapport
217             // principal, donc on affiche seulement un lien vers la page à part
218             writeln("<a href='?part=currentRequests' title='#Requetes_en_cours#'>#Details#</a>");
219             writeln(separator);
220             writeln("</div> ");
221         }
222     }
223
224     void writeTitleAndDetails() throws IOException {
225         writeTitle("hourglass.png", getString("Requetes_en_cours"));
226         write("<br/>");
227
228         if (rootCurrentContexts.isEmpty()) {
229             writeln("#Aucune_requete_en_cours#");
230             return;
231         }
232         writeContexts(rootCurrentContexts);
233
234         writeln("<div align='right'>");
235         writeln(getFormattedString("nb_requete_en_cours",
236                 integerFormat.format(rootCurrentContexts.size())));
237         writeln("</div>");
238     }
239
240     private void writeContexts(List<CounterRequestContext> contexts) throws IOException {
241         boolean displayRemoteUser = false;
242         for (final CounterRequestContext context : contexts) {
243             if (context.getRemoteUser() != null) {
244                 displayRemoteUser = true;
245                 break;
246             }
247         }
248         final HtmlTable table = new HtmlTable();
249         table.beginTable(getString("Requetes_en_cours"));
250         write("<th>#Thread#</th>");
251         if (displayRemoteUser) {
252             write("<th>#Utilisateur#</th>");
253         }
254         write("<th>#Requete#</th>");
255         write("<th class='sorttable_numeric'>#Duree_ecoulee#</th><th class='sorttable_numeric'>#Temps_moyen#</th>");
256         write("<th class='sorttable_numeric'>#Temps_cpu#</th><th class='sorttable_numeric'>#Temps_cpu_moyen#</th>");
257         // pas la peine de surcharger avec les Ko alloués
258         //        write("<th class='sorttable_numeric'>#Ko_alloues#</th><th class='sorttable_numeric'>#Ko_alloues_moyens#</th>");
259         // rq : tous ces contextes viennent du même compteur donc peu importe lequel des parentCounter
260         if (childHitsDisplayed) {
261             final String childCounterName = contexts.get(0).getParentCounter()
262                     .getChildCounterName();
263             write("<th class='sorttable_numeric'>"
264                     + getFormattedString("hits_fils", childCounterName));
265             write("</th><th class='sorttable_numeric'>"
266                     + getFormattedString("hits_fils_moyens", childCounterName));
267             write("</th><th class='sorttable_numeric'>"
268                     + getFormattedString("temps_fils", childCounterName));
269             write("</th><th class='sorttable_numeric'>"
270                     + getFormattedString("temps_fils_moyen", childCounterName) + "</th>");
271         }
272         if (stackTraceEnabled) {
273             write("<th>#Methode_executee#</th>");
274         }
275         if (systemActionsEnabled) {
276             writeln("<th class='noPrint'>#Tuer#</th>");
277         }
278         for (final CounterRequestContext context : contexts) {
279             table.nextRow();
280             writeContext(context, displayRemoteUser);
281         }
282         table.endTable();
283     }
284
285     private void writeContext(CounterRequestContext rootContext, boolean displayRemoteUser)
286             throws IOException {
287         // attention, cela ne marcherait pas sur le serveur de collecte, à partir du seul threadId
288         // s'il y a plusieurs instances en cluster
289         final ThreadInformations threadInformations = threadInformationsByThreadId
290                 .get(rootContext.getThreadId());
291         write("<td valign='top'>");
292         final String espace = "&nbsp;";
293         if (threadInformations == null) {
294             write(espace); // un décalage n'a pas permis de récupérer le thread de ce context
295         } else {
296             htmlThreadInformationsReport.writeThreadWithStackTrace(threadInformations);
297         }
298         if (displayRemoteUser) {
299             write("</td> <td valign='top'>");
300             if (rootContext.getRemoteUser() == null) {
301                 write(espace);
302             } else {
303                 write(rootContext.getRemoteUser());
304             }
305         }
306         final List<CounterRequestContext> contexts = new ArrayList<>(3);
307         contexts.add(rootContext);
308         contexts.addAll(rootContext.getChildContexts());
309         final CounterRequestContextReportHelper counterRequestContextReportHelper = new CounterRequestContextReportHelper(
310                 contexts, childHitsDisplayed);
311         write("</td> <td>");
312         writeRequests(contexts, counterRequestContextReportHelper);
313
314         write("</td> <td align='right' valign='top'>");
315         writeDurations(contexts);
316
317         for (final int[] requestValues : counterRequestContextReportHelper.getRequestValues()) {
318             writeRequestValues(requestValues);
319         }
320
321         if (stackTraceEnabled) {
322             write("</td> <td valign='top'>");
323             if (threadInformations == null) {
324                 write(espace); // un décalage n'a pas permis de récupérer le thread de ce context
325             } else {
326                 htmlThreadInformationsReport.writeExecutedMethod(threadInformations);
327             }
328         }
329         if (threadInformations == null) {
330             write("</td> <td class='noPrint'>");
331             write(espace); // un décalage n'a pas permis de récupérer le thread de ce context
332         } else {
333             htmlThreadInformationsReport.writeKillThread(threadInformations);
334         }
335         write("</td>");
336     }
337
338     private void writeRequests(List<CounterRequestContext> contexts,
339             CounterRequestContextReportHelper counterRequestContextReportHelper)
340             throws IOException {
341         int margin = 0;
342         for (final CounterRequestContext context : contexts) {
343             write("<div data-margin-left-px='");
344             write(Integer.toString(margin));
345             writeln("' class='wrappedText'>");
346             writeRequest(context, counterRequestContextReportHelper);
347             write("</div>");
348             margin += 10;
349         }
350     }
351
352     private void writeRequest(CounterRequestContext context,
353             CounterRequestContextReportHelper counterRequestContextReportHelper)
354             throws IOException {
355         final Counter parentCounter = context.getParentCounter();
356         if (parentCounter.getIconName() != null) {
357             write("<img src='?resource=");
358             write(parentCounter.getIconName());
359             write("' alt='");
360             write(parentCounter.getName());
361             write("' width='16' height='16' />&nbsp;");
362         }
363         // la période n'a pas d'importance pour writeRequestGraph
364         final HtmlCounterReport counterReport = getCounterReport(parentCounter, Period.TOUT);
365         final CounterRequest counterRequest = counterRequestContextReportHelper
366                 .getCounterRequest(context);
367         counterReport.writeRequestName(counterRequest.getId(), context.getCompleteRequestName(),
368                 HtmlCounterReport.isRequestGraphDisplayed(parentCounter), truefalse);
369     }
370
371     private HtmlCounterReport getCounterReport(Counter parentCounter, Period period) {
372         HtmlCounterReport counterReport = counterReportsByCounterName.get(parentCounter.getName());
373         if (counterReport == null) {
374             counterReport = new HtmlCounterReport(parentCounter, period.getRange(), getWriter());
375             counterReportsByCounterName.put(parentCounter.getName(), counterReport);
376         }
377         return counterReport;
378     }
379
380     private void writeDurations(List<CounterRequestContext> contexts) throws IOException {
381         boolean first = true;
382         for (final CounterRequestContext context : contexts) {
383             if (!first) {
384                 writeln("<br/>");
385             }
386             final int duration = context.getDuration(timeOfSnapshot);
387
388             final Counter parentCounter = context.getParentCounter();
389             if (parentCounter.getIconName() != null) {
390                 // on remet l'icône ici avec la durée en plus de l'icône sur la requête http, façade
391                 // ou sql car la requête peut avoir des sauts de ligne (par ex. par manque de place)
392                 // et dans ce cas les durées ne sont plus du tout alignées
393                 write("<img src='?resource=");
394                 write(parentCounter.getIconName());
395                 write("' alt='");
396                 write(parentCounter.getName());
397                 write("' width='16' height='16' />&nbsp;");
398             }
399
400             final HtmlCounterReport counterReport = counterReportsByCounterName
401                     .get(parentCounter.getName());
402             write("<span class='");
403             write(counterReport.getSlaHtmlClass(duration));
404             write("'>");
405             write(integerFormat.format(duration));
406             write("</span>");
407             first = false;
408         }
409     }
410
411     private void writeRequestValues(int[] requestValues) throws IOException {
412         write("</td> <td align='right' valign='top'>");
413         boolean first = true;
414         for (final int value : requestValues) {
415             if (!first) {
416                 writeln("<br/>");
417             }
418             if (value == -1) {
419                 write("&nbsp;");
420             } else {
421                 write(integerFormat.format(value));
422             }
423             first = false;
424         }
425     }
426 }
427