/*
* Copyright 2014 Martin Kouba
*
* 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.trimou.engine.listener;
import java.util.Collection;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import org.trimou.Mustache;
import org.trimou.engine.cache.ComputingCache;
import org.trimou.util.Checker;
import org.trimou.util.ImmutableMap;
import org.trimou.util.ImmutableSet;
import org.trimou.util.ImmutableSet.ImmutableSetBuilder;
/**
* Unlike {@link SimpleStatsCollector} this listener is able to detect rendering
* errors. Also {@link Mustache#getGeneratedId()} is used to map statistics to a
* template. On the other hand it's more resource-intensive.
*
* @author Martin Kouba
*/
public class EnhancedStatsCollector extends AbstractStatsCollector {
public static final String COMPUTING_CACHE_CONSUMER_ID = EnhancedStatsCollector.class
.getName();
private final ConcurrentMap<Long, String> idsToNames;
protected ComputingCache<Long, ConcurrentMap<Long, ExecutionData>> data;
public EnhancedStatsCollector() {
this(null, null);
}
public EnhancedStatsCollector(Predicate<String> templatePredicate,
TimeUnit timeUnit) {
super(templatePredicate, timeUnit);
idsToNames = new ConcurrentHashMap<>();
}
@Override
public void renderingStarted(MustacheRenderingEvent event) {
if (isApplied(event.getMustacheName())) {
idsToNames.putIfAbsent(event.getMustacheGeneratedId(),
event.getMustacheName());
data.get(event.getMustacheGeneratedId()).put(
event.getGeneratedId(), new ExecutionData(System.nanoTime()));
}
}
@Override
public void renderingFinished(MustacheRenderingEvent event) {
if (isApplied(event.getMustacheName())) {
data.get(event.getMustacheGeneratedId())
.get(event.getGeneratedId()).setEnd(System.nanoTime());
}
}
@Override
protected void init() {
data = configuration.getComputingCacheFactory().create(COMPUTING_CACHE_CONSUMER_ID, key ->
new ConcurrentHashMap<>(), null, null, null);
}
/**
*
* @param mustache
* @return the statistics for the given template
*/
public Stats getStats(Mustache mustache) {
Checker.checkArgumentNotNull(mustache);
ConcurrentMap<Long, ExecutionData> times = data.getIfPresent(mustache
.getGeneratedId());
if (times != null) {
return parseData(mustache.getName(), mustache.getGeneratedId(),
times.values());
}
return null;
}
/**
*
* @return the statistics
*/
public Set<Stats> getStats() {
ImmutableSetBuilder<Stats> builder = ImmutableSet.builder();
for (Entry<Long, ConcurrentMap<Long, ExecutionData>> entry : data
.getAllPresent().entrySet()) {
builder.add(parseData(idsToNames.get(entry.getKey()),
entry.getKey(), entry.getValue().values()));
}
return builder.build();
}
/**
*
* @param mustache
* @return the raw data for the given template
*/
public Collection<ExecutionData> getRawData(Mustache mustache) {
Checker.checkArgumentNotNull(mustache);
ConcurrentMap<Long, ExecutionData> executions = data.getIfPresent(mustache
.getGeneratedId());
if (executions != null) {
return ImmutableMap.copyOf(executions).values();
}
return null;
}
/**
* Drop all the collected data.
*/
public void clearData() {
data.clear();
}
private Stats parseData(String mustacheName, long mustacheId,
Collection<ExecutionData> executions) {
long errors = 0L;
long finished = 0L;
long totalTime = 0L;
long minTime = Long.MAX_VALUE;
long maxTime = 0L;
for (ExecutionData execution : executions) {
if (execution.isFinished()) {
long value = convert(execution.getValue());
finished += 1;
totalTime += value;
if (value > maxTime) {
maxTime = value;
}
if (value < minTime) {
minTime = value;
}
} else {
errors += 1;
}
}
long meanTime = (totalTime / finished);
return new Stats(mustacheId, mustacheName, finished, errors, totalTime,
meanTime, minTime, maxTime);
}
public static class ExecutionData {
private final long start;
private Long end;
ExecutionData(long start) {
this.start = start;
}
void setEnd(long end) {
this.end = end;
}
boolean isFinished() {
return end != null;
}
long getValue() {
return end - start;
}
}
}