com.arpnetworking.metrics.impl.TsdQueryLogSinkTest.java Source code

Java tutorial

Introduction

Here is the source code for com.arpnetworking.metrics.impl.TsdQueryLogSinkTest.java

Source

/**
 * Copyright 2014 Groupon.com
 *
 * 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.arpnetworking.metrics.impl;

import ch.qos.logback.classic.AsyncAppender;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.rolling.RollingFileAppender;
import ch.qos.logback.core.rolling.TimeBasedRollingPolicy;

import com.arpnetworking.logback.StenoEncoder;
import com.arpnetworking.metrics.Quantity;
import com.arpnetworking.metrics.Sink;
import com.arpnetworking.metrics.Unit;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import com.github.fge.jackson.JsonLoader;
import com.github.fge.jsonschema.core.exceptions.ProcessingException;
import com.github.fge.jsonschema.core.report.ProcessingReport;
import com.github.fge.jsonschema.main.JsonSchemaFactory;
import com.github.fge.jsonschema.main.JsonValidator;
import com.google.common.base.Throwables;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

/**
 * Tests for <code>TsdMetrics</code>.
 *
 * @author Ville Koskela (vkoskela at groupon dot com)
 */
public class TsdQueryLogSinkTest {

    @Test
    public void testBuilderWithDefaults() {
        final String expectedPath = "./target/TsdQueryLogSinkTest/testBuilderWithDefaults/";
        final TsdQueryLogSink metricsFactory = (TsdQueryLogSink) new TsdQueryLogSink.Builder().setPath(expectedPath)
                .build();

        final AsyncAppender asyncAppender = (AsyncAppender) metricsFactory.getQueryLogger()
                .getAppender("query-log-async");
        final RollingFileAppender<ILoggingEvent> rollingAppender = (RollingFileAppender<ILoggingEvent>) asyncAppender
                .getAppender("query-log");
        @SuppressWarnings("unchecked")
        final TimeBasedRollingPolicy<ILoggingEvent> rollingPolicy = (TimeBasedRollingPolicy<ILoggingEvent>) rollingAppender
                .getRollingPolicy();
        final StenoEncoder encoder = (StenoEncoder) rollingAppender.getEncoder();

        Assert.assertTrue(encoder.isImmediateFlush());
        Assert.assertEquals(expectedPath + "query.log", rollingAppender.getFile());
        Assert.assertEquals(expectedPath + "query.%d{yyyy-MM-dd-HH}.log.gz", rollingPolicy.getFileNamePattern());
    }

    @Test
    public void testCustomBuilder() {
        final String expectedPath = "./target/TsdQueryLogSinkTest/testBuilderWithoutImmediateFlush/";
        final TsdQueryLogSink metricsFactory = (TsdQueryLogSink) new TsdQueryLogSink.Builder().setPath(expectedPath)
                .setImmediateFlush(Boolean.FALSE).setName("foo").setExtension(".bar").build();

        final AsyncAppender asyncAppender = (AsyncAppender) metricsFactory.getQueryLogger()
                .getAppender("query-log-async");
        final RollingFileAppender<ILoggingEvent> rollingAppender = (RollingFileAppender<ILoggingEvent>) asyncAppender
                .getAppender("query-log");
        @SuppressWarnings("unchecked")
        final TimeBasedRollingPolicy<ILoggingEvent> rollingPolicy = (TimeBasedRollingPolicy<ILoggingEvent>) rollingAppender
                .getRollingPolicy();
        final StenoEncoder encoder = (StenoEncoder) rollingAppender.getEncoder();

        Assert.assertFalse(encoder.isImmediateFlush());
        Assert.assertEquals(expectedPath + "foo.bar", rollingAppender.getFile());
        Assert.assertEquals(expectedPath + "foo.%d{yyyy-MM-dd-HH}.bar.gz", rollingPolicy.getFileNamePattern());
    }

    @Test(expected = IllegalArgumentException.class)
    public void testBuilderNullPath() {
        new TsdQueryLogSink.Builder().setPath(null).build();
    }

