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 | |
14 | namespace { |
15 | |
16 | QString 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 | |
26 | QString 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 | |
47 | PerfRecord::PerfRecord(const QString &perfRecordOutFile) |
48 | : pid (0) |
49 | , ouFile (perfRecordOutFile) |
50 | { |
51 | setProgram("perf" ); |
52 | } |
53 | |
54 | PerfScript::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 | |
64 | StackCollapse::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 | |
75 | FlameGraph::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 | |
87 | class 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 | |
101 | FlameGraphGenTask::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 | |
245 | FlameGraphGenTask::~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 | |
280 | void FlameGraphGenTask::showWebBrowser(bool flag) |
281 | { |
282 | d->showWebBrowserFlag = flag; |
283 | } |
284 | |
285 | void 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 | |
294 | void FlameGraphGenTask::stop() |
295 | { |
296 | if (d->perfRecord && d->perfRecord->isReadable()) { |
297 | d->perfRecord->terminate(); |
298 | d->perfRecord->waitForFinished(); |
299 | } |
300 | } |
301 | |
302 | void PerfRecord::setAttachPid(uint pid) |
303 | { |
304 | this->pid = pid; |
305 | setArguments({"record" , "-g" , "-e" , "cpu-clock" , "-p" , QString::number(pid), "-o" , ouFile}); |
306 | } |
307 | |