Skip to content

Commit

Permalink
Add "thread dump" functionality (GUI, REST)
Browse files Browse the repository at this point in the history
Thread dumps now can be requested via GUI or via REST. They can
be global (all threads) or related to running tasks only. Dumps
can be displayed or attached to respective tasks as diagnostic
information.
  • Loading branch information
mederly committed Feb 11, 2019
1 parent 56250ee commit f09a5b9
Show file tree
Hide file tree
Showing 22 changed files with 818 additions and 17 deletions.
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (c) 2010-2019 Evolveum
~
~ Licensed 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.
-->

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
<body>
<wicket:panel>
<div class="row">
<div class="col-md-6">
<h3><wicket:message key="PageInternals.title.threads"/></h3>
<textarea wicket:id="result"></textarea>
<p/>
<div class="main-button-bar">
<a class="btn btn-primary" wicket:id="showAllThreads"/>
<a class="btn btn-primary" wicket:id="showTasksThreads"/>
<a class="btn btn-primary" wicket:id="recordTasksThreads"/>
</div>
</div>
</div>
</wicket:panel>
</body>
</html>
@@ -0,0 +1,145 @@
/*
* Copyright (c) 2010-2019 Evolveum
*
* Licensed 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 com.evolveum.midpoint.web.page.admin.configuration;

import com.evolveum.midpoint.gui.api.component.BasePanel;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.util.exception.CommonException;
import com.evolveum.midpoint.util.logging.LoggingUtils;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.web.component.AceEditor;
import com.evolveum.midpoint.web.component.AjaxButton;
import com.evolveum.midpoint.web.component.util.VisibleEnableBehaviour;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;

public class InternalsThreadsPanel extends BasePanel<Void> {

private static final Trace LOGGER = TraceManager.getTrace(InternalsThreadsPanel.class);

private static final long serialVersionUID = 1L;

private static final String ID_RESULT = "result";

private static final String ID_SHOW_ALL_THREADS = "showAllThreads";
private static final String ID_SHOW_TASKS_THREADS = "showTasksThreads";
private static final String ID_RECORD_TASKS_THREADS = "recordTasksThreads";

private IModel<String> resultModel = Model.of((String) null);

public InternalsThreadsPanel(String id) {
super(id);
}

@Override
protected void onInitialize() {
super.onInitialize();

AceEditor result = new AceEditor(ID_RESULT, resultModel);
result.setReadonly(true);
result.setResizeToMaxHeight(true);
result.setMode(null);
result.add(new VisibleEnableBehaviour() {
@Override
public boolean isVisible() {
return resultModel.getObject() != null;
}
});
add(result);

add(new AjaxButton(ID_SHOW_ALL_THREADS, createStringResource("InternalsThreadsPanel.button.showAllThreads")) {
private static final long serialVersionUID = 1L;
@Override
public void onClick(AjaxRequestTarget target) {
executeShowAllThreads(target);
}
});
add(new AjaxButton(ID_SHOW_TASKS_THREADS, createStringResource("InternalsThreadsPanel.button.showTasksThreads")) {
private static final long serialVersionUID = 1L;
@Override
public void onClick(AjaxRequestTarget target) {
executeShowTasksThreads(target);
}
});
add(new AjaxButton(ID_RECORD_TASKS_THREADS, createStringResource("InternalsThreadsPanel.button.recordTasksThreads")) {
private static final long serialVersionUID = 1L;
@Override
public void onClick(AjaxRequestTarget target) {
executeRecordTasksThreads(target);
}
});

}

private void executeShowAllThreads(AjaxRequestTarget target) {
Task task = getPageBase().createSimpleTask(InternalsThreadsPanel.class.getName() + ".executeShowAllThreads");
OperationResult result = task.getResult();

try {
String dump = getPageBase().getTaskService().getThreadsDump(task, result);
LOGGER.debug("Threads:\n{}", dump);
resultModel.setObject(dump);
} catch (CommonException | RuntimeException e) {
result.recordFatalError("Couldn't get threads", e);
LoggingUtils.logUnexpectedException(LOGGER, "Couldn't get threads", e);
} finally {
result.computeStatus();
}
getPageBase().showResult(result);
target.add(this, getPageBase().getFeedbackPanel());
}

private void executeShowTasksThreads(AjaxRequestTarget target) {
Task task = getPageBase().createSimpleTask(InternalsThreadsPanel.class.getName() + ".executeShowTasksThreads");
OperationResult result = task.getResult();

try {
String dump = getPageBase().getTaskService().getRunningTasksThreadsDump(task, result);
LOGGER.debug("Running tasks' threads:\n{}", dump);
resultModel.setObject(dump);
} catch (CommonException | RuntimeException e) {
result.recordFatalError("Couldn't get tasks' threads", e);
LoggingUtils.logUnexpectedException(LOGGER, "Couldn't get tasks' threads", e);
} finally {
result.computeStatus();
}
getPageBase().showResult(result);
target.add(this, getPageBase().getFeedbackPanel());
}

private void executeRecordTasksThreads(AjaxRequestTarget target) {
Task task = getPageBase().createSimpleTask(InternalsThreadsPanel.class.getName() + ".executeRecordTasksThreads");
OperationResult result = task.getResult();

try {
String info = getPageBase().getTaskService().recordRunningTasksThreadsDump(SchemaConstants.USER_REQUEST_URI, task, result);
resultModel.setObject(info);
} catch (CommonException | RuntimeException e) {
result.recordFatalError("Couldn't record tasks' threads", e);
LoggingUtils.logUnexpectedException(LOGGER, "Couldn't record tasks' threads", e);
} finally {
result.computeStatus();
}
getPageBase().showResult(result);
target.add(this, getPageBase().getFeedbackPanel());
}

}
Expand Up @@ -145,6 +145,16 @@ public WebMarkupContainer getPanel(String panelId) {
}
});

tabs.add(new AbstractTab(createStringResource("PageInternals.tab.threads")) {

private static final long serialVersionUID = 1L;

@Override
public WebMarkupContainer getPanel(String panelId) {
return initThreadsPanel(panelId);
}
});

TabbedPanel<ITab> tabPannel = new TabbedPanel<>(ID_TAB_PANEL, tabs);
add(tabPannel);

Expand All @@ -171,10 +181,12 @@ private WebMarkupContainer initCounters(String panelId) {
}

private WebMarkupContainer initCachePanel(String panelId) {
return new InternalsCachePanel(panelId);
return new InternalsCachePanel(panelId);
}

private WebMarkupContainer initThreadsPanel(String panelId) {
return new InternalsThreadsPanel(panelId);
}



}
Expand Up @@ -4253,8 +4253,13 @@ DisplayNamePanel.viewObjectDetails=View object details
PageUsersView.title=Users view
pageAdminFocus.dataProtection=Data protection
PageInternals.title.cache=Caches
PageInternals.title.threads=Threads
PageInternals.tab.cache=Cache management
PageInternals.tab.threads=Threads
InternalsCachePanel.button.clearCaches=Clear caches
InternalsThreadsPanel.button.showAllThreads=Show all threads
InternalsThreadsPanel.button.showTasksThreads=Show tasks threads
InternalsThreadsPanel.button.recordTasksThreads=Record tasks threads
PageAccountActivation.user.not.found=Unexpected problem occurs. Please contact system administrator.
propertyConstraintValidator.error=Cannot set property constraint without path defined.
ReferencePopupPanel.oid=Oid:
Expand Down Expand Up @@ -4356,4 +4361,7 @@ LoggingConfigurationTabPanel.appender.nameColumn=Name
LoggingConfigurationTabPanel.appender.patternColumn=Pattern
LoggingConfigurationTabPanel.appender.typeColumn=Type
ResourceSummaryPanel.UP=Up
ResourceSummaryPanel.DOWN=Down
ResourceSummaryPanel.DOWN=Down
operation.com.evolveum.midpoint.web.page.admin.configuration.InternalsThreadsPanel.executeShowAllThreads=Show all threads (Gui)
operation.com.evolveum.midpoint.web.page.admin.configuration.InternalsThreadsPanel.executeShowTasksThreads=Show running tasks threads (Gui)
operation.com.evolveum.midpoint.web.page.admin.configuration.InternalsThreadsPanel.executeRecordTasksThreads=Record running tasks threads (Gui)
Expand Up @@ -602,11 +602,23 @@ public abstract class SchemaConstants {

public static final ItemPath PATH_PARENT = ItemPath.create(PrismConstants.T_PARENT);
public static final ItemPath PATH_OBJECT_REFERENCE = ItemPath.create(PrismConstants.T_OBJECT_REFERENCE);


// diagnostic information types

public static final QName TASK_THREAD_DUMP = new QName(NS_C, "taskThreadDump");
public static final String TASK_THREAD_DUMP_URI = QNameUtil.qNameToUri(TASK_THREAD_DUMP);

// diagnostic information causes

public static final QName USER_REQUEST = new QName(NS_C, "userRequest");
public static final String USER_REQUEST_URI = QNameUtil.qNameToUri(USER_REQUEST);
public static final QName INTERNAL = new QName(NS_C, "internal");
public static final String INTERNAL_URI = QNameUtil.qNameToUri(INTERNAL);

//task stages
private static final String RECON_HANDLER = "http://midpoint.evolveum.com/xml/ns/public/model/synchronization/task/reconciliation/handler-3";
public static final String DRY_RUN_URI = RECON_HANDLER + "#dryRun";
public static final String SIMULATE_URI = RECON_HANDLER + "#simulate";
public static final String EXECUTE_URI = RECON_HANDLER + "#execute";

}
Expand Up @@ -432,6 +432,18 @@
</xsd:annotation>
</xsd:element>

<xsd:element name="diagnosticInformation" type="tns:DiagnosticInformationType" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>
Diagnostic information attached to this object.
</xsd:documentation>
<xsd:appinfo>
<a:since>4.0</a:since>
<a:experimental>true</a:experimental>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>

</xsd:sequence>

<xsd:attribute name="oid" type="xsd:string" use="optional">
Expand Down Expand Up @@ -2765,6 +2777,68 @@
</xsd:complexType>
<xsd:element name="task" type="tns:TaskType" substitutionGroup="c:object"/>

<xsd:complexType name="DiagnosticInformationType">
<xsd:annotation>
<xsd:documentation>
A diagnostic information attached to an object.
It can be put there because of a user request or by midPoint itself.
EXPERIMENTAL.
</xsd:documentation>
<xsd:appinfo>
<a:since>4.0</a:since>
<a:experimental>true</a:experimental>
</xsd:appinfo>
</xsd:annotation>
<xsd:sequence>
<xsd:element name="timestamp" type="xsd:dateTime">
<xsd:annotation>
<xsd:documentation>
When the information was created.
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="type" type="xsd:anyURI" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
Type of the information (e.g. thread dump).
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="cause" type="xsd:anyURI" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
Cause of the information being created (e.g. a user request).
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="nodeIdentifier" type="xsd:string" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
Identifier of a node where the information was captured.
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<!--<xsd:element name="initiatorRef" type="tns:ObjectReferenceType" minOccurs="0">-->
<!--<xsd:annotation>-->
<!--<xsd:documentation>-->
<!--Initiator of the attachment of this diagnostic information (if known and applicable).-->
<!--</xsd:documentation>-->
<!--<xsd:appinfo>-->
<!--<a:objectReferenceTargetType>tns:UserType</a:objectReferenceTargetType>-->
<!--</xsd:appinfo>-->
<!--</xsd:annotation>-->
<!--</xsd:element>-->
<xsd:element name="content" type="xsd:string" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>
A textual representation of the information.
(It can be augmented/replaced by more structured form using a subclass of DiagnosticInformationType.)
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
</xsd:complexType>

<xsd:complexType name="TaskExecutionConstraintsType">
<xsd:annotation>
<xsd:documentation>
Expand Down
28 changes: 28 additions & 0 deletions infra/util/src/main/java/com/evolveum/midpoint/util/MiscUtil.java
Expand Up @@ -23,12 +23,16 @@

import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import org.jetbrains.annotations.Nullable;

import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeConstants;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import java.io.*;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
Expand Down Expand Up @@ -735,4 +739,28 @@ public static <T> List<T> join(Collection<T> a, Collection<T> b) {
return list;
}

/**
* Thanks for this code go to https://crunchify.com/how-to-generate-java-thread-dump-programmatically/
*/
public static String takeThreadDump(@Nullable Thread thread) {
StringBuilder dump = new StringBuilder();
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
long[] threadIds = thread != null ? new long[] { thread.getId() } : threadMXBean.getAllThreadIds();
ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(threadIds, 100);
for (ThreadInfo threadInfo : threadInfos) {
dump.append("Thread name: \"");
dump.append(threadInfo.getThreadName());
dump.append("\"\n");
dump.append("Thread state: ");
dump.append(threadInfo.getThreadState());
StackTraceElement[] stackTraceElements = threadInfo.getStackTrace();
for (StackTraceElement stackTraceElement : stackTraceElements) {
dump.append("\n at ");
dump.append(stackTraceElement);
}
dump.append("\n\n");
}
return dump.toString();
}

}

0 comments on commit f09a5b9

Please sign in to comment.