Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ endif()
# ON by default with on-demand mode - zero overhead until a profiler connects.
option(FW_PROFILING "Enable Tracy profiler instrumentation" ON)

set(FW_METRICS_BACKEND "prometheus" CACHE STRING "Metrics backend")
set_property(CACHE FW_METRICS_BACKEND PROPERTY STRINGS prometheus none)
string(TOLOWER "${FW_METRICS_BACKEND}" FW_METRICS_BACKEND)
if(NOT FW_METRICS_BACKEND STREQUAL "prometheus" AND NOT FW_METRICS_BACKEND STREQUAL "none")
message(FATAL_ERROR "Unsupported FW_METRICS_BACKEND: ${FW_METRICS_BACKEND}")
endif()
message(STATUS "Metrics backend: ${FW_METRICS_BACKEND}")

if (WIN32)
if (MSVC)
add_compile_options(
Expand Down
14 changes: 14 additions & 0 deletions code/framework/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
include(UpdateGitHash)
update_git_version(FW "${CMAKE_CURRENT_SOURCE_DIR}/src/utils/version.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/version.cpp")

if(FW_METRICS_BACKEND STREQUAL "prometheus")
set(FRAMEWORK_METRICS_BACKEND_SOURCE src/metrics/backends/prometheus.cpp)
elseif(FW_METRICS_BACKEND STREQUAL "none")
set(FRAMEWORK_METRICS_BACKEND_SOURCE src/metrics/backends/none.cpp)
else()
message(FATAL_ERROR "Unsupported FW_METRICS_BACKEND: ${FW_METRICS_BACKEND}")
endif()

# List the source files
set(FRAMEWORK_SRC
src/logging/logger.cpp
Expand Down Expand Up @@ -54,6 +62,9 @@ set(FRAMEWORK_SRC

src/jobs/job_system.cpp
src/jobs/io_tasks.cpp

src/metrics/registry.cpp
${FRAMEWORK_METRICS_BACKEND_SOURCE}
)

set(FRAMEWORK_SERVER_SRC
Expand Down Expand Up @@ -303,6 +314,9 @@ target_compile_features(FrameworkV8Engine PRIVATE cxx_std_20)
# Link the solutions based on the platform
link_shared_deps(Framework)
target_link_libraries(Framework v8 v8pp)
if(FW_METRICS_BACKEND STREQUAL "prometheus")
target_link_libraries(Framework prometheus-cpp-lite)
endif()
if(LIBNODE_AVAILABLE)
target_link_libraries(Framework libnode)
endif()
Expand Down
98 changes: 92 additions & 6 deletions code/framework/src/http/webserver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,60 @@

#include <httplib.h>
#include <logging/logger.h>
#include <metrics/registry.h>

#include <chrono>

namespace Framework::HTTP {
namespace {
Metrics::Counter *RouteStatusCounter(const std::string &route, const char *method, const char *codeClass) {
return Metrics::Registry::Get().RegisterCounter("fw_http_requests_total", "HTTP requests by route, method, and status class", {{"route", route}, {"method", method}, {"code", codeClass}});
}

struct RouteMetrics {
Metrics::Counter *c2xx = nullptr;
Metrics::Counter *c4xx = nullptr;
Metrics::Counter *c5xx = nullptr;
Metrics::Counter *cother = nullptr;
Metrics::Histogram *duration = nullptr;
Metrics::Gauge *inFlight = nullptr;
};

RouteMetrics MakeRouteMetrics(const std::string &route, const char *method) {
auto &reg = Metrics::Registry::Get();
RouteMetrics metrics {
RouteStatusCounter(route, method, "2xx"),
RouteStatusCounter(route, method, "4xx"),
RouteStatusCounter(route, method, "5xx"),
RouteStatusCounter(route, method, "other"),
reg.RegisterHistogram("fw_http_request_duration_seconds", "HTTP route handler duration", Metrics::Buckets::Exponential(0.0005, 2.0, 13), {{"route", route}, {"method", method}}),
reg.RegisterGauge("fw_http_requests_in_flight", "HTTP requests currently executing a route handler", {{"route", route}, {"method", method}}),
};
return metrics;
}

void RecordRouteStatus(const RouteMetrics &c, int status) {
Metrics::Counter *sel = status >= 200 && status < 300 ? c.c2xx : status >= 400 && status < 500 ? c.c4xx : status >= 500 && status < 600 ? c.c5xx : c.cother;
if (sel) {
sel->Inc();
}
}

int EffectiveStatus(const httplib::Response &res) {
return res.status == -1 ? 200 : res.status;
}

void FinishRoute(const RouteMetrics &metrics, std::chrono::steady_clock::time_point startedAt, int status) {
RecordRouteStatus(metrics, status);
if (metrics.duration) {
metrics.duration->Observe(std::chrono::duration<double>(std::chrono::steady_clock::now() - startedAt).count());
}
if (metrics.inFlight) {
metrics.inFlight->Add(-1.0);
}
}
}

Webserver::Webserver() {
_server = std::make_shared<httplib::Server>();
}
Expand Down Expand Up @@ -77,17 +129,51 @@ namespace Framework::HTTP {
void Webserver::RegisterRequest(const std::string &path, const RequestCallback &callback) const {
if (!_running)
return;
if (!path.empty() && callback) {
_server->Get(path, callback);
}
if (path.empty() || !callback)
return;

const RouteMetrics metrics = MakeRouteMetrics(path, "GET");
RequestCallback userCb = callback;
auto wrapped = [userCb, metrics](const httplib::Request &req, httplib::Response &res) {
const auto startedAt = std::chrono::steady_clock::now();
if (metrics.inFlight) {
metrics.inFlight->Add(1.0);
}
try {
userCb(req, res);
FinishRoute(metrics, startedAt, EffectiveStatus(res));
}
catch (...) {
FinishRoute(metrics, startedAt, 500);
throw;
}
};
_server->Get(path, wrapped);
}

void Webserver::RegisterPostRequest(const std::string &path, const PostCallback &callback) const {
if (!_running)
return;
if (!path.empty() && callback) {
_server->Post(path, callback);
}
if (path.empty() || !callback)
return;

const RouteMetrics metrics = MakeRouteMetrics(path, "POST");
PostCallback userCb = callback;
auto wrapped = [userCb, metrics](const httplib::Request &req, httplib::Response &res, const httplib::ContentReader &reader) {
const auto startedAt = std::chrono::steady_clock::now();
if (metrics.inFlight) {
metrics.inFlight->Add(1.0);
}
try {
userCb(req, res, reader);
FinishRoute(metrics, startedAt, EffectiveStatus(res));
}
catch (...) {
FinishRoute(metrics, startedAt, 500);
throw;
}
};
_server->Post(path, wrapped);
}

void Webserver::ServeDirectory(const std::string &dir) {
Expand Down
Loading