001/*
002 *  Copyright 2021 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.lang.management.ManagementFactory;
019import java.lang.management.ThreadMXBean;
020import java.time.ZonedDateTime;
021import java.util.ArrayList;
022import java.util.Arrays;
023import java.util.HashMap;
024import java.util.List;
025import java.util.Map;
026import java.util.regex.Matcher;
027import java.util.regex.Pattern;
028import java.util.stream.Collectors;
029
030import org.apache.avalon.framework.component.Component;
031
032import org.ametys.core.ui.Callable;
033import org.ametys.core.util.DateUtils;
034
035/**
036 * Component used by the admin thread tool
037 */
038public class ThreadInfo implements Component
039{
040    /** Avalon role */
041    public static final String ROLE = ThreadInfo.class.getName();
042    
043    private Pattern _groupPattern = Pattern.compile("^(.*)-\\d+$");
044
045    /**
046     * Create a threaddump
047     * @return The new threaddump
048     */
049    @Callable(rights = "REPOSITORY_Rights_Access", context = "/admin")
050    public Map<String, Object> getThreadDump()
051    {
052        StringBuffer threadDump = new StringBuffer (System.lineSeparator ());
053        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean ();
054        for (java.lang.management.ThreadInfo threadInfo : threadMXBean.dumpAllThreads (true, true)) 
055        {
056            threadDump.append (threadInfo.toString ());
057        }
058        return Map.of(
059            "date", DateUtils.zonedDateTimeToString(ZonedDateTime.now()),
060            "data", threadDump.toString().replaceAll("\r", "")
061        );
062    }
063    
064    /**
065     * Dump all JVM threads.
066     * @return a JSON-oriented thread dump
067     */
068    @Callable(rights = "REPOSITORY_Rights_Access", context = "/admin")
069    public List<Map<String, Object>> dumpAllThreads()
070    {
071        List<Map<String, Object>> result = new ArrayList<>();
072        
073        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
074        java.lang.management.ThreadInfo[] threads = threadMXBean.dumpAllThreads(true, true);
075        
076        for (java.lang.management.ThreadInfo thread : threads)
077        {
078            Map<String, Object> info = new HashMap<>();
079            
080            String name = thread.getThreadName();
081            String group = "-";
082            Matcher matcher = _groupPattern.matcher(name);
083            if (matcher.matches())
084            {
085                group = matcher.group(1);
086            }
087            
088            info.put("name", name);
089            info.put("group", group);
090            info.put("state", thread.getThreadState());
091            info.put("cpu", threadMXBean.getThreadCpuTime(thread.getThreadId()));
092            info.put("blocked-time", thread.getBlockedTime());
093            info.put("waited-time", thread.getWaitedTime());
094            info.put("lock-owner", thread.getLockOwnerName());
095            
096            String trace = Arrays.stream(thread.getStackTrace())
097                                       .map(el -> _printStackTraceElement(el))
098                                       .collect(Collectors.joining("\n"));
099            info.put("stack-trace", trace);
100            info.put("stack-ametys", Arrays.stream(thread.getStackTrace()).anyMatch(el -> el.getClassName().contains("org.ametys")));
101            info.put("stack-size", thread.getStackTrace().length);
102            
103            result.add(info);
104        }
105        
106        return result;
107    }
108    
109    private String _printStackTraceElement(StackTraceElement el)
110    {
111        return el.getClassName() + "." + el.getMethodName() + "(" + (el.isNativeMethod() ? "Native Method)" : (el.getFileName() != null && el.getLineNumber() >= 0 ? el.getFileName() + ":" + el.getLineNumber() + ")" : (el.getFileName() != null ?  "" + el.getFileName() + ")" : "Unknown Source)")));
112    }
113}