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  * Developers:    Sasa Markovic (saxon@jrobin.org)
11  *
12  *
13  * This library is free software; you can redistribute it and/or modify it under the terms
14  * of the GNU Lesser General Public License as published by the Free Software Foundation;
15  * either version 2.1 of the License, or (at your option) any later version.
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 import java.util.StringTokenizer;
30
31 /**
32  * <p>Class to represent data source values for the given timestamp. Objects of this
33  * class are never created directly (no public constructor is provided). To learn more how
34  * to update RRDs, see RRDTool's
35  * <a href="../../../../man/rrdupdate.html" target="man">rrdupdate man page</a>.
36  * <p/>
37  * <p>To update a RRD with JRobin use the following procedure:</p>
38  * <p/>
39  * <ol>
40  * <li>Obtain empty Sample object by calling method {@link RrdDb#createSample(long)
41  * createSample()} on respective {@link RrdDb RrdDb} object.
42  * <li>Adjust Sample timestamp if necessary (see {@link #setTime(long) setTime()} method).
43  * <li>Supply data source values (see {@link #setValue(String, double) setValue()}).
44  * <li>Call Sample's {@link #update() update()} method.
45  * </ol>
46  * <p/>
47  * <p>Newly created Sample object contains all data source values set to 'unknown'.
48  * You should specifify only 'known' data source values. However, if you want to specify
49  * 'unknown' values too, use <code>Double.NaN</code>.</p>
50  *
51  * @author <a href="mailto:saxon@jrobin.org">Sasa Markovic</a>
52  */

