Improve the Inline Cache Stats code
authormsaboff@apple.com <msaboff@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 12 Apr 2019 00:53:12 +0000 (00:53 +0000)
committermsaboff@apple.com <msaboff@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 12 Apr 2019 00:53:12 +0000 (00:53 +0000)
https://bugs.webkit.org/show_bug.cgi?id=196836

Reviewed by Saam Barati.

Source/JavaScriptCore:

Needed to handle the case where the Identifier could be null, for example with InstanceOfAddAccessCase
and InstanceOfReplaceWithJump.

Added the ability to log the location of a GetBy and PutBy property as either on self or up the
protocol chain.

* jit/ICStats.cpp:
(JSC::ICEvent::operator< const):
(JSC::ICEvent::dump const):
* jit/ICStats.h:
(JSC::ICEvent::ICEvent):
(JSC::ICEvent::hash const):
* jit/JITOperations.cpp:
* jit/Repatch.cpp:
(JSC::tryCacheGetByID):
(JSC::tryCachePutByID):
(JSC::tryCacheInByID):

Tools:

Added a new script to consolidate and arrange the output of --useICStats option.

This script merges the output from every group into one large table and sorts it from most common to
least common.  It also counts the slow path GetById and PutById variants and then calculates the
percentage of gets or puts for each unique base,property pair compared to all the gets and puts.
Put together, this is useful to see what property accesses are not getting cached.

* Scripts/ic-stats.py: Added.
(ICStats):
(ICStats.__init__):
(ICStats.parse):
(ICStats.dumpStats):
(usage):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@244204 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/jit/ICStats.cpp
Source/JavaScriptCore/jit/ICStats.h
Source/JavaScriptCore/jit/JITOperations.cpp
Source/JavaScriptCore/jit/Repatch.cpp
Tools/ChangeLog
Tools/Scripts/ic-stats.py [new file with mode: 0755]

index 397c348..b6f41e4 100644 (file)
@@ -1,3 +1,28 @@
+2019-04-11  Michael Saboff  <msaboff@apple.com>
+
+        Improve the Inline Cache Stats code
+        https://bugs.webkit.org/show_bug.cgi?id=196836
+
+        Reviewed by Saam Barati.
+
+        Needed to handle the case where the Identifier could be null, for example with InstanceOfAddAccessCase
+        and InstanceOfReplaceWithJump.
+
+        Added the ability to log the location of a GetBy and PutBy property as either on self or up the
+        protocol chain.
+
+        * jit/ICStats.cpp:
+        (JSC::ICEvent::operator< const):
+        (JSC::ICEvent::dump const):
+        * jit/ICStats.h:
+        (JSC::ICEvent::ICEvent):
+        (JSC::ICEvent::hash const):
+        * jit/JITOperations.cpp:
+        * jit/Repatch.cpp:
+        (JSC::tryCacheGetByID):
+        (JSC::tryCachePutByID):
+        (JSC::tryCacheInByID):
+
 2019-04-11  Devin Rousso  <drousso@apple.com>
 
         Web Inspector: Timelines: can't reliably stop/start a recording
index e0abf29..8135b4f 100644 (file)
@@ -40,13 +40,18 @@ bool ICEvent::operator<(const ICEvent& other) const
     
     if (m_propertyName != other.m_propertyName)
         return codePointCompare(m_propertyName.string(), other.m_propertyName.string()) < 0;
-    
-    return m_kind < other.m_kind;
+
+    if (m_kind != other.m_kind)
+        return m_kind < other.m_kind;
+
+    return m_propertyLocation < other.m_propertyLocation;
 }
 
 void ICEvent::dump(PrintStream& out) const
 {
     out.print(m_kind, "(", m_classInfo ? m_classInfo->className : "<null>", ", ", m_propertyName, ")");
+    if (m_propertyLocation != Unknown)
+        out.print(m_propertyLocation == BaseObject ? " self" : " proto lookup");
 }
 
 void ICEvent::log() const
index 684e813..b02c409 100644 (file)
@@ -78,7 +78,13 @@ public:
         FOR_EACH_ICEVENT_KIND(ICEVENT_KIND_DECLARATION)
 #undef ICEVENT_KIND_DECLARATION
     };
