Browse Source

YARN-1806. Add ThreadDump Option in YARN UI2 to fetch for running containers

Contributed by Siddharth Ahuja. Reviewed by Akhil PB.
Prabhu Joseph 4 năm trước cách đây
mục cha
commit
75db5526b5

+ 88 - 0
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/adapters/yarn-container-threaddump.js

@@ -0,0 +1,88 @@
+/**
+ * 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.
+ */
+
+import RESTAbstractAdapter from './restabstract';
+
+export default RESTAbstractAdapter.extend({
+  address: "rmWebAddress",
+  restNameSpace: "cluster",
+
+  handleResponse(status, headers, payload, requestData) {
+    // If the user is not authorized to signal a threaddump for a container,
+    // the response contains a RemoteException with a 403 (Forbidden) status
+    // code. Extract out the error message from the RemoteException in this
+    // case.
+    // If the status is '0' or empty, it is symptomatic of the YARN role not
+    // available or able to respond or a network timeout/firewall issue.
+    if (status === 403)  {
+      if (payload
+          && typeof payload === 'object'
+          && payload.RemoteException
+          && payload.RemoteException.message) {
+        return new Error(payload.RemoteException.message);
+      }
+    } else if (status === 0 && payload === "") {
+      return new Error("Not able to connect to YARN!");
+    }
+
+    return payload;
+  },
+
+  /**
+   * Set up the POST request to use the signalToContainer REST API
+   * to signal a thread dump for a running container to RM.
+   */
+  signalContainerForThreaddump(request, containerId, user) {
+    var url = this.buildURL();
+    if (user && containerId) {
+      url += "/containers" + "/" + containerId + "/signal"
+            + "/OUTPUT_THREAD_DUMP" + "?user.name=" + user;
+    }
+    return this.ajax(url, "POST", {data: request});
+  },
+
+  ajax(url, method, hash) {
+    hash = {};
+    hash.crossDomain = true;
+    hash.xhrFields = {withCredentials: true};
+    hash.targetServer = "RM";
+    return this._super(url, method, hash);
+  },
+
+  /**
+   * Override options so that result is not expected to be JSON
+   */
+  ajaxOptions: function (url, type, options) {
+    var hash = options || {};
+    hash.url = url;
+    hash.type = type;
+    // Make sure jQuery does not try to convert response to JSON.
+    hash.dataType = 'text';
+    hash.context = this;
+
+    var headers = Ember.get(this, 'headers');
+    if (headers !== undefined) {
+      hash.beforeSend = function (xhr) {
+        Object.keys(headers).forEach(function (key) {
+          return xhr.setRequestHeader(key, headers[key]);
+        });
+      };
+    }
+    return hash;
+  }
+});

+ 94 - 0
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/adapters/yarn-node-container-log.js

@@ -0,0 +1,94 @@
+/**
+ * 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.
+ */
+
+import DS from 'ember-data';
+import Ember from 'ember';
+import Converter from 'yarn-ui/utils/converter';
+
+/**
+ * REST URL's response when fetching container logs will be
+ * in plain text format and not JSON.
+ */
+export default DS.RESTAdapter.extend({
+  headers: {
+    Accept: 'text/plain'
+  },
+
+  host: Ember.computed("address", function () {
+    return this.get(`hosts.localBaseAddress`);
+  }),
+
+  namespace: Ember.computed("restNameSpace", function () {
+    return this.get(`env.app.namespaces.node`);
+  }),
+
+  urlForQueryRecord(query) {
+    var url = this._buildURL();
+    url = url.replace("{nodeAddress}", query.nodeHttpAddr) + "/containerlogs/"
+            + query.containerId + "/" + query.fileName;
+    return url;
+  },
+
+  queryRecord: function (store, type, query) {
+    var url = this.urlForQueryRecord(query);
+    // Query params not required.
+    query = null;
+    console.log(url);
+    return this.ajax(url, 'GET', { data: query });
+  },
+
+  ajax(url, method, hash) {
+    hash = hash || {};
+    hash.crossDomain = true;
+    hash.xhrFields = {withCredentials: true};
+    hash.targetServer = "NM";
+    return this._super(url, method, hash);
+  },
+
+  /**
+   * Override options so that result is not expected to be JSON
+   */
+  ajaxOptions: function (url, type, options) {
+    var hash = options || {};
+    hash.url = url;
+    hash.type = type;
+    // Make sure jQuery does not try to convert response to JSON.
+    hash.dataType = 'text';
+    hash.context = this;
+
+    var headers = Ember.get(this, 'headers');
+    if (headers !== undefined) {
+      hash.beforeSend = function (xhr) {
+        Object.keys(headers).forEach(function (key) {
+          return xhr.setRequestHeader(key, headers[key]);
+        });
+      };
+    }
+    return hash;
+  },
+
+  handleResponse(status, headers, payload, requestData) {
+    // If the status is '0' or empty, it is symptomatic of the YARN role not
+    // available or able to respond or a network timeout/firewall issue.
+    if (status === 0 && payload === "")  {
+        return new Error("Not able to connect to YARN!");
+    }
+
+    return payload;
+  }
+});

