All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.graylog2.system.traffic.TrafficCounterService Maven / Gradle / Ivy

/*
 * Copyright (C) 2020 Graylog, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the Server Side Public License, version 1,
 * as published by MongoDB, Inc.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * Server Side Public License for more details.
 *
 * You should have received a copy of the Server Side Public License
 * along with this program. If not, see
 * .
 */
package org.graylog2.system.traffic;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableMap;
import com.mongodb.BasicDBObject;
import org.bson.types.ObjectId;
import org.graylog2.bindings.providers.MongoJackObjectMapperProvider;
import org.graylog2.database.MongoConnection;
import org.graylog2.plugin.Tools;
import org.graylog2.plugin.system.NodeId;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.mongojack.DBCursor;
import org.mongojack.DBQuery;
import org.mongojack.DBUpdate;
import org.mongojack.JacksonDBCollection;
import org.mongojack.WriteResult;
import org.mongojack.internal.update.SingleUpdateOperationValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;

public class TrafficCounterService {
    private static final Logger LOG = LoggerFactory.getLogger(TrafficCounterService.class);
    private static final String BUCKET = "bucket";

    private final JacksonDBCollection db;

    @Inject
    public TrafficCounterService(MongoConnection mongoConnection,
                                 MongoJackObjectMapperProvider mapper) {
        db = JacksonDBCollection.wrap(mongoConnection.getDatabase().getCollection("traffic"),
                TrafficDto.class,
                ObjectId.class,
                mapper.get());
        db.createIndex(new BasicDBObject(BUCKET, 1), new BasicDBObject("unique", true));
    }

    private static DateTime getDayBucketStart(DateTime observationTime) {
        return observationTime.withHourOfDay(0).withMinuteOfHour(0).withSecondOfMinute(0).withMillisOfSecond(0);
    }

    private static DateTime getHourBucketStart(DateTime observationTime) {
        return observationTime.withMinuteOfHour(0).withSecondOfMinute(0).withMillisOfSecond(0);
    }

    public void updateTraffic(DateTime observationTime, NodeId nodeId, long inLastMinute, long outLastMinute, long decodedLastMinute) {
        // we bucket traffic data by the hour and aggregate it to a day bucket for reporting
        final DateTime dayBucket = getHourBucketStart(observationTime);

        if (LOG.isDebugEnabled()) {
            LOG.debug("Updating traffic for node {} at {}:  in/decoded/out {}/{}/{} bytes",
                    nodeId, dayBucket, inLastMinute, decodedLastMinute, outLastMinute);
        }

        final String escapedNodeId = nodeId.toEscapedString();
        final WriteResult update = db.update(DBQuery.is(BUCKET, dayBucket),
                // sigh DBUpdate.inc only takes integers, but we have a long.
                new DBUpdate.Builder()
                        .addOperation("$inc", "input." + escapedNodeId,
                                new SingleUpdateOperationValue(false, false, inLastMinute))
                        .addOperation("$inc", "output." + escapedNodeId,
                                new SingleUpdateOperationValue(false, false, outLastMinute))
                        .addOperation("$inc", "decoded." + escapedNodeId,
                                new SingleUpdateOperationValue(false, false, decodedLastMinute)),
                true, false);
        if (update.getN() == 0) {
            LOG.warn("Unable to update traffic of node {}: {}", nodeId, update);
        }
    }

    /**
     * Method included for backwards compatibility in pre-5.0 Graylog versions.
     * {@see #clusterTrafficOfLastDays(Duration, Interval, boolean)}
     */
    public TrafficHistogram clusterTrafficOfLastDays(Duration daysToIncludeDuration, Interval interval) {
        return clusterTrafficOfLastDays(daysToIncludeDuration, interval, true);
    }

    /**
     * Queries traffic for the specified duration.
     * 
* The from-date is considered to be the start of the day that the duration intersects with in the past. * For example, if a duration of 1.5 days is specified, then traffic starting from the beginning of two days ago * will be returned. *
* The to-date is considered to be the current date/time (now) when {@code includeToday} false, otherwise then the * end of the previous day will be used. */ public TrafficHistogram clusterTrafficOfLastDays(Duration daysToIncludeDuration, Interval interval, boolean includeToday) { final ImmutableMap.Builder inputBuilder = ImmutableMap.builder(); final ImmutableMap.Builder outputBuilder = ImmutableMap.builder(); final ImmutableMap.Builder decodedBuilder = ImmutableMap.builder(); final DateTime now = Tools.nowUTC(); // Include traffic up until the current timestamp if includeToday is true. // Otherwise, default to the end of the previous day. final DateTime to = includeToday ? now : getDayBucketStart(now).minusMillis(1); // Make sure to include the full first day final DateTime from = getDayBucketStart(now).minus(daysToIncludeDuration); final DBQuery.Query query = DBQuery.and( DBQuery.lessThanEquals(BUCKET, to), DBQuery.greaterThanEquals(BUCKET, from) ); try (DBCursor cursor = db.find(query)) { cursor.forEach(trafficDto -> { inputBuilder.put(trafficDto.bucket(), trafficDto.input().values().stream().mapToLong(Long::valueOf).sum()); outputBuilder.put(trafficDto.bucket(), trafficDto.output().values().stream().mapToLong(Long::valueOf).sum()); decodedBuilder.put(trafficDto.bucket(), trafficDto.decoded().values().stream().mapToLong(Long::valueOf).sum()); }); Map inputHistogram = inputBuilder.build(); Map outputHistogram = outputBuilder.build(); Map decodedHistogram = decodedBuilder.build(); // we might need to aggregate the hourly database values to their UTC daily buckets if (interval == Interval.DAILY) { inputHistogram = aggregateToDaily(inputHistogram); outputHistogram = aggregateToDaily(outputHistogram); decodedHistogram = aggregateToDaily(decodedHistogram); } return TrafficHistogram.create(from, to, inputHistogram, outputHistogram, decodedHistogram); } catch (Exception e) { // TODO: remove this diagnostic logging after fixing https://github.com/Graylog2/graylog2-server/issues/9559 LOG.error("Unable to load traffic data range {} to {}", from, to); throw e; } } private TreeMap aggregateToDaily(Map histogram) { return histogram.entrySet().stream() .collect(Collectors.groupingBy(entry -> entry.getKey().withTimeAtStartOfDay(), TreeMap::new, Collectors.mapping(Map.Entry::getValue, Collectors.summingLong(Long::valueOf)))); } public enum Interval { HOURLY, DAILY } @AutoValue @JsonAutoDetect public abstract static class TrafficHistogram { @JsonCreator public static TrafficHistogram create(@JsonProperty("from") DateTime from, @JsonProperty("to") DateTime to, @JsonProperty("input") Map input, @JsonProperty("output") Map output, @JsonProperty("decoded") Map decoded) { return new AutoValue_TrafficCounterService_TrafficHistogram(from, to, input, output, decoded); } @JsonProperty public abstract DateTime from(); @JsonProperty public abstract DateTime to(); @JsonProperty public abstract Map input(); @JsonProperty public abstract Map output(); @JsonProperty public abstract Map decoded(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy