Initial check-in of sample logs data generator tool

This commit is contained in:
minwal 2021-02-21 12:35:40 +11:00
Коммит 5c0a94a64f
29 изменённых файлов: 5940 добавлений и 0 удалений

25
.github/workflows/dotnet.yml поставляемый Normal file
Просмотреть файл

@ -0,0 +1,25 @@
name: .NET
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup .NET core
uses: actions/setup-dotnet@v1.7.2
with:
dotnet-version: 3.1
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore
- name: Test
run: dotnet test --no-build --verbosity normal

351
.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,351 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
properties\launchSettings.json

Просмотреть файл

@ -0,0 +1,71 @@
{
"templateMetadata": {
"description": "Job template for Generator job"
},
"parameters": {
"jobName": {
"type": "string",
"metadata": {
"description": "Job name"
}
},
"poolId": {
"type": "string",
"metadata": {
"description": "Pool id"
}
},
"partitionStart": {
"type": "int",
"defaultValue": 0,
"metadata": {
"description": "Partition start index"
}
},
"partitionEnd": {
"type": "int",
"defaultValue": 6,
"metadata": {
"description": "Partition end index"
}
},
"size": {
"type": "string",
"defaultValue": "HundredTB",
"metadata": {
"description": "Size"
}
},
"outputContainerUrl": {
"type": "string",
"defaultValue": "YOUR BLOB CONNECTION STRING",
"metadata": {
"description": "Output container URL"
}
}
},
"job": {
"type": "Microsoft.Batch/batchAccounts/jobs",
"properties": {
"id": "[parameters('jobName')]",
"onAllTasksComplete": "terminateJob",
"poolInfo": {
"poolId": "[parameters('poolId')]"
},
"taskFactory": {
"type": "parametricSweep",
"parameterSets": [
{
"start": "[parameters('partitionStart')]",
"end": "[parameters('partitionEnd')]",
"step": 1
}
],
"repeatTask": {
"displayName": "Task for partition {0}",
"commandLine": "%AZ_BATCH_APP_PACKAGE_GENERATOR#1.0%/publish/BenchmarkLogGenerator.exe -output:AzureStorage -cc:\"[parameters('outputContainerUrl')]\" -size:[parameters('size')] -partition:{0}"
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,43 @@
{
"templateMetadata": {
"description": "Pool template for Generator app"
},
"parameters": {
"poolId": {
"type": "string",
"metadata": {
"description": "Id of Azure Batch pool"
}
},
"vmDedicatedCount": {
"type": "int",
"defaultValue": 7,
"metadata": {
"description": "The number of dedicated virtual machines"
}
}
},
"pool": {
"type": "Microsoft.Batch/batchAccounts/pools",
"apiVersion": "2019-03-01",
"properties": {
"id": "[parameters('poolId')]",
"virtualMachineConfiguration": {
"imageReference": {
"publisher": "MicrosoftWindowsServer",
"offer": "WindowsServer",
"sku": "2019-Datacenter"
},
"nodeAgentSKUId": "batch.node.windows amd64"
},
"vmSize": "Standard_D64_v3",
"targetDedicatedNodes": "[parameters('vmDedicatedCount')]",
"applicationPackageReferences": [
{
"applicationId": "GENERATOR",
"version": "1.0"
}
]
}
}
}

Просмотреть файл

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.Storage.Blobs" Version="12.4.2" />
<PackageReference Include="Microsoft.Azure.EventHubs" Version="4.2.0" />
</ItemGroup>
</Project>

25
BenchmarkLogGenerator.sln Normal file
Просмотреть файл

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29911.84
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchmarkLogGenerator", "BenchmarkLogGenerator.csproj", "{A2AC39C5-ABB4-4F3A-A404-1F62E0349AC6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A2AC39C5-ABB4-4F3A-A404-1F62E0349AC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A2AC39C5-ABB4-4F3A-A404-1F62E0349AC6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A2AC39C5-ABB4-4F3A-A404-1F62E0349AC6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A2AC39C5-ABB4-4F3A-A404-1F62E0349AC6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {98FD40EF-A52C-42D9-973F-182B6B3CB9E6}
EndGlobalSection
EndGlobal

9
CODE_OF_CONDUCT.md Normal file
Просмотреть файл

@ -0,0 +1,9 @@
# Microsoft Open Source Code of Conduct
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
Resources:
- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns

46
CommandLineArgs.cs Normal file
Просмотреть файл

@ -0,0 +1,46 @@
using BenchmarkLogGenerator.Utilities;
using System;
using System.Collections.Generic;
namespace BenchmarkLogGenerator
{
internal class CommandLineArgs
{
[CommandLineArg(
"output",
"Where the output should be written to. Valid options are: LocalDisk, AzureStorage or EventHub",
Mandatory = true)]
public WriterType outputType= WriterType.LocalDisk;
[CommandLineArg(
"localPath",
"The root folder for the output",
Mandatory = false)]
public string localPath = null;
[CommandLineArg(
"azureStorageAccountConnections",
"A comma separated list of Azure storage account connections",
ShortName = "cc", Mandatory = false)]
public string blobConnectionString = null;
[CommandLineArg(
"eventHubConnection",
"The connection string for Azure EventHub",
ShortName = "ehc", Mandatory = false)]
public string eventHubConnectionString = null;
[CommandLineArg(
"size",
"The output size, possible values are OneGB, OneTB, HundredTB",
Mandatory = false, DefaultValue = BenchmarkDataSize.OneGB)]
public BenchmarkDataSize size = BenchmarkDataSize.OneGB;
[CommandLineArg(
"partition",
"The partition id, possible values are -1 to 9, where -1 means single partition",
Mandatory = false, ShortName = "p", DefaultValue = -1)]
public int partition = -1;
}
}

163
Data/Logs.cs Normal file
Просмотреть файл

@ -0,0 +1,163 @@
namespace BenchmarkLogGenerator.Data
{
class Logs
{
public static readonly string[] IngestionLogs = new string[]
{
"AadValidator.ValidateAudienceImpl.{0}: {1} Audiences=https://{2}",
@"Audience 'https://{2}' matches the valid {0} audience regex '^https://[\w\.\-]+\.{1}\.windows\.net/?$'",
"AadValidator.ValidateIssuerImpl.{0}: Start {1} Issuer=https://{2}/",
"Gateway {0} resolved primary {1} for service 'fabric://management.admin.svc/ - ': 'net.tcp://{2}/mgmt'",
"ResponseStreamEncoder {0}: {1}: State='enabled' Reason='Accept-Encoding set to gzip' of https://{2}",
"OwinResponseStreamCompressor: State='enabled' Reason='Accept-Encoding set to deflate'",
"GetDataPullJobsOperation.OperationAsync.{0}: Downloaded '{1}' messages from https://{2} on different queues"
};
public const string IngestCommand = "$$IngestionCommand table={0} format={1}";
public const string DownloadEvent = "Downloading file path: {0}";
public const string IngestionCompletion = "IngestionCompletionEvent: finished ingestion file path: {0}";
public const string CompletedMessage = "Completion Report (HttpPost.ExecuteAsync): Completed with HttpResponseMessage StatusCode={0}";
public static readonly string[] FileFormats = new string[] { "csv", "json", "parquet", "avro" };
public static readonly string[] StatusCodes = new string[]
{
"'OK (200)'",
"'Request Timeout (408)'",
"'Internal Server Error (500)'"
};
//10
public static readonly string[] StackTraces = new string[]
{
@" at BenchmarkLogGenerator.Flows.BootFlow.GetLevel(Int64 v) in C:\Src\Tools\BenchmarkLogGenerator\Flows\BootFlow.cs:line 85",
@" at BenchmarkLogGenerator.Flows.BootFlow.<IngestionSession>d__1.MoveNext() in C:\Src\Tools\BenchmarkLogGenerator\Flows\BootFlow.cs:line 47",
@" at BenchmarkLogGenerator.Scheduler.Flow.NextStep() in C:\Src\Tools\BenchmarkLogGenerator\Scheduler.cs:line 74",
@" at BenchmarkLogGenerator.Scheduler.Step.EnqueueNextStep(Scheduler scheduler) in C:\Src\Tools\BenchmarkLogGenerator\Scheduler.cs:line 112",
@" at BenchmarkLogGenerator.Scheduler.FlowDelayStep.Execute(Scheduler scheduler) in C:\Src\Tools\BenchmarkLogGenerator\Scheduler.cs:line 137",
@" at BenchmarkLogGenerator.Scheduler.Run() in C:\Src\Tools\BenchmarkLogGenerator\Scheduler.cs:line 28",
@" at BenchmarkLogGenerator.Generator.Run(Int32 sizeFactor) in C:\Src\Tools\BenchmarkLogGenerator\Generator.cs:line 84",
@" at BenchmarkLogGenerator.Generator.<>c__DisplayClass26_0.<RunInBackground>b__0() in C:\Src\Tools\BenchmarkLogGenerator\Generator.cs:line 74",
@" at System.Threading.ThreadHelper.ThreadStart_Context(Object state)",
@" at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext)"
};
//104
public static readonly string[] ExceptionTypes = new string[]
{
"System.AccessViolation",
"System.AppDomainUnloaded",
"System.Argument",
"System.Arithmetic",
"System.ArrayTypeMismatch",
"System.BadImageFormat",
"System.CannotUnloadAppDomain",
"System.ContextMarshal",
"System.DataMisaligned",
"System.ExecutionEngine",
"System.Format",
"System.IndexOutOfRange",
"System.InsufficientExecutionStack",
"System.InvalidCast",
"System.InvalidOperation",
"System.InvalidProgram",
"System.MemberAccess",
"System.MulticastNotSupported",
"System.NotImplemented",
"System.NotSupported",
"System.NullReference",
"System.OperationCanceled",
"System.OutOfMemory",
"System.Rank",
"System.StackOverflow",
"System.Timeout",
"System.TypeInitialization",
"System.TypeLoad",
"System.TypeUnloaded",
"System.UnauthorizedAccess",
"System.UriTemplateMatch",
"System.Activities.Validation",
"System.Collections.Generic.KeyNotFound",
"System.ComponentModel.License",
"System.ComponentModel.Warning",
"System.ComponentModel.Design.Serialization.CodeDomSerializer",
"System.Configuration.Configuration",
"System.Configuration.Install.Install",
"System.Data.Data",
"System.Data.DBConcurrency",
"System.Data.OperationAborted",
"System.Data.OracleClient.Oracle",
"System.Data.SqlTypes.SqlType",
"System.Deployment.Application.Deployment",
"System.DirectoryServices.AccountManagement.Principal",
"System.Drawing.Printing.InvalidPrinter",
"System.EnterpriseServices.Registration",
"System.EnterpriseServices.ServicedComponent",
"System.IdentityModel.LimitExceeded",
"System.IdentityModel.SecurityMessageSerialization",
"System.IdentityModel.Tokens.SecurityToken",
"System.IO.InternalBufferOverflow",
"System.IO.InvalidData",
"System.IO.IO",
"System.Management.Management",
"System.Printing.Print",
"System.Reflection.AmbiguousMatch",
"System.Reflection.ReflectionTypeLoad",
"System.Resources.MissingManifestResource",
"System.Resources.MissingSatelliteAssembly",
"System.Runtime.InteropServices.External",
"System.Runtime.InteropServices.InvalidComObject",
"System.Runtime.InteropServices.InvalidOleVariantType",
"System.Runtime.InteropServices.MarshalDirective",
"System.Runtime.InteropServices.SafeArrayRankMismatch",
"System.Runtime.InteropServices.SafeArrayTypeMismatch",
"System.Runtime.Remoting.Remoting",
"System.Runtime.Remoting.Server",
"System.Runtime.Serialization.Serialization",
"System.Security.HostProtection",
"System.Security.Security",
"System.Security.Verification",
"System.Security.XmlSyntax",
"System.Security.Authentication.Authentication",
"System.Security.Cryptography.Cryptographic",
"System.Security.Policy.Policy",
"System.Security.Principal.IdentityNotMapped",
"System.ServiceModel.Dispatcher.InvalidBodyAccess",
"System.ServiceModel.Dispatcher.MultipleFilterMatches",
"System.ServiceProcess.Timeout",
"System.Threading.AbandonedMutex",
"System.Threading.SemaphoreFull",
"System.Threading.SynchronizationLock",
"System.Threading.ThreadAbort",
"System.Threading.ThreadInterrupted",
"System.Threading.ThreadStart",
"System.Threading.ThreadState",
"System.Transactions.Transaction",
"System.Web.Caching.DatabaseNotEnabledForNotification",
"System.Web.Caching.TableNotEnabledForNotification",
"System.Web.Management.SqlExecution",
"System.Web.Services.Protocols.Soap",
"System.Windows.Automation.ElementNotAvailable",
"System.Windows.Data.ValueUnavailable",
"System.Windows.Markup.XamlParse",
"System.Windows.Media.InvalidWmpVersion",
"System.Windows.Media.Animation.Animation",
"System.Workflow.Activities.EventDeliveryFailed",
"System.Workflow.Activities.WorkflowAuthorization",
"System.Workflow.Runtime.Hosting.Persistence",
"System.Workflow.Runtime.Tracking.TrackingProfileDeserialization",
"System.Xml.Xml",
"System.Xml.Schema.XmlSchema",
"System.Xml.XPath.XPath",
"System.Xml.Xsl.Xslt",
};
public readonly static string ExceptionHeader = @"Exception={0};
HResult=0x{1};
Message=exception happened;
Source=BenchmarkLogGenerator;
StackTrace:";
public const string CriticalMessage = "\"$$ALERT[NativeCrash]: Unexpected string size: 'single string size={0}, offsets array size={1}, string idx={2}, offsets32[idx+1]={3}, offsets32[idx]={4}'\"";
}
}

2367
Data/Names.cs Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

28
Enums.cs Normal file
Просмотреть файл

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace BenchmarkLogGenerator
{
public enum WriterType
{
EventHub,
LocalDisk,
AzureStorage
}
public enum BenchmarkDataSize
{
OneGB,
OneTB,
HundredTB
}
public enum Level
{
Information,
Warning,
Error,
Critical
}
}

199
Flows/IngestionFlow.cs Normal file
Просмотреть файл

@ -0,0 +1,199 @@
namespace BenchmarkLogGenerator.Flows
{
using BenchmarkLogGenerator.Data;
using Microsoft.Azure.Amqp;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Enumeration;
using System.Reflection.Metadata.Ecma335;
using System.Security.Claims;
using Step = Scheduler.Step;
public static class IngestionFlow
{
public static IEnumerable<Step> Generate(int index, int logsPerSessionFactor, int numSessionsFactor, string source)
{
var rng = Generator.Current.Rng;
//increase number of sessions by sizeFactor
int numSessions = numSessionsFactor*(4 * (80 + (rng.Next() % 1000)));
for (int i = 0; i < numSessions; ++i)
{
var cid = ToGuid(index, i);
yield return Gen.Sleep(TimeSpan.FromSeconds(rng.Next() % 64));
//traces
int rnd = rng.Next();
Gen.Spawn(IngestionSession(i, cid, rnd, rng, source, logsPerSessionFactor));
}
}
public static IEnumerable<Step> IngestionSession(int index, string cid, long rnd, Random rng, string source, int logsPerSessionFactor)
{
long stepLength = 6400000;
var format = Logs.FileFormats[rnd % 4];
//increase the number of logs per session by factor
int numFiles = logsPerSessionFactor * (5 + (int)rnd % 100);
List<string> files = new List<string>();
for (int i = 0; i < numFiles; i++)
{
var fileName = $"\"\"https://benchmarklogs3.blob.core.windows.net/benchmark/2014/{source}_{index}_{i}.{format}.gz\"\"";
files.Add(fileName);
}
yield return Gen.Sleep(TimeSpan.FromTicks(rnd % stepLength));
var rand1000 = rnd % 1000;
var node = $"Engine{(rand1000).ToString().PadLeft(12, '0')}";
var message = string.Format(Logs.IngestCommand, Names.Tables[rnd % 607], Logs.FileFormats[rnd%4]);
var filesArray = $"\"[{string.Join(",", files)}]\"";
bool isCritical = rng.Next() % 10000 <= 25;
bool isError = rng.Next() % 10000 <= 250;
int numTracesForSession = 50 + rng.Next() % 5000;
// first step send ingestion command
Gen.TraceInfo(node, Level.Information.ToString(), Names.ingestionComponents[0], cid, message, filesArray);
foreach (var file in files)
{
yield return Gen.Sleep(TimeSpan.FromTicks(rnd % stepLength));
Gen.TraceInfo(node, Level.Information.ToString(), Names.ingestionComponents[1], cid, string.Format(Logs.DownloadEvent, file), GetDownloadProperties(rnd));
//noise loop
int numIterations = (int)(numTracesForSession/files.Count);
for(int i=0; i <= numIterations; i++)
{
yield return Gen.Sleep(TimeSpan.FromTicks(rnd % stepLength));
rand1000 = rng.Next() % 1000;
node = $"Engine{(rand1000).ToString().PadLeft(12, '0')}";
var level = GetLevel(rand1000);
var component = Names.Components[rng.Next() % 128];
int messageIndex = (int)rng.Next() % 7;
message = GetMessage(messageIndex, new object[] { node, rand1000, node + "." + component + ".com" });
Gen.TraceInfo(node, level, component, cid, message, "");
}
yield return Gen.Sleep(TimeSpan.FromTicks(rnd % stepLength));
if (isError || isCritical)
{
//no ingestion - emit error trace
Gen.TraceInfo(node, Level.Error.ToString(), Names.Components[rng.Next() % 128], cid, GetException(rng.Next()), "");
}
else
{
//ingest
Gen.TraceInfo(node, Level.Information.ToString(), Names.ingestionComponents[2], cid, string.Format(Logs.IngestionCompletion, file), GetIngestionProperties(rnd, format));
}
}
yield return Gen.Sleep(TimeSpan.FromTicks(rnd % stepLength));
if (isCritical)
{
//emit critical trace
Gen.TraceInfo(node, Level.Critical.ToString(), Names.Components[rng.Next() % 128], cid, string.Format(Logs.CriticalMessage, rnd, rnd % 100000, rnd % 60000, rnd%40000, rnd%250000),"");
Gen.TraceInfo(node, Level.Information.ToString(), Names.ingestionComponents[3], cid, string.Format(Logs.CompletedMessage, Logs.StatusCodes[2]), "");
}
else if (isError)
{
Gen.TraceInfo(node, Level.Information.ToString(), Names.ingestionComponents[3], cid, string.Format(Logs.CompletedMessage, Logs.StatusCodes[1]), "");
}
else
{
Gen.TraceInfo(node, Level.Information.ToString(), Names.ingestionComponents[3], cid, string.Format(Logs.CompletedMessage, Logs.StatusCodes[0]), "");
}
}
private static string GetDownloadProperties(long rnd)
{
long size = (long)rnd % 10_000_000_000L;
long rowCount = (long)size / ((rnd % 1000) + 999);
double durationInSeconds = (size * 1.0) / (15 * 1024 * 1024) + (rnd % 17);
var duration = TimeSpan.FromSeconds(durationInSeconds).ToString();
return $"\"{{\"\"compressedSize\"\": {size},\"\"OriginalSize\"\": {size*8},\"\"downloadDuration\"\": \"\"{duration}\"\" }}\"";
}
private static string GetIngestionProperties(long rnd, string format)
{
long size = (long)rnd % 10_000_000_000L;
long rowCount = (long)size / ((rnd % 1000) + 999);
double durationInSeconds = (size * 1.0) / (15 * 1024 * 1024) + (rnd % 17);
double cpuTimeInSeconds = (size * 1.0) / (10 * 1024 * 1024) + (rnd % 110737);
var duration = TimeSpan.FromSeconds(durationInSeconds).ToString();
var cpuTime = TimeSpan.FromSeconds(cpuTimeInSeconds).ToString();
return $"\"{{\"\"size\"\": {size}, \"\"format\"\":\"\"{format}\"\", \"\"rowCount\"\":{rowCount}, \"\"cpuTime\"\":\"\"{cpuTime}\"\",\"\"duration\"\": \"\"{duration}\"\" }}\"";
}
private static string GetLevel(long v)
{
if (v <= 100)
return Level.Warning.ToString();
return Level.Information.ToString();
}
private static string GetMessage(int v, object[] replacments)
{
var m = Logs.IngestionLogs[v];
return string.Format(m, replacments);
}
private static string GetException(long v)
{
var startPosition = v % 10;
var numberOfStackSteps = 10 * startPosition;
List<string> steps = new List<string>() { string.Format(Logs.ExceptionHeader, Logs.ExceptionTypes[GetExceptionIndex(v)], (v % 10000).ToString().PadLeft(8, '0'))};
for (long i = startPosition; i<= numberOfStackSteps; i++ )
{
steps.Add(Logs.StackTraces[i % 10]);
}
return $"\"{string.Join(Environment.NewLine, steps.ToArray())}\"";
}
private static int GetExceptionIndex(long v)
{
if (v % 10 <= 4)
return (int)v % 5;
if (v % 10 >= 5 && v % 10 <= 6)
return (int)v % 40;
if (v % 10 == 7)
return (int)v % 80;
return (int)v % 104;
}
public static IEnumerable<Step> PeriodicTrace()
{
for (int i = 0; i < 100; i++)
{
yield return Gen.Sleep(TimeSpan.FromMinutes(1));
//Gen.TraceInfo("Generator: periodic trace");
}
}
public static string ToGuid(int index, int iterator)
{
ulong a;
ulong b;
unchecked
{
var x = unchecked((ulong)index);
var y = unchecked((ulong)iterator);
a = Scramble(Scramble(0x165667B19E3779F9UL, x), y);
b = Scramble(Scramble(a, y), x);
}
Span<byte> bytes = stackalloc byte[16];
BitConverter.TryWriteBytes(bytes.Slice(0, 8), a);
BitConverter.TryWriteBytes(bytes.Slice(8, 8), b);
return (new Guid(bytes)).ToString();
}
private static ulong Scramble(ulong h, ulong n)
{
unchecked
{
h += n * 0xC2B2AE3D27D4EB4FUL;
h = (h << 31) | (h >> 33);
h *= 0x9E3779B185EBCA87UL;
return h;
}
}
}
}

96
Generator.cs Normal file
Просмотреть файл

@ -0,0 +1,96 @@
using BenchmarkLogGenerator.Data;
using BenchmarkLogGenerator.Flows;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace BenchmarkLogGenerator
{
using Step = Scheduler.Step;
using Event = Scheduler.Event;
public static class Gen
{
public static void TraceInfo(string node, string level, string component, string cid, string message, string properties)
{
/* - TODO check if we need to generate the strings as invariant format */
var gen = Generator.Current;
var now = gen.Now;
Generator.Current.LogWriter.Write(
now,
Generator.Current.Source,
node,
level,
component,
cid,
message,
properties);
}
public static void CloseTracer()
{
Generator.Current.LogWriter.Close();
}
public static Step Sleep(TimeSpan duration)
{
return Generator.Current.Scheduler.DelayFlow(duration);
}
public static Event Spawn(IEnumerable<Step> steps)
{
return Generator.Current.Scheduler.ScheduleNewFlow(steps);
}
}
public sealed class Generator
{
public Scheduler Scheduler { get; private set; }
public int Seed { get; set; }
public int SessionCount { get; set; }
public Random Rng { get; private set; }
public LogWriter LogWriter { get; private set; }
public string Source;
public DateTime Now
{
get { return Scheduler.Now; }
}
[ThreadStatic] static Generator s_current;
public static Generator Current => s_current;
public static void Run(int index, LogWriter writer, int logsPerSessionFactor, int numSessionsFactor)
{
var gen = new Generator(index, writer);
gen.SetOnThread();
Gen.Spawn(IngestionFlow.Generate(index, logsPerSessionFactor, numSessionsFactor, gen.Source));
gen.Scheduler.Run();
Gen.CloseTracer();
}
public Generator(int seed, LogWriter writer)
{
Scheduler = new Scheduler(new DateTime(2014, 3, 8, 0, 0, 0, DateTimeKind.Utc));
Rng = new Random(5546548 + seed % 100);
Seed = seed;
Source = Names.Sources[seed % 1610] + seed;
LogWriter = writer;
LogWriter.Source = Source;
}
private void SetOnThread()
{
s_current = this;
}
}
}

21
LICENSE Normal file
Просмотреть файл

@ -0,0 +1,21 @@
MIT License
Copyright (c) Microsoft Corporation.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE

253
LogWriter.cs Normal file
Просмотреть файл

@ -0,0 +1,253 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Text;
using System.Threading;
using Azure.Storage.Blobs;
using BenchmarkLogGenerator.Utilities;
using Microsoft.Azure.EventHubs;
namespace BenchmarkLogGenerator
{
public abstract class LogWriter : IDisposable
{
public abstract void Write(DateTime timestamp, string source, string node, string level, string component, string cid, string message, string properties);
public abstract void Close();
public string Source { get; set; }
#region IDisposable Support
public abstract void Dispose(bool disposing);
public void Dispose()
{
Dispose(true);
}
#endregion
}
public class FileLogWriter : LogWriter
{
private const int c_maxFileSize = 100000;
private const string c_compressedSuffix = "csv.gz";
private static readonly byte[] s_newLine = Encoding.UTF8.GetBytes(Environment.NewLine);
private bool writeToStorage;
private int year, month, day, hour;
string localPath = string.Empty;
string fileName = string.Empty;
private MemoryStream activeFile;
private DirectoryInfo currentDirectory;
private string rootDirectory;
private int rowCount;
private BlobContainerClient blobContainerClient;
private string blobConnectionString;
private int fileCounter;
private string id;
private Stopwatch sw = new Stopwatch();
private bool disposedValue = false; // To detect redundant calls
public FileLogWriter(string targetPath, bool isAzureStorage, string writerId, BlobContainerClient client)
{
writeToStorage = isAzureStorage;
id = writerId;
if (writeToStorage)
{
rootDirectory = Directory.CreateDirectory(Directory.GetCurrentDirectory() + "\\temp").FullName;
blobConnectionString = targetPath;
blobContainerClient = client;
}
else
{
rootDirectory = targetPath;
}
sw.Start();
}
public override void Write(DateTime timestamp, string source, string node, string level, string component, string cid, string message, string properties)
{
try
{
if (year != timestamp.Year || month != timestamp.Month || day != timestamp.Day || hour != timestamp.Hour)
{
if (activeFile != null)
{
WriteFile();
activeFile.Close();
fileCounter++;
}
//update existing values
year = timestamp.Year;
month = timestamp.Month;
day = timestamp.Day;
hour = timestamp.Hour;
localPath = string.Join('\\', year, month.ToString("D2"), day.ToString("D2"), hour.ToString("D2"));
//Need new file
currentDirectory = Directory.CreateDirectory(string.Join('\\', rootDirectory, localPath));
activeFile = new MemoryStream();
}
else if (rowCount >= c_maxFileSize)
{
//compress file
WriteFile();
activeFile.Close();
fileCounter++;
activeFile = new MemoryStream();
rowCount = 0;
}
var log = string.Join(",", timestamp.FastToString(), source, node, level, component, cid, message, properties);
activeFile.Write(Encoding.UTF8.GetBytes(log));
activeFile.Write(s_newLine);
rowCount++;
}
catch (Exception ex)
{
var color = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Unexpected Error");
Console.WriteLine(ex.Message);
Console.ForegroundColor = color;
}
}
private void WriteFile()
{
var compressedFileName = GetFileName(currentDirectory.FullName, c_compressedSuffix);
if (File.Exists(compressedFileName))
{
File.Delete(compressedFileName);
}
using (FileStream compressedFileStream = File.Create(compressedFileName))
{
using (GZipStream compressionStream = new GZipStream(compressedFileStream, CompressionMode.Compress))
{
activeFile.Position = 0;
activeFile.CopyTo(compressionStream);
}
}
if (writeToStorage)
{
using (FileStream uploadFileStream = File.OpenRead(compressedFileName))
{
var fileName = GetFileName(localPath, c_compressedSuffix);
var blobClient = blobContainerClient.GetBlobClient(fileName);
//retry blob ingestion
bool ingestionDone = false;
while (!ingestionDone)
{
try
{
blobClient.Upload(uploadFileStream, true, new CancellationToken());
ingestionDone = true;
}
catch (Exception ex)
{
var color = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Blob upload error");
Console.WriteLine(ex.Message);
Console.ForegroundColor = color;
Thread.Sleep(100);
}
}
}
File.Delete(compressedFileName);
}
}
private string GetFileName(string root, string suffix)
{
return $"{root}\\{this.Source}_{fileCounter}.{suffix}";
}
public override void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
WriteFile();
activeFile.Dispose();
this.sw.Stop();
Console.WriteLine($"Last file closed: {this.id} time elapsed: {Math.Round(sw.Elapsed.TotalMinutes, 2)} minutes");
}
disposedValue = true;
}
}
public override void Close()
{
this.Dispose();
}
}
public class EventHubWriter : LogWriter
{
List<string> logs = new List<string>();
private bool disposedValue = false; // To detect redundant calls
int ehSendCounter;
// local root directory to write to
public string RootDirectory { get; set; }
//EventHub connection string
public string EventHubConnectionString { get; set; }
//Azure storage container connection string
public string AzureStorageContainerConnectionString { get; set; }
EventHubClient eventHubClient;
public EventHubWriter(string connectionString)
{
var builder = new EventHubsConnectionStringBuilder(connectionString)
{
TransportType = TransportType.Amqp,
OperationTimeout = TimeSpan.FromSeconds(120)
};
eventHubClient = EventHubClient.Create(builder);
}
public override void Write(DateTime timestamp, string source, string node, string level, string component, string cid, string message, string properties)
{
var log = string.Join(",", timestamp.FastToString(), source, node, level, component, cid, message, properties);
logs.Add(log);
if (logs.Count >= 2000)
{
ehSendCounter++;
SendToEventHub();
Console.WriteLine($"Message {ehSendCounter} sent");
logs = new List<string>();
}
}
private void SendToEventHub()
{
string recordString = string.Join(Environment.NewLine, logs);
EventData eventData = new EventData(Encoding.UTF8.GetBytes(recordString));
eventHubClient.SendAsync(eventData).Wait();
}
public override void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
SendToEventHub();
Console.WriteLine($"Final message to EH");
}
disposedValue = true;
}
}
public override void Close()
{
this.Dispose();
}
}
}

169
PriorityQueue.cs Normal file
Просмотреть файл

@ -0,0 +1,169 @@
using System;
using System.Collections.Generic;
namespace BenchmarkLogGenerator
{
public sealed class PriorityQueue<T> where T : IComparable<T>
{
private long m_count = long.MinValue;
private IndexedItem[] m_items;
private int m_size;
public PriorityQueue()
: this(16)
{
}
public PriorityQueue(int capacity)
{
m_items = new IndexedItem[capacity];
m_size = 0;
}
private bool IsHigherPriority(int left, int right)
{
return m_items[left].CompareTo(m_items[right]) < 0;
}
private int Percolate(int index)
{
if (index >= m_size || index < 0)
{
return index;
}
var parent = (index - 1) / 2;
while (parent >= 0 && parent != index && IsHigherPriority(index, parent))
{
// swap index and parent
var temp = m_items[index];
m_items[index] = m_items[parent];
m_items[parent] = temp;
index = parent;
parent = (index - 1) / 2;
}
return index;
}
private void Heapify(int index)
{
if (index >= m_size || index < 0)
{
return;
}
while (true)
{
var left = 2 * index + 1;
var right = 2 * index + 2;
var first = index;
if (left < m_size && IsHigherPriority(left, first))
{
first = left;
}
if (right < m_size && IsHigherPriority(right, first))
{
first = right;
}
if (first == index)
{
break;
}
// swap index and first
var temp = m_items[index];
m_items[index] = m_items[first];
m_items[first] = temp;
index = first;
}
}
public int Count => m_size;
public T Peek()
{
if (m_size == 0)
{
throw new InvalidOperationException("Heap empty");
}
return m_items[0].Value;
}
private void RemoveAt(int index)
{
m_items[index] = m_items[--m_size];
m_items[m_size] = default;
if (Percolate(index) == index)
{
Heapify(index);
}
if (m_size < m_items.Length / 4)
{
var temp = m_items;
m_items = new IndexedItem[m_items.Length / 2];
Array.Copy(temp, 0, m_items, 0, m_size);
}
}
public T Dequeue()
{
var result = Peek();
RemoveAt(0);
return result;
}
public void Enqueue(T item)
{
if (m_size >= m_items.Length)
{
var temp = m_items;
m_items = new IndexedItem[m_items.Length * 2];
Array.Copy(temp, m_items, temp.Length);
}
var index = m_size++;
m_items[index] = new IndexedItem { Value = item, Id = ++m_count };
Percolate(index);
}
public bool Remove(T item)
{
for (var i = 0; i < m_size; ++i)
{
if (EqualityComparer<T>.Default.Equals(m_items[i].Value, item))
{
RemoveAt(i);
return true;
}
}
return false;
}
private struct IndexedItem : IComparable<IndexedItem>
{
public T Value;
public long Id;
public int CompareTo(IndexedItem other)
{
var c = Value.CompareTo(other.Value);
if (c == 0)
{
c = Id.CompareTo(other.Id);
}
return c;
}
}
}
}

