summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/engine/SCons/Memoize.py70
-rw-r--r--src/engine/SCons/Script/Main.py1
-rw-r--r--src/engine/SCons/Script/__init__.py19
-rw-r--r--test/option/debug-memoizer.py42
4 files changed, 95 insertions, 37 deletions
diff --git a/src/engine/SCons/Memoize.py b/src/engine/SCons/Memoize.py
index 1dc95cb..55a10ba 100644
--- a/src/engine/SCons/Memoize.py
+++ b/src/engine/SCons/Memoize.py
@@ -378,8 +378,8 @@ class CounterEntry:
import UserDict
class Counter(UserDict.UserDict):
- def __call__(self, klass, code):
- k = (klass.__name__, id(code))
+ def __call__(self, obj, methname):
+ k = obj.__class__.__name__ + '.' + methname
try:
return self[k]
except KeyError:
@@ -390,18 +390,13 @@ CacheCount = Counter()
CacheCountSelf = Counter()
CacheCountOne = Counter()
-Code_to_Name = {}
-
def Dump():
items = CacheCount.items() + CacheCountSelf.items() + CacheCountOne.items()
- def keyify(t):
- return Code_to_Name[(t[0], t[1])]
- items = map(lambda t, k=keyify: (k(t[0]), t[1]), items)
items.sort()
for k, v in items:
print " %7d hits %7d misses %s()" % (v.hit, v.miss, k)
-def Count_cache_get(func, cdict, args, kw):
+def Count_cache_get(name, func, cdict, args, kw):
"""Called instead of name to see if this method call's return
value has been cached. If it has, just return the cached
value; if not, call the actual method and cache the return."""
@@ -410,7 +405,7 @@ def Count_cache_get(func, cdict, args, kw):
ckey = obj._MeMoIZeR_Key + ':' + _MeMoIZeR_gen_key(args, kw)
- c = CacheCount(obj.__class__, func)
+ c = CacheCount(obj, name)
rval = cdict.get(ckey, "_MeMoIZeR")
if rval is "_MeMoIZeR":
rval = cdict[ckey] = apply(func, args, kw)
@@ -420,7 +415,7 @@ def Count_cache_get(func, cdict, args, kw):
return rval
-def Count_cache_get_self(func, cdict, self):
+def Count_cache_get_self(name, func, cdict, self):
"""Called instead of func(self) to see if this method call's
return value has been cached. If it has, just return the cached
value; if not, call the actual method and cache the return.
@@ -429,7 +424,7 @@ def Count_cache_get_self(func, cdict, self):
ckey = self._MeMoIZeR_Key
- c = CacheCountSelf(self.__class__, func)
+ c = CacheCountSelf(self, name)
rval = cdict.get(ckey, "_MeMoIZeR")
if rval is "_MeMoIZeR":
rval = cdict[ckey] = func(self)
@@ -439,7 +434,7 @@ def Count_cache_get_self(func, cdict, self):
return rval
-def Count_cache_get_one(func, cdict, self, arg):
+def Count_cache_get_one(name, func, cdict, self, arg):
"""Called instead of func(self, arg) to see if this method call's
return value has been cached. If it has, just return the cached
value; if not, call the actual method and cache the return.
@@ -449,7 +444,7 @@ def Count_cache_get_one(func, cdict, self, arg):
ckey = self._MeMoIZeR_Key + ':' + \
(getattr(arg, "_MeMoIZeR_Key", None) or repr(arg))
- c = CacheCountOne(self.__class__, func)
+ c = CacheCountOne(self, name)
rval = cdict.get(ckey, "_MeMoIZeR")
if rval is "_MeMoIZeR":
rval = cdict[ckey] = func(self, arg)
@@ -465,14 +460,30 @@ MCG_dict = {
'MCGO' : Memoizer_cache_get_one,
}
+MCG_lambda = "lambda *args, **kw: MCG(methcode, methcached, args, kw)"
+MCGS_lambda = "lambda self: MCGS(methcode, methcached, self)"
+MCGO_lambda = "lambda self, arg: MCGO(methcode, methcached, self, arg)"
+
def EnableCounting():
+ """Enable counting of Memoizer hits and misses by overriding the
+ globals that hold the non-counting versions of the functions and
+ lambdas we call with the counting versions.
+ """
global MCG_dict
+ global MCG_lambda
+ global MCGS_lambda
+ global MCGO_lambda
+
MCG_dict = {
'MCG' : Count_cache_get,
'MCGS' : Count_cache_get_self,
'MCGO' : Count_cache_get_one,
}
+ MCG_lambda = "lambda *args, **kw: MCG(methname, methcode, methcached, args, kw)"
+ MCGS_lambda = "lambda self: MCGS(methname, methcode, methcached, self)"
+ MCGO_lambda = "lambda self, arg: MCGO(methname, methcode, methcached, self, arg)"
+
class _Memoizer_Simple:
@@ -585,32 +596,25 @@ def memoize_classdict(klass, modelklass, new_klassdict, cacheable, resetting):
new_klassdict['_MeMoIZeR_converted'] = 1
for name,code in cacheable.items():
- Code_to_Name[(klass.__name__, id(code))] = klass.__name__ + '.' + name
eval_dict = {
+ 'methname' : name,
'methcode' : code,
'methcached' : {},
}
eval_dict.update(MCG_dict)
- if code.func_code.co_argcount == 1 and \
- not code.func_code.co_flags & 0xC:
- compiled = \
- compile("\n"*1 +
- "lambda self: MCGS(methcode, methcached, self)",
- whoami('cache_get_self', name),
- "eval")
- elif code.func_code.co_argcount == 2 and \
- not code.func_code.co_flags & 0xC:
- compiled = \
- compile("\n"*2 +
- "lambda self, arg: MCGO(methcode, methcached, self, arg)",
- whoami('cache_get_one', name),
- "eval")
+ fc = code.func_code
+ if fc.co_argcount == 1 and not fc.co_flags & 0xC:
+ compiled = compile("\n"*1 + MCGS_lambda,
+ whoami('cache_get_self', name),
+ "eval")
+ elif fc.co_argcount == 2 and not fc.co_flags & 0xC:
+ compiled = compile("\n"*2 + MCGO_lambda,
+ whoami('cache_get_one', name),
+ "eval")
else:
- compiled = \
- compile("\n"*3 +
- "lambda *args, **kw: MCG(methcode, methcached, args, kw)",
- whoami('cache_get', name),
- "eval")
+ compiled = compile("\n"*3 + MCG_lambda,
+ whoami('cache_get', name),
+ "eval")
newmethod = eval(compiled, eval_dict, {})
new_klassdict[name] = newmethod
diff --git a/src/engine/SCons/Script/Main.py b/src/engine/SCons/Script/Main.py
index f39375a..71f6d61 100644
--- a/src/engine/SCons/Script/Main.py
+++ b/src/engine/SCons/Script/Main.py
@@ -434,7 +434,6 @@ def _set_globals(options):
if "includes" in debug_values:
print_includes = 1
if "memoizer" in debug_values:
- SCons.Memoize.EnableCounting()
print_memoizer = 1
if "memory" in debug_values:
memory_stats = []
diff --git a/src/engine/SCons/Script/__init__.py b/src/engine/SCons/Script/__init__.py
index d94fee2..7368c8c 100644
--- a/src/engine/SCons/Script/__init__.py
+++ b/src/engine/SCons/Script/__init__.py
@@ -39,9 +39,28 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import time
start_time = time.time()
+import os
import string
+import sys
import UserList
+# Special chicken-and-egg handling of the "--debug=memoizer" flags:
+# SCons.Memoize contains a metaclass implementation that affects how
+# the other classes are instantiated. The Memoizer handles optional
+# counting of the hits and misses by using a different, parallel set of
+# functions, so we don't slow down normal operation any more than we
+# have to. But if we wait to enable the counting until we've parsed
+# the command line options normally, it will be too late, because the
+# Memoizer will have already analyzed the classes that it's Memoizing
+# and bound the non-counting versions of the functions. So we have to
+# use a special-case, up-front check for the "--debug=memoizer" flag
+# and turn on Memoizer counting, if desired, before we import any of
+# the other modules that use it.
+sconsflags = string.split(os.environ.get('SCONSFLAGS', ''))
+if "--debug=memoizer" in sys.argv + sconsflags:
+ import SCons.Memoize
+ SCons.Memoize.EnableCounting()
+
import SCons.Action
import SCons.Builder
import SCons.Environment
diff --git a/test/option/debug-memoizer.py b/test/option/debug-memoizer.py
index 2b79c11..4249ca6 100644
--- a/test/option/debug-memoizer.py
+++ b/test/option/debug-memoizer.py
@@ -28,6 +28,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
Test calling the --debug=memoizer option.
"""
+import os
import string
import TestSCons
@@ -43,11 +44,46 @@ env.Cat('file.out', 'file.in')
test.write('file.in', "file.in\n")
-test.run(arguments = '--debug=memoizer')
+# The banner, and a list of representative method names that we expect
+# to show up in the output. Of course, this depends on keeping those
+# names in the implementation, so if we change them, we'll have to
+# change this test...
+expect = [
+ "Memoizer (memory cache) hits and misses",
+ "Dir.exists()",
+ "Executor.get_contents()",
+ "File._save_str()",
+ "SConsEnvironment.get_calculator()",
+]
-expect = "Memoizer (memory cache) hits and misses"
-test.fail_test(string.find(test.stdout(), expect) == -1)
+test.run(arguments = '--debug=memoizer')
+stdout = test.stdout()
+missing = filter(lambda e, s=stdout: string.find(s, e) == -1, expect)
+if missing:
+ print "Missing the following strings in the command line --debug=memoizer output:"
+ print " " + string.join(missing, "\n ")
+ print "STDOUT ============"
+ print stdout
+ test.fail_test(1)
test.must_match('file.out', "file.in\n")
+
+
+test.unlink("file.out")
+
+os.environ['SCONSFLAGS'] = '--debug=memoizer'
+
+test.run()
+stdout = test.stdout()
+missing = filter(lambda e, s=stdout: string.find(s, e) == -1, expect)
+if missing:
+ print "Missing the following strings in the SCONSFLAGS=--debug=memoizer output:"
+ print " " + string.join(missing, "\n ")
+ print "STDOUT ============"
+ print stdout
+ test.fail_test(1)
+
+
+
test.pass_test()