This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-commons-metrics-rrd4j.git
commit 9e8e6c9f538189d96a0bf5055d40e2c227f4f097 Author: Robert Munteanu <romb...@apache.org> AuthorDate: Fri Aug 18 08:47:34 2017 +0000 SLING-7055 - RRD4J metrics reporter Initial implementation Submitted-By: Marcel Reutegger git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1805386 13f79535-47bb-0310-9956-ffa450edef68 --- README.md | 16 ++ pom.xml | 105 ++++++++ .../rrd4j/impl/CodahaleMetricsReporter.java | 170 ++++++++++++ .../rrd4j/impl/CopyMetricRegistryListener.java | 113 ++++++++ .../commons/metrics/rrd4j/impl/RRD4JReporter.java | 289 +++++++++++++++++++++ .../commons/metrics/rrd4j/impl/ReporterTest.java | 97 +++++++ src/test/resources/logback-test.xml | 30 +++ 7 files changed, 820 insertions(+) diff --git a/README.md b/README.md new file mode 100644 index 0000000..542068f --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# Apache Sling RRD4J metrics reporter + +This is a bundle that stores metrics on the local filesystem using +[RRD4J](https://github.com/rrd4j/rrd4j). + +Build this bundle with Maven: + + mvn clean install + +The reporter will not store metrics by default. You need to configure it and +tell the reporter what metrics to store. + +Go to the Apache Felix Web Console and configure 'Apache Sling Metrics reporter +writing to RRD4J'. The reporter will start storing metrics once data sources +have been added and the configuration is saved. Please note, the metrics file +is recreated/cleared whenever the configuration is changed. \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..4ef5901 --- /dev/null +++ b/pom.xml @@ -0,0 +1,105 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + 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. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.sling</groupId> + <artifactId>sling</artifactId> + <version>31</version> + </parent> + + <artifactId>org.apache.sling.commons.metrics-rrd4j</artifactId> + <packaging>bundle</packaging> + <version>0.0.1-SNAPSHOT</version> + + <name>Apache Sling RRD4J metrics reporter</name> + <description> + Stores Metrics to the local filesystem using RRD4J. + </description> + + <properties> + <sling.java.version>8</sling.java.version> + </properties> + + <scm> + <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/bundles/commons/metrics-rrd4j</connection> + <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/bundles/commons/metrics-rrd4j</developerConnection> + <url>http://svn.apache.org/viewvc/sling/trunk/bundles/commons/metrics-rrd4j</url> + </scm> + + <build> + <plugins> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <extensions>true</extensions> + <configuration> + <instructions> + <Import-Package> + com.mongodb;resolution:=optional, + com.sleepycat.je;resolution:=optional, + sun.misc;resolution:=optional, + sun.nio.ch;resolution:=optional, + * + </Import-Package> + <Embed-Dependency>rrd4j</Embed-Dependency> + </instructions> + </configuration> + </plugin> + </plugins> + </build> + <dependencies> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>io.dropwizard.metrics</groupId> + <artifactId>metrics-core</artifactId> + <version>3.1.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.rrd4j</groupId> + <artifactId>rrd4j</artifactId> + <version>3.1</version> + <scope>provided</scope> + </dependency> + <!-- test dependencies --> + <dependency> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-classic</artifactId> + <version>1.1.7</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.testing.osgi-mock</artifactId> + <version>2.3.2</version> + <scope>test</scope> + </dependency> + </dependencies> +</project> diff --git a/src/main/java/org/apache/sling/commons/metrics/rrd4j/impl/CodahaleMetricsReporter.java b/src/main/java/org/apache/sling/commons/metrics/rrd4j/impl/CodahaleMetricsReporter.java new file mode 100644 index 0000000..97cf78a --- /dev/null +++ b/src/main/java/org/apache/sling/commons/metrics/rrd4j/impl/CodahaleMetricsReporter.java @@ -0,0 +1,170 @@ +/* + * 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.metrics.rrd4j.impl; + +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.ScheduledReporter; + +import org.osgi.framework.BundleContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.ConfigurationPolicy; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +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 java.io.File; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +import static org.apache.sling.commons.metrics.rrd4j.impl.RRD4JReporter.DEFAULT_STEP; + +@Component( + immediate = true, + configurationPolicy = ConfigurationPolicy.REQUIRE +) +@Designate(ocd = CodahaleMetricsReporter.Configuration.class) +public class CodahaleMetricsReporter { + + private static final Logger LOG = LoggerFactory.getLogger(CodahaleMetricsReporter.class); + + private ScheduledReporter reporter; + + private Map<String, CopyMetricRegistryListener> listeners = new ConcurrentHashMap<>(); + + @ObjectClassDefinition(name = "Apache Sling Metrics reporter writing to RRD4J", + description = "For syntax details on RRD data-source and round " + + "robin archive definitions see " + + "https://oss.oetiker.ch/rrdtool/doc/rrdcreate.en.html and " + + "https://github.com/rrd4j/rrd4j/wiki/Tutorial. Changing " + + "any attribute in this configuration will replace an " + + "existing RRD file with a empty one!") + public @interface Configuration { + + @AttributeDefinition( + name = "Data sources", + description = "RRDTool data source definitions " + + "(e.g. 'DS:oak_SESSION_LOGIN_COUNTER:COUNTER:300:0:U'). " + + "Replace colon characters in the metric name with an " + + "underscore!" + ) + String[] datasources() default {}; + + @AttributeDefinition( + name = "Step", + description = "The base interval in seconds with which data " + + "will be fed into the RRD" + ) + int step() default DEFAULT_STEP; + + @AttributeDefinition( + name = "Archives", + description = "RRDTool round robin archive definitions. The " + + "default configuration defines four archives based " + + "on a default step of five seconds: " + + "1) per minute averages for six hours, " + + "2) per five minute averages 48 hours, " + + "3) per hour averages for four weeks, " + + "4) per day averages for one year." + ) + String[] archives() default { + "RRA:AVERAGE:0.5:12:360", "RRA:AVERAGE:0.5:60:576", "RRA:AVERAGE:0.5:720:336", "RRA:AVERAGE:0.5:17280:365" + }; + + @AttributeDefinition( + name = "Path", + description = "Path of the RRD file where metrics are stored. " + + "If the path is relative, it is resolved relative to " + + "the value of the framework property 'sling.home' when " + + "available, otherwise relative to the current working " + + "directory." + ) + String path() default "metrics/metrics.rrd"; + } + + private MetricRegistry metricRegistry = new MetricRegistry(); + + @Activate + void activate(BundleContext context, Configuration config) throws Exception { + LOG.info("Starting RRD4J Metrics reporter"); + File path = new File(config.path()); + if (!path.isAbsolute()) { + String home = context.getProperty("sling.home"); + if (home != null) { + path = new File(home, path.getPath()); + } + } + + reporter = RRD4JReporter.forRegistry(metricRegistry) + .convertRatesTo(TimeUnit.SECONDS) + .convertDurationsTo(TimeUnit.MICROSECONDS) + .withPath(path) + .withDatasources(config.datasources()) + .withArchives(config.archives()) + .withStep(config.step()) + .build(); + reporter.start(config.step(), TimeUnit.SECONDS); + LOG.info("Started RRD4J Metrics reporter. Writing to " + path); + } + + @Deactivate + void deactivate() { + LOG.info("Stopping RRD4J Metrics reporter"); + reporter.stop(); + reporter = null; + LOG.info("Stopped RRD4J Metrics reporter"); + } + + @Reference( + service = MetricRegistry.class, + cardinality = ReferenceCardinality.MULTIPLE, + policy = ReferencePolicy.DYNAMIC) + synchronized void addMetricRegistry(MetricRegistry metricRegistry, + Map<String, Object> properties) { + String name = (String) properties.get("name"); + if (name == null) { + name = metricRegistry.toString(); + } + CopyMetricRegistryListener listener = new CopyMetricRegistryListener(this.metricRegistry, name); + listener.start(metricRegistry); + this.listeners.put(name, listener); + LOG.info("Bound Metrics Registry {} ",name); + } + + synchronized void removeMetricRegistry(MetricRegistry metricRegistry, + Map<String, Object> properties) { + String name = (String) properties.get("name"); + if (name == null) { + name = metricRegistry.toString(); + } + CopyMetricRegistryListener metricRegistryListener = listeners.get(name); + if ( metricRegistryListener != null) { + metricRegistryListener.stop(metricRegistry); + this.listeners.remove(name); + } + LOG.info("Unbound Metrics Registry {} ",name); + } +} diff --git a/src/main/java/org/apache/sling/commons/metrics/rrd4j/impl/CopyMetricRegistryListener.java b/src/main/java/org/apache/sling/commons/metrics/rrd4j/impl/CopyMetricRegistryListener.java new file mode 100644 index 0000000..fbb20aa --- /dev/null +++ b/src/main/java/org/apache/sling/commons/metrics/rrd4j/impl/CopyMetricRegistryListener.java @@ -0,0 +1,113 @@ +/* + * 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.metrics.rrd4j.impl; + +import com.codahale.metrics.Counter; +import com.codahale.metrics.Gauge; +import com.codahale.metrics.Histogram; +import com.codahale.metrics.Meter; +import com.codahale.metrics.Metric; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.MetricRegistryListener; +import com.codahale.metrics.Timer; + +class CopyMetricRegistryListener implements MetricRegistryListener { + + private final MetricRegistry parent; + private final String name; + + CopyMetricRegistryListener(MetricRegistry parent, String name) { + this.parent = parent; + this.name = name; + } + + void start(MetricRegistry metricRegistry) { + metricRegistry.addListener(this); + } + + void stop(MetricRegistry metricRegistry) { + metricRegistry.removeListener(this); + for(String name : metricRegistry.getMetrics().keySet()) { + removeMetric(name); + } + } + + private void addMetric(String metricName, Metric m) { + parent.register(getMetricName(metricName), m); + } + + private void removeMetric(String metricName) { + parent.remove(getMetricName(metricName)); + } + + private String getMetricName(String metricName) { + return name + "_" + metricName; + } + + @Override + public void onGaugeAdded(String s, Gauge<?> gauge) { + addMetric(s, gauge); + } + + @Override + public void onGaugeRemoved(String s) { + removeMetric(s); + } + + @Override + public void onCounterAdded(String s, Counter counter) { + addMetric(s, counter); + } + + @Override + public void onCounterRemoved(String s) { + removeMetric(s); + } + + @Override + public void onHistogramAdded(String s, Histogram histogram) { + addMetric(s, histogram); + } + + @Override + public void onHistogramRemoved(String s) { + removeMetric(s); + } + + @Override + public void onMeterAdded(String s, Meter meter) { + addMetric(s, meter); + } + + @Override + public void onMeterRemoved(String s) { + removeMetric(s); + } + + @Override + public void onTimerAdded(String s, Timer timer) { + addMetric(s, timer); + } + + @Override + public void onTimerRemoved(String s) { + removeMetric(s); + } + +} diff --git a/src/main/java/org/apache/sling/commons/metrics/rrd4j/impl/RRD4JReporter.java b/src/main/java/org/apache/sling/commons/metrics/rrd4j/impl/RRD4JReporter.java new file mode 100644 index 0000000..8eaacfa --- /dev/null +++ b/src/main/java/org/apache/sling/commons/metrics/rrd4j/impl/RRD4JReporter.java @@ -0,0 +1,289 @@ +/* + * 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.metrics.rrd4j.impl; + +import com.codahale.metrics.Counter; +import com.codahale.metrics.Gauge; +import com.codahale.metrics.Histogram; +import com.codahale.metrics.Meter; +import com.codahale.metrics.MetricFilter; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.ScheduledReporter; +import com.codahale.metrics.Timer; + +import org.rrd4j.core.RrdDb; +import org.rrd4j.core.RrdDef; +import org.rrd4j.core.Sample; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.SortedMap; +import java.util.concurrent.TimeUnit; + +import static java.lang.String.join; + +class RRD4JReporter extends ScheduledReporter { + + private static final Logger LOGGER = LoggerFactory.getLogger(RRD4JReporter.class); + static final int DEFAULT_STEP = 5; + + private final Map<String, Integer> dictionary = new HashMap<>(); + private final RrdDb rrdDB; + + static Builder forRegistry(MetricRegistry metricRegistry) { + return new Builder(metricRegistry); + } + + static class Builder { + private MetricRegistry metricRegistry; + private TimeUnit ratesUnit; + private TimeUnit durationUnit; + private File path = new File("."); + private final List<String> indexedDS = new ArrayList<>(); + private final Map<String, Integer> dictionary = new HashMap<>(); + private final List<String> archives = new ArrayList<>(); + private int step = DEFAULT_STEP; + + Builder(MetricRegistry metricRegistry ) { + this.metricRegistry = metricRegistry; + } + + Builder withPath(File path) { + this.path = path; + return this; + } + + Builder withDatasources(String[] datasources) { + this.indexedDS.clear(); + this.dictionary.clear(); + + int i = 0; + for (String ds : datasources) { + String[] tokens = ds.split(":"); + if (tokens.length != 6) { + throw new IllegalArgumentException("Invalid data source definition: " + ds); + } + dictionary.put(normalize(tokens[1]), i); + tokens[1] = String.valueOf(i); + this.indexedDS.add(checkDataSource(join(":", tokens))); + i++; + } + return this; + } + + Builder withArchives(String[] archives) { + this.archives.clear(); + this.archives.addAll(Arrays.asList(archives)); + return this; + } + + Builder withStep(int step) { + this.step = step; + return this; + } + + Builder convertRatesTo(TimeUnit ratesUnit) { + this.ratesUnit = ratesUnit; + return this; + } + + Builder convertDurationsTo(TimeUnit durationUnit) { + this.durationUnit = durationUnit; + return this; + } + + ScheduledReporter build() throws IOException { + return new RRD4JReporter(metricRegistry, "RRD4JReporter", + MetricFilter.ALL, ratesUnit, durationUnit, dictionary, createDef()); + } + + private String checkDataSource(String ds) + throws IllegalArgumentException { + new RrdDef("path").addDatasource(ds); + return ds; + } + + private RrdDef createDef() { + RrdDef def = new RrdDef(path.getPath()); + def.setStep(step); + for (String ds : indexedDS) { + def.addDatasource(ds); + } + for (String rra : archives) { + def.addArchive(rra); + } + return def; + } + } + + RRD4JReporter(MetricRegistry registry, + String name, + MetricFilter filter, + TimeUnit rateUnit, + TimeUnit durationUnit, + Map<String, Integer> dictionary, + RrdDef rrdDef) throws IOException { + super(registry, name, filter, rateUnit, durationUnit); + this.dictionary.putAll(dictionary); + this.rrdDB = createDB(rrdDef); + storeDictionary(rrdDef.getPath() + ".properties"); + } + + @Override + public void close() { + try { + rrdDB.close(); + } catch (IOException e) { + LOGGER.warn("Closing RRD failed", e); + } + super.close(); + } + + @Override + public void report(SortedMap<String, Gauge> gauges, + SortedMap<String, Counter> counters, + SortedMap<String, Histogram> histograms, + SortedMap<String, Meter> meters, + SortedMap<String, Timer> timers) { + + try { + Sample sample = rrdDB.createSample(System.currentTimeMillis() / 1000); + for (Map.Entry<String, Gauge> entry : gauges.entrySet()) { + update(sample, indexForName(entry.getKey()), entry.getValue()); + } + + for (Map.Entry<String, Counter> entry : counters.entrySet()) { + update(sample, indexForName(entry.getKey()), entry.getValue()); + } + + for (Map.Entry<String, Histogram> entry : histograms.entrySet()) { + update(sample, indexForName(entry.getKey()), entry.getValue()); + } + + for (Map.Entry<String, Meter> entry : meters.entrySet()) { + update(sample, indexForName(entry.getKey()), entry.getValue()); + } + + for (Map.Entry<String, Timer> entry : timers.entrySet()) { + update(sample, indexForName(entry.getKey()), entry.getValue()); + } + sample.update(); + } catch (IOException e) { + LOGGER.warn("Unable to write sample to RRD", e); + } + } + + private int indexForName(String name) { + Integer idx = dictionary.get(normalize(name)); + return idx != null ? idx : -1; + } + + private static String normalize(String name) { + return name.replaceAll(":", "_"); + } + + private void update(Sample sample, int nameIdx, Gauge g) { + if (nameIdx < 0) { + return; + } + Object value = g.getValue(); + if (value instanceof Number) { + sample.setValue(nameIdx, ((Number) value).doubleValue()); + } + } + + private void update(Sample sample, int nameIdx, Counter c) { + if (nameIdx < 0) { + return; + } + sample.setValue(nameIdx, c.getCount()); + } + + private void update(Sample sample, int nameIdx, Histogram h) { + if (nameIdx < 0) { + return; + } + sample.setValue(nameIdx, h.getCount()); + } + + private void update(Sample sample, int nameIdx, Timer t) { + if (nameIdx < 0) { + return; + } + sample.setValue(nameIdx, t.getCount()); + } + + + private void update(Sample sample, int nameIdx, Meter m) { + if (nameIdx < 0) { + return; + } + LOGGER.debug("Sample: {} = {}", nameIdx, m.getCount()); + sample.setValue(nameIdx, m.getCount()); + } + + private void storeDictionary(String path) throws IOException { + File dictFile = new File(path); + if (dictFile.exists() && ! dictFile.delete()) { + throw new IOException("Unable to delete dictionary file: " + dictFile.getPath()); + } + Properties dict = new Properties(); + for (Map.Entry<String, Integer> entry : dictionary.entrySet()) { + dict.put(String.valueOf(entry.getValue()), entry.getKey()); + } + try (FileOutputStream out = new FileOutputStream(dictFile)) { + dict.store(out, "RRD4JReporter dictionary"); + } + } + + private RrdDb createDB(RrdDef definition) throws IOException { + File dbFile = new File(definition.getPath()); + if (!dbFile.getParentFile().exists()) { + if (!dbFile.getParentFile().mkdirs()) { + throw new IOException("Unable to create directory for RRD file: " + dbFile.getParent()); + } + } + RrdDb db = null; + if (dbFile.exists()) { + db = new RrdDb(definition.getPath()); + if (!db.getRrdDef().equals(definition)) { + // definition changed -> re-create DB + db.close(); + if (!dbFile.delete()) { + throw new IOException("Unable to delete RRD file: " + dbFile.getPath()); + } + LOGGER.warn("Configuration changed, recreating RRD file for metrics: " + dbFile.getPath()); + db = null; + } + } + if (db == null) { + db = new RrdDb(definition); + } + return db; + } +} diff --git a/src/test/java/org/apache/sling/commons/metrics/rrd4j/impl/ReporterTest.java b/src/test/java/org/apache/sling/commons/metrics/rrd4j/impl/ReporterTest.java new file mode 100644 index 0000000..2dffcf9 --- /dev/null +++ b/src/test/java/org/apache/sling/commons/metrics/rrd4j/impl/ReporterTest.java @@ -0,0 +1,97 @@ +/* + * 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.metrics.rrd4j.impl; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +import com.codahale.metrics.Gauge; +import com.codahale.metrics.MetricRegistry; + +import org.apache.sling.commons.metrics.rrd4j.impl.CodahaleMetricsReporter; +import org.apache.sling.testing.mock.osgi.junit.OsgiContext; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.rrd4j.core.RrdDb; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class ReporterTest { + + private static final File RRD = new File(new File("target", "metrics"), "metrics.rrd"); + + private static final long TEST_VALUE = 42; + + @Rule + public final OsgiContext context = new OsgiContext(); + + private MetricRegistry registry = new MetricRegistry(); + + private CodahaleMetricsReporter reporter = new CodahaleMetricsReporter(); + + @Before + public void before() throws Exception { + RRD.delete(); + context.registerService(MetricRegistry.class, registry, "name", "sling"); + + Map<String, Object> properties = new HashMap<>(); + properties.put("step", 1L); + properties.put("datasources", new String[]{"DS:sling_myMetric:GAUGE:300:0:U"}); + properties.put("archives", new String[]{"RRA:AVERAGE:0.5:1:60"}); + properties.put("path", RRD.getPath()); + context.registerInjectActivateService(reporter, properties); + + registry.register("myMetric", new TestGauge(TEST_VALUE)); + } + + @Test + public void writeRRD() throws Exception { + assertTrue(RRD.exists()); + for (int i = 0; i < 10; i++) { + RrdDb db = new RrdDb(RRD.getPath(), true); + try { + double lastValue = db.getDatasource("0").getLastValue(); + if (lastValue == (double) TEST_VALUE) { + return; + } + } finally { + db.close(); + } + Thread.sleep(1000); + } + fail("RRD4J reporter did not update database in time"); + } + + private static final class TestGauge implements Gauge<Long> { + + private final long value; + + TestGauge(long value) { + this.value = value; + } + + @Override + public Long getValue() { + return value; + } + } +} diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml new file mode 100644 index 0000000..bcb98cc --- /dev/null +++ b/src/test/resources/logback-test.xml @@ -0,0 +1,30 @@ +<!-- + 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. + --> +<configuration> + + <appender name="file" class="ch.qos.logback.core.FileAppender"> + <file>target/unit-tests.log</file> + <encoder> + <pattern>%date{HH:mm:ss.SSS} %-5level %-40([%thread] %F:%L) %msg%n</pattern> + </encoder> + </appender> + + <root level="INFO"> + <appender-ref ref="file"/> + </root> + +</configuration> -- To stop receiving notification emails like this one, please contact "commits@sling.apache.org" <commits@sling.apache.org>.