221
Program.cs Normal file
Просмотреть файл

@ -0,0 +1,221 @@
using Azure.Core;
using Azure.Storage.Blobs;
using BenchmarkLogGenerator.Utilities;
using System;
using System.Diagnostics;
using System.Threading.Tasks;
namespace BenchmarkLogGenerator
{
class Program
{
static CommandLineArgs m_args = new CommandLineArgs();
private static readonly string[] m_basicHelpHints = { "/?", "-?", "?", "/help", "-help", "help" };
static void Main(string[] args)
{
if (args.Length > 0)
{
if (m_basicHelpHints.SafeFastAny(h => string.Equals(h, args[0], StringComparison.OrdinalIgnoreCase)))
{
PrintUsage();
}
else
{
CommandLineArgsParser.Parse(args, m_args, null, true);
}
}
else
{
PrintUsage();
}
if (m_args.outputType == WriterType.LocalDisk && m_args.localPath == null
|| m_args.outputType == WriterType.EventHub && m_args.eventHubConnectionString == null
|| m_args.outputType == WriterType.AzureStorage && m_args.blobConnectionString == null)
{
CommandLineArgsParser.WriteHelpStringToConsoleAndQuit(new string[] { }, new CommandLineArgs(), $"The output type of {m_args.outputType} was specified without the corrosponding connection/path");
}
Console.WriteLine("Starting...");
string containerName = "";
BlobContainerClient container = null;
if(m_args.outputType == WriterType.AzureStorage)
{
try
{
string[] connectionStrings = m_args.blobConnectionString.Split(",");
string blobConnectionString = "";
string partitionName = "";
if (m_args.partition == -1)
{
blobConnectionString = connectionStrings[0];
}
else
{
blobConnectionString = connectionStrings[m_args.partition];
partitionName = $"-p{m_args.partition}";
}
var blobOptions = new BlobClientOptions();
blobOptions.Retry.MaxRetries = 3;
blobOptions.Retry.Mode = RetryMode.Exponential;
BlobServiceClient blobClient = new BlobServiceClient(blobConnectionString, blobOptions);
//BlobServiceClient blobClient = new BlobServiceClient(blobConnectionString);
containerName = $"logsbenchmark-{m_args.size}{partitionName}".ToLower();
var response = blobClient.CreateBlobContainer(containerName);
container = response.Value;
}
catch (Exception ex)
{
var color = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"Error creating container {containerName}. Please verify that the container does not exist");
Console.WriteLine($"Exception Message: {ex.Message}");
Console.ForegroundColor = color;
Environment.Exit(1);
}
}
if (m_args.outputType == WriterType.LocalDisk && m_args.size == BenchmarkDataSize.HundredTB)
{
Console.WriteLine("For 100TB data size, please use Azure storage outputType.");
Environment.Exit(0);
}
Stopwatch sw = new Stopwatch();
sw.Start();
int logsPerSessionFactor = GetLogsPerSessionFactor(m_args.size);
int numSessionsFactor = GetNumSessionsFactor(m_args.size);
int start = GetStart(m_args.partition, m_args.size);
int end = GetEnd(m_args.partition, m_args.size);
var res = Parallel.For(start, end, index =>
{
Generator.Run(index, GetWriter($"iteration count: {index} ", container), logsPerSessionFactor, numSessionsFactor);
});
sw.Stop();
Console.WriteLine($"Total time {sw.ElapsedMilliseconds} ms");
}
//Factors:
//OneTB 6X more sessions, 100X more sources
//HundredTB 10X more sessions, 10X more logs per session, 10X more sources
//Expected timespan period for logs:
//~1 day for 1GB
//~9 days for 1TB
//~90 days for 100TB
private static int GetNumSessionsFactor(BenchmarkDataSize size)
{
switch (size)
{
case BenchmarkDataSize.OneGB:
return 1;
case BenchmarkDataSize.OneTB:
return 6;
case BenchmarkDataSize.HundredTB:
return 60;
default:
return 1;
}
}
private static int GetLogsPerSessionFactor(BenchmarkDataSize size)
{
switch (size)
{
case BenchmarkDataSize.OneGB:
return 1;
case BenchmarkDataSize.OneTB:
return 1;
case BenchmarkDataSize.HundredTB:
return 1;
default:
return 1;
}
}
private static int GetStart(int partition, BenchmarkDataSize size)
{
if (size == BenchmarkDataSize.HundredTB && partition > -1)
{
return partition * 100;
}
return 0;
}
private static int GetEnd(int partition, BenchmarkDataSize size)
{
if (size == BenchmarkDataSize.HundredTB && partition > -1)
{
return (partition + 1) * 100 - 1;
}
return NumThreads(size) * NumIterations(size);
}
private static int NumThreads(BenchmarkDataSize size)
{
switch (size)
{
case BenchmarkDataSize.OneGB:
return 1;
case BenchmarkDataSize.OneTB:
return 100;
case BenchmarkDataSize.HundredTB:
return 100;
default:
return 100;
}
}
private static int NumIterations(BenchmarkDataSize size)
{
switch (size)
{
case BenchmarkDataSize.OneGB:
return 1;
case BenchmarkDataSize.OneTB:
return 1;
case BenchmarkDataSize.HundredTB:
return 10;
default:
return 1;
}
}
private static void PrintUsage()
{
var esb = new ExtendedStringBuilder();
esb.AppendLine();
esb.AppendLine("The BenchmarkLogGenerator is a tool to generate logs for benchmark testing");
esb.AppendLine();
esb.AppendLine("It is invoked with the following parameters:");
esb.AppendLine("-output:Where the output should be written to. Possible values are: LocalDisk, AzureBlobStorage or EventHub");
esb.AppendLine("-localPath: The root folder");
esb.AppendLine("-azureStorageAccountConnections: A comma separated list of Azure storage account connections (can be single connection), containers will be created automaticly using the following template: logsBenchmark-{size}-p{partition}");
esb.AppendLine("-eventHubConnection: The connection string for Azure EventHub");
esb.AppendLine("-size: The output size, possible values are OneGB, OneTB, HundredTB. Default is OneGB");
esb.AppendLine("-partition: The applicable partition, between -1 to 9, where -1 means single partition. Only relevant for HundredTB size. Default is -1");
esb.AppendLine();
CommandLineArgsParser.PrintUsage(esb);
}
private static LogWriter GetWriter(string writerId, BlobContainerClient container)
{
switch (m_args.outputType)
{
case WriterType.LocalDisk:
return new FileLogWriter(m_args.localPath, false, writerId, null);
case WriterType.AzureStorage:
return new FileLogWriter(m_args.blobConnectionString, true, writerId, container);
case WriterType.EventHub:
return new EventHubWriter(m_args.eventHubConnectionString);
default:
return null;
}
}
}
}

