View Javadoc

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 }