Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions sdk/core/azure_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,8 @@ required-features = ["xml"]
[[bench]]
name = "http_transport_benchmarks"
harness = false

[[test]]
name = "perf"
path = "perf/perf.rs"
harness = false
23 changes: 23 additions & 0 deletions sdk/core/azure_core/perf-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Service: core

Project: azure_core_perf

PrimaryPackage: azure_core

PackageVersions:
# - azure_core: 1.0.0
- azure_core: source

Tests:
- Test: mock_json
Class: mock_json
Arguments:
- --count 5 --parallel 64
- --count 500 --parallel 32
- --count 50000 --parallel 32 --warmup 60 --duration 60
# - Test: mock_xml
# Class: mock_xml
# Arguments:
# - --count 5 --parallel 64
# - --count 500 --parallel 32
# - --count 50000 --parallel 32 --warmup 60 --duration 60
47 changes: 47 additions & 0 deletions sdk/core/azure_core/perf.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
trigger: none

pr: none

schedules:
- cron: "0 7 * * *"
displayName: Daily midnight build
branches:
include:
- main
always: true

parameters:
- name: PackageVersions
displayName: PackageVersions (regex of package versions to run)
type: string
default: '12|source'
- name: Tests
displayName: Tests (regex of tests to run)
type: string
default: '^(mock_json|mock_xml)$'
- name: Arguments
displayName: Arguments (regex of arguments to run)
type: string
default: '(5)|(500)|(50000)'
- name: Iterations
displayName: Iterations (times to run each test)
type: number
default: '5'
- name: Profile
type: boolean
default: false
- name: AdditionalArguments
displayName: AdditionalArguments (passed to PerfAutomation)
type: string
default: ''

extends:
template: /eng/pipelines/templates/jobs/perf.yml
parameters:
ServiceDirectory: core/azure_core
PackageVersions: ${{ parameters.PackageVersions }}
Tests: ${{ parameters.Tests }}
Arguments: ${{ parameters.Arguments }}
Iterations: ${{ parameters.Iterations }}
AdditionalArguments: ${{ parameters.AdditionalArguments }}
Profile: ${{ parameters.Profile }}
126 changes: 126 additions & 0 deletions sdk/core/azure_core/perf/mock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

pub mod json;
#[cfg(feature = "xml")]
pub mod xml;

use std::sync::Arc;

use azure_core::{
base64::{
self,
option::{deserialize, serialize},
},
http::{headers::Headers, BufResponse, ClientOptions, Pipeline, StatusCode, Transport},
time::{self, OffsetDateTime},
Bytes,
};
use azure_core_test::http::MockHttpClient;
use futures::FutureExt as _;
use serde::{Deserialize, Serialize};

const DEFAULT_COUNT: usize = 25;

#[derive(Debug, Default, Deserialize, Serialize)]
pub struct List {
#[serde(default, rename = "name")]
name: Option<String>,

#[serde(default, rename = "container")]
container: Option<ListItemsContainer>,

#[serde(default, rename = "next")]
next: Option<String>,
}

#[derive(Debug, Default, Deserialize, Serialize)]
pub struct ListItemsContainer {
#[serde(default, rename = "items")]
items: Option<Vec<ListItem>>,
}

#[derive(Debug, Default, Deserialize, Serialize)]
pub struct ListItem {
#[serde(default, rename = "name")]
name: Option<String>,

#[serde(default, rename = "properties")]
properties: Option<ListItemProperties>,
}

#[derive(Debug, Default, Deserialize, Serialize)]
pub struct ListItemProperties {
#[serde(default, rename = "etag")]
etag: Option<azure_core::http::Etag>,

#[serde(default, rename = "creationTime", with = "time::rfc7231::option")]
creation_time: Option<OffsetDateTime>,

#[serde(default, rename = "lastModified", with = "time::rfc7231::option")]
last_modified: Option<OffsetDateTime>,

#[serde(
default,
rename = "contentMD5",
serialize_with = "serialize",
deserialize_with = "deserialize"
)]
content_md5: Option<Vec<u8>>,
}

fn create_pipeline<F>(count: usize, f: F) -> azure_core::Result<Pipeline>
where
F: Fn(&List) -> azure_core::Result<Bytes>,
{
let mut list = List {
name: Some("t0123456789abcdef".into()),
..Default::default()
};
let mut items = Vec::with_capacity(count);
let now = OffsetDateTime::now_utc();
for i in 0..count {
let name = format!("testItem{i}");
let hash = base64::encode(&name).into_bytes();
items.push(ListItem {
name: Some(name),
properties: Some(ListItemProperties {
etag: Some(i.to_string().into()),
creation_time: Some(now),
last_modified: Some(now),
content_md5: Some(hash),
}),
});
}
list.container = Some(ListItemsContainer { items: Some(items) });

let body = f(&list)?;
println!("Serialized {count} items in {} bytes", body.len());

let client = Arc::new(MockHttpClient::new(move |_| {
let body = body.clone();
async move {
// Yield simulates an expected network call but kills performance by ~45%.
tokio::task::yield_now().await;
Ok(BufResponse::from_bytes(
StatusCode::Ok,
Headers::new(),
body,
))
}
.boxed()
}));
let options = ClientOptions {
transport: Some(Transport::new(client)),
..Default::default()
};
let pipeline = Pipeline::new(
Some("perf"),
Some("0.1.0"),
options,
Vec::new(),
Vec::new(),
None,
);
Ok(pipeline)
}
82 changes: 82 additions & 0 deletions sdk/core/azure_core/perf/mock/json.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

