001    /*
002     * Copyright (C) 2001 Ciaran Treanor <ciaran@codeloop.com>
003     *
004     * Distributable under GPL license.
005     * See terms of license at gnu.org.
006     *
007     * $Id: Archive.java,v 1.3 2006/12/21 18:02:42 tarus Exp $
008     */
009    package org.jrobin.core.jrrd;
010    
011    import java.io.IOException;
012    import java.io.PrintStream;
013    import java.text.DecimalFormat;
014    import java.text.NumberFormat;
015    import java.text.SimpleDateFormat;
016    import java.util.ArrayList;
017    import java.util.Calendar;
018    import java.util.Date;
019    import java.util.Iterator;
020    
021    /**
022     * Instances of this class model an archive section of an RRD file.
023     *
024     * @author <a href="mailto:ciaran@codeloop.com">Ciaran Treanor</a>
025     * @version $Revision: 1.3 $
026     */
027    public class Archive {
028    
029            RRDatabase db;
030            long offset;
031            long dataOffset;
032            long size;
033            ConsolidationFunctionType type;
034            int rowCount;
035            int pdpCount;
036            double xff;
037            ArrayList<CDPStatusBlock> cdpStatusBlocks;
038            int currentRow;
039    
040            private double[][] values;
041    
042            Archive(RRDatabase db) throws IOException {
043    
044                    this.db = db;
045    
046                    RRDFile file = db.rrdFile;
047    
048                    offset = file.getFilePointer();
049                    type =
050                                    ConsolidationFunctionType.get(file.readString(Constants.CF_NAM_SIZE));
051                    rowCount = file.readInt();
052                    pdpCount = file.readInt();
053    
054                    file.align();
055    
056                    xff = file.readDouble();
057    
058                    // Skip rest of rra_def_t.par[]
059                    file.align();
060                    file.skipBytes(72);
061    
062                    size = file.getFilePointer() - offset;
063            }
064    
065            /**
066             * Returns the type of function used to calculate the consolidated data point.
067             *
068             * @return the type of function used to calculate the consolidated data point.
069             */
070            public ConsolidationFunctionType getType() {
071                    return type;
072            }
073    
074            void loadCDPStatusBlocks(RRDFile file, int numBlocks) throws IOException {
075    
076                    cdpStatusBlocks = new ArrayList<CDPStatusBlock>();
077    
078                    for (int i = 0; i < numBlocks; i++) {
079                            cdpStatusBlocks.add(new CDPStatusBlock(file));
080                    }
081            }
082    
083            /**
084             * Returns the <code>CDPStatusBlock</code> at the specified position in this archive.
085             *
086             * @param index index of <code>CDPStatusBlock</code> to return.
087             * @return the <code>CDPStatusBlock</code> at the specified position in this archive.
088             */
089            public CDPStatusBlock getCDPStatusBlock(int index) {
090                    return cdpStatusBlocks.get(index);
091            }
092    
093            /**
094             * Returns an iterator over the CDP status blocks in this archive in proper sequence.
095             *
096             * @return an iterator over the CDP status blocks in this archive in proper sequence.
097             * @see CDPStatusBlock
098             */
099            public Iterator<CDPStatusBlock> getCDPStatusBlocks() {
100                    return cdpStatusBlocks.iterator();
101            }
102    
103            void loadCurrentRow(RRDFile file) throws IOException {
104                    currentRow = file.readInt();
105            }
106    
107            void loadData(RRDFile file, int dsCount) throws IOException {
108    
109                    dataOffset = file.getFilePointer();
110    
111                    // Skip over the data to position ourselves at the start of the next archive
112                    file.skipBytes(8 * rowCount * dsCount);
113            }
114    
115            DataChunk loadData(DataChunk chunk) throws IOException {
116    
117                    Calendar end = Calendar.getInstance();
118                    Calendar start = (Calendar) end.clone();
119    
120                    start.add(Calendar.DATE, -1);
121    
122                    loadData(chunk, start.getTime().getTime() / 1000,
123                                    end.getTime().getTime() / 1000);
124                    return chunk;
125            }
126    
127            void loadData(DataChunk chunk, long startTime, long endTime)
128                            throws IOException {
129    
130                    long pointer;
131    
132                    if (chunk.start < 0) {
133                            pointer = currentRow + 1;
134                    }
135                    else {
136                            pointer = currentRow + chunk.start + 1;
137                    }
138    
139                    db.rrdFile.ras.seek(dataOffset + (pointer * 8));
140                    //cat.debug("Archive Base: " + dataOffset + " Archive Pointer: " + pointer);
141                    //cat.debug("Start Offset: " + chunk.start + " End Offset: "
142                    //          + (rowCount - chunk.end));
143    
144                    double[][] data = chunk.data;
145    
146                    /*
147                     * This is also terrible - cleanup - CT
148                     */
149                    int row = 0;
150                    for (int i = chunk.start; i < rowCount - chunk.end; i++, row++) {
151                            if (i < 0) {                            // no valid data yet
152                                    for (int ii = 0; ii < chunk.dsCount; ii++) {
153                                            data[row][ii] = Double.NaN;
154                                    }
155                            }
156                            else if (i >= rowCount) {    // past valid data area
157                                    for (int ii = 0; ii < chunk.dsCount; ii++) {
158                                            data[row][ii] = Double.NaN;
159                                    }
160                            }
161                            else {                                     // inside the valid are but the pointer has to be wrapped
162                                    if (pointer >= rowCount) {
163                                            pointer -= rowCount;
164    
165                                            db.rrdFile.ras.seek(dataOffset + (pointer * 8));
166                                    }
167    
168                                    for (int ii = 0; ii < chunk.dsCount; ii++) {
169                                            data[row][ii] = db.rrdFile.readDouble();
170                                    }
171    
172                                    pointer++;
173                            }
174                    }
175            }
176    
177            void printInfo(PrintStream s, NumberFormat numberFormat, int index) {
178    
179                    StringBuffer sb = new StringBuffer("rra[");
180    
181                    sb.append(index);
182                    s.print(sb);
183                    s.print("].cf = \"");
184                    s.print(type);
185                    s.println("\"");
186                    s.print(sb);
187                    s.print("].rows = ");
188                    s.println(rowCount);
189                    s.print(sb);
190                    s.print("].pdp_per_row = ");
191                    s.println(pdpCount);
192                    s.print(sb);
193                    s.print("].xff = ");
194                    s.println(xff);
195                    sb.append("].cdp_prep[");
196    
197                    int cdpIndex = 0;
198    
199                    for (Iterator<CDPStatusBlock> i = cdpStatusBlocks.iterator(); i.hasNext();) {
200                            CDPStatusBlock cdp = i.next();
201    
202                            s.print(sb);
203                            s.print(cdpIndex);
204                            s.print("].value = ");
205    
206                            double value = cdp.value;
207    
208                            s.println(Double.isNaN(value)
209                                            ? "NaN"
210                                            : numberFormat.format(value));
211                            s.print(sb);
212                            s.print(cdpIndex++);
213                            s.print("].unknown_datapoints = ");
214                            s.println(cdp.unknownDatapoints);
215                    }
216            }
217    
218            void toXml(PrintStream s) {
219    
220                    try {
221                            s.println("\t<rra>");
222                            s.print("\t\t<cf> ");
223                            s.print(type);
224                            s.println(" </cf>");
225                            s.print("\t\t<pdp_per_row> ");
226                            s.print(pdpCount);
227                            s.print(" </pdp_per_row> <!-- ");
228                            s.print(db.header.pdpStep * pdpCount);
229                            s.println(" seconds -->");
230                            s.print("\t\t<xff> ");
231                            s.print(xff);
232                            s.println(" </xff>");
233                            s.println();
234                            s.println("\t\t<cdp_prep>");
235    
236                            for (int i = 0; i < cdpStatusBlocks.size(); i++) {
237                                    cdpStatusBlocks.get(i).toXml(s);
238                            }
239    
240                            s.println("\t\t</cdp_prep>");
241                            s.println("\t\t<database>");
242    
243                            long timer = -(rowCount - 1);
244                            int counter = 0;
245                            int row = currentRow;
246    
247                            db.rrdFile.ras.seek(dataOffset + (row + 1) * 16);
248    
249                            long lastUpdate = db.lastUpdate.getTime() / 1000;
250                            int pdpStep = db.header.pdpStep;
251                            NumberFormat numberFormat = new DecimalFormat("0.0000000000E0");
252                            SimpleDateFormat dateFormat =
253                                            new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
254    
255                            while (counter++ < rowCount) {
256                                    row++;
257    
258                                    if (row == rowCount) {
259                                            row = 0;
260    
261                                            db.rrdFile.ras.seek(dataOffset);
262                                    }
263    
264                                    long now = (lastUpdate - lastUpdate % (pdpCount * pdpStep))
265                                                    + (timer * pdpCount * pdpStep);
266    
267                                    timer++;
268    
269                                    s.print("\t\t\t<!-- ");
270                                    s.print(dateFormat.format(new Date(now * 1000)));
271                                    s.print(" / ");
272                                    s.print(now);
273                                    s.print(" --> ");
274    
275                                    for (int col = 0; col < db.header.dsCount; col++) {
276                                            s.print("<v> ");
277    
278                                            double value = db.rrdFile.readDouble();
279    
280                                            // NumberFormat doesn't know how to handle NaN
281                                            if (Double.isNaN(value)) {
282                                                    s.print("NaN");
283                                            }
284                                            else {
285                                                    s.print(numberFormat.format(value));
286                                            }
287    
288                                            s.print(" </v>");
289                                    }
290    
291                                    s.println("</row>");
292                            }
293    
294                            s.println("\t\t</database>");
295                            s.println("\t</rra>");
296                    }
297                    catch (IOException e) { // Is the best thing to do here?
298                            throw new RuntimeException(e.getMessage());
299                    }
300            }
301    
302            /*
303            // THIS IS THE ORIGINAL CODE: BUGGY! Replaced by Sasa Markovic with a new method
304            // Funny: the bug will appear only if dsCount != 2 :)
305            public double[][] getValuesOriginal() throws IOException {
306                    if (values != null) {
307                            return values;
308                    }
309                    values = new double[db.header.dsCount][rowCount];
310                    int row = currentRow;
311                    db.rrdFile.ras.seek(dataOffset + (row + 1) * 16); // <----- BUG (resolved below)
312                    for (int counter = 0; counter < rowCount; counter++) {
313                            row++;
314                            if (row == rowCount) {
315                                    row = 0;
316                                    db.rrdFile.ras.seek(dataOffset);
317                            }
318                            for (int col = 0; col < db.header.dsCount; col++) {
319                                    double value = db.rrdFile.readDouble();
320                                    values[col][counter] = value;
321                            }
322                    }
323                    return values;
324            }
325        */
326    
327            // Resolved bug from the original method (see above)
328            public double[][] getValues() throws IOException {
329                    // OK PART
330                    if (values != null) {
331                            return values;
332                    }
333                    values = new double[db.header.dsCount][rowCount];
334                    int row = currentRow;
335                    // HERE ARE THE DRAGONS!
336                    db.rrdFile.ras.seek(dataOffset + (row + 1) * db.header.dsCount * 8);
337                    // OK, TOO!
338                    for (int counter = 0; counter < rowCount; counter++) {
339                            row++;
340                            if (row == rowCount) {
341                                    row = 0;
342                                    db.rrdFile.ras.seek(dataOffset);
343                            }
344                            for (int col = 0; col < db.header.dsCount; col++) {
345                                    double value = db.rrdFile.readDouble();
346                                    values[col][counter] = value;
347                            }
348                    }
349                    return values;
350            }
351    
352            /**
353             * Returns the number of primary data points required for a consolidated
354             * data point in this archive.
355             *
356             * @return the number of primary data points required for a consolidated
357             *         data point in this archive.
358             */
359            public int getPdpCount() {
360                    return pdpCount;
361            }
362    
363            /**
364             * Returns the number of entries in this archive.
365             *
366             * @return the number of entries in this archive.
367             */
368            public int getRowCount() {
369                    return rowCount;
370            }
371    
372            /**
373             * Returns the X-Files Factor for this archive.
374             *
375             * @return the X-Files Factor for this archive.
376             */
377            public double getXff() {
378                    return xff;
379            }
380    
381            /**
382             * Returns a summary the contents of this archive.
383             *
384             * @return a summary of the information contained in this archive.
385             */
386            public String toString() {
387    
388                    StringBuffer sb = new StringBuffer("[Archive: OFFSET=0x");
389    
390                    sb.append(Long.toHexString(offset));
391                    sb.append(", SIZE=0x");
392                    sb.append(Long.toHexString(size));
393                    sb.append(", type=");
394                    sb.append(type);
395                    sb.append(", rowCount=");
396                    sb.append(rowCount);
397                    sb.append(", pdpCount=");
398                    sb.append(pdpCount);
399                    sb.append(", xff=");
400                    sb.append(xff);
401                    sb.append(", currentRow=");
402                    sb.append(currentRow);
403                    sb.append("]");
404    
405                    for (Iterator<CDPStatusBlock> i = cdpStatusBlocks.iterator(); i.hasNext();) {
406                            CDPStatusBlock cdp = i.next();
407    
408                            sb.append("\n\t\t");
409                            sb.append(cdp.toString());
410                    }
411    
412                    return sb.toString();
413            }
414    }