All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.netflix.spectator.tdigest.Json Maven / Gradle / Ivy

/**
 * 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 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 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 commonTags, List 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 commonTags, List 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 decode(byte[] data) throws IOException {
    return decode(data, 0, data.length);
  }

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




© 2015 - 2025 Weber Informatics LLC | Privacy Policy