diff --git a/LotusTracker.pro b/LotusTracker.pro index 39bbe88..95a00a7 100644 --- a/LotusTracker.pro +++ b/LotusTracker.pro @@ -72,8 +72,10 @@ HEADERS += \ src/ui/taboverlay.h \ src/ui/trayicon.h \ src/utils/appsettings.h \ + src/utils/influxdb.hpp \ src/utils/logger.h \ src/utils/lotusexception.h \ + src/utils/metrics.h \ src/updater/sparkleupdater.h \ src/credentials.h \ src/ganalytics.h \ @@ -108,7 +110,8 @@ SOURCES += \ src/ui/taboverlay.cpp \ src/ui/trayicon.cpp \ src/utils/appsettings.cpp \ - src/utils/logger.cpp + src/utils/logger.cpp \ + src/utils/metrics.cpp FORMS += \ src/ui/decktrackerbase.ui \ diff --git a/src/lotustracker.cpp b/src/lotustracker.cpp index 393a34f..cb47dd9 100644 --- a/src/lotustracker.cpp +++ b/src/lotustracker.cpp @@ -3,6 +3,7 @@ #include "urls.h" #include "mtg/mtgalogparser.h" #include "utils/cocoainitializer.h" +#include "utils/metrics.h" #if defined Q_OS_MAC #include "utils/macautostart.h" @@ -90,6 +91,13 @@ LotusTracker::LotusTracker(int& argc, char **argv): QApplication(argc, argv), }); checkConnection->start(3000); } + + influx_metric(influxdb_cpp::builder() + .meas("lt_app_start") + .tag("autostart", APP_SETTINGS->isAutoStartEnabled() ? "true" : "false") + .tag("new", APP_SETTINGS->isFirstRun() ? "true" : "false") + .field("count", 1) + ); } LotusTracker::~LotusTracker() diff --git a/src/utils/appsettings.cpp b/src/utils/appsettings.cpp index 1c7e71f..99fef2d 100644 --- a/src/utils/appsettings.cpp +++ b/src/utils/appsettings.cpp @@ -4,7 +4,10 @@ #include #include #include +#include +#include +#define KEY_INSTALLATION_UUID "installationUuid" #define KEY_AUTOSTART "autoStart" #define KEY_AUTOUPDATE "autoUpdate" #define KEY_FIRST_RUN "isFirstRun" @@ -57,6 +60,16 @@ AppSettings::AppSettings(QObject *parent) : QObject(parent) LOGD(QString("Settings saved in %1").arg(settings.fileName())); } +QString AppSettings::getInstallationUuid() +{ + if (!settings.contains(KEY_INSTALLATION_UUID)) { + settings.setValue(KEY_INSTALLATION_UUID, QUuid::createUuid().toString(QUuid::WithoutBraces)); + settings.sync(); + } + + return settings.value(KEY_INSTALLATION_UUID).toString(); +} + bool AppSettings::isAutoStartEnabled() { return settings.value(KEY_AUTOSTART, true).toBool(); diff --git a/src/utils/appsettings.h b/src/utils/appsettings.h index 17653d8..e7b5d99 100644 --- a/src/utils/appsettings.h +++ b/src/utils/appsettings.h @@ -5,6 +5,7 @@ #include "../entity/user.h" #include +#include class AppSettings : public QObject { @@ -16,6 +17,7 @@ class AppSettings : public QObject public: explicit AppSettings(QObject *parent = nullptr); + QString getInstallationUuid(); bool isAutoStartEnabled(); void enableAutoStart(bool enabled); bool isAutoUpdateEnabled(); diff --git a/src/utils/influxdb.hpp b/src/utils/influxdb.hpp new file mode 100644 index 0000000..0d502e6 --- /dev/null +++ b/src/utils/influxdb.hpp @@ -0,0 +1,164 @@ +/* + influxdb-cpp -- 💜 C++ client for InfluxDB. + + Copyright (c) 2010-2018 + This library is released under the MIT License. + + Please see LICENSE file or visit https://github.com/orca-zhang/influxdb-cpp for details. + + Modified by HearthSim, LLC for MinGW compatibility and size. + */ +#ifndef INFLUX_DB_HPP +#define INFLUX_DB_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace influxdb_cpp { + namespace detail { + struct meas_caller; + struct tag_or_field_caller; + struct ts_caller; + } + + struct builder { + detail::tag_or_field_caller& meas(const std::string& m) { + lines_.imbue(std::locale("C")); + lines_.clear(); + return _m(m); + } + protected: + detail::tag_or_field_caller& _m(const std::string& m) { + curr_start_ = lines_.str().length(); + has_fields_ = false; + has_tags_ = false; + + lines_ << _escape(m, ", "); + return reinterpret_cast(*this); + } + detail::tag_or_field_caller& _t(const std::string& k, const std::string& v) { + std::string kv = "," + _escape(k, ",= ") + "=" + _escape(v, ",= "); + + if (has_fields_) { + std::string data = lines_.str(); + data.insert(has_tags_ ? data.find(',', curr_start_) : data.find(' ', curr_start_), kv); + + lines_.str(""); + lines_.clear(); + lines_ << data; + } else { + lines_ << kv; + } + + has_tags_ = true; + return reinterpret_cast(*this); + } + detail::tag_or_field_caller& _f_s(const std::string& k, const std::string& v) { + lines_ << (has_fields_ ? ',' : ' '); + lines_ << _escape(k, ",= "); + lines_ << "=\""; + lines_ << _escape(v, "\""); + lines_ << '\"'; + + has_fields_ = true; + + return reinterpret_cast(*this); + } + detail::tag_or_field_caller& _f_i(const std::string& k, long long v) { + lines_ << (has_fields_ ? ',' : ' '); + lines_ << _escape(k, ",= "); + lines_ << '='; + lines_ << v << 'i'; + + has_fields_ = true; + + return reinterpret_cast(*this); + } + detail::tag_or_field_caller& _f_f(const std::string& k, double v, int prec) { + lines_ << (has_fields_ ? ',' : ' '); + lines_ << _escape(k, ",= "); + lines_.precision(prec); + lines_ << '=' << v; + + has_fields_ = true; + + return reinterpret_cast(*this); + } + detail::tag_or_field_caller& _f_b(const std::string& k, bool v) { + lines_ << (has_fields_ ? ',' : ' '); + lines_ << _escape(k, ",= "); + lines_ << '=' << (v ? 't' : 'f'); + + has_fields_ = true; + + return reinterpret_cast(*this); + } + detail::ts_caller& _ts(long long ts) { + lines_ << ' ' << ts; + + return reinterpret_cast(*this); + } + int _send_udp(const std::string& host, int port) { + QHostInfo info = QHostInfo::fromName(QString(host.c_str())); + + if (info.error() == QHostInfo::NoError) { + QHostAddress address = info.addresses().first(); + QUdpSocket *sock = new QUdpSocket(); + + lines_ << '\n'; + + if (sock->writeDatagram(&lines_.str()[0], lines_.str().length(), address, static_cast(port)) < lines_.str().length()) { + return -3; + } + } else { + return -1; + } + + return 0; + } + std::string _escape(const std::string& src, const char* escape_seq) { + size_t pos = 0, start = 0; + std::stringstream ret; + while((pos = src.find_first_of(escape_seq, start)) != std::string::npos) { + ret.write(src.c_str() + start, static_cast(pos - start)); + ret << '\\' << src[pos]; + start = ++pos; + } + ret.write(src.c_str() + start, static_cast(src.length() - start)); + return ret.str(); + } + + std::stringstream lines_; + + unsigned int curr_start_; + bool has_fields_; + bool has_tags_; + }; + + namespace detail { + struct ts_caller : public builder { + detail::tag_or_field_caller& meas(const std::string& m) { lines_ << '\n'; return _m(m); } + int send_udp(const std::string& host, int port) { return _send_udp(host, port); } + }; + struct tag_or_field_caller : public ts_caller { + detail::tag_or_field_caller& tag(const std::string& k, const std::string& v) { return _t(k, v); } + detail::tag_or_field_caller& field(const std::string& k, const std::string& v) { return _f_s(k, v); } + detail::tag_or_field_caller& field(const std::string& k, bool v) { return _f_b(k, v); } + detail::tag_or_field_caller& field(const std::string& k, short v) { return _f_i(k, v); } + detail::tag_or_field_caller& field(const std::string& k, int v) { return _f_i(k, v); } + detail::tag_or_field_caller& field(const std::string& k, long v) { return _f_i(k, v); } + detail::tag_or_field_caller& field(const std::string& k, long long v) { return _f_i(k, v); } + detail::tag_or_field_caller& field(const std::string& k, double v, int prec = 2) { return _f_f(k, v, prec); } + detail::ts_caller& timestamp(unsigned long long ts) { return _ts(static_cast(ts)); } + private: + detail::tag_or_field_caller& meas(const std::string& m); + }; + } +} + +#endif // INFLUX_DB_HPP diff --git a/src/utils/metrics.cpp b/src/utils/metrics.cpp new file mode 100644 index 0000000..1fadfaa --- /dev/null +++ b/src/utils/metrics.cpp @@ -0,0 +1,10 @@ +#include "../macros.h" +#include "influxdb.hpp" +#include "metrics.h" + +void influx_metric(influxdb_cpp::detail::tag_or_field_caller &builder) { + builder + .tag("version", LOTUS_TRACKER->applicationVersion().toStdString()) + .field("user", LOTUS_TRACKER->appSettings->getInstallationUuid().toStdString()) + .send_udp("metrics.hearthsim.net", 8094); +} diff --git a/src/utils/metrics.h b/src/utils/metrics.h new file mode 100644 index 0000000..fab7c9d --- /dev/null +++ b/src/utils/metrics.h @@ -0,0 +1,8 @@ +#ifndef METRICS_H +#define METRICS_H + +#include "influxdb.hpp" + +void influx_metric(influxdb_cpp::detail::tag_or_field_caller &); + +#endif // METRICS_H