Просмотреть файл

@ -0,0 +1,8 @@
{
"profiles": {
"BenchmarkLogGenerator": {
"commandName": "Project",
"commandLineArgs": "-output:AzureStorage -cc:\"DefaultEndpointsProtocol=https;AccountName=STORAGE_ACCOUNTNAME;AccountKey=KEY;EndpointSuffix=core.windows.net\" "
}
}
}

14
README.md Normal file
Просмотреть файл

@ -0,0 +1,14 @@
# Contributing
This project welcomes contributions and suggestions. Most contributions require you to agree to a
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
When you submit a pull request, a CLA bot will automatically determine whether you need to provide
a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions
provided by the bot. You will only need to do this once across all repos using our CLA.
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.

41
SECURITY.md Normal file
Просмотреть файл

@ -0,0 +1,41 @@
<!-- BEGIN MICROSOFT SECURITY.MD V0.0.5 BLOCK -->
## Security
Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below.
## Reporting Security Issues
**Please do not report security vulnerabilities through public GitHub issues.**
Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report).
If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc).
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc).
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
* Full paths of source file(s) related to the manifestation of the issue
* The location of the affected source code (tag/branch/commit or direct URL)
* Any special configuration required to reproduce the issue
* Step-by-step instructions to reproduce the issue
* Proof-of-concept or exploit code (if possible)
* Impact of the issue, including how an attacker might exploit the issue
This information will help us triage your report more quickly.
If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs.
## Preferred Languages
We prefer all communications to be in English.
## Policy
Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd).
<!-- END MICROSOFT SECURITY.MD BLOCK -->

318
Scheduler.cs Normal file
Просмотреть файл

