mirror of
https://github.com/lubosz/overte.git
synced 2025-04-27 02:15:38 +02:00
Merge pull request #280 from daleglass/prometheus_exporter
Add Prometheus exporter
This commit is contained in:
commit
953ade0e98
12 changed files with 653 additions and 13 deletions
assignment-client/src
domain-server
resources
src
libraries
|
@ -176,7 +176,7 @@ std::pair<AssetUtils::BakingStatus, QString> AssetServer::getAssetStatus(const A
|
|||
} else if (loaded && meta.failedLastBake) {
|
||||
return { AssetUtils::Error, meta.lastBakeErrors };
|
||||
}
|
||||
|
||||
|
||||
return { AssetUtils::Pending, "" };
|
||||
}
|
||||
|
||||
|
@ -199,7 +199,7 @@ void AssetServer::maybeBake(const AssetUtils::AssetPath& path, const AssetUtils:
|
|||
void AssetServer::createEmptyMetaFile(const AssetUtils::AssetHash& hash) {
|
||||
QString metaFilePath = "atp:/" + hash + "/meta.json";
|
||||
QFile metaFile { metaFilePath };
|
||||
|
||||
|
||||
if (!metaFile.exists()) {
|
||||
qDebug() << "Creating metafile for " << hash;
|
||||
if (metaFile.open(QFile::WriteOnly)) {
|
||||
|
@ -285,7 +285,7 @@ void updateConsumedCores() {
|
|||
auto coreCount = std::thread::hardware_concurrency();
|
||||
if (isInterfaceRunning) {
|
||||
coreCount = coreCount > MIN_CORES_FOR_MULTICORE ? CPU_AFFINITY_COUNT_HIGH : CPU_AFFINITY_COUNT_LOW;
|
||||
}
|
||||
}
|
||||
qCDebug(asset_server) << "Setting max consumed cores to " << coreCount;
|
||||
setMaxCores(coreCount);
|
||||
}
|
||||
|
@ -931,6 +931,9 @@ void AssetServer::sendStatsPacket() {
|
|||
connectionStats["5. Period (us)"] = stats.packetSendPeriod;
|
||||
connectionStats["6. Up (Mb/s)"] = stats.sentBytes * megabitsPerSecPerByte;
|
||||
connectionStats["7. Down (Mb/s)"] = stats.receivedBytes * megabitsPerSecPerByte;
|
||||
connectionStats["last_heard_time_msecs"] = date.toUTC().toMSecsSinceEpoch();
|
||||
connectionStats["last_heard_ago_msecs"] = date.msecsTo(QDateTime::currentDateTime());
|
||||
|
||||
nodeStats["Connection Stats"] = connectionStats;
|
||||
|
||||
using Events = udt::ConnectionStats::Stats::Event;
|
||||
|
@ -1147,7 +1150,7 @@ bool AssetServer::deleteMappings(const AssetUtils::AssetPathList& paths) {
|
|||
hashesToCheckForDeletion << it->second;
|
||||
|
||||
qCDebug(asset_server) << "Deleted a mapping:" << path << "=>" << it->second;
|
||||
|
||||
|
||||
_fileMappings.erase(it);
|
||||
} else {
|
||||
qCDebug(asset_server) << "Unable to delete a mapping that was not found:" << path;
|
||||
|
|
|
@ -668,6 +668,12 @@ QJsonObject AudioMixerClientData::getAudioStreamStats() {
|
|||
downstreamStats["min_gap_30s"] = formatUsecTime(streamStats._timeGapWindowMin);
|
||||
downstreamStats["max_gap_30s"] = formatUsecTime(streamStats._timeGapWindowMax);
|
||||
downstreamStats["avg_gap_30s"] = formatUsecTime(streamStats._timeGapWindowAverage);
|
||||
downstreamStats["min_gap_usecs"] = static_cast<double>(streamStats._timeGapMin);
|
||||
downstreamStats["max_gap_usecs"] = static_cast<double>(streamStats._timeGapMax);
|
||||
downstreamStats["avg_gap_usecs"] = static_cast<double>(streamStats._timeGapAverage);
|
||||
downstreamStats["min_gap_30s_usecs"] = static_cast<double>(streamStats._timeGapWindowMin);
|
||||
downstreamStats["max_gap_30s_usecs"] = static_cast<double>(streamStats._timeGapWindowMax);
|
||||
downstreamStats["avg_gap_30s_usecs"] = static_cast<double>(streamStats._timeGapWindowAverage);
|
||||
|
||||
result["downstream"] = downstreamStats;
|
||||
|
||||
|
@ -695,6 +701,13 @@ QJsonObject AudioMixerClientData::getAudioStreamStats() {
|
|||
upstreamStats["max_gap_30s"] = formatUsecTime(streamStats._timeGapWindowMax);
|
||||
upstreamStats["avg_gap_30s"] = formatUsecTime(streamStats._timeGapWindowAverage);
|
||||
|
||||
upstreamStats["min_gap_usecs"] = static_cast<double>(streamStats._timeGapMin);
|
||||
upstreamStats["max_gap_usecs"] = static_cast<double>(streamStats._timeGapMax);
|
||||
upstreamStats["avg_gap_usecs"] = static_cast<double>(streamStats._timeGapAverage);
|
||||
upstreamStats["min_gap_30s_usecs"] = static_cast<double>(streamStats._timeGapWindowMin);
|
||||
upstreamStats["max_gap_30s_usecs"] = static_cast<double>(streamStats._timeGapWindowMax);
|
||||
upstreamStats["avg_gap_30s_usecs"] = static_cast<double>(streamStats._timeGapWindowAverage);
|
||||
|
||||
result["upstream"] = upstreamStats;
|
||||
} else {
|
||||
result["upstream"] = "mic unknown";
|
||||
|
@ -725,6 +738,12 @@ QJsonObject AudioMixerClientData::getAudioStreamStats() {
|
|||
upstreamStats["max_gap_30s"] = formatUsecTime(streamStats._timeGapWindowMax);
|
||||
upstreamStats["avg_gap_30s"] = formatUsecTime(streamStats._timeGapWindowAverage);
|
||||
|
||||
upstreamStats["min_gap_usecs"] = static_cast<double>(streamStats._timeGapMin);
|
||||
upstreamStats["max_gap_usecs"] = static_cast<double>(streamStats._timeGapMax);
|
||||
upstreamStats["avg_gap_usecs"] = static_cast<double>(streamStats._timeGapAverage);
|
||||
upstreamStats["min_gap_30s_usecs"] = static_cast<double>(streamStats._timeGapWindowMin);
|
||||
upstreamStats["max_gap_30s_usecs"] = static_cast<double>(streamStats._timeGapWindowMax);
|
||||
upstreamStats["avg_gap_30s_usecs"] = static_cast<double>(streamStats._timeGapWindowAverage);
|
||||
injectorArray.push_back(upstreamStats);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,6 +89,7 @@ int OctreeServer::_shortProcessWait = 0;
|
|||
int OctreeServer::_noProcessWait = 0;
|
||||
|
||||
static const QString PERSIST_FILE_DOWNLOAD_PATH = "/models.json.gz";
|
||||
static const double NANOSECONDS_PER_SECOND = 1000000.0;;
|
||||
|
||||
|
||||
void OctreeServer::resetSendingStats() {
|
||||
|
@ -1197,7 +1198,7 @@ void OctreeServer::domainSettingsRequestComplete() {
|
|||
} else {
|
||||
beginRunning();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OctreeServer::beginRunning() {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
@ -1344,6 +1345,10 @@ QString OctreeServer::getUptime() {
|
|||
return formattedUptime;
|
||||
}
|
||||
|
||||
double OctreeServer::getUptimeSeconds() {
|
||||
return (usecTimestampNow() - _startedUSecs) / NANOSECONDS_PER_SECOND;
|
||||
}
|
||||
|
||||
QString OctreeServer::getFileLoadTime() {
|
||||
QString result;
|
||||
if (isInitialLoadComplete()) {
|
||||
|
@ -1386,6 +1391,10 @@ QString OctreeServer::getFileLoadTime() {
|
|||
return result;
|
||||
}
|
||||
|
||||
double OctreeServer::getFileLoadTimeSeconds() {
|
||||
return getLoadElapsedTime() / NANOSECONDS_PER_SECOND;
|
||||
}
|
||||
|
||||
QString OctreeServer::getConfiguration() {
|
||||
QString result;
|
||||
for (int i = 1; i < _argc; i++) {
|
||||
|
@ -1421,6 +1430,8 @@ void OctreeServer::sendStatsPacket() {
|
|||
statsArray1["4. persistFileLoadTime"] = getFileLoadTime();
|
||||
statsArray1["5. clients"] = getCurrentClientCount();
|
||||
statsArray1["6. threads"] = threadsStats;
|
||||
statsArray1["uptime_seconds"] = getUptimeSeconds();
|
||||
statsArray1["persistFileLoadTime_seconds"] = getFileLoadTimeSeconds();
|
||||
|
||||
// Octree Stats
|
||||
QJsonObject octreeStats;
|
||||
|
|
|
@ -158,7 +158,9 @@ protected:
|
|||
void initHTTPManager(int port);
|
||||
void resetSendingStats();
|
||||
QString getUptime();
|
||||
double getUptimeSeconds();
|
||||
QString getFileLoadTime();
|
||||
double getFileLoadTimeSeconds();
|
||||
QString getConfiguration();
|
||||
QString getStatusLink();
|
||||
|
||||
|
|
|
@ -57,6 +57,29 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Monitoring",
|
||||
"name": "monitoring",
|
||||
"restart": false,
|
||||
"settings": [
|
||||
{
|
||||
"name": "enable_prometheus_exporter",
|
||||
"label": "Enable Prometheus Exporter",
|
||||
"help": "Enable a Prometheus exporter to make it possible to gather the stats that are available at <a href='/'>Nodes</a> tab with a <a href='https://prometheus.io/'>Prometheus</a> server. This makes it possible to keep track of long-term domain statistics for graphing, troubleshooting, and performance monitoring.",
|
||||
"default": false,
|
||||
"type": "checkbox",
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "prometheus_exporter_port",
|
||||
"label": "Prometheus TCP Port",
|
||||
"help": "This is the port where the Prometheus exporter accepts connections. It listens both on IPv4 and IPv6 and can be accessed remotely, so you should make sure to restrict access with a firewall as needed.",
|
||||
"default": "9703",
|
||||
"type": "int",
|
||||
"advanced": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Paths",
|
||||
"html_id": "paths",
|
||||
|
|
14
domain-server/resources/prometheus_exporter/index.html
Normal file
14
domain-server/resources/prometheus_exporter/index.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Vircadia Prometheus exporter</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Vircadia Prometheus exporter</h1>
|
||||
|
||||
<p>This is the <a href="https://prometheus.io/">Prometheus</a> exporter, used to export stats about the domain server for graphing and analysis.</p>
|
||||
<p>
|
||||
<a href="/metrics">Metrics</a>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
|
@ -67,6 +67,9 @@ Q_LOGGING_CATEGORY(domain_server_ice, "hifi.domain_server.ice")
|
|||
|
||||
const QString ACCESS_TOKEN_KEY_PATH = "metaverse.access_token";
|
||||
const QString DomainServer::REPLACEMENT_FILE_EXTENSION = ".replace";
|
||||
const int MIN_PORT = 1;
|
||||
const int MAX_PORT = 65535;
|
||||
|
||||
|
||||
int const DomainServer::EXIT_CODE_REBOOT = 234923;
|
||||
|
||||
|
@ -229,7 +232,6 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
this, &DomainServer::updateDownstreamNodes);
|
||||
connect(&_settingsManager, &DomainServerSettingsManager::settingsUpdated,
|
||||
this, &DomainServer::updateUpstreamNodes);
|
||||
|
||||
setupGroupCacheRefresh();
|
||||
|
||||
optionallySetupOAuth();
|
||||
|
@ -330,6 +332,8 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
_nodePingMonitorTimer = new QTimer{ this };
|
||||
connect(_nodePingMonitorTimer, &QTimer::timeout, this, &DomainServer::nodePingMonitor);
|
||||
_nodePingMonitorTimer->start(NODE_PING_MONITOR_INTERVAL_MSECS);
|
||||
|
||||
initializeExporter();
|
||||
}
|
||||
|
||||
void DomainServer::parseCommandLine(int argc, char* argv[]) {
|
||||
|
@ -424,6 +428,11 @@ DomainServer::~DomainServer() {
|
|||
_contentManager->terminate();
|
||||
}
|
||||
|
||||
if (_httpExporterManager) {
|
||||
_httpExporterManager->close();
|
||||
delete _httpExporterManager;
|
||||
}
|
||||
|
||||
DependencyManager::destroy<AccountManager>();
|
||||
|
||||
// cleanup the AssetClient thread
|
||||
|
@ -1977,7 +1986,6 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
const QString URI_API_BACKUPS_ID = "/api/backups/";
|
||||
const QString URI_API_BACKUPS_DOWNLOAD_ID = "/api/backups/download/";
|
||||
const QString URI_API_BACKUPS_RECOVER = "/api/backups/recover/";
|
||||
|
||||
const QString UUID_REGEX_STRING = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
|
||||
|
||||
QPointer<HTTPConnection> connectionPtr { connection };
|
||||
|
@ -3037,6 +3045,27 @@ void DomainServer::updateUpstreamNodes() {
|
|||
updateReplicationNodes(Upstream);
|
||||
}
|
||||
|
||||
void DomainServer::initializeExporter()
|
||||
{
|
||||
static const QString ENABLE_EXPORTER = "monitoring.enable_prometheus_exporter";
|
||||
static const QString EXPORTER_PORT = "monitoring.prometheus_exporter_port";
|
||||
|
||||
bool isExporterEnabled = _settingsManager.valueOrDefaultValueForKeyPath(ENABLE_EXPORTER).toBool();
|
||||
int exporterPort = _settingsManager.valueOrDefaultValueForKeyPath(EXPORTER_PORT).toInt();
|
||||
|
||||
if (exporterPort < MIN_PORT || exporterPort > MAX_PORT) {
|
||||
qCWarning(domain_server) << "Prometheus exporter port " << exporterPort << " is out of range.";
|
||||
isExporterEnabled = false;
|
||||
}
|
||||
|
||||
qCDebug(domain_server) << "Setting up Prometheus exporter";
|
||||
|
||||
if (isExporterEnabled && !_httpExporterManager) {
|
||||
qCInfo(domain_server) << "Starting Prometheus exporter on port " << exporterPort;
|
||||
_httpExporterManager = new HTTPManager(QHostAddress::Any, (quint16)exporterPort, QString("%1/resources/prometheus_exporter/").arg(QCoreApplication::applicationDirPath()), &_exporter);
|
||||
}
|
||||
}
|
||||
|
||||
void DomainServer::updateReplicatedNodes() {
|
||||
// Make sure we have downstream nodes in our list
|
||||
static const QString REPLICATED_USERS_KEY = "users";
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
#include "DomainContentBackupManager.h"
|
||||
|
||||
#include "PendingAssignedNodeData.h"
|
||||
#include "DomainServerExporter.h"
|
||||
|
||||
#include <QLoggingCategory>
|
||||
|
||||
|
@ -115,7 +116,7 @@ private slots:
|
|||
void sendHeartbeatToIceServer();
|
||||
void nodePingMonitor();
|
||||
|
||||
void handleConnectedNode(SharedNodePointer newNode, quint64 requestReceiveTime);
|
||||
void handleConnectedNode(SharedNodePointer newNode, quint64 requestReceiveTime);
|
||||
void handleTempDomainSuccess(QNetworkReply* requestReply);
|
||||
void handleTempDomainError(QNetworkReply* requestReply);
|
||||
|
||||
|
@ -138,6 +139,7 @@ private slots:
|
|||
void updateReplicatedNodes();
|
||||
void updateDownstreamNodes();
|
||||
void updateUpstreamNodes();
|
||||
void initializeExporter();
|
||||
|
||||
void tokenGrantFinished();
|
||||
void profileRequestFinished();
|
||||
|
@ -234,8 +236,10 @@ private:
|
|||
std::vector<QString> _replicatedUsernames;
|
||||
|
||||
DomainGatekeeper _gatekeeper;
|
||||
DomainServerExporter _exporter;
|
||||
|
||||
HTTPManager _httpManager;
|
||||
HTTPManager* _httpExporterManager { nullptr };
|
||||
std::unique_ptr<HTTPSManager> _httpsManager;
|
||||
|
||||
QHash<QUuid, SharedAssignmentPointer> _allAssignments;
|
||||
|
|
471
domain-server/src/DomainServerExporter.cpp
Normal file
471
domain-server/src/DomainServerExporter.cpp
Normal file
|
@ -0,0 +1,471 @@
|
|||
//
|
||||
// DomainServerExporter.cpp
|
||||
// domain-server/src
|
||||
//
|
||||
// Created by Dale Glass on 3 Apr 2020.
|
||||
// Copyright 2020 Dale Glass
|
||||
//
|
||||
// Prometheus exporter
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
// TODO:
|
||||
//
|
||||
// Look into the data provided by OctreeServer::handleHTTPRequest in assignment-client/src/octree/OctreeServer.cpp
|
||||
// Turns out the octree server (entity server) can optionally deliver additional statistics via another HTTP server
|
||||
// that is disabled by default. This functionality can be enabled by setting statusPort to a port number.
|
||||
//
|
||||
// Look into what appears in Audio Mixer -> z_listeners -> jitter -> injectors, so far it's been an empty list.
|
||||
|
||||
#include <QLoggingCategory>
|
||||
#include <QUrl>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonDocument>
|
||||
#include <QRegularExpression>
|
||||
#include <QSet>
|
||||
|
||||
#include "DomainServerExporter.h"
|
||||
#include "DependencyManager.h"
|
||||
#include "LimitedNodeList.h"
|
||||
#include "HTTPConnection.h"
|
||||
#include "DomainServerNodeData.h"
|
||||
|
||||
Q_LOGGING_CATEGORY(domain_server_exporter, "hifi.domain_server.prometheus_exporter")
|
||||
|
||||
static const QMap<QString, DomainServerExporter::MetricType> TYPE_MAP {
|
||||
{ "asset_server_assignment_stats_num_queued_check_ins" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "asset_server_connection_stats_cw_p" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "asset_server_connection_stats_down_mb_s" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "asset_server_connection_stats_est_max_p_s" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "asset_server_connection_stats_last_heard_ago_msecs" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "asset_server_connection_stats_last_heard_time_msecs" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "asset_server_connection_stats_period_us" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "asset_server_connection_stats_rtt_ms" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "asset_server_connection_stats_up_mb_s" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "asset_server_downstream_stats_duplicates" , DomainServerExporter::MetricType::Counter },
|
||||
{ "asset_server_downstream_stats_recvd_p_s" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "asset_server_downstream_stats_recvd_packets" , DomainServerExporter::MetricType::Counter },
|
||||
{ "asset_server_downstream_stats_sent_ack" , DomainServerExporter::MetricType::Counter },
|
||||
{ "asset_server_io_stats_inbound_kbps" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "asset_server_io_stats_inbound_pps" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "asset_server_io_stats_outbound_kbps" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "asset_server_io_stats_outbound_pps" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "asset_server_upstream_stats_procd_ack" , DomainServerExporter::MetricType::Counter },
|
||||
{ "asset_server_upstream_stats_recvd_ack" , DomainServerExporter::MetricType::Counter },
|
||||
{ "asset_server_upstream_stats_retransmitted" , DomainServerExporter::MetricType::Counter },
|
||||
{ "asset_server_upstream_stats_sent_p_s" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "asset_server_upstream_stats_sent_packets" , DomainServerExporter::MetricType::Counter },
|
||||
{ "audio_mixer_assignment_stats_num_queued_check_ins" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_avg_listeners_per_frame" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_avg_listeners_silent_per_frame" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_avg_streams_per_frame" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_avg_timing_stats_us_per_check_time" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_avg_timing_stats_us_per_check_time_trailing" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_avg_timing_stats_us_per_events" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_avg_timing_stats_us_per_events_trailing" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_avg_timing_stats_us_per_frame" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_avg_timing_stats_us_per_frame_trailing" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_avg_timing_stats_us_per_mix" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_avg_timing_stats_us_per_mix_trailing" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_avg_timing_stats_us_per_packets" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_avg_timing_stats_us_per_packets_trailing" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_avg_timing_stats_us_per_sleep" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_avg_timing_stats_us_per_sleep_trailing" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_avg_timing_stats_us_per_tic" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_avg_timing_stats_us_per_tic_trailing" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_io_stats_inbound_kbps" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_io_stats_inbound_pps" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_io_stats_outbound_kbps" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_io_stats_outbound_pps" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_listeners_jitter_downstream_available" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_listeners_jitter_downstream_available_avg_10s" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_listeners_jitter_downstream_avg_gap_30s_usecs" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_listeners_jitter_downstream_avg_gap_usecs" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_listeners_jitter_downstream_desired" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_listeners_jitter_downstream_lost_percent" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_listeners_jitter_downstream_lost_percent_30s" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_listeners_jitter_downstream_max_gap_30s_usecs" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_listeners_jitter_downstream_max_gap_usecs" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_listeners_jitter_downstream_min_gap_30s_usecs" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_listeners_jitter_downstream_min_gap_usecs" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_listeners_jitter_downstream_not_mixed" , DomainServerExporter::MetricType::Counter },
|
||||
{ "audio_mixer_listeners_jitter_downstream_overflows" , DomainServerExporter::MetricType::Counter },
|
||||
{ "audio_mixer_listeners_jitter_downstream_starves" , DomainServerExporter::MetricType::Counter },
|
||||
{ "audio_mixer_listeners_jitter_downstream_unplayed" , DomainServerExporter::MetricType::Counter },
|
||||
{ "audio_mixer_listeners_jitter_injectors" , DomainServerExporter::MetricType::Counter },
|
||||
{ "audio_mixer_listeners_jitter_upstream_available" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_listeners_jitter_upstream_available_avg_10s" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_listeners_jitter_upstream_avg_gap_30s_usecs" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_listeners_jitter_upstream_avg_gap_usecs" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_listeners_jitter_upstream_desired_calc" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_listeners_jitter_upstream_lost_percent" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_listeners_jitter_upstream_lost_percent_30s" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_listeners_jitter_upstream_max_gap_30s_usecs" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_listeners_jitter_upstream_max_gap_usecs" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_listeners_jitter_upstream_mic_desired" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_listeners_jitter_upstream_min_gap_30s_usecs" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_listeners_jitter_upstream_min_gap_usecs" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_listeners_jitter_upstream_not_mixed" , DomainServerExporter::MetricType::Counter },
|
||||
{ "audio_mixer_listeners_jitter_upstream_overflows" , DomainServerExporter::MetricType::Counter },
|
||||
{ "audio_mixer_listeners_jitter_upstream_silents_dropped" , DomainServerExporter::MetricType::Counter },
|
||||
{ "audio_mixer_listeners_jitter_upstream_starves" , DomainServerExporter::MetricType::Counter },
|
||||
{ "audio_mixer_listeners_jitter_upstream_unplayed" , DomainServerExporter::MetricType::Counter },
|
||||
{ "audio_mixer_listeners_outbound_kbps" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_mix_stats_active_streams" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_mix_stats_active_to_inactive" , DomainServerExporter::MetricType::Counter },
|
||||
{ "audio_mixer_mix_stats_active_to_skippped" , DomainServerExporter::MetricType::Counter },
|
||||
{ "audio_mixer_mix_stats_avg_mixes_per_block" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_mix_stats_hrtf_renders" , DomainServerExporter::MetricType::Counter },
|
||||
{ "audio_mixer_mix_stats_hrtf_resets" , DomainServerExporter::MetricType::Counter },
|
||||
{ "audio_mixer_mix_stats_hrtf_updates" , DomainServerExporter::MetricType::Counter },
|
||||
{ "audio_mixer_mix_stats_inactive_streams" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_mix_stats_inactive_to_active" , DomainServerExporter::MetricType::Counter },
|
||||
{ "audio_mixer_mix_stats_inactive_to_skippped" , DomainServerExporter::MetricType::Counter },
|
||||
{ "audio_mixer_mix_stats_percent_hrtf_mixes" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_mix_stats_percent_manual_echo_mixes" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_mix_stats_percent_manual_stereo_mixes" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_mix_stats_skipped_streams" , DomainServerExporter::MetricType::Counter },
|
||||
{ "audio_mixer_mix_stats_skippped_to_active" , DomainServerExporter::MetricType::Counter },
|
||||
{ "audio_mixer_mix_stats_skippped_to_inactive" , DomainServerExporter::MetricType::Counter },
|
||||
{ "audio_mixer_mix_stats_total_mixes" , DomainServerExporter::MetricType::Counter },
|
||||
{ "audio_mixer_silent_packets_per_frame" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_threads" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_throttling_ratio" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_trailing_mix_ratio" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "audio_mixer_use_dynamic_jitter_buffers" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_assignment_stats_num_queued_check_ins" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_avatars_av_data_receive_rate" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_avatars_avg_other_av_skips_per_second" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_avatars_avg_other_av_starves_per_second" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_avatars_delta_full_vs_avatar_data_kbps" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_avatars_inbound_av_data_kbps" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_avatars_inbound_kbps" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_avatars_num_avs_sent_last_frame" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_avatars_outbound_av_data_kbps" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_avatars_outbound_av_traits_kbps" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_avatars_outbound_kbps" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_avatars_recent_other_av_in_view" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_avatars_recent_other_av_out_of_view" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_avatars_total_num_out_of_order_sends" , DomainServerExporter::MetricType::Counter },
|
||||
{ "avatar_mixer_average_listeners_last_second" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_broadcast_loop_rate" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_io_stats_inbound_kbps" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_io_stats_inbound_pps" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_io_stats_outbound_kbps" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_io_stats_outbound_pps" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_parallel_tasks_broadcast_avatar_data_functor" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_parallel_tasks_broadcast_avatar_data_innner" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_parallel_tasks_broadcast_avatar_data_lock_wait" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_parallel_tasks_broadcast_avatar_data_node_transform" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_parallel_tasks_broadcast_avatar_data_total" , DomainServerExporter::MetricType::Counter },
|
||||
{ "avatar_mixer_parallel_tasks_display_name_management_total" , DomainServerExporter::MetricType::Counter },
|
||||
{ "avatar_mixer_parallel_tasks_process_queued_avatar_data_packets_lock_wait" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_parallel_tasks_process_queued_avatar_data_packets_total" , DomainServerExporter::MetricType::Counter },
|
||||
{ "avatar_mixer_single_core_tasks_incoming_packets_handle_avatar_identity_packet" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_single_core_tasks_incoming_packets_handle_avatar_query_packet" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_single_core_tasks_incoming_packets_handle_kill_avatar_packet" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_single_core_tasks_incoming_packets_handle_node_ignore_request_packet" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_single_core_tasks_incoming_packets_handle_radius_ignore_request_packet" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_single_core_tasks_incoming_packets_handle_requests_domain_list_data_packet" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_single_core_tasks_process_events" , DomainServerExporter::MetricType::Counter },
|
||||
{ "avatar_mixer_single_core_tasks_queue_incoming_packet" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_single_core_tasks_send_stats" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_slaves_aggregate_per_frame_received_1_nodes_processed" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_slaves_aggregate_per_frame_sent_1_nodes_broadcasted_to" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_slaves_aggregate_per_frame_sent_2_average_others_included" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_slaves_aggregate_per_frame_sent_3_average_over_budget_avatars" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_slaves_aggregate_per_frame_sent_4_average_data_bytes" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_slaves_aggregate_per_frame_sent_5_average_traits_bytes" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_slaves_aggregate_per_frame_sent_6_average_identity_bytes" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_slaves_aggregate_per_frame_sent_7_average_hero_avatars" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_slaves_aggregate_per_frame_timing_1_process_incoming_packets" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_slaves_aggregate_per_frame_timing_2_ignore_calculation" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_slaves_aggregate_per_frame_timing_3_to_byte_array" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_slaves_aggregate_per_frame_timing_4_avatar_data_packing" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_slaves_aggregate_per_frame_timing_5_packet_sending" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_slaves_aggregate_per_frame_timing_6_job_elapsed_time" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_threads" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_throttling_ratio" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "avatar_mixer_trailing_mix_ratio" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_script_server_assignment_stats_num_queued_check_ins" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_script_server_io_stats_inbound_kbps" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_script_server_io_stats_inbound_pps" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_script_server_io_stats_outbound_kbps" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_script_server_io_stats_outbound_pps" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_script_server_nodes_inbound_kbit_s" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_script_server_nodes_outbound_kbit_s" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_script_server_nodes_reliable_packet_s" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_script_server_nodes_unreliable_packet_s" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_script_server_octree_stats_element_count" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_script_server_octree_stats_internal_element_count" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_script_server_octree_stats_leaf_element_count" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_script_server_script_engine_stats_number_running_scripts" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_server_assignment_stats_num_queued_check_ins" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_server_entity_server_inbound_data_packet_queue" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_server_entity_server_inbound_data_total_elements" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_server_entity_server_inbound_data_total_packets" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_server_entity_server_inbound_timing_avg_lock_wait_time_per_element" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_server_entity_server_inbound_timing_avg_lock_wait_time_per_packet" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_server_entity_server_inbound_timing_avg_process_time_per_element" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_server_entity_server_inbound_timing_avg_process_time_per_packet" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_server_entity_server_inbound_timing_avg_transit_time_per_packet" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_server_entity_server_misc_clients" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_server_entity_server_misc_persist_file_load_time_seconds" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_server_entity_server_misc_threads_handle_pacekt_send" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_server_entity_server_misc_threads_packet_distributor" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_server_entity_server_misc_threads_processing" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_server_entity_server_misc_threads_write_datagram" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_server_entity_server_misc_uptime_seconds" , DomainServerExporter::MetricType::Counter },
|
||||
{ "entity_server_entity_server_octree_element_count" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_server_entity_server_octree_internal_element_count" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_server_entity_server_octree_leaf_element_count" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_server_entity_server_outbound_data_total_bytes" , DomainServerExporter::MetricType::Counter },
|
||||
{ "entity_server_entity_server_outbound_data_total_bytes_bit_masks" , DomainServerExporter::MetricType::Counter },
|
||||
{ "entity_server_entity_server_outbound_data_total_bytes_octal_codes" , DomainServerExporter::MetricType::Counter },
|
||||
{ "entity_server_entity_server_outbound_data_total_bytes_wasted" , DomainServerExporter::MetricType::Counter },
|
||||
{ "entity_server_entity_server_outbound_data_total_packets" , DomainServerExporter::MetricType::Counter },
|
||||
{ "entity_server_entity_server_outbound_timing_avg_compress_and_write_time" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_server_entity_server_outbound_timing_avg_encode_time" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_server_entity_server_outbound_timing_avg_inside_time" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_server_entity_server_outbound_timing_avg_loop_time" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_server_entity_server_outbound_timing_avg_send_time" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_server_entity_server_outbound_timing_avg_tree_traverse_time" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_server_entity_server_outbound_timing_node_wait_time" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_server_io_stats_inbound_kbps" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_server_io_stats_inbound_pps" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_server_io_stats_outbound_kbps" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "entity_server_io_stats_outbound_pps" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "messages_mixer_assignment_stats_num_queued_check_ins" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "messages_mixer_io_stats_inbound_kbps" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "messages_mixer_io_stats_inbound_pps" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "messages_mixer_io_stats_outbound_kbps" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "messages_mixer_io_stats_outbound_pps" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "messages_mixer_messages_inbound_kbps" , DomainServerExporter::MetricType::Gauge },
|
||||
{ "messages_mixer_messages_outbound_kbps" , DomainServerExporter::MetricType::Gauge }
|
||||
};
|
||||
|
||||
|
||||
// Things we're not going to convert for various reasons, such as containing text,
|
||||
// or having a value followed by an unit ("5.2 seconds").
|
||||
//
|
||||
// Things like text like usernames have no place in the Prometheus model, so they can be skipped.
|
||||
//
|
||||
// For numeric values with an unit, instead of trying to parse it, the stats will just need to
|
||||
// have a second copy of the metric added, with the value expressed as a number, with the original
|
||||
// being blacklisted here.
|
||||
|
||||
static const QSet<QString> BLACKLIST = {
|
||||
"asset_server_connection_stats_last_heard", // Timestamp as a string
|
||||
"asset_server_username", // Username
|
||||
"audio_mixer_listeners_jitter_downstream_avg_gap", // Number as string with unit name, alternative added
|
||||
"audio_mixer_listeners_jitter_downstream_avg_gap_30s", // Number as string with unit name, alternative added
|
||||
"audio_mixer_listeners_jitter_downstream_max_gap", // Number as string with unit name, alternative added
|
||||
"audio_mixer_listeners_jitter_downstream_max_gap_30s", // Number as string with unit name, alternative added
|
||||
"audio_mixer_listeners_jitter_downstream_min_gap", // Number as string with unit name, alternative added
|
||||
"audio_mixer_listeners_jitter_downstream_min_gap_30s", // Number as string with unit name, alternative added
|
||||
"audio_mixer_listeners_jitter_injectors", // Array, empty. TODO: check if this ever contains anything.
|
||||
"audio_mixer_listeners_jitter_upstream", // Only exists in the absence of a connection
|
||||
"audio_mixer_listeners_jitter_upstream_avg_gap", // Number as string with unit name, alternative added
|
||||
"audio_mixer_listeners_jitter_upstream_avg_gap_30s", // Number as string with unit name, alternative added
|
||||
"audio_mixer_listeners_jitter_upstream_max_gap", // Number as string with unit name, alternative added
|
||||
"audio_mixer_listeners_jitter_upstream_max_gap_30s", // Number as string with unit name, alternative added
|
||||
"audio_mixer_listeners_jitter_upstream_min_gap", // Number as string with unit name, alternative added
|
||||
"audio_mixer_listeners_jitter_upstream_min_gap_30s", // Number as string with unit name, alternative added
|
||||
"audio_mixer_listeners_username", // Username
|
||||
"avatar_mixer_avatars_display_name", // Username
|
||||
"avatar_mixer_avatars_username", // Username
|
||||
"entity_script_server_nodes_node_type", // Username
|
||||
"entity_script_server_nodes_username", // Username
|
||||
"entity_server_entity_server_misc_configuration", // Text
|
||||
"entity_server_entity_server_misc_detailed_stats_url", // URL
|
||||
"entity_server_entity_server_misc_persist_file_load_time", // Number as string with unit name, alternative added
|
||||
"entity_server_entity_server_misc_uptime", // Number as string with unit name, alternative added
|
||||
"messages_mixer_messages_username" // Username
|
||||
};
|
||||
|
||||
DomainServerExporter::DomainServerExporter() {
|
||||
}
|
||||
|
||||
bool DomainServerExporter::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler) {
|
||||
const QString URI_METRICS = "/metrics";
|
||||
const QString EXPORTER_MIME_TYPE = "text/plain";
|
||||
|
||||
qCDebug(domain_server_exporter) << "Request on URL " << url;
|
||||
|
||||
if (url.path() == URI_METRICS) {
|
||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
||||
QString output = "";
|
||||
QTextStream outStream(&output);
|
||||
|
||||
nodeList->eachNode([this, &outStream](const SharedNodePointer& node) { generateMetricsForNode(outStream, node); });
|
||||
|
||||
connection->respond(HTTPConnection::StatusCode200, output.toUtf8(), qPrintable(EXPORTER_MIME_TYPE));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QString DomainServerExporter::escapeName(const QString& name) {
|
||||
QRegularExpression invalidCharacters("[^A-Za-z0-9_]");
|
||||
|
||||
QString result = name;
|
||||
|
||||
// If a key is named something like: "6. threads", turn it into just "threads"
|
||||
result.replace(QRegularExpression("^\\d+\\. "), "");
|
||||
result.replace(QRegularExpression("^\\d+_"), "");
|
||||
|
||||
// If a key is named something like "z_listeners", turn it into just "listeners"
|
||||
result.replace(QRegularExpression("^z_"), "");
|
||||
|
||||
// If a key is named something like "lost%", change it to "lost_percent_".
|
||||
// redundant underscores will be removed below.
|
||||
result.replace(QRegularExpression("%"), "_percent_");
|
||||
|
||||
// change mixedCaseNames to mixed_case_names
|
||||
result.replace(QRegularExpression("([a-z])([A-Z])"), "\\1_\\2");
|
||||
|
||||
// Replace all invalid characters with a _
|
||||
result.replace(invalidCharacters, "_");
|
||||
|
||||
// Remove any "_" characters at the beginning or end
|
||||
result.replace(QRegularExpression("^_+"), "");
|
||||
result.replace(QRegularExpression("_+$"), "");
|
||||
|
||||
// Replace any duplicated _ characters with a single one
|
||||
result.replace(QRegularExpression("_+"), "_");
|
||||
|
||||
result = result.toLower();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void DomainServerExporter::generateMetricsForNode(QTextStream& stream, const SharedNodePointer& node) {
|
||||
QJsonObject statsObject = static_cast<DomainServerNodeData*>(node->getLinkedData())->getStatsJSONObject();
|
||||
QString nodeType = NodeType::getNodeTypeName(static_cast<NodeType_t>(node->getType()));
|
||||
|
||||
stream << "\n\n\n";
|
||||
stream << "###############################################################\n";
|
||||
stream << "# " << nodeType << "\n";
|
||||
stream << "###############################################################\n";
|
||||
|
||||
generateMetricsFromJson(stream, nodeType, escapeName(nodeType), QHash<QString, QString>(), statsObject);
|
||||
}
|
||||
|
||||
void DomainServerExporter::generateMetricsFromJson(QTextStream& stream,
|
||||
QString originalPath,
|
||||
QString path,
|
||||
QHash<QString, QString> labels,
|
||||
const QJsonObject& root) {
|
||||
for (auto iter = root.constBegin(); iter != root.constEnd(); ++iter) {
|
||||
auto escapedKey = escapeName(iter.key());
|
||||
auto metricValue = iter.value();
|
||||
auto metricName = path + "_" + escapedKey;
|
||||
auto origMetricName = originalPath + " -> " + iter.key();
|
||||
|
||||
if (metricValue.isObject()) {
|
||||
QUuid possible_uuid = QUuid::fromString(iter.key());
|
||||
|
||||
if (possible_uuid.isNull()) {
|
||||
generateMetricsFromJson(stream, originalPath + " -> " + iter.key(), path + "_" + escapedKey, labels,
|
||||
iter.value().toObject());
|
||||
} else {
|
||||
labels.insert("uuid", possible_uuid.toString(QUuid::WithoutBraces));
|
||||
generateMetricsFromJson(stream, originalPath, path, labels, iter.value().toObject());
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (BLACKLIST.contains(metricName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool conversionOk = false;
|
||||
double converted = 0;
|
||||
|
||||
if (metricValue.isString()) {
|
||||
// Prometheus only deals with numeric values. See if this string contains a valid one
|
||||
|
||||
QString tmp = metricValue.toString();
|
||||
converted = tmp.toDouble(&conversionOk);
|
||||
|
||||
if (!conversionOk) {
|
||||
qCWarning(domain_server_exporter) << "Failed to convert value of " << origMetricName << " (" << metricName
|
||||
<< ") to double: " << tmp << "'";
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
stream << QString("\n# HELP %1 %2 -> %3\n").arg(metricName).arg(originalPath).arg(iter.key());
|
||||
|
||||
if (TYPE_MAP.contains(metricName)) {
|
||||
stream << "# TYPE " << metricName << " ";
|
||||
switch (TYPE_MAP[metricName]) {
|
||||
case DomainServerExporter::MetricType::Untyped:
|
||||
stream << "untyped";
|
||||
break;
|
||||
case DomainServerExporter::MetricType::Counter:
|
||||
stream << "counter";
|
||||
break;
|
||||
case DomainServerExporter::MetricType::Gauge:
|
||||
stream << "gauge";
|
||||
break;
|
||||
case DomainServerExporter::MetricType::Histogram:
|
||||
stream << "histogram";
|
||||
break;
|
||||
case DomainServerExporter::MetricType::Summary:
|
||||
stream << "summary";
|
||||
break;
|
||||
}
|
||||
stream << "\n";
|
||||
} else {
|
||||
qCWarning(domain_server_exporter)
|
||||
<< "Type for metric " << origMetricName << " (" << metricName << ") not known.";
|
||||
}
|
||||
|
||||
stream << path << "_" << escapedKey;
|
||||
if (!labels.isEmpty()) {
|
||||
stream << "{";
|
||||
|
||||
bool isFirst = true;
|
||||
QHashIterator<QString, QString> iter(labels);
|
||||
|
||||
while (iter.hasNext()) {
|
||||
iter.next();
|
||||
|
||||
if (!isFirst) {
|
||||
stream << ",";
|
||||
}
|
||||
|
||||
QString escapedValue = iter.value();
|
||||
escapedValue.replace("\\", "\\\\");
|
||||
escapedValue.replace("\"", "\\\"");
|
||||
escapedValue.replace("\n", "\\\n");
|
||||
|
||||
stream << iter.key() << "=\"" << escapedValue << "\"";
|
||||
|
||||
isFirst = false;
|
||||
}
|
||||
stream << "}";
|
||||
}
|
||||
|
||||
stream << " ";
|
||||
|
||||
if (metricValue.isBool()) {
|
||||
stream << (iter.value().toBool() ? "1" : "0");
|
||||
} else if (metricValue.isDouble()) {
|
||||
stream << metricValue.toDouble();
|
||||
} else if (metricValue.isString()) {
|
||||
// Converted above
|
||||
stream << converted;
|
||||
} else {
|
||||
qCWarning(domain_server_exporter)
|
||||
<< "Can't convert metric " << origMetricName << "(" << metricName << ") with value " << metricValue;
|
||||
}
|
||||
|
||||
stream << "\n";
|
||||
}
|
||||
}
|
55
domain-server/src/DomainServerExporter.h
Normal file
55
domain-server/src/DomainServerExporter.h
Normal file
|
@ -0,0 +1,55 @@
|
|||
//
|
||||
// DomainServerExporter.h
|
||||
// domain-server/src
|
||||
//
|
||||
// Created by Dale Glass on 3 Apr 2020.
|
||||
// Copyright 2020 Dale Glass
|
||||
//
|
||||
// Prometheus exporter
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef DOMAINSERVEREXPORTER_H
|
||||
#define DOMAINSERVEREXPORTER_H
|
||||
|
||||
#include <QObject>
|
||||
#include "HTTPManager.h"
|
||||
#include "Node.h"
|
||||
#include <QTextStream>
|
||||
#include <QJsonObject>
|
||||
#include <QRegularExpression>
|
||||
#include <QHash>
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Prometheus exporter for domain stats
|
||||
*
|
||||
* This class exportors the statistics that can be seen on the domain's page in
|
||||
* a format that can be parsed by Prometheus. This is useful for troubleshooting,
|
||||
* monitoring performance, and making pretty graphs.
|
||||
*/
|
||||
class DomainServerExporter : public HTTPRequestHandler
|
||||
{
|
||||
public:
|
||||
typedef enum {
|
||||
Untyped, /* Works the same as Gauge, with the difference of signalling that the actual type is unknown */
|
||||
Counter, /* Value only goes up. Eg, number of packets received */
|
||||
Gauge, /* Current numerical value that can go up or down. Current temperature, memory usage, etc */
|
||||
Histogram, /* Samples sorted in buckets gathered over time */
|
||||
Summary
|
||||
} MetricType;
|
||||
|
||||
DomainServerExporter();
|
||||
~DomainServerExporter() = default;
|
||||
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false) override;
|
||||
|
||||
private:
|
||||
QString escapeName(const QString &name);
|
||||
void generateMetricsForNode(QTextStream& stream, const SharedNodePointer& node);
|
||||
void generateMetricsFromJson(QTextStream& stream, QString originalPath, QString path, QHash<QString, QString> labels, const QJsonObject& obj);
|
||||
};
|
||||
|
||||
#endif // DOMAINSERVEREXPORTER_H
|
|
@ -34,7 +34,7 @@ class HTTPManager : public QTcpServer, public HTTPRequestHandler {
|
|||
public:
|
||||
/// Initializes the manager.
|
||||
HTTPManager(const QHostAddress& listenAddress, quint16 port, const QString& documentRoot, HTTPRequestHandler* requestHandler = nullptr);
|
||||
|
||||
|
||||
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false) override;
|
||||
|
||||
private slots:
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
#include "NetworkingConstants.h"
|
||||
#include "MetaverseAPI.h"
|
||||
|
||||
const unsigned short DEFAULT_DOMAIN_SERVER_PORT =
|
||||
const unsigned short DEFAULT_DOMAIN_SERVER_PORT =
|
||||
QProcessEnvironment::systemEnvironment()
|
||||
.contains("HIFI_DOMAIN_SERVER_PORT")
|
||||
? QProcessEnvironment::systemEnvironment()
|
||||
|
@ -41,7 +41,7 @@ const unsigned short DEFAULT_DOMAIN_SERVER_PORT =
|
|||
.toUShort()
|
||||
: 40102;
|
||||
|
||||
const unsigned short DEFAULT_DOMAIN_SERVER_DTLS_PORT =
|
||||
const unsigned short DEFAULT_DOMAIN_SERVER_DTLS_PORT =
|
||||
QProcessEnvironment::systemEnvironment()
|
||||
.contains("HIFI_DOMAIN_SERVER_DTLS_PORT")
|
||||
? QProcessEnvironment::systemEnvironment()
|
||||
|
@ -49,7 +49,7 @@ const unsigned short DEFAULT_DOMAIN_SERVER_DTLS_PORT =
|
|||
.toUShort()
|
||||
: 40103;
|
||||
|
||||
const quint16 DOMAIN_SERVER_HTTP_PORT =
|
||||
const quint16 DOMAIN_SERVER_HTTP_PORT =
|
||||
QProcessEnvironment::systemEnvironment()
|
||||
.contains("HIFI_DOMAIN_SERVER_HTTP_PORT")
|
||||
? QProcessEnvironment::systemEnvironment()
|
||||
|
@ -57,7 +57,7 @@ const quint16 DOMAIN_SERVER_HTTP_PORT =
|
|||
.toUInt()
|
||||
: 40100;
|
||||
|
||||
const quint16 DOMAIN_SERVER_HTTPS_PORT =
|
||||
const quint16 DOMAIN_SERVER_HTTPS_PORT =
|
||||
QProcessEnvironment::systemEnvironment()
|
||||
.contains("HIFI_DOMAIN_SERVER_HTTPS_PORT")
|
||||
? QProcessEnvironment::systemEnvironment()
|
||||
|
@ -65,6 +65,15 @@ const quint16 DOMAIN_SERVER_HTTPS_PORT =
|
|||
.toUInt()
|
||||
: 40101;
|
||||
|
||||
const quint16 DOMAIN_SERVER_EXPORTER_PORT =
|
||||
QProcessEnvironment::systemEnvironment()
|
||||
.contains("VIRCADIA_DOMAIN_SERVER_EXPORTER_PORT")
|
||||
? QProcessEnvironment::systemEnvironment()
|
||||
.value("VIRCADIA_DOMAIN_SERVER_EXPORTER_PORT")
|
||||
.toUInt()
|
||||
: 9703;
|
||||
|
||||
|
||||
const int MAX_SILENT_DOMAIN_SERVER_CHECK_INS = 5;
|
||||
|
||||
class DomainHandler : public QObject {
|
||||
|
|
Loading…
Reference in a new issue