put in brackets :p

put in brackets :p

/*
 * Copyright (c) 2013 original authors
 *
 * 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.eigengo.monitor.agent.akka;

import akka.actor.*;
import akka.japi.Creator;
import org.eigengo.monitor.agent.AgentConfiguration;
import org.eigengo.monitor.output.CounterInterface;
import scala.Option;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Contains advices for monitoring behaviour of an actor; typically imprisoned in an {@code ActorCell}.
 */
public aspect ActorCellMonitoringAspect extends AbstractMonitoringAspect issingleton() {
    private AkkaAgentConfiguration agentConfiguration;
    private final CounterInterface counterInterface;
    private final Option<String> anonymousActorClassName = Option.empty();
    // we sample based on actor type (i.e. a sampling rate of 'n' for a filter means 'every nth actor with type k, for each type k that matches that filter')
    private final ConcurrentHashMap<Option<String>, AtomicLong> samplingCounters  = new ConcurrentHashMap<Option<String>, AtomicLong>();
    // we count actors by actor type (any 'anonymous' or 'generic' actors are treated as the same type)
    private final ConcurrentHashMap<Option<String>, AtomicInteger> numberOfActors = new ConcurrentHashMap<Option<String>, AtomicInteger>();
    private final ConcurrentHashMap<ActorPath, String> pathTags                   = new ConcurrentHashMap<ActorPath, String>();

    /**
     * Constructs this aspect
     */
    public ActorCellMonitoringAspect() {
        AgentConfiguration<AkkaAgentConfiguration> configuration = getAgentConfiguration("akka", AkkaAgentConfigurationJapi.apply());
        this.agentConfiguration = configuration.agent();
        this.counterInterface = createCounterInterface(configuration.common());
    }

    /**
     * Advises the {@code ActorCell.receiveMessage(message: Object): Unit}
     * We proceed with the pointcut if the actor is to be included in the monitoring *and* this is
     * the 'multiple-of-n'th time we've seen a message for an actor with a sample rate of n.
     *
     * Currently, we sample queue size, the fact that the message is delivered, the simple name of the class of the
     * message, and the time taken to complete the actor's reactive action.
     *
     * @param actorCell the ActorCell where the actor that receives the message "lives"
     * @param msg the incoming message
     */
    Object around(ActorCell actorCell, Object msg) : Pointcuts.actorCellReceiveMessage(actorCell, msg) {
        final ActorPath actorPath = actorCell.self().path();
        final Option<String> className = getActorClassName(actorCell, actorPath);
        final PathAndClass pathAndClass = new PathAndClass(actorPath, className);
        if (!includeActorPath(pathAndClass) || !sampleMessage(pathAndClass)) return proceed(actorCell, msg);

        int samplingRate = getSampleRate(pathAndClass);

        // we tag by actor name
        final String[] tags = getTags(actorPath, className);

        // record the queue size
        this.counterInterface.recordGaugeValue(Aspects.queueSize(), actorCell.numberOfMessages(), tags);
        // record the message, general and specific
        this.counterInterface.incrementCounter(Aspects.delivered(), samplingRate, tags);
        this.counterInterface.incrementCounter(Aspects.delivered(msg), samplingRate, tags);

        // measure the time. we're using the ``nanoTime`` call to access the high-precision timer.
        // since we're not really interested in wall time, but just some increasing measure of
        // elapsed time
        Object result = null;
        final long start = System.nanoTime();
        // result will always be ``null``, because target returns ``Unit``
        result = proceed(actorCell, msg);
        // ns or µs is too fine. ms will do for now.
        final long duration = (System.nanoTime() - start) / 1000000;

        // record the actor duration
        this.counterInterface.recordExecutionTime(Aspects.actorDuration(), (int)duration, tags);

        // return null would do the trick, but we want to be _proper_.
        return result;
    }

    /**
     * Advises the {@code ActorCell.handleInvokeFailure(_, failure: Throwable): Unit}
     *
     * @param actorCell the ActorCell where the error spreads from
     * @param failure the exception that escaped from the {@code receive} method
     */
    before(ActorCell actorCell, Throwable failure) : Pointcuts.actorCellHandleInvokeFailure(actorCell, failure) {
        // record the error, general and specific
        String[] tags = getTags(actorCell.self().path(), getActorClassName(actorCell, actorCell.self().path()));
        this.counterInterface.incrementCounter(Aspects.actorError(), tags);
        this.counterInterface.incrementCounter(Aspects.actorError(failure), tags);
    }

    /**
     * Advises the {@code EventStream.publish(event: Any): Unit}
     *
     * @param event the received event
     */
    before(Object event) : Pointcuts.eventStreamPublish(event) {
        if (event instanceof UnhandledMessage) {
            UnhandledMessage unhandledMessage = (UnhandledMessage)event;
            String[] tags = getTags(unhandledMessage.recipient().path(), this.anonymousActorClassName);
            this.counterInterface.incrementCounter(Aspects.undelivered(), tags);
            this.counterInterface.incrementCounter(Aspects.undelivered(unhandledMessage.getMessage()), tags);
        }
    }

    /**
     * Advises the {@code actorOf} method of {@code ActorRefFactory} implementations
     *
     * @param props the {@code Props} instance used in the call
     * @param actor the {@code ActorRef} returned from the call
     */
    after(Props props) returning (ActorRef actor) : Pointcuts.anyActorOf(props) {
        recordActorCount(actor.path(), props, CountType.Increment);
    }

    /**
     * Advises the {@code LocalActorRef.stop} method
     *
     * @param actorCell the {@code ActorCell} of the actor being stopped
     */
    after(ActorCell actorCell) : Pointcuts.actorCellInternalStop(actorCell) {
        recordActorCount(actorCell.self().path(), actorCell.props(), CountType.Decrement);
    }

    /**
     * Records the actor count increment or decrement
     *
     * @param path the ActorPath being created or destroyed
     * @param props the Props of the actor at the {@code path}
     * @param countType the increment or decrement
     */
     private void recordActorCount(ActorPath path, Props props, CountType countType) {
         final Option<String> className = getActorClassName(props, path);
         final PathAndClass pac = new PathAndClass(path, getActorClassName(props, path));
         if (!includeActorPath(pac)) return;

         final String[] tags = getTags(path, className);
         this.numberOfActors.putIfAbsent(className, new AtomicInteger(0));
         // increment and get the current number of actors of this type (if the value was 0, then this returns 1 -- which is correct)
         final int currentNumberOfActors;
         switch (countType) {
             case Increment:
                 currentNumberOfActors = this.numberOfActors.get(className).incrementAndGet();
                 break;
             case Decrement:
                 currentNumberOfActors = this.numberOfActors.get(className).decrementAndGet();
                 break;
             default:
                 currentNumberOfActors = 0;
                 break;
         }

         this.counterInterface.recordGaugeValue(Aspects.actorCount(), currentNumberOfActors, tags);
     }

    /**
     * Injects the new {@code AkkaAgentConfiguration} instance.
     *
     * @param agentConfiguration the new configuration
     */
    synchronized final void setAgentConfiguration(AkkaAgentConfiguration agentConfiguration) {
        this.agentConfiguration = agentConfiguration;
    }

    /**
     * The count type for exhaustive matching
     */
    private static enum CountType {
        Increment, Decrement
    }

    /**
     * Decide whether to include this ActorCell in our measurements
     *
     * @param pathAndClass the PAC container
     * @return whether to include the given actor in the metrics
     */
    private boolean includeActorPath(final PathAndClass pathAndClass) {
        // do not monitor our own output code
        if (pathAndClass.actorClassName().isDefined()) {
            if (pathAndClass.actorClassName().get().startsWith("org.eigengo.monitor.output")) return false;
        }

        // include actor if it's in 'included' list
        if (this.agentConfiguration.included().accept(pathAndClass)) return true;
        // exclude actor if excluded (i.e. is in 'excluded' list, or excludeAllNotIncluded = true, and actor is not included)
        if (this.agentConfiguration.excluded().accept(pathAndClass)) return false;

        // skip system actor checks if we wish to monitor them
        if (this.agentConfiguration.includeSystemAgents()) return true;

        String userOrSystem = pathAndClass.actorPath().getElements().iterator().next();
        return "user".equals(userOrSystem);
    }

    /**
     * Lookup the sample rate for an actor from the configuration
     *
     * @param pathAndClass the PAC container
     * @return the sampling rate as non-negative integer
     */
    private int getSampleRate(final PathAndClass pathAndClass) {
        return this.agentConfiguration.sampling().getRate(pathAndClass);
    }

    /**
     * Decide whether to sample an actor on a particular occasion
     *
     * @param pathAndClass the PAC container
     * @return {@code true} if we should sample this actor
     */
    private boolean sampleMessage(final PathAndClass pathAndClass) {
        final Option<String> className = pathAndClass.actorClassName();
        int sampleRate = getSampleRate(pathAndClass);
        if (sampleRate == 1) return true;

        this.samplingCounters.putIfAbsent(className, new AtomicLong(0));
        long timesSeenSoFar = this.samplingCounters.get(className).incrementAndGet();
        return (timesSeenSoFar % sampleRate == 1); // == 1 to log first value (incrementAndGet returns updated value)
    }

    /**
     * Formats the given {@code path} in a way that can be used for tagging. For example, for
     * path akka://default/user/a/b/c, it returns akka.path:/default/user/a/b/c
     *
     * @param path the path
     * @return the tag-ready path
     */
    private String actorPathToString(ActorPath path) {
        StringBuilder sb = new StringBuilder();
        sb.append("akka.path:/");
        sb.append(path.address().system());
        for (String element : path.getElements()) {
            sb.append("/");
            sb.append(element);
        }
        return sb.toString();
    }

    /**
     * Decides whether {@code path} represents the "root" of the user actors
     *
     * @param path the actor path
     * @return {@code true} if root of user actors
     */
    private boolean isUserRoot(ActorPath path) {
        return "user".equals(path.elements().mkString("/"));
    }

    /**
     * Computes the tags for the given {@code actorPath} and {@code actor} instances.
     *
     * @param actorPath the actor path; never {@code null}
     * @param actorClassName the actor instance; may be {@code null}
     * @return non-{@code null} array of tags
     */
    private String[] getTags(final ActorPath actorPath, final Option<String> actorClassName) {
        List<String> tags = new ArrayList<String>();

        // TODO: Improve detection of routed actors. This only detects "root" unnamed actors
        String lastPathElement = actorPath.elements().last();
        if (lastPathElement.startsWith("$")) {
            // this is routed actor.
            final ActorPath parent = actorPath.parent();
            if (isUserRoot(parent)) {
                // the parent is akka://xxx/user
                tags.add(actorPathToString(actorPath));
            } else {
                // the parent is some other actor, akka://xxx/user/foo
                tags.add(actorPathToString(parent));
                if (this.agentConfiguration.includeRoutees()) tags.add(actorPathToString(actorPath));
            }
        } else {
            // there is no supervisor
            tags.add(actorPathToString(actorPath));
        }
        if (actorClassName.isDefined()) {
            tags.add(String.format("akka.type:%s.%s", actorPath.address().system(), actorClassName.get()));
        }

        return tags.toArray(new String[tags.size()]);
    }

    /**
     * returns the canonical name of the actor type associated with an ActorCell
     *
     * @param actorCell the actor cell to get the name for
     * @return the Option of actor class, never {@code null}.
     */
    private Option<String> getActorClassName(final ActorCell actorCell, final ActorPath actorPath) {
        return getActorClassName(actorCell.props(), actorPath);
    }

    /**
     * Returns the canonical name of the actor type associated with a Props instance
     *
     * Returns null if props.actorClass does not have a canonical name (i.e., if
     * it is a local or anonymous class)
     *
     * Notably, when the actor class is anonymous, the {@code getCanonicalName} returns {@code null},
     * which we deal with here.
     *
     * @param props the ActorRef {@code Props} instance
     * @return the Option of actor class, never {@code null}.
     */
    private Option<String> getActorClassName(final Props props, final ActorPath actorPath) {
        final String canonicalName = props.actorClass().getCanonicalName();
        if (this.pathTags.containsKey(actorPath)) return Option.apply(this.pathTags.get(actorPath));
        if (canonicalName == null /*if actor is anonymous*/||/*OR actor is generic*/
                canonicalName.endsWith("akka.actor.Actor")) return this.anonymousActorClassName;
        return Option.apply(canonicalName);
    }

    /**
     * Catches actors created by the japi.Creator.create(), at a point when their type is visible to the runtime.
     * We use the visibility of the type at this one pointcut to create a map from the actor path to its type,
     * which we then access within the other pointcuts (notably on actor death and message receipt) to get the
     * 'real' type at those points.
     *
     * Since the 'ActorRefFactory.actorOf' method is guaranteed to be called on the creation of any actors after the
     * initialisation of the actorSystem itself, and since we consider anonymous/untyped actors in our count, we also
     * decrement the count of 'anonymous' actors here, for we know their type to be invisible to other pointcuts, and
     * that the element mapping path to type will not have been created (i.e. we generally assume actor paths are unique,
     * and that the actorOf advice will access the 'pathTags' map before we've inserted this new value). This approach
     * may not be robust enough -- in particular, with regards the second assumption.
     *
     * Regardless, we still double check that the map element doesn't already exist. If it does, we make the assumption
     * that it was created 'a while ago', and that the path referred to an actor of the same type.
     *
     * @param actor the Actor returned by the {@code Creator.create()} method
     * */
    after() returning(final Actor actor) : Pointcuts.actorCreator() {
        final String className = actor.getClass().getCanonicalName();
        final ActorPath actorPath = actor.self().path();
        if (this.pathTags.containsKey(actorPath)) return;
        // add the path -> type pair to the pathTags map
        this.pathTags.putIfAbsent(actorPath, className);

        // safe increment of the count of actors of this type
        this.numberOfActors.putIfAbsent(Option.apply(className), new AtomicInteger(0));
        int currentNumberOfActors = this.numberOfActors.get(Option.apply(className)).incrementAndGet();
        this.counterInterface.recordGaugeValue(Aspects.actorCount(), currentNumberOfActors, getTags(actorPath, Option.apply(className)));

        // safe decrement of the count of anonymous/untyped actors
        this.numberOfActors.putIfAbsent(this.anonymousActorClassName, new AtomicInteger(0));
                // reuse of int for (almost-?)invisible performance improvement
        currentNumberOfActors = this.numberOfActors.get(this.anonymousActorClassName).decrementAndGet();
        this.counterInterface.recordGaugeValue(Aspects.actorCount(), currentNumberOfActors, getTags(actorPath, Option.apply(className)));
    }

}

