Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Telemetry metrics #397

Merged
merged 15 commits into from
Jun 12, 2019
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.mapbox.android.core.metrics;

import org.junit.Test;

import java.util.concurrent.TimeUnit;

import static org.junit.Assert.assertEquals;

public class AbstractCompositeMetricsInstrumentedTest {
@Test
public void addShortSpanMetric() throws InterruptedException {
TestCompositeMetrics metrics = new TestCompositeMetrics(TimeUnit.SECONDS.toMillis(1));
metrics.add("test", 100L);
Thread.sleep(1000L);
metrics.add("test", 10L);
Metrics firstMetric = metrics.getMetrics("test");
Metrics secondMetric = metrics.getMetrics("test");
assertEquals(100L, firstMetric.getValue());
assertEquals(10L, secondMetric.getValue());
}

private static final class TestCompositeMetrics extends AbstractCompositeMetrics {
TestCompositeMetrics(long maxLength) {
super(maxLength);
}

@Override
protected Metrics nextMetrics(long start, long end) {
return new MetricsImpl(start, end);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.mapbox.android.core.metrics;

import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* Tracks stats over rolling time window of the max length.
* Optimized for write heavy metrics.
*/
public abstract class AbstractCompositeMetrics {
private final Map<String, Deque<Metrics>> metricsMap = new ConcurrentHashMap<>();
private final long maxLength;

/**
* Create instance of the composite metric.
*
* @param maxLength max time window in milliseconds.
*/
public AbstractCompositeMetrics(long maxLength) {
this.maxLength = maxLength;
}

/**
* Called by child class when new metrics is needed.
* Concrete implementation of the metric is delegated to child class.
*
* @param start of the time span.
* @param end of the time span.
* @return reference to the new metric object.
*/
protected abstract Metrics nextMetrics(long start, long end);

/**
* Adds value to the metric and occasionally creates new metric
* if the delta is out of the exiting metric span.
*
* @param name name of the metric.
* @param delta value to increment.
*/
public void add(String name, long delta) {
long now = SystemClock.uptimeMillis();

Metrics last;
synchronized (this) {
Deque<Metrics> metrics = getOrCreateMetrics(name.trim());
if (now >= metrics.getLast().getEnd()) {
andrlee marked this conversation as resolved.
Show resolved Hide resolved
metrics.add(nextMetrics(now, now + maxLength));
}
last = metrics.getLast();
}
last.add(delta);
}

@Nullable
public Metrics getMetrics(@NonNull String name) {
Deque<Metrics> metrics = metricsMap.get(name.trim());
synchronized (this) {
return metrics != null && !metrics.isEmpty() ? metrics.pop() : null;
}
}

@NonNull
private Deque<Metrics> getOrCreateMetrics(@NonNull String name) {
Deque<Metrics> metrics;
if ((metrics = metricsMap.get(name)) == null) {
metrics = new ArrayDeque<>();
metricsMap.put(name, metrics);
}

if (metrics.isEmpty()) {
long now = SystemClock.uptimeMillis();
metrics.add(nextMetrics(now, now + maxLength));
}
return metrics;
}
}
35 changes: 35 additions & 0 deletions libcore/src/main/java/com/mapbox/android/core/metrics/Metrics.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.mapbox.android.core.metrics;

/**
* Metrics object counter over a time span
*/
public interface Metrics {

/**
* Increment metric
*
* @param delta value
*/
void add(long delta);

/**
* Return current metric value
*
* @return current state of the metric
*/
long getValue();

/**
* Return start of the time span [start, end]
*
* @return timestamp in milliseconds.
*/
long getStart();

/**
* Return end of the time span [start, end]
*
* @return timestamp in milliseconds.
*/
long getEnd();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.mapbox.android.core.metrics;

import java.util.concurrent.atomic.AtomicLong;

/**
* Default implementation of the thread safe
* metric with time span.
*/
public class MetricsImpl implements Metrics {
private final long start;
private final long end;
private final AtomicLong value;

MetricsImpl(long start, long end, long initialValue) {
if (start > end) {
this.start = end;
this.end = start;
} else {
this.start = start;
this.end = end;
}
this.value = new AtomicLong(initialValue);
}

/**
* Intantiate new metric with a span.
* @param start timestamp
* @param end timestamp
*/
public MetricsImpl(long start, long end) {
this(start, end, 0L);
}

/**
* Increment metric by delta. (thread safe)
*
* @param delta value
*/
@Override
public void add(long delta) {
value.addAndGet(delta);
}

/**
* Return metric value. (thread safe)
*
* @return metric value
*/
@Override
public long getValue() {
return value.get();
}

/**
* Return span start timestamp.
*
* @return timestamp in milliseconds
*/
@Override
public long getStart() {
return start;
}

/**
* Return span end timestamp.
*
* @return timestamp in milliseconds
*/
@Override
public long getEnd() {
return end;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.mapbox.android.core.metrics;

import org.junit.Before;
import org.junit.Test;

import java.util.concurrent.TimeUnit;

import static org.assertj.core.api.Assertions.assertThat;

public class AbstractCompositeMetricsTest {
private TestCompositeMetrics metrics;

@Before
public void setUp() {
metrics = new TestCompositeMetrics(TimeUnit.HOURS.toMillis(1));
}

andrlee marked this conversation as resolved.
Show resolved Hide resolved
@Test
public void addLongSpanMetric() {
metrics.add("test", 100L);
metrics.add("test", 10L);
Metrics firstMetric = metrics.getMetrics("test");
Metrics secondMetric = metrics.getMetrics("test");
assertThat(firstMetric.getValue()).isEqualTo(110L);
assertThat(secondMetric).isNull();
}

@Test
public void addMultipleMetrics() {
metrics.add("test", 100L);
metrics.add("foo", 10L);
Metrics test = metrics.getMetrics("test");
Metrics foo = metrics.getMetrics("foo");
assertThat(test.getValue()).isEqualTo(100L);
assertThat(foo.getValue()).isEqualTo(10L);
}

@Test
public void addRemoveMetric() {
metrics.add("test", 100L);
metrics.getMetrics("test");
metrics.add("test", 10L);
assertThat(metrics.getMetrics("test").getValue()).isEqualTo(10L);
}

@Test
public void getEmptyMetric() {
assertThat(metrics.getMetrics("test")).isNull();
}

@Test
public void getMetricsEmptyString() {
assertThat(metrics.getMetrics("")).isNull();
}

private static final class TestCompositeMetrics extends AbstractCompositeMetrics {
TestCompositeMetrics(long maxLength) {
super(maxLength);
}

@Override
protected Metrics nextMetrics(long start, long end) {
return new MetricsImpl(start, end);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.mapbox.android.core.metrics;

import android.os.SystemClock;
import org.junit.Test;

import java.util.concurrent.TimeUnit;

import static org.assertj.core.api.Assertions.assertThat;

public class MetricsImplTest {

@Test
public void wrongSpan() {
long startTime = SystemClock.elapsedRealtime();
long endTime = startTime + TimeUnit.HOURS.toMillis(24);
Metrics metrics = new MetricsImpl(endTime, startTime);
assertThat(metrics.getStart()).isEqualTo(startTime);
assertThat(metrics.getEnd()).isEqualTo(endTime);
}

@Test
public void add() {
Metrics metrics = getMetrics();
metrics.add(100L);
assertThat(metrics.getValue()).isEqualTo(100L);
}

@Test
public void subtract() {
Metrics metrics = getMetrics();
metrics.add(-100L);
assertThat(metrics.getValue()).isEqualTo(-100L);
}

@Test
public void getValue() {
Metrics metrics = getMetrics();
assertThat(metrics.getValue()).isEqualTo(0L);
}

@Test
public void getStart() {
long startTime = SystemClock.elapsedRealtime();
Metrics metrics = new MetricsImpl(startTime, startTime + TimeUnit.HOURS.toMillis(24));
assertThat(metrics.getStart()).isEqualTo(startTime);
}

@Test
public void getEnd() {
long endTime = SystemClock.elapsedRealtime();
Metrics metrics = new MetricsImpl(0, endTime);
assertThat(metrics.getStart()).isEqualTo(endTime);
}

private static Metrics getMetrics() {
long startTime = SystemClock.elapsedRealtime();
return new MetricsImpl(startTime, startTime + TimeUnit.HOURS.toMillis(24));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ private void sendBatch(List<Event> batch, Callback callback, boolean serializeNu
.post(body)
.build();

OkHttpClient client = setting.getClient(certificateBlacklist);
OkHttpClient client = setting.getClient(certificateBlacklist, batch.size());
client.newCall(request).enqueue(callback);
}

Expand Down

This file was deleted.

Loading