246 lines
8.2 KiB
C++
246 lines
8.2 KiB
C++
#include "file_metrics.h"
|
|
|
|
#include "idlib/sys/sys_networking.h"
|
|
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <mutex>
|
|
|
|
namespace {
|
|
|
|
idTCP metricsTCP;
|
|
unsigned char* sendQueue = nullptr;
|
|
int sendQueueSize = 0;
|
|
int sendIndex = 0;
|
|
int pendingIndex = 0;
|
|
char metricsServer[256] = "127.0.0.1";
|
|
unsigned short metricsPort = 8012;
|
|
int initialRetryTime = 1000;
|
|
int maximumRetryTime = 60000;
|
|
int retryTime = 0;
|
|
std::uint64_t timeoutWait = 0;
|
|
std::mutex metricsMutex;
|
|
|
|
std::uint64_t Milliseconds() {
|
|
using namespace std::chrono;
|
|
return static_cast<std::uint64_t>(duration_cast<milliseconds>(
|
|
steady_clock::now().time_since_epoch()).count());
|
|
}
|
|
|
|
void PutUInt32LE(unsigned char* output, const unsigned int value) {
|
|
output[0] = static_cast<unsigned char>(value);
|
|
output[1] = static_cast<unsigned char>(value >> 8);
|
|
output[2] = static_cast<unsigned char>(value >> 16);
|
|
output[3] = static_cast<unsigned char>(value >> 24);
|
|
}
|
|
|
|
int StringBytes(const char* text) {
|
|
return static_cast<int>(std::strlen(text == nullptr ? "" : text)) + 1;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
idFile_Metrics::idFile_Metrics(const char* streamName)
|
|
: name(streamName == nullptr ? "" : streamName), bytesSent(0) {
|
|
}
|
|
|
|
idFile_Metrics::~idFile_Metrics() {
|
|
CloseMetricsStream(name.c_str());
|
|
}
|
|
|
|
const char* idFile_Metrics::GetFullPath() const {
|
|
const netadr_t address = metricsTCP.GetAddress();
|
|
if (metricsTCP.IsOpen()) {
|
|
fullpath.Format("metrics://%u.%u.%u.%u:%u/%s",
|
|
static_cast<unsigned int>(address.ip[0]),
|
|
static_cast<unsigned int>(address.ip[1]),
|
|
static_cast<unsigned int>(address.ip[2]),
|
|
static_cast<unsigned int>(address.ip[3]),
|
|
static_cast<unsigned int>(address.port), name.c_str());
|
|
} else {
|
|
fullpath.Format("metrics://%s:%u/%s", metricsServer,
|
|
static_cast<unsigned int>(metricsPort), name.c_str());
|
|
}
|
|
return fullpath.c_str();
|
|
}
|
|
|
|
unsigned int idFile_Metrics::Read(void*, unsigned int) {
|
|
return 0;
|
|
}
|
|
|
|
unsigned int idFile_Metrics::Write(const void* buffer, const unsigned int len) {
|
|
const int written = WriteInternal(
|
|
name.c_str(), buffer, static_cast<int>(len));
|
|
bytesSent += written;
|
|
return written;
|
|
}
|
|
|
|
int idFile_Metrics::Seek(std::int64_t, fsOrigin_t) {
|
|
return -1;
|
|
}
|
|
|
|
void idFile_Metrics::Flush() {
|
|
std::lock_guard<std::mutex> lock(metricsMutex);
|
|
bool queueTraffic = false;
|
|
FlushBufferedWrites(queueTraffic);
|
|
}
|
|
|
|
void idFile_Metrics::ForceFlush() {
|
|
Flush();
|
|
}
|
|
|
|
idFile_Metrics* idFile_Metrics::OpenMetricsStream(const char* streamName) {
|
|
return new idFile_Metrics(streamName);
|
|
}
|
|
|
|
void idFile_Metrics::CloseMetricsStream(const char* streamName) {
|
|
std::lock_guard<std::mutex> lock(metricsMutex);
|
|
if (!metricsTCP.IsOpen()) return;
|
|
|
|
bool queueTraffic = false;
|
|
FlushBufferedWrites(queueTraffic);
|
|
|
|
static const char control[] = "CONTROL";
|
|
static const char close[] = "CLOSE";
|
|
const char* const safeName = streamName == nullptr ? "" : streamName;
|
|
const int payloadSize = static_cast<int>(sizeof(control) + sizeof(close))
|
|
+ StringBytes(safeName);
|
|
unsigned char prefix[4] = {};
|
|
PutUInt32LE(prefix, static_cast<unsigned int>(payloadSize));
|
|
BufferedWriteInternal(queueTraffic, prefix, sizeof(prefix));
|
|
BufferedWriteInternal(queueTraffic, control, sizeof(control));
|
|
BufferedWriteInternal(queueTraffic, close, sizeof(close));
|
|
BufferedWriteInternal(queueTraffic, safeName, StringBytes(safeName));
|
|
if (!queueTraffic) FlushBufferedWrites(queueTraffic);
|
|
}
|
|
|
|
void idFile_Metrics::ConfigureServer(const char* host, const unsigned short port,
|
|
const int initialRetryMilliseconds,
|
|
const int maximumRetryMilliseconds) {
|
|
std::lock_guard<std::mutex> lock(metricsMutex);
|
|
metricsTCP.Close();
|
|
const char* const safeHost = host == nullptr || *host == '\0'
|
|
? "127.0.0.1" : host;
|
|
std::strncpy(metricsServer, safeHost, sizeof(metricsServer) - 1);
|
|
metricsServer[sizeof(metricsServer) - 1] = '\0';
|
|
metricsPort = port;
|
|
initialRetryTime = (std::max)(1, initialRetryMilliseconds);
|
|
maximumRetryTime = (std::max)(initialRetryTime, maximumRetryMilliseconds);
|
|
retryTime = 0;
|
|
timeoutWait = 0;
|
|
sendIndex = pendingIndex = 0;
|
|
}
|
|
|
|
void idFile_Metrics::ShutdownTransport() {
|
|
std::lock_guard<std::mutex> lock(metricsMutex);
|
|
metricsTCP.Close();
|
|
std::free(sendQueue);
|
|
sendQueue = nullptr;
|
|
sendQueueSize = sendIndex = pendingIndex = 0;
|
|
retryTime = 0;
|
|
timeoutWait = 0;
|
|
}
|
|
|
|
bool idFile_Metrics::EnsureConnection() {
|
|
if (metricsTCP.IsOpen()) return true;
|
|
if (Milliseconds() < timeoutWait || metricsPort == 0) return false;
|
|
|
|
// A blocking connect gives the PC port a definite connected state. All
|
|
// subsequent writes retain the non-blocking short-write queue semantics.
|
|
if (metricsTCP.Connect(metricsServer, metricsPort, false, true, true)) {
|
|
retryTime = 0;
|
|
timeoutWait = 0;
|
|
return true;
|
|
}
|
|
|
|
retryTime = retryTime == 0 ? initialRetryTime
|
|
: (std::min)(maximumRetryTime, retryTime * 2);
|
|
timeoutWait = Milliseconds() + static_cast<std::uint64_t>(retryTime);
|
|
return false;
|
|
}
|
|
|
|
void idFile_Metrics::WriteToQueue(const void* buffer, const int len) {
|
|
if (buffer == nullptr || len <= 0) return;
|
|
const int retained = pendingIndex - sendIndex;
|
|
const int required = retained + len;
|
|
if (required > sendQueueSize) {
|
|
int newSize = (std::max)(1024, sendQueueSize);
|
|
while (newSize < required) newSize *= 2;
|
|
unsigned char* const replacement = static_cast<unsigned char*>(
|
|
std::malloc(static_cast<std::size_t>(newSize)));
|
|
if (replacement == nullptr) return;
|
|
if (retained > 0) {
|
|
std::memcpy(replacement, sendQueue + sendIndex,
|
|
static_cast<std::size_t>(retained));
|
|
}
|
|
std::free(sendQueue);
|
|
sendQueue = replacement;
|
|
sendQueueSize = newSize;
|
|
sendIndex = 0;
|
|
pendingIndex = retained;
|
|
} else if (sendIndex > 0 && pendingIndex + len > sendQueueSize) {
|
|
if (retained > 0) {
|
|
std::memmove(sendQueue, sendQueue + sendIndex,
|
|
static_cast<std::size_t>(retained));
|
|
}
|
|
sendIndex = 0;
|
|
pendingIndex = retained;
|
|
}
|
|
std::memcpy(sendQueue + pendingIndex, buffer, static_cast<std::size_t>(len));
|
|
pendingIndex += len;
|
|
}
|
|
|
|
void idFile_Metrics::BufferedWriteInternal(bool& queueTraffic,
|
|
const void* buffer, const int len) {
|
|
if (buffer == nullptr || len <= 0) return;
|
|
if (queueTraffic) {
|
|
WriteToQueue(buffer, len);
|
|
return;
|
|
}
|
|
const int written = metricsTCP.Write(buffer, len);
|
|
const int validWritten = (std::max)(0, written);
|
|
if (written != len) {
|
|
queueTraffic = true;
|
|
WriteToQueue(static_cast<const unsigned char*>(buffer) + validWritten,
|
|
len - validWritten);
|
|
}
|
|
}
|
|
|
|
void idFile_Metrics::FlushBufferedWrites(bool& queueTraffic) {
|
|
if (!metricsTCP.IsOpen() || sendIndex >= pendingIndex) {
|
|
if (sendIndex >= pendingIndex) sendIndex = pendingIndex = 0;
|
|
return;
|
|
}
|
|
const int available = pendingIndex - sendIndex;
|
|
const int written = metricsTCP.Write(sendQueue + sendIndex, available);
|
|
if (written > 0) sendIndex += written;
|
|
if (written < available) queueTraffic = true;
|
|
if (sendIndex >= pendingIndex) sendIndex = pendingIndex = 0;
|
|
}
|
|
|
|
void idFile_Metrics::WriteFrame(bool& queueTraffic, const char* streamName,
|
|
const void* buffer, const int len) {
|
|
const char* const safeName = streamName == nullptr ? "" : streamName;
|
|
const int nameBytes = StringBytes(safeName);
|
|
unsigned char prefix[4] = {};
|
|
PutUInt32LE(prefix, static_cast<unsigned int>(nameBytes + len));
|
|
BufferedWriteInternal(queueTraffic, prefix, sizeof(prefix));
|
|
BufferedWriteInternal(queueTraffic, safeName, nameBytes);
|
|
BufferedWriteInternal(queueTraffic, buffer, len);
|
|
}
|
|
|
|
int idFile_Metrics::WriteInternal(const char* streamName,
|
|
const void* buffer, const int len) {
|
|
if (buffer == nullptr || len <= 0) return 0;
|
|
std::lock_guard<std::mutex> lock(metricsMutex);
|
|
if (!EnsureConnection()) return 0;
|
|
|
|
bool queueTraffic = false;
|
|
FlushBufferedWrites(queueTraffic);
|
|
WriteFrame(queueTraffic, streamName, buffer, len);
|
|
return len;
|
|
}
|