    @Test(expected = IllegalArgumentException.class)
    public void testBuilderNullExtension() {
        new TsdQueryLogSink.Builder().setExtension(null).build();
    }

    @Test(expected = IllegalArgumentException.class)
    public void testBuilderNullImmediateFlush() {
        new TsdQueryLogSink.Builder().setImmediateFlush(null).build();
    }

    @Test(expected = IllegalArgumentException.class)
    public void testBuilderNullName() {
        new TsdQueryLogSink.Builder().setName(null).build();
    }

    @Test(expected = IllegalArgumentException.class)
    public void testBuilderEmptyName() {
        new TsdQueryLogSink.Builder().setName("").build();
    }

    @Test
    public void testBuilderPathWithoutTrailingSeparator() {
        final String expectedPath = "./target/TsdQueryLogSinkTest/testBuilderPathWithoutTrailingSeparator";
        final TsdQueryLogSink metricsFactory = (TsdQueryLogSink) new TsdQueryLogSink.Builder().setPath(expectedPath)
                .build();

        final AsyncAppender asyncAppender = (AsyncAppender) metricsFactory.getQueryLogger()
                .getAppender("query-log-async");
        final RollingFileAppender<ILoggingEvent> rollingAppender = (RollingFileAppender<ILoggingEvent>) asyncAppender
                .getAppender("query-log");
        @SuppressWarnings("unchecked")
        final TimeBasedRollingPolicy<ILoggingEvent> rollingPolicy = (TimeBasedRollingPolicy<ILoggingEvent>) rollingAppender
                .getRollingPolicy();

        Assert.assertEquals(expectedPath + "/query.log", rollingAppender.getFile());
        Assert.assertEquals(expectedPath + "/query.%d{yyyy-MM-dd-HH}.log.gz", rollingPolicy.getFileNamePattern());
    }

    @Test
    public void testObjectMapperIOException() throws IOException {
        final org.slf4j.Logger logger = createSlf4jLoggerMock();
        final ObjectMapper objectMapper = Mockito.mock(ObjectMapper.class);
        final Sink sink = new TsdQueryLogSink(new TsdQueryLogSink.Builder().setPath("./target/TsdQueryLogSinkTest")
                .setName("testObjectMapperIOException-Query"), objectMapper, logger);

        Mockito.doThrow(new JsonMappingException("JsonMappingException")).when(objectMapper)
                .writeValueAsString(Mockito.any());
        recordEmpty(sink);
        Mockito.verify(logger).warn(Mockito.argThat(Matchers.any(String.class)),
                Mockito.argThat(Matchers.any(Throwable.class)));
    }

    @Test
    public void testShutdownHookThread() throws InterruptedException {
        final LoggerContext context = Mockito.mock(LoggerContext.class);
        final Thread shutdownThread = new TsdQueryLogSink.ShutdownHookThread(context);
        shutdownThread.start();
        shutdownThread.join();
        Mockito.verify(context).stop();
    }

    @Test
    public void testEmptySerialization() throws IOException, InterruptedException {
        final File actualFile = new File("./target/TsdQueryLogSinkTest/testEmptySerialization-Query.log");
        Files.deleteIfExists(actualFile.toPath());
        final Sink sink = new TsdQueryLogSink.Builder().setPath("./target/TsdQueryLogSinkTest")
                .setName("testEmptySerialization-Query").setImmediateFlush(Boolean.TRUE).build();

        sink.record(ANNOTATIONS, TEST_EMPTY_SERIALIZATION_TIMERS, TEST_EMPTY_SERIALIZATION_COUNTERS,
                TEST_EMPTY_SERIALIZATION_GAUGES);

        // TODO(vkoskela): Add protected option to disable async [MAI-181].
        Thread.sleep(100);

        final String actualOriginalJson = fileToString(actualFile);
        assertMatchesJsonSchema(actualOriginalJson);
        final String actualComparableJson = actualOriginalJson
                .replaceAll("\"time\":\"[^\"]*\"", "\"time\":\"<TIME>\"")
                .replaceAll("\"host\":\"[^\"]*\"", "\"host\":\"<HOST>\"")
                .replaceAll("\"processId\":\"[^\"]*\"", "\"processId\":\"<PROCESSID>\"")
                .replaceAll("\"threadId\":\"[^\"]*\"", "\"threadId\":\"<THREADID>\"")
                .replaceAll("\"id\":\"[^\"]*\"", "\"id\":\"<ID>\"");
        final JsonNode actual = OBJECT_MAPPER.readTree(actualComparableJson);
        final JsonNode expected = OBJECT_MAPPER.readTree(EXPECTED_EMPTY_METRICS_JSON);

        Assert.assertEquals("expectedJson=" + OBJECT_MAPPER.writeValueAsString(expected) + " vs actualJson="
                + OBJECT_MAPPER.writeValueAsString(actual), expected, actual);
    }

