All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.neo4j.kernel.monitoring.Monitors Maven / Gradle / Ivy

/*
 * Copyright (c) 2002-2016 "Neo Technology,"
 * Network Engine for Objects in Lund AB [http://neotechnology.com]
 *
 * This file is part of Neo4j.
 *
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 .
 */
package org.neo4j.kernel.monitoring;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;

import org.neo4j.helpers.collection.Iterables;

import static org.neo4j.helpers.collection.Iterables.append;
import static org.neo4j.helpers.collection.Iterables.toArray;

/**
 * This can be used to create monitor instances using a Dynamic Proxy, which when invoked can delegate to any number of
 * listeners. Listeners typically also implement the monitor interface, but it's possible to use a reflective style
 * to either do generic listeners, or avoid the performance penalty of Method.invoke().
 *
 * The creation of monitors and registration of listeners may happen in any order. Listeners can be registered before
 * creating the actual monitor, and vice versa.
 *
 * Typically only the top level component that creates Monitors should have a reference to it. When creating subcomponents
 * that uses monitors they should get instances of the monitor interface in the constructor, or if they need to create them
 * on demand then pass in a {@link org.neo4j.function.Factory} for that monitor instead. This allows tests to not have to use
 * Monitors, and instead can pass in mocks or similar.
 *
 * The other type of component that would have direct references to the Monitors instance are those that actually implement
 * listening functionality, and must call addMonitorListener.
 *
 * Monitors is monitorable itself, through the {@link org.neo4j.kernel.monitoring.Monitors.Monitor} monitor interface.
 *
 * This class, and the proxy objects it produces, are thread-safe.
 */
public class Monitors
{
    public static final AtomicBoolean FALSE = new AtomicBoolean(false);

    public interface Monitor
    {
        void monitorCreated( Class monitorClass, String... tags );

        void monitorListenerException( Throwable throwable );

        class Adapter implements Monitor
        {
            @Override
            public void monitorCreated( Class monitorClass, String... tags )
            {
            }

            @Override
            public void monitorListenerException( Throwable throwable )
            {
            }
        }
    }

    // Concurrency: Mutation of these data structures is always guarded by the monitor lock on this Monitors instance,
    // while look-ups and reads are performed concurrently. The methodMonitorListerners lists (the map values) are
    // read concurrently by the proxies, while changing the listener set always produce new lists that atomically
    // replace the ones already in the methodMonitorListeners map.
    /** Monitor interface method -> Listeners */
    private final Map> methodMonitorListeners = new ConcurrentHashMap<>();

    /** Monitor interface -> Has Listeners? */
    private final Map,AtomicBoolean> monitoredInterfaces = new ConcurrentHashMap<>();

    /** Listener predicate -> Listener */
    private final Map, MonitorListenerInvocationHandler> monitorListeners = new ConcurrentHashMap<>();

    private final Monitor monitorsMonitor;

    public Monitors()
    {
        monitorsMonitor = newMonitor( Monitor.class );
    }

    public synchronized  T newMonitor( Class monitorClass, Class owningClass, String... tags )
    {
        Iterable tagIer = append( owningClass.getName(), Iterables.iterable( tags ) );
        String[] tagArray = toArray( String.class, tagIer );
        return newMonitor( monitorClass, tagArray );
    }

    public synchronized  T newMonitor( Class monitorClass, String... tags )
    {
        if ( !monitoredInterfaces.containsKey( monitorClass ) )
        {
            monitoredInterfaces.put( monitorClass, new AtomicBoolean(false) );

            for ( Method method : monitorClass.getMethods() )
            {
                recalculateMethodListeners( method );
            }
        }

        ClassLoader classLoader = monitorClass.getClassLoader();
        MonitorInvocationHandler monitorInvocationHandler = new MonitorInvocationHandler( tags );
        try
        {
            return monitorClass.cast( Proxy.newProxyInstance(
                    classLoader, new Class[]{monitorClass}, monitorInvocationHandler ) );
        }
        finally
        {
            if ( monitorsMonitor != null )
            {
                monitorsMonitor.monitorCreated( monitorClass, tags );
            }
        }
    }

    public synchronized void addMonitorListener( final Object monitorListener, String... tags )
    {
        MonitorListenerInvocationHandler monitorListenerInvocationHandler =
                tags.length == 0 ? new UntaggedMonitorListenerInvocationHandler( monitorListener )
                                 : new TaggedMonitorListenerInvocationHandler( monitorListener, tags );

        for ( Class monitorInterface : getInterfacesOf( monitorListener.getClass() ) )
        {
            for ( final Method method : monitorInterface.getMethods() )
            {
                monitorListeners.put(
                        Predicate.isEqual( method ),
                        monitorListenerInvocationHandler );

                recalculateMethodListeners( method );
            }
        }
    }

