Appstatisticsmonitor: Add statistics monitor plugin

The plugin adds a new tab to Navigation Widget. The tab contains
two charts CPU consumption and Memory consumption for started
applications in QtC. There is a combobox by which a monitored
application can be chosen.

Change-Id: I0747c90e73a289d65aabd31672f3accf74f00749
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Artem Sokolovskii
2022-10-11 16:44:03 +02:00
parent 19355ccfe3
commit de18097cb4
14 changed files with 866 additions and 1 deletions

View File

@@ -99,6 +99,7 @@ add_subdirectory(perfprofiler)
add_subdirectory(qbsprojectmanager)
add_subdirectory(ctfvisualizer)
add_subdirectory(squish)
add_subdirectory(appstatisticsmonitor)
# Level 8:
add_subdirectory(boot2qt)

View File

@@ -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}
}

View File

@@ -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
)

View File

@@ -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"
]
}

View File

@@ -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 <extensionsystem/iplugin.h>
#include <QString>
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<AppStatisticsMonitorManager> m_appstatisticsmonitorManager;
std::unique_ptr<AppStatisticsMonitorViewFactory> m_appstatisticsmonitorViewFactory;
};
void AppStatisticsMonitorPlugin::initialize()
{
m_appstatisticsmonitorManager = std::make_unique<AppStatisticsMonitorManager>();
m_appstatisticsmonitorViewFactory = std::make_unique<AppStatisticsMonitorViewFactory>(
m_appstatisticsmonitorManager.get());
}
} // namespace AppStatisticsMonitor::Internal
#include <appstatisticsmonitorplugin.moc>

View File

@@ -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 <utils/theme/theme.h>
#include <QPaintEvent>
#include <QPainter>
#include <QPen>
#include <QPointF>
#include <QString>
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<double> 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

View File

@@ -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 <QList>
#include <QPaintEvent>
#include <QPointF>
#include <QRectF>
#include <QWidget>
namespace AppStatisticsMonitor::Internal {
class Chart : public QWidget
{
public:
Chart(const QString &name, QWidget *parent = nullptr);
void addNewPoint(const QPointF &point);
void loadNewProcessData(QList<double> data);
double lastPointX() const;
void clear();
private:
void paintEvent(QPaintEvent *event) override;
void addPoint();
QRectF calculateDataRange() const;
void updateScalingFactors(const QRectF &dataRange);
private:
QList<QPointF> m_points;
QString m_name;
double m_xScale = 1;
double m_yScale = 1;
double m_xGridStep = 1;
double m_yGridStep = 1;
};
} // namespace AppStatisticsMonitor::Internal

View File

@@ -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 <utils/expected.h>
#include <utils/fileutils.h>
#include <utils/hostosinfo.h>
#include <QByteArray>
#include <QRegularExpression>
#include <QString>
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<double> IDataProvider::memoryConsumptionHistory() const
{
return m_memoryConsumption;
}
QList<double> 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<QByteArray> 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<QByteArray> 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<double>(sysconf(_SC_CLK_TCK));
const expected_str<QByteArray> statusFileContent = status.fileContents();
const expected_str<QByteArray> uptimeFileContent = uptimeFile.fileContents();
if (!statusFileContent.has_value() || !uptimeFileContent.has_value() || clkTck == 0)
return 0;
const QList<QByteArray> 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<double>(sysconf(_SC_CLK_TCK));
// if (!status.fileContents().has_value() || !uptimeFile.fileContents().has_value() || clkTck == 0)
// return 0;
// const QVector<QByteArray> 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

View File

@@ -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 <QList>
#include <QObject>
#include <QTimer>
namespace AppStatisticsMonitor::Internal {
class IDataProvider : public QObject
{
Q_OBJECT
public:
IDataProvider(qint64 pid, QObject *parent = nullptr);
QList<double> memoryConsumptionHistory() const;
QList<double> cpuConsumptionHistory() const;
double memoryConsumptionLast() const;
double cpuConsumptionLast() const;
protected:
virtual double getMemoryConsumption() = 0;
virtual double getCpuConsumption() = 0;
QList<double> m_memoryConsumption;
QList<double> 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

View File

@@ -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 <coreplugin/session.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/runcontrol.h>
#include <QFormLayout>
#include <QGraphicsItem>
#include <QHash>
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<qint64, QString> 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

View File

@@ -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 <coreplugin/inavigationwidgetfactory.h>
#include <QComboBox>
#include <QList>
#include <QMap>
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<qint64, QString> 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<qint64, QString> m_pidNameMap;
QHash<ProjectExplorer::RunControl *, int> m_rcPidMap;
QMap<qint64, IDataProvider *> 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

View File

@@ -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 <QCoreApplication>
namespace AppStatisticsMonitor {
struct Tr
{
Q_DECLARE_TR_FUNCTIONS(AppStatisticsMonitor)
};
} // namespace AppStatisticsMonitor

View File

@@ -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)

View File

@@ -182,6 +182,8 @@ signals:
void customParsersChanged();
void runActionsUpdated();
void runControlStarted(ProjectExplorer::RunControl *runControl);
void runControlStoped(ProjectExplorer::RunControl *runControl);
void filesRenamed(const QList<std::pair<Utils::FilePath, Utils::FilePath>> &oldAndNewPaths);