Skip to content

Refactored MetricsDefinition to use a MetricBuilder and be based off a super class #149

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions src/main/java/software/amazon/cloudwatchlogs/emf/model/Metric.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package software.amazon.cloudwatchlogs.emf.model;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import software.amazon.cloudwatchlogs.emf.serializers.StorageResolutionFilter;
import software.amazon.cloudwatchlogs.emf.serializers.StorageResolutionSerializer;
import software.amazon.cloudwatchlogs.emf.serializers.UnitDeserializer;
import software.amazon.cloudwatchlogs.emf.serializers.UnitSerializer;

/** Abstract immutable (except for name) class that all Metrics are based on. */
@Getter
public abstract class Metric<V> {
@JsonProperty("Name")
@Setter(AccessLevel.PROTECTED)
@NonNull
protected String name;

@JsonProperty("Unit")
@JsonSerialize(using = UnitSerializer.class)
@JsonDeserialize(using = UnitDeserializer.class)
protected Unit unit;

@JsonProperty("StorageResolution")
@JsonInclude(
value = JsonInclude.Include.CUSTOM,
valueFilter =
StorageResolutionFilter.class) // Do not serialize when valueFilter is true
@JsonSerialize(using = StorageResolutionSerializer.class)
protected StorageResolution storageResolution;

@JsonIgnore @Getter protected V values;

/** @return the values of this metric formatted to be flushed */
protected Object getFormattedValues() {
return this.getValues();
}

/**
* Creates a Metric with the first {@code size} values of the current metric
*
* @param size the maximum size of the returned metric's values
* @return a Metric with the first {@code size} values of the current metric.
*/
protected abstract Metric getMetricValuesUnderSize(int size);

/**
* Creates a Metric all metrics after the first {@code size} values of the current metric. If
* there are less than {@code size} values, null is returned.
*
* @param size the maximum size of the returned metric's values
* @return a Metric with the all metrics after the first {@code size} values of the current
* metric. If there are less than {@code size} values, null is returned.
*/
protected abstract Metric getMetricValuesOverSize(int size);

public abstract static class MetricBuilder<V, T extends MetricBuilder<V, T>> extends Metric<V> {

protected abstract T getThis();

/**
* Adds a value to the metric.
*
* @param value the value to be added to this metric
*/
abstract T addValue(double value);

/**
* Builds the metric.
*
* @return the built metric
*/
abstract Metric build();

protected T name(@NonNull String name) {
this.name = name;
return getThis();
}

public T unit(Unit unit) {
this.unit = unit;
return getThis();
}

public T storageResolution(StorageResolution storageResolution) {
this.storageResolution = storageResolution;
return getThis();
}

protected Metric getMetricValuesOverSize(int size) {
return build().getMetricValuesOverSize(size);
}

protected Metric getMetricValuesUnderSize(int size) {
return build().getMetricValuesUnderSize(size);
}

protected Object getFormattedValues() {
return build().getFormattedValues();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,70 +16,101 @@

package software.amazon.cloudwatchlogs.emf.model;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import software.amazon.cloudwatchlogs.emf.serializers.StorageResolutionFilter;
import software.amazon.cloudwatchlogs.emf.serializers.StorageResolutionSerializer;
import software.amazon.cloudwatchlogs.emf.serializers.UnitDeserializer;
import software.amazon.cloudwatchlogs.emf.serializers.UnitSerializer;

/** Represents the MetricDefinition of the EMF schema. */
@AllArgsConstructor
class MetricDefinition {
@NonNull
@Getter
@JsonProperty("Name")
private String name;
public class MetricDefinition extends Metric<List<Double>> {

@Getter
@JsonProperty("Unit")
@JsonSerialize(using = UnitSerializer.class)
@JsonDeserialize(using = UnitDeserializer.class)
private Unit unit;

@Getter
@Setter
@JsonProperty("StorageResolution")
@JsonInclude(
value = JsonInclude.Include.CUSTOM,
valueFilter =
StorageResolutionFilter.class) // Do not serialize when valueFilter is true
@JsonSerialize(using = StorageResolutionSerializer.class)
public StorageResolution storageResolution;

@JsonIgnore @NonNull @Getter private List<Double> values;
private MetricDefinition(
@NonNull String name,
Unit unit,
StorageResolution storageResolution,
List<Double> values) {
this.unit = unit;
this.storageResolution = storageResolution;
this.values = values;
this.name = name;
}

MetricDefinition(String name) {
this(name, Unit.NONE, StorageResolution.STANDARD, new ArrayList<>());
MetricDefinition(Unit unit, StorageResolution storageResolution, List<Double> values) {
this.unit = unit;
this.storageResolution = storageResolution;
this.values = values;
}

MetricDefinition(String name, double value) {
this(name, Unit.NONE, StorageResolution.STANDARD, value);
@Override
protected Metric getMetricValuesUnderSize(int size) {
List<Double> subList = values.subList(0, Math.min(values.size(), size));
MetricDefinition metric =
MetricDefinition.builder()
.unit(unit)
.storageResolution(storageResolution)
.values(subList)
.build();
metric.setName(name);
return metric;
}

MetricDefinition(String name, Unit unit, double value) {
this(name, unit, StorageResolution.STANDARD, new ArrayList<>(Arrays.asList(value)));
@Override
protected Metric getMetricValuesOverSize(int size) {
if (size > values.size()) {
return null;
}
List<Double> subList = values.subList(size, values.size());
MetricDefinition metric =
MetricDefinition.builder()
.name(name)
.unit(unit)
.storageResolution(storageResolution)
.values(subList)
.build();
return metric;
}

MetricDefinition(String name, StorageResolution storageResolution, double value) {
this(name, Unit.NONE, storageResolution, new ArrayList<>(Arrays.asList(value)));
public static MetricDefinitionBuilder builder() {
return new MetricDefinitionBuilder();
}

MetricDefinition(String name, Unit unit, StorageResolution storageResolution, double value) {
this(name, unit, storageResolution, new ArrayList<>(Arrays.asList(value)));
/**
* @return the values of this metric, simplified to a double instead of a list if there is only
* one value
*/
@Override
protected Object getFormattedValues() {
return values.size() == 1 ? values.get(0) : values;
}

void addValue(double value) {
values.add(value);
public static class MetricDefinitionBuilder
extends Metric.MetricBuilder<List<Double>, MetricDefinitionBuilder> {

@Override
protected MetricDefinitionBuilder getThis() {
return this;
}

public MetricDefinitionBuilder() {
this.values = new ArrayList<>();
}

@Override
public MetricDefinitionBuilder addValue(double value) {
this.values.add(value);
return this;
}

public MetricDefinitionBuilder values(@NonNull List<Double> values) {
this.values = values;
return this;
}

@Override
public MetricDefinition build() {
if (name == null) {
return new MetricDefinition(unit, storageResolution, values);
}
return new MetricDefinition(name, unit, storageResolution, values);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.stream.Collectors;
import lombok.*;
import software.amazon.cloudwatchlogs.emf.exception.DimensionSetExceededException;
import software.amazon.cloudwatchlogs.emf.exception.InvalidMetricException;

/** Represents the MetricDirective part of the EMF schema. */
@AllArgsConstructor
Expand All @@ -32,7 +33,7 @@ class MetricDirective {
@JsonProperty("Namespace")
private String namespace;

@JsonIgnore @Setter @Getter @With private Map<String, MetricDefinition> metrics;
@JsonIgnore @Setter @Getter @With private Map<String, Metric> metrics;

@JsonIgnore
@Getter(AccessLevel.PROTECTED)
Expand Down Expand Up @@ -85,16 +86,28 @@ void putMetric(String key, double value, Unit unit, StorageResolution storageRes
metrics.compute(
key,
(k, v) -> {
if (v == null) return new MetricDefinition(key, unit, storageResolution, value);
else {
v.addValue(value);
if (v == null) {
MetricDefinition.MetricDefinitionBuilder builder =
MetricDefinition.builder()
.name(k)
.unit(unit)
.storageResolution(storageResolution)
.addValue(value);
return builder;
} else if (v instanceof Metric.MetricBuilder) {
((Metric.MetricBuilder) v).addValue(value);
return v;
} else {
throw new InvalidMetricException(
String.format(
"New metrics cannot be put to the name: \"%s\", because it has been set to an immutable metric type.",
k));
}
});
}

@JsonProperty("Metrics")
Collection<MetricDefinition> getAllMetrics() {
Collection<Metric> getAllMetrics() {
return metrics.values();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,40 +323,29 @@ public List<String> serialize() throws JsonProcessingException {
return Arrays.asList(this.rootNode.serialize());
} else {
List<RootNode> nodes = new ArrayList<>();
Map<String, MetricDefinition> metrics = new HashMap<>();
Queue<MetricDefinition> metricDefinitions =
new LinkedList<>(rootNode.metrics().values());
while (!metricDefinitions.isEmpty()) {
MetricDefinition metric = metricDefinitions.poll();
Map<String, Metric> metrics = new HashMap<>();
Queue<Metric> metricQueue = new LinkedList<>(rootNode.metrics().values());
while (!metricQueue.isEmpty()) {
Metric metric = metricQueue.poll();

if (metrics.size() == Constants.MAX_METRICS_PER_EVENT
|| metrics.containsKey(metric.getName())) {
nodes.add(buildRootNode(metrics));
metrics = new HashMap<>();
}

if (metric.getValues().size() <= Constants.MAX_DATAPOINTS_PER_METRIC) {
metrics.put(metric.getName(), metric);
} else {
metrics.put(
metric.getName(),
new MetricDefinition(
metric.getName(),
metric.getUnit(),
metric.getStorageResolution(),
metric.getValues()
.subList(0, Constants.MAX_DATAPOINTS_PER_METRIC)));
metricDefinitions.offer(
new MetricDefinition(
metric.getName(),
metric.getUnit(),
metric.getStorageResolution(),
metric.getValues()
.subList(
Constants.MAX_DATAPOINTS_PER_METRIC,
metric.getValues().size())));
Metric overSizeMetric =
metric.getMetricValuesOverSize(Constants.MAX_DATAPOINTS_PER_METRIC);
Metric underSizeMetric =
metric.getMetricValuesUnderSize(Constants.MAX_DATAPOINTS_PER_METRIC);

metrics.put(metric.getName(), underSizeMetric);

if (overSizeMetric != null) {
metricQueue.offer(overSizeMetric);
}
}

if (!metrics.isEmpty()) {
nodes.add(buildRootNode(metrics));
}
Expand All @@ -368,7 +357,7 @@ public List<String> serialize() throws JsonProcessingException {
}
}

private RootNode buildRootNode(Map<String, MetricDefinition> metrics) {
private RootNode buildRootNode(Map<String, Metric> metrics) {
Metadata metadata = rootNode.getAws();
MetricDirective md = metadata.getCloudWatchMetrics().get(0);
Metadata clonedMetadata =
Expand All @@ -379,6 +368,8 @@ private RootNode buildRootNode(Map<String, MetricDefinition> metrics) {
private boolean anyMetricWithTooManyDataPoints(RootNode node) {
return node.metrics().values().stream()
.anyMatch(
metric -> metric.getValues().size() > Constants.MAX_DATAPOINTS_PER_METRIC);
metric ->
metric.getMetricValuesOverSize(Constants.MAX_DATAPOINTS_PER_METRIC)
!= null);
}
}
Loading