exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

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:
Msrc/exchange/taler-exchange-httpd_aml-statistics-get.c | 82++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Msrc/exchange/taler-exchange-httpd_config.h | 2+-
Msrc/exchangedb/pg_select_aml_statistics.c | 116++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Msrc/exchangedb/pg_select_aml_statistics.h | 16++++++++++------
Msrc/include/taler/taler_exchangedb_plugin.h | 30++++++++++++++++++++++++------
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); /**