/*
* ALMA - Atacama Large Millimiter Array
* Copyright (c) European Southern Observatory, 2011
*
* This library 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.1 of the License, or (at your option) any later version.
*
* This library 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 library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/**
*
*/
package alma.acs.monitoring.blobber;
import java.lang.reflect.Constructor;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import alma.ACSErrTypeCommon.wrappers.AcsJCouldntCreateObjectEx;
import alma.JavaContainerError.wrappers.AcsJContainerServicesEx;
import alma.MonitorArchiver.BlobberOperations;
import alma.MonitorArchiver.CollectorListStatus;
import alma.MonitorArchiver.ControllerHelper;
import alma.MonitorArchiver.ControllerOperations;
import alma.acs.alarmsystem.source.AlarmSource;
import alma.acs.component.ComponentImplBase;
import alma.acs.component.ComponentLifecycleException;
import alma.acs.concurrent.ThreadLoopRunner;
import alma.acs.concurrent.ThreadLoopRunner.ScheduleDelayMode;
import alma.acs.container.ContainerServices;
/**
* Implementation of the blobber component.
* @author apersson, hsommer
*/
public class BlobberImpl extends ComponentImplBase implements BlobberOperations {
private static final String MONITOR_BLOBBER_PLUGIN = System.getProperty("alma.acs.monitoring.blobber.plugin", "alma.acs.monitoring.blobber.BlobberPluginAlmaImpl");
/**
* The code that gets run at fixed time intervals.
*/
protected BlobberWorker myWorker;
/**
* Default interval between collecting data.
* @see BlobberPlugin#getCollectorIntervalSec()
*/
protected int collectorIntervalSec;
/**
* We use this convenience class from ACS to execute the blobber code at fixed intervals.
*/
protected ThreadLoopRunner blobberLoopRunner;
protected BlobberPlugin blobberPlugin;
protected AlarmSource alarmSource;
/**
* There have been problems with slow blobber activation (http://ictjira.alma.cl/browse/ICT-462).
* Note that also non-ACS code gets run (plugin, DAOs, JMX setup and possibly invocations).
* During startup we feed this map with "activity" - "time elapsed in ns" pairs and log a single profiling message
* in the end of {@link #execute()}.
* @see #logComponentActivationTimes()
*/
protected Map<String, Long> componentActivationTimesNanosMap = new LinkedHashMap<String, Long>();
/**
* Similar to {@link #componentActivationTimesNanosMap}, but used to compute the total component activation.
*/
protected long componentActivationStartNanos;
//////////////////////////////////////
///////// ComponentLifecycle /////////
//////////////////////////////////////
/**
* Initializes the component: creates the BlobberPlugin and the BlobberWorker objects.
* <p>
* Raises an alarm (FF="Monitoring", FM="MonitorArchiver", FC="2") if initialization fails.
* This makes sense because the blobber runs as an autostart component, and the manager being
* the client that activates this blobber would not know the alarm details.
* @TODO: Shouldn't we use a FM specific to this blobber instance?
*
* @see alma.acs.component.ComponentImplBase#initialize(alma.acs.container.ContainerServices)
*/
@Override
public void initialize(ContainerServices inContainerServices) throws ComponentLifecycleException {
long now = System.nanoTime();
componentActivationStartNanos = now;
super.initialize(inContainerServices);
componentActivationTimesNanosMap.put("super init", System.nanoTime() - now);
now = System.nanoTime();
alarmSource = inContainerServices.getAlarmSource();
// clear alarm that might have been left active from a previous run
alarmSource.clearAlarm("Monitoring", "MonitorArchiver", 2); // @TODO use component-specific alarm triplet
componentActivationTimesNanosMap.put("alarm init", System.nanoTime() - now);
try {
now = System.nanoTime();
blobberPlugin = createBlobberPlugin();
componentActivationTimesNanosMap.put("plugin creation", System.nanoTime() - now);
now = System.nanoTime();
blobberPlugin.init();
componentActivationTimesNanosMap.put("plugin init", System.nanoTime() - now);
collectorIntervalSec = blobberPlugin.getCollectorIntervalSec();
m_logger.finer("Instantiated blobber plugin object.");
// Create the blobber runnable (worker)
now = System.nanoTime();
this.myWorker = createWorker();
componentActivationTimesNanosMap.put("worker creation", System.nanoTime() - now);
m_logger.finer("Instantiated blobber worker object.");
} catch (AcsJCouldntCreateObjectEx ex) {
alarmSource.raiseAlarm("Monitoring", "MonitorArchiver", 2);
throw new ComponentLifecycleException(ex);
}
// In case this blobber is restarting after a shutdown while collectors were still assigned,
// we ask the controller to add these otherwise lost collectors.
ControllerOperations controller = null;
now = System.nanoTime();
try {
controller = ControllerHelper.narrow(m_containerServices
.getDefaultComponent("IDL:alma/MonitorArchiver/Controller:1.0"));
controller.registerKnownCollectors(name());
m_logger.finer("Requested monitor controller to re-register collectors.");
} catch (AcsJContainerServicesEx ex1) {
throw new ComponentLifecycleException(
"Failed to get ARCHIVE_CONTROLLER instance.", ex1);
} finally {
if (controller != null) {
m_containerServices.releaseComponent(controller.name(), null);
}
}
componentActivationTimesNanosMap.put("controller handshake", System.nanoTime() - now);
}
/**
* Creates (using reflection) an instance of <code>alma.acs.monitoring.blobber.BlobberPluginAlmaImpl</code>
* that <code>ARCHIVE/TMCDB/</code> modules must provide.
* <p>
* Overriding this method allows other projects or unit tests that should run without alma archive code
* to create a different implementation of <code>BlobberPlugin</code>.
*/
protected BlobberPlugin createBlobberPlugin() throws AcsJCouldntCreateObjectEx {
try {
Class<? extends BlobberPlugin> pluginClass = Class.forName(MONITOR_BLOBBER_PLUGIN).asSubclass(BlobberPlugin.class);
Constructor<? extends BlobberPlugin> ctor = pluginClass.getConstructor(ContainerServices.class);
return ctor.newInstance(m_containerServices);
} catch (Exception ex) {
AcsJCouldntCreateObjectEx ex2 = new AcsJCouldntCreateObjectEx(ex);
throw ex2;
}
}
/**
* Factored out from {@link #initialize(ContainerServices)} for testing with mock BlobberWorker
* @throws AcsJCouldntCreateObjectEx
*/
protected BlobberWorker createWorker() throws AcsJCouldntCreateObjectEx {
return new BlobberWorker(m_containerServices, blobberPlugin);
}
/**
* Repeatedly runs the blobber worker code in a separate thread (every 60 seconds). If any execution of the blobber worker
* takes longer than 60 s, then subsequent executions may start late, but will not concurrently execute.
* @see alma.acs.component.ComponentImplBase#execute()
*/
@Override
public void execute() {
long now = System.nanoTime();
blobberLoopRunner = new ThreadLoopRunner(this.myWorker, collectorIntervalSec, TimeUnit.SECONDS,
m_containerServices.getThreadFactory(), m_logger, name());
blobberLoopRunner.setDelayMode(ScheduleDelayMode.FIXED_RATE);
blobberLoopRunner.runLoop();
componentActivationTimesNanosMap.put("start worker", System.nanoTime() - now);
m_logger.info("BlobberWorker thread loop is running.");
logComponentActivationTimes();
}
/**
* Creates an INFO log from the times collected in {@link #componentActivationTimesNanosMap}
* and then nulls that map.
*/
protected void logComponentActivationTimes() {
if (componentActivationTimesNanosMap != null && m_logger.isLoggable(Level.INFO)) {
String msg = "Elapsed times in ms to activate blobber '" + name() + "': ";
for (String activityName : componentActivationTimesNanosMap.keySet()) {
long timeElapsedMillis = TimeUnit.NANOSECONDS.toMillis(
componentActivationTimesNanosMap.get(activityName) );
msg += activityName + " = " + timeElapsedMillis + ", ";
}
long totalMillis = TimeUnit.NANOSECONDS.toMillis(
System.nanoTime() - componentActivationStartNanos );
msg += "Total = " + totalMillis;
m_logger.info(msg);
}
componentActivationTimesNanosMap = null;
componentActivationStartNanos = -1;
}
/**
* Stops the blobber loop, but does not forcibly stop the running blobber.
* However this method returns after 30 seconds, even if the blobber has not yet finished by then.
* @see alma.acs.component.ComponentImplBase#cleanUp()
*/
@Override
public void cleanUp() {
if (blobberLoopRunner != null && blobberLoopRunner.isLoopRunning()) {
try {
if (blobberLoopRunner.shutdown(30, TimeUnit.SECONDS)) {
m_logger.info("Shut down the BlobberWorker thread loop.");
}
else {
m_logger.warning("Failed to cleanly shut down the blobber loop within 30 s.");
}
} catch (InterruptedException ex) {
m_logger.warning("Thread interrupted while shutting down the blobber loop");
}
}
blobberPlugin.cleanUp();
}
/////////////////////////////////
///////// IDL interface /////////
/////////////////////////////////
@Override
public CollectorListStatus addCollector(String inComponentName) {
return this.myWorker.addCollector(inComponentName);
}
@Override
public CollectorListStatus containsCollector(String inComponentName) {
return this.myWorker.containsCollector(inComponentName);
}
@Override
public CollectorListStatus removeCollector(String inComponentName) {
return this.myWorker.removeCollector(inComponentName);
}
}