@ -0,0 +1,318 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace BenchmarkLogGenerator
{
public class Scheduler
{
private PriorityQueue<Step> m_queue;
public DateTime Now { get; private set; }
public Scheduler(DateTime now)
{
m_queue = new PriorityQueue<Step>(1024);
Now = now;
}
public void Run()
{
while (m_queue.Count != 0)
{
var step = m_queue.Dequeue();
if (step.DueTime > Now)
{
Now = step.DueTime;
}
step.Execute(this);
}
}
public Event ScheduleNewFlow(IEnumerable<Step> steps)
{
var completionEvent = NewEvent();
var flow = new Flow(steps.GetEnumerator(), completionEvent);
var step = new FlowDelayStep(DateTimeUtil.Zero, flow);
Enqueue(step);
return completionEvent;
}
public Step DelayFlow(TimeSpan duration)
{
return new FlowDelayStep(DateTimeUtil.Add(Now, duration));
}
public Event NewEvent()
{
return new Event(this);
}
private void Enqueue(Step step)
{
if (step == null)
{
return;
}
m_queue.Enqueue(step);
}
internal sealed class Flow
{
private IEnumerator<Step> m_steps;
private Event m_completionEvent;
internal Flow(IEnumerator<Step> steps, Event completionEvent)
{
m_steps = steps;
m_completionEvent = completionEvent;
}
public Step NextStep()
{
if (m_steps.MoveNext())
{
var step = m_steps.Current;
step.Flow = this;
return step;
}
else
{
m_completionEvent.Signal();
return null;
}
}
}
public abstract class Step : IComparable<Step>
{
public DateTime DueTime { get; private set; }
internal Flow Flow { get; set; }
public Step(DateTime dueTime)
{
DueTime = dueTime;
}
public abstract void Execute(Scheduler scheduler);
public int CompareTo(Step other)
{
return DueTime.CompareTo(other.DueTime);
}
protected void EnqueueNextStep(Scheduler scheduler)
{
if (Flow == null)
{
return;
}
var step = Flow.NextStep();
if (step == null)
{
return;
}
// TODO: fast path (avoid enqueue)
scheduler.Enqueue(step);
}
}
internal sealed class FlowDelayStep : Step
{
public FlowDelayStep(DateTime dueTime) : base(dueTime)
{
}
public FlowDelayStep(DateTime dueTime, Flow flow) : base(dueTime)
{
Flow = flow;
}
public override void Execute(Scheduler scheduler)
{
EnqueueNextStep(scheduler);
}
}
internal sealed class BlockOnEventStep : Step
{
private Event m_event;
public BlockOnEventStep(Event evt) : base(DateTimeUtil.Zero)
{
m_event = evt;
}
public override void Execute(Scheduler scheduler)
{
if (m_event.IsSet)
{
EnqueueNextStep(scheduler);
}
else
{
m_event.AddFlow(Flow);
}
}
}
public class Event
{
public bool IsSet { get; private set; }
private Scheduler m_scheduler;
private List<Flow> m_blockedFlows;
private List<Event> m_chainedEvents;
public Event(Scheduler scheduler)
{
m_scheduler = scheduler;
m_blockedFlows = null;
m_chainedEvents = null;
}
public Step Wait()
{
return new BlockOnEventStep(this);
}
public static Event WhenAny(params Event[] events)
{
var scheduler = events[0].m_scheduler;
var whenAny = new Event(scheduler);
foreach (var evt in events)
{
evt.AddChained(whenAny);
}
return whenAny;
}
public static Event WhenAll(params Event[] events)
{
var scheduler = events[0].m_scheduler;
var whenAll = new WhenAllEvent(scheduler, events.Length);
foreach (var evt in events)
{
evt.AddChained(whenAll);
}
return whenAll;
}
internal void AddFlow(Flow flow)
{
if (IsSet)
{
m_scheduler.Enqueue(flow.NextStep());
}
else
{
AddBlockedFlow(flow);
}
}
public void AddChained(Event evt)
{
if (IsSet)
{
evt.Signal();
}
else
{
AddChainedEvent(evt);
}
}
public virtual void Signal()
{
if (!IsSet)
{
TransitionToSet();
}
}
protected void TransitionToSet()
{
IsSet = true;
if (m_chainedEvents != null)
{
foreach (var evt in m_chainedEvents)
{
evt.Signal();
}
m_chainedEvents = null;
}
if (m_blockedFlows != null)
{
foreach (var flow in m_blockedFlows)
{
m_scheduler.Enqueue(flow.NextStep());
}
m_blockedFlows = null;
}
}
private void AddBlockedFlow(Flow flow)
{
if (m_blockedFlows == null)
{
m_blockedFlows = new List<Flow>();
}
m_blockedFlows.Add(flow);
}
private void AddChainedEvent(Event evt)
{
if (m_chainedEvents == null)
{
m_chainedEvents = new List<Event>();
}
m_chainedEvents.Add(evt);
}
}
public sealed class WhenAllEvent : Event
{
private int m_count;
public WhenAllEvent(Scheduler scheduler, int count) : base(scheduler)
{
m_count = count;
}
public override void Signal()
{
if (!IsSet)
{
Debug.Assert(m_count != 0);
m_count -= 1;
if (m_count == 0)
{
TransitionToSet();
}
}
}
}
}
public static class DateTimeUtil
{
public static DateTime Zero
{
get
{
return new DateTime(0, DateTimeKind.Utc);
}
}
public static DateTime Add(DateTime dt, TimeSpan ts)
{
return DateTime.SpecifyKind(dt.Add(ts), DateTimeKind.Utc);
}
}
}

Просмотреть файл

