QScriptEngineAgent - 获取被调用者名称

时间:2014-11-22 07:50:34

标签: javascript c++ qt profiling qtscript

我尝试通过QScriptEngineAgent实现一个简单的 QtScript 性能分析器,捕获函数入口和出口。我成功订阅了QScriptEngineAgent::functionEntry()回调。现在,是否有可能在此回调中获取正在调用的函数的名称(作为字符串)?

即使我知道并非所有脚本函数都需要有一个名称,即使在最简单的情况下它似乎也不起作用。 QScriptContextInfo为此提供了便利,但似乎失败了。然后我试图获取arguments.callee.name属性的值,但它也失败了。

以下是我尝试实施该功能的粗略概述,我试图在qt-5.3.2 / linux上运行。

tmp.pro

TEMPLATE = app
TARGET = tmp
INCLUDEPATH += .
QT += core script
SOURCES += main.cpp

main.cpp

#include <QCoreApplication>
#include <QScriptEngine>
#include <QScriptEngineAgent>
#include <QScriptContextInfo>
#include <QDebug>

class MyAgent: public QScriptEngineAgent {
private:
    void functionEntry(qint64 scriptId) {
        qDebug() << "functionEntry" << scriptId;
        QScriptContext *context = engine()->currentContext();

        // QScriptContextInfo should have function name, by design, afaik
        QScriptContextInfo contextInfo(context);
        qDebug() << contextInfo.functionName();

        // probably my typical js-side arguments.callee.name would work?
        QScriptValue callee = context->callee();
        qDebug() << callee.property("name").toString();

        // function.toString() should have at least something (?)
        qDebug() << callee.toString();

        // hmm. what's our context, anyway?
        qDebug() << context->toString();
    }
public:
    MyAgent(QScriptEngine *eng) : QScriptEngineAgent(eng) {
        qDebug() << "engine" << eng;
    }
};

int main(int argc, char **argv) {
    QCoreApplication app(argc, argv);
    QScriptEngine eng;
    MyAgent agent(&eng);
    eng.setAgent(&agent);
    qDebug() << "agent " << eng.agent();
    eng.evaluate("function foo() { return 6 * 7; }"
                 "function bar() { return foo(); }");
    QScriptValue bar = eng.globalObject().property("bar");

    // See? Here the callee is printed as expected.
    qDebug() << "call " << bar.property("name").toString() << bar.toString();
    QScriptValue ret = bar.call();
    qDebug() << "answer" << ret.toNumber();
    return 0;
}

输出样本,我不满意,因为我期待看到&#34; foo&#34;和&#34; bar&#34;而不是一些空字符串:

engine QScriptEngine(0x7fffc55c4560)
agent  0x7fffc55c4570
functionEntry 140300485581200
""
""
""
"<global>() at -1"
functionEntry -1
""
""
""
"<global>() at -1"
call  "bar" "function bar() { return foo(); }"
functionEntry 140300485581200
""
""
""
"<global>() at -1"
functionEntry 140300485581200
""
""
""
"<global>() at -1"
answer 42

1 个答案:

答案 0 :(得分:0)

似乎正确的上下文仅在positionChange中可用。幸运的是,使用scriptID&gt;调用functionEntry。每当发生真正的上下文变化时为0。在下一个位置抓住上下文改变对我有用:

class ScriptProfilerAgent: public QScriptEngineAgent
{
public:
    ScriptProfilerAgent(QScriptEngine *eng)
        : QScriptEngineAgent(eng), m_stackChange(false)
    {
    }

protected:
    void scriptLoad(qint64 id, const QString &program, const QString &fileName, int baseLineNumber)
    {
        Q_UNUSED(id);Q_UNUSED(program);Q_UNUSED(fileName);Q_UNUSED(baseLineNumber);
        m_timer.start();
    }

    void functionEntry(qint64 scriptId)
    {
        if (scriptId > -1) {
            // arm the stack-tracker => positionChange
            m_stackChange = true;
        }
    }

    void positionChange(qint64 scriptId, int lineNumber, int columnNumber)
    {
        Q_UNUSED(scriptId);Q_UNUSED(lineNumber);Q_UNUSED(columnNumber);
        if (m_stackChange)
        {
            QString fn = functionNameEx();
            m_curCallStack.push(qMakePair(fn,m_timer.elapsed()));
            // reset stack tracking
            m_stackChange = false;
        }
    }

