diff --git a/sermant-agentcore/sermant-agentcore-config/config/config.properties b/sermant-agentcore/sermant-agentcore-config/config/config.properties index 5b309ba2cd..84c28eeefa 100644 --- a/sermant-agentcore/sermant-agentcore-config/config/config.properties +++ b/sermant-agentcore/sermant-agentcore-config/config/config.properties @@ -36,6 +36,8 @@ agent.service.dynamic.config.enable=true agent.service.httpserver.enable=false # xDS service switch agent.service.xds.service.enable=false +# Metric service switch +agent.service.metric.enable=false #============================= Event configuration =============================# # Event switch event.enable=false diff --git a/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/ServiceConfig.java b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/ServiceConfig.java index 4e83d045a7..f32af4434f 100644 --- a/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/ServiceConfig.java +++ b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/ServiceConfig.java @@ -49,6 +49,9 @@ public class ServiceConfig implements BaseConfig { @ConfigFieldKey("xds.service.enable") private boolean xdsServiceEnable = false; + @ConfigFieldKey("metric.enable") + private boolean metricEnable = false; + public boolean isHeartBeatEnable() { return heartBeatEnable; } @@ -105,6 +108,14 @@ public void setXdsServiceEnable(boolean xdsServiceEnable) { this.xdsServiceEnable = xdsServiceEnable; } + public boolean isMetricEnable() { + return metricEnable; + } + + public void setMetricEnable(boolean metricEnable) { + this.metricEnable = metricEnable; + } + /** * Check whether the service of the given class name is enabled. * @@ -133,6 +144,9 @@ public boolean checkServiceEnable(String serviceName) { if (ServiceManager.XDS_CORE_SERVICE_IMPL.equals(serviceName)) { return isXdsServiceEnable(); } + if (ServiceManager.METRIC_SERVICE_IMPL.equals(serviceName)) { + return isMetricEnable(); + } return false; } } diff --git a/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/ServiceManager.java b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/ServiceManager.java index d3cfa32563..1e5c10a307 100644 --- a/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/ServiceManager.java +++ b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/ServiceManager.java @@ -91,6 +91,12 @@ public class ServiceManager { public static final String XDS_CORE_SERVICE_IMPL = "io.sermant.implement.service.xds.XdsCoreServiceImpl"; + /** + * Metric Service Discover + */ + public static final String METRIC_SERVICE_IMPL = + "io.sermant.implement.service.metrics.MeterMetricServiceImpl"; + /** * logger */ diff --git a/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/metric/api/Counter.java b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/metric/api/Counter.java new file mode 100644 index 0000000000..acc3660bbb --- /dev/null +++ b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/metric/api/Counter.java @@ -0,0 +1,51 @@ +/* + * Copyright 2017 VMware, 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. + */ + +/* + * Based on io/micrometer/core/instrument/Counter.java + * from the Micrometer project. + */ + +package io.sermant.core.service.metric.api; + +/** + * metric Counter interface + * + * @author zwmagic + * @since 2024-08-16 + */ +public interface Counter { + /** + * Update the counter by one. + */ + default void increment() { + increment(1.0); + } + + /** + * Update the counter by {@code amount}. + * + * @param amount Amount to add to the counter. + */ + void increment(double amount); + + /** + * get the counter value + * + * @return The cumulative count since this counter was created. + */ + double count(); +} diff --git a/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/metric/api/DistributionStatisticConfig.java b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/metric/api/DistributionStatisticConfig.java new file mode 100644 index 0000000000..8545db5382 --- /dev/null +++ b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/metric/api/DistributionStatisticConfig.java @@ -0,0 +1,226 @@ +/* + * Copyright 2017 VMware, 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. + */ + +/* + * Based on io/micrometer/core/instrument/distribution/DistributionStatisticConfig.java + * from the Micrometer project. + */ + +package io.sermant.core.service.metric.api; + +import java.time.Duration; + +/** + * DistributionStatisticConfig + * + * @author zwmagic + * @since 2024-08-17 + */ +public class DistributionStatisticConfig { + private static final Integer EXPIRY_MINUTE = 2; + + /** + * Defines a static final constant named DEFAULT, which represents a distribution statistic configuration. This + * configuration controls and describes various aspects of data distribution statistics, such as the publication of + * percentile histograms, the precision of percentiles, and the expected value range. These configurations are + * useful for monitoring and analyzing data distributions, particularly in system performance monitoring and log + * analysis. + */ + public static final DistributionStatisticConfig DEFAULT = + new DistributionStatisticConfig().publishPercentileHistogram(false) + .percentilePrecision(1).minimumExpectedValue(1.0).maximumExpectedValue(Double.POSITIVE_INFINITY) + .distributionStatisticExpiry(Duration.ofMinutes(EXPIRY_MINUTE)) + .distributionStatisticBufferLength(3); + + private Boolean percentileHistogram; + + private double[] percentiles; + + private Integer percentilePrecision; + + private Double minimumExpectedValue; + + private Double maximumExpectedValue; + + private Duration expiry; + + private Integer bufferLength; + + /** + * Gets the percentile histogram flag. + * + * @return the percentile histogram flag + */ + public Boolean getPercentileHistogram() { + return percentileHistogram; + } + + /** + * Gets the array of percentiles. + * + * @return the array of percentiles + */ + public double[] getPercentiles() { + return percentiles; + } + + /** + * Gets the precision for percentiles. + * + * @return the precision for percentiles + */ + public Integer getPercentilePrecision() { + return percentilePrecision; + } + + /** + * Gets the minimum expected value. + * + * @return the minimum expected value + */ + public Double getMinimumExpectedValue() { + return minimumExpectedValue; + } + + /** + * Gets the maximum expected value. + * + * @return the maximum expected value + */ + public Double getMaximumExpectedValue() { + return maximumExpectedValue; + } + + /** + * Gets the expiry duration. + * + * @return the expiry duration + */ + public Duration getExpiry() { + return expiry; + } + + /** + * Gets the length of the buffer. + * + * @return the length of the buffer + */ + public Integer getBufferLength() { + return bufferLength; + } + + /** + * Produces an additional time series for each requested percentile. This percentile is computed locally, and so + * can't be aggregated with percentiles computed across other dimensions (e.g. in a different instance). Use + * {@link #publishPercentileHistogram()} to publish a histogram that can be used to generate aggregable percentile + * approximations. + * + * @param percentilesValue Percentiles to compute and publish. The 95th percentile should be expressed as + * {@code 0.95}. + * @return DistributionStatisticConfig + */ + public DistributionStatisticConfig publishPercentiles(double[] percentilesValue) { + this.percentiles = percentilesValue; + return this; + } + + /** + * Adds histogram buckets used to generate aggregable percentile approximations in monitoring systems that have + * query facilities to do so (e.g. Prometheus' {@code histogram_quantile}, Atlas' {@code :percentiles}). + * + * @return DistributionStatisticConfig + */ + public DistributionStatisticConfig publishPercentileHistogram() { + return publishPercentileHistogram(true); + } + + /** + * Adds histogram buckets used to generate aggregable percentile approximations in monitoring systems that have + * query facilities to do so (e.g. Prometheus' {@code histogram_quantile}, Atlas' {@code :percentiles}). + * + * @param enabled Determines whether percentile histograms should be published. + * @return DistributionStatisticConfig + */ + public DistributionStatisticConfig publishPercentileHistogram(Boolean enabled) { + this.percentileHistogram = enabled; + return this; + } + + /** + * Determines the number of digits of precision to maintain on the dynamic range histogram used to compute + * percentile approximations. The higher the degrees of precision, the more accurate the approximation is at the + * cost of more memory. + * + * @param digitsOfPrecision The digits of precision to maintain for percentile approximations. + * @return DistributionStatisticConfig + */ + public DistributionStatisticConfig percentilePrecision(Integer digitsOfPrecision) { + this.percentilePrecision = digitsOfPrecision; + return this; + } + + /** + * Sets the minimum value that this distribution summary is expected to observe. Sets a lower bound on histogram + * buckets that are shipped to monitoring systems that support aggregable percentile approximations. + * + * @param min The minimum value that this distribution summary is expected to observe. + * @return This builder. + */ + public DistributionStatisticConfig minimumExpectedValue(Double min) { + this.minimumExpectedValue = min; + return this; + } + + /** + * Sets the maximum value that this distribution summary is expected to observe. Sets an upper bound on histogram + * buckets that are shipped to monitoring systems that support aggregable percentile approximations. + * + * @param max The maximum value that this distribution summary is expected to observe. + * @return DistributionStatisticConfig + */ + public DistributionStatisticConfig maximumExpectedValue(Double max) { + this.maximumExpectedValue = max; + return this; + } + + /** + * Statistics emanating from a distribution summary like max, percentiles, and histogram counts decay over time to + * give greater weight to recent samples (exception: histogram counts are cumulative for those systems that expect + * cumulative histogram buckets). Samples are accumulated to such statistics in ring buffers which rotate after this + * expiry, with a buffer length of {@link #distributionStatisticBufferLength(Integer)}. + * + * @param expiryDuration The amount of time samples are accumulated to a histogram before it is reset and rotated. + * @return DistributionStatisticConfig + */ + public DistributionStatisticConfig distributionStatisticExpiry(Duration expiryDuration) { + this.expiry = expiryDuration; + return this; + } + + /** + * Statistics emanating from a distribution summary like max, percentiles, and histogram counts decay over time to + * give greater weight to recent samples (exception: histogram counts are cumulative for those systems that expect + * cumulative histogram buckets). Samples are accumulated to such statistics in ring buffers which rotate after + * {@link #distributionStatisticExpiry(Duration)}, with this buffer length. + * + * @param bufferLengthValue The number of histograms to keep in the ring buffer. + * @return DistributionStatisticConfig + */ + public DistributionStatisticConfig distributionStatisticBufferLength(Integer bufferLengthValue) { + this.bufferLength = bufferLengthValue; + return this; + } +} diff --git a/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/metric/api/Gauge.java b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/metric/api/Gauge.java new file mode 100644 index 0000000000..74320a493b --- /dev/null +++ b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/metric/api/Gauge.java @@ -0,0 +1,85 @@ +/* + * Copyright 2017 VMware, 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. + */ + +/* + * Based on io/micrometer/core/instrument/MeterRegistry.java + * from the Micrometer project. + */ + +package io.sermant.core.service.metric.api; + +import java.util.Collection; +import java.util.Map; +import java.util.function.ToDoubleFunction; + +/** + * metric + * + * @author zwmagic + * @since 2024-08-16 + */ +public interface Gauge { + /** + * Register a gauge that reports the value of the {@link Number}. + * + * @param number Thread-safe implementation of {@link Number} used to access the value. + * @param The type of the state object from which the gauge value is extracted. + * @return The number that was passed in so the registration can be done as part of an assignment statement. + */ + default T gaugeNumber(Number number) { + return (T) gaugeState(number, Number::doubleValue); + } + + /** + * Register a gauge that reports the size of the {@link Collection}. The registration will keep a weak reference to + * the collection so it will not prevent garbage collection. The collection implementation used should be thread + * safe. Note that calling {@link Collection#size()} can be expensive for some collection implementations and should + * be considered before registering. + * + * @param collection Thread-safe implementation of {@link Collection} used to access the value. + * @param The type of the state object from which the gauge value is extracted. + * @return The Collection that was passed in so the registration can be done as part of an assignment statement. + */ + default > T gaugeCollectionSize(T collection) { + return gaugeState(collection, Collection::size); + } + + /** + * Register a gauge that reports the size of the {@link Map}. The registration will keep a weak reference to the + * collection so it will not prevent garbage collection. The collection implementation used should be thread safe. + * Note that calling {@link Map#size()} can be expensive for some collection implementations and should be + * considered before registering. + * + * @param map Thread-safe implementation of {@link Map} used to access the value. + * @param The type of the state object from which the gauge value is extracted. + * @return The Map that was passed in so the registration can be done as part of an assignment statement. + */ + default > T gaugeMapSize(T map) { + return gaugeState(map, Map::size); + } + + /** + * Register a gauge that reports the value of the object after the function {@code valueFunction} is applied. The + * registration will keep a weak reference to the object so it will not prevent garbage collection. Applying + * {@code valueFunction} on the object should be thread safe. + * + * @param stateObject State object used to compute a value. + * @param valueFunction Function that produces an instantaneous gauge value from the state object. + * @param The type of the state object from which the gauge value is extracted. + * @return The state object that was passed in so the registration can be done as part of an assignment statement. + */ + T gaugeState(T stateObject, ToDoubleFunction valueFunction); +} diff --git a/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/metric/api/Metric.java b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/metric/api/Metric.java new file mode 100644 index 0000000000..9385ecf9ce --- /dev/null +++ b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/metric/api/Metric.java @@ -0,0 +1,74 @@ +/* + * Copyright 2017 VMware, 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. + */ + +/* + * Based on io/micrometer/core/instrument/MeterRegistry.java + * from the Micrometer project. + */ + +package io.sermant.core.service.metric.api; + +/** + * The Metric interface defines methods for creating different types of metrics objects, used for monitoring and + * measuring application performance metrics. It supports Counter, Gauge, and Timer metric types. + * + * @author zwmagic + * @since 2024-08-16 + */ +public interface Metric { + /** + * Creates a tagged counter with a description to record the count of a specific metric. + * + * @param metricName the name of the metric + * @param tags tags that further refine the metric data + * @param description a description of the counter's purpose + * @return the created counter object + */ + Counter counter(String metricName, Tags tags, String description); + + /** + * Creates a tagged gauge with a description to represent the current value of a specific metric. + * + * @param metricName the name of the metric + * @param tags tags that further refine the metric data + * @param description a description of the gauge's purpose + * @return the created gauge object + */ + Gauge gauge(String metricName, Tags tags, String description); + + /** + * Creates a tagged timer with a description to measure the execution time of a specific metric. + * + * @param metricName the name of the metric + * @param tags tags that further refine the metric data + * @param description a description of the timer's purpose + * @return the created timer object + */ + Timer timer(String metricName, Tags tags, String description); + + /** + * Creates a Summary metric to track distribution statistics. + * + * @param metricName the name of the metric to identify the monitoring metric + * @param tags a set of tags for categorizing and filtering the metric + * @param description a description to explain the purpose and meaning of the metric + * @param distributionStatisticConfig the configuration defining what statistics to track + * @return the created Summary metric instance + */ + Summary summary(String metricName, Tags tags, String description, + DistributionStatisticConfig distributionStatisticConfig); +} + diff --git a/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/metric/api/MetricService.java b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/metric/api/MetricService.java new file mode 100644 index 0000000000..ccb154d3a1 --- /dev/null +++ b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/metric/api/MetricService.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2024-2024 Sermant Authors. 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 io.sermant.core.service.metric.api; + +import io.sermant.core.service.BaseService; + +/** + * MetricService + * + * @author zwmagic + * @since 2024-08-19 + */ +public interface MetricService extends BaseService { + + /** + * Creates a counter to measure the number of occurrences of an event. + * + * @param metricName The name of the metric. + * @return The created counter instance. + */ + Counter counter(String metricName); + + /** + * Creates a tagged counter to measure the number of occurrences of an event with specific tags. + * + * @param metricName The name of the metric. + * @param tagKey The tag key. + * @param tagValue The tag value. + * @return The created tagged counter instance. + */ + Counter counter(String metricName, String tagKey, String tagValue); + + /** + * Creates a counter with a set of tags to measure the number of occurrences of an event with specific tags. + * + * @param metricName The name of the metric. + * @param tags The set of tags. + * @return The created counter instance with tags. + */ + Counter counter(String metricName, Tags tags); + + /** + * Creates a counter with a set of tags and a description to measure the number of occurrences of an event. + * + * @param metricName The name of the metric. + * @param tags The set of tags. + * @param description The description of the counter. + * @return The created counter instance with tags and description. + */ + Counter counter(String metricName, Tags tags, String description); + + /** + * Creates a gauge to measure the instantaneous value of a metric. + * + * @param metricName The name of the metric. + * @return The created gauge instance. + */ + Gauge gauge(String metricName); + + /** + * Creates a tagged gauge to measure the instantaneous value of a metric with specific tags. + * + * @param metricName The name of the metric. + * @param tagKey The tag key. + * @param tagValue The tag value. + * @return The created tagged gauge instance. + */ + Gauge gauge(String metricName, String tagKey, String tagValue); + + /** + * Creates a gauge with a set of tags to measure the instantaneous value of a metric with specific tags. + * + * @param metricName The name of the metric. + * @param tags The set of tags. + * @return The created gauge instance with tags. + */ + Gauge gauge(String metricName, Tags tags); + + /** + * Creates a gauge with a set of tags and a description to measure the instantaneous value of a metric. + * + * @param metricName The name of the metric. + * @param tags The set of tags. + * @param description The description of the gauge. + * @return The created gauge instance with tags and description. + */ + Gauge gauge(String metricName, Tags tags, String description); + + /** + * Creates a timer to measure the duration of an operation. + * + * @param metricName The name of the metric. + * @return The created timer instance. + */ + Timer timer(String metricName); + + /** + * Creates a tagged timer to measure the duration of an operation with specific tags. + * + * @param metricName The name of the metric. + * @param tagKey The tag key. + * @param tagValue The tag value. + * @return The created tagged timer instance. + */ + Timer timer(String metricName, String tagKey, String tagValue); + + /** + * Creates a timer with a set of tags to measure the duration of an operation with specific tags. + * + * @param metricName The name of the metric. + * @param tags The set of tags. + * @return The created timer instance with tags. + */ + Timer timer(String metricName, Tags tags); + + /** + * Creates a timer with a set of tags and a description to measure the duration of an operation. + * + * @param metricName The name of the metric. + * @param tags The set of tags. + * @param description The description of the timer. + * @return The created timer instance with tags and description. + */ + Timer timer(String metricName, Tags tags, String description); + + /** + * Creates a Summary metric without any tags. + * + * @param metricName the name of the metric + * @return the created Summary metric + */ + Summary summary(String metricName); + + /** + * Creates a Summary metric and adds a single tag to it. + * + * @param metricName the name of the metric + * @param tagKey the key of the tag + * @param tagValue the value of the tag + * @return the created Summary metric + */ + Summary summary(String metricName, String tagKey, String tagValue); + + /** + * Creates a Summary metric and adds a set of tags to it. + * + * @param metricName the name of the metric + * @param tags the set of tags + * @return the created Summary metric + */ + Summary summary(String metricName, Tags tags); + + /** + * Creates a highly customizable Summary metric that can include tags, a description, and distribution statistics + * configuration. + * + * @param metricName the name of the metric + * @param tags the set of tags + * @param description the description of the metric + * @param distributionStatisticConfig the distribution statistics configuration + * @return the created Summary metric + */ + Summary summary(String metricName, Tags tags, String description, + DistributionStatisticConfig distributionStatisticConfig); + +} diff --git a/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/metric/api/Summary.java b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/metric/api/Summary.java new file mode 100644 index 0000000000..79ea1e6842 --- /dev/null +++ b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/metric/api/Summary.java @@ -0,0 +1,69 @@ +/* + * Copyright 2017 VMware, 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. + */ + +/* + * Based on io/micrometer/core/instrument/DistributionSummary.java + * from the Micrometer project. + */ + +package io.sermant.core.service.metric.api; + +/** + * The Summary interface is used to maintain statistical information about a series of events. + * + * @author zwmagic + * @since 2024-08-17 + */ +public interface Summary { + /** + * Updates the statistics kept by the summary with the specified amount. + * + * @param amount Amount for an event being measured. For example, if the size in bytes of responses from a server. + * If the amount is less than 0, the value will be dropped. + */ + void record(double amount); + + /** + * Returns the number of times that record has been called since this timer was created. + * + * @return The number of recorded events. + */ + long count(); + + /** + * Returns the total amount of all recorded events. + * + * @return The total amount. + */ + double totalAmount(); + + /** + * Returns the distribution average for all recorded events. + * + * @return The average event size. + */ + default double mean() { + long count = count(); + return count == 0 ? 0 : totalAmount() / count; + } + + /** + * Returns the maximum time of a single event. + * + * @return The maximum event size. + */ + double max(); +} diff --git a/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/metric/api/Tags.java b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/metric/api/Tags.java new file mode 100644 index 0000000000..e58b301c96 --- /dev/null +++ b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/metric/api/Tags.java @@ -0,0 +1,123 @@ +/* + * Copyright 2017 VMware, 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. + */ + +/* + * Based on io.micrometer.core.instrument/Tags.java + * from the Micrometer project. + */ + +package io.sermant.core.service.metric.api; + +import io.sermant.core.utils.MapUtils; +import io.sermant.core.utils.StringUtils; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * metric tags + * + * @author zwmagic + * @since 2024-08-16 + */ +public final class Tags { + private final Map tags = new HashMap<>(); + + private Tags() { + } + + /** + * Creates an empty Tags object. + * + * @return An empty Tags object. + */ + public static Tags of() { + return new Tags(); + } + + /** + * Creates a Tags object and adds a key-value pair. + * + * @param key The key. + * @param value The value. + * @return A Tags object with the added key-value pair. + */ + public static Tags of(String key, Object value) { + return of().add(key, value); + } + + /** + * Creates a Tags object and copies the content from another Tags object. + * + * @param tags The Tags object to copy. + * @return A new Tags object with copied content. + */ + public static Tags of(Tags tags) { + return of(tags.getTags()); + } + + /** + * Creates a Tags object initialized with a given map of key-value pairs. + * + * @param tags The map of key-value pairs. + * @return An initialized Tags object. + */ + public static Tags of(Map tags) { + Tags result = of(); + if (!MapUtils.isEmpty(tags)) { + result.tags.putAll(tags); + } + return result; + } + + /** + * Adds a key-value pair to the current Tags object. + * + * @param key The key. + * @param value The value. + * @return The current Tags object, for chaining calls. + */ + public Tags add(String key, Object value) { + if (StringUtils.isEmpty(key) || value == null) { + return this; + } + String valueStr = value instanceof String ? (String) value : String.valueOf(value); + if (StringUtils.isEmpty(valueStr)) { + return this; + } + tags.put(key, valueStr); + return this; + } + + /** + * Gets the number of key-value pairs in the current Tags object. + * + * @return The number of key-value pairs. + */ + public int getSize() { + return tags.size(); + } + + /** + * Retrieves all key-value pairs in the current Tags object as an unmodifiable map. + * + * @return An unmodifiable map of key-value pairs. + */ + public Map getTags() { + return Collections.unmodifiableMap(tags); + } +} diff --git a/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/metric/api/Timer.java b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/metric/api/Timer.java new file mode 100644 index 0000000000..7d0a36c850 --- /dev/null +++ b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/metric/api/Timer.java @@ -0,0 +1,77 @@ +/* + * Copyright 2017 VMware, 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. + */ + +/* + * Based on io/micrometer/core/instrument/Timer.java + * from the Micrometer project. + */ + +package io.sermant.core.service.metric.api; + +import java.time.Duration; +import java.util.concurrent.TimeUnit; + +/** + * A timer interface for measuring the duration of events. + * + * @author zwmagic + * @since 2024-08-16 + */ +public interface Timer { + /** + * Starts the timer. + * + * @return The Timer object for further operations. + */ + Timer start(); + + /** + * Stops the timer and returns the elapsed time. + * + * @return The elapsed time in milliseconds. + */ + long stop(); + + /** + * Records a duration. + * + * @param duration The duration object to be recorded. + */ + void record(Duration duration); + + /** + * Updates the statistics stored by the timer with a specified amount. + * + * @param amount The duration of a single event; if amount is less than 0, the value will be removed. + * @param unit The time unit. + */ + void record(long amount, TimeUnit unit); + + /** + * Gets the number of times that stop has been called on this timer. + * + * @return The number of stop operations. + */ + long count(); + + /** + * Gets the total time of recorded events. + * + * @param unit The base unit of time to scale the total to. + * @return The total time of recorded events. + */ + double totalTime(TimeUnit unit); +} diff --git a/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/metric/config/MetricConfig.java b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/metric/config/MetricConfig.java new file mode 100644 index 0000000000..e0d249b17e --- /dev/null +++ b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/service/metric/config/MetricConfig.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2024-2024 Sermant Authors. 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 io.sermant.core.service.metric.config; + +import io.sermant.core.config.common.BaseConfig; +import io.sermant.core.config.common.ConfigTypeKey; + +/** + * Metric Configuration + * + * @author zwmagic + * @since 2024-08-19 + */ +@ConfigTypeKey("metric") +public class MetricConfig implements BaseConfig { + private static final int MAXIMUM_TIME_SERIES_VALUE = 1000; + + /** + * The metric type, currently supports prometheus. + */ + private String type = "prometheus"; + + /** + * The maximum number of metrics. + */ + private Integer maxTimeSeries = MAXIMUM_TIME_SERIES_VALUE; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public void setMaxTimeSeries(Integer maximumTimeSeries) { + this.maxTimeSeries = maximumTimeSeries; + } + + public Integer getMaxTimeSeries() { + return maxTimeSeries; + } +} diff --git a/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/utils/ReflectUtils.java b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/utils/ReflectUtils.java index 4510c547ea..5507a4f73b 100644 --- a/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/utils/ReflectUtils.java +++ b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/utils/ReflectUtils.java @@ -300,6 +300,36 @@ private static String buildMethodKey(Class clazz, String methodName, Class return sb.append(")").toString(); } + /** + * Set static field value + * + * @param clazz target class + * @param fieldName field name + * @param value value + * @return set result + */ + public static boolean setStaticFieldValue(Class clazz, String fieldName, Object value) { + if (clazz == null || StringUtils.isBlank(fieldName)) { + return false; + } + + Field field = getField(clazz, fieldName); + if (field == null) { + return false; + } + if (isFinalField(field)) { + updateFinalModifierField(field); + } + try { + field.set(null, value); + return true; + } catch (IllegalAccessException ex) { + LOGGER.warning(String.format(Locale.ENGLISH, "Set value for static field [%s] failed! %s", fieldName, + ex.getMessage())); + return false; + } + } + /** * Set field value * diff --git a/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/utils/SpiLoadUtils.java b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/utils/SpiLoadUtils.java index ad370523f8..02c66549c9 100644 --- a/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/utils/SpiLoadUtils.java +++ b/sermant-agentcore/sermant-agentcore-core/src/main/java/io/sermant/core/utils/SpiLoadUtils.java @@ -52,7 +52,7 @@ public static List loadAll(Class serviceClass, ClassLoader classLoader } list.sort(Comparator.comparingInt(obj -> { SpiWeight weight = obj.getClass().getAnnotation(SpiWeight.class); - return weight.value(); + return weight == null ? 0 : weight.value(); })); return list; } @@ -150,7 +150,6 @@ public static T getBetter(T src, T dst, WeightEqualHandler handler) { * WeightEqualHandler * * @param type - * * @since 2021-11-16 */ public interface WeightEqualHandler { diff --git a/sermant-agentcore/sermant-agentcore-core/src/main/resources/META-INF/services/io.sermant.core.config.common.BaseConfig b/sermant-agentcore/sermant-agentcore-core/src/main/resources/META-INF/services/io.sermant.core.config.common.BaseConfig index 8dc8e371d9..8d37e55ce3 100644 --- a/sermant-agentcore/sermant-agentcore-core/src/main/resources/META-INF/services/io.sermant.core.config.common.BaseConfig +++ b/sermant-agentcore/sermant-agentcore-core/src/main/resources/META-INF/services/io.sermant.core.config.common.BaseConfig @@ -10,3 +10,4 @@ io.sermant.core.plugin.config.ServiceMeta io.sermant.core.notification.config.NotificationConfig io.sermant.core.service.httpserver.config.HttpServerConfig io.sermant.core.service.xds.config.XdsConfig +io.sermant.core.service.metric.config.MetricConfig diff --git a/sermant-agentcore/sermant-agentcore-implement/pom.xml b/sermant-agentcore/sermant-agentcore-implement/pom.xml index cf156171ef..01b1d9dbdf 100644 --- a/sermant-agentcore/sermant-agentcore-implement/pom.xml +++ b/sermant-agentcore/sermant-agentcore-implement/pom.xml @@ -58,6 +58,7 @@ 2.1.2 2.13.4.2 0.1.32 + 1.9.5 @@ -173,6 +174,11 @@ grpc-protobuf ${grpc.version} + + io.micrometer + micrometer-registry-prometheus + ${micrometer.version} + junit junit diff --git a/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/metrics/MeterCounter.java b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/metrics/MeterCounter.java new file mode 100644 index 0000000000..6173762981 --- /dev/null +++ b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/metrics/MeterCounter.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2024-2024 Sermant Authors. 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 io.sermant.implement.service.metrics; + +import io.sermant.core.service.metric.api.Counter; + +/** + * metric counter implementation + * + * @author zwmagic + * @since 2024-08-19 + */ +public class MeterCounter implements Counter { + private final io.micrometer.core.instrument.Counter counter; + + /** + * Constructor to initialize the MeterCounter object. + * + * @param counter A Micrometer Counter instance used to create the MeterCounter instance. + */ + public MeterCounter(io.micrometer.core.instrument.Counter counter) { + this.counter = counter; + } + + @Override + public void increment() { + counter.increment(); + } + + @Override + public void increment(double amount) { + counter.increment(amount); + } + + @Override + public double count() { + return counter.count(); + } +} diff --git a/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/metrics/MeterGauge.java b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/metrics/MeterGauge.java new file mode 100644 index 0000000000..92acccac07 --- /dev/null +++ b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/metrics/MeterGauge.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2024-2024 Sermant Authors. 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 io.sermant.implement.service.metrics; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tag; +import io.sermant.core.service.metric.api.Gauge; + +import java.util.function.ToDoubleFunction; + +/** + * metric gauge implementation + * + * @author zwmagic + * @since 2024-08-19 + */ +public class MeterGauge implements Gauge { + private final MeterRegistry registry; + + private final String metricName; + + private final Iterable tags; + + private final String description; + + /** + * Constructs a MeterGauge instance to measure and record data for a specified metric name. + * + * @param registry The MeterRegistry instance used for registering and managing metrics. + * @param metricName The name of the metric, used to uniquely identify this metric. + * @param tags A set of tags associated with the metric, used for categorization and filtering. + * @param description The description of the metric, used to explain its purpose and meaning. + */ + public MeterGauge(MeterRegistry registry, String metricName, Iterable tags, String description) { + this.registry = registry; + this.metricName = metricName; + this.tags = tags; + this.description = description; + } + + @Override + public T gaugeState(T stateObject, ToDoubleFunction valueFunction) { + io.micrometer.core.instrument.Gauge.builder(metricName, stateObject, valueFunction) + .tags(tags).description(description).register(registry); + return stateObject; + } +} diff --git a/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/metrics/MeterMetric.java b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/metrics/MeterMetric.java new file mode 100644 index 0000000000..9c08f7b9ff --- /dev/null +++ b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/metrics/MeterMetric.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2024-2024 Sermant Authors. 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 io.sermant.implement.service.metrics; + +import com.google.common.collect.Lists; + +import io.micrometer.core.instrument.DistributionSummary; +import io.micrometer.core.instrument.DistributionSummary.Builder; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.config.MeterFilter; +import io.sermant.core.common.BootArgsIndexer; +import io.sermant.core.service.metric.api.Counter; +import io.sermant.core.service.metric.api.DistributionStatisticConfig; +import io.sermant.core.service.metric.api.Gauge; +import io.sermant.core.service.metric.api.Metric; +import io.sermant.core.service.metric.api.Summary; +import io.sermant.core.service.metric.api.Tags; +import io.sermant.core.service.metric.api.Timer; +import io.sermant.core.service.metric.config.MetricConfig; +import io.sermant.core.utils.StringUtils; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * metric + * + * @author zwmagic + * @since 2024-08-19 + */ +public class MeterMetric implements Metric { + private static final Iterable EMPTY = Collections.emptyList(); + + private final MeterRegistry registry; + + /** + * Constructor that initializes a MeterMetric instance + * + * @param registryProvider An instance of MeterRegistryProvider used for creating and managing metrics. + * @param metricConfig MetricConfig + */ + public MeterMetric(MeterRegistryProvider registryProvider, MetricConfig metricConfig) { + this.registry = registryProvider.getRegistry(); + MeterRegistry.Config config = this.registry.config(); + config.commonTags( + "agent", "sermant", + "agent.app.name", BootArgsIndexer.getAppName() + ); + config.meterFilter(MeterFilter.maximumAllowableMetrics(metricConfig.getMaxTimeSeries())); + } + + @Override + public Counter counter(String metricName, Tags tags, String description) { + io.micrometer.core.instrument.Counter counter = io.micrometer.core.instrument.Counter.builder(metricName) + .tags(getMeterTags(tags)) + .description(description) + .register(registry); + return new MeterCounter(counter); + } + + @Override + public Gauge gauge(String metricName, Tags tags, String description) { + return new MeterGauge(registry, metricName, getMeterTags(tags), description); + } + + @Override + public Timer timer(String metricName, Tags tags, String description) { + io.micrometer.core.instrument.Timer timer = io.micrometer.core.instrument.Timer.builder(metricName) + .tags(getMeterTags(tags)).description(description).register(registry); + return new MeterTimer(registry, timer); + } + + @Override + public Summary summary(String metricName, Tags tags, String description, + DistributionStatisticConfig distributionStatisticConfig) { + Builder summaryBuilder = DistributionSummary.builder(metricName) + .tags(getMeterTags(tags)) + .description(description); + + if (distributionStatisticConfig.getPercentileHistogram() != null) { + summaryBuilder.publishPercentileHistogram(distributionStatisticConfig.getPercentileHistogram()); + } + if (distributionStatisticConfig.getPercentiles() != null) { + summaryBuilder.publishPercentiles(distributionStatisticConfig.getPercentiles()); + } + if (distributionStatisticConfig.getPercentilePrecision() != null) { + summaryBuilder.percentilePrecision(distributionStatisticConfig.getPercentilePrecision()); + } + if (distributionStatisticConfig.getMinimumExpectedValue() != null) { + summaryBuilder.minimumExpectedValue(distributionStatisticConfig.getMinimumExpectedValue()); + } + if (distributionStatisticConfig.getMaximumExpectedValue() != null) { + summaryBuilder.maximumExpectedValue(distributionStatisticConfig.getMaximumExpectedValue()); + } + if (distributionStatisticConfig.getExpiry() != null) { + summaryBuilder.distributionStatisticExpiry(distributionStatisticConfig.getExpiry()); + } + if (distributionStatisticConfig.getBufferLength() != null) { + summaryBuilder.distributionStatisticBufferLength(distributionStatisticConfig.getBufferLength()); + } + DistributionSummary summary = summaryBuilder.register(registry); + return new MeterSummary(summary); + } + + /** + * Converts a Tags object into an iterable collection of Tag objects. + * + * @param tags The Tags object to convert. + * @return An iterable collection of Tag objects. Returns an empty collection if there are no tags or if tags is + * null. + */ + private Iterable getMeterTags(Tags tags) { + if (tags == null || tags.getSize() == 0) { + return EMPTY; + } + + List result = Lists.newArrayListWithCapacity(tags.getSize()); + for (Map.Entry entry : tags.getTags().entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + if (StringUtils.isEmpty(key) || StringUtils.isEmpty(value)) { + continue; + } + result.add(Tag.of(entry.getKey(), value)); + } + return result; + } +} diff --git a/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/metrics/MeterMetricServiceImpl.java b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/metrics/MeterMetricServiceImpl.java new file mode 100644 index 0000000000..bf4d9dc816 --- /dev/null +++ b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/metrics/MeterMetricServiceImpl.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2024-2024 Sermant Authors. 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 io.sermant.implement.service.metrics; + +import io.micrometer.core.instrument.Metrics; +import io.sermant.core.config.ConfigManager; +import io.sermant.core.service.metric.api.Counter; +import io.sermant.core.service.metric.api.DistributionStatisticConfig; +import io.sermant.core.service.metric.api.Gauge; +import io.sermant.core.service.metric.api.Metric; +import io.sermant.core.service.metric.api.MetricService; +import io.sermant.core.service.metric.api.Summary; +import io.sermant.core.service.metric.api.Tags; +import io.sermant.core.service.metric.api.Timer; +import io.sermant.core.service.metric.config.MetricConfig; +import io.sermant.core.utils.SpiLoadUtils; + +import java.util.Map; +import java.util.stream.Collectors; + +/** + * The MeterMetricService class manages Micrometer metrics and provides initialization and data retrieval + * functionalities. + * + * @author zwmagic + * @since 2024-08-19 + */ +public class MeterMetricServiceImpl implements MetricService { + private static MeterRegistryProvider meterRegistryProvider; + private Metric metric; + + /** + * Initializes the metric manager. If the metric manager has not been initialized, it creates and configures a + * PrometheusMeterRegistry, and sets it as the global metric manager. + */ + @Override + public void start() { + if (meterRegistryProvider != null) { + return; + } + Map providerMap = SpiLoadUtils.loadAll(MeterRegistryProvider.class, + this.getClass().getClassLoader()).stream() + .collect(Collectors.toMap(MeterRegistryProvider::getType, provider -> provider)); + MetricConfig metricConfig = ConfigManager.getConfig(MetricConfig.class); + String type = metricConfig.getType(); + meterRegistryProvider = providerMap.get(type); + if (meterRegistryProvider == null) { + throw new IllegalArgumentException("can not find metricRegistry provider by " + type); + } + Metrics.addRegistry(meterRegistryProvider.getRegistry()); + metric = new MeterMetric(meterRegistryProvider, metricConfig); + } + + /** + * Retrieves the current metric data from the metric manager. + * + * @return The metric data of the current metric manager, represented in Prometheus format. + */ + public static String getScrape() { + return meterRegistryProvider.getScrape(); + } + + @Override + public Counter counter(String metricName) { + return counter(metricName, null); + } + + @Override + public Counter counter(String metricName, String tagKey, String tagValue) { + return counter(metricName, Tags.of(tagKey, tagValue)); + } + + @Override + public Counter counter(String metricName, Tags tags) { + return counter(metricName, tags, null); + } + + @Override + public Counter counter(String metricName, Tags tags, String description) { + return metric.counter(metricName, tags, description); + } + + @Override + public Gauge gauge(String metricName) { + return gauge(metricName, null); + } + + @Override + public Gauge gauge(String metricName, String tagKey, String tagValue) { + return gauge(metricName, Tags.of(tagKey, tagValue)); + } + + @Override + public Gauge gauge(String metricName, Tags tags) { + return gauge(metricName, tags, null); + } + + @Override + public Gauge gauge(String metricName, Tags tags, String description) { + return metric.gauge(metricName, tags, description); + } + + @Override + public Timer timer(String metricName) { + return timer(metricName, null); + } + + @Override + public Timer timer(String metricName, String tagKey, String tagValue) { + return timer(metricName, Tags.of(tagKey, tagValue)); + } + + @Override + public Timer timer(String metricName, Tags tags) { + return timer(metricName, tags, null); + } + + @Override + public Timer timer(String metricName, Tags tags, String description) { + return metric.timer(metricName, tags, description); + } + + @Override + public Summary summary(String metricName) { + return summary(metricName, null); + } + + @Override + public Summary summary(String metricName, String tagKey, String tagValue) { + return summary(metricName, Tags.of(tagKey, tagValue)); + } + + @Override + public Summary summary(String metricName, Tags tags) { + return summary(metricName, tags, null, null); + } + + @Override + public Summary summary(String metricName, Tags tags, String description, + DistributionStatisticConfig distributionStatisticConfig) { + return metric.summary(metricName, tags, description, distributionStatisticConfig); + } +} diff --git a/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/metrics/MeterRegistryProvider.java b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/metrics/MeterRegistryProvider.java new file mode 100644 index 0000000000..e7ded16fb2 --- /dev/null +++ b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/metrics/MeterRegistryProvider.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2024-2024 Sermant Authors. 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 io.sermant.implement.service.metrics; + +import io.micrometer.core.instrument.MeterRegistry; + +/** + * Interface defining the core components for monitoring data collection. It provides access and configuration + * capabilities for the monitoring data registry, used for collecting and exposing metrics. + * + * @author zwmagic + * @since 2024-08-19 + */ +public interface MeterRegistryProvider { + /** + * Gets the type of the monitoring registry. + * + * @return The type of the monitoring data registry + */ + String getType(); + + /** + * Gets an instance of the monitoring data registry. + * + * @return A MeterRegistry instance + */ + MeterRegistry getRegistry(); + + /** + * Gets the configuration information for scraping monitoring data. + * + * @return The configuration information for scraping monitoring data + */ + String getScrape(); +} diff --git a/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/metrics/MeterSummary.java b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/metrics/MeterSummary.java new file mode 100644 index 0000000000..1f228ef56f --- /dev/null +++ b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/metrics/MeterSummary.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2024-2024 Sermant Authors. 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 io.sermant.implement.service.metrics; + +import io.micrometer.core.instrument.DistributionSummary; +import io.sermant.core.service.metric.api.Summary; + +/** + * The MeterSummary class implements the Summary interface and represents a metered summary, capable of recording, + * aggregating, and reporting the distribution of a certain metric. + * + * @author zwmagic + * @since 2024-08-19 + */ +public class MeterSummary implements Summary { + private final DistributionSummary summary; + + /** + * Constructor that creates a MeterSummary instance. + * + * @param distributionSummary A DistributionSummary instance used internally to record and aggregate metric data. + */ + public MeterSummary(DistributionSummary distributionSummary) { + this.summary = distributionSummary; + } + + /** + * Records a new metric value. + * + * @param amount The metric value to be recorded. + */ + @Override + public void record(double amount) { + summary.record(amount); + } + + /** + * Gets the total number of recorded metric values. + * + * @return The total number of recorded metric values. + */ + @Override + public long count() { + return summary.count(); + } + + /** + * Gets the sum of all recorded metric values. + * + * @return The sum of all recorded metric values. + */ + @Override + public double totalAmount() { + return summary.totalAmount(); + } + + /** + * Gets the maximum value among the recorded metric values. + * + * @return The maximum value among the recorded metric values. + */ + @Override + public double max() { + return summary.max(); + } +} diff --git a/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/metrics/MeterTimer.java b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/metrics/MeterTimer.java new file mode 100644 index 0000000000..8ed22f997f --- /dev/null +++ b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/metrics/MeterTimer.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2024-2024 Sermant Authors. 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 io.sermant.implement.service.metrics; + +import io.micrometer.core.instrument.MeterRegistry; +import io.sermant.core.service.metric.api.Timer; + +import java.time.Duration; +import java.util.concurrent.TimeUnit; + +/** + * metric + * + * @author zwmagic + * @since 2024-08-19 + */ +public class MeterTimer implements Timer { + private final MeterRegistry registry; + + private final io.micrometer.core.instrument.Timer timer; + + private io.micrometer.core.instrument.Timer.Sample sample; + + /** + * Constructs a timer associated with the specified MeterRegistry and Timer. This constructor initializes a + * MeterTimer object to integrate with the MeterRegistry and Timer from the Micrometer library. + * + * @param registry An instance of MeterRegistry used for creating and managing metrics. + * @param timer An instance of io.micrometer.core.instrument.Timer used for timing and recording durations. + */ + public MeterTimer(MeterRegistry registry, io.micrometer.core.instrument.Timer timer) { + this.registry = registry; + this.timer = timer; + } + + @Override + public Timer start() { + this.sample = io.micrometer.core.instrument.Timer.start(registry); + return this; + } + + @Override + public long stop() { + if (sample == null) { + return -1L; + } + return sample.stop(timer); + } + + @Override + public void record(Duration duration) { + timer.record(duration); + } + + @Override + public void record(long amount, TimeUnit unit) { + timer.record(amount, unit); + } + + @Override + public long count() { + return timer.count(); + } + + @Override + public double totalTime(TimeUnit unit) { + return timer.totalTime(unit); + } +} diff --git a/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/metrics/handler/MetricHttpRouteHandler.java b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/metrics/handler/MetricHttpRouteHandler.java new file mode 100644 index 0000000000..ed3770f131 --- /dev/null +++ b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/metrics/handler/MetricHttpRouteHandler.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2024-2024 Sermant Authors. 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 io.sermant.implement.service.metrics.handler; + +import io.prometheus.client.exporter.common.TextFormat; +import io.sermant.core.service.httpserver.annotation.HttpRouteMapping; +import io.sermant.core.service.httpserver.api.HttpMethod; +import io.sermant.core.service.httpserver.api.HttpRequest; +import io.sermant.core.service.httpserver.api.HttpResponse; +import io.sermant.core.service.httpserver.api.HttpRouteHandler; +import io.sermant.implement.service.metrics.MeterMetricServiceImpl; + +/** + * provide http metric + * + * @author zwmagic + * @since 2024-08-19 + */ +@HttpRouteMapping(path = "/metrics", method = HttpMethod.GET) +public class MetricHttpRouteHandler implements HttpRouteHandler { + private static final int SUCCESS_CODE = 200; + + @Override + public void handle(HttpRequest request, HttpResponse response) throws Exception { + String scrape = MeterMetricServiceImpl.getScrape(); + response.setStatus(SUCCESS_CODE) + .setContentType(TextFormat.CONTENT_TYPE_004) + .writeBody(scrape); + } +} diff --git a/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/metrics/prometheus/PrometheusMeterRegistryProvider.java b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/metrics/prometheus/PrometheusMeterRegistryProvider.java new file mode 100644 index 0000000000..cc6186b4e8 --- /dev/null +++ b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/metrics/prometheus/PrometheusMeterRegistryProvider.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2024-2024 Sermant Authors. 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 io.sermant.implement.service.metrics.prometheus; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.prometheus.PrometheusConfig; +import io.micrometer.prometheus.PrometheusMeterRegistry; +import io.sermant.implement.service.metrics.MeterRegistryProvider; + +/** + * Prometheus meter registry provider + * + * @author zwmagic + * @since 2024-08-19 + */ +public class PrometheusMeterRegistryProvider implements MeterRegistryProvider { + private final PrometheusMeterRegistry meterRegistry; + + /** + * Constructor that initializes the Prometheus meter registry. + */ + public PrometheusMeterRegistryProvider() { + this.meterRegistry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT); + } + + @Override + public String getType() { + return "prometheus"; + } + + @Override + public MeterRegistry getRegistry() { + return meterRegistry; + } + + @Override + public String getScrape() { + return meterRegistry.scrape(); + } +} diff --git a/sermant-agentcore/sermant-agentcore-implement/src/main/resources/META-INF/services/io.sermant.core.service.BaseService b/sermant-agentcore/sermant-agentcore-implement/src/main/resources/META-INF/services/io.sermant.core.service.BaseService index f07f9191bb..c4d5d43e0f 100644 --- a/sermant-agentcore/sermant-agentcore-implement/src/main/resources/META-INF/services/io.sermant.core.service.BaseService +++ b/sermant-agentcore/sermant-agentcore-implement/src/main/resources/META-INF/services/io.sermant.core.service.BaseService @@ -1,3 +1,4 @@ +io.sermant.implement.service.metrics.MeterMetricServiceImpl io.sermant.implement.service.heartbeat.HeartbeatServiceImpl io.sermant.implement.service.send.netty.NettyGatewayClient io.sermant.implement.service.dynamicconfig.BufferedDynamicConfigService diff --git a/sermant-agentcore/sermant-agentcore-implement/src/main/resources/META-INF/services/io.sermant.implement.service.metrics.MeterRegistryProvider b/sermant-agentcore/sermant-agentcore-implement/src/main/resources/META-INF/services/io.sermant.implement.service.metrics.MeterRegistryProvider new file mode 100644 index 0000000000..bb827698cb --- /dev/null +++ b/sermant-agentcore/sermant-agentcore-implement/src/main/resources/META-INF/services/io.sermant.implement.service.metrics.MeterRegistryProvider @@ -0,0 +1 @@ +io.sermant.implement.service.metrics.prometheus.PrometheusMeterRegistryProvider \ No newline at end of file