RMAppAttemptBlock.java

/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.hadoop.yarn.server.resourcemanager.webapp;

import static org.apache.hadoop.yarn.webapp.view.JQueryUI._EVEN;
import static org.apache.hadoop.yarn.webapp.view.JQueryUI._INFO_WRAP;
import static org.apache.hadoop.yarn.webapp.view.JQueryUI._ODD;
import static org.apache.hadoop.yarn.webapp.view.JQueryUI._TH;

import java.io.IOException;
import java.util.Collection;
import java.util.List;

import org.apache.commons.text.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.yarn.api.protocolrecords.GetApplicationAttemptReportRequest;
import org.apache.hadoop.yarn.api.protocolrecords.GetContainersRequest;
import org.apache.hadoop.yarn.api.records.ApplicationAttemptReport;
import org.apache.hadoop.yarn.api.records.ApplicationId;
import org.apache.hadoop.yarn.api.records.ContainerReport;
import org.apache.hadoop.yarn.api.records.Resource;
import org.apache.hadoop.yarn.api.records.ResourceRequest;
import org.apache.hadoop.yarn.api.records.YarnApplicationAttemptState;
import org.apache.hadoop.yarn.exceptions.YarnException;
import org.apache.hadoop.yarn.server.resourcemanager.RMServerUtils;
import org.apache.hadoop.yarn.server.resourcemanager.ResourceManager;
import org.apache.hadoop.yarn.server.resourcemanager.rmapp.RMApp;
import org.apache.hadoop.yarn.server.resourcemanager.rmapp.attempt.RMAppAttempt;
import org.apache.hadoop.yarn.server.resourcemanager.rmapp.attempt.RMAppAttemptMetrics;
import org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacityScheduler;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.AppInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ResourceRequestInfo;
import org.apache.hadoop.yarn.server.webapp.AppAttemptBlock;
import org.apache.hadoop.yarn.server.webapp.dao.AppAttemptInfo;
import org.apache.hadoop.yarn.util.Times;
import org.apache.hadoop.yarn.util.resource.Resources;
import org.apache.hadoop.yarn.webapp.hamlet2.Hamlet;
import org.apache.hadoop.yarn.webapp.hamlet2.Hamlet.DIV;
import org.apache.hadoop.yarn.webapp.hamlet2.Hamlet.TABLE;
import org.apache.hadoop.yarn.webapp.hamlet2.Hamlet.TBODY;
import org.apache.hadoop.yarn.webapp.util.WebAppUtils;
import org.apache.hadoop.yarn.webapp.view.InfoBlock;

import com.google.inject.Inject;

public class RMAppAttemptBlock extends AppAttemptBlock{

  private final ResourceManager rm;
  protected Configuration conf;
  private final static String DIAGNOSTICS_SCRIPT_BODY = new StringBuilder()
      .append("var refresh = false;")
      .append("var haveGotAppDiagnostic = false;")
      .append("function getArray(objOrArray) {")
      .append("  if(Array.isArray(objOrArray)){")
      .append("    return objOrArray;")
      .append("  } else {")
      .append("    return [objOrArray];")
      .append("  }")
      .append("}")
      .append("function getTableDataFromObjectArray(objectArray, fields) {")
      .append("  if (objectArray == undefined) {")
      .append("    return [];")
      .append("  }")
      .append("  var data = [];")
      .append("  $.each(objectArray, function(i, object) {")
      .append("    var dataItem = [];")
      .append("    for (var i=0; i<fields.length; i++) {")
      .append("      dataItem.push(object[fields[i]] != undefined ? ")
      .append("object[fields[i]] : '');")
      .append("    }")
      .append("    data.push(dataItem);")
      .append("  });")
      .append("  return data;")
      .append("}")
      .append("function sendRequest(requestURL, doneCallBackFunc,")
      .append("                      failCallBackFunc) {")
      .append(" $.ajax({")
      .append("  type: 'GET',")
      .append("  url: requestURL,")
      .append("  contentType: 'text/plain'")
      .append(" }).done(doneCallBackFunc).fail(failCallBackFunc);")
      .append("}")
      .append("function parseQueryAppActResponse(responseObj) {")
      .append(" var requestDiagnostics = [], dateTime, appDiagnostic;")
      .append(" responseObj = responseObj['appActivities'];")
      .append(" if(responseObj.hasOwnProperty('allocations')){")
      .append("  var allocations = getArray(responseObj['allocations']);")
      .append("  var allocation = allocations[0];")
      .append("  dateTime = allocation['dateTime'];")
      .append("  if(allocation.hasOwnProperty('diagnostic')){")
      .append("   appDiagnostic = allocation['diagnostic'];")
      .append("  }")
      .append("  if(allocation.hasOwnProperty('children')){")
      .append("   var requestAllocations = ")
      .append("     getArray(allocation['children']);")
      .append("   $.each(requestAllocations, function (i, requestAllocation) {")
      .append("    if(requestAllocation.hasOwnProperty('children')){")
      .append("     var allocationAttempts = ")
      .append("       getArray(requestAllocation['children']);")
      .append("     var diagnosticSummary = requestAllocation.hasOwnProperty(")
      .append("       'diagnostic')? requestAllocation['diagnostic']+'. ':'';")
      .append("     $.each(allocationAttempts, function(i,allocationAttempt) {")
      .append("      if (i > 0) {")
      .append("       diagnosticSummary += ', ';")
      .append("      }")
      .append("      diagnosticSummary += allocationAttempt['count'] + ' ' +")
      .append("        (allocationAttempt['count']=='1' ? 'node ' : 'nodes ')")
      .append("        + allocationAttempt['allocationState'];")
      .append("      if (allocationAttempt.hasOwnProperty('diagnostic')) {")
      .append("       diagnosticSummary += ' with diagnostic:' + ")
      .append("                   allocationAttempt['diagnostic'];")
      .append("      }")
      .append("     });")
      .append("     delete requestAllocation['children'];")
      .append("     requestAllocation['diagnostic']=diagnosticSummary;")
      .append("    }")
      .append("    requestDiagnostics.push(requestAllocation);")
      .append("   });")
      .append("  }")
      .append(" }")
      .append(" return [requestDiagnostics, dateTime, appDiagnostic];")
      .append("}")
      .append("function handleQueryAppActDone(data){")
      .append("  console.log('App activities:', data);")
      .append("  var results = parseQueryAppActResponse(data);")
      .append("  console.log('parsed results:', results);")
      .append("  var requestDiagnostics = results[0];")
      .append("  var dateTime = results[1];")
      .append("  var appDiagnostic = results[2];")
      .append("  haveGotAppDiagnostic = false;")
      .append("  if (appDiagnostic != undefined) {")
      .append("    haveGotAppDiagnostic = true;")
      .append("    $('#appDiagnostic').html('App diagnostic: '")
      .append("        + appDiagnostic);")
      .append("  }")
      .append("  $('#diagnosticsTableDiv').empty();")
      .append("  if (requestDiagnostics.length > 0) {")
      .append("    haveGotAppDiagnostic = true;")
      .append("    tableData = getTableDataFromObjectArray(requestDiagnostics,")
      .append(" ['requestPriority', 'allocationRequestId', 'allocationState',")
      .append(" 'diagnostic']);")
      .append("    $('#diagnosticsTableDiv').append('<table ")
      .append("      id=\"diagnosticsTable\"></table>');")
      .append("    $('#diagnosticsTable').dataTable( {")
      .append("      'aaData': tableData,")
      .append("      'aoColumns': [")
      .append("        { 'sTitle': 'Priority' },")
      .append("        { 'sTitle': 'AllocationRequestId' },")
      .append("        { 'sTitle': 'AllocationState' },")
      .append("        { 'sTitle': 'DiagnosticSummary' }")
      .append("      ],")
      .append("      bJQueryUI:true, sPaginationType: 'full_numbers',")
      .append("      iDisplayLength:20, aLengthMenu:[20, 40, 60, 80, 100]")
      .append("    });")
      .append("  }")
      .append("  if (haveGotAppDiagnostic) {")
      .append("    $('#refreshDiagnosticsBtn').attr('disabled',false);")
      .append("    $('#diagnosticsUpdateTime').html('Updated at ' + dateTime);")
      .append("  } else {")
      .append("    $('#diagnosticsUpdateTime').html('');")
      .append("    if (refresh) {")
      .append("      refreshSchedulerDiagnostics();")
      .append("    }")
      .append("  }")
      .append("}")
      .append("function handleRequestFailure(data){")
      .append(" alert('Request app activities REST API failed.');")
      .append(" console.log(data);")
      .append("}")
      .append("function handleRefreshAppActDone(data){")
      .append(" refresh = true;")
      .append(" setTimeout(function(){")
      .append("  queryAppDiagnostics();")
      .append(" }, 2000);")
      .append("}")
      .append("function refreshAppDiagnostics() {")
      .append(" $('#refreshDiagnosticsBtn').attr('disabled',true);")
      .append(" sendRequest(refreshAppActivitiesURL,handleRefreshAppActDone,")
      .append("             handleRequestFailure);")
      .append("}")
      .append("function queryAppDiagnostics() {")
      .append(" sendRequest(getAppActivitiesURL,handleQueryAppActDone,")
      .append("             handleRequestFailure);")
      .append("}")
      .append("function parseHierarchicalQueue(node, nodeInfos,")
      .append("    parentNodePath, partition) {")
      .append("  var nodePath = parentNodePath + '/' + node['name'];")
      .append("  if (node.hasOwnProperty('name')){")
      .append("    if (node['diagnostic'] == undefined || node['diagnostic']")
      .append("      .indexOf(ignoreActivityContent) == -1){")
      .append("     var nodeInfo = {};")
      .append("     nodeInfo['partition'] = partition;")
      .append("     nodeInfo['path'] = nodePath;")
      .append("     nodeInfo['allocationState'] = node['allocationState'];")
      .append("     nodeInfo['diagnostic'] = ")
      .append("      node['diagnostic'] == undefined ? '':node['diagnostic'];")
      .append("     nodeInfos.push(nodeInfo);")
      .append("    }")
      .append("  }")
      .append("  if (node.hasOwnProperty('children')){")
      .append("    var children = getArray(node['children']);")
      .append("    $.each(children, function (i, child) {")
      .append("      parseHierarchicalQueue(child, nodeInfos, nodePath,")
      .append("        partition);")
      .append("    });")
      .append("  }")
      .append("}")
      .append("function parseQueueDiagnostics(data) {")
      .append("  var nodeInfos = [], dateTime;")
      .append("  if(data.hasOwnProperty('dateTime')){")
      .append("      dateTime = data['dateTime'];")
      .append("  }")
      .append("  if(data.hasOwnProperty('allocations')){")
      .append("    var allocations = getArray(data['allocations']);")
      .append("    $.each(allocations, function (i, allocation) {")
      .append("      if (allocation.hasOwnProperty('root')) {")
      .append("        var root = allocation['root'];")
      .append("        var partition = allocation['partition'];")
      .append("        parseHierarchicalQueue(root, nodeInfos, '', partition);")
      .append("      }")
      .append("    });")
      .append("  }")
      .append("  return [nodeInfos, dateTime];")
      .append("}")
      .append("function handleRefreshSchedulerActDone(data){")
      .append(" console.log('handleRefreshSchedulerActDone', data);")
      .append(" setTimeout(function(){")
      .append("  querySchedulerDiagnostics();")
      .append("  $('#refreshDiagnosticsBtn').attr('disabled',false);")
      .append(" }, 100);")
      .append("}")
      .append("function handleQuerySchedulerActDone(data){")
      .append("  console.log('Scheduler activities:', data);")
      .append("  data = data['activities'];")
      .append("  var results = parseQueueDiagnostics(data);")
      .append("  var nodeInfos = results[0];")
      .append("  var dateTime = results[1];")
      .append("  $('#diagnosticsTableDiv').empty();")
      .append("  if(dateTime != undefined){")
      .append("   $('#diagnosticsUpdateTime').html('App diagnostics not found!")
      .append(" Got useful scheduler activities updated at '+dateTime);")
      .append("    tableData = getTableDataFromObjectArray(nodeInfos, ")
      .append(" ['partition', 'path', 'allocationState', 'diagnostic']);")
      .append("    $('#diagnosticsTableDiv').append('<table ")
      .append("      id=\"diagnosticsTable\"></table>');")
      .append("    $('#diagnosticsTable').dataTable({")
      .append("      'aaData': tableData,")
      .append("      'aoColumns': [")
      .append("        { 'sTitle': 'Partition' },")
      .append("        { 'sTitle': 'SchedulingNode' },")
      .append("        { 'sTitle': 'AllocationState' },")
      .append("        { 'sTitle': 'Diagnostic' }")
      .append("      ],")
      .append("      bJQueryUI:true, sPaginationType: 'full_numbers',")
      .append("      iDisplayLength:20, aLengthMenu:[20, 40, 60, 80, 100]")
      .append("    });")
      .append("  } else if(data.hasOwnProperty('diagnostic')){")
      .append("    $('#diagnosticsUpdateTime').html(data['diagnostic']);")
      .append("  }")
      .append("  $('#refreshDiagnosticsBtn').attr('disabled',false);")
      .append("}")
      .append("function refreshSchedulerDiagnostics() {")
      .append(" $('#refreshDiagnosticsBtn').attr('disabled',true);")
      .append(" sendRequest(schedulerActivitiesURL,")
      .append("   handleRefreshSchedulerActDone,handleRequestFailure);")
      .append("}")
      .append("function querySchedulerDiagnostics() {")
      .append(" sendRequest(schedulerActivitiesURL,")
      .append("   handleQuerySchedulerActDone,handleRequestFailure);")
      .append("}")
      .append("queryAppDiagnostics();").toString();

  @Inject
  RMAppAttemptBlock(ViewContext ctx, ResourceManager rm, Configuration conf) {
    super(null, ctx);
    this.rm = rm;
    this.conf = conf;
  }

  private void createResourceRequestsTable(Block html) {
    AppInfo app =
        new AppInfo(rm, rm.getRMContext().getRMApps()
          .get(this.appAttemptId.getApplicationId()), true,
          WebAppUtils.getHttpSchemePrefix(conf));

    List<ResourceRequestInfo> resourceRequests = app.getResourceRequests();
    if (resourceRequests == null || resourceRequests.isEmpty()) {
      return;
    }

    DIV<Hamlet> div = html.div(_INFO_WRAP);
    // Requests Table
    TBODY<TABLE<DIV<Hamlet>>> tbody = div
        .h3("Total Outstanding Resource Requests: "
            + getTotalResource(resourceRequests))
        .table("#resourceRequests").thead().tr().th(".priority", "Priority")
        .th(".allocationRequestId", "AllocationRequestId")
        .th(".resource", "ResourceName").th(".capacity", "Capability")
        .th(".containers", "NumContainers")
        .th(".relaxlocality", "RelaxLocality")
        .th(".labelexpression", "NodeLabelExpression")
        .th(".executiontype", "ExecutionType")
        .th(".allocationTags", "AllocationTags")
        .th(".placementConstraint", "PlacementConstraint").__().__().tbody();

    StringBuilder resourceRequestTableData = new StringBuilder("[\n");
    for (ResourceRequestInfo resourceRequest  : resourceRequests) {
      if (resourceRequest.getNumContainers() == 0) {
        continue;
      }
      resourceRequestTableData.append("[\"")
          .append(String.valueOf(resourceRequest.getPriority())).append("\",\"")
          .append(String.valueOf(resourceRequest.getAllocationRequestId()))
          .append("\",\"")
          .append(resourceRequest.getResourceName() == null ? "N/A"
              : resourceRequest.getResourceName())
          .append("\",\"")
          .append(StringEscapeUtils.escapeEcmaScript(StringEscapeUtils
              .escapeHtml4(String.valueOf(resourceRequest.getCapability()))))
          .append("\",\"")
          .append(String.valueOf(resourceRequest.getNumContainers()))
          .append("\",\"")
          .append(String.valueOf(resourceRequest.getRelaxLocality()))
          .append("\",\"")
          .append(resourceRequest.getNodeLabelExpression() == null ? "N/A"
              : resourceRequest.getNodeLabelExpression())
          .append("\",\"")
          .append(resourceRequest.getExecutionTypeRequest() == null ? "N/A"
              : resourceRequest.getExecutionTypeRequest().getExecutionType())
          .append("\",\"")
          .append(resourceRequest.getAllocationTags() == null ? "N/A" :
              StringUtils.join(resourceRequest.getAllocationTags(), ","))
          .append("\",\"")
          .append(resourceRequest.getPlacementConstraint() == null ? "N/A"
              : resourceRequest.getPlacementConstraint())
          .append("\"],\n");
    }
    if (resourceRequestTableData
        .charAt(resourceRequestTableData.length() - 2) == ',') {
      resourceRequestTableData.delete(resourceRequestTableData.length() - 2,
          resourceRequestTableData.length() - 1);
    }
    resourceRequestTableData.append("]");
    html.script().$type("text/javascript")
        .__("var resourceRequestsTableData=" + resourceRequestTableData).__();
    tbody.__().__();

    // create diagnostics table when CS is enabled
    createDiagnosticsTable(html, div);

    div.__();
  }

  private Resource getTotalResource(List<ResourceRequestInfo> requests) {
    Resource totalResource = Resource.newInstance(0, 0);
    if (requests == null) {
      return totalResource;
    }
    for (ResourceRequestInfo request : requests) {
      if (request.getNumContainers() == 0) {
        continue;
      }
      if (request.getResourceName() == null || request.getResourceName()
          .equals(ResourceRequest.ANY)) {
        Resources.addTo(
            totalResource,
            Resources.multiply(request.getCapability().getResource(),
            request.getNumContainers()));
      }
    }
    return totalResource;
  }

  private void createContainerLocalityTable(Block html) {
    RMAppAttemptMetrics attemptMetrics = null;
    RMAppAttempt attempt = getRMAppAttempt();
    if (attempt != null) {
      attemptMetrics = attempt.getRMAppAttemptMetrics();
    }
    
    if (attemptMetrics == null) {
      return;
    }

    DIV<Hamlet> div = html.div(_INFO_WRAP);
    TABLE<DIV<Hamlet>> table =
        div.h3(
          "Total Allocated Containers: "
              + attemptMetrics.getTotalAllocatedContainers()).h3("Each table cell"
            + " represents the number of NodeLocal/RackLocal/OffSwitch containers"
            + " satisfied by NodeLocal/RackLocal/OffSwitch resource requests.").table(
          "#containerLocality");
    table.
      tr().
        th(_TH, "").
        th(_TH, "Node Local Request").
        th(_TH, "Rack Local Request").
        th(_TH, "Off Switch Request").
        __();

    String[] containersType =
        { "Num Node Local Containers (satisfied by)", "Num Rack Local Containers (satisfied by)",
            "Num Off Switch Containers (satisfied by)" };
    boolean odd = false;
    for (int i = 0; i < attemptMetrics.getLocalityStatistics().length; i++) {
      table.tr((odd = !odd) ? _ODD : _EVEN).td(containersType[i])
        .td(String.valueOf(attemptMetrics.getLocalityStatistics()[i][0]))
        .td(i == 0 ? "" : String.valueOf(attemptMetrics.getLocalityStatistics()[i][1]))
        .td(i <= 1 ? "" : String.valueOf(attemptMetrics.getLocalityStatistics()[i][2])).__();
    }
    table.__();
    div.__();
  }

  private boolean isApplicationInFinalState(YarnApplicationAttemptState state) {
    return state == YarnApplicationAttemptState.FINISHED
        || state == YarnApplicationAttemptState.FAILED
        || state == YarnApplicationAttemptState.KILLED;
  }

  @Override
  protected void createAttemptHeadRoomTable(Block html) {
    RMAppAttempt attempt = getRMAppAttempt();
    if (attempt != null && !isApplicationInFinalState(createApplicationAttemptState(attempt))) {
      RMAppAttemptMetrics metrics = attempt.getRMAppAttemptMetrics();
      DIV<Hamlet> pdiv = html.__(InfoBlock.class).div(_INFO_WRAP);
      info("Application Attempt Overview").clear();
      info("Application Attempt Metrics").__(
          "Application Attempt Headroom : ", metrics == null ? "N/A" :
          metrics.getApplicationAttemptHeadroom());
      pdiv.__();
    }
  }

  private YarnApplicationAttemptState createApplicationAttemptState(RMAppAttempt attempt) {
    return RMServerUtils.convertRmAppAttemptStateToYarnApplicationAttemptState(
        attempt.getState(),
        attempt.getPreviousState()
    );
  }

  private RMAppAttempt getRMAppAttempt() {
    ApplicationId appId = this.appAttemptId.getApplicationId();
    RMAppAttempt attempt = null;
    RMApp rmApp = rm.getRMContext().getRMApps().get(appId);
    if (rmApp != null) { 
      attempt = rmApp.getAppAttempts().get(appAttemptId);
    }
    return attempt;
  }

  protected void generateOverview(ApplicationAttemptReport appAttemptReport,
      Collection<ContainerReport> containers, AppAttemptInfo appAttempt,
      String node) {

    RMAppAttempt rmAppAttempt = getRMAppAttempt();
    // nodes which are blacklisted by the application
    String appBlacklistedNodes =
        getNodeString(rmAppAttempt.getBlacklistedNodes());
    // nodes which are blacklisted by the RM for AM launches
    String rmBlackListedNodes =
        getNodeString(rmAppAttempt.getAMBlacklistManager()
          .getBlacklistUpdates().getBlacklistAdditions());

    info("Application Attempt Overview")
      .__(
        "Application Attempt State:",
        appAttempt.getAppAttemptState() == null ? UNAVAILABLE : appAttempt
          .getAppAttemptState())
        .__("Started:", Times.format(appAttempt.getStartedTime()))
        .__("Elapsed:",
            org.apache.hadoop.util.StringUtils.formatTime(Times.elapsed(
                appAttempt.getStartedTime(), appAttempt.getFinishedTime())))
      .__(
        "AM Container:",
        appAttempt.getAmContainerId() == null || containers == null
            || !hasAMContainer(appAttemptReport.getAMContainerId(), containers)
            ? null : root_url("container", appAttempt.getAmContainerId()),
        appAttempt.getAmContainerId() == null ? "N/A" :
          String.valueOf(appAttempt.getAmContainerId()))
      .__("Node:", node)
      .__(
        "Tracking URL:",
        appAttempt.getTrackingUrl() == null
            || appAttempt.getTrackingUrl().equals(UNAVAILABLE) ? null
            : root_url(appAttempt.getTrackingUrl()),
        appAttempt.getTrackingUrl() == null
            || appAttempt.getTrackingUrl().equals(UNAVAILABLE)
            ? "Unassigned"
            : appAttempt.getAppAttemptState() == YarnApplicationAttemptState.FINISHED
                || appAttempt.getAppAttemptState() == YarnApplicationAttemptState.FAILED
                || appAttempt.getAppAttemptState() == YarnApplicationAttemptState.KILLED
                ? "History" : "ApplicationMaster")
      .__(
        "Diagnostics Info:",
        appAttempt.getDiagnosticsInfo() == null ? "" : appAttempt
          .getDiagnosticsInfo())
      .__("Nodes blacklisted by the application:", appBlacklistedNodes)
      .__("Nodes blacklisted by the system:", rmBlackListedNodes);
  }

  private String getNodeString(Collection<String> nodes) {
    String concatinatedString = "-";
    if (null != nodes && !nodes.isEmpty()) {
      concatinatedString = StringUtils.join(nodes, ", ");
    }
    return concatinatedString;
  }

  @Override
  protected void createTablesForAttemptMetrics(Block html) {
    createContainerLocalityTable(html);
    createResourceRequestsTable(html);
  }

  @Override
  protected List<ContainerReport> getContainers(
      final GetContainersRequest request) throws YarnException, IOException {
    return rm.getClientRMService().getContainers(request).getContainerList();
  }

  @Override
  protected ApplicationAttemptReport getApplicationAttemptReport(
      final GetApplicationAttemptReportRequest request)
      throws YarnException, IOException {
    return rm.getClientRMService().getApplicationAttemptReport(request)
        .getApplicationAttemptReport();
  }

  private void createDiagnosticsTable(Block html, DIV<Hamlet> parentDiv) {
    if (!(rm.getResourceScheduler() instanceof CapacityScheduler)) {
      return;
    }
    String appActivitiesURL =
        new StringBuilder().append(RMWSConsts.RM_WEB_SERVICE_PATH).append(
            RMWSConsts.SCHEDULER_APP_ACTIVITIES.replace("{appid}",
                this.appAttemptId.getApplicationId().toString())).append("?")
            .toString();
    String refreshAppActivitiesURL = appActivitiesURL + "actions=refresh";
    String getAppActivitiesURL = appActivitiesURL
        + "actions=get&groupBy=diagnostic&summarize=true";
    String refreshAndGetAppActivitiesURL = appActivitiesURL
        + "actions=refresh&actions=get&groupBy=diagnostic&summarize=true";
    String schedulerActivitiesURL =
        new StringBuilder().append(RMWSConsts.RM_WEB_SERVICE_PATH)
            .append(RMWSConsts.SCHEDULER_ACTIVITIES).append("?")
            .append(RMWSConsts.GROUP_BY).append("=diagnostic").toString();

    DIV<DIV<Hamlet>> div = parentDiv.div();
    div.p().__("Diagnostics in cache ").__("(For more details refer to ")
        .a(refreshAndGetAppActivitiesURL, "App Activities").__(" or ")
        .a(schedulerActivitiesURL, "Scheduler Activities").__(")").__();
    div.button().$id("refreshDiagnosticsBtn")
        .$style("border-style: solid; border-color: #000000; border-width: 1px;"
                + " cursor: hand; cursor: pointer; border-radius: 4px")
        .$onclick("refreshAppDiagnostics()").b("Refresh").__();
    div.p().$id("diagnosticsUpdateTime").__();
    div.p().$id("appDiagnostic").__();
    div.div("#diagnosticsTableDiv").__();

    div.__();

    StringBuilder script = new StringBuilder();
    script
        .append("var refreshAppActivitiesURL = '")
        .append(refreshAppActivitiesURL).append("';")
        .append("var getAppActivitiesURL = '")
        .append(getAppActivitiesURL).append("';")
        .append("var schedulerActivitiesURL = '")
        .append(schedulerActivitiesURL).append("';")
        .append("var ignoreActivityContent = '")
        .append("does not need more resource';")
        .append(DIAGNOSTICS_SCRIPT_BODY);

    html.script().$type("text/javascript").__(script.toString()).__();
  }
}