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