1 /* ============================================================
2  * JRobin : Pure java implementation of RRDTool's functionality
3  * ============================================================
4  *
5  * Project Info:  http://www.jrobin.org
6  * Project Lead:  Sasa Markovic (saxon@jrobin.org);
7  *
8  * (C) Copyright 2003-2005, by Sasa Markovic.
9  *
10  * This library is free software; you can redistribute it and/or modify it under the terms
11  * of the GNU Lesser General Public License as published by the Free Software Foundation;
12  * either version 2.1 of the License, or (at your option) any later version.
13  *
14  * Developers:    Sasa Markovic (saxon@jrobin.org)
15  *
16  *
17  * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
18  * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
19  * See the GNU Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public License along with this
22  * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
23  * Boston, MA 02111-1307, USA.
24  */

25
26 package org.jrobin.core;
27
28 import java.io.IOException;
29
30 /**
31  * Class to represent archive values for a single datasource. Robin class is the heart of
32  * the so-called "round robin database" concept. Basically, each Robin object is a
33  * fixed length array of double values. Each double value reperesents consolidated, archived
34  * value for the specific timestamp. When the underlying array of double values gets completely
35  * filled, new values will replace the oldest ones.<p>
36  * <p/>
37  * Robin object does not hold values in memory - such object could be quite large.
38  * Instead of it, Robin reads them from the backend I/O only when necessary.
39  *
40  * @author <a href="mailto:saxon@jrobin.org">Sasa Markovic</a>
41  */

42 public class Robin implements RrdUpdater {
43     private Archive parentArc;
44     private RrdInt pointer;
45     private RrdDoubleArray values;
46     private int rows;
47
48     Robin(Archive parentArc, int rows, boolean shouldInitialize) throws IOException {
49         this.parentArc = parentArc;
50         this.pointer = new RrdInt(this);
51         this.values = new RrdDoubleArray(this, rows);
52         this.rows = rows;
53         if (shouldInitialize) {
54             pointer.set(0);
55             values.set(0, Double.NaN, rows);
56         }
57     }
58
59     /**
60      * Fetches all archived values.
61      *
62      * @return Array of double archive values, starting from the oldest one.
63      * @throws IOException Thrown in case of I/O specific error.
64      */

65     public double[] getValues() throws IOException {
66         return getValues(0, rows);
67     }
68
69     // stores single value
70     void store(double newValue) throws IOException {
71         int position = pointer.get();
72         values.set(position, newValue);
73         pointer.set((position + 1) % rows);
74     }
75
76     // stores the same value several times
77     void bulkStore(double newValue, int bulkCount) throws IOException {
78         assert bulkCount <= rows: "Invalid number of bulk updates: " + bulkCount +
79                 " rows=" + rows;
80         int position = pointer.get();
81         // update tail
82         int tailUpdateCount = Math.min(rows - position, bulkCount);
83         values.set(position, newValue, tailUpdateCount);
84         pointer.set((position + tailUpdateCount) % rows);
85         // do we need to update from the start?
86         int headUpdateCount = bulkCount - tailUpdateCount;
87         if (headUpdateCount > 0) {
88             values.set(0, newValue, headUpdateCount);
89             pointer.set(headUpdateCount);
90         }
91     }
92
93     void update(double[] newValues) throws IOException {
94         assert rows == newValues.length: "Invalid number of robin values supplied (" + newValues.length +
95                 "), exactly " + rows + " needed";
96         pointer.set(0);
97         values.writeDouble(0, newValues);
98     }
99
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 }
273