Skip to content
1 change: 1 addition & 0 deletions .vscode/cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@
"PCERT",
"PBYTE",
"pdbs",
"perfstress",
"phoebusm",
"Piotrowski",
"pkcs",
Expand Down
4 changes: 4 additions & 0 deletions sdk/core/perf/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ set(
inc/azure/perf/argagg.hpp
inc/azure/perf/base_test.hpp
inc/azure/perf/dynamic_test_options.hpp
inc/azure/perf/latency_stats.hpp
inc/azure/perf/options.hpp
inc/azure/perf/program.hpp
inc/azure/perf/random_stream.hpp
inc/azure/perf/result_output.hpp
inc/azure/perf/test.hpp
inc/azure/perf/test_metadata.hpp
inc/azure/perf/test_options.hpp
Expand All @@ -30,9 +32,11 @@ set(
AZURE_PERFORMANCE_SOURCE
src/arg_parser.cpp
src/base_test.cpp
src/latency_stats.cpp
src/options.cpp
src/program.cpp
src/random_stream.cpp
src/result_output.cpp
)

add_library(azure-perf ${AZURE_PERFORMANCE_HEADER} ${AZURE_PERFORMANCE_SOURCE})
Expand Down
3 changes: 3 additions & 0 deletions sdk/core/perf/inc/azure/perf.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@
#include "azure/perf/argagg.hpp"
#include "azure/perf/base_test.hpp"
#include "azure/perf/dynamic_test_options.hpp"
#include "azure/perf/latency_stats.hpp"
#include "azure/perf/options.hpp"
#include "azure/perf/program.hpp"
#include "azure/perf/random_stream.hpp"
#include "azure/perf/result_output.hpp"
#include "azure/perf/test.hpp"
#include "azure/perf/test_metadata.hpp"
#include "azure/perf/test_options.hpp"
105 changes: 105 additions & 0 deletions sdk/core/perf/inc/azure/perf/latency_stats.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

/**
* @file
* @brief Per-operation latency collector and percentile summary.
*
*/

#pragma once

#include <chrono>
#include <cstdint>
#include <mutex>
#include <string>
#include <vector>

namespace Azure { namespace Perf {

/**
* @brief Thread-safe collector of per-operation latency samples.
*
* @remark Records nanosecond-resolution durations from many worker threads and computes
* percentile summaries (p50/p90/p95/p99/max) on demand. Designed to match the latency
* reporting added in the Go perf framework so cross-language results are comparable.
*
*/
class LatencyCollector {
public:
/**
* @brief A single latency sample, optionally tagged by call type.
*
*/
struct Sample
{
std::chrono::nanoseconds Duration{0};
std::string CallType;
};

/**
* @brief Latency summary expressed in milliseconds, matching the .NET
* `Azure.Test.Perf` percentile distribution: 50, 75, 90, 99, 99.9, 99.99, 99.999, 100.
*
*/
struct Summary
{
uint64_t Count = 0;
double P50Ms = 0;
double P75Ms = 0;
double P90Ms = 0;
double P99Ms = 0;
double P999Ms = 0;
double P9999Ms = 0;
double P99999Ms = 0;
double P100Ms = 0;
double MeanMs = 0;
};

/**
* @brief Record a single latency sample with no call-type tag.
*
* @param duration The latency to record.
*/
void Record(std::chrono::nanoseconds duration);

/**
* @brief Record a single latency sample tagged with a call type.
*
* @param callType A short label for the operation (e.g. "Upload").
* @param duration The latency to record.
*/
void Record(std::string const& callType, std::chrono::nanoseconds duration);

/**
* @brief Clear all recorded samples.
*
*/
void Reset();

/**
* @brief Compute the summary over all recorded samples.
*
* @return The percentile summary.
*/
Summary Summarize() const;

/**
* @brief Compute summaries grouped by call type.
*
* @return A vector of (callType, summary) pairs, sorted by callType.
*/
std::vector<std::pair<std::string, Summary>> SummarizeByCallType() const;

/**
* @brief Snapshot all recorded samples (copy).
*
*/
std::vector<Sample> Samples() const;

private:
mutable std::mutex m_mutex;
std::vector<Sample> m_samples;
};

}} // namespace Azure::Perf
16 changes: 16 additions & 0 deletions sdk/core/perf/inc/azure/perf/options.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,22 @@ namespace Azure { namespace Perf {
*/
std::vector<std::string> TestProxies;

/**
* @brief Interval in seconds between live status lines printed during a run.
*
*/
int StatusInterval = 1;

/**
* @brief When set, write a per-operation results file (JSON) containing the per-op
* latency (ms) and the per-op size (bytes) for every measured operation, matching the
* .NET `OperationResult { Time, Size }` schema.
*
* @remark Only populated when #Latency is also enabled.
*
*/
std::string ResultsFile;

/**
* @brief Create an array of the performance framework options.
*
Expand Down
84 changes: 84 additions & 0 deletions sdk/core/perf/inc/azure/perf/result_output.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

/**
* @file
* @brief Run-summary helpers: `--results-file` writer and `#StartJobStatistics` printer.
*
*/

#pragma once

#include "azure/perf/latency_stats.hpp"

#include <cstdint>
#include <string>
#include <vector>

namespace Azure { namespace Perf {

/**
* @brief A consolidated run summary used by the framework. Fields mirror the data
* already printed in the .NET reference framework's results block.
*
*/
struct RunSummary
{
std::string TestName;
int Parallel = 1;
int DurationSeconds = 0;
int Warmup = 0;
int Iterations = 1;
uint64_t TotalOperations = 0;
double WeightedAverageSeconds = 0;
double OperationsPerSecond = 0;
double SecondsPerOperation = 0;
LatencyCollector::Summary Latency;
std::vector<std::pair<std::string, LatencyCollector::Summary>> LatencyByCallType;
};

/**
* @brief A single per-operation result, matching the .NET
* `Azure.Test.Perf.OperationResult { Time, Size }` schema.
*
* `Time` is the operation latency in milliseconds; `Size` is the operation size in
* bytes (or -1 if the test does not have a meaningful size).
*
*/
struct OperationResult
{
double Time = 0;
int64_t Size = -1;
};

/**
* @brief Write per-operation results to `path` as a JSON array of
* `OperationResult { Time, Size }` objects, matching the .NET `--results-file` output
* shape.
*
* @param path Destination file.
* @param results The per-operation samples to write.
*/
void WriteResultsFile(std::string const& path, std::vector<OperationResult> const& results);

/**
* @brief Print the `#StartJobStatistics`/`#EndJobStatistics` JSON block consumed by the
* perf-automation tool.
*
* @details The payload matches the .NET reference framework's `BenchmarkOutput`
* envelope:
* ```
* { "Metadata": [
* {"Source","Name","ShortDescription","LongDescription","Format"}
* ],
* "Measurements": [
* {"Timestamp","Name","Value"}
* ]
* }
* ```
*
* @param summary The run summary to serialize.
*/
void PrintJobStatistics(RunSummary const& summary);

}} // namespace Azure::Perf
13 changes: 13 additions & 0 deletions sdk/core/perf/src/arg_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ Azure::Perf::GlobalTestOptions Azure::Perf::Program::ArgParser::Parse(
{
options.NoCleanup = parsedArgs["NoCleanup"].as<bool>();
}
// .NET-compatible bare-switch alias --no-cleanup; presence implies true.
if (parsedArgs["NoCleanupSwitch"])
{
options.NoCleanup = true;
}
if (parsedArgs["Parallel"])
{
options.Parallel = parsedArgs["Parallel"];
Expand All @@ -103,6 +108,14 @@ Azure::Perf::GlobalTestOptions Azure::Perf::Program::ArgParser::Parse(
options.TestProxies.push_back(proxy);
}
}
if (parsedArgs["StatusInterval"])
{
options.StatusInterval = parsedArgs["StatusInterval"];
}
if (parsedArgs["ResultsFile"])
{
options.ResultsFile = parsedArgs["ResultsFile"].as<std::string>();
}

return options;
}
Loading
Loading