From 94e52256749b3e9df2cdd60319efffc0aa702d26 Mon Sep 17 00:00:00 2001 From: Jake Landis Date: Fri, 11 Aug 2017 12:20:14 -0500 Subject: [PATCH] Metrics: Add Jackson serialization to the Witness classes. Part of #7788 Fixes #7984 --- .../instrument/witness/ConfigWitness.java | 73 ++++++++++++++- .../instrument/witness/ErrorWitness.java | 60 ++++++++++++- .../instrument/witness/EventsWitness.java | 67 +++++++++++++- .../instrument/witness/MetricSerializer.java | 87 ++++++++++++++++++ .../instrument/witness/PipelineWitness.java | 62 ++++++++++++- .../instrument/witness/PipelinesWitness.java | 54 +++++++++++- .../instrument/witness/PluginWitness.java | 51 ++++++++++- .../instrument/witness/PluginsWitness.java | 88 ++++++++++++++++++- .../instrument/witness/QueueWitness.java | 50 ++++++++++- .../instrument/witness/ReloadWitness.java | 64 +++++++++++++- .../witness/SerializableWitness.java | 45 ++++++++++ .../logstash/instrument/witness/Witness.java | 51 ++++++++++- .../instrument/witness/ConfigWitnessTest.java | 55 ++++++++++++ .../instrument/witness/ErrorWitnessTest.java | 33 ++++++- .../instrument/witness/EventsWitnessTest.java | 67 ++++++++++++++ .../witness/PipelineWitnessTest.java | 53 +++++++++++ .../witness/PipelinesWitnessTest.java | 22 +++++ .../instrument/witness/PluginWitnessTest.java | 27 ++++++ .../witness/PluginsWitnessTest.java | 57 ++++++++++++ .../instrument/witness/QueueWitnessTest.java | 20 +++++ .../instrument/witness/ReloadWitnessTest.java | 49 ++++++++++- .../instrument/witness/WitnessTest.java | 60 ++++++++++++- 22 files changed, 1170 insertions(+), 25 deletions(-) create mode 100644 logstash-core/src/main/java/org/logstash/instrument/witness/MetricSerializer.java create mode 100644 logstash-core/src/main/java/org/logstash/instrument/witness/SerializableWitness.java diff --git a/logstash-core/src/main/java/org/logstash/instrument/witness/ConfigWitness.java b/logstash-core/src/main/java/org/logstash/instrument/witness/ConfigWitness.java index 131253c11..e6263930f 100644 --- a/logstash-core/src/main/java/org/logstash/instrument/witness/ConfigWitness.java +++ b/logstash-core/src/main/java/org/logstash/instrument/witness/ConfigWitness.java @@ -1,12 +1,20 @@ package org.logstash.instrument.witness; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import org.logstash.instrument.metrics.Metric; import org.logstash.instrument.metrics.gauge.BooleanGauge; import org.logstash.instrument.metrics.gauge.LongGauge; +import java.io.IOException; + /** * The witness for configuration. */ -final public class ConfigWitness { +@JsonSerialize(using = ConfigWitness.Serializer.class) +final public class ConfigWitness implements SerializableWitness { private final BooleanGauge deadLetterQueueEnabled; private final BooleanGauge configReloadAutomatic; @@ -15,6 +23,9 @@ final public class ConfigWitness { private final LongGauge batchDelay; private final LongGauge configReloadInterval; private final Snitch snitch; + private final static String KEY = "config"; + private static final Serializer SERIALIZER = new Serializer(); + /** * Constructor. @@ -92,6 +103,55 @@ final public class ConfigWitness { return this.snitch; } + @Override + public void genJson(JsonGenerator gen, SerializerProvider provider) throws IOException { + SERIALIZER.innerSerialize(this, gen, provider); + } + + /** + * The Jackson serializer. + */ + static class Serializer extends StdSerializer { + + /** + * Default constructor - required for Jackson + */ + public Serializer() { + this(ConfigWitness.class); + } + + /** + * Constructor + * + * @param t the type to serialize + */ + protected Serializer(Class t) { + super(t); + } + + @Override + public void serialize(ConfigWitness witness, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeStartObject(); + innerSerialize(witness, gen, provider); + gen.writeEndObject(); + } + + void innerSerialize(ConfigWitness witness, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeObjectFieldStart(KEY); + + MetricSerializer> longSerializer = MetricSerializer.Get.longSerializer(gen); + MetricSerializer> booleanSerializer = MetricSerializer.Get.booleanSerializer(gen); + + longSerializer.serialize(witness.batchSize); + longSerializer.serialize(witness.workers); + longSerializer.serialize(witness.batchDelay); + longSerializer.serialize(witness.configReloadInterval); + booleanSerializer.serialize(witness.configReloadAutomatic); + booleanSerializer.serialize(witness.deadLetterQueueEnabled); + gen.writeEndObject(); + } + } + /** * The snitch for the errors. Used to retrieve discrete metric values. */ @@ -105,6 +165,7 @@ final public class ConfigWitness { /** * Gets the configured batch delay + * * @return the batch delay */ public long batchDelay() { @@ -114,6 +175,7 @@ final public class ConfigWitness { /** * Gets the configured batch size + * * @return the batch size */ public long batchSize() { @@ -122,6 +184,7 @@ final public class ConfigWitness { /** * Gets if the reload automatic is configured + * * @return true if configured for automatic, false otherwise */ public boolean configReloadAutomatic() { @@ -130,6 +193,7 @@ final public class ConfigWitness { /** * Gets the configured reload interval + * * @return the configured reload interval */ public long configReloadInterval() { @@ -138,7 +202,8 @@ final public class ConfigWitness { /** * Gets if the dead letter queue is configured to be enabled - * @return true if the dead letter queue is configured to be enabled, false otherwise + * + * @return true if the dead letter queue is configured to be enabled, false otherwise */ public boolean deadLetterQueueEnabled() { return witness.deadLetterQueueEnabled.getValue(); @@ -146,11 +211,15 @@ final public class ConfigWitness { /** * Gets the number of configured workers + * * @return the configured number of workers. */ public long workers() { return witness.workers.getValue(); } + } + + } diff --git a/logstash-core/src/main/java/org/logstash/instrument/witness/ErrorWitness.java b/logstash-core/src/main/java/org/logstash/instrument/witness/ErrorWitness.java index c36c9fb42..0ff4fc669 100644 --- a/logstash-core/src/main/java/org/logstash/instrument/witness/ErrorWitness.java +++ b/logstash-core/src/main/java/org/logstash/instrument/witness/ErrorWitness.java @@ -1,5 +1,10 @@ package org.logstash.instrument.witness; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import org.logstash.instrument.metrics.Metric; import org.logstash.instrument.metrics.gauge.TextGauge; import java.io.ByteArrayOutputStream; @@ -10,11 +15,16 @@ import java.nio.charset.StandardCharsets; /** * Witness for errors. */ -public class ErrorWitness { +@JsonSerialize(using = ErrorWitness.Serializer.class) +public class ErrorWitness implements SerializableWitness { private final TextGauge message; private final TextGauge backtrace; private final Snitch snitch; + private final static String KEY = "last_error"; + private static final Serializer SERIALIZER = new Serializer(); + + private boolean dirty; //here for passivity with legacy Ruby implementation public ErrorWitness() { message = new TextGauge("message"); @@ -29,6 +39,7 @@ public class ErrorWitness { */ public void backtrace(String stackTrace) { this.backtrace.set(stackTrace); + dirty = true; } /** @@ -38,6 +49,7 @@ public class ErrorWitness { */ public void message(String message) { this.message.set(message); + dirty = true; } /** @@ -69,6 +81,52 @@ public class ErrorWitness { } } + @Override + public void genJson(JsonGenerator gen, SerializerProvider provider) throws IOException { + SERIALIZER.innerSerialize(this, gen, provider); + } + + /** + * The Jackson serializer. + */ + public static class Serializer extends StdSerializer { + + /** + * Default constructor - required for Jackson + */ + public Serializer() { + this(ErrorWitness.class); + } + + /** + * Constructor + * + * @param t the type to serialize + */ + protected Serializer(Class t) { + super(t); + } + + @Override + public void serialize(ErrorWitness witness, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeStartObject(); + innerSerialize(witness, gen, provider); + gen.writeEndObject(); + } + + void innerSerialize(ErrorWitness witness, JsonGenerator gen, SerializerProvider provider) throws IOException { + if (witness.dirty) { + gen.writeObjectFieldStart(KEY); + MetricSerializer> stringSerializer = MetricSerializer.Get.stringSerializer(gen); + stringSerializer.serialize(witness.message); + stringSerializer.serialize(witness.backtrace); + gen.writeEndObject(); + } else { + gen.writeStringField(KEY, null); + } + } + } + /** * The snitch for the errors. Used to retrieve discrete metric values. */ diff --git a/logstash-core/src/main/java/org/logstash/instrument/witness/EventsWitness.java b/logstash-core/src/main/java/org/logstash/instrument/witness/EventsWitness.java index d20c38823..b4c15890d 100644 --- a/logstash-core/src/main/java/org/logstash/instrument/witness/EventsWitness.java +++ b/logstash-core/src/main/java/org/logstash/instrument/witness/EventsWitness.java @@ -1,17 +1,27 @@ package org.logstash.instrument.witness; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import org.logstash.instrument.metrics.Metric; import org.logstash.instrument.metrics.counter.LongCounter; +import java.io.IOException; + /** * Witness for events. */ -final public class EventsWitness{ +@JsonSerialize(using = EventsWitness.Serializer.class) +final public class EventsWitness implements SerializableWitness { private LongCounter filtered; private LongCounter out; private LongCounter in; private LongCounter duration; private LongCounter queuePushDuration; + private final static String KEY = "events"; + private static final Serializer SERIALIZER = new Serializer(); private final Snitch snitch; private boolean dirty; //here for passivity with legacy Ruby implementation @@ -124,6 +134,60 @@ final public class EventsWitness{ dirty = true; } + @Override + public String asJson() throws IOException { + return dirty ? SerializableWitness.super.asJson() : ""; + } + + @Override + public void genJson(final JsonGenerator gen, SerializerProvider provider) throws IOException { + SERIALIZER.innerSerialize(this, gen, provider); + } + + /** + * The Jackson serializer. + */ + public static class Serializer extends StdSerializer { + + /** + * Default constructor - required for Jackson + */ + public Serializer() { + this(EventsWitness.class); + } + + /** + * Constructor + * + * @param t the type to serialize + */ + protected Serializer(Class t) { + super(t); + } + + @Override + public void serialize(EventsWitness witness, JsonGenerator gen, SerializerProvider provider) throws IOException { + if (witness.dirty) { + gen.writeStartObject(); + innerSerialize(witness, gen, provider); + gen.writeEndObject(); + } + } + + void innerSerialize(EventsWitness witness, JsonGenerator gen, SerializerProvider provider) throws IOException { + if (witness.dirty) { + gen.writeObjectFieldStart(KEY); + MetricSerializer> longSerializer = MetricSerializer.Get.longSerializer(gen); + longSerializer.serialize(witness.duration); + longSerializer.serialize(witness.in); + longSerializer.serialize(witness.out); + longSerializer.serialize(witness.filtered); + longSerializer.serialize(witness.queuePushDuration); + gen.writeEndObject(); + } + } + } + /** * The snitch for the {@link EventsWitness}. Allows to read discrete metrics values. */ @@ -174,6 +238,7 @@ final public class EventsWitness{ /** * Gets the duration of the queue push + * * @return the queue push duration. */ public long queuePushDuration() { diff --git a/logstash-core/src/main/java/org/logstash/instrument/witness/MetricSerializer.java b/logstash-core/src/main/java/org/logstash/instrument/witness/MetricSerializer.java new file mode 100644 index 000000000..99c2c5375 --- /dev/null +++ b/logstash-core/src/main/java/org/logstash/instrument/witness/MetricSerializer.java @@ -0,0 +1,87 @@ +package org.logstash.instrument.witness; + +import com.fasterxml.jackson.core.JsonGenerator; +import org.logstash.instrument.metrics.Metric; +import org.logstash.instrument.metrics.gauge.GaugeMetric; +import org.logstash.instrument.metrics.gauge.RubyTimeStampGauge; + +import java.io.IOException; + +/** + * Similar to the {@link java.util.function.Consumer} functional interface this is expected to operate via side effects. Differs from {@link java.util.function.Consumer} in that + * this is stricter typed, and allows for a checked {@link IOException}. + * + * @param The type of {@link GaugeMetric} to serialize + */ +@FunctionalInterface +public interface MetricSerializer> { + + /** + * Performs this operation on the given argument. + * + * @param t the input argument + */ + void serialize(T t) throws IOException; + + /** + * Helper class to create a functional fluent api. + * Usage example: {@code MetricSerializer.Get.longSerializer(gen).serialize(99);} + */ + class Get { + /** + * Proper way to serialize a {@link Long} type metric to JSON + * + * @param gen The {@link JsonGenerator} used to generate JSON + * @return the {@link MetricSerializer} which is the function used to serialize the metric + */ + static MetricSerializer> longSerializer(JsonGenerator gen) { + return m -> { + if (m != null && m.isDirty() && m.getValue() != null) { + gen.writeNumberField(m.getName(), m.getValue()); + } + }; + } + + /** + * Proper way to serialize a {@link Boolean} type metric to JSON + * + * @param gen The {@link JsonGenerator} used to generate JSON + * @return the {@link MetricSerializer} which is the function used to serialize the metric + */ + static MetricSerializer> booleanSerializer(JsonGenerator gen) { + return m -> { + if (m != null && m.isDirty() && m.getValue() != null) { + gen.writeBooleanField(m.getName(), m.getValue()); + } + }; + } + + /** + * Proper way to serialize a {@link String} type metric to JSON + * + * @param gen The {@link JsonGenerator} used to generate JSON + * @return the {@link MetricSerializer} which is the function used to serialize the metric + */ + static MetricSerializer> stringSerializer(JsonGenerator gen) { + return m -> { + if (m != null && m.isDirty() && m.getValue() != null) { + gen.writeStringField(m.getName(), m.getValue()); + } + }; + } + + /** + * Proper way to serialize a {@link RubyTimeStampGauge} type metric to JSON that should emit a {@code null} JSON value if missing + * + * @param gen The {@link JsonGenerator} used to generate JSON + * @return the {@link MetricSerializer} which is the function used to serialize the metric + */ + static MetricSerializer timestampSerializer(JsonGenerator gen) { + return m -> { + if (m != null) { + gen.writeStringField(m.getName(), m.getValue() != null ? m.getValue().toString() : null); + } + }; + } + } +} diff --git a/logstash-core/src/main/java/org/logstash/instrument/witness/PipelineWitness.java b/logstash-core/src/main/java/org/logstash/instrument/witness/PipelineWitness.java index 5d7493f73..946250baa 100644 --- a/logstash-core/src/main/java/org/logstash/instrument/witness/PipelineWitness.java +++ b/logstash-core/src/main/java/org/logstash/instrument/witness/PipelineWitness.java @@ -1,22 +1,33 @@ package org.logstash.instrument.witness; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; + +import java.io.IOException; + /** * A single pipeline witness. */ -final public class PipelineWitness { +@JsonSerialize(using = PipelineWitness.Serializer.class) +final public class PipelineWitness implements SerializableWitness { private final ReloadWitness reloadWitness; private final EventsWitness eventsWitness; private final ConfigWitness configWitness; private final PluginsWitness pluginsWitness; private final QueueWitness queueWitness; + private final String KEY; + private static final Serializer SERIALIZER = new Serializer(); /** * Constructor. * * @param pipelineName The uniquely identifying name of the pipeline. */ - public PipelineWitness(String pipelineName) { //NOTE - pipeline name is used as part of the serialization + public PipelineWitness(String pipelineName) { + this.KEY = pipelineName; this.reloadWitness = new ReloadWitness(); this.eventsWitness = new EventsWitness(); this.configWitness = new ConfigWitness(); @@ -44,6 +55,7 @@ final public class PipelineWitness { /** * Gets the {@link PluginWitness} for the given id, creates the associated {@link PluginWitness} if needed + * * @param id the id of the filter * @return the associated {@link PluginWitness} (for method chaining) */ @@ -67,6 +79,7 @@ final public class PipelineWitness { /** * Gets the {@link PluginWitness} for the given id, creates the associated {@link PluginWitness} if needed + * * @param id the id of the input * @return the associated {@link PluginWitness} (for method chaining) */ @@ -76,6 +89,7 @@ final public class PipelineWitness { /** * Gets the {@link PluginWitness} for the given id, creates the associated {@link PluginWitness} if needed + * * @param id the id of the output * @return the associated {@link PluginWitness} (for method chaining) */ @@ -109,5 +123,47 @@ final public class PipelineWitness { public QueueWitness queue() { return queueWitness; } -} + @Override + public void genJson(JsonGenerator gen, SerializerProvider provider) throws IOException { + SERIALIZER.innerSerialize(this, gen, provider); + } + + /** + * The Jackson serializer. + */ + public static class Serializer extends StdSerializer { + + /** + * Default constructor - required for Jackson + */ + public Serializer() { + this(PipelineWitness.class); + } + + /** + * Constructor + * + * @param t the type to serialize + */ + protected Serializer(Class t) { + super(t); + } + + @Override + public void serialize(PipelineWitness witness, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeStartObject(); + innerSerialize(witness, gen, provider); + gen.writeEndObject(); + } + + void innerSerialize(PipelineWitness witness, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeObjectFieldStart(witness.KEY); + witness.events().genJson(gen, provider); + witness.plugins().genJson(gen, provider); + witness.reloads().genJson(gen, provider); + witness.queue().genJson(gen, provider); + gen.writeEndObject(); + } + } +} diff --git a/logstash-core/src/main/java/org/logstash/instrument/witness/PipelinesWitness.java b/logstash-core/src/main/java/org/logstash/instrument/witness/PipelinesWitness.java index ddf01b065..bd35b782b 100644 --- a/logstash-core/src/main/java/org/logstash/instrument/witness/PipelinesWitness.java +++ b/logstash-core/src/main/java/org/logstash/instrument/witness/PipelinesWitness.java @@ -1,15 +1,25 @@ package org.logstash.instrument.witness; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; + +import java.io.IOException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * Witness for the set of pipelines. */ -final public class PipelinesWitness { +@JsonSerialize(using = PipelinesWitness.Serializer.class) +final public class PipelinesWitness implements SerializableWitness { private final Map pipelines; + private final static String KEY = "pipelines"; + private static final Serializer SERIALIZER = new Serializer(); + /** * Constructor. */ @@ -27,4 +37,46 @@ final public class PipelinesWitness { return pipelines.computeIfAbsent(name, k -> new PipelineWitness(k)); } + @Override + public void genJson(JsonGenerator gen, SerializerProvider provider) throws IOException { + SERIALIZER.innerSerialize(this, gen, provider); + } + + /** + * The Jackson serializer. + */ + public static class Serializer extends StdSerializer { + + /** + * Default constructor - required for Jackson + */ + public Serializer() { + this(PipelinesWitness.class); + } + + /** + * Constructor + * + * @param t the type to serialize + */ + protected Serializer(Class t) { + super(t); + } + + @Override + public void serialize(PipelinesWitness witness, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeStartObject(); + innerSerialize(witness, gen, provider); + gen.writeEndObject(); + } + + void innerSerialize(PipelinesWitness witness, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeObjectFieldStart(KEY); + for (Map.Entry entry : witness.pipelines.entrySet()) { + entry.getValue().genJson(gen, provider); + } + gen.writeEndObject(); + } + } + } diff --git a/logstash-core/src/main/java/org/logstash/instrument/witness/PluginWitness.java b/logstash-core/src/main/java/org/logstash/instrument/witness/PluginWitness.java index bbe743a86..0a494fdca 100644 --- a/logstash-core/src/main/java/org/logstash/instrument/witness/PluginWitness.java +++ b/logstash-core/src/main/java/org/logstash/instrument/witness/PluginWitness.java @@ -1,16 +1,25 @@ package org.logstash.instrument.witness; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import org.logstash.instrument.metrics.Metric; import org.logstash.instrument.metrics.gauge.TextGauge; +import java.io.IOException; + /** * Witness for a single plugin. */ -public class PluginWitness { +@JsonSerialize(using = PluginWitness.Serializer.class) +public class PluginWitness implements SerializableWitness { private final EventsWitness eventsWitness; private final TextGauge id; private final TextGauge name; private final Snitch snitch; + private static final Serializer SERIALIZER = new Serializer(); /** * Constructor. @@ -53,6 +62,46 @@ public class PluginWitness { return snitch; } + @Override + public void genJson(JsonGenerator gen, SerializerProvider provider) throws IOException { + SERIALIZER.innerSerialize(this, gen, provider); + } + + /** + * The Jackson JSON serializer. + */ + public static class Serializer extends StdSerializer { + + /** + * Default constructor - required for Jackson + */ + public Serializer() { + this(PluginWitness.class); + } + + /** + * Constructor + * + * @param t the type to serialize + */ + protected Serializer(Class t) { + super(t); + } + + @Override + public void serialize(PluginWitness witness, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeStartObject(); + innerSerialize(witness, gen, provider); + gen.writeEndObject(); + } + + void innerSerialize(PluginWitness witness, JsonGenerator gen, SerializerProvider provider) throws IOException { + MetricSerializer> stringSerializer = MetricSerializer.Get.stringSerializer(gen); + stringSerializer.serialize(witness.id); + witness.events().genJson(gen, provider); + stringSerializer.serialize(witness.name); + } + } /** * Snitch for a plugin. Provides discrete metric values. diff --git a/logstash-core/src/main/java/org/logstash/instrument/witness/PluginsWitness.java b/logstash-core/src/main/java/org/logstash/instrument/witness/PluginsWitness.java index 831513286..468a48e65 100644 --- a/logstash-core/src/main/java/org/logstash/instrument/witness/PluginsWitness.java +++ b/logstash-core/src/main/java/org/logstash/instrument/witness/PluginsWitness.java @@ -1,16 +1,27 @@ package org.logstash.instrument.witness; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; + +import java.io.IOException; +import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * A Witness for the set of plugins. */ -public class PluginsWitness{ +@JsonSerialize(using = PluginsWitness.Serializer.class) +public class PluginsWitness implements SerializableWitness { private final Map inputs; private final Map outputs; private final Map filters; + private final Map codecs; + private final static String KEY = "plugins"; + private static final Serializer SERIALIZER = new Serializer(); /** * Constructor. @@ -20,10 +31,12 @@ public class PluginsWitness{ this.inputs = new ConcurrentHashMap<>(); this.outputs = new ConcurrentHashMap<>(); this.filters = new ConcurrentHashMap<>(); - } + this.codecs = new ConcurrentHashMap<>(); + } /** * Gets the {@link PluginWitness} for the given id, creates the associated {@link PluginWitness} if needed + * * @param id the id of the input * @return the associated {@link PluginWitness} (for method chaining) */ @@ -33,6 +46,7 @@ public class PluginsWitness{ /** * Gets the {@link PluginWitness} for the given id, creates the associated {@link PluginWitness} if needed + * * @param id the id of the output * @return the associated {@link PluginWitness} (for method chaining) */ @@ -42,6 +56,7 @@ public class PluginsWitness{ /** * Gets the {@link PluginWitness} for the given id, creates the associated {@link PluginWitness} if needed + * * @param id the id of the filter * @return the associated {@link PluginWitness} (for method chaining) */ @@ -49,6 +64,16 @@ public class PluginsWitness{ return getPlugin(filters, id); } + /** + * Gets the {@link PluginWitness} for the given id, creates the associated {@link PluginWitness} if needed + * + * @param id the id of the codec + * @return the associated {@link PluginWitness} (for method chaining) + */ + public PluginWitness codecs(String id) { + return getPlugin(codecs, id); + } + /** * Forgets all information related to the the plugins. */ @@ -56,6 +81,7 @@ public class PluginsWitness{ inputs.clear(); outputs.clear(); filters.clear(); + codecs.clear(); } /** @@ -66,8 +92,62 @@ public class PluginsWitness{ * @return existing or new {@link PluginWitness} */ private PluginWitness getPlugin(Map plugin, String id) { - return plugin.computeIfAbsent(id, k -> new PluginWitness(k) ); + return plugin.computeIfAbsent(id, k -> new PluginWitness(k)); } -} + @Override + public void genJson(JsonGenerator gen, SerializerProvider provider) throws IOException { + SERIALIZER.innerSerialize(this, gen, provider); + } + /** + * The Jackson serializer. + */ + public static class Serializer extends StdSerializer { + + /** + * Default constructor - required for Jackson + */ + public Serializer() { + this(PluginsWitness.class); + } + + /** + * Constructor + * + * @param t the type to serialize + */ + protected Serializer(Class t) { + super(t); + } + + @Override + public void serialize(PluginsWitness witness, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeStartObject(); + innerSerialize(witness, gen, provider); + gen.writeEndObject(); + } + + void innerSerialize(PluginsWitness witness, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeObjectFieldStart(KEY); + + serializePlugins("inputs", witness.inputs, gen, provider); + serializePlugins("filters", witness.filters, gen, provider); + serializePlugins("outputs", witness.outputs, gen, provider); + //codec is not serialized + + gen.writeEndObject(); + } + + private void serializePlugins(String key, Map plugin, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeArrayFieldStart(key); + for (Map.Entry entry : plugin.entrySet()) { + gen.writeStartObject(); + entry.getValue().genJson(gen, provider); + gen.writeEndObject(); + } + gen.writeEndArray(); + + } + } +} diff --git a/logstash-core/src/main/java/org/logstash/instrument/witness/QueueWitness.java b/logstash-core/src/main/java/org/logstash/instrument/witness/QueueWitness.java index 4ec8023fc..d868eac4c 100644 --- a/logstash-core/src/main/java/org/logstash/instrument/witness/QueueWitness.java +++ b/logstash-core/src/main/java/org/logstash/instrument/witness/QueueWitness.java @@ -1,14 +1,23 @@ package org.logstash.instrument.witness; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; import org.logstash.instrument.metrics.gauge.TextGauge; +import java.io.IOException; + /** * Witness for the queue. */ -final public class QueueWitness { +@JsonSerialize(using = QueueWitness.Serializer.class) +final public class QueueWitness implements SerializableWitness { private final TextGauge type; private final Snitch snitch; + private final static String KEY = "queue"; + private static final Serializer SERIALIZER = new Serializer(); /** * Constructor. @@ -36,6 +45,45 @@ final public class QueueWitness { this.type.set(type); } + @Override + public void genJson(JsonGenerator gen, SerializerProvider provider) throws IOException { + SERIALIZER.innerSerialize(this, gen, provider); + } + + /** + * The Jackson serializer. + */ + public static class Serializer extends StdSerializer { + /** + * Default constructor - required for Jackson + */ + public Serializer() { + this(QueueWitness.class); + } + + /** + * Constructor + * + * @param t the type to serialize + */ + protected Serializer(Class t) { + super(t); + } + + @Override + public void serialize(QueueWitness witness, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeStartObject(); + innerSerialize(witness, gen, provider); + gen.writeEndObject(); + } + + void innerSerialize(QueueWitness witness, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeObjectFieldStart(KEY); + MetricSerializer.Get.stringSerializer(gen).serialize(witness.type); + gen.writeEndObject(); + } + } + /** * Snitch for queue. Provides discrete metric values. */ diff --git a/logstash-core/src/main/java/org/logstash/instrument/witness/ReloadWitness.java b/logstash-core/src/main/java/org/logstash/instrument/witness/ReloadWitness.java index 921496ce9..df0823399 100644 --- a/logstash-core/src/main/java/org/logstash/instrument/witness/ReloadWitness.java +++ b/logstash-core/src/main/java/org/logstash/instrument/witness/ReloadWitness.java @@ -1,14 +1,22 @@ package org.logstash.instrument.witness; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; import org.logstash.Timestamp; import org.logstash.ext.JrubyTimestampExtLibrary; +import org.logstash.instrument.metrics.Metric; import org.logstash.instrument.metrics.counter.LongCounter; import org.logstash.instrument.metrics.gauge.RubyTimeStampGauge; +import java.io.IOException; + /** * A witness to record reloads. */ -final public class ReloadWitness { +@JsonSerialize(using = ReloadWitness.Serializer.class) +final public class ReloadWitness implements SerializableWitness { private final LongCounter success; private final LongCounter failure; @@ -16,6 +24,9 @@ final public class ReloadWitness { private final RubyTimeStampGauge lastSuccessTimestamp; private final RubyTimeStampGauge lastFailureTimestamp; private final Snitch snitch; + private static final Serializer SERIALIZER = new Serializer(); + + private final static String KEY = "reloads"; /** * Constructor. @@ -26,6 +37,11 @@ final public class ReloadWitness { lastError = new ErrorWitness(); lastSuccessTimestamp = new RubyTimeStampGauge("last_success_timestamp"); lastFailureTimestamp = new RubyTimeStampGauge("last_failure_timestamp"); + //Legacy Ruby API initializes all of these to zero, resulting in the dirty flag + success.setDirty(true); + failure.setDirty(true); + lastFailureTimestamp.setDirty(true); + lastFailureTimestamp.setDirty(true); snitch = new Snitch(this); } @@ -99,6 +115,52 @@ final public class ReloadWitness { lastFailureTimestamp.set(timestamp); } + @Override + public void genJson(JsonGenerator gen, SerializerProvider provider) throws IOException { + SERIALIZER.innerSerialize(this, gen, provider); + } + + /** + * The Jackson serializer. + */ + public static class Serializer extends StdSerializer { + + /** + * Default constructor - required for Jackson + */ + public Serializer() { + this(ReloadWitness.class); + } + + /** + * Constructor + * + * @param t the type to serialize + */ + protected Serializer(Class t) { + super(t); + } + + @Override + public void serialize(ReloadWitness witness, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeStartObject(); + innerSerialize(witness, gen, provider); + gen.writeEndObject(); + } + + void innerSerialize(ReloadWitness witness, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeObjectFieldStart(ReloadWitness.KEY); + witness.lastError.genJson(gen, provider); + MetricSerializer> longSerializer = MetricSerializer.Get.longSerializer(gen); + MetricSerializer timestampSerializer = MetricSerializer.Get.timestampSerializer(gen); + longSerializer.serialize(witness.success); + timestampSerializer.serialize(witness.lastSuccessTimestamp); + timestampSerializer.serialize(witness.lastFailureTimestamp); + longSerializer.serialize(witness.failure); + gen.writeEndObject(); + } + } + /** * The Reload snitch. Provides a means to get discrete metric values. */ diff --git a/logstash-core/src/main/java/org/logstash/instrument/witness/SerializableWitness.java b/logstash-core/src/main/java/org/logstash/instrument/witness/SerializableWitness.java new file mode 100644 index 000000000..3598c9398 --- /dev/null +++ b/logstash-core/src/main/java/org/logstash/instrument/witness/SerializableWitness.java @@ -0,0 +1,45 @@ +package org.logstash.instrument.witness; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; +import java.io.StringWriter; + +/** + * A Witness that can be serialized as JSON. A Witness is an abstraction to the {@link org.logstash.instrument.metrics.Metric}'s that watches/witnesses what is happening inside + * of Logstash. + */ +public interface SerializableWitness { + + /** + * Generates the corresponding JSON for the witness. + * + * @param gen The {@link JsonGenerator} used to generate the JSON + * @param provider The {@link SerializerProvider} that may be used to assist with the JSON generation. + * @throws IOException if any errors occur in JSON generation + */ + void genJson(final JsonGenerator gen, SerializerProvider provider) throws IOException; + + /** + * Helper method to return the Witness as a JSON string. + * + * @return A {@link String} whose content is the JSON representation for the witness. + * @throws IOException if any errors occur in JSON generation + */ + default String asJson() throws IOException { + JsonFactory jsonFactory = new JsonFactory(); + try (StringWriter sw = new StringWriter(); + JsonGenerator gen = jsonFactory.createGenerator(sw)) { + ObjectMapper mapper = new ObjectMapper(jsonFactory); + gen.writeStartObject(); + genJson(gen, mapper.getSerializerProvider()); + gen.writeEndObject(); + gen.flush(); + sw.flush(); + return sw.toString(); + } + } +} diff --git a/logstash-core/src/main/java/org/logstash/instrument/witness/Witness.java b/logstash-core/src/main/java/org/logstash/instrument/witness/Witness.java index 4389903f5..d0628d34c 100644 --- a/logstash-core/src/main/java/org/logstash/instrument/witness/Witness.java +++ b/logstash-core/src/main/java/org/logstash/instrument/witness/Witness.java @@ -1,9 +1,15 @@ package org.logstash.instrument.witness; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; + +import java.io.IOException; import java.util.Arrays; /** - *

