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 &mdash;
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} &mdash; 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> &mdash; 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}