io.micrometer.core.instrument.Meter Maven / Gradle / Ivy
Show all versions of micrometer-core Show documentation
/**
* Copyright 2017 Pivotal Software, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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 io.micrometer.core.instrument;
import io.micrometer.core.annotation.Incubating;
import io.micrometer.core.instrument.config.NamingConvention;
import io.micrometer.core.instrument.distribution.HistogramGauges;
import io.micrometer.core.lang.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import static java.util.Collections.singletonList;
/**
* A named and dimensioned producer of one or more measurements.
*
* @author Jon Schneider
*/
public interface Meter {
static Builder builder(String name, Type type, Iterable measurements) {
return new Builder(name, type, measurements);
}
/**
* @return A unique combination of name and tags
*/
Id getId();
/**
* Get a set of measurements. Should always return the same number of measurements and in
* the same order, regardless of the level of activity or the lack thereof.
*
* @return The set of measurements that represents the instantaneous value of this meter.
*/
Iterable measure();
/**
* Custom meters may emit metrics like one of these types without implementing
* the corresponding interface. For example, a heisen-counter like structure
* will emit the same metric as a {@link Counter} but does not have the same
* increment-driven API.
*/
enum Type {
COUNTER,
GAUGE,
LONG_TASK_TIMER,
TIMER,
DISTRIBUTION_SUMMARY,
OTHER;
}
/**
* Match a {@link Meter} by type with series of dedicated functions for specific {@link Meter}s and
* return a result from the matched function.
*
* NOTE: This method contract will change in minor releases if ever a new {@link Meter} type is created.
* In this case only, this is considered a feature. By using this method, you are declaring that
* you want to be sure to handle all types of meters. A breaking API change during the introduction of
* a new {@link Meter} indicates that there is a new meter type for you to consider and the compiler will
* effectively require you to consider it.
*
* @param visitGauge function to apply for {@link Gauge}
* @param visitCounter function to apply for {@link Counter}
* @param visitTimer function to apply for {@link Timer}
* @param visitSummary function to apply for {@link DistributionSummary}
* @param visitLongTaskTimer function to apply for {@link LongTaskTimer}
* @param visitTimeGauge function to apply for {@link TimeGauge}
* @param visitFunctionCounter function to apply for {@link FunctionCounter}
* @param visitFunctionTimer function to apply for {@link FunctionTimer}
* @param visitMeter function to apply as a fallback
* @param return type of function to apply
* @return return value from the applied function
* @since 1.1.0
*/
default T match(Function visitGauge,
Function visitCounter,
Function visitTimer,
Function visitSummary,
Function visitLongTaskTimer,
Function visitTimeGauge,
Function visitFunctionCounter,
Function visitFunctionTimer,
Function visitMeter) {
if (this instanceof TimeGauge) {
return visitTimeGauge.apply((TimeGauge) this);
} else if (this instanceof Gauge) {
return visitGauge.apply((Gauge) this);
} else if (this instanceof Counter) {
return visitCounter.apply((Counter) this);
} else if (this instanceof Timer) {
return visitTimer.apply((Timer) this);
} else if (this instanceof DistributionSummary) {
return visitSummary.apply((DistributionSummary) this);
} else if (this instanceof LongTaskTimer) {
return visitLongTaskTimer.apply((LongTaskTimer) this);
} else if (this instanceof FunctionCounter) {
return visitFunctionCounter.apply((FunctionCounter) this);
} else if (this instanceof FunctionTimer) {
return visitFunctionTimer.apply((FunctionTimer) this);
} else {
return visitMeter.apply(this);
}
}
/**
* Match a {@link Meter} with a series of dedicated functions for specific {@link Meter}s and call the matching
* consumer.
*
* NOTE: This method contract will change in minor releases if ever a new {@link Meter} type is created.
* In this case only, this is considered a feature. By using this method, you are declaring that
* you want to be sure to handle all types of meters. A breaking API change during the introduction of
* a new {@link Meter} indicates that there is a new meter type for you to consider and the compiler will
* effectively require you to consider it.
*
* @param visitGauge function to apply for {@link Gauge}
* @param visitCounter function to apply for {@link Counter}
* @param visitTimer function to apply for {@link Timer}
* @param visitSummary function to apply for {@link DistributionSummary}
* @param visitLongTaskTimer function to apply for {@link LongTaskTimer}
* @param visitTimeGauge function to apply for {@link TimeGauge}
* @param visitFunctionCounter function to apply for {@link FunctionCounter}
* @param visitFunctionTimer function to apply for {@link FunctionTimer}
* @param visitMeter function to apply as a fallback
* @since 1.1.0
*/
default void use(Consumer visitGauge,
Consumer visitCounter,
Consumer visitTimer,
Consumer visitSummary,
Consumer visitLongTaskTimer,
Consumer visitTimeGauge,
Consumer visitFunctionCounter,
Consumer visitFunctionTimer,
Consumer visitMeter) {
if (this instanceof TimeGauge) {
visitTimeGauge.accept((TimeGauge) this);
} else if (this instanceof Gauge) {
visitGauge.accept((Gauge) this);
} else if (this instanceof Counter) {
visitCounter.accept((Counter) this);
} else if (this instanceof Timer) {
visitTimer.accept((Timer) this);
} else if (this instanceof DistributionSummary) {
visitSummary.accept((DistributionSummary) this);
} else if (this instanceof LongTaskTimer) {
visitLongTaskTimer.accept((LongTaskTimer) this);
} else if (this instanceof FunctionCounter) {
visitFunctionCounter.accept((FunctionCounter) this);
} else if (this instanceof FunctionTimer) {
visitFunctionTimer.accept((FunctionTimer) this);
} else {
visitMeter.accept(this);
}
}
/**
* A meter is uniquely identified by its combination of name and tags.
*/
class Id {
private final String name;
private final Tags tags;
private final Type type;
@Nullable
private final Meter.Id syntheticAssociation;
@Nullable
private final String description;
@Nullable
private final String baseUnit;
@Incubating(since = "1.1.0")
Id(String name, Tags tags, @Nullable String baseUnit, @Nullable String description, Type type,
@Nullable Meter.Id syntheticAssociation) {
this.name = name;
this.tags = tags;
this.baseUnit = baseUnit;
this.description = description;
this.type = type;
this.syntheticAssociation = syntheticAssociation;
}
public Id(String name, Tags tags, @Nullable String baseUnit, @Nullable String description, Type type) {
this(name, tags, baseUnit, description, type, null);
}
/**
* Generate a new id with a different name.
*
* @param newName The new name.
* @return A new id with the provided name. The source id remains unchanged.
*/
public Id withName(String newName) {
return new Id(newName, tags, baseUnit, description, type);
}
/**
* Generate a new id with an additional tag. If the key of the provided tag already exists, this overwrites
* the tag value.
*
* @param tag The tag to add.
* @return A new id with the provided tag added. The source id remains unchanged.
*/
public Id withTag(Tag tag) {
return withTags(singletonList(tag));
}
/**
* Generate a new id with an additional tag. If the key of the provided tag already exists, this overwrites
* the tag value.
*
* @param tags The tag to add.
* @return A new id with the provided tags added. The source id remains unchanged.
* @since 1.1.0
*/
public Id withTags(Iterable tags) {
return new Id(name, Tags.concat(getTags(), tags), baseUnit, description, type);
}
/**
* Generate a new id replacing all tags with new ones.
*
* @param tags The tag to add.
* @return A new id with the only the provided tags. The source id remains unchanged.
* @since 1.1.0
*/
public Id replaceTags(Iterable tags) {
return new Id(name, Tags.of(tags), baseUnit, description, type);
}
/**
* Generate a new id with an additional tag with a tag key of "statistic". If the "statistic" tag already exists,
* this overwrites the tag value.
*
* @param statistic The statistic tag to add.
* @return A new id with the provided tag. The source id remains unchanged.
*/
public Id withTag(Statistic statistic) {
return withTag(Tag.of("statistic", statistic.getTagValueRepresentation()));
}
/**
* Generate a new id with a different base unit.
*
* @param newBaseUnit The base unit of the new id.
* @return A new id with the provided base unit.
*/
public Id withBaseUnit(@Nullable String newBaseUnit) {
return new Id(name, tags, newBaseUnit, description, type);
}
/**
* @return The name of this meter.
*/
public String getName() {
return name;
}
/**
* @return A set of dimensions that allows you to break down the name.
*/
public List getTags() {
List tags = new ArrayList<>();
this.tags.forEach(tags::add);
return Collections.unmodifiableList(tags);
}
public Iterable getTagsAsIterable() {
return tags;
}
/**
* @param key The tag key to attempt to match.
* @return A matching tag value, or {@code null} if no tag with the provided key exists on this id.
*/
@Nullable
public String getTag(String key) {
for (Tag tag : tags) {
if (tag.getKey().equals(key))
return tag.getValue();
}
return null;
}
/**
* @return The base unit of measurement for this meter.
*/
@Nullable
public String getBaseUnit() {
return baseUnit;
}
/**
* @param namingConvention The naming convention used to normalize the id's name.
* @return A name that has been stylized to a particular monitoring system's expectations.
*/
public String getConventionName(NamingConvention namingConvention) {
return namingConvention.name(name, type, baseUnit);
}
/**
* Tags that are sorted by key and formatted
*
* @param namingConvention The naming convention used to normalize the id's name.
* @return A list of tags that have been stylized to a particular monitoring system's expectations.
*/
public List getConventionTags(NamingConvention namingConvention) {
return StreamSupport.stream(tags.spliterator(), false)
.map(t -> Tag.of(namingConvention.tagKey(t.getKey()), namingConvention.tagValue(t.getValue())))
.collect(Collectors.toList());
}
/**
* @return A description of the meter's purpose. This description text is published to monitoring systems
* that support description text.
*/
@Nullable
public String getDescription() {
return description;
}
@Override
public String toString() {
return "MeterId{" +
"name='" + name + '\'' +
", tags=" + tags +
'}';
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Meter.Id meterId = (Meter.Id) o;
return Objects.equals(name, meterId.name) && Objects.equals(tags, meterId.tags);
}
@Override
public int hashCode() {
int result = name.hashCode();
result = 31 * result + tags.hashCode();
return result;
}
/**
* The type is used by different registry implementations to structure the exposition
* of metrics to different backends.
*
* @return The meter's type.
*/
public Type getType() {
return type;
}
/**
* For internal use. Indicates that this Id is tied to a meter that is a derivative of another metric.
* For example, percentiles and histogram gauges generated by {@link HistogramGauges} are derivatives
* of a {@link Timer} or {@link DistributionSummary}.
*
* This method may be removed in future minor or major releases if we find a way to mark derivatives in a
* private way that does not have other API compatibility consequences.
*
* @return The meter id of a meter for which this metric is a synthetic derivative.
*/
@Incubating(since = "1.1.0")
@Nullable
public Meter.Id syntheticAssociation() {
return syntheticAssociation;
}
}
/**
* Fluent builder for custom meters.
*/
class Builder {
private final String name;
private final Type type;
private final Iterable measurements;
private Tags tags = Tags.empty();
@Nullable
private String description;
@Nullable
private String baseUnit;
private Builder(String name, Type type, Iterable measurements) {
this.name = name;
this.type = type;
this.measurements = measurements;
}
/**
* @param tags Must be an even number of arguments representing key/value pairs of tags.
* @return The custom meter builder with added tags.
*/
public Builder tags(String... tags) {
return tags(Tags.of(tags));
}
/**
* @param tags Tags to add to the eventual meter.
* @return The custom meter builder with added tags.
*/
public Builder tags(Iterable tags) {
this.tags = this.tags.and(tags);
return this;
}
/**
* @param key The tag key.
* @param value The tag value.
* @return The custom meter builder with a single added tag.
*/
public Builder tag(String key, String value) {
this.tags = tags.and(key, value);
return this;
}
/**
* @param description Description text of the eventual meter.
* @return The custom meter builder with added description.
*/
public Builder description(@Nullable String description) {
this.description = description;
return this;
}
/**
* @param unit Base unit of the eventual meter.
* @return The custom meter builder with added base unit.
*/
public Builder baseUnit(@Nullable String unit) {
this.baseUnit = unit;
return this;
}
/**
* Add the meter to a single registry, or return an existing meter in that registry. The returned
* meter will be unique for each registry, but each registry is guaranteed to only create one meter
* for the same combination of name and tags.
*
* @param registry A registry to add the custom meter to, if it doesn't already exist.
* @return A new or existing custom meter.
*/
public Meter register(MeterRegistry registry) {
return registry.register(new Meter.Id(name, tags, baseUnit, description, type), type, measurements);
}
}
default void close() {
}
}