Skip to content

Commit

Permalink
Add InfluxDB instrumentation to Lotus Tracker (#108)
Browse files Browse the repository at this point in the history
* Add installation UUID to AppSettings to support identifying unique installs

* Add minimal version of orca-zhang/influxdb-cpp for InfluxDB UDP access

* Add metric recording client util method and instrument app starts

* Allow tags and fields to be interleaved; record version in lt_app_start
  • Loading branch information
joolean authored and edipo2s committed Jun 20, 2019
1 parent 895b98d commit 822d665
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 1 deletion.
5 changes: 4 additions & 1 deletion LotusTracker.pro
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down Expand Up @@ -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 \
Expand Down
8 changes: 8 additions & 0 deletions src/lotustracker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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()
Expand Down
13 changes: 13 additions & 0 deletions src/utils/appsettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
#include <QDesktopWidget>
#include <QDir>
#include <QStandardPaths>
#include <QString>
#include <QUuid>

#define KEY_INSTALLATION_UUID "installationUuid"
#define KEY_AUTOSTART "autoStart"
#define KEY_AUTOUPDATE "autoUpdate"
#define KEY_FIRST_RUN "isFirstRun"
Expand Down Expand Up @@ -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();
Expand Down
2 changes: 2 additions & 0 deletions src/utils/appsettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "../entity/user.h"

#include <QSettings>
#include <QString>

class AppSettings : public QObject
{
Expand All @@ -16,6 +17,7 @@ class AppSettings : public QObject
public:
explicit AppSettings(QObject *parent = nullptr);

QString getInstallationUuid();
bool isAutoStartEnabled();
void enableAutoStart(bool enabled);
bool isAutoUpdateEnabled();
Expand Down
164 changes: 164 additions & 0 deletions src/utils/influxdb.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/*
influxdb-cpp -- 💜 C++ client for InfluxDB.
Copyright (c) 2010-2018 <http://ez8.co> <[email protected]>
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 <sstream>
#include <cstring>
#include <cstdio>
#include <memory>
#include <QHostInfo>
#include <QString>
#include <QUdpSocket>

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<detail::tag_or_field_caller&>(*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<detail::tag_or_field_caller&>(*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<detail::tag_or_field_caller&>(*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<detail::tag_or_field_caller&>(*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<detail::tag_or_field_caller&>(*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<detail::tag_or_field_caller&>(*this);
}
detail::ts_caller& _ts(long long ts) {
lines_ << ' ' << ts;

return reinterpret_cast<detail::ts_caller&>(*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<quint16>(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<int>(pos - start));
ret << '\\' << src[pos];
start = ++pos;
}
ret.write(src.c_str() + start, static_cast<int>(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<long long>(ts)); }
private:
detail::tag_or_field_caller& meas(const std::string& m);
};
}
}

#endif // INFLUX_DB_HPP
10 changes: 10 additions & 0 deletions src/utils/metrics.cpp
Original file line number Diff line number Diff line change
@@ -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);
}
8 changes: 8 additions & 0 deletions src/utils/metrics.h
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 822d665

Please sign in to comment.