af9f6e5ca0
* feat(db): add SQL query wrapper for PostgreSQL placeholder conversion - Created query_wrapper module with functions to automatically convert SQLite ? placeholders to PostgreSQL $1, $2, ... format - Updated build.rs to handle mutually exclusive feature flags - Modified one query in mixnodes.rs as proof of concept - Added type conversions for PostgreSQL compatibility (u32->i64, u16->i32) This is a checkpoint commit before converting all queries to use the wrapper. * feat(nym-node-status-api): add PostgreSQL database support via feature flags Implement dual database support for SQLite and PostgreSQL through Cargo feature flags. The implementation uses a query wrapper that automatically converts SQLite-style ? placeholders to PostgreSQL-style $1, $2, ... placeholders at runtime. Key changes: - Add query wrapper functions that handle placeholder conversion - Convert all sqlx::query\! macros to use wrapper functions - Handle type conversions between databases (i64 vs i32) - Add feature-gated implementations for database-specific SQL syntax - Update Makefile with clippy targets for both database features - Document database support in README * feat(nym-node-status-agent): add multi-API support with random selection Agents can now connect to multiple APIs and randomly select one for each testrun: - Accept multiple --server arguments in format "address:port:auth_key" - Randomly shuffle server list before attempting connections - Try each server until a testrun is obtained - Submit results back only to the API that provided the testrun - Continue to next server if one is down or has no testruns available * feat(nym-node-status): implement primary/secondary server architecture - Agent now requests testruns only from primary server (first in list) - Results are submitted to all configured servers in parallel - Secondary servers accept external testruns via new v2 endpoint - Added auto-creation of gateway and testrun records on secondary servers - New database queries: get_or_create_gateway, insert_external_testrun - Client library enhanced with submit_results_with_context method * Bump Node status API version * Fix build workdir * Bump to 3.1.4 * Fix types and queries * 3.1.6 * Fix gateway perf, bump 3.1.7 * NodeId -> i32, 3.1.8 * Bump agent version * i64 -> i32 * Use image yq * Migration and more types * Update remaining JSONB columns * Simplify server config * Update build path * Change delimiter * bump agent * Split up pg and sqlite builds * More typing fixes, build-and-push script * Fix Dockerfile-pg * Bump node-status-api * TYping * Agent build script * More logging around testruns * Fail loudly on read errors * Cleanup * Debug get gateways query * Fix get_gateways query * Use pg cert, 3.1.16 * Submit regular results to primary server * Bump freshenss cutoff * Update Cargo.lock * fix: resolve rebase conflicts and compilation errors After rebasing onto develop, fixed several issues: - Fixed borrowed data escapes error by using sqlx::query directly in transaction functions - Removed unused imports and cleaned up code - Maintained database-specific implementations for transaction functions * fmt * Make PG default to make lives easier * Performance improvements for Explorer v2 * Fix sqlite build * Fix PG migration * Tests round 1 * DB tests * More tests * And some more tests * And some more, more tests * cargo fmt * Fix some failing lints * Fix lioness version problems * Clippy in tests --------- Co-authored-by: dynco-nym <173912580+dynco-nym@users.noreply.github.com>
172 lines
4.9 KiB
Rust
172 lines
4.9 KiB
Rust
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
use crate::config::upgrade_helpers::try_load_current_config;
|
|
use crate::node::key_rotation::active_keys::ActiveSphinxKeys;
|
|
use crate::node::NymNode;
|
|
use crate::throughput_tester::client::ThroughputTestingClient;
|
|
use crate::throughput_tester::global_stats::GlobalStatsUpdater;
|
|
use crate::throughput_tester::stats::ClientStats;
|
|
use futures::future::join_all;
|
|
use human_repr::HumanDuration;
|
|
use nym_task::ShutdownToken;
|
|
use rand::{thread_rng, Rng};
|
|
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
|
use std::path::PathBuf;
|
|
use std::time::Duration;
|
|
use tokio::runtime;
|
|
use tokio::runtime::Runtime;
|
|
use tokio::time::sleep;
|
|
use tracing::{info, info_span, instrument};
|
|
use tracing_indicatif::span_ext::IndicatifSpanExt;
|
|
|
|
pub(crate) mod client;
|
|
pub(crate) mod global_stats;
|
|
mod stats;
|
|
|
|
pub struct ThroughputTest {
|
|
node_runtime: Runtime,
|
|
clients_runtime: Runtime,
|
|
}
|
|
|
|
impl ThroughputTest {
|
|
fn new(senders: usize) -> anyhow::Result<Self> {
|
|
Ok(ThroughputTest {
|
|
node_runtime: runtime::Builder::new_multi_thread()
|
|
.enable_all()
|
|
.thread_name("nym-node-pool")
|
|
.build()?,
|
|
clients_runtime: runtime::Builder::new_multi_thread()
|
|
.enable_all()
|
|
.worker_threads(senders)
|
|
.thread_name("testing-clients-pool")
|
|
.build()?,
|
|
})
|
|
}
|
|
|
|
fn prepare_nymnode(&self, config_path: PathBuf) -> anyhow::Result<NymNode> {
|
|
self.node_runtime.block_on(async {
|
|
let mut config = try_load_current_config(config_path).await?;
|
|
|
|
// make sure to change bind address to localhost!
|
|
config
|
|
.mixnet
|
|
.bind_address
|
|
.set_ip(IpAddr::V4(Ipv4Addr::LOCALHOST));
|
|
|
|
let nym_node = NymNode::new(config).await?;
|
|
Ok(nym_node)
|
|
})
|
|
}
|
|
}
|
|
|
|
#[instrument(
|
|
skip_all,
|
|
fields(
|
|
sender_id = %sender_id
|
|
)
|
|
|
|
)]
|
|
#[allow(clippy::too_many_arguments)]
|
|
async fn run_testing_client(
|
|
sender_id: usize,
|
|
node_keys: ActiveSphinxKeys,
|
|
node_listener: SocketAddr,
|
|
packet_latency_threshold: Duration,
|
|
starting_sending_batch_size: usize,
|
|
starting_sending_delay: Duration,
|
|
stats: ClientStats,
|
|
shutdown_token: ShutdownToken,
|
|
) -> anyhow::Result<()> {
|
|
let _ = sender_id;
|
|
let client = ThroughputTestingClient::try_create(
|
|
starting_sending_delay,
|
|
starting_sending_batch_size,
|
|
packet_latency_threshold,
|
|
node_keys,
|
|
node_listener,
|
|
stats,
|
|
shutdown_token,
|
|
)
|
|
.await?;
|
|
|
|
// wait a random amount of time before actually starting to desync the clients a bit
|
|
// (so they wouldn't update their rates at the same time)
|
|
let delay = Duration::from_millis(thread_rng().gen_range(10..200));
|
|
info!(
|
|
"waiting for {} before attempting to start the processing loop",
|
|
delay.human_duration()
|
|
);
|
|
sleep(delay).await;
|
|
|
|
client.run().await
|
|
}
|
|
|
|
pub(crate) fn test_mixing_throughput(
|
|
config_path: PathBuf,
|
|
senders: usize,
|
|
packet_latency_threshold: Duration,
|
|
starting_sending_batch_size: usize,
|
|
starting_sending_delay: Duration,
|
|
output_directory: PathBuf,
|
|
) -> anyhow::Result<()> {
|
|
let tester = ThroughputTest::new(senders)?;
|
|
|
|
let nym_node = tester.prepare_nymnode(config_path)?;
|
|
let listener = nym_node.config().mixnet.bind_address;
|
|
|
|
let sphinx_keys = nym_node.active_sphinx_keys()?;
|
|
|
|
let mut stats = Vec::with_capacity(senders);
|
|
for _ in 0..senders {
|
|
stats.push(ClientStats::default())
|
|
}
|
|
|
|
let header_span = info_span!("header");
|
|
header_span.pb_start();
|
|
|
|
// Bit of a hack to show a full "-----" line underneath the header.
|
|
header_span.pb_set_length(1);
|
|
header_span.pb_set_position(1);
|
|
|
|
let mut tasks_handles = Vec::new();
|
|
|
|
for (sender_id, stats) in stats.iter().enumerate() {
|
|
let token = nym_node.shutdown_token(format!("dummy-load-client-{sender_id}"));
|
|
|
|
let client_future = run_testing_client(
|
|
sender_id,
|
|
sphinx_keys.clone(),
|
|
listener,
|
|
packet_latency_threshold,
|
|
starting_sending_batch_size,
|
|
starting_sending_delay,
|
|
stats.clone(),
|
|
token,
|
|
);
|
|
let handle = tester.clients_runtime.spawn(client_future);
|
|
tasks_handles.push(handle);
|
|
}
|
|
|
|
let mut global_stats = GlobalStatsUpdater::new(
|
|
header_span,
|
|
stats,
|
|
output_directory,
|
|
nym_node.shutdown_token("global-stats"),
|
|
);
|
|
|
|
let stats_handle = tester.clients_runtime.spawn(async move {
|
|
global_stats.run().await;
|
|
Ok(())
|
|
});
|
|
tasks_handles.push(stats_handle);
|
|
|
|
tester
|
|
.node_runtime
|
|
.block_on(async move { nym_node.run_minimal_mixnet_processing().await })?;
|
|
|
|
tester.clients_runtime.block_on(join_all(tasks_handles));
|
|
|
|
Ok(())
|
|
}
|