001/*
002 *  Copyright 2015 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;
017
018import java.io.File;
019import java.io.IOException;
020import java.lang.management.ManagementFactory;
021import java.lang.management.MemoryMXBean;
022import java.lang.management.RuntimeMXBean;
023import java.lang.management.ThreadMXBean;
024import java.util.ArrayList;
025import java.util.Collections;
026import java.util.Date;
027import java.util.HashMap;
028import java.util.HashSet;
029import java.util.List;
030import java.util.Map;
031import java.util.Set;
032import java.util.SortedMap;
033import java.util.TreeMap;
034
035import org.apache.avalon.framework.activity.Initializable;
036import org.apache.avalon.framework.component.Component;
037import org.apache.avalon.framework.logger.AbstractLogEnabled;
038import org.apache.avalon.framework.service.ServiceException;
039import org.apache.avalon.framework.service.ServiceManager;
040import org.apache.avalon.framework.service.Serviceable;
041import org.apache.commons.io.FileUtils;
042import org.rrd4j.core.Archive;
043import org.rrd4j.core.RrdDb;
044
045import org.ametys.core.ui.Callable;
046import org.ametys.core.util.DateUtils;
047import org.ametys.core.util.I18nUtils;
048import org.ametys.runtime.config.Config;
049import org.ametys.runtime.plugins.admin.jvmstatus.monitoring.MonitoringConstants;
050import org.ametys.runtime.plugins.admin.jvmstatus.monitoring.MonitoringExtensionPoint;
051import org.ametys.runtime.plugins.admin.jvmstatus.monitoring.SampleManager;
052import org.ametys.runtime.plugins.admin.jvmstatus.monitoring.alerts.AlertSampleManager;
053import org.ametys.runtime.plugins.admin.jvmstatus.monitoring.alerts.AlertSampleManager.Threshold;
054import org.ametys.runtime.servlet.RuntimeConfig;
055
056/**
057 * This helper allow to get information or runs some operations on JVM system
058 */
059public class JVMStatusHelper extends AbstractLogEnabled implements Component, Serviceable, Initializable, MonitoringConstants
060{
061    /** The monitoring extension point */
062    private MonitoringExtensionPoint _monitoringExtensionPoint;
063    
064    /** Component containing i18n utility methods */
065    private I18nUtils _i18nUtils;
066
067    private String _rrdStoragePath;
068    
069    @Override
070    public void service(ServiceManager manager) throws ServiceException
071    {
072        _monitoringExtensionPoint = (MonitoringExtensionPoint) manager.lookup(MonitoringExtensionPoint.ROLE);
073        _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE);
074    }
075    
076    public void initialize() throws Exception
077    {
078        _rrdStoragePath = FileUtils.getFile(RuntimeConfig.getInstance().getAmetysHome(), RRD_STORAGE_DIRECTORY).getPath();
079    }
080    
081    /**
082     * Runs a garbage collector.
083     * @return an empty map
084     */
085    @Callable (rights = "Runtime_Rights_Admin_Access", context = "/admin")
086    public Map<String, Object> garbageCollect ()
087    {
088        if (getLogger().isInfoEnabled())
089        {
090            getLogger().info("Administrator is garbage collecting");
091        }
092        
093        System.gc();
094        
095        return Collections.EMPTY_MAP;
096    }
097    
098    /**
099     * Retrieves information about the general status of the system
100     * @return a map containing the general status information
101     */
102    @Callable (rights = "Runtime_Rights_Admin_Access", context = "/admin")
103    public Map<String, Object> getGeneralStatus()
104    {
105        Map<String, Object> result = new HashMap<>();
106        
107        result.put("osTime", DateUtils.dateToString(new Date()));
108        try
109        {
110            result.put("activeSessions", SessionCountListener.getSessionCount());
111        }
112        catch (IllegalStateException e)
113        {
114            // empty : no value in activeSession means an error
115        }
116        
117        try
118        {
119            result.put("activeSessionsDetail", ActiveSessionListener.getActiveSessionsAsJson());
120        }
121        catch (IllegalStateException e)
122        {
123            // empty : no value in activeSession means an error
124        }
125        
126        try
127        {
128            result.put("activeRequests", RequestCountListener.getCurrentRequestCount());
129        }
130        catch (IllegalStateException e)
131        {
132            // empty : no value in activeSession means an error
133        }
134        
135        
136        ThreadMXBean tBean = ManagementFactory.getThreadMXBean();
137        MemoryMXBean mBean = ManagementFactory.getMemoryMXBean();
138        RuntimeMXBean rBean = ManagementFactory.getRuntimeMXBean();
139        
140        result.put("activeThreads", tBean.getThreadCount());
141        long[] lockedThreads = ManagementFactory.getThreadMXBean().findMonitorDeadlockedThreads();
142        
143        result.put("deadlockThreads", lockedThreads != null ? String.valueOf(lockedThreads.length) : "0");
144    
145        result.put("heap-memory-max", mBean.getHeapMemoryUsage().getMax());
146        result.put("heap-memory-used", mBean.getHeapMemoryUsage().getUsed());
147        result.put("heap-memory-commited", mBean.getHeapMemoryUsage().getCommitted());
148        
149
150        result.put("startTime", DateUtils.dateToString(new Date(rBean.getStartTime())));
151        
152        return result;
153    }
154    
155    /**
156     * Retrieves the monitoring data
157     * @return a map containing the monitoring data
158     */
159    @Callable (rights = "Runtime_Rights_Admin_Access", context = "/admin")
160    public Map<String, Object> getMonitoringData()
161    {
162        Map<String, Object> result = new HashMap<> ();
163        
164        Map<String, Object> samples = new HashMap<> ();
165        List<String> periods = new ArrayList<> ();
166        
167        for (Period period : Period.values())
168        {
169            periods.add(period.toString());
170        }
171        
172        samples.put("periods", periods);
173        
174        List<Map<String, Object>> sampleList = new ArrayList<> ();
175        for (String extensionId : _monitoringExtensionPoint.getExtensionsIds())
176        {
177            Map<String, Object> sample = new HashMap<> ();
178            SampleManager sampleManager = _monitoringExtensionPoint.getExtension(extensionId);
179
180            sample.put("id", sampleManager.getId());
181            sample.put("label", _i18nUtils.translate(sampleManager.getLabel()));
182            sample.put("description", _i18nUtils.translate(sampleManager.getDescription()));
183            if (sampleManager instanceof AlertSampleManager && Config.getInstance().getValue("runtime.system.alerts.enable") == Boolean.TRUE)
184            {
185                Map<String, Object> thresholdValues = new HashMap<>();
186                
187                Map<String, Threshold> thresholds = ((AlertSampleManager) sampleManager).getThresholdValues();
188                for (String datasourceName : thresholds.keySet())
189                {
190                    thresholdValues.put(datasourceName, thresholds.get(datasourceName).getValue());
191                }
192                
193                sample.put("thresholds", thresholdValues);
194            }
195
196            File rrdFile = new File(_rrdStoragePath, sampleManager.getId() + RRD_EXT);
197            if (getLogger().isDebugEnabled())
198            {
199                getLogger().debug("Using RRD file: " + rrdFile);
200            }
201            
202            try (RrdDb rrdDb = RrdDb.of(rrdFile.getPath()))
203            {
204                sample.put("ds", rrdDb.getDsNames());
205                
206                Set<String> consolidationFunction = new HashSet<>();
207                for (int i = 0; i < rrdDb.getArcCount(); i++)
208                {
209                    Archive archive = rrdDb.getArchive(i);
210                    consolidationFunction.add(archive.getConsolFun().toString());
211                }
212                sample.put("consolFun", consolidationFunction);
213            }
214            catch (Exception e)
215            {
216                getLogger().error("Unable to collect sample for: " + sampleManager.getId(), e);
217            }
218            
219            sampleList.add(sample);
220        }
221        
222        samples.put("sampleList", sampleList);
223        result.put("samples", samples);
224        return result;
225    }
226    
227    /**
228     * Get the RRD sample data to JSON format
229     * @param sampleId the identifier of the sample to retrieve
230     * @return the sample data as a JSON
231     * @throws IOException if an error occurs
232     */
233    @Callable (rights = "Runtime_Rights_Admin_Access", context = "/admin")
234    public List<Map<String, Object>> getSamplingData(String sampleId) throws IOException
235    {
236        if (!_monitoringExtensionPoint.hasExtension(sampleId))
237        {
238            throw new IllegalArgumentException("No sample manager exists for: " + sampleId);
239        }
240        
241        File rrdStorageDir = FileUtils.getFile(RuntimeConfig.getInstance().getAmetysHome(), RRD_STORAGE_DIRECTORY);
242        File rrdFile = new File(rrdStorageDir, sampleId + RRD_EXT);
243        if (getLogger().isDebugEnabled())
244        {
245            getLogger().debug("Using RRD file: " + rrdFile);
246        }
247        
248        SortedMap<Long, Map<String, Object>> data = new TreeMap<>();
249
250        try (RrdDb rrdDb = RrdDb.of(rrdFile.getPath()))
251        {
252            for (Archive archive : _getRelevantArchives(rrdDb))
253            {
254                long archiveStartTime = archive.getStartTime();
255                for (int row = 0; row < archive.getRows(); row++)
256                {
257                    long time = (archiveStartTime + row * archive.getArcStep()) * 1000; // time in ms
258                    Map<String, Object> values;
259                    if (data.containsKey(time))
260                    {
261                        values = data.get(time);
262                    }
263                    else
264                    {
265                        values = new HashMap<>();
266                        data.put(time, values);
267                    }
268                    
269                    String[] dsNames = rrdDb.getDsNames();
270                    for (int dsIndex = 0; dsIndex < rrdDb.getDsCount(); dsIndex++)
271                    {
272                        String dsName = archive.getConsolFun().toString() + "_" + dsNames[dsIndex];
273                        double value = archive.getRobin(dsIndex).getValue(row);
274                        if (!values.containsKey(dsName))
275                        {
276                            if (Double.isNaN(value))
277                            {
278                                value = 0;
279                            }
280                            values.put(dsName, value);
281                        }
282                    }
283                }
284            }
285        }
286        
287        return _convertData(data);
288    }
289    
290    private List<Archive> _getRelevantArchives(RrdDb rrdDb)
291    {
292        // Return all the archives, but in case of bad performance, it could
293        // be interesting to return jsut some of them
294        List<Archive> result = new ArrayList<>();
295        for (int i = 0; i < rrdDb.getArcCount(); i++)
296        {
297            Archive archive = rrdDb.getArchive(i);
298            result.add(archive);
299        }
300        
301        return result;
302    }
303    
304    private List<Map<String, Object>> _convertData(SortedMap<Long, Map<String, Object>> data)
305    {
306        List<Map<String, Object>> result = new ArrayList<>();
307        
308        for (Long time : data.keySet())
309        {
310            Map<String, Object> row = data.get(time);
311            row.put("time", time);
312            result.add(row);
313        }
314        
315        return result;
316    }
317    
318    /**
319     * Retrieves the monitoring data
320     * @return a map containing the monitoring data
321     */
322    @Callable (rights = "Runtime_Rights_Admin_Access", context = "/admin")
323    public List<Map<String, String>> getSystemProperties()
324    {
325        RuntimeMXBean rBean = ManagementFactory.getRuntimeMXBean();
326
327        Map<String, String> properties = rBean.getSystemProperties();
328        return properties.entrySet().stream()
329            .filter(e -> e.getKey().indexOf(":") == -1)
330            .map(e -> Map.of("name", e.getKey(), "value", e.getValue()))
331            .toList();
332    }
333}