use super::List;
use azure_core::{
http::{Context, JsonFormat, Method, Pipeline, RawResponse, Request, Response},
json,
};
use azure_core_test::{
perf::{CreatePerfTestReturn, PerfRunner, PerfTest, PerfTestMetadata, PerfTestOption},
TestContext,
};
use futures::FutureExt as _;
use std::{hint::black_box, sync::Arc};

pub struct MockJsonTest {
pipeline: Pipeline,
}

impl MockJsonTest {
fn create_items(runner: PerfRunner) -> CreatePerfTestReturn {
async move {
let count = runner
.try_get_test_arg("count")?
.cloned()
.unwrap_or(super::DEFAULT_COUNT);
let pipeline = super::create_pipeline(count, json::to_json)?;
Ok(Box::new(MockJsonTest { pipeline }) as Box<dyn PerfTest>)
}
.boxed()
}

pub fn test_metadata() -> PerfTestMetadata {
PerfTestMetadata {
name: "mock_json",
description: "Mock transport that returns JSON",
options: vec![PerfTestOption {
name: "count",
display_message: "Number of items per page",
mandatory: false,
short_activator: None,
long_activator: "count",
expected_args_len: 1,
..Default::default()
}],
create_test: Self::create_items,
}
}
}

#[async_trait::async_trait]
impl PerfTest for MockJsonTest {
async fn setup(&self, _context: Arc<TestContext>) -> azure_core::Result<()> {
Ok(())
}

async fn run(&self, _context: Arc<TestContext>) -> azure_core::Result<()> {
let ctx = Context::new();
let mut request = Request::new(
"https://contoso.com/containers/t0123456789abcdef?api-version=2025-10-15".parse()?,
Method::Get,
);
let response = self.pipeline.send(&ctx, &mut request, None).await?;
// Make sure we deserialize the response.
let (status, headers, body) = response.deconstruct();
let response: Response<List, JsonFormat> =
RawResponse::from_bytes(status, headers, body).into();
let list: List = tokio::spawn(async move {
tokio::task::yield_now().await;
response.into_body()
})
.await
.unwrap()?;
assert_eq!(black_box(list.name), Some("t0123456789abcdef".into()));

Ok(())
}

async fn cleanup(&self, _context: Arc<TestContext>) -> azure_core::Result<()> {
Ok(())
}
}
82 changes: 82 additions & 0 deletions sdk/core/azure_core/perf/mock/xml.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

use super::List;
use azure_core::{
http::{Context, Method, Pipeline, RawResponse, Request, Response, XmlFormat},
xml,
};
use azure_core_test::{
perf::{CreatePerfTestReturn, PerfRunner, PerfTest, PerfTestMetadata, PerfTestOption},
TestContext,
};
use futures::FutureExt as _;
use std::{hint::black_box, sync::Arc};

pub struct MockXmlTest {
pipeline: Pipeline,
}

impl MockXmlTest {
fn create_items(runner: PerfRunner) -> CreatePerfTestReturn {
async move {
let count = runner
.try_get_test_arg("count")?
.cloned()
.unwrap_or(super::DEFAULT_COUNT);
let pipeline = super::create_pipeline(count, xml::to_xml)?;
Ok(Box::new(MockXmlTest { pipeline }) as Box<dyn PerfTest>)
}
.boxed()
}

pub fn test_metadata() -> PerfTestMetadata {
PerfTestMetadata {
name: "mock_xml",
description: "Mock transport that returns XML",
options: vec![PerfTestOption {
name: "count",
display_message: "Number of items per page",
mandatory: false,
short_activator: None,
long_activator: "count",
expected_args_len: 1,
..Default::default()
}],
create_test: Self::create_items,
}
}
}

#[async_trait::async_trait]
impl PerfTest for MockXmlTest {
async fn setup(&self, _context: Arc<TestContext>) -> azure_core::Result<()> {
Ok(())
}

async fn run(&self, _context: Arc<TestContext>) -> azure_core::Result<()> {
let ctx = Context::new();
let mut request = Request::new(
"https://contoso.com/containers/t0123456789abcdef?api-version=2025-10-15".parse()?,
Method::Get,
);
let response = self.pipeline.send(&ctx, &mut request, None).await?;
// Make sure we deserialize the response.
let (status, headers, body) = response.deconstruct();
let response: Response<List, XmlFormat> =
RawResponse::from_bytes(status, headers, body).into();
let list: List = tokio::spawn(async move {
tokio::task::yield_now().await;
response.into_body()
})
.await
.unwrap()?;
assert_eq!(black_box(list.name), Some("t0123456789abcdef".into()));

Ok(())
}

async fn cleanup(&self, _context: Arc<TestContext>) -> azure_core::Result<()> {
Ok(())
}
}
23 changes: 23 additions & 0 deletions sdk/core/azure_core/perf/perf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

mod mock;

use azure_core_test::perf::PerfRunner;

#[tokio::main]
async fn main() -> azure_core::Result<()> {
let runner = PerfRunner::new(
env!("CARGO_MANIFEST_DIR"),
file!(),
vec![
mock::json::MockJsonTest::test_metadata(),
#[cfg(feature = "xml")]
mock::xml::MockXmlTest::test_metadata(),
],
)?;

runner.run().await?;

Ok(())
}