Skip to content

Commit f64fda0

Browse files
authored
HADOOP-18055. Async Profiler endpoint for Hadoop daemons (#3824)
Reviewed-by: Akira Ajisaka <aajisaka@apache.org>
1 parent da0a6ba commit f64fda0

File tree

11 files changed

+868
-5
lines changed

11 files changed

+868
-5
lines changed

hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/http/HttpServer2.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.net.MalformedURLException;
2828
import java.net.URI;
2929
import java.net.URL;
30+
import java.nio.file.Files;
3031
import java.nio.file.Path;
3132
import java.nio.file.Paths;
3233
import java.util.List;
@@ -771,6 +772,26 @@ private void initializeWebServer(String name, String hostName,
771772

772773
addDefaultServlets();
773774
addPrometheusServlet(conf);
775+
addAsyncProfilerServlet(contexts);
776+
}
777+
778+
private void addAsyncProfilerServlet(ContextHandlerCollection contexts) throws IOException {
779+
final String asyncProfilerHome = ProfileServlet.getAsyncProfilerHome();
780+
if (asyncProfilerHome != null && !asyncProfilerHome.trim().isEmpty()) {
781+
addServlet("prof", "/prof", ProfileServlet.class);
782+
Path tmpDir = Paths.get(ProfileServlet.OUTPUT_DIR);
783+
if (Files.notExists(tmpDir)) {
784+
Files.createDirectories(tmpDir);
785+
}
786+
ServletContextHandler genCtx = new ServletContextHandler(contexts, "/prof-output-hadoop");
787+
genCtx.addServlet(ProfileOutputServlet.class, "/*");
788+
genCtx.setResourceBase(tmpDir.toAbsolutePath().toString());
789+
genCtx.setDisplayName("prof-output-hadoop");
790+
} else {
791+
addServlet("prof", "/prof", ProfilerDisabledServlet.class);
792+
LOG.info("ASYNC_PROFILER_HOME environment variable and async.profiler.home system property "
793+
+ "not specified. Disabling /prof endpoint.");
794+
}
774795
}
775796

776797
private void addPrometheusServlet(Configuration conf) {
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package org.apache.hadoop.http;
20+
21+
import java.io.File;
22+
import java.io.IOException;
23+
import java.util.regex.Pattern;
24+
import javax.servlet.ServletException;
25+
import javax.servlet.http.HttpServletRequest;
26+
import javax.servlet.http.HttpServletResponse;
27+
28+
import org.eclipse.jetty.servlet.DefaultServlet;
29+
import org.slf4j.Logger;
30+
import org.slf4j.LoggerFactory;
31+
32+
import org.apache.hadoop.classification.InterfaceAudience;
33+
34+
/**
35+
* Servlet to serve files generated by {@link ProfileServlet}.
36+
*/
37+
@InterfaceAudience.Private
38+
public class ProfileOutputServlet extends DefaultServlet {
39+
40+
private static final long serialVersionUID = 1L;
41+
42+
private static final Logger LOG = LoggerFactory.getLogger(ProfileOutputServlet.class);
43+
// default refresh period 2 sec
44+
private static final int REFRESH_PERIOD = 2;
45+
// Alphanumeric characters, plus percent (url-encoding), equals, ampersand, dot and hyphen
46+
private static final Pattern ALPHA_NUMERIC = Pattern.compile("[a-zA-Z0-9%=&.\\-]*");
47+
48+
@Override
49+
protected void doGet(final HttpServletRequest req, final HttpServletResponse resp)
50+
throws ServletException, IOException {
51+
if (!HttpServer2.isInstrumentationAccessAllowed(getServletContext(), req, resp)) {
52+
resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
53+
ProfileServlet.setResponseHeader(resp);
54+
resp.getWriter().write("Unauthorized: Instrumentation access is not allowed!");
55+
return;
56+
}
57+
58+
String absoluteDiskPath = getServletContext().getRealPath(req.getPathInfo());
59+
File requestedFile = new File(absoluteDiskPath);
60+
// async-profiler version 1.4 writes 'Started [cpu] profiling' to output file when profiler is
61+
// running which gets replaced by final output. If final output is not ready yet, the file size
62+
// will be <100 bytes (in all modes).
63+
if (requestedFile.length() < 100) {
64+
LOG.info("{} is incomplete. Sending auto-refresh header.", requestedFile);
65+
String refreshUrl = req.getRequestURI();
66+
// Rebuild the query string (if we have one)
67+
if (req.getQueryString() != null) {
68+
refreshUrl += "?" + sanitize(req.getQueryString());
69+
}
70+
ProfileServlet.setResponseHeader(resp);
71+
resp.setHeader("Refresh", REFRESH_PERIOD + ";" + refreshUrl);
72+
resp.getWriter().write("This page will be auto-refreshed every " + REFRESH_PERIOD
73+
+ " seconds until the output file is ready. Redirecting to " + refreshUrl);
74+
} else {
75+
super.doGet(req, resp);
76+
}
77+
}
78+
79+
static String sanitize(String input) {
80+
// Basic test to try to avoid any XSS attacks or HTML content showing up.
81+
// Duplicates HtmlQuoting a little, but avoid destroying ampersand.
82+
if (ALPHA_NUMERIC.matcher(input).matches()) {
83+
return input;
84+
}
85+
throw new RuntimeException("Non-alphanumeric data found in input, aborting.");
86+
}
87+
}

0 commit comments

Comments
 (0)