+ 281 - 0
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/controllers/yarn-app/threaddump.js

@@ -0,0 +1,281 @@
+/**
+ * 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.
+ */
+
+import Ember from 'ember';
+import Constants from 'yarn-ui/constants';
+
+export default Ember.Controller.extend({
+  queryParams: ["service", "attempt", "containerid"],
+  service: undefined,
+  attempt: undefined,
+  containerid: undefined,
+
+  selectedAttemptId: "",
+  attemptContainerList: null,
+  selectedContainerId: "",
+  selectedLogFileName: "",
+  containerLogFiles: null,
+  selectedContainerThreaddumpContent: "",
+  containerNodeMappings: [],
+  threaddumpErrorMessage: "",
+  stdoutLogsFetchFailedError: "",
+  appAttemptToStateMappings: [],
+
+  _isLoadingTopPanel: false,
+  _isLoadingBottomPanel: false,
+  _isThreaddumpAttemptFailed: false,
+  _isStdoutLogsFetchFailed: false,
+
+  initializeSelect: function(selector = ".js-fetch-attempt-containers") {
+    Ember.run.schedule("afterRender", this, function() {
+      $(selector).select2({ width: "350px", multiple: false });
+    });
+  },
+
+  actions: {
+    showContainersForAttemptId(attemptId, containerId = "") {
+      this.set("selectedAttemptId", "");
+      if (attemptId) {
+        this.set("_isLoadingTopPanel", true);
+        this.set("selectedAttemptId", attemptId);
+        this.fetchRunningContainersForAttemptId(attemptId)
+          .then(hash => {
+            let containers = null;
+            let containerIdArr = {};
+            var containerNodeMapping = null;
+            var nodeHttpAddress = null;
+
+            // Getting RUNNING containers from the RM first.
+            if (
+              hash.rmContainers.get("length") > 0 &&
+              hash.rmContainers.get("content")
+            ) {
+                // Create a container-to-NMnode mapping for later use.
+                hash.rmContainers.get("content").forEach(
+                  function(containerInfo) {
+                    nodeHttpAddress = containerInfo._data.nodeHttpAddress;
+                    nodeHttpAddress = nodeHttpAddress
+                                        .replace(/(^\w+:|^)\/\//, '');
+                      containerNodeMapping = Ember.Object.create({
+                        name: containerInfo.id,
+                        value: nodeHttpAddress
+                      });
+                      this.containerNodeMappings.push(containerNodeMapping);
+                      containerIdArr[containerInfo.id] = true;
+                }.bind(this));
+
+                containers = (containers || []).concat(
+                  hash.rmContainers.get("content")
+                );
+            }
+
+            this.set("attemptContainerList", containers);
+            this.initializeSelect(".js-fetch-threaddump-containers");
+
+            if (containerId) {
+              this.send("showThreaddumpForContainer", containerId);
+            }
+          })
+          .finally(() => {
+            this.set("_isLoadingTopPanel", false);
+          });
+      } else {
+        this.set("attemptContainerList", null);
+        this.set("selectedContainerId", "");
+        this.set("containerLogFiles", null);
+        this.set("selectedLogFileName", "");
+        this.set("selectedContainerThreaddumpContent", "");
+        this.set("containerNodeMappings", []);
+      }
+    },
+
+    showThreaddumpForContainer(containerIdForThreaddump) {
+      Ember.$("#logContentTextArea1234554321").val("");
+      this.set("showFullThreaddump", false);
+      this.set("selectedContainerId", containerIdForThreaddump);
+      this.set("_isLoadingBottomPanel", true);
+      this.set("_isStdoutLogsFetchFailed", false);
+      this.set("stdoutLogsFetchFailedError", "");
+      this.set("_isThreaddumpAttemptFailed", false);
+      this.set("threaddumpErrorMessage", "");
+
+      if (containerIdForThreaddump) {
+        this.set("selectedContainerThreaddumpContent", "");
+
+        this.fetchThreaddumpForContainer(containerIdForThreaddump)
+          .then(function() {
+            Ember.Logger.log("Threaddump attempt has been successful!");
+
+            var currentContainerId = null;
+            var currentContainerNode = null;
+            var nodeForContainerThreaddump = null;
+
+            // Fetch the NM node for the selected container from the
+            // mappings stored above when
+            this.containerNodeMappings.forEach(function(item) {
+              currentContainerId = item.name;
+              currentContainerNode = item.value;
+
+              if ((currentContainerId === containerIdForThreaddump)
+                    && nodeForContainerThreaddump == null) {
+                nodeForContainerThreaddump = currentContainerNode;
+              }
+            });
+
+            if (nodeForContainerThreaddump) {
+              // Fetch stdout logs after 1.2 seconds of a successful POST
+              // request to RM. This is to allow for sufficient time for the NM
+              // to heartbeat into RM (default of 1 second) whereupon it will
+              // receive RM's signal to post a threaddump that will be captured
+              // in the stdout logs of the container. The extra 200ms is to
+              // allow for any network/IO delays.
+              Ember.run.later((function() {
+                this.fetchLogsForContainer(nodeForContainerThreaddump,
+                                                containerIdForThreaddump,
+                                                "stdout")
+                .then(
+                  hash => {
+                   this.set("selectedContainerThreaddumpContent",
+                              hash.logs.get('logs').trim());
+                }.bind(this))
+                .catch(function(error) {
+                  this.set("_isStdoutLogsFetchFailed", true);
+                  this.set("stdoutLogsFetchFailedError", error);
+                }.bind(this))
+                .finally(function() {
+                  this.set("_isLoadingBottomPanel", false);
+                }.bind(this));
+              }.bind(this)), 1200);
+            }
+          }.bind(this), function(error) {
+            this.set("_isThreaddumpAttemptFailed", true);
+            this.set("threaddumpErrorMessage", error);
+            this.set("_isLoadingBottomPanel", false);
+          }.bind(this)).finally(function() {
+            this.set("selectedContainerThreaddumpContent", "");
+          }.bind(this));
+      } else {
+        this.set("selectedContainerId", "");
+        this.set("selectedContainerThreaddumpContent", "");
+      }
+    }
+  },
+
+  // Set up the running attempt for the first time threaddump button is clicked.
+  runningAttempt: Ember.computed("model.attempts", function() {
+    let attempts = this.get("model.attempts");
+    let runningAttemptId = null;
+
+    if (attempts && attempts.get("length") && attempts.get("content")) {
+      attempts.get("content").forEach(function(appAttempt) {
+        if (appAttempt._data.state === "RUNNING") {
+          runningAttemptId = appAttempt._data.appAttemptId;
+        }
+      });
+    }
+
+    return runningAttemptId;
+  }),
+
+  /**
+   * Create and send fetch running containers request.
+   */
+  fetchRunningContainersForAttemptId(attemptId) {
+    let request = {};
+
+    request["rmContainers"] = this.store
+      .query("yarn-container", {
+        app_attempt_id: attemptId
+      })
+      .catch(function(error) {
+        return Ember.A();
+      });
+
+    return Ember.RSVP.hash(request);
+  },
+
+  /**
+   * Create and send fetch logs request for a selected container, from a
+   * specific node.
+   */
+  fetchLogsForContainer(nodeForContainer, containerId, logFile) {
+    let request = {};
+
+    request["logs"] = this.store
+      .queryRecord("yarn-node-container-log", {
+        nodeHttpAddr: nodeForContainer,
+        containerId: containerId,
+        fileName: logFile
+      })
+
+    return Ember.RSVP.hash(request);
+  },
+
+  /**
+   * Send out a POST request to RM to signal a threaddump for the selected
+   * container for the currently logged-in user.
+   */
+  fetchThreaddumpForContainer(containerId) {
+    var adapter = this.store.adapterFor("yarn-container-threaddump");
+
+    let requestedUser = "";
+    if (this.model
+        && this.model.userInfo
+        && this.model.userInfo.get('firstObject')) {
+      requestedUser = this.model.userInfo
+                        .get('firstObject')
+                        .get('requestedUser');
+    }
+
+    return adapter.signalContainerForThreaddump({}, containerId, requestedUser);
+  },
+
+  /**
+   * Reset attributes after a refresh.
+   */
+  resetAfterRefresh() {
+    this.set("selectedAttemptId", "");
+    this.set("attemptContainerList", null);
+    this.set("selectedContainerId", "");
+    this.set("selectedLogFileName", "");
+    this.set("containerLogFiles", null);
+    this.set("selectedContainerThreaddumpContent", "");
+    this.set("containerNodeMappings", []);
+    this.set("_isThreaddumpAttemptFailed", false);
+    this.set("threaddumpErrorMessage", "");
+    this.set("_isStdoutLogsFetchFailed", false);
+    this.set("stdoutLogsFetchFailedError", "");
+    this.set("appAttemptToStateMappings", []);
+  },
+
+  // Set the threaddump content in the content area.
+  showThreaddumpContent: Ember.computed(
+    "selectedContainerThreaddumpContent",
+    function() {
+      return this.get("selectedContainerThreaddumpContent");
+    }
+  ),
+
+  /**
+   * Check if the application for the container whose threaddump is being attempted
+   * is even running.
+   */
+  isRunningApp: Ember.computed('model.app.state', function() {
+    return this.get('model.app.state') === "RUNNING";
+  })
+});

+ 22 - 0
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/mixins/app-attempt.js

@@ -34,6 +34,17 @@ export default Ember.Mixin.create({
     });
   },
 
