Skip to content

Commit 1b27d81

Browse files
committed
feat(server): add perfenc example/test
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
1 parent 230270e commit 1b27d81

File tree

6 files changed

+219
-4
lines changed

6 files changed

+219
-4
lines changed

Cargo.lock

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/ironrdp-server/Cargo.toml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,18 @@ categories.workspace = true
1414
[lib]
1515
doctest = true
1616

17+
[[example]]
18+
name = "perfenc"
19+
required-features = ["__bench"]
20+
1721
[features]
1822
default = ["rayon"]
1923
helper = ["dep:x509-cert", "dep:rustls-pemfile"]
2024
rayon = ["dep:rayon"]
2125

2226
# Internal (PRIVATE!) features used to aid testing.
2327
# Don't rely on these whatsoever. They may disappear at any time.
24-
__bench = []
28+
__bench = ["dep:visibility"]
2529

2630
[dependencies]
2731
anyhow = "1.0"
@@ -45,9 +49,13 @@ x509-cert = { version = "0.2.5", optional = true }
4549
rustls-pemfile = { version = "2.2.0", optional = true }
4650
rayon = { version = "1.10.0", optional = true }
4751
bytes = "1"
52+
visibility = { version = "0.1", optional = true }
4853

4954
[dev-dependencies]
50-
tokio = { version = "1", features = ["sync"] }
55+
tokio = { version = "1", features = ["sync", "fs"] }
56+
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
57+
pico-args = "0.5"
58+
bytesize = "1.3"
5159

5260
[lints]
5361
workspace = true
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
#![allow(unused_crate_dependencies)] // False positives because there are both a library and a binary.
2+
#![allow(clippy::print_stderr)]
3+
#![allow(clippy::print_stdout)]
4+
5+
use std::io::Write;
6+
7+
use anyhow::Context;
8+
use ironrdp_pdu::rdp::capability_sets::{CmdFlags, EntropyBits};
9+
use ironrdp_server::{
10+
bench::encoder::UpdateEncoder, BitmapUpdate, DesktopSize, DisplayUpdate, PixelFormat, RdpServerDisplayUpdates,
11+
};
12+
use tokio::{fs::File, io::AsyncReadExt};
13+
14+
#[tokio::main(flavor = "current_thread")]
15+
async fn main() -> Result<(), anyhow::Error> {
16+
setup_logging()?;
17+
let mut args = pico_args::Arguments::from_env();
18+
19+
if args.contains(["-h", "--help"]) {
20+
println!("Usage: perfenc [OPTIONS] <RGBX_INPUT_FILENAME>");
21+
println!();
22+
println!("Measure the performance of the IronRDP server encoder, given a raw RGBX video input file.");
23+
println!();
24+
println!("Options:");
25+
println!(" --width <WIDTH> Width of the display (default: 3840)");
26+
println!(" --height <HEIGHT> Height of the display (default: 2400)");
27+
println!(" --codec <CODEC> Codec to use (default: RemoteFX)");
28+
println!(" Valid values: RemoteFX, Bitmap, None");
29+
std::process::exit(0);
30+
}
31+
32+
let width = args.opt_value_from_str("--width")?.unwrap_or(3840);
33+
let height = args.opt_value_from_str("--height")?.unwrap_or(2400);
34+
let codec = args.opt_value_from_str("--codec")?.unwrap_or_else(OptCodec::default);
35+
36+
let filename: String = args.free_from_str().context("missing RGBX input filename")?;
37+
let file = File::open(&filename)
38+
.await
39+
.with_context(|| format!("Failed to open file: {}", filename))?;
40+
41+
let mut flags = CmdFlags::all();
42+
43+
#[allow(unused)]
44+
let (remotefx, qoicodec) = match codec {
45+
OptCodec::RemoteFX => (Some((EntropyBits::Rlgr3, 0)), None::<u8>),
46+
OptCodec::Bitmap => {
47+
flags -= CmdFlags::SET_SURFACE_BITS;
48+
(None, None)
49+
}
50+
OptCodec::None => (None, None),
51+
};
52+
let mut encoder = UpdateEncoder::new(DesktopSize { width, height }, flags, remotefx);
53+
54+
let mut total_raw = 0u64;
55+
let mut total_enc = 0u64;
56+
let mut n_updates = 0u64;
57+
let mut updates = DisplayUpdates::new(file, DesktopSize { width, height });
58+
while let Some(up) = updates.next_update().await {
59+
if let DisplayUpdate::Bitmap(ref up) = up {
60+
total_raw += up.data.len() as u64;
61+
} else {
62+
eprintln!("Invalid update");
63+
break;
64+
}
65+
let mut iter = encoder.update(up);
66+
loop {
67+
let Some(frag) = iter.next().await else {
68+
break;
69+
};
70+
let len = frag?.data.len() as u64;
71+
total_enc += len;
72+
}
73+
n_updates += 1;
74+
print!(".");
75+
std::io::stdout().flush().unwrap();
76+
}
77+
println!();
78+
79+
let ratio = total_enc as f64 / total_raw as f64;
80+
let percent = 100.0 - ratio * 100.0;
81+
println!("Encoder: {:?}", encoder);
82+
println!("Nb updates: {:?}", n_updates);
83+
println!(
84+
"Sum of bytes: {}/{} ({:.2}%)",
85+
bytesize::ByteSize(total_enc),
86+
bytesize::ByteSize(total_raw),
87+
percent,
88+
);
89+
Ok(())
90+
}
91+
92+
struct DisplayUpdates {
93+
file: File,
94+
desktop_size: DesktopSize,
95+
}
96+
97+
impl DisplayUpdates {
98+
fn new(file: File, desktop_size: DesktopSize) -> Self {
99+
Self { file, desktop_size }
100+
}
101+
}
102+
103+
#[async_trait::async_trait]
104+
impl RdpServerDisplayUpdates for DisplayUpdates {
105+
async fn next_update(&mut self) -> Option<DisplayUpdate> {
106+
let stride = self.desktop_size.width as usize * 4;
107+
let frame_size = stride * self.desktop_size.height as usize;
108+
let mut buf = vec![0u8; frame_size];
109+
if self.file.read_exact(&mut buf).await.is_err() {
110+
return None;
111+
}
112+
113+
let up = DisplayUpdate::Bitmap(BitmapUpdate {
114+
x: 0,
115+
y: 0,
116+
width: self.desktop_size.width.try_into().unwrap(),
117+
height: self.desktop_size.height.try_into().unwrap(),
118+
format: PixelFormat::RgbX32,
119+
data: buf.into(),
120+
stride,
121+
});
122+
Some(up)
123+
}
124+
}
125+
126+
fn setup_logging() -> anyhow::Result<()> {
127+
use tracing::metadata::LevelFilter;
128+
use tracing_subscriber::prelude::*;
129+
use tracing_subscriber::EnvFilter;
130+
131+
let fmt_layer = tracing_subscriber::fmt::layer().compact();
132+
133+
let env_filter = EnvFilter::builder()
134+
.with_default_directive(LevelFilter::WARN.into())
135+
.with_env_var("IRONRDP_LOG")
136+
.from_env_lossy();
137+
138+
tracing_subscriber::registry()
139+
.with(fmt_layer)
140+
.with(env_filter)
141+
.try_init()
142+
.context("failed to set tracing global subscriber")?;
143+
144+
Ok(())
145+
}
146+
147+
enum OptCodec {
148+
RemoteFX,
149+
Bitmap,
150+
None,
151+
}
152+
153+
impl Default for OptCodec {
154+
fn default() -> Self {
155+
Self::RemoteFX
156+
}
157+
}
158+
159+
impl core::str::FromStr for OptCodec {
160+
type Err = anyhow::Error;
161+
162+
fn from_str(s: &str) -> Result<Self, Self::Err> {
163+
match s {
164+
"remotefx" => Ok(Self::RemoteFX),
165+
"bitmap" => Ok(Self::Bitmap),
166+
"none" => Ok(Self::None),
167+
_ => Err(anyhow::anyhow!("unknown codec: {}", s)),
168+
}
169+
}
170+
}

