Author: stefanegli
Date: Mon Jul 24 09:42:32 2017
New Revision: 1802777

URL: http://svn.apache.org/viewvc?rev=1802777&view=rev
Log:
SLING-5965 : added metrics for number of, oldest and timers for scheduler jobs, 
ability to group by thread pool or custom configurable filter, plus used all of 
this to build a HealthCheck out of it which complains if there is a scheduler 
job running for more than 60 seconds

Added:
    
sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/ConfigHolder.java
   (with props)
    
sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/GaugesSupport.java
   (with props)
    
sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/MetricsHelper.java
   (with props)
    
sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/SchedulerHealthCheck.java
   (with props)
    
sling/trunk/bundles/commons/scheduler/src/test/java/org/apache/sling/commons/scheduler/impl/MetricsHelperTest.java
   (with props)
Modified:
    sling/trunk/bundles/commons/scheduler/pom.xml
    
sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/QuartzJobExecutor.java
    
sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/QuartzScheduler.java
    
sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/QuartzSchedulerConfiguration.java

Modified: sling/trunk/bundles/commons/scheduler/pom.xml
URL: 
http://svn.apache.org/viewvc/sling/trunk/bundles/commons/scheduler/pom.xml?rev=1802777&r1=1802776&r2=1802777&view=diff
==============================================================================
--- sling/trunk/bundles/commons/scheduler/pom.xml (original)
+++ sling/trunk/bundles/commons/scheduler/pom.xml Mon Jul 24 09:42:32 2017
@@ -82,11 +82,13 @@
                             !weblogic.jdbc.vendor.oracle,
                             org.apache.sling.discovery;resolution:=optional,
                             org.apache.sling.settings;resolution:=optional,
+                            org.apache.sling.hc.api;resolution:=optional,
                             *                        
                         </Import-Package>
                         <DynamicImport-Package>
                             org.apache.sling.discovery;version="[1.0,2)",
                             org.apache.sling.settings;version="[1.0,2)",
+                            org.apache.sling.hc.api,
                             commonj.work,
                             com.mchange.v2.c3p0,
                             javax.ejb,
@@ -202,5 +204,20 @@
             <version>1.0.10</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+               <groupId>org.apache.sling</groupId>
+               <artifactId>org.apache.sling.commons.metrics</artifactId>
+               <version>1.2.0</version>
+        </dependency>
+        <dependency>
+               <groupId>org.apache.sling</groupId>
+               <artifactId>org.apache.sling.hc.core</artifactId>
+               <version>1.0.6</version>
+        </dependency>
+        <dependency>
+               <groupId>org.apache.sling</groupId>
+               <artifactId>org.apache.sling.commons.osgi</artifactId>
+               <version>2.1.0</version>
+        </dependency>
     </dependencies>
 </project>

Added: 
sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/ConfigHolder.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/ConfigHolder.java?rev=1802777&view=auto
==============================================================================
--- 
sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/ConfigHolder.java
 (added)
+++ 
sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/ConfigHolder.java
 Mon Jul 24 09:42:32 2017
