libp2p_perf/
lib.rs

1// Copyright 2023 Protocol Labs.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a
4// copy of this software and associated documentation files (the "Software"),
5// to deal in the Software without restriction, including without limitation
6// the rights to use, copy, modify, merge, publish, distribute, sublicense,
7// and/or sell copies of the Software, and to permit persons to whom the
8// Software is furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19// DEALINGS IN THE SOFTWARE.
20
21//! Implementation of the [libp2p perf protocol](https://github.com/libp2p/specs/pull/478/).
22//!
23//! Do not use in untrusted environments.
24
25#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
26
27use std::fmt::Display;
28
29use libp2p_swarm::StreamProtocol;
30use web_time::Duration;
31
32pub mod client;
33mod protocol;
34pub mod server;
35
36pub const PROTOCOL_NAME: StreamProtocol = StreamProtocol::new("/perf/1.0.0");
37const RUN_TIMEOUT: Duration = Duration::from_secs(5 * 60);
38const MAX_PARALLEL_RUNS_PER_CONNECTION: usize = 1_000;
39
40#[derive(Debug, Clone, Copy)]
41pub enum RunUpdate {
42    Intermediate(Intermediate),
43    Final(Final),
44}
45
46#[derive(Debug, Clone, Copy)]
47pub struct Intermediate {
48    pub duration: Duration,
49    pub sent: usize,
50    pub received: usize,
51}
52
53impl Display for Intermediate {
54    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55        let Intermediate {
56            duration,
57            sent,
58            received,
59        } = self;
60        write!(
61            f,
62            "{:4} s uploaded {} downloaded {} ({})",
63            duration.as_secs_f64(),
64            format_bytes(*sent),
65            format_bytes(*received),
66            format_bandwidth(*duration, sent + received),
67        )?;
68
69        Ok(())
70    }
71}
72
73#[derive(Debug, Clone, Copy)]
74pub struct Final {
75    pub duration: RunDuration,
76}
77
78/// Parameters for a single run, i.e. one stream, sending and receiving data.
79///
80/// Property names are from the perspective of the actor. E.g. `to_send` is the amount of data to
81/// send, both as the client and the server.
82#[derive(Debug, Clone, Copy)]
83pub struct RunParams {
84    pub to_send: usize,
85    pub to_receive: usize,
86}
87
88/// Duration for a single run, i.e. one stream, sending and receiving data.
89#[derive(Debug, Clone, Copy)]
90pub struct RunDuration {
91    pub upload: Duration,
92    pub download: Duration,
93}
94
95#[derive(Debug, Clone, Copy)]
96pub struct Run {
97    pub params: RunParams,
98    pub duration: RunDuration,
99}
100
101const KILO: f64 = 1024.0;
102const MEGA: f64 = KILO * 1024.0;
103const GIGA: f64 = MEGA * 1024.0;
104
105fn format_bytes(bytes: usize) -> String {
106    let bytes = bytes as f64;
107    if bytes >= GIGA {
108        format!("{:.2} GiB", bytes / GIGA)
109    } else if bytes >= MEGA {
110        format!("{:.2} MiB", bytes / MEGA)
111    } else if bytes >= KILO {
112        format!("{:.2} KiB", bytes / KILO)
113    } else {
114        format!("{} B", bytes)
115    }
116}
117
118fn format_bandwidth(duration: Duration, bytes: usize) -> String {
119    const KILO: f64 = 1024.0;
120    const MEGA: f64 = KILO * 1024.0;
121    const GIGA: f64 = MEGA * 1024.0;
122
123    let bandwidth = (bytes as f64 * 8.0) / duration.as_secs_f64();
124
125    if bandwidth >= GIGA {
126        format!("{:.2} Gbit/s", bandwidth / GIGA)
127    } else if bandwidth >= MEGA {
128        format!("{:.2} Mbit/s", bandwidth / MEGA)
129    } else if bandwidth >= KILO {
130        format!("{:.2} Kbit/s", bandwidth / KILO)
131    } else {
132        format!("{:.2} bit/s", bandwidth)
133    }
134}
135
136impl Display for Run {
137    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138        let Run {
139            params: RunParams {
140                to_send,
141                to_receive,
142            },
143            duration: RunDuration { upload, download },
144        } = self;
145
146        write!(
147            f,
148            "uploaded {} in {:.4} s ({}), downloaded {} in {:.4} s ({})",
149            format_bytes(*to_send),
150            upload.as_secs_f64(),
151            format_bandwidth(*upload, *to_send),
152            format_bytes(*to_receive),
153            download.as_secs_f64(),
154            format_bandwidth(*download, *to_receive),
155        )?;
156
157        Ok(())
158    }
159}