KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jrobin > core > RrdDbPool


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, by Sasa Markovic.
9  *
10  * Developers: Sasa Markovic (saxon@jrobin.org)
11  * Arne Vandamme (cobralord@jrobin.org)
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 package org.jrobin.core;
26
27 import java.io.IOException JavaDoc;
28 import java.util.*;
29
30 /**
31  * Class to represent the pool of open RRD files.<p>
32  *
33  * To open already existing RRD file with JRobin, you have to create a
34  * {@link org.jrobin.core.RrdDb RrdDb} object by specifying RRD file path
35  * as constructor argument. This operation can be time consuming
36  * especially with large RRD files with many datasources and
37  * several long archives.<p>
38  *
39  * In a multithreaded environment you might probably need a reference to the
40  * same RRD file from two different threads (RRD file updates are performed in
41  * one thread but data fetching and graphing is performed in another one). To make
42  * the RrdDb construction process more efficient it might be convenient to open all
43  * RRD files in a centralized place. That's the purpose of RrdDbPool class.<p>
44  *
45  * How does it work? The typical usage scenario goes like this:<p>
46  *
47  * <pre>
48  * // obtain instance to RrdDbPool object
49  * RrdDbPool pool = RrdDbPool.getInstance();
50  *
51  * // request a reference to RrdDb object
52  * String path = "some_relative_or_absolute_path_to_any_RRD_file";
53  * RrdDb rrdDb = RrdDbPool.requestRrdDb(path);
54  *
55  * // reference obtained, do whatever you want with it...
56  * ...
57  * ...
58  *
59  * // once you don't need the reference, release it.
60  * // DO NOT CALL rrdDb.close() - files no longer in use are eventually closed by the pool
61  * pool.release(rrdDb);
62  *</pre>
63  *
64  * It's that simple. When the reference is requested for
65  * the first time, RrdDbPool will open the RRD file
66  * for you and make some internal note that the RRD file is used only once. When the reference
67  * to the same file (same RRD file path) is requested for the second time, the same RrdDb
68  * reference will be returned, and its usage count will be increased by one. When the
69  * reference is released its usage count will be decremented by one.<p>
70  *
71  * When the reference count drops to zero, RrdDbPool will not close the underlying
72  * RRD file immediatelly. Instead of it, it will be marked as 'eligible for closing'.
73  * If someone request the same RRD file again (before it gets closed), the same
74  * reference will be returned again.<p>
75  *
76  * RrdDbPool has a 'garbage collector' which runs in a separate
77  * thread and gets activated only when the number of RRD files kept in the
78  * pool is too big (greater than number returned from {@link #getCapacity getCapacity()}).
79  * Only RRD files with a reference count equal to zero
80  * will be eligible for closing. Unreleased RrdDb references are never invalidated.
81  * RrdDbPool object keeps track of the time when each RRD file
82  * becomes eligible for closing so that the oldest RRD file gets closed first.<p>
83  *
84  * Initial RrdDbPool capacity is set to {@link #INITIAL_CAPACITY}. Use {@link #setCapacity(int)} method to
85  * change it at any time.<p>
86  *
87  * <b>WARNING:</b>Never use close() method on the reference returned from the pool.
88  * When the reference is no longer needed, return it to the pool with the
89  * {@link #release(RrdDb) release()} method.<p>
90  *
91  * However, you are not forced to use RrdDbPool methods to obtain RrdDb references
92  * to RRD files, 'ordinary' RrdDb constructors are still available. But RrdDbPool class
93  * offers serious performance improvement especially in complex applications with many
94  * threads and many simultaneously open RRD files.<p>
95  *
96  * The pool is thread-safe.<p>
97  *
98  * <b>WARNING:</b> The pool cannot be used to manipulate RrdDb objects
99  * with {@link RrdBackend backends} different from default.<p>
100  */

