/* * Copyright 2007-2010 Sun Microsystems, Inc. * * This file is part of Project Darkstar Server. * * Project Darkstar Server is free software: you can redistribute it * and/or modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation and * distributed hereunder to you. * * Project Darkstar Server is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * -- */ package com.sun.sgs.impl.profile; import com.sun.sgs.profile.AggregateProfileCounter; import com.sun.sgs.profile.AggregateProfileOperation; import com.sun.sgs.profile.AggregateProfileSample; import com.sun.sgs.profile.ProfileCollector.ProfileLevel; import com.sun.sgs.profile.ProfileConsumer; import com.sun.sgs.profile.ProfileCounter; import com.sun.sgs.profile.ProfileOperation; import com.sun.sgs.profile.ProfileSample; import com.sun.sgs.profile.TaskProfileCounter; import com.sun.sgs.profile.TaskProfileOperation; import com.sun.sgs.profile.TaskProfileSample; import java.beans.PropertyChangeEvent; import java.util.Collection; import java.util.EmptyStackException; import java.util.LinkedList; import java.util.List; import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; /** * This simple implementation of <code>ProfileConsumer</code> reports all * data to a backing <code>ProfileCollectorImpl</code>. */ class ProfileConsumerImpl implements ProfileConsumer { /** The default aggregate sample capacity. */ public static final int DEFAULT_SAMPLE_AGGREGATE_CAPACITY = 0; /** * The default aggregate sample smoothing capacity, for calculating * the exponential weighted average of the samples. */ public static final double DEFAULT_SAMPLE_AGGREGATE_SMOOTHING = 0.7; // the fullName of the consumer private final String name; // the collector that aggregates our data private final ProfileCollectorImpl profileCollector; // the profile level for this consumer private ProfileLevel profileLevel; // the ops, samples, and counters for this consumer private final ConcurrentHashMap<String, ProfileOperation> ops; private final ConcurrentHashMap<String, ProfileSample> samples; private final ConcurrentHashMap<String, ProfileCounter> counters; /** * Creates an instance of <code>ProfileConsumerImpl</code>. * * @param profileCollector the backing <code>ProfileCollectorImpl</code> * @param name an identifier for this consumer */ ProfileConsumerImpl(ProfileCollectorImpl profileCollector, String name) { if (profileCollector == null) { throw new NullPointerException("The collector must not be null"); } this.name = name; this.profileCollector = profileCollector; // default profile level is taken from the collector this.profileLevel = profileCollector.getDefaultProfileLevel(); ops = new ConcurrentHashMap<String, ProfileOperation>(); samples = new ConcurrentHashMap<String, ProfileSample>(); counters = new ConcurrentHashMap<String, ProfileCounter>(); } /** {@inheritDoc} */ public ProfileLevel getProfileLevel() { return profileLevel; } /** {@inheritDoc} */ public void setProfileLevel(ProfileLevel level) { profileLevel = level; } /** {@inheritDoc} */ public synchronized ProfileOperation createOperation(String name, ProfileDataType type, ProfileLevel minLevel) { if (name == null) { throw new NullPointerException("Operation name must not be null"); } String fullName = getCanonicalName(name); ProfileOperation op = ops.get(fullName); if (op == null) { switch (type) { case TASK: op = new TaskProfileOperationImpl(fullName, type, minLevel); break; case AGGREGATE: op = new AggregateProfileOperationImpl(fullName, type, minLevel); break; case TASK_AND_AGGREGATE: default: op = new TaskAggregateProfileOperationImpl(fullName, type, minLevel); break; } ops.put(fullName, op); } else { // Check minLevel and type if (op instanceof AbstractProfileData) { AbstractProfileData oldData = (AbstractProfileData) op; ProfileLevel oldLevel = oldData.getMinLevel(); if (oldLevel != minLevel) { throw new IllegalArgumentException( "Operation with name " + name + " already created, but with level " + oldLevel); } ProfileDataType oldType = oldData.getType(); if (oldType != type) { throw new IllegalArgumentException( "Operation with name " + name + " already created, but with type " + oldType); } } else { // Can't happen in this implementation throw new IllegalArgumentException( "Operation with name " + name + " already created with an unknown type"); } } PropertyChangeEvent event = new PropertyChangeEvent(this, "com.sun.sgs.profile.newop", null, op); profileCollector.notifyListeners(event); return op; } /** {@inheritDoc} */ public synchronized ProfileCounter createCounter(String name, ProfileDataType type, ProfileLevel minLevel) { if (name == null) { throw new NullPointerException("Counter name must not be null"); } String fullName = getCanonicalName(name); if (counters.containsKey(fullName)) { ProfileCounter oldCounter = counters.get(fullName); // Check minLevel and type if (oldCounter instanceof AbstractProfileData) { AbstractProfileData oldData = (AbstractProfileData) oldCounter; ProfileLevel oldLevel = oldData.getMinLevel(); if (oldLevel != minLevel) { throw new IllegalArgumentException( "Counter with name " + name + " already created, but with level " + oldLevel); } ProfileDataType oldType = oldData.getType(); if (oldType != type) { throw new IllegalArgumentException( "Counter with name " + name + " already created, but with type " + oldType); } } else { // Can't happen in this implementation throw new IllegalArgumentException( "Counter with name " + name + " already created with an unknown type"); } return oldCounter; } else { ProfileCounter counter; switch (type) { case TASK: counter = new TaskProfileCounterImpl(fullName, type, minLevel); break; case AGGREGATE: counter = new AggregateProfileCounterImpl(fullName, type, minLevel); break; case TASK_AND_AGGREGATE: default: counter = new TaskAggregateProfileCounterImpl(fullName, type, minLevel); break; } counters.put(fullName, counter); return counter; } } /** * {@inheritDoc} * <p> * The default capacity of the created {@code ProfileSample} is * {@value #DEFAULT_SAMPLE_AGGREGATE_CAPACITY} and can be modified * by calling {@link AggregateProfileSample#setCapacity}. * <p> * These samples use an exponential weighted average to calculate * their averages. The default smoothing factor for the aggregate sample * is {@value #DEFAULT_SAMPLE_AGGREGATE_SMOOTHING} and can be modified * by calling {@link AggregateProfileSample#setSmoothingFactor}. */ public synchronized ProfileSample createSample(String name, ProfileDataType type, ProfileLevel minLevel) { if (name == null) { throw new NullPointerException("Sample name must not be null"); } String fullName = getCanonicalName(name); if (samples.containsKey(fullName)) { ProfileSample oldSample = samples.get(fullName); // Check minLevel and type if (oldSample instanceof AbstractProfileData) { AbstractProfileData oldData = (AbstractProfileData) oldSample; ProfileLevel oldLevel = oldData.getMinLevel(); if (oldLevel != minLevel) { throw new IllegalArgumentException( "Sample with name " + name + " already created, but with level " + oldLevel); } ProfileDataType oldType = oldData.getType(); if (oldType != type) { throw new IllegalArgumentException( "Sample with name " + name + " already created, but with type " + oldType); } } else { // Can't happen in this implementation throw new IllegalArgumentException( "Sample with name " + name + " already created with an unknown type"); } return samples.get(fullName); } else { ProfileSample sample; switch (type) { case TASK: sample = new TaskProfileSampleImpl(fullName, type, minLevel); break; case AGGREGATE: sample = new AggregateProfileSampleImpl(fullName, type, minLevel); break; case TASK_AND_AGGREGATE: default: sample = new TaskAggregateProfileSampleImpl(fullName, type, minLevel); break; } samples.put(fullName, sample); return sample; } } /** {@inheritDoc} */ public String getName() { return name; } /** * Given a name for a profiling data object, return its canonical name, * which is unique across profile consumers. * * @param name the name of the profile data * @return the canonical name for the data, which includes this collector's * name */ private String getCanonicalName(String name) { return this.name + "." + name; } /** * Package private method to access all operations. Used by the * {@code ProfileCollector}. * @return a snapshot of the registered operations */ Collection<ProfileOperation> getOperations() { return ops.values(); } /** * Abstract base class for all profile data implementations, to hold * common information. */ private abstract class AbstractProfileData { protected final String name; protected final ProfileLevel minLevel; /* Type used for error checking in factory method */ protected final ProfileDataType type; AbstractProfileData(String name, ProfileDataType type, ProfileLevel minLevel) { if (name == null) { throw new NullPointerException("Name must not be null"); } this.name = name; this.type = type; this.minLevel = minLevel; } public String getName() { return name; } public String toString() { return name; } ProfileLevel getMinLevel() { return minLevel; } ProfileDataType getType() { return type; } } /** * Aggregating profile operation. */ private class AggregateProfileOperationImpl extends AbstractProfileData implements AggregateProfileOperation { private final AtomicLong count = new AtomicLong(); AggregateProfileOperationImpl(String opName, ProfileDataType type, ProfileLevel minLevel) { super(opName, type, minLevel); } /** {@inheritDoc} */ public void clearCount() { count.set(0); } /** {@inheritDoc} */ public long getCount() { return count.get(); } /** {@inheritDoc} */ public void report() { // If the minimum level we want to profile at is greater than // the current level, just return. if (minLevel.ordinal() > profileLevel.ordinal()) { return; } count.incrementAndGet(); } } /** * Task reporting profile operation. */ private class TaskProfileOperationImpl extends AbstractProfileData implements TaskProfileOperation { TaskProfileOperationImpl(String opName, ProfileDataType type, ProfileLevel minLevel) { super(opName, type, minLevel); } /** {@inheritDoc} */ public void report() { // If the minimum level we want to profile at is greater than // the current level, just return. if (minLevel.ordinal() > profileLevel.ordinal()) { return; } try { ProfileReportImpl profileReport = profileCollector.getCurrentProfileReport(); profileReport.addOperation(name); } catch (EmptyStackException ese) { throw new IllegalStateException("Cannot report operation " + "because no task is active"); } } } /** * A profile operation which both aggregates and is reported per-task. */ private class TaskAggregateProfileOperationImpl extends AggregateProfileOperationImpl implements TaskProfileOperation { private final TaskProfileOperationImpl taskOperation; TaskAggregateProfileOperationImpl(String opName, ProfileDataType type, ProfileLevel minLevel) { super(opName, type, minLevel); taskOperation = new TaskProfileOperationImpl(opName, type, minLevel); } /** {@inheritDoc} */ public void report() { super.report(); try { taskOperation.report(); } catch (IllegalStateException e) { // there is no task to report to } } } /** * Aggregating profile counter. */ private class AggregateProfileCounterImpl extends AbstractProfileData implements AggregateProfileCounter { private final AtomicLong count = new AtomicLong(); AggregateProfileCounterImpl(String name, ProfileDataType type, ProfileLevel minLevel) { super(name, type, minLevel); } /** {@inheritDoc} */ public void incrementCount() { // If the minimum level we want to profile at is greater than // the current level, just return. if (minLevel.ordinal() > profileLevel.ordinal()) { return; } count.incrementAndGet(); } /** {@inheritDoc} */ public void incrementCount(long value) { // If the minimum level we want to profile at is greater than // the current level, just return. if (minLevel.ordinal() > profileLevel.ordinal()) { return; } if (value < 0) { throw new IllegalArgumentException("Increment value must be " + "non-negative"); } count.addAndGet(value); } /** {@inheritDoc} */ public void clearCount() { count.set(0); } /** {@inheritDoc} */ public long getCount() { return count.get(); } } /** * Profile counter which reports task-local data. */ private class TaskProfileCounterImpl extends AbstractProfileData implements TaskProfileCounter { TaskProfileCounterImpl(String name, ProfileDataType type, ProfileLevel minLevel) { super(name, type, minLevel); } public void incrementCount() { // If the minimum level we want to profile at is greater than // the current level, just return. if (minLevel.ordinal() > profileLevel.ordinal()) { return; } try { ProfileReportImpl profileReport = profileCollector.getCurrentProfileReport(); profileReport.incrementTaskCounter(name, 1L); } catch (EmptyStackException ese) { throw new IllegalStateException("Cannot report counter " + "because no task is active"); } } public void incrementCount(long value) { // If the minimum level we want to profile at is greater than // the current level, just return. if (minLevel.ordinal() > profileLevel.ordinal()) { return; } if (value < 0) { throw new IllegalArgumentException("Increment value must be " + "greater than zero"); } try { ProfileReportImpl profileReport = profileCollector.getCurrentProfileReport(); profileReport.incrementTaskCounter(name, value); } catch (EmptyStackException ese) { throw new IllegalStateException("Cannot report counter " + "because no task is active"); } } } /** * Profile counter which both aggregates globally and reports * task-local data into profile reports. */ private class TaskAggregateProfileCounterImpl extends AggregateProfileCounterImpl implements TaskProfileCounter { private final TaskProfileCounterImpl taskCounter; TaskAggregateProfileCounterImpl(String opName, ProfileDataType type, ProfileLevel minLevel) { super(opName, type, minLevel); taskCounter = new TaskProfileCounterImpl(opName, type, minLevel); } /** {@inheritDoc} */ public void incrementCount() { super.incrementCount(); try { taskCounter.incrementCount(); } catch (IllegalStateException e) { // there is no task to report to } } /** {@inheritDoc} */ public void incrementCount(long value) { super.incrementCount(value); try { taskCounter.incrementCount(value); } catch (IllegalStateException e) { // there is no task to report to } } } /** * Aggregating profile sample. */ private class AggregateProfileSampleImpl extends AbstractProfileData implements AggregateProfileSample { private final Queue<Long> samples = new ConcurrentLinkedQueue<Long>(); private final AtomicInteger roughSize = new AtomicInteger(); private volatile int capacity = DEFAULT_SAMPLE_AGGREGATE_CAPACITY; private volatile double smoothingFactor = DEFAULT_SAMPLE_AGGREGATE_SMOOTHING; private long minSampleValue = Long.MAX_VALUE; private long maxSampleValue = Long.MIN_VALUE; private final ExponentialAverage avgSampleValue = new ExponentialAverage(); AggregateProfileSampleImpl(String name, ProfileDataType type, ProfileLevel minLevel) { super(name, type, minLevel); } public void addSample(long value) { // If the minimum level we want to profile at is greater than // the current level, just return. if (minLevel.ordinal() > profileLevel.ordinal()) { return; } if (capacity > 0) { if (samples.size() >= capacity) { samples.remove(); // remove oldest } else { roughSize.incrementAndGet(); } samples.add(value); } synchronized (this) { // Update the statistics if (value > maxSampleValue) { maxSampleValue = value; } if (value < minSampleValue) { minSampleValue = value; } avgSampleValue.update(value); } } /** {@inheritDoc} */ public synchronized List<Long> getSamples() { return new LinkedList<Long>(samples); } /** {@inheritDoc} */ public int getNumSamples() { return roughSize.intValue(); } /** {@inheritDoc} */ public synchronized void clearSamples() { samples.clear(); roughSize.set(0); avgSampleValue.clear(); maxSampleValue = Long.MIN_VALUE; minSampleValue = Long.MAX_VALUE; } /** {@inheritDoc} */ public double getAverage() { return avgSampleValue.avg; } /** {@inheritDoc} */ public synchronized long getMaxSample() { return maxSampleValue; } /** {@inheritDoc} */ public synchronized long getMinSample() { return minSampleValue; } /** {@inheritDoc} */ public int getCapacity() { return capacity; } /** {@inheritDoc} */ public void setCapacity(int capacity) { if (capacity < 0) { throw new IllegalArgumentException( "capacity must not be negative"); } this.capacity = capacity; } /** {@inheritDoc} */ public void setSmoothingFactor(double smooth) { if (smooth < 0.0 || smooth > 1.0) { throw new IllegalArgumentException( "Smoothing factor must be between 0.0 and 1.0, was " + smooth); } smoothingFactor = smooth; } /** {@inheritDoc} */ public double getSmoothingFactor() { return smoothingFactor; } private class ExponentialAverage { private boolean first = true; private double last; double avg; void update(long sample) { // calculate the exponential smoothed data: // current avg = // (smoothingFactor * current sample) // + ((1 - smoothingFactor) * last avg) // This is the same as: // current avg = // (smoothingFactor * current sample) // + (last avg) // - (smoothingFactor * last avg) // Which simplifies to: // current avg = // (current sample - lastAvg) * smoothingFactor + lastAvg if (first) { avg = sample; first = false; } else { avg = (sample - last) * smoothingFactor + last; } last = avg; } void clear() { first = true; last = 0; avg = 0; } } } /** * Task-local profile sample. */ private class TaskProfileSampleImpl extends AbstractProfileData implements TaskProfileSample { TaskProfileSampleImpl(String name, ProfileDataType type, ProfileLevel minLevel) { super(name, type, minLevel); } public void addSample(long value) { // If the minimum level we want to profile at is greater than // the current level, just return. if (minLevel.ordinal() > profileLevel.ordinal()) { return; } try { ProfileReportImpl profileReport = profileCollector.getCurrentProfileReport(); profileReport.addTaskSample(name, value); } catch (EmptyStackException ese) { throw new IllegalStateException("Cannot report sample " + "because no task is active"); } } } /** * Profile sample which both aggregates and provides task-local information. */ private class TaskAggregateProfileSampleImpl extends AggregateProfileSampleImpl implements TaskProfileSample { private final TaskProfileSample taskSample; TaskAggregateProfileSampleImpl(String name, ProfileDataType type, ProfileLevel minLevel) { super(name, type, minLevel); taskSample = new TaskProfileSampleImpl(name, type, minLevel); } /** {@inheritDoc} */ public void addSample(long value) { super.addSample(value); try { taskSample.addSample(value); } catch (IllegalStateException e) { // there is no task to report to } } } }