001/*
002 *  Copyright 2012 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.runtime.plugins.admin.jvmstatus.monitoring;
017
018import java.io.File;
019import java.util.ArrayList;
020import java.util.HashMap;
021import java.util.LinkedHashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.Timer;
025import java.util.TimerTask;
026
027import javax.mail.MessagingException;
028
029import org.apache.avalon.framework.activity.Disposable;
030import org.apache.avalon.framework.activity.Initializable;
031import org.apache.avalon.framework.component.Component;
032import org.apache.avalon.framework.logger.LogEnabled;
033import org.apache.avalon.framework.logger.Logger;
034import org.apache.avalon.framework.service.ServiceException;
035import org.apache.avalon.framework.service.ServiceManager;
036import org.apache.avalon.framework.service.Serviceable;
037import org.apache.commons.io.FileUtils;
038import org.rrd4j.core.RrdDb;
039
040import org.ametys.core.util.I18nUtils;
041import org.ametys.core.util.mail.SendMailHelper;
042import org.ametys.runtime.config.Config;
043import org.ametys.runtime.i18n.I18nizableText;
044import org.ametys.runtime.plugins.admin.jvmstatus.monitoring.alerts.AlertSampleManager;
045import org.ametys.runtime.plugins.admin.jvmstatus.monitoring.alerts.AlertSampleManager.Threshold;
046import org.ametys.runtime.servlet.RuntimeConfig;
047
048/**
049 * {@link TimerTask} for creating and feeding RRDs files in order to
050 * produce graphs for monitoring:
051 * <ul>
052 *  <li>JVM uptime
053 *  <li>JVM memory status
054 *  <li>JVM thread count
055 *  <li>Servlet Engine request count
056 *  <li>Servlet Engine session count
057 * </ul>
058 */
059public class RRDsFeederTimerTask extends TimerTask implements Component, LogEnabled, Serviceable, Initializable, Disposable, MonitoringConstants
060{
061    private static final String __CONFIG_ALERTS_ENABLED = "runtime.system.alerts.enable";
062    private static final String __CONFIG_FROM_MAIL = "smtp.mail.from";
063    private static final String __CONFIG_ADMIN_MAIL = "smtp.mail.sysadminto";
064    
065    private Logger _logger;
066    private MonitoringExtensionPoint _monitoringExtensionPoint;
067    private Timer _timer;
068    private String _rrdStoragePath;
069    private I18nUtils _i18nUtils;
070    
071    /** Tells if there is a current alert, i.e. if we already sent an alert email
072     * This is  a map {sampleManagerId -&gt; datasourceName -&gt; wasAlertedLastTime} */
073    private Map<String, Map<String, Boolean>> _currentAlerts;
074    
075    public void enableLogging(Logger logger)
076    {
077        _logger = logger;
078    }
079    
080    public void service(ServiceManager manager) throws ServiceException
081    {
082        _monitoringExtensionPoint = (MonitoringExtensionPoint) manager.lookup(MonitoringExtensionPoint.ROLE);
083        _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE);
084    }
085    
086    public void initialize() throws Exception
087    {
088        _rrdStoragePath = FileUtils.getFile(RuntimeConfig.getInstance().getAmetysHome(), RRD_STORAGE_DIRECTORY).getPath();
089        
090        _logger.debug("Starting timer");
091        // Daemon thread
092        _timer = new Timer("RRDFeeder", true);
093        // Start in 30s and refresh each minutes
094        _timer.scheduleAtFixedRate(this, 30000, FEEDING_PERIOD * 1000);
095        
096        _currentAlerts = new LinkedHashMap<>();
097    }
098
099    @Override
100    public void run()
101    {
102        if (_logger.isDebugEnabled())
103        {
104            _logger.debug("Time to collect data");
105        }
106        
107        for (String extensionId : _monitoringExtensionPoint.getExtensionsIds())
108        {
109            SampleManager sampleManager = _monitoringExtensionPoint.getExtension(extensionId);
110            
111            if (sampleManager != null)
112            {
113                String sampleName = sampleManager.getId();
114                File rrdFile = new File(_rrdStoragePath, sampleName + RRD_EXT);
115                
116                if (_logger.isDebugEnabled())
117                {
118                    _logger.debug("Collecting sample for: " + sampleName);
119                }
120                
121                try (RrdDb rrdDb = RrdDb.of(rrdFile.getPath()))
122                {
123                    Map<String, Object> collectedValues = sampleManager.collect(rrdDb.createSample());
124                    if (sampleManager instanceof AlertSampleManager)
125                    {
126                        _checkIfAlert((AlertSampleManager) sampleManager, collectedValues);
127                    }
128                }
129                catch (Exception e)
130                {
131                    _logger.error("Unable to collect sample for: " + sampleName, e);
132                }
133            }
134        }
135    }
136    
137    private void _checkIfAlert(AlertSampleManager sampleManager, Map<String, Object> collectedValues)
138    {
139        if (Config.getInstance() == null)
140        {
141            return;
142        }
143        
144        if (Config.getInstance().getValue(__CONFIG_ALERTS_ENABLED, true, false))
145        {
146            if (_currentAlerts.get(sampleManager.getId()) == null)
147            {
148                _currentAlerts.put(sampleManager.getId(), new HashMap<>());
149            }
150            
151            Map<String, Threshold> thresholds = sampleManager.getThresholdValues();
152            for (String datasourceName : thresholds.keySet())
153            {
154                if (_currentAlerts.get(sampleManager.getId()).get(datasourceName) == null)
155                {
156                    _currentAlerts.get(sampleManager.getId()).put(datasourceName, false);
157                }
158                
159                Threshold threshold = thresholds.get(datasourceName);
160                if (threshold.isExceeded(collectedValues.get(datasourceName)) &&  !_currentAlerts.get(sampleManager.getId()).get(datasourceName))
161                {
162                    // Send the mail
163                    _sendAlertMail(threshold.getMailSubject(), threshold.getMailBody(), collectedValues.get(datasourceName).toString(), threshold.getValue().toString());
164                    // Memorize to not send a mail again
165                    _currentAlerts.get(sampleManager.getId()).put(datasourceName, true);
166                }
167                else if (!threshold.isExceeded(collectedValues.get(datasourceName)) &&  _currentAlerts.get(sampleManager.getId()).get(datasourceName))
168                {
169                    // Next check, we would possibly send a mail
170                    _currentAlerts.get(sampleManager.getId()).put(datasourceName, false);
171                }
172            }
173        }
174    }
175    
176    private void _sendAlertMail(I18nizableText subject, I18nizableText body, String currentValue, String thresholdValue)
177    {
178        Config config = Config.getInstance();
179        String toMail = config.getValue(__CONFIG_ADMIN_MAIL);
180        String fromMail = config.getValue(__CONFIG_FROM_MAIL);
181        try
182        {
183            String subjectStr = _i18nUtils.translate(subject, "fr"); //FIXME fr hardcoded
184            
185            List<String> bodyParams = new ArrayList<>();
186            bodyParams.add(currentValue);
187            bodyParams.add(thresholdValue);
188            I18nizableText bodyWithParams = body.isI18n() ? new I18nizableText(body.getCatalogue(), body.getKey(), bodyParams) : body;
189            String bodyStr = _i18nUtils.translate(bodyWithParams, "fr");
190            
191            SendMailHelper.sendMail(subjectStr, null, bodyStr, toMail, fromMail);
192        }
193        catch (MessagingException e)
194        {
195            if (_logger.isWarnEnabled())
196            {
197                _logger.warn("Could not send an alert e-mail to " + toMail, e);
198            }
199        }
200    }
201    
202    public void dispose()
203    {
204        _logger = null;
205        _monitoringExtensionPoint = null;
206        _rrdStoragePath = null;
207        cancel();
208        _timer.cancel();
209    }
210}