/* * Copyright 2010 Martin Grotzke * * 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 de.javakaffee.web.msm; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import javax.annotation.Nonnull; /** * @author <a href="mailto:martin.grotzke@javakaffee.de">Martin Grotzke</a> */ public class Statistics { private final AtomicLong _numRequestsWithoutSession = new AtomicLong(); private final AtomicLong _numRequestsWithTomcatFailover = new AtomicLong(); private final AtomicLong _numRequestsWithSession = new AtomicLong(); private final AtomicLong _numRequestsWithMemcachedFailover = new AtomicLong(); private final AtomicLong _numRequestsWithBackupFailure = new AtomicLong(); private final AtomicLong _numRequestsWithoutSessionAccess = new AtomicLong(); private final AtomicLong _numRequestsWithoutAttributesAccess = new AtomicLong(); private final AtomicLong _numRequestsWithoutSessionModification = new AtomicLong(); private final AtomicLong _numNonStickySessionsPingFailed = new AtomicLong(); private final AtomicLong _numNonStickySessionsReadOnlyRequest = new AtomicLong(); private final Map<StatsType, MinMaxAvgProbe> _probes; private Statistics() { _probes = new ConcurrentHashMap<Statistics.StatsType, Statistics.MinMaxAvgProbe>(); for( final StatsType item : StatsType.values() ) { _probes.put( item, new MinMaxAvgProbe() ); } } /** * Creates a new (enabled) {@link Statistics} instance. * @return a new instance. */ public static Statistics create() { return create( true ); } /** * Creates a new {@link Statistics} instance which either actually gathers * statistics or a dummy {@link Statistics} object that discards all data. * * @param enabled specifies if stats shall be gathered or discarded. * @return a new {@link Statistics} instance */ public static Statistics create( final boolean enabled ) { return enabled ? new Statistics() : DISABLED_STATS; } /** * A utility method that calculates the difference of the time * between the given <code>startInMillis</code> and {@link System#currentTimeMillis()} * and registers the difference via {@link #register(long)} for the probe of the given {@link StatsType}. * @param statsType the specific execution type that is measured. * @param startInMillis the time in millis that shall be subtracted from {@link System#currentTimeMillis()}. */ public void registerSince( @Nonnull final StatsType statsType, final long startInMillis ) { register( statsType, System.currentTimeMillis() - startInMillis ); } /** * Register the given value via {@link MinMaxAvgProbe#register(long)} for the probe of the given {@link StatsType}. * @param statsType the specific execution type that is measured. * @param value the value to register. */ public void register( @Nonnull final StatsType statsType, final long value ) { _probes.get( statsType ).register( value ); } @Nonnull public MinMaxAvgProbe getProbe( @Nonnull final StatsType statsType ) { return _probes.get( statsType ); } public void requestWithoutSession() { _numRequestsWithoutSession.incrementAndGet(); } public long getRequestsWithoutSession() { return _numRequestsWithoutSession.get(); } public void requestWithSession() { _numRequestsWithSession.incrementAndGet(); } public long getRequestsWithSession() { return _numRequestsWithSession.get(); } public void requestWithTomcatFailover() { _numRequestsWithTomcatFailover.incrementAndGet(); } public long getRequestsWithTomcatFailover() { return _numRequestsWithTomcatFailover.get(); } public void requestWithMemcachedFailover() { _numRequestsWithMemcachedFailover.incrementAndGet(); } public long getRequestsWithMemcachedFailover() { return _numRequestsWithMemcachedFailover.get(); } public void requestWithBackupFailure() { _numRequestsWithBackupFailure.incrementAndGet(); } public long getRequestsWithBackupFailure() { return _numRequestsWithBackupFailure.get(); } public void requestWithoutSessionAccess() { _numRequestsWithoutSessionAccess.incrementAndGet(); } public long getRequestsWithoutSessionAccess() { return _numRequestsWithoutSessionAccess.get(); } public void requestWithoutAttributesAccess() { _numRequestsWithoutAttributesAccess.incrementAndGet(); } public long getRequestsWithoutAttributesAccess() { return _numRequestsWithoutAttributesAccess.get(); } public void requestWithoutSessionModification() { _numRequestsWithoutSessionModification.incrementAndGet(); } public long getRequestsWithoutSessionModification() { return _numRequestsWithoutSessionModification.get(); } public void nonStickySessionsPingFailed() { _numNonStickySessionsPingFailed.incrementAndGet(); } public long getNonStickySessionsPingFailed() { return _numNonStickySessionsPingFailed.get(); } public void nonStickySessionsReadOnlyRequest() { _numNonStickySessionsReadOnlyRequest.incrementAndGet(); } public long getNonStickySessionsReadOnlyRequest() { return _numNonStickySessionsReadOnlyRequest.get(); } public static enum StatsType { /** * Provides info regarding the effective time that was required for session * backup in the request thread and it's measured for every request with a session, * even if the session id has not set memcached id (this is the time that was effectively * required as part of the client request). It should differ from {@link #getBackupProbe()} * if async session backup shall be done. * * @see BackupSessionService#backupSession(MemcachedBackupSession, boolean) */ EFFECTIVE_BACKUP, /** * Provides info regarding the time that was required for session backup, * excluding skipped backups and excluding backups where a session was relocated. */ BACKUP, ATTRIBUTES_SERIALIZATION, SESSION_DESERIALIZATION, MEMCACHED_UPDATE, LOAD_FROM_MEMCACHED, DELETE_FROM_MEMCACHED, CACHED_DATA_SIZE, /** * Lock acquiration in non-sticky session mode. */ ACQUIRE_LOCK, /** * Lock acquiration failures in non-sticky session mode. */ ACQUIRE_LOCK_FAILURE, /** * Lock release in non-sticky session mode. */ RELEASE_LOCK, /** * Time spent (in the request thread) for non-sticky sessions at the end of requests that did not access * the session (performs validity load/update, ping session, ping 2nd session backup, update validity backup in secondary memcached). */ NON_STICKY_ON_BACKUP_WITHOUT_LOADED_SESSION, /** * Time spent for non-sticky sessions after session backup in the request thread (ping session, store validity info / meta data, * store additional backup in secondary memcached). */ NON_STICKY_AFTER_BACKUP, /** * Tasks executed for non-sticky sessions after a session was loaded from memcached (load validity info / meta data). */ NON_STICKY_AFTER_LOAD_FROM_MEMCACHED, /** * Tasks executed for non-sticky sessions after a session was deleted from memcached (delete validity info and backup data). */ NON_STICKY_AFTER_DELETE_FROM_MEMCACHED } public static class MinMaxAvgProbe { private boolean _first = true; private final AtomicInteger _count = new AtomicInteger(); private long _min; private long _max; private double _avg; /** * A utility method that calculates the difference of the time * between the given <code>startInMillis</code> and {@link System#currentTimeMillis()} * and registers the difference via {@link #register(long)}. * @param startInMillis the time in millis that shall be subtracted from {@link System#currentTimeMillis()}. */ public void registerSince( final long startInMillis ) { register( System.currentTimeMillis() - startInMillis ); } /** * Register the given value. * @param value the value to register. */ public void register( final long value ) { if ( value < _min || _first ) { _min = value; } if ( value > _max || _first ) { _max = value; } _avg = ( _avg * _count.get() + value ) / _count.incrementAndGet(); _first = false; } /** * @return the count */ int getCount() { return _count.get(); } /** * @return the min */ long getMin() { return _min; } /** * @return the max */ long getMax() { return _max; } /** * @return the avg */ double getAvg() { return _avg; } /** * Returns a string array with labels and values of count, min, avg and max. * @return a String array. */ public String[] getInfo() { return new String[] { "Count = " + _count.get(), "Min = "+ _min, "Avg = "+ _avg, "Max = "+ _max }; } } private static final Statistics DISABLED_STATS = new Statistics() { @Override public void registerSince(final StatsType statsType, final long startInMillis) {}; @Override public void register(final StatsType statsType, final long startInMillis) {}; public MinMaxAvgProbe getProbe( @Nonnull final StatsType statsType ) { return new MinMaxAvgProbe(); } /** * {@inheritDoc} */ @Override public void requestWithBackupFailure() { } /** * {@inheritDoc} */ @Override public void requestWithoutSession() { } /** * {@inheritDoc} */ @Override public void requestWithoutSessionAccess() { } /** * {@inheritDoc} */ @Override public void requestWithoutSessionModification() { } /** * {@inheritDoc} */ @Override public void requestWithSession() { } /** * {@inheritDoc} */ @Override public void requestWithMemcachedFailover() { } /** * {@inheritDoc} */ @Override public void requestWithTomcatFailover() { } @Override public void nonStickySessionsPingFailed() { } @Override public void nonStickySessionsReadOnlyRequest() { } @Override public void requestWithoutAttributesAccess() { } }; }