+  fetchAppInfoFromRM(appId, store) {
+    return new Ember.RSVP.Promise(function(resolve, reject) {
+      store.find('yarn-app', appId).then(function(rmApp) {
+        resolve(rmApp);
+      }, function() {
+          console.error('Error:', 'Application not found in RM for appId: ' + appId);
+          reject(null);
+      });
+    });
+  },
+
   fetchAttemptInfoFromRMorATS(attemptId, store) {
     return new Ember.RSVP.Promise(function(resolve, reject) {
       store.findRecord('yarn-app-attempt', attemptId, {reload: true}).then(function(rmAttempt) {
@@ -62,5 +73,16 @@ export default Ember.Mixin.create({
         });
       });
     });
+  },
+
+  fetchAttemptListFromRM(appId, store) {
+    return new Ember.RSVP.Promise(function(resolve, reject) {
+      store.query('yarn-app-attempt', {appId: appId}).then(function(rmAttempts) {
+        resolve(rmAttempts);
+      }, function() {
+          console.error('Error:', 'Application attempts not found in RM for appId: ' + appId);
+          reject(null);
+        });
+    });
   }
 });

+ 23 - 0
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/models/yarn-node-container-log.js

@@ -0,0 +1,23 @@
+/**
+ * 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.
+ */
+
+import DS from 'ember-data';
+
+export default DS.Model.extend({
+  logs: DS.attr('string')
+});