@@ -0,0 +1,120 @@
+/*
+ * 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.sling.commons.scheduler.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.sling.commons.threads.ThreadPoolManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A holder of a QuartzSchedulerConfiguration which does some computation at
+ * construction time and provides accessors for those.
+ * <p>
+ * Specifically it creates maps of filter name to filter definition and
+ * vice-verca.
+ */
+public class ConfigHolder {
+
+    private static final Logger logger = 
LoggerFactory.getLogger(ConfigHolder.class);
+
+    private final QuartzSchedulerConfiguration config;
+
+    private final Map<String, String> filterSuffixes = new HashMap<>();
+    private final Map<String, Map<String, String>> filterDefinitions = new 
HashMap<>();
+
+    ConfigHolder(final QuartzSchedulerConfiguration config) {
+        this.config = config;
+        if (config == null) {
+            return;
+        }
+        final String[] groupFilter = config.metrics_filters();
+        if (groupFilter == null) {
+            return;
+        }
+        for (String aFilterDefinition : groupFilter) {
+            final String[] split = aFilterDefinition.split("=");
+            if (split == null || split.length != 2) {
+                logger.warn(
+                        "activate: ignoring wrongly formatted (expects 1 '=') 
filter definition: " + aFilterDefinition);
+                continue;
+            }
+            final String packageName = packageOf(split[1]);
+            if (packageName == null) {
+                logger.warn("activate: ignoring wrongly formatted filter 
definition, "
+                        + "expected fully qualified class name (except 
$anonymous or $inner class part) :" + aFilterDefinition);
+                continue;
+            }
+            if (split[1].contains("$") || split[1].contains("*") || 
split[1].contains("?")) {
+                logger.warn("activate: ignoring wrongly formatted filter 
definition, "
+                        + "disallowed character(s) used ($, *, ?) :" + 
aFilterDefinition);
+                continue;
+            }
+            filterSuffixes.put(split[0], split[1]);
+            Map<String, String> map = filterDefinitions.get(packageName);
+            if (map == null) {
+                map = new HashMap<>();
+                filterDefinitions.put(packageName, map);
+            }
+            map.put(split[1], split[0]);
+        }
+    }
+
+    static String packageOf(String jobClass) {
+        int lastDot = jobClass.lastIndexOf(".");
+        if (lastDot == -1) {
+            return null;
+        } else {
+            return jobClass.substring(0, lastDot);
+        }
+    }
+
+    String poolName() {
+        if (config == null) {
+            return ThreadPoolManager.DEFAULT_THREADPOOL_NAME;
+        } else {
+            return config.poolName();
+        }
+    }
+
+    String[] allowedPoolNames() {
+        if (config == null) {
+            return null;
+        } else {
+            return config.allowedPoolNames();
+        }
+    }
+
+    long slowThresholdMillis() {
+        if (config == null) {
+            return QuartzJobExecutor.DEFAULT_SLOW_JOB_THRESHOLD_MILLIS;
+        } else {
+            return config.slowThresholdMillis();
+        }
+    }
+
+    Map<String, String> getFilterSuffixes() {
+        return filterSuffixes;
+    }
+
+    Map<String, Map<String, String>> getFilterDefinitions() {
+        return filterDefinitions;
+    }
+
+}

Propchange: 
sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/ConfigHolder.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/GaugesSupport.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/GaugesSupport.java?rev=1802777&view=auto
==============================================================================
--- 
sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/GaugesSupport.java
 (added)
+++ 
sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/GaugesSupport.java
 Mon Jul 24 09:42:32 2017
@@ -0,0 +1,411 @@
+/*
+ * 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.sling.commons.scheduler.impl;
+
+import java.util.Date;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.NoSuchElementException;
+
+import org.apache.sling.commons.metrics.Gauge;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.quartz.JobDataMap;
+import org.quartz.JobDetail;
+import org.quartz.JobExecutionContext;
+import org.quartz.SchedulerException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(
+        property = {
+                Constants.SERVICE_VENDOR + "=The Apache Software Foundation"
+        },
+        immediate = true
+    )
+/**
+ * This service creates gauges for getting how long the oldest running job is
+ * already running.
+ * <p>
+ * If it hits slow jobs (which are configurable, default is at 1 second, it
+ * creates a temporary gauge for that. These temporary gauges subsequently
+ * become visible in JMX and allow to get insight information about jobs that 
eg
+ * run forever or for a very long time. The other measurements that are done,
+ * such as job timers (which also separate out slow jobs), only hit once a job
+ * is finished. This temporary gauge mechanism now additionally allows to see
+ * runtime information about currently running jobs (that might only become
+ * visible once they finish, which they might not or just in a long time)
+ */
+public class GaugesSupport {
+
+    private final static String CLEANUP_JOB_NAME = 
"org.apache.sling.commons.scheduler.impl.GaugesSupport.CleanupJob";
+
+    @SuppressWarnings("rawtypes")
+    private final class TemporaryGauge implements Gauge {
+        private final ServiceRegistration registration;
+        private final JobExecutionContext jobExecutionContext;
+        private final String gaugeName;
+        private volatile boolean unregistered = false;
+
+        private TemporaryGauge(BundleContext ctx, JobExecutionContext 
jobExecutionContext, String gaugeName) {
+            this.jobExecutionContext = jobExecutionContext;
+            this.gaugeName = gaugeName;
+
+            Dictionary<String, String> p = new Hashtable<String, String>();
+            p.put(Gauge.NAME, gaugeName);
+            registration = ctx.registerService(Gauge.class.getName(), 
TemporaryGauge.this, p);
+        }
+
+        private void unregister() {
+            synchronized (this) {
+                if (unregistered) {
+                    return;
+                }
+                unregistered = true;
+            }
+            synchronized (temporaryGauges) {
+                if (temporaryGauges.get(gaugeName) == TemporaryGauge.this) {
+                    logger.debug("unregister: unregistering active temporary 
gauge for slow job : " + gaugeName);
+                    temporaryGauges.remove(gaugeName);
+                } else {
+                    // else leaving it as is, there's already a new gauge with
+                    // the same name
+                    logger.debug("unregister: unregistering dangling temporary 
gauge for slow job : " + gaugeName);
+                }
+            }
+            registration.unregister();
+        }
+
+        @Override
+        public Long getValue() {
+            if (unregistered) {
+                return -1L;
+            }
+            if (!active) {
+                unregister();
+                return -1L; // quartzscheduler is no longer active, unregister
+            }
+            if (jobExecutionContext.getJobRunTime() != -1) {
+                unregister();
+                return -1L; // job is finished, unregister automatically
+            }
+            final Date oldestDate = jobExecutionContext.getFireTime();
+            if (oldestDate == null) {
+                // never fired? this should not happen - but unregister to be
+                // safe
+                unregister();
+                return -1L;
+            } else {
+                return System.currentTimeMillis() - oldestDate.getTime();
+            }
+        }
+    }
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    @SuppressWarnings("rawtypes")
+    private final Map<String, ServiceRegistration> gaugeRegistrations = new 
HashMap<String, ServiceRegistration>();
+    private final Map<String, TemporaryGauge> temporaryGauges = new 
HashMap<String, TemporaryGauge>();
+
+    private volatile boolean active = true;
+
+    @Reference
+    QuartzScheduler quartzScheduler;
+
+    private ConfigHolder configHolder;
+
+    private long bundleId;
+
+    @Activate
+    protected void activate(final BundleContext ctx) {
+        logger.debug("activate: activating.");
+        configHolder = quartzScheduler.configHolder;
+        active = true;
+
+        // register the gauges
+        registerGauges(ctx);
+
+        bundleId = ctx.getBundle().getBundleId();
+        try {
+            quartzScheduler.addPeriodicJob(bundleId, null, CLEANUP_JOB_NAME, 
new Runnable() {
+
+                @Override
+                public void run() {
+                    if (active) {
+                        cleanupTemporaryGauges();
+                    } // if the GaugesSupport isn't active anymore it is so
+                      // because
+                      // the QuartzScheduler was deactivated - which means we
+                      // don't
+                      // have to unregister the periodic job here.
+                    else {
+                        logger.debug("run: late executed periodic cleanup job 
- ignoring");
+                    }
+                }
+
+            }, null, 1800 /* 1800sec == 30min */, false);
+        } catch (SchedulerException e) {
+            throw new RuntimeException("Could not activate GaugesSupport due 
to " + e, e);
+        }
+    }
+
+    private void cleanupTemporaryGauges() {
+        logger.debug("cleanupTemporaryGauges: checking for unused temporary 
gauges.");
+        final long start = System.currentTimeMillis();
+        final Map<String, TemporaryGauge> localTemporaryGauges;
+        synchronized (temporaryGauges) {
+            localTemporaryGauges = new HashMap<String, 
TemporaryGauge>(temporaryGauges);
+        }
+        final Iterator<TemporaryGauge> it = 
localTemporaryGauges.values().iterator();
+        while (it.hasNext()) {
+            TemporaryGauge gauge = it.next();
+            // calling getValue will trigger the unregistration if applicable
+            gauge.getValue();
+        }
+        final int endCount;
+        synchronized (temporaryGauges) {
+            endCount = temporaryGauges.size();
+        }
+        final long diff = System.currentTimeMillis() - start;
+        logger.debug("cleanupTemporaryGauges: done. (temporary gauges at start 
: " + localTemporaryGauges.size()
+                + ", at end : " + endCount + ", cleanup took : " + diff + 
"ms)");
+    }
+
+    private void registerGauges(BundleContext ctx) {
+        createGauge(ctx, configHolder, null, null, 
QuartzScheduler.METRICS_NAME_OLDEST_RUNNING_JOB_MILLIS);
+        createGauge(ctx, configHolder, configHolder.poolName(), null,
+                QuartzScheduler.METRICS_NAME_OLDEST_RUNNING_JOB_MILLIS + 
".tp." + configHolder.poolName());
+        if (configHolder.allowedPoolNames() != null) {
+            for (String tpName : configHolder.allowedPoolNames()) {
+                createGauge(ctx, configHolder, tpName, null,
+                        QuartzScheduler.METRICS_NAME_OLDEST_RUNNING_JOB_MILLIS 
+ ".tp." + tpName);
+            }
+        }
+        for (Map.Entry<String, String> entry : 
configHolder.getFilterSuffixes().entrySet()) {
+            final String name = entry.getKey();
+            final String filterName = entry.getValue();
+            createGauge(ctx, configHolder, null, filterName,
+                    QuartzScheduler.METRICS_NAME_OLDEST_RUNNING_JOB_MILLIS + 
".filter." + name);
+        }
+    }
+
+    @SuppressWarnings("rawtypes")
+    private void createGauge(final BundleContext ctx, final ConfigHolder 
configHolder, final String tpName,
+            final String filterName, final String gaugeName) {
+        Dictionary<String, String> p = new Hashtable<String, String>();
+        p.put(Gauge.NAME, gaugeName);
+        final Gauge gauge = new Gauge() {
+            @Override
+            public Long getValue() {
+                if (!active) {
+                    return -1L; // disabled case
+                }
+                return getOldestRunningJobMillis(configHolder, ctx, tpName, 
filterName);
+            }
+        };
+        logger.debug("createGauge: registering gauge : " + gaugeName);
+        ServiceRegistration reg = ctx.registerService(Gauge.class.getName(), 
gauge, p);
+        synchronized (this.gaugeRegistrations) {
+            gaugeRegistrations.put(gaugeName, reg);
+        }
+    }
+
+    private Long getOldestRunningJobMillis(ConfigHolder configHolder, 
BundleContext ctx, String threadPoolNameOrNull,
+            String filterNameOrNull) {
+        final QuartzScheduler localQuartzScheduler = quartzScheduler;
+        if (localQuartzScheduler == null) {
+            // could happen during deactivation
+            return -1L;
+        }
+        Map<String, SchedulerProxy> schedulers = 
localQuartzScheduler.getSchedulers();
+        if (schedulers == null) {
+            // guessing this is should never happen - so just for paranoia
+            return -1L;
+        }
+
+        Date oldestDate = null;
+        if (filterNameOrNull == null && threadPoolNameOrNull != null) {
+            // if a threadPoolName is set and no filter then we go by
+            // threadPoolName
+            final SchedulerProxy schedulerProxy = 
schedulers.get(threadPoolNameOrNull);
+            oldestDate = getOldestRunningJobDate(configHolder, ctx, 
schedulerProxy, null);
+        } else {
+            // if nothing is set we iterate over everything
+            // if both threadPoolName and filter is set, filter has precedence
+            // (hence we iterate over everything but using the filter)
+            for (Map.Entry<String, SchedulerProxy> entry : 
schedulers.entrySet()) {
+                SchedulerProxy schedulerProxy = entry.getValue();
+                oldestDate = olderOf(oldestDate,
+                        getOldestRunningJobDate(configHolder, ctx, 
schedulerProxy, filterNameOrNull));
+            }
+        }
+        if (oldestDate == null) {
+            return -1L;
+        } else {
+            return System.currentTimeMillis() - oldestDate.getTime();
+        }
+    }
+
+    private Date getOldestRunningJobDate(final ConfigHolder configHolder, 
final BundleContext ctx,
+            final SchedulerProxy schedulerProxy, final String 
filterNameOrNull) {
+        if (schedulerProxy == null) {
+            return null;
+        }
+        final org.quartz.Scheduler scheduler = schedulerProxy.getScheduler();
+        if (scheduler == null) {
+            return null;
+        }
+        List<JobExecutionContext> currentlyExecutingJobs = null;
+        try {
+            currentlyExecutingJobs = scheduler.getCurrentlyExecutingJobs();
+        } catch (SchedulerException e) {
+            logger.warn("getValue: could not get currently executing jobs due 
to Exception: " + e, e);
+        }
+        if (currentlyExecutingJobs == null)
+            return null;
+        Date oldestDate = null;
+        for (JobExecutionContext jobExecutionContext : currentlyExecutingJobs) 
{
+            if (jobExecutionContext == null) {
+                continue;
+            }
+            if (filterNameOrNull != null) {
+                // apply the filter
+                JobDetail jobDetail = jobExecutionContext.getJobDetail();
+                JobDataMap map = null;
+                if (jobDetail != null) {
+                    map = jobDetail.getJobDataMap();
+                }
+                String filterName = null;
+                if (map != null) {
+                    filterName = MetricsHelper.deriveFilterName(configHolder, 
map.get(QuartzScheduler.DATA_MAP_OBJECT));
+                }
+                if (filterName == null || 
!filterNameOrNull.equals(filterName)) {
+                    // filter doens't match
+                    continue;
+                }
+                // filter matches, go ahead and get the fire time
+            }
+            final Date fireTime = jobExecutionContext.getFireTime();
+            final long elapsedMillis = System.currentTimeMillis() - 
fireTime.getTime();
+            final long slowThresholdMillis = 
configHolder.slowThresholdMillis();
+            if (slowThresholdMillis > 0 && elapsedMillis > 
slowThresholdMillis) {
+                // then create a gauge for this slow job in case there isn't 
one
+                // yet
+                createTemporaryGauge(ctx, jobExecutionContext);
+            }
+            oldestDate = olderOf(oldestDate, fireTime);
+        }
+        return oldestDate;
+    }
+
+    private static Date olderOf(Date date1, final Date date2) {
+        if (date1 == null)
+            return date2;
+        if (date2 == null)
+            return date1;
+        if (date2.before(date1)) {
+            return date2;
+        } else {
+            return date1;
+        }
+    }
+
+    private void createTemporaryGauge(final BundleContext ctx, final 
JobExecutionContext jobExecutionContext) {
+        final JobDataMap data = 
jobExecutionContext.getJobDetail().getJobDataMap();
+        final String jobName = data.getString(QuartzScheduler.DATA_MAP_NAME);
+        final String gaugeName = 
QuartzScheduler.METRICS_NAME_OLDEST_RUNNING_JOB_MILLIS + ".slow."
+                + MetricsHelper.asMetricsSuffix(jobName);
+        TemporaryGauge tempGauge;
+        synchronized (temporaryGauges) {
+            tempGauge = temporaryGauges.get(gaugeName);
+            if (tempGauge != null) {
+                // then there is already a gauge for this job execution
+                // check if it has the same jobExecutionContext
+                if (tempGauge.jobExecutionContext == jobExecutionContext) {
+                    // then all is fine, skip
+                    return;
+                }
+                // otherwise the current temporary gauge is an old one, that 
job
+                // execution has already finished
+                // so we should unregister that one and create a new one
+
+                // the unregister we want to do outside of this sync block
+                // though
+            }
+        }
+        if (tempGauge != null) {
+            logger.debug("createTemporaryGauge: unregistering temporary gauge 
for slow job : " + gaugeName);
+            tempGauge.unregister();
+        }
+        logger.debug("createTemporaryGauge: creating temporary gauge for slow 
job : " + gaugeName);
+        synchronized (this.temporaryGauges) {
+            temporaryGauges.put(gaugeName, new TemporaryGauge(ctx, 
jobExecutionContext, gaugeName));
+        }
+    }
+
+    @SuppressWarnings("rawtypes")
+    private void unregisterGauges() {
+        final Map<String, ServiceRegistration> localGaugeRegistrations;
+        synchronized (gaugeRegistrations) {
+            localGaugeRegistrations = new HashMap<String, 
ServiceRegistration>(gaugeRegistrations);
+            gaugeRegistrations.clear();
+        }
+        final Map<String, TemporaryGauge> localTemporaryGauges;
+        synchronized (temporaryGauges) {
+            localTemporaryGauges = new HashMap<String, 
TemporaryGauge>(temporaryGauges);
+        }
+
+        final Iterator<Entry<String, ServiceRegistration>> it = 
localGaugeRegistrations.entrySet().iterator();
+        while (it.hasNext()) {
+            final Entry<String, ServiceRegistration> e = it.next();
+            logger.debug("unregisterGauges: unregistering gauge : " + 
e.getKey());
+            e.getValue().unregister();
+        }
+
+        for (TemporaryGauge tempGauge : localTemporaryGauges.values()) {
+            logger.debug("unregisterGauges: unregistering temporary gauge for 
slow job : " + tempGauge.gaugeName);
+            tempGauge.unregister();
+        }
+    }
+
+    @Deactivate
+    void deactivate() {
+        logger.debug("deactivate: deactivating.");
+        active = false;
+
+        try {
+            // likely the job got removed by the QuartzScheduler itself,
+            // if not, lets remove it explicitly too
+            quartzScheduler.removeJob(bundleId, CLEANUP_JOB_NAME);
+        } catch (NoSuchElementException e) {
+            // this is fine
+        }
+
+        unregisterGauges();
+    }
+
+}

Propchange: 
sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/GaugesSupport.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/MetricsHelper.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/MetricsHelper.java?rev=1802777&view=auto
==============================================================================
--- 
sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/MetricsHelper.java
 (added)
+++ 
sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/MetricsHelper.java
 Mon Jul 24 09:42:32 2017
@@ -0,0 +1,109 @@
+/*
+ * 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.sling.commons.scheduler.impl;
+
+import java.util.Map;
+
+/**
+ * Small helper which is used to calculate metrics suffixes in a performant way
+ * (as that gets execute per job execution).
+ */
+class MetricsHelper {
+    
+    static final String UNKNOWN_JOBNAME_SUFFIX = "unknown";
+
+    /**
+     * Given a particular jobName/job tuple and based on a provided config,
+     * derive a filter name from it - if it is configured so.
+     * <p>
+     * This method is on the critical path wrt job execution so it is important
+     * for it to be implemented in a performing way.
+     */
+    static String deriveFilterName(final ConfigHolder configHolder, final 
Object job) {
+        if (configHolder == null) {
+            return null;
+        }
+        if (job == null) {
+            return null;
+        }
+        final Class<? extends Object> jobClass = job.getClass();
+        final String packageName = jobClass.getPackage().getName();
+        final Map<String, String> filterDefinitionMap = 
configHolder.getFilterDefinitions().get(packageName);
+        if (filterDefinitionMap == null) {
+            // then no match
+            return null;
+        }
+        String jobToStr = jobClass.getName();
+        final int dollarPos = jobToStr.indexOf("$");
+        if (dollarPos != -1) {
+            // cut off inner class name part
+            jobToStr = jobToStr.substring(0, dollarPos);
+        }
+        
+        return filterDefinitionMap.get(jobToStr);
+    }
+
+    /**
+     * Shortens the provided jobName for it to be useful as a metrics suffix.
+     * <p>
+     * This method will only be used for slow jobs, so it's not the
+     * most critical performance wise.
+     */
+    static String asMetricsSuffix(String jobName) {
+        if (jobName == null || jobName.length() == 0) {
+            return UNKNOWN_JOBNAME_SUFFIX;
+        }
+        // translate 
org.apache.jackrabbit.oak.jcr.observation.ChangeProcessor$4
+        // into oajojo.ChangeProcessor
+
+        // this used to do go via the job class, ie
+        // the job must be a Runnable or a scheduler Job,
+        // so getClass().getName() should return an actual package
+        // name.classname[$anonymous/inner]
+        // however, the same job class could in theory be used for
+        // multiple schedules - so the really unique thing with schedules
+        // is its name
+        // so we're also using the name here - but we're shortening it.
+
+        final StringBuffer shortified = new StringBuffer();
+        // cut off dollar
+        final int dollarPos = jobName.indexOf("$");
+        if (dollarPos != -1) {
+            jobName = jobName.substring(0, dollarPos);
+        }
+        final String[] split = jobName.split("\\.");
+        if (split.length <= 2) {
+            // then don't shorten at all
+            shortified.append(jobName);
+        } else {
+            for (int i = 0; i < split.length; i++) {
+                final String s = split[i];
+                if (i < split.length - 2) {
+                    // shorten
+                    if (s.length() > 0) {
+                        shortified.append(s.substring(0, 1));
+                    }
+                } else {
+                    // except for the last 2
+                    shortified.append(".").append(s);
+                }
+            }
+        }
+        return shortified.toString();
+    }
+
+}

Propchange: 
sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/MetricsHelper.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: 
sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/QuartzJobExecutor.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/QuartzJobExecutor.java?rev=1802777&r1=1802776&r2=1802777&view=diff
==============================================================================
--- 
sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/QuartzJobExecutor.java
 (original)
+++ 
sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/QuartzJobExecutor.java
 Mon Jul 24 09:42:32 2017
@@ -23,9 +23,13 @@ import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.Arrays;
 import java.util.Map;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 
+import org.apache.sling.commons.metrics.Counter;
+import org.apache.sling.commons.metrics.MetricsService;
+import org.apache.sling.commons.metrics.Timer;
 import org.apache.sling.commons.scheduler.JobContext;
 import org.apache.sling.commons.scheduler.Scheduler;
 import org.quartz.Job;
@@ -42,6 +46,8 @@ import org.slf4j.LoggerFactory;
  */
 public class QuartzJobExecutor implements Job {
 
+    static final int DEFAULT_SLOW_JOB_THRESHOLD_MILLIS = 1000;
+    
     /** Is discovery available? */
     public static final AtomicBoolean DISCOVERY_AVAILABLE = new 
AtomicBoolean(false);
 
@@ -66,14 +72,90 @@ public class QuartzJobExecutor implement
         public final String providedName;
         public final String name;
         public final String[] runOn;
+        
+        // SLING-5965 : piggybacking metrics field onto JobDesc
+        // to avoid having to create yet another object per job execution.
+        // creating such an additional object would require a bit more JVM-GC.
+        // but to keep JobDesc close to what it was originally intended for
+        // ('describing a job') keeping everything additional private
+        private final MetricsService metricsService;
+        private final Counter runningJobsCounter;
+        private final Counter overallRunningJobsCounter;
+        private final Timer jobDurationTimer;
+        // keeping a copy of the slowThresholdSecs to avoid having NPE when 
job is finished due to reconfig
+        private final long slowThresholdMillis;
+        private long jobStart = -1;
 
         public JobDesc(final JobDataMap data) {
             this.job = data.get(QuartzScheduler.DATA_MAP_OBJECT);
             this.name = (String) data.get(QuartzScheduler.DATA_MAP_NAME);
             this.providedName = 
(String)data.get(QuartzScheduler.DATA_MAP_PROVIDED_NAME);
             this.runOn = (String[])data.get(QuartzScheduler.DATA_MAP_RUN_ON);
+            
+            // initialize metrics fields
+            final QuartzScheduler localQuartzScheduler = (QuartzScheduler) 
data.get(QuartzScheduler.DATA_MAP_QUARTZ_SCHEDULER);
+            MetricsService localMetricsService = null;
+            ConfigHolder localConfigHolder = null;
+            if (localQuartzScheduler != null) {
+                // shouldn't be null but for paranoia
+                localMetricsService = localQuartzScheduler.metricsService;
+                localConfigHolder = localQuartzScheduler.configHolder;
+            }
+            // localMetricsService might be null during deactivation
+            metricsService = localMetricsService == null ? MetricsService.NOOP 
: localMetricsService;
+            // mainConfiguration might be null during deactivation
+            slowThresholdMillis = localConfigHolder != null ? 
localConfigHolder.slowThresholdMillis() : DEFAULT_SLOW_JOB_THRESHOLD_MILLIS;
+            
+            String metricsSuffix = "";
+            final String filterName = 
MetricsHelper.deriveFilterName(localConfigHolder, job);
+            if (filterName != null) {
+                metricsSuffix = ".filter." + filterName;
+            } else {
+                // no filter matches -> try (custom) thread pool
+                final String threadPoolName = 
data.getString(QuartzScheduler.DATA_MAP_THREAD_POOL_NAME);
+                if (threadPoolName != null) {
+                    // 'tp' stands for thread pool
+                    metricsSuffix = ".tp." + threadPoolName;
+                }
+            }
+            
+            runningJobsCounter = 
metricsService.counter(QuartzScheduler.METRICS_NAME_RUNNING_JOBS + 
metricsSuffix);
+            jobDurationTimer = 
metricsService.timer(QuartzScheduler.METRICS_NAME_TIMER + metricsSuffix);
+            overallRunningJobsCounter = metricsSuffix.length() == 0 ? null
+                    : 
metricsService.counter(QuartzScheduler.METRICS_NAME_RUNNING_JOBS);
+        }
+        
+        private void measureJobStart() {
+            // measure job start
+            if (overallRunningJobsCounter != null) 
overallRunningJobsCounter.increment();
+            runningJobsCounter.increment();
+            jobStart = System.currentTimeMillis();
+        }
+
+        private void measureJobEnd() {
+            if (jobStart == -1) {
+                // then measureJobStart was never invoked - hence not 
measuring anything
+                return;
+            }
+            
+            if (overallRunningJobsCounter != null) 
overallRunningJobsCounter.decrement();
+            runningJobsCounter.decrement();
+            final long elapsedMillis = System.currentTimeMillis() - jobStart;
+            // depending on slowness either measure via a separate 'slow' or 
the normal timer
+            // (and this triage can only be done by manual measuring)
+            if (slowThresholdMillis > 0 && elapsedMillis > 
slowThresholdMillis) {
+                // if the job was slow we (only) add it to a separate '.slow.' 
timer
+                // the idea being to not "pollute" the normal timer which would
+                // get quite skewed metrics otherwise with slow jobs around
+                final String slowTimerName = 
QuartzScheduler.METRICS_NAME_TIMER + ".slow."
+                        + MetricsHelper.asMetricsSuffix(this.name);
+                metricsService.timer(slowTimerName).update(elapsedMillis, 
TimeUnit.MILLISECONDS);
+            } else {
+                // if the job was not slow, then measure it normally
+                jobDurationTimer.update(elapsedMillis, TimeUnit.MILLISECONDS);
+            }
         }
-
+        
         public boolean isKnownJob() {
             return this.job != null && this.name != null;
         }
@@ -230,6 +312,7 @@ public class QuartzJobExecutor implement
             return;
         }
 
+        desc.measureJobStart();
         String origThreadName = Thread.currentThread().getName();
         try {
             Thread.currentThread().setName(origThreadName + "-" + desc.name);
@@ -255,6 +338,7 @@ public class QuartzJobExecutor implement
             logger.error("Exception during job execution of " + desc + " : " + 
t.getMessage(), t);
         } finally {
             Thread.currentThread().setName(origThreadName);
+            desc.measureJobEnd();
         }
     }
 

Modified: 
sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/QuartzScheduler.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/QuartzScheduler.java?rev=1802777&r1=1802776&r2=1802777&view=diff
==============================================================================
--- 
sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/QuartzScheduler.java
 (original)
+++ 
sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/QuartzScheduler.java
 Mon Jul 24 09:42:32 2017
@@ -26,6 +26,7 @@ import java.util.NoSuchElementException;
 import java.util.Set;
 import java.util.UUID;
 
+import org.apache.sling.commons.metrics.MetricsService;
 import org.apache.sling.commons.scheduler.Job;
 import org.apache.sling.commons.scheduler.ScheduleOptions;
 import org.apache.sling.commons.scheduler.Scheduler;
@@ -94,12 +95,28 @@ public class QuartzScheduler implements
     /** Map key for the bundle information (Long). */
     static final String DATA_MAP_SERVICE_ID = "QuartzJobScheduler.serviceId";
 
+    /** Map key for the quartz scheduler */
+    static final String DATA_MAP_QUARTZ_SCHEDULER = 
"QuartzJobScheduler.QuartzScheduler";
+    
+    static final String DATA_MAP_THREAD_POOL_NAME = 
"QuartzJobScheduler.threadPoolName";
+
+    static final String METRICS_NAME_RUNNING_JOBS = 
"commons.scheduler.running.jobs";
+
+    static final String METRICS_NAME_TIMER = "commons.scheduler.timer";
+
+    static final String METRICS_NAME_OLDEST_RUNNING_JOB_MILLIS = 
"commons.scheduler.oldest.running.job.millis";
+
     /** Default logger. */
     private final Logger logger = LoggerFactory.getLogger(this.getClass());
 
     @Reference
     private ThreadPoolManager threadPoolManager;
 
+    @Reference 
+    MetricsService metricsService;
+    
+    ConfigHolder configHolder;
+    
     /** The quartz schedulers. */
     private final Map<String, SchedulerProxy> schedulers = new HashMap<>();
 
@@ -128,6 +145,8 @@ public class QuartzScheduler implements
         }
         ctx.addBundleListener(this);
 
+        this.configHolder = new ConfigHolder(configuration);
+        
         this.active = true;
     }
 
@@ -254,6 +273,8 @@ public class QuartzScheduler implements
         }
         jobDataMap.put(DATA_MAP_NAME, jobName);
         jobDataMap.put(DATA_MAP_LOGGER, this.logger);
+        jobDataMap.put(DATA_MAP_QUARTZ_SCHEDULER, this);
+        jobDataMap.put(DATA_MAP_THREAD_POOL_NAME, 
getThreadPoolName(options.threadPoolName));
         if ( bundleId != null ) {
             jobDataMap.put(DATA_MAP_BUNDLE_ID, bundleId);
         }

Modified: 
sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/QuartzSchedulerConfiguration.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/QuartzSchedulerConfiguration.java?rev=1802777&r1=1802776&r2=1802777&view=diff
==============================================================================
--- 
sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/QuartzSchedulerConfiguration.java
 (original)
+++ 
sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/QuartzSchedulerConfiguration.java
 Mon Jul 24 09:42:32 2017
@@ -43,4 +43,22 @@ import org.osgi.service.metatype.annotat
             description="If a job is to be run on a single instance only, 
disable distribution and always run on leader."
         )
     boolean scheduler_useleaderforsingle() default false;
+
+    @AttributeDefinition(
+            name = "Metrics Filters",
+            description="Whether the metrics (sling:commons.scheduler.*) 
should be grouped by filters. "
+                    + "If configured this grouping happens before any thread 
pool grouping. "
+                    + "Filter format: filter1=ParticularJob"
+            )
+    String[] metrics_filters();
+    
+    @AttributeDefinition(
+            name = "Slow Job Threshold (millis)",
+            description="duration in millis after which a job is considered 
slow."
+                    + " Slow jobs are detected while measuring oldest running 
jobs (via a Gauge) and when the job is finished."
+                    + " In the former case a temporary gauge is added with the 
job name as the suffix."
+                    + " In the latter case a permanent timer is added, also 
with the job name as the suffix."
+                    + " 0 or a negative value disables this feature."
+        )
+    int slowThresholdMillis() default 
QuartzJobExecutor.DEFAULT_SLOW_JOB_THRESHOLD_MILLIS;
 }

Added: 
sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/SchedulerHealthCheck.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/SchedulerHealthCheck.java?rev=1802777&view=auto
==============================================================================
--- 
sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/SchedulerHealthCheck.java
 (added)
+++ 
sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/SchedulerHealthCheck.java
 Mon Jul 24 09:42:32 2017
@@ -0,0 +1,177 @@
+/*
+ * 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.sling.commons.scheduler.impl;
+
+import java.util.Iterator;
+import java.util.Map.Entry;
+import java.util.SortedMap;
+
+import org.apache.sling.hc.api.HealthCheck;
+import org.apache.sling.hc.api.Result;
+import org.apache.sling.hc.util.FormattingResultLog;
+import org.osgi.framework.Constants;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Modified;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.Metric;
+import com.codahale.metrics.MetricFilter;
+import com.codahale.metrics.MetricRegistry;
+
+/**
+ * HealthCheck that checks if the Sling Scheduler has any long-running (by
+ * default more than 60 seconds) Quartz-Jobs. If there are any long running
+ * Quartz-Jobs they occupy threads from the (limited) thread-pool and might 
lead
+ * to preventing other Quartz-Jobs from executing properly. The threshold
+ * (60sec) is configurable.
+ */
+@Component(service = HealthCheck.class, property = { Constants.SERVICE_VENDOR 
+ "=The Apache Software Foundation",
+        HealthCheck.NAME + "=Scheduler Health Check",
+        HealthCheck.MBEAN_NAME + "=slingCommonsSchedulerHealthCheck" }, 
immediate = true)
+@Designate(ocd = SchedulerHealthCheck.Config.class)
+public class SchedulerHealthCheck implements HealthCheck {
+
+    protected final Logger logger = LoggerFactory.getLogger(getClass());
+
+    @Reference
+    private MetricRegistry metricRegistry;
+
+    @ObjectClassDefinition(name = "Apache Sling Scheduler Health Check 
Config", description = "Apache Sling Scheduler Health Check Config")
+    public @interface Config {
+        @AttributeDefinition(name = "Acceptable Duration Millis", description 
= "Maximum a job should take (in millis) for it to be acceptable. "
+                + "Best to set this equal or higher to 
org.apache.sling.commons.scheduler.impl.QuartzScheduler.slowThresholdMillis")
+        long max_quartzJob_duration_acceptable() default 
DEFAULT_MAX_QUARTZJOB_DURATION_ACCCEPTABLE;
+    }
+
+    private static final long DEFAULT_MAX_QUARTZJOB_DURATION_ACCCEPTABLE = 
60000;
+    private long maxQuartzJobDurationAcceptable;
+
+    @Activate
+    protected void activate(Config config) {
+        configure(config);
+    }
+
+    @Modified
+    protected void modified(Config config) {
+        configure(config);
+    }
+
+    protected void configure(Config config) {
+        maxQuartzJobDurationAcceptable = 
config.max_quartzJob_duration_acceptable();
+    }
+
+    @SuppressWarnings("rawtypes")
+    @Override
+    public Result execute() {
+        final FormattingResultLog resultLog = new FormattingResultLog();
+        try {
+            long runningCount = 0;
+            final SortedMap<String, Counter> runningCntSet = 
metricRegistry.getCounters(new MetricFilter() {
+
+                @Override
+                public boolean matches(String name, Metric metric) {
+                    return 
name.equals(QuartzScheduler.METRICS_NAME_RUNNING_JOBS);
+                }
+            });
+            if (runningCntSet != null) {
+                Iterator<Counter> it = runningCntSet.values().iterator();
+                if (it.hasNext()) {
+                    runningCount = it.next().getCount();
+                }
+            }
+            final SortedMap<String, Gauge> oldestGaugeSet = 
metricRegistry.getGauges(new MetricFilter() {
+
+                @Override
+                public boolean matches(String name, Metric metric) {
+                    return 
name.equals(QuartzScheduler.METRICS_NAME_OLDEST_RUNNING_JOB_MILLIS);
+                }
+            });
+            if (oldestGaugeSet.isEmpty()) {
+                resultLog.warn("Sling Scheduler cannot find any metrics gauge 
starting with {}",
+                        
QuartzScheduler.METRICS_NAME_OLDEST_RUNNING_JOB_MILLIS);
+            } else {
+                final long oldestRunningJobInMillis = (Long) 
oldestGaugeSet.values().iterator().next().getValue();
+                if (oldestRunningJobInMillis <= -1) {
+                    resultLog.info("Sling Scheduler has no Quartz-Job running 
at this moment "
+                            + "(number of currently runnning Quartz-Jobs: 
{}).", Math.max(0, runningCount));
+                } else if (oldestRunningJobInMillis > 
maxQuartzJobDurationAcceptable) {
+                    final String slowPrefix = 
QuartzScheduler.METRICS_NAME_OLDEST_RUNNING_JOB_MILLIS + ".slow.";
+                    final MetricFilter filter = new MetricFilter() {
+
+                        @Override
+                        public boolean matches(String name, Metric metric) {
+                            return name.startsWith(slowPrefix);
+                        }
+                    };
+                    final SortedMap<String, Gauge> allGaugeSet = 
metricRegistry.getGauges(filter);
+                    if (allGaugeSet.isEmpty()) {
+                        resultLog.critical(
+                                "Sling Scheduler has Quartz-Job(s) that is/are 
running for more than the acceptable {}ms: {}ms "
+                                        + "(number of currently runnning 
Quartz-Jobs: {}). Thread-dumps can help determine exact long-running 
Quartz-Job.",
+                                maxQuartzJobDurationAcceptable, 
oldestRunningJobInMillis, Math.max(0, runningCount));
+                    } else {
+                        final StringBuffer slowNames = new StringBuffer();
+                        final Iterator<Entry<String, Gauge>> it = 
allGaugeSet.entrySet().iterator();
+                        int numSlow = 0;
+                        while (it.hasNext()) {
+                            final Entry<String, Gauge> e = it.next();
+                            final Gauge slowGauge = e.getValue();
+                            final long millis = (Long) slowGauge.getValue();
+                            if (millis < 0) {
+                                // skip - this job is no longer running
+                                continue;
+                            }
+                            if (numSlow++ > 0) {
+                                slowNames.append(", ");
+                            }
+                            
slowNames.append(e.getKey().substring(slowPrefix.length()));
+                            slowNames.append("=").append(millis).append("ms");
+                        }
+                        resultLog.critical(
+                                "Sling Scheduler has Quartz-Job(s) that is/are 
running for more than the acceptable {}ms: {}ms "
+                                        + "(number of currently runnning 
Quartz-Jobs: {}). "
+                                        + "Currently {} running slow Job(s): 
{}. "
+                                        + "Thread-dumps can help determine 
further details about long-running Quartz-Job.",
+                                maxQuartzJobDurationAcceptable, 
oldestRunningJobInMillis, Math.max(0, runningCount),
+                                numSlow, slowNames.toString());
+                    }
+                } else {
+                    resultLog.info(
+                            "Sling Scheduler has no long-running (more than 
acceptable {}ms) Quartz-Job, "
+                                    + "longest running Quartz-Job is {}ms at 
this moment (number of currently runnning Quartz-Jobs: {}).",
+                            maxQuartzJobDurationAcceptable, 
oldestRunningJobInMillis, Math.max(0, runningCount));
+                }
+            }
+        } catch (final Exception e) {
+            logger.warn("execute: metrics invocation failed with exception: 
{}", e);
+            resultLog.healthCheckError("execute: metrics invocation failed 
with exception: {}", e);
+        }
+
+        return new Result(resultLog);
+    }
+
+}

Propchange: 
sling/trunk/bundles/commons/scheduler/src/main/java/org/apache/sling/commons/scheduler/impl/SchedulerHealthCheck.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
sling/trunk/bundles/commons/scheduler/src/test/java/org/apache/sling/commons/scheduler/impl/MetricsHelperTest.java
URL: 
http://svn.apache.org/viewvc/sling/trunk/bundles/commons/scheduler/src/test/java/org/apache/sling/commons/scheduler/impl/MetricsHelperTest.java?rev=1802777&view=auto
==============================================================================
--- 
sling/trunk/bundles/commons/scheduler/src/test/java/org/apache/sling/commons/scheduler/impl/MetricsHelperTest.java
 (added)
+++ 
sling/trunk/bundles/commons/scheduler/src/test/java/org/apache/sling/commons/scheduler/impl/MetricsHelperTest.java
 Mon Jul 24 09:42:32 2017
@@ -0,0 +1,155 @@
+/*
+ * 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.sling.commons.scheduler.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.lang.annotation.Annotation;
+import java.util.Vector;
+
+import org.junit.Test;
+
+public class MetricsHelperTest {
+
+    class T1{ }
+    
+    class T2{ }
+    
+    class Tother{ }
+    
+    private final class QSC implements QuartzSchedulerConfiguration {
+        
+        private Class<? extends Annotation> annotationType;
+        private int slowThresholdMillis;
+        private boolean useleaderforsingle;
+        private String poolName;
+        private String[] metrics_filters;
+        private String[] allowedPoolNames;
+
+        @Override
+        public Class<? extends Annotation> annotationType() {
+            return annotationType;
+        }
+
+        @Override
+        public int slowThresholdMillis() {
+            return slowThresholdMillis;
+        }
+
+        @Override
+        public boolean scheduler_useleaderforsingle() {
+            return useleaderforsingle;
+        }
+
+        @Override
+        public String poolName() {
+            return poolName;
+        }
+
+        @Override
+        public String[] metrics_filters() {
+            return metrics_filters;
+        }
+
+        @Override
+        public String[] allowedPoolNames() {
+            return allowedPoolNames;
+        }
+    }
+
+    @Test
+    public void testAsMetricsSuffix_NullValues() throws Exception {
+        assertEquals(MetricsHelper.UNKNOWN_JOBNAME_SUFFIX 
,MetricsHelper.asMetricsSuffix(null));
+    }
+    
+    @Test
+    public void testAsMetricsSuffix_Suffixes() throws Exception {
+        assertEquals("bar", MetricsHelper.asMetricsSuffix("bar"));
+        assertEquals("foo.1", MetricsHelper.asMetricsSuffix("foo.1"));
+        assertEquals("abc.d.1", 
MetricsHelper.asMetricsSuffix("asd.bas.cdf.d.1"));
+        assertEquals("abcd.ex.1", 
MetricsHelper.asMetricsSuffix("asd.bas.cdf.d.ex.1"));
+        assertEquals("abcde.f.1", 
MetricsHelper.asMetricsSuffix("a.b.c.d.e.f.1"));
+
+        assertEquals("abcdef..1", 
MetricsHelper.asMetricsSuffix("a.b.c.d.e...f....1..."));
+        assertEquals("abcdef..1", 
MetricsHelper.asMetricsSuffix("a.b.c.d.e.f....1..."));
+        assertEquals("abcdef..1", 
MetricsHelper.asMetricsSuffix("a.b.c.d.e.f.....1"));
+        assertEquals("abcde.f.1", 
MetricsHelper.asMetricsSuffix("a.b.c.d.e.f.1...."));
+        assertEquals("abcde.f.1", 
MetricsHelper.asMetricsSuffix("a.b...c..d.e......f.1"));
+    }
+    
+    @Test
+    public void testDeriveFilterName_NullValues() throws Exception {
+        assertNull(MetricsHelper.deriveFilterName(null, null));
+        assertNull(MetricsHelper.deriveFilterName(null, "a"));
+        final QSC config = new QSC();
+        ConfigHolder configHolder = new ConfigHolder(config);
+        assertNull(MetricsHelper.deriveFilterName(configHolder, null));
+        assertNull(MetricsHelper.deriveFilterName(configHolder, "a"));
+        config.metrics_filters = new String[0];
+        configHolder = new ConfigHolder(config);
+        assertNull(MetricsHelper.deriveFilterName(configHolder, null));
+        assertNull(MetricsHelper.deriveFilterName(configHolder, "a"));
+        config.metrics_filters = new String[] {"unrelated", "foo.bar.Class"};
+        configHolder = new ConfigHolder(config);
+        assertNull(MetricsHelper.deriveFilterName(configHolder, null));
+        assertNull(MetricsHelper.deriveFilterName(configHolder, "a"));
+    }
+    
+    @Test
+    public void testDeriveFilterName_FilterVariations() throws Exception {
+        final QSC config = new QSC();
+        config.metrics_filters = new String[] { 
+                
"filter1=org.apache.sling.commons.scheduler.impl.MetricsHelperTest",
+                
"filter2=org.apache.sling.commons.scheduler.impl.QuartzSchedulerTest",
+                "filter3=org.apache.sling.commons.scheduler.impl.Tunused",
+                "filter4=java.lang.String"
+        };
+        final ConfigHolder configHolder = new ConfigHolder(config);
+        assertEquals("filter1", MetricsHelper.deriveFilterName(configHolder, 
new MetricsHelperTest()));
+        assertEquals("filter1", MetricsHelper.deriveFilterName(configHolder, 
new T1()));
+        assertEquals("filter1", MetricsHelper.deriveFilterName(configHolder, 
new T2()));
+        assertEquals("filter1", MetricsHelper.deriveFilterName(configHolder, 
new Tother()));
+        assertEquals("filter2", MetricsHelper.deriveFilterName(configHolder, 
new QuartzSchedulerTest()));
+        assertEquals("filter4", MetricsHelper.deriveFilterName(configHolder, 
new String()));
+        assertEquals(null, MetricsHelper.deriveFilterName(configHolder, new 
StringBuffer()));
+        assertEquals(null, MetricsHelper.deriveFilterName(configHolder, new 
TopologyHandlerTest()));
+        assertEquals(null, MetricsHelper.deriveFilterName(configHolder, new 
Vector<Object>()));
+
+        assertEquals(null, MetricsHelper.deriveFilterName(configHolder, null));
+    }
+    
+    @Test
+    public void testConfigHolder() throws Exception {
+        final QSC config = new QSC();
+        config.metrics_filters = new String[] { 
+                
"filter1=org.apache.sling.commons.scheduler.impl.MetricsHelperTest",
+                
"filter2=org.apache.sling.commons.scheduler.impl.QuartzSchedulerTest",
+                "filter3=org.apache.sling.commons.scheduler.impl.Tunused",
+                "filter4=java.lang.String",
+                "wrongFilter5=java.foo.Wrong$1",
+                "wrongFilter6=java.bar.Wrong*",
+                "wrongFilter7=java.zet.Wrong?"
+        };
+        final ConfigHolder configHolder = new ConfigHolder(config);
+        
assertTrue(configHolder.getFilterDefinitions().containsKey("java.lang"));
+        
assertTrue(configHolder.getFilterDefinitions().containsKey("org.apache.sling.commons.scheduler.impl"));
+        assertEquals(2, configHolder.getFilterDefinitions().size());
+        assertEquals(3, 
configHolder.getFilterDefinitions().get("org.apache.sling.commons.scheduler.impl").size());
+    }
+}

Propchange: 
sling/trunk/bundles/commons/scheduler/src/test/java/org/apache/sling/commons/scheduler/impl/MetricsHelperTest.java
------------------------------------------------------------------------------
    svn:eol-style = native


Reply via email to