/* ALMA - Atacama Large Millimiter Array * Copyright (c) European Southern Observatory, 2014 * * 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.alarmsystem.statistics; import java.io.File; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Logger; import alma.acs.logging.AcsLogLevel; import alma.acs.logging.level.AcsLogLevelDefinition; import cern.laser.business.pojo.AlarmMessageProcessorImpl; import com.cosylab.acs.laser.AlarmSourcesListenerCached; /** * A class to calculate various statistics * on the alarms processed by the alarm server. * <P> * The statistics are * <UL> * <LI>logged at definite time intervals to avoid flooding the logging systems * <LI>saved on a file * <P> * <code>StatsCalculator</code> logs a minimal set of statistics on the logging system * but the full statistics are saved on the file. * <P> * Statistics are logged every time interval whose default is {@value #DEFAULTTIMEINTERVAL} minutes. * The time interval can be customized by setting the {@value #TIMEINTERVALPROPNAME} java property. * A time interval of 0 minutes disable the logging of statistics (@see {@value #TIMEINTERVALPROPNAME}). * The log level is INFO by default but can be customized by setting the {@link #LOGLEVELPROPNAME} * java property. * <P> * Statistics logged on file are more complete then those logged. * The number of activations and terminations of each alarm received during the time interval is stored * in the {@link #alarmsMap} map. * <P> * Life cycle: * {@link #start()} must be called to start gathering statistics and {@link #shutdown()} must be * executed when finished. * * @author acaproni * @since ACS 2015.2 */ public class StatsCalculator implements Runnable { /** * The name of the property to customize the time interval (in minutes) to log * statistics. * <BR>Setting the value of this property to <code>0</code> disables the statistics. */ public static final String TIMEINTERVALPROPNAME="alma.acs.alarmsystem.statistics.timeinterval"; /** * The default time interval is {@value #DEFAULTTIMEINTERVAL} minutes */ public static final int DEFAULTTIMEINTERVAL=10; /** * Statistics are logged every time interval (in minutes) */ public final int timeInterval=Integer.getInteger(TIMEINTERVALPROPNAME, DEFAULTTIMEINTERVAL); /** * The executor to schedule the writing of statistics at every time interval. */ private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); /** * The number of activations received in the last time interval. * <P> * These are the active alarms received from sources. * */ private final AtomicInteger alarmsActivationForTimeInterval = new AtomicInteger(); /** * The number of terminations received in the last time interval. * <P> * These are the terminate alarms received from sources. */ private final AtomicInteger alarmsTerminationForTimeInterval = new AtomicInteger(); /** * The number of messages received from the sources NC. * <BR> * Note that a message can contain more then one FS to process */ private final AtomicInteger messagesFromSources = new AtomicInteger(); /** * The logger */ private final Logger logger; /** * The queue of messages received from the sources NC and waiting to be processed */ private final AlarmSourcesListenerCached sourceNCQueue; /** * The map of parameters for the log * (these params appear as additional data in the published log) */ private final Map<String, Long> logParams = new HashMap<String, Long>(); /** * The name of the property to customize the the level of the logs with the * statistics. * <P> * The value of this level can be a string (like Debug) or a integer (like 5 for Notice). * See {@link AcsLogLevelDefinition} for further details */ public static final String LOGLEVELPROPNAME="alma.acs.alarmsystem.statistics.loglevel"; /** * The default log level */ public static final AcsLogLevel DEFAULTLOGLEVEL = AcsLogLevel.INFO; /** * Statistics logs have <code>logLevel<code> level. */ private final AcsLogLevel logLevel; /** * The hashmap to record the activations/terminations of * each alarm during the time interval. * * <BR>It is used to write stats on file */ private final StatHashMap alarmsMap; /** * Constructor * * @param logger The logger */ public StatsCalculator(Logger logger, AlarmSourcesListenerCached sourceNCQueue) { if (logger==null) { throw new IllegalArgumentException("The logger can't be null!"); } this.logger=logger; if (sourceNCQueue==null) { throw new IllegalArgumentException("The AlarmSourcesListenerCached can't be null!"); } this.sourceNCQueue=sourceNCQueue; this.logLevel=evalLogLevel(); alarmsMap = new StatHashMap(logger,timeInterval,getStatFolder()); } /** * Evaluate the log level to use using the one defined by {@link #LOGLEVELPROPNAME} property name * or using the default. * * @return */ private AcsLogLevel evalLogLevel() { String propValue=System.getProperty(LOGLEVELPROPNAME); if (propValue==null) { // No property defined: use default! return DEFAULTLOGLEVEL; } AcsLogLevelDefinition logLevelDef=null; // The string can be an integer or a the name of the log level try { Integer level=Integer.parseInt(propValue); logLevelDef=AcsLogLevelDefinition.fromInteger(level.intValue()); } catch (Throwable t1) { // The string should contain the name of the log level like Info try { logLevelDef=AcsLogLevelDefinition.fromName(propValue); } catch (Throwable t2) { logLevelDef=null; } } if (logLevelDef==null) { // The value of the property was wrong! // Fall back to the default return DEFAULTLOGLEVEL; } return AcsLogLevel.fromAcsCoreLevel(logLevelDef); } /** * An FaultState with the passed ID and activation state * has been processed. * <P> * For the statistics, <code>StatsCalculator</code> does not distinguish if a * alarm is a change (@see {@link AlarmMessageProcessorImpl#processChange(cern.laser.source.alarmsysteminterface.FaultState, String, String, java.sql.Timestamp)} * or a backup (<code>AlarmMessageProcessorImpl#processBackup(...)</code>). * * @param alarmID The ID of the received alarm * @param active The status of the alarm (<code>true</code> means active) */ public void processedFS(String alarmID,boolean active) { if (timeInterval==0) { return; } if (active) { alarmsActivationForTimeInterval.incrementAndGet(); } else { alarmsTerminationForTimeInterval.incrementAndGet(); } alarmsMap.processedFS(alarmID,active); } /** * A message from the source NC has been received. * <BR> * Note that a source can encapsulate more FS changes in the * same message. */ public void msgFromSourceNCReceived() { messagesFromSources.incrementAndGet(); } /** * Start to collect statistics and spawn the timer task. */ public void start() { // Start the scheduled task if (timeInterval>0) { executor.scheduleWithFixedDelay(this, timeInterval, timeInterval, TimeUnit.MINUTES); Object[] params={"Time interval",Integer.valueOf(timeInterval)}; alarmsMap.start(); logger.log(AcsLogLevel.INFO,"Gathering of statistics enabled",params); } else { logger.log(AcsLogLevel.INFO,"Gathering of statistics disabled (time interval<=0)"); } } /** * This method must be called when no more statistics must be collected and * published. * It stops the timer task and frees all the resources. */ public void shutdown() { // Stop the timer task if (timeInterval>0) { logger.log(AcsLogLevel.INFO,"Shutting down stats calucaltion"); executor.shutdown(); alarmsMap.shutdown(); } } /** * The scheduler invokes this method at fixed time intervals to generate and publish statistics. * It also reset the variable to calculate the correct statistics at the next iteration. */ @Override public void run() { logStats(); logStatsOnFile(); resetStats(); } /** * Logs statistics. * <P> * <code>logStats()</code> publishes a single log using providing a map of * the calculated values i.e. a map of <name,Value> pairs. */ private void logStats() { long activations=alarmsActivationForTimeInterval.get(); long terminations=alarmsTerminationForTimeInterval.get(); logParams.put("FaultStatesProcessed", activations+terminations); logParams.put("Activations", activations); logParams.put("Terminations", terminations); logParams.put("NumOFMsgsFromSources",Long.valueOf(messagesFromSources.get())); logParams.put("MsgsWaitingToBeProcessed", Long.valueOf(sourceNCQueue.getQueueSize())); logger.log(logLevel,"Alarm server statistics",logParams); } /** * Logs the statistic in a file * <P> * The information written on file is more complete then the * statistics logged by {@link #logStats()} */ private void logStatsOnFile() { alarmsMap.calcStatistics(alarmsActivationForTimeInterval.get(),alarmsTerminationForTimeInterval.get()); } /** * Reset the fields of the statistics to be ready to gather numbers * for the next iteration. */ private void resetStats() { alarmsActivationForTimeInterval.set(0); alarmsTerminationForTimeInterval.set(0); messagesFromSources.set(0); } /** * Get the folder where the files with the statistics will be written * that normally is <code>ACS_TMP/ACS_INSTANCE.n</code> (the fallback is the current folder). * * @return The folder to host file of statistics. */ private File getStatFolder() { // Try to create the file in $ACS_TMP String acstmp = System.getProperty("ACS.tmp","."); if (!acstmp.endsWith(""+File.separator)) { acstmp=acstmp+File.separator; } String acsbaseport = System.getProperty("ACS.baseport","0"); String folderName=acstmp+File.separator+"ACS_INSTANCE."+acsbaseport; logger.log(AcsLogLevel.DEBUG,"Alarm systems statisticss will be recorded in "+folderName); return new File(folderName); } }