You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@oozie.apache.org by rk...@apache.org on 2014/06/19 01:18:22 UTC

git commit: OOZIE-1817 Oozie timers are not biased (rkanter)

Repository: oozie
Updated Branches:
  refs/heads/master d361ee4d4 -> e16448289


OOZIE-1817 Oozie timers are not biased (rkanter)


Project: http://git-wip-us.apache.org/repos/asf/oozie/repo
Commit: http://git-wip-us.apache.org/repos/asf/oozie/commit/e1644828
Tree: http://git-wip-us.apache.org/repos/asf/oozie/tree/e1644828
Diff: http://git-wip-us.apache.org/repos/asf/oozie/diff/e1644828

Branch: refs/heads/master
Commit: e164482891f0476f14480432f47689780c5c3bd8
Parents: d361ee4
Author: Robert Kanter <rk...@cloudera.com>
Authored: Wed Jun 18 16:08:58 2014 -0700
Committer: Robert Kanter <rk...@cloudera.com>
Committed: Wed Jun 18 16:08:58 2014 -0700

----------------------------------------------------------------------
 .../apache/oozie/client/rest/RestConstants.java |   2 +
 core/pom.xml                                    |  24 ++
 .../java/org/apache/oozie/command/Command.java  |  44 +--
 .../java/org/apache/oozie/command/XCommand.java |   3 +-
 .../coord/CoordActionInputCheckXCommand.java    |   6 -
 .../coord/CoordActionMaterializeCommand.java    |   3 -
 .../CoordMaterializeTransitionXCommand.java     |   5 +-
 .../oozie/service/InstrumentationService.java   |  72 ++--
 .../service/MetricsInstrumentationService.java  |  65 ++++
 .../java/org/apache/oozie/service/Services.java |  17 +-
 .../org/apache/oozie/service/XLogService.java   |   5 -
 .../apache/oozie/servlet/BaseAdminServlet.java  |  12 +-
 .../apache/oozie/servlet/JsonRestServlet.java   |   6 +-
 .../apache/oozie/servlet/V0AdminServlet.java    |   7 +-
 .../apache/oozie/servlet/V1AdminServlet.java    |   9 +-
 .../apache/oozie/servlet/V2AdminServlet.java    |  40 +++
 .../oozie/util/MetricsInstrumentation.java      | 334 +++++++++++++++++++
 .../main/java/org/apache/oozie/util/XLog.java   |   2 +
 .../service/TestInstrumentationService.java     |   8 +
 .../TestMetricsInstrumentationService.java      |  51 +++
 .../apache/oozie/util/TestInstrumentation.java  |  83 ++---
 .../oozie/util/TestMetricsInstrumentation.java  | 192 +++++++++++
 docs/src/site/twiki/AG_Install.twiki            |  20 ++
 docs/src/site/twiki/WebServicesAPI.twiki        |  89 ++++-
 pom.xml                                         |  18 +
 release-log.txt                                 |   1 +
 webapp/src/main/webapp/index.jsp                |   9 +-
 webapp/src/main/webapp/oozie-console.js         | 135 ++++++--
 28 files changed, 1108 insertions(+), 154 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/oozie/blob/e1644828/client/src/main/java/org/apache/oozie/client/rest/RestConstants.java