/*
 * Copyright (c) 2013 original authors
 *
 * 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.eigengo.monitor.agent.akka;

import akka.actor.*;
import akka.japi.Creator;
import org.eigengo.monitor.agent.AgentConfiguration;
import org.eigengo.monitor.output.CounterInterface;
import scala.Option;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Contains advices for monitoring behaviour of an actor; typically imprisoned in an {@code ActorCell}.
 */
public aspect ActorCellMonitoringAspect extends AbstractMonitoringAspect issingleton() {
    private AkkaAgentConfiguration agentConfiguration;
    private final CounterInterface counterInterface;
    private final Option<String> anonymousActorClassName = Option.empty();
    // we sample based on actor type (i.e. a sampling rate of 'n' for a filter means 'every nth actor with type k, for each type k that matches that filter')
    private final ConcurrentHashMap<Option<String>, AtomicLong> samplingCounters  = new ConcurrentHashMap<Option<String>, AtomicLong>();
    // we count actors by actor type (any 'anonymous' or 'generic' actors are treated as the same type)
    private final ConcurrentHashMap<Option<String>, AtomicInteger> numberOfActors = new ConcurrentHashMap<Option<String>, AtomicInteger>();
    private final ConcurrentHashMap<ActorPath, String> pathTags                   = new ConcurrentHashMap<ActorPath, String>();

    /**
     * Constructs this aspect
     */
    public ActorCellMonitoringAspect() {
        AgentConfiguration<AkkaAgentConfiguration> configuration = getAgentConfiguration("akka", AkkaAgentConfigurationJapi.apply());
        this.agentConfiguration = configuration.agent();
        this.counterInterface = createCounterInterface(configuration.common());
    }

    /**
     * Advises the {@code ActorCell.receiveMessage(message: Object): Unit}
     * We proceed with the pointcut if the actor is to be included in the monitoring *and* this is
     * the 'multiple-of-n'th time we've seen a message for an actor with a sample rate of n.
     *
     * Currently, we sample queue size, the fact that the message is delivered, the simple name of the class of the
     * message, and the time taken to complete the actor's reactive action.
     *
     * @param actorCell the ActorCell where the actor that receives the message "lives"
     * @param msg the incoming message
     */
    Object around(ActorCell actorCell, Object msg) : Pointcuts.actorCellReceiveMessage(actorCell, msg) {
        final ActorPath actorPath = actorCell.self().path();
        final Option<String> className = getActorClassName(actorCell, actorPath);
        final PathAndClass pathAndClass = new PathAndClass(actorPath, className);
        if (!includeActorPath(pathAndClass) || !sampleMessage(pathAndClass)) return proceed(actorCell, msg);

        int samplingRate = getSampleRate(pathAndClass);

        // we tag by actor name
        final String[] tags = getTags(actorPath, className);

        // record the queue size
        this.counterInterface.recordGaugeValue(Aspects.queueSize(), actorCell.numberOfMessages(), tags);
        // record the message, general and specific
        this.counterInterface.incrementCounter(Aspects.delivered(), samplingRate, tags);
        this.counterInterface.incrementCounter(Aspects.delivered(msg), samplingRate, tags);

        // measure the time. we're using the ``nanoTime`` call to access the high-precision timer.
        // since we're not really interested in wall time, but just some increasing measure of
        // elapsed time
        Object result = null;
        final long start = System.nanoTime();
        // result will always be ``null``, because target returns ``Unit``
        result = proceed(actorCell, msg);
        // ns or µs is too fine. ms will do for now.
        final long duration = (System.nanoTime() - start) / 1000000;

        // record the actor duration
        this.counterInterface.recordExecutionTime(Aspects.actorDuration(), (int)duration, tags);

        // return null would do the trick, but we want to be _proper_.
        return result;
    }

    /**
     * Advises the {@code ActorCell.handleInvokeFailure(_, failure: Throwable): Unit}
     *
     * @param actorCell the ActorCell where the error spreads from
     * @param failure the exception that escaped from the {@code receive} method
     */
    before(ActorCell actorCell, Throwable failure) : Pointcuts.actorCellHandleInvokeFailure(actorCell, failure) {
        // record the error, general and specific
        String[] tags = getTags(actorCell.self().path(), getActorClassName(actorCell, actorCell.self().path()));
        this.counterInterface.incrementCounter(Aspects.actorError(), tags);
        this.counterInterface.incrementCounter(Aspects.actorError(failure), tags);
    }

    /**
     * Advises the {@code EventStream.publish(event: Any): Unit}
     *
     * @param event the received event
     */
    before(Object event) : Pointcuts.eventStreamPublish(event) {
        if (event instanceof UnhandledMessage) {
            UnhandledMessage unhandledMessage = (UnhandledMessage)event;
            String[] tags = getTags(unhandledMessage.recipient().path(), this.anonymousActorClassName);
            this.counterInterface.incrementCounter(Aspects.undelivered(), tags);
            this.counterInterface.incrementCounter(Aspects.undelivered(unhandledMessage.getMessage()), tags);
        }
    }

    /**
     * Advises the {@code actorOf} method of {@code ActorRefFactory} implementations
     *
     * @param props the {@code Props} instance used in the call
     * @param actor the {@code ActorRef} returned from the call
     */
    after(Props props) returning (ActorRef actor) : Pointcuts.anyActorOf(props) {
        recordActorCount(actor.path(), props, CountType.Increment);
    }

    /**
     * Advises the {@code LocalActorRef.stop} method
     *
     * @param actorCell the {@code ActorCell} of the actor being stopped
     */
    after(ActorCell actorCell) : Pointcuts.actorCellInternalStop(actorCell) {
        recordActorCount(actorCell.self().path(), actorCell.props(), CountType.Decrement);
    }

    /**
     * Records the actor count increment or decrement
     *
     * @param path the ActorPath being created or destroyed
     * @param props the Props of the actor at the {@code path}
     * @param countType the increment or decrement
     */
     private void recordActorCount(ActorPath path, Props props, CountType countType) {
         final Option<String> className = getActorClassName(props, path);
         final PathAndClass pac = new PathAndClass(path, getActorClassName(props, path));
         if (!includeActorPath(pac)) return;

         final String[] tags = getTags(path, className);
         this.numberOfActors.putIfAbsent(className, new AtomicInteger(0));
         // increment and get the current number of actors of this type (if the value was 0, then this returns 1 -- which is correct)
         final int currentNumberOfActors;
         switch (countType) {
             case Increment:
                 currentNumberOfActors = this.numberOfActors.get(className).incrementAndGet();
                 break;
             case Decrement:
                 currentNumberOfActors = this.numberOfActors.get(className).decrementAndGet();
                 break;
             default:
                 currentNumberOfActors = 0;
                 break;
         }

         this.counterInterface.recordGaugeValue(Aspects.actorCount(), currentNumberOfActors, tags);
     }

    /**
     * Injects the new {@code AkkaAgentConfiguration} instance.
     *
     * @param agentConfiguration the new configuration
     */
    synchronized final void setAgentConfiguration(AkkaAgentConfiguration agentConfiguration) {
        this.agentConfiguration = agentConfiguration;
    }

    /**
     * The count type for exhaustive matching
     */
    private static enum CountType {
        Increment, Decrement
    }

    /**
     * Decide whether to include this ActorCell in our measurements
     *
     * @param pathAndClass the PAC container
     * @return whether to include the given actor in the metrics
     */
    private boolean includeActorPath(final PathAndClass pathAndClass) {
        // do not monitor our own output code
        if (pathAndClass.actorClassName().isDefined()) {
            if (pathAndClass.actorClassName().get().startsWith("org.eigengo.monitor.output")) return false;
        }

        // include actor if it's in 'included' list
        if (this.agentConfiguration.included().accept(pathAndClass)) return true;
        // exclude actor if excluded (i.e. is in 'excluded' list, or excludeAllNotIncluded = true, and actor is not included)
        if (this.agentConfiguration.excluded().accept(pathAndClass)) return false;

        // skip system actor checks if we wish to monitor them
        if (this.agentConfiguration.includeSystemAgents()) return true;

        String userOrSystem = pathAndClass.actorPath().getElements().iterator().next();
        return "user".equals(userOrSystem);
    }

    /**
     * Lookup the sample rate for an actor from the configuration
     *
     * @param pathAndClass the PAC container
     * @return the sampling rate as non-negative integer
     */
    private int getSampleRate(final PathAndClass pathAndClass) {
        return this.agentConfiguration.sampling().getRate(pathAndClass);
    }

    /**
     * Decide whether to sample an actor on a particular occasion
     *
     * @param pathAndClass the PAC container
     * @return {@code true} if we should sample this actor
     */
    private boolean sampleMessage(final PathAndClass pathAndClass) {
        final Option<String> className = pathAndClass.actorClassName();
        int sampleRate = getSampleRate(pathAndClass);
        if (sampleRate == 1) return true;

        this.samplingCounters.putIfAbsent(className, new AtomicLong(0));
        long timesSeenSoFar = this.samplingCounters.get(className).incrementAndGet();
        return (timesSeenSoFar % sampleRate == 1); // == 1 to log first value (incrementAndGet returns updated value)
    }

    /**
     * Formats the given {@code path} in a way that can be used for tagging. For example, for
     * path akka://default/user/a/b/c, it returns akka.path:/default/user/a/b/c
     *
     * @param path the path
     * @return the tag-ready path
     */
    private String actorPathToString(ActorPath path) {
        StringBuilder sb = new StringBuilder();
        sb.append("akka.path:/");
        sb.append(path.address().system());
        for (String element : path.getElements()) {
            sb.append("/");
            sb.append(element);
        }
        return sb.toString();
    }

    /**
     * Decides whether {@code path} represents the "root" of the user actors
     *
     * @param path the actor path
     * @return {@code true} if root of user actors
     */
    private boolean isUserRoot(ActorPath path) {
        return "user".equals(path.elements().mkString("/"));
    }

    /**
     * Computes the tags for the given {@code actorPath} and {@code actor} instances.
     *
     * @param actorPath the actor path; never {@code null}
     * @param actorClassName the actor instance; may be {@code null}
     * @return non-{@code null} array of tags
     */
    private String[] getTags(final ActorPath actorPath, final Option<String> actorClassName) {
        List<String> tags = new ArrayList<String>();

        // TODO: Improve detection of routed actors. This only detects "root" unnamed actors
        String lastPathElement = actorPath.elements().last();
        if (lastPathElement.startsWith("$")) {
            // this is routed actor.
            final ActorPath parent = actorPath.parent();
            if (isUserRoot(parent)) {
                // the parent is akka://xxx/user
                tags.add(actorPathToString(actorPath));
            } else {
                // the parent is some other actor, akka://xxx/user/foo
                tags.add(actorPathToString(parent));
                if (this.agentConfiguration.includeRoutees()) tags.add(actorPathToString(actorPath));
            }
        } else {
            // there is no supervisor
            tags.add(actorPathToString(actorPath));
        }
        if (actorClassName.isDefined()) {
            tags.add(String.format("akka.type:%s.%s", actorPath.address().system(), actorClassName.get()));
        }

        return tags.toArray(new String[tags.size()]);
    }

    /**
     * returns the canonical name of the actor type associated with an ActorCell
     *
     * @param actorCell the actor cell to get the name for
     * @return the Option of actor class, never {@code null}.
     */
    private Option<String> getActorClassName(final ActorCell actorCell, final ActorPath actorPath) {
        return getActorClassName(actorCell.props(), actorPath);
    }

    /**
     * Returns the canonical name of the actor type associated with a Props instance
     *
     * Returns null if props.actorClass does not have a canonical name (i.e., if
     * it is a local or anonymous class)
     *
     * Notably, when the actor class is anonymous, the {@code getCanonicalName} returns {@code null},
     * which we deal with here.
     *
     * @param props the ActorRef {@code Props} instance
     * @return the Option of actor class, never {@code null}.
     */
    private Option<String> getActorClassName(final Props props, final ActorPath actorPath) {
        final String canonicalName = props.actorClass().getCanonicalName();
        if (this.pathTags.containsKey(actorPath)) return Option.apply(this.pathTags.get(actorPath));
        if (canonicalName == null /*if actor is anonymous*/||/*OR actor is generic*/
                canonicalName.endsWith("akka.actor.Actor")) return this.anonymousActorClassName;
        return Option.apply(canonicalName);
    }

    /**
     * Catches actors created by the japi.Creator.create(), at a point when their type is visible to the runtime.
     * We use the visibility of the type at this one pointcut to create a map from the actor path to its type,
     * which we then access within the other pointcuts (notably on actor death and message receipt) to get the
     * 'real' type at those points.
     *
     * Since the 'ActorRefFactory.actorOf' method is guaranteed to be called on the creation of any actors after the
     * initialisation of the actorSystem itself, and since we consider anonymous/untyped actors in our count, we also
     * decrement the count of 'anonymous' actors here, for we know their type to be invisible to other pointcuts, and
     * that the element mapping path to type will not have been created (i.e. we generally assume actor paths are unique,
     * and that the actorOf advice will access the 'pathTags' map before we've inserted this new value). This approach
     * may not be robust enough -- in particular, with regards the second assumption.
     *
     * Regardless, we still double check that the map element doesn't already exist. If it does, we make the assumption
     * that it was created 'a while ago', and that the path referred to an actor of the same type.
     *
     * @param actor the Actor returned by the {@code Creator.create()} method
     * */
    after() returning(final Actor actor) : Pointcuts.actorCreator() {
        final String className = actor.getClass().getCanonicalName();
        final ActorPath actorPath = actor.self().path();
        if this.pathTags.containsKey(actorPath) return;
        // add the path -> type pair to the pathTags map
        this.pathTags.putIfAbsent(actorPath, className);

        // safe increment of the count of actors of this type
        this.numberOfActors.putIfAbsent(Option.apply(className), new AtomicInteger(0));
        int currentNumberOfActors = this.numberOfActors.get(Option.apply(className)).incrementAndGet();
        this.counterInterface.recordGaugeValue(Aspects.actorCount(), currentNumberOfActors, getTags(actorPath, Option.apply(className)));

        // safe decrement of the count of anonymous/untyped actors
        this.numberOfActors.putIfAbsent(this.anonymousActorClassName, new AtomicInteger(0));
                // reuse of int for (almost-?)invisible performance improvement
        currentNumberOfActors = this.numberOfActors.get(this.anonymousActorClassName).decrementAndGet();
        this.counterInterface.recordGaugeValue(Aspects.actorCount(), currentNumberOfActors, getTags(actorPath, Option.apply(className)));
    }

}


