001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.rest.server.management; 018 019import static org.apache.juneau.commons.utils.Shorts.*; 020 021import java.io.*; 022import java.lang.management.*; 023import java.nio.file.*; 024 025import org.apache.juneau.rest.server.*; 026 027/** 028 * Shared worker for the diagnostic dump endpoints ({@code /threaddump}, {@code /heapdump}). 029 * 030 * <p> 031 * Both the {@code *Mixin} and {@code *Resource} flavors delegate here so the two forms cannot drift — 032 * the same shared-worker pattern the health and metrics surfaces use. 033 * 034 * <p> 035 * <b>Thread dump</b> is rendered from the JDK-standard {@link ThreadMXBean} (always available). 036 * <b>Heap dump</b> is produced via the HotSpot {@code HotSpotDiagnosticMXBean#dumpHeap(String,boolean)} 037 * method, invoked <b>reflectively</b> so this module carries no compile-time dependency on 038 * {@code com.sun.management} — on a non-HotSpot JVM the heap dump degrades cleanly (the endpoints 039 * surface that as HTTP 501). 040 * 041 * <p> 042 * Exposure is governed by {@link DumpsSettings} resolved from the bean store; both dumps are 043 * <b>deny-by-default</b> — see {@link DumpsSettings}. 044 * 045 * <h5 class='section'>See Also:</h5><ul> 046 * <li class='jc'>{@link DumpsSettings} 047 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/ManagementSurface">Management Surface (Actuator-style endpoints)</a> 048 * </ul> 049 * 050 * @since 10.0.0 051 */ 052public class DumpsManager { 053 054 /** 055 * Resolves the {@link DumpsSettings} from the host context's bean store, falling back to the deny-all default. 056 * 057 * @param context The REST context whose bean store is searched. May be <jk>null</jk>. 058 * @return The registered settings, or {@link DumpsSettings#DEFAULT} when none is registered. 059 */ 060 @SuppressWarnings({ 061 "resource" // The bean store is owned by the RestContext; this adapter only borrows beans and must not close it. 062 }) 063 public DumpsSettings resolveSettings(RestContext context) { 064 if (context == null) 065 return DumpsSettings.DEFAULT; 066 return context.getBeanStore().getBean(DumpsSettings.class).orElse(DumpsSettings.DEFAULT); 067 } 068 069 /** 070 * Renders a full thread dump from the {@link ThreadMXBean}. 071 * 072 * @return A human-readable thread dump (never <jk>null</jk> or empty). 073 */ 074 public String threadDump() { 075 var tmx = ManagementFactory.getThreadMXBean(); 076 var infos = tmx.dumpAllThreads(true, true); 077 var sb = new StringBuilder(); 078 for (var info : infos) 079 sb.append(info.toString()); 080 return sb.toString(); 081 } 082 083 /** 084 * Writes a heap dump (hprof) to the supplied file via the HotSpot {@code HotSpotDiagnosticMXBean}. 085 * 086 * <p> 087 * Invoked reflectively to avoid a compile-time dependency on {@code com.sun.management}. The target file 088 * <b>must not already exist</b> (a {@code dumpHeap} requirement). 089 * 090 * @param target The hprof output file. Must not be <jk>null</jk> (and must not pre-exist). 091 * @param live If <jk>true</jk>, dump only live (reachable) objects. 092 * @return <jk>true</jk> if the dump was written; <jk>false</jk> if this JVM has no HotSpot heap-dump support 093 * (the endpoints surface that as HTTP 501). 094 */ 095 public boolean heapDump(File target, boolean live) { 096 try { 097 var diagClass = Class.forName("com.sun.management.HotSpotDiagnosticMXBean"); 098 @SuppressWarnings({ 099 "unchecked" // diagClass is the reflectively loaded HotSpotDiagnosticMXBean, which is a PlatformManagedObject, so the Class<? extends PlatformManagedObject> narrowing is valid 100 }) 101 var bean = ManagementFactory.getPlatformMXBean((Class<? extends PlatformManagedObject>) diagClass); 102 if (bean == null) 103 return false; // HTT: only on a JVM exposing the class but no platform bean — not reproducible on HotSpot CI. 104 var m = diagClass.getMethod("dumpHeap", String.class, boolean.class); 105 m.invoke(bean, target.getAbsolutePath(), live); 106 return true; 107 } catch (ClassNotFoundException | NoSuchMethodException e) { 108 return false; // HTT: non-HotSpot JVM with no heap-dump support — not reproducible on HotSpot CI (caller degrades to 501). 109 } catch (ReflectiveOperationException e) { 110 throw rex(e, "Failed to write heap dump to %s", target.getAbsolutePath()); 111 } 112 } 113 114 /** 115 * Produces a heap dump and returns it as a self-deleting input stream. 116 * 117 * <p> 118 * Writes the hprof to a fresh temp file (the {@code dumpHeap} no-pre-existing-file requirement is satisfied 119 * by deleting the just-created temp file before the dump), then returns a stream over it that deletes the 120 * backing file when closed. 121 * 122 * @param live If <jk>true</jk>, dump only live (reachable) objects. 123 * @return A stream over the hprof file, or <jk>null</jk> if this JVM has no HotSpot heap-dump support. 124 * @throws IOException If the temp file could not be created or opened. 125 */ 126 @SuppressWarnings({ 127 "java:S5443" // Heap dump is an explicitly opt-in, admin-gated diagnostic; the random-named temp file is created then immediately reclaimed for dumpHeap. 128 }) 129 public InputStream heapDumpStream(boolean live) throws IOException { 130 var f = File.createTempFile("juneau-heapdump-", ".hprof"); 131 Files.delete(f.toPath()); // dumpHeap requires the target file to not already exist. 132 if (! heapDump(f, live)) { // HTT: heapDump only returns false on a non-HotSpot JVM — not reproducible on HotSpot CI. 133 Files.deleteIfExists(f.toPath()); 134 return null; 135 } 136 return new FileInputStream(f) { 137 @Override 138 public void close() throws IOException { 139 try { 140 super.close(); 141 } finally { 142 Files.deleteIfExists(f.toPath()); 143 } 144 } 145 }; 146 } 147}