Fix --debug=memoizer so it actually prints some stats on Python 2.x.
authorstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Thu, 13 Jan 2005 18:28:05 +0000 (18:28 +0000)
committerstevenknight <stevenknight@fdb21ef1-2011-0410-befe-b5e4ea1792b1>
Thu, 13 Jan 2005 18:28:05 +0000 (18:28 +0000)
git-svn-id: http://scons.tigris.org/svn/scons/trunk@1216 fdb21ef1-2011-0410-befe-b5e4ea1792b1

src/engine/SCons/Memoize.py
src/engine/SCons/Script/Main.py
src/engine/SCons/Script/__init__.py
test/option/debug-memoizer.py

index 1dc95cb9679e7a7741dc61f5d06578d6e18267c9..55a10bac4eed46e6013c36deb4df803b31b8f2ee 100644 (file)
@@ -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
 
index f39375a9f54ea991982078fbbca86f535290f852..71f6d61822da59447162c6762313c88075a25e3e 100644 (file)
@@ -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 = []
index d94fee2c435e893be9cb3eb48739298d6835d41c..7368c8c85420c67fafb7f7387e192dc910dbc295 100644 (file)
@@ -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
index 2b79c118a816cf6b16d8d74b6579bf4367979a14..4249ca6bd3657b91ddb0737b481a626fb2583e43 100644 (file)
@@ -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()