@ -0,0 +1,647 @@
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace BenchmarkLogGenerator.Utilities
{
#region class CommandLineArgAttribute
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
public class CommandLineArgAttribute : Attribute
{
/// <summary>
/// The short name of the command-line switch.
/// </summary>
public string ShortName;
/// <summary>
/// Additional alises of the command-line switch.
/// </summary>
public string[] Aliases;
/// <summary>
/// The long name of the command-line switch.
/// </summary>
public string FullName;
/// <summary>
/// A human-readable description of the comand-line switch.
/// </summary>
public string Description;
/// <summary>
/// Default value to be used if the user doesn't specify the command-line switch.
/// </summary>
public object DefaultValue;
/// <summary>
/// Is this a mandatory switch?
/// </summary>
public bool Mandatory;
/// <summary>
/// Is this argument a secret? (If so, we won't print it.)
/// </summary>
public bool IsSecret;
/// <summary>
/// Does this switch contain "known" secrets? (If so, we don't print them.)
/// Known secrets are secrets whose pattern is recognized by <see cref="Obfuscator.RemoveKnownSecrets(string)"/>.
/// </summary>
public bool ContainsKnownSecrets;
/// <summary>
/// Does this switch support encryption? (If so, it can be attempted at being decrypted after parsing, using the 'Decrypt()' method)
/// </summary>
public bool SupportsEncryption;
/// <summary>
/// Trigger -- the name of a parameterless instance method that will get invoked
/// following processing of the field.
/// </summary>
public string WhenSet;
/// <summary>
/// Can the user include the switch without providing a value?
/// Applies only to string switches.
/// </summary>
public bool AllowNull;
public CommandLineArgAttribute(string fullName, string description)
{
ShortName = fullName;
FullName = fullName;
Description = description;
}
public CommandLineArgAttribute(string fullName, string description, params string[] aliases)
{
ShortName = fullName;
FullName = fullName;
Description = description;
Aliases = aliases;
}
public IEnumerable<string> GetShortNameAndAliases()
{
// TODO: We might want to cache this...
HashSet<string> ret = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
ret.Add(FullName);
if (!string.IsNullOrWhiteSpace(ShortName))
{
ret.Add(ShortName);
}
if (Aliases.SafeFastAny())
{
foreach (var alias in Aliases)
{
if (!string.IsNullOrWhiteSpace(alias))
{
ret.Add(alias);
}
}
}
ret.Remove(FullName);
return ret;
}
}
#endregion
#region class CommandLineArgsParser
public class CommandLineArgsParser
{
#region Private constants
private static char[] c_quote = new char[] { '"' };
private static char c_multiValueSeparator = '\x03';
private static string c_multiValueSeparatorStr = "\x03";
private static char[] c_multiValueSeparatorArray = new[] { c_multiValueSeparator };
private const BindingFlags WhenXxxLookup = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
#endregion
public static T Parse<T>(IEnumerable<string> args, T target, Action<string[], T, string> faultAction = null, bool autoHelp = false, string envVar = null)
{
if (autoHelp && faultAction == null)
{
faultAction = WriteHelpStringToConsoleAndQuit;
}
LinkedList<string> freeArgs = null; // All arguments that appear before the first arg with '/' or '-'
Dictionary<string, string> candidateArgs = null;
GetFreeAndCandidateArgs(args, envVar, ref freeArgs, ref candidateArgs);
if (autoHelp)
{
if (candidateArgs.ContainsKey("h") || candidateArgs.ContainsKey("?") || candidateArgs.ContainsKey("help"))
{
WriteHelpStringToConsoleAndQuit(args, target, null, markdown: false);
}
}
AssignArgsToTargetGetValue(args, target, faultAction, freeArgs, candidateArgs);
return target;
}
public static void WriteHelpStringToConsoleAndQuit<T>(string[] args, T target, string fault)
{
WriteHelpStringToConsoleAndQuit(args, target, fault, markdown: false);
}
public static void WriteHelpStringToConsoleAndQuit<T>(string[] args, T target, string fault, bool markdown)
{
WriteHelpStringToConsoleAndQuit<T>(args as IEnumerable<string>, target, fault, markdown);
}
private static void WriteHelpStringToConsoleAndQuit<T>(IEnumerable<string> args, T target, string fault, bool markdown)
{
var esb = new ExtendedStringBuilder();
if (!string.IsNullOrWhiteSpace(fault))
{
esb.Indent();
esb.AppendLine("Bad input:");
esb.Indent();
esb.AppendLine(fault);
esb.AppendLine();
esb.Unindent();
esb.Unindent();
}
CommandLineArgsParser.WriteHelpString(esb, target);
Console.WriteLine(esb.ToString());
Environment.Exit(0);
}
/// <summary>
/// Given one or more objects of a type whose public fields are attributed
/// by <see cref="CommandLineArgAttribute"/> (and the first of said objects
/// potentially attributed by <see cref="CommandLineArgsAttribute"/>),
/// writes a corresponding help string to the string builder.
/// </summary>
public static void WriteHelpString(ExtendedStringBuilder esb, params object[] targets)
{
// Gather all the args
var names = new HashSet<string>();
var args = new List<Tuple<CommandLineArgAttribute, FieldInfo>>();
var descriptions = new List<string>();
GatherArgsForHelpString(targets, names, args, descriptions);
// Synopsis line
esb.Indent();
esb.AppendLine("Synopsis:");
esb.Indent();
var attributes = string.Join(" ", args.Select(arg => FormatArg(arg, false)));
esb.AppendLine(attributes);
esb.Unindent();
esb.AppendLine();
// Description
if (descriptions.Count > 0)
{
esb.AppendLine("Description:");
esb.Indent();
// TODO: Smart algorithm to break lines might be added here...
foreach (var description in descriptions)
{
foreach (var line in description.SplitLines())
{
esb.AppendLine(line);
}
}
esb.Unindent();
esb.AppendLine();
}
// Arguments
esb.AppendLine("Arguments:");
bool firstArgument = true;
esb.Indent();
foreach (var arg in args)
{
if (firstArgument)
{
firstArgument = false;
}
else
{
esb.AppendLine();
}
if (arg.Item1.Mandatory)
{
esb.AppendLine("[Mandatory: True]");
}
var fullName = arg.Item1.FullName;
var aliases = arg.Item1.GetShortNameAndAliases();
esb.AppendLine(FormatArg(arg, true));
foreach (var alias in aliases)
{
esb.AppendLine("[-" + alias + ":...]");
}
esb.Indent();
foreach (var line in arg.Item1.Description.SplitLines())
{
esb.AppendLine(line); // TODO: break long lines
}
esb.Unindent();
}
if (!firstArgument)
{
esb.AppendLine();
}
esb.Unindent();
esb.Unindent();
PrintUsage(esb);
}
public static void PrintUsage(ExtendedStringBuilder esb)
{
esb.AppendLine("Usage examples:");
esb.AppendLine();
esb.Indent();
esb.AppendLine(@"[Write to local disk]");
esb.AppendLine(@"BenchmarkLogGenerator -output:LocalDisk -localPath:""c:\users\foo\documents""");
esb.AppendLine();
esb.AppendLine(@"[Write to Azure Storage container]");
esb.AppendLine(@"BenchmarkLogGenerator -output:AzureStorage -azureStorageAccountConnection:""DefaultEndpointsProtocol=https;AccountName=NAME;AccountKey=KEY;EndpointSuffix=core.windows.net""");
esb.AppendLine();
esb.AppendLine(@"[Write to EventHub]");
esb.AppendLine(@"BenchmarkLogGenerator -output:EventHub -eventHubConnection:""Endpoint=sb://EVENTHUB_NAMESPACE.windows.net/;SharedAccessKeyName=readwrite;SharedAccessKey=KEY""");
esb.AppendLine();
Console.WriteLine(esb.ToString());
Environment.Exit(1);
}
private static void GatherArgsForHelpString(object[] targets, HashSet<string> names, List<Tuple<CommandLineArgAttribute, FieldInfo>> args, List<string> descriptions)
{
foreach (var target in targets)
{
GatherArgsForHelpString(target, names, args, descriptions);
}
}
private static void GatherArgsForHelpString(object target, HashSet<string> names, List<Tuple<CommandLineArgAttribute, FieldInfo>> args, List<string> descriptions)
{
// Desciption at the target level
var targetAttribute = target.GetType().GetCustomAttribute<CommandLineArgAttribute>();
if (targetAttribute != null && !string.IsNullOrWhiteSpace(targetAttribute.Description))
{
descriptions.Add(targetAttribute.Description);
}
int insertIndex = 0;
foreach (var field in target.GetType().GetFieldsOrdered())
{
var attribute = (CommandLineArgAttribute)field.GetCustomAttribute(typeof(CommandLineArgAttribute));
if (attribute == null)
{
continue;
}
if (field.FieldType.GetCustomAttribute(typeof(CommandLineArgAttribute)) != null)
{
// Nested
GatherArgsForHelpString(field.GetValue(target), names, args, descriptions);
continue;
}
var item = new Tuple<CommandLineArgAttribute, FieldInfo>(attribute, field);
if (string.IsNullOrWhiteSpace(attribute.FullName))
{
args.Insert(insertIndex, item);
insertIndex++;
}
else
{
if (!names.Contains(attribute.FullName))
{
names.Add(attribute.FullName);
args.Add(item);
}
else
{
// TODO: We should add validation logic here, or add a disambiguation, or...
}
}
}
}
private static void GetFreeAndCandidateArgs(IEnumerable<string> args, string envVar, ref LinkedList<string> freeArgs, ref Dictionary<string, string> candidateArgs)
{
freeArgs = freeArgs ?? new LinkedList<string>(); // All arguments that appear before the first arg with '/' or '-'
candidateArgs = candidateArgs ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
bool acceptingFreeArgs = true;
// Make a list of all arg candidates
foreach (var arg in args)
{
string a = arg.Trim();
if (!StartsWithSwitchCharacter(a))
{
if (acceptingFreeArgs)
{
freeArgs.AddLast(a);
}
continue;
}
acceptingFreeArgs = false;
a = a.Substring(1);
string value;
a = a.SplitFirst(':', out value);
if (string.IsNullOrEmpty(a))
{
continue;
}
if (candidateArgs.ContainsKey(a))
{
// This is a hack to support multi-valued args:
// When we encounter more than one value for a switch, we concat the old value with
// the new value using a control character nobody would provide normally.
candidateArgs[a] = candidateArgs[a] + c_multiValueSeparator + value;
continue;
}
candidateArgs.Add(a, value);
}
}
private static bool StartsWithSwitchCharacter(string what)
{
if (string.IsNullOrEmpty(what))
{
return false;
}
char c = what[0];
// The last one is a Unicode dash, which is often generated by auto-correct tools
// such as Microsoft Word
return c == '-' || c == '/' || c == '\u2013';
}
private static void AssignArgsToTargetGetValue<T>(IEnumerable<string> args, T target, Action<string[], T, string> faultAction, LinkedList<string> freeArgs, Dictionary<string, string> candidateArgs)
{
var targetType = target.GetType();
foreach (var field in targetType.GetFieldsOrdered())
{
var attribute = (CommandLineArgAttribute)field.GetCustomAttribute(typeof(CommandLineArgAttribute));
if (attribute == null)
{
continue;
}
var aliases = attribute.GetShortNameAndAliases();
bool needsSpecifying = attribute.Mandatory;
string value;
if (field.FieldType.GetCustomAttribute(typeof(CommandLineArgAttribute)) != null)
{
var fieldValue = field.GetValue(target);
if (fieldValue != null)
{
// Nested parse
// TODO: This is typeless (T==object), ignoring faultAction and/or autoHelp
// TODO: This is done without any prefix on nested arg names
var trampolineT = typeof(CommandLineArgsParser).GetMethod("ParseTrampoline", BindingFlags.NonPublic | BindingFlags.Static);
if (trampolineT != null)
{
// private static void ParseTrampoline<T>(IEnumerable<string> args, T target)
// TODO: Add support for autoHelp, envVar to ParseTrampoline<T> and add them here
var trampoline = trampolineT.MakeGenericMethod(new Type[] { fieldValue.GetType() });
trampoline.Invoke(null, new object[] { args, fieldValue });
}
else
{
// Fallback to typeless (T is System.Object) with no faultAction and default autoHelp.
// TODO: This is done without any prefix on nested arg names
Parse(args, fieldValue);
}
}
}
else if (string.IsNullOrWhiteSpace(attribute.FullName) && freeArgs.Count > 0)
{
// A switch with no name is assumed to refer to the "free args",
// and we have some unconsumed free args, so the next one is used
// to assign a valur to the switch
value = freeArgs.First();
freeArgs.RemoveFirst();
needsSpecifying = false;
SetField(target, field, "[free arg]", value, allowNull: false);
}
else if (!string.IsNullOrWhiteSpace(attribute.FullName) && TryGetValue(candidateArgs, attribute.FullName, aliases, out value))
{
needsSpecifying = false;
SetField(target, field, attribute.FullName, value, allowNull: attribute.AllowNull);
}
else if (attribute.DefaultValue != null)
{
needsSpecifying = false;
field.SetValue(target, attribute.DefaultValue);
}
if (needsSpecifying)
{
var fault = string.Format("Argument '" + attribute.FullName + "' must be specified as it is marked as mandatory");
if (faultAction != null)
{
var argsArray = args.ToArray();
faultAction(argsArray, target, fault);
}
}
if (attribute.WhenSet != null)
{
var method = targetType.GetMethod(attribute.WhenSet, WhenXxxLookup);
//Ensure.ArgIsNotNull(method, "method(" + attribute.WhenSet + ")"); // TODO: Could use a better exception here...
method.Invoke(target, null);
}
}
}
private static void SetField(object target, FieldInfo field, string switchName, string value, bool allowNull)
{
// Support for nullable types - get the real field type
var fieldType = field.FieldType.GetTypeWithNullableSupport();
// TODO: Each call to Parse below may throw an exception.
// Since this is user-input, we should catch the exception and
// provide some means to indicate a user input error instead
// of crashing the caller.
try
{
if (fieldType == typeof(string))
{
value = GetLastValueIfMultivalue(value);
if (allowNull && value == null)
{
field.SetValue(target, null);
}
else
{
VerifyNoNullValue(switchName, value);
VerifyNoMultiValue(switchName, value);
field.SetValue(target, value.Trim(c_quote));
}
}
else if(fieldType == typeof(WriterType))
{
value = GetLastValueIfMultivalue(value);
VerifyNoNullValue(switchName, value);
VerifyNoMultiValue(switchName, value);
field.SetValue(target, Enum.Parse(typeof(WriterType), value, true));
}
else if (fieldType == typeof(BenchmarkDataSize))
{
value = GetLastValueIfMultivalue(value);
VerifyNoNullValue(switchName, value);
VerifyNoMultiValue(switchName, value);
field.SetValue(target, Enum.Parse(typeof(BenchmarkDataSize), value, true));
}
else if (fieldType == typeof(int))
{
value = GetLastValueIfMultivalue(value);
VerifyNoNullValue(switchName, value);
field.SetValue(target, int.Parse(value));
}
}
catch (Exception)
{
WriteHelpStringToConsoleAndQuit(new string[] { }, target, string.Format("CommandLineArgsParser failed to parse argument '{0}' of type '{1}' with value '{2}'",
switchName, field.FieldType.ToString(), value));
}
}
private static void VerifyNoNullValue(string switchName, string value)
{
if (value == null)
{
throw new ArgumentNullException(string.Format("No value provided for switch -{0}. Please use the format: -{0}:VALUE", switchName));
}
}
private static void VerifyNoMultiValue(string switchName, string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return;
}
if (value.Contains(c_multiValueSeparator))
{
int count = value.Split(c_multiValueSeparator).Length;
throw new ArgumentOutOfRangeException(switchName,
count.ToString() + " appearences of the switch -" + switchName + ": expecting at most one.");
}
}
private static string GetLastValueIfMultivalue(string value)
{
if (string.IsNullOrEmpty(value) || value.IndexOf(c_multiValueSeparator) < 0)
{
return value;
}
var values = value.Split(c_multiValueSeparatorArray);
return values[values.Length - 1];
}
private static string FormatArg(Tuple<CommandLineArgAttribute, FieldInfo> pair, bool includeDefaultValue)
{
var attribute = pair.Item1;
var fieldInfo = pair.Item2;
var ret = new System.Text.StringBuilder();
if (!attribute.Mandatory)
{
ret.Append('[');
}
bool hasName = false;
if (!string.IsNullOrWhiteSpace(attribute.FullName))
{
ret.Append('-');
ret.Append(attribute.FullName);
hasName = true;
}
// Support for nullable types - get the real field type
var fieldType = fieldInfo.FieldType.GetTypeWithNullableSupport();
if (typeof(IEnumerable<string>).IsAssignableFrom(fieldType))
{
ret.Append(hasName ? ":string*" : "string*");
}
else if (fieldType == typeof(bool))
{
ret.Append(hasName ? "[:true-or-false]" : "[true-or-false]");
}
else
{
ret.Append((hasName ? ":" : "") + fieldType.Name);
}
if (includeDefaultValue && attribute.DefaultValue != null)
{
var defaultValueAsString = attribute.DefaultValue.ToString();
if (!string.IsNullOrWhiteSpace(defaultValueAsString))
{
ret.Append(" (default is: " + attribute.DefaultValue.ToString() + ")");
}
}
if (!attribute.Mandatory)
{
ret.Append(']');
}
return ret.ToString();
}
private static bool TryGetValue(Dictionary<string, string> candidateArgs, string fullName, IEnumerable<string> aliases, out string value)
{
string v;
value = null;
var ret = false;
if (candidateArgs.TryGetValue(fullName, out v))
{
value = v; // No need for string.Join here
ret = true;
}
foreach (var alias in aliases)
{
if (candidateArgs.TryGetValue(alias, out v))
{
if (ret == false)
{
value = v;
}
else
{
value = string.Join(c_multiValueSeparatorStr, v, value);
}
ret = true;
}
}
return ret;
}
}
#endregion
}

Просмотреть файл

@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace BenchmarkLogGenerator.Utilities
{
public static class ExtendedArray
{
public static T[] SlowRemoveByIndex<T>(this T[] array, int index)
{
if (array == null || array.Length == 0 || index < 0 || index >= array.Length)
{
// Nothing really to do
return array;
}
if (array.Length == 1 && index == 0)
{
// Just removed the last element
return new T[0];
}
var ret = new T[array.Length - 1];
// We could use two Array.Copy calls, but this is simpler
// (and easier to prove is right):
for (int i = 0, j = 0; i < array.Length; i++)
{
if (i != index)
{
ret[j] = array[i];
j++;
}
}
return ret;
}
}
}

Просмотреть файл

