1// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4
5#include "mainwindow.h"
6#include "client.h"
7#include "jsontabwidget.h"
8#include "perfflamegraphscripts.h"
9#include "perfrecorddisplay.h"
10#include "jsontabwidget.h"
11#include "common/common.h"
12
13#include <QTime>
14#include <QMenu>
15#include <QMenuBar>
16#include <QToolBar>
17#include <QDockWidget>
18#include <QToolButton>
19#include <QLineEdit>
20#include <QRegExpValidator>
21
22class MainWindowPrivate
23{
24 friend class MainWindow;
25 JsonTabWidget *jsonTabWidget{nullptr};
26 QToolBar *toolbar{nullptr};
27 QLineEdit *editPid{nullptr};
28 QToolButton *ctrlButton{nullptr};
29 QDockWidget *perfRecordDock{nullptr};
30 PerfRecordDisplay *perfRecordDisplay{nullptr};
31 FlameGraphGenTask *flameGraphGenTask{nullptr};
32
33 Client *client{nullptr};
34 QThread *cliThread{nullptr};
35 QProcess *server{nullptr};
36 QTimer *timer{nullptr};
37 QString attach{QMenu::tr("Attach")};
38 QString strat{QToolBar::tr("Start")};
39 QString stop{QToolBar::tr("Stop")};
40 QString fromPid{QAction::tr("Pid")};
41 QString fromProgram{QAction::tr("Program")};
42 std::string host{"127.0.0.1"};
43 int port{3309};
44};
45
46MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags)
47 : QMainWindow(parent, flags)
48 , d (new MainWindowPrivate)
49{
50 d->toolbar = new QToolBar;
51 d->editPid = new QLineEdit();
52 d->editPid->setPlaceholderText("PID");
53 QRegularExpression regx("[0-9]+$");
54 QValidator *validator = new QRegularExpressionValidator(regx, d->editPid);
55 d->editPid->setValidator(validator);
56 d->ctrlButton = new QToolButton;
57 d->perfRecordDock = new QDockWidget;
58 d->perfRecordDisplay = new PerfRecordDisplay;
59 d->jsonTabWidget = new JsonTabWidget;
60
61 d->ctrlButton->setChecked(true);
62 d->ctrlButton->setCheckable(true);
63 d->ctrlButton->setText(d->strat);
64 QObject::connect(d->ctrlButton, &QToolButton::toggled, [=](bool checked){
65 if (checked) {
66 QString pid = d->editPid->text();
67 if (pid.isEmpty()) {
68 ContextDialog::okCancel(QMessageBox::tr("attach processId can't empty!"));
69 return;
70 }
71
72 QByteArray queryPidLines;
73 ProcessUtil::execute("ps", {"--pid", pid},
74 [&queryPidLines](const QByteArray &data) {
75 queryPidLines = data;
76 });
77 if (queryPidLines.isEmpty() || queryPidLines.count('\n') < 2) {
78 ContextDialog::okCancel(QMessageBox::tr("attach processId no exites!"));
79 return;
80 }
81
82 d->ctrlButton->setText(d->stop);
83 start(d->editPid->text().toUInt());
84 } else {
85 d->ctrlButton->setText(d->strat);
86 stop();
87 }
88 });
89
90 d->toolbar->addSeparator();
91 d->toolbar->addWidget(d->editPid);
92 d->toolbar->addWidget(d->ctrlButton);
93 addToolBar(d->toolbar);
94
95 d->perfRecordDock->setWidget(d->perfRecordDisplay);
96 // TODO(Any): disabled until Use hotpot(https://github.com/KDAB/hotspot) to refactor
97 // addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, d->perfRecordDock);
98
99 setCentralWidget(d->jsonTabWidget);
100}
101
102MainWindow::~MainWindow()
103{
104 stop();
105
106 if (d) {
107 delete d;
108 }
109}
110
111void MainWindow::start(uint pid)
112{
113 if (!d->server) {
114 QString toolsPath = CustomPaths::global(CustomPaths::Tools);
115 QString adapter = toolsPath + QDir::separator() + "performanceadapter";
116 d->server = new QProcess();
117
118 QObject::connect(d->server, &QProcess::errorOccurred, [=](QProcess::ProcessError error){
119 qCritical() << "server >> " << error << d->server->errorString();
120 });
121 QObject::connect(d->server, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
122 [](int exit, QProcess::ExitStatus status) {
123 qCritical() << "server >> "
124 << "exit: " << exit
125 << "status: " << status;
126 });
127#ifdef QT_DEBUG
128 QObject::connect(d->server, &QProcess::readyReadStandardError, [=](){
129 qInfo() << "server stderr >> " << d->server->readAllStandardError();
130 });
131 QObject::connect(d->server, &QProcess::readyReadStandardOutput, [=](){
132 qInfo() << "server stdout >> " << d->server->readAllStandardOutput();
133 });
134#endif
135 qInfo() << adapter << "--port" << d->port;
136 d->server->start(adapter, {"--port", QString::number(d->port)});
137 d->server->waitForReadyRead();
138 }
139
140 if (!d->client) {
141 d->client = new Client(d->host, d->port);
142 auto cliThread = new QThread();
143 d->client->moveToThread(cliThread);
144 QObject::connect(d->client, &Client::pullDataResult,
145 d->jsonTabWidget, &JsonTabWidget::parseJson,
146 Qt::UniqueConnection);
147 cliThread->start();
148 }
149
150 QObject::metaObject()->invokeMethod(d->client, "initialzation", Q_ARG(int, pid));
151
152 d->timer = new QTimer(this);
153 QObject::connect(d->timer, &QTimer::timeout, [=](){
154 if (d->client) {
155 QObject::metaObject()->invokeMethod(d->client, "pullData");
156 } else {
157 d->timer->stop();
158 }
159 });
160 d->timer->start(1000);
161
162
163 if (!d->flameGraphGenTask) {
164 d->flameGraphGenTask = new FlameGraphGenTask;
165 QObject::connect(d->perfRecordDisplay, &PerfRecordDisplay::showWebBrowserGP,
166 d->flameGraphGenTask, &FlameGraphGenTask::showWebBrowser,
167 Qt::UniqueConnection);
168
169 }
170 d->flameGraphGenTask->start(pid);
171}
172
173void MainWindow::stop()
174{
175 if (d->flameGraphGenTask) {
176 d->flameGraphGenTask->stop();
177 }
178
179 if (d->timer) {
180 d->timer->stop();
181 delete d->timer;
182 d->timer = nullptr;
183 }
184
185 if (d->cliThread) {
186 d->cliThread->exit(0);
187 delete d->cliThread;
188 d->cliThread = nullptr;
189 }
190
191 if (d->client) {
192 d->client->shutdown();
193 d->client->exit();
194 delete d->client;
195 d->client = nullptr;
196 }
197
198 if (d->server) {
199 d->server->kill();
200 delete d->server;
201 d->server = nullptr;
202 }
203}
204