From: Remi Gacogne Date: Fri, 2 Jul 2021 14:48:19 +0000 (+0200) Subject: dnsdist: Implement a web endpoint to get metrics for only one pool X-Git-Tag: dnsdist-1.7.0-alpha1~102^2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=refs%2Fpull%2F10560%2Fhead;p=thirdparty%2Fpdns.git dnsdist: Implement a web endpoint to get metrics for only one pool --- diff --git a/pdns/dnsdist-web.cc b/pdns/dnsdist-web.cc index 5eca7074e4..77f4961d26 100644 --- a/pdns/dnsdist-web.cc +++ b/pdns/dnsdist-web.cc @@ -872,6 +872,63 @@ static void handleJSONStats(const YaHTTP::Request& req, YaHTTP::Response& resp) } } +static void addServerToJSON(Json::array& servers, int id, const std::shared_ptr& a) +{ + string status; + if (a->availability == DownstreamState::Availability::Up) { + status = "UP"; + } + else if (a->availability == DownstreamState::Availability::Down) { + status = "DOWN"; + } + else { + status = (a->upStatus ? "up" : "down"); + } + + Json::array pools; + for(const auto& p: a->pools) { + pools.push_back(p); + } + + Json::object server { + {"id", id}, + {"name", a->getName()}, + {"address", a->remote.toStringWithPort()}, + {"state", status}, + {"qps", (double)a->queryLoad}, + {"qpsLimit", (double)a->qps.getRate()}, + {"outstanding", (double)a->outstanding}, + {"reuseds", (double)a->reuseds}, + {"weight", (double)a->weight}, + {"order", (double)a->order}, + {"pools", pools}, + {"latency", (double)(a->latencyUsec/1000.0)}, + {"queries", (double)a->queries}, + {"responses", (double)a->responses}, + {"sendErrors", (double)a->sendErrors}, + {"tcpDiedSendingQuery", (double)a->tcpDiedSendingQuery}, + {"tcpDiedReadingResponse", (double)a->tcpDiedReadingResponse}, + {"tcpGaveUp", (double)a->tcpGaveUp}, + {"tcpConnectTimeouts", (double)a->tcpConnectTimeouts}, + {"tcpReadTimeouts", (double)a->tcpReadTimeouts}, + {"tcpWriteTimeouts", (double)a->tcpWriteTimeouts}, + {"tcpCurrentConnections", (double)a->tcpCurrentConnections}, + {"tcpMaxConcurrentConnections", (double)a->tcpMaxConcurrentConnections}, + {"tcpNewConnections", (double)a->tcpNewConnections}, + {"tcpReusedConnections", (double)a->tcpReusedConnections}, + {"tcpAvgQueriesPerConnection", (double)a->tcpAvgQueriesPerConnection}, + {"tcpAvgConnectionDuration", (double)a->tcpAvgConnectionDuration}, + {"dropRate", (double)a->dropRate} + }; + + /* sending a latency for a DOWN server doesn't make sense */ + if (a->availability == DownstreamState::Availability::Down) { + server["latency"] = nullptr; + } + + servers.push_back(std::move(server)); +} + static void handleStats(const YaHTTP::Request& req, YaHTTP::Response& resp) { handleCORS(req, resp); @@ -881,55 +938,7 @@ static void handleStats(const YaHTTP::Request& req, YaHTTP::Response& resp) auto localServers = g_dstates.getLocal(); int num = 0; for (const auto& a : *localServers) { - string status; - if(a->availability == DownstreamState::Availability::Up) - status = "UP"; - else if(a->availability == DownstreamState::Availability::Down) - status = "DOWN"; - else - status = (a->upStatus ? "up" : "down"); - - Json::array pools; - for(const auto& p: a->pools) - pools.push_back(p); - - Json::object server{ - {"id", num++}, - {"name", a->getName()}, - {"address", a->remote.toStringWithPort()}, - {"state", status}, - {"qps", (double)a->queryLoad}, - {"qpsLimit", (double)a->qps.getRate()}, - {"outstanding", (double)a->outstanding}, - {"reuseds", (double)a->reuseds}, - {"weight", (double)a->weight}, - {"order", (double)a->order}, - {"pools", pools}, - {"latency", (double)(a->latencyUsec/1000.0)}, - {"queries", (double)a->queries}, - {"responses", (double)a->responses}, - {"sendErrors", (double)a->sendErrors}, - {"tcpDiedSendingQuery", (double)a->tcpDiedSendingQuery}, - {"tcpDiedReadingResponse", (double)a->tcpDiedReadingResponse}, - {"tcpGaveUp", (double)a->tcpGaveUp}, - {"tcpConnectTimeouts", (double)a->tcpConnectTimeouts}, - {"tcpReadTimeouts", (double)a->tcpReadTimeouts}, - {"tcpWriteTimeouts", (double)a->tcpWriteTimeouts}, - {"tcpCurrentConnections", (double)a->tcpCurrentConnections}, - {"tcpMaxConcurrentConnections", (double)a->tcpMaxConcurrentConnections}, - {"tcpNewConnections", (double)a->tcpNewConnections}, - {"tcpReusedConnections", (double)a->tcpReusedConnections}, - {"tcpAvgQueriesPerConnection", (double)a->tcpAvgQueriesPerConnection}, - {"tcpAvgConnectionDuration", (double)a->tcpAvgConnectionDuration}, - {"dropRate", (double)a->dropRate} - }; - - /* sending a latency for a DOWN server doesn't make sense */ - if (a->availability == DownstreamState::Availability::Down) { - server["latency"] = nullptr; - } - - servers.push_back(server); + addServerToJSON(servers, num++, a); } Json::array frontends; @@ -1102,6 +1111,57 @@ static void handleStats(const YaHTTP::Request& req, YaHTTP::Response& resp) resp.body = my_json.dump(); } +static void handlePoolStats(const YaHTTP::Request& req, YaHTTP::Response& resp) +{ + handleCORS(req, resp); + const auto poolName = req.getvars.find("name"); + if (poolName == req.getvars.end()) { + resp.status = 400; + return; + } + + resp.status = 200; + Json::array doc; + + auto localPools = g_pools.getLocal(); + const auto poolIt = localPools->find(poolName->second); + if (poolIt == localPools->end()) { + resp.status = 404; + return; + } + + const auto& pool = poolIt->second; + const auto& cache = pool->packetCache; + Json::object entry { + { "name", poolName->second }, + { "serversCount", (double) pool->countServers(false) }, + { "cacheSize", (double) (cache ? cache->getMaxEntries() : 0) }, + { "cacheEntries", (double) (cache ? cache->getEntriesCount() : 0) }, + { "cacheHits", (double) (cache ? cache->getHits() : 0) }, + { "cacheMisses", (double) (cache ? cache->getMisses() : 0) }, + { "cacheDeferredInserts", (double) (cache ? cache->getDeferredInserts() : 0) }, + { "cacheDeferredLookups", (double) (cache ? cache->getDeferredLookups() : 0) }, + { "cacheLookupCollisions", (double) (cache ? cache->getLookupCollisions() : 0) }, + { "cacheInsertCollisions", (double) (cache ? cache->getInsertCollisions() : 0) }, + { "cacheTTLTooShorts", (double) (cache ? cache->getTTLTooShorts() : 0) } + }; + + Json::array servers; + int num = 0; + for (const auto& a : *pool->getServers()) { + addServerToJSON(servers, num, a.second); + num++; + } + + resp.headers["Content-Type"] = "application/json"; + Json my_json = Json::object { + { "stats", entry }, + { "servers", servers } + }; + + resp.body = my_json.dump(); +} + static void handleStatsOnly(const YaHTTP::Request& req, YaHTTP::Response& resp) { handleCORS(req, resp); @@ -1298,6 +1358,7 @@ void registerBuiltInWebHandlers() registerWebHandler("/jsonstat", handleJSONStats); registerWebHandler("/metrics", handlePrometheus); registerWebHandler("/api/v1/servers/localhost", handleStats); + registerWebHandler("/api/v1/servers/localhost/pool", handlePoolStats); registerWebHandler("/api/v1/servers/localhost/statistics", handleStatsOnly); registerWebHandler("/api/v1/servers/localhost/config", handleConfigDump); registerWebHandler("/api/v1/servers/localhost/config/allow-from", handleAllowFrom); diff --git a/pdns/dnsdistdist/docs/guides/webserver.rst b/pdns/dnsdistdist/docs/guides/webserver.rst index f980cd3adf..d2f1e9699e 100755 --- a/pdns/dnsdistdist/docs/guides/webserver.rst +++ b/pdns/dnsdistdist/docs/guides/webserver.rst @@ -387,6 +387,15 @@ URL Endpoints ] } +.. http:get:: /api/v1/servers/localhost/pool?name=pool-name + + .. versionadded:: 1.6.0 + + Get a quick overview of the pool named "pool-name". + + :>json list: A list of metrics related to that pool + :>json list servers: A list of :json:object:`Server` objects present in that pool + JSON Objects ~~~~~~~~~~~~ diff --git a/regression-tests.dnsdist/test_API.py b/regression-tests.dnsdist/test_API.py index 2445ce2444..ff91b18fe2 100644 --- a/regression-tests.dnsdist/test_API.py +++ b/regression-tests.dnsdist/test_API.py @@ -23,7 +23,7 @@ class TestAPIBasics(DNSDistTest): _config_params = ['_testServerPort', '_webServerPort', '_webServerBasicAuthPassword', '_webServerAPIKey'] _config_template = """ setACL({"127.0.0.1/32", "::1/128"}) - newServer{address="127.0.0.1:%s"} + newServer{address="127.0.0.1:%s", pool={'', 'mypool'}} webserver("127.0.0.1:%s") setWebserverConfig({password="%s", apiKey="%s"}) """ @@ -134,6 +134,42 @@ class TestAPIBasics(DNSDistTest): for key in ['id', 'cacheSize', 'cacheEntries', 'cacheHits', 'cacheMisses', 'cacheDeferredInserts', 'cacheDeferredLookups', 'cacheLookupCollisions', 'cacheInsertCollisions', 'cacheTTLTooShorts']: self.assertTrue(pool[key] >= 0) + def testServersLocalhostPool(self): + """ + API: /api/v1/servers/localhost/pool?name=mypool + """ + headers = {'x-api-key': self._webServerAPIKey} + url = 'http://127.0.0.1:' + str(self._webServerPort) + '/api/v1/servers/localhost/pool?name=mypool' + r = requests.get(url, headers=headers, timeout=self._webTimeout) + self.assertTrue(r) + self.assertEqual(r.status_code, 200) + self.assertTrue(r.json()) + content = r.json() + + self.assertIn('stats', content) + self.assertIn('servers', content) + + for key in ['name', 'cacheSize', 'cacheEntries', 'cacheHits', 'cacheMisses', 'cacheDeferredInserts', 'cacheDeferredLookups', 'cacheLookupCollisions', 'cacheInsertCollisions', 'cacheTTLTooShorts']: + self.assertIn(key, content['stats']) + + for key in ['cacheSize', 'cacheEntries', 'cacheHits', 'cacheMisses', 'cacheDeferredInserts', 'cacheDeferredLookups', 'cacheLookupCollisions', 'cacheInsertCollisions', 'cacheTTLTooShorts']: + self.assertTrue(content['stats'][key] >= 0) + + for server in content['servers']: + for key in ['id', 'latency', 'name', 'weight', 'outstanding', 'qpsLimit', + 'reuseds', 'state', 'address', 'pools', 'qps', 'queries', 'order', 'sendErrors', + 'dropRate', 'responses', 'tcpDiedSendingQuery', 'tcpDiedReadingResponse', + 'tcpGaveUp', 'tcpReadTimeouts', 'tcpWriteTimeouts', 'tcpCurrentConnections', + 'tcpNewConnections', 'tcpReusedConnections', 'tcpAvgQueriesPerConnection', + 'tcpAvgConnectionDuration']: + self.assertIn(key, server) + + for key in ['id', 'latency', 'weight', 'outstanding', 'qpsLimit', 'reuseds', + 'qps', 'queries', 'order']: + self.assertTrue(server[key] >= 0) + + self.assertTrue(server['state'] in ['up', 'down', 'UP', 'DOWN']) + def testServersIDontExist(self): """ API: /api/v1/servers/idonotexist (should be 404)