    @Test
    public void testSerialization() throws IOException, InterruptedException {
        final File actualFile = new File("./target/TsdQueryLogSinkTest/testSerialization-Query.log");
        Files.deleteIfExists(actualFile.toPath());
        final Sink sink = new TsdQueryLogSink.Builder().setPath("./target/TsdQueryLogSinkTest")
                .setName("testSerialization-Query").setImmediateFlush(Boolean.TRUE).build();

        final Map<String, String> annotations = new LinkedHashMap<>(ANNOTATIONS);
        annotations.put("foo", "bar");
        sink.record(annotations, TEST_SERIALIZATION_TIMERS, TEST_SERIALIZATION_COUNTERS, TEST_SERIALIZATION_GAUGES);

        // TODO(vkoskela): Add protected option to disable async [MAI-181].
        Thread.sleep(100);

        final String actualOriginalJson = fileToString(actualFile);
        assertMatchesJsonSchema(actualOriginalJson);
        final String actualComparableJson = actualOriginalJson
                .replaceAll("\"time\":\"[^\"]*\"", "\"time\":\"<TIME>\"")
                .replaceAll("\"host\":\"[^\"]*\"", "\"host\":\"<HOST>\"")
                .replaceAll("\"processId\":\"[^\"]*\"", "\"processId\":\"<PROCESSID>\"")
                .replaceAll("\"threadId\":\"[^\"]*\"", "\"threadId\":\"<THREADID>\"")
                .replaceAll("\"id\":\"[^\"]*\"", "\"id\":\"<ID>\"");
        final JsonNode actual = OBJECT_MAPPER.readTree(actualComparableJson);
        final JsonNode expected = OBJECT_MAPPER.readTree(EXPECTED_METRICS_JSON);

        Assert.assertEquals("expectedJson=" + OBJECT_MAPPER.writeValueAsString(expected) + " vs actualJson="
                + OBJECT_MAPPER.writeValueAsString(actual), expected, actual);
    }

    private static Map<String, List<Quantity>> createQuantityMap(final Object... arguments) {
        // CHECKSTYLE.OFF: IllegalInstantiation - No Guava
        final Map<String, List<Quantity>> map = new HashMap<>();
        // CHECKSTYLE.ON: IllegalInstantiation
        List<Quantity> samples = null;
        for (final Object argument : arguments) {
            if (argument instanceof String) {
                samples = new ArrayList<>();
                map.put((String) argument, samples);
            } else if (argument instanceof Quantity) {
                assert samples != null : "first argument must be metric name";
                samples.add((Quantity) argument);
            } else {
                assert false : "unsupported argument type: " + argument.getClass();
            }
        }
        return map;
    }

    private void recordEmpty(final Sink sink) {
        sink.record(Collections.<String, String>emptyMap(), Collections.<String, List<Quantity>>emptyMap(),
                Collections.<String, List<Quantity>>emptyMap(), Collections.<String, List<Quantity>>emptyMap());
    }

    private org.slf4j.Logger createSlf4jLoggerMock() {
        return Mockito.mock(org.slf4j.Logger.class);
    }

