001 /* ============================================================
002 * JRobin : Pure java implementation of RRDTool's functionality
003 * ============================================================
004 *
005 * Project Info: http://www.jrobin.org
006 * Project Lead: Sasa Markovic (saxon@jrobin.org);
007 *
008 * (C) Copyright 2003-2005, by Sasa Markovic.
009 *
010 * This library is free software; you can redistribute it and/or modify it under the terms
011 * of the GNU Lesser General Public License as published by the Free Software Foundation;
012 * either version 2.1 of the License, or (at your option) any later version.
013 *
014 * Developers: Sasa Markovic (saxon@jrobin.org)
015 *
016 *
017 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
018 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
019 * See the GNU Lesser General Public License for more details.
020 *
021 * You should have received a copy of the GNU Lesser General Public License along with this
022 * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
023 * Boston, MA 02111-1307, USA.
024 */
025
026 package org.jrobin.core;
027
028 import java.io.IOException;
029
030 /**
031 * Class to represent archive values for a single datasource. Robin class is the heart of
032 * the so-called "round robin database" concept. Basically, each Robin object is a
033 * fixed length array of double values. Each double value reperesents consolidated, archived
034 * value for the specific timestamp. When the underlying array of double values gets completely
035 * filled, new values will replace the oldest ones.<p>
036 * <p/>
037 * Robin object does not hold values in memory - such object could be quite large.
038 * Instead of it, Robin reads them from the backend I/O only when necessary.
039 *
040 * @author <a href="mailto:saxon@jrobin.org">Sasa Markovic</a>
041 */
042 public class Robin implements RrdUpdater {
043 private Archive parentArc;
044 private RrdInt pointer;
045 private RrdDoubleArray values;
046 private int rows;
047
048 Robin(Archive parentArc, int rows, boolean shouldInitialize) throws IOException {
049 this.parentArc = parentArc;
050 this.pointer = new RrdInt(this);
051 this.values = new RrdDoubleArray(this, rows);
052 this.rows = rows;
053 if (shouldInitialize) {
054 pointer.set(0);
055 values.set(0, Double.NaN, rows);
056 }
057 }
058
059 /**
060 * Fetches all archived values.
061 *
062 * @return Array of double archive values, starting from the oldest one.
063 * @throws IOException Thrown in case of I/O specific error.
064 */
065 public double[] getValues() throws IOException {
066 return getValues(0, rows);
067 }
068
069 // stores single value
070 void store(double newValue) throws IOException {
071 int position = pointer.get();
072 values.set(position, newValue);
073 pointer.set((position + 1) % rows);
074 }
075
076 // stores the same value several times
077 void bulkStore(double newValue, int bulkCount) throws IOException {
078 assert bulkCount <= rows: "Invalid number of bulk updates: " + bulkCount +
079 " rows=" + rows;
080 int position = pointer.get();
081 // update tail
082 int tailUpdateCount = Math.min(rows - position, bulkCount);
083 values.set(position, newValue, tailUpdateCount);
084 pointer.set((position + tailUpdateCount) % rows);
085 // do we need to update from the start?
086 int headUpdateCount = bulkCount - tailUpdateCount;
087 if (headUpdateCount > 0) {
088 values.set(0, newValue, headUpdateCount);
089 pointer.set(headUpdateCount);
090 }
091 }
092
093 void update(double[] newValues) throws IOException {
094 assert rows == newValues.length: "Invalid number of robin values supplied (" + newValues.length +
095 "), exactly " + rows + " needed";
096 pointer.set(0);
097 values.writeDouble(0, newValues);
098 }
099
100 /**
101 * Updates archived values in bulk.
102 *
103 * @param newValues Array of double values to be stored in the archive
104 * @throws IOException Thrown in case of I/O error
105 * @throws RrdException Thrown if the length of the input array is different from the length of
106 * this archive
107 */
108 public void setValues(double[] newValues) throws IOException, RrdException {
109 if (rows != newValues.length) {
110 throw new RrdException("Invalid number of robin values supplied (" + newValues.length +
111 "), exactly " + rows + " needed");
112 }
113 update(newValues);
114 }
115
116 /**
117 * (Re)sets all values in this archive to the same value.
118 *
119 * @param newValue New value
120 * @throws IOException Thrown in case of I/O error
121 */
122 public void setValues(double newValue) throws IOException {
123 double[] values = new double[rows];
124 for (int i = 0; i < values.length; i++) {
125 values[i] = newValue;
126 }
127 update(values);
128 }
129
130 String dump() throws IOException {
131 StringBuffer buffer = new StringBuffer("Robin " + pointer.get() + "/" + rows + ": ");
132 double[] values = getValues();
133 for (double value : values) {
134 buffer.append(Util.formatDouble(value, true)).append(" ");
135 }
136 buffer.append("\n");
137 return buffer.toString();
138 }
139
140 /**
141 * Returns the i-th value from the Robin archive.
142 *
143 * @param index Value index
144 * @return Value stored in the i-th position (the oldest value has zero index)
145 * @throws IOException Thrown in case of I/O specific error.
146 */
147 public double getValue(int index) throws IOException {
148 int arrayIndex = (pointer.get() + index) % rows;
149 return values.get(arrayIndex);
150 }
151
152 /**
153 * Sets the i-th value in the Robin archive.
154 *
155 * @param index index in the archive (the oldest value has zero index)
156 * @param value value to be stored
157 * @throws IOException Thrown in case of I/O specific error.
158 */
159 public void setValue(int index, double value) throws IOException {
160 int arrayIndex = (pointer.get() + index) % rows;
161 values.set(arrayIndex, value);
162 }
163
164 double[] getValues(int index, int count) throws IOException {
165 assert count <= rows: "Too many values requested: " + count + " rows=" + rows;
166 int startIndex = (pointer.get() + index) % rows;
167 int tailReadCount = Math.min(rows - startIndex, count);
168 double[] tailValues = values.get(startIndex, tailReadCount);
169 if (tailReadCount < count) {
170 int headReadCount = count - tailReadCount;
171 double[] headValues = values.get(0, headReadCount);
172 double[] values = new double[count];
173 int k = 0;
174 for (double tailValue : tailValues) {
175 values[k++] = tailValue;
176 }
177 for (double headValue : headValues) {
178 values[k++] = headValue;
179 }
180 return values;
181 }
182 else {
183 return tailValues;
184 }
185 }
186
187 /**
188 * Returns the Archive object to which this Robin object belongs.
189 *
190 * @return Parent Archive object
191 */
192 public Archive getParent() {
193 return parentArc;
194 }
195
196 /**
197 * Returns the size of the underlying array of archived values.
198 *
199 * @return Number of stored values
200 */
201 public int getSize() {
202 return rows;
203 }
204
205 /**
206 * Copies object's internal state to another Robin object.
207 *
208 * @param other New Robin object to copy state to
209 * @throws IOException Thrown in case of I/O error
210 * @throws RrdException Thrown if supplied argument is not a Robin object
211 */
212 public void copyStateTo(RrdUpdater other) throws IOException, RrdException {
213 if (!(other instanceof Robin)) {
214 throw new RrdException(
215 "Cannot copy Robin object to " + other.getClass().getName());
216 }
217 Robin robin = (Robin) other;
218 int rowsDiff = rows - robin.rows;
219 if (rowsDiff == 0) {
220 // Identical dimensions. Do copy in BULK to speed things up
221 robin.pointer.set(pointer.get());
222 robin.values.writeBytes(values.readBytes());
223 }
224 else {
225 // different sizes
226 for (int i = 0; i < robin.rows; i++) {
227 int j = i + rowsDiff;
228 robin.store(j >= 0 ? getValue(j) : Double.NaN);
229 }
230 }
231 }
232
233 /**
234 * Filters values stored in this archive based on the given boundary.
235 * Archived values found to be outside of <code>[minValue, maxValue]</code> interval (inclusive)
236 * will be silently replaced with <code>NaN</code>.
237 *
238 * @param minValue lower boundary
239 * @param maxValue upper boundary
240 * @throws IOException Thrown in case of I/O error
241 */
242 public void filterValues(double minValue, double maxValue) throws IOException {
243 for (int i = 0; i < rows; i++) {
244 double value = values.get(i);
245 if (!Double.isNaN(minValue) && !Double.isNaN(value) && minValue > value) {
246 values.set(i, Double.NaN);
247 }
248 if (!Double.isNaN(maxValue) && !Double.isNaN(value) && maxValue < value) {
249 values.set(i, Double.NaN);
250 }
251 }
252 }
253
254 /**
255 * Returns the underlying storage (backend) object which actually performs all
256 * I/O operations.
257 *
258 * @return I/O backend object
259 */
260 public RrdBackend getRrdBackend() {
261 return parentArc.getRrdBackend();
262 }
263
264 /**
265 * Required to implement RrdUpdater interface. You should never call this method directly.
266 *
267 * @return Allocator object
268 */
269 public RrdAllocator getRrdAllocator() {
270 return parentArc.getRrdAllocator();
271 }
272 }