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     * This library is free software; you can redistribute it and/or modify it under the terms
011     * of the GNU Lesser General Public License as published by the Free Software Foundation;
012     * either version 2.1 of the License, or (at your option) any later version.
013     *
014     * Developers:    Sasa Markovic (saxon@jrobin.org)
015     *
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 archive values for a single datasource. Robin class is the heart of
032     * the so-called "round robin database" concept. Basically, each Robin object is a
033     * fixed length array of double values. Each double value reperesents consolidated, archived
034     * value for the specific timestamp. When the underlying array of double values gets completely
035     * filled, new values will replace the oldest ones.<p>
036     * <p/>
037     * Robin object does not hold values in memory - such object could be quite large.
038     * Instead of it, Robin reads them from the backend I/O only when necessary.
039     *
040     * @author <a href="mailto:saxon@jrobin.org">Sasa Markovic</a>
041     */
042    public class Robin implements RrdUpdater {
043            private Archive parentArc;
044            private RrdInt pointer;
045            private RrdDoubleArray values;
046            private int rows;
047    
048            Robin(Archive parentArc, int rows, boolean shouldInitialize) throws IOException {
049                    this.parentArc = parentArc;
050                    this.pointer = new RrdInt(this);
051                    this.values = new RrdDoubleArray(this, rows);
052                    this.rows = rows;
053                    if (shouldInitialize) {
054                            pointer.set(0);
055                            values.set(0, Double.NaN, rows);
056                    }
057            }
058    
059            /**
060             * Fetches all archived values.
061             *
062             * @return Array of double archive values, starting from the oldest one.
063             * @throws IOException Thrown in case of I/O specific error.
064             */
065            public double[] getValues() throws IOException {
066                    return getValues(0, rows);
067            }
068    
069            // stores single value
070            void store(double newValue) throws IOException {
071                    int position = pointer.get();
072                    values.set(position, newValue);
073                    pointer.set((position + 1) % rows);
074            }
075    
076            // stores the same value several times
077            void bulkStore(double newValue, int bulkCount) throws IOException {
078                    assert bulkCount <= rows: "Invalid number of bulk updates: " + bulkCount +
079                                    " rows=" + rows;
080                    int position = pointer.get();
081                    // update tail
082                    int tailUpdateCount = Math.min(rows - position, bulkCount);
083                    values.set(position, newValue, tailUpdateCount);
084                    pointer.set((position + tailUpdateCount) % rows);
085                    // do we need to update from the start?
086                    int headUpdateCount = bulkCount - tailUpdateCount;
087                    if (headUpdateCount > 0) {
088                            values.set(0, newValue, headUpdateCount);
089                            pointer.set(headUpdateCount);
090                    }
091            }
092    
093            void update(double[] newValues) throws IOException {
094                    assert rows == newValues.length: "Invalid number of robin values supplied (" + newValues.length +
095                                    "), exactly " + rows + " needed";
096                    pointer.set(0);
097                    values.writeDouble(0, newValues);
098            }
099    
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    }