JavaScript Object Profiler
От: Oyster Украина https://github.com/devoyster
Дата: 25.03.05 13:18
Оценка: 14 (2)
Всем добрый день!

Не так давно пришлось мне заниматься профайлингом JavaScript-классов в IE. Методов у меня было много, вложенных объектов тоже хватало, и вставлять вручную в каждый метод код для замера времени выполнения немного ... напрягало Поэтому я решился и написал простой до безобразия JS-класс, который умеет профайлить другие классы. Фактически он подменяет методы классов, вставляет свой код-обёртку для замеров времени и построения дерева вызовов, а внутри вызывает "родной" метод класса. Профайлит также и методы вложенных объектов. Проверялся только в IE 6.0.

Собственно класс называется ObjectProfiler. У него есть 3 основных мембера:

Чтобы легче было использовать этот класс на деле, я написал другой класс, ProfilerTextareaRenderer, который умеет отображать результаты профайлинга в <textarea>.

Ну и собственно пример использования (HTML):

<script language="javascript" src="ObjectProfiler.js"></script>
<script language="javascript" src="ProfilerTextareaRenderer.js"></script>

<script>
// Кастомный класс
function CustomClass()
{
    // Поля
    this._child1 = new CustomChild();
    this._child2 = new CustomChild();
    
    // Методы
    this.run = function()
    {
        this.method1();
        this.method2();
    }
    
    this.method1 = function()
    {
        this._child1.doLoop(1000000);
    }
    
    this.method2 = function()
    {
        this._child2.doLoop(100000);
    }
}

function CustomChild()
{
    this.doLoop = function(n)
    {
        for (var i = 0; i < n; ++i) {}
    }
}
</script>

<script language="javascript">
// Создаём экземпляр нашего кастомного класса
var customObject = new CustomClass();

// Создаём экземпляр профайлера
var profiler = new ObjectProfiler();

// Исключаем некоторых мемберов объекта из профайлинга
profiler.excludeNames = [ "_child2" ];

// Запускаем профайлинг
profiler.startProfiling(customObject);

// Выводить результаты будем в <textarea id="profOutput">
var profRenderer = new ProfilerTextareaRenderer();
profRenderer.minMs = 1;  // выводим только вызовы дольше 1 мс
profRenderer.registerProfiler(profiler);

window.onload = function() { profRenderer.initFromControl("profOutput"); }
</script>

<button onclick="customObject.run()">Run object method (with profiling)</button>
<textarea id="profOutput" style="width:100%;height:250px"></textarea>

Код профайлера в ответе на это сообщение.
http://rsdn.org/File/27948/bf.gif
Re: Source
От: Oyster Украина https://github.com/devoyster
Дата: 25.03.05 13:19
Оценка:
А вот и код профайлера.

ObjectProfiler.js:
// -----------------------------------------------------------------------------
// Creates new ObjectProfiler object.
// Used for JavaScript objects time profiling
function ObjectProfiler()
{
    this.calls    = [];    // calls array
    this._context = null;  // current call context
    
    this.excludeNames = [];  // array of excluded objects names
    
    this.oncallfinished = null;  // handler called when zero level call is finished
}

// -----------------------------------------------------------------------------
// Starts profiling of given object
ObjectProfiler.prototype.startProfiling = function(obj)
{
    this._startProfilingInternal("this", obj);
};

// -----------------------------------------------------------------------------
// Internal: starts profiling of given object
ObjectProfiler.prototype._startProfilingInternal = function(objName, obj)
{
    var excludeNames       = this.excludeNames;
    var excludeNamesLength = excludeNames.length;
    var isExcludedName;
    
    // Enumerate all object methods
    var member, newMethod;
    for (var name in obj) {
        // Check name
        isExcludedName = false;
        for (var i = 0; i < excludeNamesLength; ++i) {
            if (excludeNames[i] == name) {
                isExcludedName = true;
                break;
            }
        }
        if (isExcludedName) continue;
        
        member = obj[name];
        if (typeof(member) == "function") {
            // Replace each method with profiled call
            newMethod = new Function("return arguments.callee.__prof._invokeOldMethod(arguments.callee, this, arguments);");
            obj[name] = newMethod;
            
            // Set properties for new method (they'll be used in _invokeOldMethod)
            newMethod.__prof         = this;
            newMethod.__prof_old     = member;
            newMethod.__prof_name    = name;
            newMethod.__prof_objName = objName;
        } else if (member != null && member.constructor != null && typeof(member) == "object") {
            this._startProfilingInternal(name, member);
        }
    }
};

// -----------------------------------------------------------------------------
// Internal: calls given function over given object
ObjectProfiler.prototype._invokeOldMethod = function(newMethod, thisObj, args)
{
    var profiler = newMethod.__prof;
    var callContext = new this._CallContext(profiler._context, newMethod.__prof_name, newMethod.__prof_objName);
    var parentCalls = (callContext.parent != null ? callContext.parent : profiler).calls;
    
    parentCalls.push(callContext);
    profiler._context = callContext;
    
    var func = newMethod.__prof_old;
    var startTime = new Date();
    var res = func.apply(thisObj, args);
    callContext.callMs = new Date() - startTime;
    
    profiler._context = callContext.parent;
    if (callContext.parent == null && profiler.oncallfinished != null) {
        profiler.oncallfinished(callContext);
    }
    
    return res;
};

// -----------------------------------------------------------------------------
// Internal: creates new CallContext object
ObjectProfiler.prototype._CallContext = function(parent, name, objName)
{
    this.parent  = parent;
    this.name    = name;
    this.objName = objName;
    this.callMs  = 0;
    this.calls   = [];
};

ProfilerTextareaRenderer.js:
// -----------------------------------------------------------------------------
// Creates new ProfilerTextareaRenderer object.
// Renders ObjectProfiler output into <textarea>
function ProfilerTextareaRenderer()
{
    // Trick to save this
    var _this = this;
    
    this.minMs = 10;  // minimal work time ms value with which method call result is rendered
    
    this.textareaElement = null;  // <textarea> element
    
    // Handles zero-level call finished event
    this._profilerCallfinishedHandler = function(callContext)
    {
        if (callContext.callMs >= _this.minMs) {
            // Write log head
            _this.writeLine();
            _this.writeLine("************************************************************");
            
            // Start writing call context
            _this._writeCallContext(callContext, 0);
        }
    };
}

// -----------------------------------------------------------------------------
// Initializes class from <textarea> control (by given control ID)
ProfilerTextareaRenderer.prototype.initFromControl = function(clientId)
{
    this.textareaElement = document.all[clientId];
    this.writeLine("Profiling started");
};

// -----------------------------------------------------------------------------
// Registers renderer instance on profiler
ProfilerTextareaRenderer.prototype.registerProfiler = function(profiler)
{
    profiler.oncallfinished = this._profilerCallfinishedHandler;
};

// -----------------------------------------------------------------------------
// Writes to <textarea>
ProfilerTextareaRenderer.prototype.write = function(text)
{
    this.textareaElement.value += text;
};

// -----------------------------------------------------------------------------
// Writes line to <textarea>
ProfilerTextareaRenderer.prototype.writeLine = function(text)
{
    if (text != null) {
        this.write(text);
    }
    this.write("\n");
};


// -----------------------------------------------------------------------------
// Internal: writes call context <textarea>
ProfilerTextareaRenderer.prototype._writeCallContext = function(callContext, nestingLevel)
{
    if (callContext.callMs >= this.minMs) {
        // Write white space
        for (var i = 0; i < nestingLevel; ++i) {
            this.write("    ");
        }
        
        // Write results of this call
        if (callContext.objName != "this") {
            this.write(callContext.objName + ".");
        }
        this.writeLine(callContext.name + "() - " + callContext.callMs + " ms");
        
        // Write results of nesting calls
        ++nestingLevel;
        var calls       = callContext.calls;
        var callsLength = calls.length;
        for (var i = 0; i < callsLength; ++i) {
            this._writeCallContext(calls[i], nestingLevel);
        }
    }
};
http://rsdn.org/File/27948/bf.gif
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.