我尝试通过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
答案 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
....
干杯,
约翰