ThreadInfoController.java
/*
* Copyright 2020 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gringlobal.mvc.admin;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* The Class ThreadInfoController.
*
* @author Matija Obreza
*/
@Controller
@RequestMapping("/admin")
@PreAuthorize("hasAuthority('GROUP_ADMINS')")
@Slf4j
public class ThreadInfoController {
@PostMapping(value = "/action", params = "action=threaddump", produces = { MediaType.TEXT_PLAIN_VALUE })
public void threadDump(HttpServletResponse response) throws IOException {
response.setContentType(MediaType.TEXT_PLAIN_VALUE);
log.warn("Dumping thread info");
PrintWriter writer = response.getWriter();
writer.println("Thread dump:\t" + new Date());
writer.println("Hostname:\t" + java.net.InetAddress.getLocalHost().getHostName());
writer.println("CPUs:\t" + Runtime.getRuntime().availableProcessors());
writer.println("Free memory:\t" + Runtime.getRuntime().freeMemory());
writer.println("Max memory:\t" + Runtime.getRuntime().maxMemory());
writer.println("Total memory:\t" + Runtime.getRuntime().totalMemory());
writer.println();
Map<Thread, StackTraceElement[]> threadSet = Thread.getAllStackTraces();
ArrayList<Thread> sortedThreads = new ArrayList<>(threadSet.keySet());
sortedThreads.sort((t1, t2) -> t1.getName().compareTo(t2.getName()));
writer.println("\n\n*** Thread list ***\n");
for (Thread t : sortedThreads) {
writer.println(t.getName());
}
writer.println();
writer.flush();
writer.println("\n\n*** Threads ***\n");
writer.println("ID\tState\tName\tGroup");
for (Thread t : sortedThreads) {
writer.print(t.getId());
writer.print("\t");
writer.print(t.getState());
writer.print("\t");
writer.print(t.getName());
writer.print("\t");
ThreadGroup threadGroup = t.getThreadGroup();
writer.print(threadGroup == null ? "N/A" : threadGroup.getName());
writer.println();
StackTraceElement[] ste = threadSet.get(t);
Arrays.stream(ste).forEach((st) -> {
writer.print(t.getId());
writer.print("\t");
writer.print(st.getClassName());
writer.print(":");
writer.print(st.getMethodName());
writer.print("\t");
writer.print(StringUtils.defaultIfBlank(st.getFileName(), "---"));
writer.print("\t");
writer.println();
});
writer.println();
writer.flush();
}
ThreadMXBean tmxb = ManagementFactory.getThreadMXBean();
if (tmxb != null && tmxb.isThreadCpuTimeSupported()) {
writer.println("\n\n*** CPU Usage ***\n");
writer.println("ID\tCPU%\tCPU[ms]\tUser[ms]\tState\tName\tGroup");
Map<Long, ThreadCpuUsage> cpuUsage = findCpuUsage(tmxb, sortedThreads);
sortedThreads.sort((t1, t2) -> (int)(cpuUsage.get(t2.getId()).cpuTime - cpuUsage.get(t1.getId()).cpuTime));
for (Thread t : sortedThreads) {
writer.print(t.getId());
writer.print("\t");
ThreadCpuUsage cpu = cpuUsage.get(t.getId());
writer.printf("%.4f", cpu.utilization * 100);
writer.print("\t");
writer.printf("%.2f", cpu.cpuTime / 1000f);
writer.print("\t");
writer.printf("%.2f", cpu.userTime / 1000f);
writer.printf("\t");
writer.print(t.getState());
writer.print("\t");
writer.print(t.getName());
writer.print("\t");
ThreadGroup threadGroup = t.getThreadGroup();
writer.print(threadGroup == null ? "N/A" : threadGroup.getName());
writer.println();
writer.flush();
}
}
writer.flush();
}
/**
* @param tmxb
* @param threads2
* @return
*/
private Map<Long, ThreadCpuUsage> findCpuUsage(ThreadMXBean tmxb, List<? extends Thread> threads) {
Map<Long, ThreadCpuUsage> cpu = new Hashtable<Long, ThreadCpuUsage>();
for (int i = 0; i < threads.size(); i++) {
Thread thread = threads.get(i);
long threadId = thread.getId();
ThreadCpuUsage cpuUsage = new ThreadCpuUsage();
cpuUsage.cpuTime1 = tmxb.getThreadCpuTime(threadId);
cpuUsage.userTime1 = tmxb.getThreadUserTime(threadId);
cpuUsage.time1 = System.currentTimeMillis();
cpu.put(threadId, cpuUsage);
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
log.warn("Interrupted from sleep: " + e.getMessage());
}
for (int i = 0; i < threads.size(); i++) {
Thread thread = threads.get(i);
long threadId = thread.getId();
ThreadCpuUsage cpuUsage = cpu.get(threadId);
cpuUsage.cpuTime2 = tmxb.getThreadCpuTime(threadId);
cpuUsage.userTime2 = tmxb.getThreadUserTime(threadId);
cpuUsage.time2 = System.currentTimeMillis();
cpuUsage.cpuTime = cpuUsage.cpuTime2 - cpuUsage.cpuTime1;
cpuUsage.userTime = cpuUsage.userTime2 - cpuUsage.userTime1;
cpuUsage.time = cpuUsage.time2 - cpuUsage.time1;
// if (cpuUsage.cpuTime > 0) {
// cpuUsage.threadInfo = tmxb.getThreadInfo(threadId, 50);
// }
cpuUsage.utilization = (cpuUsage.cpuTime) / ((cpuUsage.time) * 1000000F);
}
return cpu;
}
private static class ThreadCpuUsage {
// public ThreadInfo threadInfo;
public long userTime1, userTime2, userTime;
public long cpuTime1, cpuTime2, cpuTime;
public long time1, time2, time;
public double utilization;
}
}