]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Implement a web endpoint to get metrics for only one pool 10560/head
authorRemi Gacogne <remi.gacogne@powerdns.com>
Fri, 2 Jul 2021 14:48:19 +0000 (16:48 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Fri, 2 Jul 2021 14:48:19 +0000 (16:48 +0200)
pdns/dnsdist-web.cc
pdns/dnsdistdist/docs/guides/webserver.rst
regression-tests.dnsdist/test_API.py

index 5eca7074e4e62b94fcf579bdfb3fbed4e9582906..77f4961d263384bb55ce022154f46ae0d2d5e2e6 100644 (file)
@@ -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<DownstreamState>& 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);
index f980cd3adf3154867be2dbf6f6c5ef6358653338..d2f1e9699ecb938b4acdf60edf2f8a9eddcdd1e2 100755 (executable)
@@ -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
 ~~~~~~~~~~~~
 
index 2445ce2444c065b81499294cd92e564220673a68..ff91b18fe2bb0e4250f31eb591e59a2a40191c95 100644 (file)
@@ -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)