1 /**
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements.  See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership.  The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License.  You may obtain a copy of the License at
9 *
10 *     http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18 
19 package org.apache.hadoop.mapreduce.v2.app.webapp;
20 
21 import static org.apache.hadoop.mapreduce.v2.app.webapp.AMParams.JOB_ID;
22 import static org.apache.hadoop.mapreduce.v2.app.webapp.AMParams.TASK_ID;
23 import static org.apache.hadoop.yarn.webapp.view.JQueryUI.C_TABLE;
24 import static org.apache.hadoop.yarn.webapp.view.JQueryUI._INFO_WRAP;
25 
26 import java.util.Map;
27 
28 import org.apache.hadoop.mapreduce.Counter;
29 import org.apache.hadoop.mapreduce.CounterGroup;
30 import org.apache.hadoop.mapreduce.Counters;
31 import org.apache.hadoop.mapreduce.v2.api.records.JobId;
32 import org.apache.hadoop.mapreduce.v2.api.records.TaskId;
33 import org.apache.hadoop.mapreduce.v2.app.AppContext;
34 import org.apache.hadoop.mapreduce.v2.app.job.Job;
35 import org.apache.hadoop.mapreduce.v2.app.job.Task;
36 import org.apache.hadoop.mapreduce.v2.util.MRApps;
37 import org.apache.hadoop.yarn.webapp.hamlet.Hamlet;
38 import org.apache.hadoop.yarn.webapp.hamlet.Hamlet.DIV;
39 import org.apache.hadoop.yarn.webapp.hamlet.Hamlet.TABLE;
40 import org.apache.hadoop.yarn.webapp.hamlet.Hamlet.TBODY;
41 import org.apache.hadoop.yarn.webapp.hamlet.Hamlet.TD;
42 import org.apache.hadoop.yarn.webapp.hamlet.Hamlet.THEAD;
43 import org.apache.hadoop.yarn.webapp.hamlet.Hamlet.TR;
44 import org.apache.hadoop.yarn.webapp.view.HtmlBlock;
45 
46 import com.google.inject.Inject;
47 
48 public class CountersBlock extends HtmlBlock {
49   Job job;
50   Task task;
51   Counters total;
52   Counters map;
53   Counters reduce;
54 
CountersBlock(AppContext appCtx, ViewContext ctx)55   @Inject CountersBlock(AppContext appCtx, ViewContext ctx) {
56     super(ctx);
57     getCounters(appCtx);
58   }
59 
render(Block html)60   @Override protected void render(Block html) {
61     if (job == null) {
62       html.
63         p()._("Sorry, no counters for nonexistent", $(JOB_ID, "job"))._();
64       return;
65     }
66     if (!$(TASK_ID).isEmpty() && task == null) {
67       html.
68         p()._("Sorry, no counters for nonexistent", $(TASK_ID, "task"))._();
69       return;
70     }
71 
72     if(total == null || total.getGroupNames() == null || total.countCounters() == 0) {
73       String type = $(TASK_ID);
74       if(type == null || type.isEmpty()) {
75         type = $(JOB_ID, "the job");
76       }
77       html.
78         p()._("Sorry it looks like ",type," has no counters.")._();
79       return;
80     }
81 
82     String urlBase;
83     String urlId;
84     if(task != null) {
85       urlBase = "singletaskcounter";
86       urlId = MRApps.toString(task.getID());
87     } else {
88       urlBase = "singlejobcounter";
89       urlId = MRApps.toString(job.getID());
90     }
91 
92 
93     int numGroups = 0;
94     TBODY<TABLE<DIV<Hamlet>>> tbody = html.
95       div(_INFO_WRAP).
96       table("#counters").
97         thead().
98           tr().
99             th(".group.ui-state-default", "Counter Group").
100             th(".ui-state-default", "Counters")._()._().
101         tbody();
102     for (CounterGroup g : total) {
103       CounterGroup mg = map == null ? null : map.getGroup(g.getName());
104       CounterGroup rg = reduce == null ? null : reduce.getGroup(g.getName());
105       ++numGroups;
106       // This is mostly for demonstration :) Typically we'd introduced
107       // a CounterGroup block to reduce the verbosity. OTOH, this
108       // serves as an indicator of where we're in the tag hierarchy.
109       TR<THEAD<TABLE<TD<TR<TBODY<TABLE<DIV<Hamlet>>>>>>>> groupHeadRow = tbody.
110         tr().
111           th().$title(g.getName()).$class("ui-state-default").
112             _(fixGroupDisplayName(g.getDisplayName()))._().
113           td().$class(C_TABLE).
114             table(".dt-counters").$id(job.getID()+"."+g.getName()).
115               thead().
116                 tr().th(".name", "Name");
117 
118       if (map != null) {
119         groupHeadRow.th("Map").th("Reduce");
120       }
121       // Ditto
122       TBODY<TABLE<TD<TR<TBODY<TABLE<DIV<Hamlet>>>>>>> group = groupHeadRow.
123             th(map == null ? "Value" : "Total")._()._().
124         tbody();
125       for (Counter counter : g) {
126         // Ditto
127         TR<TBODY<TABLE<TD<TR<TBODY<TABLE<DIV<Hamlet>>>>>>>> groupRow = group.
128           tr();
129           if (task == null && mg == null && rg == null) {
130             groupRow.td().$title(counter.getName())._(counter.getDisplayName()).
131             _();
132           } else {
133             groupRow.td().$title(counter.getName()).
134               a(url(urlBase,urlId,g.getName(),
135                   counter.getName()), counter.getDisplayName()).
136             _();
137           }
138         if (map != null) {
139           Counter mc = mg == null ? null : mg.findCounter(counter.getName());
140           Counter rc = rg == null ? null : rg.findCounter(counter.getName());
141           groupRow.
142             td(mc == null ? "0" : String.format("%,d", mc.getValue())).
143             td(rc == null ? "0" : String.format("%,d", rc.getValue()));
144         }
145         groupRow.td(String.format("%,d", counter.getValue()))._();
146       }
147       group._()._()._()._();
148     }
149     tbody._()._()._();
150   }
151 
getCounters(AppContext ctx)152   private void getCounters(AppContext ctx) {
153     JobId jobID = null;
154     TaskId taskID = null;
155     String tid = $(TASK_ID);
156     if (!tid.isEmpty()) {
157       taskID = MRApps.toTaskID(tid);
158       jobID = taskID.getJobId();
159     } else {
160       String jid = $(JOB_ID);
161       if (jid != null && !jid.isEmpty()) {
162         jobID = MRApps.toJobID(jid);
163       }
164     }
165     if (jobID == null) {
166       return;
167     }
168     job = ctx.getJob(jobID);
169     if (job == null) {
170       return;
171     }
172     if (taskID != null) {
173       task = job.getTask(taskID);
174       if (task == null) {
175         return;
176       }
177       total = task.getCounters();
178       return;
179     }
180     // Get all types of counters
181     Map<TaskId, Task> tasks = job.getTasks();
182     total = job.getAllCounters();
183     boolean needTotalCounters = false;
184     if (total == null) {
185       total = new Counters();
186       needTotalCounters = true;
187     }
188     map = new Counters();
189     reduce = new Counters();
190     for (Task t : tasks.values()) {
191       Counters counters = t.getCounters();
192       if (counters == null) {
193         continue;
194       }
195       switch (t.getType()) {
196         case MAP:     map.incrAllCounters(counters);     break;
197         case REDUCE:  reduce.incrAllCounters(counters);  break;
198       }
199       if (needTotalCounters) {
200         total.incrAllCounters(counters);
201       }
202     }
203   }
204 
fixGroupDisplayName(CharSequence name)205   private String fixGroupDisplayName(CharSequence name) {
206     return name.toString().replace(".", ".\u200B").replace("$", "\u200B$");
207   }
208 }