ro.cosu.vampires.server.actors.StatsProcessor.java Source code

Java tutorial

Introduction

Here is the source code for ro.cosu.vampires.server.actors.StatsProcessor.java

Source

/*
 *
 *  * The MIT License (MIT)
 *  * Copyright  2016 Cosmin Dumitru, http://cosu.ro <cosu@cosu.ro>
 *  *
 *  * Permission is hereby granted, free of charge, to any person obtaining a copy
 *  * of this software and associated documentation files (the Software?), to deal
 *  * in the Software without restriction, including without limitation the rights
 *  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  * copies of the Software, and to permit persons to whom the Software is
 *  * furnished to do so, subject to the following conditions:
 *  *
 *  * The above copyright notice and this permission notice shall be included in
 *  * all copies or substantial portions of the Software.
 *  *
 *  * THE SOFTWARE IS PROVIDED AS IS?, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *  * THE SOFTWARE.
 *  *
 *
 */

package ro.cosu.vampires.server.actors;

import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import com.codahale.metrics.Counter;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;

import ro.cosu.vampires.server.resources.Resource;
import ro.cosu.vampires.server.resources.ResourceInfo;
import ro.cosu.vampires.server.values.ClientInfo;
import ro.cosu.vampires.server.values.jobs.Job;
import ro.cosu.vampires.server.values.jobs.metrics.CounterSnapshot;
import ro.cosu.vampires.server.values.jobs.metrics.HistogramSnapshot;
import ro.cosu.vampires.server.values.jobs.metrics.MeterSnapshot;
import ro.cosu.vampires.server.values.jobs.metrics.Stats;
import ro.cosu.vampires.server.values.jobs.metrics.ValueSnapshot;

public class StatsProcessor {

    private static final Logger LOG = LoggerFactory.getLogger(StatsProcessor.class);

    private MetricRegistry metricRegistry = new MetricRegistry();

    private Map<String, LocalDateTime> clientsCreatedAt = Maps.newHashMap();
    private Map<String, ClientInfo> clientsInfo = Maps.newHashMap();
    private Map<String, ResourceInfo> resourcesInfo = Maps.newHashMap();

    private Stats latestStats = Stats.empty();

    public void flush() {
        Map<String, HistogramSnapshot> histograms = Maps.newHashMap();
        Map<String, MeterSnapshot> meters = Maps.newHashMap();
        Map<String, CounterSnapshot> counters = Maps.newHashMap();
        Map<String, ValueSnapshot> values = Maps.newHashMap();

        metricRegistry.getHistograms().entrySet().forEach(m -> {
            String name = m.getKey();
            Histogram value = m.getValue();
            histograms.put(name, HistogramSnapshot.fromHistogram(name, value));
        });

        metricRegistry.getMeters().entrySet().forEach(m -> {
            String name = m.getKey();
            Meter value = m.getValue();
            meters.put(name, MeterSnapshot.fromMeter(name, value));
        });

        metricRegistry.getCounters().entrySet().forEach(m -> {
            String name = m.getKey();
            Counter value = m.getValue();
            counters.put(name, CounterSnapshot.fromCounter(name, value));
        });

        metricRegistry.getGauges().entrySet().forEach(m -> {
            String name = m.getKey();
            Gauge value = m.getValue();
            values.put(name, ValueSnapshot.fromGauge(name, value));
        });

        latestStats = Stats.builder().counters(ImmutableMap.copyOf(counters)).values(ImmutableMap.copyOf(values))
                .meters(ImmutableMap.copyOf(meters)).resources(ImmutableList.copyOf(resourcesInfo.values()))
                .histograms(ImmutableMap.copyOf(histograms)).build();
    }

    public Stats getStats() {
        return latestStats;
    }

    public void process(ClientInfo message) {
        String from = message.id();
        clientsInfo.put(from, message);
    }

    public void process(ResourceInfo message) {
        String from = message.parameters().id();

        clientsCreatedAt.put(from, message.createdAt());
        boolean seenBefore = resourcesInfo.containsKey(from);
        resourcesInfo.put(from, message);
        if (seenBefore)
            return;

        // node count
        for (String key : getKeysForMetric(from, "resources")) {
            metricRegistry.counter(key).inc();
        }

        List<String> costMetricKeys = getKeysForMetric(from, "cost");
        // remove the client key from the keys. this will provide the actual value

        String clientCostMetricKey = costMetricKeys.get(0);

        // register a cost gauge for this client
        metricRegistry.register(clientCostMetricKey, (Gauge<Double>) () -> getCostForClient(from));

        // cost gauges for the rest

        for (String key : costMetricKeys) {
            // key not registered yet
            if (!metricRegistry.getGauges().containsKey(key)) {

                metricRegistry.register(key, (Gauge<Double>) () ->
                // get all the sibling gauges (but not the current gauge - nudge it with a :)
                metricRegistry
                        .getGauges((name, metric) -> name.startsWith(key + ":")
                                && name.split(":").length - key.split(":").length == 1)
                        .values().stream().mapToDouble(g -> (Double) g.getValue()).sum());
            }
        }
    }

    public void process(Job job) {

        String from = job.from();
        // do not process jobs from strangers
        if (!resourcesInfo.containsKey(from)) {
            LOG.warn("client {} not registered. skipping processing", from);
            return;
        }

        job.hostMetrics().metrics().stream().flatMap(m -> m.values().entrySet().stream()).forEach(e -> {
            // converts doubles to longs
            Double value = e.getValue();
            if (value < 100) {
                value = 1000. * value;
            }
            long rounded = Math.round(value);
            String key = e.getKey();
            updateMetric(from, key, rounded);
        });

        updateMetric(from, "duration", job.result().duration());
        updateMetric(from, "job", 0);

    }

    private void updateMetric(String clientId, String metric, long value) {

        for (String key : getKeysForMetric(clientId, metric)) {
            if ("job".equals(metric)) {
                metricRegistry.meter(key).mark();
            } else if (metric.contains("bytes")) {
                metricRegistry.counter(key).inc(value);
            } else {
                metricRegistry.histogram(key).update(value);
            }
        }
    }

    private double getCostForClient(String id) {

        long billedHours = getDurationForClient(id).toHours() + 1;
        double cost = resourcesInfo.get(id).parameters().cost();
        return billedHours * cost;
    }

    private Duration getDurationForClient(String id) {
        LocalDateTime createdAt = clientsCreatedAt.get(id);
        LocalDateTime now = LocalDateTime.now();

        return Duration.between(createdAt, now);
    }

    private String getInstanceType(String from) {
        return resourcesInfo.get(from).parameters().instanceType();
    }

    private Resource.ProviderType getProviderType(String from) {
        return resourcesInfo.get(from).parameters().providerType();
    }

    private List<String> getKeysForMetric(String clientId, String metric) {

        String instanceType = getInstanceType(clientId);
        Resource.ProviderType providerType = getProviderType(clientId);

        // return the keys in reverse order: most specific (longest) first
        return Lists.reverse(Lists.newArrayList(metric, Joiner.on(":").join(metric, providerType),
                Joiner.on(":").join(metric, providerType, instanceType),
                Joiner.on(":").join(metric, providerType, instanceType, clientId)));
    }
}