    QString functionNameEx()
    {
        QScriptContext* sc = engine()->currentContext();
        QScriptContextInfo info(sc);
        QString functionName = info.functionName();
        if (functionName.isEmpty()) {
            if (sc->parentContext()) {
                switch (info.functionType()) {
                    case QScriptContextInfo::ScriptFunction : return QLatin1String("anonymous");
                    case QScriptContextInfo::QtFunction : return QLatin1String("qtfunction");
                    case QScriptContextInfo::QtPropertyFunction : return QLatin1String("qtproperty");
                    case QScriptContextInfo::NativeFunction : return QLatin1String("native");
                }
            } else {
                return QLatin1String("global");
            }
        } else {
            return functionName;
        }
        return "??";
    }

    void functionExit(qint64 scriptId, const QScriptValue &returnValue)
    {
        Q_UNUSED(returnValue);
        if (scriptId > -1)
        {
            if (!m_curCallStack.isEmpty())
            {
                // store current fn information
                QPair<QString,qint64> f = m_curCallStack.pop();
                qint64 execTime = m_timer.elapsed()-f.second;
                {
                    FunctionStats& stats = m_callStatsByName[f.first];
                    stats.calls++;
                    stats.execTimeSum += execTime;
                }
                {
                    // build full callstack-path
                    QStringList path;path.reserve(m_curCallStack.size());
                    for (int i=0;i<m_curCallStack.size();++i)
                    {
                        path.append(m_curCallStack.at(i).first);
                    }
                    path.append(f.first);
                    FunctionStats& stats = m_callStatsByPath[path.join(">")];
                    stats.calls++;
                    stats.execTimeSum += execTime;
                }
            } else {
                qDebug() << "Something is very wrong with the call stack!";
            }
        }
    }

public:
    QString toString() const
    {
        QStringList result;
        result.append("Function\tTotal Time (ms)\tCalls");
        {
            // function name
            QStringList fnsByExecTimeSum = m_callStatsByName.keys();
            // sorted by execTimeSum
            std::sort(fnsByExecTimeSum.begin(),fnsByExecTimeSum.end(),
                      [this](const QString& a,const QString& b) -> bool {return (m_callStatsByName[a].execTimeSum) > (m_callStatsByName[b].execTimeSum);}
                    );

            for (QString fn : fnsByExecTimeSum)
            {
                result.append(QString("%1\t%2ms\t%3").arg(fn).arg(m_callStatsByName[fn].execTimeSum).arg(m_callStatsByName[fn].calls));
            }
        }
        result.append("");
        result.append("FN-Path\tTotal Time (ms)\tCalls");
        {
            // function path = call stack
            QStringList fns = m_callStatsByPath.keys();
            // sorted by key
            fns.sort();
            for (QString fn : fns)
            {
                result.append(QString("%1\t%2ms\t%3").arg(fn).arg(m_callStatsByPath[fn].execTimeSum).arg(m_callStatsByPath[fn].calls));
            }
        }
        return result.join("\n");
    }

    void reset()
    {
        m_callStatsByPath.clear();
        m_callStatsByName.clear();
    }
private:
    QStack<QPair<QString,qint64> > m_curCallStack;

    bool m_stackChange;

    struct FunctionStats
    {
        FunctionStats() : calls(0),execTimeSum(0) {}
        int calls;
        qint64 execTimeSum; // ms
    };

    QElapsedTimer m_timer;

    QHash<QString, FunctionStats> m_callStatsByPath;
    QHash<QString, FunctionStats> m_callStatsByName;
};

要使用代理,只需使用engine.setAgent(new ScriptProfilerAgent());将其设置在引擎上即可。执行后,我的穷人的探查器可以给你一个函数/堆栈路径名列表,累计执行时间和调用次数:

QString ScriptEngine::getProfilingResults()
{
    ScriptProfilerAgent* spa = dynamic_cast<ScriptProfilerAgent*>(agent());
    if (spa)
    {
        QString result = spa->toString();
        spa->reset();
        return result;
    }
    return "<no profiler agent>";
}

FN-Path Total Time (ms) Calls
global  235969ms    7
global>anonymous    0ms 38
global>run  235969ms    1
global>run>doConcept    206444ms    21603
global>run>doConcept>genData    200765ms    21603
global>run>doConcept>genData>genArticleData 198861ms    21603
global>run>doConcept>genData>genArticleData>buildInnerHTMLs 163558ms    21603
global>run>doConcept>genData>genArticleData>buildInnerHTMLs>anonymous   87582ms 1823593
....

干杯,

约翰