53 public class Sample {
54     private RrdDb parentDb;
55     private long time;
56     private String[] dsNames;
57     private double[] values;
58
59     Sample(RrdDb parentDb, long time) throws IOException {
60         this.parentDb = parentDb;
61         this.time = time;
62         this.dsNames = parentDb.getDsNames();
63         values = new double[dsNames.length];
64         clearCurrentValues();
65     }
66
67     private Sample clearCurrentValues() {
68         for (int i = 0; i < values.length; i++) {
69             values[i] = Double.NaN;
70         }
71         return this;
72     }
73
74     /**
75      * Sets single data source value in the sample.
76      *
77      * @param dsName Data source name.
78      * @param value  Data source value.
79      * @return This <code>Sample</code> object
80      * @throws RrdException Thrown if invalid data source name is supplied.
81      */

82     public Sample setValue(String dsName, double value) throws RrdException {
83         for (int i = 0; i < values.length; i++) {
84             if (dsNames[i].equals(dsName)) {
85                 values[i] = value;
86                 return this;
87             }
88         }
89         throw new RrdException("Datasource " + dsName + " not found");
90     }
91
92     /**
93      * Sets single datasource value using data source index. Data sources are indexed by
94      * the order specified during RRD creation (zero-based).
95      *
96      * @param i     Data source index
97      * @param value Data source values
98      * @return This <code>Sample</code> object
99      * @throws RrdException Thrown if data source index is invalid.
100      */

101     public Sample setValue(int i, double value) throws RrdException {
102         if (i < values.length) {
103             values[i] = value;
104             return this;
105         }
106         else {
107             throw new RrdException("Sample datasource index " + i + " out of bounds");
108         }
109     }
110
111     /**
112      * Sets some (possibly all) data source values in bulk. Data source values are
113      * assigned in the order of their definition inside the RRD.
114      *
115      * @param values Data source values.
116      * @return This <code>Sample</code> object
117      * @throws RrdException Thrown if the number of supplied values is zero or greater
118      *                      than the number of data sources defined in the RRD.
119      */

120     public Sample setValues(double[] values) throws RrdException {
121         if (values.length <= this.values.length) {
122             System.arraycopy(values, 0, this.values, 0, values.length);
123             return this;
124         }
125         else {
126             throw new RrdException("Invalid number of values specified (found " +
127                     values.length + ", only " + dsNames.length + " allowed)");
128         }
129     }
130
131     /**
132      * Returns all current data source values in the sample.
133      *
134      * @return Data source values.
135      */

136     public double[] getValues() {
137         return values;
138     }
139
140     /**
141      * Returns sample timestamp (in seconds, without milliseconds).
142      *
143      * @return Sample timestamp.
144      */

145     public long getTime() {
146         return time;
147     }
148
149     /**
150      * Sets sample timestamp. Timestamp should be defined in seconds (without milliseconds).
151      *
152      * @param time New sample timestamp.
153      * @return This <code>Sample</code> object
154      */

155     public Sample setTime(long time) {
156         this.time = time;
157         return this;
158     }
159
160     /**
161      * Returns an array of all data source names. If you try to set value for the data source
162      * name not in this array, an exception is thrown.
163      *
164      * @return Acceptable data source names.
165      */

166     public String[] getDsNames() {
167         return dsNames;
168     }
169
170     /**
171      * <p>Sets sample timestamp and data source values in a fashion similar to RRDTool.
172      * Argument string should be composed in the following way:
173      * <code>timestamp:value1:value2:...:valueN</code>.</p>
174      * <p/>
175      * <p>You don't have to supply all datasource values. Unspecified values will be treated
176      * as unknowns. To specify unknown value in the argument string, use letter 'U'
177      *
178      * @param timeAndValues String made by concatenating sample timestamp with corresponding
179      *                      data source values delmited with colons. For example:<p>
180      *                      <pre>
181      *                      1005234132:12.2:35.6:U:24.5
182      *                      NOW:12.2:35.6:U:24.5
183      *                      </pre>
184      *                      'N' stands for the current timestamp (can be replaced with 'NOW')<p>
185      *                      Method will throw an exception if timestamp is invalid (cannot be parsed as Long, and is not 'N'
186      *                      or 'NOW'). Datasource value which cannot be parsed as 'double' will be silently set to NaN.<p>
187      * @return This <code>Sample</code> object
188      * @throws RrdException Thrown if too many datasource values are supplied
189      */

190     public Sample set(String timeAndValues) throws RrdException {
191         StringTokenizer tokenizer = new StringTokenizer(timeAndValues, ":"false);
192         int n = tokenizer.countTokens();
193         if (n > values.length + 1) {
194             throw new RrdException("Invalid number of values specified (found " +
195                     values.length + ", " + dsNames.length + " allowed)");
196         }
197         String timeToken = tokenizer.nextToken();
198         try {
199             time = Long.parseLong(timeToken);
200         }
201         catch (NumberFormatException nfe) {
202             if (timeToken.equalsIgnoreCase("N") || timeToken.equalsIgnoreCase("NOW")) {
203                 time = Util.getTime();
204             }
205             else {
206                 throw new RrdException("Invalid sample timestamp: " + timeToken);
207             }
208         }
209         for (int i = 0; tokenizer.hasMoreTokens(); i++) {
210             try {
211                 values[i] = Double.parseDouble(tokenizer.nextToken());
212             }
213             catch (NumberFormatException nfe) {
214                 // NOP, value is already set to NaN
215             }
216         }
217         return this;
218     }
219
220     /**
221      * Stores sample in the corresponding RRD. If the update operation succeedes,
222      * all datasource values in the sample will be set to Double.NaN (unknown) values.
223      *
224      * @throws IOException  Thrown in case of I/O error.
225      * @throws RrdException Thrown in case of JRobin related error.
226      */

227     public void update() throws IOException, RrdException {
228         parentDb.store(this);
229         clearCurrentValues();
230     }
231
232     /**
233      * <p>Creates sample with the timestamp and data source values supplied
234      * in the argument string and stores sample in the corresponding RRD.
235      * This method is just a shortcut for:</p>
236      * <pre>
237      *     set(timeAndValues);
238      *     update();
239      * </pre>
240      *
241      * @param timeAndValues String made by concatenating sample timestamp with corresponding
242      *                      data source values delmited with colons. For example:<br>
243      *                      <code>1005234132:12.2:35.6:U:24.5</code><br>
244      *                      <code>NOW:12.2:35.6:U:24.5</code>
245      * @throws IOException  Thrown in case of I/O error.
246      * @throws RrdException Thrown in case of JRobin related error.
247      */

248     public void setAndUpdate(String timeAndValues) throws IOException, RrdException {
249         set(timeAndValues);
250         update();
251     }
252
253     /**
254      * Dumps sample content using the syntax of RRDTool's update command.
255      *
256      * @return Sample dump.
257      */

258     public String dump() {
259         StringBuffer buffer = new StringBuffer("update \"");
260         buffer.append(parentDb.getRrdBackend().getPath()).append("\" ").append(time);
261         for (double value : values) {
262             buffer.append(":");
263             buffer.append(Util.formatDouble(value, "U"false));
264         }
265         return buffer.toString();
266     }
267
268     String getRrdToolCommand() {
269         return dump();
270     }
271 }
272