    public synchronized void removeMonitorListener( Object monitorListener )
    {
        Iterator, MonitorListenerInvocationHandler>> iter =
                monitorListeners.entrySet().iterator();

        while ( iter.hasNext() )
        {
            Map.Entry, MonitorListenerInvocationHandler> handlerEntry = iter.next();
            if ( handlerEntry.getValue() instanceof UntaggedMonitorListenerInvocationHandler )
            {
                UntaggedMonitorListenerInvocationHandler handler =
                        (UntaggedMonitorListenerInvocationHandler) handlerEntry.getValue();

                if ( handler.getMonitorListener() == monitorListener )
                {
                    iter.remove();
                }
            }
        }

        recalculateAllMethodListeners();
    }

    // TODO: This is a *very* specialized API that adds sugar for monitoring a single
    //       method in a way that seems, from an end-user POV, would be much easier
    //       to do by just implementing the regular monitor interface, rather than
    //       dealing with reflection and predicates. It also seems to be used exclusively
    //       by tests. Maybe we can remove this? It seems it would simplify Monitors a lot.
    public synchronized void addMonitorListener(
            MonitorListenerInvocationHandler invocationHandler, Predicate methodSpecification )
    {
        monitorListeners.put( methodSpecification, invocationHandler );
        recalculateAllMethodListeners();
    }

    /**
     * While the intention is that the monitoring infrastructure itself should not
     * be a bottleneck (if it is, we should optimize it), components that use the
     * monitors may incur overhead in calculating whatever data they expose through
     * their monitors. If no-one is listening, this overhead is wasteful.
     *
     * This is a fast (single hash-map lookup) way to find out if there are
     * currently any listeners to a given monitor interface.
     */
    public boolean hasListeners( Class monitorClass )
    {
        return monitoredInterfaces.getOrDefault( monitorClass, FALSE ).get();
    }

    private void recalculateMethodListeners( Method method )
    {
        Class monitorClass = method.getDeclaringClass();
        List listeners = new ArrayList<>();
        for ( Map.Entry, MonitorListenerInvocationHandler> handlerEntry : monitorListeners.entrySet() )
        {
            if ( handlerEntry.getKey().test( method ) )
            {
                listeners.add( handlerEntry.getValue() );
                markMonitorHasListener( monitorClass );
            }
        }
        methodMonitorListeners.put( method, listeners );
    }

    private void recalculateAllMethodListeners()
    {
        // Mark all monitored interfaces as having no listeners
        monitoredInterfaces.values().forEach( (b) -> b.set( false ) );
        for ( Method method : methodMonitorListeners.keySet() )
        {
            recalculateMethodListeners( method );
        }
    }

    private Iterable> getInterfacesOf( Class aClass )
    {
        List> interfaces = new ArrayList<>();
        while ( aClass != null )
        {
            Collections.addAll( interfaces, aClass.getInterfaces() );
            aClass = aClass.getSuperclass();
        }
        return interfaces;
    }

    private void markMonitorHasListener( Class monitorClass )
    {
        monitoredInterfaces.getOrDefault( monitorClass, new AtomicBoolean() )
                .set( true );
    }

    private static class UntaggedMonitorListenerInvocationHandler implements MonitorListenerInvocationHandler
    {
        private final Object monitorListener;

        public UntaggedMonitorListenerInvocationHandler( Object monitorListener )
        {
            this.monitorListener = monitorListener;
        }

        public Object getMonitorListener()
        {
            return monitorListener;
        }

        @Override
        public void invoke( Object proxy, Method method, Object[] args, String... tags )
                throws Throwable
        {
            method.invoke( monitorListener, args );
        }
    }

    private static class TaggedMonitorListenerInvocationHandler
            extends UntaggedMonitorListenerInvocationHandler
    {
        private final String[] tags;

        public TaggedMonitorListenerInvocationHandler( Object monitorListener, String... tags )
        {
            super( monitorListener );
            this.tags = tags;
        }

        @Override
        public void invoke( Object proxy, Method method, Object[] args, String... tags ) throws Throwable
        {
            required:
            for ( String requiredTag : this.tags )
            {
                for ( String tag : tags )
                {
                    if ( requiredTag.equals( tag ) )
                    {
                        continue required;
                    }
                }
                return; // Not all required tags present
            }

            super.invoke( proxy, method, args, tags );
        }
    }

    private class MonitorInvocationHandler implements InvocationHandler
    {
        private String[] tags;

        public MonitorInvocationHandler( String... tags )
        {
            this.tags = tags;
        }

        @Override
        public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable
        {
            invokeMonitorListeners( proxy, method, args );
            return null;
        }

        private void invokeMonitorListeners( Object proxy, Method method, Object[] args )
        {
            List handlers = methodMonitorListeners.get( method );

            if ( handlers != null )
            {
                for ( MonitorListenerInvocationHandler monitorListenerInvocationHandler : handlers )
                {
                    try
                    {
                        monitorListenerInvocationHandler.invoke( proxy, method, args, tags );
                    }
                    catch ( Throwable e )
                    {
                        if ( !method.getDeclaringClass().equals( Monitor.class ) )
                        {
                            monitorsMonitor.monitorListenerException( e );
                        }
                    }
                }
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy