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.vmconlet; 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 */ 031@SuppressWarnings("PMD.DataflowAnomalyAnalysis") 032public class TimeSeries { 033 034 @SuppressWarnings("PMD.LooseCoupling") 035 private final LinkedList<Entry> data = new LinkedList<>(); 036 private final Duration period; 037 038 /** 039 * Instantiates a new time series. 040 * 041 * @param period the period 042 */ 043 public TimeSeries(Duration period) { 044 this.period = period; 045 } 046 047 /** 048 * Adds data to the series. 049 * 050 * @param time the time 051 * @param numbers the numbers 052 * @return the time series 053 */ 054 @SuppressWarnings("PMD.AvoidLiteralsInIfCondition") 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 public List<Entry> entries() { 087 synchronized (data) { 088 return new ArrayList<>(data); 089 } 090 } 091 092 /** 093 * The Class Entry. 094 */ 095 public static class Entry { 096 private final Instant timestamp; 097 private final Number[] values; 098 099 /** 100 * Instantiates a new entry. 101 * 102 * @param time the time 103 * @param numbers the numbers 104 */ 105 @SuppressWarnings("PMD.ArrayIsStoredDirectly") 106 public Entry(Instant time, Number... numbers) { 107 timestamp = time; 108 values = numbers; 109 } 110 111 /** 112 * Returns the entry's time. 113 * 114 * @return the instant 115 */ 116 public Instant getTime() { 117 return timestamp; 118 } 119 120 /** 121 * Returns the values. 122 * 123 * @return the number[] 124 */ 125 @SuppressWarnings("PMD.MethodReturnsInternalArray") 126 public Number[] getValues() { 127 return values; 128 } 129 130 /** 131 * Returns `true` if both entries have the same values. 132 * 133 * @param other the other 134 * @return true, if successful 135 */ 136 public boolean valuesEqual(Entry other) { 137 return Arrays.equals(values, other.values); 138 } 139 } 140}