diff --git a/s3auth-hosts/src/main/java/com/s3auth/hosts/ScheduledCloudWatch.java b/s3auth-hosts/src/main/java/com/s3auth/hosts/ScheduledCloudWatch.java
new file mode 100644
index 00000000..a6f86637
--- /dev/null
+++ b/s3auth-hosts/src/main/java/com/s3auth/hosts/ScheduledCloudWatch.java
@@ -0,0 +1,144 @@
+/**
+ * Copyright (c) 2012-2014, s3auth.com
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met: 1) Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer. 2) Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution. 3) Neither the name of the s3auth.com nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
+ * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.s3auth.hosts;
+
+import com.amazonaws.ClientConfiguration;
+import com.amazonaws.Protocol;
+import com.amazonaws.auth.BasicAWSCredentials;
+import com.amazonaws.services.cloudwatch.AmazonCloudWatch;
+import com.amazonaws.services.cloudwatch.AmazonCloudWatchClient;
+import com.amazonaws.services.cloudwatch.model.Dimension;
+import com.amazonaws.services.cloudwatch.model.MetricDatum;
+import com.amazonaws.services.cloudwatch.model.PutMetricDataRequest;
+import com.amazonaws.services.cloudwatch.model.StandardUnit;
+import com.jcabi.aspects.Loggable;
+import com.jcabi.aspects.RetryOnFailure;
+import com.jcabi.aspects.ScheduleWithFixedDelay;
+import com.jcabi.aspects.Tv;
+import com.jcabi.manifests.Manifests;
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This class obtains local stats and posts it to Amazon CloudWatch every hour.
+ *
+ *
The class is mutable and NOT thread-safe.
+ *
+ * @author Carlos Miranda (miranda.cma@gmail.com)
+ * @version $Id$
+ */
+@Loggable(Loggable.DEBUG)
+@ScheduleWithFixedDelay(delay = 1, unit = TimeUnit.HOURS)
+@SuppressWarnings("PMD.DoNotUseThreads")
+public final class ScheduledCloudWatch implements Runnable, Closeable {
+
+ /**
+ * Domain stats data to post to CloudWatch.
+ */
+ private final transient DomainStatsData data;
+
+ /**
+ * Amazon CloudWatch client.
+ */
+ private final transient AmazonCloudWatch cloudwatch;
+
+ /**
+ * Ctor.
+ * @param stats The stats data to obtain.
+ * @param cwatch The Cloudwatch client.
+ */
+ ScheduledCloudWatch(final DomainStatsData stats,
+ final AmazonCloudWatch cwatch) {
+ this.cloudwatch = cwatch;
+ this.data = stats;
+ }
+
+ /**
+ * Public ctor.
+ * @throws IOException If an IO Exception occurs
+ */
+ public ScheduledCloudWatch() throws IOException {
+ this(
+ new H2DomainStatsData(),
+ new AmazonCloudWatchClient(
+ new BasicAWSCredentials(
+ Manifests.read("S3Auth-AwsCloudWatchKey"),
+ Manifests.read("S3Auth-AwsCloudWatchSecret")
+ ),
+ new ClientConfiguration().withProtocol(Protocol.HTTP)
+ )
+ );
+ }
+
+ @Override
+ public void run() {
+ try {
+ final Map results = this.data.all();
+ for (final Entry entry : results.entrySet()) {
+ this.putData(entry.getValue(), entry.getKey());
+ }
+ } catch (final IOException ex) {
+ throw new IllegalStateException(ex);
+ }
+ }
+
+ @Override
+ public void close() {
+ // Nothing to do, ScheduleWithFixedDelay will stop the thread.
+ }
+
+ /**
+ * Put data in Amazon Cloudwatch.
+ * @param stats The stats to put
+ * @param bucket The bucket corresponding to these stats
+ */
+ @RetryOnFailure(delay = Tv.FIVE, unit = TimeUnit.SECONDS)
+ private void putData(final Stats stats, final String bucket) {
+ this.cloudwatch.putMetricData(
+ new PutMetricDataRequest()
+ .withNamespace("S3Auth")
+ .withMetricData(
+ new MetricDatum()
+ .withMetricName("BytesTransferred")
+ .withDimensions(
+ new Dimension()
+ .withName("Bucket")
+ .withValue(bucket)
+ )
+ .withUnit(StandardUnit.Bytes)
+ .withValue((double) stats.bytesTransferred())
+ )
+ );
+ }
+
+}
diff --git a/s3auth-hosts/src/test/java/com/s3auth/hosts/ScheduledCloudWatchTest.java b/s3auth-hosts/src/test/java/com/s3auth/hosts/ScheduledCloudWatchTest.java
new file mode 100644
index 00000000..f932fb56
--- /dev/null
+++ b/s3auth-hosts/src/test/java/com/s3auth/hosts/ScheduledCloudWatchTest.java
@@ -0,0 +1,82 @@
+/**
+ * Copyright (c) 2012-2014, s3auth.com
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met: 1) Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer. 2) Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution. 3) Neither the name of the s3auth.com nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
+ * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.s3auth.hosts;
+
+import com.amazonaws.services.cloudwatch.AmazonCloudWatch;
+import com.amazonaws.services.cloudwatch.model.Dimension;
+import com.amazonaws.services.cloudwatch.model.MetricDatum;
+import com.amazonaws.services.cloudwatch.model.PutMetricDataRequest;
+import com.amazonaws.services.cloudwatch.model.StandardUnit;
+import com.jcabi.aspects.Tv;
+import java.util.Collections;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+/**
+ * Test case for {@link ScheduledCloudWatch}.
+ * @author Carlos Miranda (miranda.cma@gmail.com)
+ * @version $Id$
+ */
+public final class ScheduledCloudWatchTest {
+
+ /**
+ * If an exception occurs.
+ * @throws Exception If something goes wrong.
+ */
+ @Test
+ public void postsDataToCloudWatch() throws Exception {
+ final String bucket = "bucket";
+ final long bytes = Tv.MILLION;
+ final DomainStatsData data = Mockito.mock(DomainStatsData.class);
+ Mockito.doReturn(
+ Collections.singletonMap(bucket, new Stats.Simple(bytes))
+ ).when(data).all();
+ final AmazonCloudWatch client = Mockito.mock(AmazonCloudWatch.class);
+ final ScheduledCloudWatch cloudwatch =
+ new ScheduledCloudWatch(data, client);
+ cloudwatch.run();
+ Mockito.verify(client).putMetricData(
+ new PutMetricDataRequest()
+ .withNamespace("S3Auth")
+ .withMetricData(
+ new MetricDatum()
+ .withMetricName("BytesTransferred")
+ .withDimensions(
+ new Dimension()
+ .withName("Bucket")
+ .withValue(bucket)
+ ).withUnit(StandardUnit.Bytes)
+ .withValue(Double.valueOf(bytes))
+ )
+ );
+ cloudwatch.close();
+ }
+
+}