udp: improve statistics structs

- Now, workers don't need to keep track of which atomic usize
  to update
- Additionally, prometheus now gets separate information per
  socket worker
This commit is contained in:
Joakim Frostegård 2024-02-02 13:37:43 +01:00
parent e2e525b560
commit 405bbaca93
12 changed files with 400 additions and 294 deletions

View file

@ -34,6 +34,7 @@
* Remove config key `network.poll_event_capacity` (always use 1) * Remove config key `network.poll_event_capacity` (always use 1)
* Speed up parsing and serialization of requests and responses by using * Speed up parsing and serialization of requests and responses by using
[zerocopy](https://crates.io/crates/zerocopy) [zerocopy](https://crates.io/crates/zerocopy)
* Report socket worker related prometheus stats per worker
#### Fixed #### Fixed

1
Cargo.lock generated
View file

@ -300,6 +300,7 @@ dependencies = [
"compact_str", "compact_str",
"constant_time_eq", "constant_time_eq",
"crossbeam-channel", "crossbeam-channel",
"crossbeam-utils",
"getrandom", "getrandom",
"hashbrown 0.14.3", "hashbrown 0.14.3",
"hdrhistogram", "hdrhistogram",

View file

@ -38,6 +38,7 @@ cfg-if = "1"
compact_str = "0.7" compact_str = "0.7"
constant_time_eq = "0.3" constant_time_eq = "0.3"
crossbeam-channel = "0.5" crossbeam-channel = "0.5"
crossbeam-utils = "0.8"
getrandom = "0.2" getrandom = "0.2"
hashbrown = { version = "0.14", default-features = false } hashbrown = { version = "0.14", default-features = false }
hdrhistogram = "7" hdrhistogram = "7"

View file

@ -1,13 +1,15 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::hash::Hash; use std::hash::Hash;
use std::iter::repeat_with;
use std::sync::atomic::AtomicUsize; use std::sync::atomic::AtomicUsize;
use std::sync::Arc; use std::sync::Arc;
use crossbeam_channel::{Receiver, SendError, Sender, TrySendError}; use crossbeam_channel::{Receiver, SendError, Sender, TrySendError};
use aquatic_common::access_list::AccessListArcSwap; use aquatic_common::access_list::AccessListArcSwap;
use aquatic_common::CanonicalSocketAddr; use aquatic_common::{CanonicalSocketAddr, ServerStartInstant};
use aquatic_udp_protocol::*; use aquatic_udp_protocol::*;
use crossbeam_utils::CachePadded;
use hdrhistogram::Histogram; use hdrhistogram::Histogram;
use crate::config::Config; use crate::config::Config;
@ -160,53 +162,86 @@ pub enum StatisticsMessage {
PeerRemoved(PeerId), PeerRemoved(PeerId),
} }
pub struct Statistics { #[derive(Default)]
pub requests_received: AtomicUsize, pub struct SocketWorkerStatistics {
pub responses_sent_connect: AtomicUsize, pub requests: AtomicUsize,
pub responses_sent_announce: AtomicUsize, pub responses_connect: AtomicUsize,
pub responses_sent_scrape: AtomicUsize, pub responses_announce: AtomicUsize,
pub responses_sent_error: AtomicUsize, pub responses_scrape: AtomicUsize,
pub responses_error: AtomicUsize,
pub bytes_received: AtomicUsize, pub bytes_received: AtomicUsize,
pub bytes_sent: AtomicUsize, pub bytes_sent: AtomicUsize,
pub torrents: Vec<AtomicUsize>, }
pub peers: Vec<AtomicUsize>,
pub type CachePaddedArc<T> = CachePadded<Arc<CachePadded<T>>>;
#[derive(Default)]
pub struct SwarmWorkerStatistics {
pub torrents: AtomicUsize,
pub peers: AtomicUsize,
}
#[derive(Clone, Copy, Debug)]
pub enum IpVersion {
V4,
V6,
}
#[cfg(feature = "prometheus")]
impl IpVersion {
pub fn prometheus_str(&self) -> &'static str {
match self {
Self::V4 => "4",
Self::V6 => "6",
}
}
}
#[derive(Default)]
pub struct IpVersionStatistics<T> {
pub ipv4: T,
pub ipv6: T,
}
impl<T> IpVersionStatistics<T> {
pub fn by_ip_version(&self, ip_version: IpVersion) -> &T {
match ip_version {
IpVersion::V4 => &self.ipv4,
IpVersion::V6 => &self.ipv6,
}
}
}
#[derive(Clone)]
pub struct Statistics {
pub socket: Vec<CachePaddedArc<IpVersionStatistics<SocketWorkerStatistics>>>,
pub swarm: Vec<CachePaddedArc<IpVersionStatistics<SwarmWorkerStatistics>>>,
} }
impl Statistics { impl Statistics {
pub fn new(num_swarm_workers: usize) -> Self { pub fn new(config: &Config) -> Self {
Self { Self {
requests_received: Default::default(), socket: repeat_with(Default::default)
responses_sent_connect: Default::default(), .take(config.socket_workers)
responses_sent_announce: Default::default(), .collect(),
responses_sent_scrape: Default::default(), swarm: repeat_with(Default::default)
responses_sent_error: Default::default(), .take(config.swarm_workers)
bytes_received: Default::default(), .collect(),
bytes_sent: Default::default(),
torrents: Self::create_atomic_usize_vec(num_swarm_workers),
peers: Self::create_atomic_usize_vec(num_swarm_workers),
} }
} }
fn create_atomic_usize_vec(len: usize) -> Vec<AtomicUsize> {
::std::iter::repeat_with(AtomicUsize::default)
.take(len)
.collect()
}
} }
#[derive(Clone)] #[derive(Clone)]
pub struct State { pub struct State {
pub access_list: Arc<AccessListArcSwap>, pub access_list: Arc<AccessListArcSwap>,
pub statistics_ipv4: Arc<Statistics>, pub server_start_instant: ServerStartInstant,
pub statistics_ipv6: Arc<Statistics>,
} }
impl State { impl Default for State {
pub fn new(num_swarm_workers: usize) -> Self { fn default() -> Self {
Self { Self {
access_list: Arc::new(AccessListArcSwap::default()), access_list: Arc::new(AccessListArcSwap::default()),
statistics_ipv4: Arc::new(Statistics::new(num_swarm_workers)), server_start_instant: ServerStartInstant::new(),
statistics_ipv6: Arc::new(Statistics::new(num_swarm_workers)),
} }
} }
} }

