mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-27 22:17:03 +00:00
263 lines
7.5 KiB
Plaintext
263 lines
7.5 KiB
Plaintext
/*
|
|
* CPTrace.j
|
|
*
|
|
* Created by cacaodev
|
|
* Copyright 2012 <cacaodev@gmail.com>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
var patchedClassesAndSelectors = [],
|
|
globalStack = [],
|
|
globalLevel = 0;
|
|
|
|
var Tracer = function()
|
|
{
|
|
this.td = 0;
|
|
this.tc = 0;
|
|
this.displayFunction = nil;
|
|
};
|
|
|
|
Tracer.prototype.log = function()
|
|
{
|
|
var c = globalStack.length;
|
|
|
|
for (var i = 0; i < c; i++)
|
|
{
|
|
var trace = globalStack.shift();
|
|
this.displayFunction(trace.receiver, trace.selector, trace.args, trace.duration, this.td, this.tc, trace.level);
|
|
}
|
|
};
|
|
|
|
var defaultDisplay = function(receiver, selector, args, duration, total_duration, total_count, level)
|
|
{
|
|
var message = objj_message(receiver, selector, args),
|
|
// WARNING: average duration only supported for root calls.
|
|
avg_message = (level == 0) ? (" , avg = " + (ROUND(100 * total_duration / total_count) / 100) + " ms") : "";
|
|
|
|
console.log(indent(level) + message + " in " + duration + " ms" + avg_message);
|
|
};
|
|
|
|
var indent = function(n)
|
|
{
|
|
var str = "";
|
|
while (n--)
|
|
str += " ";
|
|
|
|
return str;
|
|
};
|
|
|
|
var objj_message = function(receiver, selector, args)
|
|
{
|
|
var c = args.length,
|
|
sel = selector.split(":"),
|
|
rdesc = receiver ? [receiver description] : "<null>";
|
|
|
|
while (c--)
|
|
sel.splice(c + 1, 0, ":" + [args[c] description] + " ");
|
|
|
|
var joined = sel.join("");
|
|
|
|
if (args.length)
|
|
joined = joined.substring(0, joined.length - 1);
|
|
|
|
return ("[" + rdesc + " " + joined + "]");
|
|
};
|
|
|
|
function CPTrace(aClassName, aSelector, displayFunction)
|
|
{
|
|
var aclass = CPClassFromString(aClassName);
|
|
|
|
if (aclass)
|
|
_CPTraceClass(aclass, aSelector, displayFunction);
|
|
else if (typeof(objj_getClassList) != 'undefined')
|
|
{
|
|
var classes = [],
|
|
patchednum = 0,
|
|
numclasses = objj_getClassList(classes, 400),
|
|
regex = new RegExp(aClassName);
|
|
|
|
while (numclasses--)
|
|
{
|
|
var cls = classes[numclasses];
|
|
if (regex.test(cls))
|
|
{
|
|
console.log("Patching " + cls + " -" + aSelector);
|
|
_CPTraceClass(cls, aSelector, displayFunction);
|
|
patchednum++;
|
|
}
|
|
}
|
|
|
|
if (patchednum == 0)
|
|
console.log("Could not find any class matching '" + aClassName + "'");
|
|
else
|
|
console.log("Patched " + patchednum + " classes matching " + aClassName);
|
|
}
|
|
else
|
|
[CPException raise:CPInvalidArgumentException reason:("Unknown class name '" + aClassName + "'")];
|
|
}
|
|
|
|
var _CPTraceClass = function(aClass, aSelector, displayFunction)
|
|
{
|
|
if (![aClass instancesRespondToSelector:aSelector])
|
|
[CPException raise:CPInvalidArgumentException reason:(aClass + " does not respond to '" + aSelector + "'")];
|
|
|
|
var superclass = aClass;
|
|
while (![superclass instancesImplementsSelector:aSelector] && superclass != [CPObject class])
|
|
superclass = [superclass superclass];
|
|
|
|
var patchUniqueString = (superclass + "_" + aSelector);
|
|
if ([patchedClassesAndSelectors containsObject:patchUniqueString])
|
|
{
|
|
if (superclass == aClass)
|
|
console.log(superclass + " -" + aSelector + " is already patched. Ignoring.");
|
|
else
|
|
console.log("-" + aSelector + " is implemented in a superclass of " + aClass + " (" + superclass + ") where it's already patched. Ignoring.");
|
|
|
|
return;
|
|
}
|
|
|
|
var patched_sel = CPSelectorFromString("patched_" + CPStringFromSelector(aSelector)),
|
|
tracer = new Tracer();
|
|
|
|
tracer.displayFunction = displayFunction ? displayFunction : defaultDisplay;
|
|
|
|
class_addMethod(aClass, patched_sel, function()
|
|
{
|
|
var orig_arguments = arguments,
|
|
receiver = orig_arguments[0],
|
|
selector = orig_arguments[1],
|
|
args = [];
|
|
|
|
for (var i = 2; i < orig_arguments.length; i++)
|
|
args.push(orig_arguments[i]);
|
|
|
|
orig_arguments[1] = patched_sel;
|
|
|
|
var trace = {receiver:receiver, selector:selector, args:args, start:(new Date()), level:globalLevel};
|
|
globalStack.push(trace);
|
|
globalLevel++;
|
|
|
|
objj_msgSend.apply(objj_msgSend, orig_arguments);
|
|
|
|
var duration = (trace.duration = new Date() - trace.start);
|
|
globalLevel--;
|
|
if (globalLevel == 0)
|
|
{
|
|
if (duration > 0)
|
|
tracer.tc++;
|
|
tracer.td += trace.duration;
|
|
tracer.log();
|
|
}
|
|
|
|
}, "");
|
|
|
|
Swizzle(aClass, aSelector, patched_sel);
|
|
[patchedClassesAndSelectors addObject:patchUniqueString];
|
|
};
|
|
|
|
function CPTraceStop(aClass, aSelector)
|
|
{
|
|
var patchUniqueString = (aClass + "_" + aSelector);
|
|
if (![patchedClassesAndSelectors containsObject:patchUniqueString])
|
|
return;
|
|
|
|
var patched_sel = CPSelectorFromString("patched_" + CPStringFromSelector(aSelector));
|
|
Swizzle(CPClassFromString(aClass), patched_sel, aSelector);
|
|
[patchedClassesAndSelectors removeObject:patchUniqueString];
|
|
}
|
|
|
|
var Swizzle = function(aClass, orig_sel, new_sel)
|
|
{
|
|
var origMethod = class_getInstanceMethod(aClass, orig_sel),
|
|
newMethod = class_getInstanceMethod(aClass, new_sel);
|
|
|
|
// This check should be in class_addMethod : Don't add and return NO and if method already exists.
|
|
if (getMethodNoSuper(aClass, orig_sel) == NULL)
|
|
{
|
|
class_addMethod(aClass, orig_sel, method_getImplementation(newMethod), "");
|
|
class_replaceMethod(aClass, new_sel, method_getImplementation(origMethod), "");
|
|
}
|
|
else
|
|
method_exchangeImplementations(origMethod, newMethod);
|
|
|
|
/*
|
|
if (class_addMethod(aClass, orig_sel, method_getImplementation(newMethod), ""))
|
|
class_replaceMethod(aClass, new_sel, method_getImplementation(origMethod), "");
|
|
else
|
|
method_exchangeImplementations(origMethod, newMethod);
|
|
*/
|
|
};
|
|
|
|
|
|
var getMethodNoSuper = function(cls, sel)
|
|
{
|
|
var method_list = cls.method_list,
|
|
count = method_list.length;
|
|
|
|
while (count--)
|
|
{
|
|
var mthd = method_list[count];
|
|
if (mthd.name == sel)
|
|
return mthd;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
Utility for moving average
|
|
*/
|
|
var moving_averager = function(period)
|
|
{
|
|
var nums = [];
|
|
|
|
return function(num)
|
|
{
|
|
nums.push(num);
|
|
|
|
if (nums.length > period)
|
|
nums.splice(0,1); // remove the first element of the array
|
|
|
|
var sum = 0,
|
|
count = nums.length;
|
|
|
|
for (var i = 0; i < count; i++)
|
|
sum += nums[i];
|
|
|
|
var n = period;
|
|
|
|
if (count < period)
|
|
n = count;
|
|
|
|
return(sum / n);
|
|
}
|
|
};
|
|
|
|
@implementation CPObject (instancesImplementsSelector)
|
|
|
|
- (BOOL)instancesImplementsSelector:(SEL)aSelector
|
|
{
|
|
var methods = class_copyMethodList(self),
|
|
count = methods.length;
|
|
|
|
while (count--)
|
|
if (method_getName(methods[count]) === aSelector)
|
|
return YES;
|
|
|
|
return NO;
|
|
}
|
|
|
|
@end |