org.perf4j.log4j.GraphingStatisticsAppender Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of perf4j Show documentation
Show all versions of perf4j Show documentation
Performance statistics logging and monitoring toolkit extension to log4j and the java.util.logging framework.
/* Copyright (c) 2008-2009 HomeAway, Inc.
* All rights reserved. http://www.perf4j.org
*
* 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 org.perf4j.log4j;
import org.apache.log4j.Appender;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.Logger;
import org.apache.log4j.Level;
import org.apache.log4j.helpers.AppenderAttachableImpl;
import org.apache.log4j.spi.AppenderAttachable;
import org.apache.log4j.spi.LoggingEvent;
import org.perf4j.GroupedTimingStatistics;
import org.perf4j.StopWatch;
import org.perf4j.chart.GoogleChartGenerator;
import org.perf4j.chart.StatisticsChartGenerator;
import org.perf4j.helpers.StatsValueRetriever;
import org.perf4j.helpers.MiscUtils;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
import java.io.Flushable;
/**
* This appender is designed to be attached to an {@link AsyncCoalescingStatisticsAppender}. It takes the incoming
* GroupedTimingStatistics log messages and uses this data to update a graphical view of the logged statistics. If
* ANOTHER appender is then attached to this appender then the graph URLs will be written to the appender on a scheduled
* basis. Alternatively, the graph can be viewed by setting up a
* {@link org.perf4j.log4j.servlet.GraphingServlet} to expose the graph images.
*
* @author Alex Devine
*/
public class GraphingStatisticsAppender extends AppenderSkeleton implements AppenderAttachable, Flushable {
/**
* This class keeps track of all appenders of this type that have been created. This allows static access to
* the appenders from the org.perf4j.log4j.servlet.GraphingServlet class.
*/
protected final static Map APPENDERS_BY_NAME =
Collections.synchronizedMap(new LinkedHashMap());
// --- configuration options ---
/**
* The type of data to display on the graph. Defaults to "Mean" to display mean values. Acceptable values are any
* constant name from the {@link org.perf4j.helpers.StatsValueRetriever} class, such as Mean, Min, Max, Count,
* StdDev or TPS.
*/
private String graphType = StatsValueRetriever.MEAN_VALUE_RETRIEVER.getValueName();
/**
* A comma-separated list of the tag names that should be graphed. If not set then a separate series will be
* displayed on the graph for each tag name logged.
*/
private String tagNamesToGraph = null;
/**
* Gets the number of data points that will be written on each graph before the graph URL is written to any
* attached appenders. Thus, this option is only relevant if there are attached appenders.
* Defaults to StatisticsChartGenerator.DEFAULT_MAX_DATA_POINTS.
*/
private int dataPointsPerGraph = StatisticsChartGenerator.DEFAULT_MAX_DATA_POINTS;
// --- contained objects/state variables ---
/**
* The chart genertor, initialized in the activateOptions method, that stores the data for the chart.
*/
private StatisticsChartGenerator chartGenerator;
/**
* Keeps track of the number of logged GroupedTimingStatistics, which is used to determine when a graph should
* be written to any attached appenders.
*/
private AtomicLong numLoggedStatistics = new AtomicLong();
/**
* Keeps track of whether there is existing data that hasn't yet been flushed to downstream appenders.
*/
private volatile boolean hasUnflushedData = false;
/**
* Keeps track of the Level of the last appended event. This is just used to determine what level we send to OUR
* downstream events.
*/
private Level lastAppendedEventLevel = Level.INFO;
/**
* Any downstream appenders are contained in this AppenderAttachableImpl
*/
private final AppenderAttachableImpl downstreamAppenders = new AppenderAttachableImpl();
// --- options ---
/**
* The GraphType option is used to specify the data that should be displayed on the graph. Acceptable
* values are Mean, Min, Max, Count, StdDev and TPS (for transactions per second). Defaults to Mean if not
* explicitly set.
*
* @return The value of the GraphType option
*/
public String getGraphType() {
return graphType;
}
/**
* Sets the value of the GraphType option. This must be a valid type, one of
* Mean, Min, Max, Count, StdDev or TPS (for transactions per second).
*
* @param graphType The new value for the GraphType option.
*/
public void setGraphType(String graphType) {
this.graphType = graphType;
}
/**
* The TagNamesToGraph option is used to specify which tags should be logged as a data series on the
* graph. If not specified ALL tags will be drawn on the graph, one series for each tag.
*
* @return The value of the TagNamesToGraph option
*/
public String getTagNamesToGraph() {
return tagNamesToGraph;
}
/**
* Sets the value of the TagNamesToGraph option.
*
* @param tagNamesToGraph The new value for the TagNamesToGraph option.
*/
public void setTagNamesToGraph(String tagNamesToGraph) {
this.tagNamesToGraph = tagNamesToGraph;
}
/**
* The DataPointsPerGraph option is used to specify how much data should be displayed on each graph before
* it is written to any attached appenders. Defaults to StatisticsChartGenerator.DEFAULT_MAX_DATA_POINTS.
*
* @return The value of the DataPointsPerGraph option
*/
public int getDataPointsPerGraph() {
return dataPointsPerGraph;
}
/**
* Sets the value of the DataPointsPerGraph option.
*
* @param dataPointsPerGraph The new value for the DataPointsPerGraph option.
*/
public void setDataPointsPerGraph(int dataPointsPerGraph) {
if (dataPointsPerGraph <= 0) {
throw new IllegalArgumentException("The DataPointsPerGraph option must be positive");
}
this.dataPointsPerGraph = dataPointsPerGraph;
}
public void activateOptions() {
chartGenerator = createChartGenerator();
//update the static APPENDERS_BY_NAME object
if (getName() != null) {
APPENDERS_BY_NAME.put(getName(), this);
}
}
/**
* Helper method creates a new StatisticsChartGenerator based on the options set on this appender. By default
* a GoogleChartGenerator is created, though subclasses may override this method to create a different type of
* chart generator.
*
* @return A newly created StatisticsChartGenerator.
*/
protected StatisticsChartGenerator createChartGenerator() {
StatsValueRetriever statsValueRetriever = StatsValueRetriever.DEFAULT_RETRIEVERS.get(getGraphType());
if (statsValueRetriever == null) {
throw new RuntimeException("Unknown GraphType: " + getGraphType() +
". See the StatsValueRetriever class for the list of acceptable types.");
}
//create the chart generator and set the enabled tags
GoogleChartGenerator retVal = new GoogleChartGenerator(statsValueRetriever);
if (getTagNamesToGraph() != null) {
Set enabledTags =
new HashSet(Arrays.asList(MiscUtils.splitAndTrim(getTagNamesToGraph(), ",")));
retVal.setEnabledTags(enabledTags);
}
return retVal;
}
// --- exposed objects ---
/**
* Gets the contained StatisticsChartGenerator that is used to generate the graphs.
*
* @return The StatisticsChartGenerator used by this appender.
*/
public StatisticsChartGenerator getChartGenerator() {
return chartGenerator;
}
/**
* This static method returns any created GraphingStatisticsAppender by its name.
*
* @param appenderName the name of the GraphingStatisticsAppender to return
* @return the specified GraphingStatisticsAppender, or null if not found
*/
public static GraphingStatisticsAppender getAppenderByName(String appenderName) {
return APPENDERS_BY_NAME.get(appenderName);
}
/**
* This static method returns an unmodifiable collection of all GraphingStatisticsAppenders that have been created.
*
* @return The collection of GraphingStatisticsAppenders created in this VM.
*/
public static Collection getAllGraphingStatisticsAppenders() {
return Collections.unmodifiableCollection(APPENDERS_BY_NAME.values());
}
// --- appender attachable methods ---
public void addAppender(Appender appender) {
synchronized (downstreamAppenders) {
downstreamAppenders.addAppender(appender);
}
}
public Enumeration getAllAppenders() {
synchronized (downstreamAppenders) {
return downstreamAppenders.getAllAppenders();
}
}
public Appender getAppender(String name) {
synchronized (downstreamAppenders) {
return downstreamAppenders.getAppender(name);
}
}
public boolean isAttached(Appender appender) {
synchronized (downstreamAppenders) {
return downstreamAppenders.isAttached(appender);
}
}
public void removeAllAppenders() {
synchronized (downstreamAppenders) {
downstreamAppenders.removeAllAppenders();
}
}
public void removeAppender(Appender appender) {
synchronized (downstreamAppenders) {
downstreamAppenders.removeAppender(appender);
}
}
public void removeAppender(String name) {
synchronized (downstreamAppenders) {
downstreamAppenders.removeAppender(name);
}
}
// --- appender methods ---
protected void append(LoggingEvent event) {
Object logMessage = event.getMessage();
if (logMessage instanceof GroupedTimingStatistics && chartGenerator != null) {
chartGenerator.appendData((GroupedTimingStatistics) logMessage);
hasUnflushedData = true;
lastAppendedEventLevel = event.getLevel();
//output the graph if necessary to any attached appenders
if ((numLoggedStatistics.incrementAndGet() % getDataPointsPerGraph()) == 0) {
flush();
}
}
}
public boolean requiresLayout() {
return false;
}
public void close() {
//close any downstream appenders
synchronized (downstreamAppenders) {
flush();
for (Enumeration enumer = downstreamAppenders.getAllAppenders();
enumer != null && enumer.hasMoreElements();) {
Appender appender = (Appender) enumer.nextElement();
appender.close();
}
}
}
// --- Flushable method ---
/**
* This flush method writes the graph, with the data that exists at the time it is calld, to any attached appenders.
*/
public void flush() {
synchronized(downstreamAppenders) {
if (hasUnflushedData && downstreamAppenders.getAllAppenders() != null) {
downstreamAppenders.appendLoopOnAppenders(
new LoggingEvent(Logger.class.getName(),
Logger.getLogger(StopWatch.DEFAULT_LOGGER_NAME),
System.currentTimeMillis(),
lastAppendedEventLevel,
chartGenerator.getChartUrl(),
null)
);
hasUnflushedData = false;
}
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy