| /** |
| * 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.util.StringHelper.join; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.apache.hadoop.security.UserGroupInformation; |
| import org.apache.hadoop.util.StringUtils; |
| import org.apache.hadoop.yarn.api.records.NodeLabel; |
| import org.apache.hadoop.yarn.nodelabels.RMNodeLabel; |
| import org.apache.hadoop.yarn.server.resourcemanager.ResourceManager; |
| import org.apache.hadoop.yarn.server.resourcemanager.nodelabels.RMNodeLabelsManager; |
| import org.apache.hadoop.yarn.server.resourcemanager.scheduler.SchedulerHealth; |
| import org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CSQueue; |
| import org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacityScheduler; |
| import org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.UserInfo; |
| import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.CapacitySchedulerInfo; |
| import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.CapacitySchedulerLeafQueueInfo; |
| import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.CapacitySchedulerQueueInfo; |
| import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.PartitionQueueCapacitiesInfo; |
| import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.PartitionResourcesInfo; |
| import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ResourceInfo; |
| import org.apache.hadoop.yarn.server.security.ApplicationACLsManager; |
| import org.apache.hadoop.yarn.server.webapp.AppBlock; |
| import org.apache.hadoop.yarn.util.Times; |
| import org.apache.hadoop.yarn.util.resource.Resources; |
| import org.apache.hadoop.yarn.webapp.ResponseInfo; |
| import org.apache.hadoop.yarn.webapp.SubView; |
| 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.LI; |
| import org.apache.hadoop.yarn.webapp.hamlet2.Hamlet.TABLE; |
| import org.apache.hadoop.yarn.webapp.hamlet2.Hamlet.TBODY; |
| import org.apache.hadoop.yarn.webapp.hamlet2.Hamlet.UL; |
| import org.apache.hadoop.yarn.webapp.view.HtmlBlock; |
| import org.apache.hadoop.yarn.webapp.view.InfoBlock; |
| |
| import com.google.inject.Inject; |
| import com.google.inject.servlet.RequestScoped; |
| |
| class CapacitySchedulerPage extends RmView { |
| static final String _Q = ".ui-state-default.ui-corner-all"; |
| static final float Q_MAX_WIDTH = 0.8f; |
| static final float Q_STATS_POS = Q_MAX_WIDTH + 0.05f; |
| static final String Q_END = "left:101%"; |
| static final String Q_GIVEN = |
| "left:0%;background:none;border:1px dashed #BFBFBF"; |
| static final String Q_AUTO_CREATED = "background:#F4F0CB"; |
| static final String Q_OVER = "background:#FFA333"; |
| static final String Q_UNDER = "background:#5BD75B"; |
| static final String ACTIVE_USER = "background:#FFFF00"; // Yellow highlight |
| |
| @RequestScoped |
| static class CSQInfo { |
| CapacitySchedulerInfo csinfo; |
| CapacitySchedulerQueueInfo qinfo; |
| String label; |
| boolean isExclusiveNodeLabel; |
| } |
| |
| static class LeafQueueInfoBlock extends HtmlBlock { |
| final CapacitySchedulerLeafQueueInfo lqinfo; |
| private String nodeLabel; |
| |
| @Inject LeafQueueInfoBlock(ViewContext ctx, CSQInfo info) { |
| super(ctx); |
| lqinfo = (CapacitySchedulerLeafQueueInfo) info.qinfo; |
| nodeLabel = info.label; |
| } |
| |
| @Override |
| protected void render(Block html) { |
| if (nodeLabel == null) { |
| renderLeafQueueInfoWithoutParition(html); |
| } else { |
| renderLeafQueueInfoWithPartition(html); |
| } |
| } |
| |
| private void renderLeafQueueInfoWithPartition(Block html) { |
| String nodeLabelDisplay = nodeLabel.length() == 0 |
| ? NodeLabel.DEFAULT_NODE_LABEL_PARTITION : nodeLabel; |
| // first display the queue's label specific details : |
| ResponseInfo ri = |
| info("\'" + lqinfo.getQueuePath().substring(5) |
| + "\' Queue Status for Partition \'" + nodeLabelDisplay + "\'"); |
| renderQueueCapacityInfo(ri, nodeLabel); |
| html.__(InfoBlock.class); |
| // clear the info contents so this queue's info doesn't accumulate into |
| // another queue's info |
| ri.clear(); |
| |
| // second display the queue specific details : |
| ri = |
| info("\'" + lqinfo.getQueuePath().substring(5) + "\' Queue Status") |
| .__("Queue State:", lqinfo.getQueueState()); |
| renderCommonLeafQueueInfo(ri); |
| |
| html.__(InfoBlock.class); |
| // clear the info contents so this queue's info doesn't accumulate into |
| // another queue's info |
| ri.clear(); |
| } |
| |
| private void renderLeafQueueInfoWithoutParition(Block html) { |
| ResponseInfo ri = |
| info("\'" + lqinfo.getQueuePath().substring(5) + "\' Queue Status") |
| .__("Queue State:", lqinfo.getQueueState()); |
| renderQueueCapacityInfo(ri, ""); |
| renderCommonLeafQueueInfo(ri); |
| html.__(InfoBlock.class); |
| // clear the info contents so this queue's info doesn't accumulate into |
| // another queue's info |
| ri.clear(); |
| } |
| |
| private void renderQueueCapacityInfo(ResponseInfo ri, String label) { |
| PartitionQueueCapacitiesInfo capacities = |
| lqinfo.getCapacities().getPartitionQueueCapacitiesInfo(label); |
| PartitionResourcesInfo resourceUsages = |
| lqinfo.getResources().getPartitionResourceUsageInfo(label); |
| |
| // Get UserInfo from first user to calculate AM Resource Limit per user. |
| ResourceInfo userAMResourceLimit = null; |
| ArrayList<UserInfo> usersList = lqinfo.getUsers().getUsersList(); |
| if (!usersList.isEmpty()) { |
| userAMResourceLimit = resourceUsages.getUserAmLimit(); |
| } |
| // If no users are present or if AM limit per user doesn't exist, retrieve |
| // AM Limit for that queue. |
| if (userAMResourceLimit == null) { |
| userAMResourceLimit = resourceUsages.getAMLimit(); |
| } |
| ResourceInfo amUsed = (resourceUsages.getAmUsed() == null) |
| ? new ResourceInfo(Resources.none()) |
| : resourceUsages.getAmUsed(); |
| ri. |
| __("Used Capacity:", |
| appendPercent(resourceUsages.getUsed(), |
| capacities.getUsedCapacity() / 100)) |
| .__("Configured Capacity:", |
| capacities.getConfiguredMinResource() == null ? |
| Resources.none().toString() : |
| capacities.getConfiguredMinResource().toString()) |
| .__("Configured Max Capacity:", |
| (capacities.getConfiguredMaxResource() == null |
| || capacities.getConfiguredMaxResource().getResource() |
| .equals(Resources.none())) |
| ? "unlimited" |
| : capacities.getConfiguredMaxResource().toString()) |
| .__("Effective Capacity:", |
| appendPercent(capacities.getEffectiveMinResource(), |
| capacities.getCapacity() / 100)) |
| .__("Effective Max Capacity:", |
| appendPercent(capacities.getEffectiveMaxResource(), |
| capacities.getMaxCapacity() / 100)) |
| .__("Absolute Used Capacity:", |
| percent(capacities.getAbsoluteUsedCapacity() / 100)) |
| .__("Absolute Configured Capacity:", |
| percent(capacities.getAbsoluteCapacity() / 100)) |
| .__("Absolute Configured Max Capacity:", |
| percent(capacities.getAbsoluteMaxCapacity() / 100)) |
| .__("Used Resources:", resourceUsages.getUsed().toString()) |
| .__("Configured Max Application Master Limit:", |
| StringUtils.format("%.1f", capacities.getMaxAMLimitPercentage())) |
| .__("Max Application Master Resources:", |
| resourceUsages.getAMLimit().toString()) |
| .__("Used Application Master Resources:", amUsed.toString()) |
| .__("Max Application Master Resources Per User:", |
| userAMResourceLimit.toString()); |
| } |
| |
| private void renderCommonLeafQueueInfo(ResponseInfo ri) { |
| ri. |
| __("Num Schedulable Applications:", Integer.toString(lqinfo.getNumActiveApplications())). |
| __("Num Non-Schedulable Applications:", Integer.toString(lqinfo.getNumPendingApplications())). |
| __("Num Containers:", Integer.toString(lqinfo.getNumContainers())). |
| __("Max Applications:", Integer.toString(lqinfo.getMaxApplications())). |
| __("Max Applications Per User:", Integer.toString(lqinfo.getMaxApplicationsPerUser())). |
| __("Configured Minimum User Limit Percent:", Integer.toString(lqinfo.getUserLimit()) + "%"). |
| __("Configured User Limit Factor:", lqinfo.getUserLimitFactor()). |
| __("Accessible Node Labels:", StringUtils.join(",", lqinfo.getNodeLabels())). |
| __("Ordering Policy: ", lqinfo.getOrderingPolicyDisplayName()). |
| __("Preemption:", |
| lqinfo.getPreemptionDisabled() ? "disabled" : "enabled"). |
| __("Intra-queue Preemption:", lqinfo.getIntraQueuePreemptionDisabled() |
| ? "disabled" : "enabled"). |
| __("Default Node Label Expression:", |
| lqinfo.getDefaultNodeLabelExpression() == null |
| ? NodeLabel.DEFAULT_NODE_LABEL_PARTITION |
| : lqinfo.getDefaultNodeLabelExpression()). |
| __("Default Application Priority:", |
| Integer.toString(lqinfo.getDefaultApplicationPriority())); |
| } |
| } |
| |
| static class QueueUsersInfoBlock extends HtmlBlock { |
| final CapacitySchedulerLeafQueueInfo lqinfo; |
| private String nodeLabel; |
| |
| @Inject |
| QueueUsersInfoBlock(ViewContext ctx, CSQInfo info) { |
| super(ctx); |
| lqinfo = (CapacitySchedulerLeafQueueInfo) info.qinfo; |
| nodeLabel = info.label; |
| } |
| |
| @Override |
| protected void render(Block html) { |
| TBODY<TABLE<Hamlet>> tbody = |
| html.table("#userinfo").thead().$class("ui-widget-header").tr().th() |
| .$class("ui-state-default").__("User Name").__().th() |
| .$class("ui-state-default").__("Max Resource").__().th() |
| .$class("ui-state-default").__("Weight").__().th() |
| .$class("ui-state-default").__("Used Resource").__().th() |
| .$class("ui-state-default").__("Max AM Resource").__().th() |
| .$class("ui-state-default").__("Used AM Resource").__().th() |
| .$class("ui-state-default").__("Schedulable Apps").__().th() |
| .$class("ui-state-default").__("Non-Schedulable Apps").__().__().__() |
| .tbody(); |
| |
| PartitionResourcesInfo queueUsageResources = |
| lqinfo.getResources().getPartitionResourceUsageInfo( |
| nodeLabel == null ? "" : nodeLabel); |
| |
| ArrayList<UserInfo> users = lqinfo.getUsers().getUsersList(); |
| for (UserInfo userInfo : users) { |
| ResourceInfo resourcesUsed = userInfo.getResourcesUsed(); |
| ResourceInfo userAMLimitPerPartition = |
| queueUsageResources.getUserAmLimit(); |
| // If AM limit per user is null, use the AM limit for the queue level. |
| if (userAMLimitPerPartition == null) { |
| userAMLimitPerPartition = queueUsageResources.getAMLimit(); |
| } |
| if (userInfo.getUserWeight() != 1.0) { |
| userAMLimitPerPartition = |
| new ResourceInfo( |
| Resources.multiply(userAMLimitPerPartition.getResource(), |
| userInfo.getUserWeight())); |
| } |
| if (nodeLabel != null) { |
| resourcesUsed = userInfo.getResourceUsageInfo() |
| .getPartitionResourceUsageInfo(nodeLabel).getUsed(); |
| } |
| ResourceInfo amUsed = userInfo.getAMResourcesUsed(); |
| if (amUsed == null) { |
| amUsed = new ResourceInfo(Resources.none()); |
| } |
| String highlightIfAsking = |
| userInfo.getIsActive() ? ACTIVE_USER : null; |
| tbody.tr().$style(highlightIfAsking).td(userInfo.getUsername()) |
| .td(userInfo.getUserResourceLimit().toString()) |
| .td(String.valueOf(userInfo.getUserWeight())) |
| .td(resourcesUsed.toString()) |
| .td(userAMLimitPerPartition.toString()) |
| .td(amUsed.toString()) |
| .td(Integer.toString(userInfo.getNumActiveApplications())) |
| .td(Integer.toString(userInfo.getNumPendingApplications())).__(); |
| } |
| |
| html.div().$class("usersinfo").h5("Active Users Info").__(); |
| tbody.__().__(); |
| } |
| } |
| |
| public static class QueueBlock extends HtmlBlock { |
| final CSQInfo csqinfo; |
| |
| @Inject QueueBlock(CSQInfo info) { |
| csqinfo = info; |
| } |
| |
| @Override |
| public void render(Block html) { |
| ArrayList<CapacitySchedulerQueueInfo> subQueues = (csqinfo.qinfo == null) |
| ? csqinfo.csinfo.getQueues().getQueueInfoList() |
| : csqinfo.qinfo.getQueues().getQueueInfoList(); |
| |
| UL<Hamlet> ul = html.ul("#pq"); |
| float used; |
| float absCap; |
| float absMaxCap; |
| float absUsedCap; |
| for (CapacitySchedulerQueueInfo info : subQueues) { |
| String nodeLabel = (csqinfo.label == null) ? "" : csqinfo.label; |
| //DEFAULT_NODE_LABEL_PARTITION is accessible to all queues |
| //other exclsiveNodeLabels are accessible only if configured |
| if (!nodeLabel.isEmpty()// i.e. its DEFAULT_NODE_LABEL_PARTITION |
| && csqinfo.isExclusiveNodeLabel |
| && !info.getNodeLabels().contains("*") |
| && !info.getNodeLabels().contains(nodeLabel)) { |
| continue; |
| } |
| PartitionQueueCapacitiesInfo partitionQueueCapsInfo = info |
| .getCapacities().getPartitionQueueCapacitiesInfo(nodeLabel); |
| used = partitionQueueCapsInfo.getUsedCapacity() / 100; |
| absCap = partitionQueueCapsInfo.getAbsoluteCapacity() / 100; |
| absMaxCap = partitionQueueCapsInfo.getAbsoluteMaxCapacity() / 100; |
| absUsedCap = partitionQueueCapsInfo.getAbsoluteUsedCapacity() / 100; |
| |
| boolean isAutoCreatedLeafQueue = info.isLeafQueue() ? |
| ((CapacitySchedulerLeafQueueInfo) info).isAutoCreatedLeafQueue() |
| : false; |
| float capPercent = absMaxCap == 0 ? 0 : absCap/absMaxCap; |
| float usedCapPercent = absMaxCap == 0 ? 0 : absUsedCap/absMaxCap; |
| |
| String Q_WIDTH = width(absMaxCap * Q_MAX_WIDTH); |
| LI<UL<Hamlet>> li = ul. |
| li(). |
| a(_Q).$style(isAutoCreatedLeafQueue? join( Q_AUTO_CREATED, ";", |
| Q_WIDTH) |
| : Q_WIDTH). |
| $title(join("Absolute Capacity:", percent(absCap))). |
| span().$style(join(Q_GIVEN, ";font-size:1px;", width(capPercent))). |
| __('.').__(). |
| span().$style(join(width(usedCapPercent), |
| ";font-size:1px;left:0%;", absUsedCap > absCap ? Q_OVER : Q_UNDER)). |
| __('.').__(). |
| span(".q", "Queue: "+info.getQueuePath().substring(5)).__(). |
| span().$class("qstats").$style(left(Q_STATS_POS)). |
| __(join(percent(used), " used")).__(); |
| |
| csqinfo.qinfo = info; |
| if (info.isLeafQueue()) { |
| li.ul("#lq").li().__(LeafQueueInfoBlock.class).__().__(); |
| li.ul("#lq").li().__(QueueUsersInfoBlock.class).__().__(); |
| } else { |
| li.__(QueueBlock.class); |
| } |
| li.__(); |
| } |
| |
| ul.__(); |
| } |
| } |
| |
| static class QueuesBlock extends HtmlBlock { |
| final CapacityScheduler cs; |
| final CSQInfo csqinfo; |
| private final ResourceManager rm; |
| private List<RMNodeLabel> nodeLabelsInfo; |
| |
| @Inject QueuesBlock(ResourceManager rm, CSQInfo info) { |
| cs = (CapacityScheduler) rm.getResourceScheduler(); |
| csqinfo = info; |
| this.rm = rm; |
| RMNodeLabelsManager nodeLabelManager = |
| rm.getRMContext().getNodeLabelManager(); |
| nodeLabelsInfo = nodeLabelManager.pullRMNodeLabelsInfo(); |
| } |
| |
| @Override |
| public void render(Block html) { |
| html.__(MetricsOverviewTable.class); |
| |
| UserGroupInformation callerUGI = this.getCallerUGI(); |
| boolean isAdmin = false; |
| ApplicationACLsManager aclsManager = rm.getApplicationACLsManager(); |
| if (aclsManager.areACLsEnabled()) { |
| if (callerUGI != null && aclsManager.isAdmin(callerUGI)) { |
| isAdmin = true; |
| } |
| } else { |
| isAdmin = true; |
| } |
| |
| // only show button to dump CapacityScheduler debug logs to admins |
| if (isAdmin) { |
| html.div() |
| .button() |
| .$style( |
| "border-style: solid; border-color: #000000; border-width: 1px;" |
| + " cursor: hand; cursor: pointer; border-radius: 4px") |
| .$onclick("confirmAction()").b("Dump scheduler logs").__().select() |
| .$id("time").option().$value("60").__("1 min").__().option() |
| .$value("300").__("5 min").__().option().$value("600").__("10 min").__() |
| .__().__(); |
| |
| StringBuilder script = new StringBuilder(); |
| script |
| .append("function confirmAction() {") |
| .append(" b = confirm(\"Are you sure you wish to generate" |
| + " scheduler logs?\");") |
| .append(" if (b == true) {") |
| .append(" var timePeriod = $(\"#time\").val();") |
| .append(" $.ajax({") |
| .append(" type: 'POST',") |
| .append(" url: '/ws/v1/cluster/scheduler/logs',") |
| .append(" contentType: 'text/plain',") |
| .append(AppBlock.getCSRFHeaderString(rm.getConfig())) |
| .append(" data: 'time=' + timePeriod,") |
| .append(" dataType: 'text'") |
| .append(" }).done(function(data){") |
| .append(" setTimeout(function(){") |
| .append(" alert(\"Scheduler log is being generated.\");") |
| .append(" }, 1000);") |
| .append(" }).fail(function(data){") |
| .append( |
| " alert(\"Scheduler log generation failed. Please check the" |
| + " ResourceManager log for more information.\");") |
| .append(" console.log(data);").append(" });").append(" }") |
| .append("}"); |
| |
| html.script().$type("text/javascript").__(script.toString()).__(); |
| } |
| |
| UL<DIV<DIV<Hamlet>>> ul = html. |
| div("#cs-wrapper.ui-widget"). |
| div(".ui-widget-header.ui-corner-top"). |
| __("Application Queues").__(). |
| div("#cs.ui-widget-content.ui-corner-bottom"). |
| ul(); |
| if (cs == null) { |
| ul. |
| li(). |
| a(_Q).$style(width(Q_MAX_WIDTH)). |
| span().$style(Q_END).__("100% ").__(). |
| span(".q", "default").__().__(); |
| } else { |
| ul. |
| li().$style("margin-bottom: 1em"). |
| span().$style("font-weight: bold").__("Legend:").__(). |
| span().$class("qlegend ui-corner-all").$style(Q_GIVEN). |
| __("Capacity").__(). |
| span().$class("qlegend ui-corner-all").$style(Q_UNDER). |
| __("Used").__(). |
| span().$class("qlegend ui-corner-all").$style(Q_OVER). |
| __("Used (over capacity)").__(). |
| span().$class("qlegend ui-corner-all ui-state-default"). |
| __("Max Capacity").__(). |
| span().$class("qlegend ui-corner-all").$style(ACTIVE_USER). |
| __("Users Requesting Resources").__(). |
| span().$class("qlegend ui-corner-all").$style(Q_AUTO_CREATED). |
| __("Auto Created Queues").__(). |
| __(); |
| |
| float used = 0; |
| |
| CSQueue root = cs.getRootQueue(); |
| CapacitySchedulerInfo sinfo = new CapacitySchedulerInfo(root, cs); |
| csqinfo.csinfo = sinfo; |
| |
| boolean hasAnyLabelLinkedToNM = false; |
| if (null != nodeLabelsInfo) { |
| for (RMNodeLabel label : nodeLabelsInfo) { |
| if (label.getLabelName().length() == 0) { |
| // Skip DEFAULT_LABEL |
| continue; |
| } |
| if (label.getNumActiveNMs() > 0) { |
| hasAnyLabelLinkedToNM = true; |
| break; |
| } |
| } |
| } |
| if (!hasAnyLabelLinkedToNM) { |
| used = sinfo.getUsedCapacity() / 100; |
| //label is not enabled in the cluster or there's only "default" label, |
| ul.li(). |
| a(_Q).$style(width(Q_MAX_WIDTH)). |
| span().$style(join(width(used), ";left:0%;", |
| used > 1 ? Q_OVER : Q_UNDER)).__(".").__(). |
| span(".q", "Queue: root").__(). |
| span().$class("qstats").$style(left(Q_STATS_POS)). |
| __(join(percent(used), " used")).__(). |
| __(QueueBlock.class).__(); |
| } else { |
| for (RMNodeLabel label : nodeLabelsInfo) { |
| csqinfo.qinfo = null; |
| csqinfo.label = label.getLabelName(); |
| csqinfo.isExclusiveNodeLabel = label.getIsExclusive(); |
| String nodeLabelDisplay = csqinfo.label.length() == 0 |
| ? NodeLabel.DEFAULT_NODE_LABEL_PARTITION : csqinfo.label; |
| PartitionQueueCapacitiesInfo capacities = sinfo.getCapacities() |
| .getPartitionQueueCapacitiesInfo(csqinfo.label); |
| used = capacities.getUsedCapacity() / 100; |
| String partitionUiTag = |
| "Partition: " + nodeLabelDisplay + " " + label.getResource(); |
| ul.li(). |
| a(_Q).$style(width(Q_MAX_WIDTH)). |
| span().$style(join(width(used), ";left:0%;", |
| used > 1 ? Q_OVER : Q_UNDER)).__(".").__(). |
| span(".q", partitionUiTag).__(). |
| span().$class("qstats").$style(left(Q_STATS_POS)). |
| __(join(percent(used), " used")).__().__(); |
| |
| //for the queue hierarchy under label |
| UL<Hamlet> underLabel = html.ul("#pq"); |
| underLabel.li(). |
| a(_Q).$style(width(Q_MAX_WIDTH)). |
| span().$style(join(width(used), ";left:0%;", |
| used > 1 ? Q_OVER : Q_UNDER)).__(".").__(). |
| span(".q", "Queue: root").__(). |
| span().$class("qstats").$style(left(Q_STATS_POS)). |
| __(join(percent(used), " used")).__(). |
| __(QueueBlock.class).__().__(); |
| } |
| } |
| } |
| ul.__().__(). |
| script().$type("text/javascript"). |
| __("$('#cs').hide();").__().__(). |
| __(RMAppsBlock.class); |
| html.__(HealthBlock.class); |
| } |
| } |
| |
| public static class HealthBlock extends HtmlBlock { |
| |
| final CapacityScheduler cs; |
| |
| @Inject |
| HealthBlock(ResourceManager rm) { |
| cs = (CapacityScheduler) rm.getResourceScheduler(); |
| } |
| |
| @Override |
| public void render(HtmlBlock.Block html) { |
| SchedulerHealth healthInfo = cs.getSchedulerHealth(); |
| DIV<Hamlet> div = html.div("#health"); |
| div.h4("Aggregate scheduler counts"); |
| TBODY<TABLE<DIV<Hamlet>>> tbody = |
| div.table("#lastrun").thead().$class("ui-widget-header").tr().th() |
| .$class("ui-state-default").__("Total Container Allocations(count)") |
| .__().th().$class("ui-state-default") |
| .__("Total Container Releases(count)").__().th() |
| .$class("ui-state-default") |
| .__("Total Fulfilled Reservations(count)").__().th() |
| .$class("ui-state-default").__("Total Container Preemptions(count)") |
| .__().__().__().tbody(); |
| tbody |
| .$class("ui-widget-content") |
| .tr() |
| .td( |
| String.valueOf(cs.getRootQueueMetrics() |
| .getAggregateAllocatedContainers())) |
| .td( |
| String.valueOf(cs.getRootQueueMetrics() |
| .getAggegatedReleasedContainers())) |
| .td(healthInfo.getAggregateFulFilledReservationsCount().toString()) |
| .td(healthInfo.getAggregatePreemptionCount().toString()).__().__().__(); |
| div.h4("Last scheduler run"); |
| tbody = |
| div.table("#lastrun").thead().$class("ui-widget-header").tr().th() |
| .$class("ui-state-default").__("Time").__().th() |
| .$class("ui-state-default").__("Allocations(count - resources)").__() |
| .th().$class("ui-state-default").__("Reservations(count - resources)") |
| .__().th().$class("ui-state-default").__("Releases(count - resources)") |
| .__().__().__().tbody(); |
| tbody |
| .$class("ui-widget-content") |
| .tr() |
| .td(Times.format(healthInfo.getLastSchedulerRunTime())) |
| .td( |
| healthInfo.getAllocationCount().toString() + " - " |
| + healthInfo.getResourcesAllocated().toString()) |
| .td( |
| healthInfo.getReservationCount().toString() + " - " |
| + healthInfo.getResourcesReserved().toString()) |
| .td( |
| healthInfo.getReleaseCount().toString() + " - " |
| + healthInfo.getResourcesReleased().toString()).__().__().__(); |
| Map<String, SchedulerHealth.DetailedInformation> info = new HashMap<>(); |
| info.put("Allocation", healthInfo.getLastAllocationDetails()); |
| info.put("Reservation", healthInfo.getLastReservationDetails()); |
| info.put("Release", healthInfo.getLastReleaseDetails()); |
| info.put("Preemption", healthInfo.getLastPreemptionDetails()); |
| |
| for (Map.Entry<String, SchedulerHealth.DetailedInformation> entry : info |
| .entrySet()) { |
| String containerId = "N/A"; |
| String nodeId = "N/A"; |
| String queue = "N/A"; |
| String table = "#" + entry.getKey(); |
| div.h4("Last " + entry.getKey()); |
| tbody = |
| div.table(table).thead().$class("ui-widget-header").tr().th() |
| .$class("ui-state-default").__("Time").__().th() |
| .$class("ui-state-default").__("Container Id").__().th() |
| .$class("ui-state-default").__("Node Id").__().th() |
| .$class("ui-state-default").__("Queue").__().__().__().tbody(); |
| SchedulerHealth.DetailedInformation di = entry.getValue(); |
| if (di.getTimestamp() != 0) { |
| if (di.getContainerId() != null) { |
| containerId = di.getContainerId().toString(); |
| } |
| if (di.getNodeId() != null) { |
| nodeId = di.getNodeId().toString(); |
| } |
| queue = di.getQueue(); |
| } |
| tbody.$class("ui-widget-content").tr() |
| .td(Times.format(di.getTimestamp())).td(containerId).td(nodeId) |
| .td(queue).__().__().__(); |
| } |
| div.__(); |
| } |
| } |
| |
| @Override protected void postHead(Page.HTML<__> html) { |
| html. |
| style().$type("text/css"). |
| __("#cs { padding: 0.5em 0 1em 0; margin-bottom: 1em; position: relative }", |
| "#cs ul { list-style: none }", |
| "#cs a { font-weight: normal; margin: 2px; position: relative }", |
| "#cs a span { font-weight: normal; font-size: 80% }", |
| "#cs-wrapper .ui-widget-header { padding: 0.2em 0.5em }", |
| ".qstats { font-weight: normal; font-size: 80%; position: absolute }", |
| ".qlegend { font-weight: normal; padding: 0 1em; margin: 1em }", |
| "table.info tr th {width: 50%}").__(). // to center info table |
| script("/static/jt/jquery.jstree.js"). |
| script().$type("text/javascript"). |
| __("$(function() {", |
| " $('#cs a span').addClass('ui-corner-all').css('position', 'absolute');", |
| " $('#cs').bind('loaded.jstree', function (e, data) {", |
| " var callback = { call:reopenQueryNodes }", |
| " data.inst.open_node('#pq', callback);", |
| " }).", |
| " jstree({", |
| " core: { animation: 188, html_titles: true },", |
| " plugins: ['themeroller', 'html_data', 'ui'],", |
| " themeroller: { item_open: 'ui-icon-minus',", |
| " item_clsd: 'ui-icon-plus', item_leaf: 'ui-icon-gear'", |
| " }", |
| " });", |
| " $('#cs').bind('select_node.jstree', function(e, data) {", |
| " var q = $('.q', data.rslt.obj).first().text();", |
| " if (q == 'Queue: root') q = '';", |
| " else {", |
| " q = q.substr(q.lastIndexOf(':') + 2);", |
| " q = '^' + q.substr(q.lastIndexOf('.') + 1) + '$';", |
| " }", |
| // Update this filter column index for queue if new columns are added |
| // Current index for queue column is 5 |
| " $('#apps').dataTable().fnFilter(q, 5, true);", |
| " });", |
| " $('#cs').show();", |
| "});").__(). |
| __(SchedulerPageUtil.QueueBlockUtil.class); |
| } |
| |
| @Override protected Class<? extends SubView> content() { |
| return QueuesBlock.class; |
| } |
| |
| static String appendPercent(ResourceInfo resourceInfo, float f) { |
| if (resourceInfo == null) { |
| return ""; |
| } |
| return resourceInfo.toString() + " (" |
| + StringUtils.formatPercent(f, 1) + ")"; |
| } |
| |
| static String percent(float f) { |
| return StringUtils.formatPercent(f, 1); |
| } |
| |
| static String width(float f) { |
| return StringUtils.format("width:%.1f%%", f * 100); |
| } |
| |
| static String left(float f) { |
| return StringUtils.format("left:%.1f%%", f * 100); |
| } |
| } |