add license

add license

/*
 * hashshmash - An aspect to record HashMap/Set allocations
 * Copyright 2013 MeBigFatGuy.com
 * Copyright 2013 Dave Brosius
 *
 * 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 com.mebigfatguy.hashshmash;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

public aspect HashCollector {

    private static final Journaller journaller = new Journaller();

    after() returning (Map<?,?> m): call(HashMap.new(..)) {
        String fileName = thisJoinPointStaticPart.getSourceLocation().getFileName();
        int line = thisJoinPointStaticPart.getSourceLocation().getLine();

        HashMapDetails details = new HashMapDetails(m, new Date(), fileName + ":" + line);
        journaller.add(details);
    }

    after() returning (Set<?> s): call(HashSet.new(..)) {
        String fileName = thisJoinPointStaticPart.getSourceLocation().getFileName();
        int line = thisJoinPointStaticPart.getSourceLocation().getLine();

        HashSetDetails details = new HashSetDetails(s, new Date(), fileName + ":" + line);
        journaller.add(details);
    }

    before(): execution(public static void *.main(String[])) {
        Class<?> c = HashCollector.class;
    }
}

final class Journaller implements Runnable {

    private static final int SLEEP_TIME = 5 * 1000;

    private Field tableField;
    private ConcurrentHashMap<HashDetails, HashDetails> hDetails = new ConcurrentHashMap<HashDetails, HashDetails>();
    private PrintWriter writer;
    private boolean operational = false;

    public Journaller() {
        try {
            System.out.println("*************************");
            System.out.println("HASHSHMASH ASPECT ENABLED");
            System.out.println("*************************");

            tableField = HashMap.class.getDeclaredField("table");
            tableField.setAccessible(true);

            File dir = new File(System.getProperty("user.home"), ".hashshmash");
            dir.mkdirs();

            writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(dir, new Date().toString() + ".txt")))));

            Thread t = new Thread(this);
            t.setDaemon(true);
            operational = true;
            t.start();
        } catch (Exception e) {
        }
    }

    public void add(HashDetails details) {
        if (operational)
            hDetails.put(details,  details);
    }

    public void run() {
        try {
            while (!Thread.interrupted()) {
                Thread.sleep(SLEEP_TIME);

                for (HashDetails details : hDetails.keySet()) {
                    try {
                        Map<?, ?> map = details.getMap();
                        if (map != null) {
                            int newSize = details.getMap().size();
                            if (newSize == details.size) {
                                Entry<?, ?>[] table = (Entry<?, ?>[])tableField.get(map);
                                details.totalSlots = table.length;
                                for (Entry<?, ?> e : table) {
                                    if (e != null) {
                                        details.usedSlots++;
                                    }
                                }
                                writer.println(details);
                                hDetails.remove(details);
                            } else
                                details.size = newSize;
                        } else {
                            hDetails.remove(details);
                        }
                    } catch (Exception e) {
                        //might get ConcurrentModificationException or such, just ignore
                    }
                }
                writer.flush();
            }
        } catch (InterruptedException ie) {
        }
    }
}

abstract class HashDetails {
    private static SimpleDateFormat FORMATTER = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss-SSS");

    public Date allocTime;
    public String caller;
    public int size;
    public int usedSlots;
    public int totalSlots;

    public abstract Map<?,?> getMap();

    public abstract String getDetailType();

    public String toString() {
        return getDetailType() + "\t" + FORMATTER.format(allocTime) + "\t" + caller + "\t" + getMap().size() + "\t" + totalSlots + "\t" + usedSlots;
    }
}

class HashMapDetails extends HashDetails {
    public Map<?, ?> map;

    public HashMapDetails(Map<?, ?> m, Date d, String c) {
        map = m;
        allocTime = d;
        caller = c;
        size = map.size();
    }

    public Map<?,?> getMap() {
        return map;
    }

    public String getDetailType() {
        return map.getClass().getSimpleName();
    }
}

class HashSetDetails extends HashDetails {
    private static Field MAP_FIELD;
    static {
        try {
            MAP_FIELD = HashSet.class.getDeclaredField("map");
            MAP_FIELD.setAccessible(true);
        } catch (Exception e) {
            MAP_FIELD = null;
        }
    }

    public Set<?> set;

    public HashSetDetails(Set<?> s, Date d, String c) {
        set = s;
        allocTime = d;
        caller = c;
        size = set.size();
    }

    public Map<?,?> getMap() {
        try {
            if (MAP_FIELD == null)
                return null;

            return (Map<?,?>) MAP_FIELD.get(set);
        } catch (Exception e) {
            return null;
        }
    }

    public String getDetailType() {
        return set.getClass().getSimpleName();
    }
}


pretty up the report file

pretty up the report file

package com.mebigfatguy.hashshmash;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;

public aspect HashCollector {

    private static final Journaller journaller = new Journaller();

    after() returning (Map m): call(HashMap.new(..)) {
        String fileName = thisJoinPointStaticPart.getSourceLocation().getFileName();
        int line = thisJoinPointStaticPart.getSourceLocation().getLine();

        HashMapDetails details = new HashMapDetails(m, new Date(), fileName + ":" + line);
        journaller.add(details);
    }
}

final class Journaller implements Runnable {

    private static final int SLEEP_TIME = 10 * 1000;

    private Field tableField;
    private ConcurrentHashMap<HashMapDetails, HashMapDetails> hmDetails = new ConcurrentHashMap<HashMapDetails, HashMapDetails>();
    private PrintWriter writer;
    private SimpleDateFormat formatter;

    public Journaller() {
        try {
            tableField = HashMap.class.getDeclaredField("table");
            tableField.setAccessible(true);

            formatter = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
            File dir = new File(System.getProperty("user.home"), ".hashshmash");
            dir.mkdirs();

            writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(dir, new Date().toString() + ".txt")))));

            Thread t = new Thread(this);
            t.setDaemon(true);
            t.start();
        } catch (Exception e) {
        }
    }

    public void add(HashMapDetails details) {
        hmDetails.put(details,  details);
    }

    public void run() {
        try {
            while (!Thread.interrupted()) {
                Thread.sleep(SLEEP_TIME);

                for (HashMapDetails details : hmDetails.keySet()) {
                    try {
                        int newSize = details.map.size();
                        if (newSize == details.size) {
                            Entry<?, ?>[] table = (Entry<?, ?>[])tableField.get(details.map);
                            details.totalSlots = table.length;
                            for (Entry<?, ?> e : table) {
                                if (e != null) {
                                    details.usedSlots++;
                                }
                            }
                            writer.println(formatter.format(details.allocTime) + "\t" + details.caller + "\t" + details.map.size() + "\t" + details.totalSlots + "\t" + details.usedSlots);
                            hmDetails.remove(details);
                        } else {
                            details.size = newSize;
                        }
                    } catch (Exception e) {
                        //might get ConcurrentModificationException or such, just ignore
                    }
                }
                writer.flush();
            }
        } catch (InterruptedException ie) {
        }
    }
}

class HashMapDetails {
    public Map<?, ?> map;
    public Date allocTime;
    public String caller;
    public int size;
    public int usedSlots;
    public int totalSlots;

    public HashMapDetails(Map<?, ?> m, Date d, String c) {
        map = m;
        allocTime = d;
        caller = c;
        size = map.size();
    }
}

package com.mebigfatguy.hashshmash;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;

import org.aspectj.lang.Signature;

public aspect HashCollector {

    private static final Journaller journaller = new Journaller();

    after() returning (Map m): call(HashMap.new(..)) {
        Signature sig = thisJoinPointStaticPart.getSignature();
        int line = thisJoinPointStaticPart.getSourceLocation().getLine();

        HashMapDetails details = new HashMapDetails(m, System.currentTimeMillis(), sig + ":" + line);
        journaller.add(details);
    }
}

final class Journaller implements Runnable {

    private static final int SLEEP_TIME = 10 * 1000;

    private Field tableField;
    private ConcurrentHashMap<HashMapDetails, HashMapDetails> hmDetails = new ConcurrentHashMap<HashMapDetails, HashMapDetails>();
    private PrintWriter writer;

    public Journaller() {
        try {
            tableField = HashMap.class.getDeclaredField("table");
            tableField.setAccessible(true);

            File dir = new File(System.getProperty("user.home"), ".hashshmash");
            dir.mkdirs();

            writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(dir, new Date().toString() + ".txt")))));

            Thread t = new Thread(this);
            t.setDaemon(true);
            t.start();
        } catch (Exception e) {
        }
    }

    public void add(HashMapDetails details) {
        hmDetails.put(details,  details);
    }

    public void run() {
        try {
            while (!Thread.interrupted()) {
                Thread.sleep(SLEEP_TIME);

                for (HashMapDetails details : hmDetails.keySet()) {
                    try {
                        int newSize = details.map.size();
                        if (newSize == details.size) {
                            Entry<?, ?>[] table = (Entry<?, ?>[])tableField.get(details.map);
                            details.totalSlots = table.length;
                            for (Entry<?, ?> e : table) {
                                if (e != null) {
                                    details.usedSlots++;
                                }
                            }
                            writer.println(details.allocTime + "\t" + details.caller + "\t" + details.map.size() + "\t" + details.totalSlots + "\t" + details.usedSlots);
                            hmDetails.remove(details);
                        } else {
                            details.size = newSize;
                        }
                    } catch (Exception e) {
                        //might get ConcurrentModificationException or such, just ignore
                    }
                }
                writer.flush();
            }
        } catch (InterruptedException ie) {
        }
    }
}

class HashMapDetails {
    public Map<?, ?> map;
    public long allocTime;
    public String caller;
    public int size;
    public int usedSlots;
    public int totalSlots;

    public HashMapDetails(Map<?, ?> m, long t, String c) {
        map = m;
        allocTime = t;
        caller = c;
        size = map.size();
    }
}


'new.' -> ''

'new.' -> ''

/*
 * Copyright (c) 2013 original authors
 *
 * 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.eigengo.monitor.agent.akka;

import akka.actor.*;
import org.eigengo.monitor.agent.AgentConfiguration;
import org.eigengo.monitor.output.CounterInterface;
import scala.Option;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Contains advices for monitoring behaviour of an actor; typically imprisoned in an {@code ActorCell}.
 */
public aspect ActorCellMonitoringAspect extends AbstractMonitoringAspect issingleton() {
    private AkkaAgentConfiguration agentConfiguration;
    private final CounterInterface counterInterface;
    private final Option<String> noActorClazz = Option.empty();
    private final ConcurrentHashMap<Option<String>, AtomicLong> samplingCounters;
    private final ConcurrentHashMap<Option<String>, AtomicLong> numberOfActors;

    /**
     * Constructs this aspect
     */
    public ActorCellMonitoringAspect() {
        AgentConfiguration<AkkaAgentConfiguration> configuration = getAgentConfiguration("akka", AkkaAgentConfigurationJapi.apply());
        this.agentConfiguration = configuration.agent();
        this.counterInterface = createCounterInterface(configuration.common());
        this.samplingCounters = new ConcurrentHashMap<Option<String>, AtomicLong>();
        this.numberOfActors = new ConcurrentHashMap<Option<String>, AtomicLong>();
    }

    /**
     * Injects the new {@code AkkaAgentConfiguration} instance.
     *
     * @param agentConfiguration the new configuration
     */
    synchronized final void setAgentConfiguration(AkkaAgentConfiguration agentConfiguration) {
        this.agentConfiguration = agentConfiguration;
    }

    // decide whether to include this ActorCell in our measurements
    private boolean includeActorPath(final PathAndClass pathAndClass) {
        if (!this.agentConfiguration.incuded().accept(pathAndClass)) return false;
        if (this.agentConfiguration.excluded().accept(pathAndClass)) return false;

        if (this.agentConfiguration.includeSystemAgents()) return true;

        String userOrSystem = pathAndClass.actorPath().getElements().iterator().next();
        return "user".equals(userOrSystem);
    }

    // get the sample rate for an actor
    private int getSampleRate(final PathAndClass pathAndClass) {
        return this.agentConfiguration.sampling().getRate(pathAndClass);
    }

    // decide whether to sample an actor on a particular occasion
    private final boolean sampleMessage(final PathAndClass pathAndClass) {
        int sampleRate = getSampleRate(pathAndClass);
        if (sampleRate == 1) return true;

        this.samplingCounters.putIfAbsent(pathAndClass.actorClassName(), new AtomicLong(0));
        long timesSeenSoFar = this.samplingCounters.get(pathAndClass.actorClassName()).incrementAndGet();
        return (timesSeenSoFar % sampleRate == 1); // == 1 to log first value (incrementAndGet returns updated value)
    }

    // get the tags for the cell
    private String[] getTags(final ActorPath actorPath, final Actor actor) {
        List<String> tags = new ArrayList<String>();

        // TODO: Improve detection of routed actors. This relies only on the naming :(.
        String lastPathElement = actorPath.elements().last();
        if (lastPathElement.startsWith("$")) {
            // this is routed actor.
            tags.add(actorPath.parent().toString());
            if (this.agentConfiguration.includeRoutees()) tags.add(actorPath.toString());
        } else {
            // there is no supervisor
            tags.add(actorPath.toString());
        }
        if (actor != null) {
            tags.add(String.format("akka:%s.%s", actor.context().system().name(), actor.getClass().getCanonicalName()));
        }

        return tags.toArray(new String[tags.size()]);
    }

    /**
     * Advises the {@code ActorCell.receiveMessage(message: Object): Unit}
     * We proceed with the pointcut if the actor is to be included in the monitoring *and* this is
     * the 'multiple-of-n'th time we've seen a message for an actor with a sample rate of n.
     *
     * Currently, we sample queue size, the fact that the message is delivered, the simple name of the class of the
     * message, and the time taken to complete the actor's reactive action.
     *
     * @param actorCell the ActorCell where the actor that receives the message "lives"
     * @param msg the incoming message
     */
    Object around(ActorCell actorCell, Object msg) : Pointcuts.actorCellReceiveMessage(actorCell, msg) {
        final ActorPath actorPath = actorCell.self().path();
        final PathAndClass pathAndClass = new PathAndClass(actorPath, Option.apply(actorCell.actor().getClass().getCanonicalName()));
        if (!includeActorPath(pathAndClass) || !sampleMessage(pathAndClass)) return proceed(actorCell, msg);

        int samplingRate = getSampleRate(pathAndClass);

        // we tag by actor name
        final String[] tags = getTags(actorPath, actorCell.actor());

        // record the queue size
        this.counterInterface.recordGaugeValue("akka.queue.size", actorCell.numberOfMessages(), tags);
        // record the message, general and specific
        this.counterInterface.incrementCounter("akka.actor.delivered", samplingRate, tags);
        this.counterInterface.incrementCounter("akka.actor.delivered." + msg.getClass().getSimpleName(), samplingRate, tags);

        // measure the time. we're using the ``nanoTime`` call to access the high-precision timer.
        // since we're not really interested in wall time, but just some increasing measure of
        // elapsed time
        Object result = null;
        final long start = System.nanoTime();
        // result will always be ``null``, because target returns ``Unit``
        result = proceed(actorCell, msg);
        // ns or µs is too fine. ms will do for now.
        final long duration = (System.nanoTime() - start) / 1000000;

        // record the actor duration
        this.counterInterface.recordExecutionTime("akka.actor.duration", (int)duration, tags);

        // return null would do the trick, but we want to be _proper_.
        return result;
    }

    /**
     * Advises the {@code ActorCell.handleInvokeFailure(_, failure: Throwable): Unit}
     *
     * @param actorCell the ActorCell where the error spreads from
     * @param failure the exception that escaped from the {@code receive} method
     */
    before(ActorCell actorCell, Throwable failure) : Pointcuts.actorCellHandleInvokeFailure(actorCell, failure) {
        // record the error, general and specific
        String[] tags = getTags(actorCell.self().path(), actorCell.actor());
        this.counterInterface.incrementCounter("akka.actor.error", tags);
        this.counterInterface.incrementCounter(String.format("akka.actor.error.%s", failure.getMessage()), tags);
    }

    /**
     * Advises the {@code EventStream.publish(event: Any): Unit}
     *
     * @param event the received event
     */
    before(Object event) : Pointcuts.eventStreamPublish(event) {
        if (event instanceof UnhandledMessage) {
            UnhandledMessage unhandledMessage = (UnhandledMessage)event;
            String[] tags = getTags(unhandledMessage.recipient().path(), null);
            this.counterInterface.incrementCounter("akka.actor.undelivered", tags);
            this.counterInterface.incrementCounter("akka.actor.undelivered." + unhandledMessage.getMessage().getClass().getSimpleName(), tags);
        }
    }

    /**
     * Advises the {@code actorOf} method of {@code ActorCell} and {@code ActorSystem}
     *
     * @param props the {@code Props} instance used in the call
     * @param actor the {@code ActorRef} returned from the call
     */
    after(Props props) returning (ActorRef actor): Pointcuts.anyActorOf(props) {
        recordActorCreation(props, actor);
    }
    /**
     * Advises the {@code actorOf} method of {@code ActorCell} and {@code ActorSystem} when actor is named in construction
     *
     * @param props the {@code Props} instance used in the call
     * @param actorName the {@code String} used to name the actor at creation site
     * @param actor the {@code ActorRef} returned from the call
     */
    after(Props props, String actorName) returning (ActorRef actor): Pointcuts.namedActorOf(props, actorName) {
        recordActorCreation(props, actor);
    }

    // method containing the recording logic for advising actor creation
    void recordActorCreation(Props props, ActorRef actor) {
        if (!includeActorPath(new PathAndClass(actor.path(), this.noActorClazz))) return;
        final String uncheckedClassName = props.actorClass().getCanonicalName();
        final Option<String> className = Option.apply(uncheckedClassName);

        this.numberOfActors.putIfAbsent(className, new AtomicLong(0));
        // increment and get the current number of actors of this type (if the value was 0, then this returns 1 -- which is correct)
        final long value = this.numberOfActors.get(className).incrementAndGet();

        System.out.println("+++++"+className + " : " + value);
        // record the current number of actors of this type
        this.counterInterface.recordGaugeValue("akka.actor.count", (int)value, uncheckedClassName);
    }

    /**
     * Advises the {@code LocalActorRef.stop} method
     *
     *
     * @param actorCell the {@code ActorCell} of the actor being stopped
     */
    after(ActorCell actorCell) : Pointcuts.actorCellInternalStop(actorCell) {
        if (!includeActorPath(new PathAndClass(actorCell.self().path(), this.noActorClazz))) return;
        final String uncheckedClassName = actorCell.props().actorClass().getCanonicalName();
        final Option<String> className = Option.apply(uncheckedClassName);

        this.numberOfActors.putIfAbsent(className, new AtomicLong(0));
        final long value = this.numberOfActors.get(className).decrementAndGet();

        System.out.println("-----"+className + " : " + value);
        this.counterInterface.recordGaugeValue("akka.actor.count", (int)value, uncheckedClassName);
    }

}

