Metrics: Add Jackson serialization to the Witness classes.

Part of #7788

Fixes #7984
This commit is contained in:
Jake Landis 2017-08-11 12:20:14 -05:00
parent 89ade02bfe
commit 94e5225674
22 changed files with 1170 additions and 25 deletions

View file

@ -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<ConfigWitness> {
/**
* Default constructor - required for Jackson
*/
public Serializer() {
this(ConfigWitness.class);
}
/**
* Constructor
*
* @param t the type to serialize
*/
protected Serializer(Class<ConfigWitness> 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<Metric<Long>> longSerializer = MetricSerializer.Get.longSerializer(gen);
MetricSerializer<Metric<Boolean>> 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,6 +202,7 @@ 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
*/
public boolean deadLetterQueueEnabled() {
@ -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();
}
}
}

View file

@ -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<ErrorWitness> {
/**
* Default constructor - required for Jackson
*/
public Serializer() {
this(ErrorWitness.class);
}
/**
* Constructor
*
* @param t the type to serialize
*/
protected Serializer(Class<ErrorWitness> 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<Metric<String>> 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.
*/

View file

@ -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<EventsWitness> {
/**
* Default constructor - required for Jackson
*/
public Serializer() {
this(EventsWitness.class);
}
/**
* Constructor
*
* @param t the type to serialize
*/
protected Serializer(Class<EventsWitness> 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<Metric<Long>> 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() {

View file

@ -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 <T> The type of {@link GaugeMetric} to serialize
*/
@FunctionalInterface
public interface MetricSerializer<T extends Metric<?>> {
/**
* 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<Metric<Long>> 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<Metric<Boolean>> 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<Metric<String>> 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<RubyTimeStampGauge> timestampSerializer(JsonGenerator gen) {
return m -> {
if (m != null) {
gen.writeStringField(m.getName(), m.getValue() != null ? m.getValue().toString() : null);
}
};
}
}
}

View file

@ -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<PipelineWitness> {
/**
* Default constructor - required for Jackson
*/
public Serializer() {
this(PipelineWitness.class);
}
/**
* Constructor
*
* @param t the type to serialize
*/
protected Serializer(Class<PipelineWitness> 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();
}
}
}

View file

@ -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<String, PipelineWitness> 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<PipelinesWitness> {
/**
* Default constructor - required for Jackson
*/
public Serializer() {
this(PipelinesWitness.class);
}
/**
* Constructor
*
* @param t the type to serialize
*/
protected Serializer(Class<PipelinesWitness> 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<String, PipelineWitness> entry : witness.pipelines.entrySet()) {
entry.getValue().genJson(gen, provider);
}
gen.writeEndObject();
}
}
}

View file

@ -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<PluginWitness> {
/**
* Default constructor - required for Jackson
*/
public Serializer() {
this(PluginWitness.class);
}
/**
* Constructor
*
* @param t the type to serialize
*/
protected Serializer(Class<PluginWitness> 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<Metric<String>> 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.

View file

@ -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<String, PluginWitness> inputs;
private final Map<String, PluginWitness> outputs;
private final Map<String, PluginWitness> filters;
private final Map<String, PluginWitness> 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();
}
/**
@ -69,5 +95,59 @@ public class PluginsWitness{
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<PluginsWitness> {
/**
* Default constructor - required for Jackson
*/
public Serializer() {
this(PluginsWitness.class);
}
/**
* Constructor
*
* @param t the type to serialize
*/
protected Serializer(Class<PluginsWitness> 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<String, PluginWitness> plugin, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeArrayFieldStart(key);
for (Map.Entry<String, PluginWitness> entry : plugin.entrySet()) {
gen.writeStartObject();
entry.getValue().genJson(gen, provider);
gen.writeEndObject();
}
gen.writeEndArray();
}
}
}

View file

@ -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<QueueWitness> {
/**
* Default constructor - required for Jackson
*/
public Serializer() {
this(QueueWitness.class);
}
/**
* Constructor
*
* @param t the type to serialize
*/
protected Serializer(Class<QueueWitness> 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.
*/

View file

@ -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<ReloadWitness> {
/**
* Default constructor - required for Jackson
*/
public Serializer() {
this(ReloadWitness.class);
}
/**
* Constructor
*
* @param t the type to serialize
*/
protected Serializer(Class<ReloadWitness> 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<Metric<Long>> longSerializer = MetricSerializer.Get.longSerializer(gen);
MetricSerializer<RubyTimeStampGauge> 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.
*/

View file

@ -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();
}
}
}

View file

@ -1,5 +1,11 @@
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;
/**
@ -12,13 +18,15 @@ import java.util.Arrays;
* <p>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.</p>
* <p>All Witnesses are capable of serializing their underlying metrics as JSON.</p>
*/
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<Witness> {
/**
* Default constructor - required for Jackson
*/
public Serializer() {
this(Witness.class);
}
/**
* Constructor
*
* @param t the type to serialize
*/
protected Serializer(Class<Witness> 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);
}
}
}

View file

@ -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}}");
}
}

View file

@ -1,5 +1,6 @@
package org.logstash.instrument.witness;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Before;
import org.junit.Test;
@ -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");
}
}

View file

@ -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");
}
}

View file

@ -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");
}
}

View file

@ -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");
}
}

View file

@ -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}}");
}
}

View file

@ -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\":[]}}");
}
}

View file

@ -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\"}}");
}
}

View file

@ -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;
@ -54,4 +55,44 @@ public class ReloadWitnessTest {
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");
}
}

View file

@ -1,5 +1,6 @@
package org.logstash.instrument.witness;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Before;
import org.junit.Test;
@ -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\"");
}
}