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