@ -0,0 +1,285 @@
// ----------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Runtime.Serialization;
namespace BenchmarkLogGenerator.Utilities
{
public static class ExtendedDateTime
{
#region Public constants
/// <summary>
/// The min value of a DateTime, in UTC.
/// </summary>
public static readonly DateTime MinValueUtc = new DateTime(DateTime.MinValue.Ticks, DateTimeKind.Utc);
/// <summary>
/// The max value of a DateTime, in UTC.
/// </summary>
public static readonly DateTime MaxValueUtc = new DateTime(DateTime.MaxValue.Ticks, DateTimeKind.Utc);
/// <summary>
/// A list of datetime formats which we support but aren't supported by the default IFormatProvider
/// which we use for DateTime.Parse.
/// </summary>
public static readonly Dictionary<int, string[]> SupportedNonStandardFormats = new Dictionary<int, string[]>()
{
{ 4, new [] { "yyyy" } },
{ 6, new [] { "yyyyMM" } },
{ 8, new [] { "yyyyMMdd" } },
{ 10, new [] { "yyyyMMddHH" } },
{ 12, new [] { "yyyyMMddHHmm" } },
{ 14, new [] { "yyyyMMddHHmmss" } },
{ 17, new [] { "yyyyMMdd HH:mm:ss" } },
{ 19, new [] { "yyyyMMdd HH:mm:ss.f" } },
{ 20, new [] { "yyyyMMdd HH:mm:ss.ff" } },
{ 21, new [] { "yyyyMMdd HH:mm:ss.fff" } },
{ 22, new [] { "yyyyMMdd HH:mm:ss.ffff" } },
{ 23, new [] { "yyyyMMdd HH:mm:ss.fffff" } },
{ 24, new [] { "yyyyMMdd HH:mm:ss.ffffff" } },
{ 25, new [] { "yyyyMMdd HH:mm:ss.fffffff" } },
};
/// <summary>
/// Jan 1 1970 ("epoch")
/// </summary>
public static readonly DateTime EpochStart = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
#endregion
#region Constants
private static readonly int s_numCharactersInIso8601 = MinValueUtc.ToString("O").Length;
#endregion
#region fast tostring()
/// <summary>
/// This function provides an optimized implementation of <see cref="DateTime.ToString(string)"/>
/// for the case of the format string being "O" (the round-trip format, a.k.a. ISO8601).
/// </summary>
public static string FastToString(this DateTime value)
{
var sb = UtilsStringBuilderCache.Acquire();
FastAppendToStringBuilder(value, sb);
return UtilsStringBuilderCache.GetStringAndRelease(sb);
}
public static void FastAppendToStringBuilder(this DateTime value, System.Text.StringBuilder sb)
{
sb.EnsureCapacity(s_numCharactersInIso8601); // TODO: Make sure that this ensures the _remaining_ capacity! Also note that the capacity is different for UTC and LOCAL
int year, month, day, hour, minute, second;
long fraction;
FastGetParts(value, out year, out month, out day, out hour, out minute, out second, out fraction);
FastAppendFormattedInt4(sb, year);
sb.Append('-');
FastAppendFormattedInt2(sb, month);
sb.Append('-');
FastAppendFormattedInt2(sb, day);
sb.Append('T');
FastAppendFormattedInt2(sb, hour);
sb.Append(':');
FastAppendFormattedInt2(sb, minute);
sb.Append(':');
FastAppendFormattedInt2(sb, second);
sb.Append('.');
FastAppendFormattedInt7(sb, fraction);
switch (value.Kind)
{
case DateTimeKind.Local:
TimeSpan offset = TimeZoneInfo.Local.GetUtcOffset(value);
if (offset >= TimeSpan.Zero)
{
sb.Append('+');
}
else
{
sb.Append('-');
offset = offset.Negate();
}
FastAppendFormattedInt2(sb, offset.Hours);
sb.Append(':');
FastAppendFormattedInt2(sb, offset.Minutes);
break;
case DateTimeKind.Unspecified:
break;
case DateTimeKind.Utc:
sb.Append('Z');
break;
}
}
private static void FastAppendFormattedInt7(System.Text.StringBuilder sb, long value)
{
char g = (char)('0' + value % 10);
value = value / 10;
char f = (char)('0' + value % 10);
value = value / 10;
char e = (char)('0' + value % 10);
value = value / 10;
char d = (char)('0' + value % 10);
value = value / 10;
char c = (char)('0' + value % 10);
value = value / 10;
char b = (char)('0' + value % 10);
value = value / 10;
char a = (char)('0' + value % 10);
sb.Append(a);
sb.Append(b);
sb.Append(c);
sb.Append(d);
sb.Append(e);
sb.Append(f);
sb.Append(g);
}
private static void FastAppendFormattedInt4(System.Text.StringBuilder sb, int value)
{
char d = (char)('0' + value % 10);
value = value / 10;
char c = (char)('0' + value % 10);
value = value / 10;
char b = (char)('0' + value % 10);
value = value / 10;
char a = (char)('0' + value % 10);
sb.Append(a);
sb.Append(b);
sb.Append(c);
sb.Append(d);
}
private static void FastAppendFormattedInt2(System.Text.StringBuilder sb, int value)
{
char b = (char)('0' + value % 10);
value = value / 10;
char a = (char)('0' + value % 10);
sb.Append(a);
sb.Append(b);
}
public static void FastGetParts(this DateTime value, out int year, out int month, out int day, out int hour, out int minute, out int second, out long fraction)
{
Int64 ticks = value.Ticks;
// n = number of days since 1/1/0001
int n = (int)(ticks / TicksPerDay);
// y400 = number of whole 400-year periods since 1/1/0001
int y400 = n / DaysPer400Years;
// n = day number within 400-year period
n -= y400 * DaysPer400Years;
// y100 = number of whole 100-year periods within 400-year period
int y100 = n / DaysPer100Years;
// Last 100-year period has an extra day, so decrement result if 4
if (y100 == 4) y100 = 3;
// n = day number within 100-year period
n -= y100 * DaysPer100Years;
// y4 = number of whole 4-year periods within 100-year period
int y4 = n / DaysPer4Years;
// n = day number within 4-year period
n -= y4 * DaysPer4Years;
// y1 = number of whole years within 4-year period
int y1 = n / DaysPerYear;
// Last year has an extra day, so decrement result if 4
if (y1 == 4) y1 = 3;
year = y400 * 400 + y100 * 100 + y4 * 4 + y1 + 1;
// n = day number within year
n -= y1 * DaysPerYear;
// If day-of-year was requested, return it
//if (part == DatePartDayOfYear) return n + 1;
// Leap year calculation looks different from IsLeapYear since y1, y4,
// and y100 are relative to year 1, not year 0
bool leapYear = y1 == 3 && (y4 != 24 || y100 == 3);
int[] days = leapYear ? DaysToMonth366 : DaysToMonth365;
// All months have less than 32 days, so n >> 5 is a good conservative
// estimate for the month
int m = n >> 5 + 1;
// m = 1-based month number
while (n >= days[m]) m++;
month = m;
// 1-based day-of-month
day = n - days[m - 1] + 1;
hour = value.Hour;
minute = value.Minute;
second = value.Second;
fraction = ticks % TicksPerSecond;
}
#endregion
#region Constants
// Number of 100ns ticks per time unit
private const long TicksPerMillisecond = 10000;
private const long TicksPerSecond = TicksPerMillisecond * 1000;
private const long TicksPerMinute = TicksPerSecond * 60;
private const long TicksPerHour = TicksPerMinute * 60;
private const long TicksPerDay = TicksPerHour * 24;
// Number of milliseconds per time unit
private const int MillisPerSecond = 1000;
private const int MillisPerMinute = MillisPerSecond * 60;
private const int MillisPerHour = MillisPerMinute * 60;
private const int MillisPerDay = MillisPerHour * 24;
// Number of days in a non-leap year
private const int DaysPerYear = 365;
// Number of days in 4 years
private const int DaysPer4Years = DaysPerYear * 4 + 1; // 1461
// Number of days in 100 years
private const int DaysPer100Years = DaysPer4Years * 25 - 1; // 36524
// Number of days in 400 years
private const int DaysPer400Years = DaysPer100Years * 4 + 1; // 146097
// Number of days from 1/1/0001 to 12/31/1600
private const int DaysTo1601 = DaysPer400Years * 4; // 584388
// Number of days from 1/1/0001 to 12/30/1899
private const int DaysTo1899 = DaysPer400Years * 4 + DaysPer100Years * 3 - 367;
// Number of days from 1/1/0001 to 12/31/1969
internal const int DaysTo1970 = DaysPer400Years * 4 + DaysPer100Years * 3 + DaysPer4Years * 17 + DaysPerYear; // 719,162
// Number of days from 1/1/0001 to 12/31/9999
private const int DaysTo10000 = DaysPer400Years * 25 - 366; // 3652059
internal const long MinTicks = 0;
internal const long MaxTicks = DaysTo10000 * TicksPerDay - 1;
private const long MaxMillis = (long)DaysTo10000 * MillisPerDay;
private const long FileTimeOffset = DaysTo1601 * TicksPerDay;
private const long DoubleDateOffset = DaysTo1899 * TicksPerDay;
// The minimum OA date is 0100/01/01 (Note it's year 100).
// The maximum OA date is 9999/12/31
private const long OADateMinAsTicks = (DaysPer100Years - DaysPerYear) * TicksPerDay;
// All OA dates must be greater than (not >=) OADateMinAsDouble
private const double OADateMinAsDouble = -657435.0;
// All OA dates must be less than (not <=) OADateMaxAsDouble
private const double OADateMaxAsDouble = 2958466.0;
private const int DatePartYear = 0;
private const int DatePartDayOfYear = 1;
private const int DatePartMonth = 2;
private const int DatePartDay = 3;
private static readonly int[] DaysToMonth365 = {
0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};
private static readonly int[] DaysToMonth366 = {
0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366};
#endregion
}
}

Просмотреть файл

@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace BenchmarkLogGenerator.Utilities
{
public static class ExtendedString
{
private static readonly string[] c_newlineAsStringArray = new string[] { Environment.NewLine };
public static string SplitFirst(this string what, char delimiter, out string remaining)
{
if (what == null)
{
remaining = null;
return null;
}
int delimiterIndex = what.IndexOf(delimiter);
if (delimiterIndex < 0)
{
remaining = null;
return what;
}
var first = what.Substring(0, delimiterIndex);
remaining = what.Substring(delimiterIndex + 1);
return first;
}
public static string[] SplitLines(this string what, StringSplitOptions options = StringSplitOptions.None, StringComparison comparison = StringComparison.Ordinal)
{
// TODO: A quick-and-dirty implementation. Might consider writing
// our own in the future
if (options == StringSplitOptions.RemoveEmptyEntries)
{
return what.Split(c_newlineAsStringArray, options);
}
// TODO: Need to make sure this follows the string.Split() conventions:
if (what == null)
{
return new string[0];
}
if (what == string.Empty)
{
return new string[] { string.Empty };
}
if (!what.EndsWith(Environment.NewLine, comparison))
{
return what.Split(c_newlineAsStringArray, StringSplitOptions.None);
}
// The string ends in a newline. String.Split will return an "extra"
// entry for the empty space between the newline and the end-of-string.
// Remove that baggage:
var ret = what.Split(c_newlineAsStringArray, StringSplitOptions.None);
var length = ret.Length;
if (length > 1)
{
ret = ret.SlowRemoveByIndex(length - 1);
}
return ret;
}
}
}

