[Checkins] SVN: ZODB/trunk/src/ Merged Deiter's branch that provides object-size-based object cache

Jim Fulton jim at zope.com
Sat Sep 6 14:35:43 EDT 2008


Log message for revision 90914:
  Merged Deiter's branch that provides object-size-based object cache
  control.
  

Changed:
  U   ZODB/trunk/src/CHANGES.txt
  U   ZODB/trunk/src/ZODB/Connection.py
  U   ZODB/trunk/src/ZODB/DB.py
  U   ZODB/trunk/src/ZODB/component.xml
  U   ZODB/trunk/src/ZODB/config.py
  U   ZODB/trunk/src/ZODB/tests/testConnection.py
  U   ZODB/trunk/src/persistent/cPersistence.c
  U   ZODB/trunk/src/persistent/cPersistence.h
  U   ZODB/trunk/src/persistent/cPickleCache.c
  U   ZODB/trunk/src/persistent/tests/persistent.txt

-=-
Modified: ZODB/trunk/src/CHANGES.txt
===================================================================
--- ZODB/trunk/src/CHANGES.txt	2008-09-06 07:56:18 UTC (rev 90913)
+++ ZODB/trunk/src/CHANGES.txt	2008-09-06 18:35:41 UTC (rev 90914)
@@ -8,6 +8,17 @@
 New Features
 ------------
 
+- The connection now estimates the object size based on its pickle size
+  and informs the cache about size changes.
+
+  The database got additional configurations options (`cache-size-bytes`
+  and `historical-cache-size-bytes`) to limit the
+  cache size based on the estimated total size of cached objects.
+  The default values are 0 which has the interpretation "do not limit
+  based on the total estimated size".
+  There are corresponding methods to read and set the new configuration
+  parameters.
+
 - Cleaned-up the storage iteration API and provided an iterator implementation
   for ZEO.
 

Modified: ZODB/trunk/src/ZODB/Connection.py
===================================================================
--- ZODB/trunk/src/ZODB/Connection.py	2008-09-06 07:56:18 UTC (rev 90913)
+++ ZODB/trunk/src/ZODB/Connection.py	2008-09-06 18:35:41 UTC (rev 90914)
@@ -79,7 +79,7 @@
     ##########################################################################
     # Connection methods, ZODB.IConnection
 
-    def __init__(self, db, cache_size=400, before=None):
+    def __init__(self, db, cache_size=400, before=None, cache_size_bytes=0):
         """Create a new Connection."""
 
         self._log = logging.getLogger('ZODB.Connection')
@@ -109,7 +109,7 @@
         # Cache which can ghostify (forget the state of) objects not
         # recently used. Its API is roughly that of a dict, with
         # additional gc-related and invalidation-related methods.
-        self._cache = PickleCache(self, cache_size)
+        self._cache = PickleCache(self, cache_size, cache_size_bytes)
 
         # The pre-cache is used by get to avoid infinite loops when
         # objects immediately load their state whern they get their
@@ -629,6 +629,10 @@
                 obj._p_invalidate()
             else:
                 s = self._storage.store(oid, serial, p, '', transaction)
+            self._cache.update_object_size_estimation(oid,
+                                                   len(p)
+                                                   )
+            obj._p_estimated_size = len(p)
             self._store_count += 1
             # Put the object in the cache before handling the
             # response, just in case the response contains the
@@ -870,6 +874,10 @@
 
         self._reader.setGhostState(obj, p)
         obj._p_serial = serial
+        self._cache.update_object_size_estimation(obj._p_oid,
+                                               len(p)
+                                               )
+        obj._p_estimated_size = len(p)
 
         # Blob support
         if isinstance(obj, Blob):
@@ -1034,7 +1042,8 @@
         self._invalidated.clear()
         self._invalidatedCache = False
         cache_size = self._cache.cache_size
-        self._cache = cache = PickleCache(self, cache_size)
+        cache_size_bytes = self._cache.cache_size_bytes
+        self._cache = cache = PickleCache(self, cache_size, cache_size_bytes)
 
     ##########################################################################
     # Python protocol
