Skip to content

Latest commit

 

History

History
535 lines (427 loc) · 26.4 KB

metric-api.md

File metadata and controls

535 lines (427 loc) · 26.4 KB

Metric API

In Easeagent, Metric is inspired by Dropwizard, but Dropwizard is just the basis of Metric.

The use of time series indicators needs to be very rigorous.

In order to prevent the random use of metrics and the wrong use of metrics as relational databases, we have a strict standard for business metrics.

In order to be applicable to the business, we additionally encapsulate and define a dedicated interface.

These interfaces are related to business.

image

image

1. MetricSubType

com.megaease.easeagent.plugin.api.metric.name.MetricSubType

We have briefly categorized common business types.

name Description
DEFAULT Default,no metric with error
ERROR Metric with error
CHANNEL The metric of the connection channel, for rabbitmq.
CONSUMER The metric of the consumer of the data queue, for messaging kafka/rabbitmq consumer, etc.
PRODUCER The metric of the producer of the data queue, for messaging kafka/rabbitmq producer, etc.
CONSUMER_ERROR The error metric of the consumer of the data queue, for messaging kafka/rabbitmq consumer error, etc.
PRODUCER_ERROR The error metric of the producer of the data queue, for messaging kafka/rabbitmq producer error, etc.
NONE Unknown type

2. MetricField

com.megaease.easeagent.plugin.api.metric.name.MetricField

Different types will use different names depending on the business.

For example, the Count value of Counter and Timer can be named TotalCount or ErrorCount according to success or failure.

We have made certain specifications for the name:

public enum MetricField {
    MIN_EXECUTION_TIME("min", ConverterType.DURATION, 2),
    MAX_EXECUTION_TIME("max", ConverterType.DURATION, 2),
    MEAN_EXECUTION_TIME("mean", ConverterType.DURATION, 2),
    P25_EXECUTION_TIME("p25", ConverterType.DURATION, 2),
    P50_EXECUTION_TIME("p50", ConverterType.DURATION, 2),
    P75_EXECUTION_TIME("p75", ConverterType.DURATION, 2),
    P95_EXECUTION_TIME("p95", ConverterType.DURATION, 2),
    P98_EXECUTION_TIME("p98", ConverterType.DURATION, 2),
    P99_EXECUTION_TIME("p99", ConverterType.DURATION, 2),
    P999_EXECUTION_TIME("p999", ConverterType.DURATION, 2),
    STD("std"),
    EXECUTION_COUNT("cnt"),
    EXECUTION_ERROR_COUNT("errcnt"),
    M1_RATE("m1", ConverterType.RATE, 5),
    M5_RATE("m5", ConverterType.RATE, 5),
    M15_RATE("m15", ConverterType.RATE, 5),
    RETRY_M1_RATE("retrym1", ConverterType.RATE, 5),
    RETRY_M5_RATE("retrym5", ConverterType.RATE, 5),
    RETRY_M15_RATE("retrym15", ConverterType.RATE, 5),
    RATELIMITER_M1_RATE("rlm1", ConverterType.RATE, 5),
    RATELIMITER_M5_RATE("rlm5", ConverterType.RATE, 5),
    RATELIMITER_M15_RATE("rlm15", ConverterType.RATE, 5),
    CIRCUITBREAKER_M1_RATE("cbm1", ConverterType.RATE, 5),
    CIRCUITBREAKER_M5_RATE("cbm5", ConverterType.RATE, 5),
    CIRCUITBREAKER_M15_RATE("cbm15", ConverterType.RATE, 5),
    MEAN_RATE("mean_rate", ConverterType.RATE, 5),
    M1_ERROR_RATE("m1err", ConverterType.RATE, 5),
    M5_ERROR_RATE("m5err", ConverterType.RATE, 5),
    M15_ERROR_RATE("m15err", ConverterType.RATE, 5),
    M1_COUNT("m1cnt", ConverterType.RATE, 0),
    M5_COUNT("m5cnt", ConverterType.RATE, 0),
    M15_COUNT("m15cnt", ConverterType.RATE, 0),
    TIMES_RATE("time_rate", ConverterType.RATE, 5),
    TOTAL_COLLECTION_TIME("total_collection_time", ConverterType.RATE, 0),
    TIMES("times", ConverterType.RATE, 0),
    /* channel is for rabbitmq */
    CHANNEL_M1_RATE("channel_m1_rate", ConverterType.RATE, 5),
    CHANNEL_M5_RATE("channel_m5_rate", ConverterType.RATE, 5),
    CHANNEL_M15_RATE("channel_m15_rate", ConverterType.RATE, 5),
    QUEUE_M1_RATE("queue_m1_rate", ConverterType.RATE, 5),
    QUEUE_M5_RATE("queue_m5_rate", ConverterType.RATE, 5),
    QUEUE_M15_RATE("queue_m15_rate", ConverterType.RATE, 5),
    QUEUE_M1_ERROR_RATE("queue_m1_error_rate", ConverterType.RATE, 5),
    QUEUE_M5_ERROR_RATE("queue_m5_error_rate", ConverterType.RATE, 5),
    QUEUE_M15_ERROR_RATE("queue_m15_error_rate", ConverterType.RATE, 5),
    /*producer and consumer is for message kafka rabbitmq service*/
    PRODUCER_M1_RATE("prodrm1", ConverterType.RATE, 5),
    PRODUCER_M5_RATE("prodrm5", ConverterType.RATE, 5),
    PRODUCER_M15_RATE("prodrm15", ConverterType.RATE, 5),
    PRODUCER_M1_ERROR_RATE("prodrm1err", ConverterType.RATE, 5),
    PRODUCER_M5_ERROR_RATE("prodrm5err", ConverterType.RATE, 5),
    PRODUCER_M15_ERROR_RATE("prodrm15err", ConverterType.RATE, 5),
    CONSUMER_M1_RATE("consrm1", ConverterType.RATE, 5),
    CONSUMER_M5_RATE("consrm5", ConverterType.RATE, 5),
    CONSUMER_M15_RATE("consrm15", ConverterType.RATE, 5),
    CONSUMER_M1_ERROR_RATE("consrm1err", ConverterType.RATE, 5),
    CONSUMER_M5_ERROR_RATE("consrm5err", ConverterType.RATE, 5),
    CONSUMER_M15_ERROR_RATE("consrm15err", ConverterType.RATE, 5),
    EXECUTION_PRODUCER_ERROR_COUNT("prodrerrcnt"),
    EXECUTION_CONSUMER_ERROR_COUNT("consrerrcnt"),
    EXECUTION_PRODUCER_COUNT("prodrcnt"),
    EXECUTION_CONSUMER_COUNT("consrcnt"),
    PRODUCER_MIN_EXECUTION_TIME("prodrmin", ConverterType.DURATION, 2),
    PRODUCER_MAX_EXECUTION_TIME("prodrmax", ConverterType.DURATION, 2),
    PRODUCER_MEAN_EXECUTION_TIME("prodrmean", ConverterType.DURATION, 2),
    PRODUCER_P25_EXECUTION_TIME("prodrp25", ConverterType.DURATION, 2),
    PRODUCER_P50_EXECUTION_TIME("prodrp50", ConverterType.DURATION, 2),
    PRODUCER_P75_EXECUTION_TIME("prodrp75", ConverterType.DURATION, 2),
    PRODUCER_P95_EXECUTION_TIME("prodrp95", ConverterType.DURATION, 2),
    PRODUCER_P98_EXECUTION_TIME("prodrp98", ConverterType.DURATION, 2),
    PRODUCER_P99_EXECUTION_TIME("prodrp99", ConverterType.DURATION, 2),
    PRODUCER_P999_EXECUTION_TIME("prodrp999", ConverterType.DURATION, 2),
    CONSUMER_MIN_EXECUTION_TIME("consrmin", ConverterType.DURATION, 2),
    CONSUMER_MAX_EXECUTION_TIME("consrmax", ConverterType.DURATION, 2),
    CONSUMER_MEAN_EXECUTION_TIME("consrmean", ConverterType.DURATION, 2),
    CONSUMER_P25_EXECUTION_TIME("consrp25", ConverterType.DURATION, 2),
    CONSUMER_P50_EXECUTION_TIME("consrp50", ConverterType.DURATION, 2),
    CONSUMER_P75_EXECUTION_TIME("consrp75", ConverterType.DURATION, 2),
    CONSUMER_P95_EXECUTION_TIME("consrp95", ConverterType.DURATION, 2),
    CONSUMER_P98_EXECUTION_TIME("consrp98", ConverterType.DURATION, 2),
    CONSUMER_P99_EXECUTION_TIME("consrp99", ConverterType.DURATION, 2),
    CONSUMER_P999_EXECUTION_TIME("consrp999", ConverterType.DURATION, 2),
    NONE("", ConverterType.RATE, 0);
}

3.MetricValueFetcher

com.megaease.easeagent.plugin.api.metric.name.MetricValueFetcher

Metric has different types, and each type can calculate different data.

For example: Counter can only get Count, Timer can get Max, Mean, Min, P95 ...

Regarding the value that the type can calculate, we have encapsulated it:

public enum MetricValueFetcher {
    CountingCount(Counter::getCount, Counter.class),
    SnapshotMaxValue(Snapshot::getMax, Snapshot.class),
    SnapshotMeanValue(Snapshot::getMean, Snapshot.class),
    SnapshotMinValue(Snapshot::getMin, Snapshot.class),
    Snapshot25Percentile(s -> s.getValue(0.25), Snapshot.class),
    SnapshotMedianValue(Snapshot::getMedian, Snapshot.class),
    Snapshot50PercentileValue(Snapshot::getMedian, Snapshot.class),
    Snapshot75PercentileValue(Snapshot::get75thPercentile, Snapshot.class),
    Snapshot95PercentileValue(Snapshot::get95thPercentile, Snapshot.class),
    Snapshot98PercentileValue(Snapshot::get98thPercentile, Snapshot.class),
    Snapshot99PercentileValue(Snapshot::get99thPercentile, Snapshot.class),
    Snapshot999PercentileValue(Snapshot::get999thPercentile, Snapshot.class),
    MeteredM1Rate(Meter::getOneMinuteRate, Meter.class),
    MeteredM1RateIgnoreZero(Meter::getOneMinuteRate, Meter.class, aDouble -> aDouble),
    MeteredM5Rate(Meter::getFiveMinuteRate, Meter.class),
    MeteredM15Rate(Meter::getFifteenMinuteRate, Meter.class),
    MeteredMeanRate(Meter::getMeanRate, Meter.class),
    MeteredCount(Meter::getCount, Meter.class);
}

4. NameFactory

com.megaease.easeagent.plugin.api.metric.name.NameFactory

According to the value type and naming combination, we standardize a NameFactory.

Example:

class ServerMetric{
    @Nonnull
    public static NameFactory nameFactory() {
        return NameFactory.createBuilder()
            .counterType(MetricSubType.DEFAULT, ImmutableMap.<MetricField, MetricValueFetcher>builder()
                .put(MetricField.EXECUTION_COUNT, MetricValueFetcher.CountingCount)
                .build())
            .meterType(MetricSubType.DEFAULT, ImmutableMap.<MetricField, MetricValueFetcher>builder()
                .put(MetricField.M1_RATE, MetricValueFetcher.MeteredM1RateIgnoreZero)
                .put(MetricField.M5_RATE, MetricValueFetcher.MeteredM5Rate)
                .put(MetricField.M15_RATE, MetricValueFetcher.MeteredM15Rate)
                .put(MetricField.MEAN_RATE, MetricValueFetcher.MeteredMeanRate)
                .build())
            .counterType(MetricSubType.ERROR, ImmutableMap.<MetricField, MetricValueFetcher>builder()
                .put(MetricField.EXECUTION_ERROR_COUNT, MetricValueFetcher.CountingCount)
                .build())
            .meterType(MetricSubType.ERROR, ImmutableMap.<MetricField, MetricValueFetcher>builder()
                .put(MetricField.M1_ERROR_RATE, MetricValueFetcher.MeteredM1Rate)
                .put(MetricField.M5_ERROR_RATE, MetricValueFetcher.MeteredM5Rate)
                .put(MetricField.M15_ERROR_RATE, MetricValueFetcher.MeteredM15Rate)
                .put(MetricField.MEAN_RATE, MetricValueFetcher.MeteredMeanRate)
                .build())
            .gaugeType(MetricSubType.DEFAULT, new HashMap<>())
            .timerType(MetricSubType.DEFAULT,
                ImmutableMap.<MetricField, MetricValueFetcher>builder()
                    .put(MetricField.MIN_EXECUTION_TIME, MetricValueFetcher.SnapshotMinValue)
                    .put(MetricField.MAX_EXECUTION_TIME, MetricValueFetcher.SnapshotMaxValue)
                    .put(MetricField.MEAN_EXECUTION_TIME, MetricValueFetcher.SnapshotMeanValue)
                    .put(MetricField.P25_EXECUTION_TIME, MetricValueFetcher.Snapshot25Percentile)
                    .put(MetricField.P50_EXECUTION_TIME, MetricValueFetcher.Snapshot50PercentileValue)
                    .put(MetricField.P75_EXECUTION_TIME, MetricValueFetcher.Snapshot75PercentileValue)
                    .put(MetricField.P95_EXECUTION_TIME, MetricValueFetcher.Snapshot95PercentileValue)
                    .put(MetricField.P98_EXECUTION_TIME, MetricValueFetcher.Snapshot98PercentileValue)
                    .put(MetricField.P99_EXECUTION_TIME, MetricValueFetcher.Snapshot99PercentileValue)
                    .put(MetricField.P999_EXECUTION_TIME, MetricValueFetcher.Snapshot999PercentileValue)
                    .build())
            .build();
    }
}

4. Registry

Inherit the ServiceMetric class in order to obtain the MetricRegistry. com.megaease.easeagent.plugin.api.metric.ServiceMetric

Use the ServiceMetricRegistry interface to register and create a singleton.

com.megaease.easeagent.plugin.api.metric.ServiceMetricRegistry

Config

Metric configuration follows plugin configuration rules metric config

Tags

Metrics have values as well as tags. EaseAgent supports custom tags, but these tags must be predictable and given in advance.

To better support the business, three tags must be given: category,type and key

So it is fixed in Tags and must provide three pieces of information: category,type and keyFieldName

com.megaease.easeagent.plugin.api.metric.name.Tags

Its tag is copied as follows:

output.put("category", tags.category)
output.put("type", tags.type)
output.put(tags.keyFieldName, {@link NameFactory}.key[?])
tags.tags.forEach((k,v)->{
    output.put(k,v)
})
Singleton

The Key of the singleton is: domain, namespace, id, tags and the type of class.

Example:

public class ServerMetric extends ServiceMetric {

    public ServerMetric(@Nonnull MetricRegistry metricRegistry, @Nonnull NameFactory nameFactory) {
        super(metricRegistry, nameFactory);
    }

    public void collectMetric(String key, int statusCode, Throwable throwable, long startMillis, long endMillis) {
        Timer timer = metricRegistry.timer(nameFactory.timerName(key, MetricSubType.DEFAULT));
        timer.update(Duration.ofMillis(endMillis - startMillis));
        final Meter errorMeter = metricRegistry.meter(nameFactory.meterName(key, MetricSubType.ERROR));
        final Meter meter = metricRegistry.meter(nameFactory.meterName(key, MetricSubType.DEFAULT));
        Counter errorCounter = metricRegistry.counter(nameFactory.counterName(key, MetricSubType.ERROR));
        Counter counter = metricRegistry.counter(nameFactory.counterName(key, MetricSubType.DEFAULT));
        boolean hasException = throwable != null;
        if (statusCode >= 400 || hasException) {
            errorMeter.mark();
            errorCounter.inc();
        }
        counter.inc();
        meter.mark();

        metricRegistry.gauge(nameFactory.gaugeName(key, MetricSubType.DEFAULT), () -> () -> {
            BigDecimal m1ErrorPercent = BigDecimal.ZERO;
            BigDecimal m5ErrorPercent = BigDecimal.ZERO;
            BigDecimal m15ErrorPercent = BigDecimal.ZERO;
            BigDecimal error = BigDecimal.valueOf(errorMeter.getOneMinuteRate()).setScale(5, BigDecimal.ROUND_HALF_DOWN);
            BigDecimal n = BigDecimal.valueOf(meter.getOneMinuteRate());
            if (n.compareTo(BigDecimal.ZERO) != 0) {
                m1ErrorPercent = error.divide(n, 2, BigDecimal.ROUND_HALF_UP);
            }
            error = BigDecimal.valueOf(errorMeter.getFiveMinuteRate()).setScale(5, BigDecimal.ROUND_HALF_DOWN);
            n = BigDecimal.valueOf(meter.getFiveMinuteRate());
            if (n.compareTo(BigDecimal.ZERO) != 0) {
                m5ErrorPercent = error.divide(n, 2, BigDecimal.ROUND_HALF_UP);
            }

            error = BigDecimal.valueOf(errorMeter.getFifteenMinuteRate()).setScale(5, BigDecimal.ROUND_HALF_DOWN);
            n = BigDecimal.valueOf(meter.getFifteenMinuteRate());
            if (n.compareTo(BigDecimal.ZERO) != 0) {
                m15ErrorPercent = error.divide(n, 2, BigDecimal.ROUND_HALF_UP);
            }
            return new ErrorPercentModelGauge(m1ErrorPercent, m5ErrorPercent, m15ErrorPercent);
        });
    }

    @Nonnull
    public static NameFactory nameFactory() {
        return NameFactory.createBuilder()
            .counterType(MetricSubType.DEFAULT, ImmutableMap.<MetricField, MetricValueFetcher>builder()
                .put(MetricField.EXECUTION_COUNT, MetricValueFetcher.CountingCount)
                .build())
            .meterType(MetricSubType.DEFAULT, ImmutableMap.<MetricField, MetricValueFetcher>builder()
                .put(MetricField.M1_RATE, MetricValueFetcher.MeteredM1RateIgnoreZero)
                .put(MetricField.M5_RATE, MetricValueFetcher.MeteredM5Rate)
                .put(MetricField.M15_RATE, MetricValueFetcher.MeteredM15Rate)
                .put(MetricField.MEAN_RATE, MetricValueFetcher.MeteredMeanRate)
                .build())
            .counterType(MetricSubType.ERROR, ImmutableMap.<MetricField, MetricValueFetcher>builder()
                .put(MetricField.EXECUTION_ERROR_COUNT, MetricValueFetcher.CountingCount)
                .build())
            .meterType(MetricSubType.ERROR, ImmutableMap.<MetricField, MetricValueFetcher>builder()
                .put(MetricField.M1_ERROR_RATE, MetricValueFetcher.MeteredM1Rate)
                .put(MetricField.M5_ERROR_RATE, MetricValueFetcher.MeteredM5Rate)
                .put(MetricField.M15_ERROR_RATE, MetricValueFetcher.MeteredM15Rate)
                .put(MetricField.MEAN_RATE, MetricValueFetcher.MeteredMeanRate)
                .build())
            .gaugeType(MetricSubType.DEFAULT, new HashMap<>())
            .timerType(MetricSubType.DEFAULT,
                ImmutableMap.<MetricField, MetricValueFetcher>builder()
                    .put(MetricField.MIN_EXECUTION_TIME, MetricValueFetcher.SnapshotMinValue)
                    .put(MetricField.MAX_EXECUTION_TIME, MetricValueFetcher.SnapshotMaxValue)
                    .put(MetricField.MEAN_EXECUTION_TIME, MetricValueFetcher.SnapshotMeanValue)
                    .put(MetricField.P25_EXECUTION_TIME, MetricValueFetcher.Snapshot25Percentile)
                    .put(MetricField.P50_EXECUTION_TIME, MetricValueFetcher.Snapshot50PercentileValue)
                    .put(MetricField.P75_EXECUTION_TIME, MetricValueFetcher.Snapshot75PercentileValue)
                    .put(MetricField.P95_EXECUTION_TIME, MetricValueFetcher.Snapshot95PercentileValue)
                    .put(MetricField.P98_EXECUTION_TIME, MetricValueFetcher.Snapshot98PercentileValue)
                    .put(MetricField.P99_EXECUTION_TIME, MetricValueFetcher.Snapshot99PercentileValue)
                    .put(MetricField.P999_EXECUTION_TIME, MetricValueFetcher.Snapshot999PercentileValue)
                    .build())
            .build();
    }
}

public class DoFilterMetricInterceptor extends Interceptor {
    private static volatile ServerMetric SERVER_METRIC = null;
    private static final Object START_MACK = new Object();
    
    @Override
    public void init(Config config, String className, String methodName, String methodDescriptor) {
        SERVER_METRIC = ServiceMetricRegistry.getOrCreate(config, new Tags("application", "http-request", "url"),
            new ServiceMetricSupplier<ServerMetric>() {
                @Override
                public NameFactory newNameFactory() {
                    return ServerMetric.nameFactory();
                }
        
                @Override
                public ServerMetric newInstance(MetricRegistry metricRegistry, NameFactory nameFactory) {
                    return new ServerMetric(metricRegistry, nameFactory);
                }
            });
    }
    
    @Override
    public void before(MethodInfo methodInfo, Context context){
        context.put(START_MACK, System.currentTimeMillis());
    }
    
    @Override
    public void after(MethodInfo methodInfo, Context context){
        long start = context.remove(START_MACK);
        long end = System.currentTimeMillis();
        String key = "GET /demo";
        int statusCode = 200;
        SERVER_METRIC.collectMetric(key, statusCode, throwable, start, end);
    }
}

5. Metric

Even though EaseAgent's Metric is inspired by Dropwizard, it still hopes to decouple its implementation from Dropwizard.

So EaseAgent has its own API, although the implementation scheme currently used is Dropwizard.

com.megaease.easeagent.plugin.api.metric.MetricRegistry

com.megaease.easeagent.plugin.api.metric.Counter

com.megaease.easeagent.plugin.api.metric.Histogram

com.megaease.easeagent.plugin.api.metric.Meter

com.megaease.easeagent.plugin.api.metric.Snapshot

com.megaease.easeagent.plugin.api.metric.Timer

6. Customize

When you are sure that you want to implement your own metric, then you only need a metric output class, and finally use this output class to output to Kafka or backend.

We provide such an output interface, this interface accepts string output.

The standard of EaseMonitor is to use json format, if unnecessary, please use json as output.

com.megaease.easeagent.plugin.api.Reporter

com.megaease.easeagent.plugin.bridge.EaseAgent.metricReporterFactory

The obtained Reporter is a singleton, and the key of the singleton is namespace.

Its output configuration complies with metric configuration rules: metric config

Example:

public class MD5ReportConsumer {
    private final Config config;
    private final Reporter reporter;
    public MD5ReportConsumer() {
        this.config = AutoRefreshRegistry.getOrCreate("observability", "md5Dictionary", "metric");
        this.reporter = EaseAgent.metricReporterFactory(config);
    }

    
    @Override
    public void accept(Map<String, String> map) {
        if (!this.config.enabled()) {
            return;
        }
        map.put("host_name", HostAddress.localhost());
        map.put("host_ipv4", HostAddress.getHostIpv4());
        map.put("category", "application");
        String json = JsonUtil.toJson(item);
        this.reporter.report(json);
    }
}

7.PrometheusExports

We have redefined Prometheus Exports according to the following naming standard and statistics rules.

Metric Name

For better readability, we get the category and type from Tags, and add MetricField.field as the metric name

Name format: ${category}_${type}_${MetricField.field}

Example: application_http_request_m1

Metric Label

Except category, type, MetricField.field and value, other fields will be exported in the form of labels.

By default, there will be the following labels.

label name label value Description
MetricSubType enum The enum MetricSubType value: DEFAULT,ERROR,CHANNEL,CONSUMER,PRODUCER,CONSUMER_ERROR,PRODUCER_ERROR,NONE
MetricType enum The Metric Type by metric calculate: TimerType,HistogramType,MeterType,CounterType,GaugeType
host_ipv4 String, xxx.xxx.xxx.xx The ipv4 by host.
host_name String host name.
service String The name read from the configuration. for you service name.
system String The system read from the configuration. for you system name.
${Tags.keyFieldName} ${metricKey} The metric label by metric key.
${Tags.tags().key} ${Tags.tags().value} Your custom label, which comes from Tags
Metric Value

Under normal circumstances, each metric name should correspond to a calculation method.

Example :

Metric name is application_http_request_m1, the value is 1.0, which is the weighted average QPS of the last minute calculated.

It is calculated using Meter::getOneMinuteRate. Its corresponding MetricValueFetcher is MeteredM1RateIgnoreZero.

public class M1MetricCollect {
    static final M1Metric m1Metric;

    static {
        IPluginConfig config = EaseAgent.getConfig("observability", "collectM1", ConfigConst.PluginID.METRIC);
        Tags tags = new Tags("application", "http-request", "url").put("city", "beijing");
        ServiceMetricSupplier<M1Metric> m1MetricSupplier = new ServiceMetricSupplier<M1Metric>() {
            @Override
            public NameFactory newNameFactory() {
                return NameFactory.createBuilder()
                    .meterType(MetricSubType.DEFAULT, ImmutableMap.<MetricField, MetricValueFetcher>builder()
                        .put(MetricField.M1_RATE, MetricValueFetcher.MeteredM1RateIgnoreZero)
                        .build())
                    .build();
            }

            @Override
            public M1Metric newInstance(MetricRegistry metricRegistry, NameFactory nameFactory) {
                return new M1Metric(metricRegistry, nameFactory);
            }
        };

        m1Metric = EaseAgent.getOrCreateServiceMetric(config, tags, m1MetricSupplier);
    }

    public void collectM1() {
        String url = "GET /web_client";
        for (int i = 0; i < 100; i++) {
            m1Metric.collectMetric(url);
        }
    }

    public static class M1Metric extends ServiceMetric {

        public M1Metric(@Nonnull MetricRegistry metricRegistry, @Nonnull NameFactory nameFactory) {
            super(metricRegistry, nameFactory);
        }

        public void collectMetric(String key) {
            final Meter meter = meter(key, MetricSubType.DEFAULT);
            meter.mark();
        }
    }
}

The above example will export metrics like the following

Name and Label:

application_http_request_m1{MetricSubType="DEFAULT",MetricType="MeterType",city="beijing",host_ipv4="10.127.48.163",host_name="MacBook-Pro.local",service="demo-service",system="demo-system",url="GET /web_client"}

value :

90.0

image