    private void assertMatchesJsonSchema(final String json) {
        try {
            final JsonNode jsonNode = JsonLoader.fromString(json);
            final ProcessingReport report = VALIDATOR.validate(STENO_SCHEMA, jsonNode);
            Assert.assertTrue(report.toString(), report.isSuccess());
        } catch (final IOException | ProcessingException e) {
            Assert.fail("Failed with exception: " + e);
        }
    }

    private String fileToString(final File file) {
        try {
            return new Scanner(file, "UTF-8").useDelimiter("\\Z").next();
        } catch (final IOException ioe) {
            throw new RuntimeException(ioe);
        }
    }

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    private static final JsonValidator VALIDATOR = JsonSchemaFactory.byDefault().getValidator();
    private static final JsonNode STENO_SCHEMA;

    private static final Map<String, String> ANNOTATIONS = new LinkedHashMap<>();
    private static final Map<String, List<Quantity>> TEST_EMPTY_SERIALIZATION_TIMERS = createQuantityMap();
    private static final Map<String, List<Quantity>> TEST_EMPTY_SERIALIZATION_COUNTERS = createQuantityMap();
    private static final Map<String, List<Quantity>> TEST_EMPTY_SERIALIZATION_GAUGES = createQuantityMap();

    private static final String EXPECTED_EMPTY_METRICS_JSON = "{" + "  \"time\":\"<TIME>\","
            + "  \"name\":\"aint.metrics\"," + "  \"level\":\"info\"," + "  \"data\":{" + "    \"version\":\"2e\","
            + "    \"annotations\":{" + "      \"initTimestamp\":\"1997-07-16T19:20:30Z\","
            + "      \"finalTimestamp\":\"1997-07-16T19:20:31Z\"" + "    }" + "  }," + "  \"context\":{"
            + "    \"host\":\"<HOST>\"," + "    \"processId\":\"<PROCESSID>\"," + "    \"threadId\":\"<THREADID>\""
            + "  }," + "  \"id\":\"<ID>\"" + "}";

    private static final Map<String, List<Quantity>> TEST_SERIALIZATION_TIMERS = createQuantityMap("timerA",
            "timerB", TsdQuantity.newInstance(Long.valueOf(1L), null), "timerC",
            TsdQuantity.newInstance(Long.valueOf(2L), Unit.MILLISECOND), "timerD",
            TsdQuantity.newInstance(Long.valueOf(3L), Unit.SECOND),
            TsdQuantity.newInstance(Long.valueOf(4L), Unit.SECOND), "timerE",
            TsdQuantity.newInstance(Long.valueOf(5L), Unit.DAY),
            TsdQuantity.newInstance(Long.valueOf(6L), Unit.SECOND), "timerF",
            TsdQuantity.newInstance(Long.valueOf(7L), Unit.DAY), TsdQuantity.newInstance(Long.valueOf(8L), null),
            "timerG", TsdQuantity.newInstance(Long.valueOf(9L), null),
            TsdQuantity.newInstance(Long.valueOf(10L), null), "timerH",
            TsdQuantity.newInstance(Long.valueOf(11L), Unit.DAY),
            TsdQuantity.newInstance(Long.valueOf(12L), Unit.BYTE), "timerI",
            TsdQuantity.newInstance(Double.valueOf(1.12), null), "timerJ",
            TsdQuantity.newInstance(Double.valueOf(2.12), Unit.MILLISECOND), "timerK",
            TsdQuantity.newInstance(Double.valueOf(3.12), Unit.SECOND),
            TsdQuantity.newInstance(Double.valueOf(4.12), Unit.SECOND), "timerL",
            TsdQuantity.newInstance(Double.valueOf(5.12), Unit.DAY),
            TsdQuantity.newInstance(Double.valueOf(6.12), Unit.SECOND), "timerM",
            TsdQuantity.newInstance(Double.valueOf(7.12), Unit.DAY),
            TsdQuantity.newInstance(Double.valueOf(8.12), null), "timerN",
            TsdQuantity.newInstance(Double.valueOf(9.12), null),
            TsdQuantity.newInstance(Double.valueOf(10.12), null), "timerO",
            TsdQuantity.newInstance(Double.valueOf(11.12), Unit.DAY),
            TsdQuantity.newInstance(Double.valueOf(12.12), Unit.BYTE));