@@ -1117,6 +1126,12 @@
 
         for oid in oids:
             data, serial = src.load(oid, src)
+            obj = self._cache.get(oid, None)
+            if obj is not None:
+                self._cache.update_object_size_estimation(obj._p_oid,
+                                                       len(data)
+                                                       )
+                obj._p_estimated_size = len(data)
             if isinstance(self._reader.getGhost(data), Blob):
                 blobfilename = src.loadBlob(oid, serial)
                 s = self._storage.storeBlob(oid, serial, data, blobfilename,

Modified: ZODB/trunk/src/ZODB/DB.py
===================================================================
--- ZODB/trunk/src/ZODB/DB.py	2008-09-06 07:56:18 UTC (rev 90913)
+++ ZODB/trunk/src/ZODB/DB.py	2008-09-06 18:35:41 UTC (rev 90914)
@@ -398,8 +398,10 @@
     def __init__(self, storage,
                  pool_size=7,
                  cache_size=400,
+                 cache_size_bytes=0,
                  historical_pool_size=3,
                  historical_cache_size=1000,
+                 historical_cache_size_bytes=0,
                  historical_timeout=300,
                  database_name='unnamed',
                  databases=None,
@@ -410,10 +412,15 @@
           - `storage`: the storage used by the database, e.g. FileStorage
           - `pool_size`: expected maximum number of open connections
           - `cache_size`: target size of Connection object cache
+          - `cache_size_bytes`: target size measured in total estimated size
+               of objects in the Connection object cache.
+               "0" means unlimited.
           - `historical_pool_size`: expected maximum number of total
             historical connections
           - `historical_cache_size`: target size of Connection object cache for
             historical (`at` or `before`) connections
+          - `historical_cache_size_bytes` -- similar to `cache_size_bytes` for
+            the historical connection.
           - `historical_timeout`: minimum number of seconds that
             an unused historical connection will be kept, or None.
         """
@@ -427,7 +434,9 @@
         self.historical_pool = KeyedConnectionPool(historical_pool_size,
                                                    historical_timeout)
         self._cache_size = cache_size
+        self._cache_size_bytes = cache_size_bytes
         self._historical_cache_size = historical_cache_size
+        self._historical_cache_size_bytes = historical_cache_size_bytes
 
         # Setup storage
         self._storage=storage
@@ -642,6 +651,9 @@
     def getCacheSize(self):
         return self._cache_size
 
+    def getCacheSizeBytes(self):
+        return self._cache_size_bytes
+
     def lastTransaction(self):
         return self._storage.lastTransaction()
 
@@ -657,6 +669,9 @@
     def getHistoricalCacheSize(self):
         return self._historical_cache_size
 
+    def getHistoricalCacheSizeBytes(self):
+        return self._historical_cache_size_bytes
+
     def getHistoricalPoolSize(self):
         return self.historical_pool.size
 
@@ -720,13 +735,21 @@
             if before is not None:
                 result = self.historical_pool.pop(before)
                 if result is None:
-                    c = self.klass(self, self._historical_cache_size, before)
+                    c = self.klass(self,
+                                   self._historical_cache_size,
+                                   before,
+                                   self._historical_cache_size_bytes,
+                                   )
                     self.historical_pool.push(c, before)
                     result = self.historical_pool.pop(before)
             else:
                 result = self.pool.pop()
                 if result is None:
-                    c = self.klass(self, self._cache_size)
+                    c = self.klass(self,
+                                   self._cache_size,
+                                   None,
+                                   self._cache_size_bytes,
+                                   )
                     self.pool.push(c)
                     result = self.pool.pop()
             assert result is not None
@@ -813,6 +836,16 @@
         finally:
             self._r()
 
+    def setCacheSizeBytes(self, size):
+        self._a()
+        try:
+            self._cache_size_bytes = size
+            def setsize(c):
+                c._cache.cache_size_bytes = size
+            self.pool.map(setsize)
+        finally:
+            self._r()
+
     def setHistoricalCacheSize(self, size):       
         self._a()
         try:
@@ -823,6 +856,16 @@
         finally:
             self._r()
 
+    def setHistoricalCacheSizeBytes(self, size):       
+        self._a()
+        try:
+            self._historical_cache_size_bytes = size
+            def setsize(c):
+                c._cache.cache_size_bytes = size
+            self.historical_pool.map(setsize)
+        finally:
+            self._r()
+
     def setPoolSize(self, size):
         self._a()
         try:

Modified: ZODB/trunk/src/ZODB/component.xml
===================================================================
--- ZODB/trunk/src/ZODB/component.xml	2008-09-06 07:56:18 UTC (rev 90913)
+++ ZODB/trunk/src/ZODB/component.xml	2008-09-06 18:35:41 UTC (rev 90914)
@@ -181,6 +181,13 @@
         Target size, in number of objects, of each connection's
         object cache.
       </description>
+    <key name="cache-size-bytes" datatype="byte-size" default="0">
+      <description>
+        Target size, in total estimated size for objects, of each connection's
+        object cache.
+        "0" means no limit.
+      </description>
+    </key>
     <key name="pool-size" datatype="integer" default="7"/>
       <description>
         The expected maximum number of simultaneously open connections.
@@ -200,6 +207,12 @@
         Target size, in number of objects, of each historical connection's
         object cache.
       </description>
+    <key name="historical-cache-size-bytes" datatype="byte-size" default="0">
+      <description>
+        Target size, in total estimated size of objects, of each historical connection's
+        object cache.
+      </description>
+    </key>
     <key name="historical-timeout" datatype="time-interval"
          default="5m"/>
       <description>

Modified: ZODB/trunk/src/ZODB/config.py
===================================================================
--- ZODB/trunk/src/ZODB/config.py	2008-09-06 07:56:18 UTC (rev 90913)
+++ ZODB/trunk/src/ZODB/config.py	2008-09-06 18:35:41 UTC (rev 90914)
@@ -95,14 +95,18 @@
         section = self.config
         storage = section.storage.open()
         try:
-            return ZODB.DB(storage,
-                           pool_size=section.pool_size,
-                           cache_size=section.cache_size,
-                           historical_pool_size=section.historical_pool_size,
-                           historical_cache_size=section.historical_cache_size,
-                           historical_timeout=section.historical_timeout,
-                           database_name=section.database_name,
-                           databases=databases)
+            return ZODB.DB(
+                storage,
+                pool_size=section.pool_size,
+                cache_size=section.cache_size,
+                cache_size_bytes=section.cache_size_bytes,
+                historical_pool_size=section.historical_pool_size,
+                historical_cache_size=section.historical_cache_size,
+                historical_cache_size_bytes=section.historical_cache_size_bytes,
+                historical_timeout=section.historical_timeout,
+                database_name=section.database_name,
+                databases=databases,
+                )
         except:
             storage.close()
             raise

Modified: ZODB/trunk/src/ZODB/tests/testConnection.py
===================================================================
--- ZODB/trunk/src/ZODB/tests/testConnection.py	2008-09-06 07:56:18 UTC (rev 90913)
+++ ZODB/trunk/src/ZODB/tests/testConnection.py	2008-09-06 18:35:41 UTC (rev 90914)
@@ -512,6 +512,114 @@
         >>> db.close()
     """
 
+class _PlayPersistent(Persistent):
+    def setValueWithSize(self, size=0): self.value = size*' '
+    __init__ = setValueWithSize
+
+class EstimatedSizeTests(unittest.TestCase):
+    """check that size estimations are handled correctly."""
+
+    def setUp(self):
+        self.db = db = databaseFromString("<zodb>\n<mappingstorage/>\n</zodb>")
+        self.conn = c = db.open()
+        self.obj = obj = _PlayPersistent()
+        c.root()['obj'] = obj
+        transaction.commit()
+
+    def test_size_set_on_write_commit(self):
+        obj, cache = self.obj, self.conn._cache
+        # we have just written "obj". Its size should not be zero
+        size, cache_size = obj._p_estimated_size, cache.total_estimated_size
+        self.assert_(size > 0)
+        self.assert_(cache_size > size)
+        # increase the size, write again and check that the size changed
+        obj.setValueWithSize(1000)
+        transaction.commit()
+        new_size = obj._p_estimated_size
+        self.assert_(new_size > size)
+        self.assertEqual(cache.total_estimated_size, cache_size + new_size - size)
+
+    def test_size_set_on_write_savepoint(self):
+        obj, cache = self.obj, self.conn._cache
+        # we have just written "obj". Its size should not be zero
+        size, cache_size = obj._p_estimated_size, cache.total_estimated_size
+        # increase the size, write again and check that the size changed
+        obj.setValueWithSize(1000)
+        transaction.savepoint()
+        new_size = obj._p_estimated_size
+        self.assert_(new_size > size)
+        self.assertEqual(cache.total_estimated_size, cache_size + new_size - size)
+
+    def test_size_set_on_load(self):
+        c = self.db.open() # new connection
+        obj = c.root()['obj']
+        # the object is still a ghost and '_p_estimated_size' not yet set
+        # access to unghost
+        cache = c._cache
+        cache_size = cache.total_estimated_size
+        obj.value
+        size = obj._p_estimated_size
+        self.assert_(size > 0)
+        self.assertEqual(cache.total_estimated_size, cache_size + size)
+        # we test here as well that the deactivation works reduced the cache size
+        obj._p_deactivate()
+        self.assertEqual(cache.total_estimated_size, cache_size)
+
+
+    def test_configuration(self):
+        # verify defaults ....
+        expected = 0
+        # ... on db
+        db = self.db
+        self.assertEqual(db.getCacheSizeBytes(), expected)
+        self.assertEqual(db.getHistoricalCacheSizeBytes(), expected)
+        # ... on connection
+        conn = self.conn
+        self.assertEqual(conn._cache.cache_size_bytes, expected)
+        # verify explicit setting ...
+        expected = 10000
+        # ... on db
+        db = databaseFromString("<zodb>\n"
+                                "  cache-size-bytes %d\n"
+                                "  historical-cache-size-bytes %d\n"
+                                "  <mappingstorage />\n"
+                                "</zodb>"
+                                % (expected, expected+1)
+                                )
+        self.assertEqual(db.getCacheSizeBytes(), expected)
+        self.assertEqual(db.getHistoricalCacheSizeBytes(), expected+1)
+        # ... on connectionB
+        conn = db.open()
+        self.assertEqual(conn._cache.cache_size_bytes, expected)
+        # test huge (larger than 4 byte) size limit
+        db = databaseFromString("<zodb>\n"
+                                "  cache-size-bytes 8GB\n"
+                                "  <mappingstorage />\n"
+                                "</zodb>"
+                                )
+        self.assertEqual(db.getCacheSizeBytes(), 0x1L << 33)
+
+
+    def test_cache_garbage_collection(self):
+        db = self.db
+        # activate size based cache garbage collection
+        db.setCacheSizeBytes(1)
+        conn = self.conn
+        cache = conn._cache
+        # verify the change worked as expected
+        self.assertEqual(cache.cache_size_bytes, 1)
+        # verify our entrance assumption is fullfilled
+        self.assert_(cache.total_estimated_size > 1)
+        conn.cacheGC()
+        self.assert_(cache.total_estimated_size <= 1)
+        # sanity check
+        self.assert_(cache.total_estimated_size >= 0)
+
+
+
+
+
+
 # ---- stubs
 
 class StubObject(Persistent):
@@ -649,4 +757,5 @@
     s = unittest.makeSuite(ConnectionDotAdd, 'check')
     s.addTest(doctest.DocTestSuite())
     s.addTest(unittest.makeSuite(TestConnectionInterface))
+    s.addTest(unittest.makeSuite(EstimatedSizeTests))
     return s

Modified: ZODB/trunk/src/persistent/cPersistence.c
===================================================================
--- ZODB/trunk/src/persistent/cPersistence.c	2008-09-06 07:56:18 UTC (rev 90913)
+++ ZODB/trunk/src/persistent/cPersistence.c	2008-09-06 18:35:41 UTC (rev 90914)
@@ -89,6 +89,7 @@
         if (self->cache) {
             /* Create a node in the ring for this unghostified object. */
             self->cache->non_ghost_count++;
+	    self->cache->total_estimated_size += self->estimated_size;
 	    ring_add(&self->cache->ring_home, &self->ring);
 	    Py_INCREF(self);
         }
@@ -144,6 +145,7 @@
     /* if we're ghostifying an object, we better have some non-ghosts */
     assert(self->cache->non_ghost_count > 0);
     self->cache->non_ghost_count--;
+    self->cache->total_estimated_size -= self->estimated_size;
     ring_del(&self->ring);
 }
 
@@ -174,6 +176,7 @@
     /* If we're ghostifying an object, we better have some non-ghosts. */
     assert(self->cache->non_ghost_count > 0);
     self->cache->non_ghost_count--;
+    self->cache->total_estimated_size -= self->estimated_size;
     ring_del(&self->ring);
     self->state = cPersistent_GHOST_STATE;
     dictptr = _PyObject_GetDictPtr((PyObject *)self);
@@ -1011,6 +1014,34 @@
     return PyInt_FromLong(self->state);
 }
 
