1, an overview of the

A basic introduction to Metrics can be found in the previous article, Metrics- Service Metrics.

This article briefly describes how to integrate Metrics monitoring into our project.

The metrics-core used in this article is version 3.1.0.

< the dependency > < groupId > IO. Dropwizard. Metrics < / groupId > < artifactId > metrics - core < / artifactId > < version > 3.1.0 < / version > </dependency>Copy the code

2,

Our main monitoring requirements are as follows:

  • Machine indicators

Basic information such as memory, threads, hard disks, and service GC status are the core metrics we care about. We can consider unified collection of these machine indicators through Gauge index.

  • Service interface request frequency and duration

Request frequency and time consumption are the core indicators of our service interface performance, so we can consider using Timer indicators to collect relevant information.

  • Basic internal service data

In some scenarios, instantaneous indicators collected by internal services are reported, for example, the number of requests being processed in the Web Filter. We can also collect using the Gauge metric.

3, plan

For the above scenario, we can create and register the corresponding service metrics by writing code, but it is not very user-friendly. How can we easily and flexibly integrate Metrics statistics into our projects?

3.1 MetricSet automatically registers and collects machine indicators

  • (1) Pre-define MetricSet;

The MetricSet, MetricSet, can be defined by referring to the metrics- JVM library’s MemoryUsageGaugeSet, which defines the basic metrics of memory usage, as shown below.

/** * A set of gauges for JVM memory usage, including stats on heap vs. non-heap memory, plus * GC-specific memory pools. */
public class MemoryUsageGaugeSet implements MetricSet {
    private static final Pattern WHITESPACE = Pattern.compile("[\\s]+");

    private final MemoryMXBean mxBean;
    private final List<MemoryPoolMXBean> memoryPools;

    public MemoryUsageGaugeSet(a) {
        this(ManagementFactory.getMemoryMXBean(),
             ManagementFactory.getMemoryPoolMXBeans());
    }

    public MemoryUsageGaugeSet(MemoryMXBean mxBean, Collection
       
         memoryPools)
        {
        this.mxBean = mxBean;
        this.memoryPools = new ArrayList<MemoryPoolMXBean>(memoryPools);
    }

    @Override
    public Map<String, Metric> getMetrics(a) {
        final Map<String, Metric> gauges = new HashMap<String, Metric>();

        gauges.put("total.init".new Gauge<Long>() {
            @Override
            public Long getValue(a) {
                returnmxBean.getHeapMemoryUsage().getInit() + mxBean.getNonHeapMemoryUsage().getInit(); }}); gauges.put("total.used".new Gauge<Long>() {
            @Override
            public Long getValue(a) {
                returnmxBean.getHeapMemoryUsage().getUsed() + mxBean.getNonHeapMemoryUsage().getUsed(); }}); gauges.put("total.max".new Gauge<Long>() {
            @Override
            public Long getValue(a) {
                returnmxBean.getHeapMemoryUsage().getMax() + mxBean.getNonHeapMemoryUsage().getMax(); }}); gauges.put("total.committed".new Gauge<Long>() {
            @Override
            public Long getValue(a) {
                returnmxBean.getHeapMemoryUsage().getCommitted() + mxBean.getNonHeapMemoryUsage().getCommitted(); }}); gauges.put("heap.init".new Gauge<Long>() {
            @Override
            public Long getValue(a) {
                returnmxBean.getHeapMemoryUsage().getInit(); }}); gauges.put("heap.used".new Gauge<Long>() {
            @Override
            public Long getValue(a) {
                returnmxBean.getHeapMemoryUsage().getUsed(); }}); gauges.put("heap.max".new Gauge<Long>() {
            @Override
            public Long getValue(a) {
                returnmxBean.getHeapMemoryUsage().getMax(); }}); gauges.put("heap.committed".new Gauge<Long>() {
            @Override
            public Long getValue(a) {
                returnmxBean.getHeapMemoryUsage().getCommitted(); }}); gauges.put("heap.usage".new RatioGauge() {
            @Override
            protected Ratio getRatio(a) {
                final MemoryUsage usage = mxBean.getHeapMemoryUsage();
                returnRatio.of(usage.getUsed(), usage.getMax()); }}); gauges.put("non-heap.init".new Gauge<Long>() {
            @Override
            public Long getValue(a) {
                returnmxBean.getNonHeapMemoryUsage().getInit(); }}); gauges.put("non-heap.used".new Gauge<Long>() {
            @Override
            public Long getValue(a) {
                returnmxBean.getNonHeapMemoryUsage().getUsed(); }}); gauges.put("non-heap.max".new Gauge<Long>() {
            @Override
            public Long getValue(a) {
                returnmxBean.getNonHeapMemoryUsage().getMax(); }}); gauges.put("non-heap.committed".new Gauge<Long>() {
            @Override
            public Long getValue(a) {
                returnmxBean.getNonHeapMemoryUsage().getCommitted(); }}); gauges.put("non-heap.usage".new RatioGauge() {
            @Override
            protected Ratio getRatio(a) {
                final MemoryUsage usage = mxBean.getNonHeapMemoryUsage();
                returnRatio.of(usage.getUsed(), usage.getMax()); }});for (final MemoryPoolMXBean pool : memoryPools) {
            gauges.put(name("pools",
                            WHITESPACE.matcher(pool.getName()).replaceAll("-"),
                            "usage"),
                       new RatioGauge() {
                           @Override
                           protected Ratio getRatio(a) {
                               final long max = pool.getUsage().getMax() == -1 ?
                                       pool.getUsage().getCommitted() :
                                       pool.getUsage().getMax();
                               returnRatio.of(pool.getUsage().getUsed(), max); }}); }returnCollections.unmodifiableMap(gauges); }}Copy the code
  • (2) Automatically register MetricSet object Bean through BeanPostProcessor;
