From: Remi Gacogne Date: Tue, 19 Feb 2019 13:43:49 +0000 (+0100) Subject: API: Add an endpoint to request only a specific statistic X-Git-Tag: dnsdist-1.4.0-alpha1~58^2~5 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=5376a5d73c30033d26721ea9bb63841706afd936;p=pdns API: Add an endpoint to request only a specific statistic --- diff --git a/docs/common/api/endpoint-statistics.rst b/docs/common/api/endpoint-statistics.rst index 761980757..771dae08b 100644 --- a/docs/common/api/endpoint-statistics.rst +++ b/docs/common/api/endpoint-statistics.rst @@ -1,7 +1,7 @@ Statistics endpoint =================== -.. http:get:: /api/v1/servers/:server_id/statistics +.. http:get:: /api/v1/servers/:server_id/statistics?statistic=:statistic Query PowerDNS internal statistics. Returns a list of :json:object:`StatisticItem` elements. @@ -10,6 +10,10 @@ Statistics endpoint :param server_id: The name of the server + .. versionadded:: 4.2.0 + + :query statistic: If set to the name of a specific statistic, only this value is returned. If no statistic with that name exists, the response has a 422 status and an error message + **Example response:** .. code-block:: json diff --git a/docs/http-api/swagger/authoritative-api-swagger.yaml b/docs/http-api/swagger/authoritative-api-swagger.yaml index 8eed2164f..818f0b8c2 100644 --- a/docs/http-api/swagger/authoritative-api-swagger.yaml +++ b/docs/http-api/swagger/authoritative-api-swagger.yaml @@ -399,6 +399,14 @@ paths: required: true description: The id of the server to retrieve type: string + - name: statistic + in: query + required: false + type: string + description: | + When set to the name of a specific statistic, only this value is returned. + If no statistic with that name exists, the response has a 422 status and an error message. + responses: '200': description: List of Statistic Items @@ -408,6 +416,8 @@ paths: - $ref: '#/definitions/StatisticItem' - $ref: '#/definitions/MapStatisticItem' - $ref: '#/definitions/RingStatisticItem' + '422': + description: 'Returned when a non-existing statistic name has been requested. Contains an error message' '/servers/{server_id}/search-data': get: diff --git a/pdns/ws-api.cc b/pdns/ws-api.cc index 6106d25f3..ae62d7dd7 100644 --- a/pdns/ws-api.cc +++ b/pdns/ws-api.cc @@ -159,15 +159,29 @@ void apiServerStatistics(HttpRequest* req, HttpResponse* resp) { if(req->method != "GET") throw HttpMethodNotAllowedException(); + Json::array doc; + string name = req->getvars["statistic"]; + if (!name.empty()) { + auto stat = productServerStatisticsFetch(name); + if (!stat) { + throw ApiException("Unknown statistic name"); + } + + doc.push_back(Json::object { + { "type", "StatisticItem" }, + { "name", name }, + { "value", std::to_string(*stat) }, + }); + + resp->setBody(doc); + + return; + } + typedef map stat_items_t; stat_items_t general_stats; productServerStatisticsFetch(general_stats); - auto resp_qtype_stats = g_rs.getQTypeResponseCounts(); - auto resp_size_stats = g_rs.getSizeResponseCounts(); - auto resp_rcode_stats = g_rs.getRCodeResponseCounts(); - - Json::array doc; for(const auto& item : general_stats) { doc.push_back(Json::object { { "type", "StatisticItem" }, @@ -176,6 +190,9 @@ void apiServerStatistics(HttpRequest* req, HttpResponse* resp) { }); } + auto resp_qtype_stats = g_rs.getQTypeResponseCounts(); + auto resp_size_stats = g_rs.getSizeResponseCounts(); + auto resp_rcode_stats = g_rs.getRCodeResponseCounts(); { Json::array values; for(const auto& item : resp_qtype_stats) { diff --git a/pdns/ws-api.hh b/pdns/ws-api.hh index 0de6eda58..e280a20ab 100644 --- a/pdns/ws-api.hh +++ b/pdns/ws-api.hh @@ -41,5 +41,6 @@ DNSName apiNameToDNSName(const string& name); // To be provided by product code. void productServerStatisticsFetch(std::map& out); +boost::optional productServerStatisticsFetch(const std::string& name); #endif /* PDNS_WSAPI_HH */ diff --git a/pdns/ws-auth.cc b/pdns/ws-auth.cc index eb6a9707b..d2c3f9ae0 100644 --- a/pdns/ws-auth.cc +++ b/pdns/ws-auth.cc @@ -503,6 +503,17 @@ void productServerStatisticsFetch(map& out) out["uptime"] = std::to_string(time(0) - s_starttime); } +boost::optional productServerStatisticsFetch(const std::string& name) +{ + try { + // ::read() calls ::exists() which throws a PDNSException when the key does not exist + return S.read(name); + } + catch(...) { + return boost::none; + } +} + static void validateGatheredRRType(const DNSResourceRecord& rr) { if (rr.qtype.getCode() == QType::OPT || rr.qtype.getCode() == QType::TSIG) { throw ApiException("RRset "+rr.qname.toString()+" IN "+rr.qtype.getName()+": invalid type given"); diff --git a/pdns/ws-recursor.cc b/pdns/ws-recursor.cc index 6d262656b..a1889a3a6 100644 --- a/pdns/ws-recursor.cc +++ b/pdns/ws-recursor.cc @@ -52,6 +52,11 @@ void productServerStatisticsFetch(map& out) out.swap(stats); } +boost::optional productServerStatisticsFetch(const std::string& name) +{ + return getStatByName(name); +} + static void apiWriteConfigFile(const string& filebasename, const string& content) { if (::arg()["api-config-dir"].empty()) { diff --git a/regression-tests.api/test_Servers.py b/regression-tests.api/test_Servers.py index 80835f999..bc24d08c5 100644 --- a/regression-tests.api/test_Servers.py +++ b/regression-tests.api/test_Servers.py @@ -57,3 +57,14 @@ class Servers(ApiTestCase): self.assertIn('60', [e['name'] for e in respsize_stats]) self.assertIn('example.com/A', [e['name'] for e in queries_stats]) self.assertIn('No Error', [e['name'] for e in rcode_stats]) + + def test_read_one_statistic(self): + r = self.session.get(self.url("/api/v1/servers/localhost/statistics?statistic=uptime")) + self.assert_success_json(r) + data = r.json() + self.assertIn('uptime', [e['name'] for e in data]) + + def test_read_one_non_existent_statistic(self): + r = self.session.get(self.url("/api/v1/servers/localhost/statistics?statistic=uptimeAAAA")) + self.assertEquals(r.status_code, 422) + self.assertIn("Unknown statistic name", r.json()['error'])