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.model;
19
20 import java.util.ArrayList;
21 import java.util.Collections;
22 import java.util.HashMap;
23 import java.util.List;
24 import java.util.Map;
25
26 import net.bull.javamelody.Parameter;
27 import net.bull.javamelody.internal.model.Counter.CounterRequestComparator;
28
29 /**
30  * Agrégation des requêtes d'un compteur pour l'affichage d'une synthèse.
31  * @author Emeric Vernat
32  */

33 public class CounterRequestAggregation {
34     private final Counter counter;
35     private final List<CounterRequest> requests;
36     private final CounterRequest globalRequest;
37     private final int warningThreshold;
38     private final int severeThreshold;
39     private final boolean responseSizeDisplayed;
40     private final boolean childHitsDisplayed;
41     private final boolean timesDisplayed;
42     private final boolean cpuTimesDisplayed;
43     private final boolean allocatedKBytesDisplayed;
44     private final CounterRequest warningRequest;
45     private final CounterRequest severeRequest;
46
47     public CounterRequestAggregation(Counter counter) {
48         super();
49         assert counter != null;
50         this.counter = counter;
51         if (counter.isErrorCounter()) {
52             this.requests = counter.getOrderedByHitsRequests();
53         } else {
54             this.requests = counter.getOrderedRequests();
55         }
56         assert requests != null;
57
58         final String counterName = counter.getName();
59         this.globalRequest = new CounterRequest(counterName + " global", counterName);
60         for (final CounterRequest request : requests) {
61             // ici, pas besoin de synchronized sur request puisque ce sont des clones indépendants
62             globalRequest.addHits(request);
63         }
64
65         // on n'affiche pas la colonne "Taille de réponse" si elle est négative car non défini
66         // (pour les requêtes sql par exemple)
67         this.responseSizeDisplayed = globalRequest.getResponseSizeMean() >= 0L;
68         this.childHitsDisplayed = globalRequest.hasChildHits();
69         this.timesDisplayed = globalRequest.getMean() >= 0;
70         this.cpuTimesDisplayed = globalRequest.getCpuTimeMean() >= 0;
71         this.allocatedKBytesDisplayed = globalRequest.getAllocatedKBytesMean() >= 0;
72
73         // globalMean et globalStandardDeviation sont utilisées pour déterminer
74         // les seuils des couleurs des moyennes dans le tableau quand les paramètres
75         // warning-threshold-millis et severe-threshold-millis ne sont pas définis
76         final int globalMean = globalRequest.getMean();
77         final int globalStandardDeviation = globalRequest.getStandardDeviation();
78         this.warningThreshold = getThreshold(Parameter.WARNING_THRESHOLD_MILLIS,
79                 globalMean + globalStandardDeviation);
80         this.severeThreshold = getThreshold(Parameter.SEVERE_THRESHOLD_MILLIS,
81                 globalMean + 2 * globalStandardDeviation);
82
83         // synthèse globale avec requêtes global, warning et severe
84         // on calcule les pourcentages de requêtes dont les temps (moyens!) dépassent les 2 seuils
85         this.warningRequest = new CounterRequest(counterName + " warning", counterName);
86         this.severeRequest = new CounterRequest(counterName + " severe", counterName);
87         for (final CounterRequest request : requests) {
88             // ici, pas besoin de synchronized sur request puisque ce sont des clones indépendants
89             final int mean = request.getMean();
90             if (mean > severeThreshold) {
91                 severeRequest.addHits(request);
92             } else if (mean > warningThreshold) {
93                 warningRequest.addHits(request);
94             }
95             // les requêtes sous warning ne sont pas décomptées dans la synthèse autrement que dans global
96         }
97     }
98
99     private static int getThreshold(Parameter parameter, int defaultValue) {
100         final String param = parameter.getValue();
101         if (param == null) {
102             return defaultValue;
103         }
104         final int threshold = Integer.parseInt(param);
105         if (threshold <= 0) {
106             throw new IllegalStateException(
107                     "Le paramètre " + parameter.getCode() + " doit être > 0");
108         }
109         return threshold;
110     }
111
112     public List<CounterRequest> getRequests() {
113         return requests;
114     }
115
116     public CounterRequest getGlobalRequest() {
117         return globalRequest;
118     }
119
120     public CounterRequest getWarningRequest() {
121         return warningRequest;
122     }
123
124     public CounterRequest getSevereRequest() {
125         return severeRequest;
126     }
127
128     public int getWarningThreshold() {
129         return warningThreshold;
130     }
131
132     public int getSevereThreshold() {
133         return severeThreshold;
134     }
135
136     public boolean isResponseSizeDisplayed() {
137         return responseSizeDisplayed;
138     }
139
140     public boolean isChildHitsDisplayed() {
141         return childHitsDisplayed;
142     }
143
144     public boolean isTimesDisplayed() {
145         return timesDisplayed;
146     }
147
148     public boolean isCpuTimesDisplayed() {
149         return cpuTimesDisplayed;
150     }
151
152     public boolean isAllocatedKBytesDisplayed() {
153         return allocatedKBytesDisplayed;
154     }
155
156     public List<CounterRequest> getRequestsAggregatedOrFilteredByClassName(String requestId) {
157         final List<CounterRequest> requestsAggregatedByClassName = getRequestsAggregatedByClassName();
158         final List<CounterRequest> requestList;
159         if (requestId == null) {
160             // on va afficher la liste des requêtes aggrégées par classe
161             requestList = requestsAggregatedByClassName;
162         } else {
163             // on a un paramètre requestId, ie que l'utilisateur a cliqué sur un lien de détail
164             // des requêtes pour une classe, et on va afficher la liste des requêtes non aggrégées
165             // mais filtrées pour cette classe
166             requestList = new ArrayList<>();
167             // on recherche d'abord le nom de la classe à partir de requestId
168             for (final CounterRequest requestAggregated : requestsAggregatedByClassName) {
169                 if (requestId.equals(requestAggregated.getId())) {
170                     final String className = requestAggregated.getName();
171                     // et on filtre les requêtes pour cette classe
172                     requestList.addAll(getRequestsFilteredByClassName(className));
173                     break;
174                 }
175             }
176         }
177         return Collections.unmodifiableList(requestList);
178     }
179
180     private List<CounterRequest> getRequestsAggregatedByClassName() {
181         assert counter.isBusinessFacadeCounter();
182         final Map<String, CounterRequest> requestMap = new HashMap<>();
183         final String counterName = counter.getName();
184         for (final CounterRequest request : getRequests()) {
185             final String className = getClassNameFromRequest(request);
186             CounterRequest global = requestMap.get(className);
187             if (global == null) {
188                 global = new CounterRequest(className, counterName);
189                 requestMap.put(className, global);
190             }
191             global.addHits(request);
192         }
193         // on trie par la somme des durées
194         final List<CounterRequest> requestList = new ArrayList<>(requestMap.values());
195         if (requestList.size() > 1) {
196             Collections.sort(requestList, Collections.reverseOrder(new CounterRequestComparator()));
197         }
198         return requestList;
199     }
200
201     private List<CounterRequest> getRequestsFilteredByClassName(String className) {
202         assert counter.isBusinessFacadeCounter();
203         assert className != null;
204         final List<CounterRequest> requestList = new ArrayList<>();
205         for (final CounterRequest request : getRequests()) {
206             final String requestClassName = getClassNameFromRequest(request);
207             if (className.equals(requestClassName)) {
208                 requestList.add(request);
209             }
210         }
211         if (requestList.size() > 1) {
212             Collections.sort(requestList, Collections.reverseOrder(new CounterRequestComparator()));
213         }
214         return requestList;
215     }
216
217     private static String getClassNameFromRequest(CounterRequest request) {
218         final int lastIndexOf = request.getName().lastIndexOf('.');
219         if (lastIndexOf != -1) {
220             return request.getName().substring(0, lastIndexOf);
221         }
222         return request.getName();
223     }
224 }
225