/* * #%L * P6Spy * %% * Copyright (C) 2013 P6Spy * %% * 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. * #L% */ package com.p6spy.engine.outage; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import com.p6spy.engine.common.P6LogQuery; import com.p6spy.engine.logging.Category; /** * This class is a singleton. Since P6Spy is normally loaded by the system * classpath, it is a normally a singleton across the JVM. * The instance will determine if a statement has been long running and log * the statement when such a statment is found. It accomplishes this by * spawning a daemon thread which will wake up at configurable intervals of * time (defined in seconds) and check if a statement is still executing since * the last time the thread was awake. This is accomplished by this instance * maintaining a list of active statements. The P6Statement and P6PreparedStatement * objects will reqister their call with the instance just before the SQL call * is delegated to the real driver. Once that statement finishes executing, the * statement object will unregister that call from list. If during that time * the thread in this instance sees the execute time exceed the threshold, it is * flagged as a long-running statement and logged. The statement will continue * to be logged if it appears in future iterations of the sleep/wake cycle. * This class is implemented with lazy thread sychronization. The list of * pending statements is a synchronized container, the rest is left open to * thread hazards to reduce the performance impact. So the logging results might be * slightly unreliable, but we aren't dealing with bank accounts here so thats * okay. */ public class P6OutageDetector implements Runnable { private ConcurrentMap<Object, InvocationInfo> pendingMessages; // flag that indicates that the thread should stop running private boolean haltThread; // singleton contruct private static P6OutageDetector instance; /** Creates new P6OutageDetector */ protected P6OutageDetector() { pendingMessages = new ConcurrentHashMap<Object, InvocationInfo>(); P6LogQuery.debug("P6Spy - P6OutageDetector has been invoked."); P6LogQuery.debug("P6Spy - P6OutageOptions.getOutageDetectionIntervalMS() = " + P6OutageOptions.getActiveInstance().getOutageDetectionIntervalMS()); } /** * Gets the instance of the detector. A side effect of the first call to this method is that the * auxillary thread will be kicked off here. * * @return the P6OutageDetector instance */ static public synchronized P6OutageDetector getInstance() { if (instance == null) { instance = new P6OutageDetector(); // create and run the auxilliary thread // make it a deamon thread so it won't prevent the server from // shutting down when it wants to. ThreadGroup group = new ThreadGroup("P6SpyThreadGroup"); group.setDaemon(true); Thread outageThread = new Thread(group, instance, "P6SpyOutageThread"); outageThread.start(); } return instance; } /** * Method for running the auxillary thread. */ public void run() { while (!haltThread) { detectOutage(); try { // sleep for the configured interval // don't cache this value since the props file may be reloaded // and this value might change Thread.sleep(P6OutageOptions.getActiveInstance().getOutageDetectionIntervalMS()); } catch (Exception e) { } } } /** * Tells the auxillary thread to stop executing. Thread will exit upon waking next. */ public void shutdown() { haltThread = true; } /** * Registers the execution of a statement. This should be called just before the statement is * passed to the real driver. */ public void registerInvocation(Object jdbcObject, long startTime, String category, String ps, String sql) { pendingMessages.put(jdbcObject, new InvocationInfo(startTime, category, ps, sql)); } /** * Unregisters the execution of a statement. This should be called just after the statement is * passed to the real driver. */ public void unregisterInvocation(Object jdbcObject) { pendingMessages.remove(jdbcObject); } private void detectOutage() { int listSize = pendingMessages.size(); if (listSize == 0) { return; } P6LogQuery.debug("P6Spy - detectOutage.pendingMessage.size = " + listSize); long currentTime = System.nanoTime(); long threshold = TimeUnit.MILLISECONDS.toNanos(P6OutageOptions.getActiveInstance().getOutageDetectionIntervalMS()); for (Object jdbcObject : pendingMessages.keySet()) { // here is a thread hazard that we'll be lazy about. Another thread // might have already removed the entry from the messages map, so we // need to check if the result is null InvocationInfo ii = pendingMessages.get(jdbcObject); if (ii == null) { continue; } // has this statement exceeded the threshold? if ((currentTime - ii.startTime) > threshold) { P6LogQuery.debug("P6Spy - statement exceeded threshold - check log."); logOutage(ii); } } } private void logOutage(InvocationInfo ii) { P6LogQuery.logElapsed(-1, System.nanoTime() - ii.startTime, Category.OUTAGE, ii.preparedStmt, ii.sql); } } // inner class to hold the info about a specific statement invocation class InvocationInfo { public long startTime; public String category; public String preparedStmt; public String sql; public InvocationInfo(long startTime, String category, String ps, String sql) { this.startTime = startTime; this.category = category; this.preparedStmt = ps; this.sql = sql; } }