Primary entry point for the Witness subsystem. The Witness subsystem is an abstraction for the {@link org.logstash.instrument.metrics.Metric}'s that watches/witnesses what + *

Primary entry point for the Witness subsystem. The Witness subsystem is an abstraction for the {@link org.logstash.instrument.metrics.Metric}'s that watches/witnesses what * is happening inside Logstash.

*

Usage example to increment the events counter for the foo input in the main pipeline: * {@code Witness.instance().pipeline("main").inputs("foo").events().in(1);} @@ -12,13 +18,15 @@ import java.util.Arrays; *

A Witness may also be a snitch. Which means that those witnesses may expose a {@code snitch()} method to retrieve the underlying metric values without JSON serialization.

*

All Witnesses are capable of serializing their underlying metrics as JSON.

*/ -final public class Witness { +@JsonSerialize(using = Witness.Serializer.class) +final public class Witness implements SerializableWitness { private final ReloadWitness reloadWitness; private final EventsWitness eventsWitness; private final PipelinesWitness pipelinesWitness; private static Witness _instance; + private static final Serializer SERIALIZER = new Serializer(); /** * Constructor. Consumers should use {@link #instance()} method to obtain an instance of this class. @@ -86,4 +94,43 @@ final public class Witness { return pipelinesWitness.pipeline(name); } + @Override + public void genJson(JsonGenerator gen, SerializerProvider provider) throws IOException { + SERIALIZER.innerSerialize(this, gen, provider); + } + + /** + * The Jackson serializer. + */ + static class Serializer extends StdSerializer { + + /** + * Default constructor - required for Jackson + */ + public Serializer() { + this(Witness.class); + } + + /** + * Constructor + * + * @param t the type to serialize + */ + protected Serializer(Class t) { + super(t); + } + + @Override + public void serialize(Witness witness, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeStartObject(); + innerSerialize(witness, gen, provider); + gen.writeEndObject(); + } + + void innerSerialize(Witness witness, JsonGenerator gen, SerializerProvider provider) throws IOException { + witness.events().genJson(gen, provider); + witness.reloads().genJson(gen, provider); + witness.pipelinesWitness.genJson(gen, provider); + } + } } diff --git a/logstash-core/src/test/java/org/logstash/instrument/witness/ConfigWitnessTest.java b/logstash-core/src/test/java/org/logstash/instrument/witness/ConfigWitnessTest.java index 718b88e2d..76af3c9cf 100644 --- a/logstash-core/src/test/java/org/logstash/instrument/witness/ConfigWitnessTest.java +++ b/logstash-core/src/test/java/org/logstash/instrument/witness/ConfigWitnessTest.java @@ -1,5 +1,6 @@ package org.logstash.instrument.witness; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Before; import org.junit.Test; @@ -58,4 +59,58 @@ public class ConfigWitnessTest { assertThat(witness.snitch().workers()).isEqualTo(96); } + @Test + public void testAsJson() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + assertThat(mapper.writeValueAsString(witness)).isEqualTo(witness.asJson()).contains("config"); + } + + @Test + public void testSerializeEmpty() throws Exception { + String json = witness.asJson(); + assertThat(json).isEqualTo("{\"config\":{}}"); + } + + @Test + public void testSerializeBatchSize() throws Exception { + witness.batchSize(999); + String json = witness.asJson(); + assertThat(json).contains("999"); + } + + @Test + public void testSerializeWorkersSize() throws Exception { + witness.workers(888); + String json = witness.asJson(); + assertThat(json).isEqualTo("{\"config\":{\"workers\":888}}"); + } + + @Test + public void testSerializeBatchDelay() throws Exception { + witness.batchDelay(777); + String json = witness.asJson(); + assertThat(json).isEqualTo("{\"config\":{\"batch_delay\":777}}"); + } + + @Test + public void testSerializeAutoConfigReload() throws Exception { + witness.configReloadAutomatic(true); + String json = witness.asJson(); + assertThat(json).isEqualTo("{\"config\":{\"config_reload_automatic\":true}}"); + } + + @Test + public void testSerializeReloadInterval() throws Exception { + witness.configReloadInterval(666); + String json = witness.asJson(); + assertThat(json).isEqualTo("{\"config\":{\"config_reload_interval\":666}}"); + } + + @Test + public void testSerializeEnableDeadLetterQueue() throws Exception { + witness.deadLetterQueueEnabled(true); + String json = witness.asJson(); + assertThat(json).isEqualTo("{\"config\":{\"dead_letter_queue_enabled\":true}}"); + } + } \ No newline at end of file diff --git a/logstash-core/src/test/java/org/logstash/instrument/witness/ErrorWitnessTest.java b/logstash-core/src/test/java/org/logstash/instrument/witness/ErrorWitnessTest.java index bc8c7452f..05302e4e1 100644 --- a/logstash-core/src/test/java/org/logstash/instrument/witness/ErrorWitnessTest.java +++ b/logstash-core/src/test/java/org/logstash/instrument/witness/ErrorWitnessTest.java @@ -1,5 +1,6 @@ package org.logstash.instrument.witness; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Before; import org.junit.Test; @@ -26,7 +27,7 @@ public class ErrorWitnessTest { //as Exception RuntimeException exception = new RuntimeException("foobar"); witness.backtrace(exception); - for(StackTraceElement element : exception.getStackTrace()){ + for (StackTraceElement element : exception.getStackTrace()) { assertThat(witness.snitch().backtrace()).contains(element.toString()); } } @@ -36,4 +37,34 @@ public class ErrorWitnessTest { witness.message("baz"); assertThat(witness.snitch().message()).isEqualTo("baz"); } + + @Test + public void testAsJson() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + assertThat(mapper.writeValueAsString(witness)).isEqualTo(witness.asJson()).contains("last_error"); + } + + @Test + public void testSerializeEmpty() throws Exception { + String json = witness.asJson(); + assertThat(json).isEqualTo("{\"last_error\":null}"); + } + + @Test + public void testSerializeMessage() throws Exception { + witness.message("whoops"); + String json = witness.asJson(); + assertThat(json).isEqualTo("{\"last_error\":{\"message\":\"whoops\"}}"); + } + + @Test + public void testSerializeBackTrace() throws Exception { + witness.backtrace("ruby, backtrace"); + String json = witness.asJson(); + assertThat(json).contains("ruby").contains("backtrace"); + + witness.backtrace(new RuntimeException("Uh oh!")); + json = witness.asJson(); + assertThat(json).contains("Uh oh!").contains("ErrorWitnessTest"); + } } \ No newline at end of file diff --git a/logstash-core/src/test/java/org/logstash/instrument/witness/EventsWitnessTest.java b/logstash-core/src/test/java/org/logstash/instrument/witness/EventsWitnessTest.java index 9aba0952a..b96a614b9 100644 --- a/logstash-core/src/test/java/org/logstash/instrument/witness/EventsWitnessTest.java +++ b/logstash-core/src/test/java/org/logstash/instrument/witness/EventsWitnessTest.java @@ -1,5 +1,6 @@ package org.logstash.instrument.witness; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Before; import org.junit.Test; @@ -78,4 +79,70 @@ public class EventsWitnessTest { assertThat(witness.snitch().queuePushDuration()).isEqualTo(45); } + @Test + public void testAsJson() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + //empty + assertThat(mapper.writeValueAsString(witness)).isEqualTo(witness.asJson()).isEmpty(); + //dirty + witness.in(1); + assertThat(mapper.writeValueAsString(witness)).isEqualTo(witness.asJson()).contains("events"); + } + + @Test + public void testSerializeEmpty() throws Exception { + //Due to legacy requirements if empty, the events should not serialize at all. + assertThat(witness.asJson()).isEmpty(); + } + + @Test + public void testSerializeDuration() throws Exception { + witness.duration(999); + String json = witness.asJson(); + assertThat(json).contains("999"); + witness.forgetAll(); + json = witness.asJson(); + assertThat(json).doesNotContain("999"); + } + + @Test + public void testSerializeIn() throws Exception { + witness.in(888); + String json = witness.asJson(); + assertThat(json).contains("888"); + witness.forgetAll(); + json = witness.asJson(); + assertThat(json).doesNotContain("888"); + } + + @Test + public void testSerializeFiltered() throws Exception { + witness.filtered(777); + String json = witness.asJson(); + assertThat(json).contains("777"); + witness.forgetAll(); + json = witness.asJson(); + assertThat(json).doesNotContain("777"); + } + + @Test + public void testSerializeOut() throws Exception { + witness.out(666); + String json = witness.asJson(); + assertThat(json).contains("666"); + witness.forgetAll(); + json = witness.asJson(); + assertThat(json).doesNotContain("666"); + } + + @Test + public void testSerializeQueueDuration() throws Exception { + witness.queuePushDuration(555); + String json = witness.asJson(); + assertThat(json).contains("555"); + witness.forgetAll(); + json = witness.asJson(); + assertThat(json).doesNotContain("555"); + } + } \ No newline at end of file diff --git a/logstash-core/src/test/java/org/logstash/instrument/witness/PipelineWitnessTest.java b/logstash-core/src/test/java/org/logstash/instrument/witness/PipelineWitnessTest.java index 756e3e29c..4e6f20231 100644 --- a/logstash-core/src/test/java/org/logstash/instrument/witness/PipelineWitnessTest.java +++ b/logstash-core/src/test/java/org/logstash/instrument/witness/PipelineWitnessTest.java @@ -1,9 +1,11 @@ package org.logstash.instrument.witness; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Before; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.within; /** * Unit tests for {@link PipelineWitness} @@ -62,4 +64,55 @@ public class PipelineWitnessTest { assertThat(witness.queue().snitch().type()).isEqualTo("memory"); } + @Test + public void testAsJson() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + assertThat(mapper.writeValueAsString(witness)).isEqualTo(witness.asJson()); + } + + @Test + public void testSerializeEmpty() throws Exception { + String json = witness.asJson(); + assertThat(json).isEqualTo("{\"default\":{\"plugins\":{\"inputs\":[],\"filters\":[],\"outputs\":[]},\"reloads\":{\"last_error\":null,\"successes\":0," + + "\"last_success_timestamp\":null,\"last_failure_timestamp\":null,\"failures\":0},\"queue\":{}}}"); + } + + @Test + public void testSerializeEvents() throws Exception{ + witness.events().in(99); + String json = witness.asJson(); + assertThat(json).contains("99"); + witness.forgetEvents(); + json = witness.asJson(); + //events are forgotten + assertThat(json).doesNotContain("99"); + } + + @Test + public void testSerializePlugins() throws Exception{ + witness.inputs("aaa"); + witness.filters("bbb"); + witness.outputs("ccc"); + String json = witness.asJson(); + assertThat(json).contains("aaa").contains("bbb").contains("ccc"); + witness.forgetPlugins(); + json = witness.asJson(); + //plugins are forgotten + assertThat(json).doesNotContain("aaa").doesNotContain("bbb").doesNotContain("ccc"); + } + + @Test + public void testSerializeReloads() throws Exception{ + witness.reloads().successes(98); + String json = witness.asJson(); + assertThat(json).contains("98"); + } + + @Test + public void testSerializeQueue() throws Exception{ + witness.queue().type("quantum"); + String json = witness.asJson(); + assertThat(json).contains("quantum"); + } + } \ No newline at end of file diff --git a/logstash-core/src/test/java/org/logstash/instrument/witness/PipelinesWitnessTest.java b/logstash-core/src/test/java/org/logstash/instrument/witness/PipelinesWitnessTest.java index f41feac3b..fee73b2b7 100644 --- a/logstash-core/src/test/java/org/logstash/instrument/witness/PipelinesWitnessTest.java +++ b/logstash-core/src/test/java/org/logstash/instrument/witness/PipelinesWitnessTest.java @@ -1,5 +1,6 @@ package org.logstash.instrument.witness; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Before; import org.junit.Test; @@ -26,4 +27,25 @@ public class PipelinesWitnessTest { assertThat(witness.pipeline("default")).isNotNull(); } + @Test + public void testAsJson() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + assertThat(mapper.writeValueAsString(witness)).isEqualTo(witness.asJson()).contains("pipelines"); + } + + @Test + public void testSerializeEmpty() throws Exception { + String json = witness.asJson(); + assertThat(json).isEqualTo("{\"pipelines\":{}}"); + } + + @Test + public void testSerializePipelines() throws Exception { + witness.pipeline("aaa"); + witness.pipeline("bbb"); + witness.pipeline("ccc"); + String json = witness.asJson(); + assertThat(json).contains("aaa").contains("bbb").contains("ccc"); + } + } \ No newline at end of file diff --git a/logstash-core/src/test/java/org/logstash/instrument/witness/PluginWitnessTest.java b/logstash-core/src/test/java/org/logstash/instrument/witness/PluginWitnessTest.java index 676459ae0..56ac76a7c 100644 --- a/logstash-core/src/test/java/org/logstash/instrument/witness/PluginWitnessTest.java +++ b/logstash-core/src/test/java/org/logstash/instrument/witness/PluginWitnessTest.java @@ -1,6 +1,7 @@ package org.logstash.instrument.witness; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Before; import org.junit.Test; @@ -29,4 +30,30 @@ public class PluginWitnessTest { public void testEvents(){ assertThat(witness.events()).isNotNull(); } + + @Test + public void testAsJson() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + assertThat(mapper.writeValueAsString(witness)).isEqualTo(witness.asJson()); + } + + @Test + public void testSerializationEmpty() throws Exception { + String json = witness.asJson(); + assertThat(json).isEqualTo("{\"id\":\"123\"}"); + } + + @Test + public void testSerializationName() throws Exception { + witness.name("abc"); + String json = witness.asJson(); + assertThat(json).isEqualTo("{\"id\":\"123\",\"name\":\"abc\"}"); + } + + @Test + public void testSerializationEvents() throws Exception { + witness.events().in(); + String json = witness.asJson(); + assertThat(json).isEqualTo("{\"id\":\"123\",\"events\":{\"in\":1}}"); + } } \ No newline at end of file diff --git a/logstash-core/src/test/java/org/logstash/instrument/witness/PluginsWitnessTest.java b/logstash-core/src/test/java/org/logstash/instrument/witness/PluginsWitnessTest.java index 9956db793..105f88428 100644 --- a/logstash-core/src/test/java/org/logstash/instrument/witness/PluginsWitnessTest.java +++ b/logstash-core/src/test/java/org/logstash/instrument/witness/PluginsWitnessTest.java @@ -1,6 +1,7 @@ package org.logstash.instrument.witness; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Before; import org.junit.Test; @@ -26,11 +27,67 @@ public class PluginsWitnessTest { assertThat(witness.filters("1").events().snitch().in()).isEqualTo(98); witness.outputs("1").events().in(97); assertThat(witness.outputs("1").events().snitch().in()).isEqualTo(97); + witness.codecs("1").events().in(96); + assertThat(witness.codecs("1").events().snitch().in()).isEqualTo(96); witness.forgetAll(); assertThat(witness.inputs("1").events().snitch().in()).isEqualTo(0); assertThat(witness.filters("1").events().snitch().filtered()).isEqualTo(0); assertThat(witness.outputs("1").events().snitch().in()).isEqualTo(0); + assertThat(witness.codecs("1").events().snitch().in()).isEqualTo(0); + } + + @Test + public void testAsJson() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + assertThat(mapper.writeValueAsString(witness)).isEqualTo(witness.asJson()); + } + + @Test + public void testSerializeEmpty() throws Exception{ + String json = witness.asJson(); + assertThat(json).isEqualTo("{\"plugins\":{\"inputs\":[],\"filters\":[],\"outputs\":[]}}"); + } + + @Test + public void testSerializeInput() throws Exception{ + witness.inputs("foo"); + String json = witness.asJson(); + assertThat(json).isEqualTo("{\"plugins\":{\"inputs\":[{\"id\":\"foo\"}],\"filters\":[],\"outputs\":[]}}"); + witness.forgetAll(); + json = witness.asJson(); + assertThat(json).isEqualTo("{\"plugins\":{\"inputs\":[],\"filters\":[],\"outputs\":[]}}"); + } + + @Test + public void testSerializeFilters() throws Exception{ + witness.filters("foo"); + String json = witness.asJson(); + assertThat(json).isEqualTo("{\"plugins\":{\"inputs\":[],\"filters\":[{\"id\":\"foo\"}],\"outputs\":[]}}"); + witness.forgetAll(); + json = witness.asJson(); + assertThat(json).isEqualTo("{\"plugins\":{\"inputs\":[],\"filters\":[],\"outputs\":[]}}"); + } + + @Test + public void testSerializeOutputs() throws Exception{ + witness.outputs("foo"); + String json = witness.asJson(); + assertThat(json).isEqualTo("{\"plugins\":{\"inputs\":[],\"filters\":[],\"outputs\":[{\"id\":\"foo\"}]}}"); + witness.forgetAll(); + json = witness.asJson(); + assertThat(json).isEqualTo("{\"plugins\":{\"inputs\":[],\"filters\":[],\"outputs\":[]}}"); + } + + @Test + public void testSerializeCodecs() throws Exception{ + witness.codecs("foo"); + String json = witness.asJson(); + //codecs are not currently serialized. + assertThat(json).isEqualTo("{\"plugins\":{\"inputs\":[],\"filters\":[],\"outputs\":[]}}"); + witness.forgetAll(); + json = witness.asJson(); + assertThat(json).isEqualTo("{\"plugins\":{\"inputs\":[],\"filters\":[],\"outputs\":[]}}"); } } \ No newline at end of file diff --git a/logstash-core/src/test/java/org/logstash/instrument/witness/QueueWitnessTest.java b/logstash-core/src/test/java/org/logstash/instrument/witness/QueueWitnessTest.java index d201e4754..b56625477 100644 --- a/logstash-core/src/test/java/org/logstash/instrument/witness/QueueWitnessTest.java +++ b/logstash-core/src/test/java/org/logstash/instrument/witness/QueueWitnessTest.java @@ -1,6 +1,7 @@ package org.logstash.instrument.witness; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Before; import org.junit.Test; @@ -23,4 +24,23 @@ public class QueueWitnessTest { assertThat(witness.snitch().type()).isEqualTo("memory"); } + @Test + public void testAsJson() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + assertThat(mapper.writeValueAsString(witness)).isEqualTo(witness.asJson()); + } + + @Test + public void testSerializeEmpty() throws Exception{ + String json = witness.asJson(); + assertThat(json).isEqualTo("{\"queue\":{}}"); + } + + @Test + public void testSerializeType() throws Exception{ + witness.type("memory"); + String json = witness.asJson(); + assertThat(json).isEqualTo("{\"queue\":{\"type\":\"memory\"}}"); + } + } \ No newline at end of file diff --git a/logstash-core/src/test/java/org/logstash/instrument/witness/ReloadWitnessTest.java b/logstash-core/src/test/java/org/logstash/instrument/witness/ReloadWitnessTest.java index d0161f7bd..c333afe7a 100644 --- a/logstash-core/src/test/java/org/logstash/instrument/witness/ReloadWitnessTest.java +++ b/logstash-core/src/test/java/org/logstash/instrument/witness/ReloadWitnessTest.java @@ -1,6 +1,7 @@ package org.logstash.instrument.witness; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -24,13 +25,13 @@ public class ReloadWitnessTest { JrubyTimestampExtLibrary.RubyTimestamp rubyTimestamp; @Before - public void setup(){ + public void setup() { witness = new ReloadWitness(); when(rubyTimestamp.getTimestamp()).thenReturn(timestamp); } @Test - public void testSuccess(){ + public void testSuccess() { witness.success(); witness.lastSuccessTimestamp(rubyTimestamp); assertThat(witness.snitch().successes()).isEqualTo(1); @@ -40,7 +41,7 @@ public class ReloadWitnessTest { } @Test - public void testFailure(){ + public void testFailure() { witness.failure(); witness.lastFailureTimestamp(rubyTimestamp); assertThat(witness.snitch().failures()).isEqualTo(1); @@ -50,8 +51,48 @@ public class ReloadWitnessTest { } @Test - public void testError(){ + public void testError() { witness.error().message("foo"); assertThat(witness.error().snitch().message()).isEqualTo("foo"); } + + @Test + public void testAsJson() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + assertThat(mapper.writeValueAsString(witness)).isEqualTo(witness.asJson()); + } + + @Test + public void testSerializeEmpty() throws Exception { + String json = witness.asJson(); + assertThat(json).isEqualTo("{\"reloads\":{\"last_error\":null,\"successes\":0,\"last_success_timestamp\":null,\"last_failure_timestamp\":null,\"failures\":0}}"); + } + + @Test + public void testSerializeSuccess() throws Exception { + witness.success(); + witness.lastSuccessTimestamp(rubyTimestamp); + String json = witness.asJson(); + assertThat(json).isEqualTo("{\"reloads\":{\"last_error\":null,\"successes\":1,\"last_success_timestamp\":\"" + timestamp.toIso8601() + + "\",\"last_failure_timestamp\":null,\"failures\":0}}"); + } + + @Test + public void testSerializeFailure() throws Exception { + witness.failure(); + witness.lastFailureTimestamp(rubyTimestamp); + String json = witness.asJson(); + assertThat(json).isEqualTo("{\"reloads\":{\"last_error\":null,\"successes\":0,\"last_success_timestamp\":null,\"last_failure_timestamp\":\"" + + timestamp.toIso8601() + "\",\"failures\":1}}"); + } + + @Test + public void testSerializeError() throws Exception{ + witness.error().message("foo"); + witness.error().backtrace("bar"); + String json = witness.asJson(); + assertThat(json).contains("foo"); + assertThat(json).contains("bar"); + } + } \ No newline at end of file diff --git a/logstash-core/src/test/java/org/logstash/instrument/witness/WitnessTest.java b/logstash-core/src/test/java/org/logstash/instrument/witness/WitnessTest.java index c1e07fa81..9f0a6c292 100644 --- a/logstash-core/src/test/java/org/logstash/instrument/witness/WitnessTest.java +++ b/logstash-core/src/test/java/org/logstash/instrument/witness/WitnessTest.java @@ -1,5 +1,6 @@ package org.logstash.instrument.witness; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Before; import org.junit.Test; @@ -12,19 +13,19 @@ public class WitnessTest { private Witness witness; @Before - public void setup(){ + public void setup() { Witness.setInstance(null); } @Test - public void testInstance(){ + public void testInstance() { witness = new Witness(); Witness.setInstance(witness); assertThat(Witness.instance()).isEqualTo(witness); } @Test(expected = IllegalStateException.class) - public void testNoInstanceError(){ + public void testNoInstanceError() { Witness.instance(); } @@ -37,4 +38,57 @@ public class WitnessTest { assertThat(witness.pipelines()).isNotNull(); assertThat(witness.pipeline("foo")).isNotNull(); } + + @Test + public void testAsJson() throws Exception { + witness = new Witness(); + ObjectMapper mapper = new ObjectMapper(); + assertThat(mapper.writeValueAsString(witness)).isEqualTo(witness.asJson()); + } + + @Test + public void testSerializeEmpty() throws Exception { + witness = new Witness(); + String json = witness.asJson(); + //empty pipelines + assertThat(json).contains("\"pipelines\":{}"); + //non-empty reloads + assertThat(json).contains("{\"reloads\":{\""); + //no events + assertThat(json).doesNotContain("events"); + } + + @Test + public void testSerializeEvents() throws Exception { + witness = new Witness(); + witness.events().in(99); + String json = witness.asJson(); + assertThat(json).contains("{\"events\":{\"in\":99}"); + witness.events().forgetAll(); + json = witness.asJson(); + assertThat(json).doesNotContain("events"); + } + + @Test + public void testSerializePipelines() throws Exception { + witness = new Witness(); + witness.pipeline("foo").events().in(98); + witness.pipeline("foo").inputs("bar").events().in(99); + String json = witness.asJson(); + assertThat(json).contains("\"pipelines\":{\"foo\""); + //pipeline events + assertThat(json).contains("\"foo\":{\"events\":{\"in\":98"); + //plugin events + assertThat(json).contains("plugins\":{\"inputs\":[{\"id\":\"bar\",\"events\":{\"in\":99"); + //forget events + witness.pipeline("foo").forgetEvents(); + json = witness.asJson(); + assertThat(json).doesNotContain("98"); + //forget plugins + witness.pipeline("foo").forgetPlugins(); + json = witness.asJson(); + assertThat(json).doesNotContain("99"); + //pipelines still there + assertThat(json).contains("\"pipelines\":{\"foo\""); + } } \ No newline at end of file