----------------------------------------------------------------------
diff --git a/client/src/main/java/org/apache/oozie/client/rest/RestConstants.java b/client/src/main/java/org/apache/oozie/client/rest/RestConstants.java
index 97fd465..5d3fc62 100644
--- a/client/src/main/java/org/apache/oozie/client/rest/RestConstants.java
+++ b/client/src/main/java/org/apache/oozie/client/rest/RestConstants.java
@@ -142,6 +142,8 @@ public interface RestConstants {
 
     public static final String ADMIN_QUEUE_DUMP_RESOURCE = "queue-dump";
 
+    public static final String ADMIN_METRICS_RESOURCE = "metrics";
+
     public static final String OOZIE_ERROR_CODE = "oozie-error-code";
 
     public static final String OOZIE_ERROR_MESSAGE = "oozie-error-message";

http://git-wip-us.apache.org/repos/asf/oozie/blob/e1644828/core/pom.xml
----------------------------------------------------------------------
diff --git a/core/pom.xml b/core/pom.xml
index c935dd7..e152266 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -301,6 +301,30 @@
             <scope>test</scope>
         </dependency>
 
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <scope>compile</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>com.codahale.metrics</groupId>
+            <artifactId>metrics-core</artifactId>
+            <scope>compile</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>com.codahale.metrics</groupId>
+            <artifactId>metrics-jvm</artifactId>
+            <scope>compile</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>com.codahale.metrics</groupId>
+            <artifactId>metrics-json</artifactId>
+            <scope>compile</scope>
+        </dependency>
+
         <!-- For drawing runtime DAG -->
         <dependency>
             <groupId>net.sf.jung</groupId>

http://git-wip-us.apache.org/repos/asf/oozie/blob/e1644828/core/src/main/java/org/apache/oozie/command/Command.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/oozie/command/Command.java b/core/src/main/java/org/apache/oozie/command/Command.java
index d3f1011..0e51b8e 100644
--- a/core/src/main/java/org/apache/oozie/command/Command.java
+++ b/core/src/main/java/org/apache/oozie/command/Command.java
@@ -38,6 +38,7 @@ import org.apache.oozie.service.XLogService;
 import org.apache.oozie.store.Store;
 import org.apache.oozie.store.StoreException;
 import org.apache.oozie.store.WorkflowStore;
+import org.apache.oozie.util.InstrumentUtils;
 import org.apache.oozie.util.Instrumentation;
 import org.apache.oozie.util.ParamChecker;
 import org.apache.oozie.util.XCallable;
@@ -295,7 +296,7 @@ public abstract class Command<T, S extends Store> implements XCallable<T> {
             FaultInjection.deactivate("org.apache.oozie.command.SkipCommitFaultInjection");
             cron.stop();
             instrumentation.addCron(INSTRUMENTATION_GROUP, name, cron);
-            incrCommandCounter(1);
+            InstrumentUtils.incrCommandCounter(name, 1, instrumentation);
             log.trace(logMask, "End");
             if (locks != null) {
                 for (LockToken lock : locks) {
@@ -506,47 +507,6 @@ public abstract class Command<T, S extends Store> implements XCallable<T> {
     }
 
     /**
-     * Convenience method to increment counters.
-     *
-     * @param group the group name.
-     * @param name the counter name.
-     * @param count increment count.
-     */
-    private void incrCounter(String group, String name, int count) {
-        if (instrumentation != null) {
-            instrumentation.incr(group, name, count);
-        }
-    }
-
-    /**
-     * Used to increment command counters.
-     *
-     * @param count the increment count.
-     */
-    protected void incrCommandCounter(int count) {
-        incrCounter(INSTRUMENTATION_GROUP, name, count);
-    }
-
-    /**
-     * Used to increment job counters. The counter name s the same as the command name.
-     *
-     * @param count the increment count.
-     */
-    protected void incrJobCounter(int count) {
-        incrJobCounter(name, count);
-    }
-
-    /**
-     * Used to increment job counters.
-     *
-     * @param name the job name.
-     * @param count the increment count.
-     */
-    protected void incrJobCounter(String name, int count) {
-        incrCounter(INSTRUMENTATION_JOB_GROUP, name, count);
-    }
-
-    /**
      * Return the {@link Instrumentation} instance in use.
      *
      * @return the {@link Instrumentation} instance in use.

http://git-wip-us.apache.org/repos/asf/oozie/blob/e1644828/core/src/main/java/org/apache/oozie/command/XCommand.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/oozie/command/XCommand.java b/core/src/main/java/org/apache/oozie/command/XCommand.java
index b712ce0..44e1133 100644
--- a/core/src/main/java/org/apache/oozie/command/XCommand.java
+++ b/core/src/main/java/org/apache/oozie/command/XCommand.java
@@ -204,7 +204,6 @@ public abstract class XCommand<T> implements XCallable<T> {
         }
         lock = Services.get().get(MemoryLocksService.class).getWriteLock(getEntityKey(), getLockTimeOut());
         if (lock == null) {
-            Instrumentation instrumentation = Services.get().get(InstrumentationService.class).get();
             instrumentation.incr(INSTRUMENTATION_GROUP, getName() + ".lockTimeOut", 1);
             if (isReQueueRequired()) {
                 //if not acquire the lock, re-queue itself with default delay
@@ -242,7 +241,6 @@ public abstract class XCommand<T> implements XCallable<T> {
         }
 
         commandQueue = null;
-        Instrumentation instrumentation = Services.get().get(InstrumentationService.class).get();
         instrumentation.incr(INSTRUMENTATION_GROUP, getName() + ".executions", 1);
         Instrumentation.Cron callCron = new Instrumentation.Cron();
         try {
@@ -324,6 +322,7 @@ public abstract class XCommand<T> implements XCallable<T> {
         }
         catch (Error er) {
             LOG.error("Error, ", er);
+            instrumentation.incr(INSTRUMENTATION_GROUP, getName() + ".errors", 1);
             throw er;
         }
         finally {

http://git-wip-us.apache.org/repos/asf/oozie/blob/e1644828/core/src/main/java/org/apache/oozie/command/coord/CoordActionInputCheckXCommand.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/oozie/command/coord/CoordActionInputCheckXCommand.java b/core/src/main/java/org/apache/oozie/command/coord/CoordActionInputCheckXCommand.java
index 0dc7e33..0a9e4fa 100644
--- a/core/src/main/java/org/apache/oozie/command/coord/CoordActionInputCheckXCommand.java
+++ b/core/src/main/java/org/apache/oozie/command/coord/CoordActionInputCheckXCommand.java
@@ -54,7 +54,6 @@ import org.apache.oozie.service.Services;
 import org.apache.oozie.service.URIHandlerService;
 import org.apache.oozie.util.DateUtils;
 import org.apache.oozie.util.ELEvaluator;
-import org.apache.oozie.util.Instrumentation;
 import org.apache.oozie.util.LogUtils;
 import org.apache.oozie.util.ParamChecker;
 import org.apache.oozie.util.StatusUtils;
@@ -145,11 +144,9 @@ public class CoordActionInputCheckXCommand extends CoordinatorXCommand<Void> {
         }
 
         StringBuilder actionXml = new StringBuilder(coordAction.getActionXml());
-        Instrumentation.Cron cron = new Instrumentation.Cron();
         boolean isChangeInDependency = false;
         try {
             Configuration actionConf = new XConfiguration(new StringReader(coordAction.getRunConf()));
-            cron.start();
             Date now = new Date();
             if (coordJob.getExecutionOrder().equals(CoordinatorJobBean.Execution.LAST_ONLY)) {
                 Date nextNominalTime = computeNextNominalTime();
@@ -260,9 +257,6 @@ public class CoordActionInputCheckXCommand extends CoordinatorXCommand<Void> {
             updateCoordAction(coordAction, isChangeInDependency);
             throw new CommandException(ErrorCode.E1021, e.getMessage(), e);
         }
-        finally {
-            cron.stop();
-        }
         return null;
     }
 

http://git-wip-us.apache.org/repos/asf/oozie/blob/e1644828/core/src/main/java/org/apache/oozie/command/coord/CoordActionMaterializeCommand.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/oozie/command/coord/CoordActionMaterializeCommand.java b/core/src/main/java/org/apache/oozie/command/coord/CoordActionMaterializeCommand.java
index 6962fb2..14dee97 100644
--- a/core/src/main/java/org/apache/oozie/command/coord/CoordActionMaterializeCommand.java
+++ b/core/src/main/java/org/apache/oozie/command/coord/CoordActionMaterializeCommand.java
@@ -127,8 +127,6 @@ public class CoordActionMaterializeCommand extends CoordinatorCommand<Void> {
                 throw new CommandException(ErrorCode.E1005, ioe.getMessage(), ioe);
             }
 
-            Instrumentation.Cron cron = new Instrumentation.Cron();
-            cron.start();
             try {
                 materializeJobs(false, job, jobConf, store);
                 updateJobTable(job, store);
@@ -142,7 +140,6 @@ public class CoordActionMaterializeCommand extends CoordinatorCommand<Void> {
                 log.error("Excepion thrown :", e);
                 throw new CommandException(ErrorCode.E1001, e.getMessage(), e);
             }
-            cron.stop();
         }
         else {
             log.info("WARN: action is not in PREMATER state!  It's in state=" + job.getStatus());

http://git-wip-us.apache.org/repos/asf/oozie/blob/e1644828/core/src/main/java/org/apache/oozie/command/coord/CoordMaterializeTransitionXCommand.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/oozie/command/coord/CoordMaterializeTransitionXCommand.java b/core/src/main/java/org/apache/oozie/command/coord/CoordMaterializeTransitionXCommand.java
index a562c77..23bafb8 100644
--- a/core/src/main/java/org/apache/oozie/command/coord/CoordMaterializeTransitionXCommand.java
+++ b/core/src/main/java/org/apache/oozie/command/coord/CoordMaterializeTransitionXCommand.java
@@ -357,9 +357,10 @@ public class CoordMaterializeTransitionXCommand extends MaterializeTransitionXCo
                 throw new CommandException(ErrorCode.E1011, jex);
             }
             throw new CommandException(ErrorCode.E1012, e.getMessage(), e);
+        } finally {
+            cron.stop();
+            instrumentation.addCron(INSTRUMENTATION_GROUP, getName() + ".materialize", cron);
         }
-        cron.stop();
-
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/oozie/blob/e1644828/core/src/main/java/org/apache/oozie/service/InstrumentationService.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/oozie/service/InstrumentationService.java b/core/src/main/java/org/apache/oozie/service/InstrumentationService.java
index 80437b1..e17ceb8 100644
--- a/core/src/main/java/org/apache/oozie/service/InstrumentationService.java
+++ b/core/src/main/java/org/apache/oozie/service/InstrumentationService.java
@@ -36,29 +36,57 @@ public class InstrumentationService implements Service {
 
     public static final String CONF_LOGGING_INTERVAL = CONF_PREFIX + "logging.interval";
 
-    private final XLog log = XLog.getLog("oozieinstrumentation");
+    private final XLog log = XLog.getLog(XLog.INSTRUMENTATION_LOG_NAME);
 
-    private Instrumentation instrumentation;
+    protected static Instrumentation instrumentation = null;
+
+    private static boolean isEnabled = false;
 
     /**
      * Initialize the instrumentation service.
      *
      * @param services services instance.
      */
+    @Override
     public void init(Services services) throws ServiceException {
-        instrumentation = new Instrumentation();
+        final Instrumentation instr = new Instrumentation();
+        int interval = services.getConf().getInt(CONF_LOGGING_INTERVAL, 60);
+        initLogging(services, instr, interval);
+        instr.addVariable(JVM_INSTRUMENTATION_GROUP, "free.memory", new Instrumentation.Variable<Long>() {
+            @Override
+            public Long getValue() {
+                return Runtime.getRuntime().freeMemory();
+            }
+        });
+        instr.addVariable(JVM_INSTRUMENTATION_GROUP, "max.memory", new Instrumentation.Variable<Long>() {
+            @Override
+            public Long getValue() {
+                return Runtime.getRuntime().maxMemory();
+            }
+        });
+        instr.addVariable(JVM_INSTRUMENTATION_GROUP, "total.memory", new Instrumentation.Variable<Long>() {
+            @Override
+            public Long getValue() {
+                return Runtime.getRuntime().totalMemory();
+            }
+        });
+        instrumentation = instr;
+        isEnabled = true;
+    }
+
+    protected void initLogging(Services services, final Instrumentation instr, int interval) throws ServiceException {
         log.info("*********** Startup ***********");
-        log.info("Java System Properties: {E}{0}", mapToString(instrumentation.getJavaSystemProperties()));
-        log.info("OS Env: {E}{0}", mapToString(instrumentation.getOSEnv()));
+        log.info("Java System Properties: {E}{0}", mapToString(instr.getJavaSystemProperties()));
+        log.info("OS Env: {E}{0}", mapToString(instr.getOSEnv()));
         SchedulerService schedulerService = services.get(SchedulerService.class);
         if (schedulerService != null) {
-            instrumentation.setScheduler(schedulerService.getScheduler());
-            int interval = services.getConf().getInt(CONF_LOGGING_INTERVAL, 60);
+            instr.setScheduler(schedulerService.getScheduler());
             if (interval > 0) {
                 Runnable instrumentationLogger = new Runnable() {
+                    @Override
                     public void run() {
                         try {
-                            log.info("\n" + instrumentation.toString());
+                            log.info("\n" + instr.toString());
                         }
                         catch (Throwable ex) {
                             log.warn("Instrumentation logging error", ex);
@@ -71,24 +99,9 @@ public class InstrumentationService implements Service {
         else {
             throw new ServiceException(ErrorCode.E0100, getClass().getName(), "SchedulerService unavailable");
         }
-        instrumentation.addVariable(JVM_INSTRUMENTATION_GROUP, "free.memory", new Instrumentation.Variable<Long>() {
-            public Long getValue() {
-                return Runtime.getRuntime().freeMemory();
-            }
-        });
-        instrumentation.addVariable(JVM_INSTRUMENTATION_GROUP, "max.memory", new Instrumentation.Variable<Long>() {
-            public Long getValue() {
-                return Runtime.getRuntime().maxMemory();
-            }
-        });
-        instrumentation.addVariable(JVM_INSTRUMENTATION_GROUP, "total.memory", new Instrumentation.Variable<Long>() {
-            public Long getValue() {
-                return Runtime.getRuntime().totalMemory();
-            }
-        });
     }
 
-    private String mapToString(Map<String, String> map) {
+    protected String mapToString(Map<String, String> map) {
         String E = System.getProperty("line.separator");
         StringBuilder sb = new StringBuilder();
         for (Map.Entry<String, String> entry : map.entrySet()) {
@@ -100,7 +113,9 @@ public class InstrumentationService implements Service {
     /**
      * Destroy the instrumentation service.
      */
+    @Override
     public void destroy() {
+        isEnabled = false;
         instrumentation = null;
     }
 
@@ -109,6 +124,7 @@ public class InstrumentationService implements Service {
      *
      * @return {@link InstrumentationService}.
      */
+    @Override
     public Class<? extends Service> getInterface() {
         return InstrumentationService.class;
     }
@@ -122,4 +138,12 @@ public class InstrumentationService implements Service {
         return instrumentation;
     }
 
+    /**
+     * Returns if the InstrumentationService is enabled or not.
+     *
+     * @return true if the InstrumentationService is enabled; false if not
+     */
+    public static boolean isEnabled() {
+        return isEnabled;
+    }
 }

http://git-wip-us.apache.org/repos/asf/oozie/blob/e1644828/core/src/main/java/org/apache/oozie/service/MetricsInstrumentationService.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/oozie/service/MetricsInstrumentationService.java b/core/src/main/java/org/apache/oozie/service/MetricsInstrumentationService.java
new file mode 100644
index 0000000..e428ed8
--- /dev/null
+++ b/core/src/main/java/org/apache/oozie/service/MetricsInstrumentationService.java
@@ -0,0 +1,65 @@
+/**
+ * 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.oozie.service;
+
+import org.apache.oozie.util.Instrumentation;
+import org.apache.oozie.util.MetricsInstrumentation;
+
+
+/**
+ * This service provides an {@link Instrumentation} instance mostly compatible with the original Instrumentation, but backed by
+ * Codahale Metrics. <p/> This service depends on the {@link SchedulerService}. <p/> The {@link #CONF_LOGGING_INTERVAL}
+ * configuration property indicates how often snapshots of the instrumentation should be logged.
+ */
+public class MetricsInstrumentationService extends InstrumentationService {
+
+    private static boolean isEnabled = false;
+
+    /**
+     * Initialize the metrics instrumentation service.
+     *
+     * @param services services instance.
+     * @throws org.apache.oozie.service.ServiceException
+     */
+    @Override
+    public void init(Services services) throws ServiceException {
+        final MetricsInstrumentation instr = new MetricsInstrumentation();
+        int interval = services.getConf().getInt(CONF_LOGGING_INTERVAL, 60);
+        initLogging(services, instr, interval);
+        instrumentation = instr;
+        isEnabled = true;
+    }
+
+    /**
+     * Destroy the metrics instrumentation service.
+     */
+    @Override
+    public void destroy() {
+        isEnabled = false;
+        instrumentation = null;
+    }
+
+    /**
+     * Returns if the MetricsInstrumentationService is enabled or not.
+     *
+     * @return true if the MetricsInstrumentationService is enabled; false if not
+     */
+    public static boolean isEnabled() {
+        return isEnabled;
+    }
+}

http://git-wip-us.apache.org/repos/asf/oozie/blob/e1644828/core/src/main/java/org/apache/oozie/service/Services.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/oozie/service/Services.java b/core/src/main/java/org/apache/oozie/service/Services.java
index 5feac7b..398e91f 100644
--- a/core/src/main/java/org/apache/oozie/service/Services.java
+++ b/core/src/main/java/org/apache/oozie/service/Services.java
@@ -21,10 +21,12 @@ import org.apache.commons.logging.LogFactory;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.util.ReflectionUtils;
 import org.apache.hadoop.util.VersionInfo;
+import org.apache.oozie.BuildInfo;
 import org.apache.oozie.client.OozieClient.SYSTEM_MODE;
 import org.apache.oozie.util.DateUtils;
 import org.apache.oozie.util.XLog;
 import org.apache.oozie.util.Instrumentable;
+import org.apache.oozie.util.Instrumentation;
 import org.apache.oozie.util.IOUtils;
 import org.apache.oozie.ErrorCode;
 
@@ -218,11 +220,24 @@ public class Services {
         }
         InstrumentationService instrService = get(InstrumentationService.class);
         if (instrService != null) {
+            Instrumentation instr = instrService.get();
             for (Service service : services.values()) {
                 if (service instanceof Instrumentable) {
-                    ((Instrumentable) service).instrument(instrService.get());
+                    ((Instrumentable) service).instrument(instr);
                 }
             }
+            instr.addVariable("oozie", "version", new Instrumentation.Variable<String>() {
+                @Override
+                public String getValue() {
+                    return BuildInfo.getBuildInfo().getProperty(BuildInfo.BUILD_VERSION);
+                }
+            });
+            instr.addVariable("oozie", "mode", new Instrumentation.Variable<String>() {
+                @Override
+                public String getValue() {
+                    return getSystemMode().toString();
+                }
+            });
         }
         log.info("Initialized");
         log.info("Running with JARs for Hadoop version [{0}]", VersionInfo.getVersion());

http://git-wip-us.apache.org/repos/asf/oozie/blob/e1644828/core/src/main/java/org/apache/oozie/service/XLogService.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/oozie/service/XLogService.java b/core/src/main/java/org/apache/oozie/service/XLogService.java
index 403a089..2df062e 100644
--- a/core/src/main/java/org/apache/oozie/service/XLogService.java
+++ b/core/src/main/java/org/apache/oozie/service/XLogService.java
@@ -312,11 +312,6 @@ public class XLogService implements Service, Instrumentable {
      * @param instr instrumentation to use.
      */
     public void instrument(Instrumentation instr) {
-        instr.addVariable("oozie", "version", new Instrumentation.Variable<String>() {
-            public String getValue() {
-                return BuildInfo.getBuildInfo().getProperty(BuildInfo.BUILD_VERSION);
-            }
-        });
         instr.addVariable(INSTRUMENTATION_GROUP, "config.file", new Instrumentation.Variable<String>() {
             public String getValue() {
                 return log4jFileName;

http://git-wip-us.apache.org/repos/asf/oozie/blob/e1644828/core/src/main/java/org/apache/oozie/servlet/BaseAdminServlet.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/oozie/servlet/BaseAdminServlet.java b/core/src/main/java/org/apache/oozie/servlet/BaseAdminServlet.java
index 0739cae..128347e 100644
--- a/core/src/main/java/org/apache/oozie/servlet/BaseAdminServlet.java
+++ b/core/src/main/java/org/apache/oozie/servlet/BaseAdminServlet.java
@@ -120,7 +120,7 @@ public abstract class BaseAdminServlet extends JsonRestServlet {
             sendJsonResponse(response, HttpServletResponse.SC_OK, json);
         }
         else if (resource.equals(RestConstants.ADMIN_INSTRUMENTATION_RESOURCE)) {
-            sendJsonResponse(response, HttpServletResponse.SC_OK, instrToJson(instr));
+            sendInstrumentationResponse(response, instr);
         }
         else if (resource.equals(RestConstants.ADMIN_BUILD_VERSION_RESOURCE)) {
             JSONObject json = new JSONObject();
@@ -156,6 +156,9 @@ public abstract class BaseAdminServlet extends JsonRestServlet {
             String sharelibKey = request.getParameter(RestConstants.SHARE_LIB_REQUEST_KEY);
             sendJsonResponse(response, HttpServletResponse.SC_OK, getShareLib(sharelibKey));
         }
+        else if (resource.equals(RestConstants.ADMIN_METRICS_RESOURCE)) {
+            sendMetricsResponse(response);
+        }
     }
 
     /**
@@ -384,5 +387,12 @@ public abstract class BaseAdminServlet extends JsonRestServlet {
         return array;
     }
 
+    protected void sendInstrumentationResponse(HttpServletResponse response, Instrumentation instr)
+            throws IOException, XServletException {
+        sendJsonResponse(response, HttpServletResponse.SC_OK, instrToJson(instr));
+    }
+
     protected abstract Map<String, String> getOozieURLs() throws XServletException;
+
+    protected abstract void sendMetricsResponse(HttpServletResponse response) throws IOException, XServletException;
 }

http://git-wip-us.apache.org/repos/asf/oozie/blob/e1644828/core/src/main/java/org/apache/oozie/servlet/JsonRestServlet.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/oozie/servlet/JsonRestServlet.java b/core/src/main/java/org/apache/oozie/servlet/JsonRestServlet.java
index 5c05acd..f7458dc 100644
--- a/core/src/main/java/org/apache/oozie/servlet/JsonRestServlet.java
+++ b/core/src/main/java/org/apache/oozie/servlet/JsonRestServlet.java
@@ -48,7 +48,7 @@ import java.util.concurrent.atomic.AtomicLong;
  */
 public abstract class JsonRestServlet extends HttpServlet {
 
-    private static final String JSTON_UTF8 = RestConstants.JSON_CONTENT_TYPE + "; charset=\"UTF-8\"";
+    static final String JSON_UTF8 = RestConstants.JSON_CONTENT_TYPE + "; charset=\"UTF-8\"";
 
     protected static final String XML_UTF8 = RestConstants.XML_CONTENT_TYPE + "; charset=\"UTF-8\"";
 
@@ -367,7 +367,7 @@ public abstract class JsonRestServlet extends HttpServlet {
             throws IOException {
         response.setStatus(statusCode);
         JSONObject json = bean.toJSONObject(timeZoneId);
-        response.setContentType(JSTON_UTF8);
+        response.setContentType(JSON_UTF8);
         json.writeJSONString(response.getWriter());
     }
 
@@ -396,7 +396,7 @@ public abstract class JsonRestServlet extends HttpServlet {
             response.sendError(statusCode);
         }
         response.setStatus(statusCode);
-        response.setContentType(JSTON_UTF8);
+        response.setContentType(JSON_UTF8);
         json.writeJSONString(response.getWriter());
     }
 

http://git-wip-us.apache.org/repos/asf/oozie/blob/e1644828/core/src/main/java/org/apache/oozie/servlet/V0AdminServlet.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/oozie/servlet/V0AdminServlet.java b/core/src/main/java/org/apache/oozie/servlet/V0AdminServlet.java
index 97bd81c..803dbf6 100644
--- a/core/src/main/java/org/apache/oozie/servlet/V0AdminServlet.java
+++ b/core/src/main/java/org/apache/oozie/servlet/V0AdminServlet.java
@@ -122,6 +122,11 @@ public class V0AdminServlet extends BaseAdminServlet {
 
     @Override
     protected Map<String, String> getOozieURLs() throws XServletException {
-        throw new XServletException(HttpServletResponse.SC_BAD_REQUEST, ErrorCode.E0302, "Not supported in v1");
+        throw new XServletException(HttpServletResponse.SC_BAD_REQUEST, ErrorCode.E0302, "Not supported in v0");
+    }
+
+    @Override
+    protected void sendMetricsResponse(HttpServletResponse response) throws IOException, XServletException {
+        throw new XServletException(HttpServletResponse.SC_BAD_REQUEST, ErrorCode.E0302, "Not supported in v0");
     }
 }

http://git-wip-us.apache.org/repos/asf/oozie/blob/e1644828/core/src/main/java/org/apache/oozie/servlet/V1AdminServlet.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/oozie/servlet/V1AdminServlet.java b/core/src/main/java/org/apache/oozie/servlet/V1AdminServlet.java
index a47f737..93e5ac9 100644
--- a/core/src/main/java/org/apache/oozie/servlet/V1AdminServlet.java
+++ b/core/src/main/java/org/apache/oozie/servlet/V1AdminServlet.java
@@ -46,7 +46,7 @@ public class V1AdminServlet extends BaseAdminServlet {
 
     private static final long serialVersionUID = 1L;
     private static final String INSTRUMENTATION_NAME = "v1admin";
-    private static final ResourceInfo RESOURCES_INFO[] = new ResourceInfo[12];
+    private static final ResourceInfo RESOURCES_INFO[] = new ResourceInfo[13];
 
     static {
         RESOURCES_INFO[0] = new ResourceInfo(RestConstants.ADMIN_STATUS_RESOURCE, Arrays.asList("PUT", "GET"),
@@ -74,6 +74,8 @@ public class V1AdminServlet extends BaseAdminServlet {
                 Collections.EMPTY_LIST);
         RESOURCES_INFO[11] = new ResourceInfo(RestConstants.ADMIN_LIST_SHARELIB, Arrays.asList("GET"),
                 Collections.EMPTY_LIST);
+        RESOURCES_INFO[12] = new ResourceInfo(RestConstants.ADMIN_METRICS_RESOURCE, Arrays.asList("GET"),
+                Collections.EMPTY_LIST);
 
     }
 
@@ -163,4 +165,9 @@ public class V1AdminServlet extends BaseAdminServlet {
     protected Map<String, String> getOozieURLs() throws XServletException {
         throw new XServletException(HttpServletResponse.SC_BAD_REQUEST, ErrorCode.E0302, "Not supported in v1");
     }
+
+    @Override
+    protected void sendMetricsResponse(HttpServletResponse response) throws IOException, XServletException {
+        throw new XServletException(HttpServletResponse.SC_BAD_REQUEST, ErrorCode.E0302, "Not supported in v1");
+    }
 }

http://git-wip-us.apache.org/repos/asf/oozie/blob/e1644828/core/src/main/java/org/apache/oozie/servlet/V2AdminServlet.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/oozie/servlet/V2AdminServlet.java b/core/src/main/java/org/apache/oozie/servlet/V2AdminServlet.java
index 002a367..b51ffb8 100644
--- a/core/src/main/java/org/apache/oozie/servlet/V2AdminServlet.java
+++ b/core/src/main/java/org/apache/oozie/servlet/V2AdminServlet.java
@@ -30,9 +30,12 @@ import org.apache.oozie.client.rest.JMSConnectionInfoBean;
 import org.apache.oozie.client.rest.JsonBean;
 import org.apache.oozie.jms.JMSConnectionInfo;
 import org.apache.oozie.jms.JMSJobEventListener;
+import org.apache.oozie.service.InstrumentationService;
 import org.apache.oozie.service.JMSTopicService;
 import org.apache.oozie.service.JobsConcurrencyService;
 import org.apache.oozie.service.Services;
+import org.apache.oozie.util.Instrumentation;
+import org.apache.oozie.util.MetricsInstrumentation;
 
 /**
  * V2 admin servlet
@@ -42,9 +45,19 @@ public class V2AdminServlet extends V1AdminServlet {
 
     private static final long serialVersionUID = 1L;
     private static final String INSTRUMENTATION_NAME = "v2admin";
+    private static MetricsInstrumentation metricsInstrumentation = null;
 
     public V2AdminServlet() {
         super(INSTRUMENTATION_NAME);
+
+        // If MetricsInstrumentationService is used, we will enable the metrics endpoint and disable the instrumentation endpoint
+        Services services = Services.get();
+        if (services != null) {
+            Instrumentation instrumentation = services.get(InstrumentationService.class).get();
+            if (instrumentation instanceof MetricsInstrumentation) {
+                metricsInstrumentation = (MetricsInstrumentation) instrumentation;
+            }
+        }
     }
 
     @Override
@@ -86,4 +99,31 @@ public class V2AdminServlet extends V1AdminServlet {
         }
         return serverUrls;
     }
+
+    @Override
+    protected void sendMetricsResponse(HttpServletResponse response) throws IOException, XServletException {
+        if (metricsInstrumentation != null) {
+            response.setStatus(HttpServletResponse.SC_OK);
+            response.setContentType(JSON_UTF8);
+            try {
+                metricsInstrumentation.writeJSONResponse(response.getOutputStream());
+            } finally {
+                response.getOutputStream().close();
+            }
+        } else {
+            response.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+            response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "MetricsInstrumentationService is not running");
+        }
+    }
+
+    @Override
+    protected void sendInstrumentationResponse(HttpServletResponse response, Instrumentation instr)
+            throws IOException, XServletException {
+        if (metricsInstrumentation == null) {
+            super.sendInstrumentationResponse(response, instr);
+        } else {
+            response.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+            response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "InstrumentationService is not running");
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/oozie/blob/e1644828/core/src/main/java/org/apache/oozie/util/MetricsInstrumentation.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/oozie/util/MetricsInstrumentation.java b/core/src/main/java/org/apache/oozie/util/MetricsInstrumentation.java
new file mode 100644
index 0000000..b5b4337
--- /dev/null
+++ b/core/src/main/java/org/apache/oozie/util/MetricsInstrumentation.java
@@ -0,0 +1,334 @@
+/**
+ * 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.oozie.util;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.ExponentiallyDecayingReservoir;
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.Histogram;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.json.MetricsModule;
+import com.codahale.metrics.jvm.MemoryUsageGaugeSet;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Instrumentation framework that is mostly compatible with {@link Instrumentation} but is backed by Codahale Metrics.  This class
+ * was designed to minimize the changes required to switch from {@link Instrumentation} to {@link MetricsInstrumentation} by keeping
+ * the same API.  However, certain operations are obviously implemented differently or are no longer needed; and the output format
+ * is a little different.  Internally, this class maps Cron to {@link com.codahale.metrics.Timer}, Variable to {@link Gauge},
+ * counter to {@link Counter}, and Sampler to {@link Histogram}.
+ */
+@SuppressWarnings("unchecked")
+public class MetricsInstrumentation extends Instrumentation {
+
+    private final MetricRegistry metricRegistry;
+    private transient ObjectMapper jsonMapper;
+    private ScheduledExecutorService scheduler;
+    private final LoadingCache<String, Counter> counters;
+    private final Map<String, Gauge> gauges;
+    private final LoadingCache<String, com.codahale.metrics.Timer> timers;
+    private final Map<String, Histogram> histograms;
+    private Lock timersLock;
+    private Lock gaugesLock;
+    private Lock countersLock;
+    private Lock histogramsLock;
+
+    private static final TimeUnit RATE_UNIT = TimeUnit.MILLISECONDS;
+    private static final TimeUnit DURATION_UNIT = TimeUnit.MILLISECONDS;
+
+    /**
+     * Creates the MetricsInstrumentation and starts taking some metrics.
+     */
+    public MetricsInstrumentation() {
+        metricRegistry = new MetricRegistry();
+
+        timersLock = new ReentrantLock();
+        gaugesLock = new ReentrantLock();
+        countersLock = new ReentrantLock();
+        histogramsLock = new ReentrantLock();
+
+        // Used for writing the json for the metrics (see com.codahale.metrics.servlets.MetricsServlet)
+        // The "false" is to prevent it from printing out all of the values used in the histograms and timers
+        this.jsonMapper = new ObjectMapper().registerModule(new MetricsModule(RATE_UNIT, DURATION_UNIT, false));
+
+        // Register the JVM memory gauges and prefix the keys
+        MemoryUsageGaugeSet memorySet = new MemoryUsageGaugeSet();
+        for (String key : memorySet.getMetrics().keySet()) {
+            metricRegistry.register(MetricRegistry.name("jvm", "memory", key), memorySet.getMetrics().get(key));
+        }
+
+        // By setting this up as a cache, if a counter doesn't exist when we try to retrieve it, it will automatically be created
+        counters = CacheBuilder.newBuilder().build(
+                new CacheLoader<String, Counter>() {
+                    @Override
+                    public Counter load(String key) throws Exception {
+                        Counter counter = new Counter();
+                        metricRegistry.register(key, counter);
+                        return counter;
+                    }
+                }
+        );
+        timers = CacheBuilder.newBuilder().build(
+                new CacheLoader<String, com.codahale.metrics.Timer>() {
+                    @Override
+                    public com.codahale.metrics.Timer load(String key) throws Exception {
+                        com.codahale.metrics.Timer timer
+                                = new com.codahale.metrics.Timer(new ExponentiallyDecayingReservoir());
+                        metricRegistry.register(key, timer);
+                        return timer;
+                    }
+                }
+        );
+        gauges = new ConcurrentHashMap<String, Gauge>();
+        histograms = new ConcurrentHashMap<String, Histogram>();
+    }
+
+    /**
+     * Add a cron to an instrumentation timer. The timer is created if it does not exists. <p/>
+     * Internally, this is backed by a {@link com.codahale.metrics.Timer}.
+     *
+     * @param group timer group.
+     * @param name timer name.
+     * @param cron cron to add to the timer.
+     */
+    @Override
+    public void addCron(String group, String name, Cron cron) {
+        String key = MetricRegistry.name(group, name, "timer");
+        try {
+            timersLock.lock();
+            com.codahale.metrics.Timer timer = timers.get(key);
+            timer.update(cron.getOwn(), TimeUnit.MILLISECONDS);
+        } catch(ExecutionException ee) {
+            throw new RuntimeException(ee);
+        } finally {
+            timersLock.unlock();
+        }
+    }
+
+    /**
+     * Add an instrumentation variable. <p/>
+     * Internally, this is backed by a {@link Gauge}.
+     *
+     * @param group counter group.
+     * @param name counter name.
+     * @param variable variable to add.
+     */
+    @Override
+    public void addVariable(String group, String name, final Variable variable) {
+        Gauge gauge = new Gauge() {
+            @Override
+            public Object getValue() {
+                return variable.getValue();
+            }
+        };
+        String key = MetricRegistry.name(group, name);
+
+        try {
+            gaugesLock.lock();
+            gauges.put(key, gauge);
+            // Metrics throws an Exception if we don't do this when the key already exists
+            if (metricRegistry.getGauges().containsKey(key)) {
+                XLog.getLog(MetricsInstrumentation.class).debug("A Variable with name [" + key + "] already exists. "
+                        + " The old Variable will be overwritten, but this is not recommended");
+                metricRegistry.remove(key);
+            }
+            metricRegistry.register(key, gauge);
+        } finally {
+            gaugesLock.unlock();
+        }
+    }
+
+   /**
+     * Increment an instrumentation counter. The counter is created if it does not exists. <p/>
+     * Internally, this is backed by a {@link Counter}.
+     *
+     * @param group counter group.
+     * @param name counter name.
+     * @param count increment to add to the counter.
+     */
+    @Override
+    public void incr(String group, String name, long count) {
+        String key = MetricRegistry.name(group, name);
+        try {
+            countersLock.lock();
+            counters.get(key).inc(count);
+        } catch(ExecutionException ee) {
+            throw new RuntimeException(ee);
+        } finally {
+            countersLock.unlock();
+        }
+    }
+
+    /**
+     * Add a sampling variable. <p/>
+     * Internally, this is backed by a biased (decaying) {@link Histogram}.
+     *
+     * @param group timer group.
+     * @param name timer name.
+     * @param period (ignored)
+     * @param interval sampling frequency, how often the variable is probed.
+     * @param variable variable to sample.
+     */
+    @Override
+    public void addSampler(String group, String name, int period, int interval, Variable<Long> variable) {
+        if (scheduler == null) {
+            throw new IllegalStateException("scheduler not set, cannot sample");
+        }
+        Histogram histogram = new Histogram(new ExponentiallyDecayingReservoir());
+        Sampler sampler = new Sampler(variable, histogram);
+        scheduler.scheduleAtFixedRate(sampler, 0, interval, TimeUnit.SECONDS);
+        String key = MetricRegistry.name(group, name, "histogram");
+        try {
+            histogramsLock.lock();
+            histograms.put(key, histogram);
+            // Metrics throws an Exception if we don't do this when the key already exists
+            if (metricRegistry.getHistograms().containsKey(key)) {
+                XLog.getLog(MetricsInstrumentation.class).debug("A Sampler with name [" + key + "] already exists. "
+                        + " The old Sampler will be overwritten, but this is not recommended");
+                metricRegistry.remove(key);
+            }
+            metricRegistry.register(key, histogram);
+        } finally {
+            histogramsLock.unlock();
+        }
+    }
+
+    public static class Sampler implements Runnable {
+        private final Variable<Long> variable;
+        private final Histogram histogram;
+        public Sampler(Variable<Long> variable, Histogram histogram) {
+            this.variable = variable;
+            this.histogram = histogram;
+        }
+
+        @Override
+        public void run() {
+            histogram.update(variable.getValue());
+        }
+    }
+
+    /**
+     * Set the scheduler instance to handle the samplers.
+     *
+     * @param scheduler scheduler instance.
+     */
+    @Override
+    public void setScheduler(ScheduledExecutorService scheduler) {
+        this.scheduler = scheduler;
+    }
+
+    /**
+     * Return the string representation of the instrumentation.  It does a JSON pretty-print.
+     *
+     * @return the string representation of the instrumentation.
+     */
+    @Override
+    public String toString() {
+        try {
+            return jsonMapper.writerWithDefaultPrettyPrinter().writeValueAsString(metricRegistry);
+        } catch (JsonProcessingException jpe) {
+            throw new RuntimeException(jpe);
+        }
+    }
+
+    /**
+     * Converts the current state of the metrics and writes them to the OutputStream.
+     *
+     * @param os The OutputStream to write the metrics to
+     * @throws IOException
+     */
+    public void writeJSONResponse(OutputStream os) throws IOException {
+        jsonMapper.writer().writeValue(os, metricRegistry);
+    }
+
+    /**
+     * Returns the MetricRegistry: public for unit tests -- do not use.
+     *
+     * @return the MetricRegistry
+     */
+    @VisibleForTesting
+    MetricRegistry getMetricRegistry() {
+        return metricRegistry;
+    }
+
+    /**
+     * Not Supported: throws {@link UnsupportedOperationException}
+     *
+     * @return nothing
+     */
+    @Override
+    public Map<String, Map<String, Map<String, Object>>> getAll() {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Not Supported: throws {@link UnsupportedOperationException}
+     *
+     * @return nothing
+     */
+    @Override
+    public Map<String, Map<String, Element<Long>>> getCounters() {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Not Supported: throws {@link UnsupportedOperationException}
+     *
+     * @return nothing
+     */
+    @Override
+    public Map<String, Map<String, Element<Double>>> getSamplers() {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Not Supported: throws {@link UnsupportedOperationException}
+     *
+     * @return nothing
+     */
+    @Override
+    public Map<String, Map<String, Element<Timer>>> getTimers() {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Not Supported: throws {@link UnsupportedOperationException}
+     *
+     * @return nothing
+     */
+    @Override
+    public Map<String, Map<String, Element<Variable>>> getVariables() {
+        throw new UnsupportedOperationException();
+    }
+}

http://git-wip-us.apache.org/repos/asf/oozie/blob/e1644828/core/src/main/java/org/apache/oozie/util/XLog.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/oozie/util/XLog.java b/core/src/main/java/org/apache/oozie/util/XLog.java
index 21e00c0..31a5ba0 100644
--- a/core/src/main/java/org/apache/oozie/util/XLog.java
+++ b/core/src/main/java/org/apache/oozie/util/XLog.java
@@ -34,6 +34,8 @@ import java.util.Map;
  */
 public class XLog implements Log {
 
+    public static final String INSTRUMENTATION_LOG_NAME = "oozieinstrumentation";
+
     /**
      * <code>LogInfo</code> stores contextual information to create log prefixes. <p/> <code>LogInfo</code> uses a
      * <code>ThreadLocal</code> to propagate the context. <p/> <code>LogInfo</code> context parameters are configurable

http://git-wip-us.apache.org/repos/asf/oozie/blob/e1644828/core/src/test/java/org/apache/oozie/service/TestInstrumentationService.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/oozie/service/TestInstrumentationService.java b/core/src/test/java/org/apache/oozie/service/TestInstrumentationService.java
index b5206cf..65c4cb2 100644
--- a/core/src/test/java/org/apache/oozie/service/TestInstrumentationService.java
+++ b/core/src/test/java/org/apache/oozie/service/TestInstrumentationService.java
@@ -18,6 +18,8 @@
 package org.apache.oozie.service;
 
 import org.apache.oozie.test.XTestCase;
+import org.apache.oozie.util.Instrumentation;
+import org.apache.oozie.util.MetricsInstrumentation;
 
 public class TestInstrumentationService extends XTestCase {
 
@@ -36,6 +38,12 @@ public class TestInstrumentationService extends XTestCase {
     public void testInstrumentation() throws Exception {
         assertNotNull(Services.get().get(InstrumentationService.class));
         assertNotNull(Services.get().get(InstrumentationService.class).get());
+        Instrumentation instr = Services.get().get(InstrumentationService.class).get();
+        assertFalse(instr instanceof MetricsInstrumentation);
     }
 
+    public void testIsEnabled() {
+        assertTrue(InstrumentationService.isEnabled());
+        assertFalse(MetricsInstrumentationService.isEnabled());
+    }
 }

http://git-wip-us.apache.org/repos/asf/oozie/blob/e1644828/core/src/test/java/org/apache/oozie/service/TestMetricsInstrumentationService.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/oozie/service/TestMetricsInstrumentationService.java b/core/src/test/java/org/apache/oozie/service/TestMetricsInstrumentationService.java
new file mode 100644
index 0000000..314d5a3
--- /dev/null
+++ b/core/src/test/java/org/apache/oozie/service/TestMetricsInstrumentationService.java
@@ -0,0 +1,51 @@
+/**
+ * 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.oozie.service;
+
+import org.apache.oozie.test.XTestCase;
+import org.apache.oozie.util.Instrumentation;
+import org.apache.oozie.util.MetricsInstrumentation;
+
+public class TestMetricsInstrumentationService extends XTestCase {
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        Services services = new Services();
+        services.getConf().set(Services.CONF_SERVICE_EXT_CLASSES, "org.apache.oozie.service.MetricsInstrumentationService");
+        services.init();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        Services.get().destroy();
+        super.tearDown();
+    }
+
+    public void testInstrumentation() throws Exception {
+        assertNotNull(Services.get().get(InstrumentationService.class));
+        assertNotNull(Services.get().get(InstrumentationService.class).get());
+        Instrumentation instr = Services.get().get(InstrumentationService.class).get();
+        assertTrue(instr instanceof MetricsInstrumentation);
+    }
+
+    public void testIsEnabled() {
+        assertFalse(InstrumentationService.isEnabled());
+        assertTrue(MetricsInstrumentationService.isEnabled());
+    }
+}

http://git-wip-us.apache.org/repos/asf/oozie/blob/e1644828/core/src/test/java/org/apache/oozie/util/TestInstrumentation.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/oozie/util/TestInstrumentation.java b/core/src/test/java/org/apache/oozie/util/TestInstrumentation.java
index 6040b47..47b0d8b 100644
--- a/core/src/test/java/org/apache/oozie/util/TestInstrumentation.java
+++ b/core/src/test/java/org/apache/oozie/util/TestInstrumentation.java
@@ -237,46 +237,49 @@ public class TestInstrumentation extends XTestCase {
     public void testSamplers() throws Exception {
         Instrumentation inst = new Instrumentation();
         ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(1);
-        inst.setScheduler(scheduledExecutorService);
-
-        inst.addSampler("a", "1", 10, 1, new Instrumentation.Variable<Long>() {
-            public Long getValue() {
-                return 1L;
-            }
-        });
-        assertEquals(1, inst.getSamplers().size());
-        assertEquals(1, inst.getSamplers().get("a").size());
-
-        inst.addSampler("a", "2", 10, 1, new Instrumentation.Variable<Long>() {
-            public Long getValue() {
-                return 2L;
-            }
-        });
-        assertEquals(1, inst.getSamplers().size());
-        assertEquals(2, inst.getSamplers().get("a").size());
-
-        inst.addSampler("b", "1", 10, 1, new Instrumentation.Variable<Long>() {
-            private long counter = 0;
-
-            public Long getValue() {
-                return counter++ % 10;
-            }
-        });
-        assertEquals(2, inst.getSamplers().size());
-        assertEquals(2, inst.getSamplers().get("a").size());
-        assertEquals(1, inst.getSamplers().get("b").size());
-
-        waitFor(20 * 1000, new Predicate() {
-            public boolean evaluate() throws Exception {
-                return false;
-            }
-        });
-
-        assertEquals("", 1D, inst.getSamplers().get("a").get("1").getValue(), 0.01D);
-        assertEquals("", 2D, inst.getSamplers().get("a").get("2").getValue(), 0.02D);
-        assertEquals("", 5D, inst.getSamplers().get("b").get("1").getValue(), 0.5D);
-
-        scheduledExecutorService.shutdownNow();
+        try {
+            inst.setScheduler(scheduledExecutorService);
+
+            inst.addSampler("a", "1", 10, 1, new Instrumentation.Variable<Long>() {
+                public Long getValue() {
+                    return 1L;
+                }
+            });
+            assertEquals(1, inst.getSamplers().size());
+            assertEquals(1, inst.getSamplers().get("a").size());
+
+            inst.addSampler("a", "2", 10, 1, new Instrumentation.Variable<Long>() {
+                public Long getValue() {
+                    return 2L;
+                }
+            });
+            assertEquals(1, inst.getSamplers().size());
+            assertEquals(2, inst.getSamplers().get("a").size());
+
+            inst.addSampler("b", "1", 10, 1, new Instrumentation.Variable<Long>() {
+                private long counter = 0;
+
+                public Long getValue() {
+                    return counter++ % 10;
+                }
+            });
+            assertEquals(2, inst.getSamplers().size());
+            assertEquals(2, inst.getSamplers().get("a").size());
+            assertEquals(1, inst.getSamplers().get("b").size());
+
+            waitFor(20 * 1000, new Predicate() {
+                public boolean evaluate() throws Exception {
+                    return false;
+                }
+            });
+
+            assertEquals("", 1D, inst.getSamplers().get("a").get("1").getValue(), 0.01D);
+            assertEquals("", 2D, inst.getSamplers().get("a").get("2").getValue(), 0.02D);
+            assertEquals("", 4D, inst.getSamplers().get("b").get("1").getValue(), 0.5D);
+
+        } finally {
+            scheduledExecutorService.shutdownNow();
+        }
     }
 
     public void testAll() throws Exception {

http://git-wip-us.apache.org/repos/asf/oozie/blob/e1644828/core/src/test/java/org/apache/oozie/util/TestMetricsInstrumentation.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/oozie/util/TestMetricsInstrumentation.java b/core/src/test/java/org/apache/oozie/util/TestMetricsInstrumentation.java
new file mode 100644
index 0000000..b6aed44
--- /dev/null
+++ b/core/src/test/java/org/apache/oozie/util/TestMetricsInstrumentation.java
@@ -0,0 +1,192 @@
+/**
+ * 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.oozie.util;
+
+import com.codahale.metrics.Metric;
+import com.codahale.metrics.MetricFilter;
+import com.codahale.metrics.Timer;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import org.apache.oozie.test.XTestCase;
+
+// Most tests adpated from TestInstrumentation
+public class TestMetricsInstrumentation extends XTestCase {
+    private static final long INTERVAL = 300;
+
+    // Filter that removes the "jvm.memory" gauges
+    private final MetricFilter noJvm = new MetricFilter() {
+        @Override
+        public boolean matches(String name, Metric metric) {
+            return !name.startsWith("jvm.memory");
+        }
+    };
+
+    public void testInstrumentationCounter() throws Exception {
+        MetricsInstrumentation inst = new MetricsInstrumentation();
+        assertEquals(0, inst.getMetricRegistry().getCounters().size());
+        inst.incr("a", "1", 1);
+        assertEquals(1, inst.getMetricRegistry().getCounters().size());
+        inst.incr("a", "2", 2);
+        assertEquals(2, inst.getMetricRegistry().getCounters().size());
+        inst.incr("b", "1", 3);
+        assertEquals(3, inst.getMetricRegistry().getCounters().size());
+        assertEquals(1L, inst.getMetricRegistry().getCounters().get("a.1").getCount());
+        assertEquals(2L, inst.getMetricRegistry().getCounters().get("a.2").getCount());
+        assertEquals(3L, inst.getMetricRegistry().getCounters().get("b.1").getCount());
+    }
+
+    private long getTimerValue(Timer timer) {
+        long[] values = timer.getSnapshot().getValues();
+        // These get stored in nanoseconds but Cron is in milliseconds
+        return TimeUnit.NANOSECONDS.toMillis(values[0]);
+    }
+
+    public void testInstrumentationTimer() throws Exception {
+        MetricsInstrumentation inst = new MetricsInstrumentation();
+        assertEquals(0, inst.getMetricRegistry().getTimers().size());
+        Instrumentation.Cron cron1 = new Instrumentation.Cron();
+        inst.addCron("a", "1", cron1);
+        assertEquals(1, inst.getMetricRegistry().getTimers().size());
+        Instrumentation.Cron cron2 = new Instrumentation.Cron();
+        cron2.start();
+        Thread.sleep(INTERVAL);
+        cron2.stop();
+        inst.addCron("a", "2", cron2);
+        assertEquals(2, inst.getMetricRegistry().getTimers().size());
+        Instrumentation.Cron cron3 = new Instrumentation.Cron();
+        cron3.start();
+        Thread.sleep(INTERVAL * 2);
+        cron3.stop();
+        inst.addCron("b", "1", cron3);
+        assertEquals(3, inst.getMetricRegistry().getTimers().size());
+
+        assertEquals(cron1.getOwn(), getTimerValue(inst.getMetricRegistry().getTimers().get("a.1.timer")));
+        assertEquals(cron2.getOwn(), getTimerValue(inst.getMetricRegistry().getTimers().get("a.2.timer")));
+        assertEquals(cron3.getOwn(), getTimerValue(inst.getMetricRegistry().getTimers().get("b.1.timer")));
+    }
+
+    public void testVariables() throws Exception {
+        MetricsInstrumentation inst = new MetricsInstrumentation();
+
+        inst.addVariable("a", "1", new Instrumentation.Variable<Long>() {
+            private long counter = 0;
+
+            public Long getValue() {
+                return counter++;
+            }
+        });
+        assertEquals(1, inst.getMetricRegistry().getGauges(noJvm).size());
+
+        inst.addVariable("a", "2", new Instrumentation.Variable<Long>() {
+            private long counter = 1;
+
+            public Long getValue() {
+                return counter++;
+            }
+        });
+        assertEquals(2, inst.getMetricRegistry().getGauges(noJvm).size());
+        inst.addVariable("b", "1", new Instrumentation.Variable<Long>() {
+            private long counter = 2;
+
+            public Long getValue() {
+                return counter++;
+            }
+        });
+        assertEquals(3, inst.getMetricRegistry().getGauges(noJvm).size());
+
+        assertEquals(0L, inst.getMetricRegistry().getGauges(noJvm).get("a.1").getValue());
+        assertEquals(1L, inst.getMetricRegistry().getGauges(noJvm).get("a.2").getValue());
+        assertEquals(2L, inst.getMetricRegistry().getGauges(noJvm).get("b.1").getValue());
+        assertEquals(1L, inst.getMetricRegistry().getGauges(noJvm).get("a.1").getValue());
+        assertEquals(2L, inst.getMetricRegistry().getGauges(noJvm).get("a.2").getValue());
+        assertEquals(3L, inst.getMetricRegistry().getGauges(noJvm).get("b.1").getValue());
+    }
+
+    public void testSamplers() throws Exception {
+        MetricsInstrumentation inst = new MetricsInstrumentation();
+        ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(1);
+        try {
+            inst.setScheduler(scheduledExecutorService);
+
+            inst.addSampler("a", "1", 10, 1, new Instrumentation.Variable<Long>() {
+                public Long getValue() {
+                    return 1L;
+                }
+            });
+            assertEquals(1, inst.getMetricRegistry().getHistograms().size());
+
+            inst.addSampler("a", "2", 10, 1, new Instrumentation.Variable<Long>() {
+                public Long getValue() {
+                    return 2L;
+                }
+            });
+            assertEquals(2, inst.getMetricRegistry().getHistograms().size());
+
+            inst.addSampler("b", "1", 10, 1, new Instrumentation.Variable<Long>() {
+                private long counter = 0;
+
+                public Long getValue() {
+                    return counter++ % 10;
+                }
+            });
+            assertEquals(3, inst.getMetricRegistry().getHistograms().size());
+
+            waitFor(20 * 1000, new Predicate() {
+                public boolean evaluate() throws Exception {
+                    return false;
+                }
+            });
+
+            assertEquals(1D, inst.getMetricRegistry().getHistograms().get("a.1.histogram").getSnapshot().getMean(), 0.01D);
+            assertEquals(2D, inst.getMetricRegistry().getHistograms().get("a.2.histogram").getSnapshot().getMean(), 0.02D);
+            assertEquals(4D, inst.getMetricRegistry().getHistograms().get("b.1.histogram").getSnapshot().getMean(), 0.5D);
+        } finally {
+            scheduledExecutorService.shutdownNow();
+        }
+    }
+
+    public void testUnsupportedOpertation() {
+        MetricsInstrumentation instr = new MetricsInstrumentation();
+        try {
+            instr.getAll();
+            fail();
+        } catch (UnsupportedOperationException uoe) {
+        }
+        try {
+            instr.getCounters();
+            fail();
+        } catch (UnsupportedOperationException uoe) {
+        }
+        try {
+            instr.getSamplers();
+            fail();
+        } catch (UnsupportedOperationException uoe) {
+        }
+        try {
+            instr.getTimers();
+            fail();
+        } catch (UnsupportedOperationException uoe) {
+        }
+        try {
+            instr.getVariables();
+            fail();
+        } catch (UnsupportedOperationException uoe) {
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/oozie/blob/e1644828/docs/src/site/twiki/AG_Install.twiki
----------------------------------------------------------------------
diff --git a/docs/src/site/twiki/AG_Install.twiki b/docs/src/site/twiki/AG_Install.twiki
index ce84e54..0d22fe9 100644
--- a/docs/src/site/twiki/AG_Install.twiki
+++ b/docs/src/site/twiki/AG_Install.twiki
@@ -706,6 +706,26 @@ similar. You will probably have to add your certificate as an exception.
 
 Refer to the [[./oozie-default.xml][oozie-default.xml]] for details.
 
+---+++ Using Metrics instead of Instrumentation
+
+As of version 4.1.0, Oozie includes a replacement for the Instrumentation based on Codahale's Metrics library.  It includes a
+number of improvements over the original Instrumentation included in Oozie.  They both report most of the same information, though
+the formatting is slightly different and there's some additional information in the Metrics version; the format of the output to the
+oozie-instrumentation log is also different.  The Metrics version can be enabled by adding the =MetricsInstrumentationService= to
+the list of services:
+     <verbatim>
+     <property>
+        <name>oozie.services.ext</name>
+        <value>
+            org.apache.oozie.service.MetricsInstrumentationService
+        </value>
+     </property>
+     </verbatim>
+
+Once enabled, the =admin/instrumentation= REST endpoint will no longer be available and instead the =admin/metrics= endpoint should
+be used (see the [[WebServicesAPI#Oozie_Metrics][Web Services API]] documentation for more details); the Oozie Web UI will also
+replace the "Instrumentation" tab with a "Metrics" tab.
+
 #HA
 ---+++ High Availability (HA)
 

http://git-wip-us.apache.org/repos/asf/oozie/blob/e1644828/docs/src/site/twiki/WebServicesAPI.twiki
----------------------------------------------------------------------
diff --git a/docs/src/site/twiki/WebServicesAPI.twiki b/docs/src/site/twiki/WebServicesAPI.twiki
index 5769924..6f31240 100644
--- a/docs/src/site/twiki/WebServicesAPI.twiki
+++ b/docs/src/site/twiki/WebServicesAPI.twiki
@@ -191,7 +191,10 @@ Content-Type: application/json;charset=UTF-8
 
 _Identical to the corresponding Oozie v0 WS API_
 
-A HTTP GET request returns the Oozie instrumentation information.
+A HTTP GET request returns the Oozie instrumentation information.  Keep in mind that timers and counters that the Oozie server
+hasn't incremented yet will not show up.
+
+*Note:* If Instrumentation is enabled, then Metrics is unavailable.
 
 *Request:*
 
@@ -270,6 +273,90 @@ Content-Type: application/json;charset=UTF-8
 }
 </verbatim>
 
+---++++ Oozie Metrics
+
+_Available in the Oozie v2 WS API and later_
+
+A HTTP GET request returns the Oozie metrics information.  Keep in mind that timers and counters that the Oozie server
+hasn't incremented yet will not show up.
+
+*Note:* If Metrics is enabled, then Instrumentation is unavailable.
+
+*Request:*
+
+<verbatim>
+GET /oozie/v2/admin/metrics
+</verbatim>
+
+*Response:*
+
+<verbatim>
+HTTP/1.1 200 OK
+Content-Type: application/json;charset=UTF-8
+.
+{
+   "gauges" : {
+        "jvm.memory.non-heap.committed" : {
+          "value" : 62590976
+        },
+        "oozie.mode" : {
+          "value" : "NORMAL"
+        },
+        ...
+    },
+    "timers" : {
+        "commands.action.end.call.timer" : {
+          "mean" : 108.5,
+          "p50" : 111.5,
+          "p75" : 165.5,
+          "p999" : 169,
+          "count" : 4,
+          "p95" : 169,
+          "max" : 169,
+          "mean_rate" : 0,
+          "duration_units" : "milliseconds",
+          "p98" : 169,
+          "m1_rate" : 0,
+          "rate_units" : "calls/millisecond",
+          "m15_rate" : 0,
+          "stddev" : 62.9417720330995,
+          "m5_rate" : 0,
+          "p99" : 169,
+          "min" : 42
+        },
+        ...
+    },
+    "histograms" : {
+        "callablequeue.threads.active.histogram" : {
+          "p999" : 1,
+          "mean" : 0.0625,
+          "min" : 0,
+          "p75" : 0,
+          "p95" : 1,
+          "count" : 48,
+          "p98" : 1,
+          "stddev" : 0.24462302739504083,
+          "max" : 1,
+          "p99" : 1,
+          "p50" : 0
+        },
+        ...
+    },
+    "counters" : {
+        "commands.job.info.executions" : {
+          "count" : 9
+        },
+        "jpa.CoordJobsGetPendingJPAExecutor" : {
+          "count" : 1
+        },
+        "jpa.GET_WORKFLOW" : {
+          "count" : 10
+        },
+        ...
+    }
+}
+</verbatim>
+
 ---++++ Version
 
 _Identical to the corresponding Oozie v0 WS API_

http://git-wip-us.apache.org/repos/asf/oozie/blob/e1644828/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index b5e0e4e..bad1e0f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -742,6 +742,24 @@
                 <version>2.1.7</version>
             </dependency>
 
+            <dependency>
+                <groupId>com.codahale.metrics</groupId>
+                <artifactId>metrics-core</artifactId>
+                <version>3.0.2</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.codahale.metrics</groupId>
+                <artifactId>metrics-jvm</artifactId>
+                <version>3.0.2</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.codahale.metrics</groupId>
+                <artifactId>metrics-json</artifactId>
+                <version>3.0.2</version>
+            </dependency>
+
             <!-- For drawing runtime DAG -->
             <dependency>
                 <groupId>net.sf.jung</groupId>

http://git-wip-us.apache.org/repos/asf/oozie/blob/e1644828/release-log.txt
----------------------------------------------------------------------
diff --git a/release-log.txt b/release-log.txt
index f14f81d..01d54a4 100644
--- a/release-log.txt
+++ b/release-log.txt
@@ -1,5 +1,6 @@
 -- Oozie 4.1.0 release (trunk - unreleased)
 
+OOZIE-1817 Oozie timers are not biased (rkanter)
 OOZIE-1807 Make bundle change command synchronous (puru via rohini)
 OOZIE-1678 HA support for SLA (ryota)
 OOZIE-1685 Oozie doesn’t process correctly workflows with a non-default name node (benjzh via rohini)

http://git-wip-us.apache.org/repos/asf/oozie/blob/e1644828/webapp/src/main/webapp/index.jsp
----------------------------------------------------------------------
diff --git a/webapp/src/main/webapp/index.jsp b/webapp/src/main/webapp/index.jsp
index 3c7ffe5..feedde9 100644
--- a/webapp/src/main/webapp/index.jsp
+++ b/webapp/src/main/webapp/index.jsp
@@ -53,9 +53,14 @@
            <a href="./docs/index.html" target="bottom">Documentation</a>
         </div>
         <%@ page
-            import="org.apache.oozie.sla.service.SLAService"%>
+            import="org.apache.oozie.sla.service.SLAService"
+            import="org.apache.oozie.service.InstrumentationService"
+            import="org.apache.oozie.service.MetricsInstrumentationService"
+        %>
         <%
             boolean isSLAServiceEnabled = SLAService.isEnabled();
+            boolean isInstrumentationServiceEnabled = InstrumentationService.isEnabled();
+            boolean isMetricsInstrumentationServiceEnabled = MetricsInstrumentationService.isEnabled();
         %>
         <div id="oozie-body" style="padding:2">
             <div class="x-tab-panel-header x-unselectable x-tab-strip-top" style="width:1048">
@@ -63,6 +68,8 @@
                  <script type="text/javascript">
                     var msg = "Oozie Web Console";
                     var isSLAServiceEnabled = "<%=isSLAServiceEnabled%>";
+                    var isInstrumentationServiceEnabled = "<%=isInstrumentationServiceEnabled%>";
+                    var isMetricsInstrumentationServiceEnabled = "<%=isMetricsInstrumentationServiceEnabled%>";
                     document.title = msg;
                     document.write(msg);
                  </script>

http://git-wip-us.apache.org/repos/asf/oozie/blob/e1644828/webapp/src/main/webapp/oozie-console.js
----------------------------------------------------------------------
diff --git a/webapp/src/main/webapp/oozie-console.js b/webapp/src/main/webapp/oozie-console.js
index 764d888..8f2098d 100644
--- a/webapp/src/main/webapp/oozie-console.js
+++ b/webapp/src/main/webapp/oozie-console.js
@@ -244,7 +244,7 @@ function treeNodeFromXml(XmlEl) {
     return result;
 }
 
-function treeNodeFromJson(json, rootText) {
+function treeNodeFromJsonInstrumentation(json, rootText) {
     var result = new Ext.tree.TreeNode({
         text: rootText
     });
@@ -255,10 +255,10 @@ function treeNodeFromJson(json, rootText) {
                 if (typeof json[i] == 'object') {
                     var c;
                     if (json[i]['group']) {
-                        c = treeNodeFromJson(json[i]['data'], json[i]['group']);
+                        c = treeNodeFromJsonInstrumentation(json[i]['data'], json[i]['group']);
                     }
                     else {
-                        c = treeNodeFromJson(json[i], json[i]['name']);
+                        c = treeNodeFromJsonInstrumentation(json[i], json[i]['name']);
                     }
                     if (c)
                         result.appendChild(c);
@@ -284,6 +284,44 @@ function treeNodeFromJson(json, rootText) {
     return result;
 }
 
+function treeNodeFromJsonMetrics(json, rootText) {
+    var result = new Ext.tree.TreeNode({
+        text: rootText
+    });
+    //  For Elements, process attributes and children
+    if (typeof json === 'object') {
+        for (var i in json) {
+            if (json[i]) {
+                if (typeof json[i] == 'object') {
+                    var c;
+                    if (json[i]) {
+                        c = treeNodeFromJsonMetrics(json[i], i);
+                        if (c) {
+                            result.appendChild(c);
+                        }
+                    }
+                }
+                else if (typeof json[i] != 'function') {
+                    result.appendChild(new Ext.tree.TreeNode({
+                        text: i + " -> " + json[i]
+                    }));
+                }
+            }
+            else {
+                result.appendChild(new Ext.tree.TreeNode({
+                    text: i + " -> " + json[i]
+                }));
+            }
+        }
+    }
+    else {
+        result.appendChild(new Ext.tree.TreeNode({
+            text: json
+        }));
+    }
+    return result;
+}
+
 // Common stuff to get a paging toolbar for a data store
 function getPagingBar(dataStore) {
     var pagingBar = new Ext.PagingToolbar({
@@ -2147,23 +2185,54 @@ var viewInstrumentation = new Ext.Action({
             url: getOozieBase() + 'admin/instrumentation',
             success: function(response, request) {
                 var jsonData = eval("(" + response.responseText + ")");
-                var timers = treeNodeFromJson(jsonData["timers"], "timers");
+                var timers = treeNodeFromJsonInstrumentation(jsonData["timers"], "timers");
                 timers.expanded = false;
-                var samplers = treeNodeFromJson(jsonData["samplers"], "samplers");
+                var samplers = treeNodeFromJsonInstrumentation(jsonData["samplers"], "samplers");
                 samplers.expanded = false;
-                var counters = treeNodeFromJson(jsonData["counters"], "counters");
+                var counters = treeNodeFromJsonInstrumentation(jsonData["counters"], "counters");
                 counters.expanded = false;
-                var variables = treeNodeFromJson(jsonData["variables"], "variables");
+                var variables = treeNodeFromJsonInstrumentation(jsonData["variables"], "variables");
                 variables.expanded = false;
-                while (treeRoot.hasChildNodes()) {
-                    var child = treeRoot.firstChild;
-                    treeRoot.removeChild(child);
+                while (instrumentationTreeRoot.hasChildNodes()) {
+                    var child = instrumentationTreeRoot.firstChild;
+                    instrumentationTreeRoot.removeChild(child);
                 }
-                treeRoot.appendChild(samplers);
-                treeRoot.appendChild(counters);
-                treeRoot.appendChild(timers);
-                treeRoot.appendChild(variables);
-                treeRoot.expand(false, true);
+                instrumentationTreeRoot.appendChild(samplers);
+                instrumentationTreeRoot.appendChild(counters);
+                instrumentationTreeRoot.appendChild(timers);
+                instrumentationTreeRoot.appendChild(variables);
+                instrumentationTreeRoot.expand(false, true);
+            }
+
+        });
+    }
+
+});
+var viewMetrics = new Ext.Action({
+    text: "&nbsp;&nbsp;&nbsp;",
+    icon: 'ext-2.2/resources/images/default/grid/refresh.gif',
+    handler: function() {
+        Ext.Ajax.request({
+            url: getOozieBase() + 'admin/metrics',
+            success: function(response, request) {
+                var jsonData = eval("(" + response.responseText + ")");
+                var timers = treeNodeFromJsonMetrics(jsonData["timers"], "timers");
+                timers.expanded = false;
+                var histograms = treeNodeFromJsonMetrics(jsonData["histograms"], "histograms");
+                histograms.expanded = false;
+                var counters = treeNodeFromJsonMetrics(jsonData["counters"], "counters");
+                counters.expanded = false;
+                var gauges = treeNodeFromJsonMetrics(jsonData["gauges"], "gauges");
+                gauges.expanded = false;
+                while (metricsTreeRoot.hasChildNodes()) {
+                    var child = metricsTreeRoot.firstChild;
+                    metricsTreeRoot.removeChild(child);
+                }
+                metricsTreeRoot.appendChild(counters);
+                metricsTreeRoot.appendChild(timers);
+                metricsTreeRoot.appendChild(histograms);
+                metricsTreeRoot.appendChild(gauges);
+                metricsTreeRoot.expand(false, true);
             }
 
         });
@@ -2199,11 +2268,16 @@ var viewOSDetails = new Ext.Action({
     }
 });
 
-var treeRoot = new Ext.tree.TreeNode({
+var instrumentationTreeRoot = new Ext.tree.TreeNode({
     text: "Instrumentation",
     expanded: true
 });
 
+var metricsTreeRoot = new Ext.tree.TreeNode({
+    text: "Metrics",
+    expanded: true
+});
+
 var timeZones_store = new Ext.data.JsonStore({
     autoLoad: true,
     root: 'available-timezones',
@@ -2349,16 +2423,25 @@ function initConsole() {
         animCollapse: false,
         title: "System Info"
     });
-    var resultArea = new Ext.tree.TreePanel({
+    var instrumentationArea = new Ext.tree.TreePanel({
         autoScroll: true,
         useArrows: true,
         height: 300,
-        root: treeRoot,
+        root: instrumentationTreeRoot,
         tbar: [viewInstrumentation, {
             xtype: 'tbfill'
         }, checkStatus, serverVersion],
         title: 'Instrumentation'
-
+    });
+    var metricsArea = new Ext.tree.TreePanel({
+        autoScroll: true,
+        useArrows: true,
+        height: 300,
+        root: metricsTreeRoot,
+        tbar: [viewMetrics, {
+            xtype: 'tbfill'
+        }, checkStatus, serverVersion],
+        title: 'Metrics'
     });
 
     var slaDashboard = new Ext.Panel({
@@ -2606,7 +2689,12 @@ function initConsole() {
         tabs.add(slaDashboard);
     }
     tabs.add(adminGrid);
-    tabs.add(resultArea);
+    if (isInstrumentationServiceEnabled == "true") {
+        tabs.add(instrumentationArea);
+    }
+    if (isMetricsInstrumentationServiceEnabled == "true") {
+        tabs.add(metricsArea);
+    }
     tabs.add(settingsArea);
     tabs.setActiveTab(jobs_grid);
     // showing Workflow Jobs active tab as default
@@ -2639,7 +2727,12 @@ function initConsole() {
     checkStatus.execute();
     viewConfig.execute();
     serverVersion.execute();
-    viewInstrumentation.execute();
+    if (isInstrumentationServiceEnabled == "true") {
+        viewInstrumentation.execute();
+    }
+    if (isMetricsInstrumentationServiceEnabled == "true") {
+        viewMetrics.execute();
+    }
     var jobId = getReqParam("job");
     if (jobId != "") {
         if (jobId.endsWith("-C")) {