All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.bull.javamelody.internal.model.Counter Maven / Gradle / Ivy

There is a newer version: 2.3.0
Show newest version
/*
 * Copyright 2008-2019 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.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import net.bull.javamelody.SessionListener;
import net.bull.javamelody.internal.common.LOG;

/**
 * Données statistiques des requêtes pour un compteur nommé comme http ou sql.
 * Ces données sont accumulées au fil du temps selon les requêtes dans l'application.
 * Elles correspondent soit aux statistiques courantes depuis une date initiale,
 * soit à une période donnée pour un jour, une semaine, un mois ou une année.
 *
 * Toutes les méthodes sur une instance de cette classe sont conçues pour être thread-safe,
 * c'est-à-dire qu'elles gère un état qui est non modifiable
 * ou alors synchronisé pour être accessible et modifiable depuis plusieurs threads.
 * Les instances sont sérialisables pour pouvoir être persistées sur disque
 * et transmises au serveur de collecte.
 * @author Emeric Vernat
 */
public class Counter implements Cloneable, Serializable { // NOPMD
	/**
	 * Nom du counter des requêtes http.
	 */
	public static final String HTTP_COUNTER_NAME = "http";
	/**
	 * Nom du counter des erreurs systèmes http.
	 */
	public static final String ERROR_COUNTER_NAME = "error";
	/**
	 * Nom du counter des logs d'erreurs systèmes.
	 */
	public static final String LOG_COUNTER_NAME = "log";
	/**
	 * Nom du counter des JSPs.
	 */
	public static final String JSP_COUNTER_NAME = "jsp";
	/**
	 * Nom du counter des actions Struts.
	 */
	public static final String STRUTS_COUNTER_NAME = "struts";
	/**
	 * Nom du counter des actions JSF RI (Mojarra).
	 */
	public static final String JSF_COUNTER_NAME = "jsf";
	/**
	 * Nom du counter des requêtes SQL.
	 */
	public static final String SQL_COUNTER_NAME = "sql";
	/**
	 * Nom du counter des jobs.
	 */
	public static final String JOB_COUNTER_NAME = "job";
	/**
	 * Nom du counter des builds Jenkins.
	 */
	public static final String BUILDS_COUNTER_NAME = "builds";
	/**
	 * Nombre max d'erreurs conservées par le counter (si counter d'erreurs http ou de log d'erreurs).
	 */
	public static final int MAX_ERRORS_COUNT = 100;
	/**
	 * Caractère de remplacement s'il y a des paramètres *-transform-pattern.
	 */
	static final char TRANSFORM_REPLACEMENT_CHAR = '$';
	/**
	 * Nombre max par défaut de requêtes conservées par counter, 
* mais peut être redéfini par exemple pour le counter des erreurs http ou celui des logs. */ static final int MAX_REQUESTS_COUNT = 10000; private static final String TRANSFORM_REPLACEMENT = "\\" + TRANSFORM_REPLACEMENT_CHAR; private static final long serialVersionUID = 6759729262180992976L; private String application; private boolean displayed = true; private transient boolean used; private final String name; private final boolean errorCounter; private final String storageName; private final String iconName; // on conserve childCounterName et pas childCounter pour assurer la synchronisation/clone et la sérialisation private final String childCounterName; @SuppressWarnings("all") private final ConcurrentMap requests = new ConcurrentHashMap<>(); // note : même si rootCurrentContextsByThreadId n'est pas transient la map est normalement vide avant sérialisation // (on garde en non transient pour ne pas avoir null après désérialisation ce qui pourrait donner des NPE) @SuppressWarnings("all") private final ConcurrentMap rootCurrentContextsByThreadId = new ConcurrentHashMap<>(); private final LinkedList errors; // NOPMD private Date startDate = new Date(); private int maxRequestsCount = MAX_REQUESTS_COUNT; private long estimatedMemorySize; // Pour les contextes, on utilise un ThreadLocal et pas un InheritableThreadLocal // puisque si on crée des threads alors la requête parente peut se terminer avant les threads // et le contexte serait incomplet. private final transient ThreadLocal contextThreadLocal; private transient Pattern requestTransformPattern; /** * Comparateur pour ordonner les requêtes par sommes des durées. */ static final class CounterRequestComparator implements Comparator, Serializable { private static final long serialVersionUID = 1L; /** {@inheritDoc} */ @Override public int compare(CounterRequest request1, CounterRequest request2) { return Long.compare(request1.getDurationsSum(), request2.getDurationsSum()); } } /** * Comparateur pour ordonner les requêtes par nombre d'exécutions. */ static final class CounterRequestByHitsComparator implements Comparator, Serializable { private static final long serialVersionUID = 1L; /** {@inheritDoc} */ @Override public int compare(CounterRequest request1, CounterRequest request2) { return Long.compare(request1.getHits(), request2.getHits()); } } /** * Comparateur pour ordonner les erreurs par heures d'exécution. */ static final class CounterErrorComparator implements Comparator, Serializable { private static final long serialVersionUID = 1L; /** {@inheritDoc} */ @Override public int compare(CounterError error1, CounterError error2) { if (error1.getTime() < error2.getTime()) { return -1; } else if (error1.getTime() > error2.getTime()) { return 1; } return 0; } } /** * Comparateur pour ordonner les requêtes en cours par durées écoulées. */ public static final class CounterRequestContextComparator implements Comparator, Serializable { private static final long serialVersionUID = 1L; private final long timeOfSnapshot; public CounterRequestContextComparator(long timeOfSnapshot) { super(); this.timeOfSnapshot = timeOfSnapshot; } /** {@inheritDoc} */ @Override public int compare(CounterRequestContext context1, CounterRequestContext context2) { return Integer.compare(context1.getDuration(timeOfSnapshot), context2.getDuration(timeOfSnapshot)); } } /** * Constructeur d'un compteur. * @param name Nom du compteur (par exemple: sql...) * @param iconName Icône du compteur (par exemple: db.png) */ public Counter(String name, String iconName) { // ici, pas de compteur fils this(name, name, iconName, null, new ThreadLocal()); } /** * Constructeur d'un compteur. * @param name Nom du compteur (par exemple: sql...) * @param storageName Nom unique du compteur pour le stockage (par exemple: sql_20080724) * @param iconName Icône du compteur (par exemple: db.png) * @param childCounterName Nom du compteur fils (par exemple: sql) */ public Counter(String name, String storageName, String iconName, String childCounterName) { this(name, storageName, iconName, childCounterName, new ThreadLocal()); } /** * Constructeur d'un compteur. * @param name Nom du compteur (par exemple: http...) * @param iconName Icône du compteur (par exemple: db.png) * @param childCounter Compteur fils (par exemple: sqlCounter) */ public Counter(String name, String iconName, Counter childCounter) { this(name, name, iconName, childCounter.getName(), childCounter.contextThreadLocal); } private Counter(String name, String storageName, String iconName, String childCounterName, ThreadLocal contextThreadLocal) { super(); assert name != null; assert storageName != null; this.name = name; this.storageName = storageName; this.errorCounter = ERROR_COUNTER_NAME.equals(name) || LOG_COUNTER_NAME.equals(name) || JOB_COUNTER_NAME.equals(name); this.iconName = iconName; this.childCounterName = childCounterName; this.contextThreadLocal = contextThreadLocal; if (errorCounter) { this.errors = new LinkedList<>(); } else { this.errors = null; } } /** * Définit le code de l'application de ce counter (non null). * @param application String */ void setApplication(String application) { assert application != null; this.application = application; } /** * Retourne le code de l'application. * @return String */ String getApplication() { return application; } /** * Retourne le nom de ce counter (non null). * @return String */ public String getName() { return name; } /** * Retourne le nom de ce counter quand il est stocké sur disque (non null). * @return String */ public String getStorageName() { return storageName; } /** * Retourne le nom de l'icône de ce counter (peut être null). * @return String */ public String getIconName() { return iconName; } /** * Retourne le nom de l'éventuel counter fils (peut être null). * @return String */ public String getChildCounterName() { return childCounterName; } boolean hasChildHits() { for (final CounterRequest request : requests.values()) { if (request.hasChildHits()) { return true; } } return false; } /** * Retourne la date et l'heure de début (non null). * @return Date */ public Date getStartDate() { return startDate; } /** * Définit la date et l'heure de début (non null). * @param startDate Date */ void setStartDate(Date startDate) { assert startDate != null; this.startDate = startDate; } /** * Retourne true si ce counter est affiché dans les rapports. * @return boolean */ public boolean isDisplayed() { return displayed; } /** * Définit si ce counter est affiché dans les rapports. * @param displayed boolean */ public void setDisplayed(boolean displayed) { this.displayed = displayed; } /** * Retourne true si ce counter est utilisé * (servira éventuellement à initialiser displayed dans FilterContext). * @return boolean */ public boolean isUsed() { return used; } /** * Définit si ce counter est utilisé * (servira éventuellement à initialiser displayed dans FilterContext). * @param used boolean */ public void setUsed(boolean used) { this.used = used; } /** * Retourne l'expression régulière permettant de transformer les requêtes de ce counter * avant agrégation dans les statistiques (peut être null). * @return Pattern */ Pattern getRequestTransformPattern() { return requestTransformPattern; } /** * Définit l'expression régulière permettant de transformer les requêtes de ce counter * avant agrégation dans les statistiques. * @param requestTransformPattern Pattern */ public void setRequestTransformPattern(Pattern requestTransformPattern) { this.requestTransformPattern = requestTransformPattern; } /** * Retourne le nombre maximum de requêtes dans ce counter (entier positif). * @return int */ int getMaxRequestsCount() { return maxRequestsCount; } /** * Définit le nombre maximum de requêtes dans ce counter (entier positif). * @param maxRequestsCount int */ public void setMaxRequestsCount(int maxRequestsCount) { assert maxRequestsCount > 0; this.maxRequestsCount = maxRequestsCount; } /** * Retourne l'estimation pessimiste de l'occupation mémoire de counter * (c'est-à-dire la dernière taille sérialisée non compressée de ce counter) * @return long */ long getEstimatedMemorySize() { return estimatedMemorySize; } public void bindContextIncludingCpu(String requestName) { bindContext(requestName, requestName, null, ThreadInformations.getCurrentThreadCpuTime(), ThreadInformations.getCurrentThreadAllocatedBytes()); } public void bindContext(String requestName, String completeRequestName, HttpServletRequest httpRequest, long startCpuTime, long startAllocatedBytes) { String remoteUser = null; String sessionId = null; if (httpRequest != null) { remoteUser = httpRequest.getRemoteUser(); final HttpSession session = httpRequest.getSession(false); if (session != null) { sessionId = session.getId(); if (remoteUser == null) { final Object userAttribute = session .getAttribute(SessionListener.SESSION_REMOTE_USER); if (userAttribute instanceof String) { remoteUser = (String) userAttribute; } } } } // requestName est la même chose que ce qui sera utilisée dans addRequest, // completeRequestName est la même chose éventuellement complétée // pour cette requête à destination de l'affichage dans les requêtes courantes // (sinon mettre 2 fois la même chose) final CounterRequestContext context = new CounterRequestContext(this, contextThreadLocal.get(), requestName, completeRequestName, httpRequest, remoteUser, startCpuTime, startAllocatedBytes, sessionId); contextThreadLocal.set(context); if (context.getParentContext() == null) { rootCurrentContextsByThreadId.put(context.getThreadId(), context); } } public void unbindContext() { try { contextThreadLocal.remove(); } finally { rootCurrentContextsByThreadId.remove(Thread.currentThread().getId()); } } public void addRequestForCurrentContext(boolean systemError) { final CounterRequestContext context = contextThreadLocal.get(); if (context != null) { final long duration = context.getDuration(System.currentTimeMillis()); final int cpuUsedMillis = context.getCpuTime(); final int allocatedKBytes = context.getAllocatedKBytes(); addRequest(context.getRequestName(), duration, cpuUsedMillis, allocatedKBytes, systemError, -1); } } public void addRequestForCurrentContext(String systemErrorStackTrace) { assert errorCounter; final CounterRequestContext context = contextThreadLocal.get(); // context peut être null (depuis JobGlobalListener, cf issue 34) if (context != null) { final long duration = context.getDuration(System.currentTimeMillis()); final int cpuUsedMillis = context.getCpuTime(); final int allocatedKBytes = context.getAllocatedKBytes(); addRequest(context.getRequestName(), duration, cpuUsedMillis, allocatedKBytes, systemErrorStackTrace != null, systemErrorStackTrace, -1); } } public void addRequest(String requestName, long duration, int cpuTime, int allocatedKBytes, boolean systemError, long responseSize) { addRequest(requestName, duration, cpuTime, allocatedKBytes, systemError, null, responseSize); } private void addRequest(String requestName, long duration, int cpuTime, int allocatedKBytes, boolean systemError, String systemErrorStackTrace, long responseSize) { // la méthode addRequest n'est pas synchronisée pour ne pas avoir // de synchronisation globale à l'application sur cette instance d'objet // ce qui pourrait faire une contention et des ralentissements, // par contre la map requests est synchronisée pour les modifications concurrentes assert requestName != null; assert duration >= 0; assert cpuTime >= -1; // -1 pour requêtes sql assert allocatedKBytes >= -1; // -1 pour requêtes sql assert responseSize >= -1L; // -1 pour requêtes sql final String aggregateRequestName = getAggregateRequestName(requestName); final CounterRequestContext context = contextThreadLocal.get(); final CounterRequest request = getCounterRequestInternal(aggregateRequestName); synchronized (request) { // on synchronise par l'objet request pour éviter de mélanger des ajouts de hits // concurrents entre plusieurs threads pour le même type de requête. // Rq : on pourrait remplacer ce bloc synchronized par un synchronized // sur les méthodes addHit et addChildHits dans la classe CounterRequest. request.addHit(duration, cpuTime, allocatedKBytes, systemError, systemErrorStackTrace, responseSize); if (context != null) { // on ajoute dans la requête parente toutes les requêtes filles du contexte if (context.getParentCounter() == this) { request.addChildHits(context); } request.addChildRequests(context.getChildRequestsExecutionsByRequestId()); } } // perf: on fait le reste hors du synchronized sur request if (context != null) { if (context.getParentCounter() == this) { final CounterRequestContext parentContext = context.getParentContext(); if (parentContext == null) { // enlève du threadLocal le contexte que j'ai créé // si je suis le counter parent et s'il n'y a pas de contexte parent unbindContext(); } else { // on ajoute une requête fille dans le contexte context.addChildRequest(this, aggregateRequestName, request.getId(), duration, systemError, responseSize); // et reporte les requêtes filles dans le contexte parent et rebinde celui-ci parentContext.closeChildContext(); contextThreadLocal.set(parentContext); } } else { // on ajoute une requête fille dans le contexte // (à priori il s'agit d'une requête sql) context.addChildRequest(this, aggregateRequestName, request.getId(), duration, systemError, responseSize); } } if (systemErrorStackTrace != null) { assert errorCounter; synchronized (errors) { errors.addLast(new CounterError(requestName, systemErrorStackTrace)); if (errors.size() > MAX_ERRORS_COUNT) { errors.removeFirst(); } } } } public void addRequestForSystemError(String requestName, long duration, int cpuTime, int allocatedKBytes, String stackTrace) { // comme la méthode addRequest, cette méthode n'est pas synchronisée pour ne pas avoir // de synchronisation globale à l'application sur cette instance d'objet // ce qui pourrait faire une contention et des ralentissements, // par contre on synchronise request et errors assert requestName != null; assert duration >= -1; // -1 pour le counter de log assert cpuTime >= -1; // on ne doit conserver les stackTraces que pour les compteurs d'erreurs et de logs plus limités en taille // car sinon cela risquerait de donner des compteurs trop gros en mémoire et sur disque assert errorCounter; // le code ci-après suppose qu'il n'y a pas de contexte courant pour les erreurs systèmes // contrairement à la méthode addRequest assert contextThreadLocal.get() == null; final String aggregateRequestName = getAggregateRequestName(requestName); final CounterRequest request = getCounterRequestInternal(aggregateRequestName); synchronized (request) { request.addHit(duration, cpuTime, allocatedKBytes, true, stackTrace, -1); } synchronized (errors) { errors.addLast(new CounterError(requestName, stackTrace)); if (errors.size() > MAX_ERRORS_COUNT) { errors.removeFirst(); } } } public void addRumHit(String requestName, long networkTime, long domProcessing, long pageRendering) { assert HTTP_COUNTER_NAME.equals(name); final String aggregateRequestName = getAggregateRequestName(requestName); final CounterRequest request = requests.get(aggregateRequestName); if (request != null) { synchronized (request) { request.addRumHit(networkTime, domProcessing, pageRendering); } } } /** * Retourne true si ce counter est un counter d'error * (c'est-à-dire si son nom est "error", "log" ou "job"). * @return boolean */ public boolean isErrorCounter() { return errorCounter; } /** * Retourne true si ce counter est un counter de job * (c'est-à-dire si son nom est "job"). * @return boolean */ public boolean isJobCounter() { return JOB_COUNTER_NAME.equals(name); } /** * Retourne true si ce counter est un counter de jsp ou d'actions Struts * (c'est-à-dire si son nom est "jsp"). * @return boolean */ public boolean isJspOrStrutsCounter() { return JSP_COUNTER_NAME.equals(name) || STRUTS_COUNTER_NAME.equals(name); } /** * Retourne true si ce counter est un counter de "façades métiers" ou "business façades" * (c'est-à-dire si son nom est "ejb", "spring", "guice" ou "services"). * @return boolean */ public boolean isBusinessFacadeCounter() { return "services".equals(name) || "ejb".equals(name) || "spring".equals(name) || "guice".equals(name); } public boolean isRequestIdFromThisCounter(String requestId) { // cela marche car requestId commence par counter.getName() selon CounterRequest.buildId return requestId.startsWith(getName()); } private String getAggregateRequestName(String requestName) { final String aggregateRequestName; if (requestTransformPattern == null) { aggregateRequestName = requestName; } else { // ce pattern optionnel permet de transformer la description de la requête // pour supprimer des parties variables (identifiant d'objet par exemple) // et pour permettre l'agrégation sur cette requête final Matcher matcher = requestTransformPattern.matcher(requestName); try { aggregateRequestName = matcher.replaceAll(TRANSFORM_REPLACEMENT); } catch (final StackOverflowError e) { // regexp can throw StackOverflowError for (A|B)* // see https://github.com/javamelody/javamelody/issues/480 LOG.warn(e.toString(), e); return requestName; } } return aggregateRequestName; } void addRequestsAndErrors(Counter newCounter) { assert getName().equals(newCounter.getName()); // Pour toutes les requêtes du compteur en paramètre, // on ajoute les hits aux requêtes de ce compteur // (utilisée dans serveur de collecte). // Rq: cette méthode est thread-safe comme les autres méthodes dans cette classe, // bien que cela ne soit à priori pas nécessaire telle qu'elle est utilisée dans CollectorServlet for (final CounterRequest newRequest : newCounter.getRequests()) { if (newRequest.getHits() > 0) { final CounterRequest request = getCounterRequestInternal(newRequest.getName()); synchronized (request) { request.addHits(newRequest); } } } int size = requests.size(); final int maxRequests = getMaxRequestsCount(); if (size > maxRequests) { // Si le nombre de requêtes est supérieur à 10000 (sql non bindé par ex.), // on essaye ici d'éviter de saturer la mémoire (et le disque dur) // avec toutes ces requêtes différentes en éliminant celles ayant moins de 10 hits. // (utile pour une agrégation par année dans PeriodCounterFactory par ex.) // Mais inutile de le faire dans d'autres méthodes de Counter // car ce serait mauvais pour les perfs, cela ne laisserait aucune chance // à une nouvelle requête et car cela sera fait par la classe collector for (final CounterRequest request : requests.values()) { if (request.getHits() < 10) { removeRequest(request.getName()); size--; if (size <= maxRequests) { break; } } } } if (isErrorCounter()) { addErrors(newCounter.getErrors()); } } void addHits(CounterRequest counterRequest) { if (counterRequest.getHits() > 0) { // clone pour être thread-safe ici final CounterRequest newRequest = counterRequest.clone(); final CounterRequest request = getCounterRequestInternal(newRequest.getName()); synchronized (request) { request.addHits(newRequest); } } } public void addErrors(List counterErrorList) { assert errorCounter; if (counterErrorList.isEmpty()) { return; } synchronized (errors) { if (!errors.isEmpty() && errors.get(0).getTime() > counterErrorList.get(0).getTime()) { // tant que faire se peut on les met à peu près dans l'ordre pour le sort ci après errors.addAll(0, counterErrorList); } else { errors.addAll(counterErrorList); } if (errors.size() > 1) { // "sort" a les mêmes performances sur LinkedList que sur ArrayList car il y a un tableau intermédiaire // (selon Implementation Patterns, Kent Beck) Collections.sort(errors, new CounterErrorComparator()); while (errors.size() > MAX_ERRORS_COUNT) { errors.removeFirst(); } } } } void removeRequest(String requestName) { assert requestName != null; requests.remove(requestName); } /** * Retourne l'objet {@link CounterRequest} correspondant au contexte de requête en cours en paramètre. * @param context CounterRequestContext * @return CounterRequest */ public CounterRequest getCounterRequest(CounterRequestContext context) { return getCounterRequestByName(context.getRequestName(), false); } /** * Retourne l'objet {@link CounterRequest} correspondant au nom sans agrégation en paramètre. * @param requestName Nom de la requête sans agrégation par requestTransformPattern * @param saveRequestIfAbsent true except for current requests because the requestName may not be yet bestMatchingPattern * @return CounterRequest */ public CounterRequest getCounterRequestByName(String requestName, boolean saveRequestIfAbsent) { // l'instance de CounterRequest retournée est clonée // (nécessaire pour protéger la synchronisation interne du counter), // son état peut donc être lu sans synchronisation // mais toute modification de cet état ne sera pas conservée final String aggregateRequestName = getAggregateRequestName(requestName); final CounterRequest request = getCounterRequestInternal(aggregateRequestName, saveRequestIfAbsent); synchronized (request) { return request.clone(); } } private CounterRequest getCounterRequestInternal(String requestName) { return getCounterRequestInternal(requestName, true); } private CounterRequest getCounterRequestInternal(String requestName, boolean saveRequestIfAbsent) { CounterRequest request = requests.get(requestName); if (request == null) { request = new CounterRequest(requestName, getName()); if (saveRequestIfAbsent) { // putIfAbsent a l'avantage d'être garanti atomique, même si ce n'est pas indispensable final CounterRequest precedentRequest = requests.putIfAbsent(requestName, request); if (precedentRequest != null) { request = precedentRequest; } } } return request; } /** * Retourne l'objet {@link CounterRequest} correspondant à l'id en paramètre ou null sinon. * @param requestId Id de la requête * @return CounterRequest */ public CounterRequest getCounterRequestById(String requestId) { if (isRequestIdFromThisCounter(requestId)) { for (final CounterRequest request : requests.values()) { if (request.getId().equals(requestId)) { synchronized (request) { return request.clone(); } } } } return null; } /** * Retourne le nombre de requêtes dans ce counter. * @return int */ public int getRequestsCount() { return requests.size(); } /** * @return Liste des requêtes non triées, * la liste et ses objets peuvent être utilisés sans synchronized et sans crainte d'accès concurrents. */ public List getRequests() { // thread-safe : // on crée une copie de la collection et on clone ici chaque CounterRequest de manière synchronisée // de manière à ce que l'appelant n'ai pas à se préoccuper des synchronisations nécessaires // Rq : l'Iterator sur ConcurrentHashMap.values() est garanti ne pas lancer ConcurrentModificationException // même s'il y a des ajouts concurrents final List result = new ArrayList<>(requests.size()); for (final CounterRequest request : requests.values()) { // on synchronize sur request en cas d'ajout en parallèle d'un hit sur cette request synchronized (request) { result.add(request.clone()); } } return result; } /** * @return Liste des requêtes triées par durée cumulée décroissante, * la liste et ses objets peuvent être utilisés sans synchronized et sans crainte d'accès concurrents. */ public List getOrderedRequests() { final List requestList = getRequests(); if (requestList.size() > 1) { Collections.sort(requestList, Collections.reverseOrder(new CounterRequestComparator())); } return requestList; } /** * @return Liste des requêtes triées par hits décroissants, * la liste et ses objets peuvent être utilisés sans synchronized et sans crainte d'accès concurrents. */ List getOrderedByHitsRequests() { final List requestList = getRequests(); if (requestList.size() > 1) { Collections.sort(requestList, Collections.reverseOrder(new CounterRequestByHitsComparator())); } return requestList; } /** * @return Liste des contextes de requêtes courantes triées par durée écoulée décroissante, * la liste peut être utilisée sans synchronized et sans crainte d'accès concurrents, * toutefois les contextes ne sont pas actuellement clonés dans cette méthode. */ List getOrderedRootCurrentContexts() { final List contextList = new ArrayList<>( rootCurrentContextsByThreadId.size()); for (final CounterRequestContext rootCurrentContext : rootCurrentContextsByThreadId .values()) { contextList.add(rootCurrentContext.clone()); } if (contextList.size() > 1) { Collections.sort(contextList, Collections .reverseOrder(new CounterRequestContextComparator(System.currentTimeMillis()))); } return contextList; } /** * @return Liste des erreurs triée par date croissante, * la liste et ses objets peuvent être utilisés sans synchronized et sans crainte d'accès concurrents. */ public List getErrors() { if (errors == null) { return Collections.emptyList(); } synchronized (errors) { return new ArrayList<>(errors); } } /** * Retourne le nombre d'erreurs dans ce counter. * @return int */ public int getErrorsCount() { if (errors == null) { return 0; } synchronized (errors) { return errors.size(); } } /** * Purge les requêtes et erreurs puis positionne la date et heure de début à l'heure courante, * mais sans toucher aux requêtes en cours pour qu'elles restent affichées, * par exemple dans le serveur de collecte (#871). */ public void clear() { requests.clear(); if (errors != null) { synchronized (errors) { errors.clear(); } } startDate = new Date(); } /** {@inheritDoc} */ @Override //CHECKSTYLE:OFF public Counter clone() { // NOPMD //CHECKSTYLE:ON final Counter clone = new Counter(getName(), getStorageName(), getIconName(), getChildCounterName(), new ThreadLocal()); clone.application = getApplication(); clone.startDate = getStartDate(); clone.maxRequestsCount = getMaxRequestsCount(); clone.displayed = isDisplayed(); clone.requestTransformPattern = getRequestTransformPattern(); // on ne copie pas rootCurrentContextsByThreadId car on ne fournit pas les requêtes en cours // qui sont très rapidement obsolètes au serveur de collecte (et sinon cela poserait la question // des clones de parentCounter, de l'agrégation, de la synchro d'horloge pour la durée // et des threadId pour la stack-trace), // et on ne copie pas contextThreadLocal, // et la méthode getRequests() clone les instances de CounterRequest for (final CounterRequest request : getRequests()) { clone.requests.put(request.getName(), request); } if (errors != null) { clone.errors.addAll(getErrors()); } return clone; } /** * Enregistre le counter. * @throws IOException e */ void writeToFile() throws IOException { // on clone le counter avant de le sérialiser pour ne pas avoir de problèmes de concurrences d'accès final Counter counter = this.clone(); // on n'écrit pas rootCurrentContextsByThreadId en fichier // puisque ces données ne seront plus vraies dans quelques secondes (clear pour être sûr ici) counter.rootCurrentContextsByThreadId.clear(); estimatedMemorySize = new CounterStorage(counter).writeToFile(); } /** * Lecture du counter depuis son fichier. * @throws IOException e */ void readFromFile() throws IOException { final Counter counter = new CounterStorage(this).readFromFile(); if (counter != null) { final Counter newCounter = clone(); startDate = counter.getStartDate(); requests.clear(); for (final CounterRequest request : counter.getRequests()) { requests.put(request.getName(), request); } if (errors != null) { errors.clear(); errors.addAll(counter.getErrors()); } // on ajoute les nouvelles requêtes enregistrées avant de lire le fichier // (par ex. les premières requêtes collectées par le serveur de collecte lors de l'initialisation) addRequestsAndErrors(newCounter); } } /** {@inheritDoc} */ @Override public String toString() { return getClass().getSimpleName() + "[application=" + getApplication() + ", name=" + getName() + ", storageName=" + getStorageName() + ", startDate=" + getStartDate() + ", childCounterName=" + getChildCounterName() + ", " + requests.size() + " requests, " + (errors == null ? "" : errors.size() + " errors, ") + "maxRequestsCount=" + getMaxRequestsCount() + ", displayed=" + isDisplayed() + ']'; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy