io.silverware.microservices.monitoring.MetricsManager.java Source code

Java tutorial

Introduction

Here is the source code for io.silverware.microservices.monitoring.MetricsManager.java

Source

/*
 * -----------------------------------------------------------------------\
 * SilverWare
 *
 * Copyright (C) 2016 the original author or authors.
 *
 * 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.silverware.microservices.monitoring;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.LongAdder;

/**
 * Class responsible for managing metrics. Class works with {@link DefaultMetric} and stores recorded values for one minute. Percentiles are updated every second in separate thread.
 */
public class MetricsManager {

    private static final Logger log = LogManager.getLogger(MetricsManager.class);

    private final Metrics metrics = new DefaultMetric();

    private final LoadingCache<Long, BigDecimal> mapValues = CacheBuilder.newBuilder()
            .expireAfterWrite(1, TimeUnit.MINUTES).build(new CacheLoader<Long, BigDecimal>() {
                @Override
                public BigDecimal load(Long key) throws Exception {
                    return mapValues.get(key);
                }
            });

    private LongAdder longAdder = new LongAdder();

    private final Thread updateThread = new Thread(this::updatePercentiles);

    /**
     * Default constructor, sets true for daemon attribute of percentile updating thread and starts it.
     */
    public MetricsManager() {
        updateThread.setDaemon(true);
        updateThread.start();
    }

    /**
     * Method responsible for adding new time to values collection and also updating min, max, avg and count metrics.
     *
     * @param elapsedTime
     *       runtime of microservice method.
     */
    public void addTime(BigDecimal elapsedTime) {

        if (elapsedTime.compareTo(BigDecimal.ZERO) <= 0) {
            throw new IllegalArgumentException("Elapsed time is negative or zero");
        }

        mapValues.put(longAdder.longValue(), elapsedTime);

        longAdder.increment();

        final BigDecimal count = new BigDecimal(metrics.getCount());
        final BigDecimal averageTime = metrics.getAverageTime();
        final BigDecimal minTime = metrics.getMinTime();
        final BigDecimal maxTime = metrics.getMaxTime();

        metrics.incrementCount();

        metrics.setAverageTime((averageTime.multiply(count).add(elapsedTime)).divide(count.add(BigDecimal.ONE),
                BigDecimal.ROUND_HALF_UP));

        if (elapsedTime.compareTo(maxTime) >= 1) {
            metrics.setMaxTime(elapsedTime);
        } else {
            metrics.setMinTime(elapsedTime);
        }
    }

    /**
     * Getter for count value stored in {@link DefaultMetric}.
     *
     * @return count attribute.
     */
    public long getCount() {
        return metrics.getCount();
    }

    /**
     * Getter for average time stored in {@link DefaultMetric}.
     *
     * @return average time attribute.
     */
    public BigDecimal getAverageTime() {
        return metrics.getAverageTime();
    }

    /**
     * Getter for minimal time stored in {@link DefaultMetric}.
     *
     * @return minimal time attribute.
     */
    public BigDecimal getMinTime() {
        return metrics.getMinTime();
    }

    /**
     * Getter for maximal time stored in {@link DefaultMetric}.
     *
     * @return maximal time attribute.
     */
    public BigDecimal getMaxTime() {
        return metrics.getMaxTime();
    }

    /**
     * Getter for 90 percentile stored in {@link DefaultMetric}.
     *
     * @return 90 percentile attribute.
     */
    public BigDecimal get90Percentile() {
        return metrics.get90Percentile();
    }

    /**
     * Getter for 95 percentile stored in {@link DefaultMetric}.
     *
     * @return 95 percentile attribute.
     */
    public BigDecimal get95Percentile() {
        return metrics.get95Percentile();
    }

    /**
     * Getter for 99 percentile stored in {@link DefaultMetric}.
     *
     * @return 99 percentile attribute.
     */
    public BigDecimal get99Percentile() {
        return metrics.get99Percentile();
    }

    /**
     * Returns unmodifiable {@link List} of values currently being stored. Note: values expire after one minute.
     *
     * @return unmodifiable {@link List}.
     */
    public List<BigDecimal> getValues() {
        List<BigDecimal> values = new ArrayList<>(mapValues.asMap().values());
        Collections.sort(values);
        return Collections.unmodifiableList(values);
    }

    /**
     * Returns JSON representation of recorded metrics.
     *
     * @return {@link String} holding JSON representation of metrics.
     */
    public String toJson() {
        ObjectMapper mapper = new ObjectMapper();
        String jsonString = null;
        try {
            jsonString = mapper.writeValueAsString(metrics);
        } catch (JsonProcessingException e) {
            log.warn("Could not return metrics: ", e);
        }
        return jsonString;
    }

    @Override
    public String toString() {
        return metrics.toString();
    }

    private void updatePercentiles() {

        while (!Thread.currentThread().isInterrupted()) {
            List<BigDecimal> values = getValues();

            if (!values.isEmpty()) {
                final BigDecimal percentile90 = values.get((int) (values.size() * 0.90));
                final BigDecimal percentile95 = values.get((int) (values.size() * 0.95));
                final BigDecimal percentile99 = values.get((int) (values.size() * 0.99));

                metrics.set90Percentile(percentile90);
                metrics.set95Percentile(percentile95);
                metrics.set99Percentile(percentile99);
            }

            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                // nps, we simply ignore
            }
        }
    }
}