| 1 | /**************************************************************************** | 
| 2 | ** | 
| 3 | ** Copyright (C) 2016 The Qt Company Ltd. | 
| 4 | ** Contact: https://www.qt.io/licensing/ | 
| 5 | ** | 
| 6 | ** This file is part of the QtQml module of the Qt Toolkit. | 
| 7 | ** | 
| 8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ | 
| 9 | ** Commercial License Usage | 
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in | 
| 11 | ** accordance with the commercial license agreement provided with the | 
| 12 | ** Software or, alternatively, in accordance with the terms contained in | 
| 13 | ** a written agreement between you and The Qt Company. For licensing terms | 
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further | 
| 15 | ** information use the contact form at https://www.qt.io/contact-us. | 
| 16 | ** | 
| 17 | ** GNU General Public License Usage | 
| 18 | ** Alternatively, this file may be used under the terms of the GNU | 
| 19 | ** General Public License version 3 as published by the Free Software | 
| 20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT | 
| 21 | ** included in the packaging of this file. Please review the following | 
| 22 | ** information to ensure the GNU General Public License requirements will | 
| 23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. | 
| 24 | ** | 
| 25 | ** $QT_END_LICENSE$ | 
| 26 | ** | 
| 27 | ****************************************************************************/ | 
| 28 |  | 
| 29 | #include "qmlprofilerapplication.h" | 
| 30 | #include "constants.h" | 
| 31 | #include <QtCore/QStringList> | 
| 32 | #include <QtCore/QProcess> | 
| 33 | #include <QtCore/QTimer> | 
| 34 | #include <QtCore/QDateTime> | 
| 35 | #include <QtCore/QFileInfo> | 
| 36 | #include <QtCore/QDebug> | 
| 37 | #include <QtCore/QCommandLineParser> | 
| 38 | #include <QtCore/QTemporaryFile> | 
| 39 |  | 
| 40 | #include <iostream> | 
| 41 |  | 
| 42 | static const char commandTextC[] = | 
| 43 |         "The following commands are available:\n"  | 
| 44 |         "'r', 'record'\n"  | 
| 45 |         "    Switch recording on or off.\n"  | 
| 46 |         "'o [file]', 'output [file]'\n"  | 
| 47 |         "    Output profiling data to <file>. If no <file>\n"  | 
| 48 |         "    parameter is given, output to whatever was given\n"  | 
| 49 |         "    with --output, or standard output.\n"  | 
| 50 |         "'c', 'clear'\n"  | 
| 51 |         "    Clear profiling data recorded so far from memory.\n"  | 
| 52 |         "'f [file]', 'flush [file]'\n"  | 
| 53 |         "    Stop recording if it is running, then output the\n"  | 
| 54 |         "    data, and finally clear it from memory.\n"  | 
| 55 |         "'q', 'quit'\n"  | 
| 56 |         "    Terminate the target process if started from\n"  | 
| 57 |         "    qmlprofiler, and qmlprofiler itself." ; | 
| 58 |  | 
| 59 | static const char *features[] = { | 
| 60 |     "javascript" , | 
| 61 |     "memory" , | 
| 62 |     "pixmapcache" , | 
| 63 |     "scenegraph" , | 
| 64 |     "animations" , | 
| 65 |     "painting" , | 
| 66 |     "compiling" , | 
| 67 |     "creating" , | 
| 68 |     "binding" , | 
| 69 |     "handlingsignal" , | 
| 70 |     "inputevents" , | 
| 71 |     "debugmessages"  | 
| 72 | }; | 
| 73 |  | 
| 74 | Q_STATIC_ASSERT(sizeof(features) == MaximumProfileFeature * sizeof(char *)); | 
| 75 |  | 
| 76 | QmlProfilerApplication::QmlProfilerApplication(int &argc, char **argv) : | 
| 77 |     QCoreApplication(argc, argv), | 
| 78 |     m_runMode(LaunchMode), | 
| 79 |     m_process(nullptr), | 
| 80 |     m_hostName(QLatin1String("127.0.0.1" )), | 
| 81 |     m_port(0), | 
| 82 |     m_pendingRequest(REQUEST_NONE), | 
| 83 |     m_verbose(false), | 
| 84 |     m_recording(true), | 
| 85 |     m_interactive(false), | 
| 86 |     m_connectionAttempts(0) | 
| 87 | { | 
| 88 |     m_connection.reset(other: new QQmlDebugConnection); | 
| 89 |     m_profilerData.reset(other: new QmlProfilerData); | 
| 90 |     m_qmlProfilerClient.reset(other: new QmlProfilerClient(m_connection.data(), m_profilerData.data())); | 
| 91 |     m_connectTimer.setInterval(1000); | 
| 92 |     connect(sender: &m_connectTimer, signal: &QTimer::timeout, receiver: this, slot: &QmlProfilerApplication::tryToConnect); | 
| 93 |  | 
| 94 |     connect(sender: m_connection.data(), signal: &QQmlDebugConnection::connected, | 
| 95 |             receiver: this, slot: &QmlProfilerApplication::connected); | 
| 96 |     connect(sender: m_connection.data(), signal: &QQmlDebugConnection::disconnected, | 
| 97 |             receiver: this, slot: &QmlProfilerApplication::disconnected); | 
| 98 |  | 
| 99 |     connect(sender: m_qmlProfilerClient.data(), signal: &QmlProfilerClient::enabledChanged, | 
| 100 |             receiver: this, slot: &QmlProfilerApplication::traceClientEnabledChanged); | 
| 101 |     connect(sender: m_qmlProfilerClient.data(), signal: &QmlProfilerClient::traceStarted, | 
| 102 |             receiver: this, slot: &QmlProfilerApplication::notifyTraceStarted); | 
| 103 |     connect(sender: m_qmlProfilerClient.data(), signal: &QmlProfilerClient::error, | 
| 104 |             receiver: this, slot: &QmlProfilerApplication::logError); | 
| 105 |  | 
| 106 |     connect(sender: m_profilerData.data(), signal: &QmlProfilerData::error, | 
| 107 |             receiver: this, slot: &QmlProfilerApplication::logError); | 
| 108 |     connect(sender: m_profilerData.data(), signal: &QmlProfilerData::dataReady, | 
| 109 |             receiver: this, slot: &QmlProfilerApplication::traceFinished); | 
| 110 |  | 
| 111 | } | 
| 112 |  | 
| 113 | QmlProfilerApplication::~QmlProfilerApplication() | 
| 114 | { | 
| 115 |     if (!m_process) | 
| 116 |         return; | 
| 117 |     logStatus(status: "Terminating process ..." ); | 
| 118 |     m_process->disconnect(); | 
| 119 |     m_process->terminate(); | 
| 120 |     if (!m_process->waitForFinished(msecs: 1000)) { | 
| 121 |         logStatus(status: "Killing process ..." ); | 
| 122 |         m_process->kill(); | 
| 123 |     } | 
| 124 |     if (isInteractive()) | 
| 125 |         std::cerr << std::endl; | 
| 126 |     delete m_process; | 
| 127 | } | 
| 128 |  | 
| 129 | void QmlProfilerApplication::parseArguments() | 
| 130 | { | 
| 131 |     setApplicationName(QLatin1String("qmlprofiler" )); | 
| 132 |     setApplicationVersion(QLatin1String(qVersion())); | 
| 133 |  | 
| 134 |     QCommandLineParser parser; | 
| 135 |     parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions); | 
| 136 |     parser.setOptionsAfterPositionalArgumentsMode(QCommandLineParser::ParseAsPositionalArguments); | 
| 137 |  | 
| 138 |     parser.setApplicationDescription(QChar::LineFeed + tr( | 
| 139 |         s: "The QML Profiler retrieves QML tracing data from an application. The data\n"  | 
| 140 |         "collected can then be visualized in Qt Creator. The application to be profiled\n"  | 
| 141 |         "has to enable QML debugging. See the Qt Creator documentation on how to do\n"  | 
| 142 |         "this for different Qt versions." )); | 
| 143 |  | 
| 144 |     QCommandLineOption attach(QStringList() << QLatin1String("a" ) << QLatin1String("attach" ), | 
| 145 |                               tr(s: "Attach to an application already running on <hostname>, "  | 
| 146 |                                  "instead of starting it locally." ), | 
| 147 |                               QLatin1String("hostname" )); | 
| 148 |     parser.addOption(commandLineOption: attach); | 
| 149 |  | 
| 150 |     QCommandLineOption port(QStringList() << QLatin1String("p" ) << QLatin1String("port" ), | 
| 151 |                             tr(s: "Connect to the TCP port <port>. The default is 3768." ), | 
| 152 |                             QLatin1String("port" ), QLatin1String("3768" )); | 
| 153 |     parser.addOption(commandLineOption: port); | 
| 154 |  | 
| 155 |     QCommandLineOption output(QStringList() << QLatin1String("o" ) << QLatin1String("output" ), | 
| 156 |                               tr(s: "Save tracing data in <file>. By default the data is sent to the "  | 
| 157 |                                  "standard output." ), QLatin1String("file" ), QString()); | 
| 158 |     parser.addOption(commandLineOption: output); | 
| 159 |  | 
| 160 |     QCommandLineOption record(QLatin1String("record" ), | 
| 161 |                               tr(s: "If set to 'off', don't immediately start recording data when the "  | 
| 162 |                                  "QML engine starts, but instead either start the recording "  | 
| 163 |                                  "interactively or with the JavaScript console.profile() function. "  | 
| 164 |                                  "By default the recording starts immediately." ), | 
| 165 |                               QLatin1String("on|off" ), QLatin1String("on" )); | 
| 166 |     parser.addOption(commandLineOption: record); | 
| 167 |  | 
| 168 |     QStringList featureList; | 
| 169 |     for (int i = 0; i < MaximumProfileFeature; ++i) | 
| 170 |         featureList << QLatin1String(features[i]); | 
| 171 |  | 
| 172 |     QCommandLineOption include(QLatin1String("include" ), | 
| 173 |                                tr(s: "Comma-separated list of features to record. By default all "  | 
| 174 |                                   "features supported by the QML engine are recorded. If --include "  | 
| 175 |                                   "is specified, only the given features will be recorded. "  | 
| 176 |                                   "The following features are unserstood by qmlprofiler: %1" ).arg( | 
| 177 |                                    a: featureList.join(sep: ", " )), | 
| 178 |                                QLatin1String("feature,..." )); | 
| 179 |     parser.addOption(commandLineOption: include); | 
| 180 |  | 
| 181 |     QCommandLineOption exclude(QLatin1String("exclude" ), | 
| 182 |                             tr(s: "Comma-separated list of features to exclude when recording. By "  | 
| 183 |                                "default all features supported by the QML engine are recorded. "  | 
| 184 |                                "See --include for the features understood by qmlprofiler." ), | 
| 185 |                             QLatin1String("feature,..." )); | 
| 186 |     parser.addOption(commandLineOption: exclude); | 
| 187 |  | 
| 188 |     QCommandLineOption interactive(QLatin1String("interactive" ), | 
| 189 |                                    tr(s: "Manually control the recording from the command line. The "  | 
| 190 |                                       "profiler will not terminate itself when the application "  | 
| 191 |                                       "does so in this case." ) + QChar::Space + tr(s: commandTextC)); | 
| 192 |     parser.addOption(commandLineOption: interactive); | 
| 193 |  | 
| 194 |     QCommandLineOption verbose(QStringList() << QLatin1String("verbose" ), | 
| 195 |                                tr(s: "Print debugging output." )); | 
| 196 |     parser.addOption(commandLineOption: verbose); | 
| 197 |  | 
| 198 |     parser.addHelpOption(); | 
| 199 |     parser.addVersionOption(); | 
| 200 |  | 
| 201 |     parser.addPositionalArgument(name: QLatin1String("executable" ), | 
| 202 |                                  description: tr(s: "The executable to be started and profiled." ), | 
| 203 |                                  syntax: QLatin1String("[executable]" )); | 
| 204 |     parser.addPositionalArgument(name: QLatin1String("parameters" ), | 
| 205 |                                  description: tr(s: "Parameters for the executable to be started." ), | 
| 206 |                                  syntax: QLatin1String("[parameters...]" )); | 
| 207 |  | 
| 208 |     parser.process(app: *this); | 
| 209 |  | 
| 210 |     if (parser.isSet(option: attach)) { | 
| 211 |         m_hostName = parser.value(option: attach); | 
| 212 |         m_runMode = AttachMode; | 
| 213 |         m_port = 3768; | 
| 214 |     } | 
| 215 |  | 
| 216 |     if (parser.isSet(option: port)) { | 
| 217 |         bool isNumber; | 
| 218 |         m_port = parser.value(option: port).toUShort(ok: &isNumber); | 
| 219 |         if (!isNumber) { | 
| 220 |             logError(error: tr(s: "'%1' is not a valid port." ).arg(a: parser.value(option: port))); | 
| 221 |             parser.showHelp(exitCode: 1); | 
| 222 |         } | 
| 223 |     } else if (m_port == 0) { | 
| 224 |         QTemporaryFile file; | 
| 225 |         if (file.open()) | 
| 226 |             m_socketFile = file.fileName(); | 
| 227 |     } | 
| 228 |  | 
| 229 |     m_outputFile = parser.value(option: output); | 
| 230 |  | 
| 231 |     m_recording = (parser.value(option: record) == QLatin1String("on" )); | 
| 232 |     m_interactive = parser.isSet(option: interactive); | 
| 233 |  | 
| 234 |     quint64 features = std::numeric_limits<quint64>::max(); | 
| 235 |     if (parser.isSet(option: include)) { | 
| 236 |         if (parser.isSet(option: exclude)) { | 
| 237 |             logError(error: tr(s: "qmlprofiler can only process either --include or --exclude, not both." )); | 
| 238 |             parser.showHelp(exitCode: 4); | 
| 239 |         } | 
| 240 |         features = parseFeatures(featureList, values: parser.value(option: include), exclude: false); | 
| 241 |     } | 
| 242 |  | 
| 243 |     if (parser.isSet(option: exclude)) | 
| 244 |         features = parseFeatures(featureList, values: parser.value(option: exclude), exclude: true); | 
| 245 |  | 
| 246 |     if (features == 0) | 
| 247 |         parser.showHelp(exitCode: 4); | 
| 248 |  | 
| 249 |     m_qmlProfilerClient->setRequestedFeatures(features); | 
| 250 |  | 
| 251 |     if (parser.isSet(option: verbose)) | 
| 252 |         m_verbose = true; | 
| 253 |  | 
| 254 |     m_arguments = parser.positionalArguments(); | 
| 255 |     if (!m_arguments.isEmpty()) | 
| 256 |         m_executablePath = m_arguments.takeFirst(); | 
| 257 |  | 
| 258 |     if (m_runMode == LaunchMode && m_executablePath.isEmpty()) { | 
| 259 |         logError(error: tr(s: "You have to specify either --attach or an executable to start." )); | 
| 260 |         parser.showHelp(exitCode: 2); | 
| 261 |     } | 
| 262 |  | 
| 263 |     if (m_runMode == AttachMode && !m_executablePath.isEmpty()) { | 
| 264 |         logError(error: tr(s: "--attach cannot be used when starting an executable." )); | 
| 265 |         parser.showHelp(exitCode: 3); | 
| 266 |     } | 
| 267 | } | 
| 268 |  | 
| 269 | int QmlProfilerApplication::exec() | 
| 270 | { | 
| 271 |     QTimer::singleShot(interval: 0, receiver: this, slot: &QmlProfilerApplication::run); | 
| 272 |     return QCoreApplication::exec(); | 
| 273 | } | 
| 274 |  | 
| 275 | bool QmlProfilerApplication::isInteractive() const | 
| 276 | { | 
| 277 |     return m_interactive; | 
| 278 | } | 
| 279 |  | 
| 280 | quint64 QmlProfilerApplication::parseFeatures(const QStringList &featureList, const QString &values, | 
| 281 |                                               bool exclude) | 
| 282 | { | 
| 283 |     quint64 features = exclude ? std::numeric_limits<quint64>::max() : 0; | 
| 284 |     const QStringList givenFeatures = values.split(sep: QLatin1Char(',')); | 
| 285 |     for (const QString &f : givenFeatures) { | 
| 286 |         int index =  featureList.indexOf(t: f); | 
| 287 |         if (index < 0) { | 
| 288 |             logError(error: tr(s: "Unknown feature '%1'" ).arg(a: f)); | 
| 289 |             return 0; | 
| 290 |         } | 
| 291 |         quint64 flag = static_cast<quint64>(1) << index; | 
| 292 |         features = (exclude ? (features ^ flag) : (features | flag)); | 
| 293 |     } | 
| 294 |     if (features == 0) { | 
| 295 |         logError(error: exclude ? tr(s: "No features remaining to record after processing --exclude." ) : | 
| 296 |                            tr(s: "No features specified for --include." )); | 
| 297 |     } | 
| 298 |     return features; | 
| 299 | } | 
| 300 |  | 
| 301 | void QmlProfilerApplication::flush() | 
| 302 | { | 
| 303 |     if (m_recording) { | 
| 304 |         m_pendingRequest = REQUEST_FLUSH; | 
| 305 |         m_qmlProfilerClient->setRecording(false); | 
| 306 |     } else { | 
| 307 |         if (m_profilerData->save(filename: m_interactiveOutputFile)) { | 
| 308 |             m_profilerData->clear(); | 
| 309 |             if (!m_interactiveOutputFile.isEmpty()) | 
| 310 |                 prompt(line: tr(s: "Data written to %1." ).arg(a: m_interactiveOutputFile)); | 
| 311 |             else | 
| 312 |                 prompt(); | 
| 313 |         } else { | 
| 314 |             prompt(line: tr(s: "Saving failed." )); | 
| 315 |         } | 
| 316 |         m_interactiveOutputFile.clear(); | 
| 317 |         m_pendingRequest = REQUEST_NONE; | 
| 318 |     } | 
| 319 | } | 
| 320 |  | 
| 321 | void QmlProfilerApplication::output() | 
| 322 | { | 
| 323 |     if (m_profilerData->save(filename: m_interactiveOutputFile)) { | 
| 324 |         if (!m_interactiveOutputFile.isEmpty()) | 
| 325 |             prompt(line: tr(s: "Data written to %1." ).arg(a: m_interactiveOutputFile)); | 
| 326 |         else | 
| 327 |             prompt(); | 
| 328 |     } else { | 
| 329 |         prompt(line: tr(s: "Saving failed" )); | 
| 330 |     } | 
| 331 |  | 
| 332 |     m_interactiveOutputFile.clear(); | 
| 333 |     m_pendingRequest = REQUEST_NONE; | 
| 334 | } | 
| 335 |  | 
| 336 | bool QmlProfilerApplication::checkOutputFile(PendingRequest pending) | 
| 337 | { | 
| 338 |     if (m_interactiveOutputFile.isEmpty()) | 
| 339 |         return true; | 
| 340 |     QFileInfo file(m_interactiveOutputFile); | 
| 341 |     if (file.exists()) { | 
| 342 |         if (!file.isFile()) { | 
| 343 |             prompt(line: tr(s: "Cannot overwrite %1." ).arg(a: m_interactiveOutputFile)); | 
| 344 |             m_interactiveOutputFile.clear(); | 
| 345 |         } else { | 
| 346 |             prompt(line: tr(s: "%1 exists. Overwrite (y/n)?" ).arg(a: m_interactiveOutputFile)); | 
| 347 |             m_pendingRequest = pending; | 
| 348 |         } | 
| 349 |         return false; | 
| 350 |     } else { | 
| 351 |         return true; | 
| 352 |     } | 
| 353 | } | 
| 354 |  | 
| 355 | void QmlProfilerApplication::userCommand(const QString &command) | 
| 356 | { | 
| 357 |     auto args = command.splitRef(sep: QChar::Space, behavior: Qt::SkipEmptyParts); | 
| 358 |     if (args.isEmpty()) { | 
| 359 |         prompt(); | 
| 360 |         return; | 
| 361 |     } | 
| 362 |  | 
| 363 |     QByteArray cmd = args.takeFirst().trimmed().toLatin1(); | 
| 364 |  | 
| 365 |     if (m_pendingRequest == REQUEST_QUIT) { | 
| 366 |         if (cmd == Constants::CMD_YES || cmd == Constants::CMD_YES2) { | 
| 367 |             quit(); | 
| 368 |         } else if (cmd == Constants::CMD_NO || cmd == Constants::CMD_NO2) { | 
| 369 |             m_pendingRequest = REQUEST_NONE; | 
| 370 |             prompt(); | 
| 371 |         } else { | 
| 372 |             prompt(line: tr(s: "Really quit (y/n)?" )); | 
| 373 |         } | 
| 374 |         return; | 
| 375 |     } | 
| 376 |  | 
| 377 |     if (m_pendingRequest == REQUEST_OUTPUT_FILE || m_pendingRequest == REQUEST_FLUSH_FILE) { | 
| 378 |         if (cmd == Constants::CMD_YES || cmd == Constants::CMD_YES2) { | 
| 379 |             if (m_pendingRequest == REQUEST_OUTPUT_FILE) | 
| 380 |                 output(); | 
| 381 |             else | 
| 382 |                 flush(); | 
| 383 |         } else if (cmd == Constants::CMD_NO || cmd == Constants::CMD_NO2) { | 
| 384 |             m_pendingRequest = REQUEST_NONE; | 
| 385 |             m_interactiveOutputFile.clear(); | 
| 386 |             prompt(); | 
| 387 |         } else { | 
| 388 |             prompt(line: tr(s: "%1 exists. Overwrite (y/n)?" )); | 
| 389 |         } | 
| 390 |         return; | 
| 391 |     } | 
| 392 |  | 
| 393 |     if (cmd == Constants::CMD_RECORD || cmd == Constants::CMD_RECORD2) { | 
| 394 |         m_pendingRequest = REQUEST_TOGGLE_RECORDING; | 
| 395 |         m_qmlProfilerClient->setRecording(!m_recording); | 
| 396 |     } else if (cmd == Constants::CMD_QUIT || cmd == Constants::CMD_QUIT2) { | 
| 397 |         m_pendingRequest = REQUEST_QUIT; | 
| 398 |         if (m_recording) { | 
| 399 |             prompt(line: tr(s: "The application is still generating data. Really quit (y/n)?" )); | 
| 400 |         } else if (!m_profilerData->isEmpty()) { | 
| 401 |             prompt(line: tr(s: "There is still trace data in memory. Really quit (y/n)?" )); | 
| 402 |         } else { | 
| 403 |             quit(); | 
| 404 |         } | 
| 405 |     } else if (cmd == Constants::CMD_OUTPUT || cmd == Constants::CMD_OUTPUT2) { | 
| 406 |         if (m_recording) { | 
| 407 |             prompt(line: tr(s: "Cannot output while recording data." )); | 
| 408 |         } else if (m_profilerData->isEmpty()) { | 
| 409 |             prompt(line: tr(s: "No data was recorded so far." )); | 
| 410 |         } else { | 
| 411 |             m_interactiveOutputFile = args.length() > 0 ? args.at(i: 0).toString() : m_outputFile; | 
| 412 |             if (checkOutputFile(pending: REQUEST_OUTPUT_FILE)) | 
| 413 |                 output(); | 
| 414 |         } | 
| 415 |     } else if (cmd == Constants::CMD_CLEAR || cmd == Constants::CMD_CLEAR2) { | 
| 416 |         if (m_recording) { | 
| 417 |             prompt(line: tr(s: "Cannot clear data while recording." )); | 
| 418 |         } else if (m_profilerData->isEmpty()) { | 
| 419 |             prompt(line: tr(s: "No data was recorded so far." )); | 
| 420 |         } else { | 
| 421 |             m_profilerData->clear(); | 
| 422 |             prompt(line: tr(s: "Trace data cleared." )); | 
| 423 |         } | 
| 424 |     } else if (cmd == Constants::CMD_FLUSH || cmd == Constants::CMD_FLUSH2) { | 
| 425 |         if (!m_recording && m_profilerData->isEmpty()) { | 
| 426 |             prompt(line: tr(s: "No data was recorded so far." )); | 
| 427 |         } else { | 
| 428 |             m_interactiveOutputFile = args.length() > 0 ? args.at(i: 0).toString() : m_outputFile; | 
| 429 |             if (checkOutputFile(pending: REQUEST_FLUSH_FILE)) | 
| 430 |                 flush(); | 
| 431 |         } | 
| 432 |     } else { | 
| 433 |         prompt(line: tr(s: commandTextC)); | 
| 434 |     } | 
| 435 | } | 
| 436 |  | 
| 437 | void QmlProfilerApplication::notifyTraceStarted() | 
| 438 | { | 
| 439 |     // Synchronize to server state. It doesn't hurt to do this multiple times in a row for | 
| 440 |     // different traces. There is no symmetric event to "Complete" after all. | 
| 441 |     m_recording = true; | 
| 442 |  | 
| 443 |     if (m_pendingRequest == REQUEST_TOGGLE_RECORDING) { | 
| 444 |         m_pendingRequest = REQUEST_NONE; | 
| 445 |         prompt(line: tr(s: "Recording started" )); | 
| 446 |     } else { | 
| 447 |         prompt(line: tr(s: "Application started recording" ), ready: false); | 
| 448 |     } | 
| 449 | } | 
| 450 |  | 
| 451 | void QmlProfilerApplication::outputData() | 
| 452 | { | 
| 453 |     if (!m_profilerData->isEmpty()) { | 
| 454 |         m_profilerData->save(filename: m_outputFile); | 
| 455 |         m_profilerData->clear(); | 
| 456 |     } | 
| 457 | } | 
| 458 |  | 
| 459 | void QmlProfilerApplication::run() | 
| 460 | { | 
| 461 |     if (m_runMode == LaunchMode) { | 
| 462 |         if (!m_socketFile.isEmpty()) { | 
| 463 |             logStatus(status: QString::fromLatin1(str: "Listening on %1 ..." ).arg(a: m_socketFile)); | 
| 464 |             m_connection->startLocalServer(fileName: m_socketFile); | 
| 465 |         } | 
| 466 |         m_process = new QProcess(this); | 
| 467 |         QStringList arguments; | 
| 468 |         arguments << QString::fromLatin1(str: "-qmljsdebugger=%1:%2,block,services:CanvasFrameRate" ) | 
| 469 |                      .arg(a: QLatin1String(m_socketFile.isEmpty() ? "port"  : "file" )) | 
| 470 |                      .arg(a: m_socketFile.isEmpty() ? QString::number(m_port) : m_socketFile); | 
| 471 |         arguments << m_arguments; | 
| 472 |  | 
| 473 |         m_process->setProcessChannelMode(QProcess::MergedChannels); | 
| 474 |         connect(sender: m_process, signal: &QIODevice::readyRead, receiver: this, slot: &QmlProfilerApplication::processHasOutput); | 
| 475 |         connect(sender: m_process, signal: QOverload<int, QProcess::ExitStatus>::of(ptr: &QProcess::finished), | 
| 476 |                 context: this, slot: [this](int){ processFinished(); }); | 
| 477 |         logStatus(status: QString("Starting '%1 %2' ..." ).arg(args&: m_executablePath, | 
| 478 |                                                       args: arguments.join(sep: QLatin1Char(' ')))); | 
| 479 |         m_process->start(program: m_executablePath, arguments); | 
| 480 |         if (!m_process->waitForStarted()) { | 
| 481 |             logError(error: QString("Could not run '%1': %2" ).arg(args&: m_executablePath, | 
| 482 |                                                            args: m_process->errorString())); | 
| 483 |             exit(retcode: 1); | 
| 484 |         } | 
| 485 |     } | 
| 486 |     m_connectTimer.start(); | 
| 487 | } | 
| 488 |  | 
| 489 | void QmlProfilerApplication::tryToConnect() | 
| 490 | { | 
| 491 |     Q_ASSERT(!m_connection->isConnected()); | 
| 492 |     ++ m_connectionAttempts; | 
| 493 |  | 
| 494 |     if (!m_verbose && !(m_connectionAttempts % 5)) {// print every 5 seconds | 
| 495 |         if (m_verbose) { | 
| 496 |             if (m_socketFile.isEmpty()) | 
| 497 |                 logError(error: QString::fromLatin1(str: "Could not connect to %1:%2 for %3 seconds ..." ) | 
| 498 |                          .arg(a: m_hostName).arg(a: m_port).arg(a: m_connectionAttempts)); | 
| 499 |             else | 
| 500 |                 logError(error: QString::fromLatin1(str: "No connection received on %1 for %2 seconds ..." ) | 
| 501 |                          .arg(a: m_socketFile).arg(a: m_connectionAttempts)); | 
| 502 |         } | 
| 503 |     } | 
| 504 |  | 
| 505 |     if (m_socketFile.isEmpty()) { | 
| 506 |         logStatus(status: QString::fromLatin1(str: "Connecting to %1:%2 ..." ).arg(a: m_hostName).arg(a: m_port)); | 
| 507 |         m_connection->connectToHost(hostName: m_hostName, port: m_port); | 
| 508 |     } | 
| 509 | } | 
| 510 |  | 
| 511 | void QmlProfilerApplication::connected() | 
| 512 | { | 
| 513 |     m_connectTimer.stop(); | 
| 514 |     QString endpoint = m_socketFile.isEmpty() ? | 
| 515 |                 QString::fromLatin1(str: "%1:%2" ).arg(a: m_hostName).arg(a: m_port) : | 
| 516 |                 m_socketFile; | 
| 517 |     prompt(line: tr(s: "Connected to %1. Wait for profile data or type a command (type 'help' to show list "  | 
| 518 |               "of commands).\nRecording Status: %2" ) | 
| 519 |            .arg(a: endpoint).arg(a: m_recording ? tr(s: "on" ) : tr(s: "off" ))); | 
| 520 | } | 
| 521 |  | 
| 522 | void QmlProfilerApplication::disconnected() | 
| 523 | { | 
| 524 |     if (m_runMode == AttachMode) { | 
| 525 |         int exitCode = 0; | 
| 526 |         if (m_recording) { | 
| 527 |             logError(error: "Connection dropped while recording, last trace is damaged!" ); | 
| 528 |             exitCode = 2; | 
| 529 |         } | 
| 530 |  | 
| 531 |         if (!m_interactive ) | 
| 532 |             exit(retcode: exitCode); | 
| 533 |         else | 
| 534 |             m_qmlProfilerClient->clearAll(); | 
| 535 |     } | 
| 536 | } | 
| 537 |  | 
| 538 | void QmlProfilerApplication::processHasOutput() | 
| 539 | { | 
| 540 |     Q_ASSERT(m_process); | 
| 541 |     while (m_process->bytesAvailable()) | 
| 542 |         std::cerr << m_process->readAll().constData(); | 
| 543 | } | 
| 544 |  | 
| 545 | void QmlProfilerApplication::processFinished() | 
| 546 | { | 
| 547 |     Q_ASSERT(m_process); | 
| 548 |     int exitCode = 0; | 
| 549 |     if (m_process->exitStatus() == QProcess::NormalExit) { | 
| 550 |         logStatus(status: QString("Process exited (%1)." ).arg(a: m_process->exitCode())); | 
| 551 |         if (m_recording) { | 
| 552 |             logError(error: "Process exited while recording, last trace is damaged!" ); | 
| 553 |             exitCode = 2; | 
| 554 |         } | 
| 555 |     } else { | 
| 556 |         logError(error: "Process crashed!" ); | 
| 557 |         exitCode = 3; | 
| 558 |     } | 
| 559 |     if (!m_interactive) | 
| 560 |         exit(retcode: exitCode); | 
| 561 |     else | 
| 562 |         m_qmlProfilerClient->clearAll(); | 
| 563 | } | 
| 564 |  | 
| 565 | void QmlProfilerApplication::traceClientEnabledChanged(bool enabled) | 
| 566 | { | 
| 567 |     if (enabled) { | 
| 568 |         logStatus(status: "Trace client is attached." ); | 
| 569 |         // blocked server is waiting for recording message from both clients | 
| 570 |         // once the last one is connected, both messages should be sent | 
| 571 |         m_qmlProfilerClient->setRecording(m_recording); | 
| 572 |     } | 
| 573 | } | 
| 574 |  | 
| 575 | void QmlProfilerApplication::traceFinished() | 
| 576 | { | 
| 577 |     m_recording = false; // only on "Complete" we know that the trace is really finished. | 
| 578 |  | 
| 579 |     if (m_pendingRequest == REQUEST_FLUSH) { | 
| 580 |         flush(); | 
| 581 |     } else if (m_pendingRequest == REQUEST_TOGGLE_RECORDING) { | 
| 582 |         m_pendingRequest = REQUEST_NONE; | 
| 583 |         prompt(line: tr(s: "Recording stopped." )); | 
| 584 |     } else { | 
| 585 |         prompt(line: tr(s: "Application stopped recording." ), ready: false); | 
| 586 |     } | 
| 587 |  | 
| 588 |     m_qmlProfilerClient->clearEvents(); | 
| 589 | } | 
| 590 |  | 
| 591 | void QmlProfilerApplication::prompt(const QString &line, bool ready) | 
| 592 | { | 
| 593 |     if (m_interactive) { | 
| 594 |         if (!line.isEmpty()) | 
| 595 |             std::cerr << qPrintable(line) << std::endl; | 
| 596 |         std::cerr << "> " ; | 
| 597 |         if (ready) | 
| 598 |             emit readyForCommand(); | 
| 599 |     } | 
| 600 | } | 
| 601 |  | 
| 602 | void QmlProfilerApplication::logError(const QString &error) | 
| 603 | { | 
| 604 |     std::cerr << "Error: "  << qPrintable(error) << std::endl; | 
| 605 | } | 
| 606 |  | 
| 607 | void QmlProfilerApplication::logStatus(const QString &status) | 
| 608 | { | 
| 609 |     if (!m_verbose) | 
| 610 |         return; | 
| 611 |     std::cerr << qPrintable(status) << std::endl; | 
| 612 | } | 
| 613 |  |