/* * eXist Open Source Native XML Database * Copyright (C) 2001-04 Wolfgang M. Meier * wolfgang@exist-db.org * http://exist.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * $Id: ProcessMonitor.java 8235 2008-10-17 16:03:27Z chaeron $ */ package org.exist.storage; import org.apache.log4j.Logger; import org.exist.xquery.XQueryWatchDog; import org.exist.xquery.util.ExpressionDumper; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Set; /** * Class to keep track of all running queries in a database instance. The main * purpose of this class is to signal running queries that the database is going to * shut down. This is done through the {@link org.exist.xquery.XQueryWatchDog} * registered by each query. It is up to the query to check the watchdog's state. * If it simply ignores the terminate signal, it will be killed after the shutdown * timeout is reached. * * @author wolf */ public class ProcessMonitor { public final static String ACTION_UNSPECIFIED = "unspecified"; public final static String ACTION_VALIDATE_DOC = "validating document"; public final static String ACTION_STORE_DOC = "storing document"; public final static String ACTION_STORE_BINARY = "storing binary resource"; public final static String ACTION_REMOVE_XML = "remove XML resource"; public final static String ACTION_REMOVE_BINARY = "remove binary resource"; public final static String ACTION_REMOVE_COLLECTION = "remove collection"; public final static String ACTION_REINDEX_COLLECTION = "reindex collection"; public final static String ACTION_COPY_COLLECTION = "copy collection"; public final static String ACTION_MOVE_COLLECTION = "move collection"; public final static String ACTION_BACKUP = "backup"; private final static Logger LOG = Logger.getLogger(ProcessMonitor.class); private final static int MAX_QUERY_HISTORY = 16; //The maximum number of queries to record history for private final Set<XQueryWatchDog> runningQueries = new HashSet<XQueryWatchDog>(); private final Map<String, QueryHistory> queryHistories = new FixedSizeLinkedHashMap<String, QueryHistory>(MAX_QUERY_HISTORY); private Map<Thread, JobInfo> processes = new HashMap<Thread, JobInfo>(); public ProcessMonitor() { super(); } public void startJob(String action) { startJob(action, null); } public void startJob(String action, Object addInfo) { startJob(action, addInfo, null); } //TODO: addInfo = XmldbURI ? -shabanovd public void startJob(String action, Object addInfo, Monitor monitor) { JobInfo info = new JobInfo(action, monitor); info.setAddInfo(addInfo); synchronized (processes) { processes.put(info.getThread(), info); } } public void endJob() { synchronized (processes) { processes.remove(Thread.currentThread()); } } public JobInfo[] runningJobs() { synchronized (processes) { JobInfo jobs[] = new JobInfo[processes.size()]; int j = 0; for (Iterator i = processes.values().iterator(); i.hasNext(); j++) { jobs[j] = (JobInfo) i.next(); } return jobs; } } public void stopRunningJobs() { for (JobInfo job : processes.values()) { job.stop(); } } public void queryStarted(XQueryWatchDog watchdog) { synchronized (runningQueries) { runningQueries.add(watchdog); } } public void queryCompleted(XQueryWatchDog watchdog) { synchronized (runningQueries) { runningQueries.remove(watchdog); } String sourceKey = watchdog.getContext().getSourceKey(); synchronized(queryHistories) { QueryHistory qh = queryHistories.get(sourceKey); if(qh == null) { qh = new QueryHistory(sourceKey); } qh.setMostRecentExecutionTime(watchdog.getStartTime()); qh.setMostRecentExecutionDuration(System.currentTimeMillis() - watchdog.getStartTime()); qh.incrementInvocationCount(); queryHistories.put(sourceKey, qh); } } /** * Linked HashMap that has a fixed size * * Oldest items are removed when new items are added * if the max size is exceeded */ public class FixedSizeLinkedHashMap<K,V> extends LinkedHashMap<K,V> { private final int maxSize; public FixedSizeLinkedHashMap(int maxSize) { super(maxSize); this.maxSize = maxSize; } @Override protected boolean removeEldestEntry(Entry<K, V> entry) { return size() >= maxSize; } } public class QueryHistory { private final String source; private long mostRecentExecutionTime; private long mostRecentExecutionDuration; private int invocationCount = 0; public QueryHistory(String source) { this.source = source; } public String getSource() { return source; } public void incrementInvocationCount() { invocationCount++; } public int getInvocationCount() { return invocationCount; } public long getMostRecentExecutionTime() { return mostRecentExecutionTime; } public void setMostRecentExecutionTime(long mostRecentExecutionTime) { this.mostRecentExecutionTime = mostRecentExecutionTime; } public long getMostRecentExecutionDuration() { return mostRecentExecutionDuration; } public void setMostRecentExecutionDuration(long mostRecentExecutionDuration) { this.mostRecentExecutionDuration = mostRecentExecutionDuration; } } public QueryHistory[] getRecentQueryHistory() { QueryHistory result[] = new QueryHistory[queryHistories.size()]; return (QueryHistory[])queryHistories.values().toArray(result); } public void killAll(long waitTime) { // directly called from BrokerPool itself. no need to synchronize. XQueryWatchDog watchdog; for(Iterator i = runningQueries.iterator(); i.hasNext(); ) { watchdog = (XQueryWatchDog) i.next(); LOG.debug("Killing query: " + ExpressionDumper.dump(watchdog.getContext().getRootExpression())); watchdog.kill(waitTime); } } public XQueryWatchDog[] getRunningXQueries() { synchronized (runningQueries) { XQueryWatchDog watchdogs[] = new XQueryWatchDog[runningQueries.size()]; int j = 0; for (Iterator i = runningQueries.iterator(); i.hasNext(); j++) { watchdogs[j] = (XQueryWatchDog) i.next(); } return watchdogs; } } public final static class Monitor { boolean stop = false; public boolean proceed() { return !stop; } public void stop() { LOG.debug("Terminating job"); this.stop = true; } } public final static class JobInfo { private Thread thread; private String action; private long startTime; private Object addInfo = null; private Monitor monitor = null; public JobInfo(String action, Monitor monitor) { this.thread = Thread.currentThread(); this.action = action; this.monitor = monitor; this.startTime = System.currentTimeMillis(); } public String getAction() { return action; } public Thread getThread() { return thread; } public long getStartTime() { return startTime; } public void setAddInfo(Object info) { this.addInfo = info; } public Object getAddInfo() { return addInfo; } public void stop() { if (monitor != null) { monitor.stop(); } } } }