Updated to a supported idmapper version. Added a method for calculating the cache usage of the idmapper, and tied it to the @system command.

This commit is contained in:
Griatch 2012-04-30 00:51:36 +02:00
parent 571c7a3cab
commit e82515f8cb
5 changed files with 138 additions and 60 deletions

View file

@ -1,29 +1,24 @@
"""
This is mostly unmodified from the original idmapper.
Django ID mapper
Evennia changes:
The cache mechanism was changed from a WeakValueDictionary to a
normal dictionary. The old way caused very hard-to-diagnose bugs
over long periods of time (which Evennia requires)
added save() overloading mechanism to update cache
added get_all_cached_instances() for convenient access to objects
Modified for Evennia by making sure that no model references
leave caching unexpectedly (no use if WeakRefs).
Also adds cache_size() for monitoring the size of the cache.
"""
from django.db.models.base import Model, ModelBase
from django.db.models.signals import post_save, pre_delete, \
post_syncdb
from manager import SharedMemoryManager
class SharedMemoryModelBase(ModelBase):
#def __new__(cls, name, bases, attrs):
# super_new = super(ModelBase, cls).__new__
# parents = [b for b in bases if isinstance(b, SharedMemoryModelBase)]
# if not parents:
# # If this isn't a subclass of Model, don't do anything special.
# print "not a subclass of Model", name, bases
# return super_new(cls, name, bases, attrs)
# return super(SharedMemoryModelBase, cls).__new__(cls, name, bases, attrs)
# CL: upstream had a __new__ method that skipped ModelBase's __new__ if
# SharedMemoryModelBase was not in the model class's ancestors. It's not
# clear what was the intended purpose, but skipping ModelBase.__new__
# broke things; in particular, default manager inheritance.
def __call__(cls, *args, **kwargs):
"""
@ -35,9 +30,6 @@ class SharedMemoryModelBase(ModelBase):
def new_instance():
return super(SharedMemoryModelBase, cls).__call__(*args, **kwargs)
#if _get_full_cache:
# return cls.__instance_cache__.values()
instance_key = cls._get_cache_key(args, kwargs)
# depending on the arguments, we might not be able to infer the PK, so in that case we create a new instance
if instance_key is None:
@ -51,17 +43,17 @@ class SharedMemoryModelBase(ModelBase):
return cached_instance
def _prepare(cls):
# this is the core cache
cls.__instance_cache__ = {} #WeakValueDictionary()
cls.__instance_cache__ = {} #WeakValueDictionary()
super(SharedMemoryModelBase, cls)._prepare()
class SharedMemoryModel(Model):
# XXX: this is creating a model and it shouldn't be.. how do we properly
# subclass now?
# CL: setting abstract correctly to allow subclasses to inherit the default
# manager.
__metaclass__ = SharedMemoryModelBase
objects = SharedMemoryManager()
class Meta:
abstract = True
@ -117,9 +109,11 @@ class SharedMemoryModel(Model):
return cls.__instance_cache__.values()
get_all_cached_instances = classmethod(get_all_cached_instances)
def _flush_cached_by_key(cls, key):
del cls.__instance_cache__[key]
try:
del cls.__instance_cache__[key]
except KeyError:
pass
_flush_cached_by_key = classmethod(_flush_cached_by_key)
def flush_cached_instance(cls, instance):
@ -128,31 +122,59 @@ class SharedMemoryModel(Model):
since this is most likely called from delete(), and we want to make sure we don't cache dead objects.
"""
cls._flush_cached_by_key(instance._get_pk_val())
#key = "%s-%s" % (cls, instance.pk)
#print "uncached: %s (%s: %s) (total cached: %s)" % (instance, cls.__name__, len(cls.__instance_cache__), len(TCACHE))
flush_cached_instance = classmethod(flush_cached_instance)
def save(self, *args, **kwargs):
#ssave = super(SharedMemoryModel, self).save
#reactor.callInThread(ssave, *args, **kwargs)
super(SharedMemoryModel, self).save(*args, **kwargs)
self.__class__.cache_instance(self)
# TODO: This needs moved to the prepare stage (I believe?)
objects = SharedMemoryManager()
from django.db.models.signals import pre_delete
def flush_instance_cache(cls):
cls.__instance_cache__ = {} #WeakValueDictionary()
flush_instance_cache = classmethod(flush_instance_cache)
# Use a signal so we make sure to catch cascades.
def flush_singleton_cache(sender, instance, **kwargs):
# XXX: Is this the best way to make sure we can flush?
if isinstance(instance, SharedMemoryModel):
instance.__class__.flush_cached_instance(instance)
pre_delete.connect(flush_singleton_cache)
def flush_cache(**kwargs):
for model in SharedMemoryModel.__subclasses__():
model.flush_instance_cache()
#request_finished.connect(flush_cache)
post_syncdb.connect(flush_cache)
def flush_cached_instance(sender, instance, **kwargs):
# XXX: Is this the best way to make sure we can flush?
if not hasattr(instance, 'flush_cached_instance'):
return
sender.flush_cached_instance(instance)
pre_delete.connect(flush_cached_instance)
def update_cached_instance(sender, instance, **kwargs):
if not hasattr(instance, 'cache_instance'):
return
sender.cache_instance(instance)
post_save.connect(update_cached_instance)
def cache_size(mb=True):
"""
Returns a dictionary with estimates of the
cache size of each subclass.
mb - return the result in MB.
"""
import sys
sizedict = {"_total": [0, 0]}
def getsize(model):
instances = model.get_all_cached_instances()
linst = len(instances)
size = sum([sys.getsizeof(o) for o in instances])
size = (mb and size/1024.0) or size
return (linst, size)
def get_recurse(submodels):
for submodel in submodels:
subclasses = submodel.__subclasses__()
if not subclasses:
tup = getsize(submodel)
sizedict["_total"][0] += tup[0]
sizedict["_total"][1] += tup[1]
sizedict[submodel.__name__] = tup
else:
get_recurse(subclasses)
get_recurse(SharedMemoryModel.__subclasses__())
sizedict["_total"] = tuple(sizedict["_total"])
return sizedict
# XXX: It's to be determined if we should use this or not.
# def update_singleton_cache(sender, instance, **kwargs):
# if isinstance(instance.__class__, SharedMemoryModel):
# instance.__class__.cache_instance(instance)
# post_save.connect(flush_singleton_cache)