From: Chris Hofstaedtler Date: Wed, 3 Jan 2018 12:15:15 +0000 (+0100) Subject: API: Expose ResponseStats and Ringbuffers X-Git-Tag: dnsdist-1.3.0~113^2~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=118d3b31b7276db530f80e919d012c0d436f3fd4;p=thirdparty%2Fpdns.git API: Expose ResponseStats and Ringbuffers --- diff --git a/docs/http-api/swagger/authoritative-api-swagger.yaml b/docs/http-api/swagger/authoritative-api-swagger.yaml index cfb1cc893e..f849b9feb4 100644 --- a/docs/http-api/swagger/authoritative-api-swagger.yaml +++ b/docs/http-api/swagger/authoritative-api-swagger.yaml @@ -352,7 +352,7 @@ paths: '/servers/{server_id}/statistics': get: summary: 'Query statistics.' - description: 'Query PowerDNS internal statistics. Returns a list of StatisticItem elements.' + description: 'Query PowerDNS internal statistics. Returns a list of BaseStatisticItem derived elements.' operationId: getStats tags: - stats @@ -369,6 +369,8 @@ paths: type: array items: $ref: '#/definitions/StatisticItem' + $ref: '#/definitions/MapStatisticItem' + $ref: '#/definitions/RingStatisticItem' '/servers/{server_id}/search-log': get: @@ -879,18 +881,69 @@ definitions: type: string description: 'The value of setting name' - StatisticItem: - title: StatisticItem + BaseStatisticItem: + title: BaseStatisticItem properties: name: - type: string - description: 'set to “StatisticItem”' - type: type: string description: 'The name of this item (e.g. ‘uptime’)' - value: - type: string - description: 'The value of item' + + StatisticItem: + title: StatisticItem + allOf: + $ref: "#/definitions/BaseStatisticItem" + properties: + type: + enum: [StatisticItem] + description: 'set to "StatisticItem"' + value: + type: string + description: 'The value of item' + + MapStatisticItem: + title: MapStatisticItem + allOf: + $ref: "#/definitions/BaseStatisticItem" + properties: + type: + enum: [MapStatisticItem] + description: 'set to "MapStatisticItem"' + value: + type: array + description: 'named statistic values' + items: + type: array + properties: + name: + type: string + description: 'item name' + value: + type: string + description: 'item value' + + RingStatisticItem: + title: RingStatisticItem + allOf: + $ref: "#/definitions/BaseStatisticItem" + properties: + type: + enum: [RingStatisticItem] + description: 'set to "RingStatisticItem"' + size: + type: integer + description: 'for RingStatisticItem objects, the size of the ring' + value: + type: array + description: 'named ring statistic values' + items: + type: array + properties: + name: + type: string + description: 'item name' + value: + type: string + description: 'item value' SearchResultZone: title: SearchResultZone diff --git a/pdns/ws-api.cc b/pdns/ws-api.cc index 4258f3d0b4..dd390380f0 100644 --- a/pdns/ws-api.cc +++ b/pdns/ws-api.cc @@ -30,15 +30,23 @@ #include "json.hh" #include "version.hh" #include "arguments.hh" +#include "dnsparser.hh" +#include "responsestats.hh" +#include "statbag.hh" #include #include #include #include #include -extern string s_programname; using json11::Json; +extern string s_programname; +extern ResponseStats g_rs; +#ifndef RECURSOR +extern StatBag S; +#endif + #ifndef HAVE_STRCASESTR /* @@ -196,12 +204,15 @@ void apiServerStatistics(HttpRequest* req, HttpResponse* resp) { if(req->method != "GET") throw HttpMethodNotAllowedException(); - map items; - productServerStatisticsFetch(items); + 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(); Json::array doc; - typedef map items_t; - for(const items_t::value_type& item : items) { + for(const auto& item : general_stats) { doc.push_back(Json::object { { "type", "StatisticItem" }, { "name", item.first }, @@ -209,6 +220,66 @@ void apiServerStatistics(HttpRequest* req, HttpResponse* resp) { }); } + { + Json::array values; + for(const auto& item : resp_qtype_stats) { + if (item.second == 0) + continue; + values.push_back(Json::object { + { "name", DNSRecordContent::NumberToType(item.first) }, + { "value", std::to_string(item.second) }, + }); + } + + doc.push_back(Json::object { + { "type", "MapStatisticItem" }, + { "name", "queries-by-qtype" }, + { "value", values }, + }); + } + + { + Json::array values; + for(const auto& item : resp_size_stats) { + if (item.second == 0) + continue; + + values.push_back(Json::object { + { "name", std::to_string(item.first) }, + { "value", std::to_string(item.second) }, + }); + } + + doc.push_back(Json::object { + { "type", "MapStatisticItem" }, + { "name", "response-sizes" }, + { "value", values }, + }); + } + +#ifndef RECURSOR + for(const auto& ringName : S.listRings()) { + Json::array values; + const auto& ring = S.getRing(ringName); + for(const auto& item : ring) { + if (item.second == 0) + continue; + + values.push_back(Json::object { + { "name", item.first }, + { "value", std::to_string(item.second) }, + }); + } + + doc.push_back(Json::object { + { "type", "RingStatisticItem" }, + { "name", ringName }, + { "size", std::to_string(S.getRingSize(ringName)) }, + { "value", values }, + }); + } +#endif + resp->setBody(doc); } diff --git a/regression-tests.api/runtests.py b/regression-tests.api/runtests.py index b6270241d6..7f17a81831 100755 --- a/regression-tests.api/runtests.py +++ b/regression-tests.api/runtests.py @@ -100,6 +100,8 @@ common_args = [ "--api-key="+APIKEY ] +run_check_call(["make", "-C", "../pdns", "sdig"]) + if daemon == 'authoritative': # Prepare sqlite DB with some zones. @@ -162,6 +164,9 @@ if not available: serverproc.wait() sys.exit(2) +print "Query for example.com/A to create statistic data..." +run_check_call(["../pdns/sdig", "127.0.0.1", str(DNSPORT), "example.com", "A"]) + print "Running tests..." returncode = 0 test_env = {} diff --git a/regression-tests.api/test_Servers.py b/regression-tests.api/test_Servers.py index 7b08d763ab..10bd99d880 100644 --- a/regression-tests.api/test_Servers.py +++ b/regression-tests.api/test_Servers.py @@ -39,5 +39,18 @@ class Servers(ApiTestCase): def test_read_statistics(self): r = self.session.get(self.url("/api/v1/servers/localhost/statistics")) self.assert_success_json(r) - data = dict([(r['name'], r['value']) for r in r.json()]) - self.assertIn('uptime', data) + data = r.json() + self.assertIn('uptime', [e['name'] for e in data]) + if is_auth(): + print data + qtype_stats, respsize_stats, queries_stats = None, None, None + for elem in data: + if elem['type'] == 'MapStatisticItem' and elem['name'] == 'queries-by-qtype': + qtype_stats = elem['value'] + elif elem['type'] == 'MapStatisticItem' and elem['name'] == 'response-sizes': + respsize_stats = elem['value'] + elif elem['type'] == 'RingStatisticItem' and elem['name'] == 'queries': + queries_stats = elem['value'] + self.assertIn('A', [e['name'] for e in qtype_stats]) + self.assertIn('60', [e['name'] for e in respsize_stats]) + self.assertIn('example.com/A', [e['name'] for e in queries_stats])