OOZIE-2296 Add an Oozie diagnostic bundle tool (asasvari)

Project: http://git-wip-us.apache.org/repos/asf/oozie/repo
Commit: http://git-wip-us.apache.org/repos/asf/oozie/commit/9e8598ea
Tree: http://git-wip-us.apache.org/repos/asf/oozie/tree/9e8598ea
Diff: http://git-wip-us.apache.org/repos/asf/oozie/diff/9e8598ea

Branch: refs/heads/master
Commit: 9e8598ea48d6db78d5feaad60823b55b2998b67d
Parents: 567ce9f
Author: Attila Sasvari <asasv...@cloudera.com>
Authored: Sun Nov 5 00:41:39 2017 +0100
Committer: Attila Sasvari <asasv...@cloudera.com>
Committed: Wed Nov 15 14:43:54 2017 +0100

----------------------------------------------------------------------
 docs/src/site/twiki/DG_CommandLineTool.twiki    |  54 +++
 pom.xml                                         |   6 +-
 release-log.txt                                 |   1 +
 tools/pom.xml                                   |   4 +
 .../src/main/bin/oozie-diag-bundle-collector.sh |  61 +++
 .../oozie/tools/diag/AppInfoCollector.java      | 443 +++++++++++++++++++
 .../org/apache/oozie/tools/diag/ArgParser.java  | 176 ++++++++
 .../tools/diag/DiagBundleCollectorDriver.java   | 156 +++++++
 .../oozie/tools/diag/DiagBundleCompressor.java  |  43 ++
 .../oozie/tools/diag/DiagBundleEntryWriter.java | 118 +++++
 .../oozie/tools/diag/DiagOozieClient.java       |  52 +++
 .../oozie/tools/diag/MetricsCollector.java      | 142 ++++++
 .../tools/diag/OozieLauncherLogFetcher.java     | 135 ++++++
 .../oozie/tools/diag/ServerInfoCollector.java   | 141 ++++++
 .../oozie/tools/diag/TestAppInfoCollector.java  | 167 +++++++
 .../apache/oozie/tools/diag/TestArgParser.java  | 112 +++++
 .../oozie/tools/diag/TestMetricsCollector.java  | 228 ++++++++++
 .../tools/diag/TestServerInfoCollector.java     | 125 ++++++
 18 files changed, 2163 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/oozie/blob/9e8598ea/docs/src/site/twiki/DG_CommandLineTool.twiki
----------------------------------------------------------------------
diff --git a/docs/src/site/twiki/DG_CommandLineTool.twiki 
b/docs/src/site/twiki/DG_CommandLineTool.twiki
index d404767..24fb1c9 100644
--- a/docs/src/site/twiki/DG_CommandLineTool.twiki
+++ b/docs/src/site/twiki/DG_CommandLineTool.twiki
@@ -1939,6 +1939,60 @@ The properties file must specify the 
=mapred.mapper.class=, =mapred.reducer.clas
 
 The map-reduce job will be created and submitted. All jar files and all other 
files needed by the mapreduce job need to be uploaded onto HDFS under libpath 
beforehand. The workflow.xml will be created in Oozie server internally. Users 
can get the workflow.xml from console or command line(-definition).
 
+---++ Getting Oozie diagnostics bundles
+
+A tool that collects a diagnostic bundle of information from Oozie. Collected 
information includes available Oozie ShareLibs;
+effective configuration, system properties, environment variables, thread dump 
of the Oozie server; instrumentation logs;
+dump of queued commands; details about workflow(s) (such as workflow xml, 
logs, current state, job properties), coordinator(s)
+and bundle(s). When retrieving coordinator information, the tool will also try 
to fetch information about related child workflow(s).
+
+Syntax:
+
+<verbatim>
+$ oozie-diag-bundle-collector.sh [-jobs <id ...>] [-maxchildactions <n>]
+       [-numbundles <n>] [-numcoordinators <n>] [-numworkflows <n>] -oozie
+       <url> -output <dir>
+</verbatim>
+
+where
+ -jobs <id ...>         Detailed information on the given job IDs will be
+                        collected (default: none)
+ -maxchildactions <n>   Maximum number of Workflow or Coordinator actions
+                        that will be collected (default: 10)
+ -numbundles <n>        Detailed information on the last n Bundles will be
+                        collected (default: 0)
+ -numcoordinators <n>   Detailed information on the last n Coordinators
+                        will be collected (default: 0)
+ -numworkflows <n>      Detailed information on the last n workflows will
+                        be collected (default: 0)
+ -oozie <url>           Required: Oozie URL (or specify with OOZIE_URL env
+                        var)
+ -output <dir>          Required: Directory to output the zip file
+
+Example:
+
+<verbatim>
+$ oozie-diag-bundle-collector.sh -jobs 0000001-170918144116149-oozie-test-W 
-oozie http://localhost:11000/oozie -output diag
+
+...
+
+Using Temporary Directory: 
/var/folders/9q/f8p_r6gj0wbck49_dc092q_m0000gp/T/1505748796767-0
+Getting Sharelib Information...Done
+Getting Configuration...Done
+Getting OS Environment Variables...Done
+Getting Java System Properties...Done
+Getting Queue Dump...Done
+Getting Thread Dump...Done
+Getting Instrumentation...Done
+Getting Metrics...Skipping (Metrics are unavailable)
+Getting Details for 0000001-170918144116149-oozie-test-W...Done
+Creating Zip File: 
/var/lib/oozie/diag/oozie-diag-bundle-1505748797206.zip...Done
+</verbatim>
+
+Before executing the command, make sure OOZIE_HOME environment variable is set 
correctly. If Oozie authorization is enabled, then
+the user must be an admin user in order to perform admin operations (for 
example getting a Thread dump of the Oozie server). If the
+output directory does not exist, the tool will create it and store generated 
bundle there.
+
 [[index][::Go back to Oozie Documentation Index::]]
 
 </noautolink>

http://git-wip-us.apache.org/repos/asf/oozie/blob/9e8598ea/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 0012445..9f58011 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1537,7 +1537,11 @@
                 <artifactId>guice</artifactId>
                 <version>3.0</version>
             </dependency>
-
+            <dependency>
+                <groupId>com.google.code.findbugs</groupId>
+                <artifactId>annotations</artifactId>
+                <version>3.0.0</version>
+            </dependency>
             <dependency>
                 <groupId>org.eclipse.jetty</groupId>
                 <artifactId>jetty-server</artifactId>

http://git-wip-us.apache.org/repos/asf/oozie/blob/9e8598ea/release-log.txt
----------------------------------------------------------------------
diff --git a/release-log.txt b/release-log.txt
index 6a6e6d4..8db0dde 100644
--- a/release-log.txt
+++ b/release-log.txt
@@ -1,5 +1,6 @@
 -- Oozie 5.0.0 release (trunk - unreleased)
 
+OOZIE-2296 Add an Oozie diagnostic bundle tool (asasvari)
 OOZIE-3125 TestDBLoadDump.testImportInvalidDataLeavesTablesEmpty fails 
(asasvari)
 OOZIE-3106 upgrade surefire-plugin to 2.20.1 (dbist13 via asasvari)
 OOZIE-2061 Remove CoordJobDeleteJPAExecutor (dbist13 via gezapeti)

http://git-wip-us.apache.org/repos/asf/oozie/blob/9e8598ea/tools/pom.xml
----------------------------------------------------------------------
diff --git a/tools/pom.xml b/tools/pom.xml
index 111ce53..bdc736d 100644
--- a/tools/pom.xml
+++ b/tools/pom.xml
@@ -83,6 +83,10 @@
             <artifactId>gson</artifactId>
             <scope>compile</scope>
         </dependency>
+        <dependency>
+            <groupId>com.google.code.findbugs</groupId>
+            <artifactId>annotations</artifactId>
+        </dependency>
 
     </dependencies>
 

http://git-wip-us.apache.org/repos/asf/oozie/blob/9e8598ea/tools/src/main/bin/oozie-diag-bundle-collector.sh
----------------------------------------------------------------------
diff --git a/tools/src/main/bin/oozie-diag-bundle-collector.sh 
b/tools/src/main/bin/oozie-diag-bundle-collector.sh
new file mode 100755
index 0000000..68929e1
--- /dev/null
+++ b/tools/src/main/bin/oozie-diag-bundle-collector.sh
@@ -0,0 +1,61 @@
+#!/bin/bash
+#
+# 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.
+#
+
+# resolve links - $0 may be a softlink
+PRG="${0}"
+
+while [ -h "${PRG}" ]; do
+  ls=$(ls -ld "${PRG}")
+  link=$(expr "$ls" : '.*-> \(.*\)$')
+  if expr "$link" : '/.*' > /dev/null; then
+    PRG="$link"
+  else
+    PRG=$(dirname "${PRG}")/"$link"
+  fi
+done
+
+BASEDIR=$(dirname "${PRG}")
+cd "${BASEDIR}"/.. || (echo "Cannot change directory to ${BASEDIR}" && exit)
+BASEDIR=$(pwd)
+
+OOZIECPPATH=""
+for i in "${BASEDIR}/libtools/"*.jar; do
+  OOZIECPPATH="${OOZIECPPATH}:$i"
+done
+for i in "${BASEDIR}/lib/"*.jar; do
+  OOZIECPPATH="${OOZIECPPATH}:$i"
+done
+for i in "${BASEDIR}/libext/"*.jar; do
+  OOZIECPPATH="${OOZIECPPATH}:$i"
+done
+
+
+if test -z "${JAVA_HOME}"
+then
+    JAVA_BIN=java
+else
+    JAVA_BIN=${JAVA_HOME}/bin/java
+fi
+
+while [[ ${1} =~ ^\-D ]]; do
+  JAVA_PROPERTIES="${JAVA_PROPERTIES} ${1}"
+  shift
+done
+
+${JAVA_BIN} ${JAVA_PROPERTIES} -cp "${OOZIECPPATH}" 
org.apache.oozie.tools.diag.DiagBundleCollectorDriver "${@}"