-    
+
+    enum PropertyLocation {
+        Unknown,
+        BaseObject,
+        ProtoLookup
+    };
+
     ICEvent()
     {
     }
@@ -87,9 +93,18 @@ public:
         : m_kind(kind)
         , m_classInfo(classInfo)
         , m_propertyName(propertyName)
+        , m_propertyLocation(Unknown)
     {
     }
 
+    ICEvent(Kind kind, const ClassInfo* classInfo, const Identifier propertyName, bool isBaseProperty)
+        : m_kind(kind)
+        , m_classInfo(classInfo)
+        , m_propertyName(propertyName)
+        , m_propertyLocation(isBaseProperty ? BaseObject : ProtoLookup)
+    {
+    }
+    
     ICEvent(WTF::HashTableDeletedValueType)
         : m_kind(OperationGetById)
     {
@@ -123,7 +138,9 @@ public:
     
     unsigned hash() const
     {
-        return m_kind + WTF::PtrHash<const ClassInfo*>::hash(m_classInfo) + StringHash::hash(m_propertyName.string());
+        if (m_propertyName.isNull())
+            return m_kind + m_propertyLocation + WTF::PtrHash<const ClassInfo*>::hash(m_classInfo);
+        return m_kind + m_propertyLocation + WTF::PtrHash<const ClassInfo*>::hash(m_classInfo) + StringHash::hash(m_propertyName.string());
     }
     
     bool isHashTableDeletedValue() const
@@ -140,6 +157,7 @@ private:
     Kind m_kind { InvalidKind };
     const ClassInfo* m_classInfo { nullptr };
     Identifier m_propertyName;
+    PropertyLocation m_propertyLocation;
 };
 
 struct ICEventHash {
index 1f0a2ed..b54c92a 100644 (file)
@@ -252,9 +252,11 @@ EncodedJSValue JIT_OPERATION operationGetById(ExecState* exec, StructureStubInfo
     JSValue baseValue = JSValue::decode(base);
     PropertySlot slot(baseValue, PropertySlot::InternalMethodType::Get);
     Identifier ident = Identifier::fromUid(vm, uid);
-    
-    LOG_IC((ICEvent::OperationGetById, baseValue.classInfoOrNull(*vm), ident));
-    return JSValue::encode(baseValue.get(exec, ident, slot));
+    JSValue result = baseValue.get(exec, ident, slot);
+
+    LOG_IC((ICEvent::OperationGetById, baseValue.classInfoOrNull(*vm), ident, baseValue == slot.slotBase()));
+
+    return JSValue::encode(result);
 }
 
 EncodedJSValue JIT_OPERATION operationGetByIdGeneric(ExecState* exec, EncodedJSValue base, UniquedStringImpl* uid)
@@ -267,8 +269,11 @@ EncodedJSValue JIT_OPERATION operationGetByIdGeneric(ExecState* exec, EncodedJSV
     JSValue baseValue = JSValue::decode(base);
     PropertySlot slot(baseValue, PropertySlot::InternalMethodType::Get);
     Identifier ident = Identifier::fromUid(vm, uid);
-    LOG_IC((ICEvent::OperationGetByIdGeneric, baseValue.classInfoOrNull(*vm), ident));
-    return JSValue::encode(baseValue.get(exec, ident, slot));
+    JSValue result = baseValue.get(exec, ident, slot);
+    
+    LOG_IC((ICEvent::OperationGetByIdGeneric, baseValue.classInfoOrNull(*vm), ident, baseValue == slot.slotBase()));
+    
+    return JSValue::encode(result);
 }
 
 EncodedJSValue JIT_OPERATION operationGetByIdOptimize(ExecState* exec, StructureStubInfo* stubInfo, EncodedJSValue base, UniquedStringImpl* uid)
@@ -280,9 +285,11 @@ EncodedJSValue JIT_OPERATION operationGetByIdOptimize(ExecState* exec, Structure
     Identifier ident = Identifier::fromUid(vm, uid);
 
     JSValue baseValue = JSValue::decode(base);
-    LOG_IC((ICEvent::OperationGetByIdOptimize, baseValue.classInfoOrNull(*vm), ident));
 
     return JSValue::encode(baseValue.getPropertySlot(exec, ident, [&] (bool found, PropertySlot& slot) -> JSValue {
+        
+        LOG_IC((ICEvent::OperationGetByIdOptimize, baseValue.classInfoOrNull(*vm), ident, baseValue == slot.slotBase()));
+        
         if (stubInfo->considerCaching(exec->codeBlock(), baseValue.structureOrNull()))
             repatchGetByID(exec, baseValue, ident, slot, *stubInfo, GetByIDKind::Normal);
         return found ? slot.getValue(exec, ident) : jsUndefined();
@@ -331,10 +338,11 @@ EncodedJSValue JIT_OPERATION operationGetByIdWithThisOptimize(ExecState* exec, S
 
     JSValue baseValue = JSValue::decode(base);
     JSValue thisValue = JSValue::decode(thisEncoded);
-    LOG_IC((ICEvent::OperationGetByIdWithThisOptimize, baseValue.classInfoOrNull(*vm), ident));
 
     PropertySlot slot(thisValue, PropertySlot::InternalMethodType::Get);
     return JSValue::encode(baseValue.getPropertySlot(exec, ident, slot, [&] (bool found, PropertySlot& slot) -> JSValue {
+        LOG_IC((ICEvent::OperationGetByIdWithThisOptimize, baseValue.classInfoOrNull(*vm), ident, baseValue == slot.slotBase()));
+        
         if (stubInfo->considerCaching(exec->codeBlock(), baseValue.structureOrNull()))
             repatchGetByID(exec, baseValue, ident, slot, *stubInfo, GetByIDKind::WithThis);
         return found ? slot.getValue(exec, ident) : jsUndefined();
@@ -439,10 +447,10 @@ void JIT_OPERATION operationPutByIdStrict(ExecState* exec, StructureStubInfo* st
     
     JSValue baseValue = JSValue::decode(encodedBase);
     Identifier ident = Identifier::fromUid(vm, uid);
-    LOG_IC((ICEvent::OperationPutByIdStrict, baseValue.classInfoOrNull(*vm), ident));
-
     PutPropertySlot slot(baseValue, true, exec->codeBlock()->putByIdContext());
     baseValue.putInline(exec, ident, JSValue::decode(encodedValue), slot);
+    
+    LOG_IC((ICEvent::OperationPutByIdStrict, baseValue.classInfoOrNull(*vm), ident, slot.base() == baseValue));
 }
 
 void JIT_OPERATION operationPutByIdNonStrict(ExecState* exec, StructureStubInfo* stubInfo, EncodedJSValue encodedValue, EncodedJSValue encodedBase, UniquedStringImpl* uid)
@@ -456,9 +464,10 @@ void JIT_OPERATION operationPutByIdNonStrict(ExecState* exec, StructureStubInfo*
     
     JSValue baseValue = JSValue::decode(encodedBase);
     Identifier ident = Identifier::fromUid(vm, uid);
-    LOG_IC((ICEvent::OperationPutByIdNonStrict, baseValue.classInfoOrNull(*vm), ident));
     PutPropertySlot slot(baseValue, false, exec->codeBlock()->putByIdContext());
     baseValue.putInline(exec, ident, JSValue::decode(encodedValue), slot);
+
+    LOG_IC((ICEvent::OperationPutByIdNonStrict, baseValue.classInfoOrNull(*vm), ident, slot.base() == baseValue));
 }
 
 void JIT_OPERATION operationPutByIdDirectStrict(ExecState* exec, StructureStubInfo* stubInfo, EncodedJSValue encodedValue, EncodedJSValue encodedBase, UniquedStringImpl* uid)
@@ -472,9 +481,10 @@ void JIT_OPERATION operationPutByIdDirectStrict(ExecState* exec, StructureStubIn
     
     JSValue baseValue = JSValue::decode(encodedBase);
     Identifier ident = Identifier::fromUid(&vm, uid);
-    LOG_IC((ICEvent::OperationPutByIdDirectStrict, baseValue.classInfoOrNull(vm), ident));
     PutPropertySlot slot(baseValue, true, exec->codeBlock()->putByIdContext());
     CommonSlowPaths::putDirectWithReify(vm, exec, asObject(baseValue), ident, JSValue::decode(encodedValue), slot);
+
+    LOG_IC((ICEvent::OperationPutByIdDirectStrict, baseValue.classInfoOrNull(vm), ident, slot.base() == baseValue));
 }
 
 void JIT_OPERATION operationPutByIdDirectNonStrict(ExecState* exec, StructureStubInfo* stubInfo, EncodedJSValue encodedValue, EncodedJSValue encodedBase, UniquedStringImpl* uid)
@@ -488,9 +498,10 @@ void JIT_OPERATION operationPutByIdDirectNonStrict(ExecState* exec, StructureStu
     
     JSValue baseValue = JSValue::decode(encodedBase);
     Identifier ident = Identifier::fromUid(&vm, uid);
-    LOG_IC((ICEvent::OperationPutByIdDirectNonStrict, baseValue.classInfoOrNull(vm), ident));
     PutPropertySlot slot(baseValue, false, exec->codeBlock()->putByIdContext());
     CommonSlowPaths::putDirectWithReify(vm, exec, asObject(baseValue), ident, JSValue::decode(encodedValue), slot);
+
+    LOG_IC((ICEvent::OperationPutByIdDirectNonStrict, baseValue.classInfoOrNull(vm), ident, slot.base() == baseValue));
 }
 
 void JIT_OPERATION operationPutByIdStrictOptimize(ExecState* exec, StructureStubInfo* stubInfo, EncodedJSValue encodedValue, EncodedJSValue encodedBase, UniquedStringImpl* uid)
@@ -506,12 +517,14 @@ void JIT_OPERATION operationPutByIdStrictOptimize(ExecState* exec, StructureStub
 
     JSValue value = JSValue::decode(encodedValue);
     JSValue baseValue = JSValue::decode(encodedBase);
-    LOG_IC((ICEvent::OperationPutByIdStrictOptimize, baseValue.classInfoOrNull(*vm), ident));
     CodeBlock* codeBlock = exec->codeBlock();
     PutPropertySlot slot(baseValue, true, codeBlock->putByIdContext());
 
     Structure* structure = baseValue.isCell() ? baseValue.asCell()->structure(*vm) : nullptr;
     baseValue.putInline(exec, ident, value, slot);
+
+    LOG_IC((ICEvent::OperationPutByIdStrictOptimize, baseValue.classInfoOrNull(*vm), ident, slot.base() == baseValue));
+
     RETURN_IF_EXCEPTION(scope, void());
 
     if (accessType != static_cast<AccessType>(stubInfo->accessType))
@@ -534,12 +547,14 @@ void JIT_OPERATION operationPutByIdNonStrictOptimize(ExecState* exec, StructureS
 
     JSValue value = JSValue::decode(encodedValue);
     JSValue baseValue = JSValue::decode(encodedBase);
-    LOG_IC((ICEvent::OperationPutByIdNonStrictOptimize, baseValue.classInfoOrNull(*vm), ident));
     CodeBlock* codeBlock = exec->codeBlock();
     PutPropertySlot slot(baseValue, false, codeBlock->putByIdContext());
 
     Structure* structure = baseValue.isCell() ? baseValue.asCell()->structure(*vm) : nullptr;    
     baseValue.putInline(exec, ident, value, slot);
+
+    LOG_IC((ICEvent::OperationPutByIdNonStrictOptimize, baseValue.classInfoOrNull(*vm), ident, slot.base() == baseValue));
+
     RETURN_IF_EXCEPTION(scope, void());
 
     if (accessType != static_cast<AccessType>(stubInfo->accessType))
@@ -562,11 +577,13 @@ void JIT_OPERATION operationPutByIdDirectStrictOptimize(ExecState* exec, Structu
 
     JSValue value = JSValue::decode(encodedValue);
     JSObject* baseObject = asObject(JSValue::decode(encodedBase));
-    LOG_IC((ICEvent::OperationPutByIdDirectStrictOptimize, baseObject->classInfo(vm), ident));
     CodeBlock* codeBlock = exec->codeBlock();
     PutPropertySlot slot(baseObject, true, codeBlock->putByIdContext());
     Structure* structure = nullptr;
     CommonSlowPaths::putDirectWithReify(vm, exec, baseObject, ident, value, slot, &structure);
+
+    LOG_IC((ICEvent::OperationPutByIdDirectStrictOptimize, baseObject->classInfo(vm), ident, slot.base() == baseObject));
+
     RETURN_IF_EXCEPTION(scope, void());
     
     if (accessType != static_cast<AccessType>(stubInfo->accessType))
@@ -589,11 +606,13 @@ void JIT_OPERATION operationPutByIdDirectNonStrictOptimize(ExecState* exec, Stru
 
     JSValue value = JSValue::decode(encodedValue);
     JSObject* baseObject = asObject(JSValue::decode(encodedBase));
-    LOG_IC((ICEvent::OperationPutByIdDirectNonStrictOptimize, baseObject->classInfo(vm), ident));
     CodeBlock* codeBlock = exec->codeBlock();
     PutPropertySlot slot(baseObject, false, codeBlock->putByIdContext());
     Structure* structure = nullptr;
     CommonSlowPaths::putDirectWithReify(vm, exec, baseObject, ident, value, slot, &structure);
+
+    LOG_IC((ICEvent::OperationPutByIdDirectNonStrictOptimize, baseObject->classInfo(vm), ident, slot.base() == baseObject));
+
     RETURN_IF_EXCEPTION(scope, void());
     
     if (accessType != static_cast<AccessType>(stubInfo->accessType))
index 9875444..efca5f0 100644 (file)
@@ -267,7 +267,7 @@ static InlineCacheAction tryCacheGetByID(ExecState* exec, JSValue baseValue, con
 
                 bool generatedCodeInline = InlineAccess::generateSelfPropertyAccess(stubInfo, structure, slot.cachedOffset());
                 if (generatedCodeInline) {
-                    LOG_IC((ICEvent::GetByIdSelfPatch, structure->classInfo(), propertyName));
+                    LOG_IC((ICEvent::GetByIdSelfPatch, structure->classInfo(), propertyName, slot.slotBase() == baseValue));
                     structure->startWatchingPropertyForReplacements(vm, slot.cachedOffset());
                     ftlThunkAwareRepatchCall(codeBlock, stubInfo.slowPathCallLocation(), appropriateOptimizingGetByIdFunction(kind));
                     stubInfo.initGetByIdSelf(codeBlock, structure, slot.cachedOffset());
@@ -376,12 +376,12 @@ static InlineCacheAction tryCacheGetByID(ExecState* exec, JSValue baseValue, con
             }
         }
 
-        LOG_IC((ICEvent::GetByIdAddAccessCase, baseValue.classInfoOrNull(vm), propertyName));
+        LOG_IC((ICEvent::GetByIdAddAccessCase, baseValue.classInfoOrNull(vm), propertyName, slot.slotBase() == baseValue));
 
         result = stubInfo.addAccessCase(locker, codeBlock, propertyName, WTFMove(newCase));
 
         if (result.generatedSomeCode()) {
-            LOG_IC((ICEvent::GetByIdReplaceWithJump, baseValue.classInfoOrNull(vm), propertyName));
+            LOG_IC((ICEvent::GetByIdReplaceWithJump, baseValue.classInfoOrNull(vm), propertyName, slot.slotBase() == baseValue));
             
             RELEASE_ASSERT(result.code());
             InlineAccess::rewireStubAsJump(stubInfo, CodeLocationLabel<JITStubRoutinePtrTag>(result.code()));
@@ -475,7 +475,7 @@ static InlineCacheAction tryCachePutByID(ExecState* exec, JSValue baseValue, Str
                     
                     bool generatedCodeInline = InlineAccess::generateSelfPropertyReplace(stubInfo, structure, slot.cachedOffset());
                     if (generatedCodeInline) {
-                        LOG_IC((ICEvent::PutByIdSelfPatch, structure->classInfo(), ident));
+                        LOG_IC((ICEvent::PutByIdSelfPatch, structure->classInfo(), ident, slot.base() == baseValue));
                         ftlThunkAwareRepatchCall(codeBlock, stubInfo.slowPathCallLocation(), appropriateOptimizingPutByIdFunction(slot, putKind));
                         stubInfo.initPutByIdReplace(codeBlock, structure, slot.cachedOffset());
                         return RetryCacheLater;
@@ -588,12 +588,12 @@ static InlineCacheAction tryCachePutByID(ExecState* exec, JSValue baseValue, Str
             }
         }
 
-        LOG_IC((ICEvent::PutByIdAddAccessCase, structure->classInfo(), ident));
+        LOG_IC((ICEvent::PutByIdAddAccessCase, structure->classInfo(), ident, slot.base() == baseValue));
         
         result = stubInfo.addAccessCase(locker, codeBlock, ident, WTFMove(newCase));
 
         if (result.generatedSomeCode()) {
-            LOG_IC((ICEvent::PutByIdReplaceWithJump, structure->classInfo(), ident));
+            LOG_IC((ICEvent::PutByIdReplaceWithJump, structure->classInfo(), ident, slot.base() == baseValue));
             
             RELEASE_ASSERT(result.code());
 
@@ -654,7 +654,7 @@ static InlineCacheAction tryCacheInByID(
                 && !structure->needImpurePropertyWatchpoint()) {
                 bool generatedCodeInline = InlineAccess::generateSelfInAccess(stubInfo, structure);
                 if (generatedCodeInline) {
-                    LOG_IC((ICEvent::InByIdSelfPatch, structure->classInfo(), ident));
+                    LOG_IC((ICEvent::InByIdSelfPatch, structure->classInfo(), ident, slot.slotBase() == base));
                     structure->startWatchingPropertyForReplacements(vm, slot.cachedOffset());
                     ftlThunkAwareRepatchCall(codeBlock, stubInfo.slowPathCallLocation(), operationInByIdOptimize);
                     stubInfo.initInByIdSelf(codeBlock, structure, slot.cachedOffset());
@@ -692,7 +692,7 @@ static InlineCacheAction tryCacheInByID(
         if (!conditionSet.isValid())
             return GiveUpOnCache;
 
-        LOG_IC((ICEvent::InAddAccessCase, structure->classInfo(), ident));
+        LOG_IC((ICEvent::InAddAccessCase, structure->classInfo(), ident, slot.slotBase() == base));
 
         std::unique_ptr<AccessCase> newCase = AccessCase::create(
             vm, codeBlock, wasFound ? AccessCase::InHit : AccessCase::InMiss, wasFound ? slot.cachedOffset() : invalidOffset, structure, conditionSet, WTFMove(prototypeAccessChain));
@@ -700,7 +700,7 @@ static InlineCacheAction tryCacheInByID(
         result = stubInfo.addAccessCase(locker, codeBlock, ident, WTFMove(newCase));
 
         if (result.generatedSomeCode()) {
-            LOG_IC((ICEvent::InReplaceWithJump, structure->classInfo(), ident));
+            LOG_IC((ICEvent::InReplaceWithJump, structure->classInfo(), ident, slot.slotBase() == base));
             
             RELEASE_ASSERT(result.code());
             InlineAccess::rewireStubAsJump(stubInfo, CodeLocationLabel<JITStubRoutinePtrTag>(result.code()));
index 8ecfe11..e4981ce 100644 (file)
@@ -1,3 +1,24 @@
+2019-04-11  Michael Saboff  <msaboff@apple.com>
+
+        Improve the Inline Cache Stats code
+        https://bugs.webkit.org/show_bug.cgi?id=196836
+
+        Reviewed by Saam Barati.
+
+        Added a new script to consolidate and arrange the output of --useICStats option.
+
+        This script merges the output from every group into one large table and sorts it from most common to
+        least common.  It also counts the slow path GetById and PutById variants and then calculates the
+        percentage of gets or puts for each unique base,property pair compared to all the gets and puts.
+        Put together, this is useful to see what property accesses are not getting cached.
+
+        * Scripts/ic-stats.py: Added.
+        (ICStats):
+        (ICStats.__init__):
+        (ICStats.parse):
+        (ICStats.dumpStats):
+        (usage):
+
 2019-04-10  Alex Christensen  <achristensen@webkit.org>
 
         WKNavigationResponse._downloadAttribute should be nil when no download attribute is present
diff --git a/Tools/Scripts/ic-stats.py b/Tools/Scripts/ic-stats.py
new file mode 100755 (executable)
index 0000000..91da765
--- /dev/null
@@ -0,0 +1,125 @@
+#! /usr/bin/python
+
+# Copyright (C) 2019 Apple Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+# THE POSSIBILITY OF SUCH DAMAGE.
+
+import re
+import sys
+
+icStatRecord = re.compile(" +(\w+)\(([^,]+), ([^)]+)\)([^:]*): (\d+)")
+getByIdPrefix = "OperationGetById"
+putByIdPrefix = "OperationPutById"
+
+
+class ICStats:
+    def __init__(self):
+        self.combinedRecords = {}
+        self.slowGetById = {}
+        self.slowPutById = {}
+        self.totalSlowGetById = 0
+        self.totalSlowPutById = 0
+
+    def parse(self, file):
+        for line in file:
+            match = re.match(icStatRecord, line)
+            if match:
+                operation = match.group(1)
+                base = match.group(2)
+                property = match.group(3)
+                location = match.group(4).strip()
+                count = int(match.group(5))
+                recordKey = (operation, base, property, location)
+
+                if recordKey not in self.combinedRecords:
+                    self.combinedRecords[recordKey] = count
+                else:
+                    self.combinedRecords[recordKey] += count
+
+                if operation.startswith(getByIdPrefix):
+                    self.totalSlowGetById += count
+
+                    slowGetByIdKey = (base, property)
+                    if slowGetByIdKey not in self.slowGetById:
+                        self.slowGetById[slowGetByIdKey] = count
+                    else:
+                        self.slowGetById[slowGetByIdKey] += count
+
+                elif operation.startswith(putByIdPrefix):
+                    self.totalSlowPutById += count
+
+                    slowPutByIdKey = (base, property)
+                    if slowPutByIdKey not in self.slowPutById:
+                        self.slowPutById[slowPutByIdKey] = count
+                    else:
+                        self.slowPutById[slowPutByIdKey] += count
+
+    def dumpStats(self):
+        print "Total Slow getById = {0:>13,d}".format(self.totalSlowGetById)
+        print "Total Slow putById = {0:>13,d}".format(self.totalSlowPutById)
+
+        print "Operation                            Base                  Property                              Location          Count  % tot"
+        print "-----------------------------------  --------------------  ------------------------------------  ------------  ---------   slow"
+
+        keys = sorted(self.combinedRecords.keys(), key=lambda t: self.combinedRecords[t], reverse=True)
+        for key in keys:
+            count = self.combinedRecords[key]
+            operation = key[0]
+            base = key[1]
+            property = key[2]
+
+            if operation.startswith(getByIdPrefix):
+                slowPercent = "  {0:>4.1f}%".format(float(self.slowGetById[(base, property)] * 100) / float(self.totalSlowGetById))
+            elif operation.startswith(putByIdPrefix):
+                slowPercent = "  {0:>4.1f}%".format(float(self.slowPutById[(base, property)] * 100) / float(self.totalSlowPutById))
+            else:
+                slowPercent = ""
+
+            if len(property) > 36:
+                property = property[0:32] + "..."
+
+            print "{0:35}  {1:20}  {2:36}  {3:12}  {4:>9d}{5}".format(operation[0:34], base, property, key[3], count, slowPercent)
+
+
+def usage():
+    print "Usage: {0} [ic-stats-file]".format(sys.argv[0])
+    print "        Where <ic-stats-file> is the results of using the useICStats option."
+    exit(1)
+
+if __name__ == "__main__":
+    if len(sys.argv) > 1:
+        if sys.argv[1] == "-h" or sys.argv[1].lower() == "--help" or sys.argv[1] == "-?":
+            usage()
+        try:
+            file = open(sys.argv[1], "r")
+        except IOError as e:
+            print "Couldn't open {0}, {1}".format(sys.argv[1], e.strerror)
+            usage()
+        except:
+            print "Unexpected error:", sys.exc_info()[0]
+            usage()
+    else:
+        file = sys.stdin
+
+    icStats = ICStats()
+    icStats.parse(file)
+    icStats.dumpStats()