1// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4
5#include "perfflamegraphscripts.h"
6#include "config.h"
7
8#include <QCoreApplication>
9#include <QStandardPaths>
10#include <QDir>
11#include <QQueue>
12#include <QDesktopServices>
13
14namespace {
15
16QString flameGraphPath()
17{
18 static QString flameGraph{"FlameGraph"};
19 if (CustomPaths::installed())
20 return CustomPaths::global(CustomPaths::Scripts) + QDir::separator() + flameGraph;
21 else {
22 return QString(CMAKE_SOURCE_DIR) + QDir::separator() + "3rdparty" + QDir::separator() + flameGraph;
23 }
24}
25
26QString cachePath()
27{
28 QDir dir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
29 QString appName = QCoreApplication::applicationName();
30
31 if (!dir.exists()) {
32 dir.cdUp();
33 dir.mkdir(appName);
34 dir.cd(appName);
35 }
36
37 if (!dir.cd(PROJECT_NAME)) {
38 dir.mkdir(PROJECT_NAME);
39 dir.cd(PROJECT_NAME);
40 }
41
42 return dir.path();
43}
44
45};
46
47PerfRecord::PerfRecord(const QString &perfRecordOutFile)
48 : pid (0)
49 , ouFile (perfRecordOutFile)
50{
51 setProgram("perf");
52}
53
54PerfScript::PerfScript(const QString &perfRecordOutFileIn, const QString &outFile)
55{
56 setProgram("perf");
57 setArguments({"script", "-i", perfRecordOutFileIn});
58 setStandardOutputFile(outFile);
59 QObject::connect(this, &QProcess::errorOccurred, [=](QProcess::ProcessError error){
60 qCritical() << error << errorString();
61 });
62}
63
64StackCollapse::StackCollapse(const QString &perfScriptOutFile, const QString &outFile)
65{
66 setWorkingDirectory(flameGraphPath());
67 setProgram("perl");
68 setArguments({"./stackcollapse-perf.pl", perfScriptOutFile});
69 setStandardOutputFile(outFile);
70 QObject::connect(this, &QProcess::errorOccurred, [=](QProcess::ProcessError error){
71 qCritical() << error << errorString();
72 });
73}
74
75FlameGraph::FlameGraph(const QString &stackCollapseOutFile, const QString &outFile)
76{
77 setWorkingDirectory(flameGraphPath());
78 setProgram("perl");
79 setArguments({"./flamegraph.pl", stackCollapseOutFile});
80 setStandardOutputFile(outFile);
81 QObject::connect(this, &QProcess::errorOccurred, [=](QProcess::ProcessError error){
82 qCritical() << error << errorString();
83 });
84}
85
86
87class FlameGraphGenTaskPrivate
88{
89 friend class FlameGraphGenTask;
90 PerfRecord *perfRecord{nullptr};
91 PerfScript *perfScript{nullptr};
92 StackCollapse *stackCollapse{nullptr};
93 FlameGraph *flameGraph{nullptr};
94 QString perfRecordOutFile{cachePath() + QDir::separator() + "perfRecordOut.data"};
95 QString perfScriptOutFile{cachePath() + QDir::separator() + "perfScriptOut.data"};
96 QString stackCollapseOutFile{cachePath() + QDir::separator() + "stackCollapseOut.data"};
97 QString flameGraphOutFile{cachePath() + QDir::separator() + "flameGraphOut.svg"};
98 bool showWebBrowserFlag{false};
99};
100
101FlameGraphGenTask::FlameGraphGenTask(QObject *parent)
102 : QObject (parent)
103 , d (new FlameGraphGenTaskPrivate)
104{
105 d->perfRecord = new PerfRecord(d->perfRecordOutFile);
106 d->perfScript = new PerfScript(d->perfRecordOutFile, d->perfScriptOutFile);
107 d->stackCollapse = new StackCollapse(d->perfScriptOutFile, d->stackCollapseOutFile);
108 d->flameGraph = new FlameGraph(d->stackCollapseOutFile, d->flameGraphOutFile);
109
110 // perf recode exit
111 QObject::connect(d->perfRecord, &QProcess::errorOccurred,
112 this, [=](QProcess::ProcessError pError)
113 {
114 QString errorOut = d->perfRecord->readAllStandardError();
115 if (pError == QProcess::ProcessError::Crashed
116 && errorOut.contains("[ perf record: Woken up")
117 && errorOut.contains("times to write data ]\n[ perf record: Captured and wrote")
118 && errorOut.contains("samples) ]\n")) {
119 qInfo() << "start perfScript with crashed perfRecord";
120 d->perfScript->start();
121 } else {
122 emit error(d->perfScript->program()
123 + " " + d->perfScript->arguments().join(" ")
124 + ": " + d->perfScript->errorString());
125 }
126 });
127
128 QObject::connect(d->perfRecord, &QProcess::readAllStandardOutput,
129 this, [=](){
130 qCritical() << "perfRecord output: \n"
131 << d->perfRecord->readAllStandardOutput();
132 });
133 QObject::connect(d->perfRecord, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
134 this, [=](int exitCode, QProcess::ExitStatus status)
135 {
136 qInfo() << "perfRecord exit:" << exitCode << status;
137 if (exitCode == 0 && d->perfScript) {
138 qInfo() << "start perfScript";
139 d->perfScript->start();
140 } else {
141 qCritical() << "exit not's 0, this unknow error from perfRecord"
142 << d->perfScript->errorString();
143 emit error(d->perfScript->program()
144 + " " + d->perfScript->arguments().join(" ")
145 + ": " + d->perfScript->errorString());
146 }
147 });
148
149 QObject::connect(d->perfScript, &QProcess::readAllStandardOutput,
150 this, [=](){
151 qCritical() << "perfScript output: \n"
152 << d->perfScript->readAllStandardOutput();
153 });
154 QObject::connect(d->perfScript, &QProcess::readyReadStandardError,
155 this, [=]()
156 {
157 qCritical() << "perfScript error output: \n"
158 << d->perfScript->readAllStandardError();
159 });
160 // perf script exit
161 QObject::connect(d->perfScript, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
162 this, [=](int exitCode, QProcess::ExitStatus status)
163 {
164 qInfo() << "perfScript exit:" << exitCode << status;
165 if (exitCode == 0 && d->stackCollapse) {
166 qInfo() << "start stackCollapse script";
167 d->stackCollapse->start();
168 } else {
169 qCritical() << "exit not's 0, this unknow error from perfScript"
170 << d->perfScript->errorString();
171 emit error(d->perfScript->program()
172 + " " + d->perfScript->arguments().join(" ")
173 + ": " + d->perfScript->errorString());
174 }
175 });
176
177 QObject::connect(d->stackCollapse, &QProcess::readAllStandardOutput,
178 this, [=](){
179 qCritical() << "stackCollapse output: \n"
180 << d->stackCollapse->readAllStandardOutput()
181 << d->stackCollapse->workingDirectory()
182 << d->stackCollapse->program()
183 << d->stackCollapse->arguments();
184 });
185 QObject::connect(d->stackCollapse, &QProcess::readyReadStandardError,
186 this, [=]()
187 {
188 qCritical() << "stackCollapse error output: \n"
189 << d->stackCollapse->readAllStandardError()
190 << d->stackCollapse->workingDirectory()
191 << d->stackCollapse->program()
192 << d->stackCollapse->arguments();
193 });
194 // stackCollapse exit
195 QObject::connect(d->stackCollapse, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
196 this, [=](int exitCode, QProcess::ExitStatus status)
197 {
198 qInfo() << "stackCollapse exit:" << exitCode << status;
199 if (exitCode == 0 && d->flameGraph) {
200 qCritical() << "start flameGraph script";
201 d->flameGraph->start();
202 } else {
203 qCritical() << "exit not's 0, this unknow error from stackCollapse"
204 << d->flameGraph->errorString();
205 emit error(d->stackCollapse->program()
206 + " " + d->stackCollapse->arguments().join(" ")
207 + ": " + d->stackCollapse->errorString());
208 }
209 });
210
211 QObject::connect(d->flameGraph, &QProcess::readAllStandardOutput,
212 this, [=](){
213 qCritical() << "flameGraph output: \n"
214 << d->flameGraph->readAllStandardOutput();
215 });
216 QObject::connect(d->flameGraph, &QProcess::readyReadStandardError,
217 this, [=]()
218 {
219 qCritical() << "flameGraph error output: \n"
220 << d->flameGraph->readAllStandardError();
221 });
222 // flameGraph exit
223 QObject::connect(d->flameGraph, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
224 this, [=](int exitCode, QProcess::ExitStatus status)
225 {
226 Q_UNUSED(status)
227 qInfo() << "stackCollapse exit:" << exitCode << status;
228 if (exitCode == 0) {
229 if (d->showWebBrowserFlag) {
230 qCritical() << "show with gnome-www-browser";
231 QString defaultWebCmd = "gnome-www-browser";
232 QProcess::startDetached(defaultWebCmd, {d->flameGraphOutFile});
233 emit this->showed(d->flameGraphOutFile);
234 }
235 } else {
236 qCritical() << "exit not's 0, this unknow error from flameGraph"
237 << d->flameGraph->errorString();
238 emit error(d->flameGraph->program()
239 + " " + d->flameGraph->arguments().join(" ")
240 + ": " + d->flameGraph->errorString());
241 }
242 });
243}
244
245FlameGraphGenTask::~FlameGraphGenTask()
246{
247 if (d->flameGraph) {
248 if (d->flameGraph->isReadable()) {
249 d->flameGraph->kill();
250 d->flameGraph->waitForFinished();
251 }
252 delete d->flameGraph;
253 }
254
255 if (d->stackCollapse) {
256 if (d->stackCollapse->isReadable()) {
257 d->stackCollapse->kill();
258 d->stackCollapse->waitForFinished();
259 }
260 delete d->stackCollapse;
261 }
262
263 if (d->perfScript) {
264 if (d->perfScript->isReadable()) {
265 d->perfScript->kill();
266 d->perfScript->waitForFinished();
267 }
268 delete d->perfScript;
269 }
270
271 if (d->perfRecord) {
272 if (d->perfRecord->isReadable()) {
273 d->perfRecord->kill();
274 d->perfRecord->waitForFinished();
275 }
276 delete d->perfRecord;
277 }
278}
279
280void FlameGraphGenTask::showWebBrowser(bool flag)
281{
282 d->showWebBrowserFlag = flag;
283}
284
285void FlameGraphGenTask::start(uint pid)
286{
287 if (d->perfRecord) {
288 d->perfRecord->setAttachPid(pid);
289 d->perfRecord->start();
290 qInfo() << d->perfRecord->program() << d->perfRecord->arguments();
291 }
292}
293
294void FlameGraphGenTask::stop()
295{
296 if (d->perfRecord && d->perfRecord->isReadable()) {
297 d->perfRecord->terminate();
298 d->perfRecord->waitForFinished();
299 }
300}
301
302void PerfRecord::setAttachPid(uint pid)
303{
304 this->pid = pid;
305 setArguments({"record", "-g", "-e", "cpu-clock", "-p", QString::number(pid), "-o", ouFile});
306}
307