Skip to content

Commit 9445689

Browse files
committed
feat(server): add perfenc example/test
Make some internal APIs publicly visible thanks to "visibility" when compiling with the "__bench" feature. ("testsuite-core" also learned "__bench", because fast_path.rs is a shared file) Signed-off-by: Marc-André Lureau <[email protected]>
1 parent e844b6f commit 9445689

File tree

8 files changed

+253
-4
lines changed

8 files changed

+253
-4
lines changed

Cargo.lock

Lines changed: 22 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
@@ -15,14 +15,18 @@ categories.workspace = true
1515
doctest = true
1616
test = false
1717

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

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

2731
[dependencies]
2832
anyhow = "1.0"
@@ -46,9 +50,13 @@ x509-cert = { version = "0.2.5", optional = true }
4650
rustls-pemfile = { version = "2.2.0", optional = true }
4751
rayon = { version = "1.10.0", optional = true }
4852
bytes = "1"
53+
visibility = { version = "0.1", optional = true }
4954

5055
[dev-dependencies]
51-
tokio = { version = "1", features = ["sync"] }
56+
tokio = { version = "1", features = ["sync", "fs"] }
57+
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
58+
pico-args = "0.5"
59+
bytesize = "1.3"
5260

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

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@ const MAX_FASTPATH_UPDATE_SIZE: usize = 16_374;
88

99
const FASTPATH_HEADER_SIZE: usize = 6;
1010

11+
#[allow(unreachable_pub)]
12+
#[cfg_attr(feature = "__bench", visibility::make(pub))]
1113
pub(crate) struct UpdateFragmenter {
1214
code: UpdateCode,
1315
index: usize,
14-
data: Vec<u8>,
16+
#[doc(hidden)] // not part of the public API, used by benchmarks
17+
pub data: Vec<u8>,
1518
position: usize,
1619
}
1720

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

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

29+
#[cfg_attr(feature = "__bench", visibility::make(pub))]
2930
#[derive(Debug)]
3031
pub(crate) struct UpdateEncoderCodecs {
3132
remotefx: Option<(EntropyBits, u8)>,
3233
}
3334

3435
impl UpdateEncoderCodecs {
36+
#[cfg_attr(feature = "__bench", visibility::make(pub))]
3537
pub(crate) fn new() -> Self {
3638
Self { remotefx: None }
3739
}
3840

41+
#[cfg_attr(feature = "__bench", visibility::make(pub))]
3942
pub(crate) fn set_remotefx(&mut self, remotefx: Option<(EntropyBits, u8)>) {
4043
self.remotefx = remotefx
4144
}
@@ -47,6 +50,7 @@ impl Default for UpdateEncoderCodecs {
4750
}
4851
}
4952

53+
#[cfg_attr(feature = "__bench", visibility::make(pub))]
5054
pub(crate) struct UpdateEncoder {
5155
desktop_size: DesktopSize,
5256
// FIXME: draw updates on the framebuffer
@@ -63,6 +67,7 @@ impl fmt::Debug for UpdateEncoder {
6367
}
6468

6569
impl UpdateEncoder {
70+
#[cfg_attr(feature = "__bench", visibility::make(pub))]
6671
pub(crate) fn new(desktop_size: DesktopSize, surface_flags: CmdFlags, codecs: UpdateEncoderCodecs) -> Self {
6772
let bitmap_updater = if surface_flags.contains(CmdFlags::SET_SURFACE_BITS) {
6873
let mut bitmap = BitmapUpdater::None(NoneHandler);
@@ -83,6 +88,7 @@ impl UpdateEncoder {
8388
}
8489
}
8590

91+
#[cfg_attr(feature = "__bench", visibility::make(pub))]
8692
pub(crate) fn update(&mut self, update: DisplayUpdate) -> EncoderIter<'_> {
8793
EncoderIter {
8894
encoder: self,
@@ -167,12 +173,14 @@ impl UpdateEncoder {
167173
}
168174
}
169175

176+
#[cfg_attr(feature = "__bench", visibility::make(pub))]
170177
pub(crate) struct EncoderIter<'a> {
171178
encoder: &'a mut UpdateEncoder,
172179
update: Option<DisplayUpdate>,
173180
}
174181

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

crates/ironrdp-server/src/lib.rs

Lines changed: 9 additions & 0 deletions
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, UpdateEncoderCodecs};
3537
}
3638
}
3739

@@ -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)