http://git-wip-us.apache.org/repos/asf/oozie/blob/9e8598ea/tools/src/main/java/org/apache/oozie/tools/diag/AppInfoCollector.java
----------------------------------------------------------------------
diff --git 
a/tools/src/main/java/org/apache/oozie/tools/diag/AppInfoCollector.java 
b/tools/src/main/java/org/apache/oozie/tools/diag/AppInfoCollector.java
new file mode 100644
index 0000000..d7e4534
--- /dev/null
+++ b/tools/src/main/java/org/apache/oozie/tools/diag/AppInfoCollector.java
@@ -0,0 +1,443 @@
+/**
+ * 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.oozie.tools.diag;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.yarn.api.records.ApplicationId;
+import org.apache.hadoop.yarn.util.ConverterUtils;
+import org.apache.oozie.client.BundleJob;
+import org.apache.oozie.client.CoordinatorAction;
+import org.apache.oozie.client.CoordinatorJob;
+import org.apache.oozie.client.OozieClientException;
+import org.apache.oozie.client.WorkflowAction;
+import org.apache.oozie.client.WorkflowJob;
+import org.apache.oozie.util.XConfiguration;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.io.StringReader;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Properties;
+
+@SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = "Output 
directory is specified by user")
+class AppInfoCollector {
+    private final DiagOozieClient client;
+    private final OozieLauncherLogFetcher oozieLauncherLogFetcher;
+
+    AppInfoCollector(final Configuration hadoopConfig, final DiagOozieClient 
client) {
+        this.client = client;
+        oozieLauncherLogFetcher = new OozieLauncherLogFetcher(hadoopConfig);
+    }
+
+    private void storeWorkflowJobDetails(final File outputDir, final String 
jobId, int maxChildActions) {
+        if (jobId == null || !isWorkflow(jobId)) {
+            return;
+        }
+
+        try {
+            System.out.print("Getting Details for " + jobId + "...");
+            final File workflowOutputDir = new File(outputDir, jobId);
+            if (!createOutputDirectory(workflowOutputDir)) {
+                return;
+            }
+
+            final File resolvedActionsDir = new File(workflowOutputDir, 
"resolved-actions");
+            if (!createOutputDirectory(resolvedActionsDir)) {
+                System.out.println("Workflow details already stored.");
+                return;
+            }
+
+            final WorkflowJob job = client.getJobInfo(jobId);
+
+            try (DiagBundleEntryWriter diagBundleEntryWriter = new 
DiagBundleEntryWriter(workflowOutputDir,"info.txt")) {
+                persistWorkflowJobInfo(maxChildActions, resolvedActionsDir, 
job, diagBundleEntryWriter);
+            }
+
+            storeCommonDetails(workflowOutputDir, jobId, "workflow", 
job.getConf());
+            System.out.println("Done");
+        } catch (IOException | OozieClientException e) {
+            System.err.printf("Exception occurred during the retrieval of 
workflow information: %s%n", e.getMessage());
+        }
+    }
+
+    private void persistWorkflowJobInfo(int maxChildActions, final File 
resolvedActionsDir, final WorkflowJob job,
+                                        final DiagBundleEntryWriter 
bundleEntryWriter) throws IOException {
+        bundleEntryWriter.writeString("WORKFLOW\n")
+                         .writeString("--------\n")
+                         .writeStringValue("Workflow Id        : ", 
job.getId())
+                         .writeStringValue("Name               : ", 
job.getAppName())
+                         .writeStringValue("App Path           : ", 
job.getAppPath())
+                         .writeStringValue("User               : ", 
job.getUser())
+                         .writeStringValue("ACL                : ", 
job.getAcl())
+                         .writeStringValue("Status             : ", 
job.getStatus().toString())
+                         .writeStringValue("Console URL        : ", 
job.getConsoleUrl())
+                         .writeStringValue("External Id        : ", 
job.getExternalId())
+                         .writeStringValue("Parent Id          : ", 
job.getParentId())
+                         .writeDateValue("Created Time       : ", 
job.getCreatedTime())
+                         .writeDateValue("End Time           : ", 
job.getEndTime())
+                         .writeDateValue("Last Modified Time : ", 
job.getLastModifiedTime())
+                         .writeDateValue("Start Time         : ", 
job.getStartTime())
+                         .writeIntValue("Run                : ", job.getRun())
+                         .writeIntValue("Action Count       : ", 
job.getActions().size())
+                         .writeNewLine()
+                         .writeString("ACTIONS\n")
+                         .writeString("------\n")
+                         .flush();
+
+        final List<WorkflowAction> workflowActions = job.getActions();
+        for (int actionCount = 0; actionCount != workflowActions.size() && 
actionCount < maxChildActions; ++actionCount) {
+            final WorkflowAction action = workflowActions.get(actionCount);
+            bundleEntryWriter.writeStringValue("Action Id          : ", 
action.getId())
+                             .writeStringValue("Name               : ", 
action.getName())
+                             .writeStringValue("Type               : ", 
action.getType())
+                             .writeStringValue("Status             : ", 
action.getStatus().toString())
+                             .writeStringValue("Transition         : ", 
action.getTransition())
+                             .writeDateValue("Start Time         : ", 
action.getStartTime())
+                             .writeDateValue("End Time           : ", 
action.getEndTime())
+                             .writeStringValue("Error Code         : ", 
action.getErrorCode())
+                             .writeStringValue("Error Message      : ", 
action.getErrorMessage())
+                             .writeStringValue("Console URL        : ", 
action.getConsoleUrl())
+                             .writeStringValue("Tracker URI        : ", 
action.getTrackerUri())
+                             .writeStringValue("External Child Ids : ", 
action.getExternalChildIDs())
+                             .writeStringValue("External Id        : ", 
action.getExternalId())
+                             .writeStringValue("External Status    : ", 
action.getExternalStatus())
+                             .writeStringValue("Data               : ", 
action.getData())
+                             .writeStringValue("Stats              : ", 
action.getStats())
+                             .writeStringValue("Credentials        : ", 
action.getCred())
+                             .writeIntValue("Retries            : ", 
action.getRetries())
+                             .writeIntValue("User Retry Int     : ", 
action.getUserRetryInterval())
+                             .writeIntValue("User Retry Count   : ", 
action.getUserRetryCount())
+                             .writeIntValue("User Retry Max     : ", 
action.getUserRetryMax())
+                             .writeNewLine()
+                             .flush();
+
+            final String actionType = action.getType();
+            persistResolvedActionDefinition(action, resolvedActionsDir);
+
+            if (!isControlNode(actionType)) { // skip control nodes
+                storeOozieLauncherLog(resolvedActionsDir, action, 
job.getUser());
+            }
+        }
+    }
+
+    private boolean isControlNode(final String actionType) {
+        return isNonDecisionControlNode(actionType) || 
isDecisionNode(actionType);
+    }
+
+    private boolean isDecisionNode(final String actionType) {
+        return actionType.contains("switch");
+    }
+
+    private boolean isNonDecisionControlNode(final String actionType) {
+        return actionType.contains(":");
+    }
+
+    private void persistResolvedActionDefinition(final WorkflowAction action, 
final File resolvedActionsDir) throws IOException {
+        persistWorkflowDefinition(resolvedActionsDir, action.getName(), 
action.getConf());
+    }
+
+
+    private void storeOozieLauncherLog(final File outputDir, final 
WorkflowAction action, final String user) {
+        try (PrintStream fw = new PrintStream(new File(outputDir, "launcher_" 
+ action.getName() + ".log"),
+                StandardCharsets.UTF_8.toString())) {
+
+            final ApplicationId appId = 
ConverterUtils.toApplicationId(action.getExternalId());
+            oozieLauncherLogFetcher.dumpAllContainersLogs(appId, user, fw);
+        } catch (IOException e) {
+            System.err.printf("Exception occurred during the retrieval of 
Oozie launcher logs for workflow(s): %s%n",
+                    e.getMessage());
+        }
+    }
+
+    private void getCoordJob(final File outputDir, final String jobId, int 
maxChildActions) {
+        if (jobId == null || !isCoordinator(jobId)) {
+            return;
+        }
+
+        try {
+            System.out.print("Getting Details for " + jobId + "...");
+            final File coordOutputDir = new File(outputDir, jobId);
+
+            if (!createOutputDirectory(coordOutputDir)) {
+                return;
+            }
+
+            final CoordinatorJob job = client.getCoordJobInfo(jobId);
+
+            try (DiagBundleEntryWriter bundleEntryWriter = new 
DiagBundleEntryWriter(coordOutputDir, "info.txt")) {
+                persistCoordinatorJobInfo(maxChildActions, job, 
bundleEntryWriter);
+            }
+
+            storeCommonDetails(coordOutputDir, jobId, "coordinator", 
job.getConf());
+            System.out.println("Done");
+
+            final List<CoordinatorAction> coordinatorActions = 
job.getActions();
+            for (int i = 0; i != coordinatorActions.size() && i < 
maxChildActions; ++i) {
+                storeWorkflowJobDetails(outputDir, 
coordinatorActions.get(i).getExternalId(), maxChildActions);
+            }
+        } catch (IOException | OozieClientException e) {
+            System.err.printf(String.format("Exception occurred during the 
retrieval of coordinator information:%s%n",
+                    e.getMessage()));
+        }
+    }
+
+    private void persistCoordinatorJobInfo(int maxChildActions, final 
CoordinatorJob job,
+                                           final DiagBundleEntryWriter 
bundleEntryWriter)
+            throws IOException {
+        bundleEntryWriter.writeString("COORDINATOR\n")
+                         .writeString("-----------\n")
+                         .writeStringValue("Coordinator Id           : ", 
job.getId())
+                         .writeStringValue("Name                     : ", 
job.getAppName())
+                         .writeStringValue("App Path                 : ", 
job.getAppPath())
+                         .writeStringValue("User                     : ", 
job.getUser())
+                         .writeStringValue("ACL                      : ", 
job.getAcl())
+                         .writeStringValue("Status                   : ", 
job.getStatus().toString())
+                         .writeStringValue("Console URL              : ", 
job.getConsoleUrl())
+                         .writeStringValue("External Id              : ", 
job.getExternalId())
+                         .writeStringValue("Bundle Id                : ", 
job.getBundleId())
+                         .writeStringValue("Frequency                : ", 
job.getFrequency())
+                         .writeStringValue("Time Unit                : ", 
job.getTimeUnit().toString())
+                         .writeDateValue("Start Time               : ", 
job.getStartTime())
+                         .writeDateValue("End Time                 : ", 
job.getEndTime())
+                         .writeDateValue("Last Action Time         : ", 
job.getLastActionTime())
+                         .writeDateValue("Next Materialized Time   : ", 
job.getNextMaterializedTime())
+                         .writeDateValue("Pause Time               : ", 
job.getPauseTime())
+                         .writeStringValue("Timezone                 : ", 
job.getTimeZone())
+                         .writeIntValue("Concurrency              : ", 
job.getConcurrency())
+                         .writeIntValue("Timeout                  : ", 
job.getTimeout())
+                         .writeStringValue("Execution Order          : ", 
job.getExecutionOrder().toString())
+                         .writeIntValue("Action Count             : ", 
job.getActions().size())
+                         .writeNewLine()
+                         .writeString("ACTIONS\n")
+                         .writeString("------\n")
+                         .flush();
+
+        final List<CoordinatorAction> coordinatorActions = job.getActions();
+        for (int i = 0; i < maxChildActions && i != coordinatorActions.size(); 
++i) {
+            final CoordinatorAction action = coordinatorActions.get(i);
+            bundleEntryWriter.writeStringValue("Action Id                 : ", 
action.getId())
+                             .writeIntValue("Action Number             : ", 
action.getActionNumber())
+                             .writeStringValue("Job Id                    : ", 
action.getJobId())
+                             .writeStringValue("Status                    : ", 
action.getStatus().toString())
+                             .writeStringValue("External Id               : ", 
action.getExternalId())
+                             .writeStringValue("External Status           : ", 
action.getExternalStatus())
+                             .writeStringValue("Console URL               : ", 
action.getConsoleUrl())
+                             .writeStringValue("Tracker URI               : ", 
action.getTrackerUri())
+                             .writeDateValue("Created Time              : ", 
action.getCreatedTime())
+                             .writeDateValue("Nominal Time              : ", 
action.getNominalTime())
+                             .writeDateValue("Last Modified Time        : ", 
action.getLastModifiedTime())
+                             .writeStringValue("Error Code                : ", 
action.getErrorCode())
+                             .writeStringValue("Error Message             : ", 
action.getErrorMessage())
+                             .writeStringValue("Missing Dependencies      : ", 
action.getMissingDependencies())
+                             .writeStringValue("Push Missing Dependencies : ", 
action.getPushMissingDependencies())
+                             .writeNewLine()
+                             .flush();
+        }
+    }
+
+    private void getBundleJob(final File outputDir, final String jobId, int 
maxChildActions) {
+        if (jobId == null || !isBundle(jobId)) {
+            return;
+        }
+
+        try {
+            System.out.print("Getting Details for " + jobId + "...");
+            final File bundleOutputDir = new File(outputDir, jobId);
+
+            if (!createOutputDirectory(bundleOutputDir)) {
+                return;
+            }
+
+            final BundleJob job = client.getBundleJobInfo(jobId);
+
+            try (DiagBundleEntryWriter bundleEntryWriter = new 
DiagBundleEntryWriter(bundleOutputDir, "info.txt")) {
+                persistBundleJobInfo(job, bundleEntryWriter);
+            }
+
+            storeCommonDetails(bundleOutputDir, jobId, "bundle", 
job.getConf());
+            System.out.println("Done");
+            for (CoordinatorJob coordJob : job.getCoordinators()) {
+                getCoordJob(outputDir, coordJob.getId(), maxChildActions);
+            }
+
+
+        } catch (IOException | OozieClientException e) {
+            System.err.printf(String.format("Exception occurred during the 
retrieval of bundle information: %s%n",
+                    e.getMessage()));
+        }
+    }
+
+    private boolean createOutputDirectory(final File outputDir) throws 
IOException {
+        if (outputDir.isDirectory()) {
+            System.out.println("(Already) Done");
+            return false;
+        }
+        if (!outputDir.mkdirs()) {
+            throw new IOException("Could not create output directory: " + 
outputDir.getAbsolutePath());
+        }
+        return true;
+    }
+
+    private void persistBundleJobInfo(final BundleJob job, final 
DiagBundleEntryWriter bundleEntryWriter) throws IOException {
+        bundleEntryWriter.writeString("BUNDLE\n")
+                         .writeString("-----------\n")
+                         .writeStringValue("Bundle Id    : ", job.getId())
+                         .writeStringValue("Name         : ", job.getAppName())
+                         .writeStringValue("App Path     : ", job.getAppPath())
+                         .writeStringValue("User         : ", job.getUser())
+                         .writeStringValue("Status       : ", 
job.getStatus().toString())
+                         .writeDateValue("Created Time : ", 
job.getCreatedTime())
+                         .writeDateValue("Start Time   : ", job.getStartTime())
+                         .writeDateValue("End Time     : ", job.getEndTime())
+                         .writeDateValue("KickoffTime  : ", 
job.getKickoffTime())
+                         .writeDateValue("Pause Time   : ", job.getPauseTime())
+                         .writeIntValue("Timeout      : ", job.getTimeout())
+                         .writeStringValue("Console URL  : ", 
job.getConsoleUrl())
+                         .writeStringValue( "ACL          : ", job.getAcl())
+                         .flush();
+    }
+
+    private void storeCommonDetails(final File outputDir, final String jobId, 
final String definitionName,
+                                    final String jobPropsConfStr) {
+        try {
+            final String definition = client.getJobDefinition(jobId);
+
+            if (definition != null) {
+                persistWorkflowDefinition(outputDir, definitionName, 
definition);
+            }
+
+            if (jobPropsConfStr != null) {
+                persistJobProperties(outputDir, jobPropsConfStr);
+            }
+
+            persistJobLog(outputDir, jobId);
+        } catch (OozieClientException | IOException e) {
+            System.err.printf(String.format("Exception occurred during the 
retrieval of common job details: %s%n",
+                    e.getMessage()));
+        }
+    }
+
+    private void persistJobLog(final File outputDir, final String jobId) 
throws FileNotFoundException,
+            UnsupportedEncodingException, OozieClientException {
+        try (PrintStream ps = new PrintStream(new File(outputDir, "log.txt"), 
StandardCharsets.UTF_8.toString())) {
+            client.getJobLog(jobId, null, null, null, ps);
+        }
+    }
+
+    private void persistJobProperties(final File outputDir, final String 
jobPropsConfStr) throws IOException {
+        final StringReader sr = new StringReader(jobPropsConfStr);
+        final XConfiguration jobPropsConf = new XConfiguration(sr);
+        final Properties jobProps = jobPropsConf.toProperties();
+
+        try (OutputStream  outputStream = new FileOutputStream(new 
File(outputDir, "job.properties"))) {
+            jobProps.store(outputStream, "");
+        }
+    }
+
+    private void persistWorkflowDefinition(final File outputDir, final String 
definitionName, String definition)
+            throws IOException {
+        try (DiagBundleEntryWriter bundleEntryWriter = new 
DiagBundleEntryWriter(outputDir,
+                definitionName + ".xml")) {
+            bundleEntryWriter.writeString(definition);
+        }
+    }
+
+    void storeLastWorkflows(final File outputDir, int numWorkflows, int 
maxChildActions) {
+        if (numWorkflows == 0) {
+            return;
+        }
+
+        try {
+            final List<WorkflowJob> jobs = client.getJobsInfo(null, 0, 
numWorkflows);
+            for (WorkflowJob job : jobs) {
+                storeWorkflowJobDetails(outputDir, job.getId(), 
maxChildActions);
+            }
+        } catch (OozieClientException e) {
+            System.err.printf("Exception occurred during the retrieval of 
information on the last %d workflow(s): %s.%n",
+                    numWorkflows, e.getMessage());
+        }
+    }
+
+    void storeLastCoordinators(final File outputDir, int numCoordinators, int 
maxChildActions) {
+        if (numCoordinators == 0) {
+            return;
+        }
+
+        try {
+            final List<CoordinatorJob> jobs = client.getCoordJobsInfo(null, 0, 
numCoordinators);
+            for (CoordinatorJob job : jobs) {
+                getCoordJob(outputDir, job.getId(), maxChildActions);
+            }
+        } catch (OozieClientException e) {
+            System.err.printf("Exception occurred during the retrieval of 
information on the last %d coordinator(s): %s.%n",
+                    numCoordinators, e.getMessage());
+        }
+    }
+
+    void storeLastBundles(final File outputDir, int numBundles, int 
maxChildActions) {
+        if (numBundles == 0) {
+            return;
+        }
+
+        try {
+            final List<BundleJob> jobs = client.getBundleJobsInfo(null, 0, 
numBundles);
+            for (BundleJob job : jobs) {
+                getBundleJob(outputDir, job.getId(), maxChildActions);
+            }
+        } catch (OozieClientException e) {
+            System.err.printf("Exception occurred during the retrieval of 
information on the last %d bundle(s): %s.%n",
+                    numBundles, e.getMessage());
+        }
+    }
+
+    void getSpecificJobs(final File outputDir, final String[] jobIds, int 
maxChildActions) {
+        if (jobIds == null) {
+            return;
+        }
+
+        for (String jobId : jobIds) {
+            if (isWorkflow(jobId)) {
+                storeWorkflowJobDetails(outputDir, jobId, maxChildActions);
+            } else if (isCoordinator(jobId)) {
+                getCoordJob(outputDir, jobId, maxChildActions);
+            } else if (isBundle(jobId)) {
+                getBundleJob(outputDir, jobId, maxChildActions);
+            }
+        }
+    }
+
+    private boolean isBundle(final String jobId) {
+        return jobId.endsWith("-B");
+    }
+
+    private boolean isCoordinator(final String jobId) {
+        return jobId.endsWith("-C");
+    }
+
+    private boolean isWorkflow(final String jobId) {
+        return jobId.endsWith("-W");
+    }
+}

http://git-wip-us.apache.org/repos/asf/oozie/blob/9e8598ea/tools/src/main/java/org/apache/oozie/tools/diag/ArgParser.java
----------------------------------------------------------------------
diff --git a/tools/src/main/java/org/apache/oozie/tools/diag/ArgParser.java 
b/tools/src/main/java/org/apache/oozie/tools/diag/ArgParser.java
new file mode 100644
index 0000000..115d0ba
--- /dev/null
+++ b/tools/src/main/java/org/apache/oozie/tools/diag/ArgParser.java
@@ -0,0 +1,176 @@
+/**
+ * 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.oozie.tools.diag;
+
+import com.google.common.base.Preconditions;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.GnuParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+import org.apache.oozie.cli.OozieCLI;
+
+import java.io.File;
+import java.io.IOException;
+
+@SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = "Output 
directory is specified by user")
+class ArgParser {
+
+    private static final String OOZIE_OPTION = "oozie";
+    private static final String NUM_WORKFLOWS_OPTION = "numworkflows";
+    private static final String NUM_COORDS_OPTION = "numcoordinators";
+    private static final String NUM_BUNDLES_OPTION = "numbundles";
+    private static final String JOBS_OPTION = "jobs";
+    private static final String MAX_CHILD_ACTIONS = "maxchildactions";
+    private static final String OUTPUT_DIR_OPTION = "output";
+    private final Options options = new Options();
+    private CommandLine commandLine;
+
+    public void setCommandLine(CommandLine commandLine) {
+        this.commandLine = commandLine;
+    }
+
+    private void addNewOption(String optionName, String argName, String 
details, boolean required) {
+        addNewOption(optionName, argName, details, null, required, null, 
false);
+    }
+
+    private void addNewOption(String optionName, String argName, String 
details, Class type, boolean required) {
+        addNewOption(optionName, argName, details, type, required, null, 
false);
+    }
+
+    private void addNewOption(String optionName, String argName, String 
details, Class type, boolean required,
+                              Character valueSeparator, boolean isUnlimited) {
+        final Option option = new Option(optionName, true,
+                details);
+        option.setRequired(required);
+        option.setArgName(argName);
+        if (type != null) {
+            option.setType(type);
+        }
+        if (valueSeparator != null) {
+            option.setValueSeparator(valueSeparator);
+        }
+        if (isUnlimited) {
+            option.setArgs(Option.UNLIMITED_VALUES);
+        }
+        options.addOption(option);
+    }
+
+    Options setupOptions() {
+        addNewOption(OOZIE_OPTION, "url", String.format("Required: Oozie URL 
(or specify with %s env var)",
+                OozieCLI.ENV_OOZIE_URL),  true);
+
+        addNewOption(NUM_WORKFLOWS_OPTION, "n",
+                "Detailed information on the last n workflows will be 
collected (default: 0)", Integer.class, false);
+
+        addNewOption(NUM_COORDS_OPTION, "n",
+                "Detailed information on the last n Coordinators will be 
collected (default: 0)", Integer.class,
+                false);
+
+        addNewOption(NUM_BUNDLES_OPTION, "n",
+                "Detailed information on the last n Bundles will be collected 
(default: 0)", Integer.class, false);
+
+        addNewOption(JOBS_OPTION, "id ...",
+                "Detailed information on the given job IDs will be collected 
(default: none)", null, false,
+                ',', true);
+
+        addNewOption(MAX_CHILD_ACTIONS, "n",
+                "Maximum number of Workflow or Coordinator actions that will 
be collected (default: 10)", Integer.class,
+                false);
+
+        addNewOption(OUTPUT_DIR_OPTION, "dir", "Required: Directory to output 
the zip file",true);
+        return options;
+    }
+
+    File ensureOutputDir() throws IOException {
+        final String output = commandLine.getOptionValue(OUTPUT_DIR_OPTION);
+        Preconditions.checkNotNull(output);
+
+        final File outputDir = new File(output);
+        if (!outputDir.isDirectory() && !outputDir.mkdirs()) {
+            throw new IOException("Could not create output directory: " + 
outputDir.getAbsolutePath());
+        }
+        return outputDir;
+    }
+
+    Integer getMaxChildActions() {
+        final Integer maxChildActions = 
Integer.valueOf(commandLine.getOptionValue(MAX_CHILD_ACTIONS, "10"));
+        Preconditions.checkArgument(maxChildActions >= 0,
+                MAX_CHILD_ACTIONS + " cannot be negative");
+        return maxChildActions;
+    }
+
+    String[] getJobIds() {
+        return commandLine.getOptionValues(JOBS_OPTION);
+    }
+
+    Integer getNumBundles() {
+        final Integer numBundles = 
Integer.valueOf(commandLine.getOptionValue(NUM_BUNDLES_OPTION, "0"));
+        Preconditions.checkArgument(numBundles >= 0,
+                NUM_BUNDLES_OPTION + " cannot be negative");
+        return numBundles;
+    }
+
+    Integer getNumCoordinators() {
+        final Integer numCoords = 
Integer.valueOf(commandLine.getOptionValue(NUM_COORDS_OPTION, "0"));
+        Preconditions.checkArgument(numCoords >= 0,
+                NUM_COORDS_OPTION + " cannot be negative");
+        return numCoords;
+    }
+
+    Integer getNumWorkflows() {
+        final Integer numWorkflows = 
Integer.valueOf(commandLine.getOptionValue(NUM_WORKFLOWS_OPTION, "0"));
+        Preconditions.checkArgument(numWorkflows >= 0,
+                NUM_WORKFLOWS_OPTION + " cannot be negative");
+        return numWorkflows;
+    }
+
+    String getOozieUrl() {
+        String url = commandLine.getOptionValue(OOZIE_OPTION);
+        if (url == null) {
+            url = System.getenv(OozieCLI.ENV_OOZIE_URL);
+            if (url == null) {
+                throw new IllegalArgumentException(
+                        "Oozie URL is not available neither in command option 
or in the environment");
+            }
+        }
+        return url;
+    }
+
+    boolean parseCommandLineArguments(String[] args) {
+        final CommandLineParser parser = new GnuParser();
+        final Options options = setupOptions();
+
+        try {
+            commandLine = parser.parse(options, args);
+        } catch (final ParseException pe) {
+            System.err.print("Error: " + pe.getMessage());
+            System.err.println();
+            final HelpFormatter formatter = new HelpFormatter();
+            formatter.printHelp("DiagBundleCollectorDriver",
+                    "A tool that collects a diagnostic bundle of information 
from Oozie",
+                    options, "", true);
+            return false;
+        }
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/oozie/blob/9e8598ea/tools/src/main/java/org/apache/oozie/tools/diag/DiagBundleCollectorDriver.java
----------------------------------------------------------------------
diff --git 
a/tools/src/main/java/org/apache/oozie/tools/diag/DiagBundleCollectorDriver.java
 
b/tools/src/main/java/org/apache/oozie/tools/diag/DiagBundleCollectorDriver.java
new file mode 100644
index 0000000..de8e8b1
--- /dev/null
+++ 
b/tools/src/main/java/org/apache/oozie/tools/diag/DiagBundleCollectorDriver.java
@@ -0,0 +1,156 @@
+/**
+ * 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.oozie.tools.diag;
+
+import com.google.common.io.Files;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.time.FastDateFormat;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.oozie.client.OozieClient;
+import org.apache.oozie.client.OozieClientException;
+import org.apache.oozie.service.HadoopAccessorService;
+import org.apache.oozie.service.ServiceException;
+import org.apache.oozie.service.Services;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Locale;
+import java.util.TimeZone;
+
+@SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = "Output 
directory is specified by user")
+public class DiagBundleCollectorDriver {
+    static final FastDateFormat DATE_FORMAT = 
FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss zzz",
+            TimeZone.getTimeZone("GMT"), Locale.US);
+    private String oozieURL;
+    private Integer numWorkflows;
+    private Integer numCoords;
+    private Integer numBundles;
+    private String[] jobIds;
+    private Integer maxChildActions;
+    private File outputDir;
+    private Configuration hadoopConfig;
+
+    public static void main(String[] args) throws Exception {
+        final DiagBundleCollectorDriver oozieDiagBundleCollector = new 
DiagBundleCollectorDriver();
+        System.exit(oozieDiagBundleCollector.run(args));
+    }
+
+    private int run(final String[] args) throws Exception {
+        if (!parseCommandLineArgs(args) || !setHadoopConfig()) {
+            return -1;
+        }
+
+        final DiagOozieClient client = new DiagOozieClient(oozieURL, null);
+        checkOozieConnection(client);
+
+        final File tempDir = Files.createTempDir();
+        System.out.println("Using Temporary Directory: " + tempDir);
+
+        try {
+            collectDiagInformation(numWorkflows, numCoords, numBundles, 
jobIds, maxChildActions, client, tempDir);
+            DiagBundleCompressor.compressDiagInformationToBundle(outputDir, 
tempDir);
+        } finally {
+            FileUtils.deleteDirectory(tempDir);
+        }
+
+        System.out.println();
+        return 0;
+    }
+
+    private boolean parseCommandLineArgs(final String[] args) throws 
IOException {
+        final ArgParser cliParser = new ArgParser();
+        if (!cliParser.parseCommandLineArguments(args)) {
+            return false;
+        }
+
+        oozieURL = cliParser.getOozieUrl();
+        numWorkflows = cliParser.getNumWorkflows();
+        numCoords = cliParser.getNumCoordinators();
+        numBundles = cliParser.getNumBundles();
+        jobIds = cliParser.getJobIds();
+        maxChildActions = cliParser.getMaxChildActions();
+        outputDir = cliParser.ensureOutputDir();
+
+        return true;
+    }
+
+    private void collectDiagInformation(final Integer numWorkflows,
+                                        final Integer numCoords,
+                                        final Integer numBundles,
+                                        final String[] jobIds,
+                                        final Integer maxChildActions,
+                                        final DiagOozieClient client,
+                                        final File tempDir) {
+        final ServerInfoCollector serverInfoCollector = new 
ServerInfoCollector(client);
+        serverInfoCollector.storeShareLibInfo(tempDir);
+        serverInfoCollector.storeServerConfiguration(tempDir);
+        serverInfoCollector.storeOsEnv(tempDir);
+        serverInfoCollector.storeJavaSystemProperties(tempDir);
+        serverInfoCollector.storeCallableQueueDump(tempDir);
+        serverInfoCollector.storeThreadDump(tempDir);
+
+        final MetricsCollector metricsCollector = new MetricsCollector(client);
+        metricsCollector.storeInstrumentationInfo(tempDir);
+        metricsCollector.storeMetrics(tempDir);
+
+        final AppInfoCollector workflowInfoCollector = new 
AppInfoCollector(hadoopConfig, client);
+        workflowInfoCollector.storeLastWorkflows(tempDir, numWorkflows, 
maxChildActions);
+        workflowInfoCollector.storeLastCoordinators(tempDir, numCoords, 
maxChildActions);
+        workflowInfoCollector.storeLastBundles(tempDir, numBundles, 
maxChildActions);
+        workflowInfoCollector.getSpecificJobs(tempDir, jobIds, 
maxChildActions);
+    }
+
+    private void checkOozieConnection(final OozieClient client) throws 
OozieClientException {
+        System.out.print("Checking Connection...");
+        client.validateWSVersion();
+        System.out.println("Done");
+    }
+
+    private boolean setHadoopConfig() {
+        final String oozieHome = System.getenv("OOZIE_HOME");
+        if (oozieHome == null) {
+            System.err.println("OOZIE_HOME environment variable is not set. 
Make sure you've set it to an absolute path.");
+            return false;
+        }
+
+        System.setProperty(Services.OOZIE_HOME_DIR, oozieHome);
+        try {
+            final Services services = initOozieServices();
+            final HadoopAccessorService hadoopAccessorService = 
services.get(HadoopAccessorService.class);
+            hadoopConfig = hadoopAccessorService.createConfiguration("*");
+
+        } catch (ServiceException e) {
+            System.err.printf("Could not initialize Hadoop configuration: 
%s%n", e.getMessage());
+            return false;
+        }
+        return true;
+    }
+
+    private Services initOozieServices() throws ServiceException {
+        final Services services = new Services();
+        services.getConf()
+                .set(Services.CONF_SERVICE_CLASSES, 
"org.apache.oozie.service.LiteWorkflowAppService,"
+                        + "org.apache.oozie.service.SchedulerService,"
+                        + "org.apache.oozie.service.HadoopAccessorService,"
+                        + "org.apache.oozie.service.ShareLibService");
+        services.init();
+        return services;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/oozie/blob/9e8598ea/tools/src/main/java/org/apache/oozie/tools/diag/DiagBundleCompressor.java
----------------------------------------------------------------------
diff --git 
a/tools/src/main/java/org/apache/oozie/tools/diag/DiagBundleCompressor.java 
b/tools/src/main/java/org/apache/oozie/tools/diag/DiagBundleCompressor.java
new file mode 100644
index 0000000..7612f0d
--- /dev/null
+++ b/tools/src/main/java/org/apache/oozie/tools/diag/DiagBundleCompressor.java
@@ -0,0 +1,43 @@
+/**
+ * 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.oozie.tools.diag;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import org.apache.oozie.util.IOUtils;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.zip.ZipOutputStream;
+
+@SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = "Output 
directory is specified by user")
+class DiagBundleCompressor {
+    private static void zip(final File inputDir, final File outputFile) throws 
IOException {
+        System.out.print("Creating Zip File: " + outputFile.getAbsolutePath() 
+ "...");
+        try (ZipOutputStream zos = new ZipOutputStream(new 
FileOutputStream(outputFile))) {
+            IOUtils.zipDir(inputDir, "/", zos);
+        }
+        System.out.println("Done");
+    }
+
+    static void compressDiagInformationToBundle(final File outputDir, final 
File tempDir) throws IOException {
+        final File zipFile = new File(outputDir, "oozie-diag-bundle-" + 
System.currentTimeMillis() + ".zip");
+        zip(tempDir, zipFile);
+    }
+}

http://git-wip-us.apache.org/repos/asf/oozie/blob/9e8598ea/tools/src/main/java/org/apache/oozie/tools/diag/DiagBundleEntryWriter.java
----------------------------------------------------------------------
diff --git 
a/tools/src/main/java/org/apache/oozie/tools/diag/DiagBundleEntryWriter.java 
b/tools/src/main/java/org/apache/oozie/tools/diag/DiagBundleEntryWriter.java
new file mode 100644
index 0000000..e98e0c6
--- /dev/null
+++ b/tools/src/main/java/org/apache/oozie/tools/diag/DiagBundleEntryWriter.java
@@ -0,0 +1,118 @@
+/**
+ * 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.oozie.tools.diag;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import org.apache.oozie.util.IOUtils;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+
+@SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = "Output 
directory is specified by user")
+public class DiagBundleEntryWriter implements Closeable {
+    private final Writer writer;
+
+    DiagBundleEntryWriter(final File parentDir, final String fileName) throws 
FileNotFoundException {
+        this(new FileOutputStream(new File(parentDir, fileName)));
+    }
+
+    private DiagBundleEntryWriter(final OutputStream ous) {
+        try {
+            this.writer = new OutputStreamWriter(ous, 
StandardCharsets.UTF_8.toString());
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    DiagBundleEntryWriter writeDateValue(final String key, final Date date) 
throws IOException {
+        if (date != null) {
+            writeStringValue(key, 
DiagBundleCollectorDriver.DATE_FORMAT.format(date));
+        } else {
+            writeStringValue(key, "null");
+        }
+        return this;
+    }
+
+    DiagBundleEntryWriter writeLongValue(final String key, long value) throws 
IOException {
+        writeStringValue(key, Long.toString(value));
+        return this;
+    }
+
+    DiagBundleEntryWriter writeIntValue(final String key, int value) throws 
IOException {
+        writeStringValue(key, Integer.toString(value));
+        return this;
+    }
+
+    DiagBundleEntryWriter writeStringValue(final String key, final String 
value) throws IOException {
+        writer.write(key);
+        if (value != null && !value.isEmpty()) {
+            writer.write(String.format("\"%s\"", value));
+        } else if (value == null){
+            writeNull();
+        } else {
+            writer.write("\"\"");
+        }
+        writer.write("\n");
+        return this;
+    }
+
+    DiagBundleEntryWriter writeString(final String s) throws IOException {
+        if (s == null) {
+            writeNull();
+        }
+        else {
+            writer.write(s);
+        }
+        return this;
+    }
+
+    DiagBundleEntryWriter writeNewLine() throws IOException {
+        writer.write("\n");
+        return this;
+    }
+
+    DiagBundleEntryWriter writeNull() throws IOException {
+        writer.write("null");
+        return this;
+    }
+
+    void flush() throws IOException {
+        writer.flush();
+    }
+
+    @Override
+    public void close() {
+        try {
+            writer.flush();
+        } catch (IOException e) {
+            System.err.printf("Could not persist data. Exception: %s%n", 
e.getMessage());
+        } finally {
+            IOUtils.closeSafely(writer);
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/oozie/blob/9e8598ea/tools/src/main/java/org/apache/oozie/tools/diag/DiagOozieClient.java
----------------------------------------------------------------------
diff --git 
a/tools/src/main/java/org/apache/oozie/tools/diag/DiagOozieClient.java 
b/tools/src/main/java/org/apache/oozie/tools/diag/DiagOozieClient.java
new file mode 100644
index 0000000..735d0c9
--- /dev/null
+++ b/tools/src/main/java/org/apache/oozie/tools/diag/DiagOozieClient.java
@@ -0,0 +1,52 @@
+/**
+ * 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.oozie.tools.diag;
+
+import org.apache.oozie.client.AuthOozieClient;
+import org.apache.oozie.client.OozieClientException;
+import org.apache.oozie.util.IOUtils;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+class DiagOozieClient extends AuthOozieClient {
+    DiagOozieClient(String oozieUrl, String authOption) {
+        super(oozieUrl, authOption);
+    }
+
+    // Oozie has a jsp page with a thread dump, and the Oozie client normally 
doesn't have a way of getting it,
+    // so we're doing that here; reusing all the fancy HTTP handling code in 
AuthOozieClient
+    void saveThreadDumpPage(File file) throws OozieClientException, 
IOException {
+        final URL url = new URL(super.getOozieUrl() + "admin/jvminfo.jsp");
+        final HttpURLConnection retryableConnection = 
createRetryableConnection(url, "GET");
+
+        if ((retryableConnection.getResponseCode() == 
HttpURLConnection.HTTP_OK)) {
+            try (InputStream is = retryableConnection.getInputStream();
+            final FileOutputStream os = new FileOutputStream(file)) {
+                IOUtils.copyStream(is, os);
+            }
+        } else {
+            throw new OozieClientException("HTTP error", 
retryableConnection.getResponseMessage());
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/oozie/blob/9e8598ea/tools/src/main/java/org/apache/oozie/tools/diag/MetricsCollector.java
----------------------------------------------------------------------
diff --git 
a/tools/src/main/java/org/apache/oozie/tools/diag/MetricsCollector.java 
b/tools/src/main/java/org/apache/oozie/tools/diag/MetricsCollector.java
new file mode 100644
index 0000000..4703b2c
--- /dev/null
+++ b/tools/src/main/java/org/apache/oozie/tools/diag/MetricsCollector.java
@@ -0,0 +1,142 @@
+/**
+ * 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.oozie.tools.diag;
+
+import org.apache.oozie.client.OozieClient;
+import org.apache.oozie.client.OozieClientException;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+import java.util.TreeMap;
+
+class MetricsCollector {
+    private final OozieClient client;
+
+    MetricsCollector(OozieClient client) {
+        this.client = client;
+    }
+
+    void storeMetrics(final File outputDir) {
+        try {
+            System.out.print("Getting Metrics...");
+            final OozieClient.Metrics metrics = client.getMetrics();
+
+            if (metrics == null) {
+                System.out.println("Skipping (Metrics are unavailable)");
+                return;
+            }
+
+            try (DiagBundleEntryWriter diagEntryWriter = new 
DiagBundleEntryWriter(outputDir, "metrics.txt")) {
+                diagEntryWriter.writeString("COUNTERS\n")
+                                 .writeString("--------\n");
+
+                final Map<String, Long> counters = new 
TreeMap<>(metrics.getCounters());
+                for (Map.Entry<String, Long> ent : counters.entrySet()) {
+                    diagEntryWriter.writeLongValue(ent.getKey() + " : ", 
ent.getValue()).flush();
+                }
+                diagEntryWriter.writeNewLine()
+                               .writeString("GAUGES\n")
+                               .writeString("------\n");
+
+                final Map<String, Object> gauges = new 
TreeMap<>(metrics.getGauges());
+                for (Map.Entry<String, Object> ent : gauges.entrySet()) {
+                    diagEntryWriter.writeStringValue(ent.getKey() + " : ", 
ent.getValue().toString());
+                }
+                diagEntryWriter.writeNewLine()
+                               .writeString("TIMERS\n")
+                               .writeString("------\n");
+
+                final Map<String, OozieClient.Metrics.Timer> timers = new 
TreeMap<>(metrics.getTimers());
+                for (Map.Entry<String, OozieClient.Metrics.Timer> ent : 
timers.entrySet()) {
+                    diagEntryWriter.writeString(ent.getKey())
+                                     .writeNewLine()
+                                     .writeString(ent.getValue().toString())
+                                     .writeNewLine();
+                }
+                diagEntryWriter.writeNewLine()
+                               .writeString("HISTOGRAMS\n")
+                               .writeString("----------\n");
+                final Map<String, OozieClient.Metrics.Histogram> histograms =
+                        new TreeMap<>(metrics.getHistograms());
+                for (Map.Entry<String, OozieClient.Metrics.Histogram> ent : 
histograms.entrySet()) {
+                    diagEntryWriter.writeString(ent.getKey())
+                                     .writeNewLine()
+                                     .writeString(ent.getValue().toString())
+                                     .writeNewLine();
+                }
+                System.out.println("Done");
+            }
+        } catch (OozieClientException | IOException e) {
+            System.err.printf("Exception occurred during the retrieval of 
Oozie metrics information: %s%n", e.getMessage());
+        }
+    }
+
+    void storeInstrumentationInfo(final File outputDir) {
+        try {
+            System.out.print("Getting Instrumentation...");
+            final OozieClient.Instrumentation instrumentation = 
client.getInstrumentation();
+            if (instrumentation == null) {
+                System.out.println("Skipping (Instrumentation is 
unavailable)");
+                return;
+            }
+
+            try (DiagBundleEntryWriter diagEntryWriter = new 
DiagBundleEntryWriter(outputDir, "instrumentation.txt")) {
+                diagEntryWriter.writeString("COUNTERS\n");
+                diagEntryWriter.writeString("--------\n");
+
+                final Map<String, Long> counters = new 
TreeMap<>(instrumentation.getCounters());
+                for (Map.Entry<String, Long> ent : counters.entrySet()) {
+                    diagEntryWriter.writeLongValue(ent.getKey() + " : ", 
ent.getValue()).flush();
+                }
+                diagEntryWriter.writeNewLine();
+                diagEntryWriter.writeString("VARIABLES\n");
+                diagEntryWriter.writeString("---------\n");
+
+                final Map<String, Object> variables = new 
TreeMap<>(instrumentation.getVariables());
+                for (Map.Entry<String, Object> ent : variables.entrySet()) {
+                    diagEntryWriter.writeStringValue(ent.getKey() + " : ", 
ent.getValue().toString());
+                }
+                diagEntryWriter.writeNewLine();
+                diagEntryWriter.writeString("SAMPLERS\n");
+                diagEntryWriter.writeString("---------\n");
+
+                final Map<String, Double> samplers = new 
TreeMap<>(instrumentation.getSamplers());
+                for (Map.Entry<String, Double> ent : samplers.entrySet()) {
+                    diagEntryWriter.writeStringValue(ent.getKey() + " : ", 
ent.getValue().toString());
+                }
+                diagEntryWriter.writeNewLine();
+                diagEntryWriter.writeString("TIMERS\n");
+                diagEntryWriter.writeString("---------\n");
+
+                final Map<String, OozieClient.Instrumentation.Timer> timers =
+                        new TreeMap<>(instrumentation.getTimers());
+                for (Map.Entry<String, OozieClient.Instrumentation.Timer> ent 
: timers.entrySet()) {
+                    diagEntryWriter.writeString(ent.getKey());
+                    diagEntryWriter.writeNewLine();
+                    diagEntryWriter.writeString(ent.getValue().toString());
+                    diagEntryWriter.writeNewLine();
+                }
+                System.out.println("Done");
+            }
+        } catch (OozieClientException | IOException e) {
+            System.err.printf("Exception occurred during the retrieval of 
Oozie instrumentation information: %s%n", e.getMessage());
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/oozie/blob/9e8598ea/tools/src/main/java/org/apache/oozie/tools/diag/OozieLauncherLogFetcher.java
----------------------------------------------------------------------
diff --git 
a/tools/src/main/java/org/apache/oozie/tools/diag/OozieLauncherLogFetcher.java 
b/tools/src/main/java/org/apache/oozie/tools/diag/OozieLauncherLogFetcher.java
new file mode 100644
index 0000000..d12e963
--- /dev/null
+++ 
b/tools/src/main/java/org/apache/oozie/tools/diag/OozieLauncherLogFetcher.java
@@ -0,0 +1,135 @@
+/**
+ * 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.oozie.tools.diag;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileContext;
+import org.apache.hadoop.fs.FileStatus;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.fs.RemoteIterator;
+import org.apache.hadoop.yarn.api.records.ApplicationId;
+import org.apache.hadoop.yarn.conf.YarnConfiguration;
+import org.apache.hadoop.yarn.logaggregation.AggregatedLogFormat;
+
+import java.io.DataInputStream;
+import java.io.EOFException;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintStream;
+
+// TODO: once OOZIE-2983 ("Stream the Launcher AM Logs") is done, remove it.
+public class OozieLauncherLogFetcher {
+    private static final String TMP_FILE_SUFFIX = ".tmp";
+    final private Configuration hadoopConfig;
+
+    public OozieLauncherLogFetcher(final Configuration hadoopConfig) {
+        this.hadoopConfig = hadoopConfig;
+    }
+
+    // Borrowed code from org.apache.hadoop.yarn.logaggregation.LogCLIHelpers
+    private static void logDirNotExist(String remoteAppLogDir) {
+        System.out.println(remoteAppLogDir + "does not exist.");
+        System.out.println("Log aggregation has not completed or is not 
enabled.");
+    }
+    // Borrowed code from org.apache.hadoop.yarn.logaggregation.LogCLIHelpers
+    private static void emptyLogDir(String remoteAppLogDir) {
+        System.out.println(remoteAppLogDir + "does not have any log files.");
+    }
+    // Borrowed code from 
org.apache.hadoop.yarn.logaggregation.LogAggregationUtils
+    public static String getRemoteNodeLogDirSuffix(Configuration conf) {
+        return conf.get(YarnConfiguration.NM_REMOTE_APP_LOG_DIR_SUFFIX, 
YarnConfiguration.DEFAULT_NM_REMOTE_APP_LOG_DIR_SUFFIX);
+    }
+    // Borrowed code from 
org.apache.hadoop.yarn.logaggregation.LogAggregationUtils
+    public static Path getRemoteLogSuffixedDir(Path remoteRootLogDir, String 
user, String suffix) {
+        return suffix != null && !suffix.isEmpty() ? new 
Path(getRemoteLogUserDir(remoteRootLogDir, user), suffix) :
+                getRemoteLogUserDir(remoteRootLogDir, user);
+    }
+    // Borrowed code from 
org.apache.hadoop.yarn.logaggregation.LogAggregationUtils
+    public static Path getRemoteLogUserDir(Path remoteRootLogDir, String user) 
{
+        return new Path(remoteRootLogDir, user);
+    }
+
+    // Borrowed code from 
org.apache.hadoop.yarn.logaggregation.LogAggregationUtils
+    public static Path getRemoteAppLogDir(Path remoteRootLogDir, ApplicationId 
appId, String user, String suffix) {
+        return new Path(getRemoteLogSuffixedDir(remoteRootLogDir, user, 
suffix), appId.toString());
+    }
+
+    // Borrowed code from org.apache.hadoop.yarn.logaggregation.LogCLIHelpers
+    public int dumpAllContainersLogs(ApplicationId appId, String appOwner, 
PrintStream out) throws IOException {
+        Path remoteRootLogDir = new 
Path(hadoopConfig.get(YarnConfiguration.NM_REMOTE_APP_LOG_DIR,
+                YarnConfiguration.DEFAULT_NM_REMOTE_APP_LOG_DIR));
+        String logDirSuffix = getRemoteNodeLogDirSuffix(hadoopConfig);
+        Path remoteAppLogDir = getRemoteAppLogDir(remoteRootLogDir, appId, 
appOwner, logDirSuffix);
+
+        RemoteIterator nodeFiles;
+        try {
+            Path qualifiedLogDir = 
FileContext.getFileContext(hadoopConfig).makeQualified(remoteAppLogDir);
+            nodeFiles = FileContext.getFileContext(qualifiedLogDir.toUri(), 
hadoopConfig).listStatus(remoteAppLogDir);
+        } catch (FileNotFoundException fileNotFoundException) {
+            logDirNotExist(remoteAppLogDir.toString());
+            return -1;
+        }
+
+        boolean foundAnyLogs = false;
+
+        while(true) {
+            FileStatus thisNodeFile;
+            do {
+                if (!nodeFiles.hasNext()) {
+                    if (!foundAnyLogs) {
+                        emptyLogDir(remoteAppLogDir.toString());
+                        return -1;
+                    }
+
+                    return 0;
+                }
+
+                thisNodeFile = (FileStatus)nodeFiles.next();
+            } 
while(thisNodeFile.getPath().getName().endsWith(TMP_FILE_SUFFIX));
+
+            AggregatedLogFormat.LogReader reader = new 
AggregatedLogFormat.LogReader(hadoopConfig, thisNodeFile.getPath());
+
+            try {
+                AggregatedLogFormat.LogKey key = new 
AggregatedLogFormat.LogKey();
+                DataInputStream valueStream = reader.next(key);
+
+                while(valueStream != null) {
+                    String containerString = "\n\nContainer: " + key + " on " 
+ thisNodeFile.getPath().getName();
+                    out.println(containerString);
+                    out.println(StringUtils.repeat("=", 
containerString.length()));
+
+                    while(true) {
+                        try {
+                            
AggregatedLogFormat.LogReader.readAContainerLogsForALogType(valueStream, out,
+                                    thisNodeFile.getModificationTime());
+                            foundAnyLogs = true;
+                        } catch (EOFException eofException) {
+                            key = new AggregatedLogFormat.LogKey();
+                            valueStream = reader.next(key);
+                            break;
+                        }
+                    }
+                }
+            } finally {
+                reader.close();
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/oozie/blob/9e8598ea/tools/src/main/java/org/apache/oozie/tools/diag/ServerInfoCollector.java
----------------------------------------------------------------------
diff --git 
a/tools/src/main/java/org/apache/oozie/tools/diag/ServerInfoCollector.java 
b/tools/src/main/java/org/apache/oozie/tools/diag/ServerInfoCollector.java
new file mode 100644
index 0000000..e071a80
--- /dev/null
+++ b/tools/src/main/java/org/apache/oozie/tools/diag/ServerInfoCollector.java
@@ -0,0 +1,141 @@
+/**
+ * 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.oozie.tools.diag;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.oozie.client.OozieClientException;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.List;
+import java.util.Map;
+
+class ServerInfoCollector {
+
+    private final DiagOozieClient client;
+
+    ServerInfoCollector(final DiagOozieClient client) {
+        this.client = client;
+    }
+
+    void storeShareLibInfo(final File outputDir) {
+        try {
+            System.out.print("Getting Sharelib Information...");
+            final String[] libs = client.listShareLib(null).split("\n");
+
+            try (DiagBundleEntryWriter configEntryWriter = new 
DiagBundleEntryWriter(outputDir, "sharelib.txt")) {
+
+                // Skip i=0 because it's always "[Available Sharelib]"
+                for (int i = 1; i < libs.length; i++) {
+                    String files = client.listShareLib(libs[i]);
+                    configEntryWriter.writeString(files);
+                    configEntryWriter.writeNewLine();
+                }
+            }
+            System.out.println("Done");
+        } catch (OozieClientException | IOException e) {
+            System.err.printf("Exception occurred during the retrieval of 
ShareLib information: %s%n", e.getMessage());
+        }
+    }
+
+    void storeJavaSystemProperties(final File outputDir) {
+        try {
+            System.out.print("Getting Java System Properties...");
+            final Map<String, String> javaSysProps = 
client.getJavaSystemProperties();
+
+            try (DiagBundleEntryWriter configEntryWriter = new 
DiagBundleEntryWriter(outputDir, "java-sys-props.txt")) {
+                for (Map.Entry<String, String> ent : javaSysProps.entrySet()) {
+                    configEntryWriter.writeStringValue(ent.getKey() + " : ", 
ent.getValue());
+                }
+            }
+            System.out.println("Done");
+        } catch (OozieClientException | IOException e) {
+            System.err.printf("Exception occurred during the retrieval of Java 
system property settings for the Oozie server:" +
+                    " %s%n", e.getMessage());
+        }
+    }
+
+    void storeOsEnv(final File outputDir) {
+        try {
+            System.out.print("Getting OS Environment Variables...");
+            final Map<String, String> osEnv = client.getOSEnv();
+
+            try (DiagBundleEntryWriter configEntryWriter = new 
DiagBundleEntryWriter(outputDir,"os-env-vars.txt")) {
+                for (Map.Entry<String, String> ent : osEnv.entrySet()) {
+                    configEntryWriter.writeStringValue(ent.getKey() + " : ", 
ent.getValue());
+                }
+            }
+
+            System.out.println("Done");
+        } catch (OozieClientException | IOException e) {
+            System.err.printf("Exception occurred during the retrieval of 
environment variable settings for the Oozie server:%s%n",
+                    e.getMessage());
+        }
+    }
+
+    void storeServerConfiguration(File outputDir) {
+        try {
+            System.out.print("Getting Configuration...");
+            final Map<String, String> serverConfigMap = 
client.getServerConfiguration();
+            final Configuration serverConfig = new Configuration(false);
+            for (Map.Entry<String, String> ent : serverConfigMap.entrySet()) {
+                serverConfig.set(ent.getKey(), ent.getValue());
+            }
+
+            try (OutputStream outputStream = new FileOutputStream(
+                    new File(outputDir,  "effective-oozie-site.xml"))) {
+                serverConfig.writeXml(outputStream);
+            }
+            System.out.println("Done");
+        } catch (OozieClientException | IOException e) {
+            System.err.printf("Exception occurred during the retrieval of 
effective Oozie server configuration " +
+                    "\"oozie-site.xml\": %s%n", e.getMessage());
+        }
+    }
+
+    void storeThreadDump(final File outputDir) {
+        try {
+            System.out.print("Getting Thread Dump...");
+            client.saveThreadDumpPage(new File(outputDir, "thread-dump.html"));
+            System.out.println("Done");
+        } catch (OozieClientException | IOException e) {
+            System.err.printf("Exception occurred during the retrieval of 
Oozie server thread dump: %s%n", e.getMessage());
+        }
+    }
+
+    void storeCallableQueueDump(final File outputDir) {
+        try {
+            System.out.print("Getting Queue Dump...");
+            final List<String> queueDump = client.getQueueDump();
+
+            try (DiagBundleEntryWriter configEntryWriter = new 
DiagBundleEntryWriter(outputDir, "queue-dump.txt")) {
+                for (String d : queueDump) {
+                    configEntryWriter.writeString(d);
+                    configEntryWriter.writeNewLine();
+                }
+            }
+
+            System.out.println("Done");
+        } catch (OozieClientException | IOException e) {
+            System.err.println("Exception occurred during the retrieval of 
Oozie queue dump: " + e.getMessage());
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/oozie/blob/9e8598ea/tools/src/test/java/org/apache/oozie/tools/diag/TestAppInfoCollector.java
----------------------------------------------------------------------
diff --git 
a/tools/src/test/java/org/apache/oozie/tools/diag/TestAppInfoCollector.java 
b/tools/src/test/java/org/apache/oozie/tools/diag/TestAppInfoCollector.java
new file mode 100644
index 0000000..e6ee6b0
--- /dev/null
+++ b/tools/src/test/java/org/apache/oozie/tools/diag/TestAppInfoCollector.java
@@ -0,0 +1,167 @@
+/**
+ * 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.oozie.tools.diag;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.Path;
+import org.apache.oozie.client.*;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+import static junit.framework.TestCase.assertTrue;
+import static 
org.apache.oozie.tools.diag.TestServerInfoCollector.assertFileContains;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doReturn;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TestAppInfoCollector {
+    private final Configuration conf= new Configuration(false);
+    private File testFolder;
+    private AppInfoCollector appInfoCollector;
+
+    @Rule public final TemporaryFolder folder = new TemporaryFolder();
+    @Mock private DiagOozieClient mockOozieClient;
+    @Mock private WorkflowJob wfJob;
+    @Mock private WorkflowAction wfAction;
+    @Mock private CoordinatorJob coordJob;
+    @Mock private CoordinatorAction coordinatorAction;
+    @Mock private BundleJob bundleJob;
+
+    @Before
+    public void setup() throws IOException {
+        conf.set("yarn.resourcemanager.address", "test:1234");
+        testFolder = folder.newFolder();
+        appInfoCollector = new AppInfoCollector(conf, mockOozieClient);
+    }
+
+    @Test
+    public void testStoreLastWorkflows() throws Exception {
+        final List<WorkflowJob> wfJobs = Arrays.asList(wfJob);
+        doReturn(wfJobs).when(mockOozieClient).getJobsInfo(null, 0, 1);
+        doReturn(wfJob).when(mockOozieClient).getJobInfo(anyString());
+
+        final String wfName = "0000000-170926142250283-oozie-test-W";
+        doReturn(wfName).when(wfJob).getId();
+        doReturn("map-reduce-wf").when(wfJob)
+                .getAppName();
+        
doReturn("hdfs://localhost:9000/user/test/examples/apps/map-reduce/workflow.xml").when(wfJob)
+                .getAppPath();
+        doReturn("test").when(wfJob).getUser();
+        doReturn(null).when(wfJob).getAcl();
+        doReturn(WorkflowJob.Status.SUCCEEDED).when(wfJob).getStatus();
+        doReturn("").when(wfJob).getConsoleUrl();
+        
doReturn("http://0.0.0.0:11000/oozie?job=0000000-170926142250283-oozie-asas-W";).when(wfJob)
+                .getExternalId();
+
+        doReturn(null).when(wfJob).getParentId();
+
+        final SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd 
hh:mm:ss zzz");
+        final Date startDate = formatter.parse("2017-09-26 12:22:57 GMT");
+        doReturn(startDate).when(wfJob).getCreatedTime();
+
+        final Date endDate = formatter.parse("2017-09-26 12:32:57 GMT");
+        doReturn(endDate).when(wfJob).getEndTime();
+        doReturn(endDate).when(wfJob).getLastModifiedTime();
+        doReturn(startDate).when(wfJob).getStartTime();
+        doReturn(0).when(wfJob).getRun();
+
+        
doReturn("0000000-170926142250283-oozie-asas-W@:start:").when(wfAction).getId();
+        doReturn(":start:").when(wfAction).getName();
+        doReturn(":START:").when(wfAction).getType();
+        doReturn(WorkflowAction.Status.OK).when(wfAction).getStatus();
+        doReturn("mr-node").when(wfAction).getTransition();
+        doReturn(startDate).when(wfAction).getStartTime();
+        doReturn(endDate).when(wfAction).getEndTime();
+        doReturn(null).when(wfAction).getErrorCode();
+        doReturn(null).when(wfAction).getErrorMessage();
+        doReturn("").when(wfAction).getConsoleUrl();
+        doReturn("").when(wfAction).getTrackerUri();
+        doReturn(null).when(wfAction).getExternalChildIDs();
+        doReturn("").when(wfAction).getExternalId();
+        doReturn("OK").when(wfAction).getExternalStatus();
+        doReturn(null).when(wfAction).getData();
+        doReturn(null).when(wfAction).getStats();
+        doReturn(null).when(wfAction).getCred();
+        doReturn(0).when(wfAction).getRetries();
+        doReturn(10).when(wfAction).getUserRetryInterval();
+        doReturn(0).when(wfAction).getUserRetryCount();
+        doReturn(0).when(wfAction).getUserRetryMax();
+
+        final List<WorkflowAction> wfActions = Arrays.asList(wfAction);
+        doReturn(wfActions).when(wfJob).getActions();
+
+        appInfoCollector.storeLastWorkflows(testFolder, 1, 1);
+
+        final File infoOut = new File (testFolder,  wfName + Path.SEPARATOR+ 
"info.txt");
+        assertTrue(infoOut.exists());
+        assertFileContains(infoOut, wfName);
+    }
+
+    @Test
+    public void testStoreCoordinators() throws Exception {
+        final List<CoordinatorJob> coordJobs = Arrays.asList(coordJob);
+        doReturn(coordJobs).when(mockOozieClient).getCoordJobsInfo(null, 0, 1);
+
+        final String coordId = "0000000-170926142250283-oozie-test-C";
+        doReturn(coordId).when(coordJob).getId();
+        doReturn(Job.Status.RUNNING).when(coordJob).getStatus();
+        
doReturn(CoordinatorJob.Execution.FIFO).when(coordJob).getExecutionOrder();
+
+        final List<CoordinatorAction> coordinatorActions = 
Arrays.asList(coordinatorAction);
+        
doReturn(CoordinatorAction.Status.KILLED).when(coordinatorAction).getStatus();
+        doReturn(coordinatorActions).when(coordJob).getActions();
+        doReturn(coordJob).when(mockOozieClient).getCoordJobInfo(anyString());
+        doReturn(CoordinatorJob.Timeunit.MINUTE).when(coordJob).getTimeUnit();
+
+        appInfoCollector.storeLastCoordinators(testFolder,1,1);
+
+        final File coordInfoOut = new File(testFolder, coordId + 
Path.SEPARATOR + "info.txt");
+        assertTrue(coordInfoOut.exists());
+        assertFileContains(coordInfoOut, coordId);
+    }
+
+    @Test
+    public void testStoreLastBundles() throws Exception {
+        final List<BundleJob> bundleJobs = Arrays.asList(bundleJob);
+        doReturn(bundleJobs).when(mockOozieClient).getBundleJobsInfo(null, 0, 
1);
+
+        final String bundleId = "0000027-110322105610515-oozie-chao-B";
+        doReturn(bundleId).when(bundleJob).getId();
+        doReturn(Job.Status.RUNNING).when(bundleJob).getStatus();
+        
doReturn(bundleJob).when(mockOozieClient).getBundleJobInfo(anyString());
+
+        appInfoCollector.storeLastBundles(testFolder,1,1);
+
+        final File bundleInfoOut = new File(testFolder, bundleId + 
Path.SEPARATOR + "info.txt");
+        assertTrue(bundleInfoOut.exists());
+        assertFileContains(bundleInfoOut, bundleId);
+    }
+}
\ No newline at end of file

Reply via email to