001/* 002 * VM-Operator 003 * Copyright (C) 2023 Michael N. Lipp 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <https://www.gnu.org/licenses/>. 017 */ 018 019package org.jdrupes.vmoperator.vmmgmt; 020 021import java.time.Duration; 022import java.time.Instant; 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.LinkedList; 026import java.util.List; 027 028/** 029 * The Class TimeSeries. 030 */ 031public class TimeSeries { 032 033 @SuppressWarnings("PMD.LooseCoupling") 034 private final LinkedList<Entry> data = new LinkedList<>(); 035 private final Duration period; 036 037 /** 038 * Instantiates a new time series. 039 * 040 * @param period the period 041 */ 042 public TimeSeries(Duration period) { 043 this.period = period; 044 } 045 046 /** 047 * Adds data to the series. 048 * 049 * @param time the time 050 * @param numbers the numbers 051 * @return the time series 052 */ 053 @SuppressWarnings({ "PMD.AvoidLiteralsInIfCondition", 054 "PMD.AvoidSynchronizedStatement" }) 055 public TimeSeries add(Instant time, Number... numbers) { 056 var newEntry = new Entry(time, numbers); 057 boolean nothingNew = false; 058 synchronized (data) { 059 if (data.size() >= 2) { 060 var lastEntry = data.get(data.size() - 1); 061 var lastButOneEntry = data.get(data.size() - 2); 062 nothingNew = lastEntry.valuesEqual(lastButOneEntry) 063 && lastEntry.valuesEqual(newEntry); 064 } 065 if (nothingNew) { 066 data.removeLast(); 067 } 068 data.add(new Entry(time, numbers)); 069 070 // Purge 071 Instant limit = time.minus(period); 072 while (data.size() > 2 073 && data.get(0).getTime().isBefore(limit) 074 && data.get(1).getTime().isBefore(limit)) { 075 data.removeFirst(); 076 } 077 } 078 return this; 079 } 080 081 /** 082 * Returns the entries. 083 * 084 * @return the list 085 */ 086 @SuppressWarnings("PMD.AvoidSynchronizedStatement") 087 public List<Entry> entries() { 088 synchronized (data) { 089 return new ArrayList<>(data); 090 } 091 } 092 093 /** 094 * The Class Entry. 095 */ 096 public static class Entry { 097 private final Instant timestamp; 098 private final Number[] values; 099 100 /** 101 * Instantiates a new entry. 102 * 103 * @param time the time 104 * @param numbers the numbers 105 */ 106 @SuppressWarnings("PMD.ArrayIsStoredDirectly") 107 public Entry(Instant time, Number... numbers) { 108 timestamp = time; 109 values = numbers; 110 } 111 112 /** 113 * Returns the entry's time. 114 * 115 * @return the instant 116 */ 117 public Instant getTime() { 118 return timestamp; 119 } 120 121 /** 122 * Returns the values. 123 * 124 * @return the number[] 125 */ 126 @SuppressWarnings("PMD.MethodReturnsInternalArray") 127 public Number[] getValues() { 128 return values; 129 } 130 131 /** 132 * Returns `true` if both entries have the same values. 133 * 134 * @param other the other 135 * @return true, if successful 136 */ 137 public boolean valuesEqual(Entry other) { 138 return Arrays.equals(values, other.values); 139 } 140 } 141}