rename couner (consumer|sink) to analytics (consumer|sink). Remove the custom SpEL convert in favor of config-common

This commit is contained in:
Christian Tzolov
2020-06-30 00:13:08 +02:00
parent 7f19cfff22
commit cbeabd371e
20 changed files with 147 additions and 276 deletions

View File

@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<artifactId>config-common</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>geode-common</name>
<name>config-common</name>
<description>Function Common Configuration Components</description>
<parent>

View File

@@ -1,9 +1,9 @@
# Counter Consumer
# Analytics Consumer
The `counter-consumer` is a Java https://docs.oracle.com/javase/8/docs/api/java/util/function/Consumer.html[Consumer<Message<?>>] that computes analytics from the input data messages and publishes them as metrics to various monitoring systems.
The `analytics-consumer` is a Java https://docs.oracle.com/javase/8/docs/api/java/util/function/Consumer.html[Consumer<Message<?>>] that computes analytics from the input data messages and publishes them as metrics to various monitoring systems.
It leverages the https://micrometer.io[micrometer library] for providing a uniform programming experience across the most popular https://micrometer.io/docs[monitoring systems] and uses https://docs.spring.io/spring-integration/reference/html/spel.html#spel[Spring Expression Language (SpEL)] for defining how the metric names, values and tags are computed from the input data.
The counter-consumer can produce two metrics types:
The analytics-consumer can produce two metrics types:
- https://micrometer.io/docs/concepts#_counters[Counter] - reports a single metric, a count, that increments by a fixed, positive amount. Counters can be used for computing the rates of how the data changes in time.
- https://micrometer.io/docs/concepts#_gauges[Gauge] - reports the current value. Typical examples for gauges would be the size of a collection or map or number of threads in a running state.
@@ -14,25 +14,25 @@ NOTE: As a metrics is uniquely identified by its `name` and `dimensions`, you ca
## Beans for injection
Add the counter-consumer dependency to your POM:
Add the analytics-consumer dependency to your POM:
[source,xml]
----
<dependency>
<groupId>org.springframework.cloud.fn</groupId>
<artifactId>counter-consumer</artifactId>
<artifactId>analytics-consumer</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
----
Import the https://github.com/spring-cloud/stream-applications/blob/master/functions/consumer/counter-consumer/src/main/java/org/springframework/cloud/fn/consumer/counter/CounterConsumerConfiguration.java[CounterConsumerConfiguration] in the application and inject the following consumer bean:
Import the https://github.com/spring-cloud/stream-applications/blob/master/functions/consumer/analytics-consumer/src/main/java/org/springframework/cloud/fn/consumer/analytics/AnalyticsConsumerConfiguration.java[AnalyticsConsumerConfiguration] in the application and inject the following consumer bean:
[source,java]
----
Consumer<Message<?>> counterConsumer
Consumer<Message<?>> analyticsConsumer
----
For every input https://docs.spring.io/spring-integration/reference/html/message.html[Message] the `counterConsumer` computes the defined metrics and eventually, with the help of the micrometer library, publishes them to the backend monitoring systems. The https://docs.spring.io/spring-integration/reference/html/message.html[Message] is a generic container for data. Each Message instance includes a payload and headers containing user-extensible properties as key-value pairs. Any object can be provided as the payload.
For every input https://docs.spring.io/spring-integration/reference/html/message.html[Message] the `analyticsConsumer` computes the defined metrics and eventually, with the help of the micrometer library, publishes them to the backend monitoring systems. The https://docs.spring.io/spring-integration/reference/html/message.html[Message] is a generic container for data. Each Message instance includes a payload and headers containing user-extensible properties as key-value pairs. Any object can be provided as the payload.
The https://docs.spring.io/spring-integration/reference/html/message.html#message-builder[MessageBuilder] helps to create a Message instance from any payload content and assign any key/value as a header:
[source,java]
@@ -48,11 +48,11 @@ The `SpEL` expressions use the `headers` and `payload` keywords to access messag
[source]
----
counter.amount-expression=payload.lenght()
counter.tag.expression.my_tag=headers['kind']
analytics.amount-expression=payload.lenght()
analytics.tag.expression.my_tag=headers['kind']
----
Review the https://github.com/spring-cloud/stream-applications/blob/master/functions/consumer/counter-consumer/src/main/java/org/springframework/cloud/fn/consumer/counter/CounterConsumerProperties.java[CounterConsumerProperties]'s javadocs for further details how to use the SpEL properties.
Review the https://github.com/spring-cloud/stream-applications/blob/master/functions/consumer/analytics-consumer/src/main/java/org/springframework/cloud/fn/consumer/analytics/AnalyticsConsumerProperties.java[AnalyticsConsumerProperties]'s javadocs for further details how to use the SpEL properties.
By default, Micrometer is packed with a SimpleMeterRegistry that holds the latest value of each meter in memory and doesnt export the data anywhere.
To enable support for another monitoring system you have to add the spring-boot-starter-actuator dependency and the micrometer dependency of the monitoring system of choice:
@@ -76,13 +76,13 @@ Follow the https://docs.spring.io/spring-boot/docs/2.3.1.RELEASE/reference/html/
## Configuration Options
All `counter-consumer` configuration properties use the `counter` prefix. For more information on the various options available, please see link:src/main/java/org/springframework/cloud/fn/consumer/counter/CounterConsumerProperties.java[CounterConsumerProperties].
All `analytics-consumer` configuration properties use the `analytics` prefix. For more information on the various options available, please see link:src/main/java/org/springframework/cloud/fn/consumer/analytics/AnalyticsConsumerProperties.java[AnalyticsConsumerProperties].
All monitoring configuration properties start with a prefix `management.metrics.export`. For configuring a particular monitoring system follow the provided https://docs.spring.io/spring-boot/docs/2.3.1.RELEASE/reference/html/production-ready-features.html#production-ready-metrics-export[configuration instructions].
#### Sample Configuration
Following examples show how to configure counter and gauge metrics over a series of stock-exchange messages like this:
Following examples show how to configure `counter` and `gauge` metrics over a series of stock-exchange messages like this:
[source,json]
----
@@ -97,22 +97,22 @@ Following examples show how to configure counter and gauge metrics over a series
}
----
The following configuration will create a counter metrics called `stockrates` with two tags: `symbol` and `exchange` computed from the json fields:
The following configuration will create a `counter` metrics called `stockrates` with two tags: `symbol` and `exchange` computed from the json fields:
.Counter Metrcis Configuration - count stock transactions
|===
|Property |Description
|counter.meter-type=counter
|analytics.meter-type=counter
|Counter meter type (default)
|counter.name=stockrates
|analytics.name=stockrates
|Metrics name
|counter.tag.expression.symbol=#jsonPath(payload,'$.data.symbol')
|analytics.tag.expression.symbol=#jsonPath(payload,'$.data.symbol')
|Add tag `symbol` equal to the `date.symbol` field in the json messages.
|counter.tag.expression.exchange=#jsonPath(payload,'$.data.exchange')
|analytics.tag.expression.exchange=#jsonPath(payload,'$.data.exchange')
|Add tag `exchange` equal to the `date.exchange` field in the json messages.
|===
@@ -125,19 +125,19 @@ To measure the transaction volumes contained in the data.volume JSON fields, you
|===
|Property |Description
|counter.meter-type=gauge
|analytics.meter-type=gauge
|Gauge meter type
|counter.name=stockvolumes
|analytics.name=stockvolumes
|Metrics name
|counter.tag.expression.symbol=#jsonPath(payload,'$.data.symbol')
|analytics.tag.expression.symbol=#jsonPath(payload,'$.data.symbol')
|Add tag `symbol` equal to the `date.symbol` field in the json messages.
|counter.tag.expression.exchange=#jsonPath(payload,'$.data.exchange')
|analytics.tag.expression.exchange=#jsonPath(payload,'$.data.exchange')
|Add tag `exchange` equal to the `date.exchange` field in the json messages.
|counter.tag.amount-expression=#jsonPath(payload,'$.data.volume')
|analytics.tag.amount-expression=#jsonPath(payload,'$.data.volume')
|Set the Gauge to the `data/volume` field values.
|===
@@ -168,10 +168,10 @@ To enable one or more https://micrometer.io/docs[supported monitoring systems] y
## Tests
See this link:src/test/java/org/springframework/cloud/fn/consumer/counter[test suite] for the various ways, this consumer is used.
See this link:src/test/java/org/springframework/cloud/fn/consumer/analytics[test suite] for the various ways, this consumer is used.
## Other usage
* See the https://github.com/spring-cloud/stream-applications/blob/master/applications/sink/counter-sink/README.adoc[Counter Sink README] where this consumer is used to create a Spring Cloud Stream application where it makes a Counter sink.
* See the https://github.com/spring-cloud/stream-applications/blob/master/applications/sink/analytics-sink/README.adoc[Analytics Sink README] where this consumer is used to create a Spring Cloud Stream application where it makes a Counter sink.
* https://docs.google.com/document/d/1BHBjgMmg4a1ue2wr-dmPTfgaN0so4ufw2XkG541Ac9Q/edit?usp=sharing[Stock Exchange Sample].

