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