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    import java.util.StringTokenizer;
030    
031    /**
032     * <p>Class to represent data source values for the given timestamp. Objects of this
033     * class are never created directly (no public constructor is provided). To learn more how
034     * to update RRDs, see RRDTool's
035     * <a href="../../../../man/rrdupdate.html" target="man">rrdupdate man page</a>.
036     * <p/>
037     * <p>To update a RRD with JRobin use the following procedure:</p>
038     * <p/>
039     * <ol>
040     * <li>Obtain empty Sample object by calling method {@link RrdDb#createSample(long)
041     * createSample()} on respective {@link RrdDb RrdDb} object.
042     * <li>Adjust Sample timestamp if necessary (see {@link #setTime(long) setTime()} method).
043     * <li>Supply data source values (see {@link #setValue(String, double) setValue()}).
044     * <li>Call Sample's {@link #update() update()} method.
045     * </ol>
046     * <p/>
047     * <p>Newly created Sample object contains all data source values set to 'unknown'.
048     * You should specifify only 'known' data source values. However, if you want to specify
049     * 'unknown' values too, use <code>Double.NaN</code>.</p>
050     *
051     * @author <a href="mailto:saxon@jrobin.org">Sasa Markovic</a>
052     */
053    public class Sample {
054            private RrdDb parentDb;
055            private long time;
056            private String[] dsNames;
057            private double[] values;
058    
059            Sample(RrdDb parentDb, long time) throws IOException {
060                    this.parentDb = parentDb;
061                    this.time = time;
062                    this.dsNames = parentDb.getDsNames();
063                    values = new double[dsNames.length];
064                    clearCurrentValues();
065            }
066    
067            private Sample clearCurrentValues() {
068                    for (int i = 0; i < values.length; i++) {
069                            values[i] = Double.NaN;
070                    }
071                    return this;
072            }
073    
074            /**
075             * Sets single data source value in the sample.
076             *
077             * @param dsName Data source name.
078             * @param value  Data source value.
079             * @return This <code>Sample</code> object
080             * @throws RrdException Thrown if invalid data source name is supplied.
081             */
082            public Sample setValue(String dsName, double value) throws RrdException {
083                    for (int i = 0; i < values.length; i++) {
084                            if (dsNames[i].equals(dsName)) {
085                                    values[i] = value;
086                                    return this;
087                            }
088                    }
089                    throw new RrdException("Datasource " + dsName + " not found");
090            }
091    
092            /**
093             * Sets single datasource value using data source index. Data sources are indexed by
094             * the order specified during RRD creation (zero-based).
095             *
096             * @param i      Data source index
097             * @param value Data source values
098             * @return This <code>Sample</code> object
099             * @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    }