├── src ├── redis │ ├── Dockerfile │ ├── redis.csproj │ ├── create-resources.sh │ ├── Program.cs │ └── run.sh ├── sql │ ├── Dockerfile │ ├── sql.csproj │ ├── create-resources.sh │ ├── run.sh │ └── Program.cs ├── storage │ ├── Dockerfile │ ├── storage.csproj │ ├── create-resources.sh │ ├── Program.cs │ └── run.sh ├── cosmosdb │ ├── Dockerfile │ ├── cosmosdb.csproj │ ├── create-resources.sh │ ├── run.sh │ └── Program.cs ├── eventhub │ ├── Dockerfile │ ├── eventhub.csproj │ ├── create-resources.sh │ ├── Program.cs │ └── run.sh ├── servicebus │ ├── Dockerfile │ ├── servicebus.csproj │ ├── create-resources.sh │ ├── run.sh │ └── Program.cs └── common │ ├── common.csproj │ ├── utility │ ├── RetryHandler.cs │ ├── LatencyWorkload.cs │ └── ThroughputWorkload.cs │ ├── data │ ├── PerformanceData.cs │ └── RandomGenerator.cs │ ├── metrics │ └── Metric.cs │ └── config │ └── AppConfig.cs ├── .gitignore ├── scripts ├── azure-performance.sh ├── delete-resources.sh ├── create-resources.sh └── run.sh └── README.md /src/redis/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/dotnet:2.2-runtime 2 | 3 | WORKDIR /app 4 | COPY bin/publish/ /app 5 | 6 | ENTRYPOINT [ "dotnet", "redis.dll" ] 7 | -------------------------------------------------------------------------------- /src/sql/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/dotnet:2.2-runtime 2 | 3 | WORKDIR /app 4 | COPY bin/publish/ /app 5 | 6 | ENTRYPOINT [ "dotnet", "sql.dll" ] 7 | -------------------------------------------------------------------------------- /src/storage/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/dotnet:2.2-runtime 2 | 3 | WORKDIR /app 4 | COPY bin/publish/ /app 5 | 6 | ENTRYPOINT [ "dotnet", "storage.dll" ] 7 | -------------------------------------------------------------------------------- /src/cosmosdb/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/dotnet:2.2-runtime 2 | 3 | WORKDIR /app 4 | COPY bin/publish/ /app 5 | 6 | ENTRYPOINT [ "dotnet", "cosmosdb.dll" ] 7 | -------------------------------------------------------------------------------- /src/eventhub/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/dotnet:2.2-runtime 2 | 3 | WORKDIR /app 4 | COPY bin/publish/ /app 5 | 6 | ENTRYPOINT [ "dotnet", "eventhub.dll" ] 7 | -------------------------------------------------------------------------------- /src/servicebus/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/dotnet:2.2-runtime 2 | 3 | WORKDIR /app 4 | COPY bin/publish/ /app 5 | 6 | ENTRYPOINT [ "dotnet", "servicebus.dll" ] 7 | -------------------------------------------------------------------------------- /src/sql/sql.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.1 6 | latest 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/redis/redis.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.1 6 | latest 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/eventhub/eventhub.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.1 6 | latest 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/storage/storage.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.1 6 | latest 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/cosmosdb/cosmosdb.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.1 6 | latest 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/servicebus/servicebus.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.1 6 | latest 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/common/common.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | latest 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/common/utility/RetryHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Azure.Performance.Common 4 | { 5 | public sealed class RetryHandler 6 | { 7 | private static readonly TimeSpan InitialRetryTime = TimeSpan.FromSeconds(1); 8 | private static readonly TimeSpan MaxRetryTime = TimeSpan.FromSeconds(30); 9 | 10 | public TimeSpan RetryTime { get; private set; } = InitialRetryTime; 11 | 12 | public TimeSpan Retry() 13 | { 14 | var retryTime = RetryTime; 15 | 16 | RetryTime = RetryTime + RetryTime; 17 | if (RetryTime > MaxRetryTime) 18 | RetryTime = MaxRetryTime; 19 | 20 | return retryTime; 21 | } 22 | 23 | public TimeSpan Reset() 24 | { 25 | return RetryTime = InitialRetryTime; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/common/data/PerformanceData.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | 4 | namespace Azure.Performance.Common 5 | { 6 | public sealed class PerformanceData 7 | { 8 | [JsonProperty("id", Required = Required.Always)] 9 | public string Id { get; set; } 10 | 11 | [JsonProperty("timestamp", Required = Required.Always)] 12 | public DateTimeOffset Timestamp { get; set; } 13 | 14 | [JsonProperty("string_value")] 15 | public string StringValue { get; set; } 16 | 17 | [JsonProperty("int_value")] 18 | public int IntValue { get; set; } 19 | 20 | [JsonProperty("double_value")] 21 | public double DoubleValue { get; set; } 22 | 23 | [JsonProperty("time_value")] 24 | public TimeSpan TimeValue { get; set; } 25 | 26 | [JsonProperty("ttl")] 27 | public int TimeToLive { get; set; } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # Build results 11 | [Dd]ebug/ 12 | [Dd]ebugPublic/ 13 | [Rr]elease/ 14 | [Rr]eleases/ 15 | x64/ 16 | x86/ 17 | bld/ 18 | [Bb]in/ 19 | [Oo]bj/ 20 | [Ll]og/ 21 | 22 | # Visual Studio 2015 cache/options directory 23 | .vs/ 24 | 25 | # MSTest test Results 26 | [Tt]est[Rr]esult*/ 27 | [Bb]uild[Ll]og.* 28 | 29 | # NUNIT 30 | *.VisualState.xml 31 | TestResult.xml 32 | 33 | # Click-Once directory 34 | publish/ 35 | 36 | # NuGet Packages 37 | *.nupkg 38 | # The packages folder can be ignored because of Package Restore 39 | **/packages/* 40 | # except build/, which is used as an MSBuild target. 41 | !**/packages/build/ 42 | # Uncomment if necessary however generally it will be regenerated when needed 43 | #!**/packages/repositories.config 44 | # NuGet v3's project.json files produces more ignoreable files 45 | *.nuget.props 46 | *.nuget.targets 47 | 48 | # Visual Studio cache files 49 | # files ending in .cache can be ignored 50 | *.[Cc]ache 51 | # but keep track of directories ending in .cache 52 | !*.[Cc]ache/ 53 | -------------------------------------------------------------------------------- /src/common/data/RandomGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace Azure.Performance.Common 9 | { 10 | public static class RandomGenerator 11 | { 12 | private static readonly ThreadLocal _random = new ThreadLocal(() => new Random()); 13 | private static readonly TimeSpan _ttl = TimeSpan.FromDays(1); 14 | 15 | public static PerformanceData GetPerformanceData(string id = null) 16 | { 17 | return new PerformanceData 18 | { 19 | Id = id ?? Guid.NewGuid().ToString(), 20 | Timestamp = DateTimeOffset.UtcNow, 21 | TimeToLive = (int)_ttl.TotalSeconds, 22 | StringValue = GetRandomString(_random.Value, _random.Value.Next(16, 64)), 23 | IntValue = _random.Value.Next(), 24 | DoubleValue = _random.Value.Next(), 25 | TimeValue = TimeSpan.FromMilliseconds(_random.Value.Next(1000)), 26 | }; 27 | } 28 | 29 | private static string GetRandomString(Random random, int length) 30 | { 31 | var builder = new StringBuilder(length); 32 | for (int i = 0; i < length; i++) 33 | { 34 | builder.Append((char)('a' + random.Next(26))); 35 | } 36 | 37 | return builder.ToString(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /scripts/azure-performance.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PREFIX="azure-performance" 4 | LOCATION="westus2" 5 | DURATION=3000 6 | ACR="azureperformance" 7 | CPU=4 8 | MEMORY=1 9 | 10 | usage() { 11 | echo "Usage: $0 [-a azure-container-registry] [-p name-prefix] [-l location] [-s seconds] [-c cpu] [-m memory]" 12 | echo "" 13 | echo "Example:" 14 | echo " $0 -a $ACR -p $PREFIX -l $LOCATION -s $DURATION -c $CPU -m $MEMORY" 15 | echo "" 16 | exit 17 | } 18 | 19 | while getopts ":a:p:s:c:m:" options; do 20 | case "${options}" in 21 | a) 22 | ACR=${OPTARG} 23 | ;; 24 | p) 25 | PREFIX=${OPTARG} 26 | ;; 27 | l) 28 | LOCATION=${OPTARG} 29 | ;; 30 | s) 31 | DURATION=${OPTARG} 32 | ;; 33 | c) 34 | CPU=${OPTARG} 35 | ;; 36 | m) 37 | MEMORY=${OPTARG} 38 | ;; 39 | :) 40 | usage 41 | ;; 42 | *) 43 | usage 44 | ;; 45 | esac 46 | done 47 | 48 | # Create all Azure resources 49 | ./create-resources.sh -a $ACR -p $PREFIX -l $LOCATION 50 | 51 | # Run Azure performance workloads 52 | ./run.sh -a $ACR -p $PREFIX -s $DURATION -c $CPU -m $MEMORY 53 | 54 | # Delete all Azure resources 55 | ./delete-resources.sh -p $PREFIX 56 | -------------------------------------------------------------------------------- /src/common/metrics/Metric.cs: -------------------------------------------------------------------------------- 1 | using MathNet.Numerics.Statistics; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace Azure.Performance.Common 7 | { 8 | public sealed class Metric 9 | { 10 | private readonly List _values = new List(); 11 | 12 | public string Name { get; } 13 | public int Count => _values.Count; 14 | public double Average => _values.Average(); 15 | public double Min => _values.Min(); 16 | public double Max => _values.Max(); 17 | public double Median => _values.Median(); 18 | public double Sum => _values.Sum(); 19 | public double StdDev => _values.StandardDeviation(); 20 | public double P5 => _values.Percentile(5); 21 | public double P25 => _values.Percentile(25); 22 | public double P50 => _values.Percentile(50); 23 | public double P75 => _values.Percentile(75); 24 | public double P95 => _values.Percentile(95); 25 | public double P99 => _values.Percentile(99); 26 | public double P999 => _values.Quantile(0.999); 27 | public double P9999 => _values.Quantile(0.9999); 28 | 29 | public Metric(string name) 30 | { 31 | Name = name; 32 | } 33 | 34 | public Metric(string name, double value) 35 | { 36 | Name = name; 37 | _values.Add(value); 38 | } 39 | 40 | public Metric AddSample(double value) 41 | { 42 | _values.Insert(_values.Count, value); 43 | return this; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/redis/create-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | # Generic workload parameters 5 | RESOURCE_GROUP="azure-performance-redis" 6 | NAME=`cat /dev/urandom | tr -dc 'a-z' | fold -w 16 | head -n 1` 7 | LOCATION="westus2" 8 | 9 | # Redis specific parameters 10 | PARAMETERS='{ "sku": "Standard", "size": "c2" }' 11 | 12 | usage() { 13 | echo "Usage: $0 [-g resource-group] [-n name] [-l location] [-p parameters]" 14 | echo "" 15 | echo "Example:" 16 | echo " $0 -g $RESOURCE_GROUP -n $NAME -l $LOCATION -p '$PARAMETERS'" 17 | echo "" 18 | exit 19 | } 20 | 21 | while getopts ":g:n:l:p:" options; do 22 | case "${options}" in 23 | g) 24 | RESOURCE_GROUP=${OPTARG} 25 | ;; 26 | n) 27 | NAME=${OPTARG} 28 | ;; 29 | l) 30 | LOCATION=${OPTARG} 31 | ;; 32 | p) 33 | PARAMETERS=${OPTARG} 34 | ;; 35 | :) 36 | usage 37 | ;; 38 | *) 39 | usage 40 | ;; 41 | esac 42 | done 43 | 44 | SKU=`echo $PARAMETERS | jq '.sku' | tr -dt '"'` 45 | SIZE=`echo $PARAMETERS | jq '.size' | tr -dt '"'` 46 | 47 | # Create resource group 48 | group=`az group create --name $RESOURCE_GROUP --location $LOCATION` 49 | 50 | # Create Redis resources 51 | redis=`az redis create \ 52 | --resource-group $RESOURCE_GROUP \ 53 | --location $LOCATION \ 54 | --name $NAME \ 55 | --sku $SKU \ 56 | --vm-size $SIZE` 57 | -------------------------------------------------------------------------------- /src/storage/create-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | # Generic workload parameters 5 | RESOURCE_GROUP="azure-performance-storage" 6 | NAME=`cat /dev/urandom | tr -dc 'a-z' | fold -w 16 | head -n 1` 7 | LOCATION="westus2" 8 | 9 | # Storage specific parameters 10 | PARAMETERS='{ "sku": "Standard_LRS", "kind": "StorageV2" }' 11 | 12 | usage() { 13 | echo "Usage: $0 [-g resource-group] [-n name] [-l location] [-p parameters]" 14 | echo "" 15 | echo "Example:" 16 | echo " $0 -g $RESOURCE_GROUP -n $NAME -l $LOCATION -p '$PARAMETERS'" 17 | echo "" 18 | exit 19 | } 20 | 21 | while getopts ":g:n:l:p:" options; do 22 | case "${options}" in 23 | g) 24 | RESOURCE_GROUP=${OPTARG} 25 | ;; 26 | n) 27 | NAME=${OPTARG} 28 | ;; 29 | l) 30 | LOCATION=${OPTARG} 31 | ;; 32 | p) 33 | PARAMETERS=${OPTARG} 34 | ;; 35 | :) 36 | usage 37 | ;; 38 | *) 39 | usage 40 | ;; 41 | esac 42 | done 43 | 44 | SKU=`echo $PARAMETERS | jq '.sku' | tr -dt '"'` 45 | KIND=`echo $PARAMETERS | jq '.kind' | tr -dt '"'` 46 | 47 | # Create resource group 48 | group=`az group create --name $RESOURCE_GROUP --location $LOCATION` 49 | 50 | # Create Storage resources 51 | storage=`az storage account create \ 52 | --resource-group $RESOURCE_GROUP \ 53 | --location $LOCATION \ 54 | --name $NAME \ 55 | --sku $SKU \ 56 | --kind $KIND \ 57 | --https-only true` 58 | -------------------------------------------------------------------------------- /src/sql/create-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | # Generic workload parameters 5 | RESOURCE_GROUP="azure-performance-sql" 6 | NAME=`cat /dev/urandom | tr -dc 'a-z' | fold -w 16 | head -n 1` 7 | LOCATION="westus2" 8 | 9 | # SQL specific parameters 10 | PARAMETERS='{ "sku": "S2" }' 11 | 12 | usage() { 13 | echo "Usage: $0 [-g resource-group] [-n name] [-l location] [-p parameters]" 14 | echo "" 15 | echo "Example:" 16 | echo " $0 -g $RESOURCE_GROUP -n $NAME -l $LOCATION -p '$PARAMETERS'" 17 | echo "" 18 | exit 19 | } 20 | 21 | while getopts ":g:n:l:p:" options; do 22 | case "${options}" in 23 | g) 24 | RESOURCE_GROUP=${OPTARG} 25 | ;; 26 | n) 27 | NAME=${OPTARG} 28 | ;; 29 | l) 30 | LOCATION=${OPTARG} 31 | ;; 32 | p) 33 | PARAMETERS=${OPTARG} 34 | ;; 35 | :) 36 | usage 37 | ;; 38 | *) 39 | usage 40 | ;; 41 | esac 42 | done 43 | 44 | SKU=`echo $PARAMETERS | jq '.sku' | tr -dt '"'` 45 | 46 | # Create resource group 47 | group=`az group create --name $RESOURCE_GROUP --location $LOCATION` 48 | 49 | # Create SQL resources 50 | server=`az sql server create \ 51 | --resource-group $RESOURCE_GROUP \ 52 | --location $LOCATION \ 53 | --name $NAME \ 54 | --admin-user sqladmin \ 55 | --admin-password $NAME-P@ss` 56 | database=`az sql db create \ 57 | --resource-group $RESOURCE_GROUP \ 58 | --server $NAME \ 59 | --name $NAME \ 60 | --service-objective $SKU` 61 | firewall=`az sql server firewall-rule create \ 62 | --resource-group $RESOURCE_GROUP \ 63 | --server $NAME \ 64 | --name AllowAllWindowsAzureIps \ 65 | --start-ip-address 0.0.0.0 \ 66 | --end-ip-address 0.0.0.0` 67 | -------------------------------------------------------------------------------- /src/servicebus/create-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | # Generic workload parameters 5 | RESOURCE_GROUP="azure-performance-servicebus" 6 | NAME=`cat /dev/urandom | tr -dc 'a-z' | fold -w 16 | head -n 1` 7 | LOCATION="westus2" 8 | 9 | # ServiceBus specific parameters 10 | PARAMETERS='{ "sku": "Standard" }' 11 | 12 | usage() { 13 | echo "Usage: $0 [-g resource-group] [-n name] [-l location] [-p parameters]" 14 | echo "" 15 | echo "Example:" 16 | echo " $0 -g $RESOURCE_GROUP -n $NAME -l $LOCATION -p '$PARAMETERS'" 17 | echo "" 18 | exit 19 | } 20 | 21 | while getopts ":g:n:l:p:" options; do 22 | case "${options}" in 23 | g) 24 | RESOURCE_GROUP=${OPTARG} 25 | ;; 26 | n) 27 | NAME=${OPTARG} 28 | ;; 29 | l) 30 | LOCATION=${OPTARG} 31 | ;; 32 | p) 33 | PARAMETERS=${OPTARG} 34 | ;; 35 | :) 36 | usage 37 | ;; 38 | *) 39 | usage 40 | ;; 41 | esac 42 | done 43 | 44 | SKU=`echo $PARAMETERS | jq '.sku' | tr -dt '"'` 45 | 46 | # Create resource group 47 | group=`az group create --name $RESOURCE_GROUP --location $LOCATION` 48 | 49 | # Create ServiceBus resources 50 | namespace=`az servicebus namespace create \ 51 | --resource-group $RESOURCE_GROUP \ 52 | --name $NAME \ 53 | --sku $SKU` 54 | servicebus=`az servicebus queue create \ 55 | --resource-group $RESOURCE_GROUP \ 56 | --namespace-name $NAME \ 57 | --name $NAME \ 58 | --enable-partitioning true \ 59 | --default-message-time-to-live P1D` 60 | auth_rule=`az servicebus queue authorization-rule create \ 61 | --resource-group $RESOURCE_GROUP \ 62 | --namespace-name $NAME \ 63 | --queue-name $NAME \ 64 | --name SendKey \ 65 | --rights Send` 66 | -------------------------------------------------------------------------------- /src/cosmosdb/create-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | # Generic workload parameters 5 | RESOURCE_GROUP="azure-performance-cosmosdb" 6 | NAME=`cat /dev/urandom | tr -dc 'a-z' | fold -w 16 | head -n 1` 7 | LOCATION="westus2" 8 | 9 | # CosmosDB specific parameters 10 | PARAMETERS='{ "kind": "GlobalDocumentDB", "throughput": 400 }' 11 | 12 | usage() { 13 | echo "Usage: $0 [-g resource-group] [-n name] [-l location] [-p parameters]" 14 | echo "" 15 | echo "Example:" 16 | echo " $0 -g $RESOURCE_GROUP -n $NAME -l $LOCATION -p '$PARAMETERS'" 17 | echo "" 18 | exit 19 | } 20 | 21 | while getopts ":g:n:l:p:" options; do 22 | case "${options}" in 23 | g) 24 | RESOURCE_GROUP=${OPTARG} 25 | ;; 26 | n) 27 | NAME=${OPTARG} 28 | ;; 29 | l) 30 | LOCATION=${OPTARG} 31 | ;; 32 | p) 33 | PARAMETERS=${OPTARG} 34 | ;; 35 | :) 36 | usage 37 | ;; 38 | *) 39 | usage 40 | ;; 41 | esac 42 | done 43 | 44 | KIND=`echo $PARAMETERS | jq '.kind' | tr -dt '"'` 45 | THROUGHPUT=`echo $PARAMETERS | jq '.throughput' | tr -dt '"'` 46 | 47 | # Create resource group 48 | group=`az group create --name $RESOURCE_GROUP --location $LOCATION` 49 | 50 | # Create CosmosDB resources 51 | cosmosdb=`az cosmosdb create \ 52 | --resource-group $RESOURCE_GROUP \ 53 | --name $NAME \ 54 | --kind $KIND` 55 | database=`az cosmosdb database create \ 56 | --resource-group-name $RESOURCE_GROUP \ 57 | --name $NAME \ 58 | --db-name database` 59 | collection=`az cosmosdb collection create \ 60 | --resource-group-name $RESOURCE_GROUP \ 61 | --name $NAME \ 62 | --db-name database \ 63 | --collection-name collection \ 64 | --throughput $THROUGHPUT \ 65 | --default-ttl -1 \ 66 | --partition-key-path='/id'` 67 | -------------------------------------------------------------------------------- /src/eventhub/create-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | # Generic workload parameters 5 | RESOURCE_GROUP="azure-performance-eventhub" 6 | NAME=`cat /dev/urandom | tr -dc 'a-z' | fold -w 16 | head -n 1` 7 | LOCATION="westus2" 8 | 9 | # EventHub specific parameters 10 | PARAMETERS='{ "sku": "Standard", "capacity": 1, "partitions": 10 }' 11 | 12 | usage() { 13 | echo "Usage: $0 [-g resource-group] [-n name] [-l location] [-p parameters]" 14 | echo "" 15 | echo "Example:" 16 | echo " $0 -g $RESOURCE_GROUP -n $NAME -l $LOCATION -p '$PARAMETERS'" 17 | echo "" 18 | exit 19 | } 20 | 21 | while getopts ":g:n:l:p:" options; do 22 | case "${options}" in 23 | g) 24 | RESOURCE_GROUP=${OPTARG} 25 | ;; 26 | n) 27 | NAME=${OPTARG} 28 | ;; 29 | l) 30 | LOCATION=${OPTARG} 31 | ;; 32 | p) 33 | PARAMETERS=${OPTARG} 34 | ;; 35 | :) 36 | usage 37 | ;; 38 | *) 39 | usage 40 | ;; 41 | esac 42 | done 43 | 44 | SKU=`echo $PARAMETERS | jq '.sku' | tr -dt '"'` 45 | CAPACITY=`echo $PARAMETERS | jq '.capacity' | tr -dt '"'` 46 | PARTITIONS=`echo $PARAMETERS | jq '.partitions' | tr -dt '"'` 47 | 48 | # Create resource group 49 | group=`az group create --name $RESOURCE_GROUP --location $LOCATION` 50 | 51 | # Create EventHub resources 52 | namespace=`az eventhubs namespace create \ 53 | --resource-group $RESOURCE_GROUP \ 54 | --name $NAME \ 55 | --sku $SKU \ 56 | --capacity $CAPACITY` 57 | eventhub=`az eventhubs eventhub create \ 58 | --resource-group $RESOURCE_GROUP \ 59 | --namespace-name $NAME \ 60 | --name $NAME \ 61 | --partition-count $PARTITIONS` 62 | auth_rule=`az eventhubs eventhub authorization-rule create \ 63 | --resource-group $RESOURCE_GROUP \ 64 | --namespace-name $NAME \ 65 | --eventhub-name $NAME \ 66 | --name SendKey \ 67 | --rights Send` 68 | -------------------------------------------------------------------------------- /scripts/delete-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | PREFIX="azure-performance" 5 | 6 | usage() { 7 | echo "Usage: $0 [-p name-prefix]" 8 | echo "" 9 | echo "Example:" 10 | echo " $0 -p $PREFIX" 11 | echo "" 12 | exit 13 | } 14 | 15 | while getopts ":p:" options; do 16 | case "${options}" in 17 | p) 18 | PREFIX=${OPTARG} 19 | ;; 20 | :) 21 | usage 22 | ;; 23 | *) 24 | usage 25 | ;; 26 | esac 27 | done 28 | 29 | # 30 | # Delete resources 31 | # 32 | echo "Deleting CosmosDB latency resource group ..." 33 | az group delete --name $PREFIX-cosmosdb-latency --no-wait --yes 34 | echo "Deleting CosmosDB throughput resource group ..." 35 | az group delete --name $PREFIX-cosmosdb-throughput --no-wait --yes 36 | 37 | echo "Deleting EventHub latency resource group ..." 38 | az group delete --name $PREFIX-eventhub-latency --no-wait --yes 39 | echo "Deleting EventHub throughput resource group ..." 40 | az group delete --name $PREFIX-eventhub-throughput --no-wait --yes 41 | 42 | echo "Deleting Redis latency resource group ..." 43 | az group delete --name $PREFIX-redis-latency --no-wait --yes 44 | echo "Deleting Redis throughput resource group ..." 45 | az group delete --name $PREFIX-redis-throughput --no-wait --yes 46 | 47 | echo "Deleting ServiceBus latency resource group ..." 48 | az group delete --name $PREFIX-servicebus-latency --no-wait --yes 49 | echo "Deleting ServiceBus throughput resource group ..." 50 | az group delete --name $PREFIX-servicebus-throughput --no-wait --yes 51 | 52 | echo "Deleting SQL latency resource group ..." 53 | az group delete --name $PREFIX-sql-latency --no-wait --yes 54 | echo "Deleting SQL throughput resource group ..." 55 | az group delete --name $PREFIX-sql-throughput --no-wait --yes 56 | 57 | echo "Deleting Storage latency resource group ..." 58 | az group delete --name $PREFIX-storage-latency --no-wait --yes 59 | echo "Deleting Storage throughput resource group ..." 60 | az group delete --name $PREFIX-storage-throughput --no-wait --yes 61 | 62 | echo "Deleting shared resource group ..." 63 | az group delete --name $PREFIX --no-wait --yes 64 | -------------------------------------------------------------------------------- /src/storage/Program.cs: -------------------------------------------------------------------------------- 1 | using Azure.Performance.Common; 2 | using Microsoft.Extensions.Logging; 3 | using Newtonsoft.Json; 4 | using Microsoft.WindowsAzure.Storage; 5 | using Microsoft.WindowsAzure.Storage.Blob; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Net; 9 | using System.Text; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | 13 | namespace Azure.Performance.Storage 14 | { 15 | class Program 16 | { 17 | static async Task Main(string[] args) 18 | { 19 | int threads = AppConfig.GetOptionalSetting("Threads") ?? 32; 20 | ILogger logger = AppConfig.CreateLogger("Storage"); 21 | string workloadType = AppConfig.GetSetting("Workload"); 22 | CancellationToken cancellationToken = AppConfig.GetCancellationToken(); 23 | 24 | string connectionString = AppConfig.GetSetting("StorageConnectionString"); 25 | 26 | var storage = CloudStorageAccount.Parse(connectionString); 27 | var client = storage.CreateCloudBlobClient(); 28 | 29 | var container = client.GetContainerReference("performance"); 30 | await container.CreateIfNotExistsAsync(cancellationToken).ConfigureAwait(false); 31 | 32 | if (workloadType == "latency") 33 | { 34 | var workload = new LatencyWorkload(logger, "Storage"); 35 | await workload.InvokeAsync(threads, (value) => WriteAsync(container, value, cancellationToken), cancellationToken).ConfigureAwait(false); 36 | } 37 | if (workloadType == "throughput") 38 | { 39 | var workload = new ThroughputWorkload(logger, "Storage"); 40 | await workload.InvokeAsync(threads, () => WriteAsync(container, cancellationToken), cancellationToken).ConfigureAwait(false); 41 | } 42 | } 43 | 44 | private static Task WriteAsync(CloudBlobContainer container, PerformanceData value, CancellationToken cancellationToken) 45 | { 46 | var blob = container.GetBlockBlobReference(value.Id); 47 | return blob.UploadTextAsync(JsonConvert.SerializeObject(value), cancellationToken); 48 | } 49 | 50 | private static async Task WriteAsync(CloudBlobContainer container, CancellationToken cancellationToken) 51 | { 52 | var value = RandomGenerator.GetPerformanceData(); 53 | var blob = container.GetBlockBlobReference(value.Id); 54 | await blob.UploadTextAsync(JsonConvert.SerializeObject(value), cancellationToken).ConfigureAwait(false); 55 | return 1; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/common/config/AppConfig.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using Microsoft.Extensions.Logging.Console; 3 | using System; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace Azure.Performance.Common 8 | { 9 | public static class AppConfig 10 | { 11 | public static string GetSetting(string key) 12 | { 13 | string value = Environment.GetEnvironmentVariable(key); 14 | if (string.IsNullOrEmpty(value)) 15 | throw new ArgumentNullException(key); 16 | return value; 17 | } 18 | 19 | public static T? GetOptionalSetting(string key) where T : struct 20 | { 21 | string value = Environment.GetEnvironmentVariable(key); 22 | if (string.IsNullOrEmpty(value)) 23 | return (T?)null; 24 | 25 | if (typeof(T) == typeof(int)) 26 | return ((int?)int.Parse(value)) as T?; 27 | if (typeof(T) == typeof(uint)) 28 | return ((uint?)uint.Parse(value)) as T?; 29 | if (typeof(T) == typeof(double)) 30 | return ((double?)double.Parse(value)) as T?; 31 | if (typeof(T) == typeof(float)) 32 | return ((float?)float.Parse(value)) as T?; 33 | if (typeof(T) == typeof(bool)) 34 | return ((bool?)bool.Parse(value)) as T?; 35 | 36 | throw new NotSupportedException($"App setting of type {typeof(T)} is not supported."); 37 | } 38 | 39 | public static ILogger CreateLogger(string name) 40 | { 41 | return new ConsoleLogger(); 42 | //return new LoggerFactory() 43 | // .AddConsole() 44 | // .CreateLogger(name); 45 | } 46 | 47 | public static CancellationToken GetCancellationToken() 48 | { 49 | int seconds = AppConfig.GetOptionalSetting("Seconds") ?? 60; 50 | 51 | CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); 52 | CancellationToken cancellationToken = cancellationTokenSource.Token; 53 | 54 | Console.CancelKeyPress += delegate 55 | { 56 | cancellationTokenSource.Cancel(); 57 | }; 58 | 59 | Task.Delay(TimeSpan.FromSeconds(seconds), cancellationToken).ContinueWith(_ => 60 | { 61 | cancellationTokenSource.Cancel(); 62 | }, cancellationToken); 63 | 64 | return cancellationToken; 65 | } 66 | } 67 | 68 | public class ConsoleLogger : ILogger 69 | { 70 | IDisposable ILogger.BeginScope(TState state) 71 | { 72 | throw new NotImplementedException(); 73 | } 74 | 75 | bool ILogger.IsEnabled(LogLevel logLevel) 76 | { 77 | return true; 78 | } 79 | 80 | void ILogger.Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) 81 | { 82 | Console.WriteLine(formatter.Invoke(state, exception)); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/redis/Program.cs: -------------------------------------------------------------------------------- 1 | using Azure.Performance.Common; 2 | using Microsoft.Extensions.Logging; 3 | using Newtonsoft.Json; 4 | using StackExchange.Redis; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Net; 8 | using System.Text; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | 12 | namespace Azure.Performance.Redis 13 | { 14 | class Program 15 | { 16 | private static long _id = 0; 17 | 18 | static async Task Main(string[] args) 19 | { 20 | int threads = AppConfig.GetOptionalSetting("Threads") ?? 32; 21 | ILogger logger = AppConfig.CreateLogger("Redis"); 22 | string workloadType = AppConfig.GetSetting("Workload"); 23 | CancellationToken cancellationToken = AppConfig.GetCancellationToken(); 24 | 25 | string connectionString = AppConfig.GetSetting("RedisConnectionString"); 26 | 27 | var connection = await ConnectionMultiplexer.ConnectAsync(connectionString).ConfigureAwait(false); 28 | var client = connection.GetDatabase(); 29 | 30 | if (workloadType == "latency") 31 | { 32 | var workload = new LatencyWorkload(logger, "Redis"); 33 | await workload.InvokeAsync(threads, (value) => WriteAsync(client, value, cancellationToken), cancellationToken).ConfigureAwait(false); 34 | } 35 | if (workloadType == "throughput") 36 | { 37 | var workload = new ThroughputWorkload(logger, "Redis"); 38 | await workload.InvokeAsync(threads, () => WriteAsync(client, cancellationToken), cancellationToken).ConfigureAwait(false); 39 | } 40 | } 41 | 42 | private static Task WriteAsync(IDatabase client, PerformanceData value, CancellationToken cancellationToken) 43 | { 44 | return client.StringSetAsync(value.Id, JsonConvert.SerializeObject(value)); 45 | } 46 | 47 | private static async Task WriteAsync(IDatabase client, CancellationToken cancellationToken) 48 | { 49 | const int batchSize = 16; 50 | 51 | var values = new KeyValuePair[batchSize]; 52 | for (int i = 0; i < batchSize; i++) 53 | { 54 | long key = Interlocked.Increment(ref _id) % 1000000; 55 | var value = RandomGenerator.GetPerformanceData(); 56 | var serialized = JsonConvert.SerializeObject(value); 57 | 58 | values[i] = new KeyValuePair(key.ToString(), serialized); 59 | } 60 | 61 | await client.StringSetAsync(values).ConfigureAwait(false); 62 | 63 | return batchSize; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/sql/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | # Generic workload parameters 5 | ACR="azureperformance" 6 | RESOURCE_GROUP="azure-performance-sql" 7 | NAME="" 8 | WORKLOAD="latency" # latency or throughput 9 | THREADS=10 10 | DURATION=60 11 | CPU=4 12 | MEMORY=1 13 | 14 | usage() { 15 | echo "Usage: $0 [-a azure-container-registry] [-g resource-group] [-w workload] [-t threads] [-s seconds] [-c cpu] [-m memory]" 16 | echo "" 17 | echo "Example:" 18 | echo " $0 -a $ACR -g $RESOURCE_GROUP -w $WORKLOAD -t $THREADS -s $DURATION -c $CPU -m $MEMORY" 19 | echo "" 20 | exit 21 | } 22 | 23 | while getopts ":a:g:w:t:s:c:m:" options; do 24 | case "${options}" in 25 | a) 26 | ACR=${OPTARG} 27 | ;; 28 | g) 29 | RESOURCE_GROUP=${OPTARG} 30 | ;; 31 | w) 32 | WORKLOAD=${OPTARG} 33 | ;; 34 | t) 35 | THREADS=${OPTARG} 36 | ;; 37 | s) 38 | DURATION=${OPTARG} 39 | ;; 40 | c) 41 | CPU=${OPTARG} 42 | ;; 43 | m) 44 | MEMORY=${OPTARG} 45 | ;; 46 | :) 47 | usage 48 | ;; 49 | *) 50 | usage 51 | ;; 52 | esac 53 | done 54 | 55 | # Get SQL details 56 | sql=`az sql server list --resource-group $RESOURCE_GROUP | jq ".[0]"` 57 | NAME=`echo $sql | jq .name | tr -dt '"'` 58 | sql_connectionstring="Server=tcp:$NAME.database.windows.net,1433;Database=$NAME;User ID=sqladmin;Password=$NAME-P@ss;Encrypt=true;Connection Timeout=30;" 59 | 60 | # Get Azure container registry details 61 | acr_credentials=`az acr credential show --name $ACR` 62 | acr_username=`echo $acr_credentials | jq '.username' | tr -dt '"'` 63 | acr_password=`echo $acr_credentials | jq '.passwords[0].value' | tr -dt '"'` 64 | 65 | # Build project 66 | SRC=$(realpath $(dirname $0)) 67 | IMAGE="$ACR.azurecr.io/$RESOURCE_GROUP:latest" 68 | dotnet publish $SRC/sql.csproj -c Release -o $SRC/bin/publish > /dev/null 2>&1 69 | docker build --rm -t $IMAGE $SRC > /dev/null 2>&1 70 | docker login $ACR.azurecr.io --username $acr_username --password $acr_password > /dev/null 2>&1 71 | docker push $IMAGE > /dev/null 2>&1 72 | docker logout $ACR.azurecr.io > /dev/null 2>&1 73 | docker rmi $IMAGE > /dev/null 2>&1 74 | 75 | # Create container instance 76 | az container delete --resource-group $RESOURCE_GROUP --name $NAME --yes > /dev/null 2>&1 77 | container=`az container create \ 78 | --resource-group $RESOURCE_GROUP \ 79 | --name $NAME \ 80 | --image $IMAGE \ 81 | --registry-username $acr_username \ 82 | --registry-password $acr_password \ 83 | --environment-variables \ 84 | Workload=$WORKLOAD \ 85 | Threads=$THREADS \ 86 | Seconds=$DURATION \ 87 | --secure-environment-variables \ 88 | SqlConnectionString="$sql_connectionstring" \ 89 | --cpu $CPU \ 90 | --memory $MEMORY \ 91 | --restart-policy Never` 92 | 93 | echo $container | jq .id | tr -dt '"' 94 | -------------------------------------------------------------------------------- /src/storage/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | # Generic workload parameters 5 | ACR="azureperformance" 6 | RESOURCE_GROUP="azure-performance-storage" 7 | NAME="" 8 | WORKLOAD="latency" # latency or throughput 9 | THREADS=10 10 | DURATION=60 11 | CPU=4 12 | MEMORY=1 13 | 14 | usage() { 15 | echo "Usage: $0 [-a azure-container-registry] [-g resource-group] [-w workload] [-t threads] [-s seconds] [-c cpu] [-m memory]" 16 | echo "" 17 | echo "Example:" 18 | echo " $0 -a $ACR -g $RESOURCE_GROUP -w $WORKLOAD -t $THREADS -s $DURATION -c $CPU -m $MEMORY" 19 | echo "" 20 | exit 21 | } 22 | 23 | while getopts ":a:g:w:t:s:c:m:" options; do 24 | case "${options}" in 25 | a) 26 | ACR=${OPTARG} 27 | ;; 28 | g) 29 | RESOURCE_GROUP=${OPTARG} 30 | ;; 31 | w) 32 | WORKLOAD=${OPTARG} 33 | ;; 34 | t) 35 | THREADS=${OPTARG} 36 | ;; 37 | s) 38 | DURATION=${OPTARG} 39 | ;; 40 | c) 41 | CPU=${OPTARG} 42 | ;; 43 | m) 44 | MEMORY=${OPTARG} 45 | ;; 46 | :) 47 | usage 48 | ;; 49 | *) 50 | usage 51 | ;; 52 | esac 53 | done 54 | 55 | # Get Storage details 56 | storage=`az storage account list --resource-group $RESOURCE_GROUP | jq ".[0]"` 57 | NAME=`echo $storage | jq .name | tr -dt '"'` 58 | storage_connectionstring=`az storage account show-connection-string --resource-group $RESOURCE_GROUP --name $NAME | jq .connectionString | tr -dt '"'` 59 | 60 | # Get Azure container registry details 61 | acr_credentials=`az acr credential show --name $ACR` 62 | acr_username=`echo $acr_credentials | jq '.username' | tr -dt '"'` 63 | acr_password=`echo $acr_credentials | jq '.passwords[0].value' | tr -dt '"'` 64 | 65 | # Build project 66 | SRC=$(realpath $(dirname $0)) 67 | IMAGE="$ACR.azurecr.io/$RESOURCE_GROUP:latest" 68 | dotnet publish $SRC/storage.csproj -c Release -o $SRC/bin/publish > /dev/null 2>&1 69 | docker build --rm -t $IMAGE $SRC > /dev/null 2>&1 70 | docker login $ACR.azurecr.io --username $acr_username --password $acr_password > /dev/null 2>&1 71 | docker push $IMAGE > /dev/null 2>&1 72 | docker logout $ACR.azurecr.io > /dev/null 2>&1 73 | docker rmi $IMAGE > /dev/null 2>&1 74 | 75 | # Create container instance 76 | az container delete --resource-group $RESOURCE_GROUP --name $NAME --yes > /dev/null 2>&1 77 | container=`az container create \ 78 | --resource-group $RESOURCE_GROUP \ 79 | --name $NAME \ 80 | --image $IMAGE \ 81 | --registry-username $acr_username \ 82 | --registry-password $acr_password \ 83 | --environment-variables \ 84 | Workload=$WORKLOAD \ 85 | Threads=$THREADS \ 86 | Seconds=$DURATION \ 87 | --secure-environment-variables \ 88 | StorageConnectionString=$storage_connectionstring \ 89 | --cpu $CPU \ 90 | --memory $MEMORY \ 91 | --restart-policy Never` 92 | 93 | echo $container | jq .id | tr -dt '"' 94 | -------------------------------------------------------------------------------- /src/eventhub/Program.cs: -------------------------------------------------------------------------------- 1 | using Azure.Performance.Common; 2 | using Microsoft.Azure.EventHubs; 3 | using Microsoft.Extensions.Logging; 4 | using Newtonsoft.Json; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Net; 8 | using System.Text; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | 12 | namespace Azure.Performance.EventHub 13 | { 14 | class Program 15 | { 16 | static async Task Main(string[] args) 17 | { 18 | int threads = AppConfig.GetOptionalSetting("Threads") ?? 32; 19 | ILogger logger = AppConfig.CreateLogger("EventHub"); 20 | string workloadType = AppConfig.GetSetting("Workload"); 21 | CancellationToken cancellationToken = AppConfig.GetCancellationToken(); 22 | 23 | string connectionString = AppConfig.GetSetting("EventHubConnectionString"); 24 | 25 | var client = EventHubClient.CreateFromConnectionString(connectionString); 26 | 27 | if (workloadType == "latency") 28 | { 29 | var workload = new LatencyWorkload(logger, "EventHub"); 30 | await workload.InvokeAsync(threads, (value) => WriteAsync(client, value, cancellationToken), cancellationToken).ConfigureAwait(false); 31 | } 32 | if (workloadType == "throughput") 33 | { 34 | var workload = new ThroughputWorkload(logger, "EventHub", IsThrottlingException); 35 | await workload.InvokeAsync(threads, () => WriteAsync(client, cancellationToken), cancellationToken).ConfigureAwait(false); 36 | } 37 | } 38 | 39 | private static Task WriteAsync(EventHubClient client, PerformanceData value, CancellationToken cancellationToken) 40 | { 41 | var content = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(value)); 42 | var data = new EventData(content); 43 | 44 | return client.SendAsync(data); 45 | } 46 | 47 | private static async Task WriteAsync(EventHubClient client, CancellationToken cancellationToken) 48 | { 49 | const int batchSize = 16; 50 | 51 | var values = new EventData[batchSize]; 52 | for (int i = 0; i < batchSize; i++) 53 | { 54 | var value = RandomGenerator.GetPerformanceData(); 55 | var serialized = JsonConvert.SerializeObject(value); 56 | var content = Encoding.UTF8.GetBytes(serialized); 57 | 58 | values[i] = new EventData(content); 59 | } 60 | 61 | await client.SendAsync(values).ConfigureAwait(false); 62 | 63 | return batchSize; 64 | } 65 | 66 | private static TimeSpan? IsThrottlingException(Exception e) 67 | { 68 | var sbe = e as ServerBusyException; 69 | if (sbe == null) 70 | return null; 71 | 72 | if (!sbe.IsTransient) 73 | return null; 74 | 75 | return TimeSpan.FromSeconds(4); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/eventhub/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | # Generic workload parameters 5 | ACR="azureperformance" 6 | RESOURCE_GROUP="azure-performance-eventhub" 7 | NAME="" 8 | WORKLOAD="latency" # latency or throughput 9 | THREADS=10 10 | DURATION=60 11 | CPU=4 12 | MEMORY=1 13 | 14 | usage() { 15 | echo "Usage: $0 [-a azure-container-registry] [-g resource-group] [-w workload] [-t threads] [-s seconds] [-c cpu] [-m memory]" 16 | echo "" 17 | echo "Example:" 18 | echo " $0 -a $ACR -g $RESOURCE_GROUP -w $WORKLOAD -t $THREADS -s $DURATION -c $CPU -m $MEMORY" 19 | echo "" 20 | exit 21 | } 22 | 23 | while getopts ":a:g:w:t:s:c:m:" options; do 24 | case "${options}" in 25 | a) 26 | ACR=${OPTARG} 27 | ;; 28 | g) 29 | RESOURCE_GROUP=${OPTARG} 30 | ;; 31 | w) 32 | WORKLOAD=${OPTARG} 33 | ;; 34 | t) 35 | THREADS=${OPTARG} 36 | ;; 37 | s) 38 | DURATION=${OPTARG} 39 | ;; 40 | c) 41 | CPU=${OPTARG} 42 | ;; 43 | m) 44 | MEMORY=${OPTARG} 45 | ;; 46 | :) 47 | usage 48 | ;; 49 | *) 50 | usage 51 | ;; 52 | esac 53 | done 54 | 55 | # Get EventHub details 56 | NAME=`az eventhubs namespace list --resource-group $RESOURCE_GROUP | jq ".[0].name" | tr -dt '"'` 57 | eventhub_key=`az eventhubs eventhub authorization-rule keys list \ 58 | --resource-group $RESOURCE_GROUP \ 59 | --namespace-name $NAME \ 60 | --eventhub-name $NAME \ 61 | --name SendKey` 62 | 63 | eventhub_connectionstring=`echo $eventhub_key | jq '.primaryConnectionString' | tr -dt '"'` 64 | 65 | # Get Azure container registry details 66 | acr_credentials=`az acr credential show --name $ACR` 67 | acr_username=`echo $acr_credentials | jq '.username' | tr -dt '"'` 68 | acr_password=`echo $acr_credentials | jq '.passwords[0].value' | tr -dt '"'` 69 | 70 | # Build project 71 | SRC=$(realpath $(dirname $0)) 72 | IMAGE="$ACR.azurecr.io/$RESOURCE_GROUP:latest" 73 | dotnet publish $SRC/eventhub.csproj -c Release -o $SRC/bin/publish > /dev/null 2>&1 74 | docker build --rm -t $IMAGE $SRC > /dev/null 2>&1 75 | docker login $ACR.azurecr.io --username $acr_username --password $acr_password > /dev/null 2>&1 76 | docker push $IMAGE > /dev/null 2>&1 77 | docker logout $ACR.azurecr.io > /dev/null 2>&1 78 | docker rmi $IMAGE > /dev/null 2>&1 79 | 80 | # Create container instance 81 | az container delete --resource-group $RESOURCE_GROUP --name $NAME --yes > /dev/null 2>&1 82 | container=`az container create \ 83 | --resource-group $RESOURCE_GROUP \ 84 | --name $NAME \ 85 | --image $IMAGE \ 86 | --registry-username $acr_username \ 87 | --registry-password $acr_password \ 88 | --environment-variables \ 89 | Workload=$WORKLOAD \ 90 | Threads=$THREADS \ 91 | Seconds=$DURATION \ 92 | --secure-environment-variables \ 93 | EventHubConnectionString=$eventhub_connectionstring \ 94 | --cpu $CPU \ 95 | --memory $MEMORY \ 96 | --restart-policy Never` 97 | 98 | echo $container | jq .id | tr -dt '"' 99 | -------------------------------------------------------------------------------- /src/servicebus/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | # Generic workload parameters 5 | ACR="azureperformance" 6 | RESOURCE_GROUP="azure-performance-servicebus" 7 | NAME="" 8 | WORKLOAD="latency" # latency or throughput 9 | THREADS=10 10 | DURATION=60 11 | CPU=4 12 | MEMORY=1 13 | 14 | usage() { 15 | echo "Usage: $0 [-a azure-container-registry] [-g resource-group] [-w workload] [-t threads] [-s seconds] [-c cpu] [-m memory]" 16 | echo "" 17 | echo "Example:" 18 | echo " $0 -a $ACR -g $RESOURCE_GROUP -w $WORKLOAD -t $THREADS -s $DURATION -c $CPU -m $MEMORY" 19 | echo "" 20 | exit 21 | } 22 | 23 | while getopts ":a:g:w:t:s:c:m:" options; do 24 | case "${options}" in 25 | a) 26 | ACR=${OPTARG} 27 | ;; 28 | g) 29 | RESOURCE_GROUP=${OPTARG} 30 | ;; 31 | w) 32 | WORKLOAD=${OPTARG} 33 | ;; 34 | t) 35 | THREADS=${OPTARG} 36 | ;; 37 | s) 38 | DURATION=${OPTARG} 39 | ;; 40 | c) 41 | CPU=${OPTARG} 42 | ;; 43 | m) 44 | MEMORY=${OPTARG} 45 | ;; 46 | :) 47 | usage 48 | ;; 49 | *) 50 | usage 51 | ;; 52 | esac 53 | done 54 | 55 | # Get ServiceBus details 56 | NAME=`az servicebus namespace list --resource-group $RESOURCE_GROUP | jq ".[0].name" | tr -dt '"'` 57 | servicebus_key=`az servicebus queue authorization-rule keys list \ 58 | --resource-group $RESOURCE_GROUP \ 59 | --namespace-name $NAME \ 60 | --queue-name $NAME \ 61 | --name SendKey` 62 | 63 | servicebus_connectionstring=`echo $servicebus_key | jq '.primaryConnectionString' | tr -dt '"'` 64 | 65 | # Get Azure container registry details 66 | acr_credentials=`az acr credential show --name $ACR` 67 | acr_username=`echo $acr_credentials | jq '.username' | tr -dt '"'` 68 | acr_password=`echo $acr_credentials | jq '.passwords[0].value' | tr -dt '"'` 69 | 70 | # Build project 71 | SRC=$(realpath $(dirname $0)) 72 | IMAGE="$ACR.azurecr.io/$RESOURCE_GROUP:latest" 73 | dotnet publish $SRC/servicebus.csproj -c Release -o $SRC/bin/publish > /dev/null 2>&1 74 | docker build --rm -t $IMAGE $SRC > /dev/null 2>&1 75 | docker login $ACR.azurecr.io --username $acr_username --password $acr_password > /dev/null 2>&1 76 | docker push $IMAGE > /dev/null 2>&1 77 | docker logout $ACR.azurecr.io > /dev/null 2>&1 78 | docker rmi $IMAGE > /dev/null 2>&1 79 | 80 | # Create container instance 81 | az container delete --resource-group $RESOURCE_GROUP --name $NAME --yes > /dev/null 2>&1 82 | container=`az container create \ 83 | --resource-group $RESOURCE_GROUP \ 84 | --name $NAME \ 85 | --image $IMAGE \ 86 | --registry-username $acr_username \ 87 | --registry-password $acr_password \ 88 | --environment-variables \ 89 | Workload=$WORKLOAD \ 90 | Threads=$THREADS \ 91 | Seconds=$DURATION \ 92 | --secure-environment-variables \ 93 | ServiceBusConnectionString=$servicebus_connectionstring \ 94 | --cpu $CPU \ 95 | --memory $MEMORY \ 96 | --restart-policy Never` 97 | 98 | echo $container | jq .id | tr -dt '"' 99 | -------------------------------------------------------------------------------- /src/redis/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | # Generic workload parameters 5 | ACR="azureperformance" 6 | RESOURCE_GROUP="azure-performance-redis" 7 | NAME="" 8 | WORKLOAD="latency" # latency or throughput 9 | THREADS=10 10 | DURATION=60 11 | CPU=4 12 | MEMORY=1 13 | 14 | usage() { 15 | echo "Usage: $0 [-a azure-container-registry] [-g resource-group] [-w workload] [-t threads] [-s seconds] [-c cpu] [-m memory]" 16 | echo "" 17 | echo "Example:" 18 | echo " $0 -a $ACR -g $RESOURCE_GROUP -w $WORKLOAD -t $THREADS -s $DURATION -c $CPU -m $MEMORY" 19 | echo "" 20 | exit 21 | } 22 | 23 | while getopts ":a:g:w:t:s:c:m:" options; do 24 | case "${options}" in 25 | a) 26 | ACR=${OPTARG} 27 | ;; 28 | g) 29 | RESOURCE_GROUP=${OPTARG} 30 | ;; 31 | w) 32 | WORKLOAD=${OPTARG} 33 | ;; 34 | t) 35 | THREADS=${OPTARG} 36 | ;; 37 | s) 38 | DURATION=${OPTARG} 39 | ;; 40 | c) 41 | CPU=${OPTARG} 42 | ;; 43 | m) 44 | MEMORY=${OPTARG} 45 | ;; 46 | :) 47 | usage 48 | ;; 49 | *) 50 | usage 51 | ;; 52 | esac 53 | done 54 | 55 | # Get Redis details 56 | redis=`az redis list --resource-group $RESOURCE_GROUP | jq ".[0]"` 57 | NAME=`echo $redis | jq .name | tr -dt '"'` 58 | redis_key=`az redis list-keys --resource-group $RESOURCE_GROUP --name $NAME | jq .primaryKey | tr -dt '"'` 59 | redis_hostname=`echo $redis | jq .hostName | tr -dt '"'` 60 | redis_port=`echo $redis | jq .sslPort | tr -dt '"'` 61 | redis_connectionstring="$redis_hostname:$redis_port,password=$redis_key,ssl=True,abortConnect=False" 62 | 63 | # Get Azure container registry details 64 | acr_credentials=`az acr credential show --name $ACR` 65 | acr_username=`echo $acr_credentials | jq '.username' | tr -dt '"'` 66 | acr_password=`echo $acr_credentials | jq '.passwords[0].value' | tr -dt '"'` 67 | 68 | # Build project 69 | SRC=$(realpath $(dirname $0)) 70 | IMAGE="$ACR.azurecr.io/$RESOURCE_GROUP:latest" 71 | dotnet publish $SRC/redis.csproj -c Release -o $SRC/bin/publish > /dev/null 2>&1 72 | docker build --rm -t $IMAGE $SRC > /dev/null 2>&1 73 | docker login $ACR.azurecr.io --username $acr_username --password $acr_password > /dev/null 2>&1 74 | docker push $IMAGE > /dev/null 2>&1 75 | docker logout $ACR.azurecr.io > /dev/null 2>&1 76 | docker rmi $IMAGE > /dev/null 2>&1 77 | 78 | # Create container instance 79 | az container delete --resource-group $RESOURCE_GROUP --name $NAME --yes > /dev/null 2>&1 80 | container=`az container create \ 81 | --resource-group $RESOURCE_GROUP \ 82 | --name $NAME \ 83 | --image $IMAGE \ 84 | --registry-username $acr_username \ 85 | --registry-password $acr_password \ 86 | --environment-variables \ 87 | Workload=$WORKLOAD \ 88 | Threads=$THREADS \ 89 | Seconds=$DURATION \ 90 | --secure-environment-variables \ 91 | RedisConnectionString=$redis_connectionstring \ 92 | --cpu $CPU \ 93 | --memory $MEMORY \ 94 | --restart-policy Never` 95 | 96 | echo $container | jq .id | tr -dt '"' 97 | -------------------------------------------------------------------------------- /src/servicebus/Program.cs: -------------------------------------------------------------------------------- 1 | using Azure.Performance.Common; 2 | using Microsoft.Azure.ServiceBus; 3 | using Microsoft.Azure.ServiceBus.Core; 4 | using Microsoft.Extensions.Logging; 5 | using Newtonsoft.Json; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Net; 9 | using System.Text; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | 13 | namespace Azure.Performance.ServiceBus 14 | { 15 | class Program 16 | { 17 | static async Task Main(string[] args) 18 | { 19 | int threads = AppConfig.GetOptionalSetting("Threads") ?? 32; 20 | ILogger logger = AppConfig.CreateLogger("ServiceBus"); 21 | string workloadType = AppConfig.GetSetting("Workload"); 22 | CancellationToken cancellationToken = AppConfig.GetCancellationToken(); 23 | 24 | string connectionString = AppConfig.GetSetting("ServiceBusConnectionString"); 25 | 26 | var client = new MessageSender(new ServiceBusConnectionStringBuilder(connectionString), RetryPolicy.NoRetry); 27 | 28 | if (workloadType == "latency") 29 | { 30 | var workload = new LatencyWorkload(logger, "ServiceBus"); 31 | await workload.InvokeAsync(threads, (value) => WriteAsync(client, value, cancellationToken), cancellationToken).ConfigureAwait(false); 32 | } 33 | if (workloadType == "throughput") 34 | { 35 | var workload = new ThroughputWorkload(logger, "ServiceBus", IsThrottlingException); 36 | await workload.InvokeAsync(threads, () => WriteAsync(client, cancellationToken), cancellationToken).ConfigureAwait(false); 37 | } 38 | } 39 | 40 | private static Task WriteAsync(MessageSender client, PerformanceData value, CancellationToken cancellationToken) 41 | { 42 | var content = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(value)); 43 | var data = new Message(content); 44 | 45 | return client.SendAsync(data); 46 | } 47 | 48 | private static async Task WriteAsync(MessageSender client, CancellationToken cancellationToken) 49 | { 50 | const int batchSize = 16; 51 | 52 | var values = new Message[batchSize]; 53 | for (int i = 0; i < batchSize; i++) 54 | { 55 | var value = RandomGenerator.GetPerformanceData(); 56 | var serialized = JsonConvert.SerializeObject(value); 57 | var content = Encoding.UTF8.GetBytes(serialized); 58 | 59 | values[i] = new Message(content); 60 | } 61 | 62 | await client.SendAsync(values).ConfigureAwait(false); 63 | 64 | return batchSize; 65 | } 66 | 67 | private static TimeSpan? IsThrottlingException(Exception e) 68 | { 69 | var sbe = e as ServiceBusException; 70 | if (sbe == null) 71 | return null; 72 | 73 | if (!sbe.IsTransient) 74 | return null; 75 | 76 | return TimeSpan.FromSeconds(4); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/cosmosdb/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | # Generic workload parameters 5 | ACR="azureperformance" 6 | RESOURCE_GROUP="azure-performance-cosmosdb" 7 | NAME="" 8 | WORKLOAD="latency" # latency or throughput 9 | THREADS=10 10 | DURATION=60 11 | CPU=4 12 | MEMORY=1 13 | 14 | usage() { 15 | echo "Usage: $0 [-a azure-container-registry] [-g resource-group] [-w workload] [-t threads] [-s seconds] [-c cpu] [-m memory]" 16 | echo "" 17 | echo "Example:" 18 | echo " $0 -a $ACR -g $RESOURCE_GROUP -l $LOCATION -w $WORKLOAD -t $THREADS -s $DURATION -c $CPU -m $MEMORY" 19 | echo "" 20 | exit 21 | } 22 | 23 | while getopts ":a:g:w:t:s:c:m:" options; do 24 | case "${options}" in 25 | a) 26 | ACR=${OPTARG} 27 | ;; 28 | g) 29 | RESOURCE_GROUP=${OPTARG} 30 | ;; 31 | w) 32 | WORKLOAD=${OPTARG} 33 | ;; 34 | t) 35 | THREADS=${OPTARG} 36 | ;; 37 | s) 38 | DURATION=${OPTARG} 39 | ;; 40 | c) 41 | CPU=${OPTARG} 42 | ;; 43 | m) 44 | MEMORY=${OPTARG} 45 | ;; 46 | :) 47 | usage 48 | ;; 49 | *) 50 | usage 51 | ;; 52 | esac 53 | done 54 | 55 | # Get CosmosDB details 56 | cosmosdb=`az cosmosdb list --resource-group $RESOURCE_GROUP | jq ".[0]"` 57 | NAME=`echo $cosmosdb | jq .name | tr -dt '"'` 58 | cosmosdb_endpoint=`echo $cosmosdb | jq '.documentEndpoint' | tr -dt '"'` 59 | cosmosdb_database="database" 60 | cosmosdb_collection="collection" 61 | cosmosdb_key=`az cosmosdb list-keys --resource-group $RESOURCE_GROUP --name $NAME --output tsv --query "primaryMasterKey"` 62 | 63 | # Get Azure container registry details 64 | acr_credentials=`az acr credential show --name $ACR` 65 | acr_username=`echo $acr_credentials | jq '.username' | tr -dt '"'` 66 | acr_password=`echo $acr_credentials | jq '.passwords[0].value' | tr -dt '"'` 67 | 68 | # Build project 69 | SRC=$(realpath $(dirname $0)) 70 | IMAGE="$ACR.azurecr.io/$RESOURCE_GROUP:latest" 71 | dotnet publish $SRC/cosmosdb.csproj -c Release -o $SRC/bin/publish > /dev/null 2>&1 72 | docker build --rm -t $IMAGE $SRC > /dev/null 2>&1 73 | docker login $ACR.azurecr.io --username $acr_username --password $acr_password > /dev/null 2>&1 74 | docker push $IMAGE > /dev/null 2>&1 75 | docker logout $ACR.azurecr.io > /dev/null 2>&1 76 | docker rmi $IMAGE > /dev/null 2>&1 77 | 78 | # Create container instance 79 | az container delete --resource-group $RESOURCE_GROUP --name $NAME --yes > /dev/null 2>&1 80 | container=`az container create \ 81 | --resource-group $RESOURCE_GROUP \ 82 | --name $NAME \ 83 | --image $IMAGE \ 84 | --registry-username $acr_username \ 85 | --registry-password $acr_password \ 86 | --environment-variables \ 87 | Workload=$WORKLOAD \ 88 | Threads=$THREADS \ 89 | Seconds=$DURATION \ 90 | CosmosDbEndpoint=$cosmosdb_endpoint \ 91 | CosmosDbDatabaseId=$cosmosdb_database \ 92 | CosmosDbCollectionId=$cosmosdb_collection \ 93 | --secure-environment-variables \ 94 | CosmosDbKey=$cosmosdb_key \ 95 | --cpu $CPU \ 96 | --memory $MEMORY \ 97 | --restart-policy Never` 98 | 99 | echo $container | jq .id | tr -dt '"' 100 | -------------------------------------------------------------------------------- /src/cosmosdb/Program.cs: -------------------------------------------------------------------------------- 1 | using Azure.Performance.Common; 2 | using Microsoft.Azure.Documents; 3 | using Microsoft.Azure.Documents.Client; 4 | using Microsoft.Extensions.Logging; 5 | using Newtonsoft.Json; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Net; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | 12 | namespace Azure.Performance.CosmosDB 13 | { 14 | class Program 15 | { 16 | private static long _id = 0; 17 | 18 | static async Task Main(string[] args) 19 | { 20 | int threads = AppConfig.GetOptionalSetting("Threads") ?? 8; 21 | ILogger logger = AppConfig.CreateLogger("CosmosDB"); 22 | string workloadType = AppConfig.GetSetting("Workload"); 23 | CancellationToken cancellationToken = AppConfig.GetCancellationToken(); 24 | 25 | Uri endpoint = new Uri(AppConfig.GetSetting("CosmosDbEndpoint")); 26 | string key = AppConfig.GetSetting("CosmosDbKey"); 27 | string databaseId = AppConfig.GetSetting("CosmosDbDatabaseId"); 28 | string collectionId = AppConfig.GetSetting("CosmosDbCollectionId"); 29 | 30 | var client = new DocumentClient(endpoint, key, new ConnectionPolicy 31 | { 32 | ConnectionMode = ConnectionMode.Direct, 33 | ConnectionProtocol = Protocol.Tcp, 34 | }); 35 | 36 | var collectionUri = UriFactory.CreateDocumentCollectionUri(databaseId, collectionId); 37 | var collection = await client.ReadDocumentCollectionAsync(collectionUri).ConfigureAwait(false); 38 | 39 | if (workloadType == "latency") 40 | { 41 | var workload = new LatencyWorkload(logger, "CosmosDB"); 42 | await workload.InvokeAsync(threads, (value) => WriteAsync(client, collection, value, cancellationToken), cancellationToken).ConfigureAwait(false); 43 | } 44 | if (workloadType == "throughput") 45 | { 46 | var workload = new ThroughputWorkload(logger, "CosmosDB", IsThrottlingException); 47 | await workload.InvokeAsync(threads, () => WriteAsync(client, collection, cancellationToken), cancellationToken).ConfigureAwait(false); 48 | } 49 | } 50 | 51 | private static async Task WriteAsync(IDocumentClient client, DocumentCollection collection, PerformanceData value, CancellationToken cancellationToken) 52 | { 53 | var response = await client.UpsertDocumentAsync(collection.SelfLink, value).ConfigureAwait(false); 54 | } 55 | 56 | private static async Task WriteAsync(IDocumentClient client, DocumentCollection collection, CancellationToken cancellationToken) 57 | { 58 | long key = Interlocked.Increment(ref _id) % 1000000; 59 | var value = RandomGenerator.GetPerformanceData(key.ToString()); 60 | 61 | var response = await client.UpsertDocumentAsync(collection.SelfLink, value).ConfigureAwait(false); 62 | 63 | return 1; 64 | } 65 | 66 | private static TimeSpan? IsThrottlingException(Exception e) 67 | { 68 | var dce = e as DocumentClientException; 69 | if (dce == null) 70 | return null; 71 | 72 | if (dce.StatusCode != (HttpStatusCode)429) 73 | return null; 74 | 75 | return dce.RetryAfter; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /scripts/create-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | PREFIX="azure-performance" 5 | LOCATION="westus2" 6 | ACR="azureperformance" 7 | 8 | SRC=$(realpath $(dirname $0)/../src) 9 | COSMOSDB=$(realpath $SRC/cosmosdb) 10 | EVENTHUB=$(realpath $SRC/eventhub) 11 | REDIS=$(realpath $SRC/redis) 12 | SERVICEBUS=$(realpath $SRC/servicebus) 13 | SQL=$(realpath $SRC/sql) 14 | STORAGE=$(realpath $SRC/storage) 15 | 16 | usage() { 17 | echo "Usage: $0 [-a azure-container-registry] [-p name-prefix] [-l location]" 18 | echo "" 19 | echo "Example:" 20 | echo " $0 -a $ACR -p $PREFIX -l $LOCATION" 21 | echo "" 22 | exit 23 | } 24 | 25 | while getopts ":a:p:l:" options; do 26 | case "${options}" in 27 | a) 28 | ACR=${OPTARG} 29 | ;; 30 | p) 31 | PREFIX=${OPTARG} 32 | ;; 33 | l) 34 | LOCATION=${OPTARG} 35 | ;; 36 | :) 37 | usage 38 | ;; 39 | *) 40 | usage 41 | ;; 42 | esac 43 | done 44 | 45 | # 46 | # Create shared resources 47 | # 48 | echo "Creating shared resources ..." 49 | rg=`az group create --name $PREFIX --location $LOCATION` 50 | acr=`az acr create --resource-group $PREFIX --name $ACR --sku Standard --admin-enabled true` 51 | 52 | # 53 | # Redis Workloads 54 | # 55 | echo "Creating Redis latency workload ..." 56 | $REDIS/create-resources.sh \ 57 | -g $PREFIX-redis-latency \ 58 | -l $LOCATION \ 59 | -p '{ "sku": "Standard", "size": "c2" }' 60 | 61 | echo "Creating Redis throughput workload ..." 62 | $REDIS/create-resources.sh \ 63 | -g $PREFIX-redis-throughput \ 64 | -l $LOCATION \ 65 | -p '{ "sku": "Standard", "size": "c5" }' 66 | 67 | # 68 | # CosmosDB workloads 69 | # 70 | echo "Creating CosmosDB latency workload ..." 71 | $COSMOSDB/create-resources.sh \ 72 | -g $PREFIX-cosmosdb-latency \ 73 | -l $LOCATION \ 74 | -p '{ "kind": "GlobalDocumentDB", "throughput": 400 }' 75 | 76 | echo "Creating CosmosDB throughput workload ..." 77 | $COSMOSDB/create-resources.sh \ 78 | -g $PREFIX-cosmosdb-throughput \ 79 | -l $LOCATION \ 80 | -p '{ "kind": "GlobalDocumentDB", "throughput": 15000 }' 81 | 82 | # 83 | # EventHub workloads 84 | # 85 | echo "Creating EventHub latency workload ..." 86 | $EVENTHUB/create-resources.sh \ 87 | -g $PREFIX-eventhub-latency \ 88 | -l $LOCATION \ 89 | -p '{ "sku": "Standard", "capacity": 1, "partitions": 10 }' 90 | 91 | echo "Creating EventHub throughput workload ..." 92 | $EVENTHUB/create-resources.sh \ 93 | -g $PREFIX-eventhub-throughput \ 94 | -l $LOCATION \ 95 | -p '{ "sku": "Standard", "capacity": 10, "partitions": 32 }' 96 | 97 | # 98 | # ServiceBus workloads 99 | # 100 | echo "Creating ServiceBus latency workload ..." 101 | $SERVICEBUS/create-resources.sh \ 102 | -g $PREFIX-servicebus-latency \ 103 | -l $LOCATION \ 104 | -p '{ "sku": "Standard" }' 105 | 106 | echo "Creating ServiceBus throughput workload ..." 107 | $SERVICEBUS/create-resources.sh \ 108 | -g $PREFIX-servicebus-throughput \ 109 | -l $LOCATION \ 110 | -p '{ "sku": "Premium" }' 111 | 112 | # 113 | # SQL workloads 114 | # 115 | echo "Creating SQL latency workload ..." 116 | $SQL/create-resources.sh \ 117 | -g $PREFIX-sql-latency \ 118 | -l $LOCATION \ 119 | -p '{ "sku": "S2" }' 120 | 121 | echo "Creating SQL throughput workload ..." 122 | $SQL/create-resources.sh \ 123 | -g $PREFIX-sql-throughput \ 124 | -l $LOCATION \ 125 | -p '{ "sku": "P2" }' 126 | 127 | # 128 | # Storage workloads 129 | # 130 | echo "Creating Storage latency workload ..." 131 | $STORAGE/create-resources.sh \ 132 | -g $PREFIX-storage-latency \ 133 | -l $LOCATION \ 134 | -p '{ "sku": "Standard_LRS", "kind": "StorageV2" }' 135 | 136 | echo "Creating Storage throughput workload ..." 137 | $STORAGE/create-resources.sh \ 138 | -g $PREFIX-storage-throughput \ 139 | -l $LOCATION \ 140 | -p '{ "sku": "Standard_LRS", "kind": "StorageV2" }' 141 | -------------------------------------------------------------------------------- /src/common/utility/LatencyWorkload.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using Newtonsoft.Json; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace Azure.Performance.Common 10 | { 11 | public sealed class LatencyWorkload 12 | { 13 | private const int KeysPerTask = 10000; 14 | private const int MinWorkloadDelayInMs = 500; 15 | private const int MaxWorkloadDelayInMs = 1500; 16 | 17 | private readonly ILogger _logger; 18 | private readonly string _workloadName; 19 | private readonly int _keysPerTask; 20 | private readonly int _minWorkloadDelayInMs; 21 | private readonly int _maxWorkloadDelayInMs; 22 | 23 | private readonly Metric _latency = new Metric("latency"); 24 | private long _errors = 0; 25 | 26 | public LatencyWorkload(ILogger logger, string workloadName, int keysPerTask = KeysPerTask, int minWorkloadDelayInMs = MinWorkloadDelayInMs, int maxWorkloadDelayInMs = MaxWorkloadDelayInMs) 27 | { 28 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 29 | _workloadName = workloadName ?? throw new ArgumentNullException(nameof(workloadName)); 30 | _keysPerTask = keysPerTask; 31 | _minWorkloadDelayInMs = minWorkloadDelayInMs; 32 | _maxWorkloadDelayInMs = maxWorkloadDelayInMs; 33 | } 34 | 35 | public async Task InvokeAsync(int taskCount, Func workload, CancellationToken cancellationToken) 36 | { 37 | var timer = Stopwatch.StartNew(); 38 | 39 | // Spawn the workload workers. 40 | var tasks = new List(taskCount + 1); 41 | for (int i = 0; i < taskCount; i++) 42 | { 43 | int taskId = i; 44 | tasks.Add(Task.Run(() => CreateWorkerAsync(workload, taskId, cancellationToken))); 45 | } 46 | 47 | // Run until cancelled. 48 | await Task.WhenAll(tasks).ConfigureAwait(false); 49 | 50 | _logger.LogInformation(JsonConvert.SerializeObject(new 51 | { 52 | workload = _workloadName, 53 | type = "latency", 54 | elapsed = timer.ElapsedMilliseconds, 55 | operations = _latency.Count, 56 | errors = _errors, 57 | latency = new 58 | { 59 | average = _latency.Average, 60 | stddev = _latency.StdDev, 61 | min = _latency.Min, 62 | max = _latency.Max, 63 | p25 = _latency.P25, 64 | p50 = _latency.P50, 65 | p75 = _latency.P75, 66 | p95 = _latency.P95, 67 | p99 = _latency.P99, 68 | p999 = _latency.P999, 69 | p9999 = _latency.P9999, 70 | }, 71 | })); 72 | } 73 | 74 | private async Task CreateWorkerAsync(Func workload, int taskId, CancellationToken cancellationToken) 75 | { 76 | var timer = new Stopwatch(); 77 | var retry = new RetryHandler(); 78 | var random = new Random(); 79 | 80 | // Non-conflicting keys based on task id. 81 | int minKey = taskId * _keysPerTask; 82 | int maxKey = (taskId + 1) * _keysPerTask; 83 | 84 | while (!cancellationToken.IsCancellationRequested) 85 | { 86 | // Generate a unique, non-conflicting write for this workload invocation. 87 | long key = random.Next(minKey, maxKey); 88 | var data = RandomGenerator.GetPerformanceData(key.ToString()); 89 | 90 | timer.Restart(); 91 | try 92 | { 93 | // Invoke the workload. 94 | await workload.Invoke(data).ConfigureAwait(false); 95 | timer.Stop(); 96 | retry.Reset(); 97 | 98 | lock (_latency) 99 | { 100 | _latency.AddSample(timer.Elapsed.TotalMilliseconds); 101 | } 102 | 103 | // Delay between workload invocations. 104 | int delayInMs = random.Next(_minWorkloadDelayInMs, _maxWorkloadDelayInMs); 105 | await Task.Delay(delayInMs, cancellationToken).ConfigureAwait(false); 106 | } 107 | catch (Exception e) 108 | { 109 | if (cancellationToken.IsCancellationRequested) 110 | return; 111 | 112 | timer.Stop(); 113 | var retryAfter = retry.Retry(); 114 | 115 | // Track metrics. 116 | Interlocked.Increment(ref _errors); 117 | lock (_latency) 118 | { 119 | _latency.AddSample(timer.Elapsed.TotalMilliseconds); 120 | } 121 | 122 | // Exponential delay after exceptions. 123 | _logger.LogError(e, "Unexpected exception {ExceptionType} in {WorkloadName}. Retrying in {RetryInMs} ms.", e.GetType(), _workloadName, retryAfter.TotalMilliseconds); 124 | await Task.Delay(retryAfter, cancellationToken).ConfigureAwait(false); 125 | } 126 | } 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /scripts/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eu 3 | 4 | PREFIX="azure-performance" 5 | DURATION=600 6 | ACR="azureperformance" 7 | CPU=4 8 | MEMORY=1 9 | 10 | SRC=$(realpath $(dirname $0)/../src) 11 | COSMOSDB=$(realpath $SRC/cosmosdb) 12 | EVENTHUB=$(realpath $SRC/eventhub) 13 | REDIS=$(realpath $SRC/redis) 14 | SERVICEBUS=$(realpath $SRC/servicebus) 15 | SQL=$(realpath $SRC/sql) 16 | STORAGE=$(realpath $SRC/storage) 17 | 18 | usage() { 19 | echo "Usage: $0 [-a azure-container-registry] [-p name-prefix] [-s seconds] [-c cpu] [-m memory]" 20 | echo "" 21 | echo "Example:" 22 | echo " $0 -a $ACR -p $PREFIX -s $DURATION -c $CPU -m $MEMORY" 23 | echo "" 24 | exit 25 | } 26 | 27 | while getopts ":a:p:s:c:m:" options; do 28 | case "${options}" in 29 | a) 30 | ACR=${OPTARG} 31 | ;; 32 | p) 33 | PREFIX=${OPTARG} 34 | ;; 35 | s) 36 | DURATION=${OPTARG} 37 | ;; 38 | c) 39 | CPU=${OPTARG} 40 | ;; 41 | m) 42 | MEMORY=${OPTARG} 43 | ;; 44 | :) 45 | usage 46 | ;; 47 | *) 48 | usage 49 | ;; 50 | esac 51 | done 52 | 53 | # 54 | # CosmosDB workloads 55 | # 56 | echo "Starting CosmosDB latency workload ..." 57 | cosmosdb_latency=`$COSMOSDB/run.sh \ 58 | -a $ACR \ 59 | -g $PREFIX-cosmosdb-latency \ 60 | -w latency \ 61 | -t 10 \ 62 | -s $DURATION \ 63 | -c $CPU \ 64 | -m $MEMORY` 65 | 66 | echo "Starting CosmosDB throughput workload ..." 67 | cosmosdb_throughput=`$COSMOSDB/run.sh \ 68 | -a $ACR \ 69 | -g $PREFIX-cosmosdb-throughput \ 70 | -w throughput \ 71 | -t 32 \ 72 | -s $DURATION \ 73 | -c $CPU \ 74 | -m $MEMORY` 75 | 76 | # 77 | # EventHub workloads 78 | # 79 | echo "Starting EventHub latency workload ..." 80 | eventhub_latency=`$EVENTHUB/run.sh \ 81 | -a $ACR \ 82 | -g $PREFIX-eventhub-latency \ 83 | -w latency \ 84 | -t 10 \ 85 | -s $DURATION \ 86 | -c $CPU \ 87 | -m $MEMORY` 88 | 89 | echo "Starting EventHub throughput workload ..." 90 | eventhub_throughput=`$EVENTHUB/run.sh \ 91 | -a $ACR \ 92 | -g $PREFIX-eventhub-throughput \ 93 | -w throughput \ 94 | -t 32 \ 95 | -s $DURATION \ 96 | -c $CPU \ 97 | -m $MEMORY` 98 | 99 | # 100 | # Redis Workloads 101 | # 102 | echo "Starting Redis latency workload ..." 103 | redis_latency=`$REDIS/run.sh \ 104 | -a $ACR \ 105 | -g $PREFIX-redis-latency \ 106 | -w latency \ 107 | -t 10 \ 108 | -s $DURATION \ 109 | -c $CPU \ 110 | -m $MEMORY` 111 | 112 | echo "Starting Redis throughput workload ..." 113 | redis_throughput=`$REDIS/run.sh \ 114 | -a $ACR \ 115 | -g $PREFIX-redis-throughput \ 116 | -w throughput \ 117 | -t 64 \ 118 | -s $DURATION \ 119 | -c $CPU \ 120 | -m $MEMORY` 121 | 122 | # 123 | # ServiceBus workloads 124 | # 125 | echo "Starting ServiceBus latency workload ..." 126 | servicebus_latency=`$SERVICEBUS/run.sh \ 127 | -a $ACR \ 128 | -g $PREFIX-servicebus-latency \ 129 | -w latency \ 130 | -t 10 \ 131 | -s $DURATION \ 132 | -c $CPU \ 133 | -m $MEMORY` 134 | 135 | echo "Starting ServiceBus throughput workload ..." 136 | servicebus_throughput=`$SERVICEBUS/run.sh \ 137 | -a $ACR \ 138 | -g $PREFIX-servicebus-throughput \ 139 | -w throughput \ 140 | -t 32 \ 141 | -s $DURATION \ 142 | -c $CPU \ 143 | -m $MEMORY` 144 | 145 | # 146 | # SQL workloads 147 | # 148 | echo "Starting SQL latency workload ..." 149 | sql_latency=`$SQL/run.sh \ 150 | -a $ACR \ 151 | -g $PREFIX-sql-latency \ 152 | -w latency \ 153 | -t 10 \ 154 | -s $DURATION \ 155 | -c $CPU \ 156 | -m $MEMORY` 157 | 158 | echo "Starting SQL throughput workload ..." 159 | sql_throughput=`$SQL/run.sh \ 160 | -a $ACR \ 161 | -g $PREFIX-sql-throughput \ 162 | -w throughput \ 163 | -t 32 \ 164 | -s $DURATION \ 165 | -c $CPU \ 166 | -m $MEMORY` 167 | 168 | # 169 | # Storage workloads 170 | # 171 | echo "Starting Storage latency workload ..." 172 | storage_latency=`$STORAGE/run.sh \ 173 | -a $ACR \ 174 | -g $PREFIX-storage-latency \ 175 | -w latency \ 176 | -t 10 \ 177 | -s $DURATION \ 178 | -c $CPU \ 179 | -m $MEMORY` 180 | 181 | echo "Starting Storage throughput workload ..." 182 | storage_throughput=`$STORAGE/run.sh \ 183 | -a $ACR \ 184 | -g $PREFIX-storage-throughput \ 185 | -w throughput \ 186 | -t 32 \ 187 | -s $DURATION \ 188 | -c $CPU \ 189 | -m $MEMORY` 190 | 191 | # 192 | # Wait for all workloads to complete 193 | # 194 | echo "Waiting for workloads to complete ..." 195 | sleep $DURATION 196 | sleep 2m 197 | 198 | # 199 | # Display final metrics 200 | # 201 | echo `az container logs --id $cosmosdb_latency | tr -s "\n" | tail -n 1` 202 | echo `az container logs --id $cosmosdb_throughput | tr -s "\n" | tail -n 1` 203 | 204 | echo `az container logs --id $eventhub_latency | tr -s "\n" | tail -n 1` 205 | echo `az container logs --id $eventhub_throughput | tr -s "\n" | tail -n 1` 206 | 207 | echo `az container logs --id $redis_latency | tr -s "\n" | tail -n 1` 208 | echo `az container logs --id $redis_throughput | tr -s "\n" | tail -n 1` 209 | 210 | echo `az container logs --id $servicebus_latency | tr -s "\n" | tail -n 1` 211 | echo `az container logs --id $servicebus_throughput | tr -s "\n" | tail -n 1` 212 | 213 | echo `az container logs --id $sql_latency | tr -s "\n" | tail -n 1` 214 | echo `az container logs --id $sql_throughput | tr -s "\n" | tail -n 1` 215 | 216 | echo `az container logs --id $storage_latency | tr -s "\n" | tail -n 1` 217 | echo `az container logs --id $storage_throughput | tr -s "\n" | tail -n 1` 218 | -------------------------------------------------------------------------------- /src/common/utility/ThroughputWorkload.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using Newtonsoft.Json; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace Azure.Performance.Common 10 | { 11 | public sealed class ThroughputWorkload 12 | { 13 | private readonly ILogger _logger; 14 | private readonly string _workloadName; 15 | private readonly Func _throttle; 16 | 17 | private readonly Metric _throughput = new Metric("throughput"); 18 | private long _errors = 0; 19 | private long _operations = 0; 20 | private long _latency = 0; 21 | 22 | public ThroughputWorkload(ILogger logger, string workloadName, Func throttle = null) 23 | { 24 | _logger = logger ?? throw new ArgumentNullException(nameof(logger)); 25 | _workloadName = workloadName ?? throw new ArgumentNullException(nameof(workloadName)); 26 | _throttle = throttle ?? new Func(e => null); 27 | } 28 | 29 | public async Task InvokeAsync(int taskCount, Func> workload, CancellationToken cancellationToken) 30 | { 31 | var timer = Stopwatch.StartNew(); 32 | 33 | // Spawn the workload workers. 34 | var tasks = new List(taskCount + 1); 35 | for (int i = 0; i < taskCount; i++) 36 | { 37 | int taskId = i; 38 | tasks.Add(Task.Run(() => CreateWorkerAsync(workload, taskId, cancellationToken))); 39 | } 40 | 41 | // Spawn the metric tracker. 42 | var metrics = new Thread(() => TrackMetrics(cancellationToken)) { Priority = ThreadPriority.AboveNormal }; 43 | metrics.Start(); 44 | 45 | // Run until cancelled. 46 | await Task.WhenAll(tasks).ConfigureAwait(false); 47 | timer.Stop(); 48 | 49 | metrics.Join(); 50 | 51 | // Log final metrics. 52 | double throughput = ((double)_operations * 1000) / (double)timer.ElapsedMilliseconds; 53 | double latencyPerOperation = (double)_latency / (double)_operations; 54 | _logger.LogInformation(JsonConvert.SerializeObject(new 55 | { 56 | workload = _workloadName, 57 | type = "throughput", 58 | elapsed = timer.ElapsedMilliseconds, 59 | operations = _operations, 60 | errors = _errors, 61 | throughput = new 62 | { 63 | overall = throughput, 64 | low = _throughput.P5, 65 | high = _throughput.P95, 66 | latency = latencyPerOperation, 67 | }, 68 | })); 69 | } 70 | 71 | private void TrackMetrics(CancellationToken cancellationToken) 72 | { 73 | long startOperations = 0; 74 | long startLatency = 0; 75 | long startErrors = 0; 76 | 77 | var timer = new Stopwatch(); 78 | while (!cancellationToken.IsCancellationRequested) 79 | { 80 | try 81 | { 82 | timer.Restart(); 83 | Thread.Sleep(TimeSpan.FromSeconds(5)); 84 | timer.Stop(); 85 | 86 | // Read the latest metrics. 87 | long endOperations = Interlocked.Read(ref _operations); 88 | long endLatency = Interlocked.Read(ref _latency); 89 | long endErrors = Interlocked.Read(ref _errors); 90 | 91 | long operations = endOperations - startOperations; 92 | long latency = endLatency - startLatency; 93 | long errors = endErrors - startErrors; 94 | if (operations == 0) 95 | continue; 96 | 97 | // Log metrics - operations/sec and latency/operation. 98 | double throughput = ((double)operations * 1000) / Math.Max((double)timer.ElapsedMilliseconds, 5000); 99 | double latencyPerOperation = (double)latency / (double)operations; 100 | _logger.LogInformation(JsonConvert.SerializeObject(new 101 | { 102 | workload = _workloadName, 103 | type = "throughput", 104 | elapsed = timer.ElapsedMilliseconds, 105 | operations = operations, 106 | errors = errors, 107 | throughput = throughput, 108 | operationLatency = latencyPerOperation, 109 | })); 110 | 111 | // Update tracked matrics. 112 | _throughput.AddSample(throughput); 113 | startOperations = endOperations; 114 | startLatency = endLatency; 115 | startErrors = endErrors; 116 | } 117 | catch (Exception e) 118 | { 119 | _logger.LogError(e, "Unexpected exception {ExceptionType} in {WorkloadName} tracking metrics.", e.GetType(), _workloadName); 120 | } 121 | } 122 | } 123 | 124 | private async Task CreateWorkerAsync(Func> workload, int taskId, CancellationToken cancellationToken) 125 | { 126 | var timer = new Stopwatch(); 127 | var retry = new RetryHandler(); 128 | 129 | while (!cancellationToken.IsCancellationRequested) 130 | { 131 | timer.Restart(); 132 | try 133 | { 134 | // Invoke the workload. 135 | long operations = await workload.Invoke().ConfigureAwait(false); 136 | timer.Stop(); 137 | retry.Reset(); 138 | 139 | // Track metrics. 140 | Interlocked.Add(ref _operations, operations); 141 | Interlocked.Add(ref _latency, timer.ElapsedMilliseconds); 142 | } 143 | catch (Exception e) 144 | { 145 | timer.Stop(); 146 | 147 | if (cancellationToken.IsCancellationRequested) 148 | return; 149 | 150 | // Check if this is an exception indicating we are being throttled. 151 | var throttle = _throttle.Invoke(e); 152 | if (throttle != null) 153 | { 154 | await Task.Delay(throttle.Value, cancellationToken).ConfigureAwait(false); 155 | } 156 | else 157 | { 158 | var retryAfter = retry.Retry(); 159 | 160 | // Track metrics. 161 | Interlocked.Add(ref _latency, timer.ElapsedMilliseconds); 162 | Interlocked.Increment(ref _errors); 163 | 164 | // Exponential delay after exceptions. 165 | _logger.LogError(e, "Unexpected exception {ExceptionType} in {WorkloadName}. Retrying in {RetryInMs} ms.", e.GetType(), _workloadName, retryAfter.TotalMilliseconds); 166 | await Task.Delay(retryAfter, cancellationToken).ConfigureAwait(false); 167 | } 168 | } 169 | } 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/sql/Program.cs: -------------------------------------------------------------------------------- 1 | using Azure.Performance.Common; 2 | using Microsoft.Extensions.Logging; 3 | using Newtonsoft.Json; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Data.SqlClient; 7 | using System.Net; 8 | using System.Text; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | 12 | namespace Azure.Performance.Sql 13 | { 14 | class Program 15 | { 16 | static async Task Main(string[] args) 17 | { 18 | int threads = AppConfig.GetOptionalSetting("Threads") ?? 32; 19 | ILogger logger = AppConfig.CreateLogger("SQL"); 20 | string workloadType = AppConfig.GetSetting("Workload"); 21 | CancellationToken cancellationToken = AppConfig.GetCancellationToken(); 22 | 23 | string connectionString = AppConfig.GetSetting("SqlConnectionString"); 24 | 25 | await CreateDatabaseAsync(connectionString).ConfigureAwait(false); 26 | 27 | if (workloadType == "latency") 28 | { 29 | var workload = new LatencyWorkload(logger, "SQL"); 30 | await workload.InvokeAsync(threads, (value) => WriteAsync(connectionString, value, cancellationToken), cancellationToken).ConfigureAwait(false); 31 | } 32 | if (workloadType == "throughput") 33 | { 34 | var workload = new ThroughputWorkload(logger, "SQL"); 35 | await workload.InvokeAsync(threads, () => WriteAsync(connectionString, cancellationToken), cancellationToken).ConfigureAwait(false); 36 | } 37 | } 38 | 39 | private static async Task CreateDatabaseAsync(string connectionString) 40 | { 41 | using (var sql = new SqlConnection(connectionString)) 42 | { 43 | await sql.OpenAsync().ConfigureAwait(false); 44 | 45 | var createTableText = @" 46 | IF NOT EXISTS ( SELECT [Name] FROM sys.tables WHERE [name] = 'Performance' ) 47 | CREATE TABLE Performance 48 | ( 49 | id VARCHAR(255) NOT NULL PRIMARY KEY, 50 | timestamp DATETIMEOFFSET, 51 | string_value VARCHAR(512), 52 | int_value INT, 53 | double_value FLOAT, 54 | time_value BIGINT, 55 | ttl INT 56 | ); 57 | ELSE 58 | TRUNCATE TABLE Performance; 59 | "; 60 | 61 | using (var command = new SqlCommand(createTableText, sql)) 62 | { 63 | await command.ExecuteNonQueryAsync().ConfigureAwait(false); 64 | } 65 | } 66 | } 67 | 68 | private static async Task WriteAsync(string connectionString, PerformanceData value, CancellationToken cancellationToken) 69 | { 70 | var commandText = @" 71 | INSERT INTO dbo.Performance (id, timestamp, string_value, int_value, double_value, time_value, ttl) 72 | VALUES (@id, @timestamp, @string_value, @int_value, @double_value, @time_value, @ttl) 73 | "; 74 | 75 | using (var sql = new SqlConnection(connectionString)) 76 | using (var command = new SqlCommand(commandText, sql)) 77 | { 78 | command.Parameters.Add(new SqlParameter("@id", Guid.NewGuid().ToString())); 79 | command.Parameters.Add(new SqlParameter("@timestamp", value.Timestamp)); 80 | command.Parameters.Add(new SqlParameter("@string_value", value.StringValue)); 81 | command.Parameters.Add(new SqlParameter("@int_value", value.IntValue)); 82 | command.Parameters.Add(new SqlParameter("@double_value", value.DoubleValue)); 83 | command.Parameters.Add(new SqlParameter("@time_value", value.TimeValue.TotalMilliseconds)); 84 | command.Parameters.Add(new SqlParameter("@ttl", value.TimeToLive)); 85 | 86 | await sql.OpenAsync(cancellationToken).ConfigureAwait(false); 87 | await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false); 88 | } 89 | } 90 | 91 | private static async Task WriteAsync(string connectionString, CancellationToken cancellationToken) 92 | { 93 | const int batchSize = 16; 94 | var commandText = @" 95 | INSERT INTO dbo.Performance (id, timestamp, string_value, int_value, double_value, time_value, ttl) 96 | VALUES (@id, @timestamp, @string_value, @int_value, @double_value, @time_value, @ttl) 97 | "; 98 | 99 | using (var sql = new SqlConnection(connectionString)) 100 | { 101 | await sql.OpenAsync(cancellationToken).ConfigureAwait(false); 102 | 103 | using (var tx = sql.BeginTransaction()) 104 | { 105 | for (int i = 0; i < batchSize; i++) 106 | { 107 | var value = RandomGenerator.GetPerformanceData(); 108 | 109 | var command = new SqlCommand(commandText, sql, tx); 110 | command.Parameters.Add(new SqlParameter("@id", value.Id)); 111 | command.Parameters.Add(new SqlParameter("@timestamp", value.Timestamp)); 112 | command.Parameters.Add(new SqlParameter("@string_value", value.StringValue)); 113 | command.Parameters.Add(new SqlParameter("@int_value", value.IntValue)); 114 | command.Parameters.Add(new SqlParameter("@double_value", value.DoubleValue)); 115 | command.Parameters.Add(new SqlParameter("@time_value", value.TimeValue.TotalMilliseconds)); 116 | command.Parameters.Add(new SqlParameter("@ttl", value.TimeToLive)); 117 | 118 | await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false); 119 | } 120 | 121 | tx.Commit(); 122 | } 123 | } 124 | 125 | return batchSize; 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Azure Performance Analysis 2 | 3 | This repository contains a number of projects used to measure the latency and throughput of various Azure services. 4 | 5 | I am currently rewriting these projects to be easier to run. Each project will be a simple .NET Core application, packaged into a Linux Docker container, and deployed to Azure Container Instances. The containers can be ran anywhere (locally, Kubernetes, etc.) as they simply require environment variables to configure. Throughput and/or latency metrics are printed to standard out. 6 | 7 | *Note: Service Fabric performance numbers are removed until stateful services are supported in Service Fabric Mesh on Linux.* 8 | 9 | ## Latency Performance Analysis 10 | 11 | Latency performance analysis finds the optimal (lowest) latency by using a low throughput write-only workload with no write contention (10 writes/sec, ~1 KB). All latency numbers are in milliseconds. 12 | 13 | | Azure Service | Min | Max | P50 | P95 | P99 | P99.9 | Errors | Notes | 14 | | --------------- | :-----: | :-----: | :-----: | :-----: | :-----: | :-----: | :----: | ----- | 15 | | CosmosDB | 3.9 | 675.5 | 5.5 | 9.5 | 59.3 | 546.9 | 3 | Session consistency, default indexing, 1 region, 400 RUs, TTL = 1 day | 16 | | Event Hub | 13.7 | 927.7 | 16.7 | 94.6 | 198.0 | 739.8 | 0 | Standard, 2 partitions, 10 throughput units, 1 day message retention | 17 | | Redis | 0.9 | 207.9 | 1.5 | 3.5 | 71.8 | 99.6 | 0 | C2 Standard (async replication, no data persistence, dedicated service, moderate network bandwidth) | 18 | | Service Bus | 7.1 | 1439.2 | 14.1 | 109.9 | 359.9 | 1157.2 | 0 | Premium, 1 messaging unit, 1 GB queue, partitioning enabled, TTL = 1 day | 19 | | SQL Database | 3.4 | 142.7 | 4.6 | 9.5 | 40.5 | 95.5 | 0 | S2 Standard (50 DTUs), 1 region, insert-only writes | 20 | | Storage (Blob) | 6.0 | 260.7 | 7.7 | 30.0 | 68.7 | 133.7 | 0 | General Purpose v2, Standard, Locally-redundant storage (LRS), Hot | 21 | 22 | ## Throughput Performance Analysis 23 | 24 | Throughput performance analysis finds the optimal throughput numbers by using a high throughput write-only workload with no write contention (~1 KB writes). The Azure services are sized to provide an approximately equivalent operating cost. Cost shown is based on public Azure pricing for South Central US as calculated by https://azure.microsoft.com/en-us/pricing/calculator. 25 | 26 | | Azure Service | Throughput (writes/sec) | Low (writes/sec) | High (writes/sec) | Errors | Cost / month | Notes | 27 | | --------------- | :---------------------: | :--------------: | :---------------: | :----: | :----------: | ----- | 28 | | CosmosDB | 1,323 | 1,272 | 1,367 | 2 | $876 | Session consistency, default indexing, 1 region, 15000 RUs, TTL = 1 day | 29 | | Event Hub | 9,452 | 998 | 25,028 | 0 | ~$900* | Standard, 32 partitions, 8 throughput units, 1 day message retention | 30 | | Redis | 72,795 | 68,496 | 77,366 | 0 | $810 | P2 Premium (async replication, no data persistence, dedicated service, redis cluster, moderate network bandwidth) | 31 | | Service Bus | 14,713 | 12,747 | 15,923 | 0 | $677 | Premium, 1 messaging unit, 1 GB queue, partitioning enabled, TTL = 1 day | 32 | | SQL Database | 9,714 | 8,098 | 10,506 | 0 | $913 | P2 Premium (250 DTUs), 1 region, insert-only writes | 33 | | Storage (Blob) | 3,456 | 3,138 | 3,623 | 0 | ~$38,000* | General Purpose v2, Standard, Locally-redundant storage (LRS), Hot | 34 | 35 | *Note: Event Hub pricing is $175/mo for 8 throughput units plus $725/mo for 850M messages/day (approximate throughput at this load).* 36 | 37 | *Note: Storage pricing is ~$0.02/GB/mo plus ~$1300/day for 250M writes/day (approximate throughput at this load). Storage is not meant for this type of workload.* 38 | 39 | *Note: Low and High throughput indicates the low/high end of observed throughput for any given 5 second window (may need refinement).* 40 | 41 | # Running the workloads 42 | 43 | The scripts are separated into three phases: create Azure resources, build and run workloads, delete Azure resources. 44 | 45 | ## Create Azure resources 46 | 47 | The `scripts/create-resources.sh` will create all necessary Azure resources for all workloads measured above. Shared resources (e.g. Azure Container Registry) will be placed in 'azure-performance' resource group. A resource group is created for each Azure service and workload. E.g. 'azure-performance-cosmosdb-latency' and 'azure-performance-cosmosdb-throughput' for CosmosDB, 'azure-performance-redis-latency' and 'azure-performance-redis-throughput' for Redis, etc. The default location is West US 2. Usage: 48 | 49 | ```bash 50 | # Creates resources in West US 2 51 | ~$ ./create-resources.sh 52 | 53 | # The resource location and resource group name prefix can be specified 54 | ~$ ./create-resources.sh -l eastus -p perf-test 55 | ``` 56 | 57 | ## Run workloads 58 | 59 | The `scripts/run.sh` will build all projects, package them in Docker containers, push to the shared Azure Container Registry created in `create-resources.sh`, deploy the container to Azure Container Instances, and display the results after they complete. Usage: 60 | 61 | ```bash 62 | # Run all workloads with default parameters 63 | ~$ ./run.sh 64 | Starting CosmosDB latency workload ... 65 | Starting CosmosDB throughput workload ... 66 | ... 67 | Waiting for workloads to complete ... 68 | {"workload":"CosmosDB","type":"latency","elapsed":598994,"operations":5915,"errors":3,"latency":{"average":7.5846483685545332,"min":3.901,"max":675.52690000000007,"median":5.5326,"p25":5.1924333333333337,"p50":5.5326,"p75":5.9329,"p95":9.5226499999999739,"p99":59.322623333331926,"p999":546.89786566666635,"p9999":675.52690000000007}} 69 | {"workload":"CosmosDB","type":"throughput","elapsed":599003,"operations":792653,"errors":2,"throughput":{"overall":1323.2871955566166,"low":1271.81,"high":1367.4,"latency":23.673405639037512}} 70 | ... 71 | 72 | # The resource group name prefix, duration, container cpu and memory (GB) can be specified 73 | ~$ ./run.sh -p perf-test -s 600 -c 4 -m 1 74 | ``` 75 | 76 | ## Delete Azure resources 77 | 78 | The `scripts/delete-resources.sh` will delete all Azure resources created above. Usage: 79 | 80 | ```bash 81 | # Delete resource group 'azure-performance' for shared resources, 'azure-performance-cosmosdb-latency' and 'azure-performance-cosmosdb-throughput' for CosmosDB, etc. 82 | ~$ ./delete-resources.sh 83 | 84 | # The resource group name prefix can be specified (e.g. if using `create-resources.sh -p ` above) 85 | ~$ ./delete-resources.sh -p perf-test 86 | ``` 87 | 88 | ## Running individual workloads 89 | 90 | Each Azure service has a `create-resources.sh` and `run.sh` script that fully encapsulates creating the Azure resources and workload for that service. The `run.sh` script will build the project, package it into a Docker container, push it to an Azure Container Registry, then deploy the workload to Azure Container Instances. The instance will run for 60 seconds by default. View the container logs to see performance results, e.g. with `az container logs`. 91 | 92 | ```bash 93 | # Create resources with default name/parameters 94 | ~/src/sql$ ./create-resources.sh 95 | 96 | # Parameters for resource group, name, location, SQL parameters 97 | ~/src/sql$ ./create-resources.sh -g sql-test -n mysqlserver -l eastus -p '{ "sku": "P2" }' 98 | 99 | # Run workload with defaults 100 | ~/src/sql$ ./run.sh 101 | 102 | # Specify the Azure Container Registry (must already exist) 103 | ~/src/sql$ .run.sh -a myacr 104 | 105 | # Parameters for resource group, workload type (latency/throughput), threads, duration in seconds, container cpu and memory (GB) 106 | ~/src/sql$ ./run.sh -g sql-test -w latency -t 10 -s 60 -c 1 -m 1 107 | ~/src/sql$ ./run.sh -g sql-test -w throughput -t 32 -s 60 -c 4 -m 1 108 | 109 | # Delete the resource group to clean-up resources (default name is azure-performance-sql) 110 | ~/src/sql$ az group delete --name sql-test 111 | ``` 112 | --------------------------------------------------------------------------------