aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--BUILD1
-rw-r--r--core/src/test/java/com/google/instrumentation/stats/StatsContextFactoryTest.java85
-rw-r--r--core_impl/src/main/java/com/google/instrumentation/stats/StatsSerializer.java46
3 files changed, 120 insertions, 12 deletions
diff --git a/BUILD b/BUILD
index c0c979ef..f1d448ad 100644
--- a/BUILD
+++ b/BUILD
@@ -257,6 +257,7 @@ java_test(
name = "StatsContextFactoryTest",
srcs = ["core/src/test/java/com/google/instrumentation/stats/StatsContextFactoryTest.java"],
deps = [
+ ":shared",
":stats-core",
":stats-core_impl",
"@guava//jar",
diff --git a/core/src/test/java/com/google/instrumentation/stats/StatsContextFactoryTest.java b/core/src/test/java/com/google/instrumentation/stats/StatsContextFactoryTest.java
index 12efa98f..004d8030 100644
--- a/core/src/test/java/com/google/instrumentation/stats/StatsContextFactoryTest.java
+++ b/core/src/test/java/com/google/instrumentation/stats/StatsContextFactoryTest.java
@@ -13,8 +13,13 @@
package com.google.instrumentation.stats;
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.io.base.VarInt;
import java.io.ByteArrayInputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -24,8 +29,84 @@ import org.junit.runners.JUnit4;
*/
@RunWith(JUnit4.class)
public class StatsContextFactoryTest {
+ private static final int VALUE_TYPE_STRING = 0;
+ private static final int VALUE_TYPE_INTEGER = 1;
+ private static final int VALUE_TYPE_BOOLEAN = 2;
+
+ private static final String KEY1 = "Key";
+ private static final String VALUE_STRING = "String";
+ private static final int VALUE_INT = 10;
+ private static final boolean VALUE_BOOL = true;
+
+ @Test
+ public void testDeserializeEmptyReturnDefaultStatsContext() throws Exception {
+ StatsContext expected = Stats.getStatsContextFactory().getDefault();
+ StatsContext actual = testDeserialize(new ByteArrayInputStream(new byte[0]));
+ assertThat(actual).isEqualTo(expected);
+ }
+
+ @Test(expected = IOException.class)
+ public void testDeserializeValueTypeInteger() throws Exception {
+ // TODO(songya): test should pass after we add support for type integer
+ testDeserialize(constructInputStream(VALUE_TYPE_INTEGER));
+ }
+
+ @Test(expected = IOException.class)
+ public void testDeserializeValueTypeBoolean() throws Exception {
+ // TODO(songya): test should pass after we add support for type boolean
+ testDeserialize(constructInputStream(VALUE_TYPE_BOOLEAN));
+ }
+
@Test(expected = IOException.class)
- public void testDeserializeEmpty() throws Exception {
- Stats.getStatsContextFactory().deserialize(new ByteArrayInputStream(new byte[0]));
+ public void testDeserializeWrongFormat() throws Exception {
+ // encoded tags should follow the format [tag_type key_len key_bytes value_len value_bytes]*
+ testDeserialize(new ByteArrayInputStream(new byte[1]));
+ }
+
+ private StatsContext testDeserialize(InputStream inputStream) throws IOException {
+ return Stats.getStatsContextFactory().deserialize(inputStream);
+ }
+
+ /*
+ * TODO(songya): after supporting serialize integer and boolean,
+ * remove this method and use StatsContext.serialize() instead.
+ * Currently StatsContext.serialize() can only serialize strings.
+ */
+ private InputStream constructInputStream(int valueType) {
+ // TODO(songya): please note that ByteBuffer will overflow if > 1024 bytes were appended.
+ ByteBuffer buffer = ByteBuffer.allocate(1024);
+ VarInt.putVarInt(valueType, buffer);
+ encodeString(KEY1, buffer);
+ switch (valueType) {
+ case VALUE_TYPE_STRING:
+ encodeString(VALUE_STRING, buffer);
+ break;
+ case VALUE_TYPE_INTEGER:
+ encodeInteger(VALUE_INT, buffer);
+ break;
+ case VALUE_TYPE_BOOLEAN:
+ encodeBoolean(VALUE_BOOL, buffer);
+ break;
+ default:
+ return null;
+ }
+ return new ByteArrayInputStream(buffer.array());
+ }
+
+ private void encodeString(String input, ByteBuffer buffer) {
+ VarInt.putVarInt(input.length(), buffer);
+ for (int i = 0; i < input.length(); i++) {
+ buffer.put((byte) input.charAt(i));
+ }
+ }
+
+ private void encodeInteger(int input, ByteBuffer buffer) {
+ VarInt.putVarInt(Integer.valueOf(input).toString().length(), buffer);
+ buffer.put((byte) input);
+ }
+
+ private void encodeBoolean(boolean input, ByteBuffer buffer) {
+ VarInt.putVarInt(Boolean.valueOf(input).toString().length(), buffer);
+ buffer.put((byte) (input? 1 : 0));
}
}
diff --git a/core_impl/src/main/java/com/google/instrumentation/stats/StatsSerializer.java b/core_impl/src/main/java/com/google/instrumentation/stats/StatsSerializer.java
index 62c4ca66..4a1e08be 100644
--- a/core_impl/src/main/java/com/google/instrumentation/stats/StatsSerializer.java
+++ b/core_impl/src/main/java/com/google/instrumentation/stats/StatsSerializer.java
@@ -30,14 +30,30 @@ import java.util.Set;
* Native implementation {@link StatsContext} serialization.
*/
final class StatsSerializer {
+ // * tag_metadata is one byte and is for encoding metadata about the tag. The
+ // low 2 bits of this byte are used to encode the type of the value bytes.
+ // The high 6 bits are reserved for future use. The value_bytes type is
+ // encoded as:
+ // 00 (value 0): string (UTF-8) encoding
+ // 01 (value 1): integer (varint int64 encoding).
+ // 10 (value 2): boolean format.
+ // 11 (value 3): reserved for future use.
+ // TODO(songya): Currently we only support encoding on string type.
+ private static final int VALUE_TYPE_STRING = 0;
+ private static final int VALUE_TYPE_INTEGER = 1;
+ private static final int VALUE_TYPE_BOOLEAN = 2;
+
// Serializes a StatsContext by transforming it into a StatsContextProto. The
// encoded tags are of the form:
- // num_tags [key_len key_bytes value_len value_bytes]*
+ // [tag_metadata key_len key_bytes value_len value_bytes]*
static void serialize(StatsContextImpl context, OutputStream output) throws IOException {
+ // TODO(songya): please note that ByteBuffer will overflow if > 1024 bytes were appended
ByteBuffer buffer = ByteBuffer.allocate(1024);
+
+ // TODO(songya): add support for value types integer and boolean
Set<Entry<String, String>> tags = context.tags.entrySet();
- VarInt.putVarInt(tags.size(), buffer);
for (Entry<String, String> tag : tags) {
+ VarInt.putVarInt(VALUE_TYPE_STRING, buffer);
encode(tag.getKey(), buffer);
encode(tag.getValue(), buffer);
}
@@ -48,17 +64,27 @@ final class StatsSerializer {
}
// Deserializes based on an serialized StatsContextProto. The encoded tags are of the form:
- // num_tags [key_len key_bytes value_len value_bytes]*
+ // [tag_metadata key_len key_bytes value_len value_bytes]*
static StatsContextImpl deserialize(InputStream input) throws IOException {
try {
StatsContextProto.StatsContext context = StatsContextProto.StatsContext.parseFrom(input);
ByteBuffer buffer = context.getTags().asReadOnlyByteBuffer();
- int numTags = VarInt.getVarInt(buffer);
- HashMap<String, String> tags = new HashMap<String, String>(numTags);
- for (int i = 0; i < numTags; i++) {
- String key = decode(buffer);
- String val = decode(buffer);
- tags.put(key, val);
+ HashMap<String, String> tags = new HashMap<String, String>();
+ int limit = buffer.limit();
+ while (buffer.position() < limit) {
+ int type = VarInt.getVarInt(buffer);
+ switch (type) {
+ case VALUE_TYPE_STRING:
+ String key = decode(buffer);
+ String val = decode(buffer);
+ tags.put(key, val);
+ break;
+ case VALUE_TYPE_INTEGER:
+ case VALUE_TYPE_BOOLEAN:
+ default:
+ // TODO(songya): add support for value types integer and boolean
+ throw new IOException("Unsupported tag value type.");
+ }
}
return new StatsContextImpl(tags);
} catch (BufferUnderflowException exn) {
@@ -81,4 +107,4 @@ final class StatsSerializer {
}
return builder.toString();
}
-}
+} \ No newline at end of file