View File

@@ -3,10 +3,10 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>counter-consumer</artifactId>
<artifactId>analytics-consumer</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>counter-consumer</name>
<description>Spring Native Consumer for computing counters</description>
<name>analytics-consumer</name>
<description>Spring Native Consumer for computing meters</description>
<parent>
<groupId>org.springframework.cloud.fn</groupId>
@@ -22,8 +22,14 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
<groupId>org.springframework.cloud.fn</groupId>
<artifactId>config-common</artifactId>
<version>${spring-cloud-fn.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.cloud.fn.consumer.counter;
package org.springframework.cloud.fn.consumer.analytics;
import java.util.Arrays;
import java.util.Collection;
@@ -26,7 +26,6 @@ import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import io.micrometer.core.instrument.Meter;
@@ -37,15 +36,11 @@ import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.ConfigurationPropertiesBinding;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.convert.converter.Converter;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.messaging.Message;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
@@ -55,30 +50,15 @@ import org.springframework.util.StringUtils;
* @author Christian Tzolov
*/
@Configuration
@EnableConfigurationProperties({ CounterConsumerProperties.class })
public class CounterConsumerConfiguration {
@EnableConfigurationProperties({ AnalyticsConsumerProperties.class })
public class AnalyticsConsumerConfiguration {
private final Map<Meter.Id, AtomicLong> gaugeValues = new ConcurrentHashMap<>();
@Bean
public Function<String, Expression> stringToSpelFunction(@Lazy EvaluationContext evaluationContext) {
return new StringToSpelConversionFunction(evaluationContext);
}
@Bean
@ConfigurationPropertiesBinding
public Converter<String, Expression> propertiesSpelConverter(Function<String, Expression> stringToSpelFunction) {
return new Converter<String, Expression>() { // NOTE Using lambda causes Java Generics issues.
@Override
public Expression convert(String source) {
return stringToSpelFunction.apply(source);
}
};
}
@Bean(name = "counterConsumer")
public Consumer<Message<?>> counterConsumer(CounterConsumerProperties properties, MeterRegistry[] meterRegistries,
@Qualifier(IntegrationContextUtils.INTEGRATION_EVALUATION_CONTEXT_BEAN_NAME) EvaluationContext context) {
@Bean(name = "analyticsConsumer")
public Consumer<Message<?>> analyticsConsumer(AnalyticsConsumerProperties properties, MeterRegistry[] meterRegistries,
@Lazy
@Qualifier("integrationEvaluationContext") EvaluationContext context) {
return message -> {
@@ -150,7 +130,7 @@ public class CounterConsumerConfiguration {
}
private void recordMetrics(MeterRegistry[] meterRegistries, String meterName, Tags fixedTags, Map<String,
List<Tag>> groupedTags, double amount, CounterConsumerProperties.MeterType meterType) {
List<Tag>> groupedTags, double amount, AnalyticsConsumerProperties.MeterType meterType) {
if (!CollectionUtils.isEmpty(groupedTags)) {
groupedTags.values().stream().map(List::size).max(Integer::compareTo).ifPresent(
max -> {
@@ -175,10 +155,10 @@ public class CounterConsumerConfiguration {
}
private void record(MeterRegistry[] meterRegistries, String meterName,
Iterable<Tag> tags, double meterAmount, CounterConsumerProperties.MeterType meterType) {
Iterable<Tag> tags, double meterAmount, AnalyticsConsumerProperties.MeterType meterType) {
for (MeterRegistry meterRegistry : meterRegistries) {
if (meterType == CounterConsumerProperties.MeterType.gauge) {
if (meterType == AnalyticsConsumerProperties.MeterType.gauge) {
Meter.Id gaugeId = new Meter.Id(meterName, Tags.of(tags), null, null, Meter.Type.GAUGE);
if (!this.gaugeValues.containsKey(gaugeId)) {
this.gaugeValues.put(gaugeId, new AtomicLong((long) meterAmount));
@@ -190,7 +170,7 @@ public class CounterConsumerConfiguration {
meterRegistry.gauge(meterName, tags, this.gaugeValues.get(gaugeId), AtomicLong::doubleValue);
}
}
else if (meterType == CounterConsumerProperties.MeterType.counter) {
else if (meterType == AnalyticsConsumerProperties.MeterType.counter) {
meterRegistry.counter(meterName, tags).increment(meterAmount);
}
else {

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.cloud.fn.consumer.counter;
package org.springframework.cloud.fn.consumer.analytics;
import java.util.Map;
@@ -29,9 +29,9 @@ import org.springframework.validation.annotation.Validated;
/**
* @author Christian Tzolov
*/
@ConfigurationProperties("counter")
@ConfigurationProperties("analytics")
@Validated
public class CounterConsumerProperties {
public class AnalyticsConsumerProperties {
enum MeterType {
/** Uses the Micrometer Counter meter type. It accumulates intermediate counts toward the point where
@@ -55,31 +55,25 @@ public class CounterConsumerProperties {
private String defaultName;
/**
* The name of the counter to increment. The 'name' and 'nameExpression' are mutually exclusive.
* The name of the meter to increment. The 'name' and 'nameExpression' are mutually exclusive.
* Only one can be set.
*/
private String name;
/**
* A SpEL expression (against the incoming Message) to derive the name of the counter to increment.
* A SpEL expression (against the incoming Message) to derive the name of the meter to increment.
* The 'name' and 'nameExpression' are mutually exclusive. Only one can be set.
*/
private Expression nameExpression;
/**
* A SpEL expression (against the incoming Message) to derive the amount to add to the counter.
* If not set the counter is incremented by 1.0
* A SpEL expression (against the incoming Message) to derive the amount to add to the meter.
* If not set the meter is incremented by 1.0
*/
private Expression amountExpression;
/**
* Enables counting the number of messages processed. Uses the 'message.' counter name prefix to distinct it
* form the expression based counter. The message counter includes the fixed tags when provided.
*/
private boolean messageCounterEnabled = true;
/**
* Fixed and computed tags to be assignee with the counter increment measurement.
* Fixed and computed tags to be assignee with the meter increment measurement.
*/
private final MetricsTag tag = new MetricsTag();
@@ -130,14 +124,6 @@ public class CounterConsumerProperties {
return (nameExpression != null ? nameExpression : new LiteralExpression(getName()));
}
public boolean isMessageCounterEnabled() {
return messageCounterEnabled;
}
public void setMessageCounterEnabled(boolean messageCounterEnabled) {
this.messageCounterEnabled = messageCounterEnabled;
}
@AssertTrue(message = "exactly one of 'name' and 'nameExpression' must be set")
public boolean isExclusiveOptions() {
return getName() != null ^ getNameExpression() != null;
@@ -145,7 +131,7 @@ public class CounterConsumerProperties {
@Override
public String toString() {
return "CounterFunctionProperties{" +
return "AnalyticsFunctionProperties{" +
"defaultName='" + defaultName + '\'' +
", name=" + name +
", tag=" + tag +
@@ -155,16 +141,16 @@ public class CounterConsumerProperties {
public static class MetricsTag {
/**
* Custom tags assigned to every counter increment measurements.
* This is a map so the property convention fixed tags is: counter.tag.fixed.[tag-name]=[tag-value]
* Custom tags assigned to every meter increment measurements.
* This is a map so the property convention fixed tags is: analytics.tag.fixed.[tag-name]=[tag-value]
*/
private Map<String, String> fixed;
/**
* Computes tags from SpEL expression.
* Single SpEL expression can produce an array of values, which in turn means distinct name/value tags.
* Every name/value tag will produce a separate counter increment.
* Tag expression format is: counter.tag.expression.[tag-name]=[SpEL expression]
* Every name/value tag will produce a separate meter increment.
* Tag expression format is: analytics.tag.expression.[tag-name]=[SpEL expression]
*/
private Map<String, Expression> expression;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.cloud.fn.consumer.counter;
package org.springframework.cloud.fn.consumer.analytics;
import java.util.function.Consumer;
@@ -30,13 +30,13 @@ import org.springframework.test.annotation.DirtiesContext;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE,
properties = { "management.metrics.export.wavefront.enabled=false" })
@DirtiesContext
public class CounterConsumerParentTest {
public class AnalyticsConsumerParentTest {
@Autowired
protected SimpleMeterRegistry meterRegistry;
@Autowired
protected Consumer<Message<?>> counterConsumer;
protected Consumer<Message<?>> analyticsConsumer;
protected Message<byte[]> message(String payload) {
return MessageBuilder.withPayload(payload.getBytes()).build();

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.cloud.fn.consumer.counter;
package org.springframework.cloud.fn.consumer.analytics;
import org.junit.jupiter.api.Test;
@@ -27,17 +27,17 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Christian Tzolov
*/
@TestPropertySource(properties = {
"counter.name=counter666",
"counter.tag.expression.foo='bar'",
"counter.amount-expression=payload.length()"
"analytics.name=counter666",
"analytics.tag.expression.foo='bar'",
"analytics.amount-expression=payload.length()"
})
class CountWithAmountTest extends CounterConsumerParentTest {
class CountWithAmountTest extends AnalyticsConsumerParentTest {
@Test
void testCounterSink() {
String message = "hello world message";
double messageSize = Long.valueOf(message.length()).doubleValue();
counterConsumer.accept(new GenericMessage(message));
analyticsConsumer.accept(new GenericMessage(message));
assertThat(meterRegistry.find("counter666").counter().count()).isEqualTo(messageSize);
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.cloud.fn.consumer.counter;
package org.springframework.cloud.fn.consumer.analytics;
import java.util.Collection;
@@ -29,17 +29,17 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Christian Tzolov
*/
@TestPropertySource(properties = {
"counter.name=counter666",
"counter.tag.fixed.foo=",
"counter.tag.expression.tag666=#jsonPath(payload,'$..noField')",
"counter.tag.expression.test=#jsonPath(payload,'$..test')"
"analytics.name=counter666",
"analytics.tag.fixed.foo=",
"analytics.tag.expression.tag666=#jsonPath(payload,'$..noField')",
"analytics.tag.expression.test=#jsonPath(payload,'$..test')"
})
class EmptyTagsTests extends CounterConsumerParentTest {
class EmptyTagsTests extends AnalyticsConsumerParentTest {
@Test
void testCounterSink() {
counterConsumer.accept(message("{\"test\": \"Bar\"}"));
analyticsConsumer.accept(message("{\"test\": \"Bar\"}"));
Collection<Counter> fixedTagsCounters = meterRegistry.find("counter666").tagKeys("foo").counters();
assertThat(fixedTagsCounters.size()).isEqualTo(0);

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.cloud.fn.consumer.counter;
package org.springframework.cloud.fn.consumer.analytics;
import java.util.stream.IntStream;
@@ -29,13 +29,13 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Christian Tzolov
*/
@TestPropertySource(properties = {
"counter.name-expression=payload"
"analytics.name-expression=payload"
})
public class ExpressionCounterNameTests extends CounterConsumerParentTest {
public class ExpressionCounterNameTests extends AnalyticsConsumerParentTest {
@Test
void testCounterSink() {
IntStream.range(0, 13).forEach(i -> counterConsumer.accept(new GenericMessage("hello")));
IntStream.range(0, 13).forEach(i -> analyticsConsumer.accept(new GenericMessage("hello")));
assertThat(meterRegistry.find("hello").counter().count()).isEqualTo(13.0);
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.cloud.fn.consumer.counter;
package org.springframework.cloud.fn.consumer.analytics;
import java.util.stream.IntStream;
import java.util.stream.StreamSupport;
@@ -31,15 +31,15 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Christian Tzolov
*/
@TestPropertySource(properties = {
"counter.name=counter666",
"counter.tag.fixed.foo=bar",
"counter.tag.fixed.gork=bork"
"analytics.name=counter666",
"analytics.tag.fixed.foo=bar",
"analytics.tag.fixed.gork=bork"
})
public class FixedTagsTests extends CounterConsumerParentTest {
public class FixedTagsTests extends AnalyticsConsumerParentTest {
@Test
void testCounterSink() {
IntStream.range(0, 13).forEach(i -> counterConsumer.accept(new GenericMessage("hello")));
void testАnalyticsSink() {
IntStream.range(0, 13).forEach(i -> analyticsConsumer.accept(new GenericMessage("hello")));
Meter counterMeter = meterRegistry.find("counter666").meter();
assertThat(StreamSupport.stream(counterMeter.measure().spliterator(), false)
.mapToDouble(m -> m.getValue()).sum()).isEqualTo(13.0);

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.cloud.fn.consumer.counter;
package org.springframework.cloud.fn.consumer.analytics;
import org.junit.jupiter.api.Test;
@@ -27,28 +27,28 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Christian Tzolov
*/
@TestPropertySource(properties = {
"counter.meter-type=gauge",
"counter.name=myGauge",
"counter.tag.expression.foo='bar'",
"counter.amount-expression=payload.length()"
"analytics.meter-type=gauge",
"analytics.name=myGauge",
"analytics.tag.expression.foo='bar'",
"analytics.amount-expression=payload.length()"
})
class GaugeWithAmountTest extends CounterConsumerParentTest {
class GaugeWithAmountTest extends AnalyticsConsumerParentTest {
@Test
void testCounterSink() {
void testАnalyticsSink() {
String messageSmall = "hello";
counterConsumer.accept(new GenericMessage(messageSmall));
analyticsConsumer.accept(new GenericMessage(messageSmall));
assertThat(meterRegistry.find("myGauge").gauge().value()).isEqualTo(size(messageSmall));
assertThat(meterRegistry.find("myGauge").gauge().getId().getTags()).hasSize(1);
assertThat(meterRegistry.find("myGauge").gauge().getId().getTag("foo")).isEqualTo("bar");
String messageMiddle = "hello world";
counterConsumer.accept(new GenericMessage(messageMiddle));
analyticsConsumer.accept(new GenericMessage(messageMiddle));
assertThat(meterRegistry.find("myGauge").gauge().value()).isEqualTo(size(messageMiddle));
String messageLarge = "hello world, hello people!";
counterConsumer.accept(new GenericMessage(messageLarge));
analyticsConsumer.accept(new GenericMessage(messageLarge));
assertThat(meterRegistry.find("myGauge").gauge().value()).isEqualTo(size(messageLarge));
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.cloud.fn.consumer.counter;
package org.springframework.cloud.fn.consumer.analytics;
import java.util.stream.IntStream;
@@ -30,16 +30,16 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Christian Tzolov
*/
@TestPropertySource(properties = {
"counter.name=counter666",
"counter.tag.expression.foo='bar'",
"counter.tag.expression.gork='bork'"
"analytics.name=counter666",
"analytics.tag.expression.foo='bar'",
"analytics.tag.expression.gork='bork'"
})
public class LiteralTagExpressionsTests extends CounterConsumerParentTest {
public class LiteralTagExpressionsTests extends AnalyticsConsumerParentTest {
@Test
void testCounterSink() {
IntStream.range(0, 13).forEach(i -> counterConsumer.accept(new GenericMessage("hello")));
IntStream.range(0, 13).forEach(i -> analyticsConsumer.accept(new GenericMessage("hello")));
Counter fooCounter = meterRegistry.find("counter666").tag("foo", "bar").counter();
assertThat(fooCounter.count()).isEqualTo(13.0);

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.cloud.fn.consumer.counter;
package org.springframework.cloud.fn.consumer.analytics;
import java.util.Collection;
@@ -29,17 +29,17 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Christian Tzolov
*/
@TestPropertySource(properties = {
"counter.name=counter666",
"counter.tag.fixed.foo=",
"counter.tag.expression.tag666=#jsonPath(payload,'$..noField')",
"counter.tag.expression.test=#jsonPath(payload,'$..test')"
"analytics.name=counter666",
"analytics.tag.fixed.foo=",
"analytics.tag.expression.tag666=#jsonPath(payload,'$..noField')",
"analytics.tag.expression.test=#jsonPath(payload,'$..test')"
})
public class NullTagsTests extends CounterConsumerParentTest {
public class NullTagsTests extends AnalyticsConsumerParentTest {
@Test
void testCounterSink() {
void testАnalyticsSink() {
counterConsumer.accept(message("{\"test\": null}"));
analyticsConsumer.accept(message("{\"test\": null}"));
Collection<Counter> fixedTagsCounters = meterRegistry.find("counter666").tagKeys("foo").counters();
assertThat(fixedTagsCounters.size()).isEqualTo(0);

View File

@@ -14,14 +14,12 @@
* limitations under the License.
*/
package org.springframework.cloud.fn.consumer.counter;
package org.springframework.cloud.fn.consumer.analytics;
import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Tag;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.DefaultResourceLoader;
@@ -36,41 +34,41 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
@TestPropertySource(properties = {
"counter.meter-type=counter",
"counter.name=stocks",
"counter.tag.expression.symbol=#jsonPath(payload,'$.data.symbol')",
"counter.tag.expression.exchange=#jsonPath(payload,'$.data.exchange')"
"analytics.meter-type=counter",
"analytics.name=stocks",
"analytics.tag.expression.symbol=#jsonPath(payload,'$.data.symbol')",
"analytics.tag.expression.exchange=#jsonPath(payload,'$.data.exchange')"
})
public class StockExchangeAnalyticsTests extends CounterConsumerParentTest {
public class StockExchangeAnalyticsTests extends AnalyticsConsumerParentTest {
@Test
public void testCounter() throws IOException {
byte[] messageAppl = StreamUtils.copyToByteArray(
new DefaultResourceLoader().getResource("classpath:/data/stock_appl.json").getInputStream());
counterConsumer.accept(MessageBuilder.withPayload(messageAppl).build());
counterConsumer.accept(MessageBuilder.withPayload(messageAppl).build());
counterConsumer.accept(MessageBuilder.withPayload(messageAppl).build());
analyticsConsumer.accept(MessageBuilder.withPayload(messageAppl).build());
analyticsConsumer.accept(MessageBuilder.withPayload(messageAppl).build());
analyticsConsumer.accept(MessageBuilder.withPayload(messageAppl).build());
byte[] messageVmw = StreamUtils.copyToByteArray(
new DefaultResourceLoader().getResource("classpath:/data/stock_vmw.json").getInputStream());
counterConsumer.accept(MessageBuilder.withPayload(messageVmw).build());
counterConsumer.accept(MessageBuilder.withPayload(messageVmw).build());
analyticsConsumer.accept(MessageBuilder.withPayload(messageVmw).build());
analyticsConsumer.accept(MessageBuilder.withPayload(messageVmw).build());
Collection<Counter> counters = meterRegistry.find("stocks").counters();
assertThat(counters).hasSize(2);
Iterator<Counter> itr = counters.iterator();
Counter applCounter = itr.next();
assertThat(applCounter.count()).isEqualTo(3);
assertThat(applCounter.getId().getTags()).contains(Tag.of("symbol", "AAPL"), Tag.of("exchange", "XNAS"));
Counter vmwCounter = itr.next();
assertThat(vmwCounter.count()).isEqualTo(2);
assertThat(vmwCounter.getId().getTags()).contains(Tag.of("symbol", "VMW"), Tag.of("exchange", "NYSE"));
//Iterator<Counter> itr = counters.iterator();
//
//Counter applCounter = itr.next();
//assertThat(applCounter.count()).isEqualTo(3);
//assertThat(applCounter.getId().getTags()).contains(Tag.of("symbol", "AAPL"), Tag.of("exchange", "XNAS"));
//
//Counter vmwCounter = itr.next();
//assertThat(vmwCounter.count()).isEqualTo(2);
//assertThat(vmwCounter.getId().getTags()).contains(Tag.of("symbol", "VMW"), Tag.of("exchange", "NYSE"));
}
}

View File

@@ -28,31 +28,31 @@ import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.fn.consumer.counter.CounterConsumerConfiguration;
import org.springframework.cloud.fn.consumer.analytics.AnalyticsConsumerConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
/**
* Sample Spring Boot Application that uses the counterConsumer to compute running stats from
* Sample Spring Boot Application that uses the analyticsConsumer to compute running stats from
* stock exchange messages.
*
* Counter configuration:
* <code>
* --counter.meter-type=counter
* --counter.name=stocks
* --counter.tag.expression.symbol=#jsonPath(payload,'$.data.symbol')
* --counter.tag.expression.exchange=#jsonPath(payload,'$.data.exchange')
* --analytics.meter-type=counter
* --analytics.name=stocks
* --analytics.tag.expression.symbol=#jsonPath(payload,'$.data.symbol')
* --analytics.tag.expression.exchange=#jsonPath(payload,'$.data.exchange')
* </code>
*
* Gauge configuration:
* <code>
* --counter.meter-type=gauge
* --counter.name=stocks
* --counter.tag.expression.symbol=#jsonPath(payload,'$.data.symbol')
* --counter.tag.expression.exchange=#jsonPath(payload,'$.data.exchange')
* --counter.amount-expression=#jsonPath(payload,'$.data.volume')
* --analytics.meter-type=gauge
* --analytics.name=stocks
* --analytics.tag.expression.symbol=#jsonPath(payload,'$.data.symbol')
* --analytics.tag.expression.exchange=#jsonPath(payload,'$.data.exchange')
* --analytics.amount-expression=#jsonPath(payload,'$.data.volume')
* </code>
*
* Sample Wavefront configuration:
@@ -65,7 +65,7 @@ import org.springframework.messaging.support.MessageBuilder;
*
* @author Christian Tzolov
*/
@Import(CounterConsumerConfiguration.class)
@Import(AnalyticsConsumerConfiguration.class)
@SpringBootApplication
public class StockExchangeAnalyticsExample {
@@ -74,7 +74,7 @@ public class StockExchangeAnalyticsExample {
}
@Bean
public CommandLineRunner commandLineRunner(Consumer<Message<?>> counterConsumer,
public CommandLineRunner commandLineRunner(Consumer<Message<?>> analyticsConsumer,
MeterRegistry meterRegistry, Supplier<String> stockMessageGenerator) {
// Run every second.
@@ -83,7 +83,7 @@ public class StockExchangeAnalyticsExample {
String message = stockMessageGenerator.get();
// Submit new message using the stockMessageGenerator to generate random stock messages.
counterConsumer.accept(MessageBuilder.withPayload(message).build());
analyticsConsumer.accept(MessageBuilder.withPayload(message).build());
// Print current stock meters
System.out.println(meterRegistry.getMeters().stream()

View File

@@ -1,61 +0,0 @@
/*
* Copyright 2020-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.fn.consumer.counter;
import java.util.function.Function;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ParseException;
import org.springframework.expression.spel.standard.SpelExpression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
/**
* Converter from String to Spring Expression.
* <p>
* TODO: This could be a top level project.
*/
public class StringToSpelConversionFunction implements Function<String, Expression> {
private final SpelExpressionParser parser;
private final EvaluationContext evaluationContext;
public StringToSpelConversionFunction(EvaluationContext evaluationContext) {
this(new SpelExpressionParser(), evaluationContext);
}
public StringToSpelConversionFunction(SpelExpressionParser parser, EvaluationContext evaluationContext) {
this.evaluationContext = evaluationContext;
this.parser = parser;
}
@Override
public Expression apply(String source) {
try {
Expression expression = parser.parseExpression(source);
if (expression instanceof SpelExpression) {
((SpelExpression) expression).setEvaluationContext(evaluationContext);
}
return expression;
}
catch (ParseException e) {
throw new IllegalArgumentException(String.format(
"Could not convert '%s' into a SpEL expression", source), e);
}
}
}

View File

@@ -1,38 +0,0 @@
/*
* Copyright 2020-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.fn.consumer.counter;
import java.util.function.Function;
import org.springframework.core.convert.converter.Converter;
/**
* @author Christian Tzolov
*/
public class ConverterFunctionAdapter<S, T> implements Converter<S, T> {
private Function<S, T> function;
public ConverterFunctionAdapter(Function<S, T> function) {
this.function = function;
}
@Override
public T convert(S s) {
return this.function.apply(s);
}
}

View File

@@ -53,7 +53,7 @@
<module>common/tensorflow-common</module>
<module>consumer/cassandra-consumer</module>
<module>consumer/counter-consumer</module>
<module>consumer/analytics-consumer</module>
<module>consumer/file-consumer</module>
<module>consumer/ftp-consumer</module>
<module>consumer/geode-consumer</module>