View file

@ -16,10 +16,10 @@ use aquatic_common::access_list::update_access_list;
#[cfg(feature = "cpu-pinning")] #[cfg(feature = "cpu-pinning")]
use aquatic_common::cpu_pinning::{pin_current_if_configured_to, WorkerIndex}; use aquatic_common::cpu_pinning::{pin_current_if_configured_to, WorkerIndex};
use aquatic_common::privileges::PrivilegeDropper; use aquatic_common::privileges::PrivilegeDropper;
use aquatic_common::ServerStartInstant;
use common::{ use common::{
ConnectedRequestSender, ConnectedResponseSender, SocketWorkerIndex, State, SwarmWorkerIndex, ConnectedRequestSender, ConnectedResponseSender, SocketWorkerIndex, State, Statistics,
SwarmWorkerIndex,
}; };
use config::Config; use config::Config;
use workers::socket::ConnectionValidator; use workers::socket::ConnectionValidator;
@ -31,7 +31,8 @@ pub const APP_VERSION: &str = env!("CARGO_PKG_VERSION");
pub fn run(config: Config) -> ::anyhow::Result<()> { pub fn run(config: Config) -> ::anyhow::Result<()> {
let mut signals = Signals::new([SIGUSR1])?; let mut signals = Signals::new([SIGUSR1])?;
let state = State::new(config.swarm_workers); let state = State::default();
let statistics = Statistics::new(&config);
let connection_validator = ConnectionValidator::new(&config)?; let connection_validator = ConnectionValidator::new(&config)?;
let priv_dropper = PrivilegeDropper::new(config.privileges.clone(), config.socket_workers); let priv_dropper = PrivilegeDropper::new(config.privileges.clone(), config.socket_workers);
let mut join_handles = Vec::new(); let mut join_handles = Vec::new();
@ -46,8 +47,6 @@ pub fn run(config: Config) -> ::anyhow::Result<()> {
let (statistics_sender, statistics_receiver) = unbounded(); let (statistics_sender, statistics_receiver) = unbounded();
let server_start_instant = ServerStartInstant::new();
for i in 0..config.swarm_workers { for i in 0..config.swarm_workers {
let (request_sender, request_receiver) = bounded(config.worker_channel_size); let (request_sender, request_receiver) = bounded(config.worker_channel_size);
@ -68,6 +67,7 @@ pub fn run(config: Config) -> ::anyhow::Result<()> {
let request_receiver = request_receivers.remove(&i).unwrap().clone(); let request_receiver = request_receivers.remove(&i).unwrap().clone();
let response_sender = ConnectedResponseSender::new(response_senders.clone()); let response_sender = ConnectedResponseSender::new(response_senders.clone());
let statistics_sender = statistics_sender.clone(); let statistics_sender = statistics_sender.clone();
let statistics = statistics.swarm[i].clone();
let handle = Builder::new() let handle = Builder::new()
.name(format!("swarm-{:02}", i + 1)) .name(format!("swarm-{:02}", i + 1))
@ -83,7 +83,7 @@ pub fn run(config: Config) -> ::anyhow::Result<()> {
let mut worker = SwarmWorker { let mut worker = SwarmWorker {
config, config,
state, state,
server_start_instant, statistics,
request_receiver, request_receiver,
response_sender, response_sender,
statistics_sender, statistics_sender,
@ -105,6 +105,7 @@ pub fn run(config: Config) -> ::anyhow::Result<()> {
ConnectedRequestSender::new(SocketWorkerIndex(i), request_senders.clone()); ConnectedRequestSender::new(SocketWorkerIndex(i), request_senders.clone());
let response_receiver = response_receivers.remove(&i).unwrap(); let response_receiver = response_receivers.remove(&i).unwrap();
let priv_dropper = priv_dropper.clone(); let priv_dropper = priv_dropper.clone();
let statistics = statistics.socket[i].clone();
let handle = Builder::new() let handle = Builder::new()
.name(format!("socket-{:02}", i + 1)) .name(format!("socket-{:02}", i + 1))
@ -118,10 +119,10 @@ pub fn run(config: Config) -> ::anyhow::Result<()> {
); );
workers::socket::run_socket_worker( workers::socket::run_socket_worker(
state,
config, config,
state,
statistics,
connection_validator, connection_validator,
server_start_instant,
request_sender, request_sender,
response_receiver, response_receiver,
priv_dropper, priv_dropper,
@ -147,7 +148,12 @@ pub fn run(config: Config) -> ::anyhow::Result<()> {
WorkerIndex::Util, WorkerIndex::Util,
); );
workers::statistics::run_statistics_worker(config, state, statistics_receiver) workers::statistics::run_statistics_worker(
config,
state,
statistics,
statistics_receiver,
)
}) })
.with_context(|| "spawn statistics worker")?; .with_context(|| "spawn statistics worker")?;

View file

@ -4,7 +4,6 @@ use std::time::{Duration, Instant};
use anyhow::Context; use anyhow::Context;
use aquatic_common::access_list::AccessListCache; use aquatic_common::access_list::AccessListCache;
use aquatic_common::ServerStartInstant;
use mio::net::UdpSocket; use mio::net::UdpSocket;
use mio::{Events, Interest, Poll, Token}; use mio::{Events, Interest, Poll, Token};
@ -35,11 +34,11 @@ enum PollMode {
pub struct SocketWorker { pub struct SocketWorker {
config: Config, config: Config,
shared_state: State, shared_state: State,
statistics: CachePaddedArc<IpVersionStatistics<SocketWorkerStatistics>>,
request_sender: ConnectedRequestSender, request_sender: ConnectedRequestSender,
response_receiver: ConnectedResponseReceiver, response_receiver: ConnectedResponseReceiver,
access_list_cache: AccessListCache, access_list_cache: AccessListCache,
validator: ConnectionValidator, validator: ConnectionValidator,
server_start_instant: ServerStartInstant,
pending_scrape_responses: PendingScrapeResponseSlab, pending_scrape_responses: PendingScrapeResponseSlab,
socket: UdpSocket, socket: UdpSocket,
opt_resend_buffer: Option<Vec<(CanonicalSocketAddr, Response)>>, opt_resend_buffer: Option<Vec<(CanonicalSocketAddr, Response)>>,
@ -51,10 +50,10 @@ pub struct SocketWorker {
impl SocketWorker { impl SocketWorker {
pub fn run( pub fn run(
shared_state: State,
config: Config, config: Config,
shared_state: State,
statistics: CachePaddedArc<IpVersionStatistics<SocketWorkerStatistics>>,
validator: ConnectionValidator, validator: ConnectionValidator,
server_start_instant: ServerStartInstant,
request_sender: ConnectedRequestSender, request_sender: ConnectedRequestSender,
response_receiver: ConnectedResponseReceiver, response_receiver: ConnectedResponseReceiver,
priv_dropper: PrivilegeDropper, priv_dropper: PrivilegeDropper,
@ -66,8 +65,8 @@ impl SocketWorker {
let mut worker = Self { let mut worker = Self {
config, config,
shared_state, shared_state,
statistics,
validator, validator,
server_start_instant,
request_sender, request_sender,
response_receiver, response_receiver,
access_list_cache, access_list_cache,
@ -96,7 +95,7 @@ impl SocketWorker {
Duration::from_secs(self.config.cleaning.pending_scrape_cleaning_interval); Duration::from_secs(self.config.cleaning.pending_scrape_cleaning_interval);
let mut pending_scrape_valid_until = ValidUntil::new( let mut pending_scrape_valid_until = ValidUntil::new(
self.server_start_instant, self.shared_state.server_start_instant,
self.config.cleaning.max_pending_scrape_age, self.config.cleaning.max_pending_scrape_age,
); );
let mut last_pending_scrape_cleaning = Instant::now(); let mut last_pending_scrape_cleaning = Instant::now();
@ -133,7 +132,7 @@ impl SocketWorker {
for (addr, response) in resend_buffer.drain(..) { for (addr, response) in resend_buffer.drain(..) {
Self::send_response( Self::send_response(
&self.config, &self.config,
&self.shared_state, &self.statistics,
&mut self.socket, &mut self.socket,
&mut self.buffer, &mut self.buffer,
&mut None, &mut None,
@ -159,7 +158,7 @@ impl SocketWorker {
// Run periodic ValidUntil updates and state cleaning // Run periodic ValidUntil updates and state cleaning
if iter_counter % 256 == 0 { if iter_counter % 256 == 0 {
let seconds_since_start = self.server_start_instant.seconds_elapsed(); let seconds_since_start = self.shared_state.server_start_instant.seconds_elapsed();
pending_scrape_valid_until = ValidUntil::new_with_now( pending_scrape_valid_until = ValidUntil::new_with_now(
seconds_since_start, seconds_since_start,
@ -180,26 +179,49 @@ impl SocketWorker {
} }
fn read_and_handle_requests(&mut self, pending_scrape_valid_until: ValidUntil) { fn read_and_handle_requests(&mut self, pending_scrape_valid_until: ValidUntil) {
let mut requests_received_ipv4: usize = 0; let max_scrape_torrents = self.config.protocol.max_scrape_torrents;
let mut requests_received_ipv6: usize = 0;
let mut bytes_received_ipv4: usize = 0;
let mut bytes_received_ipv6 = 0;
loop { loop {
match self.socket.recv_from(&mut self.buffer[..]) { match self.socket.recv_from(&mut self.buffer[..]) {
Ok((bytes_read, src)) => { Ok((bytes_read, src)) => {
if src.port() == 0 { let src_port = src.port();
::log::debug!("Ignored request from {} because source port is zero", src); let src = CanonicalSocketAddr::new(src);
// Use canonical address for statistics
let opt_statistics = if self.config.statistics.active() {
if src.is_ipv4() {
let statistics = &self.statistics.ipv4;
statistics
.bytes_received
.fetch_add(bytes_read + EXTRA_PACKET_SIZE_IPV4, Ordering::Relaxed);
Some(statistics)
} else {
let statistics = &self.statistics.ipv6;
statistics
.bytes_received
.fetch_add(bytes_read + EXTRA_PACKET_SIZE_IPV6, Ordering::Relaxed);
Some(statistics)
}
} else {
None
};
if src_port == 0 {
::log::debug!("Ignored request because source port is zero");
continue; continue;
} }
let src = CanonicalSocketAddr::new(src); match Request::parse_bytes(&self.buffer[..bytes_read], max_scrape_torrents) {
let request_parsable = match Request::parse_bytes(
&self.buffer[..bytes_read],
self.config.protocol.max_scrape_torrents,
) {
Ok(request) => { Ok(request) => {
if let Some(statistics) = opt_statistics {
statistics.requests.fetch_add(1, Ordering::Relaxed);
}
if let Err(HandleRequestError::RequestChannelFull(failed_requests)) = if let Err(HandleRequestError::RequestChannelFull(failed_requests)) =
self.handle_request(pending_scrape_valid_until, request, src) self.handle_request(pending_scrape_valid_until, request, src)
{ {
@ -208,52 +230,36 @@ impl SocketWorker {
break; break;
} }
}
Err(RequestParseError::Sendable {
connection_id,
transaction_id,
err,
}) if self.validator.connection_id_valid(src, connection_id) => {
let response = ErrorResponse {
transaction_id,
message: err.into(),
};
true Self::send_response(
&self.config,
&self.statistics,
&mut self.socket,
&mut self.buffer,
&mut self.opt_resend_buffer,
Response::Error(response),
src,
);
::log::debug!("request parse error (sent error response): {:?}", err);
} }
Err(err) => { Err(err) => {
::log::debug!("Request::from_bytes error: {:?}", err); ::log::debug!(
"request parse error (didn't send error response): {:?}",
if let RequestParseError::Sendable { err
connection_id, );
transaction_id,
err,
} = err
{
if self.validator.connection_id_valid(src, connection_id) {
let response = ErrorResponse {
transaction_id,
message: err.into(),
};
Self::send_response(
&self.config,
&self.shared_state,
&mut self.socket,
&mut self.buffer,
&mut self.opt_resend_buffer,
Response::Error(response),
src,
);
}
}
false
} }
}; };
// Update statistics for converted address
if src.is_ipv4() {
if request_parsable {
requests_received_ipv4 += 1;
}
bytes_received_ipv4 += bytes_read + EXTRA_PACKET_SIZE_IPV4;
} else {
if request_parsable {
requests_received_ipv6 += 1;
}
bytes_received_ipv6 += bytes_read + EXTRA_PACKET_SIZE_IPV6;
}
} }
Err(err) if err.kind() == ErrorKind::WouldBlock => { Err(err) if err.kind() == ErrorKind::WouldBlock => {
break; break;
@ -263,25 +269,6 @@ impl SocketWorker {
} }
} }
} }
if self.config.statistics.active() {
self.shared_state
.statistics_ipv4
.requests_received
.fetch_add(requests_received_ipv4, Ordering::Relaxed);
self.shared_state
.statistics_ipv6
.requests_received
.fetch_add(requests_received_ipv6, Ordering::Relaxed);
self.shared_state
.statistics_ipv4
.bytes_received
.fetch_add(bytes_received_ipv4, Ordering::Relaxed);
self.shared_state
.statistics_ipv6
.bytes_received
.fetch_add(bytes_received_ipv6, Ordering::Relaxed);
}
} }
fn handle_request( fn handle_request(
@ -303,7 +290,7 @@ impl SocketWorker {
Self::send_response( Self::send_response(
&self.config, &self.config,
&self.shared_state, &self.statistics,
&mut self.socket, &mut self.socket,
&mut self.buffer, &mut self.buffer,
&mut self.opt_resend_buffer, &mut self.opt_resend_buffer,
@ -339,7 +326,7 @@ impl SocketWorker {
Self::send_response( Self::send_response(
&self.config, &self.config,
&self.shared_state, &self.statistics,
&mut self.socket, &mut self.socket,
&mut self.buffer, &mut self.buffer,
&mut self.opt_resend_buffer, &mut self.opt_resend_buffer,
@ -407,7 +394,7 @@ impl SocketWorker {
Self::send_response( Self::send_response(
&self.config, &self.config,
&self.shared_state, &self.statistics,
&mut self.socket, &mut self.socket,
&mut self.buffer, &mut self.buffer,
&mut self.opt_resend_buffer, &mut self.opt_resend_buffer,
@ -419,7 +406,7 @@ impl SocketWorker {
fn send_response( fn send_response(
config: &Config, config: &Config,
shared_state: &State, statistics: &CachePaddedArc<IpVersionStatistics<SocketWorkerStatistics>>,
socket: &mut UdpSocket, socket: &mut UdpSocket,
buffer: &mut [u8], buffer: &mut [u8],
opt_resend_buffer: &mut Option<Vec<(CanonicalSocketAddr, Response)>>, opt_resend_buffer: &mut Option<Vec<(CanonicalSocketAddr, Response)>>,
@ -447,7 +434,7 @@ impl SocketWorker {
match socket.send_to(&buffer.into_inner()[..bytes_written], addr) { match socket.send_to(&buffer.into_inner()[..bytes_written], addr) {
Ok(amt) if config.statistics.active() => { Ok(amt) if config.statistics.active() => {
let stats = if canonical_addr.is_ipv4() { let stats = if canonical_addr.is_ipv4() {
let stats = &shared_state.statistics_ipv4; let stats = &statistics.ipv4;
stats stats
.bytes_sent .bytes_sent
@ -455,7 +442,7 @@ impl SocketWorker {
stats stats
} else { } else {
let stats = &shared_state.statistics_ipv6; let stats = &statistics.ipv6;
stats stats
.bytes_sent .bytes_sent
@ -466,18 +453,16 @@ impl SocketWorker {
match response { match response {
Response::Connect(_) => { Response::Connect(_) => {
stats.responses_sent_connect.fetch_add(1, Ordering::Relaxed); stats.responses_connect.fetch_add(1, Ordering::Relaxed);
} }
Response::AnnounceIpv4(_) | Response::AnnounceIpv6(_) => { Response::AnnounceIpv4(_) | Response::AnnounceIpv6(_) => {
stats stats.responses_announce.fetch_add(1, Ordering::Relaxed);
.responses_sent_announce
.fetch_add(1, Ordering::Relaxed);
} }
Response::Scrape(_) => { Response::Scrape(_) => {
stats.responses_sent_scrape.fetch_add(1, Ordering::Relaxed); stats.responses_scrape.fetch_add(1, Ordering::Relaxed);
} }
Response::Error(_) => { Response::Error(_) => {
stats.responses_sent_error.fetch_add(1, Ordering::Relaxed); stats.responses_error.fetch_add(1, Ordering::Relaxed);
} }
} }
} }

View file

@ -5,11 +5,14 @@ mod uring;
mod validator; mod validator;
use anyhow::Context; use anyhow::Context;
use aquatic_common::{privileges::PrivilegeDropper, ServerStartInstant}; use aquatic_common::privileges::PrivilegeDropper;
use socket2::{Domain, Protocol, Socket, Type}; use socket2::{Domain, Protocol, Socket, Type};
use crate::{ use crate::{
common::{ConnectedRequestSender, ConnectedResponseReceiver, State}, common::{
CachePaddedArc, ConnectedRequestSender, ConnectedResponseReceiver, IpVersionStatistics,
SocketWorkerStatistics, State,
},
config::Config, config::Config,
}; };
@ -37,10 +40,10 @@ const EXTRA_PACKET_SIZE_IPV4: usize = 8 + 18 + 20 + 8;
const EXTRA_PACKET_SIZE_IPV6: usize = 8 + 18 + 40 + 8; const EXTRA_PACKET_SIZE_IPV6: usize = 8 + 18 + 40 + 8;
pub fn run_socket_worker( pub fn run_socket_worker(
shared_state: State,
config: Config, config: Config,
shared_state: State,
statistics: CachePaddedArc<IpVersionStatistics<SocketWorkerStatistics>>,
validator: ConnectionValidator, validator: ConnectionValidator,
server_start_instant: ServerStartInstant,
request_sender: ConnectedRequestSender, request_sender: ConnectedRequestSender,
response_receiver: ConnectedResponseReceiver, response_receiver: ConnectedResponseReceiver,
priv_dropper: PrivilegeDropper, priv_dropper: PrivilegeDropper,
@ -49,10 +52,10 @@ pub fn run_socket_worker(
match self::uring::supported_on_current_kernel() { match self::uring::supported_on_current_kernel() {
Ok(()) => { Ok(()) => {
return self::uring::SocketWorker::run( return self::uring::SocketWorker::run(
shared_state,
config, config,
shared_state,
statistics,
validator, validator,
server_start_instant,
request_sender, request_sender,
response_receiver, response_receiver,
priv_dropper, priv_dropper,
@ -67,10 +70,10 @@ pub fn run_socket_worker(
} }
self::mio::SocketWorker::run( self::mio::SocketWorker::run(
shared_state,
config, config,
shared_state,
statistics,
validator, validator,
server_start_instant,
request_sender, request_sender,
response_receiver, response_receiver,
priv_dropper, priv_dropper,

View file

@ -11,7 +11,6 @@ use std::sync::atomic::Ordering;
use anyhow::Context; use anyhow::Context;
use aquatic_common::access_list::AccessListCache; use aquatic_common::access_list::AccessListCache;
use aquatic_common::ServerStartInstant;
use io_uring::opcode::Timeout; use io_uring::opcode::Timeout;
use io_uring::types::{Fixed, Timespec}; use io_uring::types::{Fixed, Timespec};
use io_uring::{IoUring, Probe}; use io_uring::{IoUring, Probe};
@ -76,11 +75,11 @@ impl CurrentRing {
pub struct SocketWorker { pub struct SocketWorker {
config: Config, config: Config,
shared_state: State, shared_state: State,
statistics: CachePaddedArc<IpVersionStatistics<SocketWorkerStatistics>>,
request_sender: ConnectedRequestSender, request_sender: ConnectedRequestSender,
response_receiver: ConnectedResponseReceiver, response_receiver: ConnectedResponseReceiver,
access_list_cache: AccessListCache, access_list_cache: AccessListCache,
validator: ConnectionValidator, validator: ConnectionValidator,
server_start_instant: ServerStartInstant,
#[allow(dead_code)] #[allow(dead_code)]
socket: UdpSocket, socket: UdpSocket,
pending_scrape_responses: PendingScrapeResponseSlab, pending_scrape_responses: PendingScrapeResponseSlab,
@ -97,10 +96,10 @@ pub struct SocketWorker {
impl SocketWorker { impl SocketWorker {
pub fn run( pub fn run(
shared_state: State,
config: Config, config: Config,
shared_state: State,
statistics: CachePaddedArc<IpVersionStatistics<SocketWorkerStatistics>>,
validator: ConnectionValidator, validator: ConnectionValidator,
server_start_instant: ServerStartInstant,
request_sender: ConnectedRequestSender, request_sender: ConnectedRequestSender,
response_receiver: ConnectedResponseReceiver, response_receiver: ConnectedResponseReceiver,
priv_dropper: PrivilegeDropper, priv_dropper: PrivilegeDropper,
@ -164,14 +163,16 @@ impl SocketWorker {
cleaning_timeout_sqe.clone(), cleaning_timeout_sqe.clone(),
]; ];
let pending_scrape_valid_until = let pending_scrape_valid_until = ValidUntil::new(
ValidUntil::new(server_start_instant, config.cleaning.max_pending_scrape_age); shared_state.server_start_instant,
config.cleaning.max_pending_scrape_age,
);
let mut worker = Self { let mut worker = Self {
config, config,
shared_state, shared_state,
statistics,
validator, validator,
server_start_instant,
request_sender, request_sender,
response_receiver, response_receiver,
access_list_cache, access_list_cache,
@ -293,7 +294,7 @@ impl SocketWorker {
} }
USER_DATA_PULSE_TIMEOUT => { USER_DATA_PULSE_TIMEOUT => {
self.pending_scrape_valid_until = ValidUntil::new( self.pending_scrape_valid_until = ValidUntil::new(
self.server_start_instant, self.shared_state.server_start_instant,
self.config.cleaning.max_pending_scrape_age, self.config.cleaning.max_pending_scrape_age,
); );
@ -302,7 +303,7 @@ impl SocketWorker {
} }
USER_DATA_CLEANING_TIMEOUT => { USER_DATA_CLEANING_TIMEOUT => {
self.pending_scrape_responses self.pending_scrape_responses
.clean(self.server_start_instant.seconds_elapsed()); .clean(self.shared_state.server_start_instant.seconds_elapsed());
self.resubmittable_sqe_buf self.resubmittable_sqe_buf
.push(self.cleaning_timeout_sqe.clone()); .push(self.cleaning_timeout_sqe.clone());
@ -322,9 +323,9 @@ impl SocketWorker {
self.send_buffers.response_type_and_ipv4(send_buffer_index); self.send_buffers.response_type_and_ipv4(send_buffer_index);
let (statistics, extra_bytes) = if receiver_is_ipv4 { let (statistics, extra_bytes) = if receiver_is_ipv4 {
(&self.shared_state.statistics_ipv4, EXTRA_PACKET_SIZE_IPV4) (&self.statistics.ipv4, EXTRA_PACKET_SIZE_IPV4)
} else { } else {
(&self.shared_state.statistics_ipv6, EXTRA_PACKET_SIZE_IPV6) (&self.statistics.ipv6, EXTRA_PACKET_SIZE_IPV6)
}; };
statistics statistics
@ -332,10 +333,10 @@ impl SocketWorker {
.fetch_add(result as usize + extra_bytes, Ordering::Relaxed); .fetch_add(result as usize + extra_bytes, Ordering::Relaxed);
let response_counter = match response_type { let response_counter = match response_type {
ResponseType::Connect => &statistics.responses_sent_connect, ResponseType::Connect => &statistics.responses_connect,
ResponseType::Announce => &statistics.responses_sent_announce, ResponseType::Announce => &statistics.responses_announce,
ResponseType::Scrape => &statistics.responses_sent_scrape, ResponseType::Scrape => &statistics.responses_scrape,
ResponseType::Error => &statistics.responses_sent_error, ResponseType::Error => &statistics.responses_error,
}; };
response_counter.fetch_add(1, Ordering::Relaxed); response_counter.fetch_add(1, Ordering::Relaxed);
@ -433,15 +434,15 @@ impl SocketWorker {
if self.config.statistics.active() { if self.config.statistics.active() {
let (statistics, extra_bytes) = if addr.is_ipv4() { let (statistics, extra_bytes) = if addr.is_ipv4() {
(&self.shared_state.statistics_ipv4, EXTRA_PACKET_SIZE_IPV4) (&self.statistics.ipv4, EXTRA_PACKET_SIZE_IPV4)
} else { } else {
(&self.shared_state.statistics_ipv6, EXTRA_PACKET_SIZE_IPV6) (&self.statistics.ipv6, EXTRA_PACKET_SIZE_IPV6)
}; };
statistics statistics
.bytes_received .bytes_received
.fetch_add(buffer.len() + extra_bytes, Ordering::Relaxed); .fetch_add(buffer.len() + extra_bytes, Ordering::Relaxed);
statistics.requests_received.fetch_add(1, Ordering::Relaxed); statistics.requests.fetch_add(1, Ordering::Relaxed);
} }
} }

View file

@ -1,43 +1,41 @@
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::time::Instant; use std::time::Instant;
use hdrhistogram::Histogram; use hdrhistogram::Histogram;
use num_format::{Locale, ToFormattedString}; use num_format::{Locale, ToFormattedString};
use serde::Serialize; use serde::Serialize;
use crate::common::Statistics;
use crate::config::Config; use crate::config::Config;
use super::{IpVersion, Statistics};
#[cfg(feature = "prometheus")] #[cfg(feature = "prometheus")]
macro_rules! set_peer_histogram_gauge { macro_rules! set_peer_histogram_gauge {
($ip_version:ident, $data:expr, $type_label:expr) => { ($ip_version:expr, $data:expr, $type_label:expr) => {
::metrics::gauge!( ::metrics::gauge!(
"aquatic_peers_per_torrent", "aquatic_peers_per_torrent",
"type" => $type_label, "type" => $type_label,
"ip_version" => $ip_version.clone(), "ip_version" => $ip_version,
) )
.set($data as f64); .set($data as f64);
}; };
} }
pub struct StatisticsCollector { pub struct StatisticsCollector {
shared: Arc<Statistics>, statistics: Statistics,
ip_version: IpVersion,
last_update: Instant, last_update: Instant,
pending_histograms: Vec<Histogram<u64>>, pending_histograms: Vec<Histogram<u64>>,
last_complete_histogram: PeerHistogramStatistics, last_complete_histogram: PeerHistogramStatistics,
#[cfg(feature = "prometheus")]
ip_version: String,
} }
impl StatisticsCollector { impl StatisticsCollector {
pub fn new(shared: Arc<Statistics>, #[cfg(feature = "prometheus")] ip_version: String) -> Self { pub fn new(statistics: Statistics, ip_version: IpVersion) -> Self {
Self { Self {
shared, statistics,
last_update: Instant::now(), last_update: Instant::now(),
pending_histograms: Vec::new(), pending_histograms: Vec::new(),
last_complete_histogram: Default::default(), last_complete_histogram: Default::default(),
#[cfg(feature = "prometheus")]
ip_version, ip_version,
} }
} }
@ -55,27 +53,177 @@ impl StatisticsCollector {
&mut self, &mut self,
#[cfg(feature = "prometheus")] config: &Config, #[cfg(feature = "prometheus")] config: &Config,
) -> CollectedStatistics { ) -> CollectedStatistics {
let requests_received = Self::fetch_and_reset(&self.shared.requests_received); let mut requests = 0;
let responses_sent_connect = Self::fetch_and_reset(&self.shared.responses_sent_connect); let mut responses_connect: usize = 0;
let responses_sent_announce = Self::fetch_and_reset(&self.shared.responses_sent_announce); let mut responses_announce: usize = 0;
let responses_sent_scrape = Self::fetch_and_reset(&self.shared.responses_sent_scrape); let mut responses_scrape: usize = 0;
let responses_sent_error = Self::fetch_and_reset(&self.shared.responses_sent_error); let mut responses_error: usize = 0;
let mut bytes_received: usize = 0;
let mut bytes_sent: usize = 0;
let mut num_torrents: usize = 0;
let mut num_peers: usize = 0;
let bytes_received = Self::fetch_and_reset(&self.shared.bytes_received); #[cfg(feature = "prometheus")]
let bytes_sent = Self::fetch_and_reset(&self.shared.bytes_sent); let ip_version_prometheus_str = self.ip_version.prometheus_str();
let num_torrents_by_worker: Vec<usize> = self for (i, statistics) in self
.shared .statistics
.torrents .socket
.iter() .iter()
.map(|n| n.load(Ordering::Relaxed)) .map(|s| s.by_ip_version(self.ip_version))
.collect(); .enumerate()
let num_peers_by_worker: Vec<usize> = self {
.shared {
.peers let n = statistics.requests.fetch_and(0, Ordering::Relaxed);
requests += n;
#[cfg(feature = "prometheus")]
if config.statistics.run_prometheus_endpoint {
::metrics::counter!(
"aquatic_requests_total",
"ip_version" => ip_version_prometheus_str,
"worker_index" => i.to_string(),
)
.increment(n.try_into().unwrap());
}
}
{
let n = statistics.responses_connect.fetch_and(0, Ordering::Relaxed);
responses_connect += n;
#[cfg(feature = "prometheus")]
if config.statistics.run_prometheus_endpoint {
::metrics::counter!(
"aquatic_responses_total",
"type" => "connect",
"ip_version" => ip_version_prometheus_str,
"worker_index" => i.to_string(),
)
.increment(n.try_into().unwrap());
}
}
{
let n = statistics
.responses_announce
.fetch_and(0, Ordering::Relaxed);
responses_announce += n;
#[cfg(feature = "prometheus")]
if config.statistics.run_prometheus_endpoint {
::metrics::counter!(
"aquatic_responses_total",
"type" => "announce",
"ip_version" => ip_version_prometheus_str,
"worker_index" => i.to_string(),
)
.increment(n.try_into().unwrap());
}
}
{
let n = statistics.responses_scrape.fetch_and(0, Ordering::Relaxed);
responses_scrape += n;
#[cfg(feature = "prometheus")]
if config.statistics.run_prometheus_endpoint {
::metrics::counter!(
"aquatic_responses_total",
"type" => "scrape",
"ip_version" => ip_version_prometheus_str,
"worker_index" => i.to_string(),
)
.increment(n.try_into().unwrap());
}
}
{
let n = statistics.responses_error.fetch_and(0, Ordering::Relaxed);
responses_error += n;
#[cfg(feature = "prometheus")]
if config.statistics.run_prometheus_endpoint {
::metrics::counter!(
"aquatic_responses_total",
"type" => "error",
"ip_version" => ip_version_prometheus_str,
"worker_index" => i.to_string(),
)
.increment(n.try_into().unwrap());
}
}
{
let n = statistics.bytes_received.fetch_and(0, Ordering::Relaxed);
bytes_received += n;
#[cfg(feature = "prometheus")]
if config.statistics.run_prometheus_endpoint {
::metrics::counter!(
"aquatic_rx_bytes",
"ip_version" => ip_version_prometheus_str,
"worker_index" => i.to_string(),
)
.increment(n.try_into().unwrap());
}
}
{
let n = statistics.bytes_sent.fetch_and(0, Ordering::Relaxed);
bytes_sent += n;
#[cfg(feature = "prometheus")]
if config.statistics.run_prometheus_endpoint {
::metrics::counter!(
"aquatic_tx_bytes",
"ip_version" => ip_version_prometheus_str,
"worker_index" => i.to_string(),
)
.increment(n.try_into().unwrap());
}
}
}
for (i, statistics) in self
.statistics
.swarm
.iter() .iter()
.map(|n| n.load(Ordering::Relaxed)) .map(|s| s.by_ip_version(self.ip_version))
.collect(); .enumerate()
{
{
let n = statistics.torrents.load(Ordering::Relaxed);
num_torrents += n;
#[cfg(feature = "prometheus")]
if config.statistics.run_prometheus_endpoint {
::metrics::gauge!(
"aquatic_torrents",
"ip_version" => ip_version_prometheus_str,
"worker_index" => i.to_string(),
)
.set(n as f64);
}
}
{
let n = statistics.peers.load(Ordering::Relaxed);
num_peers += n;
#[cfg(feature = "prometheus")]
if config.statistics.run_prometheus_endpoint {
::metrics::gauge!(
"aquatic_peers",
"ip_version" => ip_version_prometheus_str,
"worker_index" => i.to_string(),
)
.set(n as f64);
}
}
}
let elapsed = { let elapsed = {
let now = Instant::now(); let now = Instant::now();
@ -88,84 +236,16 @@ impl StatisticsCollector {
}; };
#[cfg(feature = "prometheus")] #[cfg(feature = "prometheus")]
if config.statistics.run_prometheus_endpoint { if config.statistics.run_prometheus_endpoint && config.statistics.torrent_peer_histograms {
::metrics::counter!( self.last_complete_histogram
"aquatic_requests_total", .update_metrics(ip_version_prometheus_str);
"ip_version" => self.ip_version.clone(),
)
.increment(requests_received.try_into().unwrap());
::metrics::counter!(
"aquatic_responses_total",
"type" => "connect",
"ip_version" => self.ip_version.clone(),
)
.increment(responses_sent_connect.try_into().unwrap());
::metrics::counter!(
"aquatic_responses_total",
"type" => "announce",
"ip_version" => self.ip_version.clone(),
)
.increment(responses_sent_announce.try_into().unwrap());
::metrics::counter!(
"aquatic_responses_total",
"type" => "scrape",
"ip_version" => self.ip_version.clone(),
)
.increment(responses_sent_scrape.try_into().unwrap());
::metrics::counter!(
"aquatic_responses_total",
"type" => "error",
"ip_version" => self.ip_version.clone(),
)
.increment(responses_sent_error.try_into().unwrap());
::metrics::counter!(
"aquatic_rx_bytes",
"ip_version" => self.ip_version.clone(),
)
.increment(bytes_received.try_into().unwrap());
::metrics::counter!(
"aquatic_tx_bytes",
"ip_version" => self.ip_version.clone(),
)
.increment(bytes_sent.try_into().unwrap());
for (worker_index, n) in num_torrents_by_worker.iter().copied().enumerate() {
::metrics::gauge!(
"aquatic_torrents",
"ip_version" => self.ip_version.clone(),
"worker_index" => worker_index.to_string(),
)
.set(n as f64);
}
for (worker_index, n) in num_peers_by_worker.iter().copied().enumerate() {
::metrics::gauge!(
"aquatic_peers",
"ip_version" => self.ip_version.clone(),
"worker_index" => worker_index.to_string(),
)
.set(n as f64);
}
if config.statistics.torrent_peer_histograms {
self.last_complete_histogram
.update_metrics(self.ip_version.clone());
}
} }
let num_peers: usize = num_peers_by_worker.into_iter().sum(); let requests_per_second = requests as f64 / elapsed;
let num_torrents: usize = num_torrents_by_worker.into_iter().sum(); let responses_per_second_connect = responses_connect as f64 / elapsed;
let responses_per_second_announce = responses_announce as f64 / elapsed;
let requests_per_second = requests_received as f64 / elapsed; let responses_per_second_scrape = responses_scrape as f64 / elapsed;
let responses_per_second_connect = responses_sent_connect as f64 / elapsed; let responses_per_second_error = responses_error as f64 / elapsed;
let responses_per_second_announce = responses_sent_announce as f64 / elapsed;
let responses_per_second_scrape = responses_sent_scrape as f64 / elapsed;
let responses_per_second_error = responses_sent_error as f64 / elapsed;
let bytes_received_per_second = bytes_received as f64 / elapsed; let bytes_received_per_second = bytes_received as f64 / elapsed;
let bytes_sent_per_second = bytes_sent as f64 / elapsed; let bytes_sent_per_second = bytes_sent as f64 / elapsed;
@ -193,10 +273,6 @@ impl StatisticsCollector {
peer_histogram: self.last_complete_histogram.clone(), peer_histogram: self.last_complete_histogram.clone(),
} }
} }
fn fetch_and_reset(atomic: &AtomicUsize) -> usize {
atomic.fetch_and(0, Ordering::Relaxed)
}
} }
#[derive(Clone, Debug, Serialize)] #[derive(Clone, Debug, Serialize)]
@ -253,7 +329,7 @@ impl PeerHistogramStatistics {
} }
#[cfg(feature = "prometheus")] #[cfg(feature = "prometheus")]
fn update_metrics(&self, ip_version: String) { fn update_metrics(&self, ip_version: &'static str) {
set_peer_histogram_gauge!(ip_version, self.min, "min"); set_peer_histogram_gauge!(ip_version, self.min, "min");
set_peer_histogram_gauge!(ip_version, self.p10, "p10"); set_peer_histogram_gauge!(ip_version, self.p10, "p10");
set_peer_histogram_gauge!(ip_version, self.p20, "p20"); set_peer_histogram_gauge!(ip_version, self.p20, "p20");

View file

@ -44,6 +44,7 @@ struct TemplateData {
pub fn run_statistics_worker( pub fn run_statistics_worker(
config: Config, config: Config,
shared_state: State, shared_state: State,
statistics: Statistics,
statistics_receiver: Receiver<StatisticsMessage>, statistics_receiver: Receiver<StatisticsMessage>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let process_peer_client_data = { let process_peer_client_data = {
@ -68,16 +69,8 @@ pub fn run_statistics_worker(
None None
}; };
let mut ipv4_collector = StatisticsCollector::new( let mut ipv4_collector = StatisticsCollector::new(statistics.clone(), IpVersion::V4);
shared_state.statistics_ipv4, let mut ipv6_collector = StatisticsCollector::new(statistics, IpVersion::V6);
#[cfg(feature = "prometheus")]
"4".into(),
);
let mut ipv6_collector = StatisticsCollector::new(
shared_state.statistics_ipv6,
#[cfg(feature = "prometheus")]
"6".into(),
);
// Store a count to enable not removing peers from the count completely // Store a count to enable not removing peers from the count completely
// just because they were removed from one torrent // just because they were removed from one torrent

View file

@ -5,7 +5,6 @@ use std::sync::atomic::Ordering;
use std::time::Duration; use std::time::Duration;
use std::time::Instant; use std::time::Instant;
use aquatic_common::ServerStartInstant;
use crossbeam_channel::Receiver; use crossbeam_channel::Receiver;
use crossbeam_channel::Sender; use crossbeam_channel::Sender;
use rand::{rngs::SmallRng, SeedableRng}; use rand::{rngs::SmallRng, SeedableRng};
@ -20,7 +19,7 @@ use storage::TorrentMaps;
pub struct SwarmWorker { pub struct SwarmWorker {
pub config: Config, pub config: Config,
pub state: State, pub state: State,
pub server_start_instant: ServerStartInstant, pub statistics: CachePaddedArc<IpVersionStatistics<SwarmWorkerStatistics>>,
pub request_receiver: Receiver<(SocketWorkerIndex, ConnectedRequest, CanonicalSocketAddr)>, pub request_receiver: Receiver<(SocketWorkerIndex, ConnectedRequest, CanonicalSocketAddr)>,
pub response_sender: ConnectedResponseSender, pub response_sender: ConnectedResponseSender,
pub statistics_sender: Sender<StatisticsMessage>, pub statistics_sender: Sender<StatisticsMessage>,
@ -33,8 +32,10 @@ impl SwarmWorker {
let mut rng = SmallRng::from_entropy(); let mut rng = SmallRng::from_entropy();
let timeout = Duration::from_millis(self.config.request_channel_recv_timeout_ms); let timeout = Duration::from_millis(self.config.request_channel_recv_timeout_ms);
let mut peer_valid_until = let mut peer_valid_until = ValidUntil::new(
ValidUntil::new(self.server_start_instant, self.config.cleaning.max_peer_age); self.state.server_start_instant,
self.config.cleaning.max_peer_age,
);
let cleaning_interval = Duration::from_secs(self.config.cleaning.torrent_cleaning_interval); let cleaning_interval = Duration::from_secs(self.config.cleaning.torrent_cleaning_interval);
let statistics_update_interval = Duration::from_secs(self.config.statistics.interval); let statistics_update_interval = Duration::from_secs(self.config.statistics.interval);
@ -110,17 +111,18 @@ impl SwarmWorker {
if iter_counter % 128 == 0 { if iter_counter % 128 == 0 {
let now = Instant::now(); let now = Instant::now();
peer_valid_until = peer_valid_until = ValidUntil::new(
ValidUntil::new(self.server_start_instant, self.config.cleaning.max_peer_age); self.state.server_start_instant,
self.config.cleaning.max_peer_age,
);
if now > last_cleaning + cleaning_interval { if now > last_cleaning + cleaning_interval {
torrents.clean_and_update_statistics( torrents.clean_and_update_statistics(
&self.config, &self.config,
&self.state, &self.state,
&self.statistics,
&self.statistics_sender, &self.statistics_sender,
&self.state.access_list, &self.state.access_list,
self.server_start_instant,
self.worker_index,
); );
last_cleaning = now; last_cleaning = now;
@ -128,10 +130,14 @@ impl SwarmWorker {
if self.config.statistics.active() if self.config.statistics.active()
&& now > last_statistics_update + statistics_update_interval && now > last_statistics_update + statistics_update_interval
{ {
self.state.statistics_ipv4.torrents[self.worker_index.0] self.statistics
.store(torrents.ipv4.num_torrents(), Ordering::Release); .ipv4
self.state.statistics_ipv6.torrents[self.worker_index.0] .torrents
.store(torrents.ipv6.num_torrents(), Ordering::Release); .store(torrents.ipv4.num_torrents(), Ordering::Relaxed);
self.statistics
.ipv6
.torrents
.store(torrents.ipv6.num_torrents(), Ordering::Relaxed);
last_statistics_update = now; last_statistics_update = now;
} }

View file

@ -3,7 +3,6 @@ use std::sync::Arc;
use aquatic_common::IndexMap; use aquatic_common::IndexMap;
use aquatic_common::SecondsSinceServerStart; use aquatic_common::SecondsSinceServerStart;
use aquatic_common::ServerStartInstant;
use aquatic_common::{ use aquatic_common::{
access_list::{create_access_list_cache, AccessListArcSwap, AccessListCache, AccessListMode}, access_list::{create_access_list_cache, AccessListArcSwap, AccessListCache, AccessListMode},
ValidUntil, ValidUntil,
@ -41,14 +40,13 @@ impl TorrentMaps {
&mut self, &mut self,
config: &Config, config: &Config,
state: &State, state: &State,
statistics: &CachePaddedArc<IpVersionStatistics<SwarmWorkerStatistics>>,
statistics_sender: &Sender<StatisticsMessage>, statistics_sender: &Sender<StatisticsMessage>,
access_list: &Arc<AccessListArcSwap>, access_list: &Arc<AccessListArcSwap>,
server_start_instant: ServerStartInstant,
worker_index: SwarmWorkerIndex,
) { ) {
let mut cache = create_access_list_cache(access_list); let mut cache = create_access_list_cache(access_list);
let mode = config.access_list.mode; let mode = config.access_list.mode;
let now = server_start_instant.seconds_elapsed(); let now = state.server_start_instant.seconds_elapsed();
let ipv4 = let ipv4 =
self.ipv4 self.ipv4
@ -58,8 +56,8 @@ impl TorrentMaps {
.clean_and_get_statistics(config, statistics_sender, &mut cache, mode, now); .clean_and_get_statistics(config, statistics_sender, &mut cache, mode, now);
if config.statistics.active() { if config.statistics.active() {
state.statistics_ipv4.peers[worker_index.0].store(ipv4.0, Ordering::Release); statistics.ipv4.peers.store(ipv4.0, Ordering::Relaxed);
state.statistics_ipv6.peers[worker_index.0].store(ipv6.0, Ordering::Release); statistics.ipv6.peers.store(ipv6.0, Ordering::Relaxed);
if let Some(message) = ipv4.1.map(StatisticsMessage::Ipv4PeerHistogram) { if let Some(message) = ipv4.1.map(StatisticsMessage::Ipv4PeerHistogram) {
if let Err(err) = statistics_sender.try_send(message) { if let Err(err) = statistics_sender.try_send(message) {