Просмотреть файл

@ -0,0 +1,250 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BenchmarkLogGenerator.Utilities
{
public static class ExtendedEnumerable
{
public static bool SafeFastAny<T>(this IEnumerable<T> collection, Func<T, bool> predicate)
{
if (!collection.SafeFastAny())
{
return false;
}
return collection.Any(predicate);
}
public static bool SafeFastAny(this IEnumerable collection)
{
if (collection == null)
{
return false;
}
{ // Scope to ensure no cross-talk with next block
if (collection is System.Collections.ICollection asICollection)
{
return asICollection.Count != 0;
}
}
var e = collection.GetEnumerator();
using (e as IDisposable)
{
if (e.MoveNext()) return true;
}
return false;
}
}
public sealed class ExtendedStringBuilder
{
#region fields and properties
private const int c_tabSize = 4;
private int m_tabSize;
private int m_indentation;
private char[] m_indentors;
private bool m_firstColumn;
public StringBuilder StringBuilder { get; private set; }
#endregion
#region constructor
/// <summary>
/// Creates the StringBuilder with no inedtation
/// </summary>
public ExtendedStringBuilder()
: this(c_tabSize, 0, null)
{
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="tabSize">How many spaces to indent by</param>
/// <param name="initialIndentation">Initial indentation to start with</param>
/// <param name="indentors">Two characters -- one to indent, another to unindent (null means don't add indent/unindent chars)</param>
public ExtendedStringBuilder(int tabSize, int initialIndentation, char[] indentors)
{
m_tabSize = tabSize;
m_indentation = initialIndentation;
m_indentors = indentors;
m_firstColumn = true;
StringBuilder = new StringBuilder();
}
#endregion
#region public methods
/// <summary>
/// Appends multiple lines to a string builder
/// </summary>
public void AppendLines(string str)
{
if (string.IsNullOrEmpty(str))
{
StringBuilder.AppendLine();
m_firstColumn = true;
return;
}
int current = 0;
while (true)
{
AppendIndent();
var nl = str.IndexOf('\r', current);
if (nl < 0)
{
// Everything remaining is a single line
if (current == 0)
{
StringBuilder.AppendLine(str);
}
else
{
for (; current < str.Length; current++)
{
StringBuilder.Append(str[current]);
}
StringBuilder.AppendLine();
}
m_firstColumn = true;
return;
}
for (; current < nl; current++)
{
StringBuilder.Append(str[current]);
}
StringBuilder.AppendLine();
m_firstColumn = true;
current++; // Move beyond the \r
if (current < str.Length)
{
if (str[current] == '\n')
{
current++;
}
}
if (current == str.Length)
{
return;
}
}
}
private void AppendIndent()
{
if (m_firstColumn)
{
for (int i = 0; i < m_indentation * m_tabSize; i++)
{
StringBuilder.Append(' ');
}
m_firstColumn = false;
}
}
private void UpdateFirstColumn(string writtenLast)
{
if (!string.IsNullOrEmpty(writtenLast))
{
m_firstColumn = writtenLast.EndsWith(System.Environment.NewLine);
}
}
/// <summary>
/// Appends a string to the string builder
/// </summary>
/// <param name="str">The string to append</param>
public void Append(string str)
{
AppendIndent();
StringBuilder.Append(str);
UpdateFirstColumn(str);
}
/// <summary>
/// Appends a string to the string builder (no indent).
/// </summary>
/// <param name="str">The string to append</param>
public void AppendNoIndent(string str)
{
StringBuilder.Append(str);
UpdateFirstColumn(str);
}
/// <summary>
/// Appends a line to the string builder
/// </summary>
/// <param name="str">The line to append</param>
public void AppendLine(string str)
{
AppendIndent();
StringBuilder.AppendLine(str);
m_firstColumn = true;
}
/// <summary>
/// Appends a character to the string builder
/// </summary>
/// <param name="c">The character to append</param>
public void AppendLine(char c)
{
for (int i = 0; i < m_indentation * m_tabSize; i++)
{
StringBuilder.Append(' ');
}
StringBuilder.Append(c);
StringBuilder.AppendLine();
m_firstColumn = true;
}
/// <summary>
/// Appends an empty line to the string builder
/// </summary>
public void AppendLine()
{
StringBuilder.AppendLine();
m_firstColumn = true;
}
/// <summary>
/// returns the built string
/// </summary>
override public string ToString()
{
return StringBuilder.ToString();
}
/// <summary>
/// Increase the indent level, potentially adding an indentor line.
/// </summary>
public void Indent()
{
if (m_indentors != null)
{
AppendLine(m_indentors[0]);
}
m_indentation++;
}
/// <summary>
/// Decrease the indent level, potentially adding an unindentor line
/// </summary>
public void Unindent()
{
m_indentation--;
if (m_indentors != null)
{
AppendLine(m_indentors[1]);
}
}
#endregion
}
}

27
Utilities/ExtendedType.cs Normal file
Просмотреть файл

@ -0,0 +1,27 @@
using System;
using System.Linq;
using System.Reflection;
namespace BenchmarkLogGenerator.Utilities
{
public static class ExtendedType
{
public static FieldInfo[] GetFieldsOrdered(this Type type)
{
return type.GetFields().OrderBy(fi => fi.MetadataToken).ToArray();
}
public static Type GetTypeWithNullableSupport(this Type type)
{
if (type.Name.StartsWith("Nullable"))
{
var declaredFields = ((TypeInfo)type).DeclaredFields;
var valueFieldInfo = declaredFields.Where(fi => string.Equals(fi.Name, "value", StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
return (valueFieldInfo != default(FieldInfo)) ? valueFieldInfo.FieldType : type;
}
return type;
}
}
}

Просмотреть файл

@ -0,0 +1,140 @@
using System;
using System.Text;
namespace BenchmarkLogGenerator.Utilities
{
#region class StringBuilderCache
/// <summary>
/// A helper for creating a per-thread ([ThreadStatic]) StringBuilder cache.
///
/// This code is stolen from the .NET Framework source code. The code requires
/// the caller to declare a [ThreadStatic] field member of type StringBuilder,
/// and provide a reference to that field with each operation. See
/// <see cref="UtilsStringBuilderCache"/> for an example.
///
/// Note that it's not advisable to share such objects if their lifetime
/// overlaps (which is why <see cref="UtilsStringBuilderCache"/> is made
/// internal -- to prevent people from making mistakes).
/// </summary>
public static class StringBuilderCache
{
private const int MAX_BUILDER_SIZE = 24 * 1024;
private const int DEFAULT_CAPACITY = 16;
/// <summary>
/// Given a [ThreadStatic] field, returns a "clean" instance of <see cref="StringBuilder"/>.
/// </summary>
/// <param name="threadStaticStringBuilder">[ThreadStatic] static System.Text.StringBuilder s_myStringBuilderCache</param>
/// <param name="capacity">Capacity to ensure the returned object holds.</param>
/// <param name="maxBuilderSize">The maximum size to allow the string builder to grow to.</param>
/// <returns></returns>
public static StringBuilder Acquire(ref StringBuilder threadStaticStringBuilder, int capacity = DEFAULT_CAPACITY, int maxBuilderSize = MAX_BUILDER_SIZE)
{
if (capacity <= maxBuilderSize)
{
StringBuilder sb = threadStaticStringBuilder;
if (sb != null)
{
// Avoid stringbuilder block fragmentation by getting a new StringBuilder
// when the requested size is larger than the current capacity
if (capacity <= sb.Capacity)
{
threadStaticStringBuilder = null;
sb.Clear();
return sb;
}
}
}
return new StringBuilder(capacity);
}
/// <summary>
/// Given a [ThreadStatic] field, returns an instance of <see cref="StringBuilder"/> with the given initial value.
/// </summary>
/// <param name="threadStaticStringBuilder">[ThreadStatic] static System.Text.StringBuilder s_myStringBuilderCache</param>
/// <param name="value">Initial value to assign the <see cref="StringBuilder"/> being returned.</param>
/// <returns></returns>
public static StringBuilder Acquire(ref StringBuilder threadStaticStringBuilder, string value)
{
StringBuilder sb = Acquire(ref threadStaticStringBuilder, System.Math.Max(value.Length, DEFAULT_CAPACITY));
sb.Append(value);
return sb;
}
/// <summary>
/// Given a [ThreadStatic] field and an existing <see cref="StringBuilder"/> that was acquired from it,
/// release the acquired instance to make it available in the future to other functions.
/// </summary>
/// <param name="threadStaticStringBuilder">[ThreadStatic] static System.Text.StringBuilder s_myStringBuilderCache</param>
/// <param name="sb"></param>
public static void Release(ref StringBuilder threadStaticStringBuilder, StringBuilder sb, int maxBuilderSize = MAX_BUILDER_SIZE)
{
if (sb.Capacity <= maxBuilderSize)
{
threadStaticStringBuilder = sb;
}
}
/// <summary>
/// Given a [ThreadStatic] field and an existing <see cref="StringBuilder"/> that was acquired from it,
/// release the acquired instance to make it available in the future to other functions.
/// Returns the string held in the returned <paramref name="sb"/>.
/// </summary>
/// <param name="threadStaticStringBuilder">[ThreadStatic] static System.Text.StringBuilder s_myStringBuilderCache</param>
/// <param name="sb"></param>
public static string GetStringAndRelease(ref StringBuilder threadStaticStringBuilder, StringBuilder sb, int maxBuilderSize = MAX_BUILDER_SIZE)
{
string result = sb.ToString();
Release(ref threadStaticStringBuilder, sb, maxBuilderSize);
return result;
}
public static string GetStringAndClear(ref StringBuilder threadStaticStringBuilder, StringBuilder sb)
{
string result = sb.ToString();
sb.Clear();
return result;
}
}
#endregion
#region class UtilsStringBuilderCache
/// <summary>
/// A per-thread cache of up to one <see cref="StringBuilder"/> object.
/// This code is stolen from the .NET Framework source code. It is explicitly
/// </summary>
internal static class UtilsStringBuilderCache
{
private const int MAX_BUILDER_SIZE = 24*1024; // Originally 260
private const int DEFAULT_CAPACITY = 16;
[ThreadStatic]
private static StringBuilder t_cachedInstance;
public static StringBuilder Acquire(int capacity = DEFAULT_CAPACITY)
{
return StringBuilderCache.Acquire(ref t_cachedInstance, capacity);
}
public static StringBuilder Acquire(string value)
{
return StringBuilderCache.Acquire(ref t_cachedInstance, value);
}
public static void Release(StringBuilder sb)
{
StringBuilderCache.Release(ref t_cachedInstance, sb);
}
public static string GetStringAndRelease(StringBuilder sb)
{
return StringBuilderCache.GetStringAndRelease(ref t_cachedInstance, sb);
}
public static string GetStringAndClear(StringBuilder sb)
{
return StringBuilderCache.GetStringAndClear(ref t_cachedInstance, sb);
}
}
#endregion
}

Просмотреть файл

@ -0,0 +1,5 @@
{
"configProperties": {
"System.GC.Server": true
}
}