diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt index 605bdc39315..0923e3d49a8 100644 --- a/src/plugins/CMakeLists.txt +++ b/src/plugins/CMakeLists.txt @@ -99,6 +99,7 @@ add_subdirectory(perfprofiler) add_subdirectory(qbsprojectmanager) add_subdirectory(ctfvisualizer) add_subdirectory(squish) +add_subdirectory(appstatisticsmonitor) # Level 8: add_subdirectory(boot2qt) diff --git a/src/plugins/appstatisticsmonitor/AppStatisticsMonitor.json.in b/src/plugins/appstatisticsmonitor/AppStatisticsMonitor.json.in new file mode 100644 index 00000000000..98f7d715900 --- /dev/null +++ b/src/plugins/appstatisticsmonitor/AppStatisticsMonitor.json.in @@ -0,0 +1,19 @@ +{ + "Name" : "AppStatisticsMonitor", + "Version" : "${IDE_VERSION}", + "CompatVersion" : "${IDE_VERSION_COMPAT}", + "Experimental" : true, + "Vendor" : "The Qt Company Ltd", + "Copyright" : "(C) ${IDE_COPYRIGHT_YEAR} The Qt Company Ltd", + "License" : [ "Commercial Usage", + "", + "Licensees holding valid Qt Commercial licenses may use this plugin in accordance with the Qt Commercial License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and The Qt Company.", + "", + "GNU General Public License Usage", + "", + "Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html." + ], + "Description" : "AppStatisticsMonitor, a plugin designed to enhance your Qt Creator experience. With its seamless integration, this plugin adds a dedicated tab to your Navigation Widget, providing insightful visualizations of CPU and Memory consumption for running applications within Qt Creator. Simply select the desired application from the combobox to monitor its performance in real-time.", + "Url" : "http://www.qt.io", + ${IDE_PLUGIN_DEPENDENCIES} +} diff --git a/src/plugins/appstatisticsmonitor/CMakeLists.txt b/src/plugins/appstatisticsmonitor/CMakeLists.txt new file mode 100644 index 00000000000..891da1f812a --- /dev/null +++ b/src/plugins/appstatisticsmonitor/CMakeLists.txt @@ -0,0 +1,10 @@ +add_qtc_plugin(AppStatisticsMonitor + SKIP_TRANSLATION + PLUGIN_DEPENDS Core ProjectExplorer + SOURCES + appstatisticsmonitorplugin.cpp + chart.cpp chart.h + manager.cpp manager.h + idataprovider.h idataprovider.cpp +) + diff --git a/src/plugins/appstatisticsmonitor/appstatisticsmonitor.qbs b/src/plugins/appstatisticsmonitor/appstatisticsmonitor.qbs new file mode 100644 index 00000000000..ae8ff235fdc --- /dev/null +++ b/src/plugins/appstatisticsmonitor/appstatisticsmonitor.qbs @@ -0,0 +1,21 @@ +import qbs 1.0 + +QtcPlugin { + name: "AppStatisticsMonitor" + + Depends { name: "Core" } + Depends { name: "ProjectExplorer" } + Depends { name: "Qt"; submodules: ["widgets", "xml", "network"] } + + files: [ + "appstatisticsmonitorplugin.cpp", + "chart.h", + "chart.cpp", + "manager.h", + "manager.cpp", + "idataprovider.h", + "idataprovider.cpp", + "tr.h" + ] +} + diff --git a/src/plugins/appstatisticsmonitor/appstatisticsmonitorplugin.cpp b/src/plugins/appstatisticsmonitor/appstatisticsmonitorplugin.cpp new file mode 100644 index 00000000000..4b83334cf2e --- /dev/null +++ b/src/plugins/appstatisticsmonitor/appstatisticsmonitorplugin.cpp @@ -0,0 +1,32 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "manager.h" + +#include + +#include + +namespace AppStatisticsMonitor::Internal { + +class AppStatisticsMonitorPlugin final : public ExtensionSystem::IPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "AppStatisticsMonitor.json") + +private: + void initialize() final; + std::unique_ptr m_appstatisticsmonitorManager; + std::unique_ptr m_appstatisticsmonitorViewFactory; +}; + +void AppStatisticsMonitorPlugin::initialize() +{ + m_appstatisticsmonitorManager = std::make_unique(); + m_appstatisticsmonitorViewFactory = std::make_unique( + m_appstatisticsmonitorManager.get()); +} + +} // namespace AppStatisticsMonitor::Internal + +#include diff --git a/src/plugins/appstatisticsmonitor/chart.cpp b/src/plugins/appstatisticsmonitor/chart.cpp new file mode 100644 index 00000000000..6388f675cbf --- /dev/null +++ b/src/plugins/appstatisticsmonitor/chart.cpp @@ -0,0 +1,164 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "chart.h" + +#include + +#include +#include +#include +#include +#include + +namespace AppStatisticsMonitor::Internal { + +static const int padding = 40; +static const int numPadding = 10; +static const QRectF dataRangeDefault = QRectF(0, 0, 5, 1); + +Chart::Chart(const QString &name, QWidget *parent) + : QWidget(parent) + , m_name(name) +{ + setMinimumHeight(200); + setMinimumWidth(400); +} + +void Chart::addNewPoint(const QPointF &point) +{ + m_points.append(point); + update(); +} + +void Chart::loadNewProcessData(QList data) +{ + clear(); + for (long i = 0; i < data.size(); ++i) { + m_points.append(QPointF(i + 1, data[i])); + } + update(); +} + +double Chart::lastPointX() const +{ + if (m_points.isEmpty()) + return 0; + return m_points.last().x(); +} + +void Chart::clear() +{ + m_points.clear(); + addNewPoint({0, 0}); + update(); +} + +void Chart::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event); + QPainter painter(this); + + painter.fillRect(rect(), Utils::creatorTheme()->color(Utils::Theme::Token_Background_Default)); + + // add the name of the chart in the middle of the widget width and on the top + painter.drawText( + rect(), + Qt::AlignHCenter | Qt::AlignTop, + m_name + QString::number(m_points.last().y(), 'g', 4) + "%"); + + const QRectF dataRange = calculateDataRange(); + updateScalingFactors(dataRange); + + for (double x = dataRange.left(); x <= dataRange.right(); x += m_xGridStep) { + double xPos = padding + (x - dataRange.left()) * m_xScale; + if (xPos < padding || xPos > width() - padding) + continue; + painter.setPen(Utils::creatorTheme()->color(Utils::Theme::Token_Foreground_Default)); + painter.drawLine(xPos, padding, xPos, height() - padding); + + painter.setPen(Utils::creatorTheme()->color(Utils::Theme::Token_Text_Muted)); + painter.drawText(xPos, height() - numPadding, QString::number(x)); + } + + for (double y = dataRange.top(); y <= dataRange.bottom(); y += m_yGridStep) { + double yPos = height() - padding - (y - (int) dataRange.top()) * m_yScale; + if (yPos < padding || yPos > height() - padding) + continue; + + painter.setPen(Utils::creatorTheme()->color(Utils::Theme::Token_Foreground_Default)); + painter.drawLine(padding, yPos, width() - padding, yPos); + + painter.setPen(Utils::creatorTheme()->color(Utils::Theme::Token_Text_Muted)); + painter.drawText(numPadding, yPos, QString::number(y)); + } + + painter.setPen(Utils::creatorTheme()->color(Utils::Theme::Token_Foreground_Default)); + painter.drawLine(padding, height() - padding, width() - padding, height() - padding); // X axis + painter.drawLine(padding, height() - padding, padding, padding); // Y axis + + QPen pen(Utils::creatorTheme()->color(Utils::Theme::Token_Accent_Default)); + pen.setWidth(2); + painter.setPen(pen); + painter.setRenderHint(QPainter::Antialiasing); + for (int i = 1; i < m_points.size(); ++i) { + QPointF startPoint( + padding + (m_points[i - 1].x() - dataRange.left()) * m_xScale, + height() - padding - (m_points[i - 1].y() - dataRange.top()) * m_yScale); + QPointF endPoint( + padding + (m_points[i].x() - dataRange.left()) * m_xScale, + height() - padding - (m_points[i].y() - dataRange.top()) * m_yScale); + painter.drawLine(startPoint, endPoint); + } +} + +void Chart::addPoint() +{ + for (int i = 0; i < 10; ++i) { + double x = m_points.size(); + double y = sin(x) + 10 * cos(x / 10) + 10; + m_points.append(QPointF(x, y)); + } + update(); +} + +QRectF Chart::calculateDataRange() const +{ + QRectF dataRange = QRectF(0, 0, 0, 0); + + if (m_points.isEmpty()) + return dataRange; + + for (const QPointF &point : m_points) { + dataRange.setLeft(qMin(dataRange.left(), point.x())); + dataRange.setRight(qMax(dataRange.right(), point.x())); + + dataRange.setBottom(qMin(dataRange.bottom(), point.y())); + dataRange.setTop(qMax(dataRange.top(), point.y())); + } + dataRange.setRight(round(dataRange.right()) + 1); + dataRange.setTop(round(dataRange.top()) + 1); + + dataRange = dataRange.united(dataRangeDefault); + return dataRange; +} + +void Chart::updateScalingFactors(const QRectF &dataRange) +{ + const double xRange = dataRange.right() - dataRange.left(); + double yRange = dataRange.bottom() - dataRange.top(); + yRange = yRange == 0 ? dataRange.top() : yRange; + + m_xGridStep = qRound(xRange / 10); + m_xGridStep = m_xGridStep == 0 ? 1 : m_xGridStep; + + m_yGridStep = yRange / 5; + m_yGridStep = qRound(m_yGridStep * 10.0) / 10.0; + if (yRange > 10) + m_yGridStep = qRound(m_yGridStep); + m_yGridStep = qMax(m_yGridStep, 0.1); + + m_xScale = (width() - 2 * padding) / xRange; + m_yScale = (height() - 2 * padding) / yRange; +} +} // namespace AppStatisticsMonitor::Internal diff --git a/src/plugins/appstatisticsmonitor/chart.h b/src/plugins/appstatisticsmonitor/chart.h new file mode 100644 index 00000000000..0a9ed927512 --- /dev/null +++ b/src/plugins/appstatisticsmonitor/chart.h @@ -0,0 +1,40 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 +#pragma once + +#include +#include +#include +#include +#include + +namespace AppStatisticsMonitor::Internal { + +class Chart : public QWidget +{ +public: + Chart(const QString &name, QWidget *parent = nullptr); + + void addNewPoint(const QPointF &point); + void loadNewProcessData(QList data); + double lastPointX() const; + + void clear(); + +private: + void paintEvent(QPaintEvent *event) override; + void addPoint(); + QRectF calculateDataRange() const; + void updateScalingFactors(const QRectF &dataRange); + +private: + QList m_points; + QString m_name; + + double m_xScale = 1; + double m_yScale = 1; + double m_xGridStep = 1; + double m_yGridStep = 1; +}; + +} // namespace AppStatisticsMonitor::Internal diff --git a/src/plugins/appstatisticsmonitor/idataprovider.cpp b/src/plugins/appstatisticsmonitor/idataprovider.cpp new file mode 100644 index 00000000000..bad905da876 --- /dev/null +++ b/src/plugins/appstatisticsmonitor/idataprovider.cpp @@ -0,0 +1,233 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "idataprovider.h" + +#include +#include +#include + +#include +#include +#include + +using namespace Utils; + +namespace AppStatisticsMonitor::Internal { + +IDataProvider::IDataProvider(qint64 pid, QObject *parent) + : QObject(parent) + , m_pid(pid) +{ + m_timer.setInterval(1000); + connect(&m_timer, &QTimer::timeout, this, [this] { handleTimeout(); }); + m_timer.start(); +} + +void IDataProvider::handleTimeout() +{ + m_memoryConsumption.append(getMemoryConsumption()); + m_cpuConsumption.append(getCpuConsumption()); + emit newDataAvailable(); +} + +QList IDataProvider::memoryConsumptionHistory() const +{ + return m_memoryConsumption; +} + +QList IDataProvider::cpuConsumptionHistory() const +{ + return m_cpuConsumption; +} + +double IDataProvider::memoryConsumptionLast() const +{ + return m_memoryConsumption.isEmpty() ? 0 : m_memoryConsumption.last(); +} + +double IDataProvider::cpuConsumptionLast() const +{ + return m_cpuConsumption.isEmpty() ? 0 : m_cpuConsumption.last(); +} + +// ------------------------- LinuxDataProvider -------------------------------- +#ifdef Q_OS_LINUX +class LinuxDataProvider : public IDataProvider +{ +public: + LinuxDataProvider(qint64 pid, QObject *parent = nullptr) + : IDataProvider(pid, parent) + {} + + double getMemoryConsumption() + { + const FilePath statusMemory = FilePath::fromString( + QStringLiteral("/proc/%1/status").arg(m_pid)); + const expected_str statusMemoryContent = statusMemory.fileContents(); + + if (!statusMemoryContent) + return 0; + + int vmPeak = 0; + const static QRegularExpression numberRX(QLatin1String("[^0-9]+")); + for (const QByteArray &element : statusMemoryContent.value().split('\n')) { + if (element.startsWith("VmHWM")) { + const QString p = QString::fromUtf8(element); + vmPeak = p.split(numberRX, Qt::SkipEmptyParts)[0].toLong(); + } + } + + const FilePath meminfoFile("/proc/meminfo"); + const expected_str meminfoContent = meminfoFile.fileContents(); + if (!meminfoContent) + return 0; + + const auto meminfo = meminfoContent.value().split('\n'); + if (meminfo.isEmpty()) + return 0; + + const auto parts = QString::fromUtf8(meminfo.front()).split(numberRX, Qt::SkipEmptyParts); + if (parts.isEmpty()) + return 0; + + return double(vmPeak) / parts[0].toDouble() * 100; + } + + // Provides the CPU usage from the last request + double getCpuConsumption() + { + const FilePath status = FilePath::fromString(QStringLiteral("/proc/%1/stat").arg(m_pid)); + const FilePath uptimeFile = FilePath::fromString(QStringLiteral("/proc/uptime")); + const double clkTck = static_cast(sysconf(_SC_CLK_TCK)); + const expected_str statusFileContent = status.fileContents(); + const expected_str uptimeFileContent = uptimeFile.fileContents(); + + if (!statusFileContent.has_value() || !uptimeFileContent.has_value() || clkTck == 0) + return 0; + + const QList processStatus = statusFileContent.value().split(' '); + if (processStatus.isEmpty() || processStatus.size() < 22) + return 0; + + const double uptime = uptimeFileContent.value().split(' ')[0].toDouble(); + + const double utime = processStatus[13].toDouble() / clkTck; + const double stime = processStatus[14].toDouble() / clkTck; + const double cutime = processStatus[15].toDouble() / clkTck; + const double cstime = processStatus[16].toDouble() / clkTck; + const double starttime = processStatus[21].toDouble() / clkTck; + + // Calculate CPU usage for last request + const double currentTotalTime = utime + stime + cutime + cstime; + + const double elapsed = uptime - starttime; + const double clicks = (currentTotalTime - m_lastTotalTime) * clkTck; + const double timeClicks = (elapsed - m_lastRequestStartTime) * clkTck; + + m_lastTotalTime = currentTotalTime; + m_lastRequestStartTime = elapsed; + + return timeClicks > 0 ? 100 * (clicks / timeClicks) : 0; + } + + // Provides the usage all over the process lifetime + // Can be used in the future for the process lifetime statistics + + // double LinuxDataProvider::getCpuConsumption() + // { + // const FilePath status = FilePath::fromString( + // QStringLiteral("/proc/%1/stat").arg(m_pid)); + // const FilePath uptimeFile = FilePath::fromString(QStringLiteral("/proc/uptime")); + // const double clkTck = static_cast(sysconf(_SC_CLK_TCK)); + + // if (!status.fileContents().has_value() || !uptimeFile.fileContents().has_value() || clkTck == 0) + // return 0; + + // const QVector processStatus = status.fileContents().value().split(' ').toVector(); + // const double uptime = uptimeFile.fileContents().value().split(' ')[0].toDouble(); + + // const double utime = processStatus[13].toDouble() / clkTck; + // const double stime = processStatus[14].toDouble() / clkTck; + // const double cutime = processStatus[15].toDouble() / clkTck; + // const double cstime = processStatus[16].toDouble() / clkTck; + // const double starttime = processStatus[21].toDouble() / clkTck; + + // const double elapsed = uptime - starttime; + // const double usage_sec = utime + stime + cutime + cstime; + // const double usage = 100 * usage_sec / elapsed; + + // return usage; + // } +}; + +#endif + +// ------------------------- WindowsDataProvider -------------------------------- + +#ifdef Q_OS_WIN +class WindowsDataProvider : public IDataProvider +{ +public: + WindowsDataProvider(qint64 pid, QObject *parent = nullptr) + : IDataProvider(pid, parent) + {} + + double getMemoryConsumption() { return 0; } + + double getCpuConsumption() { return 0; } + +#if 0 + double getMemoryConsumptionWindows(qint64 pid) + { + HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid); + if (hProcess == NULL) { + std::cerr << "Failed to open process. Error code: " << GetLastError() << std::endl; + return 1; + } + + PROCESS_MEMORY_COUNTERS_EX pmc; + if (!GetProcessMemoryInfo(hProcess, (PROCESS_MEMORY_COUNTERS*)&pmc, sizeof(pmc))) { + std::cerr << "Failed to retrieve process memory information. Error code: " << GetLastError() << std::endl; + CloseHandle(hProcess); + return 1; + } + + std::cout << "Process ID: " << pid << std::endl; + std::cout << "Memory consumption: " << pmc.PrivateUsage << " bytes" << std::endl; + + CloseHandle(hProcess); + return pmc.PrivateUsage; + } +#endif +}; +#endif + +// ------------------------- MacDataProvider -------------------------------- + +#ifdef Q_OS_MACOS +class MacDataProvider : public IDataProvider +{ +public: + MacDataProvider(qint64 pid, QObject *parent = nullptr) + : IDataProvider(pid, parent) + {} + + double getMemoryConsumption() { return 0; } + + double getCpuConsumption() { return 0; } +}; +#endif + +IDataProvider *createDataProvider(qint64 pid) +{ +#ifdef Q_OS_WIN + return new WindowsDataProvider(pid); +#elif defined(Q_OS_MACOS) + return new MacDataProvider(pid); +#else // Q_OS_LINUX + return new LinuxDataProvider(pid); +#endif +} + +} // namespace AppStatisticsMonitor::Internal diff --git a/src/plugins/appstatisticsmonitor/idataprovider.h b/src/plugins/appstatisticsmonitor/idataprovider.h new file mode 100644 index 00000000000..42e60ebb8bf --- /dev/null +++ b/src/plugins/appstatisticsmonitor/idataprovider.h @@ -0,0 +1,44 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 +#pragma once + +#include +#include +#include + +namespace AppStatisticsMonitor::Internal { + +class IDataProvider : public QObject +{ + Q_OBJECT +public: + IDataProvider(qint64 pid, QObject *parent = nullptr); + + QList memoryConsumptionHistory() const; + QList cpuConsumptionHistory() const; + + double memoryConsumptionLast() const; + double cpuConsumptionLast() const; + +protected: + virtual double getMemoryConsumption() = 0; + virtual double getCpuConsumption() = 0; + + QList m_memoryConsumption; + QList m_cpuConsumption; + qint64 m_pid; + double m_lastTotalTime; + double m_lastRequestStartTime; + +signals: + void newDataAvailable(); + +private: + void handleTimeout(); + + QTimer m_timer; +}; + +IDataProvider *createDataProvider(qint64 pid); + +} // AppStatisticsMonitor::Internal diff --git a/src/plugins/appstatisticsmonitor/manager.cpp b/src/plugins/appstatisticsmonitor/manager.cpp new file mode 100644 index 00000000000..ad17b80dd78 --- /dev/null +++ b/src/plugins/appstatisticsmonitor/manager.cpp @@ -0,0 +1,219 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "manager.h" + +#include "chart.h" +#include "idataprovider.h" + +#include + +#include +#include + +#include +#include +#include + +using namespace ProjectExplorer; +using namespace Utils; + +namespace AppStatisticsMonitor::Internal { + +class AppStatisticsMonitorView : public QWidget +{ +public: + explicit AppStatisticsMonitorView( + AppStatisticsMonitorManager *appStatisticManager, QWidget *parent = nullptr); + + ~AppStatisticsMonitorView() override; + +private: + QComboBox *m_comboBox; + + Chart *m_memChart; + Chart *m_cpuChart; + + AppStatisticsMonitorManager *m_manager; +}; + +AppStatisticsMonitorManager::AppStatisticsMonitorManager() +{ + connect( + ProjectExplorer::ProjectExplorerPlugin::instance(), + &ProjectExplorer::ProjectExplorerPlugin::runControlStarted, + this, + [this](RunControl *runControl) { + qint64 pid = runControl->applicationProcessHandle().pid(); + + m_pidNameMap[pid] = runControl->displayName(); + m_rcPidMap[runControl] = pid; + + m_currentDataProvider = createDataProvider(pid); + m_pidDataProviders.insert(pid, m_currentDataProvider); + + emit appStarted(runControl->displayName(), pid); + }); + + connect( + ProjectExplorer::ProjectExplorerPlugin::instance(), + &ProjectExplorer::ProjectExplorerPlugin::runControlStoped, + this, + [this](RunControl *runControl) { + const auto pidIt = m_rcPidMap.constFind(runControl); + if (pidIt == m_rcPidMap.constEnd()) + return; + + const qint64 pid = pidIt.value(); + m_rcPidMap.erase(pidIt); + + m_pidNameMap.remove(pid); + delete m_pidDataProviders[pid]; + m_pidDataProviders.remove(pid); + if (m_pidDataProviders.isEmpty()) + setCurrentDataProvider(-1); + else + setCurrentDataProvider(m_pidDataProviders.keys().last()); + + emit appStoped(pid); + }); +} + +QString AppStatisticsMonitorManager::nameForPid(qint64 pid) const +{ + const auto pidIt = m_pidNameMap.constFind(pid); + if (pidIt == m_pidNameMap.constEnd()) + return {}; + + return pidIt.value(); +} + +IDataProvider *AppStatisticsMonitorManager::currentDataProvider() const +{ + return m_currentDataProvider; +} + +void AppStatisticsMonitorManager::setCurrentDataProvider(qint64 pid) +{ + m_currentDataProvider = nullptr; + const auto pidIt = m_pidDataProviders.constFind(pid); + if (pidIt == m_pidDataProviders.constEnd()) + return; + + m_currentDataProvider = pidIt.value(); + connect( + m_currentDataProvider, + &IDataProvider::newDataAvailable, + this, + &AppStatisticsMonitorManager::newDataAvailable); +} + +QHash AppStatisticsMonitorManager::pidNameMap() const +{ + return m_pidNameMap; +} + +// AppStatisticsMonitorView +AppStatisticsMonitorView::AppStatisticsMonitorView(AppStatisticsMonitorManager *dapManager, QWidget *) + : m_manager(dapManager) +{ + auto layout = new QVBoxLayout; + auto form = new QFormLayout; + setLayout(layout); + + m_comboBox = new QComboBox(); + form->addRow(m_comboBox); + + m_memChart = new Chart("Memory consumption "); + m_memChart->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + m_memChart->clear(); + form->addRow(m_memChart); + + m_cpuChart = new Chart("CPU consumption "); + m_cpuChart->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + m_cpuChart->clear(); + form->addRow(m_cpuChart); + + layout->addLayout(form); + + for (auto pidName : m_manager->pidNameMap().asKeyValueRange()) { + qint64 pid = pidName.first; + m_comboBox->addItem(pidName.second + " : " + QString::number(pid), pid); + } + m_comboBox->setCurrentIndex(m_comboBox->count() - 1); + + m_memChart->clear(); + m_cpuChart->clear(); + + auto updateCharts = [this](int index) { + m_manager->setCurrentDataProvider(m_comboBox->itemData(index).toLongLong()); + if (m_manager->currentDataProvider() != nullptr) { + m_memChart->loadNewProcessData( + m_manager->currentDataProvider()->memoryConsumptionHistory()); + m_cpuChart->loadNewProcessData( + m_manager->currentDataProvider()->cpuConsumptionHistory()); + } + }; + + if (m_comboBox->count() != 0) + updateCharts(m_comboBox->currentIndex()); + + connect(m_comboBox, &QComboBox::currentIndexChanged, this, [updateCharts](int index) { + updateCharts(index); + }); + + connect( + m_manager, + &AppStatisticsMonitorManager::appStarted, + this, + [this](const QString &name, qint64 pid) { + if (pid != m_comboBox->currentData()) { + m_comboBox->addItem(name + " : " + QString::number(pid), pid); + + m_memChart->clear(); + m_cpuChart->clear(); + + m_comboBox->setCurrentIndex(m_comboBox->count() - 1); + } + }); + + connect(m_manager, &AppStatisticsMonitorManager::appStoped, this, [this](qint64 pid) { + m_memChart->addNewPoint({m_memChart->lastPointX() + 1, 0}); + m_cpuChart->addNewPoint({m_cpuChart->lastPointX() + 1, 0}); + const int indx = m_comboBox->findData(pid); + if (indx != -1) + m_comboBox->removeItem(indx); + }); + + connect(m_manager, &AppStatisticsMonitorManager::newDataAvailable, this, [this] { + const IDataProvider *currentDataProvider = m_manager->currentDataProvider(); + if (currentDataProvider != nullptr) { + m_memChart->addNewPoint( + {(double) currentDataProvider->memoryConsumptionHistory().size(), + currentDataProvider->memoryConsumptionLast()}); + m_cpuChart->addNewPoint( + {(double) currentDataProvider->cpuConsumptionHistory().size(), + currentDataProvider->cpuConsumptionLast()}); + } + }); +} + +AppStatisticsMonitorView::~AppStatisticsMonitorView() = default; + +// AppStatisticsMonitorViewFactory +AppStatisticsMonitorViewFactory::AppStatisticsMonitorViewFactory( + AppStatisticsMonitorManager *appStatisticManager) + : m_manager(appStatisticManager) +{ + setDisplayName(("AppStatisticsMonitor")); + setPriority(300); + setId("AppStatisticsMonitor"); + setActivationSequence(QKeySequence("Alt+S")); +} + +Core::NavigationView AppStatisticsMonitorViewFactory::createWidget() +{ + return {new AppStatisticsMonitorView(m_manager), {}}; +} + +} // namespace AppStatisticsMonitor::Internal diff --git a/src/plugins/appstatisticsmonitor/manager.h b/src/plugins/appstatisticsmonitor/manager.h new file mode 100644 index 00000000000..beba9cec721 --- /dev/null +++ b/src/plugins/appstatisticsmonitor/manager.h @@ -0,0 +1,60 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "chart.h" + +#include "idataprovider.h" + +#include + +#include +#include +#include + +namespace Core { class IContext; } +namespace ProjectExplorer { class RunControl; } + +namespace AppStatisticsMonitor::Internal { + +class AppStatisticsMonitorManager : public QObject +{ + Q_OBJECT +public: + AppStatisticsMonitorManager(); + + QString nameForPid(qint64 pid) const; + QHash pidNameMap() const; + + double memoryConsumption(qint64 pid) const; + double cpuConsumption(qint64 pid) const; + + IDataProvider *currentDataProvider() const; + void setCurrentDataProvider(qint64 pid); + +signals: + void newDataAvailable(); + void appStarted(const QString &name, qint64 pid); + void appStoped(qint64 pid); + +private: + QHash m_pidNameMap; + QHash m_rcPidMap; + + QMap m_pidDataProviders; + IDataProvider *m_currentDataProvider; +}; + +class AppStatisticsMonitorViewFactory : public Core::INavigationWidgetFactory +{ +public: + AppStatisticsMonitorViewFactory(AppStatisticsMonitorManager *appStatisticManager); + +private: + Core::NavigationView createWidget() override; + + AppStatisticsMonitorManager *m_manager; +}; + +} // namespace AppStatisticsMonitor::Internal diff --git a/src/plugins/appstatisticsmonitor/tr.h b/src/plugins/appstatisticsmonitor/tr.h new file mode 100644 index 00000000000..fad1692e079 --- /dev/null +++ b/src/plugins/appstatisticsmonitor/tr.h @@ -0,0 +1,15 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +namespace AppStatisticsMonitor { + +struct Tr +{ + Q_DECLARE_TR_FUNCTIONS(AppStatisticsMonitor) +}; + +} // namespace AppStatisticsMonitor diff --git a/src/plugins/projectexplorer/projectexplorer.cpp b/src/plugins/projectexplorer/projectexplorer.cpp index 7ec216c138b..a62ab627bef 100644 --- a/src/plugins/projectexplorer/projectexplorer.cpp +++ b/src/plugins/projectexplorer/projectexplorer.cpp @@ -1890,7 +1890,6 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er #ifdef WITH_TESTS addTestCreator(&createSanitizerOutputParserTest); #endif - return true; } @@ -2456,6 +2455,12 @@ void ProjectExplorerPluginPrivate::startRunControl(RunControl *runControl) ++m_activeRunControlCount; runControl->initiateStart(); doUpdateRunActions(); + connect(runControl, &RunControl::started, m_instance, [runControl] { + emit m_instance->runControlStarted(runControl); + }); + connect(runControl, &RunControl::stopped, m_instance, [runControl] { + emit m_instance->runControlStoped(runControl); + }); } void ProjectExplorerPluginPrivate::showOutputPaneForRunControl(RunControl *runControl) diff --git a/src/plugins/projectexplorer/projectexplorer.h b/src/plugins/projectexplorer/projectexplorer.h index 0c737aa9ab7..3c932f759e8 100644 --- a/src/plugins/projectexplorer/projectexplorer.h +++ b/src/plugins/projectexplorer/projectexplorer.h @@ -182,6 +182,8 @@ signals: void customParsersChanged(); void runActionsUpdated(); + void runControlStarted(ProjectExplorer::RunControl *runControl); + void runControlStoped(ProjectExplorer::RunControl *runControl); void filesRenamed(const QList> &oldAndNewPaths);