+ 1 - 0
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/router.js

@@ -63,6 +63,7 @@ Router.map(function() {
     this.route('charts');
     this.route('configs');
     this.route('logs');
+    this.route('threaddump');
   });
   this.route('yarn-component-instances', function() {
     this.route('info', {path: '/:component_name/info'});

+ 68 - 0
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/routes/yarn-app/threaddump.js

@@ -0,0 +1,68 @@
+/**
+ * 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.
+ */
+
+import Ember from 'ember';
+import AbstractRoute from '../abstract';
+import AppAttemptMixin from 'yarn-ui/mixins/app-attempt';
+
+export default AbstractRoute.extend(AppAttemptMixin, {
+  model(param, transition) {
+    const { app_id } = this.paramsFor('yarn-app');
+    const { service } = param;
+    transition.send('updateBreadcrumbs', app_id, service, [{text: 'Threaddump'}]);
+    return Ember.RSVP.hash({
+      appId: app_id,
+      serviceName: service,
+      attempts: this.fetchAttemptListFromRM(app_id, this.store)
+        .catch(function(error) {
+          Ember.Logger.log("App attempt list fetch failed!");
+          Ember.Logger.log(error);
+          return [];
+        }),
+      app: this.fetchAppInfoFromRM(app_id, this.store),
+      userInfo: this.store.findAll('cluster-user-info', {reload: true})
+        .catch(function(error) {
+            Ember.Logger.log("userInfo querying failed");
+            Ember.Logger.log(error);
+            return null;
+        })
+    });
+  },
+
+  activate() {
+    const controller = this.controllerFor("yarn-app.threaddump");
+    const { attempt, containerid } = this.paramsFor('yarn-app.threaddump');
+    controller.resetAfterRefresh();
+    controller.initializeSelect();
+    if (attempt) {
+      controller.send("showContainersForAttemptId", attempt, containerid);
+    } else {
+      controller.set("selectedAttemptId", "");
+    }
+  },
+
+  unloadAll() {
+    this.store.unloadAll('yarn-app-attempt');
+    this.store.unloadAll('yarn-container');
+    this.store.unloadAll('yarn-node-container-log');
+    this.store.unloadAll('cluster-user-info');
+    if (this.controller) {
+      this.controller.resetAfterRefresh();
+    }
+  }
+});

+ 50 - 0
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/serializers/yarn-node-container-log.js

@@ -0,0 +1,50 @@
+/**
+ * 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.
+ */
+import DS from 'ember-data';
+
+export default DS.JSONAPISerializer.extend({
+
+  internalNormalizeSingleResponse(store, primaryModelClass, payload) {
+    var fixedPayload = {
+      id: "yarn_node_container_log" + "_" + Date.now(),
+      type: primaryModelClass.modelName,
+      attributes: {
+        logs: payload
+      }
+    };
+    return fixedPayload;
+  },
+
+  normalizeSingleResponse(store, primaryModelClass, payload/*, id, requestType*/) {
+    var p = this.internalNormalizeSingleResponse(store,
+      primaryModelClass, payload);
+
+    return { data: p };
+  },
+
+  normalizeArrayResponse(store, primaryModelClass, payload/*, id, requestType*/) {
+    var normalizedArrayResponse = {
+      data: []
+    };
+
+    if (payload) {
+      normalizedArrayResponse.data = [this.internalNormalizeSingleResponse(store, primaryModelClass, payload)];
+    }
+    return normalizedArrayResponse;
+  }
+});

+ 3 - 0
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/yarn-app.hbs

@@ -163,6 +163,9 @@
             {{#link-to 'yarn-app.logs' tagName="li" class=(if (eq target.currentPath 'yarn-app.logs') "active")}}
               {{#link-to 'yarn-app.logs' appId (query-params service=serviceName)}}Logs{{/link-to}}
             {{/link-to}}
+            {{#link-to 'yarn-app.threaddump' tagName="li" class=(if (eq target.currentPath 'yarn-app.threaddump') "active")}}
+              {{#link-to 'yarn-app.threaddump' appId (query-params service=serviceName)}}Threaddump{{/link-to}}
+            {{/link-to}}
           </ul>
         </ul>
       </div>

+ 113 - 0
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/yarn-app/threaddump.hbs

@@ -0,0 +1,113 @@
+{{!
+ * 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.
+}}
+
+<div class="row">
+  <div class="col-md-12">
+    <div class="panel panel-default">
+      <div class="panel-heading">
+        Threaddump {{collapsible-panel targetId="threaddumpFilesCollapsablePanel"}}
+      </div>
+      <div class="panel-body" id="threaddumpFilesCollapsablePanel">
+        {{#if _isLoadingTopPanel}}
+          <div class="text-center" style="z-index: 100; position: absolute; left: 46%; top: 45px;">
+            <img src="assets/images/spinner.gif" alt="Loading...">
+          </div>
+        {{/if}}
+        {{#unless isRunningApp}}
+          <div class="row">
+            <div class="col-md-8 col-md-offset-2 alert alert-warning text-center">
+              <span class="glyphicon glyphicon-warning-sign" style="padding-right: 10px"></span>
+              <span>Threaddump cannot be collected for an application that is not running.</span>
+            </div>
+          </div>
+        {{else}}
+            {{#if runningAttempt}}
+                <div class="row">
+                    <div class="col-md-6">
+                        <label>Choose the running attempt to fetch containers</label>
+                        <div>
+                            <select class="js-fetch-attempt-containers" onchange={{action "showContainersForAttemptId" value="target.value"}} style="max-width:350px;">
+                                <option value="" selected={{eq selectedAttemptId ''}}>None</option>
+                                <option value="{{runningAttempt}}" selected={{eq selectedAttemptId runningAttempt}}>{{runningAttempt}}</option>
+                            </select>
+                        </div>
+                    </div>
+                    {{#if attemptContainerList}}
+                        <div class="col-md-6">
+                            <label>Choose container to fetch threaddump</label>
+                            <div>
+                                <select class="js-fetch-threaddump-containers" onchange={{action "showThreaddumpForContainer" value="target.value"}} style="max-width:350px">
+                                    <option value="" selected={{eq selectedContainerId ''}}>None</option>
+                                    {{#each attemptContainerList as |container|}}
+                                        <option value="{{container.id}}" selected={{eq selectedContainerId container.id}}>{{container.id}}</option>
+                                    {{/each}}
+                                </select>
+                            </div>
+                        </div>
+                    {{else}}
+                        {{#if (and selectedAttemptId (not _isLoadingTopPanel))}}
+                            <div class="col-md-4">
+                                <h4 class="text-center" style="margin-top:25px;">No container data available!</h4>
+                            </div>
+                        {{/if}}
+                    {{/if}}
+                </div>
+            {{else}}
+                <div class="col-md-12">
+                    <h4 class="text-center">No data available!</h4>
+                </div>
+            {{/if}}
+        {{/unless}}
+      </div>
+    </div>
+  </div>
+</div>
+{{#if selectedContainerId}}
+  <div class="row">
+    <div class="col-md-12">
+      <div class="panel panel-default" style="min-height:150px;">
+        <div class="panel-heading">
+          Threaddump: [ {{selectedContainerId}} &amp; {{selectedAttemptId}} ]
+          {{collapsible-panel targetId="threaddumpContentCollapsablePanel"}}
+        </div>
+        <div class="panel-body" id="threaddumpContentCollapsablePanel">
+          {{#if _isLoadingBottomPanel}}
+            <div class="text-center" style="z-index: 100; position: absolute; left: 46%;">
+              <img src="assets/images/spinner.gif" alt="Loading...">
+            </div>
+          {{/if}}
+          <div>
+            {{#if _isThreaddumpAttemptFailed}}
+              <div class="col-md-10 col-md-offset-1 alert alert-warning text-center">
+                <span class="glyphicon glyphicon-warning-sign" style="padding-right: 10px"></span>
+                <span>Threaddump fetch failed for container: {{selectedContainerId}} due to: &#8220;{{threaddumpErrorMessage}}&#8221;</span>
+              </div>
+            {{else if _isStdoutLogsFetchFailed}}
+              <div class="col-md-10 col-md-offset-1 alert alert-warning text-center">
+                <span class="glyphicon glyphicon-warning-sign" style="padding-right: 10px"></span>
+                <span>Logs fetch failed for container: {{selectedContainerId}} due to: &#8220;{{stdoutLogsFetchFailedError}}&#8221;</span>
+              </div>
+            {{else}}
+                <pre id="logContentTextArea1234554321" class="log-content-area">{{showThreaddumpContent}}</pre>
+            {{/if}}
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+{{/if}}