Skip to content

Commit 91cd633

Browse files
authored
Add JSON, XML mock perf tests for azure_core (#3212)
Uses a mock transport to more accurately test the pipeline and deserialization.
1 parent a83f397 commit 91cd633

File tree

7 files changed

+388
-0
lines changed

7 files changed

+388
-0
lines changed

sdk/core/azure_core/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,8 @@ required-features = ["xml"]
104104
[[bench]]
105105
name = "http_transport_benchmarks"
106106
harness = false
107+
108+
[[test]]
109+
name = "perf"
110+
path = "perf/perf.rs"
111+
harness = false

sdk/core/azure_core/perf-tests.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
Service: core
2+
3+
Project: azure_core_perf
4+
5+
PrimaryPackage: azure_core
6+
7+
PackageVersions:
8+
# - azure_core: 1.0.0
9+
- azure_core: source
10+
11+
Tests:
12+
- Test: mock_json
13+
Class: mock_json
14+
Arguments:
15+
- --count 5 --parallel 64
16+
- --count 500 --parallel 32
17+
- --count 50000 --parallel 32 --warmup 60 --duration 60
18+
# - Test: mock_xml
19+
# Class: mock_xml
20+
# Arguments:
21+
# - --count 5 --parallel 64
22+
# - --count 500 --parallel 32
23+
# - --count 50000 --parallel 32 --warmup 60 --duration 60

sdk/core/azure_core/perf.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
trigger: none
2+
3+
pr: none
4+
5+
schedules:
6+
- cron: "0 7 * * *"
7+
displayName: Daily midnight build
8+
branches:
9+
include:
10+
- main
11+
always: true
12+
13+
parameters:
14+
- name: PackageVersions
15+
displayName: PackageVersions (regex of package versions to run)
16+
type: string
17+
default: '12|source'
18+
- name: Tests
19+
displayName: Tests (regex of tests to run)
20+
type: string
21+
default: '^(mock_json|mock_xml)$'
22+
- name: Arguments
23+
displayName: Arguments (regex of arguments to run)
24+
type: string
25+
default: '(5)|(500)|(50000)'
26+
- name: Iterations
27+
displayName: Iterations (times to run each test)
28+
type: number
29+
default: '5'
30+
- name: Profile
31+
type: boolean
32+
default: false
33+
- name: AdditionalArguments
34+
displayName: AdditionalArguments (passed to PerfAutomation)
35+
type: string
36+
default: ''
37+
38+
extends:
39+
template: /eng/pipelines/templates/jobs/perf.yml
40+
parameters:
41+
ServiceDirectory: core/azure_core
42+
PackageVersions: ${{ parameters.PackageVersions }}
43+
Tests: ${{ parameters.Tests }}
44+
Arguments: ${{ parameters.Arguments }}
45+
Iterations: ${{ parameters.Iterations }}
46+
AdditionalArguments: ${{ parameters.AdditionalArguments }}
47+
Profile: ${{ parameters.Profile }}

sdk/core/azure_core/perf/mock.rs

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
pub mod json;
5+
#[cfg(feature = "xml")]
6+
pub mod xml;
7+
8+
use std::sync::Arc;
9+
10+
use azure_core::{
11+
base64::{
12+
self,
13+
option::{deserialize, serialize},
14+
},
15+
http::{headers::Headers, BufResponse, ClientOptions, Pipeline, StatusCode, Transport},
16+
time::{self, OffsetDateTime},
17+
Bytes,
18+
};
19+
use azure_core_test::http::MockHttpClient;
20+
use futures::FutureExt as _;
21+
use serde::{Deserialize, Serialize};
22+
23+
const DEFAULT_COUNT: usize = 25;
24+
25+
#[derive(Debug, Default, Deserialize, Serialize)]
26+
pub struct List {
27+
#[serde(default, rename = "name")]
28+
name: Option<String>,
29+
30+
#[serde(default, rename = "container")]
31+
container: Option<ListItemsContainer>,
32+
33+
#[serde(default, rename = "next")]
34+
next: Option<String>,
35+
}
36+
37+
#[derive(Debug, Default, Deserialize, Serialize)]
38+
pub struct ListItemsContainer {
39+
#[serde(default, rename = "items")]
40+
items: Option<Vec<ListItem>>,
41+
}
42+
43+
#[derive(Debug, Default, Deserialize, Serialize)]
44+
pub struct ListItem {
45+
#[serde(default, rename = "name")]
46+
name: Option<String>,
47+
48+
#[serde(default, rename = "properties")]
49+
properties: Option<ListItemProperties>,
50+
}
51+
52+
#[derive(Debug, Default, Deserialize, Serialize)]
53+
pub struct ListItemProperties {
54+
#[serde(default, rename = "etag")]
55+
etag: Option<azure_core::http::Etag>,
56+
57+
#[serde(default, rename = "creationTime", with = "time::rfc7231::option")]
58+
creation_time: Option<OffsetDateTime>,
59+
60+
#[serde(default, rename = "lastModified", with = "time::rfc7231::option")]
61+
last_modified: Option<OffsetDateTime>,
62+
63+
#[serde(
64+
default,
65+
rename = "contentMD5",
66+
serialize_with = "serialize",
67+
deserialize_with = "deserialize"
68+
)]
69+
content_md5: Option<Vec<u8>>,
70+
}
71+
72+
fn create_pipeline<F>(count: usize, f: F) -> azure_core::Result<Pipeline>
73+
where
74+
F: Fn(&List) -> azure_core::Result<Bytes>,
75+
{
76+
let mut list = List {
77+
name: Some("t0123456789abcdef".into()),
78+
..Default::default()
79+
};
80+
let mut items = Vec::with_capacity(count);
81+
let now = OffsetDateTime::now_utc();
82+
for i in 0..count {
83+
let name = format!("testItem{i}");
84+
let hash = base64::encode(&name).into_bytes();
85+
items.push(ListItem {
86+
name: Some(name),
87+
properties: Some(ListItemProperties {
88+
etag: Some(i.to_string().into()),
89+
creation_time: Some(now),
90+
last_modified: Some(now),
91+
content_md5: Some(hash),
92+
}),
93+
});
94+
}
95+
list.container = Some(ListItemsContainer { items: Some(items) });
96+
97+
let body = f(&list)?;
98+
println!("Serialized {count} items in {} bytes", body.len());
99+
100+
let client = Arc::new(MockHttpClient::new(move |_| {
101+
let body = body.clone();
102+
async move {
103+
// Yield simulates an expected network call but kills performance by ~45%.
104+
tokio::task::yield_now().await;
105+
Ok(BufResponse::from_bytes(
106+
StatusCode::Ok,
107+
Headers::new(),
108+
body,
109+
))
110+
}
111+
.boxed()
112+
}));
113+
let options = ClientOptions {
114+
transport: Some(Transport::new(client)),
115+
..Default::default()
116+
};
117+
let pipeline = Pipeline::new(
118+
Some("perf"),
119+
Some("0.1.0"),
120+
options,
121+
Vec::new(),
122+
Vec::new(),
123+
None,
124+
);
125+
Ok(pipeline)
126+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
use super::List;
5+
use azure_core::{
6+
http::{Context, JsonFormat, Method, Pipeline, RawResponse, Request, Response},
7+
json,
8+
};
9+
use azure_core_test::{
10+
perf::{CreatePerfTestReturn, PerfRunner, PerfTest, PerfTestMetadata, PerfTestOption},
11+
TestContext,
12+
};
13+
use futures::FutureExt as _;
14+
use std::{hint::black_box, sync::Arc};
15+
16+
pub struct MockJsonTest {
17+
pipeline: Pipeline,
18+
}
19+
20+
impl MockJsonTest {
21+
fn create_items(runner: PerfRunner) -> CreatePerfTestReturn {
22+
async move {
23+
let count = runner
24+
.try_get_test_arg("count")?
25+
.cloned()
26+
.unwrap_or(super::DEFAULT_COUNT);
27+
let pipeline = super::create_pipeline(count, json::to_json)?;
28+
Ok(Box::new(MockJsonTest { pipeline }) as Box<dyn PerfTest>)
29+
}
30+
.boxed()
31+
}
32+
33+
pub fn test_metadata() -> PerfTestMetadata {
34+
PerfTestMetadata {
35+
name: "mock_json",
36+
description: "Mock transport that returns JSON",
37+
options: vec![PerfTestOption {
38+
name: "count",
39+
display_message: "Number of items per page",
40+
mandatory: false,
41+
short_activator: None,
42+
long_activator: "count",
43+
expected_args_len: 1,
44+
..Default::default()
45+
}],
46+
create_test: Self::create_items,
47+
}
48+
}
49+
}
50+
51+
#[async_trait::async_trait]
52+
impl PerfTest for MockJsonTest {
53+
async fn setup(&self, _context: Arc<TestContext>) -> azure_core::Result<()> {
54+
Ok(())
55+
}
56+
57+
async fn run(&self, _context: Arc<TestContext>) -> azure_core::Result<()> {
58+
let ctx = Context::new();
59+
let mut request = Request::new(
60+
"https://contoso.com/containers/t0123456789abcdef?api-version=2025-10-15".parse()?,
61+
Method::Get,
62+
);
63+
let response = self.pipeline.send(&ctx, &mut request, None).await?;
64+
// Make sure we deserialize the response.
65+
let (status, headers, body) = response.deconstruct();
66+
let response: Response<List, JsonFormat> =
67+
RawResponse::from_bytes(status, headers, body).into();
68+
let list: List = tokio::spawn(async move {
69+
tokio::task::yield_now().await;
70+
response.into_body()
71+
})
72+
.await
73+
.unwrap()?;
74+
assert_eq!(black_box(list.name), Some("t0123456789abcdef".into()));
75+
76+
Ok(())
77+
}
78+
79+
async fn cleanup(&self, _context: Arc<TestContext>) -> azure_core::Result<()> {
80+
Ok(())
81+
}
82+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
use super::List;
5+
use azure_core::{
6+
http::{Context, Method, Pipeline, RawResponse, Request, Response, XmlFormat},
7+
xml,
8+
};
9+
use azure_core_test::{
10+
perf::{CreatePerfTestReturn, PerfRunner, PerfTest, PerfTestMetadata, PerfTestOption},
11+
TestContext,
12+
};
13+
use futures::FutureExt as _;
14+
use std::{hint::black_box, sync::Arc};
15+
16+
pub struct MockXmlTest {
17+
pipeline: Pipeline,
18+
}
19+
20+
impl MockXmlTest {
21+
fn create_items(runner: PerfRunner) -> CreatePerfTestReturn {
22+
async move {
23+
let count = runner
24+
.try_get_test_arg("count")?
25+
.cloned()
26+
.unwrap_or(super::DEFAULT_COUNT);
27+
let pipeline = super::create_pipeline(count, xml::to_xml)?;
28+
Ok(Box::new(MockXmlTest { pipeline }) as Box<dyn PerfTest>)
29+
}
30+
.boxed()
31+
}
32+
33+
pub fn test_metadata() -> PerfTestMetadata {
34+
PerfTestMetadata {
35+
name: "mock_xml",
36+
description: "Mock transport that returns XML",
37+
options: vec![PerfTestOption {
38+
name: "count",
39+
display_message: "Number of items per page",
40+
mandatory: false,
41+
short_activator: None,
42+
long_activator: "count",
43+
expected_args_len: 1,
44+
..Default::default()
45+
}],
46+
create_test: Self::create_items,
47+
}
48+
}
49+
}
50+
51+
#[async_trait::async_trait]
52+
impl PerfTest for MockXmlTest {
53+
async fn setup(&self, _context: Arc<TestContext>) -> azure_core::Result<()> {
54+
Ok(())
55+
}
56+
57+
async fn run(&self, _context: Arc<TestContext>) -> azure_core::Result<()> {
58+
let ctx = Context::new();
59+
let mut request = Request::new(
60+
"https://contoso.com/containers/t0123456789abcdef?api-version=2025-10-15".parse()?,
61+
Method::Get,
62+
);
63+
let response = self.pipeline.send(&ctx, &mut request, None).await?;
64+
// Make sure we deserialize the response.
65+
let (status, headers, body) = response.deconstruct();
66+
let response: Response<List, XmlFormat> =
67+
RawResponse::from_bytes(status, headers, body).into();
68+
let list: List = tokio::spawn(async move {
69+
tokio::task::yield_now().await;
70+
response.into_body()
71+
})
72+
.await
73+
.unwrap()?;
74+
assert_eq!(black_box(list.name), Some("t0123456789abcdef".into()));
75+
76+
Ok(())
77+
}
78+
79+
async fn cleanup(&self, _context: Arc<TestContext>) -> azure_core::Result<()> {
80+
Ok(())
81+
}
82+
}

sdk/core/azure_core/perf/perf.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
mod mock;
5+
6+
use azure_core_test::perf::PerfRunner;
7+
8+
#[tokio::main]
9+
async fn main() -> azure_core::Result<()> {
10+
let runner = PerfRunner::new(
11+
env!("CARGO_MANIFEST_DIR"),
12+
file!(),
13+
vec![
14+
mock::json::MockJsonTest::test_metadata(),
15+
#[cfg(feature = "xml")]
16+
mock::xml::MockXmlTest::test_metadata(),
17+
],
18+
)?;
19+
20+
runner.run().await?;
21+
22+
Ok(())
23+
}

0 commit comments

Comments
 (0)