[IMP] Performance improvement: improved cache with context, better get_object
[odoo/odoo.git] / openerp / tools / cache.py
1 import lru
2 import logging
3
4 logger = logging.getLogger(__name__)
5
6 class ormcache(object):
7     """ LRU cache decorator for orm methods,
8     """
9
10     def __init__(self, skiparg=2, size=8192, multi=None, timeout=None):
11         self.skiparg = skiparg
12         self.size = size
13         self.method = None
14         self.stat_miss = 0
15         self.stat_hit = 0
16         self.stat_err = 0
17
18     def __call__(self,m):
19         self.method = m
20         def lookup(self2, cr, *args, **argv):
21             r = self.lookup(self2, cr, *args, **argv)
22             return r
23         lookup.clear_cache = self.clear
24         return lookup
25
26     def stat(self):
27         return "lookup-stats hit=%s miss=%s err=%s ratio=%.1f" % (self.stat_hit,self.stat_miss,self.stat_err, (100*float(self.stat_hit))/(self.stat_miss+self.stat_hit) )
28
29     def lru(self, self2):
30         try:
31             ormcache = getattr(self2, '_ormcache')
32         except AttributeError:
33             ormcache = self2._ormcache = {}
34         try:
35             d = ormcache[self.method]
36         except KeyError:
37             d = ormcache[self.method] = lru.LRU(self.size)
38         return d
39
40     def lookup(self, self2, cr, *args, **argv):
41         d = self.lru(self2)
42         key = args[self.skiparg-2:]
43         try:
44            r = d[key]
45            self.stat_hit += 1
46            return r
47         except KeyError:
48            self.stat_miss += 1
49            value = d[key] = self.method(self2, cr, *args)
50            return value
51         except TypeError:
52            self.stat_err += 1
53            return self.method(self2, cr, *args)
54
55     def clear(self, self2, *args):
56         """ Remove *args entry from the cache or all keys if *args is undefined 
57         """
58         d = self.lru(self2)
59         if args:
60             logger.warn("ormcache.clear arguments are deprecated and ignored "
61                         "(while clearing caches on (%s).%s)",
62                         self2._name, self.method.__name__)
63         d.clear()
64         self2.pool._any_cache_cleared = True
65
66 class ormcache_context(ormcache):
67     def __init__(self, skiparg=2, size=8192, accepted_keys=()):
68         super(ormcache_context,self).__init__(skiparg,size)
69         self.accepted_keys = accepted_keys
70
71     def lookup(self, self2, cr, *args, **argv):
72         d = self.lru(self2)
73
74         context = argv.get('context', {})
75         ckey = filter(lambda x: x[0] in self.accepted_keys, context.items())
76         ckey.sort()
77
78         d = self.lru(self2)
79         key = args[self.skiparg-2:]+tuple(ckey)
80         try:
81            r = d[key]
82            self.stat_hit += 1
83            return r
84         except KeyError:
85            self.stat_miss += 1
86            value = d[key] = self.method(self2, cr, *args, **argv)
87            return value
88         except TypeError:
89            self.stat_err += 1
90            return self.method(self2, cr, *args, **argv)
91
92
93 class ormcache_multi(ormcache):
94     def __init__(self, skiparg=2, size=8192, multi=3):
95         super(ormcache_multi,self).__init__(skiparg,size)
96         self.multi = multi - 2
97
98     def lookup(self, self2, cr, *args, **argv):
99         d = self.lru(self2)
100         args = list(args)
101         multi = self.multi
102         ids = args[multi]
103         r = {}
104         miss = []
105
106         for i in ids:
107             args[multi] = i
108             key = tuple(args[self.skiparg-2:])
109             try:
110                r[i] = d[key]
111                self.stat_hit += 1
112             except Exception:
113                self.stat_miss += 1
114                miss.append(i)
115
116         if miss:
117             args[multi] = miss
118             r.update(self.method(self2, cr, *args))
119
120         for i in miss:
121             args[multi] = i
122             key = tuple(args[self.skiparg-2:])
123             d[key] = r[i]
124
125         return r
126
127 class dummy_cache(object):
128     """ Cache decorator replacement to actually do no caching.
129     """
130     def __init__(self, *l, **kw):
131         pass
132     def __call__(self, fn):
133         fn.clear_cache = self.clear
134         return fn
135     def clear(self, *l, **kw):
136         pass
137
138 if __name__ == '__main__':
139
140     class A():
141         @ormcache()
142         def m(self,a,b):
143             print  "A::m(", self,a,b
144             return 1
145
146         @ormcache_multi(multi=3)
147         def n(self,cr,uid,ids):
148             print  "m", self,cr,uid,ids
149             return dict([(i,i) for i in ids])
150
151     a=A()
152     r=a.m(1,2)
153     r=a.m(1,2)
154     r=a.n("cr",1,[1,2,3,4])
155     r=a.n("cr",1,[1,2])
156     print r
157     for i in a._ormcache:
158         print a._ormcache[i].d
159     a.m.clear_cache()
160     a.n.clear_cache(a,1,1)
161     r=a.n("cr",1,[1,2])
162     print r
163     r=a.n("cr",1,[1,2])
164
165 # For backward compatibility
166 cache = ormcache
167
168 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: