MAPREDUCE-3001. Added task-specific counters to AppMaster and JobHistory web-UIs. Contributed by Robert Joseph Evans.
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1177203 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
44090734c9
commit
71886657f8
@ -321,6 +321,9 @@ Release 0.23.0 - Unreleased
|
|||||||
MAPREDUCE-3099. Add docs for setting up a single node MRv2 cluster.
|
MAPREDUCE-3099. Add docs for setting up a single node MRv2 cluster.
|
||||||
(mahadev)
|
(mahadev)
|
||||||
|
|
||||||
|
MAPREDUCE-3001. Added task-specific counters to AppMaster and JobHistory
|
||||||
|
web-UIs. (Robert Joseph Evans via vinodkv)
|
||||||
|
|
||||||
OPTIMIZATIONS
|
OPTIMIZATIONS
|
||||||
|
|
||||||
MAPREDUCE-2026. Make JobTracker.getJobCounters() and
|
MAPREDUCE-2026. Make JobTracker.getJobCounters() and
|
||||||
|
@ -28,4 +28,6 @@ public interface AMParams {
|
|||||||
static final String TASK_ID = "task.id";
|
static final String TASK_ID = "task.id";
|
||||||
static final String TASK_TYPE = "task.type";
|
static final String TASK_TYPE = "task.type";
|
||||||
static final String ATTEMPT_STATE = "attempt.state";
|
static final String ATTEMPT_STATE = "attempt.state";
|
||||||
|
static final String COUNTER_GROUP = "counter.group";
|
||||||
|
static final String COUNTER_NAME = "counter.name";
|
||||||
}
|
}
|
||||||
|
@ -34,9 +34,14 @@ public void setup() {
|
|||||||
route(pajoin("/job", JOB_ID), AppController.class, "job");
|
route(pajoin("/job", JOB_ID), AppController.class, "job");
|
||||||
route(pajoin("/conf", JOB_ID), AppController.class, "conf");
|
route(pajoin("/conf", JOB_ID), AppController.class, "conf");
|
||||||
route(pajoin("/jobcounters", JOB_ID), AppController.class, "jobCounters");
|
route(pajoin("/jobcounters", JOB_ID), AppController.class, "jobCounters");
|
||||||
|
route(pajoin("/singlejobcounter",JOB_ID, COUNTER_GROUP, COUNTER_NAME),
|
||||||
|
AppController.class, "singleJobCounter");
|
||||||
route(pajoin("/tasks", JOB_ID, TASK_TYPE), AppController.class, "tasks");
|
route(pajoin("/tasks", JOB_ID, TASK_TYPE), AppController.class, "tasks");
|
||||||
route(pajoin("/attempts", JOB_ID, TASK_TYPE, ATTEMPT_STATE),
|
route(pajoin("/attempts", JOB_ID, TASK_TYPE, ATTEMPT_STATE),
|
||||||
AppController.class, "attempts");
|
AppController.class, "attempts");
|
||||||
route(pajoin("/task", TASK_ID), AppController.class, "task");
|
route(pajoin("/task", TASK_ID), AppController.class, "task");
|
||||||
|
route(pajoin("/taskcounters", TASK_ID), AppController.class, "taskCounters");
|
||||||
|
route(pajoin("/singletaskcounter",TASK_ID, COUNTER_GROUP, COUNTER_NAME),
|
||||||
|
AppController.class, "singleTaskCounter");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,8 @@
|
|||||||
|
|
||||||
import static org.apache.hadoop.yarn.util.StringHelper.join;
|
import static org.apache.hadoop.yarn.util.StringHelper.join;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URLDecoder;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
@ -30,7 +32,7 @@
|
|||||||
import org.apache.hadoop.mapreduce.v2.api.records.TaskId;
|
import org.apache.hadoop.mapreduce.v2.api.records.TaskId;
|
||||||
import org.apache.hadoop.mapreduce.v2.util.MRApps;
|
import org.apache.hadoop.mapreduce.v2.util.MRApps;
|
||||||
import org.apache.hadoop.yarn.conf.YarnConfiguration;
|
import org.apache.hadoop.yarn.conf.YarnConfiguration;
|
||||||
import org.apache.hadoop.yarn.util.Apps;
|
import org.apache.hadoop.yarn.util.StringHelper;
|
||||||
import org.apache.hadoop.yarn.util.Times;
|
import org.apache.hadoop.yarn.util.Times;
|
||||||
import org.apache.hadoop.yarn.webapp.Controller;
|
import org.apache.hadoop.yarn.webapp.Controller;
|
||||||
import org.apache.hadoop.yarn.webapp.View;
|
import org.apache.hadoop.yarn.webapp.View;
|
||||||
@ -41,7 +43,7 @@
|
|||||||
* This class renders the various pages that the web app supports.
|
* This class renders the various pages that the web app supports.
|
||||||
*/
|
*/
|
||||||
public class AppController extends Controller implements AMParams {
|
public class AppController extends Controller implements AMParams {
|
||||||
final App app;
|
protected final App app;
|
||||||
|
|
||||||
protected AppController(App app, Configuration conf, RequestContext ctx,
|
protected AppController(App app, Configuration conf, RequestContext ctx,
|
||||||
String title) {
|
String title) {
|
||||||
@ -110,6 +112,54 @@ public void jobCounters() {
|
|||||||
render(countersPage());
|
render(countersPage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display a page showing a task's counters
|
||||||
|
*/
|
||||||
|
public void taskCounters() {
|
||||||
|
requireTask();
|
||||||
|
if (app.getTask() != null) {
|
||||||
|
setTitle(StringHelper.join("Counters for ", $(TASK_ID)));
|
||||||
|
}
|
||||||
|
render(countersPage());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the class that will render the /singlejobcounter page
|
||||||
|
*/
|
||||||
|
protected Class<? extends View> singleCounterPage() {
|
||||||
|
return SingleCounterPage.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the /singlejobcounter page
|
||||||
|
* @throws IOException on any error.
|
||||||
|
*/
|
||||||
|
public void singleJobCounter() throws IOException{
|
||||||
|
requireJob();
|
||||||
|
set(COUNTER_GROUP, URLDecoder.decode($(COUNTER_GROUP), "UTF-8"));
|
||||||
|
set(COUNTER_NAME, URLDecoder.decode($(COUNTER_NAME), "UTF-8"));
|
||||||
|
if (app.getJob() != null) {
|
||||||
|
setTitle(StringHelper.join($(COUNTER_GROUP)," ",$(COUNTER_NAME),
|
||||||
|
" for ", $(JOB_ID)));
|
||||||
|
}
|
||||||
|
render(singleCounterPage());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the /singletaskcounter page
|
||||||
|
* @throws IOException on any error.
|
||||||
|
*/
|
||||||
|
public void singleTaskCounter() throws IOException{
|
||||||
|
requireTask();
|
||||||
|
set(COUNTER_GROUP, URLDecoder.decode($(COUNTER_GROUP), "UTF-8"));
|
||||||
|
set(COUNTER_NAME, URLDecoder.decode($(COUNTER_NAME), "UTF-8"));
|
||||||
|
if (app.getTask() != null) {
|
||||||
|
setTitle(StringHelper.join($(COUNTER_GROUP)," ",$(COUNTER_NAME),
|
||||||
|
" for ", $(TASK_ID)));
|
||||||
|
}
|
||||||
|
render(singleCounterPage());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the class that will render the /tasks page
|
* @return the class that will render the /tasks page
|
||||||
*/
|
*/
|
||||||
|
@ -61,6 +61,29 @@ public class CountersBlock extends HtmlBlock {
|
|||||||
p()._("Sorry, no counters for nonexistent", $(TASK_ID, "task"))._();
|
p()._("Sorry, no counters for nonexistent", $(TASK_ID, "task"))._();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(total == null || total.getAllCounterGroups() == null ||
|
||||||
|
total.getAllCounterGroups().size() <= 0) {
|
||||||
|
String type = $(TASK_ID);
|
||||||
|
if(type == null || type.isEmpty()) {
|
||||||
|
type = $(JOB_ID, "the job");
|
||||||
|
}
|
||||||
|
html.
|
||||||
|
p()._("Sorry it looks like ",type," has no counters.")._();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String urlBase;
|
||||||
|
String urlId;
|
||||||
|
if(task != null) {
|
||||||
|
urlBase = "singletaskcounter";
|
||||||
|
urlId = MRApps.toString(task.getID());
|
||||||
|
} else {
|
||||||
|
urlBase = "singlejobcounter";
|
||||||
|
urlId = MRApps.toString(job.getID());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
int numGroups = 0;
|
int numGroups = 0;
|
||||||
TBODY<TABLE<DIV<Hamlet>>> tbody = html.
|
TBODY<TABLE<DIV<Hamlet>>> tbody = html.
|
||||||
div(_INFO_WRAP).
|
div(_INFO_WRAP).
|
||||||
@ -79,12 +102,13 @@ public class CountersBlock extends HtmlBlock {
|
|||||||
// serves as an indicator of where we're in the tag hierarchy.
|
// serves as an indicator of where we're in the tag hierarchy.
|
||||||
TR<THEAD<TABLE<TD<TR<TBODY<TABLE<DIV<Hamlet>>>>>>>> groupHeadRow = tbody.
|
TR<THEAD<TABLE<TD<TR<TBODY<TABLE<DIV<Hamlet>>>>>>>> groupHeadRow = tbody.
|
||||||
tr().
|
tr().
|
||||||
th().$title(g.getName()).
|
th().$title(g.getName()).$class("ui-state-default").
|
||||||
_(fixGroupDisplayName(g.getDisplayName()))._().
|
_(fixGroupDisplayName(g.getDisplayName()))._().
|
||||||
td().$class(C_TABLE).
|
td().$class(C_TABLE).
|
||||||
table(".dt-counters").
|
table(".dt-counters").
|
||||||
thead().
|
thead().
|
||||||
tr().th(".name", "Name");
|
tr().th(".name", "Name");
|
||||||
|
|
||||||
if (map != null) {
|
if (map != null) {
|
||||||
groupHeadRow.th("Map").th("Reduce");
|
groupHeadRow.th("Map").th("Reduce");
|
||||||
}
|
}
|
||||||
@ -97,7 +121,9 @@ public class CountersBlock extends HtmlBlock {
|
|||||||
TR<TBODY<TABLE<TD<TR<TBODY<TABLE<DIV<Hamlet>>>>>>>> groupRow = group.
|
TR<TBODY<TABLE<TD<TR<TBODY<TABLE<DIV<Hamlet>>>>>>>> groupRow = group.
|
||||||
tr().
|
tr().
|
||||||
td().$title(counter.getName()).
|
td().$title(counter.getName()).
|
||||||
_(counter.getDisplayName())._();
|
a(url(urlBase,urlId,g.getName(),
|
||||||
|
counter.getName()), counter.getDisplayName()).
|
||||||
|
_();
|
||||||
if (map != null) {
|
if (map != null) {
|
||||||
Counter mc = mg == null ? null : mg.getCounter(counter.getName());
|
Counter mc = mg == null ? null : mg.getCounter(counter.getName());
|
||||||
Counter rc = rg == null ? null : rg.getCounter(counter.getName());
|
Counter rc = rg == null ? null : rg.getCounter(counter.getName());
|
||||||
@ -121,7 +147,7 @@ private void getCounters(AppContext ctx) {
|
|||||||
jobID = taskID.getJobId();
|
jobID = taskID.getJobId();
|
||||||
} else {
|
} else {
|
||||||
String jid = $(JOB_ID);
|
String jid = $(JOB_ID);
|
||||||
if (!jid.isEmpty()) {
|
if (jid != null && !jid.isEmpty()) {
|
||||||
jobID = MRApps.toJobID(jid);
|
jobID = MRApps.toJobID(jid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,13 +20,19 @@
|
|||||||
|
|
||||||
import org.apache.hadoop.yarn.webapp.SubView;
|
import org.apache.hadoop.yarn.webapp.SubView;
|
||||||
|
|
||||||
|
import static org.apache.hadoop.mapreduce.v2.app.webapp.AMParams.TASK_ID;
|
||||||
import static org.apache.hadoop.yarn.webapp.view.JQueryUI.*;
|
import static org.apache.hadoop.yarn.webapp.view.JQueryUI.*;
|
||||||
|
|
||||||
public class CountersPage extends AppView {
|
public class CountersPage extends AppView {
|
||||||
|
|
||||||
@Override protected void preHead(Page.HTML<_> html) {
|
@Override protected void preHead(Page.HTML<_> html) {
|
||||||
commonPreHead(html);
|
commonPreHead(html);
|
||||||
set(initID(ACCORDION, "nav"), "{autoHeight:false, active:2}");
|
String tid = $(TASK_ID);
|
||||||
|
String activeNav = "3";
|
||||||
|
if(tid == null || tid.isEmpty()) {
|
||||||
|
activeNav = "2";
|
||||||
|
}
|
||||||
|
set(initID(ACCORDION, "nav"), "{autoHeight:false, active:"+activeNav+"}");
|
||||||
set(DATATABLES_SELECTOR, "#counters .dt-counters");
|
set(DATATABLES_SELECTOR, "#counters .dt-counters");
|
||||||
set(initSelector(DATATABLES),
|
set(initSelector(DATATABLES),
|
||||||
"{bJQueryUI:true, sDom:'t', iDisplayLength:-1}");
|
"{bJQueryUI:true, sDom:'t', iDisplayLength:-1}");
|
||||||
@ -35,9 +41,9 @@ public class CountersPage extends AppView {
|
|||||||
@Override protected void postHead(Page.HTML<_> html) {
|
@Override protected void postHead(Page.HTML<_> html) {
|
||||||
html.
|
html.
|
||||||
style("#counters, .dt-counters { table-layout: fixed }",
|
style("#counters, .dt-counters { table-layout: fixed }",
|
||||||
"#counters th { overflow: hidden; vertical-align: center }",
|
"#counters th { overflow: hidden; vertical-align: middle }",
|
||||||
"#counters .dataTables_wrapper { min-height: 1em }",
|
"#counters .dataTables_wrapper { min-height: 1em }",
|
||||||
"#counters .group { width: 10em }",
|
"#counters .group { width: 15em }",
|
||||||
"#counters .name { width: 30em }");
|
"#counters .name { width: 30em }");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,6 +55,14 @@ public class NavBlock extends HtmlBlock {
|
|||||||
li().a(url("conf", jobid), "Configuration")._().
|
li().a(url("conf", jobid), "Configuration")._().
|
||||||
li().a(url("tasks", jobid, "m"), "Map tasks")._().
|
li().a(url("tasks", jobid, "m"), "Map tasks")._().
|
||||||
li().a(url("tasks", jobid, "r"), "Reduce tasks")._()._();
|
li().a(url("tasks", jobid, "r"), "Reduce tasks")._()._();
|
||||||
|
if (app.getTask() != null) {
|
||||||
|
String taskid = MRApps.toString(app.getTask().getID());
|
||||||
|
nav.
|
||||||
|
h3("Task").
|
||||||
|
ul().
|
||||||
|
li().a(url("task", taskid), "Task Overview")._().
|
||||||
|
li().a(url("taskcounters", taskid), "Counters")._()._();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
nav.
|
nav.
|
||||||
h3("Tools").
|
h3("Tools").
|
||||||
|
@ -0,0 +1,151 @@
|
|||||||
|
/**
|
||||||
|
* 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.mapreduce.v2.app.webapp;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
import org.apache.hadoop.mapreduce.v2.api.records.Counter;
|
||||||
|
import org.apache.hadoop.mapreduce.v2.api.records.CounterGroup;
|
||||||
|
import org.apache.hadoop.mapreduce.v2.api.records.JobId;
|
||||||
|
import org.apache.hadoop.mapreduce.v2.api.records.TaskAttemptId;
|
||||||
|
import org.apache.hadoop.mapreduce.v2.api.records.TaskId;
|
||||||
|
import org.apache.hadoop.mapreduce.v2.app.AppContext;
|
||||||
|
import org.apache.hadoop.mapreduce.v2.app.job.Job;
|
||||||
|
import org.apache.hadoop.mapreduce.v2.app.job.Task;
|
||||||
|
import org.apache.hadoop.mapreduce.v2.app.job.TaskAttempt;
|
||||||
|
import org.apache.hadoop.mapreduce.v2.util.MRApps;
|
||||||
|
import org.apache.hadoop.yarn.webapp.hamlet.Hamlet;
|
||||||
|
import org.apache.hadoop.yarn.webapp.hamlet.Hamlet.DIV;
|
||||||
|
import org.apache.hadoop.yarn.webapp.hamlet.Hamlet.TABLE;
|
||||||
|
import org.apache.hadoop.yarn.webapp.hamlet.Hamlet.TBODY;
|
||||||
|
import org.apache.hadoop.yarn.webapp.hamlet.Hamlet.TR;
|
||||||
|
import org.apache.hadoop.yarn.webapp.view.HtmlBlock;
|
||||||
|
|
||||||
|
import static org.apache.hadoop.mapreduce.v2.app.webapp.AMWebApp.*;
|
||||||
|
import static org.apache.hadoop.yarn.webapp.view.JQueryUI.*;
|
||||||
|
|
||||||
|
public class SingleCounterBlock extends HtmlBlock {
|
||||||
|
protected TreeMap<String, Long> values = new TreeMap<String, Long>();
|
||||||
|
protected Job job;
|
||||||
|
protected Task task;
|
||||||
|
|
||||||
|
@Inject SingleCounterBlock(AppContext appCtx, ViewContext ctx) {
|
||||||
|
super(ctx);
|
||||||
|
this.populateMembers(appCtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override protected void render(Block html) {
|
||||||
|
if (job == null) {
|
||||||
|
html.
|
||||||
|
p()._("Sorry, no counters for nonexistent", $(JOB_ID, "job"))._();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!$(TASK_ID).isEmpty() && task == null) {
|
||||||
|
html.
|
||||||
|
p()._("Sorry, no counters for nonexistent", $(TASK_ID, "task"))._();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String columnType = task == null ? "Task" : "Task Attempt";
|
||||||
|
|
||||||
|
TBODY<TABLE<DIV<Hamlet>>> tbody = html.
|
||||||
|
div(_INFO_WRAP).
|
||||||
|
table("#singleCounter").
|
||||||
|
thead().
|
||||||
|
tr().
|
||||||
|
th(".ui-state-default", columnType).
|
||||||
|
th(".ui-state-default", "Value")._()._().
|
||||||
|
tbody();
|
||||||
|
for (Map.Entry<String, Long> entry : values.entrySet()) {
|
||||||
|
TR<TBODY<TABLE<DIV<Hamlet>>>> row = tbody.tr();
|
||||||
|
String id = entry.getKey();
|
||||||
|
String val = entry.getValue().toString();
|
||||||
|
if(task != null) {
|
||||||
|
row.td(id);
|
||||||
|
row.td().br().$title(val)._()._(val)._();
|
||||||
|
} else {
|
||||||
|
row.td().a(url("singletaskcounter",entry.getKey(),
|
||||||
|
$(COUNTER_GROUP), $(COUNTER_NAME)), id)._();
|
||||||
|
row.td().br().$title(val)._().a(url("singletaskcounter",entry.getKey(),
|
||||||
|
$(COUNTER_GROUP), $(COUNTER_NAME)), val)._();
|
||||||
|
}
|
||||||
|
row._();
|
||||||
|
}
|
||||||
|
tbody._()._()._();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void populateMembers(AppContext ctx) {
|
||||||
|
JobId jobID = null;
|
||||||
|
TaskId taskID = null;
|
||||||
|
String tid = $(TASK_ID);
|
||||||
|
if (!tid.isEmpty()) {
|
||||||
|
taskID = MRApps.toTaskID(tid);
|
||||||
|
jobID = taskID.getJobId();
|
||||||
|
} else {
|
||||||
|
String jid = $(JOB_ID);
|
||||||
|
if (!jid.isEmpty()) {
|
||||||
|
jobID = MRApps.toJobID(jid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (jobID == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
job = ctx.getJob(jobID);
|
||||||
|
if (job == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (taskID != null) {
|
||||||
|
task = job.getTask(taskID);
|
||||||
|
if (task == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for(Map.Entry<TaskAttemptId, TaskAttempt> entry :
|
||||||
|
task.getAttempts().entrySet()) {
|
||||||
|
long value = 0;
|
||||||
|
CounterGroup group = entry.getValue().getCounters()
|
||||||
|
.getCounterGroup($(COUNTER_GROUP));
|
||||||
|
if(group != null) {
|
||||||
|
Counter c = group.getCounter($(COUNTER_NAME));
|
||||||
|
if(c != null) {
|
||||||
|
value = c.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
values.put(MRApps.toString(entry.getKey()), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Get all types of counters
|
||||||
|
Map<TaskId, Task> tasks = job.getTasks();
|
||||||
|
for(Map.Entry<TaskId, Task> entry : tasks.entrySet()) {
|
||||||
|
long value = 0;
|
||||||
|
CounterGroup group = entry.getValue().getCounters()
|
||||||
|
.getCounterGroup($(COUNTER_GROUP));
|
||||||
|
if(group != null) {
|
||||||
|
Counter c = group.getCounter($(COUNTER_NAME));
|
||||||
|
if(c != null) {
|
||||||
|
value = c.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
values.put(MRApps.toString(entry.getKey()), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
/**
|
||||||
|
* 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.mapreduce.v2.app.webapp;
|
||||||
|
|
||||||
|
import static org.apache.hadoop.mapreduce.v2.app.webapp.AMParams.TASK_ID;
|
||||||
|
import static org.apache.hadoop.yarn.webapp.view.JQueryUI.*;
|
||||||
|
|
||||||
|
import org.apache.hadoop.mapreduce.v2.app.webapp.SingleCounterBlock;
|
||||||
|
import org.apache.hadoop.yarn.webapp.SubView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the counters page
|
||||||
|
*/
|
||||||
|
public class SingleCounterPage extends AppView {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (non-Javadoc)
|
||||||
|
* @see org.apache.hadoop.mapreduce.v2.hs.webapp.HsView#preHead(org.apache.hadoop.yarn.webapp.hamlet.Hamlet.HTML)
|
||||||
|
*/
|
||||||
|
@Override protected void preHead(Page.HTML<_> html) {
|
||||||
|
commonPreHead(html);
|
||||||
|
String tid = $(TASK_ID);
|
||||||
|
String activeNav = "3";
|
||||||
|
if(tid == null || tid.isEmpty()) {
|
||||||
|
activeNav = "2";
|
||||||
|
}
|
||||||
|
set(initID(ACCORDION, "nav"), "{autoHeight:false, active:"+activeNav+"}");
|
||||||
|
set(DATATABLES_ID, "singleCounter");
|
||||||
|
set(initID(DATATABLES, "singleCounter"), counterTableInit());
|
||||||
|
setTableStyles(html, "singleCounter");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The end of a javascript map that is the jquery datatable
|
||||||
|
* configuration for the jobs table. the Jobs table is assumed to be
|
||||||
|
* rendered by the class returned from {@link #content()}
|
||||||
|
*/
|
||||||
|
private String counterTableInit() {
|
||||||
|
return tableInit().
|
||||||
|
append(",aoColumnDefs:[").
|
||||||
|
append("{'sType':'title-numeric', 'aTargets': [ 1 ] }").
|
||||||
|
append("]}").
|
||||||
|
toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The content of this page is the CountersBlock now.
|
||||||
|
* @return CountersBlock.class
|
||||||
|
*/
|
||||||
|
@Override protected Class<? extends SubView> content() {
|
||||||
|
return SingleCounterBlock.class;
|
||||||
|
}
|
||||||
|
}
|
@ -108,7 +108,7 @@ protected Collection<TaskAttempt> getTaskAttempts() {
|
|||||||
|
|
||||||
@Override protected void preHead(Page.HTML<_> html) {
|
@Override protected void preHead(Page.HTML<_> html) {
|
||||||
commonPreHead(html);
|
commonPreHead(html);
|
||||||
set(initID(ACCORDION, "nav"), "{autoHeight:false, active:2}");
|
set(initID(ACCORDION, "nav"), "{autoHeight:false, active:3}");
|
||||||
set(DATATABLES_ID, "attempts");
|
set(DATATABLES_ID, "attempts");
|
||||||
set(initID(DATATABLES, "attempts"), attemptsTableInit());
|
set(initID(DATATABLES, "attempts"), attemptsTableInit());
|
||||||
setTableStyles(html, "attempts");
|
setTableStyles(html, "attempts");
|
||||||
|
@ -36,7 +36,6 @@
|
|||||||
import org.apache.hadoop.yarn.api.records.ApplicationAttemptId;
|
import org.apache.hadoop.yarn.api.records.ApplicationAttemptId;
|
||||||
import org.apache.hadoop.yarn.api.records.ApplicationId;
|
import org.apache.hadoop.yarn.api.records.ApplicationId;
|
||||||
import org.apache.hadoop.yarn.event.EventHandler;
|
import org.apache.hadoop.yarn.event.EventHandler;
|
||||||
import org.apache.hadoop.yarn.util.Apps;
|
|
||||||
import org.apache.hadoop.yarn.webapp.WebApps;
|
import org.apache.hadoop.yarn.webapp.WebApps;
|
||||||
import org.apache.hadoop.yarn.webapp.test.WebAppTests;
|
import org.apache.hadoop.yarn.webapp.test.WebAppTests;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@ -87,6 +86,7 @@ public Map<JobId, Job> getAllJobs() {
|
|||||||
return jobs; // OK
|
return jobs; // OK
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
@Override
|
@Override
|
||||||
public EventHandler getEventHandler() {
|
public EventHandler getEventHandler() {
|
||||||
return null;
|
return null;
|
||||||
@ -163,6 +163,23 @@ public static Map<String, String> getTaskParams(AppContext appContext) {
|
|||||||
new TestAppContext());
|
new TestAppContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test public void testCountersView() {
|
||||||
|
AppContext appContext = new TestAppContext();
|
||||||
|
Map<String, String> params = getJobParams(appContext);
|
||||||
|
WebAppTests.testPage(CountersPage.class, AppContext.class,
|
||||||
|
appContext, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void testSingleCounterView() {
|
||||||
|
AppContext appContext = new TestAppContext();
|
||||||
|
Map<String, String> params = getJobParams(appContext);
|
||||||
|
params.put(AMParams.COUNTER_GROUP,
|
||||||
|
"org.apache.hadoop.mapreduce.FileSystemCounter");
|
||||||
|
params.put(AMParams.COUNTER_NAME, "HDFS_WRITE_OPS");
|
||||||
|
WebAppTests.testPage(SingleCounterPage.class, AppContext.class,
|
||||||
|
appContext, params);
|
||||||
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
WebApps.$for("yarn", AppContext.class, new TestAppContext(0, 8, 88, 4)).
|
WebApps.$for("yarn", AppContext.class, new TestAppContext(0, 8, 88, 4)).
|
||||||
at(58888).inDevMode().start(new AMWebApp()).joinThread();
|
at(58888).inDevMode().start(new AMWebApp()).joinThread();
|
||||||
|
@ -18,6 +18,8 @@
|
|||||||
|
|
||||||
package org.apache.hadoop.mapreduce.v2.hs.webapp;
|
package org.apache.hadoop.mapreduce.v2.hs.webapp;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.apache.hadoop.conf.Configuration;
|
import org.apache.hadoop.conf.Configuration;
|
||||||
import org.apache.hadoop.mapreduce.v2.app.webapp.App;
|
import org.apache.hadoop.mapreduce.v2.app.webapp.App;
|
||||||
import org.apache.hadoop.mapreduce.v2.app.webapp.AppController;
|
import org.apache.hadoop.mapreduce.v2.app.webapp.AppController;
|
||||||
@ -57,7 +59,7 @@ protected Class<? extends View> jobPage() {
|
|||||||
* @see org.apache.hadoop.mapreduce.v2.app.webapp.AppController#countersPage()
|
* @see org.apache.hadoop.mapreduce.v2.app.webapp.AppController#countersPage()
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected Class<? extends View> countersPage() {
|
public Class<? extends View> countersPage() {
|
||||||
return HsCountersPage.class;
|
return HsCountersPage.class;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,6 +111,15 @@ public void jobCounters() {
|
|||||||
super.jobCounters();
|
super.jobCounters();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (non-Javadoc)
|
||||||
|
* @see org.apache.hadoop.mapreduce.v2.app.webapp.AppController#taskCounters()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void taskCounters() {
|
||||||
|
super.taskCounters();
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
* @see org.apache.hadoop.mapreduce.v2.app.webapp.AppController#tasks()
|
* @see org.apache.hadoop.mapreduce.v2.app.webapp.AppController#tasks()
|
||||||
@ -157,4 +168,31 @@ protected Class<? extends View> aboutPage() {
|
|||||||
public void about() {
|
public void about() {
|
||||||
render(aboutPage());
|
render(aboutPage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (non-Javadoc)
|
||||||
|
* @see org.apache.hadoop.mapreduce.v2.app.webapp.AppController#singleCounterPage()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected Class<? extends View> singleCounterPage() {
|
||||||
|
return HsSingleCounterPage.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (non-Javadoc)
|
||||||
|
* @see org.apache.hadoop.mapreduce.v2.app.webapp.AppController#singleJobCounter()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void singleJobCounter() throws IOException{
|
||||||
|
super.singleJobCounter();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (non-Javadoc)
|
||||||
|
* @see org.apache.hadoop.mapreduce.v2.app.webapp.AppController#singleTaskCounter()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void singleTaskCounter() throws IOException{
|
||||||
|
super.singleTaskCounter();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,11 +18,12 @@
|
|||||||
|
|
||||||
package org.apache.hadoop.mapreduce.v2.hs.webapp;
|
package org.apache.hadoop.mapreduce.v2.hs.webapp;
|
||||||
|
|
||||||
|
import static org.apache.hadoop.mapreduce.v2.app.webapp.AMParams.TASK_ID;
|
||||||
|
import static org.apache.hadoop.yarn.webapp.view.JQueryUI.*;
|
||||||
|
|
||||||
import org.apache.hadoop.mapreduce.v2.app.webapp.CountersBlock;
|
import org.apache.hadoop.mapreduce.v2.app.webapp.CountersBlock;
|
||||||
import org.apache.hadoop.yarn.webapp.SubView;
|
import org.apache.hadoop.yarn.webapp.SubView;
|
||||||
|
|
||||||
import static org.apache.hadoop.yarn.webapp.view.JQueryUI.*;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the counters page
|
* Render the counters page
|
||||||
*/
|
*/
|
||||||
@ -34,7 +35,12 @@ public class HsCountersPage extends HsView {
|
|||||||
*/
|
*/
|
||||||
@Override protected void preHead(Page.HTML<_> html) {
|
@Override protected void preHead(Page.HTML<_> html) {
|
||||||
commonPreHead(html);
|
commonPreHead(html);
|
||||||
set(initID(ACCORDION, "nav"), "{autoHeight:false, active:1}");
|
String tid = $(TASK_ID);
|
||||||
|
String activeNav = "2";
|
||||||
|
if(tid == null || tid.isEmpty()) {
|
||||||
|
activeNav = "1";
|
||||||
|
}
|
||||||
|
set(initID(ACCORDION, "nav"), "{autoHeight:false, active:"+activeNav+"}");
|
||||||
set(DATATABLES_SELECTOR, "#counters .dt-counters");
|
set(DATATABLES_SELECTOR, "#counters .dt-counters");
|
||||||
set(initSelector(DATATABLES),
|
set(initSelector(DATATABLES),
|
||||||
"{bJQueryUI:true, sDom:'t', iDisplayLength:-1}");
|
"{bJQueryUI:true, sDom:'t', iDisplayLength:-1}");
|
||||||
@ -47,9 +53,9 @@ public class HsCountersPage extends HsView {
|
|||||||
@Override protected void postHead(Page.HTML<_> html) {
|
@Override protected void postHead(Page.HTML<_> html) {
|
||||||
html.
|
html.
|
||||||
style("#counters, .dt-counters { table-layout: fixed }",
|
style("#counters, .dt-counters { table-layout: fixed }",
|
||||||
"#counters th { overflow: hidden; vertical-align: center }",
|
"#counters th { overflow: hidden; vertical-align: middle }",
|
||||||
"#counters .dataTables_wrapper { min-height: 1em }",
|
"#counters .dataTables_wrapper { min-height: 1em }",
|
||||||
"#counters .group { width: 10em }",
|
"#counters .group { width: 15em }",
|
||||||
"#counters .name { width: 30em }");
|
"#counters .name { width: 30em }");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,6 +55,14 @@ public class HsNavBlock extends HtmlBlock {
|
|||||||
li().a(url("conf", jobid), "Configuration")._().
|
li().a(url("conf", jobid), "Configuration")._().
|
||||||
li().a(url("tasks", jobid, "m"), "Map tasks")._().
|
li().a(url("tasks", jobid, "m"), "Map tasks")._().
|
||||||
li().a(url("tasks", jobid, "r"), "Reduce tasks")._()._();
|
li().a(url("tasks", jobid, "r"), "Reduce tasks")._()._();
|
||||||
|
if (app.getTask() != null) {
|
||||||
|
String taskid = MRApps.toString(app.getTask().getID());
|
||||||
|
nav.
|
||||||
|
h3("Task").
|
||||||
|
ul().
|
||||||
|
li().a(url("task", taskid), "Task Overview")._().
|
||||||
|
li().a(url("taskcounters", taskid), "Counters")._()._();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
nav.
|
nav.
|
||||||
h3("Tools").
|
h3("Tools").
|
||||||
|
@ -0,0 +1,69 @@
|
|||||||
|
/**
|
||||||
|
* 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.mapreduce.v2.hs.webapp;
|
||||||
|
|
||||||
|
import static org.apache.hadoop.mapreduce.v2.app.webapp.AMParams.TASK_ID;
|
||||||
|
import static org.apache.hadoop.yarn.webapp.view.JQueryUI.*;
|
||||||
|
|
||||||
|
import org.apache.hadoop.mapreduce.v2.app.webapp.SingleCounterBlock;
|
||||||
|
import org.apache.hadoop.yarn.webapp.SubView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the counters page
|
||||||
|
*/
|
||||||
|
public class HsSingleCounterPage extends HsView {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (non-Javadoc)
|
||||||
|
* @see org.apache.hadoop.mapreduce.v2.hs.webapp.HsView#preHead(org.apache.hadoop.yarn.webapp.hamlet.Hamlet.HTML)
|
||||||
|
*/
|
||||||
|
@Override protected void preHead(Page.HTML<_> html) {
|
||||||
|
commonPreHead(html);
|
||||||
|
String tid = $(TASK_ID);
|
||||||
|
String activeNav = "2";
|
||||||
|
if(tid == null || tid.isEmpty()) {
|
||||||
|
activeNav = "1";
|
||||||
|
}
|
||||||
|
set(initID(ACCORDION, "nav"), "{autoHeight:false, active:"+activeNav+"}");
|
||||||
|
set(DATATABLES_ID, "singleCounter");
|
||||||
|
set(initID(DATATABLES, "singleCounter"), counterTableInit());
|
||||||
|
setTableStyles(html, "singleCounter");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The end of a javascript map that is the jquery datatable
|
||||||
|
* configuration for the jobs table. the Jobs table is assumed to be
|
||||||
|
* rendered by the class returned from {@link #content()}
|
||||||
|
*/
|
||||||
|
private String counterTableInit() {
|
||||||
|
return tableInit().
|
||||||
|
append(", aoColumnDefs:[").
|
||||||
|
append("{'sType':'title-numeric', 'aTargets': [ 1 ] }").
|
||||||
|
append("]}").
|
||||||
|
toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The content of this page is the CountersBlock now.
|
||||||
|
* @return CountersBlock.class
|
||||||
|
*/
|
||||||
|
@Override protected Class<? extends SubView> content() {
|
||||||
|
return SingleCounterBlock.class;
|
||||||
|
}
|
||||||
|
}
|
@ -250,7 +250,7 @@ protected Collection<TaskAttempt> getTaskAttempts() {
|
|||||||
@Override protected void preHead(Page.HTML<_> html) {
|
@Override protected void preHead(Page.HTML<_> html) {
|
||||||
commonPreHead(html);
|
commonPreHead(html);
|
||||||
//override the nav config from commonPReHead
|
//override the nav config from commonPReHead
|
||||||
set(initID(ACCORDION, "nav"), "{autoHeight:false, active:1}");
|
set(initID(ACCORDION, "nav"), "{autoHeight:false, active:2}");
|
||||||
//Set up the java script and CSS for the attempts table
|
//Set up the java script and CSS for the attempts table
|
||||||
set(DATATABLES_ID, "attempts");
|
set(DATATABLES_ID, "attempts");
|
||||||
set(initID(DATATABLES, "attempts"), attemptsTableInit());
|
set(initID(DATATABLES, "attempts"), attemptsTableInit());
|
||||||
|
@ -41,10 +41,15 @@ public void setup() {
|
|||||||
route(pajoin("/job", JOB_ID), HsController.class, "job");
|
route(pajoin("/job", JOB_ID), HsController.class, "job");
|
||||||
route(pajoin("/conf", JOB_ID), HsController.class, "conf");
|
route(pajoin("/conf", JOB_ID), HsController.class, "conf");
|
||||||
route(pajoin("/jobcounters", JOB_ID), HsController.class, "jobCounters");
|
route(pajoin("/jobcounters", JOB_ID), HsController.class, "jobCounters");
|
||||||
|
route(pajoin("/singlejobcounter",JOB_ID, COUNTER_GROUP, COUNTER_NAME),
|
||||||
|
HsController.class, "singleJobCounter");
|
||||||
route(pajoin("/tasks", JOB_ID, TASK_TYPE), HsController.class, "tasks");
|
route(pajoin("/tasks", JOB_ID, TASK_TYPE), HsController.class, "tasks");
|
||||||
route(pajoin("/attempts", JOB_ID, TASK_TYPE, ATTEMPT_STATE),
|
route(pajoin("/attempts", JOB_ID, TASK_TYPE, ATTEMPT_STATE),
|
||||||
HsController.class, "attempts");
|
HsController.class, "attempts");
|
||||||
route(pajoin("/task", TASK_ID), HsController.class, "task");
|
route(pajoin("/task", TASK_ID), HsController.class, "task");
|
||||||
|
route(pajoin("/taskcounters", TASK_ID), HsController.class, "taskCounters");
|
||||||
|
route(pajoin("/singletaskcounter",TASK_ID, COUNTER_GROUP, COUNTER_NAME),
|
||||||
|
HsController.class, "singleTaskCounter");
|
||||||
route("/about", HsController.class, "about");
|
route("/about", HsController.class, "about");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,17 +26,13 @@
|
|||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.apache.hadoop.mapreduce.v2.api.records.JobId;
|
import org.apache.hadoop.mapreduce.v2.api.records.JobId;
|
||||||
import org.apache.hadoop.mapreduce.v2.api.records.TaskId;
|
|
||||||
import org.apache.hadoop.mapreduce.v2.app.AppContext;
|
import org.apache.hadoop.mapreduce.v2.app.AppContext;
|
||||||
import org.apache.hadoop.mapreduce.v2.app.MockJobs;
|
import org.apache.hadoop.mapreduce.v2.app.MockJobs;
|
||||||
import org.apache.hadoop.mapreduce.v2.app.job.Job;
|
import org.apache.hadoop.mapreduce.v2.app.job.Job;
|
||||||
import org.apache.hadoop.mapreduce.v2.app.job.Task;
|
|
||||||
import org.apache.hadoop.mapreduce.v2.app.webapp.AMParams;
|
|
||||||
import org.apache.hadoop.mapreduce.v2.app.webapp.TestAMWebApp;
|
import org.apache.hadoop.mapreduce.v2.app.webapp.TestAMWebApp;
|
||||||
import org.apache.hadoop.yarn.Clock;
|
import org.apache.hadoop.yarn.Clock;
|
||||||
import org.apache.hadoop.yarn.api.records.ApplicationAttemptId;
|
import org.apache.hadoop.yarn.api.records.ApplicationAttemptId;
|
||||||
@ -92,6 +88,7 @@ public Map<JobId, Job> getAllJobs() {
|
|||||||
return jobs; // OK
|
return jobs; // OK
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
@Override
|
@Override
|
||||||
public EventHandler getEventHandler() {
|
public EventHandler getEventHandler() {
|
||||||
return null;
|
return null;
|
||||||
@ -171,4 +168,16 @@ public void testTaskView() {
|
|||||||
WebAppTests.testPage(HsConfPage.class, AppContext.class,
|
WebAppTests.testPage(HsConfPage.class, AppContext.class,
|
||||||
new TestAppContext());
|
new TestAppContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test public void testAboutView() {
|
||||||
|
LOG.info("HsAboutPage");
|
||||||
|
WebAppTests.testPage(HsAboutPage.class, AppContext.class,
|
||||||
|
new TestAppContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void testSingleCounterView() {
|
||||||
|
LOG.info("HsSingleCounterPage");
|
||||||
|
WebAppTests.testPage(HsSingleCounterPage.class, AppContext.class,
|
||||||
|
new TestAppContext());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user