/*
 * Copyright (c) 2013 original authors
 *
 * 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.eigengo.monitor.agent.akka;

import akka.actor.*;
import org.eigengo.monitor.agent.AgentConfiguration;
import org.eigengo.monitor.output.CounterInterface;
import scala.Option;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Contains advices for monitoring behaviour of an actor; typically imprisoned in an {@code ActorCell}.
 */
public aspect ActorCellMonitoringAspect extends AbstractMonitoringAspect issingleton() {
    private AkkaAgentConfiguration agentConfiguration;
    private final CounterInterface counterInterface;
    private final Option<String> noActorClazz = Option.empty();
    private final ConcurrentHashMap<Option<String>, AtomicLong> samplingCounters;
    private final ConcurrentHashMap<Option<String>, AtomicLong> numberOfActors;

    /**
     * Constructs this aspect
     */
    public ActorCellMonitoringAspect() {
        AgentConfiguration<AkkaAgentConfiguration> configuration = getAgentConfiguration("akka", AkkaAgentConfigurationJapi.apply());
        this.agentConfiguration = configuration.agent();
        this.counterInterface = createCounterInterface(configuration.common());
        this.samplingCounters = new ConcurrentHashMap<Option<String>, AtomicLong>();
        this.numberOfActors = new ConcurrentHashMap<Option<String>, AtomicLong>();
    }

    /**
     * Injects the new {@code AkkaAgentConfiguration} instance.
     *
     * @param agentConfiguration the new configuration
     */
    synchronized final void setAgentConfiguration(AkkaAgentConfiguration agentConfiguration) {
        this.agentConfiguration = agentConfiguration;
    }

    // decide whether to include this ActorCell in our measurements
    private boolean includeActorPath(final PathAndClass pathAndClass) {
        if (!this.agentConfiguration.incuded().accept(pathAndClass)) return false;
        if (this.agentConfiguration.excluded().accept(pathAndClass)) return false;

        if (this.agentConfiguration.includeSystemAgents()) return true;

        String userOrSystem = pathAndClass.actorPath().getElements().iterator().next();
        return "user".equals(userOrSystem);
    }

    // get the sample rate for an actor
    private int getSampleRate(final PathAndClass pathAndClass) {
        return this.agentConfiguration.sampling().getRate(pathAndClass);
    }

    // decide whether to sample an actor on a particular occasion
    private final boolean sampleMessage(final PathAndClass pathAndClass) {
        int sampleRate = getSampleRate(pathAndClass);
        if (sampleRate == 1) return true;

        this.samplingCounters.putIfAbsent(pathAndClass.actorClassName(), new AtomicLong(0));
        long timesSeenSoFar = this.samplingCounters.get(pathAndClass.actorClassName()).incrementAndGet();
        return (timesSeenSoFar % sampleRate == 1); // == 1 to log first value (incrementAndGet returns updated value)
    }

    // get the tags for the cell
    private String[] getTags(final ActorPath actorPath, final Actor actor) {
        List<String> tags = new ArrayList<String>();

        // TODO: Improve detection of routed actors. This relies only on the naming :(.
        String lastPathElement = actorPath.elements().last();
        if (lastPathElement.startsWith("$")) {
            // this is routed actor.
            tags.add(actorPath.parent().toString());
            if (this.agentConfiguration.includeRoutees()) tags.add(actorPath.toString());
        } else {
            // there is no supervisor
            tags.add(actorPath.toString());
        }
        if (actor != null) {
            tags.add(String.format("akka:%s.%s", actor.context().system().name(), actor.getClass().getCanonicalName()));
        }

        return tags.toArray(new String[tags.size()]);
    }

    /**
     * Advises the {@code ActorCell.receiveMessage(message: Object): Unit}
     * We proceed with the pointcut if the actor is to be included in the monitoring *and* this is
     * the 'multiple-of-n'th time we've seen a message for an actor with a sample rate of n.
     *
     * Currently, we sample queue size, the fact that the message is delivered, the simple name of the class of the
     * message, and the time taken to complete the actor's reactive action.
     *
     * @param actorCell the ActorCell where the actor that receives the message "lives"
     * @param msg the incoming message
     */
    Object around(ActorCell actorCell, Object msg) : Pointcuts.actorCellReceiveMessage(actorCell, msg) {
        final ActorPath actorPath = actorCell.self().path();
        final PathAndClass pathAndClass = new PathAndClass(actorPath, Option.apply(actorCell.actor().getClass().getCanonicalName()));
        if (!includeActorPath(pathAndClass) || !sampleMessage(pathAndClass)) return proceed(actorCell, msg);

        int samplingRate = getSampleRate(pathAndClass);

        // we tag by actor name
        final String[] tags = getTags(actorPath, actorCell.actor());

        // record the queue size
        this.counterInterface.recordGaugeValue("akka.queue.size", actorCell.numberOfMessages(), tags);
        // record the message, general and specific
        this.counterInterface.incrementCounter("akka.actor.delivered", samplingRate, tags);
        this.counterInterface.incrementCounter("akka.actor.delivered." + msg.getClass().getSimpleName(), samplingRate, tags);

        // measure the time. we're using the ``nanoTime`` call to access the high-precision timer.
        // since we're not really interested in wall time, but just some increasing measure of
        // elapsed time
        Object result = null;
        final long start = System.nanoTime();
        // result will always be ``null``, because target returns ``Unit``
        result = proceed(actorCell, msg);
        // ns or µs is too fine. ms will do for now.
        final long duration = (System.nanoTime() - start) / 1000000;

        // record the actor duration
        this.counterInterface.recordExecutionTime("akka.actor.duration", (int)duration, tags);

        // return null would do the trick, but we want to be _proper_.
        return result;
    }

    /**
     * Advises the {@code ActorCell.handleInvokeFailure(_, failure: Throwable): Unit}
     *
     * @param actorCell the ActorCell where the error spreads from
     * @param failure the exception that escaped from the {@code receive} method
     */
    before(ActorCell actorCell, Throwable failure) : Pointcuts.actorCellHandleInvokeFailure(actorCell, failure) {
        // record the error, general and specific
        String[] tags = getTags(actorCell.self().path(), actorCell.actor());
        this.counterInterface.incrementCounter("akka.actor.error", tags);
        this.counterInterface.incrementCounter(String.format("akka.actor.error.%s", failure.getMessage()), tags);
    }

    /**
     * Advises the {@code EventStream.publish(event: Any): Unit}
     *
     * @param event the received event
     */
    before(Object event) : Pointcuts.eventStreamPublish(event) {
        if (event instanceof UnhandledMessage) {
            UnhandledMessage unhandledMessage = (UnhandledMessage)event;
            String[] tags = getTags(unhandledMessage.recipient().path(), null);
            this.counterInterface.incrementCounter("akka.actor.undelivered", tags);
            this.counterInterface.incrementCounter("akka.actor.undelivered." + unhandledMessage.getMessage().getClass().getSimpleName(), tags);
        }
    }

    /**
     * Advises the {@code actorOf} method of {@code ActorCell} and {@code ActorSystem}
     *
     * @param props the {@code Props} instance used in the call
     * @param actor the {@code ActorRef} returned from the call
     */
    after(Props props) returning (ActorRef actor): Pointcuts.anyActorOf(props) {
        recordActorCreation(props, actor);
    }
    /**
     * Advises the {@code actorOf} method of {@code ActorCell} and {@code ActorSystem} when actor is named in construction
     *
     * @param props the {@code Props} instance used in the call
     * @param actorName the {@code String} used to name the actor at creation site
     * @param actor the {@code ActorRef} returned from the call
     */
    after(Props props, String actorName) returning (ActorRef actor): Pointcuts.namedActorOf(props, actorName) {
        recordActorCreation(props, actor);
    }

    // method containing the recording logic for advising actor creation
    void recordActorCreation(Props props, ActorRef actor) {
        if (!includeActorPath(new PathAndClass(actor.path(), this.noActorClazz))) return;
        final String uncheckedClassName = props.actorClass().getCanonicalName();
        final Option<String> className = Option.apply(uncheckedClassName);

        this.numberOfActors.putIfAbsent(className, new AtomicLong(0));
        // increment and get the current number of actors of this type (if the value was 0, then this returns 1 -- which is correct)
        final long value = this.numberOfActors.get(className).incrementAndGet();

        System.out.println("+++++"+className + " : " + value);
        // record the current number of actors of this type
        this.counterInterface.recordGaugeValue("akka.actor.new.count", (int)value, uncheckedClassName);
    }

    /**
     * Advises the {@code LocalActorRef.stop} method
     *
     *
     * @param actorCell the {@code ActorCell} of the actor being stopped
     */
    after(ActorCell actorCell) : Pointcuts.actorCellInternalStop(actorCell) {
        if (!includeActorPath(new PathAndClass(actorCell.self().path(), this.noActorClazz))) return;
        final String uncheckedClassName = actorCell.props().actorClass().getCanonicalName();
        final Option<String> className = Option.apply(uncheckedClassName);

        this.numberOfActors.putIfAbsent(className, new AtomicLong(0));
        final long value = this.numberOfActors.get(className).decrementAndGet();

        System.out.println("-----"+className + " : " + value);
        this.counterInterface.recordGaugeValue("akka.actor.count", (int)value, uncheckedClassName);
    }

}


