diff --git a/api/api-doc/metrics.def.json b/api/api-doc/metrics.def.json new file mode 100644 index 000000000000..c8ae6c64d431 --- /dev/null +++ b/api/api-doc/metrics.def.json @@ -0,0 +1,34 @@ + "metrics_config": { + "id": "metrics_config", + "summary": "An entry in the metrics configuration", + "properties": { + "source_labels": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The source labels, a match is based on concatination of the labels" + }, + "action": { + "type": "string", + "description": "The action to perfrom on match", + "enum": ["skip_when_empty", "report_when_empty", "replace", "keep", "drop", "drop_label"] + }, + "target_label": { + "type": "string", + "description": "The application state version" + }, + "replacement": { + "type": "string", + "description": "The replacement string to use when replacing a value" + }, + "regex": { + "type": "string", + "description": "The regex string to use when replacing a value" + }, + "separator": { + "type": "string", + "description": "The separator string to use when concatinating the labels" + } + } + } \ No newline at end of file diff --git a/api/api-doc/metrics.json b/api/api-doc/metrics.json new file mode 100644 index 000000000000..76b47a75bd3f --- /dev/null +++ b/api/api-doc/metrics.json @@ -0,0 +1,66 @@ + "/v2/metrics-config/":{ + "get":{ + "description":"Return the metrics layer configuration", + "operationId":"get_metrics_config", + "produces":[ + "application/json" + ], + "tags":[ + "metrics" + ], + "parameters":[ + ], + "responses":{ + "200":{ + "schema": { + "type":"array", + "items":{ + "$ref":"#/definitions/metrics_config", + "description":"metrics Config value" + } + } + }, + "default":{ + "description":"unexpected error", + "schema":{ + "$ref":"#/definitions/ErrorModel" + } + } + } + }, + "post": { + "description":"Set the metrics layer relabel configuration", + "operationId":"set_metrics_config", + "produces":[ + "application/json" + ], + "tags":[ + "metrics" + ], + "parameters":[ + { + "in":"body", + "name":"conf", + "description":"An array of relabel_config objects", + "schema": { + "type":"array", + "items":{ + "$ref":"#/definitions/metrics_config", + "description":"metrics Config value" + } + } + } + ], + "responses":{ + "200":{ + "description": "OK" + }, + "default":{ + "description":"unexpected error", + "schema":{ + "$ref":"#/definitions/ErrorModel" + } + } + } + } + } diff --git a/api/api-doc/swagger20_header.json b/api/api-doc/swagger20_header.json index 68dea43250ce..484d260a436e 100644 --- a/api/api-doc/swagger20_header.json +++ b/api/api-doc/swagger20_header.json @@ -16,7 +16,7 @@ } }, "host": "{{Host}}", - "basePath": "/v2", + "basePath": "/", "schemes": [ "http" ], diff --git a/api/api.cc b/api/api.cc index c04fb9080ffc..f47b17d51dc2 100644 --- a/api/api.cc +++ b/api/api.cc @@ -60,8 +60,10 @@ future<> set_server_init(http_context& ctx) { rb->set_api_doc(r); rb02->set_api_doc(r); rb02->register_api_file(r, "swagger20_header"); + rb02->register_api_file(r, "metrics"); rb->register_function(r, "system", "The system related API"); + rb02->add_definitions_file(r, "metrics"); set_system(ctx, r); }); } @@ -69,7 +71,7 @@ future<> set_server_init(http_context& ctx) { future<> set_server_config(http_context& ctx, const db::config& cfg) { auto rb02 = std::make_shared < api_registry_builder20 > (ctx.api_doc, "/v2"); return ctx.http_server.set_routes([&ctx, &cfg, rb02](routes& r) { - set_config(rb02, ctx, r, cfg); + set_config(rb02, ctx, r, cfg, false); }); } diff --git a/api/config.cc b/api/config.cc index 92c234b520ba..ba4d359579fb 100644 --- a/api/config.cc +++ b/api/config.cc @@ -45,7 +45,7 @@ future<> get_config_swagger_entry(std::string_view name, const std::string& desc } else { ss <<','; }; - ss << "\"/config/" << name <<"\": {" + ss << "\"/v2/config/" << name <<"\": {" "\"get\": {" "\"description\": \"" << boost::replace_all_copy(boost::replace_all_copy(boost::replace_all_copy(description,"\n","\\n"),"\"", "''"), "\t", " ") <<"\"," "\"operationId\": \"find_config_"<< name <<"\"," @@ -76,9 +76,9 @@ future<> get_config_swagger_entry(std::string_view name, const std::string& desc namespace cs = httpd::config_json; -void set_config(std::shared_ptr < api_registry_builder20 > rb, http_context& ctx, routes& r, const db::config& cfg) { - rb->register_function(r, [&cfg] (output_stream& os) { - return do_with(true, [&os, &cfg] (bool& first) { +void set_config(std::shared_ptr < api_registry_builder20 > rb, http_context& ctx, routes& r, const db::config& cfg, bool first) { + rb->register_function(r, [&cfg, first] (output_stream& os) { + return do_with(first, [&os, &cfg] (bool& first) { auto f = make_ready_future(); for (auto&& cfg_ref : cfg.values()) { auto&& cfg = cfg_ref.get(); diff --git a/api/config.hh b/api/config.hh index c9b4c51a9962..94bdf08c2295 100644 --- a/api/config.hh +++ b/api/config.hh @@ -13,5 +13,5 @@ namespace api { -void set_config(std::shared_ptr rb, http_context& ctx, httpd::routes& r, const db::config& cfg); +void set_config(std::shared_ptr rb, http_context& ctx, httpd::routes& r, const db::config& cfg, bool first = false); } diff --git a/api/system.cc b/api/system.cc index 32ab318e59bd..e6f6f6d00054 100644 --- a/api/system.cc +++ b/api/system.cc @@ -7,10 +7,18 @@ */ #include "api/api-doc/system.json.hh" +#include "api/api-doc/metrics.json.hh" + #include "api/api.hh" #include +#include +#include #include +#include +#include +#include "utils/rjson.hh" + #include "log.hh" #include "replica/database.hh" @@ -20,8 +28,77 @@ namespace api { using namespace seastar::httpd; namespace hs = httpd::system_json; +namespace hm = httpd::metrics_json; void set_system(http_context& ctx, routes& r) { + hm::get_metrics_config.set(r, [](const_req req) { + std::vector res; + res.resize(seastar::metrics::get_relabel_configs().size()); + size_t i = 0; + for (auto&& r : seastar::metrics::get_relabel_configs()) { + res[i].action = r.action; + res[i].target_label = r.target_label; + res[i].replacement = r.replacement; + res[i].separator = r.separator; + res[i].source_labels = r.source_labels; + res[i].regex = r.expr.str(); + i++; + } + return res; + }); + + hm::set_metrics_config.set(r, [](std::unique_ptr req) -> future { + rapidjson::Document doc; + doc.Parse(req->content.c_str()); + if (!doc.IsArray()) { + throw bad_param_exception("Expected a json array"); + } + std::vector relabels; + relabels.resize(doc.Size()); + for (rapidjson::SizeType i = 0; i < doc.Size(); i++) { + const auto& element = doc[i]; + if (element.HasMember("source_labels")) { + std::vector source_labels; + source_labels.resize(element["source_labels"].Size()); + + for (size_t j = 0; j < element["source_labels"].Size(); j++) { + source_labels[j] = element["source_labels"][j].GetString(); + } + relabels[i].source_labels = source_labels; + } + if (element.HasMember("action")) { + relabels[i].action = seastar::metrics::relabel_config_action(element["action"].GetString()); + } + if (element.HasMember("replacement")) { + relabels[i].replacement = element["replacement"].GetString(); + } + if (element.HasMember("separator")) { + relabels[i].separator = element["separator"].GetString(); + } + if (element.HasMember("target_label")) { + relabels[i].target_label = element["target_label"].GetString(); + } + if (element.HasMember("regex")) { + relabels[i].expr = element["regex"].GetString(); + } + } + return do_with(std::move(relabels), false, [](const std::vector& relabels, bool& failed) { + return smp::invoke_on_all([&relabels, &failed] { + return metrics::set_relabel_configs(relabels).then([&failed](const metrics::metric_relabeling_result& result) { + if (result.metrics_relabeled_due_to_collision > 0) { + failed = true; + } + return; + }); + }).then([&failed](){ + if (failed) { + throw bad_param_exception("conflicts found during relabeling"); + } + return make_ready_future(seastar::json::json_void()); + }); + }); + }); + hs::get_system_uptime.set(r, [](const_req req) { return std::chrono::duration_cast(engine().uptime()).count(); }); diff --git a/configure.py b/configure.py index e6d335721b3a..08f344b734ac 100755 --- a/configure.py +++ b/configure.py @@ -1140,6 +1140,7 @@ def find_headers(repodir, excluded_dirs): 'api/task_manager_test.cc', 'api/config.cc', Json2Code('api/api-doc/config.json'), + Json2Code('api/api-doc/metrics.json'), 'api/error_injection.cc', Json2Code('api/api-doc/error_injection.json'), 'api/authorization_cache.cc',