    private static final Map<String, List<Quantity>> TEST_SERIALIZATION_COUNTERS = createQuantityMap("counterA",
            "counterB", TsdQuantity.newInstance(Long.valueOf(11L), null), "counterC",
            TsdQuantity.newInstance(Long.valueOf(12L), Unit.MILLISECOND), "counterD",
            TsdQuantity.newInstance(Long.valueOf(13L), Unit.SECOND),
            TsdQuantity.newInstance(Long.valueOf(14L), Unit.SECOND), "counterE",
            TsdQuantity.newInstance(Long.valueOf(15L), Unit.DAY),
            TsdQuantity.newInstance(Long.valueOf(16L), Unit.SECOND), "counterF",
            TsdQuantity.newInstance(Long.valueOf(17L), Unit.DAY), TsdQuantity.newInstance(Long.valueOf(18L), null),
            "counterG", TsdQuantity.newInstance(Long.valueOf(19L), null),
            TsdQuantity.newInstance(Long.valueOf(110L), null), "counterH",
            TsdQuantity.newInstance(Long.valueOf(111L), Unit.DAY),
            TsdQuantity.newInstance(Long.valueOf(112L), Unit.BYTE), "counterI",
            TsdQuantity.newInstance(Double.valueOf(11.12), null), "counterJ",
            TsdQuantity.newInstance(Double.valueOf(12.12), Unit.MILLISECOND), "counterK",
            TsdQuantity.newInstance(Double.valueOf(13.12), Unit.SECOND),
            TsdQuantity.newInstance(Double.valueOf(14.12), Unit.SECOND), "counterL",
            TsdQuantity.newInstance(Double.valueOf(15.12), Unit.DAY),
            TsdQuantity.newInstance(Double.valueOf(16.12), Unit.SECOND), "counterM",
            TsdQuantity.newInstance(Double.valueOf(17.12), Unit.DAY),
            TsdQuantity.newInstance(Double.valueOf(18.12), null), "counterN",
            TsdQuantity.newInstance(Double.valueOf(19.12), null),
            TsdQuantity.newInstance(Double.valueOf(110.12), null), "counterO",
            TsdQuantity.newInstance(Double.valueOf(111.12), Unit.DAY),
            TsdQuantity.newInstance(Double.valueOf(112.12), Unit.BYTE));

    private static final Map<String, List<Quantity>> TEST_SERIALIZATION_GAUGES = createQuantityMap("gaugeA",
            "gaugeB", TsdQuantity.newInstance(Long.valueOf(21L), null), "gaugeC",
            TsdQuantity.newInstance(Long.valueOf(22L), Unit.MILLISECOND), "gaugeD",
            TsdQuantity.newInstance(Long.valueOf(23L), Unit.SECOND),
            TsdQuantity.newInstance(Long.valueOf(24L), Unit.SECOND), "gaugeE",
            TsdQuantity.newInstance(Long.valueOf(25L), Unit.DAY),
            TsdQuantity.newInstance(Long.valueOf(26L), Unit.SECOND), "gaugeF",
            TsdQuantity.newInstance(Long.valueOf(27L), Unit.DAY), TsdQuantity.newInstance(Long.valueOf(28L), null),
            "gaugeG", TsdQuantity.newInstance(Long.valueOf(29L), null),
            TsdQuantity.newInstance(Long.valueOf(210L), null), "gaugeH",
            TsdQuantity.newInstance(Long.valueOf(211L), Unit.DAY),
            TsdQuantity.newInstance(Long.valueOf(212L), Unit.BYTE), "gaugeI",
            TsdQuantity.newInstance(Double.valueOf(21.12), null), "gaugeJ",
            TsdQuantity.newInstance(Double.valueOf(22.12), Unit.MILLISECOND), "gaugeK",
            TsdQuantity.newInstance(Double.valueOf(23.12), Unit.SECOND),
            TsdQuantity.newInstance(Double.valueOf(24.12), Unit.SECOND), "gaugeL",
            TsdQuantity.newInstance(Double.valueOf(25.12), Unit.DAY),
            TsdQuantity.newInstance(Double.valueOf(26.12), Unit.SECOND), "gaugeM",
            TsdQuantity.newInstance(Double.valueOf(27.12), Unit.DAY),
            TsdQuantity.newInstance(Double.valueOf(28.12), null), "gaugeN",
            TsdQuantity.newInstance(Double.valueOf(29.12), null),
            TsdQuantity.newInstance(Double.valueOf(210.12), null), "gaugeO",
            TsdQuantity.newInstance(Double.valueOf(211.12), Unit.DAY),
            TsdQuantity.newInstance(Double.valueOf(212.12), Unit.BYTE));