+ remove unneeded catch for exceptions inside AbstractCacheAspect.aj

/*
 * Copyright 2002-2011 the original author or authors.
 *
 * 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.springframework.cache.aspectj;

import java.lang.reflect.Method;
import java.util.concurrent.Callable;

import org.aspectj.lang.annotation.SuppressAjWarnings;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.cache.interceptor.CacheAspectSupport;
import org.springframework.cache.interceptor.CacheOperationSource;

/**
 * Abstract superaspect for AspectJ cache aspects. Concrete
 * subaspects will implement the cacheMethodExecution()
 * pointcut using a strategy such as Java 5 annotations.
 *
 *
Suitable for use inside or outside the Spring IoC container.
 * Set the "cacheManager" property appropriately, allowing
 * use of any cache implementation supported by Spring.
 *
 *
NB: If a method implements an interface that is itself
 * cache annotated, the relevant Spring cache definition
 * will not be resolved.
 *
 * @author Costin Leau
 * @since 3.1
 */
public abstract aspect AbstractCacheAspect extends CacheAspectSupport {

	protected AbstractCacheAspect() {
	}

	/**
	 * Construct object using the given caching metadata retrieval strategy.
	 * @param cos {@link CacheOperationSource} implementation, retrieving Spring
	 * cache metadata for each joinpoint.
	 */
	protected AbstractCacheAspect(CacheOperationSource... cos) {
		setCacheOperationSources(cos);
	}

	@SuppressAjWarnings("adviceDidNotMatch")
	Object around(final Object cachedObject) : cacheMethodExecution(cachedObject){
		MethodSignature methodSignature = (MethodSignature) thisJoinPoint.getSignature();
		Method method = methodSignature.getMethod();

		Callable<Object> ajInvocation = new Callable<Object>() {
			public Object call() {
				return proceed(cachedObject);
			}
		};

		return execute(ajInvocation, thisJoinPoint.getTarget(), method, thisJoinPoint.getArgs());
	}

	/**
	 * Concrete subaspects must implement this pointcut, to identify
	 * cached methods. For each selected joinpoint, {@link CacheOperationDefinition}
	 * will be retrieved using Spring's {@link CacheOperationSource} interface.
	 */
	protected abstract pointcut cacheMethodExecution(Object cachedObject);

}

