/*
* SystemResource.java
*
* Copyright (c) 2013, Instituto Superior Técnico. All rights reserved.
*
* This file is part of bennu-core.
*
* bennu-core is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* bennu-core is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with bennu-core. If not, see <http://www.gnu.org/licenses/>.
*/
package org.fenixedu.bennu.core.api;
import java.io.File;
import java.io.IOException;
import java.lang.Thread.State;
import java.lang.management.ClassLoadingMXBean;
import java.lang.management.CompilationMXBean;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryUsage;
import java.lang.management.OperatingSystemMXBean;
import java.lang.management.RuntimeMXBean;
import java.lang.management.ThreadMXBean;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
import javax.management.MBeanServer;
import javax.management.ObjectInstance;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import org.fenixedu.bennu.core.api.json.KeyValuePropertiesViewer;
import org.fenixedu.bennu.core.groups.Group;
import org.fenixedu.bennu.core.rest.BennuRestResource;
import org.fenixedu.bennu.core.rest.Healthcheck;
import org.fenixedu.bennu.core.rest.Healthcheck.Result;
import org.fenixedu.commons.configuration.ConfigurationInvocationHandler;
import pt.ist.fenixframework.FenixFramework;
import pt.ist.fenixframework.core.SharedIdentityMap;
import com.google.common.base.Stopwatch;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
@Path("/bennu-core/system")
public class SystemResource extends BennuRestResource {
private static String getHostName() {
try {
return InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
return "<Host name unknown>";
}
}
@GET
@Path("info")
@SuppressWarnings("restriction")
@Produces({ MediaType.APPLICATION_JSON })
public JsonObject info(@Context HttpServletRequest request, @QueryParam("full") @DefaultValue("true") Boolean full) {
accessControl(Group.managers());
JsonObject json = new JsonObject();
if (full) {
// fenix-framework projects
json.add("projects", getBuilder().view(FenixFramework.getProject().getProjects()));
// libraries
json.add("libraries", getLibs(request));
// system properties
json.add("sys", getBuilder().view(System.getProperties(), Properties.class, KeyValuePropertiesViewer.class));
// environment properties
JsonArray env = new JsonArray();
for (Entry<String, String> entry : System.getenv().entrySet()) {
JsonObject object = new JsonObject();
object.addProperty("key", entry.getKey());
object.addProperty("value", entry.getValue());
env.add(object);
}
json.add("env", env);
JsonArray headers = new JsonArray();
for (final Enumeration<String> e = request.getHeaderNames(); e.hasMoreElements();) {
String header = e.nextElement();
JsonObject object = new JsonObject();
object.addProperty("key", header);
object.addProperty("value", request.getHeader(header));
headers.add(object);
}
json.add("http", headers);
json.add(
"conf",
getBuilder().view(ConfigurationInvocationHandler.rawProperties(), Properties.class,
KeyValuePropertiesViewer.class));
}
JsonObject metrics = new JsonObject();
metrics.addProperty("cacheSize", SharedIdentityMap.getCache().size());
metrics.addProperty("project", FenixFramework.getProject().getName());
metrics.addProperty("version", FenixFramework.getProject().getVersion());
metrics.addProperty("hostname", getHostName());
MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
metrics.addProperty("jvm.memory.total.used", memoryBean.getHeapMemoryUsage().getUsed()
+ memoryBean.getNonHeapMemoryUsage().getUsed());
metrics.addProperty("jvm.memory.total.max", memoryBean.getHeapMemoryUsage().getMax()
+ memoryBean.getNonHeapMemoryUsage().getMax());
metrics.addProperty("jvm.memory.heap.used", memoryBean.getHeapMemoryUsage().getUsed());
metrics.addProperty("jvm.memory.heap.max", memoryBean.getHeapMemoryUsage().getMax());
metrics.addProperty("jvm.memory.non-heap.used", memoryBean.getNonHeapMemoryUsage().getUsed());
metrics.addProperty("jvm.memory.non-heap.max", memoryBean.getNonHeapMemoryUsage().getMax());
ThreadMXBean threads = ManagementFactory.getThreadMXBean();
metrics.addProperty("jvm.threads.count", threads.getThreadCount());
ClassLoadingMXBean classLoading = ManagementFactory.getClassLoadingMXBean();
metrics.addProperty("jvm.classloading.loaded.total", classLoading.getTotalLoadedClassCount());
metrics.addProperty("jvm.classloading.loaded", classLoading.getLoadedClassCount());
metrics.addProperty("jvm.classloading.unloaded", classLoading.getUnloadedClassCount());
RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();
metrics.addProperty("jvm.runtime.name", runtime.getName());
metrics.addProperty("jvm.runtime.vm.name", runtime.getVmName());
metrics.addProperty("jvm.runtime.vm.vendor", runtime.getVmVendor());
metrics.addProperty("jvm.runtime.vm.version", runtime.getVmVersion());
metrics.addProperty("jvm.runtime.spec.name", runtime.getSpecName());
metrics.addProperty("jvm.runtime.spec.vendor", runtime.getSpecVendor());
metrics.addProperty("jvm.runtime.spec.version", runtime.getSpecVersion());
metrics.addProperty("jvm.runtime.uptime", runtime.getUptime() / 1000);
metrics.addProperty("jvm.runtime.start.time", runtime.getStartTime());
metrics.addProperty("now", System.currentTimeMillis());
CompilationMXBean compilation = ManagementFactory.getCompilationMXBean();
metrics.addProperty("jit.name", compilation.getName());
if (compilation.isCompilationTimeMonitoringSupported()) {
metrics.addProperty("jit.total.time", compilation.getTotalCompilationTime());
}
OperatingSystemMXBean os = ManagementFactory.getOperatingSystemMXBean();
metrics.addProperty("os.name", os.getName());
metrics.addProperty("os.arch", os.getArch());
metrics.addProperty("os.version", os.getVersion());
metrics.addProperty("os.available.cpus", os.getAvailableProcessors());
metrics.addProperty("os.load.average", os.getSystemLoadAverage());
if (os instanceof com.sun.management.OperatingSystemMXBean) {
com.sun.management.OperatingSystemMXBean osBean = (com.sun.management.OperatingSystemMXBean) os;
metrics.addProperty("os.system.cpu.usage", osBean.getSystemCpuLoad());
metrics.addProperty("os.process.cpu.usage", osBean.getProcessCpuLoad());
metrics.addProperty("os.process.cpu.time", osBean.getProcessCpuTime());
metrics.addProperty("os.free.physical.memory", osBean.getFreePhysicalMemorySize());
metrics.addProperty("os.total.physical.memory", osBean.getTotalPhysicalMemorySize());
metrics.addProperty("os.committed.vm.size", osBean.getCommittedVirtualMemorySize());
}
if (os instanceof com.sun.management.UnixOperatingSystemMXBean) {
com.sun.management.UnixOperatingSystemMXBean osBean = (com.sun.management.UnixOperatingSystemMXBean) os;
metrics.addProperty("os.process.max.fd.count", osBean.getMaxFileDescriptorCount());
metrics.addProperty("os.process.open.fd.count", osBean.getOpenFileDescriptorCount());
}
JsonArray gc = new JsonArray();
for (GarbageCollectorMXBean bean : ManagementFactory.getGarbageCollectorMXBeans()) {
JsonObject beanJson = new JsonObject();
beanJson.addProperty("name", bean.getName());
beanJson.addProperty("valid", bean.isValid());
beanJson.addProperty("pools", Arrays.stream(bean.getMemoryPoolNames()).collect(Collectors.joining(", ")));
beanJson.addProperty("collectionCount", bean.getCollectionCount());
beanJson.addProperty("collectionTime", bean.getCollectionTime());
gc.add(beanJson);
}
metrics.add("gc", gc);
JsonArray memory = new JsonArray();
for (MemoryPoolMXBean pool : ManagementFactory.getMemoryPoolMXBeans()) {
JsonObject beanJson = new JsonObject();
beanJson.addProperty("name", pool.getName());
beanJson.addProperty("type", pool.getType().toString());
beanJson.add("usage", addUsage(pool.getUsage()));
beanJson.add("peak", addUsage(pool.getPeakUsage()));
beanJson.add("collection", addUsage(pool.getCollectionUsage()));
beanJson.addProperty("managers", Arrays.stream(pool.getMemoryManagerNames()).collect(Collectors.joining(", ")));
memory.add(beanJson);
}
metrics.add("memory", memory);
json.add("metrics", metrics);
return json;
}
private static final JsonObject addUsage(MemoryUsage usage) {
JsonObject json = new JsonObject();
if (usage != null) {
json.addProperty("init", usage.getInit());
json.addProperty("used", usage.getUsed());
json.addProperty("committed", usage.getCommitted());
json.addProperty("max", usage.getMax() < 0 ? usage.getUsed() : usage.getMax());
}
return json;
}
private static JsonElement libs;
/*
* Going through all the libs can be slow, and they never change,
* so cache them.
*/
private static JsonElement getLibs(HttpServletRequest req) {
JsonElement json = libs;
if (json == null) {
try {
List<JarFile> libraries = new ArrayList<>();
String libPath = req.getServletContext().getRealPath("/WEB-INF/lib");
// in jetty this is not possible.
if (libPath != null) {
File[] files = new File(libPath).listFiles();
if (files != null) {
for (File file : files) {
if (file.getName().endsWith(".jar")) {
libraries.add(new JarFile(file));
}
}
}
}
json = getBuilder().view(libraries);
libs = json;
} catch (IOException e) {
throw new Error(e);
}
}
return json;
}
@PUT
@Path("/full-gc")
public JsonObject fullGC(@Context HttpServletRequest request) {
accessControl(Group.managers());
System.gc();
return info(request, false);
}
@GET
@Path("thread-dump")
@Produces(MediaType.APPLICATION_JSON)
public JsonObject threadDump() {
accessControl(Group.managers());
JsonObject json = new JsonObject();
JsonArray array = new JsonArray();
int total = 0;
Map<Thread, StackTraceElement[]> threads = Thread.getAllStackTraces();
for (State state : State.values()) {
json.addProperty(state.name(), 0);
}
for (Entry<Thread, StackTraceElement[]> entry : threads.entrySet()) {
JsonObject thread = new JsonObject();
String state = entry.getKey().getState().name();
thread.addProperty("name", entry.getKey().toString());
thread.addProperty("state", state);
StringBuilder builder = new StringBuilder();
for (StackTraceElement element : entry.getValue()) {
builder.append(element);
builder.append("\n");
}
thread.addProperty("id", entry.getKey().getId());
thread.addProperty("stacktrace", builder.toString());
array.add(thread);
json.addProperty(state, json.get(state).getAsInt() + 1);
total++;
}
json.addProperty("totalThreads", total);
json.add("threads", array);
return json;
}
@GET
@Path("healthcheck")
@Produces(MediaType.APPLICATION_JSON)
public JsonArray healthChecks() {
accessControl(Group.managers());
JsonArray json = new JsonArray();
for (Healthcheck check : healthchecks) {
JsonObject obj = new JsonObject();
obj.addProperty("name", check.getName());
Stopwatch stop = Stopwatch.createStarted();
Result result = check.execute();
stop.stop();
obj.add("result", result.toJson());
obj.addProperty("time", stop.elapsed(TimeUnit.MILLISECONDS));
json.add(obj);
}
return json;
}
private static final Collection<Healthcheck> healthchecks = new ConcurrentLinkedQueue<>();
public static void registerHealthcheck(Healthcheck healthcheck) {
healthchecks.add(Objects.requireNonNull(healthcheck));
}
@GET
@Path("/jmx")
@Produces(MediaType.APPLICATION_JSON)
public JsonObject getJmxInfo() {
accessControl(Group.managers());
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
Set<ObjectInstance> objects = mbs.queryMBeans(null, null);
JsonObject json = new JsonObject();
for (ObjectInstance instance : objects) {
String domain = instance.getObjectName().getDomain();
if (!json.has(domain)) {
json.add(domain, new JsonArray());
}
json.get(domain).getAsJsonArray().add(getBuilder().view(instance.getObjectName()));
}
return json;
}
@GET
@Path("/modules")
@Produces(MediaType.APPLICATION_JSON)
public JsonElement moduleInfo() {
return view(FenixFramework.getProject().getProjects(), "modules");
}
}