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 * Developers: Sasa Markovic (saxon@jrobin.org)
011 *
012 *
013 * This library is free software; you can redistribute it and/or modify it under the terms
014 * of the GNU Lesser General Public License as published by the Free Software Foundation;
015 * either version 2.1 of the License, or (at your option) any later version.
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 single RRD archive in a RRD with its internal state.
032 * Normally, you don't need methods to manipulate archive objects directly
033 * because JRobin framework does it automatically for you.<p>
034 * <p/>
035 * Each archive object consists of three parts: archive definition, archive state objects
036 * (one state object for each datasource) and round robin archives (one round robin for
037 * each datasource). API (read-only) is provided to access each of theese parts.<p>
038 *
039 * @author <a href="mailto:saxon@jrobin.org">Sasa Markovic</a>
040 */
041 public class Archive implements RrdUpdater, ConsolFuns {
042 private RrdDb parentDb;
043 // definition
044 private RrdString consolFun;
045 private RrdDouble xff;
046 private RrdInt steps, rows;
047 // state
048 private Robin[] robins;
049 private ArcState[] states;
050
051 Archive(RrdDb parentDb, ArcDef arcDef) throws IOException {
052 boolean shouldInitialize = arcDef != null;
053 this.parentDb = parentDb;
054 consolFun = new RrdString(this, true); // constant, may be cached
055 xff = new RrdDouble(this);
056 steps = new RrdInt(this, true); // constant, may be cached
057 rows = new RrdInt(this, true); // constant, may be cached
058 if (shouldInitialize) {
059 consolFun.set(arcDef.getConsolFun());
060 xff.set(arcDef.getXff());
061 steps.set(arcDef.getSteps());
062 rows.set(arcDef.getRows());
063 }
064 int n = parentDb.getHeader().getDsCount();
065 states = new ArcState[n];
066 robins = new Robin[n];
067 for (int i = 0; i < n; i++) {
068 states[i] = new ArcState(this, shouldInitialize);
069 int numRows = rows.get();
070 robins[i] = new Robin(this, numRows, shouldInitialize);
071 }
072 }
073
074 // read from XML
075 Archive(RrdDb parentDb, DataImporter reader, int arcIndex) throws IOException, RrdException {
076 this(parentDb, new ArcDef(
077 reader.getConsolFun(arcIndex), reader.getXff(arcIndex),
078 reader.getSteps(arcIndex), reader.getRows(arcIndex)));
079 int n = parentDb.getHeader().getDsCount();
080 for (int i = 0; i < n; i++) {
081 // restore state
082 states[i].setAccumValue(reader.getStateAccumValue(arcIndex, i));
083 states[i].setNanSteps(reader.getStateNanSteps(arcIndex, i));
084 // restore robins
085 double[] values = reader.getValues(arcIndex, i);
086 robins[i].update(values);
087 }
088 }
089
090 /**
091 * Returns archive time step in seconds. Archive step is equal to RRD step
092 * multiplied with the number of archive steps.
093 *
094 * @return Archive time step in seconds
095 * @throws IOException Thrown in case of I/O error.
096 */
097 public long getArcStep() throws IOException {
098 long step = parentDb.getHeader().getStep();
099 return step * steps.get();
100 }
101
102 String dump() throws IOException {
103 StringBuffer buffer = new StringBuffer("== ARCHIVE ==\n");
104 buffer.append("RRA:").append(consolFun.get()).append(":").append(xff.get()).append(":").append(steps.get()).
105 append(":").append(rows.get()).append("\n");
106 buffer.append("interval [").append(getStartTime()).append(", ").append(getEndTime()).append("]" + "\n");
107 for (int i = 0; i < robins.length; i++) {
108 buffer.append(states[i].dump());
109 buffer.append(robins[i].dump());
110 }
111 return buffer.toString();
112 }
113
114 RrdDb getParentDb() {
115 return parentDb;
116 }
117
118 void archive(int dsIndex, double value, long numUpdates) throws IOException {
119 Robin robin = robins[dsIndex];
120 ArcState state = states[dsIndex];
121 long step = parentDb.getHeader().getStep();
122 long lastUpdateTime = parentDb.getHeader().getLastUpdateTime();
123 long updateTime = Util.normalize(lastUpdateTime, step) + step;
124 long arcStep = getArcStep();
125 // finish current step
126 while (numUpdates > 0) {
127 accumulate(state, value);
128 numUpdates--;
129 if (updateTime % arcStep == 0) {
130 finalizeStep(state, robin);
131 break;
132 }
133 else {
134 updateTime += step;
135 }
136 }
137 // update robin in bulk
138 int bulkUpdateCount = (int) Math.min(numUpdates / steps.get(), (long) rows.get());
139 robin.bulkStore(value, bulkUpdateCount);
140 // update remaining steps
141 long remainingUpdates = numUpdates % steps.get();
142 for (long i = 0; i < remainingUpdates; i++) {
143 accumulate(state, value);
144 }
145 }
146
147 private void accumulate(ArcState state, double value) throws IOException {
148 if (Double.isNaN(value)) {
149 state.setNanSteps(state.getNanSteps() + 1);
150 }
151 else {
152 if (consolFun.get().equals(CF_MIN)) {
153 state.setAccumValue(Util.min(state.getAccumValue(), value));
154 }
155 else if (consolFun.get().equals(CF_MAX)) {
156 state.setAccumValue(Util.max(state.getAccumValue(), value));
157 }
158 else if (consolFun.get().equals(CF_LAST)) {
159 state.setAccumValue(value);
160 }
161 else if (consolFun.get().equals(CF_AVERAGE)) {
162 state.setAccumValue(Util.sum(state.getAccumValue(), value));
163 }
164 }
165 }
166
167 private void finalizeStep(ArcState state, Robin robin) throws IOException {
168 // should store
169 long arcSteps = steps.get();
170 double arcXff = xff.get();
171 long nanSteps = state.getNanSteps();
172 //double nanPct = (double) nanSteps / (double) arcSteps;
173 double accumValue = state.getAccumValue();
174 if (nanSteps <= arcXff * arcSteps && !Double.isNaN(accumValue)) {
175 if (consolFun.get().equals(CF_AVERAGE)) {
176 accumValue /= (arcSteps - nanSteps);
177 }
178 robin.store(accumValue);
179 }
180 else {
181 robin.store(Double.NaN);
182 }
183 state.setAccumValue(Double.NaN);
184 state.setNanSteps(0);
185 }
186
187 /**
188 * Returns archive consolidation function ("AVERAGE", "MIN", "MAX" or "LAST").
189 *
190 * @return Archive consolidation function.
191 * @throws IOException Thrown in case of I/O error.
192 */
193 public String getConsolFun() throws IOException {
194 return consolFun.get();
195 }
196
197 /**
198 * Returns archive X-files factor.
199 *
200 * @return Archive X-files factor (between 0 and 1).
201 * @throws IOException Thrown in case of I/O error.
202 */
203 public double getXff() throws IOException {
204 return xff.get();
205 }
206
207 /**
208 * Returns the number of archive steps.
209 *
210 * @return Number of archive steps.
211 * @throws IOException Thrown in case of I/O error.
212 */
213 public int getSteps() throws IOException {
214 return steps.get();
215 }
216
217 /**
218 * Returns the number of archive rows.
219 *
220 * @return Number of archive rows.
221 * @throws IOException Thrown in case of I/O error.
222 */
223 public int getRows() throws IOException {
224 return rows.get();
225 }
226
227 /**
228 * Returns current starting timestamp. This value is not constant.
229 *
230 * @return Timestamp corresponding to the first archive row
231 * @throws IOException Thrown in case of I/O error.
232 */
233 public long getStartTime() throws IOException {
234 long endTime = getEndTime();
235 long arcStep = getArcStep();
236 long numRows = rows.get();
237 return endTime - (numRows - 1) * arcStep;
238 }
239
240 /**
241 * Returns current ending timestamp. This value is not constant.
242 *
243 * @return Timestamp corresponding to the last archive row
244 * @throws IOException Thrown in case of I/O error.
245 */
246 public long getEndTime() throws IOException {
247 long arcStep = getArcStep();
248 long lastUpdateTime = parentDb.getHeader().getLastUpdateTime();
249 return Util.normalize(lastUpdateTime, arcStep);
250 }
251
252 /**
253 * Returns the underlying archive state object. Each datasource has its
254 * corresponding ArcState object (archive states are managed independently
255 * for each RRD datasource).
256 *
257 * @param dsIndex Datasource index
258 * @return Underlying archive state object
259 */
260 public ArcState getArcState(int dsIndex) {
261 return states[dsIndex];
262 }
263
264 /**
265 * Returns the underlying round robin archive. Robins are used to store actual
266 * archive values on a per-datasource basis.
267 *
268 * @param dsIndex Index of the datasource in the RRD.
269 * @return Underlying round robin archive for the given datasource.
270 */
271 public Robin getRobin(int dsIndex) {
272 return robins[dsIndex];
273 }
274
275 FetchData fetchData(FetchRequest request) throws IOException, RrdException {
276 long arcStep = getArcStep();
277 long fetchStart = Util.normalize(request.getFetchStart(), arcStep);
278 long fetchEnd = Util.normalize(request.getFetchEnd(), arcStep);
279 if (fetchEnd < request.getFetchEnd()) {
280 fetchEnd += arcStep;
281 }
282 long startTime = getStartTime();
283 long endTime = getEndTime();
284 String[] dsToFetch = request.getFilter();
285 if (dsToFetch == null) {
286 dsToFetch = parentDb.getDsNames();
287 }
288 int dsCount = dsToFetch.length;
289 int ptsCount = (int) ((fetchEnd - fetchStart) / arcStep + 1);
290 long[] timestamps = new long[ptsCount];
291 double[][] values = new double[dsCount][ptsCount];
292 long matchStartTime = Math.max(fetchStart, startTime);
293 long matchEndTime = Math.min(fetchEnd, endTime);
294 double[][] robinValues = null;
295 if (matchStartTime <= matchEndTime) {
296 // preload robin values
297 int matchCount = (int) ((matchEndTime - matchStartTime) / arcStep + 1);
298 int matchStartIndex = (int) ((matchStartTime - startTime) / arcStep);
299 robinValues = new double[dsCount][];
300 for (int i = 0; i < dsCount; i++) {
301 int dsIndex = parentDb.getDsIndex(dsToFetch[i]);
302 robinValues[i] = robins[dsIndex].getValues(matchStartIndex, matchCount);
303 }
304 }
305 for (int ptIndex = 0; ptIndex < ptsCount; ptIndex++) {
306 long time = fetchStart + ptIndex * arcStep;
307 timestamps[ptIndex] = time;
308 for (int i = 0; i < dsCount; i++) {
309 double value = Double.NaN;
310 if (time >= matchStartTime && time <= matchEndTime) {
311 // inbound time
312 int robinValueIndex = (int) ((time - matchStartTime) / arcStep);
313 assert robinValues != null;
314 value = robinValues[i][robinValueIndex];
315 }
316 values[i][ptIndex] = value;
317 }
318 }
319 FetchData fetchData = new FetchData(this, request);
320 fetchData.setTimestamps(timestamps);
321 fetchData.setValues(values);
322 return fetchData;
323 }
324
325 void appendXml(XmlWriter writer) throws IOException {
326 writer.startTag("rra");
327 writer.writeTag("cf", consolFun.get());
328 writer.writeComment(getArcStep() + " seconds");
329 writer.writeTag("pdp_per_row", steps.get());
330 writer.writeTag("xff", xff.get());
331 writer.startTag("cdp_prep");
332 for (ArcState state : states) {
333 state.appendXml(writer);
334 }
335 writer.closeTag(); // cdp_prep
336 writer.startTag("database");
337 long startTime = getStartTime();
338 for (int i = 0; i < rows.get(); i++) {
339 long time = startTime + i * getArcStep();
340 writer.writeComment(Util.getDate(time) + " / " + time);
341 writer.startTag("row");
342 for (Robin robin : robins) {
343 writer.writeTag("v", robin.getValue(i));
344 }
345 writer.closeTag(); // row
346 }
347 writer.closeTag(); // database
348 writer.closeTag(); // rra
349 }
350
351 /**
352 * Copies object's internal state to another Archive object.
353 *
354 * @param other New Archive object to copy state to
355 * @throws IOException Thrown in case of I/O error
356 * @throws RrdException Thrown if supplied argument is not an Archive object
357 */
358 public void copyStateTo(RrdUpdater other) throws IOException, RrdException {
359 if (!(other instanceof Archive)) {
360 throw new RrdException(
361 "Cannot copy Archive object to " + other.getClass().getName());
362 }
363 Archive arc = (Archive) other;
364 if (!arc.consolFun.get().equals(consolFun.get())) {
365 throw new RrdException("Incompatible consolidation functions");
366 }
367 if (arc.steps.get() != steps.get()) {
368 throw new RrdException("Incompatible number of steps");
369 }
370 int count = parentDb.getHeader().getDsCount();
371 for (int i = 0; i < count; i++) {
372 int j = Util.getMatchingDatasourceIndex(parentDb, i, arc.parentDb);
373 if (j >= 0) {
374 states[i].copyStateTo(arc.states[j]);
375 robins[i].copyStateTo(arc.robins[j]);
376 }
377 }
378 }
379
380 /**
381 * Sets X-files factor to a new value.
382 *
383 * @param xff New X-files factor value. Must be >= 0 and < 1.
384 * @throws RrdException Thrown if invalid value is supplied
385 * @throws IOException Thrown in case of I/O error
386 */
387 public void setXff(double xff) throws RrdException, IOException {
388 if (xff < 0D || xff >= 1D) {
389 throw new RrdException("Invalid xff supplied (" + xff + "), must be >= 0 and < 1");
390 }
391 this.xff.set(xff);
392 }
393
394 /**
395 * Returns the underlying storage (backend) object which actually performs all
396 * I/O operations.
397 *
398 * @return I/O backend object
399 */
400 public RrdBackend getRrdBackend() {
401 return parentDb.getRrdBackend();
402 }
403
404 /**
405 * Required to implement RrdUpdater interface. You should never call this method directly.
406 *
407 * @return Allocator object
408 */
409 public RrdAllocator getRrdAllocator() {
410 return parentDb.getRrdAllocator();
411 }
412 }