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

org.rapidoid.timeseries.TimeSeries Maven / Gradle / Ivy

/*-
 * #%L
 * rapidoid-commons
 * %%
 * Copyright (C) 2014 - 2017 Nikolche Mihajlovski and contributors
 * %%
 * 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.
 * #L%
 */

package org.rapidoid.timeseries;

import org.rapidoid.RapidoidThing;
import org.rapidoid.annotation.Authors;
import org.rapidoid.annotation.Since;
import org.rapidoid.collection.Coll;
import org.rapidoid.commons.Stats;
import org.rapidoid.u.U;
import org.rapidoid.util.SlidingWindowList;

import java.util.*;


@Authors("Nikolche Mihajlovski")
@Since("5.1.0")
public class TimeSeries extends RapidoidThing {

	private static final int OVERVIEW_SIZE_THRESHOLD = 120;

	private static final long MILLIS_IN_MINUTE = 60 * 1000;
	private static final long MILLIS_IN_HOUR = 60 * MILLIS_IN_MINUTE;
	private static final long MILLIS_IN_DAY = 24 * MILLIS_IN_HOUR;
	private static final long MILLIS_IN_MONTH = 28 * MILLIS_IN_DAY; // simplified as 4 weeks

	private final List values;

	private final Stats stats = new Stats();

	private final Map monthly = Coll.autoExpandingMap(Long.class, Stats.class);

	private final Map daily = Coll.autoExpandingMap(Long.class, Stats.class);

	private final Map hourly = Coll.autoExpandingMap(Long.class, Stats.class);

	private final Map minutely = Coll.autoExpandingMap(Long.class, Stats.class);

	private final Map perTenSeconds = Coll.autoExpandingMap(Long.class, Stats.class);

	private volatile String title;

	public TimeSeries() {
		this(7 * 24 * 3600);
	}

	public TimeSeries(int maxSize) {
		this.values = new SlidingWindowList<>(maxSize);
	}

	public void put(long timestamp, double value) {

		synchronized (this) {
			TSValue ts = new TSValue(timestamp, value);

			int pos = Collections.binarySearch(values, ts);
			if (pos < 0) pos = ~pos;

			values.add(pos, ts);
		}

		stats.add(value);

		long month = month(timestamp);
		long day = day(timestamp);
		long hour = hour(timestamp);
		long minute = minute(timestamp);

		monthly.get(month).add(value);
		daily.get(day).add(value);
		hourly.get(hour).add(value);
		minutely.get(minute).add(value);
	}

	public NavigableMap values() {
		return null;
	}

	@Override
	public String toString() {
		return U.frmt("TimeSerie(%s)", stats);
	}

	public NavigableMap overview() {
		synchronized (this) {
			if (!values.isEmpty()) {
				return overviewOf(U.first(values).timestamp, U.last(values).timestamp);
			} else {
				return new TreeMap<>();
			}
		}
	}

	public NavigableMap overview(long from, long to) {
		return overviewOf(from, to);
	}

	private NavigableMap overviewOf(long from, long to) {

		NavigableMap overview = new TreeMap();

		long diff = to - from;
		U.must(diff >= 0);

		synchronized (this) {
			if (values.size() <= OVERVIEW_SIZE_THRESHOLD) {
				putAll(overview, values);
				return overview;
			}
		}

		double diffMinutes = ((double) diff) / MILLIS_IN_MINUTE;
		double diffHours = diffMinutes / 60;
		double diffDays = diffHours / 24;

		if (diffDays > 180) { // more than 6 months => monthly
			long fromMonth = month(from);
			long toMonth = month(to);

			for (long month = fromMonth; month <= toMonth; month++) {
				double avg = monthly.get(month).avg();
				overview.put(month * MILLIS_IN_MONTH, avg);
			}

			return overview;
		}

		if (diffDays > 15) { // 15 - 180 days -> daily
			long fromDay = day(from);
			long toDay = day(to);

			for (long day = fromDay; day <= toDay; day++) {
				double avg = daily.get(day).avg();
				overview.put(day * MILLIS_IN_DAY, avg);
			}

			return overview;
		}

		if (diffDays > 0.25) { // 6 hours - 14 days -> hourly
			long fromHour = hour(from);
			long toHour = hour(to);

			for (long hour = fromHour; hour <= toHour; hour++) {
				double avg = hourly.get(hour).avg();
				overview.put(hour * MILLIS_IN_HOUR, avg);
			}

			return overview;
		}

		if (diffMinutes > 30) { // more than 30 minutes -> minutely

			long fromMinute = minute(from);
			long toMinute = minute(to);

			for (long minute = fromMinute; minute <= toMinute; minute++) {
				double avg = minutely.get(minute).avg();
				overview.put(minute * MILLIS_IN_MINUTE, avg);
			}

			return overview;
		}

		// less than 30 minutes
		synchronized (this) {

			int pos1 = Collections.binarySearch(values, new TSValue(from, 0));
			if (pos1 < 0) pos1 = ~pos1;

			int pos2 = Collections.binarySearch(values, new TSValue(to, 0));
			if (pos2 < 0) pos2 = ~pos2;

			List sub = pos1 <= pos2 ? values.subList(pos1, pos2) : values.subList(pos2, pos1);
			putAll(overview, sub);
		}

		return overview;
	}

	private void putAll(Map dest, List src) {
		for (TSValue ts : src) {
			dest.put(ts.timestamp, ts.value);
		}
	}

	private static long month(long timestamp) {
		return timestamp / MILLIS_IN_MONTH;
	}

	private static long day(long timestamp) {
		return timestamp / MILLIS_IN_DAY;
	}

	private static long hour(long timestamp) {
		return timestamp / MILLIS_IN_HOUR;
	}

	private static long minute(long timestamp) {
		return timestamp / MILLIS_IN_MINUTE;
	}

	public TimeSeries title(String title) {
		this.title = title;
		return this;
	}

	public String title() {
		return title;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy