Add /api/stats/database/upstreams

Signed-off-by: DL6ER <dl6er@dl6er.de>
This commit is contained in:
DL6ER 2019-12-06 10:33:22 +00:00
parent a7500f1d58
commit ef5cfe0a17
No known key found for this signature in database
GPG Key ID: 00135ACBD90B28DD
6 changed files with 205 additions and 4 deletions

View File

@ -141,7 +141,8 @@ int api_auth(struct mg_connection *conn)
for(unsigned int i = 0; i < API_MAX_CLIENTS; i++)
{
// Expired slow, mark as unused
if(auth_data[i].valid_until < now)
if(auth_data[i].used &&
auth_data[i].valid_until < now)
{
if(config.debug & DEBUG_API)
{

View File

@ -9,6 +9,8 @@
* Please see LICENSE file for your rights under this license. */
#include "../cJSON/cJSON.h"
// logg()
#include "../log.h"
#define JSON_NEW_OBJ() cJSON_CreateObject();
#define JSON_NEW_ARRAY() cJSON_CreateArray();
@ -21,6 +23,7 @@
{ \
cJSON_Delete(object); \
send_http_internal_error(conn); \
logg("JSON_OBJ_COPY_STR FAILED (key: \"%s\", string: \"%s\")!", key, string); \
return 500; \
} \
cJSON_AddItemToObject(object, key, string_item); \
@ -32,6 +35,7 @@
{ \
cJSON_Delete(object); \
send_http_internal_error(conn); \
logg("JSON_OBJ_REF_STR FAILED (key: \"%s\", string: \"%s\")!", key, string); \
return 500; \
} \
cJSON_AddItemToObject(object, key, string_item); \
@ -42,6 +46,7 @@
{ \
cJSON_Delete(object); \
send_http_internal_error(conn); \
logg("JSON_OBJ_ADD_NUMBER FAILED!"); \
return 500; \
} \
}
@ -52,6 +57,7 @@
{ \
cJSON_Delete(object); \
send_http_internal_error(conn); \
logg("JSON_OBJ_ADD_NULL FAILED!"); \
return 500; \
} \
cJSON_AddItemToObject(object, key, null_item); \
@ -63,6 +69,7 @@
{ \
cJSON_Delete(object); \
send_http_internal_error(conn); \
logg("JSON_OBJ_ADD_BOOL FAILED!"); \
return 500; \
} \
cJSON_AddItemToObject(object, key, bool_item); \
@ -84,6 +91,7 @@
{ \
cJSON_Delete(array); \
send_http_internal_error(conn); \
logg("JSON_ARRAY_REF_STR FAILED!"); \
return 500; \
} \
cJSON_AddItemToArray(array, string_item); \
@ -95,6 +103,7 @@
{ \
cJSON_Delete(array); \
send_http_internal_error(conn); \
logg("JSON_ARRAY_COPY_STR FAILED!"); \
return 500; \
} \
cJSON_AddItemToArray(array, string_item); \
@ -113,6 +122,7 @@
{ \
cJSON_Delete(object); \
send_http_internal_error(conn); \
logg("JSON_SEND_OBJECT FAILED!"); \
return 500; \
} \
send_http(conn, "application/json; charset=utf-8", msg); \
@ -126,6 +136,7 @@
{ \
cJSON_Delete(object); \
send_http_internal_error(conn); \
logg("JSON_SEND_OBJECT_CODE FAILED!"); \
return 500; \
} \
send_http_code(conn, "application/json; charset=utf-8", code, msg); \
@ -139,6 +150,7 @@
{ \
cJSON_Delete(object); \
send_http_internal_error(conn); \
logg("JSON_SEND_OBJECT_AND_HEADERS FAILED!"); \
return 500; \
} \
send_http(conn, "application/json; charset=utf-8", additional_headers, msg); \
@ -153,6 +165,7 @@
{ \
cJSON_Delete(object); \
send_http_internal_error(conn); \
logg("JSON_SEND_OBJECT_AND_HEADERS_CODE FAILED!"); \
return 500; \
} \
send_http_code(conn, "application/json; charset=utf-8", additional_headers, code, msg); \

View File

@ -135,6 +135,10 @@ int api_handler(struct mg_connection *conn, void *ignored)
{
ret = api_stats_database_query_types(conn);
}
else if(startsWith("/api/stats/database/upstreams", request->local_uri))
{
ret = api_stats_database_upstreams(conn);
}
/******************************** api/version ****************************/
else if(startsWith("/api/version", request->local_uri))
{

View File

@ -33,6 +33,7 @@ int api_stats_database_top_items(bool blocked, bool domains, struct mg_connectio
int api_stats_database_summary(struct mg_connection *conn);
int api_stats_database_overTime_clients(struct mg_connection *conn);
int api_stats_database_query_types(struct mg_connection *conn);
int api_stats_database_upstreams(struct mg_connection *conn);
// FTL methods
int api_ftl_clientIP(struct mg_connection *conn);

View File

@ -273,7 +273,20 @@ int api_stats_database_top_items(bool blocked, bool domains, struct mg_connectio
if( rc != SQLITE_OK ){
logg("api_stats_database_overTime_history() - SQL error prepare (%i): %s",
rc, sqlite3_errmsg(FTL_db));
return false;
dbclose();
// Relock shared memory
lock_shm();
cJSON *json = JSON_NEW_OBJ();
JSON_OBJ_ADD_NUMBER(json, "from", from);
JSON_OBJ_ADD_NUMBER(json, "until", until);
JSON_OBJ_REF_STR(json, "querystr", querystr);
return send_json_error(conn, 500,
"internal_error",
"Failed to prepare query string",
json);
}
// Bind from to prepared statement
@ -350,7 +363,9 @@ int api_stats_database_top_items(bool blocked, bool domains, struct mg_connectio
JSON_OBJ_COPY_STR(item, (domains ? "domain" : "ip"), string);
// Add empty name field for top_client requests
if(!domains)
{
JSON_OBJ_REF_STR(item, "name", "");
}
JSON_OBJ_ADD_NUMBER(item, "count", count);
JSON_ARRAY_ADD_ITEM(top_items, item);
total += count;
@ -419,6 +434,10 @@ int api_stats_database_summary(struct mg_connection *conn)
if(sum_queries < 0 || blocked_queries < 0 || total_clients < 0)
{
// Close (= unlock) database connection
dbclose();
// Relock shared memory
lock_shm();
@ -772,3 +791,166 @@ int api_stats_database_query_types(struct mg_connection *conn)
// Send JSON object
JSON_SEND_OBJECT(json);
}
int api_stats_database_upstreams(struct mg_connection *conn)
{
int from = 0, until = 0;
const struct mg_request_info *request = mg_get_request_info(conn);
if(request->query_string != NULL)
{
int num;
if((num = get_int_var(request->query_string, "from")) > 0)
from = num;
if((num = get_int_var(request->query_string, "until")) > 0)
until = num;
}
// Check if we received the required information
if(from == 0 || until == 0)
{
cJSON *json = JSON_NEW_OBJ();
JSON_OBJ_ADD_NUMBER(json, "from", from);
JSON_OBJ_ADD_NUMBER(json, "until", until);
return send_json_error(conn, 400,
"bad_request",
"You need to specify both \"from\" and \"until\" in the request.",
json);
}
// Unlock shared memory (DNS resolver can continue to work while we're preforming database queries)
unlock_shm();
// Open the database (this also locks the database)
dbopen();
// Perform simple SQL queries
unsigned int sum_queries = 0;
const char *querystr;
querystr = "SELECT COUNT(*) FROM queries "
"WHERE timestamp >= :from AND timestamp <= :until "
"AND status = 2";
int forwarded_queries = db_query_int_from_until(querystr, from, until);
sum_queries = forwarded_queries;
querystr = "SELECT COUNT(*) FROM queries "
"WHERE timestamp >= :from AND timestamp <= :until "
"AND status = 3";
int cached_queries = db_query_int_from_until(querystr, from, until);
sum_queries += cached_queries;
querystr = "SELECT COUNT(*) FROM queries "
"WHERE timestamp >= :from AND timestamp <= :until "
"AND status != 0 AND status != 2 AND status != 3";
int blocked_queries = db_query_int_from_until(querystr, from, until);
sum_queries += blocked_queries;
querystr = "SELECT forward,COUNT(*) FROM queries "
"WHERE timestamp >= :from AND timestamp <= :until "
"AND forward IS NOT NULL "
"GROUP BY forward ORDER BY forward";
// Prepare SQLite statement
sqlite3_stmt *stmt;
int rc = sqlite3_prepare_v2(FTL_db, querystr, -1, &stmt, NULL);
if( rc != SQLITE_OK ){
logg("api_stats_database_overTime_clients() - SQL error prepare (%i): %s",
rc, sqlite3_errmsg(FTL_db));
// Relock shared memory
lock_shm();
cJSON *json = JSON_NEW_OBJ();
JSON_OBJ_ADD_NUMBER(json, "from", from);
JSON_OBJ_ADD_NUMBER(json, "until", until);
return send_json_error(conn, 500,
"internal_error",
"Failed to prepare statement",
json);
}
// Bind from to prepared statement
if((rc = sqlite3_bind_int(stmt, 1, from)) != SQLITE_OK)
{
logg("api_stats_database_overTime_clients(): Failed to bind from (error %d) - %s",
rc, sqlite3_errmsg(FTL_db));
sqlite3_reset(stmt);
sqlite3_finalize(stmt);
dbclose();
// Relock shared memory
lock_shm();
cJSON *json = JSON_NEW_OBJ();
JSON_OBJ_ADD_NUMBER(json, "from", from);
JSON_OBJ_ADD_NUMBER(json, "until", until);
return send_json_error(conn, 500,
"internal_error",
"Failed to bind from",
json);
}
// Bind until to prepared statement
if((rc = sqlite3_bind_int(stmt, 2, until)) != SQLITE_OK)
{
logg("api_stats_database_overTime_clients(): Failed to bind until (error %d) - %s",
rc, sqlite3_errmsg(FTL_db));
sqlite3_reset(stmt);
sqlite3_finalize(stmt);
dbclose();
// Relock shared memory
lock_shm();
cJSON *json = JSON_NEW_OBJ();
JSON_OBJ_ADD_NUMBER(json, "from", from);
JSON_OBJ_ADD_NUMBER(json, "until", until);
return send_json_error(conn, 500,
"internal_error",
"Failed to bind until",
json);
}
// Loop over clients and accumulate results
cJSON *upstreams = JSON_NEW_ARRAY();
while((rc = sqlite3_step(stmt)) == SQLITE_ROW)
{
const char* upstream = (char*)sqlite3_column_text(stmt, 0);
const int count = sqlite3_column_int(stmt, 1);
cJSON *item = JSON_NEW_OBJ();
JSON_OBJ_COPY_STR(item, "ip", upstream);
JSON_OBJ_REF_STR(item, "name", "");
JSON_OBJ_ADD_NUMBER(item, "count", count);
JSON_ARRAY_ADD_ITEM(upstreams, item);
sum_queries += count;
}
sqlite3_finalize(stmt);
// Add cache and blocklist as upstreams
cJSON *cached = JSON_NEW_OBJ();
JSON_OBJ_REF_STR(cached, "ip", "");
JSON_OBJ_REF_STR(cached, "name", "cache");
JSON_OBJ_ADD_NUMBER(cached, "count", cached_queries);
JSON_ARRAY_ADD_ITEM(upstreams, cached);
logg("%d / %d / %d", forwarded_queries, cached_queries, blocked_queries);
cJSON *blocked = JSON_NEW_OBJ();
JSON_OBJ_REF_STR(blocked, "ip", "");
JSON_OBJ_REF_STR(blocked, "name", "blocklist");
JSON_OBJ_ADD_NUMBER(blocked, "count", blocked_queries);
JSON_ARRAY_ADD_ITEM(upstreams, blocked);
// Close (= unlock) database connection
dbclose();
// Re-lock shared memory before returning back to router subroutine
lock_shm();
// Send JSON object
cJSON *json = JSON_NEW_OBJ();
JSON_OBJ_ADD_ITEM(json, "upstreams", upstreams);
JSON_OBJ_ADD_NUMBER(json, "forwarded_queries", forwarded_queries);
JSON_OBJ_ADD_NUMBER(json, "total_queries", sum_queries);
JSON_SEND_OBJECT(json);
}

View File

@ -252,7 +252,7 @@ void _lock_shm(const char* func, const int line, const char * file) {
}
if(result != 0)
logg("Failed to obtain SHM lock: %s", strerror(result));
logg("Failed to obtain SHM lock: %s in %s() (%s:%i)", strerror(result), func, file, line);
}
void _unlock_shm(const char* func, const int line, const char * file) {
@ -262,7 +262,7 @@ void _unlock_shm(const char* func, const int line, const char * file) {
logg("Removed lock in %s() (%s:%i)", func, file, line);
if(result != 0)
logg("Failed to unlock SHM lock: %s", strerror(result));
logg("Failed to unlock SHM lock: %s in %s() (%s:%i)", strerror(result), func, file, line);
}
bool init_shmem(void)