/* * Copyright (C) 2005-2007 Alfresco Software Limited. * * This program 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 2 * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * As a special exception to the terms and conditions of version 2.0 of * the GPL, you may redistribute this Program in connection with Free/Libre * and Open Source Software ("FLOSS") applications as described in Alfresco's * FLOSS exception. You should have recieved a copy of the text describing * the FLOSS exception, and it is also available here: * http://www.alfresco.com/legal/licensing */ package org.alfresco.repo.transaction; import java.lang.reflect.Method; import java.util.HashMap; import java.util.List; import java.util.Map; import org.alfresco.util.resource.MethodResourceManager; import org.alfresco.util.resource.MethodResourceManager.MethodStatistics; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; /** * This interceptor gathers basic method call statistics and calls the * {@link org.alfresco.util.resource.MethodResourceManager resource managers} for * further processing. The resource managers are called after the method * invocations, but it doesn't matter too much since they are called on a time * frequency basis. *

* It acts as a sampling tool, but doesn't make any decisions or take any action * with regard system or transaction-related resources. All samples are stored * against the current transaction. If there is no current transaction then no * action will be taken with respect to the resource management. *

* The default is to activate after 10s and call through every 5s. *

* This class supports both interceptor-based calling as well as manual calling. * Long-running processes can call an this manually on every iteration and * get the same behaviour of regular calls to the resouce managers. *

* The current thread is marked on first entry and all subsequent nested re-entries * will just get passed down to the underlying delegate invocation method. * * @see org.alfresco.util.resource.MethodResourceManager * * @author Derek Hulley * @since 2.1.3 */ public class SingleEntryTransactionResourceInterceptor implements MethodInterceptor { private ThreadLocal threadLocalReentryCheck; private List methodResourceManagers; /** Default 10000ms (10s) */ private long elapsedTimeBeforeActivationMillis = 10000L; /** Default 5000ms (5s) */ private long resourceManagerCallFrequencyMillis = 5000L; /** When the last call to the resource managers was made by this instance */ private volatile long lastCallMillis; /** * A resource key unique to this interceptor. This avoids clashes with other instances up * and down the stack operating in the same transaction. */ private String resourceKey; public SingleEntryTransactionResourceInterceptor() { resourceKey = "MethodStats" + super.toString(); threadLocalReentryCheck = new ThreadLocal(); } /** * Set the method-based resource managers that will be notified of the statistics. * * @param methodResourceManagers a list of resource managers - may be null or empty */ public void setMethodResourceManagers(List methodResourceManagers) { this.methodResourceManagers = methodResourceManagers; } /** * Set the minimum number of seconds that a transaction must have been running for * before method sampling begins. The interceptor does nothing prior to this. * * @param elapsedTimeBeforeActivationMillis an initial idle time in milliseconds */ public void setElapsedTimeBeforeActivationMillis(long elapsedTimeBeforeActivationMillis) { this.elapsedTimeBeforeActivationMillis = elapsedTimeBeforeActivationMillis; } /** * Set the approximate time between calls to the * {@link #setMethodResourceManagers(List) registered resource managers}. This applies to this instance * of the interceptor and not to the transaction. If a single instance of this * class is used in multiple places, then the resource managers will still only get called at a steady * rate. This is mainly in order to streamline the interception prior to the activation phase, but suits * the resource managers since they get given the exact methods that were called anyway. * * @param resourceManagerCallFrequencyMillis an approximate time between calls to the resource managers */ public void setResourceManagerCallFrequencyMillis(long resourceManagerCallFrequencyMillis) { this.resourceManagerCallFrequencyMillis = resourceManagerCallFrequencyMillis; } public Object invoke(MethodInvocation invocation) throws Throwable { if (threadLocalReentryCheck.get() == Boolean.TRUE) { // We're already in a wrapped resource, so avoid doing anything return invocation.proceed(); } else if (methodResourceManagers == null || methodResourceManagers.size() == 0) { // We just ignore everything return invocation.proceed(); } try { // Mark this thread threadLocalReentryCheck.set(Boolean.TRUE); return invokeInternal(invocation); } finally { // Unmark this thread threadLocalReentryCheck.set(null); } } private Object invokeInternal(MethodInvocation invocation) throws Throwable { // Get the txn start time long txnStartTime = AlfrescoTransactionSupport.getTransactionStartTime(); if (txnStartTime < 0) { // There is no transaction return invocation.proceed(); } // Check if the required time has passed long now = System.currentTimeMillis(); long txnElapsedTime = (now - txnStartTime); if (txnElapsedTime < elapsedTimeBeforeActivationMillis) { // It's not been long enough return invocation.proceed(); } // We need to start timing the method calls Method calledMethod = invocation.getMethod(); long beforeNs = System.nanoTime(); Object ret = invocation.proceed(); long deltaNs = System.nanoTime() - beforeNs; // Get the method stats @SuppressWarnings("unchecked") Map methodStatsByMethod = (Map) AlfrescoTransactionSupport.getResource(resourceKey); if (methodStatsByMethod == null) { methodStatsByMethod = new HashMap(11); AlfrescoTransactionSupport.bindResource(resourceKey, methodStatsByMethod); } // Update method stats MethodStatistics calledMethodStats = methodStatsByMethod.get(calledMethod); if (calledMethodStats == null) { calledMethodStats = new MethodStatistics(); methodStatsByMethod.put(calledMethod, calledMethodStats); } calledMethodStats.accumulateNs(deltaNs); // Check if we need to call the resource managers to clean up if ((now - lastCallMillis) >= resourceManagerCallFrequencyMillis) { for (MethodResourceManager resourceManager : methodResourceManagers) { resourceManager.manageResources(methodStatsByMethod, txnElapsedTime, calledMethod); } lastCallMillis = now; } // Done return ret; } /** * An alternative method allowing a manual call to check the resources. This is useful * in the cases where long running iterations don't necessarily pass through the * necessary API stack, or where the specific resources in hand can't be dealt with * blindly before and after resource management. The elapsed time should be that of the * iteration within the method (it is assumed that there won't be more than one). *

* If you have a loop in a method that doesn't call anything that can be intercepted * and handle safely, then get a pre-configured instance (usually from the application context) * and mimic the interceptor call. *

* You should get the Method, which is used for informational purposes, in a * single call when you calling code is loaded by the classloader. Introspecting every time * you wish to call this method is unnecessary. * * @param calledMethod the method that this check applies to * @param deltaNs the time in milliseconds that the repeated operation took */ public void performManualCheck(Method calledMethod, long deltaNs) { /* * This is mainly duplicated code, but it can be heavily used so nice patterns * are not preferable to speed of execution. */ if (methodResourceManagers == null || methodResourceManagers.size() == 0) { // We just ignore everything return; } // Get the txn start time long txnStartTime = AlfrescoTransactionSupport.getTransactionStartTime(); if (txnStartTime < 0) { // There is no transaction return; } // Check if the required time has passed long now = System.currentTimeMillis(); long txnElapsedTime = (now - txnStartTime); if (txnElapsedTime < elapsedTimeBeforeActivationMillis) { // It's not been long enough return; } // Get the method stats @SuppressWarnings("unchecked") Map methodStatsByMethod = (Map) AlfrescoTransactionSupport.getResource(resourceKey); if (methodStatsByMethod == null) { methodStatsByMethod = new HashMap(11); AlfrescoTransactionSupport.bindResource(resourceKey, methodStatsByMethod); } // Update method stats MethodStatistics calledMethodStats = methodStatsByMethod.get(calledMethod); if (calledMethodStats == null) { calledMethodStats = new MethodStatistics(); methodStatsByMethod.put(calledMethod, calledMethodStats); } calledMethodStats.accumulateNs(deltaNs); // Check if we need to call the resource managers to clean up if ((now - lastCallMillis) >= resourceManagerCallFrequencyMillis) { for (MethodResourceManager resourceManager : methodResourceManagers) { resourceManager.manageResources(methodStatsByMethod, txnElapsedTime, calledMethod); } lastCallMillis = now; } // Done return; } }