/*
* Copyright 2008-2014 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.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 net.bull.javamelody.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
*/
class CounterRequestContext implements ICounterRequestContext, Cloneable, Serializable {
private static final long serialVersionUID = 1L;
private static final Long ONE = 1L;
// 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 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;
CounterRequestContext(Counter parentCounter, CounterRequestContext parentContext,
String requestName, String completeRequestName, String remoteUser, long startCpuTime) {
this(parentCounter, parentContext, requestName, completeRequestName, 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, 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.remoteUser = remoteUser;
this.threadId = threadId;
this.startTime = startTime;
this.startCpuTime = startCpuTime;
}
Counter getParentCounter() {
return parentCounter;
}
void setParentCounter(Counter parentCounter) {
assert parentCounter != null
&& this.parentCounter.getName().equals(parentCounter.getName());
this.parentCounter = parentCounter;
}
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);
}
}
}
CounterRequestContext getParentContext() {
return parentContext;
}
String getRequestName() {
return requestName;
}
String getCompleteRequestName() {
return completeRequestName;
}
String getRemoteUser() {
return remoteUser;
}
long getThreadId() {
return threadId;
}
int getDuration(long timeOfSnapshot) {
// durée écoulée (non négative même si resynchro d'horloge)
return (int) Math.max(timeOfSnapshot - startTime, 0);
}
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;
}
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;
}
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;
}
boolean hasChildHits() {
return parentCounter.getChildCounterName() != null
&& (getTotalChildHits() > 0 || parentCounter.hasChildHits());
}
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 += 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(), 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() + ']';
}
}