feat: auto-flush stats timer, .env.json defaults, expose round-trip+timer tests
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"DATABASE_URL": "sqlite://system.db",
|
||||
"STATS_FLUSH_SECS": "5"
|
||||
}
|
||||
@@ -83,7 +83,8 @@ ExitCode RunExposeServe(const ParsedArgs& args, Output& out) {
|
||||
auto& sc = realtime::expose::StatsCollector::Global();
|
||||
sc.Configure(realtime::expose::MakeStatsStore(""));
|
||||
std::string serr;
|
||||
(void)sc.Flush(serr); // ensure schema exists; batch flushes follow on timer
|
||||
(void)sc.Flush(serr); // ensure schema exists
|
||||
sc.StartAutoFlush(); // time-based persisted flush (STATS_FLUSH_SECS, default 5s)
|
||||
molo::app::views::View::SetObserver([](const std::string& m, std::uint64_t bi,
|
||||
std::uint64_t bo, std::uint64_t us) {
|
||||
realtime::expose::StatsCollector::Global().Note(m, bi, bo, us);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
|
||||
// Minimal libpq declarations (no dev headers on build hosts); link -lpq.
|
||||
extern "C" {
|
||||
@@ -283,6 +284,31 @@ std::unique_ptr<StatsStore> MakeStatsStore(const std::string& database_url) {
|
||||
return std::make_unique<SqliteStore>(path);
|
||||
}
|
||||
|
||||
static int FlushIntervalSecs() {
|
||||
std::string v = EnvJsonValue("STATS_FLUSH_SECS");
|
||||
if (v.empty()) v = EnvOr("STATS_FLUSH_SECS", "5");
|
||||
try { int n = std::stoi(v); return n > 0 ? n : 5; } catch (...) { return 5; }
|
||||
}
|
||||
|
||||
void StatsCollector::StartAutoFlush(std::chrono::seconds interval) {
|
||||
StopAutoFlush();
|
||||
if (interval.count() <= 0) interval = std::chrono::seconds(FlushIntervalSecs());
|
||||
flush_run_.store(true);
|
||||
flush_thread_ = std::thread([this, interval] {
|
||||
while (flush_run_.load()) {
|
||||
std::this_thread::sleep_for(interval);
|
||||
if (!flush_run_.load()) break;
|
||||
std::string err;
|
||||
Flush(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void StatsCollector::StopAutoFlush() {
|
||||
flush_run_.store(false);
|
||||
if (flush_thread_.joinable()) flush_thread_.join();
|
||||
}
|
||||
|
||||
StatsCollector& StatsCollector::Global() {
|
||||
static StatsCollector g;
|
||||
return g;
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
// atomic in-memory accumulator + a pluggable Store backend; flush is
|
||||
// explicit (timer-driven by caller) inside one transaction.
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <thread>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
@@ -49,6 +51,12 @@ class StatsCollector {
|
||||
}
|
||||
bool enabled() const { return enabled_.load(std::memory_order_relaxed); }
|
||||
void set_enabled(bool v) { enabled_.store(v, std::memory_order_relaxed); }
|
||||
~StatsCollector() { StopAutoFlush(); }
|
||||
// Time-based persisted flush: background thread calls Flush() every
|
||||
// interval (default 5s; STATS_FLUSH_SECS in .env.json/env overrides).
|
||||
// Idempotent; safe to call twice.
|
||||
void StartAutoFlush(std::chrono::seconds interval = std::chrono::seconds(5));
|
||||
void StopAutoFlush();
|
||||
|
||||
void Note(const std::string& method, std::uint64_t bytes_in,
|
||||
std::uint64_t bytes_out, std::uint64_t latency_us);
|
||||
@@ -61,6 +69,8 @@ class StatsCollector {
|
||||
std::mutex mu_;
|
||||
std::unique_ptr<StatsStore> store_;
|
||||
std::map<std::string, MethodStat> pending_;
|
||||
std::thread flush_thread_;
|
||||
std::atomic<bool> flush_run_{false};
|
||||
};
|
||||
|
||||
} // namespace molo::realtime::expose
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
@@ -91,6 +92,11 @@ int main() {
|
||||
assert(sc.Query(out, false, err));
|
||||
assert(out.find("GET") != std::string::npos && out.find("count=1") != std::string::npos);
|
||||
assert(sc.Query(out, true, err) && out.front() == '[');
|
||||
sc.StartAutoFlush(std::chrono::seconds(1));
|
||||
sc.Note("POST", 1024, 1024, 1000);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(2500));
|
||||
sc.StopAutoFlush();
|
||||
assert(sc.Query(out, false, err) && out.find("POST") != std::string::npos);
|
||||
std::remove(db.c_str());
|
||||
}
|
||||
printf("expose_test PASS\n");
|
||||
|
||||
Reference in New Issue
Block a user