/
CounterRequestContext.java
336 lines (302 loc) · 12.3 KB
/
CounterRequestContext.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
/*
* 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.internal.model;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import net.bull.javamelody.internal.model.CounterRequest.ICounterRequestContext;
/**
* Contexte d'une requête pour un compteur (non synchronisé).
* Le contexte sera initialisé dans un ThreadLocal puis sera utilisé à l'enregistrement de la requête parente.
* Par exemple, le contexte d'une requête http a zéro ou plusieurs requêtes sql.
* @author Emeric Vernat
*/
public class CounterRequestContext implements ICounterRequestContext, Cloneable, Serializable {
private static final long serialVersionUID = 1L;
private static final Long ONE = 1L;
private static final String SPRING_BEST_MATCHING_PATTERN_ATTRIBUTE = "org.springframework.web.servlet.HandlerMapping.bestMatchingPattern";
// attention de ne pas sérialiser le counter d'origine vers le serveur de collecte, le vrai ayant été cloné
private Counter parentCounter;
private final CounterRequestContext parentContext;
private CounterRequestContext currentChildContext;
private final String requestName;
private final String completeRequestName;
private final transient HttpServletRequest httpRequest;
private final String remoteUser;
private final long threadId;
// attention, si sérialisation vers serveur de collecte, la durée peut être impactée s'il y a désynchronisation d'horloge
private final long startTime;
private final long startCpuTime;
// ces 2 champs sont initialisés à 0
private int childHits;
private int childDurationsSum;
@SuppressWarnings("all")
private Map<String, Long> childRequestsExecutionsByRequestId;
public CounterRequestContext(Counter parentCounter, CounterRequestContext parentContext,
String requestName, String completeRequestName, HttpServletRequest httpRequest,
String remoteUser, long startCpuTime) {
this(parentCounter, parentContext, requestName, completeRequestName, httpRequest,
remoteUser, Thread.currentThread().getId(), System.currentTimeMillis(),
startCpuTime);
if (parentContext != null) {
parentContext.setCurrentChildContext(this);
}
}
// constructeur privé pour la méthode clone
// CHECKSTYLE:OFF
private CounterRequestContext(Counter parentCounter, CounterRequestContext parentContext,
String requestName, String completeRequestName, HttpServletRequest httpRequest,
String remoteUser, long threadId, long startTime, long startCpuTime) {
// CHECKSTYLE:ON
super();
assert parentCounter != null;
assert requestName != null;
assert completeRequestName != null;
this.parentCounter = parentCounter;
// parentContext est non null si on a ejb dans http
// et il est null pour http ou pour ejb sans http
this.parentContext = parentContext;
this.requestName = requestName;
this.completeRequestName = completeRequestName;
this.httpRequest = httpRequest;
this.remoteUser = remoteUser;
this.threadId = threadId;
this.startTime = startTime;
this.startCpuTime = startCpuTime;
}
public Counter getParentCounter() {
return parentCounter;
}
void setParentCounter(Counter parentCounter) {
assert parentCounter != null
&& this.parentCounter.getName().equals(parentCounter.getName());
this.parentCounter = parentCounter;
}
public static void replaceParentCounters(List<CounterRequestContext> rootCurrentContexts,
List<Counter> newParentCounters) {
final Map<String, Counter> newParentCountersByName = new HashMap<String, Counter>(
newParentCounters.size());
for (final Counter counter : newParentCounters) {
newParentCountersByName.put(counter.getName(), counter);
}
replaceParentCounters(rootCurrentContexts, newParentCountersByName);
}
private static void replaceParentCounters(List<CounterRequestContext> rootCurrentContexts,
Map<String, Counter> newParentCountersByName) {
for (final CounterRequestContext context : rootCurrentContexts) {
final Counter newParentCounter = newParentCountersByName
.get(context.getParentCounter().getName());
if (newParentCounter != null) {
// si le counter n'est pas/plus affiché, newParentCounter peut être null
context.setParentCounter(newParentCounter);
}
final List<CounterRequestContext> childContexts = context.getChildContexts();
if (!childContexts.isEmpty()) {
replaceParentCounters(childContexts, newParentCountersByName);
}
}
}
public CounterRequestContext getParentContext() {
return parentContext;
}
public static String getHttpRequestName(HttpServletRequest httpRequest, String requestName) {
if (httpRequest != null) {
final String bestMatchingPattern = (String) httpRequest
.getAttribute(SPRING_BEST_MATCHING_PATTERN_ATTRIBUTE);
if (bestMatchingPattern != null) {
return bestMatchingPattern;
}
}
return requestName;
}
public String getRequestName() {
return getHttpRequestName(httpRequest, requestName);
}
public String getCompleteRequestName() {
return completeRequestName;
}
public String getRemoteUser() {
return remoteUser;
}
public long getThreadId() {
return threadId;
}
public int getDuration(long timeOfSnapshot) {
// durée écoulée (non négative même si resynchro d'horloge)
return (int) Math.max(timeOfSnapshot - startTime, 0);
}
public int getCpuTime() {
if (startCpuTime < 0) {
return -1;
}
final int cpuTime = (int) (ThreadInformations.getThreadCpuTime(getThreadId())
- startCpuTime) / 1000000;
// pas de négatif ici sinon on peut avoir une assertion si elles sont activées
return Math.max(cpuTime, 0);
}
/** {@inheritDoc} */
@Override
public int getChildHits() {
return childHits;
}
/** {@inheritDoc} */
@Override
public int getChildDurationsSum() {
return childDurationsSum;
}
/** {@inheritDoc} */
@Override
public Map<String, Long> getChildRequestsExecutionsByRequestId() {
if (childRequestsExecutionsByRequestId == null) {
return Collections.emptyMap();
}
// pas de nouvelle instance de map ici pour raison de perf
// (la méthode est utilisée sur un seul thread)
return childRequestsExecutionsByRequestId;
}
public int getTotalChildHits() {
// childHits de ce contexte plus tous ceux des contextes fils,
// il vaut mieux appeler cette méthode sur un clone du contexte pour avoir un résultat stable
// puisque les contextes fils des requêtes en cours peuvent changer à tout moment
int result = getChildHits();
CounterRequestContext childContext = getCurrentChildContext();
while (childContext != null) {
result += childContext.getChildHits();
childContext = childContext.getCurrentChildContext();
}
return result;
}
public int getTotalChildDurationsSum() {
// childDurationsSum de ce contexte plus tous ceux des contextes fils,
// il vaut mieux appeler cette méthode sur un clone du contexte pour avoir un résultat stable
// puisque les contextes fils des requêtes en cours peuvent changer à tout moment
int result = getChildDurationsSum();
CounterRequestContext childContext = getCurrentChildContext();
while (childContext != null) {
result += childContext.getChildDurationsSum();
childContext = childContext.getCurrentChildContext();
}
return result;
}
public boolean hasChildHits() {
return parentCounter.getChildCounterName() != null
&& (getTotalChildHits() > 0 || parentCounter.hasChildHits());
}
public List<CounterRequestContext> getChildContexts() {
// il vaut mieux appeler cette méthode sur un clone du contexte pour avoir un résultat stable
// puisque les contextes fils des requêtes en cours peuvent changer à tout moment
final List<CounterRequestContext> childContexts;
CounterRequestContext childContext = getCurrentChildContext();
if (childContext == null) {
childContexts = Collections.emptyList();
} else {
childContexts = new ArrayList<CounterRequestContext>(2);
}
while (childContext != null) {
childContexts.add(childContext);
childContext = childContext.getCurrentChildContext();
}
return Collections.unmodifiableList(childContexts);
}
private CounterRequestContext getCurrentChildContext() {
return currentChildContext;
}
private void setCurrentChildContext(CounterRequestContext currentChildContext) {
this.currentChildContext = currentChildContext;
}
@SuppressWarnings("unused")
void addChildRequest(Counter childCounter, String request, String requestId, long duration,
boolean systemError, int responseSize) {
// si je suis le counter fils du counter du contexte parent
// comme sql pour http alors on ajoute la requête fille
if (parentContext != null && parentCounter.getName()
.equals(parentContext.getParentCounter().getChildCounterName())) {
childHits++;
childDurationsSum += (int) duration;
}
// pour drill-down on conserve pour chaque requête mère, les requêtes filles appelées et le
// nombre d'exécutions pour chacune
if (parentContext == null) {
addChildRequestForDrillDown(requestId);
} else {
parentContext.addChildRequestForDrillDown(requestId);
}
}
private void addChildRequestForDrillDown(String requestId) {
if (childRequestsExecutionsByRequestId == null) {
childRequestsExecutionsByRequestId = new LinkedHashMap<String, Long>();
}
Long nbExecutions = childRequestsExecutionsByRequestId.get(requestId);
if (nbExecutions == null) {
nbExecutions = ONE;
} else {
nbExecutions += 1;
}
childRequestsExecutionsByRequestId.put(requestId, nbExecutions);
}
void closeChildContext() {
final CounterRequestContext childContext = getCurrentChildContext();
childHits += childContext.getChildHits();
childDurationsSum += childContext.getChildDurationsSum();
// ce contexte fils est terminé
setCurrentChildContext(null);
}
/** {@inheritDoc} */
@Override
//CHECKSTYLE:OFF
public CounterRequestContext clone() { // NOPMD
//CHECKSTYLE:ON
// ce clone n'est valide que pour un contexte root sans parent
assert getParentContext() == null;
return clone(null);
}
private CounterRequestContext clone(CounterRequestContext parentContextClone) {
final Counter counter = getParentCounter();
// s'il fallait un clone du parentCounter pour sérialiser, on pourrait faire seulement ça:
// final Counter parentCounterClone = new Counter(counter.getName(), counter.getStorageName(),
// counter.getIconName(), counter.getChildCounterName(), null);
final CounterRequestContext clone = new CounterRequestContext(counter, parentContextClone,
getRequestName(), getCompleteRequestName(), httpRequest, getRemoteUser(),
getThreadId(), startTime, startCpuTime);
clone.childHits = getChildHits();
clone.childDurationsSum = getChildDurationsSum();
final CounterRequestContext childContext = getCurrentChildContext();
if (childContext != null) {
clone.currentChildContext = childContext.clone(clone);
}
if (childRequestsExecutionsByRequestId != null) {
clone.childRequestsExecutionsByRequestId = new LinkedHashMap<String, Long>(
childRequestsExecutionsByRequestId);
}
return clone;
}
/** {@inheritDoc} */
@Override
public String toString() {
return getClass().getSimpleName() + "[parentCounter=" + getParentCounter().getName()
+ ", completeRequestName=" + getCompleteRequestName() + ", threadId="
+ getThreadId() + ", startTime=" + startTime + ", childHits=" + getChildHits()
+ ", childDurationsSum=" + getChildDurationsSum() + ", childContexts="
+ getChildContexts() + ']';
}
}