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 flow
    AcquireTokenByUsernamePassword(scopes, username, password)
  • If ClientSecret is provided > Client-credentials authentication flow
    AcquireTokenAsync(resource, clientCredential)
  • If ReplyUrl is provided > Interactive User-credentials authentication flow
    AcquireTokenAsync(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 the Perfx 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 last 1 hour with 10 retries)
  • Enter c to clear the console
  • Enter q to quit
  • Enter ? to print this help
                        
                    dotnet tool install -g perfx
                    
The exit-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

Plugins are useful to override the default behavior (Authentication / Endpoint details / Read / Save)

  • Create a .NET Standard project and add reference to Prefx.Core project
  • Add a class that implements IPlugin interface (which in turn implements IOutput 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));
    }
                            
                        
  • Update the 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>
     ...
                                
                            
  • Build the project and copy the build-output to Documents/Perfx/Plugins folder (Documents can be some other base-folder depending on your usage)
  • Optionally, if you have multiple IPlugin implementations, you can also update the value of PluginClassName with the specific implementation-class-full-name (e.g. MyPluginAssembly.MyPlugin1)
Sample: Perfx.SamplePlugin