/**
* 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) [2013], VMware, 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.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hyperic.hq.agent.server.AgentStartException;
import org.hyperic.hq.agent.server.AgentStorageProvider;
import org.hyperic.hq.bizapp.agent.CommandsAPIInfo;
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.bizapp.shared.lather.TopNSendReport_args;
import org.hyperic.hq.measurement.TopNSchedule;
import org.hyperic.hq.measurement.agent.commands.ScheduleTopn_args;
import org.hyperic.hq.plugin.system.ProcessData;
import org.hyperic.hq.plugin.system.ProcessReport;
import org.hyperic.hq.plugin.system.TopData;
import org.hyperic.hq.plugin.system.TopReport;
import org.hyperic.hq.util.properties.PropertiesUtil;
import org.hyperic.sigar.Humidor;
import org.hyperic.sigar.Sigar;
import org.hyperic.sigar.SigarException;
import org.hyperic.sigar.SigarProxy;
class TopNScheduler {
private static final int SEND_INTERVAL = 1;
private static final int MAX_BATCHSIZE = 5;
private static final int MAX_ELEMENT_TO_STORE = 24 * 60;
private static final String DATA_FOLDERNAME = "topn_records";
private static final String SCHEDULE_FILE = "topn_schedule";
private final AgentStorageProvider storage;
private final Log log;
private Sigar _sigarImpl;
private Humidor _humidor;
private ScheduledExecutorService scheduler;
private ScheduledExecutorService sender;
private final MeasurementCallbackClient client;
private String agentToken;
private boolean deductServerTimeDiff = true;
TopNScheduler(AgentStorageProvider storage, Properties config) throws AgentStartException {
this.log = LogFactory.getLog(TopNScheduler.class);
this.storage = storage;
this.client = setupClient();
// by default we the deduction feature is on
Boolean deductServerOffset = PropertiesUtil.getBooleanValue(
config.getProperty(ServerTimeDiff.PROP_DEDUCT_SERVER_TIME_DIFF), true);
deductServerTimeDiff = deductServerOffset;
createSender();
loadScheduleData();
}
private void loadScheduleData() {
TopNSchedule schedule = null;
if (null != (schedule = storage.<TopNSchedule> getObject(SCHEDULE_FILE))) {
scheduleTopN(schedule);
}
}
private void createSender() {
sender = Executors.newScheduledThreadPool(1, new ThreadFactory() {
private final AtomicLong i = new AtomicLong(0);
public Thread newThread(Runnable r) {
return new Thread(r, "TopNSender" + i.getAndIncrement());
}
});
sender.scheduleAtFixedRate(new Runnable() {
public void run() {
boolean success;
List<TopReport> reports = new ArrayList<TopReport>();
for (TopReport report : storage.<TopReport> getObjectsFromFolder(DATA_FOLDERNAME, MAX_BATCHSIZE)) {
reports.add(report);
}
// If we don't have anything to send -- move along
if (reports.isEmpty()) {
log.debug("No TopN records were found in the storage");
return;
}
log.debug("Sending " + reports.size() + " TopN entries " + "to server");
success = false;
try {
TopNSendReport_args report = new TopNSendReport_args();
if (agentToken == null) {
agentToken = storage.getValue(CommandsAPIInfo.PROP_AGENT_TOKEN);
}
report.setAgentToken(agentToken);
report.setTopReports(reports);
client.topNSendReport(report);
success = true;
} catch (AgentCallbackClientException exc) {
log.error("Error sending TOPN data to server: " + exc.getMessage());
}
// delete the records we sent from the storage
if (success) {
List<String> filesToDelete = new ArrayList<String>();
for (TopReport report : reports) {
filesToDelete.add(String.valueOf(report.getCreateTime()));
}
storage.deleteObjectsFromFolder(DATA_FOLDERNAME,
filesToDelete.toArray(new String[filesToDelete.size()]));
}
}
// TimeUnit.MINUTE does not work on java5
}, SEND_INTERVAL * 60, SEND_INTERVAL * 60, TimeUnit.SECONDS);
}
private void createScheduler() {
scheduler = Executors.newScheduledThreadPool(1, new ThreadFactory() {
private final AtomicLong i = new AtomicLong(0);
public Thread newThread(Runnable r) {
return new Thread(r, "TopNScheduler" + i.getAndIncrement());
}
});
}
public void unscheduleTopN() {
scheduler.shutdown();
storage.deleteObject(SCHEDULE_FILE);
}
public void scheduleTopN(final ScheduleTopn_args args) {
TopNSchedule schedule = new TopNSchedule();
schedule.setQueryFilter(args.getQueryFilter());
schedule.setInterval(args.getInterval());
schedule.setNumberOfProcesses(args.getNumberOfProcesses());
schedule.setLastUpdateTime(System.currentTimeMillis());
// Store the schedule data in the local storage
storage.saveObject(schedule, SCHEDULE_FILE);
scheduleTopN(schedule);
}
private void scheduleTopN(final TopNSchedule schedule) {
log.info("Scheduling TopN gathering task at interval of " + schedule.getInterval() + " minutes");
// If the scheduler is alive we need to kill it and create a new one
// with the new scheduling data
if ((null != scheduler) && !scheduler.isShutdown()) {
scheduler.shutdown();
}
createScheduler();
scheduler.scheduleAtFixedRate(new Runnable() {
public void run() {
TopData data = null;
try {
data = TopData.gather(getSigar(), schedule.getQueryFilter());
} catch (SigarException e) {
log.error("Unable to gather Top data", e);
}
if (null != data) {
TopReport report = generateTopReport(data);
report.filterTopProcesses(schedule.getNumberOfProcesses());
try {
storage.addObjectToFolder(DATA_FOLDERNAME, report, report.getCreateTime(), MAX_ELEMENT_TO_STORE);
} catch (Exception ex) {
log.error("Unable to store TopN data", ex);
}
}
}
// TimeUnit.MINUTE does not work on java5
}, 10, schedule.getInterval() * 60, TimeUnit.SECONDS);
}
/**
* Shut down the schedule thread.
*/
void die() {
if (sender != null) {
sender.shutdown();
}
if (scheduler != null) {
scheduler.shutdown();
}
if (_sigarImpl != null) {
_sigarImpl.close();
_sigarImpl = null;
}
}
private TopReport generateTopReport(TopData data) {
TopReport report = new TopReport();
if (deductServerTimeDiff
&& (Math.abs(ServerTimeDiff.getInstance().getServerTimeDiff()) > ServerTimeDiff.MIN_OFFSET_FOR_DEDUCTION)) {
// deduct the server time offset from the metric
// value time stamp
report.setCreateTime(System.currentTimeMillis() + ServerTimeDiff.getInstance().getServerTimeDiff());
} else {
report.setCreateTime(System.currentTimeMillis());
}
report.setUpTime(data.getUptime().toString());
report.setCpu(data.getCpu().toString());
report.setMem(data.getMem().toString());
report.setSwap(data.getSwap().toString());
report.setProcStat(data.getProcStat().toString());
for (ProcessData process : data.getProcesses()) {
ProcessReport processReport = new ProcessReport(process);
report.addProcess(processReport);
}
return report;
}
private MeasurementCallbackClient setupClient() throws AgentStartException {
StorageProviderFetcher fetcher;
fetcher = new StorageProviderFetcher(storage);
return new MeasurementCallbackClient(fetcher);
}
private synchronized SigarProxy getSigar() {
if (_humidor == null) {
_sigarImpl = new Sigar();
_humidor = new Humidor(_sigarImpl);
}
return _humidor.getSigar();
}
}