public class UserDefinedMetricBeanPostProcessor implements BeanPostProcessor {

    private final Logger LOG = LoggerFactory.getLogger(getClass());

    private final MetricRegistry metrics = MetricBeans.getRegistry();

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

        if (bean instanceof MetricSet) {
            MetricSet metricSet = (MetricSet) bean;
            if(! canRegister(beanName)) {return bean;
            }
            String metricName;
            if (isJvmCollector(beanName)) {
                metricName = Config.getProjectPrefix() + "." + beanName;
            } else {
                // Generate the Metric name according to the rule
                metricName = Util.forMetricBean(bean.getClass(), beanName);
            }
            try {
                metrics.register(metricName, metricSet);
                LOG.debug("Registered metric named {} in registry. class: {}.", metricName, metricSet);
            } catch (IllegalArgumentException ex) {
                LOG.warn("Error injecting metric for field. bean named {}.", metricName, ex); }}return bean;
    }

    private boolean isJvmCollector(String beanName) {
        return beanName.indexOf("jvm") != -1;
    }

    private boolean canRegister(String beanName) {
        return !isJvmCollector(beanName) || Config.canJvmCollectorStart();
    }
}
Copy the code
  • (3) Define bean objects in spring XML files or via Spring annotations;
<! -- Define Jvm monitor object --> <bean id="jvm.memory" class="com.codahale.metrics.jvm.MemoryUsageGaugeSet"/ > <! Automatically add user defined monitor object Metric--> <bean class="com.test.metrics.collector.UserDefinedMetricBeanPostProcessor"/>
Copy the code

You can customize the MetricSet as required to realize automatic registration and reporting of service indicators.

3.2. Implement automatic registration of member variables with annotations

We can implement automatic registration of member variables with annotations. The annotation of member variables can be obtained in BeanPostProcessor. If the annotation is our target, the variable information can be obtained by reflection for automatic registration.

As an example, Gauged annotations allow member variables to be automatically registered and reported.

  • (1) Gauged annotation definition;
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
public @interface Gauged {
    String name(a) default "";
}
Copy the code
  • (2) Parse and register Gauge annotations using BeanPostProcessor;

The core code is as follows:

    protected void withField(finalObject bean, String beanName, Class<? > targetClass,final Field field) {
        ReflectionUtils.makeAccessible(field);

        final Gauged annotation = field.getAnnotation(Gauged.class);
        final String metricName = Util.forGauge(targetClass, field, annotation);

        metrics.register(metricName, new Gauge<Object>() {
            @Override
            public Object getValue(a) {
                returnReflectionUtils.getField(field, bean); }}); LOG.debug("Created gauge {} for field {}.{}", metricName, targetClass.getCanonicalName(), field.getName());
    }
Copy the code
  • (3) Define the corresponding BeanPostProcessor in the spring. XML file.

The basic usage is as follows:

@Component
public class GaugeUsage {

    @Gauged(name = "gaugeField")
    private int gaugedField = 999;
    
}
Copy the code

3.3. Implement interception statistics of method section with annotations

Based on Spring AOP, time statistics of interface calls can be implemented.

The Timed annotation is used as an example to measure the time consumption of an interface method.

  • (1) Timed annotation definition;
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.ANNOTATION_TYPE })
public @interface Timed {
    String name(a) default "";
}
Copy the code
  • (2) definition of Timed section;
@Component
@Aspect
public class MetricAspect {
    @Around("@annotation(timed)")
    public Object processTimerAnnotation(ProceedingJoinPoint joinPoint, Timed timed) throws Throwable {
        Class clazz = joinPoint.getTarget().getClass();
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        String metricName = Util.forTimedMethod(clazz, method, timed);
        Timer timer = MetricBeans.timer(metricName);
        final Timer.Context context = timer.time();
        try {
            return joinPoint.proceed();
        } finally{ context.stop(); }}}Copy the code
  • (3) By defining MetricAspect in spring. XML file, the statistics of request frequency and time consuming with Timed annotation interface can be realized.

The basic example is as follows:

@Component
public class TimedUsage {

    // the Timed annotation lets the monitor component create a Timer object that counts metrics such as the number of times the method is executed and the execution time
    @Timed(name = "simple-timed-method")
    public void timedMethod(a) {
        for (int i = 0; i < 1000; i++) {
        }
    }
}
Copy the code

4, summarize

Currently we mainly use BenPostProcessor and Spring AOP to intercept class instances to achieve automatic registration and collection of service metrics.