From efeeb41fa2e64cf7c74ebea4d3ad55fefa69f685 Mon Sep 17 00:00:00 2001 From: Nelson Nobre Date: Mon, 2 Jun 2025 14:27:48 +0100 Subject: [PATCH 01/10] feat: Project Kickoff --- .config/dotnet-tools.json | 12 + .editorconfig | 488 ++++ .github/CODEOWNERS | 1 + .github/ISSUE_TEMPLATE/bug-report.yml | 101 + .github/ISSUE_TEMPLATE/config.yml | 1 + .github/ISSUE_TEMPLATE/documentation.yml | 26 + .github/ISSUE_TEMPLATE/feature-request.yml | 46 + .github/ISSUE_TEMPLATE/support.yml | 25 + .github/ISSUE_TEMPLATE/task.yml | 17 + .github/PULL_REQUEST_TEMPLATE.md | 9 + .github/dependabot.yml | 41 + .github/hooks/commit-msg | 37 + .github/workflows/codeql-analysis.yml | 72 + .github/workflows/markdown-link-check.json | 3 + .github/workflows/markdown-link-check.yml | 30 + .github/workflows/nuget-vulnerabilites.yml | 42 + .github/workflows/publish.yml | 44 + .github/workflows/release.yml | 43 + .../workflows/sonarcloud-and-mutations.yml | 148 ++ .github/workflows/tests.yml | 115 + .gitignore | 486 ++++ .releaserc | 33 + .vscode/launch.json | 21 + .vscode/tasks.json | 17 + CHANGELOG.md | 0 CODE_OF_CONDUCT.md | 128 + CONTRIBUTING.md | 100 + PowerUtils.BenchmarkDotnet.Reporter.slnx | 4 + README.md | 175 ++ README.nuget.org.md | 20 + SECURITY.md | 44 + assets/logo/logo.svg | 296 +++ assets/logo/logo_128x128.png | Bin 0 -> 7957 bytes global.json | 5 + src/Commands/ComparerCommand.cs | 149 ++ src/Exporters/ConsoleExporter.cs | 145 + src/Exporters/HitTxtExporter.cs | 33 + src/Exporters/IExporter.cs | 8 + src/Exporters/JsonExporter.cs | 25 + src/Exporters/MarkdownExporter.cs | 134 + src/Helpers/BeautifyExtentions.cs | 98 + src/Helpers/ComparableExtensions.cs | 21 + src/Helpers/IOHelpers.cs | 75 + src/Models/ComparerReport.cs | 31 + src/Models/ComparisonStatus.cs | 10 + src/Models/FullJsonResport.cs | 149 ++ src/Models/MemoryThreshold.cs | 84 + src/Models/MetricComparison.cs | 80 + src/Models/TimeThreshold.cs | 84 + ...PowerUtils.BenchmarkDotnet.Reporter.csproj | 102 + src/Program.cs | 32 + src/Properties/launchSettings.json | 7 + src/ToolCommands.cs | 89 + src/Validations/ReportValidation.cs | 75 + src/Validations/ValidationException.cs | 10 + stryker.bat | 4 + .../Commands/ComparerCommandTests.cs | 516 ++++ .../Exporters/ConsoleExporterTests.cs | 406 +++ .../Exporters/HitTxtExporterTests.cs | 136 + .../Exporters/JsonExporterTests.cs | 58 + .../Exporters/MarkdownExporterTests.cs | 284 ++ .../BeautifyExtentions/MemoryBeautifyTests.cs | 39 + .../PercentageBeautifyTests.cs | 36 + .../BeautifyExtentions/TimeBeautifyTests.cs | 45 + .../Helpers/ComparableExtensionsTest.cs | 22 + .../IOHelpersTests/GetFullJsonReportTest.cs | 102 + .../Helpers/IOHelpersTests/PrintTest.cs | 29 + .../IOHelpersTests/ReadFullJsonReportTest.cs | 22 + .../Helpers/IOHelpersTests/WriteFileTest.cs | 43 + .../Models/ComparerReportTests.cs | 65 + .../Models/MemoryThresholdTests.cs | 43 + .../Models/MetricComparisonTests.cs | 172 ++ .../Models/TimeThresholdTests.cs | 43 + ...tils.BenchmarkDotnet.Reporter.Tests.csproj | 70 + .../ToolCommandsTest.cs | 163 ++ .../Validations/ReportValidationTest.cs | 254 ++ .../Validations/ValidationExceptionTest.cs | 56 + .../report-01/Benchmark-report-full.json | 1516 +++++++++++ .../report-01/Benchmark-report-github.md | 14 + .../test-data/report-01/Benchmark-report.csv | 3 + .../test-data/report-01/Benchmark-report.html | 31 + .../report-02/Benchmark-report-full.json | 1943 ++++++++++++++ .../report-02/Benchmark-report-github.md | 14 + .../test-data/report-02/Benchmark-report.csv | 3 + .../test-data/report-02/Benchmark-report.html | 31 + .../report-10/Benchmark-report-full.json | 1943 ++++++++++++++ .../report-10/Benchmark-report-github.md | 14 + .../test-data/report-10/Benchmark-report.csv | 3 + .../test-data/report-10/Benchmark-report.html | 31 + .../report-99/Benchmark-report-full.json | 2328 +++++++++++++++++ .../report-99/Benchmark-report-github.md | 14 + .../test-data/report-99/Benchmark-report.csv | 3 + .../test-data/report-99/Benchmark-report.html | 31 + 93 files changed, 14601 insertions(+) create mode 100644 .config/dotnet-tools.json create mode 100644 .editorconfig create mode 100644 .github/CODEOWNERS create mode 100644 .github/ISSUE_TEMPLATE/bug-report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/documentation.yml create mode 100644 .github/ISSUE_TEMPLATE/feature-request.yml create mode 100644 .github/ISSUE_TEMPLATE/support.yml create mode 100644 .github/ISSUE_TEMPLATE/task.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/dependabot.yml create mode 100644 .github/hooks/commit-msg create mode 100644 .github/workflows/codeql-analysis.yml create mode 100644 .github/workflows/markdown-link-check.json create mode 100644 .github/workflows/markdown-link-check.yml create mode 100644 .github/workflows/nuget-vulnerabilites.yml create mode 100644 .github/workflows/publish.yml create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/sonarcloud-and-mutations.yml create mode 100644 .github/workflows/tests.yml create mode 100644 .gitignore create mode 100644 .releaserc create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json create mode 100644 CHANGELOG.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 PowerUtils.BenchmarkDotnet.Reporter.slnx create mode 100644 README.md create mode 100644 README.nuget.org.md create mode 100644 SECURITY.md create mode 100644 assets/logo/logo.svg create mode 100644 assets/logo/logo_128x128.png create mode 100644 global.json create mode 100644 src/Commands/ComparerCommand.cs create mode 100644 src/Exporters/ConsoleExporter.cs create mode 100644 src/Exporters/HitTxtExporter.cs create mode 100644 src/Exporters/IExporter.cs create mode 100644 src/Exporters/JsonExporter.cs create mode 100644 src/Exporters/MarkdownExporter.cs create mode 100644 src/Helpers/BeautifyExtentions.cs create mode 100644 src/Helpers/ComparableExtensions.cs create mode 100644 src/Helpers/IOHelpers.cs create mode 100644 src/Models/ComparerReport.cs create mode 100644 src/Models/ComparisonStatus.cs create mode 100644 src/Models/FullJsonResport.cs create mode 100644 src/Models/MemoryThreshold.cs create mode 100644 src/Models/MetricComparison.cs create mode 100644 src/Models/TimeThreshold.cs create mode 100644 src/PowerUtils.BenchmarkDotnet.Reporter.csproj create mode 100644 src/Program.cs create mode 100644 src/Properties/launchSettings.json create mode 100644 src/ToolCommands.cs create mode 100644 src/Validations/ReportValidation.cs create mode 100644 src/Validations/ValidationException.cs create mode 100644 stryker.bat create mode 100644 tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Commands/ComparerCommandTests.cs create mode 100644 tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Exporters/ConsoleExporterTests.cs create mode 100644 tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Exporters/HitTxtExporterTests.cs create mode 100644 tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Exporters/JsonExporterTests.cs create mode 100644 tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Exporters/MarkdownExporterTests.cs create mode 100644 tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Helpers/BeautifyExtentions/MemoryBeautifyTests.cs create mode 100644 tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Helpers/BeautifyExtentions/PercentageBeautifyTests.cs create mode 100644 tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Helpers/BeautifyExtentions/TimeBeautifyTests.cs create mode 100644 tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Helpers/ComparableExtensionsTest.cs create mode 100644 tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Helpers/IOHelpersTests/GetFullJsonReportTest.cs create mode 100644 tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Helpers/IOHelpersTests/PrintTest.cs create mode 100644 tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Helpers/IOHelpersTests/ReadFullJsonReportTest.cs create mode 100644 tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Helpers/IOHelpersTests/WriteFileTest.cs create mode 100644 tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Models/ComparerReportTests.cs create mode 100644 tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Models/MemoryThresholdTests.cs create mode 100644 tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Models/MetricComparisonTests.cs create mode 100644 tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Models/TimeThresholdTests.cs create mode 100644 tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/PowerUtils.BenchmarkDotnet.Reporter.Tests.csproj create mode 100644 tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/ToolCommandsTest.cs create mode 100644 tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Validations/ReportValidationTest.cs create mode 100644 tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Validations/ValidationExceptionTest.cs create mode 100644 tests/test-data/report-01/Benchmark-report-full.json create mode 100644 tests/test-data/report-01/Benchmark-report-github.md create mode 100644 tests/test-data/report-01/Benchmark-report.csv create mode 100644 tests/test-data/report-01/Benchmark-report.html create mode 100644 tests/test-data/report-02/Benchmark-report-full.json create mode 100644 tests/test-data/report-02/Benchmark-report-github.md create mode 100644 tests/test-data/report-02/Benchmark-report.csv create mode 100644 tests/test-data/report-02/Benchmark-report.html create mode 100644 tests/test-data/report-10/Benchmark-report-full.json create mode 100644 tests/test-data/report-10/Benchmark-report-github.md create mode 100644 tests/test-data/report-10/Benchmark-report.csv create mode 100644 tests/test-data/report-10/Benchmark-report.html create mode 100644 tests/test-data/report-99/Benchmark-report-full.json create mode 100644 tests/test-data/report-99/Benchmark-report-github.md create mode 100644 tests/test-data/report-99/Benchmark-report.csv create mode 100644 tests/test-data/report-99/Benchmark-report.html diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 0000000..9777bcd --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-stryker": { + "version": "4.5.1", + "commands": [ + "dotnet-stryker" + ] + } + } +} \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f906d6a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,488 @@ +############################### +# Core EditorConfig Options # +############################### + +# Remove the line below if you want to inherit .editorconfig settings from higher directories +root = true + + + + +# All files +[*] +charset = utf-8 +# Indentation and spacing +indent_size = 4 +indent_style = space +tab_width = 4 + +# New line preferences +end_of_line = crlf +insert_final_newline = false + +# Max line length +guidelines = 80 +max_line_length = 120 + + +# Visual Studio XML Project Files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] +indent_size = 2 + +# Yaml files +[*.{yaml,yml}] +indent_size = 2 + +# Config files +[*.{xml,props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] +indent_size = 2 + +# JSON Files +[*.{json,json5,webmanifest}] +indent_size = 2 + +# Code files +[*.{cs,csx,vb,vbx}] +indent_size = 4 + +# Visual Studio Solution Files +[*.{sln,slnx}] +indent_size = 2 + +# Markdown Files +[*.{md,mdx}] +trim_trailing_whitespace = false + +# Web Files +[*.{htm,html,js,jsm,ts,tsx,cjs,cts,ctsx,mjs,mts,mtsx,css,sass,scss,less,pcss,svg,vue}] +indent_size = 2 + +# Batch Files +[*.{cmd,bat}] +end_of_line = crlf + +# Bash Files +[*.sh] +end_of_line = lf + + + +############################### +# .NET Coding Conventions # +############################### + +[*.{cs,vb,csproj}] + +insert_final_newline = true # Insert a newline at the end of the file when you save it +trim_trailing_whitespace = true # Trim trailing whitespace on lines when you press enter + + +[*.{cs,vb}] + +# Indentation and spacing +tab_width = 4 + +#### .NET Coding Conventions #### + +# Organize usings +dotnet_separate_import_directive_groups = false # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#dotnet_separate_import_directive_groups +dotnet_sort_system_directives_first = true # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#dotnet_sort_system_directives_first +file_header_template = unset + +# this. and Me. preferences +dotnet_style_qualification_for_event = false:suggestion +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_property = false:suggestion + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0047-ide0048#dotnet_style_parentheses_in_arithmetic_binary_operators +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggestion #https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0047-ide0048#dotnet_style_parentheses_in_other_binary_operators +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0047-ide0048#dotnet_style_parentheses_in_other_operators +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0047-ide0048#dotnet_style_parentheses_in_relational_binary_operators + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members +dotnet_style_readonly_field = true:suggestion + +# Expression-level preferences +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_collection_initializer = false # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0028#dotnet_style_collection_initializer +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_namespace_match_folder = true # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#dotnet_style_namespace_match_folder +dotnet_style_null_propagation = true:suggestion +dotnet_style_object_initializer = false # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0017#overview +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_prefer_compound_assignment = true +dotnet_style_prefer_conditional_expression_over_assignment = false:suggestion +dotnet_style_prefer_conditional_expression_over_return = false:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent +dotnet_style_prefer_simplified_boolean_expressions = true +dotnet_style_prefer_simplified_interpolation = true + +# Field preferences +dotnet_style_readonly_field = true + +# Parameter preferences +dotnet_code_quality_unused_parameters = all + +# Suppression preferences +dotnet_remove_unnecessary_suppression_exclusions = none + +# New line preferences +dotnet_style_allow_multiple_blank_lines_experimental = true +dotnet_style_allow_statement_immediately_after_block_experimental = true + + +dotnet_diagnostic.CS8019.severity = warning # (unused directives) Who the compiler handles unused usings +dotnet_diagnostic.IDE0005.severity = warning # (unused directives) Who the IDE handles unused usings +dotnet_diagnostic.CS1591.severity = none # Disable: Missing XML comment for publicly visible type or member + + +############################### +# C# Coding Conventions # +############################### +[*.cs] + +# var preferences +csharp_style_var_elsewhere = true:suggestion +csharp_style_var_for_built_in_types = true:suggestion +csharp_style_var_when_type_is_apparent = true:suggestion + +# Pattern matching preferences +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion + +# Expression-bodied members +csharp_style_expression_bodied_accessors = true:suggestion +csharp_style_expression_bodied_constructors = true:suggestion +csharp_style_expression_bodied_indexers = true:suggestion +csharp_style_expression_bodied_lambdas = true:suggestion +csharp_style_expression_bodied_local_functions = true:suggestion +csharp_style_expression_bodied_methods = true:suggestion +csharp_style_expression_bodied_operators = true:suggestion +csharp_style_expression_bodied_properties = true:suggestion + +# Pattern matching preferences +csharp_style_pattern_matching_over_as_with_null_check = true +csharp_style_pattern_matching_over_is_with_cast_check = true +csharp_style_prefer_not_pattern = true +csharp_style_prefer_pattern_matching = false # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0078#csharp_style_prefer_pattern_matching +csharp_style_prefer_switch_expression = true + +# Null-checking preferences +csharp_style_conditional_delegate_call = true:suggestion +csharp_style_throw_expression = true:suggestion +dotnet_style_coalesce_expression = false:none + +# Modifier preferences +csharp_prefer_static_local_function = true:suggestion +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion + +# Code-block preferences +csharp_prefer_braces = true:suggestion # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0011#csharp_prefer_braces +csharp_prefer_simple_using_statement = false:suggestion # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0063#csharp_prefer_simple_using_statement +# https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_style_namespace_declarations +# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0160-ide0161 +csharp_style_namespace_declarations = file_scoped:suggestion +dotnet_diagnostic.IDE0160.severity = warning +dotnet_diagnostic.IDE0161.severity = none + +# Expression-level preferences +csharp_prefer_simple_default_expression = true:suggestion # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0034#csharp_prefer_simple_default_expression +csharp_style_deconstructed_variable_declaration = true:suggestion # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0042#csharp_style_deconstructed_variable_declaration +csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0090#csharp_style_implicit_object_creation_when_type_is_apparent +csharp_style_inlined_variable_declaration = true:suggestion # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0018#csharp_style_inlined_variable_declaration +csharp_style_pattern_local_over_anonymous_function = true:suggestion # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0039#csharp_style_pattern_local_over_anonymous_function +csharp_style_prefer_index_operator = true:suggestion # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0056#csharp_style_prefer_index_operator +csharp_style_prefer_null_check_over_type_check = true:suggestion +csharp_style_prefer_range_operator = true:suggestion # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0057#csharp_style_prefer_range_operator +csharp_style_throw_expression = true:suggestion # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0016 +csharp_style_unused_value_assignment_preference = discard_variable:suggestion # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0059#csharp_style_unused_value_assignment_preference +#csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0058#csharp_style_unused_value_expression_statement_preference + +# 'using' directive preferences +csharp_using_directive_placement = outside_namespace:suggestion # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_using_directive_placement + +# New line preferences +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false:suggestion +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true +csharp_style_allow_embedded_statements_on_same_line_experimental = true + + + + +############################### +# C# Formatting Rules # +############################### + +# New line preferences +csharp_new_line_before_catch = true # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_new_line_before_catch +csharp_new_line_before_else = true # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_new_line_before_else +csharp_new_line_before_finally = true # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_new_line_before_finally +csharp_new_line_before_members_in_anonymous_types = true # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_new_line_before_members_in_anonymous_types +csharp_new_line_before_members_in_object_initializers = true # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_new_line_before_members_in_object_initializers +csharp_new_line_before_open_brace = all # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_new_line_before_open_brace +csharp_new_line_between_query_expression_clauses = true # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_new_line_between_query_expression_clauses + +# Indentation preferences +csharp_indent_block_contents = true # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_indent_block_contents +csharp_indent_braces = false # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_indent_braces +csharp_indent_case_contents = true # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_indent_case_contents +csharp_indent_case_contents_when_block = false # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_indent_case_contents_when_block +csharp_indent_labels = flush_left # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_indent_labels +csharp_indent_switch_labels = true # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_indent_switch_labels + +# Space preferences +csharp_space_after_cast = false # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_space_after_cast +csharp_space_after_colon_in_inheritance_clause = true # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_space_after_colon_in_inheritance_clause +csharp_space_after_comma = true # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_space_after_comma +csharp_space_after_dot = false # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_space_after_dot +csharp_space_after_keywords_in_control_flow_statements = false # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_space_after_keywords_in_control_flow_statements +csharp_space_after_semicolon_in_for_statement = true # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_space_after_semicolon_in_for_statement +csharp_space_around_binary_operators = before_and_after # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_space_around_binary_operators +csharp_space_around_declaration_statements = false # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_space_around_declaration_statements +csharp_space_before_colon_in_inheritance_clause = true # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_space_before_colon_in_inheritance_clause +csharp_space_before_comma = false # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_space_before_comma +csharp_space_before_dot = false # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_space_before_dot +csharp_space_before_open_square_brackets = false # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_space_before_open_square_brackets +csharp_space_before_semicolon_in_for_statement = false # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_space_before_semicolon_in_for_statement +csharp_space_between_empty_square_brackets = false # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_space_between_empty_square_brackets +csharp_space_between_method_call_empty_parameter_list_parentheses = false # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_space_between_method_call_empty_parameter_list_parentheses +csharp_space_between_method_call_name_and_opening_parenthesis = false # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_space_between_method_call_name_and_opening_parenthesis +csharp_space_between_method_call_parameter_list_parentheses = false # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_space_between_method_call_parameter_list_parentheses +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false # csharp_space_between_method_declaration_empty_parameter_list_parentheses +csharp_space_between_method_declaration_name_and_open_parenthesis = false # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_space_between_method_declaration_name_and_open_parenthesis +csharp_space_between_method_declaration_parameter_list_parentheses = false # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_space_between_method_declaration_parameter_list_parentheses +#csharp_space_between_parentheses = false # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_space_between_parentheses +csharp_space_between_square_brackets = false # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_space_between_square_brackets + +# Wrapping preferences +csharp_preserve_single_line_blocks = true # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_preserve_single_line_blocks +csharp_preserve_single_line_statements = false # https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#csharp_preserve_single_line_statements + + + + +############################### +# Naming Conventions # +############################### + +# CONSTANTS +dotnet_naming_rule.constant_should_be_upper_snake_case.severity = suggestion +dotnet_naming_rule.constant_should_be_upper_snake_case.symbols = constant +dotnet_naming_rule.constant_should_be_upper_snake_case.style = upper_snake_case +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.applicable_accessibilities = * +dotnet_naming_symbols.constant_fields.required_modifiers = const + + +dotnet_naming_rule.variable_should_be_camel_case.severity = suggestion +dotnet_naming_rule.variable_should_be_camel_case.symbols = variable +dotnet_naming_rule.variable_should_be_camel_case.style = camel_case + +dotnet_naming_rule.parameter_should_be_camel_case.severity = suggestion +dotnet_naming_rule.parameter_should_be_camel_case.symbols = parameter +dotnet_naming_rule.parameter_should_be_camel_case.style = camel_case + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.public_method_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.public_method_should_be_pascal_case.symbols = public_method +dotnet_naming_rule.public_method_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.internal_method_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.internal_method_should_be_pascal_case.symbols = internal_method +dotnet_naming_rule.internal_method_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.protected_method_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.protected_method_should_be_pascal_case.symbols = protected_method +dotnet_naming_rule.protected_method_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.private_method_should_be__camalcase.severity = suggestion +dotnet_naming_rule.private_method_should_be__camalcase.symbols = private_method +dotnet_naming_rule.private_method_should_be__camalcase.style = _camalcase + +dotnet_naming_rule.public_property_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.public_property_should_be_pascal_case.symbols = public_property +dotnet_naming_rule.public_property_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.internal_property_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.internal_property_should_be_pascal_case.symbols = internal_property +dotnet_naming_rule.internal_property_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.protected_property_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.protected_property_should_be_pascal_case.symbols = protected_property +dotnet_naming_rule.protected_property_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.private_property_should_be__camalcase.severity = suggestion +dotnet_naming_rule.private_property_should_be__camalcase.symbols = private_property +dotnet_naming_rule.private_property_should_be__camalcase.style = _camalcase + +dotnet_naming_rule.public_filed_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.public_filed_should_be_pascal_case.symbols = public_filed +dotnet_naming_rule.public_filed_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.internal_field_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.internal_field_should_be_pascal_case.symbols = internal_field +dotnet_naming_rule.internal_field_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.protected_field_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.protected_field_should_be_pascal_case.symbols = protected_field +dotnet_naming_rule.protected_field_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.private_field_should_be__camalcase.severity = suggestion +dotnet_naming_rule.private_field_should_be__camalcase.symbols = private_field +dotnet_naming_rule.private_field_should_be__camalcase.style = _camalcase + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.private_method.applicable_kinds = method +dotnet_naming_symbols.private_method.applicable_accessibilities = private +dotnet_naming_symbols.private_method.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.constant.applicable_kinds = field, local +dotnet_naming_symbols.constant.applicable_accessibilities = * +dotnet_naming_symbols.constant.required_modifiers = const + +dotnet_naming_symbols.public_method.applicable_kinds = method +dotnet_naming_symbols.public_method.applicable_accessibilities = public +dotnet_naming_symbols.public_method.required_modifiers = + +dotnet_naming_symbols.protected_method.applicable_kinds = method +dotnet_naming_symbols.protected_method.applicable_accessibilities = protected +dotnet_naming_symbols.protected_method.required_modifiers = + +dotnet_naming_symbols.internal_method.applicable_kinds = method +dotnet_naming_symbols.internal_method.applicable_accessibilities = internal +dotnet_naming_symbols.internal_method.required_modifiers = + +dotnet_naming_symbols.parameter.applicable_kinds = parameter +dotnet_naming_symbols.parameter.applicable_accessibilities = local +dotnet_naming_symbols.parameter.required_modifiers = + +dotnet_naming_symbols.public_property.applicable_kinds = property +dotnet_naming_symbols.public_property.applicable_accessibilities = public +dotnet_naming_symbols.public_property.required_modifiers = + +dotnet_naming_symbols.internal_property.applicable_kinds = property +dotnet_naming_symbols.internal_property.applicable_accessibilities = internal +dotnet_naming_symbols.internal_property.required_modifiers = + +dotnet_naming_symbols.protected_property.applicable_kinds = property +dotnet_naming_symbols.protected_property.applicable_accessibilities = protected +dotnet_naming_symbols.protected_property.required_modifiers = + +dotnet_naming_symbols.private_property.applicable_kinds = property +dotnet_naming_symbols.private_property.applicable_accessibilities = private +dotnet_naming_symbols.private_property.required_modifiers = + +dotnet_naming_symbols.public_filed.applicable_kinds = field +dotnet_naming_symbols.public_filed.applicable_accessibilities = public +dotnet_naming_symbols.public_filed.required_modifiers = + +dotnet_naming_symbols.internal_field.applicable_kinds = field +dotnet_naming_symbols.internal_field.applicable_accessibilities = internal +dotnet_naming_symbols.internal_field.required_modifiers = + +dotnet_naming_symbols.protected_field.applicable_kinds = field +dotnet_naming_symbols.protected_field.applicable_accessibilities = protected +dotnet_naming_symbols.protected_field.required_modifiers = + +dotnet_naming_symbols.private_field.applicable_kinds = field +dotnet_naming_symbols.private_field.applicable_accessibilities = private +dotnet_naming_symbols.private_field.required_modifiers = + +dotnet_naming_symbols.variable.applicable_kinds = local +dotnet_naming_symbols.variable.applicable_accessibilities = local +dotnet_naming_symbols.variable.required_modifiers = + + + + +############################### +# Naming Conventions # +############################### + +# Style Definitions +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_naming_style._camalcase.required_prefix = _ +dotnet_naming_style._camalcase.required_suffix = +dotnet_naming_style._camalcase.word_separator = +dotnet_naming_style._camalcase.capitalization = camel_case + +dotnet_naming_style.upper_snake_case.required_prefix = +dotnet_naming_style.upper_snake_case.required_suffix = +dotnet_naming_style.upper_snake_case.word_separator = _ +dotnet_naming_style.upper_snake_case.capitalization = all_upper + +dotnet_naming_style.camel_case.required_prefix = +dotnet_naming_style.camel_case.required_suffix = +dotnet_naming_style.camel_case.word_separator = +dotnet_naming_style.camel_case.capitalization = camel_case + + + + +############################### +# Hide dotnet messages # +############################### + +# CA1848: Use the LoggerMessage delegates +dotnet_diagnostic.CA1848.severity = none + +# CA2254: Template should be a static expression +dotnet_diagnostic.CA2254.severity = none + + + + +############################### +# Message adjustment # +############################### + +# CA2211: Non-constant fields should not be visible +dotnet_diagnostic.CA2211.severity = warning + +# CA1852: Seal internal types (disabled for Program.cs to not show the warning to top-level programs) +dotnet_diagnostic.CA1852.severity = warning +[Program.cs] +dotnet_diagnostic.CA1852.severity = none + + +############################### +# VB Coding Conventions # +############################### +[*.vb] +# Modifier preferences +visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..da5a95a --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @NelsonBN diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 0000000..a440801 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,101 @@ +--- +name: "🐞 Bug report" +description: Report a bug +title: "(Short description)" +labels: [bug] +assignees: [NelsonBN] + + + +body: + - type: textarea + id: description + attributes: + label: Describe the bug + description: What is the problem? A clear and concise description of the bug. + validations: + required: true + + + - type: textarea + id: expected + attributes: + label: Expected behavior + description: | + What did you expect to happen? + validations: + required: true + + + - type: textarea + id: current + attributes: + label: Current behavior + description: | + What actually happened? + + Please include full errors, uncaught exceptions, stack traces, and relevant logs. + If service responses are relevant, please include wire logs. + validations: + required: false + + + - type: textarea + id: reproduction + attributes: + label: Reproduction Steps + description: | + Provide a self-contained, concise snippet of code that can be used to reproduce the issue. + For more complex issues provide a repo with the smallest sample that reproduces the bug. + + Avoid including business logic or unrelated code, it makes diagnosis more difficult. + The code sample should be an SSCCE. See http://sscce.org/ for details. In short, please provide a code sample that we can copy/paste, run and reproduce. + validations: + required: false + + + - type: textarea + id: solution + attributes: + label: Possible Solution + description: | + Suggest a fix/reason for the bug + validations: + required: false + + + - type: textarea + id: context + attributes: + label: Additional Information/Context + description: | + Anything else that might be relevant for troubleshooting this bug. Providing context helps us come up with a solution that is most useful in the real world. + validations: + required: false + + + - type: input + id: nuget-version-used + attributes: + label: Targeted nuGet version + placeholder: v1.0.0, v1.0.1, etc. + validations: + required: true + + + - type: input + id: framework-version-used + attributes: + label: Targeted .NET Platform + placeholder: .NET6.0, .NET5.0 .NET Core 3.1, .NET Framework 4.7, etc. + validations: + required: true + + + - type: input + id: operating-system + attributes: + label: Operating System and version + placeholder: Windows 10, OSX Mojave, Ubuntu, AmazonLinux, etc. + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..3ba13e0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false diff --git a/.github/ISSUE_TEMPLATE/documentation.yml b/.github/ISSUE_TEMPLATE/documentation.yml new file mode 100644 index 0000000..dcc26f7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation.yml @@ -0,0 +1,26 @@ +--- +name: "📕 Documentation issue or suggestion" +description: Report an issue or a suggestion for documentation +title: "(Short description)" +labels: [documentation] +assignees: [NelsonBN] + + + +body: + - type: textarea + id: description + attributes: + label: Describe the issue + description: A clear and concise description of the issue. + validations: + required: true + + + - type: textarea + id: links + attributes: + label: Links + description: Include links to affected documentation page(s). + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml new file mode 100644 index 0000000..2bbe7f9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -0,0 +1,46 @@ +--- +name: "🚀 Feature request" +description: "Suggest an idea for this project" +title: "(Short description)" +labels: [feature-request] +assignees: [NelsonBN] + + + +body: + - type: textarea + id: description + attributes: + label: Describe the feature + description: A clear and concise description of the feature you are proposing. + validations: + required: true + + + - type: textarea + id: use-case + attributes: + label: Use Case + description: Why do you need this feature? For example "I'm always frustrated when..."" + validations: + required: true + + + - type: textarea + id: solution + attributes: + label: Proposed Solution + description: Suggest how to implement the addition or change. Please include prototype/workaround/sketch/reference implementation. + validations: + required: false + + + - type: checkboxes + id: ack + attributes: + label: Acknowledgements + options: + - label: I may be able to implement this feature request + required: false + - label: This feature might incur a breaking change + required: false diff --git a/.github/ISSUE_TEMPLATE/support.yml b/.github/ISSUE_TEMPLATE/support.yml new file mode 100644 index 0000000..f6fc091 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/support.yml @@ -0,0 +1,25 @@ +--- +name: "💊 Support" +description: "Request support about this project" +title: "(short issue description)" +labels: [support] +assignees: [NelsonBN] + + + +body: + - type: input + id: topic + attributes: + label: Question title + placeholder: Short description of your question + validations: + required: true + + + - type: textarea + id: question + attributes: + label: Describe your question + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/task.yml b/.github/ISSUE_TEMPLATE/task.yml new file mode 100644 index 0000000..440127f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/task.yml @@ -0,0 +1,17 @@ +--- +name: "🔧 Task" +description: Report a task suggestion or project maintenance +title: "(Short description)" +labels: [task] +assignees: [NelsonBN] + + + +body: + - type: textarea + id: description + attributes: + label: Describe the task + description: A clear and concise description of the task. + validations: + required: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..72a4f34 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,9 @@ +## Describe your changes + +## Issue number or link + +## Checklist before requesting a review +- [ ] I have performed a self-review of my code. +- [ ] Implemented tests for new features. +- [ ] I tested the features already implemented. +- [ ] Added usage examples in the readme. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..f2a3e39 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,41 @@ +version: 2 +updates: + + - package-ecosystem: "github-actions" + directory: "/" # Location of package manifests + schedule: + interval: "daily" + assignees: + - "NelsonBN" + reviewers: + - "NelsonBN" + commit-message: + prefix: "build" + include: "scope" + target-branch: "main" + + - package-ecosystem: "nuget" + directory: "/" # Location of package manifests + schedule: + interval: "daily" + assignees: + - "NelsonBN" + reviewers: + - "NelsonBN" + commit-message: + prefix: "build" + include: "scope" + target-branch: "main" + + - package-ecosystem: "dotnet-sdk" + directory: "/" # Location of package manifests + schedule: + interval: "daily" + assignees: + - "NelsonBN" + reviewers: + - "NelsonBN" + commit-message: + prefix: "build" + include: "scope" + target-branch: "main" diff --git a/.github/hooks/commit-msg b/.github/hooks/commit-msg new file mode 100644 index 0000000..b196b72 --- /dev/null +++ b/.github/hooks/commit-msg @@ -0,0 +1,37 @@ +#!/bin/sh +# +# An example hook script to check the commit log message. +# Called by "git commit" with one argument, the name of the file +# that has the commit message. The hook should exit with non-zero +# status after issuing an appropriate message if it wants to stop the +# commit. The hook is allowed to edit the commit message file. +# +# To enable this hook, rename this file to "commit-msg". + +# Uncomment the below to add a Signed-off-by line to the message. +# Doing this in a hook is a bad idea in general, but the prepare-commit-msg +# hook is more suited to it. +# +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" + +# This example catches duplicate Signed-off-by lines. + +test "" = "$(grep '^Signed-off-by: ' "$1" | + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { + echo >&2 Duplicate Signed-off-by lines. + exit 1 +} +if ! head -1 "$1" | grep -qE "^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test|merge)(\(.+?\))?(\!)?: .{3,}$"; then + echo "Aborting commit. Your commit message is invalid. See some examples below:" >&2 + echo "feat(logging): added logs for failed signups" >&2 + echo "fix(homepage): fixed image gallery" >&2 + echo "test(homepage): updated tests" >&2 + echo "docs(readme): added new logging table information" >&2 + echo "For more information check https://www.conventionalcommits.org/en/v1.0.0/ for more details" >&2 + exit 1 +fi +if ! head -1 "$1" | grep -qE "^.{9,85}$"; then + echo "Aborting commit. Your commit message is too long. Max length is 89 characters" >&2 + exit 1 +fi \ No newline at end of file diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..81b52e7 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,72 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ "main" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "main" ] + schedule: + - cron: '0 5 * * 0' + +jobs: + analyze: + name: 'CodeQL Analyze' + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'csharp' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v4.2.2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/markdown-link-check.json b/.github/workflows/markdown-link-check.json new file mode 100644 index 0000000..3fff32c --- /dev/null +++ b/.github/workflows/markdown-link-check.json @@ -0,0 +1,3 @@ +{ + "aliveStatusCodes": [429, 200] +} \ No newline at end of file diff --git a/.github/workflows/markdown-link-check.yml b/.github/workflows/markdown-link-check.yml new file mode 100644 index 0000000..a9f6253 --- /dev/null +++ b/.github/workflows/markdown-link-check.yml @@ -0,0 +1,30 @@ +name: 'Check Markdown links' + + +on: + workflow_dispatch: + + # Schedule to run every week on Sunday at 00:00 UTC + schedule: + - cron: '0 0 * * 0' + + pull_request: + types: [opened, reopened, edited, synchronize] + branches: + - main + + +jobs: + markdown-link-check: + runs-on: ubuntu-latest + + steps: + - name: "Checkout" + uses: actions/checkout@v4.2.2 + + - name: 'Readme analysis' + uses: gaurav-nelson/github-action-markdown-link-check@v1 + with: + use-verbose-mode: 'yes' + base-branch: 'main' + config-file: '.github/workflows/markdown-link-check.json' diff --git a/.github/workflows/nuget-vulnerabilites.yml b/.github/workflows/nuget-vulnerabilites.yml new file mode 100644 index 0000000..c889c74 --- /dev/null +++ b/.github/workflows/nuget-vulnerabilites.yml @@ -0,0 +1,42 @@ +name: 'NuGet vulnerabilites' + + +on: + workflow_dispatch: + + # Schedule to run every week on Sunday at 00:00 UTC + schedule: + - cron: '0 0 * * 0' + + pull_request: + types: [opened, reopened, edited, synchronize] + branches: + - main + + +jobs: + + sonar-scanner: + name: "NuGet vulnerabilites" + runs-on: ubuntu-latest + + steps: + - name: "Checkout" + uses: actions/checkout@v4.2.2 + + - name: "Setup .NET" + uses: actions/setup-dotnet@v4.3.1 + with: + global-json-file: 'global.json' + + - name: "Restore dependencies" + run: dotnet restore + + - name: "Build" + run: dotnet build --configuration Release --no-restore + + - name: "Checking NuGet vulnerabilites" + run: | + dotnet list package --vulnerable --include-transitive 2>&1 | tee build.log + echo "Analyze dotnet list package command log output..." + grep -q -i "\bcritical\b\|\bhigh\b\|\bmoderate\b\|\blow\b" build.log && { echo "Security Vulnerabilities found on the log output"; exit 1; } || exit 0 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..4c32195 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,44 @@ +name: 'Publish' + + +on: + workflow_dispatch: + + release: + types: [created] + + +env: + PACKAGE_PATH: ./src/**/*.nupkg + NUGET_SERVER: https://api.nuget.org/v3/index.json + + +jobs: + + deploy-nuget: + name: "Deploy nuget" + runs-on: 'ubuntu-latest' + + steps: + - name: "Checkout" + uses: actions/checkout@v4.2.2 + with: + fetch-depth: 0 + persist-credentials: false + + - name: "Setup .NET" + uses: actions/setup-dotnet@v4.3.1 + with: + global-json-file: 'global.json' + + - name: "Restore dependencies" + run: dotnet restore + + - name: "Build" + run: dotnet build -c Release --no-restore + + - name: "Pack" + run: dotnet pack -c Release --no-build + + - name: "Publish nuget" + run: dotnet nuget push ${{ env.PACKAGE_PATH }} --api-key ${{ secrets.NUGET_TOKEN }} --source ${{ env.NUGET_SERVER }} --skip-duplicate diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..f81a2ad --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,43 @@ +name: 'Release' + + +on: + workflow_dispatch: + + push: + branches: + - main + paths: + - "src/**" + + +jobs: + + release-version: + name: "Release new version" + runs-on: 'ubuntu-latest' + + steps: + - name: "Checkout" + uses: actions/checkout@v4.2.2 + with: + fetch-depth: 0 + persist-credentials: false + + - name: "Semantic Release" + id: 'semantic-release' + uses: cycjimmy/semantic-release-action@v4.2.0 + with: + semantic_version: 24.1.2 + extra_plugins: | + semantic-release-plugin-csproj + @semantic-release/changelog + @semantic-release/git + env: + GH_TOKEN: ${{ secrets.GH_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: "New version" + run: | + echo "Published new version: ${{ steps.semantic-release.outputs.new_release_published }}" + echo "New version: ${{ steps.semantic-release.outputs.new_release_version }}" diff --git a/.github/workflows/sonarcloud-and-mutations.yml b/.github/workflows/sonarcloud-and-mutations.yml new file mode 100644 index 0000000..f759e60 --- /dev/null +++ b/.github/workflows/sonarcloud-and-mutations.yml @@ -0,0 +1,148 @@ +name: 'SonarCloud and Mutation Tests' + + +on: + workflow_dispatch: + + push: # First pipeline to run when deploy a new version + branches: + - main + paths: + - "src/**" # Only run when exists changes in source code + - "tests/**" # Only run when exists changes in tests code + + pull_request: + types: [opened, reopened, edited, synchronize] + branches: + - main + + +env: + FRAMEWORK_VERSION: 'net9.0' + + PROJECT_FILE: 'PowerUtils.BenchmarkDotnet.Reporter.csproj' + TEST_PROJECT_PATH: 'tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/PowerUtils.BenchmarkDotnet.Reporter.Tests.csproj' + + # Set up the .NET environment to improve test performance and reliability + DOTNET_CLI_TELEMETRY_OPTOUT: true # Disable .NET CLI telemetry + DOTNET_NOLOGO: true # Disable .NET CLI logo + DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION: true # Enable ANSI color redirection + TERM: xterm # Enable ANSI color redirection + NUGET_XMLDOC_MODE: skip # Disable NuGet XML documentation generation + + +permissions: + pull-requests: write # To can create a comment with the results + + +jobs: + + sonar-scanner: + name: "Sonar Scanner and Mutation Tests" + runs-on: ubuntu-latest + + steps: + - name: "Checkout" + uses: actions/checkout@v4.2.2 + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + + - name: "Define branch name for Stryker" + run: | + if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then + echo "STRYKER_BRANCH=main" >> $GITHUB_ENV + else + echo "STRYKER_BRANCH=${{ github.head_ref }}-${{ github.run_number }}" >> $GITHUB_ENV + fi + + - name: "Display branch name for Stryker" + run: echo "Stryker branch is '${{ env.STRYKER_BRANCH }}'" + + - name: "Setup .NET" + uses: actions/setup-dotnet@v4.3.1 + with: + global-json-file: 'global.json' + + # Hack reasons: + # - The last version of the `xunit.runner.visualstudio` doesn't generate the report for some OLD SDK versions like .net 5 + # - The Stryker versions after 4.0.0 is not compatible with net7.0 + - name: "Replace TargetFrameworks" + run: | + find . -type f -name "*.csproj" -exec sed -i 's/.*<\/TargetFrameworks>/${{ env.FRAMEWORK_VERSION }}<\/TargetFrameworks>/g' {} + + + - name: "Set up JDK" + uses: actions/setup-java@v4.7.1 + with: + distribution: 'adopt' + java-version: '17' + + - name: "Restore .NET Tools" + run: dotnet tool restore + + - name: "Create cache directory" + run: mkdir -p ~/sonar/cache + + - name: "Cache SonarCloud packages" + uses: actions/cache@v4 + with: + path: ~/sonar/cache + key: ${{ runner.os }}-sonar-${{ hashFiles('**/*.csproj') }} + restore-keys: | + ${{ runner.os }}-sonar- + ${{ runner.os }}- + + - name: "Cache SonarCloud scanner" + id: cache-sonar-scanner + uses: actions/cache@v4 + with: + path: ./.sonar/scanner + key: ${{ runner.os }}-sonar-scanner + restore-keys: ${{ runner.os }}-sonar-scanner + + - name: "Install SonarCloud scanner" + if: steps.cache-sonar-scanner.outputs.cache-hit != 'true' + run: | + mkdir -p ./.sonar/scanner + dotnet tool update dotnet-sonarscanner --tool-path ./.sonar/scanner + dotnet tool update dotnet-coverage --tool-path ./.sonar/scanner + + - name: "Run Stryker" + run: | + dotnet stryker \ + -tp ${{ env.TEST_PROJECT_PATH }} \ + -p ${{ env.PROJECT_FILE }} \ + --reporter json \ + --reporter markdown \ + --reporter progress \ + --reporter dashboard --dashboard-api-key ${{ secrets.STRYKER_API_KEY }} \ + --version ${{ env.STRYKER_BRANCH }} \ + -O ${{ github.workspace }}/mutations + + - name: "Convert report to Sonar" + run: jq -f tests/mutation-report-to-sonar.jq '${{ github.workspace }}/mutations/reports/mutation-report.json' > '${{ github.workspace }}/mutations/reports/mutation-sonar.json' + + - name: "Add Stryker Report in PR Comment" + uses: marocchino/sticky-pull-request-comment@v2 + if: github.event_name == 'pull_request' + with: + header: stryker-report + hide_and_recreate: true + hide_classify: "OUTDATED" + message: | + 🚀 **Stryker report generated** 🚀 + [![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2F${{ github.repository_owner }}%2F${{ github.event.repository.name }}%2F${{ env.STRYKER_BRANCH }})](https://dashboard.stryker-mutator.io/reports/github.com/${{ github.repository_owner }}/${{ github.event.repository.name }}/${{ env.STRYKER_BRANCH }}) + To more details: https://dashboard.stryker-mutator.io/reports/github.com/${{ github.repository_owner }}/${{ github.event.repository.name }}/${{ env.STRYKER_BRANCH }} + + - name: "Publish report" + run: cat $(find ${{ github.workspace }}/mutations/reports/* -name "*.md") > $GITHUB_STEP_SUMMARY + + - name: "Build and analyze" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + shell: pwsh + run: | + ./.sonar/scanner/dotnet-sonarscanner begin /k:"${{ secrets.SONAR_PROJECT_KEY }}" /o:"${{ secrets.SONAR_ORGANIZATION }}" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.vscoveragexml.reportsPaths=coverage.xml /d:sonar.projectBaseDir="$(pwd)" /d:sonar.externalIssuesReportPaths="${{ github.workspace }}/mutations/reports/mutation-sonar.json" + dotnet build -c Release --no-incremental + ./.sonar/scanner/dotnet-coverage collect "dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=opencover -c Release --no-restore --no-build" -f xml -o "coverage.xml" + ./.sonar/scanner/dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..25439bd --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,115 @@ +name: 'Tests' + + +on: + workflow_dispatch: + + pull_request: + types: [opened, reopened, edited, synchronize] + branches: + - main + + +env: + COVERAGE_REPORT_DIRECTORY: 'CodeCoverageReports' + + # Set up the .NET environment to improve test performance and reliability + DOTNET_CLI_TELEMETRY_OPTOUT: true # Disable .NET CLI telemetry + DOTNET_NOLOGO: true # Disable .NET CLI logo + DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION: true # Enable ANSI color redirection + TERM: xterm # Enable ANSI color redirection + NUGET_XMLDOC_MODE: skip # Disable NuGet XML documentation generation + + +jobs: + + test-project: + name: "Test nuget" + runs-on: ubuntu-22.04 + permissions: + contents: read + issues: read + checks: write + pull-requests: write + + steps: + - name: "Checkout" + uses: actions/checkout@v4.2.2 + + - name: "Setup .NET" + uses: actions/setup-dotnet@v4.3.1 + with: + global-json-file: 'global.json' + + - name: "Restore dependencies" + run: dotnet restore + + - name: "Build" + run: dotnet build --configuration Release --no-restore + + - name: "Run tests" + run: dotnet test --configuration Release --no-build --verbosity normal --results-directory ${{ github.workspace }}/${{ env.COVERAGE_REPORT_DIRECTORY }} --collect:"XPlat Code Coverage" + + - name: "Combine Coverage Reports" # This is because one report is produced per project, and we want one result for all of them. + uses: danielpalme/ReportGenerator-GitHub-Action@5.4.7 + with: + reports: "**/*.cobertura.xml" # REQUIRED # The coverage reports that should be parsed (separated by semicolon). Globbing is supported. + targetdir: "${{ github.workspace }}" # REQUIRED # The directory where the generated report should be saved. + reporttypes: "Cobertura" # The output formats and scope (separated by semicolon) Values: Badges, Clover, Cobertura, CsvSummary, Html, Html_Dark, Html_Light, Html_BlueRed, HtmlChart, HtmlInline, HtmlInline_AzurePipelines, HtmlInline_AzurePipelines_Dark, HtmlInline_AzurePipelines_Light, HtmlSummary, JsonSummary, Latex, LatexSummary, lcov, MarkdownSummary, MarkdownSummaryGithub, MarkdownDeltaSummary, MHtml, PngChart, SonarQube, TeamCitySummary, TextSummary, TextDeltaSummary, Xml, XmlSummary + verbosity: "Info" # The verbosity level of the log messages. Values: Verbose, Info, Warning, Error, Off + title: "Code Coverage" # Optional title. + tag: "${{ github.run_number }}_${{ github.run_id }}" # Optional tag or build version. + customSettings: "" # Optional custom settings (separated by semicolon). See: https://github.com/danielpalme/ReportGenerator/wiki/Settings. + toolpath: "reportgeneratortool" # Default directory for installing the dotnet tool. + + - name: "Upload Combined Coverage" + uses: actions/upload-artifact@v4.6.2 + with: + name: 'Coverage' + path: ${{ github.workspace }}/Cobertura.xml + if-no-files-found: error + + - name: "Publish code coverage report" + uses: irongut/CodeCoverageSummary@v1.3.0 + with: + filename: "Cobertura.xml" + badge: true + fail_below_min: false # just informative for now + format: markdown + hide_branch_rate: false + hide_complexity: false + indicators: true + output: both + thresholds: "10 30" + + - name: "Add Coverage in PR Comment" + uses: marocchino/sticky-pull-request-comment@v2 + if: github.event_name == 'pull_request' + with: + header: coverage-report + hide_and_recreate: true + hide_classify: "OUTDATED" + path: code-coverage-results.md + + - name: "Upload Test Results" + uses: actions/upload-artifact@v4.6.2 + with: + name: 'TestResults' + path: ${{ github.workspace }}/${{ env.COVERAGE_REPORT_DIRECTORY }}/**/* + if-no-files-found: error + + - name: "Publish Test Results" + uses: dorny/test-reporter@v2 + if: always() + with: + name: "Test Results" + path: "${{ github.workspace }}/${{ env.COVERAGE_REPORT_DIRECTORY }}/**/*.trx" + reporter: dotnet-trx + + - name: "Publish Test Summary" + uses: EnricoMi/publish-unit-test-result-action@v2.20.0 + if: always() + with: + check_name: "Test Summary" + # NOTE: using trx_files instead of files due to https://github.com/EnricoMi/publish-unit-test-result-action/issues/424 + trx_files: "${{ github.workspace }}/${{ env.COVERAGE_REPORT_DIRECTORY }}/**/*.trx" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3cdda3f --- /dev/null +++ b/.gitignore @@ -0,0 +1,486 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from `dotnet new gitignore` + +# dotenv files +.env + +# 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/ +[Ww][Ii][Nn]32/ +[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 +project.lock.json +project.fragment.lock.json +artifacts/ + +# Tye +.tye/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# 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 +*.tlog +*.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 + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# 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 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# 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/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# 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/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml +.idea/ + +## +## Visual studio for Mac +## + + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/main/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/main/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Vim temporary swap files +*.swp + +BenchmarkReporter/ diff --git a/.releaserc b/.releaserc new file mode 100644 index 0000000..4d8318c --- /dev/null +++ b/.releaserc @@ -0,0 +1,33 @@ +{ + "branches": ["main"], + + "plugins": [ + ["@semantic-release/commit-analyzer", { + "releaseRules": [ + { "type": "refactor", "release": "patch" }, + { "type": "perf", "release": "patch" } + ] + }], + ["@semantic-release/release-notes-generator", { + "presetConfig": { + "types": [ + { "type": "refactor", "section": "Refactors", "hidden": false }, + { "type": "perf", "section": "Performance Improvements", "hidden": false } + ] + } + }], + ["semantic-release-plugin-csproj", { + "projectFile": "src/PowerUtils.BenchmarkDotnet.Reporter.csproj" + }], + "@semantic-release/changelog", + "@semantic-release/github", + ["@semantic-release/git", { + "path": "@semantic-release/git", + "assets": [ + "CHANGELOG.md", + "src/**.csproj" + ], + "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" + }] + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..a9a9a6b --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,21 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": ".NET", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/src/bin/Debug/net9.0/PBReporter.dll", + "args": [ + //"--version" + //"compare", "-h" + "compare", "-f", "console", "-b", "../tests/test-data/report-02/Benchmark-report-full.json", "-t", "../tests/test-data/report-01/Benchmark-report-full.json", "-tm", "4%", "-ta", "5%" + //"compare", "-f", "markdown", "-b", "../tests/test-data/report-01/Benchmark-report-full.json", "-t", "../tests/test-data/report-02/Benchmark-report-full.json", "-tm", "12ns", "-ta", "5%" + ], + "cwd": "${workspaceFolder}/src", + "console": "internalConsole", + "stopAtEntry": false + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..1575f47 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,17 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/PowerUtils.BenchmarkDotnet.Reporter.slnx", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary;ForceNoAlign" + ], + "problemMatcher": "$msCompile" + } + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e69de29 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..1f05ca1 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +technobre@gmail.com. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..ba0673c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,100 @@ +# Contributing + +We would love for you to contribute to this project and help make it even better than it is today! +As a contributor, here are the guidelines we would like you to follow. It does help everyone to accept your Pull Requests with maximum awesomeness: + +- [ Code of Conduct](#-code-of-conduct) +- [ General Seps](#-general-seps) +- [ Commits and Pull Requests](#-commits-and-pull-requests) + - [ Conventional Commits](#-conventional-commits) + - [ Common types you can use (based on the Angular convention):](#-common-types-you-can-use-based-on-the-angular-convention) +- [ Coding Standards](#-coding-standards) +- [ Tests](#-tests) + + + +## Code of Conduct + +Please read and follow our [Code of Conduct][coc]. + + + +## General Seps + +1. Check if there is already an open issue for the subject; +2. Open an issue to discuss the new feature; +3. Fork this repository; +4. Create your feature branch: `git checkout -b feat/my-new-feature`; +5. Add files changed: `git add --all`; +6. Commit your changes: `git commit -m "feat: Add some feature"`; +7. Push to the branch: `git push origin feat/my-new-feature`; +8. Submit a pull request; + + +## Commits and Pull Requests + +* :x: **AVOID** breaking the continuous integration build. +* :heavy_check_mark: **DO** atomic commits to make it easier to analyze changes. +* :heavy_check_mark: **DO** keep pull requests small so they can be easily reviewed. +* :heavy_check_mark: **DO** only commit with conventional commit patterns + +### Conventional Commits +To know more about conventional commits, visit [Conventional Commits](https://conventionalcommits.org/). + +In general the pattern mostly looks like this: +``` +(): + │ │ │ + │ │ └─â̏ Summary in present tense. Not capitalized. No period at the end. + │ │ + │ └─â̏ Commit Scope: * + │ + └─â̏ Commit Type: feat|fix|docs|style|refactor|perf|build|test|ci|chore|merge|revert +``` + +Real world examples can look like this: +``` +chore: run tests on travis ci +``` +``` +fix(server): send cors headers +``` +``` +feat(blog): add comment section +``` + +#### Common types you can use (based on the Angular convention): + +- `feat:` A new feature +- `fix:` A bug fix +- `docs:` Documentation only changes +- `style:` Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) +- `refactor:` A code change that neither fixes a bug nor adds a feature +- `perf:` A code change that improves performance +- `build:` Changes that affect the build system or external dependencies (example scopes: nugets, npm, SDKs, etc) +- `test:` Adding missing tests or correcting existing tests +- `ci:` Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs) +- `chore:` Other changes that don't modify src or test files +- `merge:` Merge a branch into other branch +- `revert:` Reverts a previous commit + + + +## Coding Standards +* :heavy_check_mark: **DO** add XML comment documentation to new classes, methods or parameters. +* :heavy_check_mark: **DO** add a test class for each feature and a test method for each +* :heavy_check_mark: **DO** use language conventions to make code easy to understand quickly. See some tips here: [dofactory](https://www.dofactory.com/csharp-coding-standards); +* :heavy_check_mark: **CONSIDER** using SOLID patterns; + + + +## Tests +* :heavy_check_mark: **DO** add a unit test if your Pull Requests resolves an issue or adds features. +* :heavy_check_mark: **CONSIDER** using test patterns like "AAA" and "Given When Then"; +* :heavy_check_mark: **DO** add a test class for each feature and a test method for each assertion; +* :heavy_check_mark: **DO** make sure unit tests pass. +* :x: **AVOID** adding tests just to get coverage on sonarcloud. + +***Disclaimer*** +- 1ī¸âƒŖ Unit in Unit Test is not a method/function. +- 2ī¸âƒŖ One assertion per test doesn't mean invoking the Assert method only once. diff --git a/PowerUtils.BenchmarkDotnet.Reporter.slnx b/PowerUtils.BenchmarkDotnet.Reporter.slnx new file mode 100644 index 0000000..094f354 --- /dev/null +++ b/PowerUtils.BenchmarkDotnet.Reporter.slnx @@ -0,0 +1,4 @@ + + + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..5322fed --- /dev/null +++ b/README.md @@ -0,0 +1,175 @@ +# PowerUtils.BenchmarkDotnet.Reporter + +![Logo](https://raw.githubusercontent.com/TechNobre/PowerUtils.BenchmarkDotnet.Reporter/main/assets/logo/logo_128x128.png) + +***Tool to analyze and compare .NET benchmark reports*** + +![Tests](https://github.com/TechNobre/PowerUtils.BenchmarkDotnet.Reporter/actions/workflows/tests.yml/badge.svg) +[![Mutation tests](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2FTechNobre%2FPowerUtils.BenchmarkDotnet.Reporter%2Fmain)](https://dashboard.stryker-mutator.io/reports/github.com/TechNobre/PowerUtils.BenchmarkDotnet.Reporter/main) + +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=TechNobre_PowerUtils.BenchmarkDotnet.Reporter&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=TechNobre_PowerUtils.BenchmarkDotnet.Reporter) +[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=TechNobre_PowerUtils.BenchmarkDotnet.Reporter&metric=coverage)](https://sonarcloud.io/summary/new_code?id=TechNobre_PowerUtils.BenchmarkDotnet.Reporter) +[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=TechNobre_PowerUtils.BenchmarkDotnet.Reporter&metric=reliability_rating)](https://sonarcloud.io/summary/new_code?id=TechNobre_PowerUtils.BenchmarkDotnet.Reporter) +[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=TechNobre_PowerUtils.BenchmarkDotnet.Reporter&metric=bugs)](https://sonarcloud.io/summary/new_code?id=TechNobre_PowerUtils.BenchmarkDotnet.Reporter) + +[![NuGet](https://img.shields.io/nuget/v/PowerUtils.BenchmarkDotnet.Reporter.svg)](https://www.nuget.org/packages/PowerUtils.BenchmarkDotnet.Reporter) +[![Nuget](https://img.shields.io/nuget/dt/PowerUtils.BenchmarkDotnet.Reporter.svg)](https://www.nuget.org/packages/PowerUtils.BenchmarkDotnet.Reporter) +[![License: MIT](https://img.shields.io/github/license/TechNobre/PowerUtils.BenchmarkDotnet.Reporter.svg)](https://github.com/TechNobre/PowerUtils.BenchmarkDotnet.Reporter/blob/main/LICENSE) + + + +**PowerUtils.BenchmarkDotnet.Reporter** is a command-line tool used to analyze and compare .NET benchmark reports generated by [BenchmarkDotNet](https://www.nuget.org/packages/benchmarkdotnet). This tool is designed to run locally or in CI/CD pipelines, providing a simple way to visualize and compare benchmark results. + + + +- [Prepare environment](#prepare-environment) + - [Install Tool](#install-tool) + - [Update Tool](#update-tool) + - [List installed tools](#list-installed-tools) + - [Uninstall Tool](#uninstall-tool) +- [How to use](#how-to-use) + - [Run the tool](#run-the-tool) + - [Commands](#commands) + - [`compare`](#compare) + - [Options:](#options) + - [Example of usage:](#example-of-usage) +- [Contribution](#contribution) + + + +## Prepare environment + +### Install Tool + +This package is available through Nuget Repository: https://www.nuget.org/packages/PowerUtils.BenchmarkDotnet.Reporter + +**Locally** +```bash +# Create a tool manifest file in the current directory +dotnet new tool-manifest +# Install the tool in the current directory +dotnet tool install PowerUtils.BenchmarkDotnet.Reporter +``` + +**Globally** +```bash +dotnet tool install --global PowerUtils.BenchmarkDotnet.Reporter +``` + +### Update Tool + +**Locally** +```bash +dotnet tool update PowerUtils.BenchmarkDotnet.Reporter +``` + +**Update** +```bash +dotnet tool update --global PowerUtils.BenchmarkDotnet.Reporter +``` + +### List installed tools + +**Locally** +```bash +dotnet tool list +``` + +**Globally** +```bash +dotnet tool list --global +``` + +### Uninstall Tool + +**Locally** +```bash +dotnet tool uninstall PowerUtils.BenchmarkDotnet.Reporter +``` + +**Globally** +```bash +dotnet tool uninstall --global PowerUtils.BenchmarkDotnet.Reporter +``` + + + +## How to use + +### Run the tool + +**Locally** +```bash +dotnet pbreporter [command] [options] +``` + +**Globally** +```bash +pbreporter [command] [options] +``` + +### Commands + +#### `compare` + +Compares two benchmark reports and generates a report with the differences. + +**Example:** +```bash +pbreporter compare -b baseline-full.json -t target-full.json +``` + +##### Options: + +* (`-b`, `--baseline`) ``: Path to the folder or file with Baseline report. **[Required]** +* (`-t`, `--target`) ``: Path to the folder or file with target reports. **[Required]** +* (`-tm`, `--threshold-mean`) ``: Throw an error when the mean threshold is met. Examples: 5%, 10ms, 10Îŧs, 100ns, 1s. +* (`-ta`, `--threshold-allocation`) ``: Throw an error when the allocation threshold is met. Examples: 5%, 10b, 10kb, 100mb, 1gb. +* (`-f`, `--format`) ``: Output format for the report. **[default: console]** +* (`-o`, `--output`) ``: Output directory to export the diff report. Default is current directory. **[default: ./BenchmarkReporter]** +* (`-?`, `-h`, `--help`): Show help and usage information + +##### Example of usage: + +**Simple usage** +```bash +pbreporter compare -b target-full.json -t baseline-full.json +``` + +**With output format and directory** +```bash +pbreporter compare -b target-full.json -t baseline-full.json -f json -f markdown -o ./out +``` + +**With thresholds** +```bash +pbreporter compare -b target-full.json -t baseline-full.json -tm 5% -ta 12b +``` + +**With thresholds and output threshold report** +```bash +pbreporter compare -b target-full.json -t baseline-full.json -tm 5% -f hit-txt +``` +> Note: The `hit-txt` format only will generate when at least one threshold is hit. + +**With console output** +```bash +pbreporter compare -b target-full.json -t baseline-full.json -f console +``` +> Note: The `console` format displays the comparison report directly in the terminal instead of creating a file. + +**With Markdown output** +```bash +pbreporter compare -b target-full.json -t baseline-full.json -f markdown +``` +> Note: The `markdown` format is ideal for generating reports to upload to GitHub or other platforms that support Markdown rendering. + +**With multiple formats** +```bash +pbreporter compare -b target-full.json -t baseline-full.json -f json -f markdown -f console +``` + + +## Contribution + +If you have any questions, comments, or suggestions, please open an [issue](https://github.com/TechNobre/PowerUtils.BenchmarkDotnet.Reporter/issues/new/choose) or create a [pull request](https://github.com/TechNobre/PowerUtils.BenchmarkDotnet.Reporter/compare) diff --git a/README.nuget.org.md b/README.nuget.org.md new file mode 100644 index 0000000..a8af725 --- /dev/null +++ b/README.nuget.org.md @@ -0,0 +1,20 @@ +***Tool to analyze and compare .NET benchmark reports*** + +![Tests](https://github.com/TechNobre/PowerUtils.BenchmarkDotnet.Reporter/actions/workflows/tests.yml/badge.svg) +[![Mutation tests](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2FTechNobre%2FPowerUtils.BenchmarkDotnet.Reporter%2Fmain)](https://dashboard.stryker-mutator.io/reports/github.com/TechNobre/PowerUtils.BenchmarkDotnet.Reporter/main) + +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=TechNobre_PowerUtils.BenchmarkDotnet.Reporter&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=TechNobre_PowerUtils.BenchmarkDotnet.Reporter) +[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=TechNobre_PowerUtils.BenchmarkDotnet.Reporter&metric=coverage)](https://sonarcloud.io/summary/new_code?id=TechNobre_PowerUtils.BenchmarkDotnet.Reporter) +[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=TechNobre_PowerUtils.BenchmarkDotnet.Reporter&metric=reliability_rating)](https://sonarcloud.io/summary/new_code?id=TechNobre_PowerUtils.BenchmarkDotnet.Reporter) +[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=TechNobre_PowerUtils.BenchmarkDotnet.Reporter&metric=bugs)](https://sonarcloud.io/summary/new_code?id=TechNobre_PowerUtils.BenchmarkDotnet.Reporter) + + + +**PowerUtils.BenchmarkDotnet.Reporter** is a command-line tool used to analyze and compare .NET benchmark reports generated by [BenchmarkDotNet](https://www.nuget.org/packages/benchmarkdotnet). This tool is designed to run locally or in CI/CD pipelines, providing a simple way to visualize and compare benchmark results. + + + + +## Contribution + +If you have any questions, comments, or suggestions, please open an [issue](https://github.com/TechNobre/PowerUtils.BenchmarkDotnet.Reporter/issues/new/choose) or create a [pull request](https://github.com/TechNobre/PowerUtils.BenchmarkDotnet.Reporter/compare) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..87a3ff6 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,44 @@ +# Security + + +## Reporting Vulnerabilities + +We take the security of our repositories seriously, which includes all source code repositories managed through our [GitHub organizations TechNobre](https://github.com/TechNobre). + +All of our projects use GitHub issues to track issues. If you believe you have found a security vulnerability in any of our repositories, please use the issues area of the respective project to report the problem to us. + +Furthermore, if you believe you have a solution for a vulnerability in any of our repositories, feel free to open a Pull Request with your suggested improvement. We will carefully review your contribution, and if it is confirmed to address the issue, it will be implemented in a future version. + +Thank you for collaborating with us to make our projects safer and more secure. + +### Preferred Language + +We prefer all communications to be in English. + + +## Support + +The support for a specific version of .NET in our project follows the same lifecycle as the .NET framework itself. However, even after the official support for a version of .NET ends, we will continue to make efforts to support older versions as long as it is feasible and does not hinder the progress of our project. + +We understand that some users may be using older versions of .NET due to specific restrictions or requirements. We will strive to minimize any security issues and ensure a reliable experience, as long as maintaining these versions does not pose a significant obstacle to the project's progress. + +However, it is important to note that after the support for a specific version of .NET ends, we reserve the right to discontinue support for that version if it hinders the progress and continuous evolution of our project. In such cases, we strongly recommend users to update to the latest supported versions to ensure security and take advantage of the latest features and improvements. + +We are committed to balancing support for older versions with the need to advance and innovate. We value the feedback and needs of our community and will do our best to ensure a smooth transition and provide clear guidance regarding the supported versions of .NET. + +### Previous Versions + +Earlier versions of this project may not receive regular security updates. We strongly recommend updating to the latest version to ensure security and benefit from the latest fixes and improvements. + + +## Feature Discontinuation + +In our project, discontinuation of a specific feature will be notified with a 6-month advance notice. We will utilize techniques such as using the `[Obsolete]` attribute provided by .NET itself and other available resources to indicate the discontinuation of the feature. These notifications will be communicated clearly and documented. + +During this 6-month period, we encourage users to migrate to available alternatives or adjust their implementations to accommodate the discontinuation of the feature. We will strive to provide clear guidance and support during this transition period. + +After the 6-month notification period, the discontinued feature will be removed from the project. The removal will be planned and controlled to minimize any negative impact. + +However, in cases where the continued presence of the discontinued feature may compromise the security of our project, we reserve the right to remove the feature immediately without prior notice. The security and integrity of our project are our primary concerns, and we will take necessary measures to ensure its protection and stability. + +We appreciate your understanding and collaboration during the feature discontinuation process, and we are available to clarify any doubts or provide additional guidance. diff --git a/assets/logo/logo.svg b/assets/logo/logo.svg new file mode 100644 index 0000000..360ac96 --- /dev/null +++ b/assets/logo/logo.svg @@ -0,0 +1,296 @@ + + + + + + + + + + + + + + + + + + + + + + + + Power + Utils + + + + + + + Power + Utils + + + + + + + + + + + image/svg+xml + + + + + + + + Utils + Power + + + + BenchmarkDotnetReporter + + + + + + + diff --git a/assets/logo/logo_128x128.png b/assets/logo/logo_128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..f695b6368b2241f6bb367c2e31e0e051dc4e1c46 GIT binary patch literal 7957 zcmXw81ymI8*B)T$TDlt)5b3T3ln@1%?r!N$VWlNS5JVb8q+7aS=^r2x5-zwj($e)G zzjMAhXLioa`_A0^-gxeF-+c?$R3#>$CjbC|_?g;MZSactcjDoIzkTvnqu>SKL(Rw= z05GlooftiWdRO2>Iv-_2A02mlAAc(^JHX%HpWn&N+1tj-!;atG%OPuDmL33@3!gnz z)D6hq%Lz2qF<2N3`-x?R&sK;@5F1-ZrOf2)IuNHZW9$px^lex$&UUI?@GJ9MdNniq zP1DtL!D-Jq!~ct(sz;A2rE(=9e@^R+nK-tV?K4E=pW)Z{v8K&y4_ff?^VhB~FD?Tn zq|URS{O3J!oV_Ua z&&uL6#PbUZFyw;%vgYw%vQo#dZf?$Pd^rB@cNic$BGQ(#vsxU{?VE;}bhgshZBCPt zQ6NZ9Zc^PIlB5J~8+G1Q=l(I?D42Rzu|%BoIqUgB@R2Mt6K2AB9IM3<`wOqRAgDm@ zAle(KhWfZH*pfT88$bqdbruS_1vGzqkXRCaC+m?Jec@I~~~TaD9bNPpJ> zks-mve4X#aHBV0Te}oDRZp&)u*&%-Rb3c}mq~zmANhgnJBLsDN4MSLJB3h-*^1i$v z7Sziy!LEP$#_}55C@O2$S-~+aq0atozvcB?S0s23q)yr2j>qA${{8gjCi}+3j2iHt zuGuC0aliW-xs5$M!M;@udow@*@1=5f=!Sb`k|T)^BKUV-wwX#SuTwNUD(IHzL>$zI zeU7Ay<5%q5ITtK{MYldq?XP$^Vzv@J_#OG^{+8?q?mcOzlL%DMo663pQYH*9u2Dph z)<;9YBLL6%P%biJQ1DTn7B4B<1&xK+|4qg%pR0L`#yCVr8yAbE(}crCuGzN*DYHw6 zX*h|BzIfLiVRW0CM17({TeSyI`>}^3t|nnxV)y=&xy!h~WR8sE@&#ZitZ=l==NNWL zT6DZ<2Iwxr{9BJ0rp#n92NvkkSl(y?3LmYwFB8Q{2U-oZI<|~-1%jx*diMiNM_O_SSyDeWrNF47D)#N*QV6_BB z6usH_)rM!RQBVIs{NQe!xUnH|;+PnS*SJq4d%%I07)NfeFAER__V6UYvU&!3WJVi_w$LKYy<{LCN6K7y6 zblR86?5^rV#*q%>;fR6@a>&~W01$oXKiOF>RZDy~osP-(e9kq08v}wbdI>9R_{llx zFyH@;m*7u+)rwzy9f7+%nTyn&gmqab;q9piDNBMNrO5=-#ZgjQn19c)2hHRIZvfSh z0lnDtb=9K8hq-YlP;sTzs9*?Enl;2ZCv9AagNpaUgP0;UZKx(3=COXfKEg4?WLc2( zjyM@re!1d_ai|fD|EBu2ieITKHY}9;hpM<%FHsaVM$OEvM4?;<0|p&SAKbAL`0bMj zQKkRyE6G(>mte(SUwiQ?P#qPthzyNy^DqJew4MQrq3ai~V^~){kS??zVIWw}KkI!g zJti4DotexXslBc;Dn{(zSQ!(u?%nhfdwgESGjAMKZ`^Y-rADlXUAwyB_(CMv$X3+) zD#ShM&u**rXyYH&e1GhyY1U?%OWXAUp6o0p0NjK=`Gb@Ymz9vE zW~5&y;}Rf!0Wf{n%6OC8YiN$Z6>(+J|DrQCAHgC)xuDbLh&GhPfIORcH_>~?F;#PK zve2-!-_f1o<6q_y9LH(`x%H(u%^5TTB$wGMvBn>iB=TEbP zTh9ZWb%uV+gScq1juHE&d6ZBo_7orz_iSEHRXssW=unUydhHS^L9o6<4|fK|(SdU4 z?z4=^E*ZE=;KoBDtmkT76K)1TzZt7h<`ZYu5lT%DZq9PQrWW?E%bP%-#`6*#M-l;E{P*Yn&76?LMEB~4CzN~qfI<

v0Vk z(0xrKNJm$^uGR-6*`z+~F1!(%)7S1b#3Y;l%|GizEd6o)+~o5qwh3eJSHA9HZ;VjI z&nX11=IT;-zOT!7rWrq|V~5>8NR^4fTv^&pIgst-6y;oJbSc~^dh6j5xB|pZ}pyGn>-QkK3HZ|^yF^s@##Dg9SuU7QvtTvfO3@y|*Ozd4X6TqJ;Uq(^w~#XeZ?yt!JgrSyfL-+WoAh z*ttQP_G>8K#*`d!T8{rG0 zrT{jvIsJmO6PVC9p2OeafaAn|#MO}ff}~POH1D;J?tgT^%zvkU%`4^^N3}e~2e(JG zS3JlBZMZnYmLW$s)ohHyB#42wmRIK)m7{GGt3Mb)P2s1r@OTML9FJBNr5hy5iBy+c z{u9n#HEwpb5a5?jsTg*dl>L`XTC5Dkq}&=}g>C4#bU{1Ht_NvTjh$00BOi?<47 zWhE~qq{`PkBx!}LA+GXzdU{{0s#Ll{ZynJlPs%p{;>tqA7&E#CGahCqS+G-Wn$8}8yy`j(vS$f2~cm1z$MlG z-F16r!pX(8I>-_hIlOiye2z|rA-`%Bz01iV?)cS7s^H%nAOX)gPaW{|6fq37hIE`T z5k+}x-A}7;3FPGmrrzmGD=n_*Dr3a6mEAu-5S!T^dcfUOQSj15yub>D1F^3tPUII5 zQ1$doMTm+{&JAj{2A}WuxQI7}j5`tGJSji%z(&d;HZ~vYTOngKGXp!WIY69NOLvH% z`G5yl9z}sk(RO%`7QQ0F->iDH7Cpdv5zzsbkOHX|rVsTL@blzL{oJ0cBDvbWZ!&rU z0PPuoY-_UQ{&v>r{2mokaEKYuag9|Ia7ZniLa?_jw{oiaO$MKWBoL#lqA_81Z|Tqw z($z%)R^-XGgQt5}b97ErDIv-nd0%r~WBb9jyb$?MB z7<}T9Pnd$cZLgoR8Z|N;Q=wg@op1p2zZCEIMsZ|VX%}>)zM9>C(&4qc<(eQ+81?LG zZKdD*PwbNV&uk?_Z*On@%q!2+9X;Xfar>gM`#b&*{zrw4D0KR|P*e)ARh`{HR{}Cs zu=0gFW}nbJ^=La+I*hqj91liYm9>M_{y+lqp6ecGbdCiS1-HHX>BN=1f9zSZJCL@s z^U7sOEr<4Cxs3+2H&-5wfO&$L^K5=|Gc6V_31{E!%}u_pv;((|aOUChr~tYoFSIUG zg^!v;LV_j&cbX-}8NWyhFgr(OUxjtzuD>idUHG%=v;Agermgau&*jle#X7!SEA)X^ z>Cu}Qe{9LX&)GcHomag3D0hY#kyIouVqw8}$X58~ylc!)jfns#n4aG&eEb>6!Ds zX`9m|+ruAR~zz)Boy^FOqN`ZyG#Df_i<<7eG!NLHU)AWgz|mg*>cZG5q(i-W9HoQ&acr} zjx)YO-wc~Q*r@qUWA)1Qtp?(#Y@1&^=p%*QSZ->Z3%u$byURFMD732Wv_8DLm~Evi!AkF9Y8G{AQz?*=FR6-C?_$lA>Ti6{asIlr}N#iqM6uc`*fCOWY44Oow0_e zXYEW45MW6+MBaMe+|24Ed3yO)7eJh4+6C~ox1^xg$ zoCgODl3(1gUG7)(J5Xd7JLT1^DUxpSVElY8E?&*B_$c+&iyO#ZftQ%&G)4~4^g8tG z#%_iYZx7<~+^#c!#@|X zrehI2*l}$01^trEO_x5qBjaVU$#OMMFh^9Ty)zRZeqTtd}VGiRtKy#P1m} zAFBASy(uT9n7^PZW?x-ix2-cW|6c9U!7@nM!cmJPm$*9F$6bR5G8eVdcCrD7mbP|w zQ^)}%Qo7RI{_iLr`IcG9&v8lC3#&QTt|R;i65soeF>!QL@DO39CASV2bAi6&(-_-8 zaS$hE-nc~`C@srSBm@D9+Pom~M@svG1+C|iUpNEVreLMls469gPI@)W(68OTHsprY zD3^FZHO#hQii1)1lU5D`J$*i$yP`Ky<)K8LkF>fxct8nc_6O(_Ac*x{($##S$@ymE zMkyChyl{h!`F7$qt_6ypX%3^sLn42!09*VJXhVPqW!{UJ{qGavCmYJJo@VChFB&#! z|B;z|bLmNctbr79o~s(@jUqJ3Ip}{&akS`uCb2#;;^=1W3!rExKh0W2?uRf_=CwnVsv9reilBJ73%_>HHR{ba2jXcWc0Y%6b8%spmX@Z~WQsVj z^NyS_%a>?mMT1)?lr%JpH*-rBF7q{3w8H(JB|hEw7%L#}?K(Jcao{9~w4Gr_Zk`eQ zS1Hw=*<~HI62?59>szG2%msd9qf9h88!M6}A@Zg7HYdx&sSj;VHpUa8SV3tqBuq+h z=nSFb)2EOmX1N_twvVDt>F(@d*CwYnWfK#lBYl zJ;w`wA~}rUJ(r0~&@m1oE~8Lwka8(j(&utftGxI#jRYeJ@yEvKS*z62C;@Y6BJ538 zi_74W1FT7l@tB(k9&!Iuyv917q`ruY)6-u_huCS%ZhVrcfL{jsF^cqlwhLmXv#1+v zu}9OD{CbvgcUN>jDriaTM}kVfESzFHHINIs^4L@s(a^laCBzw7Ilpe=v1&yn6}X|5 zjM*kFU|5$s!$_S~(pp9X}t{$qlGNmW}2IlXTp0x1pvtWF+5J0zR*?0m#c&!I}t zuZxG`KLTOe;jeRZBM}KN`T8J=3a@=ccDimK?Y(}zAIWl`Hjq@4=)M&2!=pNtyB^;c z)k)~fKLd#ySdXBXWv~y7>dWkufpW@6wC|GRh7C5XMAT@mPADvg4mW!mELAD4#h9K~oEg952` z{77V6it4Y7v_~oSFg`Gb%LJVY>d1i zAJ~4cM5{#1wZSglP9RX{{=bTI7`7#wqB9c&opo7%_yUTEbp`+=_J*dRi)pB5PCUR~ ziQuNk;}P})W#}G(GO)9=v+zSPCF5I3aWY37U~6wL?W(u4KJ{gJuei;3H+m92v9MSF z)1`l4z=)~Z3!6GVDiSi6{OKe>HdzwkLlxtVn6n6D5)#s|H+PuGRo2%}Uu0yiG_1um zGBRrRpLjzNr!VL@$=ZHA^5`QhtaAo>{ww%EOLOrLs*43oxVlV(SFd=#PfrW^S2?#O z>6BD`#iVvp+^Ydka(?Dhb^w9I9|Vy{ORJtCTMq#l^*6 z%gU6%S9p1OCyP``)rz%2d`PwbURztc#*3bQ9pT-lmB1P}+?zW~&NP&2%vk#99y4StfKB%*0jHZ1282kITb&c^nKLi(-Ls*!c zi;L^<=twazP^!wbCEUCth)qr|$A>;ME$!*T3Kb<~erG4MQUTOOoPmdjhnt^2T8&c) zoJ>wm4t~`0^4N|xQN^Rh6eQy*b`~8$RK}Im;B@vBJaBrm*E)eu>zS|3pd%IB$ms3T zl4<|opv@LMUfBNPaP_0KFK?zwsjl_-Cowe*4dsJh9c1L>nEV-pEv88znSkD2pAY`s zo*w>tet;VMBu4!(*#?=w<~mQpEE}7h&B$lgN(qjr8XD5#g?A@1NDNhZPvpuCjgJp3 zx_4>N$$NQuz01o>wi`_^sH-C{C@A0-5GVUsi>j|Y4}&bX6mVu5~lt5 zCu~461{61Xmn{RSwi6&)9qA?G^ zINx9HyFA$hJs3VV_QFv#*VBBRXl$u}`xNXoqlaz74f$|lY&=hYH98x#pU#@B90dr{ zC)#GXiDwn)mshqnArCj85R8Z(pwZZMhBa~3#|iZ6WWkeXGPm8`3UMs*Ihqc7v!Lcs zMn<9!>H|Y!sG#(0)$(zS7AdiK~-vnLi6)tDQ$FD}TK2R}bc_4qR{G_>wT#Bet0 z+nzJ*czjxQ&wKC1&0HGWayIj9f1i}k*}7T8lJ5%owl|%_bONk zXJ%&pEm@aWSwR7@9D<37$(lMm6egRytHPL=T8|XSd0Vni%pkr3f{e1378z*g=h3Pv zDoPJhjUG?5`X7*FzEBDJ%ZY~xQACW}KY5T^@H~UxZ&aszX6;~F_5ifTkZm1_r~PCB zu;n8iJ75hqESwJ?qK8sAK*7j2ShxyM$OFyn*tZl93t#5R!%KB1cTd;m?S(V*^~>yKXug!($IL1uL^eD;{7$_X%-a5JDQ}mc5f2(NGBSOW zW^-ArqR?+&zv3~7yRe>PUWCXy*-kmyot#X-Y<6cW1JH>Qa8uKSh6d4#tE>0^TmKk3 z9UYzR6~#kMOOR~^tbZ{0hjCy-N)Li1?B)=Qot>Q(idSRRhclK3W2v_vXK&I!4U+4; zIeyMfNigp0UtbQ<$^@d_Lt)~bAw5uqM{`I{?G=U?nHr*WesNI%E{#xstdM# z``uPpkw4=}`3$>&0I6A^EZqWChGZN$(9J`~R}v zg7K4*#j9PKw#2s79w^e1g2uPDu_>6|hFe%9Gs*b*3_Yi1QKrR5s1)^p%vsRykD=`4 zT57Y;HaQUQYVPNkS~7tYFkywhlY8PFsl@hKn`hlN?(sh-T0wxHKYxC{_D&t{=Oz_M zvTC1yfQR`nrX}1MF|n{b_ZGxt&zHOdj-SP&Hwp{cD@>Y5Ad0N)?3m>S)j073?1}tU z7F|hGMXIU6yI{2xAKU}m0;}Fge8Y|)v0s6kOuD+d@CuJ6m!H4)&Xyzl_EvJ`f_rU+ z3qbJh?CgvMSuj{I)6}`Z#85k_0n0C#SbT;xfL9A+U|=Bh?Ne@UZr1Zt8*mu=<+Eq_ z4aSebU6$@u54f?hI*)|3G`%b{sf61SqN@ugMKk%Tk(-AHBYE%%STD7VjbEK?xC{Ih zMUnW;V~M)cHX3WxI&}H&QWu@mJBcR0(gbDiU5_99*5Uo2@AnrcqM&bn7B&KhbWcoF z&k%gfk?{w0l@cXA`7Ym(E!%`T@(|by9}Iy628tFCVKg>2Cj43NkZoB{EHNvUf|ZOx|MNv}T!n>uEMG4~Lc; readFullJsonReport, + IReportValidation validation, + IServiceProvider provider) : IComparerCommand +{ + private readonly Func _readFullJsonReport = readFullJsonReport; + private readonly IReportValidation _validation = validation; + private readonly IServiceProvider _provider = provider; + + + public void Execute( + string? baseline, + string? target, + string? meanThreshold, + string? allocationThreshold, + string[] formats, + string output) + { + var baselineReport = _readFullJsonReport(baseline); + var targetReport = _readFullJsonReport(target); + + var comparerReport = new ComparerReport + { + Warnings = _validation.HostEnvironmentValidate(baselineReport, targetReport) + }; + + + + foreach(var baselineBenchmark in baselineReport.Benchmarks ?? []) + { + var targetBenchmark = targetReport.Benchmarks? + .SingleOrDefault(s => s.FullName.Equivalente(baselineBenchmark.FullName)); + + var current = new Comparison + { + Name = baselineBenchmark.Method, + FullName = baselineBenchmark.FullName, + + Mean = MetricComparison.CalculateExecutionTime( + baselineBenchmark.Statistics?.Mean, + targetBenchmark?.Statistics?.Mean), + + Allocated = MetricComparison.CalculateMemoryUsage( + baselineBenchmark.Memory?.BytesAllocatedPerOperation, + targetBenchmark?.Memory?.BytesAllocatedPerOperation) + }; + + if(targetBenchmark is not null) + { + // Remove a target benchmark because matched with baseline. + // The purpose is to keep only the unmatched benchmarks in the target report for next loop only have unmatched benchmarks. + targetReport.Benchmarks?.Remove(targetBenchmark); + } + + comparerReport.Add(current); + } + + // Add the unmatched benchmarks from the target report. + foreach(var targetBenchmark in targetReport.Benchmarks ?? []) + { + comparerReport.Add(new() + { + Name = targetBenchmark.Method, + FullName = targetBenchmark.FullName, + + Mean = MetricComparison.CalculateExecutionTime( + null, + targetBenchmark?.Statistics?.Mean), + + Allocated = MetricComparison.CalculateMemoryUsage( + null, + targetBenchmark?.Memory?.BytesAllocatedPerOperation) + }); + } + + + + if(!string.IsNullOrWhiteSpace(meanThreshold)) + { + var meanThresholdValue = TimeThreshold.Parse(meanThreshold); + + foreach(var comparison in comparerReport.Comparisons) + { + if(meanThresholdValue.IsPercentage) + { // If the threshold is a percentage, the validation is done against the percentage difference + if(comparison.Mean?.DiffPercentage > meanThresholdValue.Value) + { + comparerReport.HitThresholds.Add($"Mean threshold hit for '{comparison.FullName}'"); + } + } + else + { // If the threshold is a value, the validation is done against the absolute difference + if(comparison.Mean?.Diff > meanThresholdValue.Value) + { + comparerReport.HitThresholds.Add($"Mean threshold hit for '{comparison.FullName}'"); + } + } + } + } + + + if(!string.IsNullOrWhiteSpace(allocationThreshold)) + { + var allocationThresholdValue = MemoryThreshold.Parse(allocationThreshold); + + foreach(var comparison in comparerReport.Comparisons) + { + if(allocationThresholdValue.IsPercentage) + { // If the threshold is a percentage, the validation is done against the percentage difference + if(comparison.Allocated?.DiffPercentage > allocationThresholdValue.Value) + { + comparerReport.HitThresholds.Add($"Allocation threshold hit for '{comparison.FullName}'"); + } + } + else + { // If the threshold is a value, the validation is done against the absolute difference + if(comparison.Allocated?.Diff > allocationThresholdValue.Value) + { + comparerReport.HitThresholds.Add($"Allocation threshold hit for '{comparison.FullName}'"); + } + } + } + } + + foreach(var format in formats) + { + // Generate the final report using the specified exporter + _provider + .GetRequiredKeyedService(format.ToLower()) + .Generate(comparerReport, output); + } + } +} diff --git a/src/Exporters/ConsoleExporter.cs b/src/Exporters/ConsoleExporter.cs new file mode 100644 index 0000000..307bd2f --- /dev/null +++ b/src/Exporters/ConsoleExporter.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections.Generic; +using PowerUtils.BenchmarkDotnet.Reporter.Helpers; +using PowerUtils.BenchmarkDotnet.Reporter.Models; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Exporters; + +public sealed class ConsoleExporter(IOHelpers.Printer printer) : IExporter +{ + private readonly IOHelpers.Printer _printer = printer; + + public void Generate(ComparerReport report, string outputDirectory) + { + _printer(Environment.NewLine); + _printer("══════════════════════════════════════════════════════════════════════════════════"); + _printer(Environment.NewLine); + _printer(" BENCHMARK COMPARISON REPORT"); + _printer(Environment.NewLine); + _printer("══════════════════════════════════════════════════════════════════════════════════"); + _printer(Environment.NewLine); + _printer(Environment.NewLine); + + if(report.Warnings.Count != 0) + { + _printer("âš ī¸ WARNINGS:"); + _printer(Environment.NewLine); + _printer(Environment.NewLine); + + foreach(var warning in report.Warnings) + { + _printer($" â€ĸ {warning}"); + _printer(Environment.NewLine); + } + + _printer(Environment.NewLine); + _printer("................................................................................."); + _printer(Environment.NewLine); + _printer(Environment.NewLine); + } + + _printer("📊 RESULTS:"); + _printer(Environment.NewLine); + _printer(Environment.NewLine); + + if(report.Comparisons.Count == 0) + { + _printer(" No comparisons found."); + _printer(Environment.NewLine); + } + else + { + const int SPACE_BETWEEN_COLUMNS = 5; + var rows = new List + { + new[] { "Report", "Method", "Mean", "Allocated" } + }; + + var reportWidth = rows[0][0].Length + SPACE_BETWEEN_COLUMNS; + var methodWidth = rows[0][1].Length + SPACE_BETWEEN_COLUMNS; + var meanWidth = rows[0][2].Length + SPACE_BETWEEN_COLUMNS; + var allocatedWidth = rows[0][3].Length; + + + foreach(var comparison in report.Comparisons) + { + var row = new string[] { + "Baseline", + comparison.Name ?? "", + comparison.Mean?.Baseline.BeautifyTime() ?? "", + comparison.Allocated?.Baseline.BeautifyMemory() ?? "" + }; + rows.Add(row); + + reportWidth = Math.Max(reportWidth, row[0].Length + SPACE_BETWEEN_COLUMNS); + methodWidth = Math.Max(methodWidth, row[1].Length + SPACE_BETWEEN_COLUMNS); + meanWidth = Math.Max(meanWidth, row[2].Length + SPACE_BETWEEN_COLUMNS); + allocatedWidth = Math.Max(allocatedWidth, row[3].Length); + + + var mean = comparison.Mean?.Target.BeautifyTime(); + if(comparison.Mean?.Status is ComparisonStatus.Better or ComparisonStatus.Worse) + { + mean = $"{mean} ({comparison.Mean?.DiffPercentage.BeautifyPercentage()})"; + } + + var allocated = comparison.Allocated?.Target.BeautifyMemory(); + if(comparison.Allocated?.Status is ComparisonStatus.Better or ComparisonStatus.Worse) + { + allocated = $"{allocated} ({comparison.Allocated?.DiffPercentage.BeautifyPercentage()})"; + } + + + row = [ + "Target", + comparison.Mean?.Status is ComparisonStatus.Removed or ComparisonStatus.New + ? $"[{comparison.Mean?.Status.ToString().ToUpper()}]" + : "", + mean ?? "", + allocated ?? "" + ]; + + rows.Add(row); + + reportWidth = Math.Max(reportWidth, row[0].Length + SPACE_BETWEEN_COLUMNS); + methodWidth = Math.Max(methodWidth, row[1].Length + SPACE_BETWEEN_COLUMNS); + meanWidth = Math.Max(meanWidth, row[2].Length + SPACE_BETWEEN_COLUMNS); + allocatedWidth = Math.Max(allocatedWidth, row[3].Length); + } + + // Header + _printer($"{rows[0][0].PadRight(reportWidth)}{rows[0][1].PadRight(methodWidth)}{rows[0][2].PadRight(meanWidth)}{rows[0][3]}"); + _printer(Environment.NewLine); + _printer(new string('─', reportWidth + methodWidth + meanWidth + allocatedWidth)); + _printer(Environment.NewLine); + + for(var i = 1; i < rows.Count; i++) + { + var row = rows[i]; + _printer($"{row[0].PadRight(reportWidth)}{row[1].PadRight(methodWidth)}{row[2].PadRight(meanWidth)}{row[3]}"); + _printer(Environment.NewLine); + } + } + + if(report.HitThresholds.Count != 0) + { + _printer(Environment.NewLine); + _printer("................................................................................."); + _printer(Environment.NewLine); + _printer(Environment.NewLine); + _printer("🚨 THRESHOLD VIOLATIONS:"); + _printer(Environment.NewLine); + _printer(Environment.NewLine); + + foreach(var hit in report.HitThresholds) + { + _printer($" â€ĸ {hit}"); + _printer(Environment.NewLine); + } + } + + _printer(Environment.NewLine); + _printer("══════════════════════════════════════════════════════════════════════════════════"); + _printer(Environment.NewLine); + } +} diff --git a/src/Exporters/HitTxtExporter.cs b/src/Exporters/HitTxtExporter.cs new file mode 100644 index 0000000..a59c082 --- /dev/null +++ b/src/Exporters/HitTxtExporter.cs @@ -0,0 +1,33 @@ +using System.IO; +using System.Text; +using PowerUtils.BenchmarkDotnet.Reporter.Models; +using static PowerUtils.BenchmarkDotnet.Reporter.Helpers.IOHelpers; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Exporters; + +public sealed class HitTxtExporter(FileWriter writer) : IExporter +{ + private readonly FileWriter _writer = writer; + + public void Generate(ComparerReport report, string outputDirectory) + { + var sb = new StringBuilder(); + + foreach(var warning in report.Warnings) + { + sb.AppendLine(warning); + } + + foreach(var hit in report.HitThresholds) + { + sb.AppendLine(hit); + } + + if(sb.Length > 0) + { + _writer( + Path.Combine(outputDirectory, "benchmark-comparison-hits.txt"), + sb.ToString()); + } + } +} diff --git a/src/Exporters/IExporter.cs b/src/Exporters/IExporter.cs new file mode 100644 index 0000000..9f184bd --- /dev/null +++ b/src/Exporters/IExporter.cs @@ -0,0 +1,8 @@ +using PowerUtils.BenchmarkDotnet.Reporter.Models; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Exporters; + +public interface IExporter +{ + void Generate(ComparerReport report, string outputDirectory); +} diff --git a/src/Exporters/JsonExporter.cs b/src/Exporters/JsonExporter.cs new file mode 100644 index 0000000..7bbd5fb --- /dev/null +++ b/src/Exporters/JsonExporter.cs @@ -0,0 +1,25 @@ +using System.IO; +using System.Text.Encodings.Web; +using System.Text.Json; +using PowerUtils.BenchmarkDotnet.Reporter.Models; +using static PowerUtils.BenchmarkDotnet.Reporter.Helpers.IOHelpers; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Exporters; + +public sealed class JsonExporter(FileWriter writer) : IExporter +{ + private readonly FileWriter _writer = writer; + + private static readonly JsonSerializerOptions _options = new() + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + WriteIndented = true + }; + + public void Generate(ComparerReport report, string outputDirectory) + => _writer( + Path.Combine(outputDirectory, "benchmark-comparison-report.json"), + JsonSerializer.Serialize( + report, + _options)); +} diff --git a/src/Exporters/MarkdownExporter.cs b/src/Exporters/MarkdownExporter.cs new file mode 100644 index 0000000..7e81f9d --- /dev/null +++ b/src/Exporters/MarkdownExporter.cs @@ -0,0 +1,134 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using MarkdownLog; +using PowerUtils.BenchmarkDotnet.Reporter.Helpers; +using PowerUtils.BenchmarkDotnet.Reporter.Models; +using static PowerUtils.BenchmarkDotnet.Reporter.Helpers.IOHelpers; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Exporters; + +public sealed class MarkdownExporter(FileWriter writer) : IExporter +{ + private readonly FileWriter _writer = writer; + + public void Generate(ComparerReport report, string outputDirectory) + { + var sb = new StringBuilder(); + sb.AppendLine("# BENCHMARK COMPARISON REPORT"); + sb.AppendLine(); + + if(report.Warnings.Count != 0) + { + sb.AppendLine("## âš ī¸ WARNINGS:"); + sb.AppendLine(); + } + + foreach(var warning in report.Warnings) + { + sb.AppendLine($" * {warning}"); + } + + if(report.Warnings.Count != 0) + { + sb.AppendLine(); + sb.AppendLine(); + } + + sb.AppendLine("## 📊 RESULTS:"); + sb.AppendLine(); + + if(report.Comparisons.Count == 0) + { + sb.Append(" NO COMPARISONS FOUND."); + } + else + { + var table = new Table + { + Columns = [ + new() + { + HeaderCell = new TableCell { Text = "Report" } + }, + new() + { + HeaderCell = new TableCell { Text = "Method" } + }, + new() + { + HeaderCell = new TableCell { Text = "Mean" }, + Alignment = TableColumnAlignment.Right, + }, + new() + { + HeaderCell = new TableCell { Text = "Allocated" }, + Alignment = TableColumnAlignment.Right, + } + ] + }; + + + var rows = new List(); + foreach(var comparison in report.Comparisons) + { + rows.Add(new() + { + Cells = + [ + new TableCell { Text = "Baseline" }, + new TableCell { Text = comparison.Name }, + new TableCell { Text = comparison.Mean?.Baseline.BeautifyTime() }, + new TableCell { Text = comparison.Allocated?.Baseline.BeautifyMemory() }, + ] + }); + + + var mean = comparison.Mean?.Target.BeautifyTime(); + if(comparison.Mean?.Status is ComparisonStatus.Better or ComparisonStatus.Worse) + { + mean = $"{mean} ({comparison.Mean?.DiffPercentage.BeautifyPercentage()})"; + } + + var allocated = comparison.Allocated?.Target.BeautifyMemory(); + if(comparison.Allocated?.Status is ComparisonStatus.Better or ComparisonStatus.Worse) + { + allocated = $"{allocated} ({comparison.Allocated?.DiffPercentage.BeautifyPercentage()})"; + } + + rows.Add(new() + { + Cells = + [ + new TableCell { Text = "Target" }, + new TableCell { Text = comparison.Mean?.Status is ComparisonStatus.Removed or ComparisonStatus.New + ? $"[{comparison.Mean?.Status.ToString().ToUpper()}]" + : "" }, + new TableCell { Text = mean }, + new TableCell { Text = allocated }, + ] + }); + } + table.Rows = rows; + sb.Append(table.ToMarkdown()); + } + + + if(report.HitThresholds.Count != 0) + { + sb.AppendLine(); + sb.AppendLine(); + sb.AppendLine("## 🚨 THRESHOLD VIOLATIONS:"); + sb.AppendLine(); + foreach(var hitThreshold in report.HitThresholds.Order()) + { + sb.AppendLine($" * {hitThreshold};"); + } + } + + _writer( + Path.Combine(outputDirectory, "benchmark-comparison-report.md"), + sb.ToString()); + } +} diff --git a/src/Helpers/BeautifyExtentions.cs b/src/Helpers/BeautifyExtentions.cs new file mode 100644 index 0000000..9e5816f --- /dev/null +++ b/src/Helpers/BeautifyExtentions.cs @@ -0,0 +1,98 @@ +using System; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Helpers; + +public static class BeautifyExtentions +{ + public static string BeautifyTime(this decimal? time) + => time is null + ? "" + : time.Value.BeautifyTime(); + + public static string BeautifyTime(this decimal time) + { + var unit = "ns"; + if(time >= 1000) + { + time /= 1000; + unit = "Îŧs"; + + if(time >= 1000) + { + time /= 1000; + unit = "ms"; + + if(time >= 1000) + { + time /= 1000; + unit = "s"; + + + if(time >= 60) + { + var minutes = Math.Floor(time / 60); + var seconds = time % 60; + + return seconds == 0 + ? $"{minutes}m" + : $"{minutes}m {seconds:N0}s"; + } + } + } + } + + // Format with max 3 decimal places by removing trailing zeros + var formattedTime = time.ToString("N3").TrimEnd('0').TrimEnd('.'); + return $"{formattedTime} {unit}"; + } + + public static string BeautifyMemory(this decimal? memory) + => memory is null + ? "" + : memory.Value.BeautifyMemory(); + + public static string BeautifyMemory(this decimal memory) + { + var unit = "B"; + + if(memory >= 1024) + { + memory /= 1024; + unit = "KB"; + + if(memory >= 1024) + { + memory /= 1024; + unit = "MB"; + + if(memory >= 1024) + { + memory /= 1024; + unit = "GB"; + + if(memory >= 1024) + { + memory /= 1024; + unit = "TB"; + } + } + } + } + + // Format with max 3 decimal places by removing trailing zeros + var formattedMemory = memory.ToString("N3").TrimEnd('0').TrimEnd('.'); + return $"{formattedMemory} {unit}"; + } + + public static string BeautifyPercentage(this decimal? percentage) + => percentage is null + ? "" + : percentage.Value.BeautifyPercentage(); + + public static string BeautifyPercentage(this decimal percentage) + { + // Format with max 2 decimal places by removing trailing zeros + var formattedPercentage = percentage.ToString("N2").TrimEnd('0').TrimEnd('.'); + return $"{formattedPercentage}%"; + } +} diff --git a/src/Helpers/ComparableExtensions.cs b/src/Helpers/ComparableExtensions.cs new file mode 100644 index 0000000..610c3fe --- /dev/null +++ b/src/Helpers/ComparableExtensions.cs @@ -0,0 +1,21 @@ +using System; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Helpers; + +public static class ComparableExtensions +{ + public static bool Equivalente(this string? left, string? right) + { + if(left is null && right is null) + { + return true; + } + + if(left is null || right is null) + { + return false; + } + + return left.Equals(right, StringComparison.InvariantCultureIgnoreCase); + } +} diff --git a/src/Helpers/IOHelpers.cs b/src/Helpers/IOHelpers.cs new file mode 100644 index 0000000..baeb039 --- /dev/null +++ b/src/Helpers/IOHelpers.cs @@ -0,0 +1,75 @@ +using System; +using System.IO; +using System.Text.Json; +using PowerUtils.BenchmarkDotnet.Reporter.Models; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Helpers; + +public static class IOHelpers +{ + public const string REPORT_FILE_ENDS = "full.json"; + + + public delegate void Printer(string message); + public static void Print(string message) + => Console.Write(message); + + + public delegate void FileWriter(string path, string content); + public static void WriteFile(string path, string content) + { + path = Path.GetFullPath(path); + + var pathDir = Path.GetDirectoryName(path); + ArgumentNullException.ThrowIfNull(pathDir); + Directory.CreateDirectory(pathDir); + + File.WriteAllText(path, content); + + Print($"{Environment.NewLine}File exported to: '{path}'"); + } + + public static BenchmarkFullJsonResport ReadFullJsonReport(string? path) + { + path = GetFullJsonReport(path); + var content = File.ReadAllText(path); + + return JsonSerializer.Deserialize(content) + ?? throw new InvalidOperationException($"Failed to deserialize the {path} file."); + } + + public static string GetFullJsonReport(string? path) + { + if(string.IsNullOrWhiteSpace(path)) + { + throw new FileNotFoundException("The provided path is null or empty."); + } + + if(Directory.Exists(path)) + { + var files = Directory.GetFiles( + path, + $"*{REPORT_FILE_ENDS}", + SearchOption.AllDirectories); + + if(files.Length == 0) + { + throw new FileNotFoundException($"No {REPORT_FILE_ENDS} files found in the provided directory", path); + } + + if(files.Length > 1) + { + throw new FileNotFoundException($"Multiple {REPORT_FILE_ENDS} files found in the provided directory", path); + } + + return files[0]; + } + + if(File.Exists(path) && path.EndsWith(REPORT_FILE_ENDS, StringComparison.InvariantCultureIgnoreCase)) + { + return path; + } + + throw new FileNotFoundException($"The provided path '{path}' doesn't exist or is not a {REPORT_FILE_ENDS} file", path); + } +} diff --git a/src/Models/ComparerReport.cs b/src/Models/ComparerReport.cs new file mode 100644 index 0000000..62666f8 --- /dev/null +++ b/src/Models/ComparerReport.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Models; + +public sealed class ComparerReport +{ + public List Warnings { get; init; } = []; + public List Comparisons { get; private set; } = []; + public List HitThresholds { get; init; } = []; + + public void Add(Comparison comparison) + { + if(comparison.Mean is null && comparison.Allocated is null) + { + return; + } + + Comparisons.Add(comparison); + } + + + public sealed class Comparison + { + public string? Name { get; init; } + public string? FullName { get; init; } + + + public MetricComparison? Mean { get; set; } + public MetricComparison? Allocated { get; set; } + } +} diff --git a/src/Models/ComparisonStatus.cs b/src/Models/ComparisonStatus.cs new file mode 100644 index 0000000..53595de --- /dev/null +++ b/src/Models/ComparisonStatus.cs @@ -0,0 +1,10 @@ +namespace PowerUtils.BenchmarkDotnet.Reporter.Models; + +public enum ComparisonStatus +{ + New, + Worse, + Equal, + Better, + Removed +} diff --git a/src/Models/FullJsonResport.cs b/src/Models/FullJsonResport.cs new file mode 100644 index 0000000..ad040c2 --- /dev/null +++ b/src/Models/FullJsonResport.cs @@ -0,0 +1,149 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Models; +public sealed class BenchmarkFullJsonResport +{ // Generated with https://json2csharp.com/ + public string? Title { get; set; } + public HostEnvironmentInfoRecord? HostEnvironmentInfo { get; set; } + public List? Benchmarks { get; set; } + + + + public sealed class HostEnvironmentInfoRecord + { + public string? BenchmarkDotNetCaption { get; set; } + public string? BenchmarkDotNetVersion { get; set; } + public string? OsVersion { get; set; } + public string? ProcessorName { get; set; } + public int? PhysicalProcessorCount { get; set; } + public int? PhysicalCoreCount { get; set; } + public int? LogicalCoreCount { get; set; } + public string? RuntimeVersion { get; set; } + public string? Architecture { get; set; } + public bool? HasAttachedDebugger { get; set; } + public bool? HasRyuJit { get; set; } + public string? Configuration { get; set; } + public string? JitModules { get; set; } + public string? DotNetCliVersion { get; set; } + public ChronometerFrequencyRecord? ChronometerFrequency { get; set; } + public string? HardwareTimerKind { get; set; } + + + public sealed class ChronometerFrequencyRecord + { + public int Hertz { get; set; } + } + } + + public sealed class BenchmarkRecord + { + public string? DisplayInfo { get; set; } + public string? Namespace { get; set; } + public string? Type { get; set; } + public string? Method { get; set; } + public string? MethodTitle { get; set; } + public string? Parameters { get; set; } + public string? FullName { get; set; } + public string? HardwareIntrinsics { get; set; } + public StatisticsRecord? Statistics { get; set; } + public MemoryRecord? Memory { get; set; } + public List? Measurements { get; set; } + public List? Metrics { get; set; } + + + + public sealed class StatisticsRecord + { + public List? OriginalValues { get; set; } + public int N { get; set; } + public decimal Min { get; set; } + public decimal LowerFence { get; set; } + public decimal Q1 { get; set; } + public decimal Median { get; set; } + public decimal Mean { get; set; } + public decimal Q3 { get; set; } + public decimal UpperFence { get; set; } + public decimal Max { get; set; } + public decimal InterquartileRange { get; set; } + public List? LowerOutliers { get; set; } + public List? UpperOutliers { get; set; } + public List? AllOutliers { get; set; } + public decimal StandardError { get; set; } + public decimal Variance { get; set; } + public decimal StandardDeviation { get; set; } + public decimal Skewness { get; set; } + public decimal Kurtosis { get; set; } + public ConfidenceIntervalRecord? ConfidenceInterval { get; set; } + public PercentilesRecord? Percentiles { get; set; } + + + + public sealed class ConfidenceIntervalRecord + { + public int N { get; set; } + public decimal Mean { get; set; } + public decimal StandardError { get; set; } + public int Level { get; set; } + public decimal Margin { get; set; } + public decimal Lower { get; set; } + public decimal Upper { get; set; } + } + + public sealed class PercentilesRecord + { + public decimal P0 { get; set; } + public decimal P25 { get; set; } + public decimal P50 { get; set; } + public decimal P67 { get; set; } + public decimal P80 { get; set; } + public decimal P85 { get; set; } + public decimal P90 { get; set; } + public decimal P95 { get; set; } + public decimal P100 { get; set; } + } + } + + public sealed class MemoryRecord + { + public int Gen0Collections { get; set; } + public int Gen1Collections { get; set; } + public int Gen2Collections { get; set; } + public int TotalOperations { get; set; } + public int BytesAllocatedPerOperation { get; set; } + } + + public sealed class MeasurementRecord + { + public string? IterationMode { get; set; } + public string? IterationStage { get; set; } + public int LaunchIndex { get; set; } + public int IterationIndex { get; set; } + public int Operations { get; set; } + public int Nanoseconds { get; set; } + } + + public sealed class MetricRecord + { + public decimal Value { get; set; } + public DescriptorRecord? Descriptor { get; set; } + + + + public sealed class DescriptorRecord + { + public string? Id { get; set; } + public string? DisplayName { get; set; } + public string? Legend { get; set; } + public string? NumberFormat { get; set; } + public int UnitType { get; set; } + public string? Unit { get; set; } + public bool TheGreaterTheBetter { get; set; } + public int PriorityInCategory { get; set; } + } + } + } +} diff --git a/src/Models/MemoryThreshold.cs b/src/Models/MemoryThreshold.cs new file mode 100644 index 0000000..c40936e --- /dev/null +++ b/src/Models/MemoryThreshold.cs @@ -0,0 +1,84 @@ +using System; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Models; + +public readonly struct MemoryThreshold +{ + public readonly decimal Value; + public readonly bool IsPercentage; + + private MemoryThreshold(decimal value, bool isPercentage) + { + Value = value; + IsPercentage = isPercentage; + } + + + public static bool TryParse(string? value, out MemoryThreshold threshold) + { + threshold = default; + + if(string.IsNullOrWhiteSpace(value)) + { + return false; + } + + // Find the position where the numeric part ends + var i = 0; + while(i < value.Length && char.IsDigit(value[i])) + { + i++; + } + + // Extract the value part + if(!decimal.TryParse(value[..i], out var numericValue)) + { + return false; + } + + if(numericValue <= 0) + { + return false; + } + + var isPercentage = false; + + // Extract the unit part + var unit = value[i..]; + + // Match unit string to enum + switch(unit.ToLowerInvariant()) + { + case "b": + break; + case "kb": + numericValue *= 1_000; + break; + case "mb": + numericValue *= 1_000 * 1_000; + break; + case "gb": + numericValue *= 1_000 * 1_000 * 1_000; + break; + case "%": + isPercentage = true; + break; + default: + return false; + } + + threshold = new MemoryThreshold(numericValue, isPercentage); + return true; + } + + public static MemoryThreshold Parse(string? value) + { + if(TryParse(value, out var threshold)) + { + return threshold; + } + throw new FormatException($"The value '{value}' is not a valid threshold."); + } + + public static implicit operator decimal(MemoryThreshold threshold) => threshold.Value; +} diff --git a/src/Models/MetricComparison.cs b/src/Models/MetricComparison.cs new file mode 100644 index 0000000..220526a --- /dev/null +++ b/src/Models/MetricComparison.cs @@ -0,0 +1,80 @@ +namespace PowerUtils.BenchmarkDotnet.Reporter.Models; + +public sealed record MetricComparison +{ + public const string DEFAULT_UNIT_EXECUTION_TIME = "ns"; + public const string DEFAULT_UNIT_MEMORY_USAGE = "B"; + + + public string? Unit { get; init; } + public decimal? Baseline { get; private set; } + public decimal? Target { get; private set; } + public decimal? Diff { get; private set; } + public decimal? DiffPercentage { get; private set; } + public ComparisonStatus Status { get; private set; } + + private MetricComparison() { } + + + public static MetricComparison? CalculateExecutionTime(decimal? baseline, decimal? target) + => _calculate(baseline, target, DEFAULT_UNIT_EXECUTION_TIME); + + public static MetricComparison? CalculateMemoryUsage(decimal? baseline, decimal? target) + => _calculate(baseline, target, DEFAULT_UNIT_MEMORY_USAGE); + + private static MetricComparison? _calculate(decimal? baseline, decimal? target, string unit) + { + if(baseline is null && target is null) + { + return null; + } + + var comparison = new MetricComparison + { + Unit = unit, + Baseline = baseline, + Target = target + }; + + + if(baseline is not null && target is not null) + { + // When there is a baseline and target, we calculate the difference and percentage + comparison.Diff = target - baseline; + if(baseline != 0) + { + var percentage = comparison.Diff / baseline; + comparison.DiffPercentage = (decimal?)((double)percentage * 100); + } + + if(comparison.Diff == 0) + { + // The baseline and target are equal + comparison.Status = ComparisonStatus.Equal; + } + else if(comparison.Diff > 0) + { + // The target is worse than the baseline + comparison.Status = ComparisonStatus.Worse; + } + else + { + // The target is better than the baseline + comparison.Status = ComparisonStatus.Better; + } + } + else if(baseline is null) + { + // When there is no baseline, we consider the target as new + comparison.Status = ComparisonStatus.New; + } + else + { + // When there is no target, we consider the target as removed + comparison.Status = ComparisonStatus.Removed; + } + + + return comparison; + } +} diff --git a/src/Models/TimeThreshold.cs b/src/Models/TimeThreshold.cs new file mode 100644 index 0000000..8836b85 --- /dev/null +++ b/src/Models/TimeThreshold.cs @@ -0,0 +1,84 @@ +using System; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Models; + +public readonly struct TimeThreshold +{ + public readonly decimal Value; + public readonly bool IsPercentage; + + private TimeThreshold(decimal value, bool isPercentage) + { + Value = value; + IsPercentage = isPercentage; + } + + + public static bool TryParse(string? value, out TimeThreshold threshold) + { + threshold = default; + + if(string.IsNullOrWhiteSpace(value)) + { + return false; + } + + // Find the position where the numeric part ends + var i = 0; + while(i < value.Length && char.IsDigit(value[i])) + { + i++; + } + + // Extract the value part + if(!decimal.TryParse(value[..i], out var numericValue)) + { + return false; + } + + if(numericValue <= 0) + { + return false; + } + + var isPercentage = false; + + // Extract the unit part + var unit = value[i..]; + + // Match unit string to enum + switch(unit.ToLowerInvariant()) + { + case "ns": + break; + case "Îŧs": + numericValue *= 1_000; + break; + case "ms": + numericValue *= 1_000 * 1_000; + break; + case "s": + numericValue *= 1_000 * 1_000 * 1_000; + break; + case "%": + isPercentage = true; + break; + default: + return false; + } + + threshold = new TimeThreshold(numericValue, isPercentage); + return true; + } + + public static TimeThreshold Parse(string? value) + { + if(TryParse(value, out var threshold)) + { + return threshold; + } + throw new FormatException($"The value '{value}' is not a valid threshold."); + } + + public static implicit operator decimal(TimeThreshold threshold) => threshold.Value; +} diff --git a/src/PowerUtils.BenchmarkDotnet.Reporter.csproj b/src/PowerUtils.BenchmarkDotnet.Reporter.csproj new file mode 100644 index 0000000..bec6d16 --- /dev/null +++ b/src/PowerUtils.BenchmarkDotnet.Reporter.csproj @@ -0,0 +1,102 @@ +īģŋ + + + 38e8dca3-2e2f-4bc7-a90a-b5cbca282627 + + Exe + net9.0 + + PBReporter + PowerUtils.BenchmarkDotnet.Reporter + + 1.0.0 + + bin\ + + enable + 94a48ccf-feff-42b4-9dd8-595ea3b49567 + + + + + True + pbreporter + + + + + + $([System.DateTime]::UtcNow.ToString(yyyy)) + + PowerUtils.BenchmarkDotnet.Reporter + PowerUtils.BenchmarkDotnet.Reporter + PowerUtils.BenchmarkDotnet.Reporter + + Nelson Nobre + TechNobre + + MIT + LICENSE + Copyright Š $(CurrentYear) by TechNobre + + Tool to analyze and compare .NET benchmark reports +

Tool to analyze and compare .NET benchmark reports + PowerUtils;Utils;Helpers;Results;Reports;BenchmarkDotnet;Benchmark;Tools + https://github.com/TechNobre/PowerUtils.BenchmarkDotnet.Reporter + en-GB + + logo_128x128.png + README.nuget.org.md + + git + https://github.com/TechNobre/PowerUtils.BenchmarkDotnet.Reporter + + + + + + true + true + true + true + true + snupkg + + + + + + bin\PowerUtils.BenchmarkDotnet.Reporter.xml + true + $(NoWarn);1591 + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + diff --git a/src/Program.cs b/src/Program.cs new file mode 100644 index 0000000..373d737 --- /dev/null +++ b/src/Program.cs @@ -0,0 +1,32 @@ +using System; +using System.CommandLine; +using System.Globalization; +using System.Threading; +using Microsoft.Extensions.DependencyInjection; +using PowerUtils.BenchmarkDotnet.Reporter; +using PowerUtils.BenchmarkDotnet.Reporter.Commands; +using PowerUtils.BenchmarkDotnet.Reporter.Exporters; +using PowerUtils.BenchmarkDotnet.Reporter.Helpers; +using PowerUtils.BenchmarkDotnet.Reporter.Models; +using PowerUtils.BenchmarkDotnet.Reporter.Validations; + +// We print a lot of numbers here and we want to make it always in invariant way +Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; + +var serviceCollection = new ServiceCollection(); +serviceCollection + .AddTransient(sp => + (message) => IOHelpers.Print(message)) + .AddTransient(sp => + (path, content) => IOHelpers.WriteFile(path, content)) + .AddTransient>(sp => + (path) => IOHelpers.ReadFullJsonReport(path)) + .AddTransient() + .AddKeyedTransient("markdown") + .AddKeyedTransient("json") + .AddKeyedTransient("hit-txt") + .AddKeyedTransient("console") + .AddTransient(); + +var tool = new ToolCommands(serviceCollection.BuildServiceProvider()); +tool.Invoke(args); diff --git a/src/Properties/launchSettings.json b/src/Properties/launchSettings.json new file mode 100644 index 0000000..048a75b --- /dev/null +++ b/src/Properties/launchSettings.json @@ -0,0 +1,7 @@ +{ + "profiles": { + "PBReporter": { + "commandName": "Project" + } + } +} diff --git a/src/ToolCommands.cs b/src/ToolCommands.cs new file mode 100644 index 0000000..3893fd5 --- /dev/null +++ b/src/ToolCommands.cs @@ -0,0 +1,89 @@ +using System; +using System.CommandLine; +using Microsoft.Extensions.DependencyInjection; +using PowerUtils.BenchmarkDotnet.Reporter.Commands; + +namespace PowerUtils.BenchmarkDotnet.Reporter; + +public sealed class ToolCommands : RootCommand +{ + public ToolCommands(IServiceProvider provider) + { + var baseline = _createBaselineOption(); + var target = _createTargetOption(); + + var meanThreshold = _createMeanThresholdOption(); + var allocationThreshold = _createAllocationThresholdOption(); + + var formats = _createFormatsOption(); + var output = _createOutputOption(); + + var compareCommand = new Command( + "compare", + "Compare two BenchmarkDotNet reports and produce a diff report.") + { + baseline, + target, + meanThreshold, + allocationThreshold, + formats, + output + }; + + compareCommand + .SetHandler( + provider.GetRequiredService().Execute, + baseline, + target, + meanThreshold, + allocationThreshold, + formats, + output); + + Add(compareCommand); + } + + + private static Option _createBaselineOption() + => new( + ["-b", "--baseline"], + "Path to the folder or file with Baseline report.") + { + IsRequired = true + }; + + private static Option _createTargetOption() + => new( + ["-t", "--target"], + "Path to the folder or file with target reports.") + { + IsRequired = true + }; + + private static Option _createMeanThresholdOption() + => new( + ["-tm", "--threshold-mean"], + "Throw an error when the mean threshold is met. Examples: 5%, 10ms, 10Îŧs, 100ns, 1s."); + + private static Option _createAllocationThresholdOption() + => new( + ["-ta", "--threshold-allocation"], + "Throw an error when the allocation threshold is met. Examples: 5%, 10b, 10kb, 100mb, 1gb."); + + private static Option _createFormatsOption() + => new Option( + ["-f", "--format"], + () => ["console"], + "Output format for the report.") + .FromAmong( + "console", + "markdown", + "json", + "hit-txt"); + + private static Option _createOutputOption() + => new( + ["-o", "--output"], + () => "./BenchmarkReporter", + "Output directory to export the diff report. Default is current directory."); +} diff --git a/src/Validations/ReportValidation.cs b/src/Validations/ReportValidation.cs new file mode 100644 index 0000000..12419f8 --- /dev/null +++ b/src/Validations/ReportValidation.cs @@ -0,0 +1,75 @@ +using System.Collections.Generic; +using PowerUtils.BenchmarkDotnet.Reporter.Helpers; +using PowerUtils.BenchmarkDotnet.Reporter.Models; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Validations; + +public interface IReportValidation +{ + List HostEnvironmentValidate(BenchmarkFullJsonResport baseline, BenchmarkFullJsonResport target); +} + +public sealed class ReportValidation : IReportValidation +{ + public List HostEnvironmentValidate(BenchmarkFullJsonResport baseline, BenchmarkFullJsonResport target) + { + var messages = new List(); + + if(baseline.HostEnvironmentInfo?.OsVersion.Equivalente(target.HostEnvironmentInfo?.OsVersion) == false) + { + messages.Add($"OS Version is different: '{baseline.HostEnvironmentInfo?.OsVersion}' != '{target.HostEnvironmentInfo?.OsVersion}'"); + } + + if(baseline.HostEnvironmentInfo?.ProcessorName.Equivalente(target.HostEnvironmentInfo?.ProcessorName) == false) + { + messages.Add($"Processor Name is different: '{baseline.HostEnvironmentInfo?.ProcessorName}' != '{target.HostEnvironmentInfo?.ProcessorName}'"); + } + + if(baseline.HostEnvironmentInfo?.PhysicalProcessorCount != target.HostEnvironmentInfo?.PhysicalProcessorCount) + { + messages.Add($"Physical Processor Count is different: '{baseline.HostEnvironmentInfo?.PhysicalProcessorCount}' != '{target.HostEnvironmentInfo?.PhysicalProcessorCount}'"); + } + + if(baseline.HostEnvironmentInfo?.PhysicalCoreCount != target.HostEnvironmentInfo?.PhysicalCoreCount) + { + messages.Add($"Physical Core Count is different: '{baseline.HostEnvironmentInfo?.PhysicalCoreCount}' != '{target.HostEnvironmentInfo?.PhysicalCoreCount}'"); + } + + if(baseline.HostEnvironmentInfo?.LogicalCoreCount != target.HostEnvironmentInfo?.LogicalCoreCount) + { + messages.Add($"Logical Core Count is different: '{baseline.HostEnvironmentInfo?.LogicalCoreCount}' != '{target.HostEnvironmentInfo?.LogicalCoreCount}'"); + } + + if(baseline.HostEnvironmentInfo?.RuntimeVersion.Equivalente(target.HostEnvironmentInfo?.RuntimeVersion) == false) + { + messages.Add($"Runtime Version is different: '{baseline.HostEnvironmentInfo?.RuntimeVersion}' != '{target.HostEnvironmentInfo?.RuntimeVersion}'"); + } + + if(baseline.HostEnvironmentInfo?.Architecture.Equivalente(target.HostEnvironmentInfo?.Architecture) == false) + { + messages.Add($"Architecture is different: '{baseline.HostEnvironmentInfo?.Architecture}' != '{target.HostEnvironmentInfo?.Architecture}'"); + } + + if(baseline.HostEnvironmentInfo?.DotNetCliVersion.Equivalente(target.HostEnvironmentInfo?.DotNetCliVersion) == false) + { + messages.Add($"DotNet CLI Version is different: '{baseline.HostEnvironmentInfo?.DotNetCliVersion}' != '{target.HostEnvironmentInfo?.DotNetCliVersion}'"); + } + + if(baseline.HostEnvironmentInfo?.ChronometerFrequency?.Hertz != target.HostEnvironmentInfo?.ChronometerFrequency?.Hertz) + { + messages.Add($"Chronometer Frequency is different: '{baseline.HostEnvironmentInfo?.ChronometerFrequency?.Hertz}' != '{target.HostEnvironmentInfo?.ChronometerFrequency?.Hertz}'"); + } + + if("RELEASE".Equivalente(baseline.HostEnvironmentInfo?.Configuration) == false) + { + messages.Add($"The baseline report wasn't executed in RELEASE mode: '{baseline.HostEnvironmentInfo?.Configuration}'"); + } + + if("RELEASE".Equivalente(target.HostEnvironmentInfo?.Configuration) == false) + { + messages.Add($"The target report wasn't executed in RELEASE mode: '{target.HostEnvironmentInfo?.Configuration}'"); + } + + return messages; + } +} diff --git a/src/Validations/ValidationException.cs b/src/Validations/ValidationException.cs new file mode 100644 index 0000000..107ac4b --- /dev/null +++ b/src/Validations/ValidationException.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Validations; + +public sealed class ValidationException(string message) : Exception(message) +{ + public ValidationException(List messages) + : this(string.Join(" | ", messages)) { } +} diff --git a/stryker.bat b/stryker.bat new file mode 100644 index 0000000..9fd73ad --- /dev/null +++ b/stryker.bat @@ -0,0 +1,4 @@ +dotnet tool restore +dotnet restore +dotnet build --no-restore +dotnet stryker -tp tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/PowerUtils.BenchmarkDotnet.Reporter.Tests.csproj -p PowerUtils.BenchmarkDotnet.Reporter.csproj --reporter json --reporter cleartext --reporter html -o diff --git a/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Commands/ComparerCommandTests.cs b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Commands/ComparerCommandTests.cs new file mode 100644 index 0000000..e74c5e6 --- /dev/null +++ b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Commands/ComparerCommandTests.cs @@ -0,0 +1,516 @@ +using System; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using PowerUtils.BenchmarkDotnet.Reporter.Commands; +using PowerUtils.BenchmarkDotnet.Reporter.Exporters; +using PowerUtils.BenchmarkDotnet.Reporter.Models; +using PowerUtils.BenchmarkDotnet.Reporter.Validations; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Tests.Commands; + +public sealed class ComparerCommandTests +{ + private readonly Func _readFullJsonReport; + private readonly BenchmarkFullJsonResport _baselineReport; + private readonly BenchmarkFullJsonResport _targetReport; + + private readonly IReportValidation _validation; + private readonly IExporter _exporter; + + private readonly ComparerCommand _command; + + + public ComparerCommandTests() + { + _baselineReport = new(); + _targetReport = new(); + + _readFullJsonReport = (path) + => path switch + { + "baseline" => _baselineReport, + "target" => _targetReport, + _ => throw new ArgumentException() + }; + + _validation = Substitute.For(); + _exporter = Substitute.For(); + + + var provider = Substitute.For(); + provider + .GetRequiredKeyedService(Arg.Any(), Arg.Any()) + .Returns(_exporter); + + _command = new( + _readFullJsonReport, + _validation, + provider); + } + + + [Fact] + public void When_Report_Has_Warnings_And_WarningThrow_Equals_False_Should_Not_Throw_Exception() + { + // Arrange + var expectedMessage = Guid.NewGuid().ToString(); + + _validation + .HostEnvironmentValidate(Arg.Any(), Arg.Any()) + .Returns([expectedMessage]); + + + // Act + void act() => _command.Execute( + "baseline", + "target", + null, + null, + ["xpto"], + ""); + + + // Assert + Should.NotThrow(act); + } + + [Fact] + public void When_Baseline_Doesnt_Have_Benchmarks_Should_Generate_Zero_Comparisons() + { + // Arrange & Act + _command.Execute( + "baseline", + "target", + null, + null, + ["xpto"], + ""); + + + // Assert + _exporter + .Received() + .Generate( + Arg.Is(i => i.Comparisons.Count == 0), + Arg.Any()); + } + + [Fact] + public void When_Only_Have_Method_On_Baseline_Should_Have_OneComparation_With_Status_Removed() + { + // Arrange + (_baselineReport.Benchmarks = []).Add(new() + { + Statistics = new() + { + Mean = 12 + } + }); + + + // Act + _command.Execute( + "baseline", + "target", + null, + null, + ["xpto"], + ""); + + + // Assert + _exporter + .Received() + .Generate( + Arg.Is(i => + i.Comparisons.Select(s => s.Mean!.Status).First() == ComparisonStatus.Removed && + i.Comparisons.Select(s => s.Mean!.Unit).First() == "ns"), + Arg.Any()); + } + + [Fact] + public void When_Only_Have_Method_On_Target_Should_Have_OneComparation_With_Status_New() + { + // Arrange + (_targetReport.Benchmarks = []).Add(new() + { + Memory = new() + { + BytesAllocatedPerOperation = 120 + } + }); + + + // Act + _command.Execute( + "baseline", + "target", + null, + null, + ["xpto"], + ""); + + + // Assert + _exporter + .Received() + .Generate( + Arg.Is(i => + i.Comparisons.Select(s => s.Allocated!.Status).First() == ComparisonStatus.New && + i.Comparisons.Select(s => s.Allocated!.Unit).First() == "B"), + Arg.Any()); + } + + [Fact] + public void When_Has_Invalid_Mean_Threshold_Should_Throw_Exception() + { + // Arrange & Act + void act() => _command.Execute( + "baseline", + "target", + "invalid", + null, + ["xpto"], + ""); + + + // Assert + Should.Throw(act); + } + + [Fact] + public void When_Has_Invalid_Allocation_Threshold_Should_Throw_Exception() + { + // Arrange & Act + void act() => _command.Execute( + "baseline", + "target", + null, + "invalid", + ["xpto"], + ""); + + + // Assert + Should.Throw(act); + } + + [Fact] + public void Should_Register_Thrashold_Values_Above_Setted_PercentsThrashold() + { + // Arrange + _baselineReport.Benchmarks = []; + _targetReport.Benchmarks = []; + + _baselineReport.Benchmarks.Add(new() + { + FullName = "test hit", + Method = "test hit", + Statistics = new() + { + Mean = 12 + }, + Memory = new() + { + BytesAllocatedPerOperation = 120 + } + }); + + _targetReport.Benchmarks.Add(new() + { + FullName = "test hit", + Method = "tetest hitst", + Statistics = new() + { + Mean = 1200 + }, + Memory = new() + { + BytesAllocatedPerOperation = 120000 + } + }); + + _baselineReport.Benchmarks.Add(new() + { + FullName = "test equals", + Method = "test equals", + Statistics = new() + { + Mean = 45 + }, + Memory = new() + { + BytesAllocatedPerOperation = 1234 + } + }); + + _targetReport.Benchmarks.Add(new() + { + FullName = "test equals", + Method = "test equals", + Statistics = new() + { + Mean = 45 + }, + Memory = new() + { + BytesAllocatedPerOperation = 1234 + } + }); + + + // Act + _command.Execute( + "baseline", + "target", + "10%", + "11%", + ["xpto"], + ""); + + + // Assert + _exporter + .Received() + .Generate( + Arg.Is(i => + i.HitThresholds.Contains("Mean threshold hit for 'test hit'") && + i.HitThresholds.Contains("Allocation threshold hit for 'test hit'")), + Arg.Any()); + } + + [Fact] + public void Should_Register_Thrashold_Values_Above_Setted_UnitThrashold() + { + // Arrange + _baselineReport.Benchmarks = []; + _targetReport.Benchmarks = []; + + _baselineReport.Benchmarks.Add(new() + { + FullName = "test hit", + Method = "test hit", + Statistics = new() + { + Mean = 12 + }, + Memory = new() + { + BytesAllocatedPerOperation = 120 + } + }); + + _targetReport.Benchmarks.Add(new() + { + FullName = "test hit", + Method = "tetest hitst", + Statistics = new() + { + Mean = 1200 + }, + Memory = new() + { + BytesAllocatedPerOperation = 120000 + } + }); + + _baselineReport.Benchmarks.Add(new() + { + FullName = "test equals", + Method = "test equals", + Statistics = new() + { + Mean = 45 + }, + Memory = new() + { + BytesAllocatedPerOperation = 1234 + } + }); + + _targetReport.Benchmarks.Add(new() + { + FullName = "test equals", + Method = "test equals", + Statistics = new() + { + Mean = 45 + }, + Memory = new() + { + BytesAllocatedPerOperation = 1234 + } + }); + + + // Act + _command.Execute( + "baseline", + "target", + "5ns", + "5B", + ["xpto"], + ""); + + + // Assert + _exporter + .Received() + .Generate( + Arg.Is(i => + i.HitThresholds.Contains("Mean threshold hit for 'test hit'") && + i.HitThresholds.Contains("Allocation threshold hit for 'test hit'")), + Arg.Any()); + } + + [Fact] + public void When_Worning_Is_Generated_Should_Be_Stored_In_ComparerReport() + { + // Arrange + const string EXPECTED_MESSAGE = "Processor Name is different"; + + _validation + .HostEnvironmentValidate(Arg.Any(), Arg.Any()) + .Returns([EXPECTED_MESSAGE]); + + + // Act + _command.Execute( + "baseline", + "target", + null, + null, + ["xpto"], + ""); + + + // Assert + _exporter + .Received() + .Generate( + Arg.Is(i => + i.Warnings.Contains(EXPECTED_MESSAGE)), + Arg.Any()); + } + + [Fact] + public void When_Target_Doesnt_Have_Benchmarks_Should_Generate_Comparisons_With_Status_Removed() + { + // Arrange + (_baselineReport.Benchmarks = []).Add(new() + { + FullName = "TestMethod", + Method = "TestMethod", + Statistics = new() + { + Mean = 12 + }, + Memory = new() + { + BytesAllocatedPerOperation = 120 + } + }); + _targetReport.Benchmarks = []; + + + // Act + _command.Execute( + "baseline", + "target", + null, + null, + ["xpto"], + ""); + + + // Assert + _exporter + .Received() + .Generate( + Arg.Is(i => + i.Comparisons.Count == 1 && + i.Comparisons.First().Allocated!.Status == ComparisonStatus.Removed && + i.Comparisons.First().Mean!.Status == ComparisonStatus.Removed), + Arg.Any()); + } + + [Fact] + public void Each_Method_In_Benchmarks_Should_Appear_Once_In_ComparerReport() + { + // Arrange + _baselineReport.Benchmarks = []; + _targetReport.Benchmarks = []; + + _baselineReport.Benchmarks.Add(new() + { + FullName = "method one", + Method = "method 1", + Statistics = new() + { + Mean = 12 + }, + Memory = new() + { + BytesAllocatedPerOperation = 120 + } + }); + _baselineReport.Benchmarks.Add(new() + { + FullName = "method two", + Method = "method 2", + Statistics = new() + { + Mean = 13 + }, + Memory = new() + { + BytesAllocatedPerOperation = 130 + } + }); + + _targetReport.Benchmarks.Add(new() + { + FullName = "method one", + Method = "method 1", + Statistics = new() + { + Mean = 12 + }, + Memory = new() + { + BytesAllocatedPerOperation = 120 + } + }); + _targetReport.Benchmarks.Add(new() + { + FullName = "method two", + Method = "method 2", + Statistics = new() + { + Mean = 13 + }, + Memory = new() + { + BytesAllocatedPerOperation = 130 + } + }); + + + // Act + _command.Execute( + "baseline", + "target", + "10%", + "11%", + ["xpto"], + ""); + + + // Assert + _exporter + .Received() + .Generate( + Arg.Is(i => i.Comparisons.Count == 2), + Arg.Any()); + } +} diff --git a/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Exporters/ConsoleExporterTests.cs b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Exporters/ConsoleExporterTests.cs new file mode 100644 index 0000000..64e2a88 --- /dev/null +++ b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Exporters/ConsoleExporterTests.cs @@ -0,0 +1,406 @@ +using System; +using System.Collections.Generic; +using PowerUtils.BenchmarkDotnet.Reporter.Exporters; +using PowerUtils.BenchmarkDotnet.Reporter.Models; +using static PowerUtils.BenchmarkDotnet.Reporter.Helpers.IOHelpers; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Tests.Exporters; + +public sealed class ConsoleExporterTests +{ + private readonly ConsoleExporter _exporter; + private readonly List _output = []; + + public ConsoleExporterTests() + { + Printer printer = _output.Add; + _exporter = new ConsoleExporter(printer); + } + + + [Fact] + public void When_Doest_Have_Warnings_And_Results_Should_Print_Only_Message_NoComparisonsFound() + { + // Arrange + var report = new ComparerReport(); + + + // Act + _exporter.Generate(report, ""); + + + // Assert + _output[0].ShouldBe(Environment.NewLine); + _output[1].ShouldBe("══════════════════════════════════════════════════════════════════════════════════"); + _output[2].ShouldBe(Environment.NewLine); + _output[3].ShouldBe(" BENCHMARK COMPARISON REPORT"); + _output[4].ShouldBe(Environment.NewLine); + _output[5].ShouldBe("══════════════════════════════════════════════════════════════════════════════════"); + _output[6].ShouldBe(Environment.NewLine); + _output[7].ShouldBe(Environment.NewLine); + _output[8].ShouldBe("📊 RESULTS:"); + _output[9].ShouldBe(Environment.NewLine); + _output[10].ShouldBe(Environment.NewLine); + _output[11].ShouldBe(" No comparisons found."); + _output[12].ShouldBe(Environment.NewLine); + _output[13].ShouldBe(Environment.NewLine); + _output[14].ShouldBe("══════════════════════════════════════════════════════════════════════════════════"); + _output[15].ShouldBe(Environment.NewLine); + } + + [Fact] + public void When_Has_Only_Warnings_Should_Print_Only_Warnings() + { + // Arrange + var report = new ComparerReport + { + Warnings = [ + "Warning 1", + "Warning 2" + ] + }; + + + // Act + _exporter.Generate(report, ""); + + + // Assert + + _output[0].ShouldBe(Environment.NewLine); + _output[1].ShouldBe("══════════════════════════════════════════════════════════════════════════════════"); + _output[2].ShouldBe(Environment.NewLine); + _output[3].ShouldBe(" BENCHMARK COMPARISON REPORT"); + _output[4].ShouldBe(Environment.NewLine); + _output[5].ShouldBe("══════════════════════════════════════════════════════════════════════════════════"); + _output[6].ShouldBe(Environment.NewLine); + _output[7].ShouldBe(Environment.NewLine); + _output[8].ShouldBe("âš ī¸ WARNINGS:"); + _output[9].ShouldBe(Environment.NewLine); + _output[10].ShouldBe(Environment.NewLine); + _output[11].ShouldBe(" â€ĸ Warning 1"); + _output[12].ShouldBe(Environment.NewLine); + _output[13].ShouldBe(" â€ĸ Warning 2"); + _output[14].ShouldBe(Environment.NewLine); + _output[15].ShouldBe(Environment.NewLine); + _output[16].ShouldBe("................................................................................."); + _output[17].ShouldBe(Environment.NewLine); + _output[18].ShouldBe(Environment.NewLine); + _output[19].ShouldBe("📊 RESULTS:"); + _output[20].ShouldBe(Environment.NewLine); + _output[21].ShouldBe(Environment.NewLine); + _output[22].ShouldBe(" No comparisons found."); + _output[23].ShouldBe(Environment.NewLine); + _output[24].ShouldBe(Environment.NewLine); + _output[25].ShouldBe("══════════════════════════════════════════════════════════════════════════════════"); + _output[26].ShouldBe(Environment.NewLine); + } + + [Fact] + public void When_All_Results_Are_Equals_Should_Print_Without_Labels() + { + // Arrange + var report = new ComparerReport(); + report.Comparisons.Add(new() + { + Name = "Name", + FullName = "Full", + + Mean = MetricComparison.CalculateExecutionTime(12, 12), + Allocated = MetricComparison.CalculateMemoryUsage(20, 20) + }); + + + // Act + _exporter.Generate(report, ""); + + + // Assert + _output[0].ShouldBe(Environment.NewLine); + _output[1].ShouldBe("══════════════════════════════════════════════════════════════════════════════════"); + _output[2].ShouldBe(Environment.NewLine); + _output[3].ShouldBe(" BENCHMARK COMPARISON REPORT"); + _output[4].ShouldBe(Environment.NewLine); + _output[5].ShouldBe("══════════════════════════════════════════════════════════════════════════════════"); + _output[6].ShouldBe(Environment.NewLine); + _output[7].ShouldBe(Environment.NewLine); + _output[8].ShouldBe("📊 RESULTS:"); + _output[9].ShouldBe(Environment.NewLine); + _output[10].ShouldBe(Environment.NewLine); + _output[11].ShouldBe("Report Method Mean Allocated"); + _output[12].ShouldBe(Environment.NewLine); + _output[13].ShouldBe("───────────────────────────────────────────"); + _output[14].ShouldBe(Environment.NewLine); + _output[15].ShouldBe("Baseline Name 12 ns 20 B"); + _output[16].ShouldBe(Environment.NewLine); + _output[17].ShouldBe("Target 12 ns 20 B"); + _output[18].ShouldBe(Environment.NewLine); + _output[19].ShouldBe(Environment.NewLine); + _output[20].ShouldBe("══════════════════════════════════════════════════════════════════════════════════"); + _output[21].ShouldBe(Environment.NewLine); + } + + [Fact] + public void When_Have_TwoResults_Should_Print_FourRows_In_Table() + { + // Arrange + var report = new ComparerReport(); + report.Comparisons.Add(new() + { + Name = "Method1", + FullName = "FullMethod1", + + Mean = MetricComparison.CalculateExecutionTime(43, 43), + Allocated = MetricComparison.CalculateMemoryUsage(122, 122) + }); + report.Comparisons.Add(new() + { + Name = "Method2", + FullName = "FullMethod2", + + Mean = MetricComparison.CalculateExecutionTime(52, 52), + Allocated = MetricComparison.CalculateMemoryUsage(21, 21) + }); + + + // Act + _exporter.Generate(report, ""); + + + // Assert + _output[0].ShouldBe(Environment.NewLine); + _output[1].ShouldBe("══════════════════════════════════════════════════════════════════════════════════"); + _output[2].ShouldBe(Environment.NewLine); + _output[3].ShouldBe(" BENCHMARK COMPARISON REPORT"); + _output[4].ShouldBe(Environment.NewLine); + _output[5].ShouldBe("══════════════════════════════════════════════════════════════════════════════════"); + _output[6].ShouldBe(Environment.NewLine); + _output[7].ShouldBe(Environment.NewLine); + _output[8].ShouldBe("📊 RESULTS:"); + _output[9].ShouldBe(Environment.NewLine); + _output[10].ShouldBe(Environment.NewLine); + _output[11].ShouldBe("Report Method Mean Allocated"); + _output[12].ShouldBe(Environment.NewLine); + _output[13].ShouldBe("────────────────────────────────────────────"); + _output[14].ShouldBe(Environment.NewLine); + _output[15].ShouldBe("Baseline Method1 43 ns 122 B"); + _output[16].ShouldBe(Environment.NewLine); + _output[17].ShouldBe("Target 43 ns 122 B"); + _output[18].ShouldBe(Environment.NewLine); + _output[19].ShouldBe("Baseline Method2 52 ns 21 B"); + _output[20].ShouldBe(Environment.NewLine); + _output[21].ShouldBe("Target 52 ns 21 B"); + _output[22].ShouldBe(Environment.NewLine); + _output[23].ShouldBe(Environment.NewLine); + _output[24].ShouldBe("══════════════════════════════════════════════════════════════════════════════════"); + _output[25].ShouldBe(Environment.NewLine); + } + + + + [Fact] + public void When_Benchmarks_Have_Different_Values_Should_Print_Differences_In_ResultRows() + { + // Arrange + var report = new ComparerReport(); + report.Comparisons.Add(new() + { + Name = "Method1", + FullName = "FullMethod1", + + Mean = MetricComparison.CalculateExecutionTime(50, 25), + Allocated = MetricComparison.CalculateMemoryUsage(100, 75) + }); + + + // Act + _exporter.Generate(report, ""); + + + // Assert + _output[0].ShouldBe(Environment.NewLine); + _output[1].ShouldBe("══════════════════════════════════════════════════════════════════════════════════"); + _output[2].ShouldBe(Environment.NewLine); + _output[3].ShouldBe(" BENCHMARK COMPARISON REPORT"); + _output[4].ShouldBe(Environment.NewLine); + _output[5].ShouldBe("══════════════════════════════════════════════════════════════════════════════════"); + _output[6].ShouldBe(Environment.NewLine); + _output[7].ShouldBe(Environment.NewLine); + _output[8].ShouldBe("📊 RESULTS:"); + _output[9].ShouldBe(Environment.NewLine); + _output[10].ShouldBe(Environment.NewLine); + _output[11].ShouldBe("Report Method Mean Allocated"); + _output[12].ShouldBe(Environment.NewLine); + _output[13].ShouldBe("─────────────────────────────────────────────────────"); + _output[14].ShouldBe(Environment.NewLine); + _output[15].ShouldBe("Baseline Method1 50 ns 100 B"); + _output[16].ShouldBe(Environment.NewLine); + _output[17].ShouldBe("Target 25 ns (-50%) 75 B (-25%)"); + _output[18].ShouldBe(Environment.NewLine); + _output[19].ShouldBe(Environment.NewLine); + _output[20].ShouldBe("══════════════════════════════════════════════════════════════════════════════════"); + _output[21].ShouldBe(Environment.NewLine); + } + + [Fact] + public void When_Baseline_Doesnt_Have_Values_Shouldnt_Print_Value_Only_TargetRow() + { + // Arrange + var report = new ComparerReport(); + report.Comparisons.Add(new() + { + Name = "xpto", + FullName = "Full", + + Mean = MetricComparison.CalculateExecutionTime(null, 12), + Allocated = MetricComparison.CalculateMemoryUsage(null, 37) + }); + + + // Act + _exporter.Generate(report, ""); + + + // Assert + _output[0].ShouldBe(Environment.NewLine); + _output[1].ShouldBe("══════════════════════════════════════════════════════════════════════════════════"); + _output[2].ShouldBe(Environment.NewLine); + _output[3].ShouldBe(" BENCHMARK COMPARISON REPORT"); + _output[4].ShouldBe(Environment.NewLine); + _output[5].ShouldBe("══════════════════════════════════════════════════════════════════════════════════"); + _output[6].ShouldBe(Environment.NewLine); + _output[7].ShouldBe(Environment.NewLine); + _output[8].ShouldBe("📊 RESULTS:"); + _output[9].ShouldBe(Environment.NewLine); + _output[10].ShouldBe(Environment.NewLine); + _output[11].ShouldBe("Report Method Mean Allocated"); + _output[12].ShouldBe(Environment.NewLine); + _output[13].ShouldBe("───────────────────────────────────────────"); + _output[14].ShouldBe(Environment.NewLine); + _output[15].ShouldBe("Baseline xpto "); + _output[16].ShouldBe(Environment.NewLine); + _output[17].ShouldBe("Target [NEW] 12 ns 37 B"); + _output[18].ShouldBe(Environment.NewLine); + _output[19].ShouldBe(Environment.NewLine); + _output[20].ShouldBe("══════════════════════════════════════════════════════════════════════════════════"); + _output[21].ShouldBe(Environment.NewLine); + } + + [Fact] + public void When_Target_Doesnt_Have_Target_Values_Shouldnt_Print_Only_BaselineRow() + { + // Arrange + var report = new ComparerReport(); + report.Comparisons.Add(new() + { + Name = "wdcs", + FullName = "Full", + + Mean = MetricComparison.CalculateExecutionTime(12, null), + Allocated = MetricComparison.CalculateMemoryUsage(20, null) + }); + + + // Act + _exporter.Generate(report, ""); + + + // Assert + _output[0].ShouldBe(Environment.NewLine); + _output[1].ShouldBe("══════════════════════════════════════════════════════════════════════════════════"); + _output[2].ShouldBe(Environment.NewLine); + _output[3].ShouldBe(" BENCHMARK COMPARISON REPORT"); + _output[4].ShouldBe(Environment.NewLine); + _output[5].ShouldBe("══════════════════════════════════════════════════════════════════════════════════"); + _output[6].ShouldBe(Environment.NewLine); + _output[7].ShouldBe(Environment.NewLine); + _output[8].ShouldBe("📊 RESULTS:"); + _output[9].ShouldBe(Environment.NewLine); + _output[10].ShouldBe(Environment.NewLine); + _output[11].ShouldBe("Report Method Mean Allocated"); + _output[12].ShouldBe(Environment.NewLine); + _output[13].ShouldBe("──────────────────────────────────────────────"); + _output[14].ShouldBe(Environment.NewLine); + _output[15].ShouldBe("Baseline wdcs 12 ns 20 B"); + _output[16].ShouldBe(Environment.NewLine); + _output[17].ShouldBe("Target [REMOVED] "); + _output[18].ShouldBe(Environment.NewLine); + _output[19].ShouldBe(Environment.NewLine); + _output[20].ShouldBe("══════════════════════════════════════════════════════════════════════════════════"); + _output[21].ShouldBe(Environment.NewLine); + } + + [Theory] + [InlineData(ComparisonStatus.Removed, "REMOVED")] + [InlineData(ComparisonStatus.New, "NEW")] + public void When_Has_Status_To_Show_Lable_Should_Show_Correspondent_Name(ComparisonStatus status, string expected) + { + // Arrange + var report = new ComparerReport(); + report.Comparisons.Add(new() + { + Name = "Name", + FullName = "Full", + + Mean = status == ComparisonStatus.Removed + ? MetricComparison.CalculateExecutionTime(12, null) + : MetricComparison.CalculateMemoryUsage(null, 120) + }); + + + // Act + _exporter.Generate(report, ""); + + + // Assert + var targetLine = _output?[^5]; + var methodColumn = targetLine? + .Split([' '], StringSplitOptions.RemoveEmptyEntries)[1] + .Trim(' ', '[', ']'); + + methodColumn.ShouldBe(expected); + } + + [Fact] + public void When_Has_HitThresholds_Should_Print_Them() + { + // Arrange + var report = new ComparerReport(); + report.HitThresholds.Add("Hit Threshold 1"); + report.HitThresholds.Add("Hit Threshold 2"); + + + // Act + _exporter.Generate(report, ""); + + + // Assert + _output[0].ShouldBe(Environment.NewLine); + _output[1].ShouldBe("══════════════════════════════════════════════════════════════════════════════════"); + _output[2].ShouldBe(Environment.NewLine); + _output[3].ShouldBe(" BENCHMARK COMPARISON REPORT"); + _output[4].ShouldBe(Environment.NewLine); + _output[5].ShouldBe("══════════════════════════════════════════════════════════════════════════════════"); + _output[6].ShouldBe(Environment.NewLine); + _output[7].ShouldBe(Environment.NewLine); + _output[8].ShouldBe("📊 RESULTS:"); + _output[9].ShouldBe(Environment.NewLine); + _output[10].ShouldBe(Environment.NewLine); + _output[11].ShouldBe(" No comparisons found."); + _output[12].ShouldBe(Environment.NewLine); + _output[13].ShouldBe(Environment.NewLine); + _output[14].ShouldBe("................................................................................."); + _output[15].ShouldBe(Environment.NewLine); + _output[16].ShouldBe(Environment.NewLine); + _output[17].ShouldBe("🚨 THRESHOLD VIOLATIONS:"); + _output[18].ShouldBe(Environment.NewLine); + _output[19].ShouldBe(Environment.NewLine); + _output[20].ShouldBe(" â€ĸ Hit Threshold 1"); + _output[21].ShouldBe(Environment.NewLine); + _output[22].ShouldBe(" â€ĸ Hit Threshold 2"); + _output[23].ShouldBe(Environment.NewLine); + _output[24].ShouldBe(Environment.NewLine); + _output[25].ShouldBe("══════════════════════════════════════════════════════════════════════════════════"); + _output[26].ShouldBe(Environment.NewLine); + } +} diff --git a/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Exporters/HitTxtExporterTests.cs b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Exporters/HitTxtExporterTests.cs new file mode 100644 index 0000000..2d16fd3 --- /dev/null +++ b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Exporters/HitTxtExporterTests.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; +using System.IO; +using PowerUtils.BenchmarkDotnet.Reporter.Exporters; +using PowerUtils.BenchmarkDotnet.Reporter.Models; +using static PowerUtils.BenchmarkDotnet.Reporter.Helpers.IOHelpers; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Tests.Exporters; + +public sealed class HitTxtExporterTests +{ + private readonly HitTxtExporter _exporter; + private List _output = []; + + public HitTxtExporterTests() + { + void writer(string path, string content) + => _output = [.. content.Split(Environment.NewLine)]; + _exporter = new HitTxtExporter(writer); + } + + + [Fact] + public void When_Doesnt_Have_Hits_Shouldnt_Generate_Report() + { + // Arrange + var report = new ComparerReport(); + + + // Act + _exporter.Generate(report, ""); + + + // Assert + _output.ShouldBeEmpty(); + } + + + [Fact] + public void Should_Generate_Report_Only_With_Warnings() + { + // Arrange + var report = new ComparerReport + { + Warnings = [ + "Warning 1", + "Warning 2" + ] + }; + + + // Act + _exporter.Generate(report, ""); + + + // Assert + _output[0].ShouldBe("Warning 1"); + _output[1].ShouldBe("Warning 2"); + _output[2].ShouldBe(""); + } + + [Fact] + public void Should_Generate_Report_Only_With_Thrasholds() + { + // Arrange + var report = new ComparerReport + { + HitThresholds = [ + "hit1", + "hit2", + "hit3" + ] + }; + + + // Act + _exporter.Generate(report, ""); + + + // Assert + _output[0].ShouldBe("hit1"); + _output[1].ShouldBe("hit2"); + _output[2].ShouldBe("hit3"); + _output[3].ShouldBe(""); + } + + [Fact] + public void Should_Generate_Report_With_All() + { + // Arrange + var report = new ComparerReport() + { + Warnings = [ + "Warning 2" + ], + HitThresholds = [ + "hit1", + "hit3" + ] + }; + + + // Act + _exporter.Generate(report, ""); + + + // Assert + _output[0].ShouldBe("Warning 2"); + _output[1].ShouldBe("hit1"); + _output[2].ShouldBe("hit3"); + _output[3].ShouldBe(""); + } + + [Fact] + public void Validate_If_FileOutputHitTxt_Is_Created() + { + // Arrange + FileWriter writer = WriteFile; + var output = new HitTxtExporter(writer); + var report = new ComparerReport() + { + HitThresholds = [ "hit1" ] + }; + var outputDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + + var expectedFileName = Path.Combine(outputDirectory, "benchmark-comparison-hits.txt"); + + + // Act + output.Generate(report, outputDirectory); + + + // Assert + File.Exists(expectedFileName).ShouldBeTrue(); + } +} diff --git a/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Exporters/JsonExporterTests.cs b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Exporters/JsonExporterTests.cs new file mode 100644 index 0000000..fc6bcf4 --- /dev/null +++ b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Exporters/JsonExporterTests.cs @@ -0,0 +1,58 @@ +using System.IO; +using PowerUtils.BenchmarkDotnet.Reporter.Exporters; +using PowerUtils.BenchmarkDotnet.Reporter.Models; +using static PowerUtils.BenchmarkDotnet.Reporter.Helpers.IOHelpers; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Tests.Exporters; + +public sealed class JsonExporterTests +{ + private readonly FileWriter _writer; + private string? _output; + + public JsonExporterTests() + => _writer = (path, content) => _output = content; + + + [Fact] + public void Validate_Json_Structure() + { + // Arrange + var report = new ComparerReport(); + var output = new JsonExporter(_writer); + + + // Act + output.Generate(report, ""); + + + // Assert + _output.ShouldBe(""" + { + "Warnings": [], + "Comparisons": [], + "HitThresholds": [] + } + """); + } + + [Fact] + public void Validate_If_FileOutputJson_Is_Created() + { + // Arrange + FileWriter writer = WriteFile; + var output = new JsonExporter(writer); + var report = new ComparerReport(); + var outputDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + + var expectedFileName = Path.Combine(outputDirectory, "benchmark-comparison-report.json"); + + + // Act + output.Generate(report, outputDirectory); + + + // Assert + File.Exists(expectedFileName).ShouldBeTrue(); + } +} diff --git a/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Exporters/MarkdownExporterTests.cs b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Exporters/MarkdownExporterTests.cs new file mode 100644 index 0000000..4e72e4d --- /dev/null +++ b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Exporters/MarkdownExporterTests.cs @@ -0,0 +1,284 @@ +using System; +using System.Collections.Generic; +using System.IO; +using PowerUtils.BenchmarkDotnet.Reporter.Exporters; +using PowerUtils.BenchmarkDotnet.Reporter.Models; +using static PowerUtils.BenchmarkDotnet.Reporter.Helpers.IOHelpers; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Tests.Exporters; + +public sealed class MarkdownExporterTests +{ + private readonly MarkdownExporter _exporter; + private List _output = []; + + public MarkdownExporterTests() + { + void writer(string path, string content) + => _output = [.. content.Split(Environment.NewLine)]; + _exporter = new MarkdownExporter(writer); + } + + + [Fact] + public void When_Doest_Have_Warnings_And_Results_Should_Print_Only_Message_NoComparisonsFound() + { + // Arrange + var report = new ComparerReport(); + + + // Act + _exporter.Generate(report, ""); + + + // Assert + _output[0].ShouldBe("# BENCHMARK COMPARISON REPORT"); + _output[1].ShouldBe(""); + _output[2].ShouldBe("## 📊 RESULTS:"); + _output[3].ShouldBe(""); + _output[4].ShouldBe(" NO COMPARISONS FOUND."); + } + + [Fact] + public void When_Has_Only_Warnings_Should_Print_Only_Warnings() + { + // Arrange + var report = new ComparerReport + { + Warnings = [ + "Warning 1", + "Warning 2" + ] + }; + + + // Act + _exporter.Generate(report, ""); + + + // Assert + _output[0].ShouldBe("# BENCHMARK COMPARISON REPORT"); + _output[1].ShouldBe(""); + _output[2].ShouldBe("## âš ī¸ WARNINGS:"); + _output[3].ShouldBe(""); + _output[4].ShouldBe(" * Warning 1"); + _output[5].ShouldBe(" * Warning 2"); + _output[6].ShouldBe(""); + _output[7].ShouldBe(""); + _output[8].ShouldBe("## 📊 RESULTS:"); + _output[9].ShouldBe(""); + _output[10].ShouldBe(" NO COMPARISONS FOUND."); + } + + [Fact] + public void When_All_Results_Are_Equals_Should_Print_Without_Labels() + { + // Arrange + var report = new ComparerReport(); + report.Comparisons.Add(new() + { + Name = "Name", + FullName = "Full", + + Mean = MetricComparison.CalculateExecutionTime(12, 12), + Allocated = MetricComparison.CalculateMemoryUsage(20, 20) + }); + + + // Act + _exporter.Generate(report, ""); + + + // Assert + _output[0].ShouldBe("# BENCHMARK COMPARISON REPORT"); + _output[1].ShouldBe(""); + _output[2].ShouldBe("## 📊 RESULTS:"); + _output[3].ShouldBe(""); + _output[4].ShouldBe(" Report | Method | Mean | Allocated"); + _output[5].ShouldBe(" -------- | ------ | -----:| ---------:"); + _output[6].ShouldBe(" Baseline | Name | 12 ns | 20 B"); + _output[7].ShouldBe(" Target | | 12 ns | 20 B"); + _output[8].ShouldBe(""); + } + + [Fact] + public void When_Have_TwoResults_Should_Print_FourRows_In_Table() + { + // Arrange + var report = new ComparerReport(); + report.Comparisons.Add(new() + { + Name = "Method1", + FullName = "FullMethod1", + + Mean = MetricComparison.CalculateExecutionTime(43, 43), + Allocated = MetricComparison.CalculateMemoryUsage(122, 122) + }); + report.Comparisons.Add(new() + { + Name = "Method2", + FullName = "FullMethod2", + + Mean = MetricComparison.CalculateExecutionTime(52, 52), + Allocated = MetricComparison.CalculateMemoryUsage(21, 21) + }); + + + // Act + _exporter.Generate(report, ""); + + + // Assert + _output[0].ShouldBe("# BENCHMARK COMPARISON REPORT"); + _output[1].ShouldBe(""); + _output[2].ShouldBe("## 📊 RESULTS:"); + _output[3].ShouldBe(""); + _output[4].ShouldBe(" Report | Method | Mean | Allocated"); + _output[5].ShouldBe(" -------- | ------- | -----:| ---------:"); + _output[6].ShouldBe(" Baseline | Method1 | 43 ns | 122 B"); + _output[7].ShouldBe(" Target | | 43 ns | 122 B"); + _output[8].ShouldBe(" Baseline | Method2 | 52 ns | 21 B"); + _output[9].ShouldBe(" Target | | 52 ns | 21 B"); + _output[10].ShouldBe(""); + } + + [Fact] + public void When_Baseline_Doesnt_Have_Values_Shouldnt_Print_Value_Only_TargetRow() + { + // Arrange + var report = new ComparerReport(); + report.Comparisons.Add(new() + { + Name = "xpto", + FullName = "Full", + + Mean = MetricComparison.CalculateExecutionTime(null, 12), + Allocated = MetricComparison.CalculateMemoryUsage(null, 37) + }); + + + // Act + _exporter.Generate(report, ""); + + + // Assert + _output[0].ShouldBe("# BENCHMARK COMPARISON REPORT"); + _output[1].ShouldBe(""); + _output[2].ShouldBe("## 📊 RESULTS:"); + _output[3].ShouldBe(""); + _output[4].ShouldBe(" Report | Method | Mean | Allocated"); + _output[5].ShouldBe(" -------- | ------ | -----:| ---------:"); + _output[6].ShouldBe(" Baseline | xpto | | "); + _output[7].ShouldBe(" Target | [NEW] | 12 ns | 37 B"); + _output[8].ShouldBe(""); + } + + [Fact] + public void When_Target_Doesnt_Have_Target_Values_Shouldnt_Print_Only_BaselineRow() + { + // Arrange + var report = new ComparerReport(); + report.Comparisons.Add(new() + { + Name = "wdcs", + FullName = "Full", + + Mean = MetricComparison.CalculateExecutionTime(12, null), + Allocated = MetricComparison.CalculateMemoryUsage(20, null) + }); + + + // Act + _exporter.Generate(report, ""); + + + // Assert + _output[0].ShouldBe("# BENCHMARK COMPARISON REPORT"); + _output[1].ShouldBe(""); + _output[2].ShouldBe("## 📊 RESULTS:"); + _output[3].ShouldBe(""); + _output[4].ShouldBe(" Report | Method | Mean | Allocated"); + _output[5].ShouldBe(" -------- | --------- | -----:| ---------:"); + _output[6].ShouldBe(" Baseline | wdcs | 12 ns | 20 B"); + _output[7].ShouldBe(" Target | [REMOVED] | | "); + _output[8].ShouldBe(""); + } + + [Theory] + [InlineData(ComparisonStatus.Removed, "REMOVED")] + [InlineData(ComparisonStatus.New, "NEW")] + public void When_Has_Status_To_Show_Lable_Should_Show_Correspondent_Name(ComparisonStatus status, string expected) + { + // Arrange + var report = new ComparerReport(); + report.Comparisons.Add(new() + { + Name = "Name", + FullName = "Full", + + Mean = status == ComparisonStatus.Removed + ? MetricComparison.CalculateExecutionTime(12, null) + : MetricComparison.CalculateMemoryUsage(null, 120) + }); + + + // Act + _exporter.Generate(report, ""); + + + // Assert + var targetLine = _output?[^2]; + var methodColumn = targetLine? + .Split('|')[1] + .Trim(' ', '[', ']'); + + methodColumn.ShouldBe(expected); + } + + [Fact] + public void When_Has_HitThresholds_Should_Print_Them() + { + // Arrange + var report = new ComparerReport(); + report.HitThresholds.Add("Hit Threshold 1"); + report.HitThresholds.Add("Hit Threshold 2"); + + + // Act + _exporter.Generate(report, ""); + + + // Assert + _output[0].ShouldBe("# BENCHMARK COMPARISON REPORT"); + _output[1].ShouldBe(""); + _output[2].ShouldBe("## 📊 RESULTS:"); + _output[3].ShouldBe(""); + _output[4].ShouldBe(" NO COMPARISONS FOUND."); + _output[5].ShouldBe(""); + _output[6].ShouldBe("## 🚨 THRESHOLD VIOLATIONS:"); + _output[7].ShouldBe(""); + _output[8].ShouldBe(" * Hit Threshold 1;"); + _output[9].ShouldBe(" * Hit Threshold 2;"); + _output[10].ShouldBe(""); + } + + [Fact] + public void Validate_If_FileOutputMarkdown_Is_Created() + { + // Arrange + FileWriter writer = WriteFile; + var output = new MarkdownExporter(writer); + var report = new ComparerReport(); + var outputDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + + var expectedFileName = Path.Combine(outputDirectory, "benchmark-comparison-report.md"); + + + // Act + output.Generate(report, outputDirectory); + + + // Assert + File.Exists(expectedFileName).ShouldBeTrue(); + } +} diff --git a/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Helpers/BeautifyExtentions/MemoryBeautifyTests.cs b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Helpers/BeautifyExtentions/MemoryBeautifyTests.cs new file mode 100644 index 0000000..3f4cf2d --- /dev/null +++ b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Helpers/BeautifyExtentions/MemoryBeautifyTests.cs @@ -0,0 +1,39 @@ +using PowerUtils.BenchmarkDotnet.Reporter.Helpers; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Tests.Helpers.BeautifyExtentions; + +public static class MemoryBeautifyTests +{ + [Fact] + public static void When_Memory_Is_Null_Should_Return_Empty() + { + // Arrange + decimal? memory = null; + + + // Act + var act = memory.BeautifyMemory(); + + + // Assert + act.ShouldBeEmpty(); + } + + [Theory] + [InlineData(1, "1 B")] + [InlineData(1024, "1 KB")] + [InlineData(3421, "3.341 KB")] + [InlineData(1048576, "1 MB")] + [InlineData(3434455, "3.275 MB")] + [InlineData(1073741824, "1 GB")] + [InlineData(1099511627776, "1 TB")] + public static void From_Bytes_To(decimal memory, string expected) + { + // Arrange & Act + var act = memory.BeautifyMemory(); + + + // Assert + act.ShouldBe(expected); + } +} diff --git a/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Helpers/BeautifyExtentions/PercentageBeautifyTests.cs b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Helpers/BeautifyExtentions/PercentageBeautifyTests.cs new file mode 100644 index 0000000..b9680f2 --- /dev/null +++ b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Helpers/BeautifyExtentions/PercentageBeautifyTests.cs @@ -0,0 +1,36 @@ +using PowerUtils.BenchmarkDotnet.Reporter.Helpers; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Tests.Helpers.BeautifyExtentions; + +public static class PercentageBeautifyTests +{ + [Fact] + public static void When_Percentage_Is_Null_Should_Return_Empty() + { + // Arrange + decimal? memory = null; + + + // Act + var act = memory.BeautifyPercentage(); + + + // Assert + act.ShouldBeEmpty(); + } + + [Theory] + [InlineData(1, "1%")] + [InlineData(10.24, "10.24%")] + [InlineData(10.2439, "10.24%")] + [InlineData(10.249, "10.25%")] + public static void From_Percentage(decimal percentage, string expected) + { + // Arrange & Act + var act = percentage.BeautifyPercentage(); + + + // Assert + act.ShouldBe(expected); + } +} diff --git a/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Helpers/BeautifyExtentions/TimeBeautifyTests.cs b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Helpers/BeautifyExtentions/TimeBeautifyTests.cs new file mode 100644 index 0000000..7536af4 --- /dev/null +++ b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Helpers/BeautifyExtentions/TimeBeautifyTests.cs @@ -0,0 +1,45 @@ +using PowerUtils.BenchmarkDotnet.Reporter.Helpers; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Tests.Helpers.BeautifyExtentions; + +public static class TimeBeautifyTests +{ + [Fact] + public static void When_Time_Is_Null_Should_Return_Empty() + { + // Arrange + decimal? time = null; + + + // Act + var act = time.BeautifyTime(); + + + // Assert + act.ShouldBeEmpty(); + } + + [Theory] + [InlineData(1, "1 ns")] + [InlineData(814, "814 ns")] + [InlineData(999, "999 ns")] + [InlineData(1000, "1 Îŧs")] + [InlineData(3457, "3.457 Îŧs")] + [InlineData(999999, "999.999 Îŧs")] + [InlineData(1000000, "1 ms")] + [InlineData(5243454, "5.243 ms")] + [InlineData(999998888, "999.999 ms")] + [InlineData(1000000000, "1 s")] + [InlineData(6234242345, "6.234 s")] + [InlineData(60000000000, "1m")] + [InlineData(70345345345, "1m 10s")] + public static void From_Nanoseconds_To(decimal time, string expected) + { + // Arrange & Act + var act = time.BeautifyTime(); + + + // Assert + act.ShouldBe(expected); + } +} diff --git a/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Helpers/ComparableExtensionsTest.cs b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Helpers/ComparableExtensionsTest.cs new file mode 100644 index 0000000..e45ad16 --- /dev/null +++ b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Helpers/ComparableExtensionsTest.cs @@ -0,0 +1,22 @@ +using PowerUtils.BenchmarkDotnet.Reporter.Helpers; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Tests.Helpers; + +public sealed class ComparableExtensionsTest +{ + [Theory] + [InlineData(null, null, true)] + [InlineData(null, "test", false)] + [InlineData("test", null, false)] + [InlineData("Test", "test", true)] + [InlineData("Test1", "Test2", false)] + public void String_Validate_Result_Equivalente_Operation(string? left, string? right, bool expected) + { + // Arrange & Act + var act = left.Equivalente(right); + + + // Assert + act.ShouldBe(expected); + } +} diff --git a/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Helpers/IOHelpersTests/GetFullJsonReportTest.cs b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Helpers/IOHelpersTests/GetFullJsonReportTest.cs new file mode 100644 index 0000000..4f04ac6 --- /dev/null +++ b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Helpers/IOHelpersTests/GetFullJsonReportTest.cs @@ -0,0 +1,102 @@ +using System; +using System.IO; +using PowerUtils.BenchmarkDotnet.Reporter.Helpers; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Tests.Helpers.IOHelpersTests; + +public sealed class GetFullJsonReportTest : IDisposable +{ + private readonly string _tempDirectory; + + + public GetFullJsonReportTest() + { + _tempDirectory = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(_tempDirectory); + } + + public void Dispose() + { + if(Directory.Exists(_tempDirectory)) + { + Directory.Delete(_tempDirectory, true); + } + } + + + [Fact] + public void When_File_Exits_Should_Return_True() + { + // Arrange + var path = Path.Combine(_tempDirectory, $"report.{IOHelpers.REPORT_FILE_ENDS}"); + File.WriteAllText(path, "{}"); + + + // Act + var act = IOHelpers.GetFullJsonReport(path); + + + // Assert + act.ShouldBe(path); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void When_Path_Isnt_Deffined_Should_Throw_NotFoundException(string? path) + { + // Arrange & Act + string act() => IOHelpers.GetFullJsonReport(path); + + + // Assert + Should.Throw(act) + .Message.ShouldContain("The provided path is null or empty."); + } + + [Fact] + public void When_File_Doesnt_Exist_Should_Throw_FileNotFoundException() + { + // Arrange & Act + string act() => IOHelpers.GetFullJsonReport(_tempDirectory); + + + // Assert + Should.Throw(act) + .Message.ShouldContain($"No {IOHelpers.REPORT_FILE_ENDS} files found in the provided directory"); + } + + [Fact] + public void When_File_Is_Invalid_Should_Throw_FileNotFoundException() + { + // Arrange + var path = Path.Combine(_tempDirectory, "nonexistent.json"); + + + // Act + string act() => IOHelpers.GetFullJsonReport(path); + + + // Assert + Should.Throw(act) + .Message.ShouldContain($"The provided path '{path}' doesn't exist or is not a {IOHelpers.REPORT_FILE_ENDS} file"); + } + + [Fact] + public void When_There_Are_Multiple_Files_Whith_Same_Name_Should_Throw_FileNotFoundException() + { + // Arrange + File.WriteAllText(Path.Combine(_tempDirectory, $"report1.{IOHelpers.REPORT_FILE_ENDS}"), "{}"); + File.WriteAllText(Path.Combine(_tempDirectory, $"report2.{IOHelpers.REPORT_FILE_ENDS}"), "{}"); + + + // Act + string act() => IOHelpers.GetFullJsonReport(_tempDirectory); + + + // Assert + Should.Throw(act) + .Message.ShouldContain($"Multiple {IOHelpers.REPORT_FILE_ENDS} files found in the provided directory"); + } +} diff --git a/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Helpers/IOHelpersTests/PrintTest.cs b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Helpers/IOHelpersTests/PrintTest.cs new file mode 100644 index 0000000..08c3a3d --- /dev/null +++ b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Helpers/IOHelpersTests/PrintTest.cs @@ -0,0 +1,29 @@ +using System; +using System.IO; +using PowerUtils.BenchmarkDotnet.Reporter.Helpers; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Tests.Helpers.IOHelpersTests; + +public sealed class PrintTest +{ + [Fact] + public void When_Pass_Message_Should_Print_It() + { + // Arrange + var originalOutput = Console.Out; + using var stringWriter = new StringWriter(); + Console.SetOut(stringWriter); + var expectedMessage = "Test message"; + + + // Act + IOHelpers.Print(expectedMessage); + + + // Assert + var output = stringWriter.ToString(); + output.ShouldBe(expectedMessage); + + Console.SetOut(originalOutput); + } +} diff --git a/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Helpers/IOHelpersTests/ReadFullJsonReportTest.cs b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Helpers/IOHelpersTests/ReadFullJsonReportTest.cs new file mode 100644 index 0000000..4dc5a3d --- /dev/null +++ b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Helpers/IOHelpersTests/ReadFullJsonReportTest.cs @@ -0,0 +1,22 @@ +using System.IO; +using PowerUtils.BenchmarkDotnet.Reporter.Helpers; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Tests.Helpers.IOHelpersTests; + +public sealed class ReadFullJsonReportTest +{ + [Fact] + public void When_Pass_Valid_File_Should_Return_Report() + { + // Arrange + var path = Path.GetFullPath(Path.Combine("test-data", "report-01", "Benchmark-report-full.json")); + + + // Act + var act = IOHelpers.ReadFullJsonReport(path); + + + // Assert + act.ShouldNotBeNull(); + } +} diff --git a/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Helpers/IOHelpersTests/WriteFileTest.cs b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Helpers/IOHelpersTests/WriteFileTest.cs new file mode 100644 index 0000000..7847898 --- /dev/null +++ b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Helpers/IOHelpersTests/WriteFileTest.cs @@ -0,0 +1,43 @@ +using System; +using System.IO; +using PowerUtils.BenchmarkDotnet.Reporter.Helpers; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Tests.Helpers.IOHelpersTests; + +public sealed class WriteFileTest : IDisposable +{ + private readonly string _tempDirectory; + + + public WriteFileTest() + { + _tempDirectory = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(_tempDirectory); + } + + public void Dispose() + { + if(Directory.Exists(_tempDirectory)) + { + Directory.Delete(_tempDirectory, true); + } + } + + + [Fact] + public void When_Pass_Path_And_Content_Should_Create_File() + { + // Arrange + var path = Path.Combine(_tempDirectory, "test.txt"); + var content = "Test content"; + + + // Act + IOHelpers.WriteFile(path, content); + + + // Assert + File.Exists(path).ShouldBeTrue(); + File.ReadAllText(path).ShouldBe(content); + } +} diff --git a/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Models/ComparerReportTests.cs b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Models/ComparerReportTests.cs new file mode 100644 index 0000000..4d08401 --- /dev/null +++ b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Models/ComparerReportTests.cs @@ -0,0 +1,65 @@ +using PowerUtils.BenchmarkDotnet.Reporter.Models; +using static PowerUtils.BenchmarkDotnet.Reporter.Models.ComparerReport; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Tests.Models; + +public sealed class ComparerReportTests +{ + [Fact] + public void When_Comparison_Hasnt_Mean_And_Allocated_Shouldnt_Add() + { + // Arrange + var report = new ComparerReport(); + + + // Act + report.Add(new Comparison + { + Mean = null, + Allocated = null + }); + + + // Assert + report.Comparisons.ShouldBeEmpty(); + } + + [Fact] + public void When_Comparison_Has_Mean_Should_Add() + { + // Arrange + var report = new ComparerReport(); + + + // Act + report.Add(new Comparison + { + Mean = MetricComparison.CalculateExecutionTime(1, 1 + ), + Allocated = null + }); + + + // Assert + report.Comparisons.Count.ShouldBe(1); + } + + [Fact] + public void When_Comparison_Has_Allocated_Should_Add() + { + // Arrange + var report = new ComparerReport(); + + + // Act + report.Add(new Comparison + { + Mean = null, + Allocated = MetricComparison.CalculateMemoryUsage(1, 1) + }); + + + // Assert + report.Comparisons.Count.ShouldBe(1); + } +} diff --git a/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Models/MemoryThresholdTests.cs b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Models/MemoryThresholdTests.cs new file mode 100644 index 0000000..4426017 --- /dev/null +++ b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Models/MemoryThresholdTests.cs @@ -0,0 +1,43 @@ +using PowerUtils.BenchmarkDotnet.Reporter.Models; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Tests.Models; + +public sealed class MemoryThresholdTests +{ + [Theory] + [InlineData("1B", 1, false)] + [InlineData("101B", 101, false)] + [InlineData("1KB", 1_000, false)] + [InlineData("1123KB", 1_123_000, false)] + [InlineData("1MB", 1_000_000, false)] + [InlineData("1234MB", 1234000000, false)] + [InlineData("1GB", 1000000000, false)] + [InlineData("1234GB", 1234000000000, false)] + [InlineData("15%", 15, true)] + [InlineData("100%", 100, true)] + public void From_Text_To_MemoryThreshold(string value, decimal expectedValue, bool expectedIsPercentage) + { + // Arrange & Act + var threshold = MemoryThreshold.Parse(value); + + + // Assert + threshold.Value.ShouldBe(expectedValue); + threshold.IsPercentage.ShouldBe(expectedIsPercentage); + } + + [Fact] + public void Memory_Conversion() + { + // Arrange + var threshold = MemoryThreshold.Parse("124KB"); + + + // Act + decimal act = threshold; + + + // Assert + act.ShouldBe(124000); + } +} diff --git a/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Models/MetricComparisonTests.cs b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Models/MetricComparisonTests.cs new file mode 100644 index 0000000..a814f19 --- /dev/null +++ b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Models/MetricComparisonTests.cs @@ -0,0 +1,172 @@ +using PowerUtils.BenchmarkDotnet.Reporter.Models; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Tests.Models; + +public sealed class MetricComparisonTests +{ + [Fact] + public void When_Baseline_And_Target_Is_Null_Should_Return_Null() + { + // Arrange + decimal? baseline = null; + decimal? target = null; + + + // Act + var act = MetricComparison.CalculateExecutionTime(baseline, target); + + + // Assert + act.ShouldBeNull(); + } + + [Fact] + public void When_Baseline_Is_Null_And_Target_Is_Not_Null_Should_Return_New() + { + // Arrange + decimal? baseline = null; + decimal? target = 12; + + + // Act + var act = MetricComparison.CalculateExecutionTime(baseline, target); + + + // Assert + act.ShouldNotBeNull(); + act.Status.ShouldBe(ComparisonStatus.New); + act.Target.ShouldBe(target); + } + + [Fact] + public void When_Baseline_Is_Not_Null_And_Target_Is_Null_Should_Return_Removed() + { + // Arrange + decimal? baseline = 12; + decimal? target = null; + + + // Act + var act = MetricComparison.CalculateExecutionTime(baseline, target); + + + // Assert + act.ShouldNotBeNull(); + act.Status.ShouldBe(ComparisonStatus.Removed); + act.Baseline.ShouldBe(baseline); + } + + [Fact] + public void When_Baseline_Is_Equal_Target_Should_Return_Equal() + { + // Arrange + decimal? baseline = 12; + decimal? target = 12; + + + // Act + var act = MetricComparison.CalculateExecutionTime(baseline, target); + + + // Assert + act.ShouldNotBeNull(); + act.Status.ShouldBe(ComparisonStatus.Equal); + act.Baseline.ShouldBe(baseline); + act.Target.ShouldBe(target); + act.Diff.ShouldBe(0); + act.DiffPercentage.ShouldBe(0); + } + + [Fact] + public void When_Baseline_Is_Less_Target_Should_Return_Less() + { + // Arrange + decimal? baseline = 12; + decimal? target = 15; + + + // Act + var act = MetricComparison.CalculateExecutionTime(baseline, target); + + + // Assert + act.ShouldNotBeNull(); + act.Status.ShouldBe(ComparisonStatus.Worse); + act.Baseline.ShouldBe(baseline); + act.Target.ShouldBe(target); + act.Diff.ShouldBe(3); + act.DiffPercentage.ShouldBe(25); + } + + [Fact] + public void When_Baseline_Is_Greater_Target_Should_Return_Greater() + { + // Arrange + decimal? baseline = 15; + decimal? target = 12; + + + // Act + var act = MetricComparison.CalculateExecutionTime(baseline, target); + + + // Assert + act.ShouldNotBeNull(); + act.Status.ShouldBe(ComparisonStatus.Better); + act.Baseline.ShouldBe(baseline); + act.Target.ShouldBe(target); + act.Diff.ShouldBe(-3); + act.DiffPercentage.ShouldBe(-20); + } + + [Fact] + public void When_Baseline_Is_Zero_And_Target_Is_Zero_Should_Return_Equal() + { + // Arrange + decimal? baseline = 0; + decimal? target = 0; + + + // Act + var act = MetricComparison.CalculateExecutionTime(baseline, target); + + + // Assert + act.ShouldNotBeNull(); + act.Status.ShouldBe(ComparisonStatus.Equal); + act.Baseline.ShouldBe(baseline); + act.Target.ShouldBe(target); + act.Diff.ShouldBe(0); + act.DiffPercentage.ShouldBeNull(); + } + + [Fact] + public void When_Calculate_Using_CalculateExecutionTime_Should_Return_Unit_NS() + { + // Arrange + decimal? baseline = 100; + decimal? target = 120; + + // Act + var result = MetricComparison.CalculateExecutionTime(baseline, target); + + // Assert + result.ShouldNotBeNull(); + result.Unit.ShouldBe("ns"); + } + + [Fact] + public void When_Calculate_Using_CalculateMemoryUsage_Should_Return_Unit_B() + { + // Arrange + decimal? baseline = 1000; + decimal? target = 800; + + // Act + var result = MetricComparison.CalculateMemoryUsage(baseline, target); + + // Assert + result.ShouldNotBeNull(); + result.Unit.ShouldBe("B"); + } +} diff --git a/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Models/TimeThresholdTests.cs b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Models/TimeThresholdTests.cs new file mode 100644 index 0000000..96bf91c --- /dev/null +++ b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Models/TimeThresholdTests.cs @@ -0,0 +1,43 @@ +using PowerUtils.BenchmarkDotnet.Reporter.Models; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Tests.Models; + +public sealed class TimeThresholdTests +{ + [Theory] + [InlineData("1ns", 1, false)] + [InlineData("101ns", 101, false)] + [InlineData("1Îŧs", 1000, false)] + [InlineData("1123Îŧs", 1123000, false)] + [InlineData("1ms", 1000000, false)] + [InlineData("1234ms", 1234000000, false)] + [InlineData("1s", 1000000000, false)] + [InlineData("1234s", 1234000000000, false)] + [InlineData("15%", 15, true)] + [InlineData("100%", 100, true)] + public void From_Text_To_TimeThreshold(string value, decimal expectedValue, bool expectedIsPercentage) + { + // Arrange & Act + var threshold = TimeThreshold.Parse(value); + + + // Assert + threshold.Value.ShouldBe(expectedValue); + threshold.IsPercentage.ShouldBe(expectedIsPercentage); + } + + [Fact] + public void Time_Conversion() + { + // Arrange + var threshold = TimeThreshold.Parse("124Îŧs"); + + + // Act + decimal act = threshold; + + + // Assert + act.ShouldBe(124000); + } +} diff --git a/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/PowerUtils.BenchmarkDotnet.Reporter.Tests.csproj b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/PowerUtils.BenchmarkDotnet.Reporter.Tests.csproj new file mode 100644 index 0000000..4f62fd3 --- /dev/null +++ b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/PowerUtils.BenchmarkDotnet.Reporter.Tests.csproj @@ -0,0 +1,70 @@ + + + + net9.0 + + PowerUtils.BenchmarkDotnet.Reporter.Tests + PowerUtils.BenchmarkDotnet.Reporter.Tests + + false + + enable + + + + + trx%3bLogFileName=$(MSBuildProjectName)_$(TargetFramework).trx + $(DefaultItemExcludes);coverage.*opencover.xml + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + all + runtime; build; native; contentfiles; analyzers + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/ToolCommandsTest.cs b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/ToolCommandsTest.cs new file mode 100644 index 0000000..0d50c7c --- /dev/null +++ b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/ToolCommandsTest.cs @@ -0,0 +1,163 @@ +using System; +using System.Linq; +using PowerUtils.BenchmarkDotnet.Reporter.Commands; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Tests; + +public sealed class ToolCommandsTest +{ + private readonly IServiceProvider _provider; + + public ToolCommandsTest() + { + _provider = Substitute.For(); + var command = Substitute.For(); + + _provider + .GetService(typeof(IComparerCommand)) + .Returns(command); + } + + [Fact] + public void Constructor_ShouldAddCommands() + { + // Arrange & Act + var toolCommands = new ToolCommands(_provider); + + + // Assert + toolCommands.Subcommands.Count.ShouldBe(1); + } + + [Fact] + public void RootCommands_ShouldContain_CompareCommand() + { + // Arrange + var toolCommands = new ToolCommands(_provider); + + + // Act + var compareCommand = toolCommands.Subcommands.Single(c => c.Name == "compare"); + + + // Assert + toolCommands.Subcommands.ShouldContain(compareCommand); + } + + [Fact] + public void CompareCommand_ShouldHave_4Options() + { + // Arrange & Act + var toolCommands = new ToolCommands(_provider); + + + // Assert + var compareCommand = toolCommands.Subcommands.Single(c => c.Name == "compare"); + + compareCommand.Options.Count.ShouldBe(6); + } + + [Fact] + public void CompareCommand_ShouldHave_BaselineOption() + { + // Arrange & Act + var toolCommands = new ToolCommands(_provider); + + + // Assert + var compareCommand = toolCommands.Subcommands.Single(c => c.Name == "compare"); + var baselineOption = compareCommand.Options.Single(o => o.Name == "baseline"); + + baselineOption.ValueType.ShouldBe(typeof(string)); + baselineOption.Aliases.ShouldContain("-b"); + baselineOption.Aliases.ShouldContain("--baseline"); + baselineOption.IsRequired.ShouldBeTrue(); + baselineOption.Description.ShouldBe("Path to the folder or file with Baseline report."); + } + + [Fact] + public void CompareCommand_ShouldHave_TargetOption() + { + // Arrange & Act + var toolCommands = new ToolCommands(_provider); + + + // Assert + var compareCommand = toolCommands.Subcommands.Single(c => c.Name == "compare"); + var targetOption = compareCommand.Options.Single(o => o.Name == "target"); + + targetOption.ValueType.ShouldBe(typeof(string)); + targetOption.Aliases.ShouldContain("-t"); + targetOption.Aliases.ShouldContain("--target"); + targetOption.IsRequired.ShouldBeTrue(); + targetOption.Description.ShouldBe("Path to the folder or file with target reports."); + } + + [Fact] + public void CompareCommand_ShouldHave_ThresholdMeanOption() + { + // Arrange & Act + var toolCommands = new ToolCommands(_provider); + + + // Assert + var compareCommand = toolCommands.Subcommands.Single(c => c.Name == "compare"); + var meanThresholdOption = compareCommand.Options.Single(o => o.Name == "threshold-mean"); + + meanThresholdOption.ValueType.ShouldBe(typeof(string)); + meanThresholdOption.Aliases.ShouldContain("-tm"); + meanThresholdOption.Aliases.ShouldContain("--threshold-mean"); + meanThresholdOption.Description.ShouldBe("Throw an error when the mean threshold is met. Examples: 5%, 10ms, 10Îŧs, 100ns, 1s."); + } + + [Fact] + public void CompareCommand_ShouldHave_ThresholdAllocationOption() + { + // Arrange & Act + var toolCommands = new ToolCommands(_provider); + + + // Assert + var compareCommand = toolCommands.Subcommands.Single(c => c.Name == "compare"); + var allocationThresholdOption = compareCommand.Options.Single(o => o.Name == "threshold-allocation"); + + allocationThresholdOption.ValueType.ShouldBe(typeof(string)); + allocationThresholdOption.Aliases.ShouldContain("-ta"); + allocationThresholdOption.Aliases.ShouldContain("--threshold-allocation"); + allocationThresholdOption.Description.ShouldBe("Throw an error when the allocation threshold is met. Examples: 5%, 10b, 10kb, 100mb, 1gb."); + } + + [Fact] + public void CompareCommand_ShouldHave_FormatsOption() + { + // Arrange & Act + var toolCommands = new ToolCommands(_provider); + + + // Assert + var compareCommand = toolCommands.Subcommands.Single(c => c.Name == "compare"); + var formatsOption = compareCommand.Options.Single(o => o.Name == "format"); + + formatsOption.ValueType.ShouldBe(typeof(string[])); + formatsOption.Aliases.ShouldContain("-f"); + formatsOption.Aliases.ShouldContain("--format"); + formatsOption.Description.ShouldBe("Output format for the report."); + } + + [Fact] + public void CompareCommand_ShouldHave_OutputOption() + { + // Arrange & Act + var toolCommands = new ToolCommands(_provider); + + + // Assert + var compareCommand = toolCommands.Subcommands.Single(c => c.Name == "compare"); + var outputOption = compareCommand.Options.Single(o => o.Name == "output"); + + outputOption.ValueType.ShouldBe(typeof(string)); + outputOption.Aliases.ShouldContain("-o"); + outputOption.Aliases.ShouldContain("--output"); + outputOption.Description.ShouldBe("Output directory to export the diff report. Default is current directory."); + } +} diff --git a/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Validations/ReportValidationTest.cs b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Validations/ReportValidationTest.cs new file mode 100644 index 0000000..b284ddd --- /dev/null +++ b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Validations/ReportValidationTest.cs @@ -0,0 +1,254 @@ +using PowerUtils.BenchmarkDotnet.Reporter.Models; +using PowerUtils.BenchmarkDotnet.Reporter.Validations; + +namespace PowerUtils.BenchmarkDotnet.Reporter.Tests.Validations; + +public class ReportValidationTest +{ + [Fact] + public void When_All_Properties_Equal_Returns_EmptyList() + { + // Arrange + var baseline = _createBenchmarkReport(); + var target = _createBenchmarkReport(); + var validation = new ReportValidation(); + + + // Act + var result = validation.HostEnvironmentValidate(baseline, target); + + + // Assert + result.ShouldBeEmpty(); + } + + [Fact] + public void When_OsVersion_Is_Different_Returns_Message() + { + // Arrange + var baseline = _createBenchmarkReport(osVersion: "Windows 10"); + var target = _createBenchmarkReport(osVersion: "Windows 11"); + var validation = new ReportValidation(); + + + // Act + var result = validation.HostEnvironmentValidate(baseline, target); + + + // Assert + result.Count.ShouldBe(1); + result[0].ShouldContain("OS Version is different"); + } + + [Fact] + public void When_ProcessorName_Is_Different_Returns_Message() + { + // Arrange + var baseline = _createBenchmarkReport(processorName: "AMD Ryzen 6 3600X"); + var target = _createBenchmarkReport(processorName: "AMD Ryzen 5 3600X"); + var validation = new ReportValidation(); + + + // Act + var result = validation.HostEnvironmentValidate(baseline, target); + + + // Assert + result.Count.ShouldBe(1); + result[0].ShouldContain("Processor Name is different"); + } + + [Fact] + public void When_PhysicalProcessorCount_Is_Different_Returns_Message() + { + // Arrange + var baseline = _createBenchmarkReport(physicalProcessorCount: 1); + var target = _createBenchmarkReport(physicalProcessorCount: 3); + var validation = new ReportValidation(); + + + // Act + var result = validation.HostEnvironmentValidate(baseline, target); + + + // Assert + result.Count.ShouldBe(1); + result[0].ShouldContain("Physical Processor Count is different"); + } + + [Fact] + public void When_PhysicalCoreCount_Is_Different_Returns_Message() + { + // Arrange + var baseline = _createBenchmarkReport(physicalCoreCount: 1); + var target = _createBenchmarkReport(physicalCoreCount: 3); + var validation = new ReportValidation(); + + + // Act + var result = validation.HostEnvironmentValidate(baseline, target); + + + // Assert + result.Count.ShouldBe(1); + result[0].ShouldContain("Physical Core Count is different"); + } + + [Fact] + public void When_LogicalCoreCount_Is_Different_Returns_Message() + { + // Arrange + var baseline = _createBenchmarkReport(logicalCoreCount: 1); + var target = _createBenchmarkReport(logicalCoreCount: 3); + var validation = new ReportValidation(); + + + // Act + var result = validation.HostEnvironmentValidate(baseline, target); + + + // Assert + result.Count.ShouldBe(1); + result[0].ShouldContain("Logical Core Count is different"); + } + + [Fact] + public void When_RuntimeVersion_Is_Different_Returns_Message() + { + // Arrange + var baseline = _createBenchmarkReport(runtimeVersion: ".NET 9.0.2 (9.0.225.6610)"); + var target = _createBenchmarkReport(runtimeVersion: ".NET 19.0.2 (9.0.225.6610)"); + var validation = new ReportValidation(); + + + // Act + var result = validation.HostEnvironmentValidate(baseline, target); + + + // Assert + result.Count.ShouldBe(1); + result[0].ShouldContain("Runtime Version is different"); + } + + [Fact] + public void When_Architecture_Is_Different_Returns_Message() + { + // Arrange + var baseline = _createBenchmarkReport(architecture: "X64"); + var target = _createBenchmarkReport(architecture: "X32"); + var validation = new ReportValidation(); + + + // Act + var result = validation.HostEnvironmentValidate(baseline, target); + + + // Assert + result.Count.ShouldBe(1); + result[0].ShouldContain("Architecture is different"); + } + + [Fact] + public void When_DotNetCliVersion_Is_Different_Returns_Message() + { + // Arrange + var baseline = _createBenchmarkReport(dotNetCliVersion: "10.0.100-preview.1.25120.13"); + var target = _createBenchmarkReport(dotNetCliVersion: "11.0.100-preview.1.25120.13"); + var validation = new ReportValidation(); + + + // Act + var result = validation.HostEnvironmentValidate(baseline, target); + + + // Assert + result.Count.ShouldBe(1); + result[0].ShouldContain("DotNet CLI Version is different"); + } + + [Fact] + public void When_Hertz_Is_Different_Returns_Message() + { + // Arrange + var baseline = _createBenchmarkReport(hertz: 122); + var target = _createBenchmarkReport(hertz: 23423); + var validation = new ReportValidation(); + + + // Act + var result = validation.HostEnvironmentValidate(baseline, target); + + + // Assert + result.Count.ShouldBe(1); + result[0].ShouldContain("Chronometer Frequency is different"); + } + + [Fact] + public void When_BaselineConfiguration_Is_Not_Release_Returns_Message() + { + // Arrange + var baseline = _createBenchmarkReport(configuration: "ddd"); + var target = _createBenchmarkReport(); + var validation = new ReportValidation(); + + + // Act + var result = validation.HostEnvironmentValidate(baseline, target); + + + // Assert + result.Count.ShouldBe(1); + result[0].ShouldContain("The baseline report wasn't executed in RELEASE mode"); + } + + [Fact] + public void When_TargetConfiguration_Is_Not_Release_Returns_Message() + { + // Arrange + var baseline = _createBenchmarkReport(); + var target = _createBenchmarkReport(configuration: "ddd"); + var validation = new ReportValidation(); + + + // Act + var result = validation.HostEnvironmentValidate(baseline, target); + + + // Assert + result.Count.ShouldBe(1); + result[0].ShouldContain("The target report wasn't executed in RELEASE mode"); + } + + private static BenchmarkFullJsonResport _createBenchmarkReport( + string osVersion = "Windows 11 (10.0.26100.3323)", + string processorName = "AMD Ryzen 5 3600X", + int? physicalProcessorCount = 1, + int? physicalCoreCount = 6, + int? logicalCoreCount = 12, + string runtimeVersion = ".NET 9.0.2 (9.0.225.6610)", + string architecture = "X64", + string dotNetCliVersion = "10.0.100-preview.1.25120.13", + int hertz = 10000000, + string configuration = "RELEASE") => new() + { + HostEnvironmentInfo = new() + { + OsVersion = osVersion, + ProcessorName = processorName, + PhysicalProcessorCount = physicalProcessorCount, + PhysicalCoreCount = physicalCoreCount, + LogicalCoreCount = logicalCoreCount, + RuntimeVersion = runtimeVersion, + Architecture = architecture, + DotNetCliVersion = dotNetCliVersion, + ChronometerFrequency = new() { Hertz = hertz }, + Configuration = configuration, + BenchmarkDotNetCaption = "BenchmarkDotNet", + BenchmarkDotNetVersion = "0.14.0", + HardwareTimerKind = "Unknown" + }, + + Benchmarks = [] + }; +} diff --git a/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Validations/ValidationExceptionTest.cs b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Validations/ValidationExceptionTest.cs new file mode 100644 index 0000000..3406a78 --- /dev/null +++ b/tests/PowerUtils.BenchmarkDotnet.Reporter.Tests/Validations/ValidationExceptionTest.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using PowerUtils.BenchmarkDotnet.Reporter.Validations; +namespace PowerUtils.BenchmarkDotnet.Reporter.Tests.Validations; + +public sealed class ValidationExceptionTest +{ + [Fact] + public void When_Pass_Message_ShouldSetSameMessage() + { + // Arrange + var message = "Error message"; + + + // Act + var act = new ValidationException(message); + + + // Assert + act.Message.ShouldBe(message); + } + + [Fact] + public void Constructor_WithMessagesList_ShouldJoinMessagesWithPipe() + { + // Arrange + var messages = new List + { + "First error", + "Second error", + "Third error" + }; + + + // Act + var act = new ValidationException(messages); + + + // Assert + act.Message.ShouldBe("First error | Second error | Third error"); + } + + [Fact] + public void When_Pass_MessageList_ShouldJoinMessagesWithPipe() + { + // Arrange + var messages = new List(); + + + // Act + var act = new ValidationException(messages); + + + // Assert + act.Message.ShouldBeEmpty(); + } +} diff --git a/tests/test-data/report-01/Benchmark-report-full.json b/tests/test-data/report-01/Benchmark-report-full.json new file mode 100644 index 0000000..c14f407 --- /dev/null +++ b/tests/test-data/report-01/Benchmark-report-full.json @@ -0,0 +1,1516 @@ +{ + "Title":"Benchmark-20250308-191538", + "HostEnvironmentInfo":{ + "BenchmarkDotNetCaption":"BenchmarkDotNet", + "BenchmarkDotNetVersion":"0.14.0", + "OsVersion":"Windows 11 (10.0.26100.3323)", + "ProcessorName":"AMD Ryzen 5 3600X", + "PhysicalProcessorCount":1, + "PhysicalCoreCount":6, + "LogicalCoreCount":12, + "RuntimeVersion":".NET 9.0.2 (9.0.225.6610)", + "Architecture":"X64", + "HasAttachedDebugger":false, + "HasRyuJit":true, + "Configuration":"RELEASE", + "DotNetCliVersion":"10.0.100-preview.1.25120.13", + "ChronometerFrequency":{ + "Hertz":10000000 + }, + "HardwareTimerKind":"Unknown" + }, + "Benchmarks":[ + { + "DisplayInfo":"Benchmark.StringConcat: DefaultJob", + "Namespace":null, + "Type":"Benchmark", + "Method":"StringConcat", + "MethodTitle":"StringConcat", + "Parameters":"", + "FullName":"Benchmark.StringConcat", + "HardwareIntrinsics":"AVX2,AES,BMI1,BMI2,FMA,LZCNT,PCLMUL,POPCNT VectorSize=256", + "Statistics":{ + "OriginalValues":[ + 14.563485980033875,15.450254082679749,15.543633699417114,15.554186701774597,15.643396973609924,15.533214807510376,16.163161396980286,15.360012650489807,15.7083660364151,15.574643015861511,15.368443727493286,15.797820687294006,15.577062964439392,15.453746914863586,15.843746066093445,15.746000409126282 + ], + "N":16, + "Min":14.563485980033875, + "LowerFence":15.055522322654724, + "Q1":15.452873706817627, + "Median":15.564414858818054, + "Mean":15.555073507130146, + "Q3":15.717774629592896, + "UpperFence":16.1151260137558, + "Max":16.163161396980286, + "InterquartileRange":0.26490092277526855, + "LowerOutliers":[ + 14.563485980033875 + ], + "UpperOutliers":[ + 16.163161396980286 + ], + "AllOutliers":[ + 14.563485980033875,16.163161396980286 + ], + "StandardError":0.08313234991024189, + "Variance":0.11057580162558231, + "StandardDeviation":0.33252939964096756, + "Skewness":-1.2180855382616556, + "Kurtosis":5.718635652949811, + "ConfidenceInterval":{ + "N":16, + "Mean":15.555073507130146, + "StandardError":0.08313234991024189, + "Level":12, + "Margin":0.3385785411920758, + "Lower":15.21649496593807, + "Upper":15.893652048322222 + }, + "Percentiles":{ + "P0":14.563485980033875, + "P25":15.452873706817627, + "P50":15.564414858818054, + "P67":15.646645426750183, + "P80":15.746000409126282, + "P85":15.784865617752075, + "P90":15.820783376693726, + "P95":15.923599898815155, + "P100":16.163161396980286 + } + }, + "Memory":{ + "Gen0Collections":192, + "Gen1Collections":0, + "Gen2Collections":0, + "TotalOperations":33554432, + "BytesAllocatedPerOperation":48 + }, + "Measurements":[ + { + "IterationMode":"Overhead", + "IterationStage":"Jitting", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":1, + "Nanoseconds":163100 + },{ + "IterationMode":"Workload", + "IterationStage":"Jitting", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":1, + "Nanoseconds":753000 + },{ + "IterationMode":"Overhead", + "IterationStage":"Jitting", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":16, + "Nanoseconds":248200 + },{ + "IterationMode":"Workload", + "IterationStage":"Jitting", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":16, + "Nanoseconds":168700 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":16, + "Nanoseconds":2100 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":32, + "Nanoseconds":2500 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":64, + "Nanoseconds":2500 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":128, + "Nanoseconds":10800 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":256, + "Nanoseconds":18800 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":512, + "Nanoseconds":27900 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":1024, + "Nanoseconds":54700 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":2048, + "Nanoseconds":81900 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":9, + "Operations":4096, + "Nanoseconds":154500 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":10, + "Operations":8192, + "Nanoseconds":221000 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":11, + "Operations":16384, + "Nanoseconds":513700 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":12, + "Operations":32768, + "Nanoseconds":1013200 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":13, + "Operations":65536, + "Nanoseconds":1908900 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":14, + "Operations":131072, + "Nanoseconds":3941200 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":15, + "Operations":262144, + "Nanoseconds":7307900 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":16, + "Operations":524288, + "Nanoseconds":11684100 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":17, + "Operations":1048576, + "Nanoseconds":22037600 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":18, + "Operations":2097152, + "Nanoseconds":44884300 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":19, + "Operations":4194304, + "Nanoseconds":84395700 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":20, + "Operations":8388608, + "Nanoseconds":140642100 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":21, + "Operations":16777216, + "Nanoseconds":271985700 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":22, + "Operations":33554432, + "Nanoseconds":541668400 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":33554432, + "Nanoseconds":77748100 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":33554432, + "Nanoseconds":77414800 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":33554432, + "Nanoseconds":71804800 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":33554432, + "Nanoseconds":51963500 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":33554432, + "Nanoseconds":52123700 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":33554432, + "Nanoseconds":52018500 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":33554432, + "Nanoseconds":53937400 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":33554432, + "Nanoseconds":52244500 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":33554432, + "Nanoseconds":53915700 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":33554432, + "Nanoseconds":51612400 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":33554432, + "Nanoseconds":52080300 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":33554432, + "Nanoseconds":52593900 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":33554432, + "Nanoseconds":52146700 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":33554432, + "Nanoseconds":51998200 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":33554432, + "Nanoseconds":52868500 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":33554432, + "Nanoseconds":52380400 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":9, + "Operations":33554432, + "Nanoseconds":52116400 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":10, + "Operations":33554432, + "Nanoseconds":52588300 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":11, + "Operations":33554432, + "Nanoseconds":52289400 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":12, + "Operations":33554432, + "Nanoseconds":52846500 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":13, + "Operations":33554432, + "Nanoseconds":54797400 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":14, + "Operations":33554432, + "Nanoseconds":52118500 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":15, + "Operations":33554432, + "Nanoseconds":51959100 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":33554432, + "Nanoseconds":543264400 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":33554432, + "Nanoseconds":547927900 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":33554432, + "Nanoseconds":554565700 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":33554432, + "Nanoseconds":572905600 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":33554432, + "Nanoseconds":554491400 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":33554432, + "Nanoseconds":547313600 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":33554432, + "Nanoseconds":547342000 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":33554432, + "Nanoseconds":549765200 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":9, + "Operations":33554432, + "Nanoseconds":540175500 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":33554432, + "Nanoseconds":540958900 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":33554432, + "Nanoseconds":570713900 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":33554432, + "Nanoseconds":573847200 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":33554432, + "Nanoseconds":574201300 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":33554432, + "Nanoseconds":577194700 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":33554432, + "Nanoseconds":573497600 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":33554432, + "Nanoseconds":594635100 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":33554432, + "Nanoseconds":567685900 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":9, + "Operations":33554432, + "Nanoseconds":608228800 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":10, + "Operations":33554432, + "Nanoseconds":579374700 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":11, + "Operations":33554432, + "Nanoseconds":574887700 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":12, + "Operations":33554432, + "Nanoseconds":567968800 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":13, + "Operations":33554432, + "Nanoseconds":582376300 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":14, + "Operations":33554432, + "Nanoseconds":574968900 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":15, + "Operations":33554432, + "Nanoseconds":570831100 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":16, + "Operations":33554432, + "Nanoseconds":583917300 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":17, + "Operations":33554432, + "Nanoseconds":580637500 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":33554432, + "Nanoseconds":488669500 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":33554432, + "Nanoseconds":518424500 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":33554432, + "Nanoseconds":521557800 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":33554432, + "Nanoseconds":521911900 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":33554432, + "Nanoseconds":524905300 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":33554432, + "Nanoseconds":521208200 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":33554432, + "Nanoseconds":542345700 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":33554432, + "Nanoseconds":515396500 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":9, + "Operations":33554432, + "Nanoseconds":527085300 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":10, + "Operations":33554432, + "Nanoseconds":522598300 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":11, + "Operations":33554432, + "Nanoseconds":515679400 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":12, + "Operations":33554432, + "Nanoseconds":530086900 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":13, + "Operations":33554432, + "Nanoseconds":522679500 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":14, + "Operations":33554432, + "Nanoseconds":518541700 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":15, + "Operations":33554432, + "Nanoseconds":531627900 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":16, + "Operations":33554432, + "Nanoseconds":528348100 + } + ], + "Metrics":[ + { + "Value":0.0057220458984375, + "Descriptor":{ + "Id":"Gen0Collects", + "DisplayName":"Gen0", + "Legend":"GC Generation 0 collects per 1000 operations", + "NumberFormat":"#0.0000", + "UnitType":0, + "Unit":"Count", + "TheGreaterTheBetter":false, + "PriorityInCategory":0 + } + },{ + "Value":0, + "Descriptor":{ + "Id":"Gen1Collects", + "DisplayName":"Gen1", + "Legend":"GC Generation 1 collects per 1000 operations", + "NumberFormat":"#0.0000", + "UnitType":0, + "Unit":"Count", + "TheGreaterTheBetter":false, + "PriorityInCategory":1 + } + },{ + "Value":0, + "Descriptor":{ + "Id":"Gen2Collects", + "DisplayName":"Gen2", + "Legend":"GC Generation 2 collects per 1000 operations", + "NumberFormat":"#0.0000", + "UnitType":0, + "Unit":"Count", + "TheGreaterTheBetter":false, + "PriorityInCategory":2 + } + },{ + "Value":48, + "Descriptor":{ + "Id":"Allocated Memory", + "DisplayName":"Allocated", + "Legend":"Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)", + "NumberFormat":"0.##", + "UnitType":2, + "Unit":"B", + "TheGreaterTheBetter":false, + "PriorityInCategory":3 + } + } + ] + },{ + "DisplayInfo":"Benchmark.StringJoin: DefaultJob", + "Namespace":null, + "Type":"Benchmark", + "Method":"StringJoin", + "MethodTitle":"StringJoin", + "Parameters":"", + "FullName":"Benchmark.StringJoin", + "HardwareIntrinsics":"AVX2,AES,BMI1,BMI2,FMA,LZCNT,PCLMUL,POPCNT VectorSize=256", + "Statistics":{ + "OriginalValues":[ + 16.011926531791687,16.761597990989685,17.04905331134796,16.77703857421875,16.820374131202698,16.8985515832901,16.837158799171448,16.866081953048706,16.807398200035095,16.886553168296814,16.64295196533203,16.40799343585968,16.400322318077087 + ], + "N":13, + "Min":16.011926531791687, + "LowerFence":16.30825698375702, + "Q1":16.64295196533203, + "Median":16.807398200035095, + "Mean":16.705153997127827, + "Q3":16.866081953048706, + "UpperFence":17.20077693462372, + "Max":17.04905331134796, + "InterquartileRange":0.2231299877166748, + "LowerOutliers":[ + 16.011926531791687 + ], + "UpperOutliers":[ + + ], + "AllOutliers":[ + 16.011926531791687 + ], + "StandardError":0.07719610123208262, + "Variance":0.07747009459064132, + "StandardDeviation":0.2783345012581827, + "Skewness":-1.1574712559246962, + "Kurtosis":3.398504673431691, + "ConfidenceInterval":{ + "N":13, + "Mean":16.705153997127827, + "StandardError":0.07719610123208262, + "Level":12, + "Margin":0.3333166529384781, + "Lower":16.37183734418935, + "Upper":17.038470650066305 + }, + "Percentiles":{ + "P0":16.011926531791687, + "P25":16.64295196533203, + "P50":16.807398200035095, + "P67":16.838315725326538, + "P80":16.87836468219757, + "P85":16.88895285129547, + "P90":16.896151900291443, + "P95":16.958752274513245, + "P100":17.04905331134796 + } + }, + "Memory":{ + "Gen0Collections":192, + "Gen1Collections":0, + "Gen2Collections":0, + "TotalOperations":33554432, + "BytesAllocatedPerOperation":48 + }, + "Measurements":[ + { + "IterationMode":"Overhead", + "IterationStage":"Jitting", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":1, + "Nanoseconds":167100 + },{ + "IterationMode":"Workload", + "IterationStage":"Jitting", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":1, + "Nanoseconds":691900 + },{ + "IterationMode":"Overhead", + "IterationStage":"Jitting", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":16, + "Nanoseconds":149700 + },{ + "IterationMode":"Workload", + "IterationStage":"Jitting", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":16, + "Nanoseconds":159600 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":16, + "Nanoseconds":1800 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":32, + "Nanoseconds":2300 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":64, + "Nanoseconds":2600 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":128, + "Nanoseconds":16400 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":256, + "Nanoseconds":32500 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":512, + "Nanoseconds":31800 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":1024, + "Nanoseconds":60700 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":2048, + "Nanoseconds":111900 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":9, + "Operations":4096, + "Nanoseconds":187800 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":10, + "Operations":8192, + "Nanoseconds":329400 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":11, + "Operations":16384, + "Nanoseconds":720000 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":12, + "Operations":32768, + "Nanoseconds":1797300 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":13, + "Operations":65536, + "Nanoseconds":2998500 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":14, + "Operations":131072, + "Nanoseconds":5316100 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":15, + "Operations":262144, + "Nanoseconds":11930500 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":16, + "Operations":524288, + "Nanoseconds":18183400 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":17, + "Operations":1048576, + "Nanoseconds":33795500 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":18, + "Operations":2097152, + "Nanoseconds":67097700 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":19, + "Operations":4194304, + "Nanoseconds":109510500 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":20, + "Operations":8388608, + "Nanoseconds":146022200 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":21, + "Operations":16777216, + "Nanoseconds":292405100 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":22, + "Operations":33554432, + "Nanoseconds":581745400 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":33554432, + "Nanoseconds":76536000 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":33554432, + "Nanoseconds":76518000 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":33554432, + "Nanoseconds":69392500 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":33554432, + "Nanoseconds":51322100 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":33554432, + "Nanoseconds":51231000 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":33554432, + "Nanoseconds":51162200 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":33554432, + "Nanoseconds":51671700 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":33554432, + "Nanoseconds":51380000 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":9, + "Operations":33554432, + "Nanoseconds":52050100 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":10, + "Operations":33554432, + "Nanoseconds":51792300 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":33554432, + "Nanoseconds":52175400 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":33554432, + "Nanoseconds":51718000 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":33554432, + "Nanoseconds":51345700 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":33554432, + "Nanoseconds":54810900 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":33554432, + "Nanoseconds":59733200 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":33554432, + "Nanoseconds":52897700 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":33554432, + "Nanoseconds":51478500 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":33554432, + "Nanoseconds":52138900 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":9, + "Operations":33554432, + "Nanoseconds":52001500 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":10, + "Operations":33554432, + "Nanoseconds":51609100 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":11, + "Operations":33554432, + "Nanoseconds":52452700 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":12, + "Operations":33554432, + "Nanoseconds":52521900 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":13, + "Operations":33554432, + "Nanoseconds":51730600 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":14, + "Operations":33554432, + "Nanoseconds":52055600 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":15, + "Operations":33554432, + "Nanoseconds":57486100 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":33554432, + "Nanoseconds":601455500 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":33554432, + "Nanoseconds":582306600 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":33554432, + "Nanoseconds":583150000 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":33554432, + "Nanoseconds":615092300 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":33554432, + "Nanoseconds":582150100 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":33554432, + "Nanoseconds":591418600 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":33554432, + "Nanoseconds":597080200 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":33554432, + "Nanoseconds":605837000 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":9, + "Operations":33554432, + "Nanoseconds":584745000 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":33554432, + "Nanoseconds":589410000 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":33554432, + "Nanoseconds":614564800 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":33554432, + "Nanoseconds":624210200 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":33554432, + "Nanoseconds":615082900 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":33554432, + "Nanoseconds":616537000 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":33554432, + "Nanoseconds":632309100 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":33554432, + "Nanoseconds":619160200 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":33554432, + "Nanoseconds":617100200 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":9, + "Operations":33554432, + "Nanoseconds":618070700 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":10, + "Operations":33554432, + "Nanoseconds":616101600 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":11, + "Operations":33554432, + "Nanoseconds":618757600 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":12, + "Operations":33554432, + "Nanoseconds":610583700 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":13, + "Operations":33554432, + "Nanoseconds":639238500 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":14, + "Operations":33554432, + "Nanoseconds":602699800 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":15, + "Operations":33554432, + "Nanoseconds":602442400 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":33554432, + "Nanoseconds":537271100 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":33554432, + "Nanoseconds":562425900 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":33554432, + "Nanoseconds":572071300 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":33554432, + "Nanoseconds":562944000 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":33554432, + "Nanoseconds":564398100 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":33554432, + "Nanoseconds":567021300 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":33554432, + "Nanoseconds":564961300 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":33554432, + "Nanoseconds":565931800 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":9, + "Operations":33554432, + "Nanoseconds":563962700 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":10, + "Operations":33554432, + "Nanoseconds":566618700 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":11, + "Operations":33554432, + "Nanoseconds":558444800 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":12, + "Operations":33554432, + "Nanoseconds":550560900 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":13, + "Operations":33554432, + "Nanoseconds":550303500 + } + ], + "Metrics":[ + { + "Value":0.0057220458984375, + "Descriptor":{ + "Id":"Gen0Collects", + "DisplayName":"Gen0", + "Legend":"GC Generation 0 collects per 1000 operations", + "NumberFormat":"#0.0000", + "UnitType":0, + "Unit":"Count", + "TheGreaterTheBetter":false, + "PriorityInCategory":0 + } + },{ + "Value":0, + "Descriptor":{ + "Id":"Gen1Collects", + "DisplayName":"Gen1", + "Legend":"GC Generation 1 collects per 1000 operations", + "NumberFormat":"#0.0000", + "UnitType":0, + "Unit":"Count", + "TheGreaterTheBetter":false, + "PriorityInCategory":1 + } + },{ + "Value":0, + "Descriptor":{ + "Id":"Gen2Collects", + "DisplayName":"Gen2", + "Legend":"GC Generation 2 collects per 1000 operations", + "NumberFormat":"#0.0000", + "UnitType":0, + "Unit":"Count", + "TheGreaterTheBetter":false, + "PriorityInCategory":2 + } + },{ + "Value":48, + "Descriptor":{ + "Id":"Allocated Memory", + "DisplayName":"Allocated", + "Legend":"Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)", + "NumberFormat":"0.##", + "UnitType":2, + "Unit":"B", + "TheGreaterTheBetter":false, + "PriorityInCategory":3 + } + } + ] + } + ] +} diff --git a/tests/test-data/report-01/Benchmark-report-github.md b/tests/test-data/report-01/Benchmark-report-github.md new file mode 100644 index 0000000..6812410 --- /dev/null +++ b/tests/test-data/report-01/Benchmark-report-github.md @@ -0,0 +1,14 @@ +``` + +BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3323) +AMD Ryzen 5 3600X, 1 CPU, 12 logical and 6 physical cores +.NET SDK 10.0.100-preview.1.25120.13 + [Host] : .NET 9.0.2 (9.0.225.6610), X64 RyuJIT AVX2 + DefaultJob : .NET 9.0.2 (9.0.225.6610), X64 RyuJIT AVX2 + + +``` +| Method | Mean | Error | StdDev | Ratio | RatioSD | Rank | Gen0 | Allocated | Alloc Ratio | +|------------- |---------:|---------:|---------:|------:|--------:|-----:|-------:|----------:|------------:| +| StringConcat | 15.56 ns | 0.339 ns | 0.333 ns | 0.93 | 0.02 | I | 0.0057 | 48 B | 1.00 | +| StringJoin | 16.71 ns | 0.333 ns | 0.278 ns | 1.00 | 0.02 | II | 0.0057 | 48 B | 1.00 | diff --git a/tests/test-data/report-01/Benchmark-report.csv b/tests/test-data/report-01/Benchmark-report.csv new file mode 100644 index 0000000..06bcf54 --- /dev/null +++ b/tests/test-data/report-01/Benchmark-report.csv @@ -0,0 +1,3 @@ +Method,Job,AnalyzeLaunchVariance,EvaluateOverhead,MaxAbsoluteError,MaxRelativeError,MinInvokeCount,MinIterationTime,OutlierMode,Affinity,EnvironmentVariables,Jit,LargeAddressAware,Platform,PowerPlanMode,Runtime,AllowVeryLargeObjects,Concurrent,CpuGroups,Force,HeapAffinitizeMask,HeapCount,NoAffinitize,RetainVm,Server,Arguments,BuildConfiguration,Clock,EngineFactory,NuGetReferences,Toolchain,IsMutator,InvocationCount,IterationCount,IterationTime,LaunchCount,MaxIterationCount,MaxWarmupIterationCount,MemoryRandomization,MinIterationCount,MinWarmupIterationCount,RunStrategy,UnrollFactor,WarmupCount,Mean,Error,StdDev,Ratio,RatioSD,Rank,Gen0,Allocated,Alloc Ratio +StringConcat,DefaultJob,False,Default,Default,Default,Default,Default,Default,111111111111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 9.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,15.56 ns,0.339 ns,0.333 ns,0.93,0.02,I,0.0057,48 B,1.00 +StringJoin,DefaultJob,False,Default,Default,Default,Default,Default,Default,111111111111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 9.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,16.71 ns,0.333 ns,0.278 ns,1.00,0.02,II,0.0057,48 B,1.00 diff --git a/tests/test-data/report-01/Benchmark-report.html b/tests/test-data/report-01/Benchmark-report.html new file mode 100644 index 0000000..b58a859 --- /dev/null +++ b/tests/test-data/report-01/Benchmark-report.html @@ -0,0 +1,31 @@ + + + + +Benchmark-20250308-191538 + + + + +

+BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3323)
+AMD Ryzen 5 3600X, 1 CPU, 12 logical and 6 physical cores
+.NET SDK 10.0.100-preview.1.25120.13
+  [Host]     : .NET 9.0.2 (9.0.225.6610), X64 RyuJIT AVX2
+  DefaultJob : .NET 9.0.2 (9.0.225.6610), X64 RyuJIT AVX2
+
+
+ + + + + + +
MethodMeanErrorStdDevRatioRatioSDRankGen0AllocatedAlloc Ratio
StringConcat15.56 ns0.339 ns0.333 ns0.930.02I0.005748 B1.00
StringJoin16.71 ns0.333 ns0.278 ns1.000.02II0.005748 B1.00
+ + diff --git a/tests/test-data/report-02/Benchmark-report-full.json b/tests/test-data/report-02/Benchmark-report-full.json new file mode 100644 index 0000000..859e466 --- /dev/null +++ b/tests/test-data/report-02/Benchmark-report-full.json @@ -0,0 +1,1943 @@ +{ + "Title":"Benchmark-20250308-230345", + "HostEnvironmentInfo":{ + "BenchmarkDotNetCaption":"BenchmarkDotNet", + "BenchmarkDotNetVersion":"0.14.0", + "OsVersion":"Windows 11 (10.0.26100.3323)", + "ProcessorName":"AMD Ryzen 5 3600X", + "PhysicalProcessorCount":1, + "PhysicalCoreCount":6, + "LogicalCoreCount":12, + "RuntimeVersion":".NET 9.0.2 (9.0.225.6610)", + "Architecture":"X64", + "HasAttachedDebugger":false, + "HasRyuJit":true, + "Configuration":"RELEASE", + "DotNetCliVersion":"10.0.100-preview.1.25120.13", + "ChronometerFrequency":{ + "Hertz":10000000 + }, + "HardwareTimerKind":"Unknown" + }, + "Benchmarks":[ + { + "DisplayInfo":"Benchmark.StringConcat: DefaultJob", + "Namespace":null, + "Type":"Benchmark", + "Method":"StringConcat", + "MethodTitle":"StringConcat", + "Parameters":"", + "FullName":"Benchmark.StringConcat", + "HardwareIntrinsics":"AVX2,AES,BMI1,BMI2,FMA,LZCNT,PCLMUL,POPCNT VectorSize=256", + "Statistics":{ + "OriginalValues":[ + 14.594775438308716,15.302872657775879,15.152806043624878,14.790865778923035,14.908444881439209,14.912262558937073,15.31381905078888,15.210828185081482,15.139663219451904,15.39255976676941,15.664532780647278,15.295547246932983,15.734750032424927,15.008017420768738,14.898484945297241,14.67844545841217 + ], + "N":16, + "Min":14.594775438308716, + "LowerFence":14.306473359465599, + "Q1":14.905954897403717, + "Median":15.146234631538391, + "Mean":15.124917216598988, + "Q3":15.305609256029129, + "UpperFence":15.905090793967247, + "Max":15.734750032424927, + "InterquartileRange":0.399654358625412, + "LowerOutliers":[ + + ], + "UpperOutliers":[ + + ], + "AllOutliers":[ + + ], + "StandardError":0.08135706381812795, + "Variance":0.1059035493297111, + "StandardDeviation":0.3254282552725118, + "Skewness":0.2046662480487116, + "Kurtosis":2.0598553132936965, + "ConfidenceInterval":{ + "N":16, + "Mean":15.124917216598988, + "StandardError":0.08135706381812795, + "Level":12, + "Margin":0.33134821778710166, + "Lower":14.793568998811885, + "Upper":15.45626543438609 + }, + "Percentiles":{ + "P0":14.594775438308716, + "P25":14.905954897403717, + "P50":15.146234631538391, + "P67":15.295913517475128, + "P80":15.31381905078888, + "P85":15.372874587774277, + "P90":15.528546273708344, + "P95":15.68208709359169, + "P100":15.734750032424927 + } + }, + "Memory":{ + "Gen0Collections":192, + "Gen1Collections":0, + "Gen2Collections":0, + "TotalOperations":33554432, + "BytesAllocatedPerOperation":48 + }, + "Measurements":[ + { + "IterationMode":"Overhead", + "IterationStage":"Jitting", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":1, + "Nanoseconds":161200 + },{ + "IterationMode":"Workload", + "IterationStage":"Jitting", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":1, + "Nanoseconds":541500 + },{ + "IterationMode":"Overhead", + "IterationStage":"Jitting", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":16, + "Nanoseconds":189800 + },{ + "IterationMode":"Workload", + "IterationStage":"Jitting", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":16, + "Nanoseconds":165500 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":16, + "Nanoseconds":1500 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":32, + "Nanoseconds":1800 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":64, + "Nanoseconds":4000 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":128, + "Nanoseconds":10700 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":256, + "Nanoseconds":20300 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":512, + "Nanoseconds":11900 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":1024, + "Nanoseconds":49600 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":2048, + "Nanoseconds":116500 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":9, + "Operations":4096, + "Nanoseconds":179600 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":10, + "Operations":8192, + "Nanoseconds":238000 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":11, + "Operations":16384, + "Nanoseconds":559400 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":12, + "Operations":32768, + "Nanoseconds":1223100 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":13, + "Operations":65536, + "Nanoseconds":2004700 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":14, + "Operations":131072, + "Nanoseconds":3700200 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":15, + "Operations":262144, + "Nanoseconds":6672600 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":16, + "Operations":524288, + "Nanoseconds":11495600 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":17, + "Operations":1048576, + "Nanoseconds":20119100 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":18, + "Operations":2097152, + "Nanoseconds":41679300 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":19, + "Operations":4194304, + "Nanoseconds":80137100 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":20, + "Operations":8388608, + "Nanoseconds":143740200 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":21, + "Operations":16777216, + "Nanoseconds":259876600 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":22, + "Operations":33554432, + "Nanoseconds":519629100 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":33554432, + "Nanoseconds":76198800 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":33554432, + "Nanoseconds":76265500 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":33554432, + "Nanoseconds":72376200 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":33554432, + "Nanoseconds":51496800 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":33554432, + "Nanoseconds":52360500 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":33554432, + "Nanoseconds":51853300 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":33554432, + "Nanoseconds":54868600 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":33554432, + "Nanoseconds":53515500 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":33554432, + "Nanoseconds":51949800 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":33554432, + "Nanoseconds":51684500 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":33554432, + "Nanoseconds":51801900 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":33554432, + "Nanoseconds":52019500 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":33554432, + "Nanoseconds":52952500 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":33554432, + "Nanoseconds":53183500 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":9, + "Operations":33554432, + "Nanoseconds":52425000 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":10, + "Operations":33554432, + "Nanoseconds":51427000 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":11, + "Operations":33554432, + "Nanoseconds":51622000 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":12, + "Operations":33554432, + "Nanoseconds":52606700 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":13, + "Operations":33554432, + "Nanoseconds":52420800 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":14, + "Operations":33554432, + "Nanoseconds":53629800 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":15, + "Operations":33554432, + "Nanoseconds":51473900 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":33554432, + "Nanoseconds":522643400 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":33554432, + "Nanoseconds":532803100 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":33554432, + "Nanoseconds":549603800 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":33554432, + "Nanoseconds":557198900 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":33554432, + "Nanoseconds":551633600 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":33554432, + "Nanoseconds":522478300 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":33554432, + "Nanoseconds":530028900 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":33554432, + "Nanoseconds":522122900 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":33554432, + "Nanoseconds":542140200 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":33554432, + "Nanoseconds":565900000 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":33554432, + "Nanoseconds":560864600 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":33554432, + "Nanoseconds":548719900 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":33554432, + "Nanoseconds":552665200 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":33554432, + "Nanoseconds":552793300 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":33554432, + "Nanoseconds":566267300 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":33554432, + "Nanoseconds":562811500 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":9, + "Operations":33554432, + "Nanoseconds":560423600 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":10, + "Operations":33554432, + "Nanoseconds":568909400 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":11, + "Operations":33554432, + "Nanoseconds":578035300 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":12, + "Operations":33554432, + "Nanoseconds":565654200 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":13, + "Operations":33554432, + "Nanoseconds":580391400 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":14, + "Operations":33554432, + "Nanoseconds":556006300 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":15, + "Operations":33554432, + "Nanoseconds":552331000 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":16, + "Operations":33554432, + "Nanoseconds":544947700 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":33554432, + "Nanoseconds":489719400 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":33554432, + "Nanoseconds":513479200 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":33554432, + "Nanoseconds":508443800 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":33554432, + "Nanoseconds":496299100 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":33554432, + "Nanoseconds":500244400 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":33554432, + "Nanoseconds":500372500 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":33554432, + "Nanoseconds":513846500 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":33554432, + "Nanoseconds":510390700 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":9, + "Operations":33554432, + "Nanoseconds":508002800 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":10, + "Operations":33554432, + "Nanoseconds":516488600 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":11, + "Operations":33554432, + "Nanoseconds":525614500 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":12, + "Operations":33554432, + "Nanoseconds":513233400 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":13, + "Operations":33554432, + "Nanoseconds":527970600 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":14, + "Operations":33554432, + "Nanoseconds":503585500 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":15, + "Operations":33554432, + "Nanoseconds":499910200 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":16, + "Operations":33554432, + "Nanoseconds":492526900 + } + ], + "Metrics":[ + { + "Value":0.0057220458984375, + "Descriptor":{ + "Id":"Gen0Collects", + "DisplayName":"Gen0", + "Legend":"GC Generation 0 collects per 1000 operations", + "NumberFormat":"#0.0000", + "UnitType":0, + "Unit":"Count", + "TheGreaterTheBetter":false, + "PriorityInCategory":0 + } + },{ + "Value":0, + "Descriptor":{ + "Id":"Gen1Collects", + "DisplayName":"Gen1", + "Legend":"GC Generation 1 collects per 1000 operations", + "NumberFormat":"#0.0000", + "UnitType":0, + "Unit":"Count", + "TheGreaterTheBetter":false, + "PriorityInCategory":1 + } + },{ + "Value":0, + "Descriptor":{ + "Id":"Gen2Collects", + "DisplayName":"Gen2", + "Legend":"GC Generation 2 collects per 1000 operations", + "NumberFormat":"#0.0000", + "UnitType":0, + "Unit":"Count", + "TheGreaterTheBetter":false, + "PriorityInCategory":2 + } + },{ + "Value":48, + "Descriptor":{ + "Id":"Allocated Memory", + "DisplayName":"Allocated", + "Legend":"Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)", + "NumberFormat":"0.##", + "UnitType":2, + "Unit":"B", + "TheGreaterTheBetter":false, + "PriorityInCategory":3 + } + } + ] + },{ + "DisplayInfo":"Benchmark.StringJoin: DefaultJob", + "Namespace":null, + "Type":"Benchmark", + "Method":"StringJoin", + "MethodTitle":"StringJoin", + "Parameters":"", + "FullName":"Benchmark.StringJoin", + "HardwareIntrinsics":"AVX2,AES,BMI1,BMI2,FMA,LZCNT,PCLMUL,POPCNT VectorSize=256", + "Statistics":{ + "OriginalValues":[ + 15.501916408538818,16.114601492881775,16.07182025909424,16.36185050010681,17.538052797317505,17.012304067611694,17.042624950408936,16.862303018569946,16.77825152873993,17.028969526290894,16.15769863128662,16.337063908576965,16.68049991130829,16.3478821516037,16.364336013793945,16.076770424842834,16.7138010263443,16.246002912521362,16.15976095199585,16.276413202285767,16.067761182785034,16.422995924949646,15.254256129264832,15.12957215309143,15.222573280334473,15.206742286682129,15.154889225959778,15.14635980129242,15.306326746940613,15.184822678565979,16.883638501167297,16.240841150283813,15.519979596138,15.412932634353638,15.11600911617279,15.241014957427979,15.479454398155212,15.161806344985962,15.27681052684784,15.160945057868958,15.422266721725464,15.628841519355774,15.16978144645691,15.205875039100647,15.241754055023193,15.171873569488525,15.402451157569885,15.385153889656067 + ], + "N":48, + "Min":15.11600911617279, + "LowerFence":13.56394998729229, + "Q1":15.236404538154602, + "Median":15.574410557746887, + "Mean":15.872721932828426, + "Q3":16.351374238729477, + "UpperFence":18.02382878959179, + "Max":17.538052797317505, + "InterquartileRange":1.1149697005748749, + "LowerOutliers":[ + + ], + "UpperOutliers":[ + + ], + "AllOutliers":[ + + ], + "StandardError":0.0990135629034302, + "Variance":0.4705769106639134, + "StandardDeviation":0.6859860863486325, + "Skewness":0.5203043366081913, + "Kurtosis":1.9708451758282963, + "ConfidenceInterval":{ + "N":48, + "Mean":15.872721932828426, + "StandardError":0.0990135629034302, + "Level":12, + "Margin":0.3475278314048107, + "Lower":15.525194101423615, + "Upper":16.220249764233237 + }, + "Percentiles":{ + "P0":15.11600911617279, + "P25":15.236404538154602, + "P50":15.574410557746887, + "P67":16.243370413780212, + "P80":16.399531960487366, + "P85":16.7121359705925, + "P90":16.86870366334915, + "P95":17.023136615753174, + "P100":17.538052797317505 + } + }, + "Memory":{ + "Gen0Collections":192, + "Gen1Collections":0, + "Gen2Collections":0, + "TotalOperations":33554432, + "BytesAllocatedPerOperation":48 + }, + "Measurements":[ + { + "IterationMode":"Overhead", + "IterationStage":"Jitting", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":1, + "Nanoseconds":179700 + },{ + "IterationMode":"Workload", + "IterationStage":"Jitting", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":1, + "Nanoseconds":888700 + },{ + "IterationMode":"Overhead", + "IterationStage":"Jitting", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":16, + "Nanoseconds":175200 + },{ + "IterationMode":"Workload", + "IterationStage":"Jitting", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":16, + "Nanoseconds":214100 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":16, + "Nanoseconds":4600 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":32, + "Nanoseconds":3000 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":64, + "Nanoseconds":3200 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":128, + "Nanoseconds":14400 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":256, + "Nanoseconds":24600 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":512, + "Nanoseconds":29700 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":1024, + "Nanoseconds":61800 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":2048, + "Nanoseconds":140100 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":9, + "Operations":4096, + "Nanoseconds":226800 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":10, + "Operations":8192, + "Nanoseconds":310600 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":11, + "Operations":16384, + "Nanoseconds":698000 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":12, + "Operations":32768, + "Nanoseconds":1431400 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":13, + "Operations":65536, + "Nanoseconds":2803000 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":14, + "Operations":131072, + "Nanoseconds":5617700 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":15, + "Operations":262144, + "Nanoseconds":9610400 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":16, + "Operations":524288, + "Nanoseconds":16870900 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":17, + "Operations":1048576, + "Nanoseconds":32289500 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":18, + "Operations":2097152, + "Nanoseconds":66903000 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":19, + "Operations":4194304, + "Nanoseconds":116192300 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":20, + "Operations":8388608, + "Nanoseconds":141950200 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":21, + "Operations":16777216, + "Nanoseconds":282862600 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":22, + "Operations":33554432, + "Nanoseconds":560015400 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":33554432, + "Nanoseconds":76163700 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":33554432, + "Nanoseconds":76987600 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":33554432, + "Nanoseconds":70440100 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":33554432, + "Nanoseconds":50970800 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":33554432, + "Nanoseconds":50780900 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":33554432, + "Nanoseconds":50919000 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":33554432, + "Nanoseconds":50964900 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":33554432, + "Nanoseconds":50895000 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":33554432, + "Nanoseconds":51114700 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":33554432, + "Nanoseconds":50962500 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":33554432, + "Nanoseconds":51834300 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":33554432, + "Nanoseconds":51913000 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":33554432, + "Nanoseconds":52038500 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":33554432, + "Nanoseconds":50966000 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":33554432, + "Nanoseconds":51346700 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":33554432, + "Nanoseconds":51742900 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":9, + "Operations":33554432, + "Nanoseconds":51593600 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":10, + "Operations":33554432, + "Nanoseconds":54250700 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":11, + "Operations":33554432, + "Nanoseconds":50924600 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":12, + "Operations":33554432, + "Nanoseconds":52571900 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":13, + "Operations":33554432, + "Nanoseconds":51063000 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":14, + "Operations":33554432, + "Nanoseconds":50950200 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":15, + "Operations":33554432, + "Nanoseconds":51161900 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":33554432, + "Nanoseconds":582516800 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":33554432, + "Nanoseconds":567267300 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":33554432, + "Nanoseconds":561705900 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":33554432, + "Nanoseconds":563927800 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":33554432, + "Nanoseconds":561105700 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":33554432, + "Nanoseconds":563565500 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":33554432, + "Nanoseconds":561026500 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":33554432, + "Nanoseconds":571504700 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":33554432, + "Nanoseconds":592063000 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":33554432, + "Nanoseconds":590627500 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":33554432, + "Nanoseconds":600359300 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":33554432, + "Nanoseconds":639826100 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":33554432, + "Nanoseconds":622184900 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":33554432, + "Nanoseconds":623202300 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":33554432, + "Nanoseconds":617151700 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":9, + "Operations":33554432, + "Nanoseconds":614331400 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":10, + "Operations":33554432, + "Nanoseconds":622744100 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":11, + "Operations":33554432, + "Nanoseconds":593509100 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":12, + "Operations":33554432, + "Nanoseconds":599527600 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":13, + "Operations":33554432, + "Nanoseconds":611051400 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":14, + "Operations":33554432, + "Nanoseconds":599890600 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":15, + "Operations":33554432, + "Nanoseconds":600442700 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":16, + "Operations":33554432, + "Nanoseconds":590793600 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":17, + "Operations":33554432, + "Nanoseconds":657663100 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":18, + "Operations":33554432, + "Nanoseconds":612168800 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":19, + "Operations":33554432, + "Nanoseconds":596472100 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":20, + "Operations":33554432, + "Nanoseconds":593578300 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":21, + "Operations":33554432, + "Nanoseconds":597492500 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":22, + "Operations":33554432, + "Nanoseconds":590491300 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":23, + "Operations":33554432, + "Nanoseconds":602411000 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":24, + "Operations":33554432, + "Nanoseconds":563194600 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":25, + "Operations":33554432, + "Nanoseconds":559010900 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":26, + "Operations":33554432, + "Nanoseconds":562131500 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":27, + "Operations":33554432, + "Nanoseconds":561600300 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":28, + "Operations":33554432, + "Nanoseconds":559860400 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":29, + "Operations":33554432, + "Nanoseconds":559574200 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":30, + "Operations":33554432, + "Nanoseconds":564941800 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":31, + "Operations":33554432, + "Nanoseconds":560864800 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":32, + "Operations":33554432, + "Nanoseconds":617867600 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":33, + "Operations":33554432, + "Nanoseconds":596298900 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":34, + "Operations":33554432, + "Nanoseconds":572110800 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":35, + "Operations":33554432, + "Nanoseconds":568518900 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":36, + "Operations":33554432, + "Nanoseconds":558555800 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":37, + "Operations":33554432, + "Nanoseconds":562750300 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":38, + "Operations":33554432, + "Nanoseconds":570751000 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":39, + "Operations":33554432, + "Nanoseconds":560092500 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":40, + "Operations":33554432, + "Nanoseconds":563951400 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":41, + "Operations":33554432, + "Nanoseconds":560063600 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":42, + "Operations":33554432, + "Nanoseconds":568832100 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":43, + "Operations":33554432, + "Nanoseconds":575763600 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":44, + "Operations":33554432, + "Nanoseconds":560360100 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":45, + "Operations":33554432, + "Nanoseconds":561571200 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":46, + "Operations":33554432, + "Nanoseconds":562775100 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":47, + "Operations":33554432, + "Nanoseconds":560430300 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":48, + "Operations":33554432, + "Nanoseconds":568167200 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":49, + "Operations":33554432, + "Nanoseconds":567586800 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":33554432, + "Nanoseconds":520158000 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":33554432, + "Nanoseconds":540716300 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":33554432, + "Nanoseconds":539280800 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":33554432, + "Nanoseconds":549012600 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":33554432, + "Nanoseconds":588479400 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":33554432, + "Nanoseconds":570838200 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":33554432, + "Nanoseconds":571855600 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":33554432, + "Nanoseconds":565805000 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":9, + "Operations":33554432, + "Nanoseconds":562984700 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":10, + "Operations":33554432, + "Nanoseconds":571397400 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":11, + "Operations":33554432, + "Nanoseconds":542162400 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":12, + "Operations":33554432, + "Nanoseconds":548180900 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":13, + "Operations":33554432, + "Nanoseconds":559704700 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":14, + "Operations":33554432, + "Nanoseconds":548543900 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":15, + "Operations":33554432, + "Nanoseconds":549096000 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":16, + "Operations":33554432, + "Nanoseconds":539446900 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":17, + "Operations":33554432, + "Nanoseconds":560822100 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":18, + "Operations":33554432, + "Nanoseconds":545125400 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":19, + "Operations":33554432, + "Nanoseconds":542231600 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":20, + "Operations":33554432, + "Nanoseconds":546145800 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":21, + "Operations":33554432, + "Nanoseconds":539144600 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":22, + "Operations":33554432, + "Nanoseconds":551064300 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":23, + "Operations":33554432, + "Nanoseconds":511847900 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":24, + "Operations":33554432, + "Nanoseconds":507664200 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":25, + "Operations":33554432, + "Nanoseconds":510784800 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":26, + "Operations":33554432, + "Nanoseconds":510253600 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":27, + "Operations":33554432, + "Nanoseconds":508513700 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":28, + "Operations":33554432, + "Nanoseconds":508227500 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":29, + "Operations":33554432, + "Nanoseconds":513595100 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":30, + "Operations":33554432, + "Nanoseconds":509518100 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":31, + "Operations":33554432, + "Nanoseconds":566520900 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":32, + "Operations":33554432, + "Nanoseconds":544952200 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":33, + "Operations":33554432, + "Nanoseconds":520764100 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":34, + "Operations":33554432, + "Nanoseconds":517172200 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":35, + "Operations":33554432, + "Nanoseconds":507209100 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":36, + "Operations":33554432, + "Nanoseconds":511403600 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":37, + "Operations":33554432, + "Nanoseconds":519404300 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":38, + "Operations":33554432, + "Nanoseconds":508745800 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":39, + "Operations":33554432, + "Nanoseconds":512604700 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":40, + "Operations":33554432, + "Nanoseconds":508716900 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":41, + "Operations":33554432, + "Nanoseconds":517485400 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":42, + "Operations":33554432, + "Nanoseconds":524416900 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":43, + "Operations":33554432, + "Nanoseconds":509013400 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":44, + "Operations":33554432, + "Nanoseconds":510224500 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":45, + "Operations":33554432, + "Nanoseconds":511428400 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":46, + "Operations":33554432, + "Nanoseconds":509083600 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":47, + "Operations":33554432, + "Nanoseconds":516820500 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":48, + "Operations":33554432, + "Nanoseconds":516240100 + } + ], + "Metrics":[ + { + "Value":0.0057220458984375, + "Descriptor":{ + "Id":"Gen0Collects", + "DisplayName":"Gen0", + "Legend":"GC Generation 0 collects per 1000 operations", + "NumberFormat":"#0.0000", + "UnitType":0, + "Unit":"Count", + "TheGreaterTheBetter":false, + "PriorityInCategory":0 + } + },{ + "Value":0, + "Descriptor":{ + "Id":"Gen1Collects", + "DisplayName":"Gen1", + "Legend":"GC Generation 1 collects per 1000 operations", + "NumberFormat":"#0.0000", + "UnitType":0, + "Unit":"Count", + "TheGreaterTheBetter":false, + "PriorityInCategory":1 + } + },{ + "Value":0, + "Descriptor":{ + "Id":"Gen2Collects", + "DisplayName":"Gen2", + "Legend":"GC Generation 2 collects per 1000 operations", + "NumberFormat":"#0.0000", + "UnitType":0, + "Unit":"Count", + "TheGreaterTheBetter":false, + "PriorityInCategory":2 + } + },{ + "Value":48, + "Descriptor":{ + "Id":"Allocated Memory", + "DisplayName":"Allocated", + "Legend":"Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)", + "NumberFormat":"0.##", + "UnitType":2, + "Unit":"B", + "TheGreaterTheBetter":false, + "PriorityInCategory":3 + } + } + ] + } + ] +} diff --git a/tests/test-data/report-02/Benchmark-report-github.md b/tests/test-data/report-02/Benchmark-report-github.md new file mode 100644 index 0000000..f119b64 --- /dev/null +++ b/tests/test-data/report-02/Benchmark-report-github.md @@ -0,0 +1,14 @@ +``` + +BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3323) +AMD Ryzen 5 3600X, 1 CPU, 12 logical and 6 physical cores +.NET SDK 10.0.100-preview.1.25120.13 + [Host] : .NET 9.0.2 (9.0.225.6610), X64 RyuJIT AVX2 + DefaultJob : .NET 9.0.2 (9.0.225.6610), X64 RyuJIT AVX2 + + +``` +| Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Rank | Gen0 | Allocated | Alloc Ratio | +|------------- |---------:|---------:|---------:|---------:|------:|--------:|-----:|-------:|----------:|------------:| +| StringConcat | 15.12 ns | 0.331 ns | 0.325 ns | 15.15 ns | 0.95 | 0.04 | I | 0.0057 | 48 B | 1.00 | +| StringJoin | 15.87 ns | 0.348 ns | 0.686 ns | 15.57 ns | 1.00 | 0.06 | I | 0.0057 | 48 B | 1.00 | diff --git a/tests/test-data/report-02/Benchmark-report.csv b/tests/test-data/report-02/Benchmark-report.csv new file mode 100644 index 0000000..a321bd4 --- /dev/null +++ b/tests/test-data/report-02/Benchmark-report.csv @@ -0,0 +1,3 @@ +Method,Job,AnalyzeLaunchVariance,EvaluateOverhead,MaxAbsoluteError,MaxRelativeError,MinInvokeCount,MinIterationTime,OutlierMode,Affinity,EnvironmentVariables,Jit,LargeAddressAware,Platform,PowerPlanMode,Runtime,AllowVeryLargeObjects,Concurrent,CpuGroups,Force,HeapAffinitizeMask,HeapCount,NoAffinitize,RetainVm,Server,Arguments,BuildConfiguration,Clock,EngineFactory,NuGetReferences,Toolchain,IsMutator,InvocationCount,IterationCount,IterationTime,LaunchCount,MaxIterationCount,MaxWarmupIterationCount,MemoryRandomization,MinIterationCount,MinWarmupIterationCount,RunStrategy,UnrollFactor,WarmupCount,Mean,Error,StdDev,Median,Ratio,RatioSD,Rank,Gen0,Allocated,Alloc Ratio +StringConcat,DefaultJob,False,Default,Default,Default,Default,Default,Default,111111111111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 9.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,15.12 ns,0.331 ns,0.325 ns,15.15 ns,0.95,0.04,I,0.0057,48 B,1.00 +StringJoin,DefaultJob,False,Default,Default,Default,Default,Default,Default,111111111111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 9.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,15.87 ns,0.348 ns,0.686 ns,15.57 ns,1.00,0.06,I,0.0057,48 B,1.00 diff --git a/tests/test-data/report-02/Benchmark-report.html b/tests/test-data/report-02/Benchmark-report.html new file mode 100644 index 0000000..43a50e3 --- /dev/null +++ b/tests/test-data/report-02/Benchmark-report.html @@ -0,0 +1,31 @@ + + + + +Benchmark-20250308-230345 + + + + +

+BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3323)
+AMD Ryzen 5 3600X, 1 CPU, 12 logical and 6 physical cores
+.NET SDK 10.0.100-preview.1.25120.13
+  [Host]     : .NET 9.0.2 (9.0.225.6610), X64 RyuJIT AVX2
+  DefaultJob : .NET 9.0.2 (9.0.225.6610), X64 RyuJIT AVX2
+
+
+ + + + + + +
MethodMeanErrorStdDevMedianRatioRatioSDRankGen0AllocatedAlloc Ratio
StringConcat15.12 ns0.331 ns0.325 ns15.15 ns0.950.04I0.005748 B1.00
StringJoin15.87 ns0.348 ns0.686 ns15.57 ns1.000.06I0.005748 B1.00
+ + diff --git a/tests/test-data/report-10/Benchmark-report-full.json b/tests/test-data/report-10/Benchmark-report-full.json new file mode 100644 index 0000000..6732053 --- /dev/null +++ b/tests/test-data/report-10/Benchmark-report-full.json @@ -0,0 +1,1943 @@ +{ + "Title":"Benchmark-20250308-230345", + "HostEnvironmentInfo":{ + "BenchmarkDotNetCaption":"BenchmarkDotNet", + "BenchmarkDotNetVersion":"0.14.0", + "OsVersion":"Windows 11 (10.0.26100.3323)", + "ProcessorName":"AMD Ryzen 5 3600X", + "PhysicalProcessorCount":1, + "PhysicalCoreCount":6, + "LogicalCoreCount":12, + "RuntimeVersion":".NET 9.0.2 (9.0.225.6610)", + "Architecture":"X64", + "HasAttachedDebugger":false, + "HasRyuJit":true, + "Configuration":"RELEASE", + "DotNetCliVersion":"10.0.100-preview.1.25120.13", + "ChronometerFrequency":{ + "Hertz":10000000 + }, + "HardwareTimerKind":"Unknown" + }, + "Benchmarks":[ + { + "DisplayInfo":"Benchmark.StringConcat: DefaultJob", + "Namespace":null, + "Type":"Benchmark", + "Method":"MethodTest", + "MethodTitle":"MethodTest", + "Parameters":"", + "FullName":"Benchmark.MethodTest", + "HardwareIntrinsics":"AVX2,AES,BMI1,BMI2,FMA,LZCNT,PCLMUL,POPCNT VectorSize=256", + "Statistics":{ + "OriginalValues":[ + 14.594775438308716,15.302872657775879,15.152806043624878,14.790865778923035,14.908444881439209,14.912262558937073,15.31381905078888,15.210828185081482,15.139663219451904,15.39255976676941,15.664532780647278,15.295547246932983,15.734750032424927,15.008017420768738,14.898484945297241,14.67844545841217 + ], + "N":16, + "Min":14.594775438308716, + "LowerFence":14.306473359465599, + "Q1":14.905954897403717, + "Median":15.146234631538391, + "Mean":15.124917216598988, + "Q3":15.305609256029129, + "UpperFence":15.905090793967247, + "Max":15.734750032424927, + "InterquartileRange":0.399654358625412, + "LowerOutliers":[ + + ], + "UpperOutliers":[ + + ], + "AllOutliers":[ + + ], + "StandardError":0.08135706381812795, + "Variance":0.1059035493297111, + "StandardDeviation":0.3254282552725118, + "Skewness":0.2046662480487116, + "Kurtosis":2.0598553132936965, + "ConfidenceInterval":{ + "N":16, + "Mean":15.124917216598988, + "StandardError":0.08135706381812795, + "Level":12, + "Margin":0.33134821778710166, + "Lower":14.793568998811885, + "Upper":15.45626543438609 + }, + "Percentiles":{ + "P0":14.594775438308716, + "P25":14.905954897403717, + "P50":15.146234631538391, + "P67":15.295913517475128, + "P80":15.31381905078888, + "P85":15.372874587774277, + "P90":15.528546273708344, + "P95":15.68208709359169, + "P100":15.734750032424927 + } + }, + "Memory":{ + "Gen0Collections":192, + "Gen1Collections":0, + "Gen2Collections":0, + "TotalOperations":33554432, + "BytesAllocatedPerOperation":48 + }, + "Measurements":[ + { + "IterationMode":"Overhead", + "IterationStage":"Jitting", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":1, + "Nanoseconds":161200 + },{ + "IterationMode":"Workload", + "IterationStage":"Jitting", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":1, + "Nanoseconds":541500 + },{ + "IterationMode":"Overhead", + "IterationStage":"Jitting", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":16, + "Nanoseconds":189800 + },{ + "IterationMode":"Workload", + "IterationStage":"Jitting", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":16, + "Nanoseconds":165500 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":16, + "Nanoseconds":1500 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":32, + "Nanoseconds":1800 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":64, + "Nanoseconds":4000 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":128, + "Nanoseconds":10700 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":256, + "Nanoseconds":20300 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":512, + "Nanoseconds":11900 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":1024, + "Nanoseconds":49600 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":2048, + "Nanoseconds":116500 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":9, + "Operations":4096, + "Nanoseconds":179600 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":10, + "Operations":8192, + "Nanoseconds":238000 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":11, + "Operations":16384, + "Nanoseconds":559400 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":12, + "Operations":32768, + "Nanoseconds":1223100 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":13, + "Operations":65536, + "Nanoseconds":2004700 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":14, + "Operations":131072, + "Nanoseconds":3700200 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":15, + "Operations":262144, + "Nanoseconds":6672600 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":16, + "Operations":524288, + "Nanoseconds":11495600 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":17, + "Operations":1048576, + "Nanoseconds":20119100 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":18, + "Operations":2097152, + "Nanoseconds":41679300 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":19, + "Operations":4194304, + "Nanoseconds":80137100 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":20, + "Operations":8388608, + "Nanoseconds":143740200 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":21, + "Operations":16777216, + "Nanoseconds":259876600 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":22, + "Operations":33554432, + "Nanoseconds":519629100 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":33554432, + "Nanoseconds":76198800 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":33554432, + "Nanoseconds":76265500 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":33554432, + "Nanoseconds":72376200 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":33554432, + "Nanoseconds":51496800 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":33554432, + "Nanoseconds":52360500 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":33554432, + "Nanoseconds":51853300 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":33554432, + "Nanoseconds":54868600 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":33554432, + "Nanoseconds":53515500 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":33554432, + "Nanoseconds":51949800 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":33554432, + "Nanoseconds":51684500 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":33554432, + "Nanoseconds":51801900 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":33554432, + "Nanoseconds":52019500 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":33554432, + "Nanoseconds":52952500 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":33554432, + "Nanoseconds":53183500 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":9, + "Operations":33554432, + "Nanoseconds":52425000 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":10, + "Operations":33554432, + "Nanoseconds":51427000 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":11, + "Operations":33554432, + "Nanoseconds":51622000 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":12, + "Operations":33554432, + "Nanoseconds":52606700 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":13, + "Operations":33554432, + "Nanoseconds":52420800 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":14, + "Operations":33554432, + "Nanoseconds":53629800 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":15, + "Operations":33554432, + "Nanoseconds":51473900 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":33554432, + "Nanoseconds":522643400 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":33554432, + "Nanoseconds":532803100 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":33554432, + "Nanoseconds":549603800 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":33554432, + "Nanoseconds":557198900 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":33554432, + "Nanoseconds":551633600 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":33554432, + "Nanoseconds":522478300 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":33554432, + "Nanoseconds":530028900 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":33554432, + "Nanoseconds":522122900 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":33554432, + "Nanoseconds":542140200 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":33554432, + "Nanoseconds":565900000 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":33554432, + "Nanoseconds":560864600 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":33554432, + "Nanoseconds":548719900 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":33554432, + "Nanoseconds":552665200 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":33554432, + "Nanoseconds":552793300 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":33554432, + "Nanoseconds":566267300 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":33554432, + "Nanoseconds":562811500 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":9, + "Operations":33554432, + "Nanoseconds":560423600 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":10, + "Operations":33554432, + "Nanoseconds":568909400 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":11, + "Operations":33554432, + "Nanoseconds":578035300 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":12, + "Operations":33554432, + "Nanoseconds":565654200 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":13, + "Operations":33554432, + "Nanoseconds":580391400 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":14, + "Operations":33554432, + "Nanoseconds":556006300 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":15, + "Operations":33554432, + "Nanoseconds":552331000 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":16, + "Operations":33554432, + "Nanoseconds":544947700 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":33554432, + "Nanoseconds":489719400 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":33554432, + "Nanoseconds":513479200 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":33554432, + "Nanoseconds":508443800 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":33554432, + "Nanoseconds":496299100 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":33554432, + "Nanoseconds":500244400 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":33554432, + "Nanoseconds":500372500 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":33554432, + "Nanoseconds":513846500 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":33554432, + "Nanoseconds":510390700 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":9, + "Operations":33554432, + "Nanoseconds":508002800 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":10, + "Operations":33554432, + "Nanoseconds":516488600 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":11, + "Operations":33554432, + "Nanoseconds":525614500 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":12, + "Operations":33554432, + "Nanoseconds":513233400 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":13, + "Operations":33554432, + "Nanoseconds":527970600 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":14, + "Operations":33554432, + "Nanoseconds":503585500 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":15, + "Operations":33554432, + "Nanoseconds":499910200 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":16, + "Operations":33554432, + "Nanoseconds":492526900 + } + ], + "Metrics":[ + { + "Value":0.0057220458984375, + "Descriptor":{ + "Id":"Gen0Collects", + "DisplayName":"Gen0", + "Legend":"GC Generation 0 collects per 1000 operations", + "NumberFormat":"#0.0000", + "UnitType":0, + "Unit":"Count", + "TheGreaterTheBetter":false, + "PriorityInCategory":0 + } + },{ + "Value":0, + "Descriptor":{ + "Id":"Gen1Collects", + "DisplayName":"Gen1", + "Legend":"GC Generation 1 collects per 1000 operations", + "NumberFormat":"#0.0000", + "UnitType":0, + "Unit":"Count", + "TheGreaterTheBetter":false, + "PriorityInCategory":1 + } + },{ + "Value":0, + "Descriptor":{ + "Id":"Gen2Collects", + "DisplayName":"Gen2", + "Legend":"GC Generation 2 collects per 1000 operations", + "NumberFormat":"#0.0000", + "UnitType":0, + "Unit":"Count", + "TheGreaterTheBetter":false, + "PriorityInCategory":2 + } + },{ + "Value":48, + "Descriptor":{ + "Id":"Allocated Memory", + "DisplayName":"Allocated", + "Legend":"Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)", + "NumberFormat":"0.##", + "UnitType":2, + "Unit":"B", + "TheGreaterTheBetter":false, + "PriorityInCategory":3 + } + } + ] + },{ + "DisplayInfo":"Benchmark.StringJoin: DefaultJob", + "Namespace":null, + "Type":"Benchmark", + "Method":"StringJoin", + "MethodTitle":"StringJoin", + "Parameters":"", + "FullName":"Benchmark.StringJoin", + "HardwareIntrinsics":"AVX2,AES,BMI1,BMI2,FMA,LZCNT,PCLMUL,POPCNT VectorSize=256", + "Statistics":{ + "OriginalValues":[ + 15.501916408538818,16.114601492881775,16.07182025909424,16.36185050010681,17.538052797317505,17.012304067611694,17.042624950408936,16.862303018569946,16.77825152873993,17.028969526290894,16.15769863128662,16.337063908576965,16.68049991130829,16.3478821516037,16.364336013793945,16.076770424842834,16.7138010263443,16.246002912521362,16.15976095199585,16.276413202285767,16.067761182785034,16.422995924949646,15.254256129264832,15.12957215309143,15.222573280334473,15.206742286682129,15.154889225959778,15.14635980129242,15.306326746940613,15.184822678565979,16.883638501167297,16.240841150283813,15.519979596138,15.412932634353638,15.11600911617279,15.241014957427979,15.479454398155212,15.161806344985962,15.27681052684784,15.160945057868958,15.422266721725464,15.628841519355774,15.16978144645691,15.205875039100647,15.241754055023193,15.171873569488525,15.402451157569885,15.385153889656067 + ], + "N":48, + "Min":15.11600911617279, + "LowerFence":13.56394998729229, + "Q1":15.236404538154602, + "Median":15.574410557746887, + "Mean":15.872721932828426, + "Q3":16.351374238729477, + "UpperFence":18.02382878959179, + "Max":17.538052797317505, + "InterquartileRange":1.1149697005748749, + "LowerOutliers":[ + + ], + "UpperOutliers":[ + + ], + "AllOutliers":[ + + ], + "StandardError":0.0990135629034302, + "Variance":0.4705769106639134, + "StandardDeviation":0.6859860863486325, + "Skewness":0.5203043366081913, + "Kurtosis":1.9708451758282963, + "ConfidenceInterval":{ + "N":48, + "Mean":15.872721932828426, + "StandardError":0.0990135629034302, + "Level":12, + "Margin":0.3475278314048107, + "Lower":15.525194101423615, + "Upper":16.220249764233237 + }, + "Percentiles":{ + "P0":15.11600911617279, + "P25":15.236404538154602, + "P50":15.574410557746887, + "P67":16.243370413780212, + "P80":16.399531960487366, + "P85":16.7121359705925, + "P90":16.86870366334915, + "P95":17.023136615753174, + "P100":17.538052797317505 + } + }, + "Memory":{ + "Gen0Collections":192, + "Gen1Collections":0, + "Gen2Collections":0, + "TotalOperations":33554432, + "BytesAllocatedPerOperation":48 + }, + "Measurements":[ + { + "IterationMode":"Overhead", + "IterationStage":"Jitting", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":1, + "Nanoseconds":179700 + },{ + "IterationMode":"Workload", + "IterationStage":"Jitting", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":1, + "Nanoseconds":888700 + },{ + "IterationMode":"Overhead", + "IterationStage":"Jitting", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":16, + "Nanoseconds":175200 + },{ + "IterationMode":"Workload", + "IterationStage":"Jitting", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":16, + "Nanoseconds":214100 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":16, + "Nanoseconds":4600 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":32, + "Nanoseconds":3000 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":64, + "Nanoseconds":3200 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":128, + "Nanoseconds":14400 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":256, + "Nanoseconds":24600 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":512, + "Nanoseconds":29700 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":1024, + "Nanoseconds":61800 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":2048, + "Nanoseconds":140100 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":9, + "Operations":4096, + "Nanoseconds":226800 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":10, + "Operations":8192, + "Nanoseconds":310600 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":11, + "Operations":16384, + "Nanoseconds":698000 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":12, + "Operations":32768, + "Nanoseconds":1431400 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":13, + "Operations":65536, + "Nanoseconds":2803000 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":14, + "Operations":131072, + "Nanoseconds":5617700 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":15, + "Operations":262144, + "Nanoseconds":9610400 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":16, + "Operations":524288, + "Nanoseconds":16870900 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":17, + "Operations":1048576, + "Nanoseconds":32289500 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":18, + "Operations":2097152, + "Nanoseconds":66903000 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":19, + "Operations":4194304, + "Nanoseconds":116192300 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":20, + "Operations":8388608, + "Nanoseconds":141950200 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":21, + "Operations":16777216, + "Nanoseconds":282862600 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":22, + "Operations":33554432, + "Nanoseconds":560015400 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":33554432, + "Nanoseconds":76163700 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":33554432, + "Nanoseconds":76987600 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":33554432, + "Nanoseconds":70440100 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":33554432, + "Nanoseconds":50970800 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":33554432, + "Nanoseconds":50780900 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":33554432, + "Nanoseconds":50919000 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":33554432, + "Nanoseconds":50964900 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":33554432, + "Nanoseconds":50895000 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":33554432, + "Nanoseconds":51114700 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":33554432, + "Nanoseconds":50962500 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":33554432, + "Nanoseconds":51834300 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":33554432, + "Nanoseconds":51913000 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":33554432, + "Nanoseconds":52038500 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":33554432, + "Nanoseconds":50966000 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":33554432, + "Nanoseconds":51346700 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":33554432, + "Nanoseconds":51742900 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":9, + "Operations":33554432, + "Nanoseconds":51593600 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":10, + "Operations":33554432, + "Nanoseconds":54250700 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":11, + "Operations":33554432, + "Nanoseconds":50924600 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":12, + "Operations":33554432, + "Nanoseconds":52571900 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":13, + "Operations":33554432, + "Nanoseconds":51063000 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":14, + "Operations":33554432, + "Nanoseconds":50950200 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":15, + "Operations":33554432, + "Nanoseconds":51161900 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":33554432, + "Nanoseconds":582516800 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":33554432, + "Nanoseconds":567267300 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":33554432, + "Nanoseconds":561705900 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":33554432, + "Nanoseconds":563927800 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":33554432, + "Nanoseconds":561105700 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":33554432, + "Nanoseconds":563565500 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":33554432, + "Nanoseconds":561026500 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":33554432, + "Nanoseconds":571504700 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":33554432, + "Nanoseconds":592063000 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":33554432, + "Nanoseconds":590627500 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":33554432, + "Nanoseconds":600359300 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":33554432, + "Nanoseconds":639826100 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":33554432, + "Nanoseconds":622184900 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":33554432, + "Nanoseconds":623202300 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":33554432, + "Nanoseconds":617151700 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":9, + "Operations":33554432, + "Nanoseconds":614331400 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":10, + "Operations":33554432, + "Nanoseconds":622744100 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":11, + "Operations":33554432, + "Nanoseconds":593509100 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":12, + "Operations":33554432, + "Nanoseconds":599527600 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":13, + "Operations":33554432, + "Nanoseconds":611051400 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":14, + "Operations":33554432, + "Nanoseconds":599890600 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":15, + "Operations":33554432, + "Nanoseconds":600442700 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":16, + "Operations":33554432, + "Nanoseconds":590793600 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":17, + "Operations":33554432, + "Nanoseconds":657663100 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":18, + "Operations":33554432, + "Nanoseconds":612168800 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":19, + "Operations":33554432, + "Nanoseconds":596472100 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":20, + "Operations":33554432, + "Nanoseconds":593578300 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":21, + "Operations":33554432, + "Nanoseconds":597492500 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":22, + "Operations":33554432, + "Nanoseconds":590491300 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":23, + "Operations":33554432, + "Nanoseconds":602411000 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":24, + "Operations":33554432, + "Nanoseconds":563194600 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":25, + "Operations":33554432, + "Nanoseconds":559010900 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":26, + "Operations":33554432, + "Nanoseconds":562131500 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":27, + "Operations":33554432, + "Nanoseconds":561600300 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":28, + "Operations":33554432, + "Nanoseconds":559860400 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":29, + "Operations":33554432, + "Nanoseconds":559574200 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":30, + "Operations":33554432, + "Nanoseconds":564941800 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":31, + "Operations":33554432, + "Nanoseconds":560864800 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":32, + "Operations":33554432, + "Nanoseconds":617867600 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":33, + "Operations":33554432, + "Nanoseconds":596298900 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":34, + "Operations":33554432, + "Nanoseconds":572110800 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":35, + "Operations":33554432, + "Nanoseconds":568518900 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":36, + "Operations":33554432, + "Nanoseconds":558555800 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":37, + "Operations":33554432, + "Nanoseconds":562750300 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":38, + "Operations":33554432, + "Nanoseconds":570751000 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":39, + "Operations":33554432, + "Nanoseconds":560092500 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":40, + "Operations":33554432, + "Nanoseconds":563951400 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":41, + "Operations":33554432, + "Nanoseconds":560063600 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":42, + "Operations":33554432, + "Nanoseconds":568832100 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":43, + "Operations":33554432, + "Nanoseconds":575763600 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":44, + "Operations":33554432, + "Nanoseconds":560360100 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":45, + "Operations":33554432, + "Nanoseconds":561571200 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":46, + "Operations":33554432, + "Nanoseconds":562775100 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":47, + "Operations":33554432, + "Nanoseconds":560430300 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":48, + "Operations":33554432, + "Nanoseconds":568167200 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":49, + "Operations":33554432, + "Nanoseconds":567586800 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":33554432, + "Nanoseconds":520158000 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":33554432, + "Nanoseconds":540716300 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":33554432, + "Nanoseconds":539280800 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":33554432, + "Nanoseconds":549012600 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":33554432, + "Nanoseconds":588479400 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":33554432, + "Nanoseconds":570838200 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":33554432, + "Nanoseconds":571855600 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":33554432, + "Nanoseconds":565805000 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":9, + "Operations":33554432, + "Nanoseconds":562984700 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":10, + "Operations":33554432, + "Nanoseconds":571397400 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":11, + "Operations":33554432, + "Nanoseconds":542162400 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":12, + "Operations":33554432, + "Nanoseconds":548180900 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":13, + "Operations":33554432, + "Nanoseconds":559704700 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":14, + "Operations":33554432, + "Nanoseconds":548543900 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":15, + "Operations":33554432, + "Nanoseconds":549096000 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":16, + "Operations":33554432, + "Nanoseconds":539446900 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":17, + "Operations":33554432, + "Nanoseconds":560822100 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":18, + "Operations":33554432, + "Nanoseconds":545125400 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":19, + "Operations":33554432, + "Nanoseconds":542231600 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":20, + "Operations":33554432, + "Nanoseconds":546145800 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":21, + "Operations":33554432, + "Nanoseconds":539144600 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":22, + "Operations":33554432, + "Nanoseconds":551064300 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":23, + "Operations":33554432, + "Nanoseconds":511847900 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":24, + "Operations":33554432, + "Nanoseconds":507664200 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":25, + "Operations":33554432, + "Nanoseconds":510784800 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":26, + "Operations":33554432, + "Nanoseconds":510253600 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":27, + "Operations":33554432, + "Nanoseconds":508513700 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":28, + "Operations":33554432, + "Nanoseconds":508227500 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":29, + "Operations":33554432, + "Nanoseconds":513595100 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":30, + "Operations":33554432, + "Nanoseconds":509518100 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":31, + "Operations":33554432, + "Nanoseconds":566520900 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":32, + "Operations":33554432, + "Nanoseconds":544952200 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":33, + "Operations":33554432, + "Nanoseconds":520764100 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":34, + "Operations":33554432, + "Nanoseconds":517172200 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":35, + "Operations":33554432, + "Nanoseconds":507209100 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":36, + "Operations":33554432, + "Nanoseconds":511403600 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":37, + "Operations":33554432, + "Nanoseconds":519404300 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":38, + "Operations":33554432, + "Nanoseconds":508745800 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":39, + "Operations":33554432, + "Nanoseconds":512604700 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":40, + "Operations":33554432, + "Nanoseconds":508716900 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":41, + "Operations":33554432, + "Nanoseconds":517485400 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":42, + "Operations":33554432, + "Nanoseconds":524416900 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":43, + "Operations":33554432, + "Nanoseconds":509013400 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":44, + "Operations":33554432, + "Nanoseconds":510224500 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":45, + "Operations":33554432, + "Nanoseconds":511428400 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":46, + "Operations":33554432, + "Nanoseconds":509083600 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":47, + "Operations":33554432, + "Nanoseconds":516820500 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":48, + "Operations":33554432, + "Nanoseconds":516240100 + } + ], + "Metrics":[ + { + "Value":0.0057220458984375, + "Descriptor":{ + "Id":"Gen0Collects", + "DisplayName":"Gen0", + "Legend":"GC Generation 0 collects per 1000 operations", + "NumberFormat":"#0.0000", + "UnitType":0, + "Unit":"Count", + "TheGreaterTheBetter":false, + "PriorityInCategory":0 + } + },{ + "Value":0, + "Descriptor":{ + "Id":"Gen1Collects", + "DisplayName":"Gen1", + "Legend":"GC Generation 1 collects per 1000 operations", + "NumberFormat":"#0.0000", + "UnitType":0, + "Unit":"Count", + "TheGreaterTheBetter":false, + "PriorityInCategory":1 + } + },{ + "Value":0, + "Descriptor":{ + "Id":"Gen2Collects", + "DisplayName":"Gen2", + "Legend":"GC Generation 2 collects per 1000 operations", + "NumberFormat":"#0.0000", + "UnitType":0, + "Unit":"Count", + "TheGreaterTheBetter":false, + "PriorityInCategory":2 + } + },{ + "Value":48, + "Descriptor":{ + "Id":"Allocated Memory", + "DisplayName":"Allocated", + "Legend":"Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)", + "NumberFormat":"0.##", + "UnitType":2, + "Unit":"B", + "TheGreaterTheBetter":false, + "PriorityInCategory":3 + } + } + ] + } + ] +} diff --git a/tests/test-data/report-10/Benchmark-report-github.md b/tests/test-data/report-10/Benchmark-report-github.md new file mode 100644 index 0000000..f119b64 --- /dev/null +++ b/tests/test-data/report-10/Benchmark-report-github.md @@ -0,0 +1,14 @@ +``` + +BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3323) +AMD Ryzen 5 3600X, 1 CPU, 12 logical and 6 physical cores +.NET SDK 10.0.100-preview.1.25120.13 + [Host] : .NET 9.0.2 (9.0.225.6610), X64 RyuJIT AVX2 + DefaultJob : .NET 9.0.2 (9.0.225.6610), X64 RyuJIT AVX2 + + +``` +| Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Rank | Gen0 | Allocated | Alloc Ratio | +|------------- |---------:|---------:|---------:|---------:|------:|--------:|-----:|-------:|----------:|------------:| +| StringConcat | 15.12 ns | 0.331 ns | 0.325 ns | 15.15 ns | 0.95 | 0.04 | I | 0.0057 | 48 B | 1.00 | +| StringJoin | 15.87 ns | 0.348 ns | 0.686 ns | 15.57 ns | 1.00 | 0.06 | I | 0.0057 | 48 B | 1.00 | diff --git a/tests/test-data/report-10/Benchmark-report.csv b/tests/test-data/report-10/Benchmark-report.csv new file mode 100644 index 0000000..a321bd4 --- /dev/null +++ b/tests/test-data/report-10/Benchmark-report.csv @@ -0,0 +1,3 @@ +Method,Job,AnalyzeLaunchVariance,EvaluateOverhead,MaxAbsoluteError,MaxRelativeError,MinInvokeCount,MinIterationTime,OutlierMode,Affinity,EnvironmentVariables,Jit,LargeAddressAware,Platform,PowerPlanMode,Runtime,AllowVeryLargeObjects,Concurrent,CpuGroups,Force,HeapAffinitizeMask,HeapCount,NoAffinitize,RetainVm,Server,Arguments,BuildConfiguration,Clock,EngineFactory,NuGetReferences,Toolchain,IsMutator,InvocationCount,IterationCount,IterationTime,LaunchCount,MaxIterationCount,MaxWarmupIterationCount,MemoryRandomization,MinIterationCount,MinWarmupIterationCount,RunStrategy,UnrollFactor,WarmupCount,Mean,Error,StdDev,Median,Ratio,RatioSD,Rank,Gen0,Allocated,Alloc Ratio +StringConcat,DefaultJob,False,Default,Default,Default,Default,Default,Default,111111111111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 9.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,15.12 ns,0.331 ns,0.325 ns,15.15 ns,0.95,0.04,I,0.0057,48 B,1.00 +StringJoin,DefaultJob,False,Default,Default,Default,Default,Default,Default,111111111111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 9.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,15.87 ns,0.348 ns,0.686 ns,15.57 ns,1.00,0.06,I,0.0057,48 B,1.00 diff --git a/tests/test-data/report-10/Benchmark-report.html b/tests/test-data/report-10/Benchmark-report.html new file mode 100644 index 0000000..43a50e3 --- /dev/null +++ b/tests/test-data/report-10/Benchmark-report.html @@ -0,0 +1,31 @@ + + + + +Benchmark-20250308-230345 + + + + +

+BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3323)
+AMD Ryzen 5 3600X, 1 CPU, 12 logical and 6 physical cores
+.NET SDK 10.0.100-preview.1.25120.13
+  [Host]     : .NET 9.0.2 (9.0.225.6610), X64 RyuJIT AVX2
+  DefaultJob : .NET 9.0.2 (9.0.225.6610), X64 RyuJIT AVX2
+
+
+ + + + + + +
MethodMeanErrorStdDevMedianRatioRatioSDRankGen0AllocatedAlloc Ratio
StringConcat15.12 ns0.331 ns0.325 ns15.15 ns0.950.04I0.005748 B1.00
StringJoin15.87 ns0.348 ns0.686 ns15.57 ns1.000.06I0.005748 B1.00
+ + diff --git a/tests/test-data/report-99/Benchmark-report-full.json b/tests/test-data/report-99/Benchmark-report-full.json new file mode 100644 index 0000000..119fec2 --- /dev/null +++ b/tests/test-data/report-99/Benchmark-report-full.json @@ -0,0 +1,2328 @@ +{ + "Title":"Benchmark-20250309-000554", + "HostEnvironmentInfo":{ + "BenchmarkDotNetCaption":"BenchmarkDotNet", + "BenchmarkDotNetVersion":"0.14.0", + "OsVersion":"Windows 10 (10.0.26100.3323)", + "ProcessorName":"AMD Ryzen 5 3600X", + "PhysicalProcessorCount":1, + "PhysicalCoreCount":6, + "LogicalCoreCount":12, + "RuntimeVersion":".NET 9.0.2 (9.0.225.6610)", + "Architecture":"X64", + "HasAttachedDebugger":false, + "HasRyuJit":true, + "Configuration":"RELEASE", + "DotNetCliVersion":"10.0.100-preview.1.25120.13", + "ChronometerFrequency":{ + "Hertz":10000000 + }, + "HardwareTimerKind":"Unknown" + }, + "Benchmarks":[ + { + "DisplayInfo":"Benchmark.StringJoin: DefaultJob", + "Namespace":null, + "Type":"Benchmark", + "Method":"StringJoin", + "MethodTitle":"StringJoin", + "Parameters":"", + "FullName":"Benchmark.StringJoin", + "HardwareIntrinsics":"AVX2,AES,BMI1,BMI2,FMA,LZCNT,PCLMUL,POPCNT VectorSize=256", + "Statistics":{ + "OriginalValues":[ + 13898065.625,14014254.6875,13897585.9375,13596782.8125,13638954.6875,13769428.125,13556540.625,13621389.0625,13540712.5,13520207.8125,13716912.5,13625445.3125,13619175,13655415.625 + ], + "N":14, + "Min":13520207.8125, + "LowerFence":13371503.3203125, + "Q1":13602380.859375, + "Median":13632200, + "Mean":13690776.450892856, + "Q3":13756299.21875, + "UpperFence":13987176.7578125, + "Max":14014254.6875, + "InterquartileRange":153918.359375, + "LowerOutliers":[ + + ], + "UpperOutliers":[ + 14014254.6875 + ], + "AllOutliers":[ + 14014254.6875 + ], + "StandardError":40182.18421420594, + "Variance":22604510995.14133, + "StandardDeviation":150347.96638179492, + "Skewness":0.8270436794227263, + "Kurtosis":2.306039925882999, + "ConfidenceInterval":{ + "N":14, + "Mean":13690776.450892856, + "StandardError":40182.18421420594, + "Level":12, + "Margin":169602.2379659043, + "Lower":13521174.212926952, + "Upper":13860378.68885876 + }, + "Percentiles":{ + "P0":13520207.8125, + "P25":13602380.859375, + "P50":13632200, + "P67":13699078.40625, + "P80":13820691.25, + "P85":13897609.921875, + "P90":13897921.71875, + "P95":13938731.796875, + "P100":14014254.6875 + } + }, + "Memory":{ + "Gen0Collections":2891, + "Gen1Collections":511, + "Gen2Collections":0, + "TotalOperations":64, + "BytesAllocatedPerOperation":379436510 + }, + "Measurements":[ + { + "IterationMode":"Overhead", + "IterationStage":"Jitting", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":1, + "Nanoseconds":166300 + },{ + "IterationMode":"Workload", + "IterationStage":"Jitting", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":1, + "Nanoseconds":25827600 + },{ + "IterationMode":"Overhead", + "IterationStage":"Jitting", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":16, + "Nanoseconds":148400 + },{ + "IterationMode":"Workload", + "IterationStage":"Jitting", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":16, + "Nanoseconds":296577200 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":16, + "Nanoseconds":256823300 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":32, + "Nanoseconds":495841400 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":64, + "Nanoseconds":1174459100 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":64, + "Nanoseconds":1200 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":64, + "Nanoseconds":300 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":64, + "Nanoseconds":300 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":64, + "Nanoseconds":300 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":64, + "Nanoseconds":400 + },{ + "IterationMode":"Overhead", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":64, + "Nanoseconds":400 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":64, + "Nanoseconds":400 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":64, + "Nanoseconds":400 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":64, + "Nanoseconds":500 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":64, + "Nanoseconds":800 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":64, + "Nanoseconds":500 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":64, + "Nanoseconds":500 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":64, + "Nanoseconds":300 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":64, + "Nanoseconds":600 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":9, + "Operations":64, + "Nanoseconds":600 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":10, + "Operations":64, + "Nanoseconds":600 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":11, + "Operations":64, + "Nanoseconds":600 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":12, + "Operations":64, + "Nanoseconds":500 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":13, + "Operations":64, + "Nanoseconds":1000 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":14, + "Operations":64, + "Nanoseconds":300 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":15, + "Operations":64, + "Nanoseconds":400 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":16, + "Operations":64, + "Nanoseconds":600 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":17, + "Operations":64, + "Nanoseconds":400 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":18, + "Operations":64, + "Nanoseconds":300 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":19, + "Operations":64, + "Nanoseconds":500 + },{ + "IterationMode":"Overhead", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":20, + "Operations":64, + "Nanoseconds":1700 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":64, + "Nanoseconds":893760800 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":64, + "Nanoseconds":987046400 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":64, + "Nanoseconds":996909400 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":64, + "Nanoseconds":989069000 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":64, + "Nanoseconds":987880900 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":64, + "Nanoseconds":973568200 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":64, + "Nanoseconds":972817000 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":64, + "Nanoseconds":983248600 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":9, + "Operations":64, + "Nanoseconds":979515500 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":64, + "Nanoseconds":889476700 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":64, + "Nanoseconds":896912800 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":64, + "Nanoseconds":889446000 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":64, + "Nanoseconds":870194600 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":64, + "Nanoseconds":872893600 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":64, + "Nanoseconds":881243900 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":64, + "Nanoseconds":867619100 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":64, + "Nanoseconds":871769400 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":9, + "Operations":64, + "Nanoseconds":866606100 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":10, + "Operations":64, + "Nanoseconds":865293800 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":11, + "Operations":64, + "Nanoseconds":877882900 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":12, + "Operations":64, + "Nanoseconds":872029000 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":13, + "Operations":64, + "Nanoseconds":871627700 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":14, + "Operations":64, + "Nanoseconds":873947100 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":15, + "Operations":64, + "Nanoseconds":910566500 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":64, + "Nanoseconds":889476200 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":64, + "Nanoseconds":896912300 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":64, + "Nanoseconds":889445500 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":64, + "Nanoseconds":870194100 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":64, + "Nanoseconds":872893100 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":64, + "Nanoseconds":881243400 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":64, + "Nanoseconds":867618600 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":64, + "Nanoseconds":871768900 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":9, + "Operations":64, + "Nanoseconds":866605600 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":10, + "Operations":64, + "Nanoseconds":865293300 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":11, + "Operations":64, + "Nanoseconds":877882400 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":12, + "Operations":64, + "Nanoseconds":872028500 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":13, + "Operations":64, + "Nanoseconds":871627200 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":14, + "Operations":64, + "Nanoseconds":873946600 + } + ], + "Metrics":[ + { + "Value":45171.875, + "Descriptor":{ + "Id":"Gen0Collects", + "DisplayName":"Gen0", + "Legend":"GC Generation 0 collects per 1000 operations", + "NumberFormat":"#0.0000", + "UnitType":0, + "Unit":"Count", + "TheGreaterTheBetter":false, + "PriorityInCategory":0 + } + },{ + "Value":7984.375, + "Descriptor":{ + "Id":"Gen1Collects", + "DisplayName":"Gen1", + "Legend":"GC Generation 1 collects per 1000 operations", + "NumberFormat":"#0.0000", + "UnitType":0, + "Unit":"Count", + "TheGreaterTheBetter":false, + "PriorityInCategory":1 + } + },{ + "Value":0, + "Descriptor":{ + "Id":"Gen2Collects", + "DisplayName":"Gen2", + "Legend":"GC Generation 2 collects per 1000 operations", + "NumberFormat":"#0.0000", + "UnitType":0, + "Unit":"Count", + "TheGreaterTheBetter":false, + "PriorityInCategory":2 + } + },{ + "Value":379436510, + "Descriptor":{ + "Id":"Allocated Memory", + "DisplayName":"Allocated", + "Legend":"Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)", + "NumberFormat":"0.##", + "UnitType":2, + "Unit":"B", + "TheGreaterTheBetter":false, + "PriorityInCategory":3 + } + } + ] + },{ + "DisplayInfo":"Benchmark.StringConcat: DefaultJob", + "Namespace":null, + "Type":"Benchmark", + "Method":"StringConcat", + "MethodTitle":"StringConcat", + "Parameters":"", + "FullName":"Benchmark.StringConcat", + "HardwareIntrinsics":"AVX2,AES,BMI1,BMI2,FMA,LZCNT,PCLMUL,POPCNT VectorSize=256", + "Statistics":{ + "OriginalValues":[ + 24699456.25,32780946.875,19114778.125,26372334.375,23531375,29708659.375,24062900,25990940.625,23778787.5,30207884.375,23547734.375,26159675,23554462.5,29761775,24393806.25,25781018.75,23486496.875,29975387.5,23357071.875,26305550,23588496.875,29744612.5,23753659.375,25959096.875,23529659.375,30008171.875,24328615.625,26367825,23823071.875,29619762.5,23820318.75,25727053.125,23762562.5,30120050,23106568.75,26097296.875,23464737.5,31405940.625,24763318.75,26050412.5,23922243.75,29669440.625,23245846.875,26207050,23603587.5,29681331.25,23933815.625,26106068.75,23703228.125,29637959.375,24009143.75,26391812.5,23588606.25,29430493.75,23759493.75,25738896.875,23956571.875,29502390.625,24063178.125,26249959.375,23482340.625,29786109.375,25238978.125,27153184.375,24054759.375,29623553.125,24823487.5,31415693.75,23744281.25,32924909.375,27080843.75,30989025,24104750,31291331.25,24885343.75,26067653.125,23512409.375,30121681.25,23417831.25,26203996.875,23512100,29585531.25,24189221.875,25777312.5,23485493.75,30222878.125,24164340.625,26243537.5,23477106.25,29557731.25,23715396.875,25872034.375,23603484.375,30130521.875,23406193.75,26302625,23652250,29694990.625,24485553.125,25904828.125 + ], + "N":100, + "Min":19114778.125, + "LowerFence":15103948.4375, + "Q1":23751314.84375, + "Median":25732975, + "Mean":26089186.84375, + "Q3":29516225.78125, + "UpperFence":38163592.1875, + "Max":32924909.375, + "InterquartileRange":5764910.9375, + "LowerOutliers":[ + + ], + "UpperOutliers":[ + + ], + "AllOutliers":[ + + ], + "StandardError":282617.3894952885, + "Variance":7987258884513.161, + "StandardDeviation":2826173.894952885, + "Skewness":0.5895868787828163, + "Kurtosis":2.352901287482295, + "ConfidenceInterval":{ + "N":100, + "Mean":26089186.84375, + "StandardError":282617.3894952885, + "Level":12, + "Margin":958505.0241462145, + "Lower":25130681.819603786, + "Upper":27047691.867896214 + }, + "Percentiles":{ + "P0":19114778.125, + "P25":23751314.84375, + "P50":25732975, + "P67":26303590.25, + "P80":29644255.625, + "P85":29747186.875, + "P90":30120213.125, + "P95":31004140.3125, + "P100":32924909.375 + } + }, + "Memory":{ + "Gen0Collections":1445, + "Gen1Collections":255, + "Gen2Collections":0, + "TotalOperations":32, + "BytesAllocatedPerOperation":379436516 + }, + "Measurements":[ + { + "IterationMode":"Overhead", + "IterationStage":"Jitting", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":1, + "Nanoseconds":178200 + },{ + "IterationMode":"Workload", + "IterationStage":"Jitting", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":1, + "Nanoseconds":35362000 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":2, + "Nanoseconds":58567000 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":3, + "Nanoseconds":59945200 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":4, + "Nanoseconds":117404100 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":5, + "Nanoseconds":118478800 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":6, + "Nanoseconds":139136400 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":7, + "Nanoseconds":163195900 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":8, + "Nanoseconds":202502900 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":9, + "Nanoseconds":244073200 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":9, + "Operations":10, + "Nanoseconds":295443800 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":10, + "Operations":11, + "Nanoseconds":294298700 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":11, + "Operations":12, + "Nanoseconds":343230200 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":12, + "Operations":13, + "Nanoseconds":296844900 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":13, + "Operations":14, + "Nanoseconds":293875800 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":14, + "Operations":15, + "Nanoseconds":320088500 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":15, + "Operations":16, + "Nanoseconds":423468600 + },{ + "IterationMode":"Workload", + "IterationStage":"Pilot", + "LaunchIndex":1, + "IterationIndex":16, + "Operations":32, + "Nanoseconds":907983900 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":32, + "Nanoseconds":843346800 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":32, + "Nanoseconds":689658500 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":32, + "Nanoseconds":860462500 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":32, + "Nanoseconds":818983500 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":32, + "Nanoseconds":918430200 + },{ + "IterationMode":"Workload", + "IterationStage":"Warmup", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":32, + "Nanoseconds":685961100 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":32, + "Nanoseconds":790382600 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":32, + "Nanoseconds":1048990300 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":32, + "Nanoseconds":611672900 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":32, + "Nanoseconds":843914700 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":32, + "Nanoseconds":753004000 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":32, + "Nanoseconds":950677100 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":32, + "Nanoseconds":770012800 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":32, + "Nanoseconds":831710100 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":9, + "Operations":32, + "Nanoseconds":760921200 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":10, + "Operations":32, + "Nanoseconds":966652300 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":11, + "Operations":32, + "Nanoseconds":753527500 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":12, + "Operations":32, + "Nanoseconds":837109600 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":13, + "Operations":32, + "Nanoseconds":753742800 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":14, + "Operations":32, + "Nanoseconds":952376800 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":15, + "Operations":32, + "Nanoseconds":780601800 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":16, + "Operations":32, + "Nanoseconds":824992600 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":17, + "Operations":32, + "Nanoseconds":751567900 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":18, + "Operations":32, + "Nanoseconds":959212400 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":19, + "Operations":32, + "Nanoseconds":747426300 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":20, + "Operations":32, + "Nanoseconds":841777600 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":21, + "Operations":32, + "Nanoseconds":754831900 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":22, + "Operations":32, + "Nanoseconds":951827600 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":23, + "Operations":32, + "Nanoseconds":760117100 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":24, + "Operations":32, + "Nanoseconds":830691100 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":25, + "Operations":32, + "Nanoseconds":752949100 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":26, + "Operations":32, + "Nanoseconds":960261500 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":27, + "Operations":32, + "Nanoseconds":778515700 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":28, + "Operations":32, + "Nanoseconds":843770400 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":29, + "Operations":32, + "Nanoseconds":762338300 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":30, + "Operations":32, + "Nanoseconds":947832400 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":31, + "Operations":32, + "Nanoseconds":762250200 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":32, + "Operations":32, + "Nanoseconds":823265700 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":33, + "Operations":32, + "Nanoseconds":760402000 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":34, + "Operations":32, + "Nanoseconds":963841600 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":35, + "Operations":32, + "Nanoseconds":739410200 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":36, + "Operations":32, + "Nanoseconds":835113500 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":37, + "Operations":32, + "Nanoseconds":750871600 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":38, + "Operations":32, + "Nanoseconds":1004990100 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":39, + "Operations":32, + "Nanoseconds":792426200 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":40, + "Operations":32, + "Nanoseconds":833613200 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":41, + "Operations":32, + "Nanoseconds":765511800 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":42, + "Operations":32, + "Nanoseconds":949422100 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":43, + "Operations":32, + "Nanoseconds":743867100 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":44, + "Operations":32, + "Nanoseconds":838625600 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":45, + "Operations":32, + "Nanoseconds":755314800 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":46, + "Operations":32, + "Nanoseconds":949802600 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":47, + "Operations":32, + "Nanoseconds":765882100 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":48, + "Operations":32, + "Nanoseconds":835394200 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":49, + "Operations":32, + "Nanoseconds":758503300 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":50, + "Operations":32, + "Nanoseconds":948414700 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":51, + "Operations":32, + "Nanoseconds":768292600 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":52, + "Operations":32, + "Nanoseconds":844538000 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":53, + "Operations":32, + "Nanoseconds":754835400 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":54, + "Operations":32, + "Nanoseconds":941775800 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":55, + "Operations":32, + "Nanoseconds":760303800 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":56, + "Operations":32, + "Nanoseconds":823644700 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":57, + "Operations":32, + "Nanoseconds":766610300 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":58, + "Operations":32, + "Nanoseconds":944076500 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":59, + "Operations":32, + "Nanoseconds":770021700 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":60, + "Operations":32, + "Nanoseconds":839998700 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":61, + "Operations":32, + "Nanoseconds":751434900 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":62, + "Operations":32, + "Nanoseconds":953155500 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":63, + "Operations":32, + "Nanoseconds":807647300 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":64, + "Operations":32, + "Nanoseconds":868901900 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":65, + "Operations":32, + "Nanoseconds":769752300 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":66, + "Operations":32, + "Nanoseconds":947953700 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":67, + "Operations":32, + "Nanoseconds":794351600 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":68, + "Operations":32, + "Nanoseconds":1005302200 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":69, + "Operations":32, + "Nanoseconds":759817000 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":70, + "Operations":32, + "Nanoseconds":1053597100 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":71, + "Operations":32, + "Nanoseconds":866587000 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":72, + "Operations":32, + "Nanoseconds":991648800 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":73, + "Operations":32, + "Nanoseconds":771352000 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":74, + "Operations":32, + "Nanoseconds":1001322600 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":75, + "Operations":32, + "Nanoseconds":796331000 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":76, + "Operations":32, + "Nanoseconds":834164900 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":77, + "Operations":32, + "Nanoseconds":752397100 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":78, + "Operations":32, + "Nanoseconds":963893800 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":79, + "Operations":32, + "Nanoseconds":749370600 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":80, + "Operations":32, + "Nanoseconds":838527900 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":81, + "Operations":32, + "Nanoseconds":752387200 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":82, + "Operations":32, + "Nanoseconds":946737000 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":83, + "Operations":32, + "Nanoseconds":774055100 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":84, + "Operations":32, + "Nanoseconds":824874000 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":85, + "Operations":32, + "Nanoseconds":751535800 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":86, + "Operations":32, + "Nanoseconds":967132100 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":87, + "Operations":32, + "Nanoseconds":773258900 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":88, + "Operations":32, + "Nanoseconds":839793200 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":89, + "Operations":32, + "Nanoseconds":751267400 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":90, + "Operations":32, + "Nanoseconds":945847400 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":91, + "Operations":32, + "Nanoseconds":758892700 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":92, + "Operations":32, + "Nanoseconds":827905100 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":93, + "Operations":32, + "Nanoseconds":755311500 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":94, + "Operations":32, + "Nanoseconds":964176700 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":95, + "Operations":32, + "Nanoseconds":748998200 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":96, + "Operations":32, + "Nanoseconds":841684000 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":97, + "Operations":32, + "Nanoseconds":756872000 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":98, + "Operations":32, + "Nanoseconds":950239700 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":99, + "Operations":32, + "Nanoseconds":783537700 + },{ + "IterationMode":"Workload", + "IterationStage":"Actual", + "LaunchIndex":1, + "IterationIndex":100, + "Operations":32, + "Nanoseconds":828954500 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":1, + "Operations":32, + "Nanoseconds":790382600 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":2, + "Operations":32, + "Nanoseconds":1048990300 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":3, + "Operations":32, + "Nanoseconds":611672900 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":4, + "Operations":32, + "Nanoseconds":843914700 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":5, + "Operations":32, + "Nanoseconds":753004000 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":6, + "Operations":32, + "Nanoseconds":950677100 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":7, + "Operations":32, + "Nanoseconds":770012800 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":8, + "Operations":32, + "Nanoseconds":831710100 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":9, + "Operations":32, + "Nanoseconds":760921200 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":10, + "Operations":32, + "Nanoseconds":966652300 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":11, + "Operations":32, + "Nanoseconds":753527500 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":12, + "Operations":32, + "Nanoseconds":837109600 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":13, + "Operations":32, + "Nanoseconds":753742800 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":14, + "Operations":32, + "Nanoseconds":952376800 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":15, + "Operations":32, + "Nanoseconds":780601800 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":16, + "Operations":32, + "Nanoseconds":824992600 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":17, + "Operations":32, + "Nanoseconds":751567900 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":18, + "Operations":32, + "Nanoseconds":959212400 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":19, + "Operations":32, + "Nanoseconds":747426300 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":20, + "Operations":32, + "Nanoseconds":841777600 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":21, + "Operations":32, + "Nanoseconds":754831900 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":22, + "Operations":32, + "Nanoseconds":951827600 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":23, + "Operations":32, + "Nanoseconds":760117100 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":24, + "Operations":32, + "Nanoseconds":830691100 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":25, + "Operations":32, + "Nanoseconds":752949100 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":26, + "Operations":32, + "Nanoseconds":960261500 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":27, + "Operations":32, + "Nanoseconds":778515700 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":28, + "Operations":32, + "Nanoseconds":843770400 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":29, + "Operations":32, + "Nanoseconds":762338300 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":30, + "Operations":32, + "Nanoseconds":947832400 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":31, + "Operations":32, + "Nanoseconds":762250200 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":32, + "Operations":32, + "Nanoseconds":823265700 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":33, + "Operations":32, + "Nanoseconds":760402000 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":34, + "Operations":32, + "Nanoseconds":963841600 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":35, + "Operations":32, + "Nanoseconds":739410200 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":36, + "Operations":32, + "Nanoseconds":835113500 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":37, + "Operations":32, + "Nanoseconds":750871600 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":38, + "Operations":32, + "Nanoseconds":1004990100 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":39, + "Operations":32, + "Nanoseconds":792426200 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":40, + "Operations":32, + "Nanoseconds":833613200 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":41, + "Operations":32, + "Nanoseconds":765511800 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":42, + "Operations":32, + "Nanoseconds":949422100 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":43, + "Operations":32, + "Nanoseconds":743867100 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":44, + "Operations":32, + "Nanoseconds":838625600 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":45, + "Operations":32, + "Nanoseconds":755314800 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":46, + "Operations":32, + "Nanoseconds":949802600 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":47, + "Operations":32, + "Nanoseconds":765882100 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":48, + "Operations":32, + "Nanoseconds":835394200 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":49, + "Operations":32, + "Nanoseconds":758503300 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":50, + "Operations":32, + "Nanoseconds":948414700 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":51, + "Operations":32, + "Nanoseconds":768292600 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":52, + "Operations":32, + "Nanoseconds":844538000 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":53, + "Operations":32, + "Nanoseconds":754835400 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":54, + "Operations":32, + "Nanoseconds":941775800 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":55, + "Operations":32, + "Nanoseconds":760303800 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":56, + "Operations":32, + "Nanoseconds":823644700 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":57, + "Operations":32, + "Nanoseconds":766610300 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":58, + "Operations":32, + "Nanoseconds":944076500 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":59, + "Operations":32, + "Nanoseconds":770021700 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":60, + "Operations":32, + "Nanoseconds":839998700 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":61, + "Operations":32, + "Nanoseconds":751434900 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":62, + "Operations":32, + "Nanoseconds":953155500 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":63, + "Operations":32, + "Nanoseconds":807647300 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":64, + "Operations":32, + "Nanoseconds":868901900 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":65, + "Operations":32, + "Nanoseconds":769752300 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":66, + "Operations":32, + "Nanoseconds":947953700 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":67, + "Operations":32, + "Nanoseconds":794351600 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":68, + "Operations":32, + "Nanoseconds":1005302200 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":69, + "Operations":32, + "Nanoseconds":759817000 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":70, + "Operations":32, + "Nanoseconds":1053597100 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":71, + "Operations":32, + "Nanoseconds":866587000 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":72, + "Operations":32, + "Nanoseconds":991648800 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":73, + "Operations":32, + "Nanoseconds":771352000 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":74, + "Operations":32, + "Nanoseconds":1001322600 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":75, + "Operations":32, + "Nanoseconds":796331000 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":76, + "Operations":32, + "Nanoseconds":834164900 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":77, + "Operations":32, + "Nanoseconds":752397100 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":78, + "Operations":32, + "Nanoseconds":963893800 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":79, + "Operations":32, + "Nanoseconds":749370600 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":80, + "Operations":32, + "Nanoseconds":838527900 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":81, + "Operations":32, + "Nanoseconds":752387200 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":82, + "Operations":32, + "Nanoseconds":946737000 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":83, + "Operations":32, + "Nanoseconds":774055100 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":84, + "Operations":32, + "Nanoseconds":824874000 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":85, + "Operations":32, + "Nanoseconds":751535800 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":86, + "Operations":32, + "Nanoseconds":967132100 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":87, + "Operations":32, + "Nanoseconds":773258900 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":88, + "Operations":32, + "Nanoseconds":839793200 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":89, + "Operations":32, + "Nanoseconds":751267400 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":90, + "Operations":32, + "Nanoseconds":945847400 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":91, + "Operations":32, + "Nanoseconds":758892700 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":92, + "Operations":32, + "Nanoseconds":827905100 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":93, + "Operations":32, + "Nanoseconds":755311500 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":94, + "Operations":32, + "Nanoseconds":964176700 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":95, + "Operations":32, + "Nanoseconds":748998200 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":96, + "Operations":32, + "Nanoseconds":841684000 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":97, + "Operations":32, + "Nanoseconds":756872000 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":98, + "Operations":32, + "Nanoseconds":950239700 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":99, + "Operations":32, + "Nanoseconds":783537700 + },{ + "IterationMode":"Workload", + "IterationStage":"Result", + "LaunchIndex":1, + "IterationIndex":100, + "Operations":32, + "Nanoseconds":828954500 + } + ], + "Metrics":[ + { + "Value":45156.25, + "Descriptor":{ + "Id":"Gen0Collects", + "DisplayName":"Gen0", + "Legend":"GC Generation 0 collects per 1000 operations", + "NumberFormat":"#0.0000", + "UnitType":0, + "Unit":"Count", + "TheGreaterTheBetter":false, + "PriorityInCategory":0 + } + },{ + "Value":7968.75, + "Descriptor":{ + "Id":"Gen1Collects", + "DisplayName":"Gen1", + "Legend":"GC Generation 1 collects per 1000 operations", + "NumberFormat":"#0.0000", + "UnitType":0, + "Unit":"Count", + "TheGreaterTheBetter":false, + "PriorityInCategory":1 + } + },{ + "Value":0, + "Descriptor":{ + "Id":"Gen2Collects", + "DisplayName":"Gen2", + "Legend":"GC Generation 2 collects per 1000 operations", + "NumberFormat":"#0.0000", + "UnitType":0, + "Unit":"Count", + "TheGreaterTheBetter":false, + "PriorityInCategory":2 + } + },{ + "Value":379436516, + "Descriptor":{ + "Id":"Allocated Memory", + "DisplayName":"Allocated", + "Legend":"Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)", + "NumberFormat":"0.##", + "UnitType":2, + "Unit":"B", + "TheGreaterTheBetter":false, + "PriorityInCategory":3 + } + } + ] + } + ] +} diff --git a/tests/test-data/report-99/Benchmark-report-github.md b/tests/test-data/report-99/Benchmark-report-github.md new file mode 100644 index 0000000..c91bece --- /dev/null +++ b/tests/test-data/report-99/Benchmark-report-github.md @@ -0,0 +1,14 @@ +``` + +BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3323) +AMD Ryzen 5 3600X, 1 CPU, 12 logical and 6 physical cores +.NET SDK 10.0.100-preview.1.25120.13 + [Host] : .NET 9.0.2 (9.0.225.6610), X64 RyuJIT AVX2 + DefaultJob : .NET 9.0.2 (9.0.225.6610), X64 RyuJIT AVX2 + + +``` +| Method | Mean | Error | StdDev | Ratio | RatioSD | Rank | Gen0 | Gen1 | Allocated | Alloc Ratio | +|------------- |---------:|---------:|---------:|------:|--------:|-----:|-----------:|----------:|----------:|------------:| +| StringJoin | 13.69 ms | 0.170 ms | 0.150 ms | 1.00 | 0.01 | I | 45171.8750 | 7984.3750 | 361.86 MB | 1.00 | +| StringConcat | 26.09 ms | 0.959 ms | 2.826 ms | 1.91 | 0.21 | II | 45156.2500 | 7968.7500 | 361.86 MB | 1.00 | diff --git a/tests/test-data/report-99/Benchmark-report.csv b/tests/test-data/report-99/Benchmark-report.csv new file mode 100644 index 0000000..9973523 --- /dev/null +++ b/tests/test-data/report-99/Benchmark-report.csv @@ -0,0 +1,3 @@ +Method,Job,AnalyzeLaunchVariance,EvaluateOverhead,MaxAbsoluteError,MaxRelativeError,MinInvokeCount,MinIterationTime,OutlierMode,Affinity,EnvironmentVariables,Jit,LargeAddressAware,Platform,PowerPlanMode,Runtime,AllowVeryLargeObjects,Concurrent,CpuGroups,Force,HeapAffinitizeMask,HeapCount,NoAffinitize,RetainVm,Server,Arguments,BuildConfiguration,Clock,EngineFactory,NuGetReferences,Toolchain,IsMutator,InvocationCount,IterationCount,IterationTime,LaunchCount,MaxIterationCount,MaxWarmupIterationCount,MemoryRandomization,MinIterationCount,MinWarmupIterationCount,RunStrategy,UnrollFactor,WarmupCount,Mean,Error,StdDev,Ratio,RatioSD,Rank,Gen0,Gen1,Allocated,Alloc Ratio +StringJoin,DefaultJob,False,Default,Default,Default,Default,Default,Default,111111111111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 9.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,13.69 ms,0.170 ms,0.150 ms,1.00,0.01,I,45171.8750,7984.3750,361.86 MB,1.00 +StringConcat,DefaultJob,False,Default,Default,Default,Default,Default,Default,111111111111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 9.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,26.09 ms,0.959 ms,2.826 ms,1.91,0.21,II,45156.2500,7968.7500,361.86 MB,1.00 diff --git a/tests/test-data/report-99/Benchmark-report.html b/tests/test-data/report-99/Benchmark-report.html new file mode 100644 index 0000000..d358902 --- /dev/null +++ b/tests/test-data/report-99/Benchmark-report.html @@ -0,0 +1,31 @@ + + + + +Benchmark-20250309-000554 + + + + +

+BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3323)
+AMD Ryzen 5 3600X, 1 CPU, 12 logical and 6 physical cores
+.NET SDK 10.0.100-preview.1.25120.13
+  [Host]     : .NET 9.0.2 (9.0.225.6610), X64 RyuJIT AVX2
+  DefaultJob : .NET 9.0.2 (9.0.225.6610), X64 RyuJIT AVX2
+
+
+ + + + + + +
MethodMeanErrorStdDevRatioRatioSDRankGen0Gen1AllocatedAlloc Ratio
StringJoin13.69 ms0.170 ms0.150 ms1.000.01I45171.87507984.3750361.86 MB1.00
StringConcat26.09 ms0.959 ms2.826 ms1.910.21II45156.25007968.7500361.86 MB1.00
+ + From a8f17f0a4d826c34895b5f5fdefd715c2ad66fc5 Mon Sep 17 00:00:00 2001 From: Nelson Nobre Date: Mon, 2 Jun 2025 15:35:54 +0100 Subject: [PATCH 02/10] fix: typo in `nuget-vulnerabilites.yml` Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/nuget-vulnerabilites.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nuget-vulnerabilites.yml b/.github/workflows/nuget-vulnerabilites.yml index c889c74..2c5e7ce 100644 --- a/.github/workflows/nuget-vulnerabilites.yml +++ b/.github/workflows/nuget-vulnerabilites.yml @@ -35,7 +35,7 @@ jobs: - name: "Build" run: dotnet build --configuration Release --no-restore - - name: "Checking NuGet vulnerabilites" + - name: "Checking NuGet vulnerabilities" run: | dotnet list package --vulnerable --include-transitive 2>&1 | tee build.log echo "Analyze dotnet list package command log output..." From cbebfcef6ea3d003f150987a8c7206e9631396a1 Mon Sep 17 00:00:00 2001 From: Nelson Nobre Date: Mon, 2 Jun 2025 15:44:57 +0100 Subject: [PATCH 03/10] ci: fix typo 'nuget-vulnerabilites.yml' --- .github/workflows/nuget-vulnerabilites.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nuget-vulnerabilites.yml b/.github/workflows/nuget-vulnerabilites.yml index 2c5e7ce..6c5d293 100644 --- a/.github/workflows/nuget-vulnerabilites.yml +++ b/.github/workflows/nuget-vulnerabilites.yml @@ -1,4 +1,4 @@ -name: 'NuGet vulnerabilites' +name: 'NuGet vulnerabilities' on: From 52429475848370580b7f4743a37a09229f71f02c Mon Sep 17 00:00:00 2001 From: Nelson Nobre Date: Mon, 2 Jun 2025 15:49:32 +0100 Subject: [PATCH 04/10] test: add jq filter for transforming mutation testing reports --- tests/mutation-report-to-sonar.jq | 50 +++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 tests/mutation-report-to-sonar.jq diff --git a/tests/mutation-report-to-sonar.jq b/tests/mutation-report-to-sonar.jq new file mode 100644 index 0000000..be1ea30 --- /dev/null +++ b/tests/mutation-report-to-sonar.jq @@ -0,0 +1,50 @@ +# This jq filter transforms a mutation testing report to the SonarQube generic issue import format. + +.framework.name as $frameworkName +| .projectRoot as $projectRoot +| .files +| to_entries +| { + issues: map( + .value.mutants[] as $mutants + | del(.value) as $file + | $mutants + | select(.status == ("Survived", "NoCoverage")) + | ( + if .replacement then + "The " + .mutatorName + " was mutated to " + .replacement + " without any tests failing." + else + "The " + .mutatorName + " was mutated without any tests failing." + end + ) as $mutation + | { + engineId: ($frameworkName // "Mutation Testing"), + ruleId: ("Mutant" + .status), + primaryLocation: { + message: ( + if .status == "NoCoverage" then + "A mutant was not covered by any of the tests. " + $mutation + else + "A mutant survived after running the tests. " + $mutation + end + ), + filePath: ( + if $projectRoot then + $file.key | sub("^" + $projectRoot + "/"; "") + else + $file.key + end + ), + textRange: { + startLine: .location.start.line, + endLine: .location.end.line, + startColumn: (.location.start.column - 1), + endColumn: (.location.end.column - 1) + } + }, + type: "CODE_SMELL", + severity: "MAJOR", + effortMinutes: 10 + } + ) +} From fb72e524740dd0a8647184aa3cfe1543b3b8f37c Mon Sep 17 00:00:00 2001 From: Nelson Nobre Date: Thu, 12 Jun 2025 08:48:13 +0100 Subject: [PATCH 05/10] ci: refactor vulnerability check workflow --- .../{nuget-vulnerabilites.yml => vulnerability-check.yml} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename .github/workflows/{nuget-vulnerabilites.yml => vulnerability-check.yml} (91%) diff --git a/.github/workflows/nuget-vulnerabilites.yml b/.github/workflows/vulnerability-check.yml similarity index 91% rename from .github/workflows/nuget-vulnerabilites.yml rename to .github/workflows/vulnerability-check.yml index 6c5d293..803264d 100644 --- a/.github/workflows/nuget-vulnerabilites.yml +++ b/.github/workflows/vulnerability-check.yml @@ -1,4 +1,4 @@ -name: 'NuGet vulnerabilities' +name: 'Vulnerability Check' on: @@ -16,8 +16,8 @@ on: jobs: - sonar-scanner: - name: "NuGet vulnerabilites" + nuget-vulnerabilities-check: + name: "NuGet vulnerabilities" runs-on: ubuntu-latest steps: From a10157e29f683fcfc9b25fc88e920e458325dfe1 Mon Sep 17 00:00:00 2001 From: Nelson Nobre Date: Thu, 12 Jun 2025 10:28:09 +0100 Subject: [PATCH 06/10] ci: update job name for markdown link check workflow --- .github/workflows/markdown-link-check.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/markdown-link-check.yml b/.github/workflows/markdown-link-check.yml index a9f6253..c82bd19 100644 --- a/.github/workflows/markdown-link-check.yml +++ b/.github/workflows/markdown-link-check.yml @@ -16,6 +16,7 @@ on: jobs: markdown-link-check: + name: "Markdown link check" runs-on: ubuntu-latest steps: From 01aa7d2674d4a768984aca540b10409f8dd2513d Mon Sep 17 00:00:00 2001 From: Nelson Nobre Date: Thu, 12 Jun 2025 10:56:14 +0100 Subject: [PATCH 07/10] ci: rename test job from "Test nuget" to "Test project" --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 25439bd..613b46e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -24,7 +24,7 @@ env: jobs: test-project: - name: "Test nuget" + name: "Test project" runs-on: ubuntu-22.04 permissions: contents: read From 15c4e2b67f027b157e2e51b35abad02ac8c4e221 Mon Sep 17 00:00:00 2001 From: Nelson Nobre Date: Thu, 12 Jun 2025 11:26:31 +0100 Subject: [PATCH 08/10] docs: fix typo in general steps section of contributing guidelines --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ba0673c..57322cc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ We would love for you to contribute to this project and help make it even better As a contributor, here are the guidelines we would like you to follow. It does help everyone to accept your Pull Requests with maximum awesomeness: - [ Code of Conduct](#-code-of-conduct) -- [ General Seps](#-general-seps) +- [ General Steps](#-general-steps) - [ Commits and Pull Requests](#-commits-and-pull-requests) - [ Conventional Commits](#-conventional-commits) - [ Common types you can use (based on the Angular convention):](#-common-types-you-can-use-based-on-the-angular-convention) @@ -19,7 +19,7 @@ Please read and follow our [Code of Conduct][coc]. -##
General Seps +## General Steps 1. Check if there is already an open issue for the subject; 2. Open an issue to discuss the new feature; From 194c42e7cddf5204294a36b8242e3c44ca463c74 Mon Sep 17 00:00:00 2001 From: Nelson Nobre Date: Thu, 12 Jun 2025 11:38:57 +0100 Subject: [PATCH 09/10] docs: fix formatting inconsistencies in contributing guidelines --- CONTRIBUTING.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 57322cc..4e7433f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,23 +3,23 @@ We would love for you to contribute to this project and help make it even better than it is today! As a contributor, here are the guidelines we would like you to follow. It does help everyone to accept your Pull Requests with maximum awesomeness: -- [ Code of Conduct](#-code-of-conduct) -- [ General Steps](#-general-steps) -- [ Commits and Pull Requests](#-commits-and-pull-requests) - - [ Conventional Commits](#-conventional-commits) - - [ Common types you can use (based on the Angular convention):](#-common-types-you-can-use-based-on-the-angular-convention) -- [ Coding Standards](#-coding-standards) -- [ Tests](#-tests) +- [Code of Conduct](#code-of-conduct) +- [General Steps](#general-steps) +- [Commits and Pull Requests](#commits-and-pull-requests) + - [Conventional Commits](#conventional-commits) + - [Common types you can use (based on the Angular convention)](#common-types-you-can-use-based-on-the-angular-convention) +- [Coding Standards](#coding-standards) +- [Tests](#tests) -## Code of Conduct +## Code of Conduct Please read and follow our [Code of Conduct][coc]. -## General Steps +## General Steps 1. Check if there is already an open issue for the subject; 2. Open an issue to discuss the new feature; @@ -31,14 +31,14 @@ Please read and follow our [Code of Conduct][coc]. 8. Submit a pull request; -## Commits and Pull Requests +## Commits and Pull Requests * :x: **AVOID** breaking the continuous integration build. * :heavy_check_mark: **DO** atomic commits to make it easier to analyze changes. * :heavy_check_mark: **DO** keep pull requests small so they can be easily reviewed. * :heavy_check_mark: **DO** only commit with conventional commit patterns -### Conventional Commits +### Conventional Commits To know more about conventional commits, visit [Conventional Commits](https://conventionalcommits.org/). In general the pattern mostly looks like this: @@ -63,7 +63,7 @@ fix(server): send cors headers feat(blog): add comment section ``` -#### Common types you can use (based on the Angular convention): +#### Common types you can use (based on the Angular convention) - `feat:` A new feature - `fix:` A bug fix @@ -80,7 +80,7 @@ feat(blog): add comment section -## Coding Standards +## Coding Standards * :heavy_check_mark: **DO** add XML comment documentation to new classes, methods or parameters. * :heavy_check_mark: **DO** add a test class for each feature and a test method for each * :heavy_check_mark: **DO** use language conventions to make code easy to understand quickly. See some tips here: [dofactory](https://www.dofactory.com/csharp-coding-standards); @@ -88,7 +88,7 @@ feat(blog): add comment section -## Tests +## Tests * :heavy_check_mark: **DO** add a unit test if your Pull Requests resolves an issue or adds features. * :heavy_check_mark: **CONSIDER** using test patterns like "AAA" and "Given When Then"; * :heavy_check_mark: **DO** add a test class for each feature and a test method for each assertion; From cc18a5a0af1589b52447f92fc95a061c9258ade2 Mon Sep 17 00:00:00 2001 From: Nelson Nobre Date: Thu, 12 Jun 2025 12:00:07 +0100 Subject: [PATCH 10/10] ci: update max length for commit messages to 72 characters --- .github/hooks/commit-msg | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/hooks/commit-msg b/.github/hooks/commit-msg index b196b72..bb66012 100644 --- a/.github/hooks/commit-msg +++ b/.github/hooks/commit-msg @@ -31,7 +31,7 @@ if ! head -1 "$1" | grep -qE "^(build|chore|ci|docs|feat|fix|perf|refactor|rever echo "For more information check https://www.conventionalcommits.org/en/v1.0.0/ for more details" >&2 exit 1 fi -if ! head -1 "$1" | grep -qE "^.{9,85}$"; then - echo "Aborting commit. Your commit message is too long. Max length is 89 characters" >&2 +if ! head -1 "$1" | grep -qE "^.{9,72}$"; then + echo "Aborting commit. Your commit message is too long. Max length is 72 characters" >&2 exit 1 -fi \ No newline at end of file +fi