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.util.ArrayList;
021import java.util.Arrays;
022import java.util.HashMap;
023import java.util.List;
024import java.util.Map;
025import java.util.regex.Matcher;
026import java.util.regex.Pattern;
027import java.util.stream.Collectors;
028
029import org.apache.avalon.framework.component.Component;
030
031import org.ametys.core.ui.Callable;
032
033/**
034 * Component used by the admin thread tool
035 */
036public class ThreadInfo implements Component
037{
038    /** Avalon role */
039    public static final String ROLE = ThreadInfo.class.getName();
040    
041    private Pattern _groupPattern = Pattern.compile("^(.*)-\\d+$");
042    
043    /**
044     * Dump all JVM threads.
045     * @return a JSON-oriented thread dump
046     */
047    @Callable
048    public List<Map<String, Object>> dumpAllThreads()
049    {
050        List<Map<String, Object>> result = new ArrayList<>();
051        
052        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
053        java.lang.management.ThreadInfo[] threads = threadMXBean.dumpAllThreads(true, true);
054        
055        for (java.lang.management.ThreadInfo thread : threads)
056        {
057            Map<String, Object> info = new HashMap<>();
058            
059            String name = thread.getThreadName();
060            String group = "-";
061            Matcher matcher = _groupPattern.matcher(name);
062            if (matcher.matches())
063            {
064                group = matcher.group(1);
065            }
066            
067            info.put("name", name);
068            info.put("group", group);
069            info.put("state", thread.getThreadState());
070            info.put("cpu", threadMXBean.getThreadCpuTime(thread.getThreadId()));
071            info.put("blocked-time", thread.getBlockedTime());
072            info.put("waited-time", thread.getWaitedTime());
073            info.put("lock-owner", thread.getLockOwnerName());
074            
075            String trace = Arrays.stream(thread.getStackTrace())
076                                       .map(el -> _printStackTraceElement(el))
077                                       .collect(Collectors.joining("\n"));
078            info.put("stack-trace", trace);
079            info.put("stack-ametys", Arrays.stream(thread.getStackTrace()).anyMatch(el -> el.getClassName().contains("org.ametys")));
080            info.put("stack-size", thread.getStackTrace().length);
081            
082            result.add(info);
083        }
084        
085        return result;
086    }
087    
088    private String _printStackTraceElement(StackTraceElement el)
089    {
090        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)")));
091    }
092}