commit 2719fdbf22a155861fe0d6374bfb9da787dd0a19
parent b3401596a3ef38220da7a4c94ea4bfe10aca71b0
Author: Christian Grothoff <christian@grothoff.org>
Date: Thu, 3 Jul 2025 13:17:11 +0200
implement bulk statistics support (fixes #10128)
Diffstat:
5 files changed, 208 insertions(+), 38 deletions(-)
diff --git a/src/exchange/taler-exchange-httpd_aml-statistics-get.c b/src/exchange/taler-exchange-httpd_aml-statistics-get.c
@@ -31,6 +31,38 @@
#include "taler-exchange-httpd_aml-statistics-get.h"
#include "taler-exchange-httpd_metrics.h"
+/**
+ * Maximum number of statistics that can be requested in one go.
+ */
+#define MAX_STATS 64
+
+
+/**
+ * Function called with AML statistics (counters).
+ *
+ * @param cls JSON array to build
+ * @param name name of the counter
+ * @param cnt number of events for @a name in the query range
+ */
+static void
+add_stat (
+ void *cls,
+ const char *name,
+ uint64_t cnt)
+{
+ json_t *stats = cls;
+
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ stats,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("event",
+ name),
+ GNUNET_JSON_pack_uint64 ("counter",
+ cnt))));
+}
+
MHD_RESULT
TEH_handler_aml_kyc_statistics_get (
@@ -40,8 +72,9 @@ TEH_handler_aml_kyc_statistics_get (
{
struct GNUNET_TIME_Timestamp start_date;
struct GNUNET_TIME_Timestamp end_date;
- const char *name = args[0];
- uint64_t cnt;
+ const char *all_names = args[0];
+ json_t *stats;
+ size_t max_names;
if ( (NULL == args[0]) ||
(NULL != args[1]) )
@@ -53,7 +86,6 @@ TEH_handler_aml_kyc_statistics_get (
TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
rc->url);
}
-
TALER_MHD_parse_request_timestamp (rc->connection,
"start_date",
&start_date);
@@ -64,19 +96,51 @@ TEH_handler_aml_kyc_statistics_get (
end_date = GNUNET_TIME_absolute_to_timestamp (
GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_SECONDS));
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Looking for stat on %s from [%llu,%llu)\n",
- name,
+ "Looking for stats on %s from [%llu,%llu)\n",
+ all_names,
(unsigned long long) start_date.abs_time.abs_value_us,
(unsigned long long) end_date.abs_time.abs_value_us);
+ /* Estimate number of names in 'all_names' */
+ max_names = 1;
+ for (size_t i = 0; '\0' != all_names[i]; i++)
+ if (' ' == all_names[i])
+ max_names++;
+ if (max_names > MAX_STATS)
{
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_URI_TOO_LONG,
+ TALER_EC_GENERIC_URI_TOO_LONG,
+ rc->url);
+ }
+ stats = json_array ();
+ GNUNET_assert (NULL != stats);
+ {
+ char *buf;
+ const char *names[GNUNET_NZL (max_names)];
enum GNUNET_DB_QueryStatus qs;
+ size_t num_names = 0;
+ buf = GNUNET_strdup (all_names);
+ for (const char *tok = strtok (buf,
+ " ");
+ NULL != tok;
+ tok = strtok (NULL,
+ " "))
+ {
+ GNUNET_assert (num_names < max_names);
+ names[num_names++] = tok;
+ }
qs = TEH_plugin->select_aml_statistics (
TEH_plugin->cls,
- name,
+ num_names,
+ names,
start_date,
end_date,
- &cnt);
+ &add_stat,
+ stats);
+ GNUNET_free (buf);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
@@ -100,8 +164,8 @@ TEH_handler_aml_kyc_statistics_get (
return TALER_MHD_REPLY_JSON_PACK (
rc->connection,
MHD_HTTP_OK,
- GNUNET_JSON_pack_uint64 ("counter",
- cnt));
+ GNUNET_JSON_pack_array_steal ("statistics",
+ stats));
}
}
diff --git a/src/exchange/taler-exchange-httpd_config.h b/src/exchange/taler-exchange-httpd_config.h
@@ -41,7 +41,7 @@
*
* Returned via both /config and /keys endpoints.
*/
-#define EXCHANGE_PROTOCOL_VERSION "30:0:8"
+#define EXCHANGE_PROTOCOL_VERSION "30:1:8"
/**
diff --git a/src/exchangedb/pg_select_aml_statistics.c b/src/exchangedb/pg_select_aml_statistics.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2024 Taler Systems SA
+ Copyright (C) 2024, 2025 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -26,37 +26,121 @@
#include "pg_helper.h"
+/**
+ * Closure for #get_statistics_cb().
+ */
+struct GetStatisticsContext
+{
+ /**
+ * Function to call per result.
+ */
+ TALER_EXCHANGEDB_AmlStatisticsCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Flag set to #GNUNET_OK as long as everything is fine.
+ */
+ enum GNUNET_GenericReturnValue status;
+
+};
+
+
+/**
+ * Invoke the callback for each result.
+ *
+ * @param cls a `struct GetStatisticsContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+get_statistics_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct GetStatisticsContext *ctx = cls;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ uint64_t val;
+ char *name;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("name",
+ &name),
+ GNUNET_PQ_result_spec_uint64 ("value",
+ &val),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->status = GNUNET_SYSERR;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ name,
+ val);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
enum GNUNET_DB_QueryStatus
TEH_PG_select_aml_statistics (
void *cls,
- const char *name,
+ size_t num_names,
+ const char *names[static num_names],
struct GNUNET_TIME_Timestamp start_date,
struct GNUNET_TIME_Timestamp end_date,
- uint64_t *cnt)
+ TALER_EXCHANGEDB_AmlStatisticsCallback cb,
+ void *cb_cls)
{
struct PostgresClosure *pg = cls;
+ struct GetStatisticsContext ctx = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (name),
+ GNUNET_PQ_query_param_array_ptrs_string (num_names,
+ names,
+ pg->conn),
GNUNET_PQ_query_param_timestamp (&start_date),
GNUNET_PQ_query_param_timestamp (&end_date),
GNUNET_PQ_query_param_end
};
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("count",
- cnt),
- GNUNET_PQ_result_spec_end
- };
+ enum GNUNET_DB_QueryStatus qs;
PREPARE (pg,
"select_aml_statistics",
"SELECT "
- " COUNT(*) AS count"
+ " event_type AS name"
+ ",COUNT(*) AS value"
" FROM kyc_events"
- " WHERE event_type=$1"
+ " WHERE event_type IN $1"
" AND event_timestamp >= $2"
- " AND event_timestamp < $3;");
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "select_aml_statistics",
- params,
- rs);
+ " AND event_timestamp < $3"
+ " GROUP BY event_type;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "select_aml_statistics",
+ params,
+ &get_statistics_cb,
+ &ctx);
+ GNUNET_PQ_cleanup_query_params_closures (params);
+ if (GNUNET_OK != ctx.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
}
diff --git a/src/exchangedb/pg_select_aml_statistics.h b/src/exchangedb/pg_select_aml_statistics.h
@@ -26,22 +26,26 @@
#include "taler/taler_exchangedb_plugin.h"
/**
- * Obtain the AML statistics for a given key and
+ * Obtain the AML statistics for a given set of @a names and
* timeframe.
*
* @param cls closure
- * @param name name of the statistic
+ * @param num_names length of the @e names array
+ * @param names array of names of the statistics to fetch
* @param start_date start of time range
* @param end_date end of time range
- * @param[out] cnt number of events in this time range
- * @return database transaction status, 0 if no threshold was set
+ * @param cb function to call on each statistic
+ * @param cb_cls closure for @a cb
+ * @return database transaction status
*/
enum GNUNET_DB_QueryStatus
TEH_PG_select_aml_statistics (
void *cls,
- const char *name,
+ size_t num_names,
+ const char *names[static num_names],
struct GNUNET_TIME_Timestamp start_date,
struct GNUNET_TIME_Timestamp end_date,
- uint64_t *cnt);
+ TALER_EXCHANGEDB_AmlStatisticsCallback cb,
+ void *cb_cls);
#endif
diff --git a/src/include/taler/taler_exchangedb_plugin.h b/src/include/taler/taler_exchangedb_plugin.h
@@ -3649,6 +3649,20 @@ typedef enum GNUNET_GenericReturnValue
/**
+ * Function called with AML statistics (counters).
+ *
+ * @param cls closure
+ * @param name name of the counter
+ * @param cnt number of events for @a name in the query range
+ */
+typedef void
+(*TALER_EXCHANGEDB_AmlStatisticsCallback)(
+ void *cls,
+ const char *name,
+ uint64_t cnt);
+
+
+/**
* Function called about reserve closing operations
* the aggregator triggered.
*
@@ -7615,23 +7629,27 @@ struct TALER_EXCHANGEDB_Plugin
/**
- * Obtain the AML statistics for a given key and
+ * Obtain the AML statistics for a given set of @a names and
* timeframe.
*
* @param cls closure
- * @param name name of the statistic
+ * @param num_names length of the @e names array
+ * @param names array of names of the statistics to fetch
* @param start_date start of time range
* @param end_date end of time range
- * @param[out] cnt number of events in this time range
- * @return database transaction status, 0 if no threshold was set
+ * @param cb function to call on each statistic
+ * @param cb_cls closure for @a cb
+ * @return database transaction status
*/
enum GNUNET_DB_QueryStatus
(*select_aml_statistics)(
void *cls,
- const char *name,
+ size_t num_names,
+ const char *names[static num_names],
struct GNUNET_TIME_Timestamp start_date,
struct GNUNET_TIME_Timestamp end_date,
- uint64_t *cnt);
+ TALER_EXCHANGEDB_AmlStatisticsCallback cb,
+ void *cb_cls);
/**