A "no-code" (yet extensible) Azure API Performance benchmarking tool (for Developers) based on App-Insights (request-duration) - with multiple output-formats and customizations
💡 If App-Insights is configured, the durations are taken from Request-logs; else, from local timestamps
installation
# Install from nuget.org
dotnet tool install -g perfx --no-cache
# Upgrade to latest version from nuget.org
dotnet tool update -g perfx --no-cache
# Install a specific version from nuget.org
dotnet tool install -g perfx --version 1.0.x
# Uninstall
dotnet tool uninstall -g perfx
🔰 If you don't have .NET Core 3.1 installed, download and install it.
🔰 If the Tool is not accessible post installation, add
%USERPROFILE%\.dotnet\tools
to the PATH env-var.
configuration
Populate the following JSON and save it to your Documents/Perfx
folder with the
name: Perfx.Settings.json
(or something like MyApp1.Settings.json
)
{
"Tenant": "YOUR_COMPANY_NAME",
"UserId": "",
"Password": "",
"ClientId": "",
"ClientSecret": "",
"ResourceUrl": "",
"ReplyUrl": "",
"ApiScopes": [
"api://YOUR-API-SCOPES"
],
"FormatArgs": {
"api": "https://YOUR-{env}-API.COM"
},
"Endpoints": [
"{api}/route1",
"{api}/route2"
],
"ResponseTimeSla": 5,
"ResponseSizeSla": 200,
"Iterations": 5,
"OutputFormats": [
"Excel",
"Sql::Conn-string..."
],
"AppInsightsAppId": "",
"AppInsightsApiKey": "",
"ReadResponseHeadersOnly": false,
"InputsFile": "Perfx_Inputs.xlsx",
"PluginClassName": null
}
details
KEY | TYPE | VALUE | DEFAULT |
---|---|---|---|
QuiteMode | bool |
Just run (based on the information specified in the Settings file) and exit! Used in case of DevOps/CI pipelines |
false |
Tenant | string |
Azure Tenant Name/ID Used in case of User-Creds / Client-Creds authentication |
|
UserId | string |
Azure AD User-ID Used in case of User-Creds silent-authentication only |
|
Password | string |
Azure AD Password Used in case of User-Creds silent-authentication only |
|
ClientId | string |
Azure AD Application/Client ID Used in case of User-Creds / Client-Creds authentication |
|
ClientSecret | string |
Azure AD Application/Client Secret Used in case of Client-Creds authentication only |
|
ResourceUrl | string |
Azure AD Application Resource Url Used in case of User-Creds / Client-Creds authentication |
ClientId |
ReplyUrl | string |
Azure AD Application Reply/Redirect Url Used in case of User-Creds interactive-authentication only |
|
ApiScopes | string[] |
Azure AD API Scopes Used in case of User-Creds / Client-Creds authentication |
api://ClientId /.default |
FormatArgs | string-object pairs |
Macros to be replaced in the Endpoints | |
Endpoints | string[] |
URLs to test To override the default ResponseTimeSla/ResponseSizeSla prefix the endpoint with expected-duration-sla-in-seconds|expected-size-sla-in-kilo-bytes:: e.g. 2.5::https://my-api/... (where 2.5 seconds is the expected SLA for the specific endpoint) e.g. 2.5|200::https://my-api/... (where 2.5 seconds is the expected duration SLA and 200 Kb is the expected size SLA for the specific endpoint) |
|
AppInsightsAppId | string |
Azure Application Insights App-ID to fetch the request-duration-logs from | |
AppInsightsApiKey | string |
Azure Application Insights API-Key to fetch the request-duration-logs from | |
Iterations | int |
# of iterations to run for each Endpoint | 5 |
OutputFormats | string[] |
The output formats in which the results would be saved Values: Excel / Csv / Json / Sql To provide the conn-strings or custom output-file-names, suffix it with ::conn-string e.g. Sql::Data Source=(localdb)... or Excel::My_Run_Results.xlsx
|
|
ResponseTimeSla | float |
The expected response-time SLA in seconds (generic for all Endpoints) | 5 |
ResponseSizeSla | float |
The expected response-size SLA in Kb (generic for all Endpoints) | 200 |
ReadResponseHeadersOnly | bool |
Specifies if the HttpClient should assume completion as soon as a response is available and headers are read (content is not read yet) | false |
InputsFile | string |
File (under 'Documents/Perfx' folder) containing additional information for each of the Endpoints | Perfx_Inputs.xlsx |
PluginClassName | string |
One of the classes that implements IPlugin interface (only used when there are multiple
implementations in the dlls placed under 'Documents/Perfx/Plugins' folder)
|
Order of Authentication:
- If
Tenant
is null or empty>
No Authentication- If
Password
is provided>
Silent User-credentials authentication flowAcquireTokenByUsernamePassword(scopes, username, password
)- If
ClientSecret
is provided>
Client-credentials authentication flowAcquireTokenAsync(resource, clientCredential)
- If
ReplyUrl
is provided>
Interactive User-credentials authentication flowAcquireTokenAsync(resource, clientId, redirectUri, parameters)
logging
You can (optionally) populate the following JSON and save it to your 'Documents/Perfx' folder with the
name: Perfx.Logging.json
{
"Logging": {
"LogLevel": {
"Default": "Warning"
},
"Console": {
"IncludeScopes": true,
"LogLevel": {
"Default": "Warning"
}
},
"Debug": {
"LogLevel": {
"Default": "Information"
}
}
}
}
"System.Net.Http.HttpClient": "Information"
inputs
Additional details (e.g. http-method
(defaults to GET
) / headers
/ body
/ query-params
) for the Endpoints
defined the Perfx.Settings.json
(default) under Documents/Perfx
can be provided in Perfx_Inputs.xlsx
under the same folder (or using the Plugin model outlined below)
By default, if an entry/row for an Endpoint exists in Perfx_Inputs.xlsx
, the corresponding row-index of that
entry is considered for the specific iteration. If the number of entries/rows for an Endpoint do not match up
with the number of iterations, the first entry is taken into consideration for the subsequent iterations
outputs
Results are saved to your Documents/Perfx
with the name of the input settings JSON file (default is Perfx.Settings.json
).
e.g. Perfx_Results.xlsx
/ Perfx_Results.csv & Perfx_Stats.csv
/ Perfx_Results.json & Perfx_Stats.json
/ [dbo.][Perfx_Results] & [dbo].[Perfx_Stats]
Sql tables (depending on the specified OutputFormat
in
the settings file
usage
perfx [MyApp1.Settings.json]
- If the specified path or the current-dir contains a valid Settings file, the tool uses/creates the
Perfx
folder under that path (for processing inputs/outputs); else, it falls back to thePerfx
folder under My Documents- If no argument is supplied,
Perfx.Settings.json
is used as input by default- Set QuiteMode to
true
in the Settings file to run once and exit (e.g. DevOps Pipelines)
- Enter
r:10
to run the benchmarks (10 times
) - Enter
s
to print the stats/details for the previous run - Enter
l:1h:10
to fetch app-insights request-duration logs for the previous run (in the last1 hour
with10 retries
) - Enter
c
to clear the console - Enter
q
to quit - Enter
?
to print this help
dotnet tool install -g perfx
Theexit-code / %errorlevel%
of the app will be equal to the count of results that match the following criteria:
90th-percentile of requests>
response-time-sla(OR)
max-response-size>
response-size-sla(OR)
status-codes other than 200>
1%
plugins
- Create a .NET Standard project and add reference to
Prefx.Core
project - Add a class that implements
IPlugin
interface (which in turn implementsIOutput
interface)
public Task IPlugin.GetAuthToken(Settings settings)
{
// NOTE: By default Perfx uses IPublicClientApplication's AcquireTokenSilent/AcquireTokenByUsernamePassword/AcquireTokenAsync (see 'Order of Authentication' note in the docs)
// If you want to override that behavior and provide a custom implementation, go ahead...
// If not, throw NotImplementedException or NotSupportedException, to trigger the default implementation
var userId = settings.UserId;
var pwd = settings.Password;
// Get more settings as required...
return Task.FromResult("someToken1");
}
public Task> IPlugin.GetEndpointDetails(Settings settings)
{
// NOTE: By default Perfx uses Documents/Perfx/Perfx_Inputs.xlsx
// If you want to override that behavior and provide a custom implementation, go ahead...
// If not, throw NotImplementedException or NotSupportedException, to trigger the default implementation
var endpointDetails = new List();
foreach (var endpoint in settings.Endpoints.Select((e, i) => (url: e, index: i)))
{
if (endpoint.url.Contains("odata"))
{
// Do whatever - based on the endpoint
endpointDetails.Add(new Endpoint { Method = HttpMethod.Get.ToString(), Query = "?$top=10" });
}
else if (endpoint.url.EndsWith("route1"))
{
// Do whatever - based on the endpoint
endpointDetails.Add(new Endpoint { Method = HttpMethod.Get.ToString(), Query = "/1" });
}
}
return Task.FromResult(endpointDetails);
}
public Task IOutput.Save(IEnumerable results, Settings settings)
{
// If you want to override that behavior and provide a custom implementation, go ahead...
// If not, throw NotImplementedException or NotSupportedException, to trigger the default implementation
// Do something and return true
return Task.FromResult(false);
}
public Task> IOutput.Read(Settings settings)
{
// If you want to override that behavior and provide a custom implementation, go ahead...
// If not, throw NotImplementedException or NotSupportedException, to trigger the default implementation
// Do and return something
return Task.FromResult(default(IList));
}
csproj
file as follows:
...
<TargetFramework>netcoreapp3.1</TargetFramework>
<Platforms>x64</Platforms>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
...
<ProjectReference Include="Perfx.Core.csproj">
<Private>false</Private>
<ExcludeAssets>runtime</ExcludeAssets>
</ProjectReference>
...
Documents/Perfx/Plugins
folder (Documents can be some other base-folder depending on your usage)IPlugin
implementations, you can also update the value of PluginClassName
with the specific implementation-class-full-name (e.g. MyPluginAssembly.MyPlugin1
)Sample: Perfx.SamplePlugin