From 4a09f0f0ec81be9ddb473f36ca9bb19eabac8b78 Mon Sep 17 00:00:00 2001 From: Adam Treat Date: Tue, 2 May 2023 20:31:17 -0400 Subject: [PATCH] More extensive usage stats to help diagnose errors and problems in the ui. --- CMakeLists.txt | 1 + chat.cpp | 11 ++- chat.h | 1 + chatllm.cpp | 1 - download.cpp | 19 +++++ download.h | 1 + main.qml | 6 +- network.cpp | 140 ++++++++++++++++++++++++++++++++-- network.h | 22 +++++- qml/ChatDrawer.qml | 6 +- qml/ModelDownloaderDialog.qml | 5 ++ qml/NetworkDialog.qml | 6 ++ qml/SettingsDialog.qml | 4 + sysinfo.h | 48 ++++++++++++ 14 files changed, 253 insertions(+), 18 deletions(-) create mode 100644 sysinfo.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 7aa0747a..bddcdd1f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,6 +64,7 @@ qt_add_executable(chat download.h download.cpp network.h network.cpp llm.h llm.cpp + sysinfo.h ) qt_add_qml_module(chat diff --git a/chat.cpp b/chat.cpp index e234d9d6..15430148 100644 --- a/chat.cpp +++ b/chat.cpp @@ -18,6 +18,7 @@ Chat::Chat(QObject *parent) connect(m_llmodel, &ChatLLM::threadCountChanged, this, &Chat::threadCountChanged, Qt::QueuedConnection); connect(m_llmodel, &ChatLLM::threadCountChanged, this, &Chat::syncThreadCount, Qt::QueuedConnection); connect(m_llmodel, &ChatLLM::recalcChanged, this, &Chat::recalcChanged, Qt::QueuedConnection); + connect(m_llmodel, &ChatLLM::recalcChanged, this, &Chat::handleRecalculating, Qt::QueuedConnection); connect(m_llmodel, &ChatLLM::generatedNameChanged, this, &Chat::generatedNameChanged, Qt::QueuedConnection); connect(this, &Chat::promptRequested, m_llmodel, &ChatLLM::prompt, Qt::QueuedConnection); @@ -37,7 +38,6 @@ Chat::Chat(QObject *parent) void Chat::reset() { stopGenerating(); - qDebug() << "reset blocking"; emit resetContextRequested(); // blocking queued connection m_id = Network::globalInstance()->generateUniqueId(); emit idChanged(); @@ -80,8 +80,10 @@ void Chat::responseStopped() { m_responseInProgress = false; emit responseInProgressChanged(); - if (m_llmodel->generatedName().isEmpty()) + if (m_llmodel->generatedName().isEmpty()) { + Network::globalInstance()->sendChatStarted(); emit generateNameRequested(); + } } QString Chat::modelName() const @@ -143,3 +145,8 @@ void Chat::generatedNameChanged() m_name = words.mid(0, wordCount).join(' '); emit nameChanged(); } + +void Chat::handleRecalculating() +{ + Network::globalInstance()->sendRecalculatingContext(m_chatModel->count()); +} diff --git a/chat.h b/chat.h index 8d7169c9..c81509b9 100644 --- a/chat.h +++ b/chat.h @@ -79,6 +79,7 @@ private Q_SLOTS: void responseStarted(); void responseStopped(); void generatedNameChanged(); + void handleRecalculating(); private: ChatLLM *m_llmodel; diff --git a/chatllm.cpp b/chatllm.cpp index 247410eb..4872dee3 100644 --- a/chatllm.cpp +++ b/chatllm.cpp @@ -43,7 +43,6 @@ ChatLLM::ChatLLM() connect(&m_llmThread, &QThread::started, this, &ChatLLM::loadModel); connect(this, &ChatLLM::sendStartup, Network::globalInstance(), &Network::sendStartup); connect(this, &ChatLLM::sendModelLoaded, Network::globalInstance(), &Network::sendModelLoaded); - connect(this, &ChatLLM::sendResetContext, Network::globalInstance(), &Network::sendResetContext); m_llmThread.setObjectName("llm thread"); // FIXME: Should identify these with chat name m_llmThread.start(); } diff --git a/download.cpp b/download.cpp index 1cc1f456..c639f1a1 100644 --- a/download.cpp +++ b/download.cpp @@ -1,4 +1,5 @@ #include "download.h" +#include "network.h" #include #include @@ -202,6 +203,7 @@ void Download::downloadModel(const QString &modelFile) return; } + Network::globalInstance()->sendDownloadStarted(modelFile); QNetworkRequest request("http://gpt4all.io/models/" + modelFile); QSslConfiguration conf = request.sslConfiguration(); conf.setPeerVerifyMode(QSslSocket::VerifyNone); @@ -219,6 +221,8 @@ void Download::cancelDownload(const QString &modelFile) QNetworkReply *modelReply = m_activeDownloads.keys().at(i); QUrl url = modelReply->request().url(); if (url.toString().endsWith(modelFile)) { + Network::globalInstance()->sendDownloadCanceled(modelFile); + // Disconnect the signals disconnect(modelReply, &QNetworkReply::downloadProgress, this, &Download::handleDownloadProgress); disconnect(modelReply, &QNetworkReply::finished, this, &Download::handleModelDownloadFinished); @@ -386,6 +390,20 @@ void Download::parseReleaseJsonFile(const QByteArray &jsonData) emit releaseInfoChanged(); } +void Download::handleErrorOccurred(QNetworkReply::NetworkError code) +{ + QNetworkReply *modelReply = qobject_cast(sender()); + if (!modelReply) + return; + + QString modelFilename = modelReply->url().fileName(); + qWarning() << "ERROR: Network error occurred attemptint to download" + << modelFilename + << "code:" << code + << "errorString" << modelReply->errorString(); + Network::globalInstance()->sendDownloadError(modelFilename, (int)code, modelReply->errorString()); + cancelDownload(modelFilename); +} void Download::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { @@ -509,6 +527,7 @@ void Download::handleHashAndSaveFinished(bool success, // The hash and save should send back with tempfile closed Q_ASSERT(!tempFile->isOpen()); QString modelFilename = modelReply->url().fileName(); + Network::globalInstance()->sendDownloadFinished(modelFilename, success); ModelInfo info = m_modelMap.value(modelFilename); info.calcHash = false; diff --git a/download.h b/download.h index ffd355e0..fc88ff07 100644 --- a/download.h +++ b/download.h @@ -95,6 +95,7 @@ private Q_SLOTS: void handleSslErrors(QNetworkReply *reply, const QList &errors); void handleModelsJsonDownloadFinished(); void handleReleaseJsonDownloadFinished(); + void handleErrorOccurred(QNetworkReply::NetworkError code); void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); void handleModelDownloadFinished(); void handleHashAndSaveFinished(bool success, diff --git a/main.qml b/main.qml index 3dc7c0cf..59b6dd75 100644 --- a/main.qml +++ b/main.qml @@ -290,9 +290,10 @@ Window { } onClicked: { - if (Network.isActive) + if (Network.isActive) { Network.isActive = false - else + Network.sendNetworkToggled(false); + } else networkDialog.open() } } @@ -472,6 +473,7 @@ Window { } onClicked: { + Network.sendResetContext(chatModel.count) currentChat.reset(); } } diff --git a/network.cpp b/network.cpp index 51cbf01b..dfafddf8 100644 --- a/network.cpp +++ b/network.cpp @@ -1,5 +1,6 @@ #include "network.h" #include "llm.h" +#include "sysinfo.h" #include #include @@ -13,7 +14,7 @@ //#define DEBUG -#ifdef __APPLE__ +#if defined(Q_OS_MAC) #include std::string getCPUModel() { char buffer[256]; @@ -204,11 +205,15 @@ void Network::sendModelLoaded() sendMixpanelEvent("model_load"); } -void Network::sendResetContext() +void Network::sendResetContext(int conversationLength) { if (!m_usageStatsActive) return; - sendMixpanelEvent("reset_context"); + + KeyValue kv; + kv.key = QString("length"); + kv.value = QJsonValue(conversationLength); + sendMixpanelEvent("reset_context", QVector{kv}); } void Network::sendStartup() @@ -228,7 +233,122 @@ void Network::sendCheckForUpdates() sendMixpanelEvent("check_for_updates"); } -void Network::sendMixpanelEvent(const QString &ev) +void Network::sendModelDownloaderDialog() +{ + if (!m_usageStatsActive) + return; + sendMixpanelEvent("download_dialog"); +} + +void Network::sendDownloadStarted(const QString &model) +{ + if (!m_usageStatsActive) + return; + KeyValue kv; + kv.key = QString("model"); + kv.value = QJsonValue(model); + sendMixpanelEvent("download_started", QVector{kv}); +} + +void Network::sendDownloadCanceled(const QString &model) +{ + if (!m_usageStatsActive) + return; + KeyValue kv; + kv.key = QString("model"); + kv.value = QJsonValue(model); + sendMixpanelEvent("download_canceled", QVector{kv}); +} + +void Network::sendDownloadError(const QString &model, int code, const QString &errorString) +{ + if (!m_usageStatsActive) + return; + KeyValue kv; + kv.key = QString("model"); + kv.value = QJsonValue(model); + KeyValue kvCode; + kvCode.key = QString("code"); + kvCode.value = QJsonValue(code); + KeyValue kvError; + kvError.key = QString("error"); + kvError.value = QJsonValue(errorString); + sendMixpanelEvent("download_error", QVector{kv, kvCode, kvError}); +} + +void Network::sendDownloadFinished(const QString &model, bool success) +{ + if (!m_usageStatsActive) + return; + KeyValue kv; + kv.key = QString("model"); + kv.value = QJsonValue(model); + KeyValue kvSuccess; + kvSuccess.key = QString("success"); + kvSuccess.value = QJsonValue(success); + sendMixpanelEvent("download_finished", QVector{kv, kvSuccess}); +} + +void Network::sendSettingsDialog() +{ + if (!m_usageStatsActive) + return; + sendMixpanelEvent("settings_dialog"); +} + +void Network::sendNetworkToggled(bool isActive) +{ + if (!m_usageStatsActive) + return; + KeyValue kv; + kv.key = QString("isActive"); + kv.value = QJsonValue(isActive); + sendMixpanelEvent("network_toggled", QVector{kv}); +} + +void Network::sendNewChat(int count) +{ + if (!m_usageStatsActive) + return; + KeyValue kv; + kv.key = QString("number_of_chats"); + kv.value = QJsonValue(count); + sendMixpanelEvent("new_chat", QVector{kv}); +} + +void Network::sendRemoveChat() +{ + if (!m_usageStatsActive) + return; + sendMixpanelEvent("remove_chat"); +} + +void Network::sendRenameChat() +{ + if (!m_usageStatsActive) + return; + sendMixpanelEvent("rename_chat"); +} + +void Network::sendChatStarted() +{ + if (!m_usageStatsActive) + return; + sendMixpanelEvent("chat_started"); +} + +void Network::sendRecalculatingContext(int conversationLength) +{ + if (!m_usageStatsActive) + return; + + KeyValue kv; + kv.key = QString("length"); + kv.value = QJsonValue(conversationLength); + sendMixpanelEvent("recalc_context", QVector{kv}); +} + +void Network::sendMixpanelEvent(const QString &ev, const QVector &values) { if (!m_usageStatsActive) return; @@ -250,16 +370,20 @@ void Network::sendMixpanelEvent(const QString &ev) if (ev == "startup") { const QSize display = QGuiApplication::primaryScreen()->size(); properties.insert("display", QString("%1x%2").arg(display.width()).arg(display.height())); + properties.insert("ram", getSystemTotalRAM()); #if defined(__x86_64__) || defined(__i386__) - properties.insert("avx", __builtin_cpu_supports("avx")); - properties.insert("avx2", __builtin_cpu_supports("avx2")); - properties.insert("fma", __builtin_cpu_supports("fma")); + properties.insert("avx", bool(__builtin_cpu_supports("avx"))); + properties.insert("avx2", bool(__builtin_cpu_supports("avx2"))); + properties.insert("fma", bool(__builtin_cpu_supports("fma"))); #endif -#ifdef __APPLE__ +#if defined(Q_OS_MAC) properties.insert("cpu", QString::fromStdString(getCPUModel())); #endif } + for (auto p : values) + properties.insert(p.key, p.value); + QJsonObject event; event.insert("event", ev); event.insert("properties", properties); diff --git a/network.h b/network.h index 6e7b55df..ddab64e2 100644 --- a/network.h +++ b/network.h @@ -4,6 +4,12 @@ #include #include #include +#include + +struct KeyValue { + QString key; + QJsonValue value; +}; class Network : public QObject { @@ -31,9 +37,21 @@ Q_SIGNALS: public Q_SLOTS: void sendOptOut(); void sendModelLoaded(); - void sendResetContext(); void sendStartup(); void sendCheckForUpdates(); + Q_INVOKABLE void sendModelDownloaderDialog(); + Q_INVOKABLE void sendResetContext(int conversationLength); + void sendDownloadStarted(const QString &model); + void sendDownloadCanceled(const QString &model); + void sendDownloadError(const QString &model, int code, const QString &errorString); + void sendDownloadFinished(const QString &model, bool success); + Q_INVOKABLE void sendSettingsDialog(); + Q_INVOKABLE void sendNetworkToggled(bool active); + Q_INVOKABLE void sendNewChat(int count); + Q_INVOKABLE void sendRemoveChat(); + Q_INVOKABLE void sendRenameChat(); + void sendChatStarted(); + void sendRecalculatingContext(int conversationLength); private Q_SLOTS: void handleIpifyFinished(); @@ -45,7 +63,7 @@ private Q_SLOTS: private: void sendHealth(); void sendIpify(); - void sendMixpanelEvent(const QString &event); + void sendMixpanelEvent(const QString &event, const QVector &values = QVector()); void sendMixpanel(const QByteArray &json, bool isOptOut = false); bool packageAndSendJson(const QString &ingestId, const QString &json); diff --git a/qml/ChatDrawer.qml b/qml/ChatDrawer.qml index 6b69dc45..1d581639 100644 --- a/qml/ChatDrawer.qml +++ b/qml/ChatDrawer.qml @@ -55,6 +55,7 @@ Drawer { } onClicked: { LLM.chatListModel.addChat(); + Network.sendNewChat(LLM.chatListModel.count) } } @@ -110,11 +111,9 @@ Drawer { background: Rectangle { color: "transparent" } - Keys.onReturnPressed: (event)=> { - changeName(); - } onEditingFinished: { changeName(); + Network.sendRenameChat() } function changeName() { LLM.chatListModel.get(index).name = chatName.text @@ -209,6 +208,7 @@ Drawer { } onClicked: { LLM.chatListModel.removeChat(LLM.chatListModel.get(index)) + Network.sendRemoveChat() } Accessible.role: Accessible.Button Accessible.name: qsTr("Confirm delete of the chat") diff --git a/qml/ModelDownloaderDialog.qml b/qml/ModelDownloaderDialog.qml index 24b26d13..ef882563 100644 --- a/qml/ModelDownloaderDialog.qml +++ b/qml/ModelDownloaderDialog.qml @@ -6,6 +6,7 @@ import QtQuick.Dialogs import QtQuick.Layouts import download import llm +import network Dialog { id: modelDownloaderDialog @@ -23,6 +24,10 @@ Dialog { radius: 10 } + onOpened: { + Network.sendModelDownloaderDialog(); + } + property string defaultModelPath: Download.defaultLocalModelsPath() property alias modelPath: settings.modelPath Settings { diff --git a/qml/NetworkDialog.qml b/qml/NetworkDialog.qml index 1d1a4dad..775f6672 100644 --- a/qml/NetworkDialog.qml +++ b/qml/NetworkDialog.qml @@ -161,10 +161,16 @@ NOTE: By turning on this feature, you will be sending your data to the GPT4All O } onAccepted: { + if (Network.isActive) + return Network.isActive = true; + Network.sendNetworkToggled(true); } onRejected: { + if (!Network.isActive) + return Network.isActive = false; + Network.sendNetworkToggled(false); } } diff --git a/qml/SettingsDialog.qml b/qml/SettingsDialog.qml index 41df9818..902a0b61 100644 --- a/qml/SettingsDialog.qml +++ b/qml/SettingsDialog.qml @@ -23,6 +23,10 @@ Dialog { radius: 10 } + onOpened: { + Network.sendSettingsDialog(); + } + Theme { id: theme } diff --git a/sysinfo.h b/sysinfo.h new file mode 100644 index 00000000..4a02826f --- /dev/null +++ b/sysinfo.h @@ -0,0 +1,48 @@ +#include +#include +#include +#include +#include + +#if defined(Q_OS_MAC) +#include +#include +#endif + +#if defined(Q_OS_WIN) +#include +#endif + +QString getSystemTotalRAM() +{ + qint64 totalRAM = 0; + +#if defined(Q_OS_LINUX) + QFile file("/proc/meminfo"); + if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { + QTextStream in(&file); + QString line = in.readLine(); + while (!line.isNull()) { + if (line.startsWith("MemTotal")) { + QStringList parts = line.split(QRegularExpression("\\s+")); + totalRAM = parts[1].toLongLong() * 1024; // Convert from KB to bytes + break; + } + line = in.readLine(); + } + file.close(); + } +#elif defined(Q_OS_MAC) + int mib[2] = {CTL_HW, HW_MEMSIZE}; + size_t length = sizeof(totalRAM); + sysctl(mib, 2, &totalRAM, &length, NULL, 0); +#elif defined(Q_OS_WIN) + MEMORYSTATUSEX memoryStatus; + memoryStatus.dwLength = sizeof(memoryStatus); + GlobalMemoryStatusEx(&memoryStatus); + totalRAM = memoryStatus.ullTotalPhys; +#endif + + double totalRAM_GB = static_cast(totalRAM) / (1024 * 1024 * 1024); + return QString::number(totalRAM_GB, 'f', 2) + " GB"; +}