Skip to content

Commit

Permalink
Merge 'Add the metrics config api' from Amnon Heiman
Browse files Browse the repository at this point in the history
This series is based on top of the seastar relabel config API.

The series adds a REST API for the configuration, it allows to get and set it.

The API is registered under the V2 prefix and uses the swagger 2.0 definition.

After this series to get the current relabel-config configuration:

```
    curl -X GET --header 'Accept: application/json' 'http://localhost:10000/v2/metrics-config/'
```

A set config example:
```
    curl -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' -d '[ \
       { \
         "source_labels": [ \
           "__name__" \
         ], \
         "action": "replace", \
         "target_label": "level", \
         "replacement": "1", \
         "regex": "io_que.*" \
       } \
     ]' 'http://localhost:10000/v2/metrics-config/'
```

This is how it looks like in the UI
![image](https://user-images.githubusercontent.com/2118079/230763730-bafcaf8b-ea6d-4a6c-a778-6271fa3b6f82.png)

Closes #12670

* github.com:scylladb/scylladb:
  api: Add the metrics API
  api/config: make it optional if the config API is the first to register
  api: Add the metrics.json Swagger file
  Preparing for V2 API from files
  • Loading branch information
denesb committed Jul 18, 2023
2 parents f03efd7 + 123dd44 commit 6961fbc
Show file tree
Hide file tree
Showing 8 changed files with 187 additions and 7 deletions.
34 changes: 34 additions & 0 deletions 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"
}
}
}
66 changes: 66 additions & 0 deletions 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"
}
}
}
}
}
2 changes: 1 addition & 1 deletion api/api-doc/swagger20_header.json
Expand Up @@ -16,7 +16,7 @@
}
},
"host": "{{Host}}",
"basePath": "/v2",
"basePath": "/",
"schemes": [
"http"
],
Expand Down
4 changes: 3 additions & 1 deletion api/api.cc
Expand Up @@ -60,16 +60,18 @@ 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);
});
}

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);
});
}

Expand Down
8 changes: 4 additions & 4 deletions api/config.cc
Expand Up @@ -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 <<"\","
Expand Down Expand Up @@ -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<char>& 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<char>& 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();
Expand Down
2 changes: 1 addition & 1 deletion api/config.hh
Expand Up @@ -13,5 +13,5 @@

namespace api {

void set_config(std::shared_ptr<httpd::api_registry_builder20> rb, http_context& ctx, httpd::routes& r, const db::config& cfg);
void set_config(std::shared_ptr<httpd::api_registry_builder20> rb, http_context& ctx, httpd::routes& r, const db::config& cfg, bool first = false);
}
77 changes: 77 additions & 0 deletions api/system.cc
Expand Up @@ -7,10 +7,18 @@
*/

#include "api/api-doc/system.json.hh"
#include "api/api-doc/metrics.json.hh"

#include "api/api.hh"

#include <seastar/core/reactor.hh>
#include <seastar/core/metrics_api.hh>
#include <seastar/core/relabel_config.hh>
#include <seastar/http/exception.hh>
#include <seastar/util/short_streams.hh>
#include <seastar/http/short_streams.hh>
#include "utils/rjson.hh"

#include "log.hh"
#include "replica/database.hh"

Expand All @@ -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<hm::metrics_config> 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<http::request> req) -> future<json::json_return_type> {
rapidjson::Document doc;
doc.Parse(req->content.c_str());
if (!doc.IsArray()) {
throw bad_param_exception("Expected a json array");
}
std::vector<seastar::metrics::relabel_config> 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<std::string> 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<seastar::metrics::relabel_config>& 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<json::json_return_type>(seastar::json::json_void());
});
});
});

hs::get_system_uptime.set(r, [](const_req req) {
return std::chrono::duration_cast<std::chrono::milliseconds>(engine().uptime()).count();
});
Expand Down
1 change: 1 addition & 0 deletions configure.py
Expand Up @@ -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',
Expand Down

0 comments on commit 6961fbc

Please sign in to comment.