+static PyObject *
+Per_get_estimated_size(cPersistentObject *self)
+{
+  return PyInt_FromLong(self->estimated_size);
+}
+
+static int
+Per_set_estimated_size(cPersistentObject *self, PyObject *v)
+{
+    if (v) {
+        if (PyInt_Check(v)) {
+	    if (PyInt_AS_LONG(v) < 0) {
+	        PyErr_SetString(PyExc_ValueError,
+			        "_p_estimated_size must not be negative");
+	        return -1;
+	    }
+	    self->estimated_size = PyInt_AS_LONG(v);
+	}
+	else {
+	    PyErr_SetString(PyExc_ValueError,
+			    "_p_estimated_size must be an integer");
+	    return -1;
+	}
+    } else
+        self->estimated_size = 0;
+    return 0;
+}
+
 static PyGetSetDef Per_getsets[] = {
     {"_p_changed", (getter)Per_get_changed, (setter)Per_set_changed},
     {"_p_jar", (getter)Per_get_jar, (setter)Per_set_jar},
@@ -1018,6 +1049,9 @@
     {"_p_oid", (getter)Per_get_oid, (setter)Per_set_oid},
     {"_p_serial", (getter)Per_get_serial, (setter)Per_set_serial},
     {"_p_state", (getter)Per_get_state},
+    {"_p_estimated_size",
+     (getter)Per_get_estimated_size, (setter)Per_set_estimated_size
+    },
     {NULL}
 };
 

