diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/adapters/yarn-container-threaddump.js b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/adapters/yarn-container-threaddump.js new file mode 100644 index 0000000000..c4e9382b9b --- /dev/null +++ b/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; + } +}); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/adapters/yarn-node-container-log.js b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/adapters/yarn-node-container-log.js new file mode 100644 index 0000000000..ba01a7037d --- /dev/null +++ b/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; + } +}); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/controllers/yarn-app/threaddump.js b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/controllers/yarn-app/threaddump.js new file mode 100644 index 0000000000..4b1e57b5ec --- /dev/null +++ b/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"; + }) +}); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/mixins/app-attempt.js b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/mixins/app-attempt.js index a90bf36fd9..a1b9a2f0a2 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/mixins/app-attempt.js +++ b/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); + }); + }); } }); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/models/yarn-node-container-log.js b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/models/yarn-node-container-log.js new file mode 100644 index 0000000000..01dc6db1cb --- /dev/null +++ b/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') +}); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/router.js b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/router.js index 477bfc923e..48a2e1b385 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/router.js +++ b/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'}); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/routes/yarn-app/threaddump.js b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/routes/yarn-app/threaddump.js new file mode 100644 index 0000000000..676c9d254a --- /dev/null +++ b/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(); + } + } +}); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/serializers/yarn-node-container-log.js b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/serializers/yarn-node-container-log.js new file mode 100644 index 0000000000..6f75a30455 --- /dev/null +++ b/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; + } +}); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/yarn-app.hbs b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/yarn-app.hbs index c16a7a5f7c..f6421d4492 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/yarn-app.hbs +++ b/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}} diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/yarn-app/threaddump.hbs b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/yarn-app/threaddump.hbs new file mode 100644 index 0000000000..4f7cfaaa24 --- /dev/null +++ b/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. +}} + +
{{showThreaddumpContent}}+ {{/if}} +