|
| 1 | +From 3f32872b0c49ed7125803fd599e7f1318ce8c00a Mon Sep 17 00:00:00 2001 |
| 2 | +From: Siegfried Weber < [email protected]> |
| 3 | +Date: Tue, 6 Feb 2024 16:10:54 +0100 |
| 4 | +Subject: HBASE-28242: Updates async-profiler support |
| 5 | + |
| 6 | +--- |
| 7 | + .../hadoop/hbase/http/ProfileServlet.java | 205 +++++++++++------- |
| 8 | + 1 file changed, 121 insertions(+), 84 deletions(-) |
| 9 | + |
| 10 | +diff --git a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/ProfileServlet.java b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/ProfileServlet.java |
| 11 | +index e92b4f9ae0..521ad7c380 100644 |
| 12 | +--- a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/ProfileServlet.java |
| 13 | ++++ b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/ProfileServlet.java |
| 14 | +@@ -28,9 +28,11 @@ import java.util.concurrent.TimeUnit; |
| 15 | + import java.util.concurrent.atomic.AtomicInteger; |
| 16 | + import java.util.concurrent.locks.Lock; |
| 17 | + import java.util.concurrent.locks.ReentrantLock; |
| 18 | ++ |
| 19 | + import javax.servlet.http.HttpServlet; |
| 20 | + import javax.servlet.http.HttpServletRequest; |
| 21 | + import javax.servlet.http.HttpServletResponse; |
| 22 | ++ |
| 23 | + import org.apache.hadoop.hbase.util.ProcessUtils; |
| 24 | + import org.apache.yetus.audience.InterfaceAudience; |
| 25 | + import org.slf4j.Logger; |
| 26 | +@@ -40,23 +42,60 @@ import org.apache.hbase.thirdparty.com.google.common.base.Joiner; |
| 27 | + |
| 28 | + /** |
| 29 | + * Servlet that runs async-profiler as web-endpoint. Following options from async-profiler can be |
| 30 | +- * specified as query paramater. // -e event profiling event: cpu|alloc|lock|cache-misses etc. // -d |
| 31 | +- * duration run profiling for 'duration' seconds (integer) // -i interval sampling interval in |
| 32 | +- * nanoseconds (long) // -j jstackdepth maximum Java stack depth (integer) // -b bufsize frame |
| 33 | +- * buffer size (long) // -t profile different threads separately // -s simple class names instead of |
| 34 | +- * FQN // -o fmt[,fmt...] output format: summary|traces|flat|collapsed|svg|tree|jfr|html // --width |
| 35 | +- * px SVG width pixels (integer) // --height px SVG frame height pixels (integer) // --minwidth px |
| 36 | +- * skip frames smaller than px (double) // --reverse generate stack-reversed FlameGraph / Call tree |
| 37 | +- * Example: - To collect 30 second CPU profile of current process (returns FlameGraph svg) curl |
| 38 | +- * "http://localhost:10002/prof" - To collect 1 minute CPU profile of current process and output in |
| 39 | +- * tree format (html) curl "http://localhost:10002/prof?output=tree&duration=60" - To collect 30 |
| 40 | +- * second heap allocation profile of current process (returns FlameGraph svg) curl |
| 41 | +- * "http://localhost:10002/prof?event=alloc" - To collect lock contention profile of current process |
| 42 | +- * (returns FlameGraph svg) curl "http://localhost:10002/prof?event=lock" Following event types are |
| 43 | +- * supported (default is 'cpu') (NOTE: not all OS'es support all events) // Perf events: // cpu // |
| 44 | +- * page-faults // context-switches // cycles // instructions // cache-references // cache-misses // |
| 45 | +- * branches // branch-misses // bus-cycles // L1-dcache-load-misses // LLC-load-misses // |
| 46 | +- * dTLB-load-misses // mem:breakpoint // trace:tracepoint // Java events: // alloc // lock |
| 47 | ++ * specified as query parameter. |
| 48 | ++ * <ul> |
| 49 | ++ * <li>-e event profiling event: cpu|alloc|lock|cache-misses etc.</li> |
| 50 | ++ * <li>-d duration run profiling for 'duration' seconds (integer), default 10s</li> |
| 51 | ++ * <li>-i interval sampling interval in nanoseconds (long), default 10ms</li> |
| 52 | ++ * <li>-j jstackdepth maximum Java stack depth (integer), default 2048</li> |
| 53 | ++ * <li>-t profile different threads separately</li> |
| 54 | ++ * <li>-s simple class names instead of FQN</li> |
| 55 | ++ * <li>-g print method signatures</li> |
| 56 | ++ * <li>-a annotate Java methods</li> |
| 57 | ++ * <li>-l prepend library names</li> |
| 58 | ++ * <li>-o fmt output format: flat|traces|collapsed|flamegraph|tree|jfr</li> |
| 59 | ++ * <li>--minwidth pct skip frames smaller than pct% (double)</li> |
| 60 | ++ * <li>--reverse generate stack-reversed FlameGraph / Call tree</li> |
| 61 | ++ * </ul> |
| 62 | ++ * Example: |
| 63 | ++ * <ul> |
| 64 | ++ * <li>To collect 30 second CPU profile of current process (returns FlameGraph svg): |
| 65 | ++ * {@code curl http://localhost:10002/prof"}</li> |
| 66 | ++ * <li>To collect 1 minute CPU profile of current process and output in tree format (html) |
| 67 | ++ * {@code curl "http://localhost:10002/prof?output=tree&duration=60"}</li> |
| 68 | ++ * <li>To collect 30 second heap allocation profile of current process (returns FlameGraph): |
| 69 | ++ * {@code curl "http://localhost:10002/prof?event=alloc"}</li> |
| 70 | ++ * <li>To collect lock contention profile of current process (returns FlameGraph): |
| 71 | ++ * {@code curl "http://localhost:10002/prof?event=lock"}</li> |
| 72 | ++ * </ul> |
| 73 | ++ * Following event types are supported (default is 'cpu') (NOTE: not all OS'es support all |
| 74 | ++ * events).<br/> |
| 75 | ++ * Basic events: |
| 76 | ++ * <ul> |
| 77 | ++ * <li>cpu</li> |
| 78 | ++ * <li>alloc</li> |
| 79 | ++ * <li>lock</li> |
| 80 | ++ * <li>wall</li> |
| 81 | ++ * <li>itimer</li> |
| 82 | ++ * </ul> |
| 83 | ++ * Perf events: |
| 84 | ++ * <ul> |
| 85 | ++ * <li>L1-dcache-load-misses</li> |
| 86 | ++ * <li>LLC-load-misses</li> |
| 87 | ++ * <li>branch-instructions</li> |
| 88 | ++ * <li>branch-misses</li> |
| 89 | ++ * <li>bus-cycles</li> |
| 90 | ++ * <li>cache-misses</li> |
| 91 | ++ * <li>cache-references</li> |
| 92 | ++ * <li>context-switches</li> |
| 93 | ++ * <li>cpu</li> |
| 94 | ++ * <li>cycles</li> |
| 95 | ++ * <li>dTLB-load-misses</li> |
| 96 | ++ * <li>instructions</li> |
| 97 | ++ * <li>mem:breakpoint</li> |
| 98 | ++ * <li>page-faults</li> |
| 99 | ++ * <li>trace:tracepoint</li> |
| 100 | ++ * </ul> |
| 101 | + */ |
| 102 | + @InterfaceAudience.Private |
| 103 | + public class ProfileServlet extends HttpServlet { |
| 104 | +@@ -81,19 +120,20 @@ public class ProfileServlet extends HttpServlet { |
| 105 | + WALL("wall"), |
| 106 | + ALLOC("alloc"), |
| 107 | + LOCK("lock"), |
| 108 | +- PAGE_FAULTS("page-faults"), |
| 109 | ++ ITIMER("itimer"), |
| 110 | ++ BRANCH_INSTRUCTIONS("branch-instructions"), |
| 111 | ++ BRANCH_MISSES("branch-misses"), |
| 112 | ++ BUS_CYCLES("bus-cycles"), |
| 113 | ++ CACHE_MISSES("cache-misses"), |
| 114 | ++ CACHE_REFERENCES("cache-references"), |
| 115 | + CONTEXT_SWITCHES("context-switches"), |
| 116 | + CYCLES("cycles"), |
| 117 | ++ DTLB_LOAD_MISSES("dTLB-load-misses"), |
| 118 | + INSTRUCTIONS("instructions"), |
| 119 | +- CACHE_REFERENCES("cache-references"), |
| 120 | +- CACHE_MISSES("cache-misses"), |
| 121 | +- BRANCHES("branches"), |
| 122 | +- BRANCH_MISSES("branch-misses"), |
| 123 | +- BUS_CYCLES("bus-cycles"), |
| 124 | + L1_DCACHE_LOAD_MISSES("L1-dcache-load-misses"), |
| 125 | + LLC_LOAD_MISSES("LLC-load-misses"), |
| 126 | +- DTLB_LOAD_MISSES("dTLB-load-misses"), |
| 127 | + MEM_BREAKPOINT("mem:breakpoint"), |
| 128 | ++ PAGE_FAULTS("page-faults"), |
| 129 | + TRACE_TRACEPOINT("trace:tracepoint"),; |
| 130 | + |
| 131 | + private final String internalName; |
| 132 | +@@ -102,11 +142,11 @@ public class ProfileServlet extends HttpServlet { |
| 133 | + this.internalName = internalName; |
| 134 | + } |
| 135 | + |
| 136 | +- public String getInternalName() { |
| 137 | ++ String getInternalName() { |
| 138 | + return internalName; |
| 139 | + } |
| 140 | + |
| 141 | +- public static Event fromInternalName(final String name) { |
| 142 | ++ static Event fromInternalName(final String name) { |
| 143 | + for (Event event : values()) { |
| 144 | + if (event.getInternalName().equalsIgnoreCase(name)) { |
| 145 | + return event; |
| 146 | +@@ -117,30 +157,26 @@ public class ProfileServlet extends HttpServlet { |
| 147 | + } |
| 148 | + } |
| 149 | + |
| 150 | +- enum Output { |
| 151 | +- SUMMARY, |
| 152 | +- TRACES, |
| 153 | +- FLAT, |
| 154 | ++ private enum Output { |
| 155 | + COLLAPSED, |
| 156 | +- // No SVG in 2.x asyncprofiler. |
| 157 | +- SVG, |
| 158 | +- TREE, |
| 159 | ++ FLAMEGRAPH, |
| 160 | ++ FLAT, |
| 161 | + JFR, |
| 162 | +- // In 2.x asyncprofiler, this is how you get flamegraphs. |
| 163 | +- HTML |
| 164 | ++ TRACES, |
| 165 | ++ TREE |
| 166 | + } |
| 167 | + |
| 168 | + @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "SE_TRANSIENT_FIELD_NOT_RESTORED", |
| 169 | + justification = "This class is never serialized nor restored.") |
| 170 | +- private transient Lock profilerLock = new ReentrantLock(); |
| 171 | ++ private final transient Lock profilerLock = new ReentrantLock(); |
| 172 | + private transient volatile Process process; |
| 173 | +- private String asyncProfilerHome; |
| 174 | ++ private final String asyncProfilerHome; |
| 175 | + private Integer pid; |
| 176 | + |
| 177 | + public ProfileServlet() { |
| 178 | + this.asyncProfilerHome = getAsyncProfilerHome(); |
| 179 | + this.pid = ProcessUtils.getPid(); |
| 180 | +- LOG.info("Servlet process PID: " + pid + " asyncProfilerHome: " + asyncProfilerHome); |
| 181 | ++ LOG.info("Servlet process PID: {} asyncProfilerHome: {}", pid, asyncProfilerHome); |
| 182 | + } |
| 183 | + |
| 184 | + @Override |
| 185 | +@@ -159,9 +195,9 @@ public class ProfileServlet extends HttpServlet { |
| 186 | + setResponseHeader(resp); |
| 187 | + resp.getWriter() |
| 188 | + .write("ASYNC_PROFILER_HOME env is not set.\n\n" |
| 189 | +- + "Please ensure the prerequsites for the Profiler Servlet have been installed and the\n" |
| 190 | ++ + "Please ensure the prerequisites for the Profiler Servlet have been installed and the\n" |
| 191 | + + "environment is properly configured. For more information please see\n" |
| 192 | +- + "http://hbase.apache.org/book.html#profiler\n"); |
| 193 | ++ + "https://hbase.apache.org/book.html#profiler\n"); |
| 194 | + return; |
| 195 | + } |
| 196 | + |
| 197 | +@@ -177,18 +213,18 @@ public class ProfileServlet extends HttpServlet { |
| 198 | + return; |
| 199 | + } |
| 200 | + |
| 201 | +- final int duration = getInteger(req, "duration", DEFAULT_DURATION_SECONDS); |
| 202 | +- final Output output = getOutput(req); |
| 203 | +- final Event event = getEvent(req); |
| 204 | +- final Long interval = getLong(req, "interval"); |
| 205 | +- final Integer jstackDepth = getInteger(req, "jstackdepth", null); |
| 206 | +- final Long bufsize = getLong(req, "bufsize"); |
| 207 | +- final boolean thread = req.getParameterMap().containsKey("thread"); |
| 208 | +- final boolean simple = req.getParameterMap().containsKey("simple"); |
| 209 | +- final Integer width = getInteger(req, "width", null); |
| 210 | +- final Integer height = getInteger(req, "height", null); |
| 211 | +- final Double minwidth = getMinWidth(req); |
| 212 | +- final boolean reverse = req.getParameterMap().containsKey("reverse"); |
| 213 | ++ Event event = getEvent(req); |
| 214 | ++ int duration = getInteger(req, "duration", DEFAULT_DURATION_SECONDS); |
| 215 | ++ Long interval = getLong(req, "interval"); |
| 216 | ++ Integer jstackDepth = getInteger(req, "jstackdepth", null); |
| 217 | ++ boolean thread = req.getParameterMap().containsKey("thread"); |
| 218 | ++ boolean simple = req.getParameterMap().containsKey("simple"); |
| 219 | ++ boolean signature = req.getParameterMap().containsKey("signature"); |
| 220 | ++ boolean annotate = req.getParameterMap().containsKey("annotate"); |
| 221 | ++ boolean prependLib = req.getParameterMap().containsKey("prependlib"); |
| 222 | ++ Output output = getOutput(req); |
| 223 | ++ Double minwidth = getMinWidth(req); |
| 224 | ++ boolean reverse = req.getParameterMap().containsKey("reverse"); |
| 225 | + |
| 226 | + if (process == null || !process.isAlive()) { |
| 227 | + try { |
| 228 | +@@ -208,11 +244,7 @@ public class ProfileServlet extends HttpServlet { |
| 229 | + cmd.add("-e"); |
| 230 | + cmd.add(event.getInternalName()); |
| 231 | + cmd.add("-d"); |
| 232 | +- cmd.add("" + duration); |
| 233 | +- cmd.add("-o"); |
| 234 | +- cmd.add(output.name().toLowerCase()); |
| 235 | +- cmd.add("-f"); |
| 236 | +- cmd.add(outputFile.getAbsolutePath()); |
| 237 | ++ cmd.add(String.valueOf(duration)); |
| 238 | + if (interval != null) { |
| 239 | + cmd.add("-i"); |
| 240 | + cmd.add(interval.toString()); |
| 241 | +@@ -221,24 +253,25 @@ public class ProfileServlet extends HttpServlet { |
| 242 | + cmd.add("-j"); |
| 243 | + cmd.add(jstackDepth.toString()); |
| 244 | + } |
| 245 | +- if (bufsize != null) { |
| 246 | +- cmd.add("-b"); |
| 247 | +- cmd.add(bufsize.toString()); |
| 248 | +- } |
| 249 | + if (thread) { |
| 250 | + cmd.add("-t"); |
| 251 | + } |
| 252 | + if (simple) { |
| 253 | + cmd.add("-s"); |
| 254 | + } |
| 255 | +- if (width != null) { |
| 256 | +- cmd.add("--width"); |
| 257 | +- cmd.add(width.toString()); |
| 258 | ++ if (signature) { |
| 259 | ++ cmd.add("-g"); |
| 260 | + } |
| 261 | +- if (height != null) { |
| 262 | +- cmd.add("--height"); |
| 263 | +- cmd.add(height.toString()); |
| 264 | ++ if (annotate) { |
| 265 | ++ cmd.add("-a"); |
| 266 | + } |
| 267 | ++ if (prependLib) { |
| 268 | ++ cmd.add("-l"); |
| 269 | ++ } |
| 270 | ++ cmd.add("-o"); |
| 271 | ++ cmd.add(output.name().toLowerCase()); |
| 272 | ++ cmd.add("-f"); |
| 273 | ++ cmd.add(outputFile.getAbsolutePath()); |
| 274 | + if (minwidth != null) { |
| 275 | + cmd.add("--minwidth"); |
| 276 | + cmd.add(minwidth.toString()); |
| 277 | +@@ -246,6 +279,7 @@ public class ProfileServlet extends HttpServlet { |
| 278 | + if (reverse) { |
| 279 | + cmd.add("--reverse"); |
| 280 | + } |
| 281 | ++ |
| 282 | + cmd.add(pid.toString()); |
| 283 | + process = ProcessUtils.runCmdAsync(cmd); |
| 284 | + |
| 285 | +@@ -256,7 +290,10 @@ public class ProfileServlet extends HttpServlet { |
| 286 | + resp.getWriter() |
| 287 | + .write("Started [" + event.getInternalName() |
| 288 | + + "] profiling. This page will automatically redirect to " + relativeUrl + " after " |
| 289 | +- + duration + " seconds.\n\nCommand:\n" + Joiner.on(" ").join(cmd)); |
| 290 | ++ + duration + " seconds. " |
| 291 | ++ + "If empty diagram and Linux 4.6+, see 'Basic Usage' section on the Async " |
| 292 | ++ + "Profiler Home Page, https://github.com/jvm-profiling-tools/async-profiler." |
| 293 | ++ + "\n\nCommand:\n" + Joiner.on(" ").join(cmd)); |
| 294 | + |
| 295 | + // to avoid auto-refresh by ProfileOutputServlet, refreshDelay can be specified |
| 296 | + // via url param |
| 297 | +@@ -274,8 +311,9 @@ public class ProfileServlet extends HttpServlet { |
| 298 | + resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); |
| 299 | + resp.getWriter() |
| 300 | + .write("Unable to acquire lock. Another instance of profiler might be running."); |
| 301 | +- LOG.warn("Unable to acquire lock in " + lockTimeoutSecs |
| 302 | +- + " seconds. Another instance of profiler might be running."); |
| 303 | ++ LOG.warn( |
| 304 | ++ "Unable to acquire lock in {} seconds. Another instance of profiler might be running.", |
| 305 | ++ lockTimeoutSecs); |
| 306 | + } |
| 307 | + } catch (InterruptedException e) { |
| 308 | + LOG.warn("Interrupted while acquiring profile lock.", e); |
| 309 | +@@ -288,9 +326,9 @@ public class ProfileServlet extends HttpServlet { |
| 310 | + } |
| 311 | + } |
| 312 | + |
| 313 | +- private Integer getInteger(final HttpServletRequest req, final String param, |
| 314 | ++ private static Integer getInteger(final HttpServletRequest req, final String param, |
| 315 | + final Integer defaultValue) { |
| 316 | +- final String value = req.getParameter(param); |
| 317 | ++ String value = req.getParameter(param); |
| 318 | + if (value != null) { |
| 319 | + try { |
| 320 | + return Integer.valueOf(value); |
| 321 | +@@ -301,8 +339,8 @@ public class ProfileServlet extends HttpServlet { |
| 322 | + return defaultValue; |
| 323 | + } |
| 324 | + |
| 325 | +- private Long getLong(final HttpServletRequest req, final String param) { |
| 326 | +- final String value = req.getParameter(param); |
| 327 | ++ private static Long getLong(final HttpServletRequest req, final String param) { |
| 328 | ++ String value = req.getParameter(param); |
| 329 | + if (value != null) { |
| 330 | + try { |
| 331 | + return Long.valueOf(value); |
| 332 | +@@ -313,8 +351,8 @@ public class ProfileServlet extends HttpServlet { |
| 333 | + return null; |
| 334 | + } |
| 335 | + |
| 336 | +- private Double getMinWidth(final HttpServletRequest req) { |
| 337 | +- final String value = req.getParameter("minwidth"); |
| 338 | ++ private static Double getMinWidth(final HttpServletRequest req) { |
| 339 | ++ String value = req.getParameter("minwidth"); |
| 340 | + if (value != null) { |
| 341 | + try { |
| 342 | + return Double.valueOf(value); |
| 343 | +@@ -325,8 +363,8 @@ public class ProfileServlet extends HttpServlet { |
| 344 | + return null; |
| 345 | + } |
| 346 | + |
| 347 | +- private Event getEvent(final HttpServletRequest req) { |
| 348 | +- final String eventArg = req.getParameter("event"); |
| 349 | ++ private static Event getEvent(final HttpServletRequest req) { |
| 350 | ++ String eventArg = req.getParameter("event"); |
| 351 | + if (eventArg != null) { |
| 352 | + Event event = Event.fromInternalName(eventArg); |
| 353 | + return event == null ? Event.CPU : event; |
| 354 | +@@ -334,16 +372,16 @@ public class ProfileServlet extends HttpServlet { |
| 355 | + return Event.CPU; |
| 356 | + } |
| 357 | + |
| 358 | +- private Output getOutput(final HttpServletRequest req) { |
| 359 | +- final String outputArg = req.getParameter("output"); |
| 360 | ++ private static Output getOutput(final HttpServletRequest req) { |
| 361 | ++ String outputArg = req.getParameter("output"); |
| 362 | + if (req.getParameter("output") != null) { |
| 363 | + try { |
| 364 | + return Output.valueOf(outputArg.trim().toUpperCase()); |
| 365 | + } catch (IllegalArgumentException e) { |
| 366 | +- return Output.HTML; |
| 367 | ++ return Output.FLAMEGRAPH; |
| 368 | + } |
| 369 | + } |
| 370 | +- return Output.HTML; |
| 371 | ++ return Output.FLAMEGRAPH; |
| 372 | + } |
| 373 | + |
| 374 | + static void setResponseHeader(final HttpServletResponse response) { |
| 375 | +@@ -375,8 +413,7 @@ public class ProfileServlet extends HttpServlet { |
| 376 | + .write("The profiler servlet was disabled at startup.\n\n" |
| 377 | + + "Please ensure the prerequisites for the Profiler Servlet have been installed and the\n" |
| 378 | + + "environment is properly configured. For more information please see\n" |
| 379 | +- + "http://hbase.apache.org/book.html#profiler\n"); |
| 380 | +- return; |
| 381 | ++ + "https://hbase.apache.org/book.html#profiler\n"); |
| 382 | + } |
| 383 | + |
| 384 | + } |
0 commit comments