Modified: ZODB/trunk/src/persistent/cPersistence.h
===================================================================
--- ZODB/trunk/src/persistent/cPersistence.h	2008-09-06 07:56:18 UTC (rev 90913)
+++ ZODB/trunk/src/persistent/cPersistence.h	2008-09-06 18:35:41 UTC (rev 90914)
@@ -23,11 +23,12 @@
 #define CACHE_HEAD \
     PyObject_HEAD \
     CPersistentRing ring_home; \
-    int non_ghost_count;
+    int non_ghost_count; \
+    PY_LONG_LONG total_estimated_size;   /* total estimated size of items in cache */
 
 struct ccobject_head_struct;
 
-typedef struct ccobject_head_struct PerCache;
+typedef struct ccobject_head_struct PerCache;    
 
 /* How big is a persistent object?
 
@@ -38,13 +39,14 @@
     8  ring struct
     8  serialno
     4  state + extra
+    4  size info
 
-  (52) so far
+  (56) so far
 
     4  dict ptr
     4  weaklist ptr
   -------------------------
-   64  only need 62, but obmalloc rounds up to multiple of eight
+   68  only need 62, but obmalloc rounds up to multiple of eight
 
   Even a ghost requires 64 bytes.  It's possible to make a persistent
   instance with slots and no dict, which changes the storage needed.
@@ -59,7 +61,8 @@
     CPersistentRing ring; \
     char serial[8]; \
     signed char state; \
-    unsigned char reserved[3];
+    unsigned char reserved[3]; \
+    unsigned long estimated_size;
 
 #define cPersistent_GHOST_STATE -1
 #define cPersistent_UPTODATE_STATE 0

Modified: ZODB/trunk/src/persistent/cPickleCache.c
===================================================================
--- ZODB/trunk/src/persistent/cPickleCache.c	2008-09-06 07:56:18 UTC (rev 90913)
+++ ZODB/trunk/src/persistent/cPickleCache.c	2008-09-06 18:35:41 UTC (rev 90914)
@@ -116,6 +116,7 @@
     PyObject *data;                      /* oid -> object dict */
     PyObject *jar;                       /* Connection object */
     int cache_size;                      /* target number of items in cache */