/*
 * Copyright 2002-2011 the original author or authors.
 *
 * 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.springframework.cache.aspectj;

import java.lang.reflect.Method;
import java.util.concurrent.Callable;

import org.aspectj.lang.annotation.SuppressAjWarnings;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.cache.interceptor.CacheAspectSupport;
import org.springframework.cache.interceptor.CacheOperationSource;

/**
 * Abstract superaspect for AspectJ cache aspects. Concrete
 * subaspects will implement the cacheMethodExecution()
 * pointcut using a strategy such as Java 5 annotations.
 *
 *
Suitable for use inside or outside the Spring IoC container.
 * Set the "cacheManager" property appropriately, allowing
 * use of any cache implementation supported by Spring.
 *
 *
NB: If a method implements an interface that is itself
 * cache annotated, the relevant Spring cache definition
 * will not be resolved.
 *
 * @author Costin Leau
 * @since 3.1
 */
public abstract aspect AbstractCacheAspect extends CacheAspectSupport {

	protected AbstractCacheAspect() {
	}

	/**
	 * Construct object using the given caching metadata retrieval strategy.
	 * @param cos {@link CacheOperationSource} implementation, retrieving Spring
	 * cache metadata for each joinpoint.
	 */
	protected AbstractCacheAspect(CacheOperationSource... cos) {
		setCacheOperationSources(cos);
	}

	@SuppressAjWarnings("adviceDidNotMatch")
	Object around(final Object cachedObject) : cacheMethodExecution(cachedObject){
		MethodSignature methodSignature = (MethodSignature) thisJoinPoint.getSignature();
		Method method = methodSignature.getMethod();

		Callable<Object> ajInvocation = new Callable<Object>() {
			public Object call() {
				return proceed(cachedObject);
			}
		};

		try {
			return execute(ajInvocation, thisJoinPoint.getTarget(), method, thisJoinPoint.getArgs());
		}
		catch (Exception ex) {
			throw new UnsupportedOperationException("Should not throw exception", ex);
		}
	}

	/**
	 * Concrete subaspects must implement this pointcut, to identify
	 * cached methods. For each selected joinpoint, {@link CacheOperationDefinition}
	 * will be retrieved using Spring's {@link CacheOperationSource} interface.
	 */
	protected abstract pointcut cacheMethodExecution(Object cachedObject);

}