/*
* NOTE: This copyright does *not* cover user programs that use HQ
* program services by normal system calls through the application
* program interfaces provided as part of the Hyperic Plug-in Development
* Kit or the Hyperic Client Development Kit - this is merely considered
* normal use of the program, and does *not* fall under the heading of
* "derived work".
*
* Copyright (C) [2004, 2005, 2006], Hyperic, Inc.
* This file is part of HQ.
*
* HQ is free software; you can redistribute it and/or modify
* it under the terms version 2 of the GNU General Public License as
* published by the Free Software Foundation. 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA.
*/
package org.hyperic.hq.measurement.agent.server;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Properties;
import org.hyperic.hq.agent.server.AgentStartException;
import org.hyperic.hq.agent.server.AgentStorageException;
import org.hyperic.hq.agent.server.AgentStorageProvider;
import org.hyperic.hq.bizapp.client.AgentCallbackClientException;
import org.hyperic.hq.bizapp.client.MeasurementCallbackClient;
import org.hyperic.hq.bizapp.client.StorageProviderFetcher;
import org.hyperic.hq.measurement.data.TrackEventReport;
import org.hyperic.hq.product.ConfigTrackPluginManager;
import org.hyperic.hq.product.LogTrackPluginManager;
import org.hyperic.hq.product.TrackEvent;
import org.hyperic.hq.product.TrackEventPluginManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
class TrackerThread implements Runnable {
private static final String PROP_MAXEVENTBATCHSIZE =
"agent.eventReportBatchSize";
private static final int MAX_EVENT_BATCHSIZE = 100;
private static final String CONFIGTRACK_LISTNAME = "configtrack_spool";
private static final String LOGTRACK_LISTNAME = "logtrack_spool";
private static final int LOGTRACK_RECSIZE = 5120;
private volatile boolean shouldDie;
private volatile Thread myThread;
private Object interrupter;
private long waitTime;
private int maxEventBatchSize = MAX_EVENT_BATCHSIZE;
private AgentStorageProvider storage;
private MeasurementCallbackClient client;
private ConfigTrackPluginManager ctManager;
private LogTrackPluginManager ltManager;
private Log log;
TrackerThread(ConfigTrackPluginManager ctManager,
LogTrackPluginManager ltManager,
AgentStorageProvider storage,
Properties bootProps)
throws AgentStartException
{
this.ctManager = ctManager;
this.ltManager = ltManager;
this.storage = storage;
this.shouldDie = false;
this.myThread = null;
this.interrupter = new Object();
this.client = setupClient();
this.waitTime = TrackEventPluginManager.DEFAULT_INTERVAL;
this.log = LogFactory.getLog(TrackerThread.class);
String info = bootProps.getProperty(CONFIGTRACK_LISTNAME);
if (info != null) {
storage.addOverloadedInfo(CONFIGTRACK_LISTNAME, info);
}
info = bootProps.getProperty(LOGTRACK_LISTNAME);
if (info != null) {
storage.addOverloadedInfo(LOGTRACK_LISTNAME, info);
}
// Create list early since we want a larger recordsize than the default of 1k.
try {
this.storage.createList(LOGTRACK_LISTNAME, LOGTRACK_RECSIZE);
} catch (AgentStorageException ignore) {
// Most likely an agent update where the existing spool
// already exists. Will fall back to the old 1k size.
}
String sMaxBatchSize = bootProps.getProperty(PROP_MAXEVENTBATCHSIZE);
if(sMaxBatchSize != null){
try {
this.maxEventBatchSize = Integer.parseInt(sMaxBatchSize);
} catch(NumberFormatException exc){
throw new AgentStartException(PROP_MAXEVENTBATCHSIZE + " is not " +
"a valid integer ('" +
sMaxBatchSize + "')");
}
}
this.log.info("Event report batch size set to " + this.maxEventBatchSize);
}
private void interruptMe()
{
synchronized(this.interrupter) {
this.interrupter.notify();
}
}
private MeasurementCallbackClient setupClient()
throws AgentStartException
{
StorageProviderFetcher fetcher;
fetcher = new StorageProviderFetcher(this.storage);
return new MeasurementCallbackClient(fetcher);
}
private void flushEvents(LinkedList events, String dListName)
{
if (events.isEmpty()) {
return;
}
for(Iterator i = events.iterator(); i.hasNext(); ) {
TrackEvent event = (TrackEvent)i.next();
try {
String data = event.encode();
this.storage.addToList(dListName, data);
this.log.debug("Stored event (" + data.length() +
" bytes) " + event);
} catch (Exception e) {
this.log.error("Unable to store data", e);
}
}
}
private void flushLogTrackEvents()
{
LinkedList events = ltManager.getEvents();
flushEvents(events, LOGTRACK_LISTNAME);
}
private void flushConfigTrackEvents()
{
LinkedList events = ctManager.getEvents();
flushEvents(events, CONFIGTRACK_LISTNAME);
}
private void processDlist(String dListName)
{
boolean moreEventsToProcess = true;
while (moreEventsToProcess) {
moreEventsToProcess =
processNextEventReport(dListName, maxEventBatchSize);
}
try {
this.storage.flush();
} catch(AgentStorageException exc){
this.log.error("Failed to flush agent storage", exc);
}
}
/**
* Process the next event report.
*
* @param dListName
* @param batchSize The event batch size per report.
* @return <code>true</code> if there are more events to process.
*/
private boolean processNextEventReport(String dListName, int batchSize) {
boolean moreEventsToProcess = false;
Iterator i = this.storage.getListIterator(dListName);
if (i == null) {
return false;
}
TrackEventReport report = new TrackEventReport();
int numEventsProcessed = 0;
while (numEventsProcessed <= batchSize && i.hasNext()) {
TrackEvent event;
try {
event = TrackEvent.decode((String)i.next());
} catch (Exception e) {
this.log.error("Unable to decode record -- deleting: " + e);
continue;
} finally {
numEventsProcessed++;
moreEventsToProcess = i.hasNext();
}
this.log.debug("Adding event to report=" + event);
report.addEvent(event);
}
if (!sendReportToServer(dListName, report)) {
// If there is an error sending to the server, try again later.
moreEventsToProcess = false;
numEventsProcessed = 0;
}
removeProcessedEventsFromStorage(dListName, numEventsProcessed);
return moreEventsToProcess;
}
private boolean sendReportToServer(String dListName,
TrackEventReport report) {
boolean succeeded = true;
if (report.getEvents().length > 0) {
this.log.debug("Sending report to server");
try {
if (dListName.equals(LOGTRACK_LISTNAME)) {
this.client.trackSendLog(report);
} else if (dListName.equals(CONFIGTRACK_LISTNAME)) {
this.client.trackSendConfigChange(report);
} else {
throw new IllegalArgumentException("Unknown DList name");
}
this.log.debug("Completed sending report to server");
} catch (AgentCallbackClientException e) {
this.log.error("Error sending report to server: " + e);
succeeded = false;
}
}
return succeeded;
}
private void removeProcessedEventsFromStorage(String dListName,
int numEventsProcessed) {
for(Iterator i= this.storage.getListIterator(dListName);
numEventsProcessed > 0 && i != null && i.hasNext();
numEventsProcessed--) {
i.next();
i.remove();
}
}
public void die()
{
// Before exiting, make sure we flush any outstanding events.
flushLogTrackEvents();
flushConfigTrackEvents();
this.shouldDie = true;
this.interruptMe();
}
public void run()
{
this.myThread = Thread.currentThread();
while(this.shouldDie == false) {
// Flush log track events
flushLogTrackEvents();
// Flush config track events
flushConfigTrackEvents();
// Process the log track dlist
processDlist(LOGTRACK_LISTNAME);
// Process the config track dlist
processDlist(CONFIGTRACK_LISTNAME);
synchronized(this.interrupter) {
try {
this.interrupter.wait(waitTime);
} catch (InterruptedException e) {
}
}
}
}
}