-
Notifications
You must be signed in to change notification settings - Fork 9.2k
YARN-11823: add new endpoints for getting jstacks of ApplicationId and NodeManager process #8123
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Hean-Chhinling
wants to merge
38
commits into
apache:trunk
Choose a base branch
from
Hean-Chhinling:trunk_YARN-11823
base: trunk
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
38 commits
Select commit
Hold shift + click to select a range
80814ff
YARN-11823: add new endpoints for getting jstacks of application and …
86a99ec
Adding number of jstack through rest api
3709a54
Fix the integer data type and remove the plain text output to allow e…
c3176f0
Merge branch 'apache:trunk' into trunk_YARN-11823
Hean-Chhinling fee2f9e
Clean get script location and import each module individually
Hean-Chhinling 38c757f
Add Unit-test for the new diagnostic service class
Hean-Chhinling 0157b0f
YARN-11823: fix the script for collection jstack application
Hean-Chhinling 0a3300c
YARN-11823: use shellExecutor remove python script and guard the endp…
Hean-Chhinling 824dfa1
YARN-11823: update handling exceptions, app_id checks and change the …
Hean-Chhinling ff7dde6
YARN-11823: use processHandle to get NM process ID and fixing the Jst…
Hean-Chhinling 7b3ca17
YARN-11823: update to get process from NMcontext
Hean-Chhinling a26987c
YARN-11823: Get the container process id from NodeManager Context
Hean-Chhinling 0a60eb2
YARN-11823: remove unused method and improve code
Hean-Chhinling 62f76d6
YARN-11823: Add unit test for the DiagnosticJStackService
Hean-Chhinling 3f28796
YARN-11823: fix checkstyle and bug issue
Hean-Chhinling cf2253b
YARN-11823: clean up code at getApplicationPID to be more readable
Hean-Chhinling a57c522
YARN-11823: add a working solution without using sudo
Hean-Chhinling 25ac6e1
YARN-11823: fix the absolute path jstack issue
Hean-Chhinling 24d413e
Merge branch 'trunk' into trunk_YARN-11823
Hean-Chhinling 0dd4870
YARN-11823: Clean up code to use ContainerExecutor effectively
Hean-Chhinling 75bcbab
YARN-11823: cleaned up code and to display containerId along with pro…
Hean-Chhinling a455836
YARN-11823: remove catching general exception exceptions and add text…
Hean-Chhinling 3431c95
YARN-11823: check application authorization and put the non java cont…
Hean-Chhinling dd0c140
YARN-11823: fix handling the exception
Hean-Chhinling fdff845
YARN-11823: fix the exception response
Hean-Chhinling 5168e01
YARN-11823: Use commandLine to get the java process and handle except…
Hean-Chhinling 563278a
YARN-11823: fix unit-test after adding security measure
Hean-Chhinling 9f76e75
YARN-11823: Handle the PID no longer exist without exception to not b…
Hean-Chhinling cde856a
YARN-11823: Add a check for AdminACL to run NodeManager Jstack
Hean-Chhinling 86abf94
YARN-11823: Update to match exactly java process and handle exception…
Hean-Chhinling 48f5ad4
YARN-11823: fix checkstyle and bug warning
Hean-Chhinling 391abdd
YARN-11823: Fix checkstyle
Hean-Chhinling 76d9973
YARN-11823: Add documentation for the two new endpoints and remove so…
Hean-Chhinling 563d26e
YARN-11823: Fixed check style problem
Hean-Chhinling a400f5d
YARN-11823: Fixed checkstyle
Hean-Chhinling 3afde29
YARN-11823: Fix checkstyle indentation
Hean-Chhinling 4af1089
YARN-11823: Add a unit-test and display the command
Hean-Chhinling 3d40555
Merge remote-tracking branch 'ling_upstream/trunk_YARN-11823' into tr…
Hean-Chhinling File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
229 changes: 229 additions & 0 deletions
229
...c/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/DiagnosticJStackService.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,229 @@ | ||
| /** * Licensed to the Apache Software Foundation (ASF) under one | ||
| * or more contributor license agreements. See the NOTICE file | ||
| * distributed with this work for additional information | ||
| * regarding copyright ownership. The ASF licenses this file | ||
| * to you under the Apache License, Version 2.0 (the | ||
| * "License"); you may not use this file except in compliance | ||
| * with the License. You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package org.apache.hadoop.yarn.server.nodemanager.webapp; | ||
|
|
||
|
|
||
| import org.apache.hadoop.conf.Configuration; | ||
| import org.apache.hadoop.security.UserGroupInformation; | ||
| import org.apache.hadoop.util.Shell; | ||
| import org.apache.hadoop.yarn.api.records.ApplicationAccessType; | ||
| import org.apache.hadoop.yarn.api.records.ApplicationId; | ||
| import org.apache.hadoop.yarn.api.records.ContainerId; | ||
| import org.apache.hadoop.yarn.exceptions.YarnRuntimeException; | ||
| import org.apache.hadoop.yarn.server.nodemanager.Context; | ||
| import org.apache.hadoop.yarn.server.nodemanager.containermanager.application.Application; | ||
| import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.privileged.PrivilegedOperationExecutor; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
|
|
||
| import javax.servlet.http.HttpServletRequest; | ||
| import java.io.IOException; | ||
| import java.util.Map; | ||
| import java.util.List; | ||
| import java.util.HashMap; | ||
| import java.util.Optional; | ||
| import java.util.Arrays; | ||
|
|
||
| public class DiagnosticJStackService { | ||
|
|
||
| private static final Logger LOG = LoggerFactory.getLogger(DiagnosticJStackService.class); | ||
|
Hean-Chhinling marked this conversation as resolved.
|
||
|
|
||
|
Hean-Chhinling marked this conversation as resolved.
|
||
| private static final String NM_USER = System.getProperty("user.name"); | ||
| private static final String JSTACK_PATH = System.getProperty("java.home") + "/bin/jstack"; | ||
| private final Context context; | ||
| private final Configuration conf; | ||
|
|
||
| public DiagnosticJStackService(Context context) { | ||
| this.context = context; | ||
| this.conf = context.getConf(); | ||
| } | ||
|
|
||
| public String collectNodeThreadDump(int numberOfJStack, HttpServletRequest req) | ||
| throws IOException { | ||
| checkShellNotWindows(); | ||
|
|
||
| long nodeManagerPid = ProcessHandle.current().pid(); | ||
|
|
||
| checkAdminACL(req); | ||
|
|
||
| return runJStack(nodeManagerPid, numberOfJStack); | ||
| } | ||
|
|
||
| private void checkAdminACL(HttpServletRequest req) throws IOException { | ||
| UserGroupInformation callerUGI = getUserGroupInformation(req); | ||
|
|
||
| boolean isAdmin = context.getApplicationACLsManager().isAdmin(callerUGI); | ||
|
|
||
| if (!isAdmin) { | ||
| throw new YarnRuntimeException("User " + callerUGI.getShortUserName() + | ||
| " is not authorized to run jstack on NodeManager "); | ||
| } | ||
| } | ||
|
|
||
| public String collectApplicationThreadDump( | ||
| String appId, int numberOfJStack, HttpServletRequest req) | ||
| throws IOException { | ||
| checkShellNotWindows(); | ||
|
|
||
| ApplicationId applicationId = ApplicationId.fromString(appId); | ||
|
Hean-Chhinling marked this conversation as resolved.
|
||
|
|
||
| Application app = context.getApplications().get(applicationId); | ||
| if (app == null){ | ||
| throw new YarnRuntimeException("Application " + applicationId + " does not exist"); | ||
| } | ||
|
|
||
| checkApplicationACL(req, app); | ||
|
|
||
| Map<ContainerId, List<Long>> containerPids = getApplicationContainerPids(app); | ||
|
|
||
| return runJStack(containerPids, numberOfJStack); | ||
| } | ||
|
|
||
| private void checkApplicationACL(HttpServletRequest req, Application app) throws IOException { | ||
| UserGroupInformation callerUGI = getUserGroupInformation(req); | ||
|
|
||
| boolean isAuthorized = context.getApplicationACLsManager().checkAccess( | ||
| callerUGI, ApplicationAccessType.VIEW_APP, app.getUser(), app.getAppId() | ||
| ); | ||
|
|
||
| if(!isAuthorized){ | ||
| throw new YarnRuntimeException("User " + callerUGI.getShortUserName() + | ||
| " is not authorized to view application " + app.getAppId()); | ||
| } | ||
|
|
||
| } | ||
|
|
||
| private void checkShellNotWindows() { | ||
| if (Shell.WINDOWS) { | ||
| throw new UnsupportedOperationException("Not implemented for Windows."); | ||
| } | ||
| } | ||
|
|
||
| protected Map<ContainerId, List<Long>> getApplicationContainerPids(Application app){ | ||
| Map<ContainerId, List<Long>> containerPids = new HashMap<>(); | ||
|
|
||
| for (ContainerId containerId : app.getContainers().keySet()){ | ||
| String pidForContainerIdStr = context.getContainerExecutor().getProcessId(containerId); | ||
| long parentPid = Long.parseLong(pidForContainerIdStr); | ||
|
|
||
| List<Long> javaContainerPids = ProcessHandle.of(parentPid).stream() | ||
| .flatMap(ProcessHandle::descendants) | ||
| .filter(childProcess -> { | ||
| String cmdLine = childProcess.info().commandLine().orElse("").trim(); | ||
| if (cmdLine.isEmpty()){ | ||
| return false; | ||
| } | ||
| String executable = cmdLine.split("\\s+")[0]; | ||
| // The first token is always the executable binary | ||
| return executable.equals("java") || executable.endsWith("/java"); | ||
| }) | ||
| .map(ProcessHandle::pid) | ||
| .toList(); | ||
|
|
||
| containerPids.put(containerId, javaContainerPids); | ||
|
|
||
| } | ||
|
|
||
| LOG.info("Application PIDs by ContainerId: {}", containerPids); | ||
|
|
||
| return containerPids; | ||
| } | ||
|
|
||
| private String runJStack(Map<ContainerId, List<Long>> containerPids, int numJStacks){ | ||
| StringBuilder result = new StringBuilder(); | ||
|
|
||
| for(Map.Entry<ContainerId, List<Long>> entry : containerPids.entrySet()){ | ||
| ContainerId containerId = entry.getKey(); | ||
| List<Long> javaContainerPids = entry.getValue(); | ||
|
|
||
| if (javaContainerPids.isEmpty()){ | ||
| result.append(String.format("=== Thread Dumps for ContainerId: %s%n is skipped " + | ||
| "because no Java Process ID exist ===", containerId.toString())); | ||
| } else { | ||
| for (Long pid : javaContainerPids) { | ||
| result.append(String.format( | ||
| "=== Thread Dumps for ContainerId: %s, PID: %d ===%n%s%n", | ||
| containerId.toString(), pid, runJStack(pid, numJStacks))); | ||
| } | ||
| } | ||
|
|
||
| } | ||
|
|
||
| return result.toString(); | ||
| } | ||
|
|
||
| private String runJStack(long pid, int numJStacks) { | ||
| Optional<ProcessHandle> processHandleOpt = ProcessHandle.of(pid); | ||
|
|
||
| if (processHandleOpt.isEmpty()){ | ||
| String msg = String.format("Process with PID " + pid + " is no longer exists"); | ||
| LOG.warn(msg); | ||
| return "Status: Skipped Process with PID " + msg; | ||
| } | ||
|
|
||
| ProcessHandle processHandle = processHandleOpt.get(); | ||
|
|
||
| String runningUser = processHandle.info().user().orElse(NM_USER); | ||
| String containerExecutorPath = | ||
| PrivilegedOperationExecutor.getContainerExecutorExecutablePath(conf); | ||
|
|
||
| String[] jstackCommand = { | ||
| containerExecutorPath, "--run-jstack", runningUser, String.valueOf(pid), JSTACK_PATH | ||
| }; | ||
|
|
||
| LOG.info("Running JStack command: {}", Arrays.toString(jstackCommand)); | ||
|
|
||
| StringBuilder result = new StringBuilder(); | ||
|
|
||
| for (int i = 0; i < numJStacks; i++) { | ||
| Shell.ShellCommandExecutor cmd = | ||
| new Shell.ShellCommandExecutor(jstackCommand, null, null, 60_000); | ||
|
|
||
| try { | ||
| cmd.execute(); | ||
| result.append(String.format( | ||
| "--- JStack iteration %d for PID: %d ---%n%s%n", i, pid, cmd.getOutput())); | ||
| } catch (IOException e) { | ||
| result.append(String.format( | ||
| "Failed to run jstack on PID: " + pid + " at iteration: " + i + | ||
| " (Process likely exited before/during running jstack): " + e.getMessage())); | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| return result.toString(); | ||
| } | ||
|
|
||
| private UserGroupInformation getUserGroupInformation(HttpServletRequest req) throws IOException { | ||
| String remoteUser = req.getRemoteUser(); | ||
| UserGroupInformation callerUGI; | ||
|
|
||
| if (remoteUser != null) { | ||
| callerUGI = UserGroupInformation.createRemoteUser(remoteUser); | ||
| } else { | ||
| callerUGI = UserGroupInformation.getCurrentUser(); // Fallback to current OS user | ||
| } | ||
|
|
||
| LOG.info("Checking ACL for Caller UGI: {}", callerUGI.toString()); | ||
|
|
||
| return callerUGI; | ||
|
|
||
| } | ||
|
|
||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.