101 public class RrdDbPool implements Runnable JavaDoc {
102     private static RrdDbPool ourInstance;
103     private static final boolean DEBUG = false;
104
105     /**
106      * Constant to represent the maximum number of internally open RRD files
107      * which still does not force garbage collector (the process which closes RRD files) to run.
108      */

109     public static final int INITIAL_CAPACITY = 100;
110     private int capacity = INITIAL_CAPACITY;
111
112     private Map rrdMap = new HashMap();
113     private List rrdGcList = new LinkedList();
114     private RrdBackendFactory factory;
115     private int poolHitsCount, poolRequestsCount;
116
117     /**
118      * Returns an instance to RrdDbPool object. Only one such object may exist in each JVM.
119      * @return Instance to RrdDbPool object.
120      */

121     public synchronized static RrdDbPool getInstance() {
122         if (ourInstance == null) {
123             ourInstance = new RrdDbPool();
124             ourInstance.startGarbageCollector();
125         }
126         return ourInstance;
127     }
128
129     private RrdDbPool() {
130     }
131
132     private void startGarbageCollector() {
133         Thread JavaDoc gcThread = new Thread JavaDoc(this);
134         gcThread.setDaemon(true);
135         gcThread.start();
136     }
137
138     /**
139      * Returns a reference to an existing RRD file with the specified path.
140      * If the file is already open in the pool, existing reference to it will be returned.
141      * Otherwise, the file is open and a newly created reference to it is returned.
142      *
143      * @param path Relative or absolute path to a RRD file.
144      * @return Reference to a RrdDb object (RRD file).
145      * @throws IOException Thrown in case of I/O error.
146      * @throws RrdException Thrown in case of JRobin specific error.
147      */

148     public synchronized RrdDb requestRrdDb(String JavaDoc path) throws IOException JavaDoc, RrdException {
149         String JavaDoc keypath = getCanonicalPath(path);
150         RrdDb rrdDbRequested;
151         if (rrdMap.containsKey(keypath)) {
152             // already open
153
RrdEntry rrdEntry = (RrdEntry) rrdMap.get(keypath);
154             reportUsage(rrdEntry);
155             debug("EXISTING: " + rrdEntry.dump());
156             rrdDbRequested = rrdEntry.getRrdDb();
157             poolHitsCount++;
158         } else {
159             // not found, open it
160
RrdDb rrdDb = new RrdDb(path, getFactory());
161             addRrdEntry(keypath, rrdDb);
162             rrdDbRequested = rrdDb;
163         }
164         poolRequestsCount++;
165         return rrdDbRequested;
166     }
167
168     /**
169      * Returns a reference to a new RRD file. The new file will have the specified
170      * relative or absolute path, and its contents will be provided from the specified
171      * XML file (RRDTool comaptible).
172      * @param path Relative or absolute path to a new RRD file.
173      * @param xmlPath Relative or absolute path to an existing XML dump file (RRDTool comaptible)
174      * @return Reference to a RrdDb object (RRD file).
175      * @throws IOException Thrown in case of I/O error.
176      * @throws RrdException Thrown in case of JRobin specific error.
177      */

178     public synchronized RrdDb requestRrdDb(String JavaDoc path, String JavaDoc xmlPath)
179         throws IOException JavaDoc, RrdException {
180         String JavaDoc keypath = getCanonicalPath(path);
181         prooveInactive(keypath);
182         RrdDb rrdDb = new RrdDb(path, xmlPath, getFactory());
183         addRrdEntry(keypath, rrdDb);
184         poolRequestsCount++;
185         return rrdDb;
186     }
187
188     /**
189      * Returns a reference to a new RRD file. The new file will be created based on the
190      * definition contained in a RrdDef object.
191      * @param rrdDef RRD definition object
192      * @return Reference to a RrdDb object (RRD file).
193      * @throws IOException Thrown in case of I/O error.
194      * @throws RrdException Thrown in case of JRobin specific error.
195      */

196     public synchronized RrdDb requestRrdDb(RrdDef rrdDef) throws IOException JavaDoc, RrdException {
197         String JavaDoc path = rrdDef.getPath();
198         String JavaDoc keypath = getCanonicalPath(path);
199         prooveInactive(keypath);
200         RrdDb rrdDb = new RrdDb(rrdDef, getFactory());
201         addRrdEntry(keypath, rrdDb);
202         poolRequestsCount++;
203         return rrdDb;
204     }
205
206     private void reportUsage(RrdEntry rrdEntry) {
207         if(rrdEntry.reportUsage() == 1) {
208             // must not be garbage collected
209
rrdGcList.remove(rrdEntry);
210         }
211     }
212
213     private void reportRelease(RrdEntry rrdEntry) {
214         if(rrdEntry.reportRelease() == 0) {
215             // ready to be garbage collected
216
rrdGcList.add(rrdEntry);
217         }
218     }
219
220     private void addRrdEntry(String JavaDoc keypath, RrdDb rrdDb) throws IOException JavaDoc {
221         RrdEntry newEntry = new RrdEntry(rrdDb);
222         reportUsage(newEntry);
223         debug("NEW: " + newEntry.dump());
224         rrdMap.put(keypath, newEntry);
225         // notify garbage collector
226
notify();
227     }
228
229     private void prooveInactive(String JavaDoc keypath) throws RrdException, IOException JavaDoc {
230         if(rrdMap.containsKey(keypath)) {
231             // already open, check if active (not released)
232
RrdEntry rrdEntry = (RrdEntry) rrdMap.get(keypath);
233             if(rrdEntry.isInUse()) {
234                 // not released, not allowed here
235
throw new RrdException("VALIDATOR: Cannot create new RrdDb file. " +
236                     "File " + keypath + " already active in pool");
237             }
238             else {
239                 // open but released... safe to close it
240
debug("WILL BE RECREATED: " + rrdEntry.dump());
241                 removeRrdEntry(rrdEntry);
242             }
243         }
244     }
245
246     private void removeRrdEntry(RrdEntry rrdEntry) throws IOException JavaDoc {
247         rrdEntry.closeRrdDb();
248         rrdMap.values().remove(rrdEntry);
249         rrdGcList.remove(rrdEntry);
250         debug("REMOVED: " + rrdEntry.dump());
251     }
252
253     /**
254      * Method used to report that the reference to a RRD file is no longer needed. File that
255      * is no longer needed (all references to it are released) is marked 'eligible for
256      * closing'. It will be eventually closed by the pool when the number of open RRD files
257      * becomes too big. Most recently released files will be closed last.
258      * @param rrdDb Reference to RRD file that is no longer needed.
259      * @throws IOException Thrown in case of I/O error.
260      * @throws RrdException Thrown in case of JRobin specific error.
261      */

262     public synchronized void release(RrdDb rrdDb) throws IOException JavaDoc, RrdException {
263         if(rrdDb == null) {
264             // we don't want NullPointerException
265
return;
266         }
267         if(rrdDb.isClosed()) {
268             throw new RrdException("Cannot release: already closed");
269         }
270         String JavaDoc keypath = rrdDb.getCanonicalPath();
271         if(rrdMap.containsKey(keypath)) {
272             RrdEntry rrdEntry = (RrdEntry) rrdMap.get(keypath);
273             reportRelease(rrdEntry);
274             debug("RELEASED: " + rrdEntry.dump());
275         }
276         else {
277             throw new RrdException("RRD file " + keypath + " not in the pool");
278         }
279         // notify garbage collector
280
notify();
281     }
282
283     /**
284      * This method runs garbage collector in a separate thread. If the number of
285      * open RRD files kept in the pool is too big (greater than number
286      * returned from {@link #getCapacity getCapacity()}), garbage collector will try
287      * to close and remove RRD files with a reference count equal to zero.
288      * Never call this method directly.
289      */

290     public void run() {
291         debug("GC: started");
292         synchronized (this) {
293             for (; ;) {
294                 while (rrdMap.size() > capacity && rrdGcList.size() > 0) {
295                     try {
296                         RrdEntry oldestRrdEntry = (RrdEntry) rrdGcList.get(0);
297                         debug("GC: closing " + oldestRrdEntry.dump());
298                         removeRrdEntry(oldestRrdEntry);
299                     } catch (IOException JavaDoc e) {
300                         e.printStackTrace();
301                     }
302                 }
303
304                 try {
305                     debug("GC: waiting: " +
306                             rrdMap.size() + " open, " +
307                             rrdGcList.size() + " released, " +
308                             "capacity = " + capacity + ", " +
309                             "hits = " + poolHitsCount + ", " +
310                             "requests = " + poolRequestsCount);
311                     wait();
312                     debug("GC: running");
313                 } catch (InterruptedException JavaDoc e) {
314                 }
315             }
316         }
317     }
318
319     protected void finalize() throws IOException JavaDoc {
320         reset();
321     }
322
323     /**
324      * Clears the internal state of the pool entirely. All open RRD files are closed.
325      * @throws IOException Thrown in case of I/O related error.
326      */

327     public synchronized void reset() throws IOException JavaDoc {
328         Iterator it = rrdMap.values().iterator();
329         while(it.hasNext()) {
330             RrdEntry rrdEntry = (RrdEntry) it.next();
331             rrdEntry.closeRrdDb();
332         }
333         rrdMap.clear();
334         rrdGcList.clear();
335         debug("Nothing left in the pool");
336     }
337
338     private static String JavaDoc getCanonicalPath(String JavaDoc path) throws IOException JavaDoc {
339         return RrdFileBackend.getCanonicalPath(path);
340     }
341
342     private static void debug(String JavaDoc msg) {
343         if(DEBUG) {
344             System.out.println("POOL: " + msg);
345         }
346     }
347
348     /**
349      * Returns the internal state of the pool. Useful for debugging purposes.
350      * @return Internal pool state (list of open RRD files, with the number of usages for
351      * each one).
352      * @throws IOException Thrown in case of I/O error.
353      */

354     public synchronized String JavaDoc dump() throws IOException JavaDoc {
355         StringBuffer JavaDoc buff = new StringBuffer JavaDoc();
356         Iterator it = rrdMap.values().iterator();
357         while(it.hasNext()) {
358             RrdEntry rrdEntry = (RrdEntry) it.next();
359             buff.append(rrdEntry.dump());
360             buff.append("\n");
361         }
362         return buff.toString();
363     }
364
365     /**
366      * Returns maximum number of internally open RRD files
367      * which still does not force garbage collector to run.
368      *
369      * @return Desired nuber of open files held in the pool.
370      */

371     public synchronized int getCapacity() {
372         return capacity;
373     }
374
375     /**
376      * Sets maximum number of internally open RRD files
377      * which still does not force garbage collector to run.
378      *
379      * @param capacity Desired number of open files to hold in the pool
380      */

381     public synchronized void setCapacity(int capacity) {
382         this.capacity = capacity;
383         debug("Capacity set to: " + capacity);
384     }
385
386     private RrdBackendFactory getFactory() throws RrdException {
387         if(factory == null) {
388             factory = RrdBackendFactory.getDefaultFactory();
389             if(!(factory instanceof RrdFileBackendFactory)) {
390                 factory = null;
391                 throw new RrdException(
392                     "RrdDbPool cannot work with factories not derived from RrdFileBackendFactory");
393             }
394         }
395         return factory;
396     }
397
398     private class RrdEntry {
399         private RrdDb rrdDb;
400         private int usageCount;
401
402         public RrdEntry(RrdDb rrdDb) {
403             this.rrdDb = rrdDb;
404         }
405
406         RrdDb getRrdDb() {
407             return rrdDb;
408         }
409
410         int reportUsage() {
411             assert usageCount >= 0: "Unexpected reportUsage count: " + usageCount;
412             return ++usageCount;
413         }
414
415         int reportRelease() {
416             assert usageCount > 0: "Unexpected reportRelease count: " + usageCount;
417             return --usageCount;
418         }
419
420         boolean isInUse() {
421             return usageCount > 0;
422         }
423
424         void closeRrdDb() throws IOException JavaDoc {
425             rrdDb.close();
426         }
427
428         String JavaDoc dump() throws IOException JavaDoc {
429             String JavaDoc keypath = rrdDb.getCanonicalPath();
430             return keypath + " [" + usageCount + "]";
431         }
432     }
433
434     /**
435      * Calculates pool's efficency ratio. The ratio is obtained by dividing the number of
436      * RrdDb requests served from the internal pool of open RRD files
437      * with the number of total RrdDb requests.
438      * @return Pool's efficiency ratio as a double between 1 (best) and 0 (worst). If no RrdDb reference
439      * was ever requested, 1 would be returned.
440      */

441     public synchronized double getPoolEfficency() {
442         if(poolRequestsCount == 0) {
443             return 1.0;
444         }
445         double ratio = (double) poolHitsCount / (double) poolRequestsCount;
446         // round to 3 decimal digits
447
return Math.round(ratio * 1000.0) / 1000.0;
448     }
449
450     /**
451      * Returns the number of RRD requests served from the internal pool of open RRD files
452      * @return The number of pool "hits".
453      */

454     public synchronized int getPoolHitsCount() {
455         return poolHitsCount;
456     }
457
458     /**
459      * Returns the total number of RRD requests successfully served by this pool.
460      * @return Total number of RRD requests
461      */

462     public synchronized int getPoolRequestsCount() {
463         return poolRequestsCount;
464     }
465 }
466
467
Popular Tags