+    PY_LONG_LONG cache_size_bytes;       /* target total estimated size of items in cache */
 
     /* Most of the time the ring contains only:
        * many nodes corresponding to persistent objects
@@ -167,7 +168,7 @@
 }
 
 static int
-scan_gc_items(ccobject *self, int target)
+scan_gc_items(ccobject *self, int target, PY_LONG_LONG target_bytes)
 {
     /* This function must only be called with the ring lock held,
        because it places non-object placeholders in the ring.
@@ -189,7 +190,11 @@
      */
     insert_after(&before_original_home, self->ring_home.r_prev);
     here = self->ring_home.r_next;   /* least recently used object */
-    while (here != &before_original_home && self->non_ghost_count > target) {
+    while (here != &before_original_home &&
+	   (self->non_ghost_count > target
+	    || (target_bytes && self->total_estimated_size > target_bytes)
+	    )
+	   ) {
 	assert(self->ring_lock);
 	assert(here != &self->ring_home);
 
@@ -244,7 +249,7 @@
 }
 
 static PyObject *
-lockgc(ccobject *self, int target_size)
+lockgc(ccobject *self, int target_size, PY_LONG_LONG target_size_bytes)
 {
     /* This is thread-safe because of the GIL, and there's nothing
      * in between checking the ring_lock and acquiring it that calls back
@@ -256,7 +261,7 @@
     }
 
     self->ring_lock = 1;
-    if (scan_gc_items(self, target_size) < 0) {
+    if (scan_gc_items(self, target_size, target_size_bytes) < 0) {
         self->ring_lock = 0;
         return NULL;
     }
@@ -272,6 +277,7 @@
     int obsolete_arg = -999;
     int starting_size = self->non_ghost_count;
     int target_size = self->cache_size;
+    PY_LONG_LONG target_size_bytes = self->cache_size_bytes;
 
     if (self->cache_drain_resistance >= 1) {
         /* This cache will gradually drain down to a small size. Check
@@ -294,7 +300,7 @@
          < 0))
         return NULL;
 
-    return lockgc(self, target_size);
+    return lockgc(self, target_size, target_size_bytes);
 }
 
 static PyObject *
@@ -307,7 +313,7 @@
     if (!PyArg_ParseTuple(args, "|i:full_sweep", &dt))
 	return NULL;
     if (dt == -999)
-	return lockgc(self, 0);
+        return lockgc(self, 0, 0);
     else
 	return cc_incrgc(self, args);
 }
@@ -327,7 +333,7 @@
          < 0))
         return NULL;
 
-    return lockgc(self, 0);
+    return lockgc(self, 0, 0);
 }
 
 static int
@@ -629,6 +635,32 @@
     return PyInt_FromLong(c);
 }
 
+static PyObject *
+cc_update_object_size_estimation(ccobject *self, PyObject *args)
+{
+    PyObject *oid;
+    cPersistentObject *v;
+    unsigned int new_size;
+    if (!PyArg_ParseTuple(args, "OI:updateObjectSizeEstimation", &oid, &new_size))
+	return NULL;
+    /* Note: reference borrowed */
+    v = (cPersistentObject *)PyDict_GetItem(self->data, oid);
+    if (v) {
+        /* we know this object -- update our "total_size_estimation"
+           we must only update when the object is in the ring
+	*/
+        if (v->ring.r_next) {
+            self->total_estimated_size += new_size - v->estimated_size;
+	    /* we do this in "Connection" as we need it even when the
+	       object is not in the cache (or not the ring)
+	    */
+	    /* v->estimated_size = new_size; */
+	}
+    }
+    Py_RETURN_NONE;
+ }
+
+
 static struct PyMethodDef cc_methods[] = {
     {"items", (PyCFunction)cc_items, METH_NOARGS,
      "Return list of oid, object pairs for all items in cache."},
@@ -655,6 +687,10 @@
      "ringlen() -- Returns number of non-ghost items in cache."},
     {"debug_info", (PyCFunction)cc_debug_info, METH_NOARGS,
      "debug_info() -- Returns debugging data about objects in the cache."},
+    {"update_object_size_estimation",
+     (PyCFunction)cc_update_object_size_estimation,
+     METH_VARARGS,
+     "update_object_size_estimation(oid, new_size) -- update the caches size estimation for *oid* (if this is known to the cache)."},
     {NULL, NULL}		/* sentinel */
 };
 
@@ -662,9 +698,10 @@
 cc_init(ccobject *self, PyObject *args, PyObject *kwds)
 {
     int cache_size = 100;
+    PY_LONG_LONG cache_size_bytes = 0;
     PyObject *jar;
 
-    if (!PyArg_ParseTuple(args, "O|i", &jar, &cache_size))
+    if (!PyArg_ParseTuple(args, "O|iL", &jar, &cache_size, &cache_size_bytes))
 	return -1;
 
     self->jar = NULL;
@@ -687,7 +724,9 @@
     self->jar = jar;
     Py_INCREF(jar);
     self->cache_size = cache_size;
+    self->cache_size_bytes = cache_size_bytes;
     self->non_ghost_count = 0;
+    self->total_estimated_size = 0;
     self->klass_count = 0;
     self->cache_drain_resistance = 0;
     self->ring_lock = 0;
@@ -1018,6 +1057,8 @@
 
 static PyMemberDef cc_members[] = {
     {"cache_size", T_INT, offsetof(ccobject, cache_size)},
+    {"cache_size_bytes", T_LONG, offsetof(ccobject, cache_size_bytes)},
+    {"total_estimated_size", T_LONG, offsetof(ccobject, total_estimated_size), RO},
     {"cache_drain_resistance", T_INT,
      offsetof(ccobject, cache_drain_resistance)},
     {"cache_non_ghost_count", T_INT, offsetof(ccobject, non_ghost_count), RO},

Modified: ZODB/trunk/src/persistent/tests/persistent.txt
===================================================================
--- ZODB/trunk/src/persistent/tests/persistent.txt	2008-09-06 07:56:18 UTC (rev 90913)
+++ ZODB/trunk/src/persistent/tests/persistent.txt	2008-09-06 18:35:41 UTC (rev 90914)
@@ -85,7 +85,25 @@
   >>> p.x
   2
 
+We can store a size estimation in ``_p_estimated_size``. Its default is 0.
+The size estimation can be used by a cache associated with the data manager
+to help in the implementation of its replacement strategy or its size bounds.
+Of course, the estimated size must not be negative.
 
+  >>> p._p_estimated_size
+  0
+  >>> p._p_estimated_size = 1000
+  >>> p._p_estimated_size
+  1000
+  >>> p._p_estimated_size = -1
+  Traceback (most recent call last):
+  ....
+  ValueError: _p_estimated_size must not be negative
+  
+    
+
+
+
 Test Persistent with Data Manager
 ---------------------------------
 



More information about the Checkins mailing list