diff --git a/ffwd-client/pom.xml b/ffwd-client/pom.xml
index de511ea..9a415e9 100644
--- a/ffwd-client/pom.xml
+++ b/ffwd-client/pom.xml
@@ -32,6 +32,21 @@
protoc
pom
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+ org.mockito
+ mockito-junit-jupiter
+ test
+
-
diff --git a/ffwd-client/src/main/java/com/spotify/ffwd/FastForward.java b/ffwd-client/src/main/java/com/spotify/ffwd/FastForward.java
index fbd1c53..76b0255 100644
--- a/ffwd-client/src/main/java/com/spotify/ffwd/FastForward.java
+++ b/ffwd-client/src/main/java/com/spotify/ffwd/FastForward.java
@@ -1,8 +1,8 @@
/*-
* -\-\-
- * FastForward Client
+ * FastForward Java Client
* --
- * Copyright (C) 2016 - 2019 Spotify AB
+ * Copyright (C) 2016 - 2020 Spotify AB
* --
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,10 +31,31 @@
public class FastForward {
+ /**
+ * If you are using an older version, please use Version.V0.
+ * This variable will eventually be removed.
+ */
+ @Deprecated()
public static final int LATEST_VERSION = 0;
+
public static final String DEFAULT_HOST = "localhost";
public static final int DEFAULT_PORT = 19091;
+ public enum Version {
+ V0(0),
+ V1(1);
+ private final int version;
+
+ Version(final int version) {
+ this.version = version;
+ }
+
+ public int getVersion() {
+ return version;
+ }
+
+ }
+
public static FastForward setup() throws UnknownHostException, SocketException {
return setup(InetAddress.getByName(DEFAULT_HOST), DEFAULT_PORT);
@@ -57,6 +78,7 @@ public static FastForward setup(InetAddress addr) throws SocketException {
* Initialization method for a FastForward client.
*
* @return A new instance of a FastForward client.
+ *
* @throws SocketException If a datagram socket cannot be created.
*/
public static FastForward setup(InetAddress addr, int port) throws SocketException {
@@ -74,15 +96,26 @@ private FastForward(InetAddress addr, int port, DatagramSocket socket) {
this.socket = socket;
}
+ protected FastForward() throws UnknownHostException, SocketException {
+ this.addr = InetAddress.getByName(DEFAULT_HOST);
+ this.port = DEFAULT_PORT;
+ this.socket = new DatagramSocket();
+ }
+
+
public void send(Metric metric) throws IOException {
- sendFrame(metric.serialize());
+ sendFrame(metric.serialize(), Version.V0);
}
- private void sendFrame(byte[] bytes) throws IOException {
+ public void send(com.spotify.ffwd.v1.Metric metric) throws IOException {
+ sendFrame(metric.serialize(), Version.V1);
+ }
+
+ void sendFrame(byte[] bytes, Version v) throws IOException {
final ByteBuffer buffer = ByteBuffer.allocate(bytes.length + 8);
buffer.order(ByteOrder.BIG_ENDIAN);
- buffer.putInt(LATEST_VERSION);
+ buffer.putInt(v.getVersion());
buffer.putInt(buffer.capacity());
buffer.put(bytes);
buffer.rewind();
@@ -100,4 +133,8 @@ public static Metric metric(String key) {
return new Metric().key(key);
}
+ public static com.spotify.ffwd.v1.Metric metricV1(String key) {
+ return new com.spotify.ffwd.v1.Metric().key(key);
+ }
+
}
diff --git a/ffwd-client/src/main/java/com/spotify/ffwd/v1/Metric.java b/ffwd-client/src/main/java/com/spotify/ffwd/v1/Metric.java
index 6443760..8f1acf4 100644
--- a/ffwd-client/src/main/java/com/spotify/ffwd/v1/Metric.java
+++ b/ffwd-client/src/main/java/com/spotify/ffwd/v1/Metric.java
@@ -18,24 +18,6 @@
* -/-/-
*/
-/*
- * FastForward Client
- * --
- * Copyright (C) 2016 - 2019 Spotify AB
- * --
- * Licensed 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 com.spotify.ffwd.v1;
import com.google.protobuf.ByteString;
@@ -67,7 +49,7 @@ public Metric() {
this.has = 0;
this.time = 0;
this.key = null;
- this.value = null;
+ this.value = Value.doubleValue(0);
this.host = null;
this.tags = new ArrayList<>();
this.attributes = new HashMap<>();
@@ -80,7 +62,7 @@ public Metric(
this.has = has;
this.time = time;
this.key = key;
- this.value = value;
+ this.value = (value == null) ? Value.doubleValue(0) : value;
this.host = host;
this.tags = tags;
this.attributes = attributes;
@@ -103,7 +85,8 @@ public Metric key(String key) {
}
public Metric value(Value value) {
- return new Metric(set(VALUE), time, key, value, host, tags, attributes);
+ Value newValue = (value == null) ? Value.doubleValue(0) : value;
+ return new Metric(set(VALUE), time, key, newValue, host, tags, attributes);
}
public Metric host(String host) {
@@ -149,10 +132,12 @@ public byte[] serialize() {
builder.setValue(Protocol1.Value.newBuilder().setDoubleValue(doubleValue.getValue()));
} else if (value instanceof Value.DistributionValue) {
Value.DistributionValue distributionValue = (Value.DistributionValue) value;
- ByteString byteString = ByteString.copyFrom(distributionValue.getValue());
+ ByteString byteString = ByteString.copyFrom(distributionValue.getValue().array());
builder.setValue(Protocol1.Value.newBuilder().setDistributionValue(byteString));
+ } else {
+ throw new IllegalArgumentException("Failed to identify distribution type : [" + value
+ + "]");
}
-
}
if (test(HOST)) {
diff --git a/ffwd-client/src/main/java/com/spotify/ffwd/v1/Value.java b/ffwd-client/src/main/java/com/spotify/ffwd/v1/Value.java
index b07506e..23ae869 100644
--- a/ffwd-client/src/main/java/com/spotify/ffwd/v1/Value.java
+++ b/ffwd-client/src/main/java/com/spotify/ffwd/v1/Value.java
@@ -21,12 +21,16 @@
package com.spotify.ffwd.v1;
import com.google.auto.value.AutoValue;
-
import java.nio.ByteBuffer;
import java.util.function.Function;
-
+/**
+ * The actual value of the data point.
+ * Currently we support two value types:
+ * Double and Distribution.
+ *
+ */
public abstract class Value {
Value() {}
diff --git a/ffwd-client/src/main/proto/protocol1.proto b/ffwd-client/src/main/proto/protocol1.proto
index c5f8603..ed7d269 100644
--- a/ffwd-client/src/main/proto/protocol1.proto
+++ b/ffwd-client/src/main/proto/protocol1.proto
@@ -37,6 +37,7 @@ message Metric {
}
+// Actual data point value. We currently support 2 types, distribution and double.
message Value {
oneof value {
double double_value = 1;
diff --git a/ffwd-client/src/test/java/com/spotify/ffwd/FastForwardTest.java b/ffwd-client/src/test/java/com/spotify/ffwd/FastForwardTest.java
new file mode 100644
index 0000000..6dace2d
--- /dev/null
+++ b/ffwd-client/src/test/java/com/spotify/ffwd/FastForwardTest.java
@@ -0,0 +1,93 @@
+/*-
+ * -\-\-
+ * FastForward Java Client
+ * --
+ * Copyright (C) 2016 - 2020 Spotify AB
+ * --
+ * Licensed 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 com.spotify.ffwd;
+
+import com.spotify.ffwd.v1.Metric;
+import java.io.IOException;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.times;
+
+
+public class FastForwardTest {
+ private static final String KEY = "key";
+
+
+ @Spy
+ private FastForward client;
+
+ private ArgumentCaptor inputCaptor;
+ private ArgumentCaptor versionCaptor;
+
+ @BeforeEach
+ public void before() throws IOException{
+ MockitoAnnotations.initMocks(this);
+ inputCaptor = ArgumentCaptor.forClass(byte[].class);
+ versionCaptor = ArgumentCaptor.forClass(FastForward.Version.class);
+ Mockito.doNothing().when(client).sendFrame(inputCaptor.capture(), versionCaptor.capture());
+ }
+
+ @Test
+ public void testSendV1() throws SocketException, UnknownHostException , IOException {
+ final Metric metricV1 = FastForward.metricV1(KEY);
+ client.send(metricV1);
+ Mockito.verify(client,times(1)).sendFrame(Mockito.any(byte[].class) ,
+ Mockito.any(FastForward.Version.class));
+ assertArrayEquals(metricV1.serialize(), inputCaptor.getValue());
+ assertEquals(FastForward.Version.V1,versionCaptor.getValue());
+ }
+
+ @Test
+ public void testSendV0() throws SocketException, UnknownHostException , IOException {
+ final com.spotify.ffwd.Metric metricV0 = FastForward.metric(KEY);
+ client.send(metricV0);
+ Mockito.verify(client,times(1)).sendFrame(Mockito.any(byte[].class) ,
+ Mockito.any(FastForward.Version.class));
+ assertArrayEquals(metricV0.serialize(), inputCaptor.getValue());
+ assertEquals(FastForward.Version.V0,versionCaptor.getValue());
+ }
+
+ @Test
+ public void testMetricV0(){
+ com.spotify.ffwd.Metric metricV0 = FastForward.metric(KEY);
+ assertEquals(com.spotify.ffwd.Metric.class, metricV0.getClass());
+ assertEquals(KEY,metricV0.getKey());
+ }
+
+ @Test
+ public void testMetricV1(){
+ Metric metricV1 = FastForward.metricV1(KEY);
+ assertEquals(Metric.class, metricV1.getClass());
+ assertEquals(KEY,metricV1.getKey());
+ }
+
+
+}
diff --git a/ffwd-client/src/test/java/com/spotify/ffwd/v1/MetricTest.java b/ffwd-client/src/test/java/com/spotify/ffwd/v1/MetricTest.java
new file mode 100644
index 0000000..383c351
--- /dev/null
+++ b/ffwd-client/src/test/java/com/spotify/ffwd/v1/MetricTest.java
@@ -0,0 +1,119 @@
+/*-
+ * -\-\-
+ * FastForward Java Client
+ * --
+ * Copyright (C) 2016 - 2020 Spotify AB
+ * --
+ * Licensed 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 com.spotify.ffwd.v1;
+
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+
+import com.google.protobuf.ByteString;
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.spotify.ffwd.protocol1.Protocol1;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+
+public class MetricTest {
+
+ private static final String HOST = "host";
+ private static final String KEY ="key";
+ private static final String TAG1 = "tag1";
+ private static final String TAG2 = "tag2";
+ private static final String KEYVALUE ="val1";
+ private static final String DISTRIBUTION_DATA_POINT ="FAKEDATAFAKEDATAFAKEDATA";
+
+
+ @Test
+ public void testMetricValueNullDefault(){
+ Metric metric = new Metric();
+ assertNotNull(metric.getValue());
+ double actual = extractValue(metric);
+ assertEquals(0, actual);
+ }
+
+ @Test
+ public void testMetricValueNull() {
+ final long time = System.currentTimeMillis();
+ final Metric metricV1 = createMetric(null, time);
+ assertNotNull(metricV1.getValue());
+ double actual = extractValue(metricV1);
+ assertEquals(0, actual);
+ }
+
+ @Test
+ public void testMetricDoubleValue(){
+ final double expected = 8.908;
+ final Metric metricV1 = createMetric(Value.doubleValue(expected), System.currentTimeMillis());
+ double actual = extractValue(metricV1);
+ assertEquals(expected, actual );
+ }
+
+ @Test
+ public void testSerializeValueNull() throws InvalidProtocolBufferException {
+ final double expectedDataPoint = 0;
+ Metric metricV1 = new Metric();
+ byte[] bytes = metricV1.serialize();
+ Protocol1.Metric out = Protocol1.Message.newBuilder().mergeFrom(bytes).build().getMetric();
+ Protocol1.Value outVal = out.getValue();
+ assertEquals(expectedDataPoint, outVal.getDoubleValue());
+}
+
+ @Test
+ public void testSerializeDoubleValue() throws InvalidProtocolBufferException {
+ final double pointVal = 0.9897;
+ Metric metricV1 = (new Metric()).value(Value.doubleValue(pointVal));
+ byte[] bytes = metricV1.serialize();
+ Protocol1.Metric out = Protocol1.Message.newBuilder().mergeFrom(bytes).build().getMetric();
+ Protocol1.Value outVal = out.getValue();
+ assertEquals(pointVal,outVal.getDoubleValue());
+ }
+
+ @Test
+ public void testSerializeDistributionValue() throws InvalidProtocolBufferException {
+ final byte [] pointVal = DISTRIBUTION_DATA_POINT.getBytes(StandardCharsets.UTF_8);
+ ByteBuffer byteBuffer = ByteBuffer.allocate(pointVal.length);
+ byteBuffer.put(pointVal);
+ Metric metricV1 = (new Metric()).value(Value.distributionValue(byteBuffer));
+ byte[] bytes = metricV1.serialize();
+ Protocol1.Metric out = Protocol1.Message.newBuilder().mergeFrom(bytes).build().getMetric();
+ Protocol1.Value outVal = out.getValue();
+ assertEquals(ByteString.copyFrom(pointVal),outVal.getDistributionValue());
+ }
+
+
+ private Metric createMetric(Value value, long time){
+ final long has = 1L;
+ return new Metric(has, time,KEY,
+ value, HOST, List.of(TAG1,TAG2
+ ), Map.of(KEY,KEYVALUE) );
+ }
+
+ private double extractValue(Metric metric){
+ Value value = metric.getValue();
+ Double actual = ((Value.DoubleValue) value).getValue();
+ return actual;
+ }
+
+
+}
diff --git a/ffwd-client/src/test/java/com/spotify/ffwd/v1/ValueTest.java b/ffwd-client/src/test/java/com/spotify/ffwd/v1/ValueTest.java
new file mode 100644
index 0000000..ab5301f
--- /dev/null
+++ b/ffwd-client/src/test/java/com/spotify/ffwd/v1/ValueTest.java
@@ -0,0 +1,99 @@
+/*-
+ * -\-\-
+ * FastForward Java Client
+ * --
+ * Copyright (C) 2016 - 2020 Spotify AB
+ * --
+ * Licensed 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 com.spotify.ffwd.v1;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+import org.junit.jupiter.api.Test;
+
+public class ValueTest {
+ private static final double DOUBLE_VAL = 0.976;
+ private static final ByteBuffer DISTRIBUTION_VAL = createSample();
+
+
+ @Test
+ public void testDoubleValue(){
+ Value value = Value.doubleValue(DOUBLE_VAL);
+ assertTrue(value instanceof Value.DoubleValue);
+ }
+
+ @Test
+ public void testDistributionValue(){
+ Value value = Value.distributionValue(DISTRIBUTION_VAL);
+ assertTrue(value instanceof Value.DistributionValue);
+ }
+
+ @Test
+ public void testMatch(){
+ List expected = new ArrayList<>();
+ Value value1 = Value.doubleValue(DOUBLE_VAL);
+ expected.add(value1);
+ Value value2 = Value.doubleValue(DOUBLE_VAL*2);
+ expected.add(value2);
+ Value value3 = Value.distributionValue(createSample());
+ expected.add(value3);
+ List