    private static final String EXPECTED_METRICS_JSON = "{" + "  \"time\":\"<TIME>\","
            + "  \"name\":\"aint.metrics\"," + "  \"level\":\"info\"," + "  \"data\":{" + "    \"version\":\"2e\","
            + "    \"annotations\":{" + "      \"initTimestamp\":\"1997-07-16T19:20:30Z\","
            + "      \"finalTimestamp\":\"1997-07-16T19:20:31Z\"," + "      \"foo\":\"bar\"" + "    },"
            + "    \"counters\":{" + "      \"counterA\":{\"values\":[]},"
            + "      \"counterB\":{\"values\":[{\"value\":11}]},"
            + "      \"counterC\":{\"values\":[{\"value\":12,\"unit\":\"millisecond\"}]},"
            + "      \"counterD\":{\"values\":[{\"value\":13,\"unit\":\"second\"},{\"value\":14,\"unit\":\"second\"}]},"
            + "      \"counterE\":{\"values\":[{\"value\":15,\"unit\":\"day\"},{\"value\":16,\"unit\":\"second\"}]},"
            + "      \"counterF\":{\"values\":[{\"value\":17,\"unit\":\"day\"},{\"value\":18}]},"
            + "      \"counterG\":{\"values\":[{\"value\":19},{\"value\":110}]},"
            + "      \"counterH\":{\"values\":[{\"value\":111,\"unit\":\"day\"},{\"value\":112,\"unit\":\"byte\"}]},"
            + "      \"counterI\":{\"values\":[{\"value\":11.12}]},"
            + "      \"counterJ\":{\"values\":[{\"value\":12.12,\"unit\":\"millisecond\"}]},"
            + "      \"counterK\":{\"values\":[{\"value\":13.12,\"unit\":\"second\"},{\"value\":14.12,\"unit\":\"second\"}]},"
            + "      \"counterL\":{\"values\":[{\"value\":15.12,\"unit\":\"day\"},{\"value\":16.12,\"unit\":\"second\"}]},"
            + "      \"counterM\":{\"values\":[{\"value\":17.12,\"unit\":\"day\"},{\"value\":18.12}]},"
            + "      \"counterN\":{\"values\":[{\"value\":19.12},{\"value\":110.12}]},"
            + "      \"counterO\":{\"values\":[{\"value\":111.12,\"unit\":\"day\"},{\"value\":112.12,\"unit\":\"byte\"}]}"
            + "    }," + "    \"gauges\":{" + "      \"gaugeA\":{\"values\":[]},"
            + "      \"gaugeB\":{\"values\":[{\"value\":21}]},"
            + "      \"gaugeC\":{\"values\":[{\"value\":22,\"unit\":\"millisecond\"}]},"
            + "      \"gaugeD\":{\"values\":[{\"value\":23,\"unit\":\"second\"},{\"value\":24,\"unit\":\"second\"}]},"
            + "      \"gaugeE\":{\"values\":[{\"value\":25,\"unit\":\"day\"},{\"value\":26,\"unit\":\"second\"}]},"
            + "      \"gaugeF\":{\"values\":[{\"value\":27,\"unit\":\"day\"},{\"value\":28}]},"
            + "      \"gaugeG\":{\"values\":[{\"value\":29},{\"value\":210}]},"
            + "      \"gaugeH\":{\"values\":[{\"value\":211,\"unit\":\"day\"},{\"value\":212,\"unit\":\"byte\"}]},"
            + "      \"gaugeI\":{\"values\":[{\"value\":21.12}]},"
            + "      \"gaugeJ\":{\"values\":[{\"value\":22.12,\"unit\":\"millisecond\"}]},"
            + "      \"gaugeK\":{\"values\":[{\"value\":23.12,\"unit\":\"second\"},{\"value\":24.12,\"unit\":\"second\"}]},"
            + "      \"gaugeL\":{\"values\":[{\"value\":25.12,\"unit\":\"day\"},{\"value\":26.12,\"unit\":\"second\"}]},"
            + "      \"gaugeM\":{\"values\":[{\"value\":27.12,\"unit\":\"day\"},{\"value\":28.12}]},"
            + "      \"gaugeN\":{\"values\":[{\"value\":29.12},{\"value\":210.12}]},"
            + "      \"gaugeO\":{\"values\":[{\"value\":211.12,\"unit\":\"day\"},{\"value\":212.12,\"unit\":\"byte\"}]}"
            + "    }," + "    \"timers\":{" + "     \"timerA\":{\"values\":[]},"
            + "      \"timerB\":{\"values\":[{\"value\":1}]},"
            + "      \"timerC\":{\"values\":[{\"value\":2,\"unit\":\"millisecond\"}]},"
            + "      \"timerD\":{\"values\":[{\"value\":3,\"unit\":\"second\"},{\"value\":4,\"unit\":\"second\"}]},"
            + "      \"timerE\":{\"values\":[{\"value\":5,\"unit\":\"day\"},{\"value\":6,\"unit\":\"second\"}]},"
            + "      \"timerF\":{\"values\":[{\"value\":7,\"unit\":\"day\"},{\"value\":8}]},"
            + "      \"timerG\":{\"values\":[{\"value\":9},{\"value\":10}]},"
            + "      \"timerH\":{\"values\":[{\"value\":11,\"unit\":\"day\"},{\"value\":12,\"unit\":\"byte\"}]},"
            + "      \"timerI\":{\"values\":[{\"value\":1.12}]},"
            + "      \"timerJ\":{\"values\":[{\"value\":2.12,\"unit\":\"millisecond\"}]},"
            + "      \"timerK\":{\"values\":[{\"value\":3.12,\"unit\":\"second\"},{\"value\":4.12,\"unit\":\"second\"}]},"
            + "      \"timerL\":{\"values\":[{\"value\":5.12,\"unit\":\"day\"},{\"value\":6.12,\"unit\":\"second\"}]},"
            + "      \"timerM\":{\"values\":[{\"value\":7.12,\"unit\":\"day\"},{\"value\":8.12}]},"
            + "      \"timerN\":{\"values\":[{\"value\":9.12},{\"value\":10.12}]},"
            + "      \"timerO\":{\"values\":[{\"value\":11.12,\"unit\":\"day\"},{\"value\":12.12,\"unit\":\"byte\"}]}"
            + "    }" + "  }," + "  \"context\":{" + "    \"host\":\"<HOST>\","
            + "    \"processId\":\"<PROCESSID>\"," + "    \"threadId\":\"<THREADID>\"" + "  },"
            + "  \"id\":\"<ID>\"" + "}";

    static {
        JsonNode jsonNode = null;
        try {
            // Normally this is being executed from the project directory (e.g. root/client-java)
            jsonNode = JsonLoader.fromPath("../doc/query-log-schema-2e.json");
        } catch (final IOException e1) {
            try {
                // Under some IDE setups this may be executed from the workspace root (e.g. root)
                jsonNode = JsonLoader.fromPath("doc/query-log-schema-2e.json");
            } catch (final IOException e2) {
                Throwables.propagate(e2);
            }
        }
        STENO_SCHEMA = jsonNode;

        ANNOTATIONS.put("initTimestamp", "1997-07-16T19:20:30Z");
        ANNOTATIONS.put("finalTimestamp", "1997-07-16T19:20:31Z");
    }
}