com.netflix.spectator.tdigest.Json.java Source code

Java tutorial

Introduction

Here is the source code for com.netflix.spectator.tdigest.Json.java

Source

/**
 * Copyright 2015 Netflix, Inc.
 *
 * 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.netflix.spectator.tdigest;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.dataformat.smile.SmileFactory;
import com.fasterxml.jackson.dataformat.smile.SmileGenerator;
import com.fasterxml.jackson.dataformat.smile.SmileParser;
import com.netflix.spectator.api.Id;
import com.netflix.spectator.api.Registry;
import com.netflix.spectator.api.Tag;
import com.tdunning.math.stats.AVLTreeDigest;
import com.tdunning.math.stats.TDigest;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Helper for encoding and decoding digest measurements.
 */
final class Json {

    private static final SmileFactory FACTORY = new SmileFactory();

    static {
        FACTORY.enable(SmileGenerator.Feature.WRITE_HEADER).disable(SmileGenerator.Feature.WRITE_END_MARKER)
                .enable(SmileGenerator.Feature.CHECK_SHARED_NAMES)
                .enable(SmileGenerator.Feature.CHECK_SHARED_STRING_VALUES)
                .disable(SmileParser.Feature.REQUIRE_HEADER);
    }

    private final Registry registry;

    /**
     * Create a new instance that will use the specified registry to create id objects during
     * deserialization.
     */
    Json(Registry registry) {
        this.registry = registry;
    }

    /** Create a new generator for the output stream. */
    JsonGenerator newGenerator(OutputStream out) throws IOException {
        return FACTORY.createGenerator(out);
    }

    /** Encode the measurement using the generator. */
    void encode(Map<String, String> commonTags, TDigestMeasurement m, JsonGenerator gen) throws IOException {
        TDigest digest = m.value();
        digest.compress();
        ByteBuffer buf = ByteBuffer.allocate(digest.byteSize());
        digest.asBytes(buf);

        gen.writeStartArray();
        gen.writeStartObject();
        gen.writeStringField("name", m.id().name());
        for (Map.Entry<String, String> e : commonTags.entrySet()) {
            gen.writeStringField(e.getKey(), e.getValue());
        }
        for (Tag t : m.id().tags()) {
            gen.writeStringField(t.key(), t.value());
        }
        gen.writeEndObject();
        gen.writeNumber(m.timestamp());
        gen.writeBinary(buf.array());
        gen.writeEndArray();
    }

    /** Encode the measurements using the generator. */
    void encode(Map<String, String> commonTags, List<TDigestMeasurement> ms, JsonGenerator gen) throws IOException {
        gen.writeStartArray();
        for (TDigestMeasurement m : ms) {
            encode(commonTags, m, gen);
        }
        gen.writeEndArray();
    }

    /** Return a byte-array with the encoded measurements. */
    byte[] encode(Map<String, String> commonTags, List<TDigestMeasurement> ms) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try (JsonGenerator gen = FACTORY.createGenerator(baos)) {
            encode(commonTags, ms, gen);
        }
        return baos.toByteArray();
    }

    private void require(boolean condition, String msg) {
        if (!condition) {
            throw new IllegalArgumentException(msg);
        }
    }

    private void expect(JsonParser parser, JsonToken expected) throws IOException {
        JsonToken t = parser.nextToken();
        if (t != expected) {
            String msg = String.format("expected %s, but found %s", expected, t);
            throw new IllegalArgumentException(msg);
        }
    }

    private TDigestMeasurement decode(JsonParser parser) throws IOException {
        expect(parser, JsonToken.START_OBJECT);
        require("name".equals(parser.nextFieldName()), "expected name");
        Id id = registry.createId(parser.nextTextValue());
        while (parser.nextToken() == JsonToken.FIELD_NAME) {
            id = id.withTag(parser.getText(), parser.nextTextValue());
        }
        long t = parser.nextLongValue(-1L);
        expect(parser, JsonToken.VALUE_EMBEDDED_OBJECT);
        TDigest v = AVLTreeDigest.fromBytes(ByteBuffer.wrap(parser.getBinaryValue()));
        expect(parser, JsonToken.END_ARRAY);
        return new TDigestMeasurement(id, t, v);
    }

    /** Decode a list of measurements from a byte array. */
    List<TDigestMeasurement> decode(byte[] data) throws IOException {
        return decode(data, 0, data.length);
    }

    /** Decode a list of measurements from a range of a byte array. */
    List<TDigestMeasurement> decode(byte[] data, int offset, int length) throws IOException {
        JsonParser parser = FACTORY.createParser(data, offset, length);
        List<TDigestMeasurement> ms = new ArrayList<>();
        expect(parser, JsonToken.START_ARRAY);
        while (parser.nextToken() == JsonToken.START_ARRAY) {
            ms.add(decode(parser));
        }
        return ms;
    }
}