crates/ironrdp-server/src/encoder/fast_path.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ const MAX_FASTPATH_UPDATE_SIZE: usize = 16_374;
1010

1111
const FASTPATH_HEADER_SIZE: usize = 6;
1212

13+
#[cfg_attr(feature = "__bench", visibility::make(pub))]
1314
pub(crate) struct UpdateFragmenter {
1415
code: UpdateCode,
1516
index: usize,
16-
data: Vec<u8>,
17+
#[doc(hidden)] // not part of the public API, used by benchmarks
18+
pub data: Vec<u8>,
1719
position: usize,
1820
}
1921

crates/ironrdp-server/src/encoder/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ enum CodecId {
2626
None = 0x0,
2727
}
2828

29+
#[cfg_attr(feature = "__bench", visibility::make(pub))]
2930
pub(crate) struct UpdateEncoder {
3031
desktop_size: DesktopSize,
3132
// FIXME: draw updates on the framebuffer
@@ -42,6 +43,7 @@ impl fmt::Debug for UpdateEncoder {
4243
}
4344

4445
impl UpdateEncoder {
46+
#[cfg_attr(feature = "__bench", visibility::make(pub))]
4547
pub(crate) fn new(desktop_size: DesktopSize, surface_flags: CmdFlags, remotefx: Option<(EntropyBits, u8)>) -> Self {
4648
let bitmap_updater = if !surface_flags.contains(CmdFlags::SET_SURFACE_BITS) {
4749
BitmapUpdater::Bitmap(BitmapHandler::new())
@@ -59,6 +61,7 @@ impl UpdateEncoder {
5961
}
6062
}
6163

64+
#[cfg_attr(feature = "__bench", visibility::make(pub))]
6265
pub(crate) fn update(&mut self, update: DisplayUpdate) -> EncoderIter<'_> {
6366
EncoderIter {
6467
encoder: self,
@@ -148,12 +151,14 @@ impl UpdateEncoder {
148151
}
149152
}
150153

154+
#[cfg_attr(feature = "__bench", visibility::make(pub))]
151155
pub(crate) struct EncoderIter<'a> {
152156
encoder: &'a mut UpdateEncoder,
153157
update: Option<DisplayUpdate>,
154158
}
155159

156160
impl EncoderIter<'_> {
161+
#[cfg_attr(feature = "__bench", visibility::make(pub))]
157162
pub(crate) async fn next(&mut self) -> Option<Result<UpdateFragmenter>> {
158163
let update = self.update.take()?;
159164
let encoder = &mut self.encoder;

crates/ironrdp-server/src/lib.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ pub mod bench {
3232
pub mod rfx {
3333
pub use crate::encoder::rfx::bench::{rfx_enc, rfx_enc_tile};
3434
}
35+
36+
pub use crate::encoder::UpdateEncoder;
3537
}
3638
}
3739

@@ -40,7 +42,7 @@ macro_rules! time_warn {
4042
($context:expr, $threshold_ms:expr, $op:expr) => {{
4143
#[cold]
4244
fn warn_log(context: &str, duration: u128) {
43-
use ::core::sync::atomic::AtomicUsize;
45+
use core::sync::atomic::AtomicUsize;
4446

4547
static COUNT: AtomicUsize = AtomicUsize::new(0);
4648
let current_count = COUNT.fetch_add(1, ::core::sync::atomic::Ordering::Relaxed);
@@ -58,3 +60,10 @@ macro_rules! time_warn {
5860
result
5961
}};
6062
}
63+
64+
#[cfg(test)]
65+
mod tests {
66+
use bytesize as _;
67+
use pico_args as _;
68+
use tracing_subscriber as _;
69+
}

0 commit comments

Comments
 (0)