├── 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 |
--------------------------------------------------------------------------------