├── .editorconfig ├── .gitattributes ├── .github ├── CODEOWNERS ├── FUNDING.yml ├── renovate.json ├── settings.yml └── workflows │ ├── build-win.yml │ ├── build.yml │ ├── ci.yml │ └── labeled.yml ├── .gitignore ├── .ruleset ├── Directory.Build.props ├── LICENSE ├── Prometheus.Client.sln ├── Prometheus.Client.snk ├── README.md ├── docs └── benchmarks │ ├── CollectingBenchmarks.md │ ├── CreationBenchmarks.md │ ├── GeneralUseCase.md │ ├── README.md │ ├── SampleBenchmarks.md │ ├── SampleResolvingBenchmarks.md │ ├── generalcase.png │ └── overview.ods ├── entity-framework-extensions-sponsor.png ├── icon.png ├── src ├── Prometheus.Client.Abstractions │ ├── Collectors │ │ ├── CollectorConfiguration.cs │ │ ├── ICollector.cs │ │ └── ICollectorRegistry.cs │ ├── CounterExtensions.cs │ ├── CounterInt64Extensions.cs │ ├── GaugeExtensions.cs │ ├── GaugeInt64Extensions.cs │ ├── HistogramExtensions.cs │ ├── HistogramState.cs │ ├── ICounter.cs │ ├── IGauge.cs │ ├── IHistogram.cs │ ├── IMetric.cs │ ├── IMetricFactory.cs │ ├── IMetricFamily.cs │ ├── ISummary.cs │ ├── IUntyped.cs │ ├── IValueObserver.cs │ ├── MetricFamilyExtensions.cs │ ├── MetricType.cs │ ├── MetricsWriter │ │ ├── ILabelWriter.cs │ │ ├── IMetricsWriter.cs │ │ ├── ISampleWriter.cs │ │ └── MetricsWriterExtensions.cs │ ├── Prometheus.Client.Abstractions.csproj │ ├── QuantileEpsilonPair.cs │ ├── SummaryExtensions.cs │ ├── SummaryState.cs │ ├── UntypedExtensions.cs │ └── ValueObserverExtensions.cs └── Prometheus.Client │ ├── Collectors │ ├── CollectorRegistry.cs │ ├── CollectorRegistryExtensions.cs │ ├── DefaultCollectors.cs │ ├── DotNetStats │ │ ├── CollectorRegistryExtensions.cs │ │ ├── GCCollectionCountCollector.cs │ │ └── GCTotalMemoryCollector.cs │ ├── MetricWriterWrapper.cs │ └── ProcessStats │ │ ├── CollectorRegistryExtensions.cs │ │ └── ProcessCollector.cs │ ├── Counter.cs │ ├── CounterInt64.cs │ ├── Gauge.cs │ ├── GaugeInt64.cs │ ├── Histogram.cs │ ├── HistogramConfiguration.cs │ ├── HistogramImpl │ ├── HistogramHighBucketsStore.cs │ ├── HistogramLowBucketsStore.cs │ └── IHistogramBucketStore.cs │ ├── LabelsHelper.cs │ ├── MetricBase.cs │ ├── MetricConfiguration.cs │ ├── MetricFactory.cs │ ├── MetricFamily.cs │ ├── Metrics.cs │ ├── MetricsWriter │ └── MetricsTextWriter.cs │ ├── Prometheus.Client.csproj │ ├── ScrapeHandler.cs │ ├── Summary.cs │ ├── SummaryConfiguration.cs │ ├── SummaryImpl │ ├── Invariant.cs │ ├── QuantileStream.cs │ ├── Sample.cs │ └── SampleStream.cs │ ├── ThreadSafeDouble.cs │ ├── ThreadSafeLong.cs │ └── Untyped.cs ├── stylecop.json └── tests ├── Prometheus.Client.Benchmarks.Comparison ├── ComparisonBenchmarkBase.cs ├── Counter │ ├── CounterCollectingBenchmarks.cs │ ├── CounterCreationBenchmarks.cs │ ├── CounterGeneralUseCaseBenchmarks.cs │ ├── CounterSampleBenchmarks.cs │ └── CounterSampleResolvingBenchmarks.cs ├── Gauge │ ├── GaugeCollectingBenchmarks.cs │ ├── GaugeCreationBenchmarks.cs │ ├── GaugeGeneralUseCaseBenchmarks.cs │ ├── GaugeSampleBenchmarks.cs │ └── GaugeSampleResolvingBenchmarks.cs ├── Histogram │ ├── HistogramCollectingBenchmarks.cs │ ├── HistogramCreationBenchmarks.cs │ ├── HistogramGeneralUseCaseBenchmarks.cs │ ├── HistogramSampleBenchmarks.cs │ └── HistogramSampleResolvingBenchmarks.cs ├── Program.cs ├── Prometheus.Client.Benchmarks.Comparison.csproj └── Summary │ ├── SummaryCollectingBenchmarks.cs │ ├── SummaryCreationBenchmarks.cs │ ├── SummaryGeneralUseCaseBenchmarks.cs │ ├── SummarySampleBenchmarks.cs │ └── SummarySampleResolvingBenchmarks.cs ├── Prometheus.Client.Benchmarks ├── Counter │ ├── CounterCollection.cs │ ├── CounterCreation.cs │ └── CounterUsage.cs ├── DefaultMetricsCollection.cs ├── Gauge │ ├── GaugeCollection.cs │ ├── GaugeCreation.cs │ └── GaugeUsage.cs ├── Histogram │ ├── HistogramCollection.cs │ ├── HistogramCreation.cs │ └── HistogramUsage.cs ├── Program.cs ├── Prometheus.Client.Benchmarks.csproj ├── SerializationBenchmarks.cs └── Summary │ ├── SummaryCollection.cs │ ├── SummaryCreation.cs │ └── SummaryUsage.cs └── Prometheus.Client.Tests ├── CollectionTestHelper.cs ├── CollectorRegistryExtensionsTests.cs ├── CollectorRegistryTests.cs ├── CollectorTests ├── CollectorRegistryExtensionsTests.cs ├── GCCollectionCountCollectorTests.cs ├── GCTotalMemoryCollectorTests.cs └── ProcessCollectorTests.cs ├── CounterInt64Tests ├── CollectionTests.cs ├── ExtensionsTests.cs ├── FactoryTests.cs ├── Resources │ ├── CounterTests_Collection.txt │ ├── CounterTests_Empty.txt │ └── CounterTests_SuppressEmpty.txt ├── SampleTests.cs └── ThreadingTests.cs ├── CounterTests ├── CollectionTests.cs ├── ExtensionsTests.cs ├── FactoryTests.cs ├── Resources │ ├── CounterTests_Collection.txt │ ├── CounterTests_Empty.txt │ └── CounterTests_SuppressEmpty.txt ├── SampleTests.cs └── ThreadingTests.cs ├── GaugeInt64Tests ├── CollectionTests.cs ├── ExtensionsTests.cs ├── FactoryTests.cs ├── Resources │ ├── GaugeTests_Collection.txt │ ├── GaugeTests_Empty.txt │ └── GaugeTests_SuppressEmpty.txt ├── SampleTests.cs └── ThreadingTests.cs ├── GaugeTests ├── CollectionTests.cs ├── ExtensionsTests.cs ├── FactoryTests.cs ├── Resources │ ├── GaugeTests_Collection.txt │ ├── GaugeTests_Empty.txt │ └── GaugeTests_SuppressEmpty.txt ├── SampleTests.cs └── ThreadingTests.cs ├── HistogramTests ├── CollectionTests.cs ├── ExtensionsTests.cs ├── FactoryTests.cs ├── HistogramConfigurationTests.cs ├── Resources │ ├── HistogramTests_Collection.txt │ ├── HistogramTests_Empty.txt │ └── HistogramTests_SuppressEmpty.txt ├── SampleTests.cs └── ThreadingTests.cs ├── LabelsHelperTests.cs ├── MetricBaseTests.cs ├── MetricConfigurationTests.cs ├── MetricFactoryTests.cs ├── MetricFamilyTests.cs ├── Mocks ├── DummyCollector.cs ├── DummyMetric.cs └── IDummyMetric.cs ├── Prometheus.Client.Tests.csproj ├── StringExtensions.cs ├── SummaryTests ├── ExtensionsTests.cs ├── FactoryTests.cs ├── RandomExtensions.cs ├── SampleStreamTests.cs ├── SampleTests.cs ├── SummaryTests.cs └── ThreadingTests.cs ├── ThreadSafeDoubleTests.cs ├── ThreadSafeLongTests.cs └── UntypedTests ├── CollectionTests.cs ├── ExtensionsTests.cs ├── FactoryTests.cs ├── Resources ├── UntypedTests_Collection.txt └── UntypedTests_Empty.txt ├── SampleTests.cs └── ThreadingTests.cs /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 2 7 | indent_style = space 8 | max_line_length = 160 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.cs] 13 | indent_size = 4 14 | 15 | ## Dotnet code style settings: 16 | 17 | # Sort using and Import directives with System.* appearing first 18 | dotnet_sort_system_directives_first = true 19 | # Avoid "this." and "Me." if not necessary 20 | dotnet_style_qualification_for_field = false:suggestion 21 | dotnet_style_qualification_for_property = false:suggestion 22 | dotnet_style_qualification_for_method = false:suggestion 23 | dotnet_style_qualification_for_event = false:suggestion 24 | 25 | # Use language keywords instead of framework type names for type references 26 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 27 | dotnet_style_predefined_type_for_member_access = true:suggestion 28 | 29 | # Suggest more modern language features when available 30 | dotnet_style_object_initializer = true:suggestion 31 | dotnet_style_collection_initializer = true:suggestion 32 | dotnet_style_coalesce_expression = true:suggestion 33 | dotnet_style_null_propagation = true:suggestion 34 | dotnet_style_explicit_tuple_names = true:suggestion 35 | 36 | # CSharp code style settings: 37 | 38 | # Prefer "var" everywhere 39 | csharp_style_var_for_built_in_types = false:none 40 | csharp_style_var_when_type_is_apparent = true:suggestion 41 | csharp_style_var_elsewhere = true:suggestion 42 | 43 | # Prefer method-like constructs to have a block body 44 | csharp_style_expression_bodied_methods = false:none 45 | csharp_style_expression_bodied_constructors = false:none 46 | csharp_style_expression_bodied_operators = false:none 47 | 48 | # Prefer property-like constructs to have an expression-body 49 | csharp_style_expression_bodied_properties = true:none 50 | csharp_style_expression_bodied_indexers = true:none 51 | csharp_style_expression_bodied_accessors = true:none 52 | 53 | # Suggest more modern language features when available 54 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 55 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 56 | csharp_style_inlined_variable_declaration = true:suggestion 57 | csharp_style_throw_expression = true:suggestion 58 | csharp_style_conditional_delegate_call = true:suggestion 59 | 60 | # Newline settings 61 | csharp_new_line_before_else = true 62 | csharp_new_line_before_catch = true 63 | csharp_new_line_before_finally = true 64 | 65 | ## Naming 66 | 67 | ### private fields should be _camelCase 68 | 69 | dotnet_naming_style.underscore_prefix.capitalization = camel_case 70 | dotnet_naming_style.underscore_prefix.required_prefix = _ 71 | 72 | dotnet_naming_rule.private_fields_with_underscore.symbols = private_fields 73 | dotnet_naming_rule.private_fields_with_underscore.style = underscore_prefix 74 | dotnet_naming_rule.private_fields_with_underscore.severity = suggestion 75 | 76 | dotnet_naming_symbols.private_fields.applicable_kinds = field 77 | dotnet_naming_symbols.private_fields.applicable_accessibilities = private 78 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @phnx47 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: phnx47 2 | ko_fi: phnx47 3 | buy_me_a_coffee: phnx47 4 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [":dependencyDashboard", ":semanticPrefixFixDepsChoreOthers", "group:monorepos", "workarounds:all"], 4 | "labels": ["dependencies"], 5 | "assignees": ["phnx47"], 6 | "semanticCommits": "disabled", 7 | "commitMessageAction": "Bump", 8 | "commitMessageTopic": "{{depName}}", 9 | "packageRules": [ 10 | { 11 | "automerge": true, 12 | "groupName": "coverlet packages", 13 | "matchSourceUrls": ["https://github.com/coverlet-coverage{/,}**"] 14 | }, 15 | { 16 | "automerge": true, 17 | "extends": ["monorepo:vstest", "monorepo:xunit-dotnet"] 18 | }, 19 | { 20 | "matchManagers": ["github-actions"], 21 | "enabled": false 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /.github/settings.yml: -------------------------------------------------------------------------------- 1 | repository: 2 | topics: metrics, prometheus, prometheus-client 3 | has_issues: true 4 | has_wiki: false 5 | default_branch: main 6 | allow_auto_merge: true 7 | delete_branch_on_merge: true 8 | 9 | labels: 10 | - name: dependencies 11 | color: '0052CC' 12 | description: 13 | 14 | - name: bug 15 | color: 'D73A4A' 16 | description: 17 | 18 | - name: documentation 19 | color: '0075CA' 20 | description: 21 | 22 | - name: duplicate 23 | color: 'CFD3D7' 24 | description: 25 | 26 | - name: enhancement 27 | color: 'A2EEEF' 28 | description: 29 | 30 | - name: good first issue 31 | color: '7057FF' 32 | description: 33 | 34 | - name: help wanted 35 | color: '008672' 36 | description: 37 | 38 | - name: invalid 39 | color: 'E4E669' 40 | description: 41 | 42 | - name: question 43 | color: 'D876E3' 44 | description: 45 | 46 | - name: wontfix 47 | color: 'FFFFFF' 48 | description: 49 | 50 | - name: vulnerability 51 | color: 'D1260F' 52 | description: 53 | 54 | - name: sync 55 | color: '6E81A3' 56 | description: 57 | 58 | branches: 59 | - name: main 60 | protection: 61 | required_pull_request_reviews: null 62 | required_status_checks: 63 | strict: false 64 | contexts: ['Build & Test', 'Build & Test (Windows)'] 65 | enforce_admins: false 66 | required_linear_history: false 67 | restrictions: null 68 | -------------------------------------------------------------------------------- /.github/workflows/build-win.yml: -------------------------------------------------------------------------------- 1 | name: Build Win 2 | 3 | on: 4 | push: 5 | branches: 6 | - "main" 7 | pull_request: 8 | branches: 9 | - "main" 10 | workflow_dispatch: 11 | 12 | jobs: 13 | build-win: 14 | name: Build & Test (Windows) 15 | runs-on: windows-2025 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | 20 | - name: Setup .NET 21 | uses: actions/setup-dotnet@v4 22 | with: 23 | dotnet-version: | 24 | 6.0.x 25 | 8.0.x 26 | 27 | - name: Run tests 28 | run: dotnet test -c Release -p:CollectCoverage=false 29 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - "main" 7 | workflow_dispatch: 8 | workflow_call: 9 | 10 | jobs: 11 | build: 12 | name: Build & Test 13 | runs-on: ubuntu-24.04 14 | 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | 19 | - name: Setup .NET 20 | uses: actions/setup-dotnet@v4 21 | with: 22 | dotnet-version: | 23 | 6.0.x 24 | 8.0.x 25 | 26 | - name: Build 27 | run: dotnet build -c Release 28 | 29 | - name: Run tests with Coverage 30 | run: dotnet test --no-build -c Release -p:CollectCoverage=true -e:CoverletOutputFormat=opencover 31 | 32 | - name: Publish to Codecov 33 | uses: codecov/codecov-action@v5 34 | with: 35 | fail_ci_if_error: true 36 | token: ${{ secrets.CODECOV_TOKEN }} 37 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - "main" 7 | tags: 8 | - "v*" 9 | 10 | jobs: 11 | build: 12 | name: CI Build 13 | uses: ./.github/workflows/build.yml 14 | secrets: inherit 15 | 16 | pack: 17 | name: Create NuGet packages 18 | needs: [build] 19 | runs-on: ubuntu-24.04 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v4 23 | with: 24 | fetch-depth: 0 25 | - name: Set Dev version 26 | if: github.ref == 'refs/heads/main' 27 | run: | 28 | version="$(git describe --long --tags | sed 's/^v//;0,/-/s//./')" 29 | if [ -z "${version}" ]; then 30 | version="0.0.0.$(git rev-list --count HEAD)-g$(git rev-parse --short HEAD)" 31 | fi 32 | echo "VERSION=${version}" >> $GITHUB_ENV 33 | 34 | - name: Set Release version 35 | if: startsWith(github.ref, 'refs/tags/v') 36 | run: echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV 37 | 38 | - name: Pack artifacts 39 | run: dotnet pack -p:PackageVersion="${{ env.VERSION }}" -o packages 40 | 41 | - name: Upload artifacts 42 | uses: actions/upload-artifact@v4 43 | with: 44 | name: packages 45 | path: packages/*nupkg 46 | 47 | github: 48 | name: Deploy to GitHub 49 | needs: [pack] 50 | runs-on: ubuntu-24.04 51 | steps: 52 | - name: Download artifacts 53 | uses: actions/download-artifact@v4 54 | with: 55 | name: packages 56 | - name: Push to pkg.github.com 57 | run: | 58 | dotnet nuget push "*.nupkg" \ 59 | --skip-duplicate \ 60 | -k ${{ secrets.GITHUB_TOKEN }} \ 61 | -s https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json 62 | 63 | release: 64 | name: Create GitHub release 65 | needs: [pack] 66 | if: startsWith(github.ref, 'refs/tags/v') 67 | runs-on: ubuntu-24.04 68 | steps: 69 | - name: Checkout 70 | uses: actions/checkout@v4 71 | - name: Download artifacts 72 | uses: actions/download-artifact@v4 73 | with: 74 | name: packages 75 | path: packages 76 | - name: Create GitHub Release 77 | run: gh release create ${{ github.ref_name }} packages/*nupkg 78 | env: 79 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 80 | 81 | nuget: 82 | name: Deploy to NuGet 83 | needs: [release] 84 | if: startsWith(github.ref, 'refs/tags/v') 85 | runs-on: ubuntu-24.04 86 | steps: 87 | - name: Download artifacts 88 | uses: actions/download-artifact@v4 89 | with: 90 | name: packages 91 | - name: Push to nuget.org 92 | run: | 93 | dotnet nuget push "*.nupkg" \ 94 | -k ${{ secrets.NUGET_DEPLOY_KEY }} \ 95 | -s https://api.nuget.org/v3/index.json 96 | -------------------------------------------------------------------------------- /.github/workflows/labeled.yml: -------------------------------------------------------------------------------- 1 | name: PR Labeled 2 | 3 | on: 4 | pull_request: 5 | types: [labeled] 6 | branches: 7 | - "main" 8 | 9 | permissions: 10 | pull-requests: write 11 | contents: write 12 | 13 | jobs: 14 | automerge: 15 | name: Enable auto-merge 16 | runs-on: ubuntu-24.04 17 | if: github.actor == 'phnx47-bot' && contains(github.event.pull_request.labels.*.name, 'sync') 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v4 21 | 22 | - name: Run command 23 | run: gh pr merge -s --auto ${{ github.event.pull_request.number }} 24 | env: 25 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.user 2 | *.opencover.xml 3 | *.orig 4 | *.nupkg 5 | *.snupkg 6 | .idea 7 | .vs 8 | bin 9 | obj 10 | BenchmarkDotNet.Artifacts 11 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12.0 4 | prom-client-net contributors 5 | Copyright © prom-client-net 6 | $(MSBuildProjectName) 7 | prometheus;metrics 8 | icon.png 9 | README.md 10 | MIT 11 | git 12 | true 13 | true 14 | true 15 | true 16 | snupkg 17 | true 18 | nupkgs 19 | $(SolutionDir)$(SolutionName).snk 20 | $(SolutionDir).ruleset 21 | CS1591;NETSDK1138 22 | 23 | 24 | true 25 | 26 | 27 | 28 | 29 | 30 | stylecop.json 31 | 32 | 33 | 34 | 35 | all 36 | runtime; build; native; contentfiles; analyzers 37 | 38 | 39 | all 40 | runtime; build; native; contentfiles; analyzers 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) prom-client-net 4 | Copyright (c) 2015 andrasm 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /Prometheus.Client.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prom-client-net/prom-client/6b86639d8b2db9dbecf2a82989ae28147c805da0/Prometheus.Client.snk -------------------------------------------------------------------------------- /docs/benchmarks/CollectingBenchmarks.md: -------------------------------------------------------------------------------- 1 | # Metric Collecting Benchmarks 2 | 3 | ## Legend 4 | * **Collecting** - system generates 100 counters with 100 unique samples for each and performs scrape 5 | 6 | ## Benchmark Results 7 | 8 |
9 | Counter 10 | 11 | | Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | 12 | |-------------------- |----------:|---------:|---------:|------:|--------:|------:|------:|------:|----------:| 13 | | Collecting_Baseline | 5.877 ms | 1.340 ms | 1.543 ms | 1.00 | 0.00 | - | - | - | 406.96 KB | 14 | | Collecting | 15.325 ms | 4.407 ms | 5.075 ms | 2.72 | 0.83 | - | - | - | 6.57 KB | 15 | 16 |
17 | 18 |
19 | Gauge 20 | 21 | | Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | 22 | |-------------------- |----------:|---------:|---------:|----------:|------:|--------:|------:|------:|------:|----------:| 23 | | Collecting_Baseline | 5.907 ms | 1.401 ms | 1.614 ms | 6.648 ms | 1.00 | 0.00 | - | - | - | 406.93 KB | 24 | | Collecting | 17.514 ms | 5.210 ms | 5.999 ms | 13.524 ms | 3.04 | 0.76 | - | - | - | 6.57 KB | 25 | 26 |
27 | 28 |
29 | Histogram 30 | 31 | | Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | 32 | |-------------------- |----------:|---------:|---------:|------:|--------:|----------:|------:|------:|-----------:| 33 | | Collecting_Baseline | 73.26 ms | 6.958 ms | 8.013 ms | 1.00 | 0.00 | 1000.0000 | - | - | 5405.19 KB | 34 | | Collecting | 192.72 ms | 1.541 ms | 1.775 ms | 2.65 | 0.21 | - | - | - | 7.6 KB | 35 | 36 |
37 | 38 |
39 | Summary 40 | 41 | | Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | 42 | |-------------------- |---------:|---------:|---------:|------:|--------:|------:|------:|------:|-----------:| 43 | | Collecting_Baseline | 17.16 ms | 2.583 ms | 2.975 ms | 1.00 | 0.00 | - | - | - | 1031.99 KB | 44 | | Collecting | 77.84 ms | 1.061 ms | 1.222 ms | 4.64 | 0.61 | - | - | - | 6.66 KB | 45 | 46 |
47 | 48 | ## Conclusions 49 | Current version of the library shows significant less allocation, but much poorer performance with comparison to prometheus-net. Profiling indicates that most of the time spend on converting strings to Utf8 encoding. Upcoming Net5 release includes Uft8String class that should help in this case. 50 | -------------------------------------------------------------------------------- /docs/benchmarks/README.md: -------------------------------------------------------------------------------- 1 | # Comparison Benchmarks 2 | 3 | There are number of benchmarks in Prometheus.Client.Benchmarks.Comparison project dedicated to compare performance and memory allocation of the Prometheus.Client library with prometheus-net. All benchamrks were done for Prometheus.Client v 4.0.0 and prometheus-net 3.6.0. 4 | 5 | ## General structure 6 | 7 | All benchmarks are grouped by usage aspect (for example: metrics creation, collecting, etc). 8 | 9 | Each benchmark has multiple measures: 10 | * **baseline** - it's measure of the scenario implemented by prometheus-net library. 11 | * **array** - implementation by Prometheus.Client library by providing labels as a params array. 12 | * **tuple** - implementation by Prometheus.Client library by providing labels via tuple (new labels API introduced in v4). 13 | * for Counter and Gauge there are additional measures with **Int64** suffix for metrics based on int64 value. 14 | 15 | ## Benchmarks List 16 | 17 | * [General use case](GeneralUseCase.md) 18 | * [Collecting benchmarks](CollectingBenchmarks.md) 19 | * [Metric creation](CreationBenchmarks.md) 20 | * [Sample resolving benchmarks](SampleResolvinfBenchmarks.md) 21 | * [Sample benchmarks](SampleBenchmarks.md) 22 | 23 | ## Hardware 24 | 25 | All benchmarks were created for the following hardware. 26 | 27 | ``` 28 | 29 | BenchmarkDotNet=v0.12.0, OS=ubuntu 20.04 30 | Intel Core i7-8550U CPU 1.80GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores 31 | .NET Core SDK=3.1.101 32 | [Host] : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT 33 | Job-GLKCBR : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT 34 | 35 | ``` 36 | 37 | Fill free to contact us if there is any additional benchmarks that should be done or any errors in the results interpretation. 38 | -------------------------------------------------------------------------------- /docs/benchmarks/SampleResolvingBenchmarks.md: -------------------------------------------------------------------------------- 1 | # Sample Resolving Benchmarks 2 | 3 | ## Legend 4 | * **ResolveLabeled** - performs 10 000 resolving of the sample (call WithLabels method) with 1000 unique combination of labels. 5 | 6 | ## Benchmark Results 7 | 8 |
9 | Counter 10 | 11 | | Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | 12 | |-------------------------- |---------:|----------:|----------:|------:|--------:|------:|------:|------:|----------:| 13 | | ResolveLabeled_Baseline | 3.880 ms | 0.0897 ms | 0.1033 ms | 1.00 | 0.00 | - | - | - | 2000000 B | 14 | | ResolveLabeled_Array | 2.498 ms | 0.0426 ms | 0.0491 ms | 0.64 | 0.02 | - | - | - | 640000 B | 15 | | ResolveLabeled_Tuple | 2.310 ms | 0.0335 ms | 0.0386 ms | 0.60 | 0.02 | - | - | - | - | 16 | | ResolveLabeled_Int64Array | 2.499 ms | 0.0331 ms | 0.0382 ms | 0.64 | 0.02 | - | - | - | 640000 B | 17 | | ResolveLabeled_Int64Tuple | 2.202 ms | 0.0214 ms | 0.0247 ms | 0.57 | 0.02 | - | - | - | - | 18 | 19 |
20 | 21 |
22 | Gauge 23 | 24 | | Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | 25 | |-------------------------- |---------:|----------:|----------:|------:|--------:|------:|------:|------:|----------:| 26 | | ResolveLabeled_Baseline | 3.846 ms | 0.0494 ms | 0.0569 ms | 1.00 | 0.00 | - | - | - | 2000000 B | 27 | | ResolveLabeled_Array | 2.524 ms | 0.0318 ms | 0.0367 ms | 0.66 | 0.01 | - | - | - | 640000 B | 28 | | ResolveLabeled_Tuple | 2.193 ms | 0.0161 ms | 0.0185 ms | 0.57 | 0.01 | - | - | - | - | 29 | | ResolveLabeled_Int64Array | 2.652 ms | 0.0423 ms | 0.0487 ms | 0.69 | 0.02 | - | - | - | 640000 B | 30 | | ResolveLabeled_Int64Tuple | 2.355 ms | 0.0573 ms | 0.0660 ms | 0.61 | 0.02 | - | - | - | - | 31 | 32 |
33 | 34 |
35 | Histogram 36 | 37 | | Method | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated | 38 | |------------------------ |---------:|----------:|----------:|------:|------:|------:|------:|----------:| 39 | | ResolveLabeled_Baseline | 3.955 ms | 0.0681 ms | 0.0784 ms | 1.00 | - | - | - | 2000000 B | 40 | | ResolveLabeled_Array | 2.545 ms | 0.0250 ms | 0.0288 ms | 0.64 | - | - | - | 640000 B | 41 | | ResolveLabeled_Tuples | 2.257 ms | 0.0194 ms | 0.0224 ms | 0.57 | - | - | - | - | 42 | 43 |
44 | 45 |
46 | Summary 47 | 48 | | Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | 49 | |------------------------ |---------:|----------:|----------:|------:|--------:|------:|------:|------:|----------:| 50 | | ResolveLabeled_Baseline | 3.963 ms | 0.1823 ms | 0.2100 ms | 1.00 | 0.00 | - | - | - | 2000000 B | 51 | | ResolveLabeled_Array | 2.429 ms | 0.0478 ms | 0.0551 ms | 0.61 | 0.03 | - | - | - | 640000 B | 52 | | ResolveLabeled_Tuple | 2.338 ms | 0.2923 ms | 0.3366 ms | 0.59 | 0.09 | - | - | - | - | 53 | 54 |
55 | 56 | ## Conclusions 57 | Resolving labels via ValueTuple shows no allocation at all, params array also has some optimization on our side. 58 | -------------------------------------------------------------------------------- /docs/benchmarks/generalcase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prom-client-net/prom-client/6b86639d8b2db9dbecf2a82989ae28147c805da0/docs/benchmarks/generalcase.png -------------------------------------------------------------------------------- /docs/benchmarks/overview.ods: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prom-client-net/prom-client/6b86639d8b2db9dbecf2a82989ae28147c805da0/docs/benchmarks/overview.ods -------------------------------------------------------------------------------- /entity-framework-extensions-sponsor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prom-client-net/prom-client/6b86639d8b2db9dbecf2a82989ae28147c805da0/entity-framework-extensions-sponsor.png -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prom-client-net/prom-client/6b86639d8b2db9dbecf2a82989ae28147c805da0/icon.png -------------------------------------------------------------------------------- /src/Prometheus.Client.Abstractions/Collectors/CollectorConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Prometheus.Client.Collectors; 4 | 5 | public class CollectorConfiguration 6 | { 7 | public CollectorConfiguration(string name) 8 | { 9 | if (string.IsNullOrEmpty(name)) 10 | throw new ArgumentNullException(nameof(name)); 11 | 12 | Name = name; 13 | } 14 | 15 | public string Name { get; } 16 | } 17 | -------------------------------------------------------------------------------- /src/Prometheus.Client.Abstractions/Collectors/ICollector.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Prometheus.Client.MetricsWriter; 3 | 4 | namespace Prometheus.Client.Collectors; 5 | 6 | public interface ICollector 7 | { 8 | CollectorConfiguration Configuration { get; } 9 | 10 | IReadOnlyList MetricNames { get; } 11 | 12 | void Collect(IMetricsWriter writer); 13 | } 14 | -------------------------------------------------------------------------------- /src/Prometheus.Client.Abstractions/Collectors/ICollectorRegistry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Prometheus.Client.MetricsWriter; 5 | 6 | namespace Prometheus.Client.Collectors; 7 | 8 | public interface ICollectorRegistry 9 | { 10 | void Add(ICollector collector); 11 | 12 | bool TryGet(string name, out ICollector collector); 13 | 14 | TCollector GetOrAdd(TConfig config, Func collectorFactory) 15 | where TCollector : class, ICollector 16 | where TConfig : CollectorConfiguration; 17 | 18 | ICollector Remove(string name); 19 | 20 | bool Remove(ICollector collector); 21 | 22 | Task CollectToAsync(IMetricsWriter writer, CancellationToken ct = default); 23 | } 24 | -------------------------------------------------------------------------------- /src/Prometheus.Client.Abstractions/HistogramExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | #if NET6_0_OR_GREATER 3 | using System.Runtime.CompilerServices; 4 | #endif 5 | 6 | namespace Prometheus.Client; 7 | 8 | public static class HistogramExtensions 9 | { 10 | public static void Observe(this IHistogram observer, double val, DateTimeOffset timestamp) 11 | { 12 | observer.Observe(val, timestamp.ToUnixTimeMilliseconds()); 13 | } 14 | 15 | public static void Observe(this IMetricFamily metricFamily, double val) 16 | { 17 | metricFamily.Unlabelled.Observe(val); 18 | } 19 | 20 | public static void Observe(this IMetricFamily metricFamily, double val, long timestamp) 21 | { 22 | metricFamily.Unlabelled.Observe(val, timestamp); 23 | } 24 | 25 | public static void Observe(this IMetricFamily metricFamily, double val, DateTimeOffset timestamp) 26 | { 27 | metricFamily.Unlabelled.Observe(val, timestamp.ToUnixTimeMilliseconds()); 28 | } 29 | 30 | public static void Observe(this IMetricFamily metricFamily, double val) 31 | #if NET6_0_OR_GREATER 32 | where TLabels : struct, ITuple, IEquatable 33 | #else 34 | where TLabels : struct, IEquatable 35 | #endif 36 | { 37 | metricFamily.Unlabelled.Observe(val); 38 | } 39 | 40 | public static void Observe(this IMetricFamily metricFamily, double val, long timestamp) 41 | #if NET6_0_OR_GREATER 42 | where TLabels : struct, ITuple, IEquatable 43 | #else 44 | where TLabels : struct, IEquatable 45 | #endif 46 | { 47 | metricFamily.Unlabelled.Observe(val, timestamp); 48 | } 49 | 50 | public static void Observe(this IMetricFamily metricFamily, double val, DateTimeOffset timestamp) 51 | #if NET6_0_OR_GREATER 52 | where TLabels : struct, ITuple, IEquatable 53 | #else 54 | where TLabels : struct, IEquatable 55 | #endif 56 | { 57 | metricFamily.Unlabelled.Observe(val, timestamp.ToUnixTimeMilliseconds()); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Prometheus.Client.Abstractions/HistogramState.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Prometheus.Client; 4 | 5 | public readonly struct HistogramState 6 | { 7 | public HistogramState(long count, double sum, IReadOnlyList> buckets) 8 | { 9 | Count = count; 10 | Sum = sum; 11 | Buckets = buckets; 12 | } 13 | 14 | public long Count { get; } 15 | 16 | public double Sum { get; } 17 | 18 | public IReadOnlyList> Buckets { get; } 19 | } 20 | -------------------------------------------------------------------------------- /src/Prometheus.Client.Abstractions/ICounter.cs: -------------------------------------------------------------------------------- 1 | namespace Prometheus.Client; 2 | 3 | /// 4 | /// Counter metric type 5 | /// 6 | /// https://prometheus.io/docs/concepts/metric_types/#counter 7 | /// 8 | /// 9 | public interface ICounter : IMetric 10 | where T : struct 11 | { 12 | void Inc(); 13 | 14 | void Inc(T increment); 15 | 16 | void Inc(T increment, long? timestamp); 17 | 18 | void IncTo(T value); 19 | 20 | void IncTo(T value, long? timestamp); 21 | } 22 | 23 | public interface ICounter : ICounter 24 | { 25 | } 26 | -------------------------------------------------------------------------------- /src/Prometheus.Client.Abstractions/IGauge.cs: -------------------------------------------------------------------------------- 1 | namespace Prometheus.Client; 2 | 3 | /// 4 | /// Gauge metric type 5 | /// 6 | /// https://prometheus.io/docs/concepts/metric_types/#gauge 7 | /// 8 | /// 9 | public interface IGauge : IMetric 10 | where T : struct 11 | { 12 | void Inc(); 13 | 14 | void Inc(T increment); 15 | 16 | void Inc(T increment, long? timestamp); 17 | 18 | void IncTo(T value); 19 | 20 | void IncTo(T value, long? timestamp); 21 | 22 | void Set(T val); 23 | 24 | void Set(T val, long? timestamp); 25 | 26 | void Dec(); 27 | 28 | void Dec(T decrement); 29 | 30 | void Dec(T decrement, long? timestamp); 31 | 32 | void DecTo(T value); 33 | 34 | void DecTo(T value, long? timestamp); 35 | } 36 | 37 | public interface IGauge : IGauge 38 | { 39 | } 40 | -------------------------------------------------------------------------------- /src/Prometheus.Client.Abstractions/IHistogram.cs: -------------------------------------------------------------------------------- 1 | namespace Prometheus.Client; 2 | 3 | /// 4 | /// Histogram metric type 5 | /// 6 | /// https://prometheus.io/docs/concepts/metric_types/#histogram 7 | /// 8 | /// 9 | public interface IHistogram : IMetric, IValueObserver 10 | { 11 | } 12 | -------------------------------------------------------------------------------- /src/Prometheus.Client.Abstractions/IMetric.cs: -------------------------------------------------------------------------------- 1 | namespace Prometheus.Client; 2 | 3 | public interface IMetric : IMetric 4 | where TState : struct 5 | { 6 | TState Value { get; } 7 | 8 | void Reset(); 9 | } 10 | 11 | public interface IMetric 12 | { 13 | } 14 | -------------------------------------------------------------------------------- /src/Prometheus.Client.Abstractions/IMetricFamily.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | #if NET6_0_OR_GREATER 4 | using System.Runtime.CompilerServices; 5 | #endif 6 | 7 | namespace Prometheus.Client; 8 | 9 | public interface IMetricFamily 10 | where TMetric : IMetric 11 | { 12 | string Name { get; } 13 | TMetric Unlabelled { get; } 14 | IEnumerable, TMetric>> Labelled { get; } 15 | TMetric WithLabels(params string[] labels); 16 | TMetric RemoveLabelled(params string[] labels); 17 | IReadOnlyList LabelNames { get; } 18 | } 19 | 20 | public interface IMetricFamily 21 | where TMetric : IMetric 22 | #if NET6_0_OR_GREATER 23 | where TLabels : struct, ITuple, IEquatable 24 | #else 25 | where TLabels : struct, IEquatable 26 | #endif 27 | { 28 | string Name { get; } 29 | TMetric Unlabelled { get; } 30 | IEnumerable> Labelled { get; } 31 | TMetric WithLabels(TLabels labels); 32 | TMetric RemoveLabelled(TLabels labels); 33 | TLabels LabelNames { get; } 34 | } 35 | -------------------------------------------------------------------------------- /src/Prometheus.Client.Abstractions/ISummary.cs: -------------------------------------------------------------------------------- 1 | namespace Prometheus.Client; 2 | 3 | /// 4 | /// Summary metric type 5 | /// 6 | /// https://prometheus.io/docs/concepts/metric_types/#summary 7 | /// 8 | /// 9 | public interface ISummary : IMetric, IValueObserver 10 | { 11 | } 12 | -------------------------------------------------------------------------------- /src/Prometheus.Client.Abstractions/IUntyped.cs: -------------------------------------------------------------------------------- 1 | namespace Prometheus.Client; 2 | 3 | /// 4 | /// Untyped metric type 5 | /// 6 | public interface IUntyped : IMetric 7 | { 8 | void Set(double val); 9 | 10 | void Set(double val, long? timestamp); 11 | } 12 | -------------------------------------------------------------------------------- /src/Prometheus.Client.Abstractions/IValueObserver.cs: -------------------------------------------------------------------------------- 1 | namespace Prometheus.Client; 2 | 3 | public interface IValueObserver 4 | { 5 | void Observe(double val); 6 | 7 | void Observe(double val, long? timestamp); 8 | } 9 | -------------------------------------------------------------------------------- /src/Prometheus.Client.Abstractions/MetricFamilyExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Prometheus.Client; 4 | 5 | public static class MetricFamilyExtensions 6 | { 7 | /// 8 | /// Extension method to workaround lack of single item tuple support 9 | /// 10 | public static TMetric WithLabels(this IMetricFamily> metricFamily, string label) 11 | where TMetric : IMetric 12 | { 13 | return metricFamily.WithLabels(ValueTuple.Create(label)); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Prometheus.Client.Abstractions/MetricType.cs: -------------------------------------------------------------------------------- 1 | namespace Prometheus.Client; 2 | 3 | public enum MetricType 4 | { 5 | Counter = 0, 6 | 7 | Gauge = 1, 8 | 9 | Summary = 2, 10 | 11 | Untyped = 3, 12 | 13 | Histogram = 4 14 | } 15 | -------------------------------------------------------------------------------- /src/Prometheus.Client.Abstractions/MetricsWriter/ILabelWriter.cs: -------------------------------------------------------------------------------- 1 | namespace Prometheus.Client.MetricsWriter; 2 | 3 | public interface ILabelWriter 4 | { 5 | ILabelWriter WriteLabel(string name, string value); 6 | 7 | ISampleWriter EndLabels(); 8 | } 9 | -------------------------------------------------------------------------------- /src/Prometheus.Client.Abstractions/MetricsWriter/IMetricsWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace Prometheus.Client.MetricsWriter; 6 | 7 | public interface IMetricsWriter : IDisposable 8 | { 9 | IMetricsWriter StartMetric(string metricName); 10 | 11 | IMetricsWriter WriteHelp(string help); 12 | 13 | IMetricsWriter WriteType(MetricType metricType); 14 | 15 | ISampleWriter StartSample(string suffix = ""); 16 | 17 | IMetricsWriter EndMetric(); 18 | 19 | Task CloseWriterAsync(CancellationToken ct = default); 20 | 21 | Task FlushAsync(CancellationToken ct = default); 22 | } 23 | -------------------------------------------------------------------------------- /src/Prometheus.Client.Abstractions/MetricsWriter/ISampleWriter.cs: -------------------------------------------------------------------------------- 1 | namespace Prometheus.Client.MetricsWriter; 2 | 3 | public interface ISampleWriter 4 | { 5 | ILabelWriter StartLabels(); 6 | 7 | ISampleWriter WriteValue(double value); 8 | 9 | ISampleWriter WriteTimestamp(long timestamp); 10 | 11 | IMetricsWriter EndSample(); 12 | } 13 | -------------------------------------------------------------------------------- /src/Prometheus.Client.Abstractions/MetricsWriter/MetricsWriterExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Prometheus.Client.MetricsWriter; 5 | 6 | public static class MetricsWriterExtensions 7 | { 8 | public static IMetricsWriter WriteSample( 9 | this IMetricsWriter writer, 10 | double value, 11 | string suffix = "", 12 | IReadOnlyList labelNames = null, 13 | IReadOnlyList labelValues = null, 14 | long? timestamp = null) 15 | { 16 | var sampleWriter = writer.StartSample(suffix); 17 | if ((labelValues != null) && (labelValues.Count > 0)) 18 | { 19 | var labelWriter = sampleWriter.StartLabels(); 20 | labelWriter.WriteLabels(labelNames, labelValues); 21 | labelWriter.EndLabels(); 22 | } 23 | 24 | sampleWriter.WriteValue(value); 25 | if (timestamp.HasValue) 26 | sampleWriter.WriteTimestamp(timestamp.Value); 27 | 28 | sampleWriter.EndSample(); 29 | return writer; 30 | } 31 | 32 | public static IMetricsWriter WriteMetricHeader( 33 | this IMetricsWriter writer, 34 | string metricName, 35 | MetricType metricType, 36 | string help = "") 37 | { 38 | writer.StartMetric(metricName); 39 | if (!string.IsNullOrEmpty(help)) 40 | writer.WriteHelp(help); 41 | 42 | writer.WriteType(metricType); 43 | return writer; 44 | } 45 | 46 | public static ILabelWriter WriteLabels( 47 | this ILabelWriter labelWriter, 48 | IReadOnlyList labelNames, 49 | IReadOnlyList labelValues 50 | ) 51 | { 52 | if (labelNames == null) 53 | throw new ArgumentNullException(nameof(labelNames)); 54 | 55 | if (labelValues == null) 56 | throw new ArgumentNullException(nameof(labelValues)); 57 | 58 | if (labelNames.Count != labelValues.Count) 59 | { 60 | throw new InvalidOperationException("Label names and values does not match"); 61 | } 62 | 63 | for (int i = 0; i < labelNames.Count; i++) 64 | labelWriter.WriteLabel(labelNames[i], labelValues[i]); 65 | 66 | return labelWriter; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Prometheus.Client.Abstractions/Prometheus.Client.Abstractions.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Prometheus.Client 4 | net462;net47;netstandard2.0;net6.0 5 | Abstractions for Prometheus.Client 6 | https://github.com/prom-client-net/prom-client 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Prometheus.Client.Abstractions/QuantileEpsilonPair.cs: -------------------------------------------------------------------------------- 1 | namespace Prometheus.Client; 2 | 3 | public readonly struct QuantileEpsilonPair 4 | { 5 | public QuantileEpsilonPair(double quantile, double epsilon) 6 | { 7 | Quantile = quantile; 8 | Epsilon = epsilon; 9 | } 10 | 11 | public double Quantile { get; } 12 | public double Epsilon { get; } 13 | } 14 | -------------------------------------------------------------------------------- /src/Prometheus.Client.Abstractions/SummaryExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | #if NET6_0_OR_GREATER 4 | using System.Runtime.CompilerServices; 5 | #endif 6 | 7 | namespace Prometheus.Client; 8 | 9 | public static class SummaryExtensions 10 | { 11 | public static void Observe(this ISummary observer, double val, DateTimeOffset timestamp) 12 | { 13 | observer.Observe(val, timestamp.ToUnixTimeMilliseconds()); 14 | } 15 | 16 | public static void Observe(this IMetricFamily metricFamily, double val) 17 | { 18 | metricFamily.Unlabelled.Observe(val); 19 | } 20 | 21 | public static void Observe(this IMetricFamily metricFamily, double val, long timestamp) 22 | { 23 | metricFamily.Unlabelled.Observe(val, timestamp); 24 | } 25 | 26 | public static void Observe(this IMetricFamily metricFamily, double val, DateTimeOffset timestamp) 27 | { 28 | metricFamily.Unlabelled.Observe(val, timestamp.ToUnixTimeMilliseconds()); 29 | } 30 | 31 | public static void Observe(this IMetricFamily metricFamily, double val) 32 | #if NET6_0_OR_GREATER 33 | where TLabels : struct, ITuple, IEquatable 34 | #else 35 | where TLabels : struct, IEquatable 36 | #endif 37 | { 38 | metricFamily.Unlabelled.Observe(val); 39 | } 40 | 41 | public static void Observe(this IMetricFamily metricFamily, double val, long timestamp) 42 | #if NET6_0_OR_GREATER 43 | where TLabels : struct, ITuple, IEquatable 44 | #else 45 | where TLabels : struct, IEquatable 46 | #endif 47 | { 48 | metricFamily.Unlabelled.Observe(val, timestamp); 49 | } 50 | 51 | public static void Observe(this IMetricFamily metricFamily, double val, DateTimeOffset timestamp) 52 | #if NET6_0_OR_GREATER 53 | where TLabels : struct, ITuple, IEquatable 54 | #else 55 | where TLabels : struct, IEquatable 56 | #endif 57 | { 58 | metricFamily.Unlabelled.Observe(val, timestamp.ToUnixTimeMilliseconds()); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Prometheus.Client.Abstractions/SummaryState.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Prometheus.Client; 4 | 5 | public readonly struct SummaryState 6 | { 7 | public SummaryState(long count, double sum, IReadOnlyList> quantiles) 8 | { 9 | Count = count; 10 | Sum = sum; 11 | Quantiles = quantiles; 12 | } 13 | 14 | public long Count { get; } 15 | 16 | public double Sum { get; } 17 | 18 | public IReadOnlyList> Quantiles { get; } 19 | } 20 | -------------------------------------------------------------------------------- /src/Prometheus.Client.Abstractions/UntypedExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | #if NET6_0_OR_GREATER 3 | using System.Runtime.CompilerServices; 4 | #endif 5 | 6 | namespace Prometheus.Client; 7 | 8 | public static class UntypedExtensions 9 | { 10 | public static void Set(this IUntyped untyped, double val, DateTimeOffset timestamp) 11 | { 12 | untyped.Set(val, timestamp.ToUnixTimeMilliseconds()); 13 | } 14 | 15 | public static void Set(this IMetricFamily metricFamily, double val) 16 | { 17 | metricFamily.Unlabelled.Set(val); 18 | } 19 | 20 | public static void Set(this IMetricFamily metricFamily, double val, int timestamp) 21 | { 22 | metricFamily.Unlabelled.Set(val, timestamp); 23 | } 24 | 25 | public static void Set(this IMetricFamily metricFamily, double val, DateTimeOffset timestamp) 26 | { 27 | metricFamily.Unlabelled.Set(val, timestamp); 28 | } 29 | 30 | public static void Set(this IMetricFamily metricFamily, double val) 31 | #if NET6_0_OR_GREATER 32 | where TLabels : struct, ITuple, IEquatable 33 | #else 34 | where TLabels : struct, IEquatable 35 | #endif 36 | { 37 | metricFamily.Unlabelled.Set(val); 38 | } 39 | 40 | public static void Set(this IMetricFamily metricFamily, double val, int timestamp) 41 | #if NET6_0_OR_GREATER 42 | where TLabels : struct, ITuple, IEquatable 43 | #else 44 | where TLabels : struct, IEquatable 45 | #endif 46 | { 47 | metricFamily.Unlabelled.Set(val, timestamp); 48 | } 49 | 50 | public static void Set(this IMetricFamily metricFamily, double val, DateTimeOffset timestamp) 51 | #if NET6_0_OR_GREATER 52 | where TLabels : struct, ITuple, IEquatable 53 | #else 54 | where TLabels : struct, IEquatable 55 | #endif 56 | { 57 | metricFamily.Unlabelled.Set(val, timestamp); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Prometheus.Client.Abstractions/ValueObserverExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Runtime.CompilerServices; 4 | using System.Threading.Tasks; 5 | 6 | namespace Prometheus.Client; 7 | 8 | public static class ValueObserverExtensions 9 | { 10 | public static void ObserveDuration(this IValueObserver observer, Action method) 11 | { 12 | var ts = Stopwatch.GetTimestamp(); 13 | try 14 | { 15 | method(); 16 | } 17 | finally 18 | { 19 | observer.Observe(GetDuration(ts)); 20 | } 21 | } 22 | 23 | public static T ObserveDuration(this IValueObserver observer, Func method) 24 | { 25 | var ts = Stopwatch.GetTimestamp(); 26 | try 27 | { 28 | return method(); 29 | } 30 | finally 31 | { 32 | observer.Observe(GetDuration(ts)); 33 | } 34 | } 35 | 36 | public static async Task ObserveDurationAsync(this IValueObserver observer, Func method) 37 | { 38 | var ts = Stopwatch.GetTimestamp(); 39 | try 40 | { 41 | await method().ConfigureAwait(false); 42 | } 43 | finally 44 | { 45 | observer.Observe(GetDuration(ts)); 46 | } 47 | } 48 | 49 | public static async Task ObserveDurationAsync(this IValueObserver observer, Func> method) 50 | { 51 | var ts = Stopwatch.GetTimestamp(); 52 | try 53 | { 54 | return await method().ConfigureAwait(false); 55 | } 56 | finally 57 | { 58 | observer.Observe(GetDuration(ts)); 59 | } 60 | } 61 | 62 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 63 | private static double GetDuration(long startTs) 64 | { 65 | var elapsed = Stopwatch.GetTimestamp() - startTs; 66 | var frequency = (double)Stopwatch.Frequency; 67 | 68 | return elapsed / frequency; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Prometheus.Client/Collectors/CollectorRegistryExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Prometheus.Client.Collectors; 4 | 5 | public static class CollectorRegistryExtensions 6 | { 7 | public static void MoveTo(this ICollectorRegistry registry, string collectorName, ICollectorRegistry destination) 8 | { 9 | if (destination == null) 10 | throw new ArgumentNullException(nameof(destination)); 11 | 12 | var collector = registry.Remove(collectorName); 13 | if (collector == null) 14 | throw new ArgumentOutOfRangeException(nameof(collectorName), collectorName, "Collector does not exist in the source registry"); 15 | 16 | destination.Add(collector); 17 | } 18 | 19 | public static void CopyTo(this ICollectorRegistry registry, string collectorName, ICollectorRegistry destination) 20 | { 21 | if (destination == null) 22 | throw new ArgumentNullException(nameof(destination)); 23 | 24 | if (!registry.TryGet(collectorName, out var collector)) 25 | { 26 | throw new ArgumentOutOfRangeException(nameof(collectorName), collectorName, "Collector does not exist in the source registry"); 27 | } 28 | 29 | destination.Add(collector); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Prometheus.Client/Collectors/DefaultCollectors.cs: -------------------------------------------------------------------------------- 1 | using Prometheus.Client.Collectors.DotNetStats; 2 | using Prometheus.Client.Collectors.ProcessStats; 3 | 4 | namespace Prometheus.Client.Collectors; 5 | 6 | public static class DefaultCollectors 7 | { 8 | public static ICollectorRegistry UseDefaultCollectors(this ICollectorRegistry registry) 9 | { 10 | return UseDefaultCollectors(registry, string.Empty); 11 | } 12 | 13 | public static ICollectorRegistry UseDefaultCollectors(this ICollectorRegistry registry, string prefixName) 14 | { 15 | registry.UseDotNetStats(prefixName); 16 | registry.UseProcessStats(prefixName); 17 | 18 | return registry; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Prometheus.Client/Collectors/DotNetStats/CollectorRegistryExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Prometheus.Client.Collectors.DotNetStats; 4 | 5 | public static class CollectorRegistryExtensions 6 | { 7 | public static ICollectorRegistry UseDotNetStats(this ICollectorRegistry registry) 8 | { 9 | return UseDotNetStats(registry, string.Empty); 10 | } 11 | 12 | public static ICollectorRegistry UseDotNetStats(this ICollectorRegistry registry, string prefixName) 13 | { 14 | registry.Add(new GCCollectionCountCollector(prefixName)); 15 | registry.Add(new GCTotalMemoryCollector(prefixName)); 16 | 17 | return registry; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Prometheus.Client/Collectors/DotNetStats/GCCollectionCountCollector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Prometheus.Client.MetricsWriter; 4 | 5 | namespace Prometheus.Client.Collectors.DotNetStats; 6 | 7 | /// 8 | /// Collector for GC collection count. 9 | /// 10 | public class GCCollectionCountCollector : ICollector 11 | { 12 | private const string _help = "GC collection count"; 13 | private readonly string _name; 14 | private readonly string[] _genNames; 15 | 16 | public CollectorConfiguration Configuration { get; } 17 | 18 | public IReadOnlyList MetricNames { get; } 19 | 20 | public GCCollectionCountCollector() 21 | : this(string.Empty) 22 | { 23 | } 24 | 25 | public GCCollectionCountCollector(string prefixName) 26 | { 27 | _name = prefixName + "dotnet_collection_count_total"; 28 | Configuration = new CollectorConfiguration(nameof(GCCollectionCountCollector)); 29 | MetricNames = [_name]; 30 | _genNames = new string[GC.MaxGeneration + 1]; 31 | for (var gen = 0; gen <= GC.MaxGeneration; gen++) 32 | _genNames[gen] = gen.ToString(); 33 | } 34 | 35 | public void Collect(IMetricsWriter writer) 36 | { 37 | writer.WriteMetricHeader(_name, MetricType.Counter, _help); 38 | 39 | for (var gen = 0; gen <= GC.MaxGeneration; gen++) 40 | { 41 | writer.StartSample() 42 | .StartLabels() 43 | .WriteLabel("generation", _genNames[gen]) 44 | .EndLabels() 45 | .WriteValue(GC.CollectionCount(gen)) 46 | .EndSample(); 47 | } 48 | 49 | writer.EndMetric(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Prometheus.Client/Collectors/DotNetStats/GCTotalMemoryCollector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Prometheus.Client.MetricsWriter; 4 | 5 | namespace Prometheus.Client.Collectors.DotNetStats; 6 | 7 | /// 8 | /// Collector for GC total known allocated memory. 9 | /// 10 | public class GCTotalMemoryCollector : ICollector 11 | { 12 | private const string _help = "Total known allocated memory in bytes"; 13 | private readonly string _name; 14 | 15 | public CollectorConfiguration Configuration { get; } 16 | public IReadOnlyList MetricNames { get; } 17 | 18 | public GCTotalMemoryCollector() 19 | : this(string.Empty) 20 | { 21 | } 22 | 23 | public GCTotalMemoryCollector(string prefixName) 24 | { 25 | _name = prefixName + "dotnet_total_memory_bytes"; 26 | 27 | Configuration = new CollectorConfiguration(nameof(GCTotalMemoryCollector)); 28 | MetricNames = [_name]; 29 | } 30 | 31 | public void Collect(IMetricsWriter writer) 32 | { 33 | writer.WriteMetricHeader(_name, MetricType.Gauge, _help); 34 | writer.WriteSample(GC.GetTotalMemory(false)); 35 | writer.EndMetric(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Prometheus.Client/Collectors/MetricWriterWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Prometheus.Client.MetricsWriter; 5 | 6 | namespace Prometheus.Client.Collectors; 7 | 8 | /// 9 | /// This helper class wraps provided base writer and ensure that each metric 10 | /// uses only names exposed through MetricNames property to avoid names conflict 11 | /// 12 | internal sealed class MetricWriterWrapper : IMetricsWriter 13 | { 14 | private readonly IMetricsWriter _baseWriter; 15 | private ICollector _currentCollector; 16 | 17 | public MetricWriterWrapper(IMetricsWriter baseWriter) 18 | { 19 | _baseWriter = baseWriter; 20 | } 21 | 22 | public void SetCurrentCollector(ICollector collector) 23 | { 24 | _currentCollector = collector; 25 | } 26 | 27 | public Task FlushAsync(CancellationToken ct = default) 28 | { 29 | return _baseWriter.FlushAsync(ct); 30 | } 31 | 32 | public Task CloseWriterAsync(CancellationToken ct = default) 33 | { 34 | return _baseWriter.CloseWriterAsync(ct); 35 | } 36 | 37 | public void Dispose() 38 | { 39 | _currentCollector = null; 40 | _baseWriter.Dispose(); 41 | } 42 | 43 | public IMetricsWriter StartMetric(string metricName) 44 | { 45 | for (var i = 0; i < _currentCollector.MetricNames.Count; i++) 46 | { 47 | if (string.Equals(metricName, _currentCollector.MetricNames[i], StringComparison.Ordinal)) 48 | { 49 | return _baseWriter.StartMetric(metricName); 50 | } 51 | } 52 | 53 | throw new InvalidOperationException("Cannot use a metric name other than reserved by collector."); 54 | } 55 | 56 | public ISampleWriter StartSample(string suffix = "") 57 | { 58 | return _baseWriter.StartSample(suffix); 59 | } 60 | 61 | public IMetricsWriter WriteHelp(string help) 62 | { 63 | return _baseWriter.WriteHelp(help); 64 | } 65 | 66 | public IMetricsWriter WriteType(MetricType metricType) 67 | { 68 | return _baseWriter.WriteType(metricType); 69 | } 70 | 71 | public IMetricsWriter EndMetric() 72 | { 73 | return _baseWriter.EndMetric(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Prometheus.Client/Collectors/ProcessStats/CollectorRegistryExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace Prometheus.Client.Collectors.ProcessStats; 4 | 5 | public static class CollectorRegistryExtensions 6 | { 7 | public static ICollectorRegistry UseProcessStats(this ICollectorRegistry registry) 8 | { 9 | return UseProcessStats(registry, string.Empty); 10 | } 11 | 12 | public static ICollectorRegistry UseProcessStats(this ICollectorRegistry registry, string prefixName) 13 | { 14 | registry.Add(new ProcessCollector(Process.GetCurrentProcess(), prefixName)); 15 | 16 | return registry; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Prometheus.Client/Counter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.CompilerServices; 4 | using Prometheus.Client.MetricsWriter; 5 | 6 | namespace Prometheus.Client; 7 | 8 | /// 9 | public sealed class Counter : MetricBase, ICounter 10 | { 11 | private ThreadSafeDouble _value; 12 | 13 | public Counter(MetricConfiguration configuration, IReadOnlyList labels) 14 | : base(configuration, labels) 15 | { 16 | } 17 | 18 | public void Inc() 19 | { 20 | IncInternal(1.0D, null); 21 | } 22 | 23 | public void Inc(double increment) 24 | { 25 | Inc(increment, null); 26 | } 27 | 28 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 29 | public void Inc(double increment, long? timestamp) 30 | { 31 | if (ThreadSafeDouble.IsNaN(increment)) 32 | return; 33 | 34 | if (increment < 0.0D) 35 | throw new ArgumentOutOfRangeException(nameof(increment), "Counter cannot go down"); 36 | 37 | IncInternal(increment, timestamp); 38 | } 39 | 40 | public void IncTo(double value) 41 | => IncTo(value, null); 42 | 43 | public void IncTo(double value, long? timestamp) 44 | { 45 | _value.IncTo(value); 46 | TrackObservation(timestamp); 47 | } 48 | 49 | public double Value => _value.Value; 50 | 51 | public void Reset() 52 | { 53 | _value.Value = default; 54 | } 55 | 56 | protected internal override void Collect(IMetricsWriter writer) 57 | { 58 | writer.WriteSample(Value, string.Empty, Configuration.LabelNames, LabelValues, Timestamp); 59 | } 60 | 61 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 62 | private void IncInternal(double increment, long? timestamp) 63 | { 64 | _value.Add(increment); 65 | TrackObservation(timestamp); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Prometheus.Client/CounterInt64.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.CompilerServices; 4 | using Prometheus.Client.MetricsWriter; 5 | 6 | namespace Prometheus.Client; 7 | 8 | public sealed class CounterInt64 : MetricBase, ICounter 9 | { 10 | private ThreadSafeLong _value; 11 | 12 | public CounterInt64(MetricConfiguration configuration, IReadOnlyList labels) 13 | : base(configuration, labels) 14 | { 15 | } 16 | 17 | public void Inc() 18 | { 19 | IncInternal(1, null); 20 | } 21 | 22 | public void Inc(long increment) 23 | { 24 | Inc(increment, null); 25 | } 26 | 27 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 28 | public void Inc(long increment, long? timestamp) 29 | { 30 | if (increment < 0) 31 | throw new ArgumentOutOfRangeException(nameof(increment), "Counter cannot go down"); 32 | 33 | IncInternal(increment, timestamp); 34 | } 35 | 36 | public void IncTo(long value) 37 | => IncTo(value, null); 38 | 39 | public void IncTo(long value, long? timestamp) 40 | { 41 | _value.IncTo(value); 42 | TrackObservation(timestamp); 43 | } 44 | 45 | public long Value => _value.Value; 46 | 47 | public void Reset() 48 | { 49 | _value.Value = default; 50 | } 51 | 52 | protected internal override void Collect(IMetricsWriter writer) 53 | { 54 | writer.WriteSample(Value, string.Empty, Configuration.LabelNames, LabelValues, Timestamp); 55 | } 56 | 57 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 58 | private void IncInternal(long increment, long? timestamp) 59 | { 60 | _value.Add(increment); 61 | TrackObservation(timestamp); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Prometheus.Client/Gauge.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Runtime.CompilerServices; 3 | using Prometheus.Client.MetricsWriter; 4 | 5 | namespace Prometheus.Client; 6 | 7 | /// 8 | public sealed class Gauge : MetricBase, IGauge 9 | { 10 | private ThreadSafeDouble _value; 11 | 12 | internal Gauge(MetricConfiguration configuration, IReadOnlyList labels) 13 | : base(configuration, labels) 14 | { 15 | } 16 | 17 | public void Inc() 18 | { 19 | IncInternal(1.0D, null); 20 | } 21 | 22 | public void Inc(double increment) 23 | { 24 | Inc(increment, null); 25 | } 26 | 27 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 28 | public void Inc(double increment, long? timestamp) 29 | { 30 | if (ThreadSafeDouble.IsNaN(increment)) 31 | return; 32 | 33 | IncInternal(increment, timestamp); 34 | } 35 | 36 | public void IncTo(double value) 37 | => IncTo(value, null); 38 | 39 | public void IncTo(double value, long? timestamp) 40 | { 41 | _value.IncTo(value); 42 | TrackObservation(timestamp); 43 | } 44 | 45 | public void Set(double val) 46 | { 47 | Set(val, null); 48 | } 49 | 50 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 51 | public void Set(double val, long? timestamp) 52 | { 53 | _value.Value = val; 54 | TrackObservation(timestamp); 55 | } 56 | 57 | public void Dec() 58 | { 59 | IncInternal(-1.0D, null); 60 | } 61 | 62 | public void Dec(double decrement) 63 | { 64 | Dec(decrement, null); 65 | } 66 | 67 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 68 | public void Dec(double decrement, long? timestamp) 69 | { 70 | if (ThreadSafeDouble.IsNaN(decrement)) 71 | return; 72 | 73 | IncInternal(-decrement, timestamp); 74 | } 75 | 76 | public void DecTo(double value) 77 | => DecTo(value, null); 78 | 79 | public void DecTo(double value, long? timestamp) 80 | { 81 | _value.DecTo(value); 82 | TrackObservation(timestamp); 83 | } 84 | 85 | public double Value => _value.Value; 86 | 87 | public void Reset() 88 | { 89 | _value.Value = default; 90 | } 91 | 92 | protected internal override void Collect(IMetricsWriter writer) 93 | { 94 | writer.WriteSample(Value, string.Empty, Configuration.LabelNames, LabelValues, Timestamp); 95 | } 96 | 97 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 98 | private void IncInternal(double increment, long? timestamp) 99 | { 100 | _value.Add(increment); 101 | TrackObservation(timestamp); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/Prometheus.Client/GaugeInt64.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Runtime.CompilerServices; 3 | using Prometheus.Client.MetricsWriter; 4 | 5 | namespace Prometheus.Client; 6 | 7 | /// 8 | public sealed class GaugeInt64 : MetricBase, IGauge 9 | { 10 | private ThreadSafeLong _value; 11 | 12 | internal GaugeInt64(MetricConfiguration configuration, IReadOnlyList labels) 13 | : base(configuration, labels) 14 | { 15 | } 16 | 17 | public void Inc() 18 | { 19 | IncInternal(1, null); 20 | } 21 | 22 | public void Inc(long increment) 23 | { 24 | Inc(increment, null); 25 | } 26 | 27 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 28 | public void Inc(long increment, long? timestamp) 29 | { 30 | IncInternal(increment, timestamp); 31 | } 32 | 33 | public void IncTo(long value) 34 | => IncTo(value, null); 35 | 36 | public void IncTo(long value, long? timestamp) 37 | { 38 | _value.IncTo(value); 39 | TrackObservation(timestamp); 40 | } 41 | 42 | public void Set(long val) 43 | { 44 | Set(val, null); 45 | } 46 | 47 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 48 | public void Set(long val, long? timestamp) 49 | { 50 | _value.Value = val; 51 | TrackObservation(timestamp); 52 | } 53 | 54 | public void Dec() 55 | { 56 | IncInternal(-1, null); 57 | } 58 | 59 | public void Dec(long decrement) 60 | { 61 | Dec(decrement, null); 62 | } 63 | 64 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 65 | public void Dec(long decrement, long? timestamp) 66 | { 67 | IncInternal(-decrement, timestamp); 68 | } 69 | 70 | public void DecTo(long value) 71 | => DecTo(value, null); 72 | 73 | public void DecTo(long value, long? timestamp) 74 | { 75 | _value.DecTo(value); 76 | TrackObservation(timestamp); 77 | } 78 | 79 | public long Value => _value.Value; 80 | 81 | public void Reset() 82 | { 83 | _value.Value = default; 84 | } 85 | 86 | protected internal override void Collect(IMetricsWriter writer) 87 | { 88 | writer.WriteSample(Value, string.Empty, Configuration.LabelNames, LabelValues, Timestamp); 89 | } 90 | 91 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 92 | private void IncInternal(long increment, long? timestamp) 93 | { 94 | _value.Add(increment); 95 | TrackObservation(timestamp); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Prometheus.Client/Histogram.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Runtime.CompilerServices; 3 | using Prometheus.Client.HistogramImpl; 4 | using Prometheus.Client.MetricsWriter; 5 | 6 | namespace Prometheus.Client; 7 | 8 | /// 9 | public sealed class Histogram : MetricBase, IHistogram 10 | { 11 | private readonly IHistogramBucketStore _bucketsStore; 12 | private ThreadSafeDouble _sum = new ThreadSafeDouble(0.0D); 13 | 14 | public Histogram(HistogramConfiguration configuration, IReadOnlyList labels) 15 | : base(configuration, labels) 16 | { 17 | if (configuration.Buckets.Length >= 25) 18 | _bucketsStore = new HistogramHighBucketsStore(configuration.Buckets); 19 | else 20 | _bucketsStore = new HistogramLowBucketsStore(configuration.Buckets); 21 | } 22 | 23 | public HistogramState Value => ForkState(); 24 | 25 | public void Reset() 26 | { 27 | _bucketsStore.Reset(); 28 | _sum.Value = default; 29 | } 30 | 31 | public void Observe(double val) 32 | { 33 | Observe(val, null); 34 | } 35 | 36 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 37 | public void Observe(double val, long? timestamp) 38 | { 39 | if (ThreadSafeDouble.IsNaN(val)) 40 | return; 41 | 42 | _bucketsStore.Observe(val); 43 | _sum.Add(val); 44 | TrackObservation(timestamp); 45 | } 46 | 47 | protected internal override void Collect(IMetricsWriter writer) 48 | { 49 | long cumulativeCount = 0L; 50 | 51 | for (int i = 0; i < _bucketsStore.Buckets.Length; i++) 52 | { 53 | cumulativeCount += _bucketsStore.Buckets[i].Value; 54 | var bucketSample = writer.StartSample("_bucket"); 55 | var labelWriter = bucketSample.StartLabels(); 56 | if (LabelValues != null && LabelValues.Count > 0) 57 | labelWriter.WriteLabels(Configuration.LabelNames, LabelValues); 58 | 59 | string labelValue = Configuration.FormattedBuckets[i]; 60 | labelWriter.WriteLabel("le", labelValue); 61 | labelWriter.EndLabels(); 62 | 63 | bucketSample.WriteValue(cumulativeCount); 64 | if (Timestamp.HasValue) 65 | bucketSample.WriteTimestamp(Timestamp.Value); 66 | 67 | bucketSample.EndSample(); 68 | } 69 | 70 | writer.WriteSample(_sum.Value, "_sum", Configuration.LabelNames, LabelValues, Timestamp); 71 | writer.WriteSample(cumulativeCount, "_count", Configuration.LabelNames, LabelValues, Timestamp); 72 | } 73 | 74 | private HistogramState ForkState() 75 | { 76 | long cumulativeCount = 0L; 77 | var buckets = new KeyValuePair[_bucketsStore.Buckets.Length]; 78 | 79 | for (int i = 0; i < _bucketsStore.Buckets.Length; i++) 80 | { 81 | cumulativeCount += _bucketsStore.Buckets[i].Value; 82 | var bound = (i == _bucketsStore.Buckets.Length - 1) ? double.PositiveInfinity : Configuration.Buckets[i]; 83 | 84 | buckets[i] = new KeyValuePair(bound, cumulativeCount); 85 | } 86 | 87 | return new HistogramState(cumulativeCount, _sum.Value, buckets); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Prometheus.Client/HistogramConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Linq; 4 | 5 | namespace Prometheus.Client; 6 | 7 | public class HistogramConfiguration : MetricConfiguration 8 | { 9 | private static readonly double[] _defaultBuckets = { .005, .01, .025, .05, .075, .1, .25, .5, .75, 1, 2.5, 5, 7.5, 10 }; 10 | private static readonly string[] _defaultFormattedBuckets; 11 | 12 | private readonly Lazy _formattedBuckets; 13 | 14 | static HistogramConfiguration() 15 | { 16 | _defaultFormattedBuckets = GetFormattedBuckets(_defaultBuckets); 17 | } 18 | 19 | public HistogramConfiguration(string name, string help, string[] labels, double[] buckets, bool includeTimestamp, TimeSpan timeToLive) 20 | : base(name, help, labels, includeTimestamp, timeToLive) 21 | { 22 | if (LabelNames.Any(l => l == "le")) 23 | throw new ArgumentException("'le' is a reserved label name"); 24 | 25 | if (buckets == null) 26 | { 27 | Buckets = _defaultBuckets; 28 | _formattedBuckets = new Lazy(() => _defaultFormattedBuckets); 29 | } 30 | else 31 | { 32 | Buckets = buckets; 33 | 34 | if (Buckets.Length == 0) 35 | throw new ArgumentException("Histogram must have at least one bucket"); 36 | 37 | var lastVal = double.MinValue; 38 | foreach (var val in buckets) 39 | { 40 | if (lastVal >= val) 41 | throw new ArgumentException("Bucket values must be increasing"); 42 | 43 | lastVal = val; 44 | } 45 | 46 | _formattedBuckets = new Lazy(() => GetFormattedBuckets(Buckets)); 47 | } 48 | } 49 | 50 | public double[] Buckets { get; } 51 | 52 | internal string[] FormattedBuckets => _formattedBuckets.Value; 53 | 54 | private static string[] GetFormattedBuckets(double[] buckets) 55 | { 56 | var result = new string[buckets.Length + 1]; 57 | for (var i = 0; i < buckets.Length; i++) 58 | { 59 | result[i] = buckets[i].ToString(CultureInfo.InvariantCulture); 60 | } 61 | 62 | result[buckets.Length] = "+Inf"; 63 | return result; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Prometheus.Client/HistogramImpl/HistogramHighBucketsStore.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Prometheus.Client.HistogramImpl; 4 | 5 | internal sealed class HistogramHighBucketsStore: IHistogramBucketStore 6 | { 7 | private readonly double[] _bounds; 8 | 9 | public HistogramHighBucketsStore(double[] bucketBounds) 10 | { 11 | _bounds = bucketBounds; 12 | Buckets = new ThreadSafeLong[bucketBounds.Length + 1]; // Allocate one more element for +Inf value 13 | } 14 | 15 | public void Observe(double value) 16 | { 17 | var bucketIndex = Array.BinarySearch(_bounds, value); 18 | if (bucketIndex < 0) 19 | bucketIndex = ~bucketIndex; 20 | 21 | Buckets[bucketIndex].Add(1); 22 | } 23 | 24 | public void Reset() 25 | { 26 | for (var i = 0; i < Buckets.Length; i++) 27 | { 28 | Buckets[i].Value = default; 29 | } 30 | } 31 | 32 | public ThreadSafeLong[] Buckets { get; } 33 | } 34 | -------------------------------------------------------------------------------- /src/Prometheus.Client/HistogramImpl/HistogramLowBucketsStore.cs: -------------------------------------------------------------------------------- 1 | namespace Prometheus.Client.HistogramImpl; 2 | 3 | internal sealed class HistogramLowBucketsStore : IHistogramBucketStore 4 | { 5 | private readonly double[] _bounds; 6 | 7 | public HistogramLowBucketsStore(double[] bucketBounds) 8 | { 9 | _bounds = bucketBounds; 10 | Buckets = new ThreadSafeLong[bucketBounds.Length + 1]; // Allocate one more element for +Inf value 11 | } 12 | 13 | public void Observe(double value) 14 | { 15 | var bucketIndex = Buckets.Length - 1; 16 | 17 | for (var i = 0; i < _bounds.Length; i++) 18 | { 19 | if (value <= _bounds[i]) 20 | { 21 | bucketIndex = i; 22 | break; 23 | } 24 | } 25 | 26 | Buckets[bucketIndex].Add(1); 27 | } 28 | 29 | public void Reset() 30 | { 31 | for (var i = 0; i < Buckets.Length; i++) 32 | { 33 | Buckets[i].Value = default; 34 | } 35 | } 36 | 37 | public ThreadSafeLong[] Buckets { get; } 38 | } 39 | -------------------------------------------------------------------------------- /src/Prometheus.Client/HistogramImpl/IHistogramBucketStore.cs: -------------------------------------------------------------------------------- 1 | namespace Prometheus.Client.HistogramImpl; 2 | 3 | internal interface IHistogramBucketStore 4 | { 5 | void Observe(double value); 6 | 7 | void Reset(); 8 | 9 | ThreadSafeLong[] Buckets { get; } 10 | } 11 | -------------------------------------------------------------------------------- /src/Prometheus.Client/MetricBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.CompilerServices; 4 | using System.Threading; 5 | using Prometheus.Client.MetricsWriter; 6 | 7 | namespace Prometheus.Client; 8 | 9 | public abstract class MetricBase 10 | where TConfig: MetricConfiguration 11 | { 12 | protected readonly TConfig Configuration; 13 | private readonly bool _includeTimestamp; 14 | private readonly bool _computeTimestamp; 15 | private readonly TimeSpan _timeToLive; 16 | private readonly Func _currentTimeProvider; 17 | private long _timestamp; 18 | 19 | protected MetricBase(TConfig config, IReadOnlyList labelValues, Func currentTimeProvider = null) 20 | { 21 | Configuration = config; 22 | _currentTimeProvider = currentTimeProvider; 23 | _timeToLive = config.TimeToLive; 24 | _includeTimestamp = config.IncludeTimestamp; 25 | _computeTimestamp = _includeTimestamp || _timeToLive > TimeSpan.Zero; 26 | 27 | LabelValues = labelValues; 28 | } 29 | 30 | protected internal IReadOnlyList LabelValues { get; } 31 | 32 | protected internal long? Timestamp 33 | { 34 | get 35 | { 36 | if (!_includeTimestamp) 37 | return null; 38 | 39 | return Interlocked.Read(ref _timestamp); 40 | } 41 | } 42 | 43 | protected internal abstract void Collect(IMetricsWriter writer); 44 | 45 | protected DateTimeOffset GetUtcNow() 46 | { 47 | if (_currentTimeProvider == null) 48 | return DateTimeOffset.UtcNow; 49 | 50 | return _currentTimeProvider(); 51 | } 52 | 53 | public bool IsExpired() 54 | { 55 | if (_timeToLive == TimeSpan.Zero) 56 | return false; 57 | 58 | long now = GetUtcNow().ToUnixTimeMilliseconds(); 59 | long last = Interlocked.Read(ref _timestamp); 60 | return TimeSpan.FromMilliseconds(now - last) > _timeToLive; 61 | } 62 | 63 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 64 | protected void TrackObservation(long? timestamp = null) 65 | { 66 | if (!_computeTimestamp) 67 | return; 68 | 69 | long now = GetUtcNow().ToUnixTimeMilliseconds(); 70 | 71 | if (!timestamp.HasValue) 72 | { 73 | // no needs to check anything, null means now. 74 | Interlocked.Exchange(ref _timestamp, now); 75 | return; 76 | } 77 | 78 | // use now if provided timestamp is in future 79 | if (timestamp > now) 80 | { 81 | Interlocked.Exchange(ref _timestamp, now); 82 | return; 83 | } 84 | 85 | // use provided timestamp unless current timestamp is bigger 86 | while (true) 87 | { 88 | long current = Interlocked.Read(ref _timestamp); 89 | if (current > timestamp) 90 | return; 91 | 92 | if (current == Interlocked.CompareExchange(ref _timestamp, timestamp.Value, current)) 93 | return; 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Prometheus.Client/MetricConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.CompilerServices; 4 | using Prometheus.Client.Collectors; 5 | 6 | namespace Prometheus.Client; 7 | 8 | public class MetricConfiguration : CollectorConfiguration 9 | { 10 | public MetricConfiguration(string name, string help, string[] labels, bool includeTimestamp, TimeSpan timeToLive) 11 | : base(name) 12 | { 13 | Help = help; 14 | IncludeTimestamp = includeTimestamp; 15 | TimeToLive = timeToLive == TimeSpan.MaxValue ? TimeSpan.Zero : timeToLive; 16 | LabelNames = labels ?? Array.Empty(); 17 | 18 | if (labels != null) 19 | { 20 | foreach (string labelName in labels) 21 | { 22 | if (string.IsNullOrEmpty(labelName)) 23 | throw new ArgumentException("Label name cannot be empty"); 24 | 25 | if (!ValidateLabelName(labelName)) 26 | throw new ArgumentException($"Invalid label name: {labelName}"); 27 | } 28 | } 29 | } 30 | 31 | public string Help { get; } 32 | 33 | public bool IncludeTimestamp { get; } 34 | 35 | public TimeSpan TimeToLive { get; } 36 | 37 | public IReadOnlyList LabelNames { get; } 38 | 39 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 40 | private static bool ValidateLabelName(string labelName) 41 | { 42 | if (labelName.Length >= 2 && labelName[0] == '_' && labelName[1] == '_') 43 | return false; 44 | 45 | if (char.IsDigit(labelName[0])) 46 | return false; 47 | 48 | foreach (var ch in labelName) 49 | { 50 | if ((ch >= 'a' && ch <= 'z') 51 | || (ch >= 'A' && ch <= 'Z') 52 | || char.IsDigit(ch) 53 | || ch == '_' 54 | || ch == ':') 55 | continue; 56 | 57 | return false; 58 | } 59 | 60 | return true; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Prometheus.Client/Metrics.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Prometheus.Client.Collectors; 3 | 4 | namespace Prometheus.Client; 5 | 6 | /// 7 | /// Static container for DefaultCollectorRegistry and DefaultFactory 8 | /// 9 | public static class Metrics 10 | { 11 | private static readonly Lazy _defaultCollectorRegistry = new(() => new CollectorRegistry()); 12 | 13 | private static readonly Lazy _defaultFactory = new(() => new MetricFactory(DefaultCollectorRegistry)); 14 | 15 | public static ICollectorRegistry DefaultCollectorRegistry => _defaultCollectorRegistry.Value; 16 | 17 | public static IMetricFactory DefaultFactory => _defaultFactory.Value; 18 | } 19 | -------------------------------------------------------------------------------- /src/Prometheus.Client/Prometheus.Client.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net462;net47;netstandard2.0;net6.0 4 | .NET client for Prometheus 5 | https://github.com/prom-client-net/prom-client 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Prometheus.Client/ScrapeHandler.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Prometheus.Client.Collectors; 5 | using Prometheus.Client.MetricsWriter; 6 | 7 | namespace Prometheus.Client; 8 | 9 | public static class ScrapeHandler 10 | { 11 | public static async Task ProcessAsync(ICollectorRegistry registry, Stream outputStream, CancellationToken ct = default) 12 | { 13 | using var metricsWriter = new MetricsTextWriter(outputStream); 14 | 15 | await registry.CollectToAsync(metricsWriter, ct).ConfigureAwait(false); 16 | await metricsWriter.CloseWriterAsync(ct).ConfigureAwait(false); 17 | } 18 | 19 | public static async Task ProcessAsync(ICollectorRegistry registry, CancellationToken ct = default) 20 | { 21 | // leave open 22 | var stream = new MemoryStream(); 23 | await ProcessAsync(registry, stream, ct); 24 | stream.Position = 0; 25 | return stream; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Prometheus.Client/SummaryImpl/Invariant.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Prometheus.Client.SummaryImpl; 4 | 5 | internal delegate double Invariant(SampleStream stream, double r); 6 | 7 | internal static class Invariants 8 | { 9 | // LowBiased returns an invariant for low-biased quantiles 10 | // (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but 11 | // error guarantees can still be given even for the lower ranks of the data 12 | // distribution. 13 | // 14 | // The provided epsilon is a relative error, i.e. the true quantile of a value 15 | // returned by a query is guaranteed to be within (1±Epsilon)*Quantile. 16 | // 17 | // See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error 18 | // properties. 19 | public static Invariant LowBiased(double epsilon) => (stream, r) => 2 * epsilon * r; 20 | 21 | // HighBiased returns an invariant for high-biased quantiles 22 | // (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but 23 | // error guarantees can still be given even for the higher ranks of the data 24 | // distribution. 25 | // 26 | // The provided epsilon is a relative error, i.e. the true quantile of a value 27 | // returned by a query is guaranteed to be within 1-(1±Epsilon)*(1-Quantile). 28 | // 29 | // See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error 30 | // properties. 31 | public static Invariant HighBiased(double epsilon) => (stream, r) => 2 * epsilon * (stream.Count - r); 32 | 33 | // Targeted returns an invariant concerned with a particular set of 34 | // quantile values that are supplied a priori. Knowing these a priori reduces 35 | // space and computation time. The targets map maps the desired quantiles to 36 | // their absolute errors, i.e. the true quantile of a value returned by a query 37 | // is guaranteed to be within (Quantile±Epsilon). 38 | // 39 | // See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error properties. 40 | public static Invariant Targeted(IReadOnlyList targets) 41 | { 42 | return (stream, r) => 43 | { 44 | double m = double.MaxValue; 45 | 46 | for (var i = 0; i < targets.Count; i++) 47 | { 48 | var target = targets[i]; 49 | double f; 50 | if (target.Quantile * stream.Count <= r) 51 | f = (2 * target.Epsilon * r) / target.Quantile; 52 | else 53 | f = (2 * target.Epsilon * (stream.Count - r)) / (1 - target.Quantile); 54 | 55 | if (f < m) 56 | m = f; 57 | } 58 | 59 | return m; 60 | }; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Prometheus.Client/SummaryImpl/Sample.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Prometheus.Client.SummaryImpl; 4 | 5 | // Sample holds an observed value and meta information for compression. 6 | internal readonly struct Sample 7 | { 8 | public static Sample Empty = default; 9 | 10 | public Sample(double value, int width, int delta) 11 | { 12 | Value = value; 13 | Width = width; 14 | Delta = delta; 15 | } 16 | 17 | public static bool IsEmpty(Sample sample) 18 | { 19 | return Math.Abs(sample.Value - 0) < double.Epsilon 20 | && sample.Width == default 21 | && sample.Delta == default; 22 | } 23 | 24 | public double Value { get; } 25 | public int Width { get; } 26 | public int Delta { get; } 27 | } 28 | -------------------------------------------------------------------------------- /src/Prometheus.Client/SummaryImpl/SampleStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Prometheus.Client.SummaryImpl; 5 | 6 | internal class SampleStream 7 | { 8 | private static readonly Predicate _isEmptySample = Sample.IsEmpty; 9 | 10 | private readonly Invariant _invariant; 11 | private readonly List _samples = new List(); 12 | 13 | private int _n; 14 | 15 | public SampleStream(Invariant invariant) 16 | { 17 | _invariant = invariant; 18 | } 19 | 20 | public int Count => _n; 21 | 22 | public void InsertRange(ReadOnlySpan samples) 23 | { 24 | // TODO(beorn7): This tries to merge not only individual samples, but 25 | // whole summaries. The paper doesn't mention merging summaries at 26 | // all. Unittests show that the merging is inaccurate. Find out how to 27 | // do merges properly. 28 | 29 | double r = 0; 30 | int i = 0; 31 | 32 | foreach (var sample in samples) 33 | { 34 | bool inserted = false; 35 | for (; i < _samples.Count; i++) 36 | { 37 | var c = _samples[i]; 38 | 39 | if (c.Value > sample) 40 | { 41 | // Insert at position i 42 | _samples.Insert(i, 43 | new Sample(sample, 1, (int)Math.Max(0, Math.Floor(_invariant(this, r)) - 1))); 44 | 45 | i++; 46 | inserted = true; 47 | break; 48 | } 49 | 50 | r += c.Width; 51 | } 52 | 53 | if (!inserted) 54 | { 55 | _samples.Add(new Sample(sample, 1, 0)); 56 | i++; 57 | } 58 | 59 | _n++; 60 | r += 1; 61 | } 62 | 63 | Compress(); 64 | } 65 | 66 | private void Compress() 67 | { 68 | if (_samples.Count < 2) 69 | return; 70 | 71 | var x = _samples[_samples.Count - 1]; 72 | int xi = _samples.Count - 1; 73 | double r = _n - 1 - x.Width; 74 | 75 | for (int i = _samples.Count - 2; i >= 0; i--) 76 | { 77 | var c = _samples[i]; 78 | 79 | if (c.Width + x.Width + x.Delta <= _invariant(this, r)) 80 | { 81 | x = new Sample(x.Value, x.Width + c.Width, x.Delta); 82 | _samples[xi] = x; 83 | _samples[i] = Sample.Empty; 84 | } 85 | else 86 | { 87 | x = c; 88 | xi = i; 89 | } 90 | 91 | r -= c.Width; 92 | } 93 | 94 | _samples.RemoveAll(_isEmptySample); 95 | } 96 | 97 | public void Reset() 98 | { 99 | _samples.Clear(); 100 | _n = 0; 101 | } 102 | 103 | public double Query(double q) 104 | { 105 | if (_samples.Count == 0) 106 | return double.NaN; 107 | 108 | double t = q * _n; 109 | t += _invariant(this, t) / 2; 110 | t = Math.Ceiling(t); 111 | var p = _samples[0]; 112 | double r = 0; 113 | 114 | for (int i = 1; i < _samples.Count; i++) 115 | { 116 | var c = _samples[i]; 117 | r += p.Width; 118 | 119 | if (r + c.Width + c.Delta > t) 120 | return p.Value; 121 | 122 | p = c; 123 | } 124 | 125 | return p.Value; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/Prometheus.Client/ThreadSafeDouble.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | 4 | namespace Prometheus.Client; 5 | 6 | internal struct ThreadSafeDouble 7 | { 8 | private long _value; 9 | private bool _isNan; 10 | 11 | public ThreadSafeDouble(double value) 12 | { 13 | _value = BitConverter.DoubleToInt64Bits(value); 14 | _isNan = IsNaN(value); 15 | } 16 | 17 | public double Value 18 | { 19 | get 20 | { 21 | return BitConverter.Int64BitsToDouble(Interlocked.Read(ref _value)); 22 | } 23 | set 24 | { 25 | if (IsNaN(value)) 26 | Volatile.Write(ref _isNan, true); 27 | 28 | Interlocked.Exchange(ref _value, BitConverter.DoubleToInt64Bits(value)); 29 | } 30 | } 31 | 32 | public void Add(double increment) 33 | { 34 | if (IsNaN(increment)) 35 | throw new InvalidOperationException("Cannot increment the NaN value."); 36 | 37 | long currentValue = Interlocked.Read(ref _value); 38 | 39 | while (true) 40 | { 41 | if (Volatile.Read(ref _isNan)) 42 | throw new InvalidOperationException("Cannot increment the NaN value."); 43 | 44 | double computedValue = BitConverter.Int64BitsToDouble(currentValue) + increment; 45 | 46 | var lastValue = Interlocked.CompareExchange(ref _value, BitConverter.DoubleToInt64Bits(computedValue), currentValue); 47 | 48 | if (lastValue == currentValue) 49 | return; 50 | 51 | currentValue = lastValue; 52 | } 53 | } 54 | 55 | public void IncTo(double value) 56 | { 57 | if (IsNaN(value)) 58 | throw new InvalidOperationException("Cannot increment the NaN value."); 59 | 60 | long currentValue = Interlocked.Read(ref _value); 61 | 62 | while (true) 63 | { 64 | if (Volatile.Read(ref _isNan)) 65 | throw new InvalidOperationException("Cannot increment the NaN value."); 66 | 67 | double decodedValue = BitConverter.Int64BitsToDouble(currentValue); 68 | 69 | if (decodedValue >= value) 70 | return; 71 | 72 | var lastValue = Interlocked.CompareExchange(ref _value, BitConverter.DoubleToInt64Bits(value), currentValue); 73 | 74 | if (lastValue == currentValue) 75 | return; 76 | 77 | currentValue = lastValue; 78 | } 79 | } 80 | 81 | public void DecTo(double value) 82 | { 83 | if (IsNaN(value)) 84 | throw new InvalidOperationException("Cannot decrement the NaN value."); 85 | 86 | long currentValue = Interlocked.Read(ref _value); 87 | 88 | while (true) 89 | { 90 | if (Volatile.Read(ref _isNan)) 91 | throw new InvalidOperationException("Cannot decrement the NaN value."); 92 | 93 | double decodedValue = BitConverter.Int64BitsToDouble(currentValue); 94 | 95 | if (decodedValue <= value) 96 | return; 97 | 98 | var lastValue = Interlocked.CompareExchange(ref _value, BitConverter.DoubleToInt64Bits(value), currentValue); 99 | 100 | if (lastValue == currentValue) 101 | return; 102 | 103 | currentValue = lastValue; 104 | } 105 | } 106 | 107 | internal static bool IsNaN(double val) 108 | { 109 | if (val >= 0d || val < 0d) 110 | return false; 111 | 112 | return true; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/Prometheus.Client/ThreadSafeLong.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | 3 | namespace Prometheus.Client; 4 | 5 | internal struct ThreadSafeLong 6 | { 7 | private long _value; 8 | 9 | public ThreadSafeLong(long value) 10 | { 11 | _value = value; 12 | } 13 | 14 | public long Value 15 | { 16 | get => Interlocked.Read(ref _value); 17 | set => Interlocked.Exchange(ref _value, value); 18 | } 19 | 20 | public void Add(long increment) 21 | { 22 | Interlocked.Add(ref _value, increment); 23 | } 24 | 25 | public void IncTo(long value) 26 | { 27 | var currentValue = Interlocked.Read(ref _value); 28 | 29 | while (true) 30 | { 31 | if (currentValue >= value) 32 | return; 33 | 34 | var lastValue = Interlocked.CompareExchange(ref _value, value, currentValue); 35 | if (lastValue == currentValue) 36 | return; 37 | 38 | currentValue = lastValue; 39 | } 40 | } 41 | 42 | public void DecTo(long value) 43 | { 44 | var currentValue = Interlocked.Read(ref _value); 45 | 46 | while (true) 47 | { 48 | if (currentValue <= value) 49 | return; 50 | 51 | var lastValue = Interlocked.CompareExchange(ref _value, value, currentValue); 52 | if (lastValue == currentValue) 53 | return; 54 | 55 | currentValue = lastValue; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Prometheus.Client/Untyped.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Runtime.CompilerServices; 3 | using Prometheus.Client.MetricsWriter; 4 | 5 | namespace Prometheus.Client; 6 | 7 | /// 8 | public sealed class Untyped : MetricBase, IUntyped 9 | { 10 | internal Untyped(MetricConfiguration configuration, IReadOnlyList labels) 11 | : base(configuration, labels) 12 | { 13 | } 14 | 15 | private ThreadSafeDouble _value; 16 | 17 | public void Set(double val) 18 | { 19 | Set(val, null); 20 | } 21 | 22 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 23 | public void Set(double val, long? timestamp) 24 | { 25 | _value.Value = val; 26 | TrackObservation(timestamp); 27 | } 28 | 29 | public double Value => _value.Value; 30 | 31 | public void Reset() 32 | { 33 | _value.Value = default; 34 | } 35 | 36 | protected internal override void Collect(IMetricsWriter writer) 37 | { 38 | writer.WriteSample(Value, string.Empty, Configuration.LabelNames, LabelValues, Timestamp); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /stylecop.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", 3 | "settings": { 4 | "layoutRules": { 5 | "newlineAtEndOfFile": "require" 6 | }, 7 | "documentationRules": { 8 | "xmlHeader": false, 9 | "copyrightText": "", 10 | "documentInterfaces": false, 11 | "documentExposedElements": false, 12 | "documentInternalElements": false, 13 | "documentPrivateElements": false, 14 | "documentPrivateFields": false 15 | }, 16 | "orderingRules": { 17 | "usingDirectivesPlacement": "outsideNamespace", 18 | "systemUsingDirectivesFirst": true, 19 | "blankLinesBetweenUsingGroups": "omit", 20 | "elementOrder": [ 21 | "kind", 22 | "constant", 23 | "static", 24 | "readonly" 25 | ] 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Benchmarks.Comparison/ComparisonBenchmarkBase.cs: -------------------------------------------------------------------------------- 1 | extern alias Their; 2 | using System; 3 | using System.Linq; 4 | using Prometheus.Client.Collectors; 5 | 6 | namespace Prometheus.Client.Benchmarks.Comparison; 7 | 8 | public abstract class ComparisonBenchmarkBase 9 | { 10 | private IMetricFactory _factory; 11 | private ICollectorRegistry _registry; 12 | private Their.Prometheus.CollectorRegistry _theirRegistry; 13 | private Their.Prometheus.MetricFactory _theirMetricFactory; 14 | 15 | protected const string HelpText = "arbitrary help message for metric, not relevant for benchmarking"; 16 | 17 | protected ComparisonBenchmarkBase() 18 | { 19 | ResetFactories(); 20 | } 21 | 22 | protected void ResetFactories() 23 | { 24 | _registry = new CollectorRegistry(); 25 | _factory = new MetricFactory(_registry); 26 | 27 | _theirRegistry = Their.Prometheus.Metrics.NewCustomRegistry(); 28 | _theirMetricFactory = Their.Prometheus.Metrics.WithCustomRegistry(_theirRegistry); 29 | 30 | GC.Collect(); 31 | } 32 | 33 | protected string[] GenerateMetricNames(int count, double duplicates = 0) 34 | { 35 | return GenerateData(count, duplicates, n => $"metric_{n}"); 36 | } 37 | 38 | protected string[] GenerateLabelNames(int count) 39 | { 40 | var result = new string[count]; 41 | for (var i = 0; i < count; i++) 42 | result[i] = $"label_{i}"; 43 | 44 | return result; 45 | } 46 | 47 | protected string[][] GenerateLabelValues(int count, int labels, double duplicates = 0) 48 | { 49 | var labelsRange = Enumerable.Range(1, labels).ToArray(); 50 | 51 | return GenerateData(count, duplicates, n => labelsRange.Select(i => $"label{n}_variant{i}").ToArray()); 52 | } 53 | 54 | protected IMetricFactory OurMetricFactory => _factory; 55 | 56 | protected ICollectorRegistry OurCollectorRegistry => _registry; 57 | 58 | public Their::Prometheus.MetricFactory TheirMetricFactory => _theirMetricFactory; 59 | 60 | protected Their::Prometheus.CollectorRegistry TheirCollectorRegistry => _theirRegistry; 61 | 62 | private T[] GenerateData(int count, double duplicates, Func valueGenerator) 63 | { 64 | var groupWidth = 1; 65 | if (duplicates > 0) 66 | groupWidth = (int)Math.Ceiling(count * duplicates); 67 | 68 | var result = new T[count]; 69 | var groupNum = 1; 70 | var i = 0; 71 | 72 | while (i < result.Length) 73 | { 74 | var currentWidth = Math.Min(groupWidth, result.Length - i); 75 | Array.Fill(result, valueGenerator(groupNum), i, currentWidth); 76 | 77 | i += currentWidth; 78 | groupNum++; 79 | } 80 | 81 | Shuffle(result); 82 | 83 | return result; 84 | } 85 | 86 | private static void Shuffle(T[] array) 87 | { 88 | var r = new Random(); 89 | 90 | for (int i = array.Length - 1; i > 0; i--) 91 | { 92 | int j = r.Next(0, i + 1); 93 | 94 | var temp = array[i]; 95 | array[i] = array[j]; 96 | array[j] = temp; 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Benchmarks.Comparison/Counter/CounterCollectingBenchmarks.cs: -------------------------------------------------------------------------------- 1 | extern alias Their; 2 | using System; 3 | using System.IO; 4 | using System.Linq; 5 | using BenchmarkDotNet.Attributes; 6 | 7 | namespace Prometheus.Client.Benchmarks.Comparison.Counter; 8 | 9 | public class CounterCollectingBenchmarks : ComparisonBenchmarkBase 10 | { 11 | private const int _metricsCount = 100; 12 | private const int _labelsCount = 5; 13 | private const int _variantsCount = 100; 14 | 15 | public CounterCollectingBenchmarks() 16 | { 17 | var labelNames = GenerateLabelNames(_labelsCount).ToArray(); 18 | var labelVariants = GenerateLabelValues(_variantsCount, _labelsCount); 19 | var rnd = new Random(); 20 | 21 | foreach (var metric in GenerateMetricNames(_metricsCount)) 22 | { 23 | var ourMetric = OurMetricFactory.CreateCounter(metric, HelpText, labelNames); 24 | var theirMetric = TheirMetricFactory.CreateCounter(metric, HelpText, labelNames); 25 | 26 | foreach (var labels in labelVariants) 27 | { 28 | var val = rnd.Next(10000); 29 | ourMetric.WithLabels(labels).Inc(val); 30 | theirMetric.WithLabels(labels).Inc(val); 31 | } 32 | } 33 | } 34 | 35 | [Benchmark(Baseline = true)] 36 | public void Collecting_Baseline() 37 | { 38 | using var stream = Stream.Null; 39 | TheirCollectorRegistry.CollectAndExportAsTextAsync(stream).GetAwaiter().GetResult(); 40 | } 41 | 42 | [Benchmark] 43 | public void Collecting() 44 | { 45 | using var stream = Stream.Null; 46 | ScrapeHandler.ProcessAsync(OurCollectorRegistry , stream).GetAwaiter().GetResult(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Benchmarks.Comparison/Counter/CounterSampleBenchmarks.cs: -------------------------------------------------------------------------------- 1 | extern alias Their; 2 | using BenchmarkDotNet.Attributes; 3 | 4 | namespace Prometheus.Client.Benchmarks.Comparison.Counter; 5 | 6 | public class CounterSampleBenchmarks : ComparisonBenchmarkBase 7 | { 8 | private const int _opIterations = 10_000_000; 9 | 10 | private ICounter _counter; 11 | private ICounter _counterInt64; 12 | private Their.Prometheus.ICounter _theirCounter; 13 | 14 | [IterationSetup] 15 | public void Setup() 16 | { 17 | _counter = OurMetricFactory.CreateCounter("testcounter", string.Empty); 18 | _counterInt64 = OurMetricFactory.CreateCounterInt64("testcounterInt64", string.Empty); 19 | 20 | _theirCounter = TheirMetricFactory.CreateCounter("testcounter", string.Empty); 21 | } 22 | 23 | [Benchmark(Baseline = true)] 24 | [BenchmarkCategory("IncDefault")] 25 | public void IncDefault_Baseline() 26 | { 27 | for (var i = 0; i < _opIterations; i++) 28 | _theirCounter.Inc(); 29 | } 30 | 31 | [Benchmark] 32 | [BenchmarkCategory("IncDefault")] 33 | public void IncDefault() 34 | { 35 | for (var i = 0; i < _opIterations; i++) 36 | _counter.Inc(); 37 | } 38 | 39 | [Benchmark] 40 | [BenchmarkCategory("IncDefault")] 41 | public void IncDefault_Int64() 42 | { 43 | for (var i = 0; i < _opIterations; i++) 44 | _counterInt64.Inc(); 45 | } 46 | 47 | [Benchmark(Baseline = true)] 48 | [BenchmarkCategory("Inc")] 49 | public void Inc_Baseline() 50 | { 51 | for (var i = 0; i < _opIterations; i++) 52 | _theirCounter.Inc(i); 53 | } 54 | 55 | [Benchmark] 56 | [BenchmarkCategory("Inc")] 57 | public void Inc() 58 | { 59 | for (var i = 0; i < _opIterations; i++) 60 | _counter.Inc(i); 61 | } 62 | 63 | [Benchmark] 64 | [BenchmarkCategory("Inc")] 65 | public void Inc_Int64() 66 | { 67 | for (var i = 0; i < _opIterations; i++) 68 | _counterInt64.Inc(i); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Benchmarks.Comparison/Counter/CounterSampleResolvingBenchmarks.cs: -------------------------------------------------------------------------------- 1 | extern alias Their; 2 | using BenchmarkDotNet.Attributes; 3 | 4 | namespace Prometheus.Client.Benchmarks.Comparison.Counter; 5 | 6 | public class CounterSampleResolvingBenchmarks : ComparisonBenchmarkBase 7 | { 8 | private IMetricFamily _counterFamily; 9 | private IMetricFamily _counterTuplesFamily; 10 | private IMetricFamily> _counterInt64Family; 11 | private IMetricFamily, (string, string, string, string, string)> _counterInt64TuplesFamily; 12 | private Their.Prometheus.Counter _theirCounter; 13 | 14 | private string[][] _labels; 15 | 16 | [GlobalSetup] 17 | public void Setup() 18 | { 19 | _labels = GenerateLabelValues(10_000, 5, 0.1); 20 | 21 | _counterTuplesFamily = OurMetricFactory.CreateCounter("_counterFamilyTuples", string.Empty, ("label1", "label2", "label3", "label4", "label5" )); 22 | _counterFamily = OurMetricFactory.CreateCounter("_counterFamily", string.Empty, "label1", "label2", "label3", "label4", "label5"); 23 | _counterInt64TuplesFamily = OurMetricFactory.CreateCounterInt64("_counterInt64FamilyTuples", string.Empty, ("label1", "label2", "label3", "label4", "label5" )); 24 | _counterInt64Family = OurMetricFactory.CreateCounterInt64("_counterInt64Family", string.Empty, "label1", "label2", "label3", "label4", "label5"); 25 | _theirCounter = TheirMetricFactory.CreateCounter("_counter", string.Empty, "label1", "label2", "label3", "label4", "label5"); 26 | 27 | foreach (var lbls in _labels) 28 | { 29 | _theirCounter.WithLabels(lbls[0], lbls[1], lbls[2],lbls[3],lbls[4]); 30 | _counterFamily.WithLabels(lbls[0], lbls[1], lbls[2],lbls[3],lbls[4]); 31 | _counterTuplesFamily.WithLabels((lbls[0], lbls[1], lbls[2],lbls[3],lbls[4])); 32 | _counterInt64Family.WithLabels(lbls[0], lbls[1], lbls[2],lbls[3],lbls[4]); 33 | _counterInt64TuplesFamily.WithLabels((lbls[0], lbls[1], lbls[2],lbls[3],lbls[4])); 34 | } 35 | } 36 | 37 | [Benchmark(Baseline = true)] 38 | [BenchmarkCategory("ResolveLabeled")] 39 | public void ResolveLabeled_Baseline() 40 | { 41 | foreach (var lbls in _labels) 42 | _theirCounter.WithLabels(lbls[0], lbls[1], lbls[2], lbls[3], lbls[4]); 43 | } 44 | 45 | [Benchmark] 46 | [BenchmarkCategory("ResolveLabeled")] 47 | public void ResolveLabeled_Array() 48 | { 49 | foreach (var lbls in _labels) 50 | _counterFamily.WithLabels(lbls[0], lbls[1], lbls[2], lbls[3], lbls[4]); 51 | } 52 | 53 | [Benchmark] 54 | [BenchmarkCategory("ResolveLabeled")] 55 | public void ResolveLabeled_Tuple() 56 | { 57 | foreach (var lbls in _labels) 58 | _counterTuplesFamily.WithLabels((lbls[0], lbls[1], lbls[2], lbls[3], lbls[4])); 59 | } 60 | 61 | [Benchmark] 62 | [BenchmarkCategory("ResolveLabeled")] 63 | public void ResolveLabeled_Int64Array() 64 | { 65 | foreach (var lbls in _labels) 66 | _counterInt64Family.WithLabels(lbls[0], lbls[1], lbls[2], lbls[3], lbls[4]); 67 | } 68 | 69 | [Benchmark] 70 | [BenchmarkCategory("ResolveLabeled")] 71 | public void ResolveLabeled_Int64Tuple() 72 | { 73 | foreach (var lbls in _labels) 74 | _counterInt64TuplesFamily.WithLabels((lbls[0], lbls[1], lbls[2], lbls[3], lbls[4])); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Benchmarks.Comparison/Gauge/GaugeCollectingBenchmarks.cs: -------------------------------------------------------------------------------- 1 | extern alias Their; 2 | using System; 3 | using System.IO; 4 | using BenchmarkDotNet.Attributes; 5 | 6 | namespace Prometheus.Client.Benchmarks.Comparison.Gauge; 7 | 8 | public class GaugeCollectingBenchmarks : ComparisonBenchmarkBase 9 | { 10 | private const int _metricsCount = 100; 11 | private const int _labelsCount = 5; 12 | private const int _variantsCount = 100; 13 | 14 | private const string _helpText = "some help text"; 15 | 16 | public GaugeCollectingBenchmarks() 17 | { 18 | var labelNames = GenerateLabelNames(_labelsCount); 19 | var labelVariants = GenerateLabelValues(_variantsCount, _labelsCount); 20 | var rnd = new Random(); 21 | 22 | foreach (var metric in GenerateMetricNames(_metricsCount)) 23 | { 24 | var ourMetric = OurMetricFactory.CreateGauge(metric, _helpText, labelNames); 25 | var theirMetric = TheirMetricFactory.CreateGauge(metric, _helpText, labelNames); 26 | 27 | foreach (var labels in labelVariants) 28 | { 29 | var val = rnd.Next(10000); 30 | ourMetric.WithLabels(labels).Inc(val); 31 | theirMetric.WithLabels(labels).Inc(val); 32 | } 33 | } 34 | } 35 | 36 | [Benchmark(Baseline = true)] 37 | public void Collecting_Baseline() 38 | { 39 | using var stream = Stream.Null; 40 | TheirCollectorRegistry.CollectAndExportAsTextAsync(stream).GetAwaiter().GetResult(); 41 | } 42 | 43 | [Benchmark] 44 | public void Collecting() 45 | { 46 | using var stream = Stream.Null; 47 | ScrapeHandler.ProcessAsync(OurCollectorRegistry , stream).GetAwaiter().GetResult(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Benchmarks.Comparison/Gauge/GaugeSampleResolvingBenchmarks.cs: -------------------------------------------------------------------------------- 1 | extern alias Their; 2 | using BenchmarkDotNet.Attributes; 3 | 4 | namespace Prometheus.Client.Benchmarks.Comparison.Gauge; 5 | 6 | public class GaugeSampleResolvingBenchmarks : ComparisonBenchmarkBase 7 | { 8 | private IMetricFamily _gaugeFamily; 9 | private IMetricFamily _gaugeTuplesFamily; 10 | private IMetricFamily> _gaugeInt64Family; 11 | private IMetricFamily, (string, string, string, string, string)> _gaugeInt64TuplesFamily; 12 | private Their.Prometheus.Gauge _theirGauge; 13 | 14 | private string[][] _labels; 15 | 16 | [GlobalSetup] 17 | public void Setup() 18 | { 19 | _labels = GenerateLabelValues(10_000, 5, 0.1); 20 | 21 | _gaugeTuplesFamily = OurMetricFactory.CreateGauge("_gaugeFamilyTuples", HelpText, ("label1", "label2", "label3", "label4", "label5" )); 22 | _gaugeFamily = OurMetricFactory.CreateGauge("_gaugeFamily", HelpText, "label1", "label2", "label3", "label4", "label5"); 23 | _gaugeInt64TuplesFamily = OurMetricFactory.CreateGaugeInt64("_gaugeInt64FamilyTuples", HelpText, ("label1", "label2", "label3", "label4", "label5" )); 24 | _gaugeInt64Family = OurMetricFactory.CreateGaugeInt64("_gaugeInt64Family", HelpText, "label1", "label2", "label3", "label4", "label5"); 25 | _theirGauge = TheirMetricFactory.CreateGauge("_gauge", HelpText, "label1", "label2", "label3", "label4", "label5"); 26 | 27 | foreach (var lbls in _labels) 28 | { 29 | _theirGauge.WithLabels(lbls[0], lbls[1], lbls[2],lbls[3],lbls[4]); 30 | _gaugeFamily.WithLabels(lbls[0], lbls[1], lbls[2],lbls[3],lbls[4]); 31 | _gaugeTuplesFamily.WithLabels((lbls[0], lbls[1], lbls[2],lbls[3],lbls[4])); 32 | _gaugeInt64Family.WithLabels(lbls[0], lbls[1], lbls[2],lbls[3],lbls[4]); 33 | _gaugeInt64TuplesFamily.WithLabels((lbls[0], lbls[1], lbls[2],lbls[3],lbls[4])); 34 | } 35 | } 36 | 37 | [Benchmark(Baseline = true)] 38 | [BenchmarkCategory("ResolveLabeled")] 39 | public void ResolveLabeled_Baseline() 40 | { 41 | foreach (var lbls in _labels) 42 | _theirGauge.WithLabels(lbls[0], lbls[1], lbls[2],lbls[3],lbls[4]); 43 | } 44 | 45 | [Benchmark] 46 | [BenchmarkCategory("ResolveLabeled")] 47 | public void ResolveLabeled_Array() 48 | { 49 | foreach (var lbls in _labels) 50 | _gaugeFamily.WithLabels(lbls[0], lbls[1], lbls[2],lbls[3],lbls[4]); 51 | } 52 | 53 | [Benchmark] 54 | [BenchmarkCategory("ResolveLabeled")] 55 | public void ResolveLabeled_Tuple() 56 | { 57 | foreach (var lbls in _labels) 58 | _gaugeTuplesFamily.WithLabels((lbls[0], lbls[1], lbls[2],lbls[3],lbls[4])); 59 | } 60 | 61 | [Benchmark] 62 | [BenchmarkCategory("ResolveLabeled")] 63 | public void ResolveLabeled_Int64Array() 64 | { 65 | foreach (var lbls in _labels) 66 | _gaugeInt64Family.WithLabels(lbls[0], lbls[1], lbls[2],lbls[3],lbls[4]); 67 | } 68 | 69 | [Benchmark] 70 | [BenchmarkCategory("ResolveLabeled")] 71 | public void ResolveLabeled_Int64Tuple() 72 | { 73 | foreach (var lbls in _labels) 74 | _gaugeInt64TuplesFamily.WithLabels((lbls[0], lbls[1], lbls[2],lbls[3],lbls[4])); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Benchmarks.Comparison/Histogram/HistogramCollectingBenchmarks.cs: -------------------------------------------------------------------------------- 1 | extern alias Their; 2 | using System; 3 | using System.IO; 4 | using System.Linq; 5 | using BenchmarkDotNet.Attributes; 6 | 7 | namespace Prometheus.Client.Benchmarks.Comparison.Histogram; 8 | 9 | public class HistogramCollectingBenchmarks : ComparisonBenchmarkBase 10 | { 11 | private const int _metricsCount = 100; 12 | private const int _labelsCount = 5; 13 | private const int _variantsCount = 100; 14 | private const int _observationsCount = 100; 15 | 16 | public HistogramCollectingBenchmarks() 17 | { 18 | var labelNames = GenerateLabelNames(_labelsCount).ToArray(); 19 | var labelVariants = GenerateLabelValues(_variantsCount, _labelsCount); 20 | var rnd = new Random(); 21 | 22 | foreach (var metric in GenerateMetricNames(_metricsCount)) 23 | { 24 | var ourMetric = OurMetricFactory.CreateHistogram(metric, HelpText, labelNames); 25 | var theirMetric = TheirMetricFactory.CreateHistogram(metric, HelpText, labelNames); 26 | 27 | foreach (var labels in labelVariants) 28 | { 29 | for (var i = 0; i < _observationsCount; i++) 30 | { 31 | var val = rnd.Next(10); 32 | ourMetric.WithLabels(labels).Observe(val); 33 | theirMetric.WithLabels(labels).Observe(val); 34 | } 35 | } 36 | } 37 | } 38 | 39 | [Benchmark(Baseline = true)] 40 | public void Collecting_Baseline() 41 | { 42 | using var stream = Stream.Null; 43 | TheirCollectorRegistry.CollectAndExportAsTextAsync(stream).GetAwaiter().GetResult(); 44 | } 45 | 46 | [Benchmark] 47 | public void Collecting() 48 | { 49 | using var stream = Stream.Null; 50 | ScrapeHandler.ProcessAsync(OurCollectorRegistry , stream).GetAwaiter().GetResult(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Benchmarks.Comparison/Histogram/HistogramSampleBenchmarks.cs: -------------------------------------------------------------------------------- 1 | extern alias Their; 2 | using System.Linq; 3 | using BenchmarkDotNet.Attributes; 4 | 5 | namespace Prometheus.Client.Benchmarks.Comparison.Histogram; 6 | 7 | public class HistogramSampleBenchmarks : ComparisonBenchmarkBase 8 | { 9 | private const int _opIterations = 1_000_000; 10 | private readonly double[] _bucketsMany; 11 | 12 | private IHistogram _histogramDefaultBuckets; 13 | private IHistogram _histogramManyBuckets; 14 | private Their.Prometheus.IHistogram _theirHistogramDefaultBuckets; 15 | private Their.Prometheus.IHistogram _theirHistogramManyBuckets; 16 | 17 | public HistogramSampleBenchmarks() 18 | { 19 | _bucketsMany = Enumerable.Range(0, 100).Select(i => (double)(i * 10)).ToArray(); 20 | } 21 | 22 | [IterationSetup] 23 | public void Setup() 24 | { 25 | _histogramDefaultBuckets = OurMetricFactory.CreateHistogram("testhistogram1", HelpText); 26 | _histogramManyBuckets = OurMetricFactory.CreateHistogram("testhistogram2", HelpText, false, _bucketsMany); 27 | 28 | _theirHistogramDefaultBuckets = TheirMetricFactory.CreateHistogram("testhistogram1", HelpText); 29 | _theirHistogramManyBuckets = TheirMetricFactory.CreateHistogram("testhistogram2", HelpText, new Their.Prometheus.HistogramConfiguration() { Buckets = _bucketsMany}); 30 | } 31 | 32 | [Benchmark(Baseline = true)] 33 | [BenchmarkCategory("Observe")] 34 | public void Observe_Baseline() 35 | { 36 | for (var i = 0; i < _opIterations; i++) 37 | _theirHistogramDefaultBuckets.Observe(i); 38 | } 39 | 40 | [Benchmark] 41 | [BenchmarkCategory("Observe")] 42 | public void Observe() 43 | { 44 | for (var i = 0; i < _opIterations; i++) 45 | _histogramDefaultBuckets.Observe(i); 46 | } 47 | 48 | [Benchmark(Baseline = true)] 49 | [BenchmarkCategory("ManyBuckets_Observe")] 50 | public void ManyBuckets_Observe_Baseline() 51 | { 52 | for (var i = 0; i < _opIterations; i++) 53 | _theirHistogramManyBuckets.Observe(i); 54 | } 55 | 56 | [Benchmark] 57 | [BenchmarkCategory("ManyBuckets_Observe")] 58 | public void ManyBuckets_Observe() 59 | { 60 | for (var i = 0; i < _opIterations; i++) 61 | _histogramManyBuckets.Observe(i); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Benchmarks.Comparison/Histogram/HistogramSampleResolvingBenchmarks.cs: -------------------------------------------------------------------------------- 1 | extern alias Their; 2 | using BenchmarkDotNet.Attributes; 3 | 4 | namespace Prometheus.Client.Benchmarks.Comparison.Histogram; 5 | 6 | public class HistogramSampleResolvingBenchmarks : ComparisonBenchmarkBase 7 | { 8 | private IMetricFamily _histogramFamily; 9 | private IMetricFamily _histogramTuplesFamily; 10 | private Their.Prometheus.Histogram _theirHistogram; 11 | 12 | private string[][] _labels; 13 | 14 | [GlobalSetup] 15 | public void Setup() 16 | { 17 | _labels = GenerateLabelValues(10_000, 5, 0.1); 18 | 19 | _histogramTuplesFamily = OurMetricFactory.CreateHistogram("_histogramFamilyTuples", HelpText, ("label1", "label2", "label3", "label4", "label5" )); 20 | _histogramFamily = OurMetricFactory.CreateHistogram("_histogramFamily", HelpText, "label1", "label2", "label3", "label4", "label5"); 21 | _theirHistogram = TheirMetricFactory.CreateHistogram("_histogram", HelpText, "label1", "label2", "label3", "label4", "label5"); 22 | 23 | foreach (var lbls in _labels) 24 | { 25 | _theirHistogram.WithLabels(lbls[0], lbls[1], lbls[2], lbls[3], lbls[4]); 26 | _histogramFamily.WithLabels(lbls[0], lbls[1], lbls[2], lbls[3], lbls[4]); 27 | _histogramTuplesFamily.WithLabels((lbls[0], lbls[1], lbls[2], lbls[3], lbls[4])); 28 | } 29 | } 30 | 31 | [Benchmark(Baseline = true)] 32 | [BenchmarkCategory("ResolveLabeled")] 33 | public void ResolveLabeled_Baseline() 34 | { 35 | foreach (var lbls in _labels) 36 | _theirHistogram.WithLabels(lbls[0], lbls[1], lbls[2], lbls[3], lbls[4]); 37 | } 38 | 39 | [Benchmark] 40 | [BenchmarkCategory("ResolveLabeled")] 41 | public void ResolveLabeled_Array() 42 | { 43 | foreach (var lbls in _labels) 44 | _histogramFamily.WithLabels(lbls[0], lbls[1], lbls[2], lbls[3], lbls[4]); 45 | } 46 | 47 | [Benchmark] 48 | [BenchmarkCategory("ResolveLabeled")] 49 | public void ResolveLabeled_Tuples() 50 | { 51 | foreach (var lbls in _labels) 52 | _histogramTuplesFamily.WithLabels((lbls[0], lbls[1], lbls[2], lbls[3], lbls[4])); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Benchmarks.Comparison/Program.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Configs; 2 | using BenchmarkDotNet.Diagnosers; 3 | using BenchmarkDotNet.Engines; 4 | using BenchmarkDotNet.Jobs; 5 | using BenchmarkDotNet.Running; 6 | 7 | namespace Prometheus.Client.Benchmarks.Comparison; 8 | 9 | internal class Program 10 | { 11 | private static void Main(string[] args) 12 | { 13 | BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, 14 | DefaultConfig.Instance 15 | .AddDiagnoser(MemoryDiagnoser.Default) 16 | .AddJob(new Job 17 | { 18 | Run = 19 | { 20 | RunStrategy = RunStrategy.Monitoring, IterationCount = 20, WarmupCount = 2, 21 | } 22 | }) 23 | .AddLogicalGroupRules(BenchmarkLogicalGroupRule.ByCategory) 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Benchmarks.Comparison/Prometheus.Client.Benchmarks.Comparison.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net8.0 4 | false 5 | Exe 6 | $(NoWarn);SA1133 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | Their 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Benchmarks.Comparison/Summary/SummaryCollectingBenchmarks.cs: -------------------------------------------------------------------------------- 1 | extern alias Their; 2 | using System; 3 | using System.IO; 4 | using System.Linq; 5 | using BenchmarkDotNet.Attributes; 6 | 7 | namespace Prometheus.Client.Benchmarks.Comparison.Summary; 8 | 9 | public class SummaryCollectingBenchmarks : ComparisonBenchmarkBase 10 | { 11 | private const int _metricsCount = 100; 12 | private const int _labelsCount = 5; 13 | private const int _variantsCount = 100; 14 | private const int _observationsCount = 100; 15 | 16 | public SummaryCollectingBenchmarks() 17 | { 18 | var labelNames = GenerateLabelNames(_labelsCount).ToArray(); 19 | var labelVariants = GenerateLabelValues(_variantsCount, _labelsCount); 20 | var rnd = new Random(); 21 | 22 | foreach (var metric in GenerateMetricNames(_metricsCount, 0)) 23 | { 24 | var ourMetric = OurMetricFactory.CreateSummary(metric, HelpText, labelNames); 25 | var theirMetric = TheirMetricFactory.CreateSummary(metric, HelpText, labelNames); 26 | 27 | foreach (var labels in labelVariants) 28 | { 29 | for (var i = 0; i < _observationsCount; i++) 30 | { 31 | var val = rnd.Next(10); 32 | ourMetric.WithLabels(labels).Observe(val); 33 | theirMetric.WithLabels(labels).Observe(val); 34 | } 35 | } 36 | } 37 | } 38 | 39 | [Benchmark(Baseline = true)] 40 | public void Collecting_Baseline() 41 | { 42 | using var stream = Stream.Null; 43 | TheirCollectorRegistry.CollectAndExportAsTextAsync(stream).GetAwaiter().GetResult(); 44 | } 45 | 46 | [Benchmark] 47 | public void Collecting() 48 | { 49 | using var stream = Stream.Null; 50 | ScrapeHandler.ProcessAsync(OurCollectorRegistry , stream).GetAwaiter().GetResult(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Benchmarks.Comparison/Summary/SummarySampleBenchmarks.cs: -------------------------------------------------------------------------------- 1 | extern alias Their; 2 | using System; 3 | using BenchmarkDotNet.Attributes; 4 | 5 | namespace Prometheus.Client.Benchmarks.Comparison.Summary; 6 | 7 | public class SummarySampleBenchmarks : ComparisonBenchmarkBase 8 | { 9 | private const int _opIterations = 100000; 10 | 11 | private ISummary _summary; 12 | private Their.Prometheus.ISummary _theirSummary; 13 | private double[] _dataset; 14 | 15 | [IterationSetup] 16 | public void Setup() 17 | { 18 | _summary = OurMetricFactory.CreateSummary("testSummary1", HelpText); 19 | _theirSummary = TheirMetricFactory.CreateSummary("testSummary1", HelpText); 20 | _dataset = new double[_opIterations]; 21 | 22 | var rnd = new Random(); 23 | for (int i = 0; i < _opIterations; i++) 24 | { 25 | _dataset[i] = rnd.NextDouble() * 100_000d; 26 | } 27 | } 28 | 29 | [Benchmark(Baseline = true)] 30 | [BenchmarkCategory("Observe")] 31 | public void Observe_Baseline() 32 | { 33 | for (var i = 0; i < _opIterations; i++) 34 | _theirSummary.Observe(_dataset[i]); 35 | } 36 | 37 | [Benchmark] 38 | [BenchmarkCategory("Observe")] 39 | public void Observe() 40 | { 41 | for (var i = 0; i < _opIterations; i++) 42 | _summary.Observe(_dataset[i]); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Benchmarks.Comparison/Summary/SummarySampleResolvingBenchmarks.cs: -------------------------------------------------------------------------------- 1 | extern alias Their; 2 | using BenchmarkDotNet.Attributes; 3 | 4 | namespace Prometheus.Client.Benchmarks.Comparison.Summary; 5 | 6 | public class SummarySampleResolvingBenchmarks : ComparisonBenchmarkBase 7 | { 8 | private IMetricFamily _summaryFamily; 9 | private IMetricFamily _summaryTuplesFamily; 10 | private Their.Prometheus.Summary _theirSummary; 11 | 12 | private string[][] _labels; 13 | 14 | [GlobalSetup] 15 | public void Setup() 16 | { 17 | _labels = GenerateLabelValues(10_000, 5, 0.1); 18 | 19 | _summaryTuplesFamily = OurMetricFactory.CreateSummary("_summaryFamilyTuples", string.Empty, ("label1", "label2", "label3", "label4", "label5" )); 20 | _summaryFamily = OurMetricFactory.CreateSummary("_summaryFamily", string.Empty, "label1", "label2", "label3", "label4", "label5"); 21 | _theirSummary = TheirMetricFactory.CreateSummary("_summary", string.Empty, "label1", "label2", "label3", "label4", "label5"); 22 | 23 | foreach (var lbls in _labels) 24 | { 25 | _theirSummary.WithLabels(lbls[0], lbls[1], lbls[2], lbls[3], lbls[4]); 26 | _summaryFamily.WithLabels(lbls[0], lbls[1], lbls[2], lbls[3], lbls[4]); 27 | _summaryTuplesFamily.WithLabels((lbls[0], lbls[1], lbls[2], lbls[3], lbls[4])); 28 | } 29 | } 30 | 31 | [Benchmark(Baseline = true)] 32 | [BenchmarkCategory("ResolveLabeled")] 33 | public void ResolveLabeled_Baseline() 34 | { 35 | foreach (var lbls in _labels) 36 | _theirSummary.WithLabels(lbls[0], lbls[1], lbls[2], lbls[3], lbls[4]); 37 | } 38 | 39 | [Benchmark] 40 | [BenchmarkCategory("ResolveLabeled")] 41 | public void ResolveLabeled_Array() 42 | { 43 | foreach (var lbls in _labels) 44 | _summaryFamily.WithLabels(lbls[0], lbls[1], lbls[2], lbls[3], lbls[4]); 45 | } 46 | 47 | [Benchmark] 48 | [BenchmarkCategory("ResolveLabeled")] 49 | public void ResolveLabeled_Tuple() 50 | { 51 | foreach (var lbls in _labels) 52 | _summaryTuplesFamily.WithLabels((lbls[0], lbls[1], lbls[2], lbls[3], lbls[4])); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Benchmarks/Counter/CounterCollection.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using BenchmarkDotNet.Attributes; 3 | using Prometheus.Client.Collectors; 4 | 5 | namespace Prometheus.Client.Benchmarks.Counter; 6 | 7 | [MemoryDiagnoser] 8 | [MinColumn, MaxColumn, MeanColumn, MedianColumn] 9 | public class CounterCollection 10 | { 11 | private CollectorRegistry _registry; 12 | private MemoryStream _stream; 13 | 14 | [GlobalSetup] 15 | public void Setup() 16 | { 17 | _registry = new CollectorRegistry(); 18 | var factory = new MetricFactory(_registry); 19 | var counter = factory.CreateCounter("counter", string.Empty, "label"); 20 | counter.Inc(); 21 | counter.WithLabels("test").Inc(2); 22 | 23 | _stream = new MemoryStream(); 24 | } 25 | 26 | [IterationSetup] 27 | public void IterationSetup() 28 | { 29 | _stream.Seek(0, SeekOrigin.Begin); 30 | } 31 | 32 | [Benchmark] 33 | public void Collect() 34 | { 35 | ScrapeHandler.ProcessAsync(_registry, _stream).GetAwaiter().GetResult(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Benchmarks/Counter/CounterCreation.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using Prometheus.Client.Collectors; 3 | 4 | namespace Prometheus.Client.Benchmarks.Counter; 5 | 6 | [MemoryDiagnoser] 7 | [MinColumn, MaxColumn, MeanColumn, MedianColumn] 8 | public class CounterCreation 9 | { 10 | private IMetricFactory _factory; 11 | 12 | [GlobalSetup] 13 | public void Setup() 14 | { 15 | _factory = new MetricFactory(new CollectorRegistry()); 16 | } 17 | 18 | [Benchmark] 19 | public ICounter Creation() 20 | { 21 | return _factory.CreateCounter("counter", string.Empty); 22 | } 23 | 24 | [Benchmark] 25 | public IMetricFamily CreationWithLabels() 26 | { 27 | return _factory.CreateCounter("counter", "help", "label1", "label2"); 28 | } 29 | 30 | [Benchmark] 31 | public IMetricFamily CreationWithTupleLabels() 32 | { 33 | return _factory.CreateCounter("counter", "help", ("label1", "label2")); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Benchmarks/Counter/CounterUsage.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using Prometheus.Client.Collectors; 3 | 4 | namespace Prometheus.Client.Benchmarks.Counter; 5 | 6 | [MemoryDiagnoser] 7 | [MinColumn, MaxColumn, MeanColumn, MedianColumn] 8 | public class CounterUsage 9 | { 10 | private IMetricFamily _counter; 11 | 12 | [GlobalSetup] 13 | public void Setup() 14 | { 15 | var factory = new MetricFactory(new CollectorRegistry()); 16 | _counter = factory.CreateCounter("counter", string.Empty, "label1", "label2"); 17 | } 18 | 19 | [Benchmark] 20 | public ICounter LabelledCreation() 21 | { 22 | return _counter.WithLabels("test label1", "test label2"); 23 | } 24 | 25 | [Benchmark] 26 | public void Inc() 27 | { 28 | _counter.Inc(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Benchmarks/DefaultMetricsCollection.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using BenchmarkDotNet.Attributes; 3 | using Prometheus.Client.Collectors; 4 | 5 | namespace Prometheus.Client.Benchmarks; 6 | 7 | [MemoryDiagnoser] 8 | [MinColumn, MaxColumn, MeanColumn, MedianColumn] 9 | public class DefaultMetricsCollection 10 | { 11 | private CollectorRegistry _registry; 12 | private MemoryStream _stream; 13 | 14 | [GlobalSetup] 15 | public void Setup() 16 | { 17 | _registry = new CollectorRegistry(); 18 | _registry.UseDefaultCollectors(); 19 | 20 | _stream = new MemoryStream(); 21 | } 22 | 23 | [IterationSetup] 24 | public void IterationSetup() 25 | { 26 | _stream.Seek(0, SeekOrigin.Begin); 27 | } 28 | 29 | [Benchmark] 30 | public void Collect() 31 | { 32 | ScrapeHandler.ProcessAsync(_registry, _stream).GetAwaiter().GetResult(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Benchmarks/Gauge/GaugeCollection.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using BenchmarkDotNet.Attributes; 3 | using Prometheus.Client.Collectors; 4 | 5 | namespace Prometheus.Client.Benchmarks.Gauge; 6 | 7 | [MemoryDiagnoser] 8 | [MinColumn, MaxColumn, MeanColumn, MedianColumn] 9 | public class GaugeCollection 10 | { 11 | private CollectorRegistry _registry; 12 | private MemoryStream _stream; 13 | 14 | [GlobalSetup] 15 | public void Setup() 16 | { 17 | _registry = new CollectorRegistry(); 18 | var factory = new MetricFactory(_registry); 19 | var gauge = factory.CreateGauge("gauge", string.Empty, "label"); 20 | gauge.Inc(); 21 | gauge.WithLabels("test").Inc(2); 22 | 23 | _stream = new MemoryStream(); 24 | } 25 | 26 | [IterationSetup] 27 | public void IterationSetup() 28 | { 29 | _stream.Seek(0, SeekOrigin.Begin); 30 | } 31 | 32 | [Benchmark] 33 | public void Collect() 34 | { 35 | ScrapeHandler.ProcessAsync(_registry, _stream).GetAwaiter().GetResult(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Benchmarks/Gauge/GaugeCreation.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using Prometheus.Client.Collectors; 3 | 4 | namespace Prometheus.Client.Benchmarks.Gauge; 5 | 6 | [MemoryDiagnoser] 7 | [MinColumn, MaxColumn, MeanColumn, MedianColumn] 8 | public class GaugeCreation 9 | { 10 | private IMetricFactory _factory; 11 | 12 | [GlobalSetup] 13 | public void Setup() 14 | { 15 | _factory = new MetricFactory(new CollectorRegistry()); 16 | } 17 | 18 | [Benchmark] 19 | public IGauge Creation() 20 | { 21 | return _factory.CreateGauge("gauge", string.Empty); 22 | } 23 | 24 | [Benchmark] 25 | public IMetricFamily CreationWithLabels() 26 | { 27 | return _factory.CreateGauge("gauge", "help", "label1", "label2"); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Benchmarks/Gauge/GaugeUsage.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using Prometheus.Client.Collectors; 3 | 4 | namespace Prometheus.Client.Benchmarks.Gauge; 5 | 6 | [MemoryDiagnoser] 7 | [MinColumn, MaxColumn, MeanColumn, MedianColumn] 8 | public class GaugeUsage 9 | { 10 | private IMetricFamily _gauge; 11 | 12 | [GlobalSetup] 13 | public void Setup() 14 | { 15 | var factory = new MetricFactory(new CollectorRegistry()); 16 | _gauge = factory.CreateGauge("gauge", string.Empty, "label1", "label2"); 17 | } 18 | 19 | [Benchmark] 20 | public IGauge LabelledCreation() 21 | { 22 | return _gauge.WithLabels("test label"); 23 | } 24 | 25 | [Benchmark] 26 | public void Inc() 27 | { 28 | _gauge.Inc(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Benchmarks/Histogram/HistogramCollection.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using BenchmarkDotNet.Attributes; 3 | using Prometheus.Client.Collectors; 4 | 5 | namespace Prometheus.Client.Benchmarks.Histogram; 6 | 7 | [MemoryDiagnoser] 8 | [MinColumn, MaxColumn, MeanColumn, MedianColumn] 9 | public class HistogramCollection 10 | { 11 | private CollectorRegistry _registry; 12 | private MemoryStream _stream; 13 | 14 | [GlobalSetup] 15 | public void Setup() 16 | { 17 | _registry = new CollectorRegistry(); 18 | var factory = new MetricFactory(_registry); 19 | var histogram = factory.CreateHistogram("histogram", string.Empty, "label"); 20 | histogram.Observe(1); 21 | histogram.Observe(10); 22 | histogram.Observe(20); 23 | histogram.WithLabels("test").Observe(2); 24 | 25 | _stream = new MemoryStream(); 26 | } 27 | 28 | [IterationSetup] 29 | public void IterationSetup() 30 | { 31 | _stream.Seek(0, SeekOrigin.Begin); 32 | } 33 | 34 | [Benchmark] 35 | public void Collect() 36 | { 37 | ScrapeHandler.ProcessAsync(_registry, _stream).GetAwaiter().GetResult(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Benchmarks/Histogram/HistogramCreation.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using Prometheus.Client.Collectors; 3 | 4 | namespace Prometheus.Client.Benchmarks.Histogram; 5 | 6 | [MemoryDiagnoser] 7 | [MinColumn, MaxColumn, MeanColumn, MedianColumn] 8 | public class HistogramCreation 9 | { 10 | private IMetricFactory _factory; 11 | 12 | [GlobalSetup] 13 | public void Setup() 14 | { 15 | _factory = new MetricFactory(new CollectorRegistry()); 16 | } 17 | 18 | [Benchmark] 19 | public IHistogram Creation() 20 | { 21 | return _factory.CreateHistogram("histogram", string.Empty); 22 | } 23 | 24 | [Benchmark] 25 | public IMetricFamily CreationWithLabels() 26 | { 27 | return _factory.CreateHistogram("histogram", "help", "label1", "label2"); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Benchmarks/Histogram/HistogramUsage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using BenchmarkDotNet.Attributes; 3 | using Prometheus.Client.Collectors; 4 | 5 | namespace Prometheus.Client.Benchmarks.Histogram; 6 | 7 | [MemoryDiagnoser] 8 | [MinColumn, MaxColumn, MeanColumn, MedianColumn] 9 | public class HistogramUsage 10 | { 11 | private IMetricFamily _histogram; 12 | private Random _rnd; 13 | 14 | [GlobalSetup] 15 | public void Setup() 16 | { 17 | var factory = new MetricFactory(new CollectorRegistry()); 18 | _histogram = factory.CreateHistogram("histogram", string.Empty, "label1", "label2"); 19 | _rnd = new Random(); 20 | } 21 | 22 | [Benchmark] 23 | public IHistogram LabelledCreation() 24 | { 25 | return _histogram.WithLabels("test label"); 26 | } 27 | 28 | [Benchmark] 29 | public void Observe() 30 | { 31 | _histogram.Observe(_rnd.Next(100)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Benchmarks/Program.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Running; 2 | 3 | namespace Prometheus.Client.Benchmarks; 4 | 5 | class Program 6 | { 7 | static void Main() 8 | { 9 | BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Benchmarks/Prometheus.Client.Benchmarks.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0 4 | false 5 | $(NoWarn);SA1133 6 | Exe 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Benchmarks/SerializationBenchmarks.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using BenchmarkDotNet.Attributes; 3 | using Prometheus.Client.Collectors; 4 | 5 | namespace Prometheus.Client.Benchmarks; 6 | 7 | [MemoryDiagnoser] 8 | public class SerializationBenchmarks 9 | { 10 | // Metric -> Variant -> Label values 11 | private static readonly string[][][] _labelValueRows; 12 | 13 | private const int _metricCount = 100; 14 | private const int _variantCount = 100; 15 | private const int _labelCount = 5; 16 | 17 | private const string _help = "arbitrary help message for metric, not relevant for benchmarking"; 18 | 19 | static SerializationBenchmarks() 20 | { 21 | _labelValueRows = new string[_metricCount][][]; 22 | 23 | for (var metricIndex = 0; metricIndex < _metricCount; metricIndex++) 24 | { 25 | var variants = new string[_variantCount][]; 26 | _labelValueRows[metricIndex] = variants; 27 | 28 | for (var variantIndex = 0; variantIndex < _variantCount; variantIndex++) 29 | { 30 | var values = new string[_labelCount]; 31 | _labelValueRows[metricIndex][variantIndex] = values; 32 | 33 | for (var labelIndex = 0; labelIndex < _labelCount; labelIndex++) 34 | values[labelIndex] = $"metric{metricIndex:D2}_label{labelIndex:D2}_variant{variantIndex:D2}"; 35 | } 36 | } 37 | } 38 | 39 | private readonly CollectorRegistry _registry = new CollectorRegistry(); 40 | private readonly IMetricFamily[] _counters; 41 | private readonly IMetricFamily[] _gauges; 42 | private readonly IMetricFamily[] _summaries; 43 | private readonly IMetricFamily[] _histograms; 44 | 45 | public SerializationBenchmarks() 46 | { 47 | _counters = new IMetricFamily[_metricCount]; 48 | _gauges = new IMetricFamily[_metricCount]; 49 | _summaries = new IMetricFamily[_metricCount]; 50 | _histograms = new IMetricFamily[_metricCount]; 51 | 52 | var factory = new MetricFactory(_registry); 53 | 54 | // Just use 1st variant for the keys (all we care about are that there is some name-like value in there). 55 | for (var metricIndex = 0; metricIndex < _metricCount; metricIndex++) 56 | { 57 | _counters[metricIndex] = factory.CreateCounter($"counter{metricIndex:D2}", _help, _labelValueRows[metricIndex][0]); 58 | _gauges[metricIndex] = factory.CreateGauge($"gauge{metricIndex:D2}", _help, _labelValueRows[metricIndex][0]); 59 | _summaries[metricIndex] = factory.CreateSummary($"summary{metricIndex:D2}", _help, _labelValueRows[metricIndex][0]); 60 | _histograms[metricIndex] = factory.CreateHistogram($"histogram{metricIndex:D2}", _help, _labelValueRows[metricIndex][0]); 61 | } 62 | } 63 | 64 | [GlobalSetup] 65 | public void GenerateData() 66 | { 67 | for (var metricIndex = 0; metricIndex < _metricCount; metricIndex++) 68 | { 69 | for (var variantIndex = 0; variantIndex < _variantCount; variantIndex++) 70 | { 71 | _counters[metricIndex].WithLabels(_labelValueRows[metricIndex][variantIndex]).Inc(); 72 | _gauges[metricIndex].WithLabels(_labelValueRows[metricIndex][variantIndex]).Inc(); 73 | _summaries[metricIndex].WithLabels(_labelValueRows[metricIndex][variantIndex]).Observe(variantIndex); 74 | _histograms[metricIndex].WithLabels(_labelValueRows[metricIndex][variantIndex]).Observe(variantIndex); 75 | } 76 | } 77 | } 78 | 79 | [Benchmark] 80 | public void CollectAndSerialize() 81 | { 82 | using (var stream = Stream.Null) 83 | { 84 | ScrapeHandler.ProcessAsync(_registry, stream).GetAwaiter().GetResult(); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Benchmarks/Summary/SummaryCollection.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using BenchmarkDotNet.Attributes; 3 | using Prometheus.Client.Collectors; 4 | 5 | namespace Prometheus.Client.Benchmarks.Summary; 6 | 7 | [MemoryDiagnoser] 8 | [MinColumn, MaxColumn, MeanColumn, MedianColumn] 9 | public class SummaryCollection 10 | { 11 | private CollectorRegistry _registry; 12 | private MemoryStream _stream; 13 | 14 | [GlobalSetup] 15 | public void Setup() 16 | { 17 | _registry = new CollectorRegistry(); 18 | var factory = new MetricFactory(_registry); 19 | var summary = factory.CreateSummary("summary", string.Empty, "label"); 20 | summary.Observe(1); 21 | summary.Observe(10); 22 | summary.Observe(20); 23 | summary.WithLabels("test").Observe(2); 24 | 25 | _stream = new MemoryStream(); 26 | } 27 | 28 | [IterationSetup] 29 | public void IterationSetup() 30 | { 31 | _stream.Seek(0, SeekOrigin.Begin); 32 | } 33 | 34 | [Benchmark] 35 | public void Collect() 36 | { 37 | ScrapeHandler.ProcessAsync(_registry, _stream).GetAwaiter().GetResult(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Benchmarks/Summary/SummaryCreation.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using Prometheus.Client.Collectors; 3 | 4 | namespace Prometheus.Client.Benchmarks.Summary; 5 | 6 | [MemoryDiagnoser] 7 | [MinColumn, MaxColumn, MeanColumn, MedianColumn] 8 | public class SummaryCreation 9 | { 10 | private IMetricFactory _factory; 11 | 12 | [GlobalSetup] 13 | public void Setup() 14 | { 15 | _factory = new MetricFactory(new CollectorRegistry()); 16 | } 17 | 18 | [Benchmark] 19 | public ISummary Creation(int i) 20 | { 21 | return _factory.CreateSummary($"summary1_{i.ToString()}", string.Empty); 22 | } 23 | 24 | [Benchmark] 25 | public IMetricFamily CreationWithLabels() 26 | { 27 | return _factory.CreateSummary("summary2", "help", "label1", "label2"); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Benchmarks/Summary/SummaryUsage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using BenchmarkDotNet.Attributes; 3 | using Prometheus.Client.Collectors; 4 | 5 | namespace Prometheus.Client.Benchmarks.Summary; 6 | 7 | [MemoryDiagnoser] 8 | [MinColumn, MaxColumn, MeanColumn, MedianColumn] 9 | public class SummaryUsage 10 | { 11 | private IMetricFamily _summary; 12 | private Random _rnd; 13 | 14 | [GlobalSetup] 15 | public void Setup() 16 | { 17 | var factory = new MetricFactory(new CollectorRegistry()); 18 | _summary = factory.CreateSummary("summary", string.Empty, "label1", "label2"); 19 | _rnd = new Random(); 20 | } 21 | 22 | [Benchmark] 23 | public ISummary LabelledCreation() 24 | { 25 | return _summary.WithLabels("test label"); 26 | } 27 | 28 | [Benchmark] 29 | public void Observe() 30 | { 31 | _summary.Observe(_rnd.Next(100)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/CollectionTestHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Reflection; 4 | using System.Threading.Tasks; 5 | using Prometheus.Client.Collectors; 6 | using Prometheus.Client.MetricsWriter; 7 | using Xunit; 8 | 9 | namespace Prometheus.Client.Tests; 10 | 11 | internal static class CollectionTestHelper 12 | { 13 | public static Task TestCollectionAsync(Action metricsSetup, string resourceName) 14 | { 15 | return TestCollectionAsync(registry => 16 | { 17 | var factory = new MetricFactory(registry); 18 | metricsSetup(factory); 19 | }, resourceName); 20 | } 21 | 22 | public static async Task TestCollectionAsync(Action setup, string resourceName) 23 | { 24 | var registry = new CollectorRegistry(); 25 | 26 | setup(registry); 27 | 28 | string formattedText; 29 | 30 | using (var stream = new MemoryStream()) 31 | { 32 | using (var writer = new MetricsTextWriter(stream)) 33 | { 34 | await registry.CollectToAsync(writer); 35 | 36 | await writer.CloseWriterAsync(); 37 | } 38 | 39 | stream.Seek(0, SeekOrigin.Begin); 40 | 41 | using (var streamReader = new StreamReader(stream)) 42 | { 43 | formattedText = await streamReader.ReadToEndAsync(); 44 | } 45 | } 46 | 47 | Assert.Equal(GetFileContent(resourceName), formattedText); 48 | } 49 | 50 | private static string GetFileContent(string resourcePath) 51 | { 52 | var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourcePath)!; 53 | using var reader = new StreamReader(stream); 54 | return reader.ReadToEnd().ToUnixLineEndings(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/CollectorRegistryExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Prometheus.Client.Collectors; 3 | using Xunit; 4 | 5 | namespace Prometheus.Client.Tests; 6 | 7 | public class CollectorRegistryExtensionsTests 8 | { 9 | [Fact] 10 | public void MoveToThrowsOnNullDestination() 11 | { 12 | var source = new CollectorRegistry(); 13 | var factory = new MetricFactory(source); 14 | 15 | factory.CreateCounter("test_counter", string.Empty).Inc(); 16 | 17 | Assert.Throws(() => source.MoveTo("test_counter", null)); 18 | } 19 | 20 | [Fact] 21 | public void MoveToThrowsOnNonExistingCollector() 22 | { 23 | var source = new CollectorRegistry(); 24 | var destination = new CollectorRegistry(); 25 | 26 | Assert.Throws(() => source.MoveTo("test_counter", destination)); 27 | } 28 | 29 | [Fact] 30 | public void MoveToRemovesCollectorFromSource() 31 | { 32 | var source = new CollectorRegistry(); 33 | var factory = new MetricFactory(source); 34 | var destination = new CollectorRegistry(); 35 | factory.CreateCounter("test_counter", string.Empty).Inc(); 36 | 37 | source.MoveTo("test_counter", destination); 38 | Assert.False(source.TryGet("test_counter", out _)); 39 | } 40 | 41 | [Fact] 42 | public void MoveToAddsCollectorToDestination() 43 | { 44 | var source = new CollectorRegistry(); 45 | var factory = new MetricFactory(source); 46 | var destination = new CollectorRegistry(); 47 | factory.CreateCounter("test_counter", string.Empty).Inc(); 48 | 49 | source.MoveTo("test_counter", destination); 50 | Assert.True(destination.TryGet("test_counter", out _)); 51 | } 52 | 53 | [Fact] 54 | public void CopyToThrowsOnNullDestination() 55 | { 56 | var source = new CollectorRegistry(); 57 | var factory = new MetricFactory(source); 58 | 59 | factory.CreateCounter("test_counter", string.Empty).Inc(); 60 | 61 | Assert.Throws(() => source.CopyTo("test_counter", null)); 62 | } 63 | 64 | [Fact] 65 | public void CopyToThrowsOnNonExistingCollector() 66 | { 67 | var source = new CollectorRegistry(); 68 | var destination = new CollectorRegistry(); 69 | 70 | Assert.Throws(() => source.CopyTo("test_counter", destination)); 71 | } 72 | 73 | [Fact] 74 | public void CopyToRetainsCollectorInSource() 75 | { 76 | var source = new CollectorRegistry(); 77 | var factory = new MetricFactory(source); 78 | var destination = new CollectorRegistry(); 79 | factory.CreateCounter("test_counter", string.Empty).Inc(); 80 | 81 | source.CopyTo("test_counter", destination); 82 | Assert.True(source.TryGet("test_counter", out _)); 83 | } 84 | 85 | [Fact] 86 | public void CopyToAddsCollectorToDestination() 87 | { 88 | var source = new CollectorRegistry(); 89 | var factory = new MetricFactory(source); 90 | var destination = new CollectorRegistry(); 91 | factory.CreateCounter("test_counter", string.Empty).Inc(); 92 | 93 | source.CopyTo("test_counter", destination); 94 | Assert.True(destination.TryGet("test_counter", out _)); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/CollectorTests/CollectorRegistryExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using Prometheus.Client.Collectors; 2 | using Prometheus.Client.Collectors.DotNetStats; 3 | using Prometheus.Client.Collectors.ProcessStats; 4 | using Xunit; 5 | 6 | namespace Prometheus.Client.Tests.CollectorTests; 7 | 8 | public class CollectorRegistryExtensionsTests 9 | { 10 | [Fact] 11 | public void UseDotNetStats_TryGet() 12 | { 13 | var collectorRegistry = new CollectorRegistry(); 14 | collectorRegistry.UseDotNetStats(); 15 | 16 | Assert.True(collectorRegistry.TryGet(nameof(GCCollectionCountCollector), out _)); 17 | Assert.True(collectorRegistry.TryGet(nameof(GCTotalMemoryCollector), out _)); 18 | } 19 | 20 | [Fact] 21 | public void UseDotNetStatsWithPrefix_TryGet() 22 | { 23 | var collectorRegistry = new CollectorRegistry(); 24 | collectorRegistry.UseDotNetStats("prefix"); 25 | 26 | Assert.True(collectorRegistry.TryGet(nameof(GCCollectionCountCollector), out _)); 27 | Assert.True(collectorRegistry.TryGet(nameof(GCTotalMemoryCollector), out _)); 28 | } 29 | 30 | [Fact] 31 | public void UseProcessStats_TryGet() 32 | { 33 | var collectorRegistry = new CollectorRegistry(); 34 | collectorRegistry.UseProcessStats(); 35 | 36 | Assert.True(collectorRegistry.TryGet(nameof(ProcessCollector), out _)); 37 | } 38 | 39 | [Fact] 40 | public void UseProcessStatsWithPrefix_TryGet() 41 | { 42 | var collectorRegistry = new CollectorRegistry(); 43 | collectorRegistry.UseProcessStats("prefix"); 44 | 45 | Assert.True(collectorRegistry.TryGet(nameof(ProcessCollector), out _)); 46 | } 47 | 48 | [Fact] 49 | public void UseDefaultCollectors_TryGet() 50 | { 51 | var collectorRegistry = new CollectorRegistry(); 52 | collectorRegistry.UseDefaultCollectors(); 53 | 54 | Assert.True(collectorRegistry.TryGet(nameof(GCCollectionCountCollector), out _)); 55 | Assert.True(collectorRegistry.TryGet(nameof(GCTotalMemoryCollector), out _)); 56 | Assert.True(collectorRegistry.TryGet(nameof(ProcessCollector), out _)); 57 | } 58 | 59 | [Fact] 60 | public void UseDefaultCollectorsWithPrefix_TryGet() 61 | { 62 | var collectorRegistry = new CollectorRegistry(); 63 | collectorRegistry.UseDefaultCollectors("prefix"); 64 | 65 | Assert.True(collectorRegistry.TryGet(nameof(GCCollectionCountCollector), out _)); 66 | Assert.True(collectorRegistry.TryGet(nameof(GCTotalMemoryCollector), out _)); 67 | Assert.True(collectorRegistry.TryGet(nameof(ProcessCollector), out _)); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/CollectorTests/GCCollectionCountCollectorTests.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | using System.Text; 4 | using Prometheus.Client.Collectors.DotNetStats; 5 | using Prometheus.Client.MetricsWriter; 6 | using Xunit; 7 | 8 | namespace Prometheus.Client.Tests.CollectorTests; 9 | 10 | public class GCCollectionCountCollectorTests 11 | { 12 | [Theory] 13 | [InlineData("")] 14 | [InlineData("123")] 15 | [InlineData("promitor_")] 16 | [InlineData("myprefix_")] 17 | public void Check_MetricNames(string prefixName) 18 | { 19 | var collector = new GCCollectionCountCollector(prefixName); 20 | 21 | Assert.Equal(prefixName + "dotnet_collection_count_total", collector.MetricNames.First()); 22 | } 23 | 24 | [Fact] 25 | public void Check_Collect_NoPrefix() 26 | { 27 | using var stream = new MemoryStream(); 28 | var metricWriter = new MetricsTextWriter(stream); 29 | var collector = new GCCollectionCountCollector(); 30 | collector.Collect(metricWriter); 31 | metricWriter.FlushAsync(); 32 | 33 | var response = Encoding.UTF8.GetString(stream.ToArray()); 34 | 35 | Assert.Contains("# TYPE dotnet_collection_count_total counter", response); 36 | } 37 | 38 | [Theory] 39 | [InlineData("")] 40 | [InlineData("123")] 41 | [InlineData("promitor_")] 42 | [InlineData("myprefix_")] 43 | public void Check_Collect(string prefixName) 44 | { 45 | using var stream = new MemoryStream(); 46 | var metricWriter = new MetricsTextWriter(stream); 47 | var collector = new GCCollectionCountCollector(prefixName); 48 | collector.Collect(metricWriter); 49 | metricWriter.FlushAsync(); 50 | 51 | var response = Encoding.UTF8.GetString(stream.ToArray()); 52 | 53 | Assert.Contains($"# TYPE {prefixName}dotnet_collection_count_total counter", response); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/CollectorTests/GCTotalMemoryCollectorTests.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | using System.Text; 4 | using Prometheus.Client.Collectors.DotNetStats; 5 | using Prometheus.Client.MetricsWriter; 6 | using Xunit; 7 | 8 | namespace Prometheus.Client.Tests.CollectorTests; 9 | 10 | public class GCTotalMemoryCollectorTests 11 | { 12 | [Theory] 13 | [InlineData("")] 14 | [InlineData("123")] 15 | [InlineData("promitor_")] 16 | [InlineData("myprefix_")] 17 | public void Check_MetricNames(string prefixName) 18 | { 19 | var collector = new GCTotalMemoryCollector(prefixName); 20 | 21 | Assert.Equal(prefixName + "dotnet_total_memory_bytes", collector.MetricNames.First()); 22 | } 23 | 24 | [Fact] 25 | public void Check_Collect_NoPrefix() 26 | { 27 | using var stream = new MemoryStream(); 28 | var metricWriter = new MetricsTextWriter(stream); 29 | var collector = new GCTotalMemoryCollector(); 30 | collector.Collect(metricWriter); 31 | metricWriter.FlushAsync(); 32 | 33 | var response = Encoding.UTF8.GetString(stream.ToArray()); 34 | 35 | Assert.Contains("# TYPE dotnet_total_memory_bytes gauge", response); 36 | } 37 | 38 | [Theory] 39 | [InlineData("")] 40 | [InlineData("123")] 41 | [InlineData("promitor_")] 42 | [InlineData("myprefix_")] 43 | public void Check_Collect(string prefixName) 44 | { 45 | using var stream = new MemoryStream(); 46 | var metricWriter = new MetricsTextWriter(stream); 47 | var collector = new GCTotalMemoryCollector(prefixName); 48 | collector.Collect(metricWriter); 49 | metricWriter.FlushAsync(); 50 | 51 | var response = Encoding.UTF8.GetString(stream.ToArray()); 52 | 53 | Assert.Contains($"# TYPE {prefixName}dotnet_total_memory_bytes gauge", response); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/CollectorTests/ProcessCollectorTests.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.IO; 3 | using System.Text; 4 | using Prometheus.Client.Collectors.ProcessStats; 5 | using Prometheus.Client.MetricsWriter; 6 | using Xunit; 7 | 8 | namespace Prometheus.Client.Tests.CollectorTests; 9 | 10 | public class ProcessCollectorTests 11 | { 12 | [Theory] 13 | [InlineData("")] 14 | [InlineData("123")] 15 | [InlineData("promitor_")] 16 | [InlineData("myprefix_")] 17 | public void Check_MetricNames(string prefixName) 18 | { 19 | var collector = new ProcessCollector(Process.GetCurrentProcess(), prefixName); 20 | 21 | Assert.Contains(prefixName + "process_cpu_seconds_total", collector.MetricNames); 22 | Assert.Contains(prefixName + "process_virtual_memory_bytes", collector.MetricNames); 23 | Assert.Contains(prefixName + "process_working_set_bytes", collector.MetricNames); 24 | Assert.Contains(prefixName + "process_private_memory_bytes", collector.MetricNames); 25 | Assert.Contains(prefixName + "process_num_threads", collector.MetricNames); 26 | Assert.Contains(prefixName + "process_processid", collector.MetricNames); 27 | Assert.Contains(prefixName + "process_start_time_seconds", collector.MetricNames); 28 | } 29 | 30 | [Fact] 31 | public void Check_Collect_NoPrefix() 32 | { 33 | using var stream = new MemoryStream(); 34 | var metricWriter = new MetricsTextWriter(stream); 35 | var collector = new ProcessCollector(Process.GetCurrentProcess()); 36 | collector.Collect(metricWriter); 37 | metricWriter.FlushAsync(); 38 | 39 | var response = Encoding.UTF8.GetString(stream.ToArray()); 40 | 41 | Assert.Contains("# TYPE process_cpu_seconds_total counter", response); 42 | Assert.Contains("# TYPE process_virtual_memory_bytes gauge", response); 43 | Assert.Contains("# TYPE process_working_set_bytes gauge", response); 44 | Assert.Contains("# TYPE process_private_memory_bytes gauge", response); 45 | Assert.Contains("# TYPE process_num_threads gauge", response); 46 | Assert.Contains("# TYPE process_processid gauge", response); 47 | Assert.Contains("# TYPE process_start_time_seconds gauge", response); 48 | } 49 | 50 | [Theory] 51 | [InlineData(null)] 52 | [InlineData("")] 53 | [InlineData("123")] 54 | [InlineData("promitor_")] 55 | [InlineData("myprefix_")] 56 | public void Check_Collect(string prefixName) 57 | { 58 | using var stream = new MemoryStream(); 59 | var metricWriter = new MetricsTextWriter(stream); 60 | var collector = new ProcessCollector(Process.GetCurrentProcess(), prefixName); 61 | collector.Collect(metricWriter); 62 | metricWriter.FlushAsync(); 63 | 64 | var response = Encoding.UTF8.GetString(stream.ToArray()); 65 | 66 | Assert.Contains($"# TYPE {prefixName}process_cpu_seconds_total counter", response); 67 | Assert.Contains($"# TYPE {prefixName}process_virtual_memory_bytes gauge", response); 68 | Assert.Contains($"# TYPE {prefixName}process_working_set_bytes gauge", response); 69 | Assert.Contains($"# TYPE {prefixName}process_private_memory_bytes gauge", response); 70 | Assert.Contains($"# TYPE {prefixName}process_num_threads gauge", response); 71 | Assert.Contains($"# TYPE {prefixName}process_processid gauge", response); 72 | Assert.Contains($"# TYPE {prefixName}process_start_time_seconds gauge", response); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/CounterInt64Tests/CollectionTests.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Xunit; 3 | 4 | namespace Prometheus.Client.Tests.CounterInt64Tests; 5 | 6 | public class CollectionTests 7 | { 8 | private const string _resourcesNamespace = "Prometheus.Client.Tests.CounterInt64Tests.Resources"; 9 | 10 | [Fact] 11 | public Task EmptyCollection() 12 | { 13 | return CollectionTestHelper.TestCollectionAsync(factory => { 14 | factory.CreateCounterInt64("test", "with help text"); 15 | }, $"{_resourcesNamespace}.CounterTests_Empty.txt"); 16 | } 17 | 18 | [Fact] 19 | public Task SuppressEmptySamples() 20 | { 21 | return CollectionTestHelper.TestCollectionAsync(factory => { 22 | var counter = factory.CreateCounterInt64("test", "with help text", "category"); 23 | counter.WithLabels("some").Inc(5); 24 | }, $"{_resourcesNamespace}.CounterTests_SuppressEmpty.txt"); 25 | } 26 | 27 | [Fact] 28 | public Task Collection() 29 | { 30 | return CollectionTestHelper.TestCollectionAsync(factory => { 31 | var counter = factory.CreateCounterInt64("test", "with help text", false, "category"); 32 | counter.Unlabelled.Inc(); 33 | counter.WithLabels("some").Inc(2); 34 | 35 | var counter2 = factory.CreateCounterInt64("nextcounter", "with help text", ("group", "type")); 36 | counter2.Unlabelled.Inc(10); 37 | counter2.WithLabels(("any", "2")).Inc(5); 38 | }, $"{_resourcesNamespace}.CounterTests_Collection.txt"); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/CounterInt64Tests/Resources/CounterTests_Collection.txt: -------------------------------------------------------------------------------- 1 | # HELP nextcounter with help text 2 | # TYPE nextcounter counter 3 | nextcounter 10 4 | nextcounter{group="any",type="2"} 5 5 | # HELP test with help text 6 | # TYPE test counter 7 | test 1 8 | test{category="some"} 2 9 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/CounterInt64Tests/Resources/CounterTests_Empty.txt: -------------------------------------------------------------------------------- 1 | # HELP test with help text 2 | # TYPE test counter 3 | test 0 4 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/CounterInt64Tests/Resources/CounterTests_SuppressEmpty.txt: -------------------------------------------------------------------------------- 1 | # HELP test with help text 2 | # TYPE test counter 3 | test{category="some"} 5 4 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/CounterInt64Tests/SampleTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | 4 | namespace Prometheus.Client.Tests.CounterInt64Tests; 5 | 6 | public class SampleTests 7 | { 8 | [Theory] 9 | [InlineData(0)] 10 | [InlineData(1)] 11 | [InlineData(3)] 12 | public void CanIncrement(long inc) 13 | { 14 | var counter = CreateCounter(); 15 | counter.Inc(inc); 16 | 17 | Assert.Equal(inc, counter.Value); 18 | } 19 | 20 | [Theory] 21 | [InlineData(-1)] 22 | [InlineData(-3)] 23 | public void CannotDecrement(long inc) 24 | { 25 | var counter = CreateCounter(); 26 | Assert.Throws(() => counter.Inc(inc)); 27 | } 28 | 29 | [Fact] 30 | public void DefaultIncrement() 31 | { 32 | var counter = CreateCounter(); 33 | counter.Inc(); 34 | 35 | Assert.Equal(1, counter.Value); 36 | } 37 | 38 | [Fact] 39 | public void ShouldResetValue() 40 | { 41 | var counter = CreateCounter(); 42 | counter.Inc(); 43 | 44 | counter.Reset(); 45 | Assert.Equal(0, counter.Value); 46 | } 47 | 48 | [Theory] 49 | [InlineData(0, 0, 0)] 50 | [InlineData(2, 10, 10)] 51 | [InlineData(10, 2, 10)] 52 | public void IncTo(long initial, long value, long expected) 53 | { 54 | var counter = CreateCounter(); 55 | counter.Inc(initial); 56 | 57 | counter.IncTo(value); 58 | 59 | Assert.Equal(expected, counter.Value); 60 | } 61 | 62 | private CounterInt64 CreateCounter() 63 | { 64 | var config = new MetricConfiguration("test", string.Empty, Array.Empty(), false, TimeSpan.Zero); 65 | return new CounterInt64(config, Array.Empty()); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/CounterInt64Tests/ThreadingTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Xunit; 5 | 6 | namespace Prometheus.Client.Tests.CounterInt64Tests; 7 | 8 | public class ThreadingTests 9 | { 10 | [Theory] 11 | [InlineData(10000, 1)] 12 | [InlineData(10000, 10)] 13 | [InlineData(10000, 100)] 14 | public async Task ObserveInParallel(int observations, int threads) 15 | { 16 | var metric = CreateCounter(); 17 | 18 | var tasks = Enumerable.Range(0, threads) 19 | .Select(n => Task.Run(() => 20 | { 21 | long vl = 0; 22 | var rnd = new Random(); 23 | for (var i = 0; i < observations; i++) 24 | { 25 | metric.Inc(rnd.Next()); 26 | if(i % 100 == 0) 27 | vl = metric.Value; 28 | } 29 | 30 | metric.Reset(); 31 | })) 32 | .ToArray(); 33 | 34 | await Task.WhenAll(tasks); 35 | } 36 | 37 | private CounterInt64 CreateCounter() 38 | { 39 | var config = new MetricConfiguration("test", string.Empty, Array.Empty(), false, TimeSpan.Zero); 40 | return new CounterInt64(config, Array.Empty()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/CounterTests/CollectionTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Xunit; 5 | 6 | namespace Prometheus.Client.Tests.CounterTests; 7 | 8 | public class CollectionTests 9 | { 10 | private const string _resourcesNamespace = "Prometheus.Client.Tests.CounterTests.Resources"; 11 | 12 | [Fact] 13 | public Task EmptyCollection() 14 | { 15 | return CollectionTestHelper.TestCollectionAsync(factory => { 16 | factory.CreateCounter("test", "with help text"); 17 | }, $"{_resourcesNamespace}.CounterTests_Empty.txt"); 18 | } 19 | 20 | [Fact] 21 | public Task SuppressEmptySamples() 22 | { 23 | return CollectionTestHelper.TestCollectionAsync(factory => { 24 | var counter = factory.CreateCounter("test", "with help text", "category"); 25 | counter.WithLabels("some").Inc(5.5); 26 | }, $"{_resourcesNamespace}.CounterTests_SuppressEmpty.txt"); 27 | } 28 | 29 | [Fact] 30 | public Task Collection() 31 | { 32 | return CollectionTestHelper.TestCollectionAsync(factory => { 33 | var counter = factory.CreateCounter("test", "with help text", "category"); 34 | counter.Unlabelled.Inc(); 35 | counter.WithLabels("some").Inc(2.1); 36 | 37 | var counter2 = factory.CreateCounter("nextcounter", "with help text", ("group", "type")); 38 | counter2.Unlabelled.Inc(10.1); 39 | counter2.WithLabels(("any", "2")).Inc(5.2); 40 | }, $"{_resourcesNamespace}.CounterTests_Collection.txt"); 41 | } 42 | 43 | [Fact] 44 | public Task RemoveExpiredSerieDueToTtl() 45 | { 46 | return CollectionTestHelper.TestCollectionAsync(registry => 47 | { 48 | var factoryWithoutTtl = new MetricFactory(registry); 49 | var counterWithoutTtl = factoryWithoutTtl.CreateCounter("test", "with help text", "category"); 50 | counterWithoutTtl.Unlabelled.Inc(); 51 | counterWithoutTtl.WithLabels("some").Inc(2.1); 52 | 53 | var factoryWithTtl = new MetricFactory(registry, TimeSpan.FromSeconds(1)); 54 | var counterWithTtl = factoryWithTtl.CreateCounter("nextcounter", "with help text", ("group", "type")); 55 | counterWithTtl.WithLabels(("old", "serie")).Inc(8.7); 56 | 57 | Thread.Sleep(1100); 58 | 59 | counterWithTtl.Unlabelled.Inc(10.1); 60 | counterWithTtl.WithLabels(("any", "2")).Inc(5.2); 61 | }, $"{_resourcesNamespace}.CounterTests_Collection.txt"); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/CounterTests/Resources/CounterTests_Collection.txt: -------------------------------------------------------------------------------- 1 | # HELP nextcounter with help text 2 | # TYPE nextcounter counter 3 | nextcounter 10.1 4 | nextcounter{group="any",type="2"} 5.2 5 | # HELP test with help text 6 | # TYPE test counter 7 | test 1 8 | test{category="some"} 2.1 9 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/CounterTests/Resources/CounterTests_Empty.txt: -------------------------------------------------------------------------------- 1 | # HELP test with help text 2 | # TYPE test counter 3 | test 0 4 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/CounterTests/Resources/CounterTests_SuppressEmpty.txt: -------------------------------------------------------------------------------- 1 | # HELP test with help text 2 | # TYPE test counter 3 | test{category="some"} 5.5 4 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/CounterTests/SampleTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | 4 | namespace Prometheus.Client.Tests.CounterTests; 5 | 6 | public class SampleTests 7 | { 8 | [Theory] 9 | [InlineData(0)] 10 | [InlineData(1)] 11 | [InlineData(3.1)] 12 | public void CanIncrement(double inc) 13 | { 14 | var counter = CreateCounter(); 15 | counter.Inc(inc); 16 | 17 | Assert.Equal(inc, counter.Value); 18 | } 19 | 20 | [Fact] 21 | public void ShouldIgnoreNaN() 22 | { 23 | var counter = CreateCounter(); 24 | counter.Inc(42); 25 | 26 | counter.Inc(double.NaN); 27 | 28 | Assert.Equal(42, counter.Value); 29 | } 30 | 31 | [Theory] 32 | [InlineData(-1)] 33 | [InlineData(-3.1)] 34 | public void CannotDecrement(double inc) 35 | { 36 | var counter = CreateCounter(); 37 | Assert.Throws(() => counter.Inc(inc)); 38 | } 39 | 40 | [Fact] 41 | public void DefaultIncrement() 42 | { 43 | var counter = CreateCounter(); 44 | counter.Inc(); 45 | 46 | Assert.Equal(1, counter.Value); 47 | } 48 | 49 | [Fact] 50 | public void ShouldResetValue() 51 | { 52 | var counter = CreateCounter(); 53 | counter.Inc(); 54 | 55 | counter.Reset(); 56 | Assert.Equal(0, counter.Value); 57 | } 58 | 59 | [Theory] 60 | [InlineData(0, 0, 0)] 61 | [InlineData(2, 10, 10)] 62 | [InlineData(10, 2, 10)] 63 | public void IncTo(double initial, double value, double expected) 64 | { 65 | var counter = CreateCounter(); 66 | counter.Inc(initial); 67 | 68 | counter.IncTo(value); 69 | 70 | Assert.Equal(expected, counter.Value); 71 | } 72 | 73 | [Fact] 74 | public void IncToThrowsOnNaN() 75 | { 76 | var counter = CreateCounter(); 77 | Assert.Throws(() => counter.IncTo(double.NaN)); 78 | } 79 | 80 | private Counter CreateCounter() 81 | { 82 | var config = new MetricConfiguration("test", string.Empty, Array.Empty(), false, TimeSpan.Zero); 83 | return new Counter(config, Array.Empty()); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/CounterTests/ThreadingTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Xunit; 5 | 6 | namespace Prometheus.Client.Tests.CounterTests; 7 | 8 | public class ThreadingTests 9 | { 10 | [Theory] 11 | [InlineData(10000, 1)] 12 | [InlineData(10000, 10)] 13 | [InlineData(10000, 100)] 14 | public async Task ObserveInParallel(int observations, int threads) 15 | { 16 | var metric = CreateCounter(); 17 | 18 | var tasks = Enumerable.Range(0, threads) 19 | .Select(n => Task.Run(() => 20 | { 21 | double vl = 0; 22 | var rnd = new Random(); 23 | for (var i = 0; i < observations; i++) 24 | { 25 | metric.Inc(rnd.NextDouble()); 26 | if(i % 100 == 0) 27 | vl = metric.Value; 28 | } 29 | 30 | metric.Reset(); 31 | })) 32 | .ToArray(); 33 | 34 | await Task.WhenAll(tasks); 35 | } 36 | 37 | private Counter CreateCounter() 38 | { 39 | var config = new MetricConfiguration("test", string.Empty, Array.Empty(), false, TimeSpan.Zero); 40 | return new Counter(config, Array.Empty()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/GaugeInt64Tests/CollectionTests.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Xunit; 3 | 4 | namespace Prometheus.Client.Tests.GaugeInt64Tests; 5 | 6 | public class CollectionTests 7 | { 8 | private const string _resourcesNamespace = "Prometheus.Client.Tests.GaugeInt64Tests.Resources"; 9 | 10 | [Fact] 11 | public Task EmptyCollection() 12 | { 13 | return CollectionTestHelper.TestCollectionAsync(factory => { 14 | factory.CreateGaugeInt64("test", "with help text"); 15 | }, $"{_resourcesNamespace}.GaugeTests_Empty.txt"); 16 | } 17 | 18 | [Fact] 19 | public Task SuppressEmptySamples() 20 | { 21 | return CollectionTestHelper.TestCollectionAsync(factory => { 22 | var gauge = factory.CreateGaugeInt64("test", "with help text", "category"); 23 | gauge.WithLabels("some").Inc(5); 24 | }, $"{_resourcesNamespace}.GaugeTests_SuppressEmpty.txt"); 25 | } 26 | 27 | [Fact] 28 | public Task Collection() 29 | { 30 | return CollectionTestHelper.TestCollectionAsync(factory => { 31 | var gauge = factory.CreateGaugeInt64("test", "with help text", "category"); 32 | gauge.Inc(); 33 | gauge.WithLabels("some").Inc(5); 34 | 35 | var gauge2 = factory.CreateGaugeInt64("nextgauge", "with help text", "group", "type"); 36 | gauge2.Inc(); 37 | gauge2.WithLabels("any", "2").Dec(5); 38 | }, $"{_resourcesNamespace}.GaugeTests_Collection.txt"); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/GaugeInt64Tests/Resources/GaugeTests_Collection.txt: -------------------------------------------------------------------------------- 1 | # HELP nextgauge with help text 2 | # TYPE nextgauge gauge 3 | nextgauge 1 4 | nextgauge{group="any",type="2"} -5 5 | # HELP test with help text 6 | # TYPE test gauge 7 | test 1 8 | test{category="some"} 5 9 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/GaugeInt64Tests/Resources/GaugeTests_Empty.txt: -------------------------------------------------------------------------------- 1 | # HELP test with help text 2 | # TYPE test gauge 3 | test 0 4 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/GaugeInt64Tests/Resources/GaugeTests_SuppressEmpty.txt: -------------------------------------------------------------------------------- 1 | # HELP test with help text 2 | # TYPE test gauge 3 | test{category="some"} 5 4 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/GaugeInt64Tests/SampleTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | 4 | namespace Prometheus.Client.Tests.GaugeInt64Tests; 5 | 6 | public class SampleTests 7 | { 8 | [Theory] 9 | [InlineData(0)] 10 | [InlineData(1)] 11 | [InlineData(3)] 12 | public void CanIncrement(long inc) 13 | { 14 | var gauge = CreateGauge(); 15 | gauge.Inc(inc); 16 | 17 | Assert.Equal(inc, gauge.Value); 18 | } 19 | 20 | [Theory] 21 | [InlineData(0)] 22 | [InlineData(1)] 23 | [InlineData(3)] 24 | public void CanDecrement(long dec) 25 | { 26 | var gauge = CreateGauge(); 27 | gauge.Dec(dec); 28 | 29 | Assert.Equal(-dec, gauge.Value); 30 | } 31 | 32 | [Theory] 33 | [InlineData(0)] 34 | [InlineData(1)] 35 | [InlineData(42)] 36 | [InlineData(-42)] 37 | public void CanSetValue(long value) 38 | { 39 | var gauge = CreateGauge(); 40 | gauge.Set(value); 41 | 42 | Assert.Equal(value, gauge.Value); 43 | } 44 | 45 | [Fact] 46 | public void DefaultDecValue() 47 | { 48 | var gauge = CreateGauge(); 49 | gauge.Dec(); 50 | 51 | Assert.Equal(-1, gauge.Value); 52 | } 53 | 54 | [Fact] 55 | public void DefaultIncValue() 56 | { 57 | var gauge = CreateGauge(); 58 | gauge.Inc(); 59 | 60 | Assert.Equal(1, gauge.Value); 61 | } 62 | 63 | [Fact] 64 | public void ShouldResetValue() 65 | { 66 | var gauge = CreateGauge(); 67 | gauge.Inc(); 68 | 69 | gauge.Reset(); 70 | Assert.Equal(0, gauge.Value); 71 | } 72 | 73 | [Theory] 74 | [InlineData(0, 0, 0)] 75 | [InlineData(2, 10, 10)] 76 | [InlineData(10, 2, 10)] 77 | [InlineData(-10, 10, 10)] 78 | [InlineData(-10, -2, -2)] 79 | [InlineData(-10, -20, -10)] 80 | public void IncTo(long initial, long value, long expected) 81 | { 82 | var gauge = CreateGauge(); 83 | gauge.Set(initial); 84 | 85 | gauge.IncTo(value); 86 | 87 | Assert.Equal(expected, gauge.Value); 88 | } 89 | 90 | [Theory] 91 | [InlineData(0, 0, 0)] 92 | [InlineData(2, 10, 2)] 93 | [InlineData(10, 2, 2)] 94 | [InlineData(-10, 10, -10)] 95 | [InlineData(-10, -2, -10)] 96 | [InlineData(-10, -20, -20)] 97 | public void DecTo(long initial, long value, long expected) 98 | { 99 | var gauge = CreateGauge(); 100 | gauge.Set(initial); 101 | 102 | gauge.DecTo(value); 103 | 104 | Assert.Equal(expected, gauge.Value); 105 | } 106 | 107 | private IGauge CreateGauge() 108 | { 109 | var config = new MetricConfiguration("test", string.Empty, Array.Empty(), false, TimeSpan.Zero); 110 | return new GaugeInt64(config, Array.Empty()); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/GaugeInt64Tests/ThreadingTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Xunit; 5 | 6 | namespace Prometheus.Client.Tests.GaugeInt64Tests; 7 | 8 | public class ThreadingTests 9 | { 10 | [Theory] 11 | [InlineData(10000, 1)] 12 | [InlineData(10000, 10)] 13 | [InlineData(10000, 100)] 14 | public async Task ObserveInParallel(int observations, int threads) 15 | { 16 | var metric = CreateGauge(); 17 | 18 | var tasks = Enumerable.Range(0, threads) 19 | .Select(n => Task.Run(() => 20 | { 21 | long vl; 22 | var rnd = new Random(); 23 | for (var i = 0; i < observations; i++) 24 | { 25 | metric.Inc(rnd.Next()); 26 | if (i % 100 == 0) 27 | vl = metric.Value; 28 | } 29 | 30 | metric.Reset(); 31 | })) 32 | .ToArray(); 33 | 34 | await Task.WhenAll(tasks); 35 | } 36 | 37 | private IGauge CreateGauge() 38 | { 39 | var config = new MetricConfiguration("test", string.Empty, Array.Empty(), false, TimeSpan.Zero); 40 | return new GaugeInt64(config, Array.Empty()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/GaugeTests/CollectionTests.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Xunit; 3 | 4 | namespace Prometheus.Client.Tests.GaugeTests; 5 | 6 | public class CollectionTests 7 | { 8 | private const string _resourcesNamespace = "Prometheus.Client.Tests.GaugeTests.Resources"; 9 | 10 | [Fact] 11 | public Task EmptyCollection() 12 | { 13 | return CollectionTestHelper.TestCollectionAsync(factory => { 14 | factory.CreateGauge("test", "with help text"); 15 | }, $"{_resourcesNamespace}.GaugeTests_Empty.txt"); 16 | } 17 | 18 | [Fact] 19 | public Task SuppressEmptySamples() 20 | { 21 | return CollectionTestHelper.TestCollectionAsync(factory => { 22 | var gauge = factory.CreateGauge("test", "with help text", "category"); 23 | gauge.WithLabels("some").Inc(5.5); 24 | }, $"{_resourcesNamespace}.GaugeTests_SuppressEmpty.txt"); 25 | } 26 | 27 | [Fact] 28 | public Task Collection() 29 | { 30 | return CollectionTestHelper.TestCollectionAsync(factory => { 31 | var gauge = factory.CreateGauge("test", "with help text", "category"); 32 | gauge.Inc(); 33 | gauge.WithLabels("some").Inc(5.5); 34 | 35 | var gauge2 = factory.CreateGauge("nextgauge", "with help text", "group", "type"); 36 | gauge2.Inc(); 37 | gauge2.WithLabels("any", "2").Dec(5.2); 38 | 39 | var nanGauge = factory.CreateGauge("nangauge", "example of NaN"); 40 | nanGauge.Set(double.NaN); 41 | }, $"{_resourcesNamespace}.GaugeTests_Collection.txt"); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/GaugeTests/Resources/GaugeTests_Collection.txt: -------------------------------------------------------------------------------- 1 | # HELP nangauge example of NaN 2 | # TYPE nangauge gauge 3 | nangauge NaN 4 | # HELP nextgauge with help text 5 | # TYPE nextgauge gauge 6 | nextgauge 1 7 | nextgauge{group="any",type="2"} -5.2 8 | # HELP test with help text 9 | # TYPE test gauge 10 | test 1 11 | test{category="some"} 5.5 12 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/GaugeTests/Resources/GaugeTests_Empty.txt: -------------------------------------------------------------------------------- 1 | # HELP test with help text 2 | # TYPE test gauge 3 | test 0 4 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/GaugeTests/Resources/GaugeTests_SuppressEmpty.txt: -------------------------------------------------------------------------------- 1 | # HELP test with help text 2 | # TYPE test gauge 3 | test{category="some"} 5.5 4 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/GaugeTests/ThreadingTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Xunit; 5 | 6 | namespace Prometheus.Client.Tests.GaugeTests; 7 | 8 | public class ThreadingTests 9 | { 10 | [Theory] 11 | [InlineData(10000, 1)] 12 | [InlineData(10000, 10)] 13 | [InlineData(10000, 100)] 14 | public async Task ObserveInParallel(int observations, int threads) 15 | { 16 | var metric = CreateGauge(); 17 | 18 | var tasks = Enumerable.Range(0, threads) 19 | .Select(n => Task.Run(() => 20 | { 21 | double vl; 22 | var rnd = new Random(); 23 | for (var i = 0; i < observations; i++) 24 | { 25 | metric.Inc(rnd.NextDouble()); 26 | if (i % 100 == 0) 27 | vl = metric.Value; 28 | } 29 | 30 | metric.Reset(); 31 | })) 32 | .ToArray(); 33 | 34 | await Task.WhenAll(tasks); 35 | } 36 | 37 | private IGauge CreateGauge() 38 | { 39 | var config = new MetricConfiguration("test", string.Empty, Array.Empty(), false, TimeSpan.Zero); 40 | return new Gauge(config, Array.Empty()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/HistogramTests/CollectionTests.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Xunit; 3 | 4 | namespace Prometheus.Client.Tests.HistogramTests; 5 | 6 | public class CollectionTests 7 | { 8 | private const string _resourcesNamespace = "Prometheus.Client.Tests.HistogramTests.Resources"; 9 | 10 | [Fact] 11 | public Task EmptyCollection() 12 | { 13 | return CollectionTestHelper.TestCollectionAsync(factory => { 14 | factory.CreateHistogram("hist1", "help", false, new[] { 1.0, 2.0, 3.0 }); 15 | }, $"{_resourcesNamespace}.HistogramTests_Empty.txt"); 16 | } 17 | 18 | [Fact] 19 | public Task SuppressEmptySamples() 20 | { 21 | return CollectionTestHelper.TestCollectionAsync(factory => { 22 | factory.CreateHistogram("hist1", "help", new[] { -5.0, 0, 5.0, 10 }, "type"); 23 | }, $"{_resourcesNamespace}.HistogramTests_SuppressEmpty.txt"); 24 | } 25 | 26 | [Fact] 27 | public Task Collection() 28 | { 29 | return CollectionTestHelper.TestCollectionAsync(factory => { 30 | var histogram = factory.CreateHistogram("hist1", "help", new[] { 1.0, 2.0, 3.0 }); 31 | histogram.Observe(1.5); 32 | histogram.Observe(2.5); 33 | histogram.Observe(1); 34 | histogram.Observe(2.4); 35 | histogram.Observe(2.1); 36 | histogram.Observe(0.4); 37 | histogram.Observe(1.4); 38 | histogram.Observe(1.5); 39 | histogram.Observe(3.9); 40 | 41 | var histogram2 = factory.CreateHistogram("hist2", "help2", new[] { -5.0, 0, 5.0, 10 }); 42 | histogram2.Observe(-20); 43 | histogram2.Observe(-1); 44 | histogram2.Observe(0); 45 | histogram2.Observe(2.5); 46 | histogram2.Observe(5); 47 | histogram2.Observe(9); 48 | histogram2.Observe(11); 49 | }, $"{_resourcesNamespace}.HistogramTests_Collection.txt"); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/HistogramTests/ExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NSubstitute; 3 | using Xunit; 4 | 5 | namespace Prometheus.Client.Tests.HistogramTests; 6 | 7 | public class ExtensionsTests 8 | { 9 | [Fact] 10 | public void ObserveWithTs() 11 | { 12 | var metric = Substitute.For(); 13 | var ts = DateTimeOffset.UtcNow; 14 | var inc = 2; 15 | 16 | metric.Observe(inc, ts); 17 | 18 | metric.Received().Observe(inc, ts.ToUnixTimeMilliseconds()); 19 | } 20 | 21 | [Fact] 22 | public void UnlabelledObserve() 23 | { 24 | var family = MockFamily(); 25 | var val = 2; 26 | 27 | family.Observe(val); 28 | 29 | family.Unlabelled.Received().Observe(val); 30 | } 31 | 32 | [Fact] 33 | public void UnlabelledObserveWithTs() 34 | { 35 | var family = MockFamily(); 36 | var ts = 123; 37 | var val = 2; 38 | 39 | family.Observe(val, ts); 40 | 41 | family.Unlabelled.Received().Observe(val, ts); 42 | } 43 | 44 | [Fact] 45 | public void UnlabelledObserveWithTsDate() 46 | { 47 | var family = MockFamily(); 48 | var ts = DateTimeOffset.UtcNow; 49 | var val = 2; 50 | 51 | family.Observe(val, ts); 52 | 53 | family.Unlabelled.Received().Observe(val, ts.ToUnixTimeMilliseconds()); 54 | } 55 | 56 | [Fact] 57 | public void UnlabelledTupleObserve() 58 | { 59 | var family = MockFamilyTuple(); 60 | var val = 2; 61 | 62 | family.Observe(val); 63 | 64 | family.Unlabelled.Received().Observe(val); 65 | } 66 | 67 | [Fact] 68 | public void UnlabelledTupleObserveWithTs() 69 | { 70 | var family = MockFamilyTuple(); 71 | var ts = 123; 72 | var val = 2; 73 | 74 | family.Observe(val, ts); 75 | 76 | family.Unlabelled.Received().Observe(val, ts); 77 | } 78 | 79 | [Fact] 80 | public void UnlabelledTupleObserveWithTsDate() 81 | { 82 | var family = MockFamilyTuple(); 83 | var ts = DateTimeOffset.UtcNow; 84 | var val = 2; 85 | 86 | family.Observe(val, ts); 87 | 88 | family.Unlabelled.Received().Observe(val, ts.ToUnixTimeMilliseconds()); 89 | } 90 | 91 | private IMetricFamily MockFamily() 92 | { 93 | var metric = Substitute.For(); 94 | var family = Substitute.For>(); 95 | family.Unlabelled.Returns(metric); 96 | 97 | return family; 98 | } 99 | 100 | private IMetricFamily MockFamilyTuple() 101 | { 102 | var metric = Substitute.For(); 103 | var family = Substitute.For>(); 104 | family.Unlabelled.Returns(metric); 105 | 106 | return family; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/HistogramTests/HistogramConfigurationTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | 4 | namespace Prometheus.Client.Tests.HistogramTests; 5 | 6 | public class HistogramConfigurationTests 7 | { 8 | [Fact] 9 | public void ShouldNotAllowReservedLabelNames() 10 | { 11 | HistogramConfiguration Create() 12 | { 13 | return new HistogramConfiguration( 14 | "test_name", 15 | string.Empty, 16 | new[] {"le"}, 17 | null, 18 | false, 19 | TimeSpan.Zero); 20 | } 21 | 22 | Assert.Throws(Create); 23 | } 24 | 25 | [Fact] 26 | public void ShouldNotAllowEmptyBuckets() 27 | { 28 | HistogramConfiguration Create() 29 | { 30 | return new HistogramConfiguration( 31 | "test_name", 32 | string.Empty, 33 | Array.Empty(), 34 | new double[0], 35 | false, 36 | TimeSpan.Zero); 37 | } 38 | 39 | Assert.Throws(Create); 40 | } 41 | 42 | [Fact] 43 | public void ShouldNotAllowWrongBuckets() 44 | { 45 | HistogramConfiguration Create() 46 | { 47 | return new HistogramConfiguration( 48 | "test_name", 49 | string.Empty, 50 | Array.Empty(), 51 | new [] { 0d, -1d }, 52 | false, 53 | TimeSpan.Zero); 54 | } 55 | 56 | Assert.Throws(Create); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/HistogramTests/Resources/HistogramTests_Collection.txt: -------------------------------------------------------------------------------- 1 | # HELP hist1 help 2 | # TYPE hist1 histogram 3 | hist1_bucket{le="1"} 2 4 | hist1_bucket{le="2"} 5 5 | hist1_bucket{le="3"} 8 6 | hist1_bucket{le="+Inf"} 9 7 | hist1_sum 16.7 8 | hist1_count 9 9 | # HELP hist2 help2 10 | # TYPE hist2 histogram 11 | hist2_bucket{le="-5"} 1 12 | hist2_bucket{le="0"} 3 13 | hist2_bucket{le="5"} 5 14 | hist2_bucket{le="10"} 6 15 | hist2_bucket{le="+Inf"} 7 16 | hist2_sum 6.5 17 | hist2_count 7 18 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/HistogramTests/Resources/HistogramTests_Empty.txt: -------------------------------------------------------------------------------- 1 | # HELP hist1 help 2 | # TYPE hist1 histogram 3 | hist1_bucket{le="1"} 0 4 | hist1_bucket{le="2"} 0 5 | hist1_bucket{le="3"} 0 6 | hist1_bucket{le="+Inf"} 0 7 | hist1_sum 0 8 | hist1_count 0 9 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/HistogramTests/Resources/HistogramTests_SuppressEmpty.txt: -------------------------------------------------------------------------------- 1 | # HELP hist1 help 2 | # TYPE hist1 histogram 3 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/HistogramTests/ThreadingTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Xunit; 5 | 6 | namespace Prometheus.Client.Tests.HistogramTests; 7 | 8 | public class ThreadingTests 9 | { 10 | [Theory] 11 | [InlineData(10000, 1)] 12 | [InlineData(10000, 10)] 13 | [InlineData(10000, 100)] 14 | public async Task ObserveInParallel(int observations, int threads) 15 | { 16 | var metric = CreateHistogram(); 17 | 18 | var tasks = Enumerable.Range(0, threads) 19 | .Select(n => Task.Run(() => 20 | { 21 | HistogramState vl; 22 | var rnd = new Random(); 23 | for (var i = 0; i < observations; i++) 24 | { 25 | metric.Observe(rnd.NextDouble()); 26 | if (i % 100 == 0) 27 | vl = metric.Value; 28 | } 29 | 30 | metric.Reset(); 31 | })) 32 | .ToArray(); 33 | 34 | await Task.WhenAll(tasks); 35 | } 36 | 37 | private IHistogram CreateHistogram(double[] buckets = null) 38 | { 39 | var config = new HistogramConfiguration("test", string.Empty, Array.Empty(), buckets, false, TimeSpan.Zero); 40 | return new Histogram(config, Array.Empty()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/MetricBaseTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Prometheus.Client.Tests.Mocks; 3 | using Xunit; 4 | 5 | namespace Prometheus.Client.Tests; 6 | 7 | public class MetricBaseTests 8 | { 9 | [Theory] 10 | [InlineData(1586594808L, null, 1586594808L)] 11 | [InlineData(1586594808L, 1586594808L, 1586594808L)] 12 | [InlineData(1586594808L, 1586594900L, 1586594808L)] 13 | [InlineData(1586594808L, 1586594700L, 1586594700L)] 14 | public void TimestampTests(long now, long? ts, long expectedTs) 15 | { 16 | var config = new MetricConfiguration("test", string.Empty, Array.Empty(), true, TimeSpan.Zero); 17 | 18 | var metric = new DummyMetric(config, Array.Empty(), () => DateTimeOffset.FromUnixTimeMilliseconds(now)); 19 | metric.Observe(ts); 20 | 21 | Assert.Equal(expectedTs, metric.Timestamp); 22 | } 23 | 24 | [Fact] 25 | public void ShouldIgnoreTsIfDisabled() 26 | { 27 | var config = new MetricConfiguration("test", string.Empty, Array.Empty(), false, TimeSpan.Zero); 28 | 29 | var metric = new DummyMetric(config, Array.Empty(), null); 30 | metric.Observe(1586594808); 31 | 32 | Assert.False(metric.Timestamp.HasValue); 33 | } 34 | 35 | [Fact] 36 | public void ShouldIgnoreTsIfDisabledAndTtlEnabled() 37 | { 38 | var config = new MetricConfiguration("test", string.Empty, Array.Empty(), false, TimeSpan.FromSeconds(1)); 39 | 40 | var metric = new DummyMetric(config, Array.Empty(), null); 41 | metric.Observe(1586594808); 42 | 43 | Assert.False(metric.Timestamp.HasValue); 44 | } 45 | 46 | [Fact] 47 | public void ShouldIgnoreTsIfCurrentIsMore() 48 | { 49 | var config = new MetricConfiguration("test", string.Empty, Array.Empty(), true, TimeSpan.Zero); 50 | 51 | var metric = new DummyMetric(config, Array.Empty(), null); 52 | metric.Observe(1586594808); 53 | 54 | metric.Observe(1586594700); 55 | 56 | Assert.Equal(1586594808, metric.Timestamp); 57 | } 58 | 59 | [Theory] 60 | [InlineData(1587000000L, 1586999999L, false)] 61 | [InlineData(1587000000L, 1587000000L, false)] 62 | [InlineData(1587000000L, 1587000100L, false)] 63 | [InlineData(1587000000L, 1587001000L, true)] 64 | [InlineData(1587000000L, 1587050000L, true)] 65 | public void ShouldExpireWhenTtlOutlived(long last, long now, bool isExpiredExpected) 66 | { 67 | var config = new MetricConfiguration("test", string.Empty, Array.Empty(), false, TimeSpan.FromMilliseconds(500)); 68 | 69 | var metric = new DummyMetric(config, Array.Empty(), () => DateTimeOffset.FromUnixTimeMilliseconds(now)); 70 | metric.Observe(last); 71 | 72 | Assert.Equal(isExpiredExpected, metric.IsExpired()); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/MetricConfigurationTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | 4 | namespace Prometheus.Client.Tests; 5 | 6 | public class MetricConfigurationTests 7 | { 8 | [Theory] 9 | [InlineData(null)] 10 | [InlineData("")] 11 | public void ThrowOnInvalidCollectorName(string collectorName) 12 | { 13 | Assert.Throws(() => new MetricConfiguration(collectorName, string.Empty, null, false, TimeSpan.Zero)); 14 | } 15 | 16 | [Theory] 17 | [InlineData("")] 18 | [InlineData(null)] 19 | [InlineData("my-metric")] 20 | [InlineData("my!metric")] 21 | [InlineData("my%metric")] 22 | [InlineData("123label")] 23 | [InlineData("__")] 24 | [InlineData("__label")] 25 | public void ThrowOnInvalidLabels(string label) 26 | { 27 | Assert.Throws(() => new MetricConfiguration("test_metric", string.Empty, new[] { label }, false, TimeSpan.Zero)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/MetricFactoryTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NSubstitute; 3 | using Prometheus.Client.Collectors; 4 | using Xunit; 5 | 6 | namespace Prometheus.Client.Tests; 7 | 8 | public class MetricFactoryTests 9 | { 10 | [Theory] 11 | [InlineData(0)] 12 | [InlineData(1)] 13 | [InlineData(16)] 14 | public void FactoryProxyUsesCache(int labelsCount) 15 | { 16 | var registry = new CollectorRegistry(); 17 | var factory = new MetricFactory(registry); 18 | 19 | var fn1 = factory.GetCounterFactory(labelsCount); 20 | var fn2 = factory.GetCounterFactory(labelsCount); 21 | 22 | Assert.True(fn1 == fn2); 23 | } 24 | 25 | [Fact] 26 | public void ReleaseCallRegistry() 27 | { 28 | var metricName = "some_metric"; 29 | var registry = Substitute.For(); 30 | var factory = new MetricFactory(registry); 31 | 32 | factory.Release(metricName); 33 | 34 | registry.Received().Remove(metricName); 35 | } 36 | 37 | [Theory] 38 | [InlineData("")] 39 | [InlineData(null)] 40 | public void ReleaseThrowsOnNull(string metricName) 41 | { 42 | var registry = Substitute.For(); 43 | var factory = new MetricFactory(registry); 44 | 45 | Assert.Throws(() => factory.Release(metricName)); 46 | } 47 | 48 | [Fact] 49 | public void Release2CallRegistry() 50 | { 51 | var metricName = "some_metric"; 52 | var metricFamily = Substitute.For>(); 53 | metricFamily.Name.Returns(metricName); 54 | 55 | var registry = Substitute.For(); 56 | var factory = new MetricFactory(registry); 57 | 58 | factory.Release(metricFamily); 59 | 60 | registry.Received().Remove(metricName); 61 | } 62 | 63 | [Fact] 64 | public void Release2ThrowsOnNull() 65 | { 66 | var registry = Substitute.For(); 67 | var factory = new MetricFactory(registry); 68 | IMetricFamily family = null; 69 | 70 | Assert.Throws(() => factory.Release(family)); 71 | } 72 | 73 | [Fact] 74 | public void Release3CallRegistry() 75 | { 76 | var metricName = "some_metric"; 77 | var metricFamily = Substitute.For>(); 78 | metricFamily.Name.Returns(metricName); 79 | 80 | var registry = Substitute.For(); 81 | var factory = new MetricFactory(registry); 82 | 83 | factory.Release(metricFamily); 84 | 85 | registry.Received().Remove(metricName); 86 | } 87 | 88 | [Fact] 89 | public void Release3ThrowsOnNull() 90 | { 91 | var registry = Substitute.For(); 92 | var factory = new MetricFactory(registry); 93 | IMetricFamily family = null; 94 | 95 | Assert.Throws(() => factory.Release(family)); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/Mocks/DummyCollector.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Prometheus.Client.Collectors; 3 | using Prometheus.Client.MetricsWriter; 4 | 5 | namespace Prometheus.Client.Tests.Mocks; 6 | 7 | public class DummyCollector : ICollector 8 | { 9 | public DummyCollector(string collectorName, params string[] metricNames) 10 | { 11 | Configuration = new CollectorConfiguration(collectorName); 12 | MetricNames = metricNames; 13 | } 14 | 15 | public CollectorConfiguration Configuration { get; } 16 | public IReadOnlyList MetricNames { get; } 17 | public void Collect(IMetricsWriter writer) 18 | { 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/Mocks/DummyMetric.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Prometheus.Client.MetricsWriter; 4 | 5 | namespace Prometheus.Client.Tests.Mocks; 6 | 7 | internal class DummyMetric : MetricBase, IDummyMetric 8 | { 9 | public DummyMetric(MetricConfiguration config, IReadOnlyList labelValues, Func currentTimeProvider) 10 | : base(config, labelValues, currentTimeProvider) 11 | { 12 | } 13 | 14 | public void Observe(long? ts) 15 | { 16 | TrackObservation(ts); 17 | } 18 | 19 | protected internal override void Collect(IMetricsWriter writer) 20 | { 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/Mocks/IDummyMetric.cs: -------------------------------------------------------------------------------- 1 | namespace Prometheus.Client.Tests.Mocks; 2 | 3 | public interface IDummyMetric : IMetric 4 | { 5 | void Observe(long? ts); 6 | } 7 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/Prometheus.Client.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net6.0;net8.0 4 | net462;net47;net481;net6.0;net8.0 5 | false 6 | true 7 | $(NoWarn);CS0618 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers 19 | 20 | 21 | all 22 | runtime; build; native; contentfiles; analyzers; buildtransitive 23 | 24 | 25 | all 26 | runtime; build; native; contentfiles; analyzers; buildtransitive 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Prometheus.Client.Tests; 4 | 5 | public static class StringExtensions 6 | { 7 | private const string _unix = "\n"; 8 | private const string _nonUnix = "\r\n"; 9 | 10 | public static string ToUnixLineEndings(this string s) 11 | { 12 | return Environment.NewLine == _unix ? s : s.Replace(_nonUnix, _unix); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/SummaryTests/ExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NSubstitute; 3 | using Xunit; 4 | 5 | namespace Prometheus.Client.Tests.SummaryTests; 6 | 7 | public class ExtensionsTests 8 | { 9 | [Fact] 10 | public void ObserveWithTs() 11 | { 12 | var metric = Substitute.For(); 13 | var ts = DateTimeOffset.UtcNow; 14 | var inc = 2; 15 | 16 | metric.Observe(inc, ts); 17 | 18 | metric.Received().Observe(inc, ts.ToUnixTimeMilliseconds()); 19 | } 20 | 21 | [Fact] 22 | public void UnlabelledObserve() 23 | { 24 | var family = MockFamily(); 25 | var val = 2; 26 | 27 | family.Observe(val); 28 | 29 | family.Unlabelled.Received().Observe(val); 30 | } 31 | 32 | [Fact] 33 | public void UnlabelledObserveWithTs() 34 | { 35 | var family = MockFamily(); 36 | var ts = 123; 37 | var val = 2; 38 | 39 | family.Observe(val, ts); 40 | 41 | family.Unlabelled.Received().Observe(val, ts); 42 | } 43 | 44 | [Fact] 45 | public void UnlabelledObserveWithTsDate() 46 | { 47 | var family = MockFamily(); 48 | var ts = DateTimeOffset.UtcNow; 49 | var val = 2; 50 | 51 | family.Observe(val, ts); 52 | 53 | family.Unlabelled.Received().Observe(val, ts.ToUnixTimeMilliseconds()); 54 | } 55 | 56 | [Fact] 57 | public void UnlabelledTupleObserve() 58 | { 59 | var family = MockFamilyTuple(); 60 | var val = 2; 61 | 62 | family.Observe(val); 63 | 64 | family.Unlabelled.Received().Observe(val); 65 | } 66 | 67 | [Fact] 68 | public void UnlabelledTupleObserveWithTs() 69 | { 70 | var family = MockFamilyTuple(); 71 | var ts = 123; 72 | var val = 2; 73 | 74 | family.Observe(val, ts); 75 | 76 | family.Unlabelled.Received().Observe(val, ts); 77 | } 78 | 79 | [Fact] 80 | public void UnlabelledTupleObserveWithTsDate() 81 | { 82 | var family = MockFamilyTuple(); 83 | var ts = DateTimeOffset.UtcNow; 84 | var val = 2; 85 | 86 | family.Observe(val, ts); 87 | 88 | family.Unlabelled.Received().Observe(val, ts.ToUnixTimeMilliseconds()); 89 | } 90 | 91 | private IMetricFamily MockFamily() 92 | { 93 | var metric = Substitute.For(); 94 | var family = Substitute.For>(); 95 | family.Unlabelled.Returns(metric); 96 | 97 | return family; 98 | } 99 | 100 | private IMetricFamily MockFamilyTuple() 101 | { 102 | var metric = Substitute.For(); 103 | var family = Substitute.For>(); 104 | family.Unlabelled.Returns(metric); 105 | 106 | return family; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/SummaryTests/FactoryTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Prometheus.Client.Collectors; 3 | using Xunit; 4 | 5 | namespace Prometheus.Client.Tests.SummaryTests; 6 | 7 | public class FactoryTests 8 | { 9 | private readonly IMetricFactory _factory; 10 | 11 | public FactoryTests() 12 | { 13 | _factory = new MetricFactory(new CollectorRegistry()); 14 | } 15 | 16 | [Fact] 17 | public void ThrowOnNameConflict_Strings() 18 | { 19 | _factory.CreateHistogram("test_histogram", string.Empty, "label1", "label2"); 20 | 21 | Assert.Throws(() => _factory.CreateSummary("test_histogram", string.Empty, "label1", "testlabel")); 22 | Assert.Throws(() => _factory.CreateSummary("test_histogram", string.Empty, new[] { "label1" })); 23 | Assert.Throws(() => _factory.CreateSummary("test_histogram", string.Empty, "label1", "label2", "label3")); 24 | } 25 | 26 | [Fact] 27 | public void SingleLabel_ConvertToTuple() 28 | { 29 | var metric = _factory.CreateSummary("metricname", "help", "label"); 30 | Assert.Equal(typeof(ValueTuple), metric.LabelNames.GetType()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/SummaryTests/RandomExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Prometheus.Client.Tests.SummaryTests; 4 | 5 | public static class RandomExtensions 6 | { 7 | public static double NormDouble(this Random r) 8 | { 9 | double u1 = r.NextDouble(); 10 | double u2 = r.NextDouble(); 11 | 12 | return Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/SummaryTests/SampleTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Xunit; 4 | 5 | namespace Prometheus.Client.Tests.SummaryTests; 6 | 7 | public class SampleTests 8 | { 9 | [Fact] 10 | public void ResetShouldClearObservations() 11 | { 12 | var summary = CreateSummary(); 13 | summary.Observe(123); 14 | 15 | summary.Reset(); 16 | 17 | var state = summary.Value; 18 | Assert.Equal(0,state.Count); 19 | Assert.Equal(0, state.Sum); 20 | Assert.True(state.Quantiles.All(b => double.IsNaN(b.Value))); 21 | } 22 | 23 | private ISummary CreateSummary() 24 | { 25 | var config = new SummaryConfiguration("test", string.Empty, Array.Empty(), false, TimeSpan.Zero); 26 | return new Summary(config, Array.Empty()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/SummaryTests/ThreadingTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Xunit; 5 | 6 | namespace Prometheus.Client.Tests.SummaryTests; 7 | 8 | public class ThreadingTests 9 | { 10 | [Theory] 11 | [InlineData(10000, 1)] 12 | [InlineData(10000, 10)] 13 | [InlineData(10000, 100)] 14 | public async Task ObserveInParallel(int observations, int threads) 15 | { 16 | var metric = CreateSummary(); 17 | 18 | var tasks = Enumerable.Range(0, threads) 19 | .Select(n => Task.Run(() => 20 | { 21 | SummaryState vl; 22 | var rnd = new Random(); 23 | for (var i = 0; i < observations; i++) 24 | { 25 | metric.Observe(rnd.NextDouble()); 26 | 27 | if (i % 100 == 0) 28 | vl = metric.Value; 29 | } 30 | 31 | metric.Reset(); 32 | })) 33 | .ToArray(); 34 | 35 | await Task.WhenAll(tasks); 36 | } 37 | 38 | private Summary CreateSummary() 39 | { 40 | var config = new SummaryConfiguration("test", string.Empty, Array.Empty(), false, TimeSpan.Zero); 41 | return new Summary(config, Array.Empty()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/UntypedTests/CollectionTests.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Xunit; 3 | 4 | namespace Prometheus.Client.Tests.UntypedTests; 5 | 6 | public class CollectionTests 7 | { 8 | private const string _resourcesNamespace = "Prometheus.Client.Tests.UntypedTests.Resources"; 9 | 10 | [Fact] 11 | public Task EmptyCollection() 12 | { 13 | return CollectionTestHelper.TestCollectionAsync(factory => { 14 | factory.CreateUntyped("test", "with help text"); 15 | }, $"{_resourcesNamespace}.UntypedTests_Empty.txt"); 16 | } 17 | 18 | [Fact] 19 | public Task Collection() 20 | { 21 | return CollectionTestHelper.TestCollectionAsync(factory => { 22 | var untyped = factory.CreateUntyped("test", "with help text", "category"); 23 | untyped.Set(1); 24 | untyped.WithLabels("some").Set(double.NaN); 25 | 26 | var untyped2 = factory.CreateUntyped("nextuntyped", "with help text", "group", "type"); 27 | untyped2.Set(10); 28 | untyped2.WithLabels("any", "2").Set(5); 29 | }, $"{_resourcesNamespace}.UntypedTests_Collection.txt"); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/UntypedTests/ExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NSubstitute; 3 | using Xunit; 4 | 5 | namespace Prometheus.Client.Tests.UntypedTests; 6 | 7 | public class ExtensionsTests 8 | { 9 | [Fact] 10 | public void SetWithTs() 11 | { 12 | var metric = Substitute.For(); 13 | var ts = DateTimeOffset.UtcNow; 14 | var val = 2; 15 | 16 | metric.Set(val, ts); 17 | 18 | metric.Received().Set(val, ts.ToUnixTimeMilliseconds()); 19 | } 20 | 21 | [Fact] 22 | public void UnlabelledSet() 23 | { 24 | var family = MockFamily(); 25 | var val = 2; 26 | 27 | family.Set(val); 28 | 29 | family.Unlabelled.Received().Set(val); 30 | } 31 | 32 | [Fact] 33 | public void UnlabelledSetWithTs() 34 | { 35 | var family = MockFamily(); 36 | var ts = 123; 37 | var val = 2; 38 | 39 | family.Set(val, ts); 40 | 41 | family.Unlabelled.Received().Set(val, ts); 42 | } 43 | 44 | [Fact] 45 | public void UnlabelledSetWithTsDate() 46 | { 47 | var family = MockFamily(); 48 | var ts = DateTimeOffset.UtcNow; 49 | var val = 2; 50 | 51 | family.Set(val, ts); 52 | 53 | family.Unlabelled.Received().Set(val, ts.ToUnixTimeMilliseconds()); 54 | } 55 | 56 | [Fact] 57 | public void UnlabelledTupleSet() 58 | { 59 | var family = MockFamilyTuple(); 60 | var val = 2; 61 | 62 | family.Set(val); 63 | 64 | family.Unlabelled.Received().Set(val); 65 | } 66 | 67 | [Fact] 68 | public void UnlabelledTupleSetWithTs() 69 | { 70 | var family = MockFamilyTuple(); 71 | var ts = 123; 72 | var val = 2; 73 | 74 | family.Set(val, ts); 75 | 76 | family.Unlabelled.Received().Set(val, ts); 77 | } 78 | 79 | [Fact] 80 | public void UnlabelledTupleSetWithTsDate() 81 | { 82 | var family = MockFamilyTuple(); 83 | var ts = DateTimeOffset.UtcNow; 84 | var val = 2; 85 | 86 | family.Set(val, ts); 87 | 88 | family.Unlabelled.Received().Set(val, ts.ToUnixTimeMilliseconds()); 89 | } 90 | 91 | private IMetricFamily MockFamily() 92 | { 93 | var metric = Substitute.For(); 94 | var family = Substitute.For>(); 95 | family.Unlabelled.Returns(metric); 96 | 97 | return family; 98 | } 99 | 100 | private IMetricFamily MockFamilyTuple() 101 | { 102 | var metric = Substitute.For(); 103 | var family = Substitute.For>(); 104 | family.Unlabelled.Returns(metric); 105 | 106 | return family; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/UntypedTests/Resources/UntypedTests_Collection.txt: -------------------------------------------------------------------------------- 1 | # HELP nextuntyped with help text 2 | # TYPE nextuntyped untyped 3 | nextuntyped 10 4 | nextuntyped{group="any",type="2"} 5 5 | # HELP test with help text 6 | # TYPE test untyped 7 | test 1 8 | test{category="some"} NaN 9 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/UntypedTests/Resources/UntypedTests_Empty.txt: -------------------------------------------------------------------------------- 1 | # HELP test with help text 2 | # TYPE test untyped 3 | test 0 4 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/UntypedTests/SampleTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | 4 | namespace Prometheus.Client.Tests.UntypedTests; 5 | 6 | public class SampleTests 7 | { 8 | [Theory] 9 | [InlineData(0)] 10 | [InlineData(1)] 11 | [InlineData(3.1)] 12 | [InlineData(-42)] 13 | [InlineData(double.NaN)] 14 | public void CanSet(double val) 15 | { 16 | var untyped = CreateUntyped(); 17 | untyped.Set(val); 18 | 19 | Assert.Equal(val, untyped.Value); 20 | } 21 | 22 | [Fact] 23 | public void ShouldResetValue() 24 | { 25 | var untyped = CreateUntyped(); 26 | untyped.Set(12); 27 | 28 | untyped.Reset(); 29 | Assert.Equal(0, untyped.Value); 30 | } 31 | 32 | private IUntyped CreateUntyped() 33 | { 34 | var config = new MetricConfiguration("test", string.Empty, Array.Empty(), false, TimeSpan.Zero); 35 | return new Untyped(config, Array.Empty()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/Prometheus.Client.Tests/UntypedTests/ThreadingTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Xunit; 5 | 6 | namespace Prometheus.Client.Tests.UntypedTests; 7 | 8 | public class ThreadingTests 9 | { 10 | [Theory] 11 | [InlineData(10000, 1)] 12 | [InlineData(10000, 10)] 13 | [InlineData(10000, 100)] 14 | public async Task ObserveInParallel(int observations, int threads) 15 | { 16 | var metric = CreateUntyped(); 17 | 18 | var tasks = Enumerable.Range(0, threads) 19 | .Select(n => Task.Run(() => 20 | { 21 | double vl; 22 | var rnd = new Random(); 23 | for (var i = 0; i < observations; i++) 24 | { 25 | metric.Set(rnd.NextDouble()); 26 | if (i % 100 == 0) 27 | vl = metric.Value; 28 | } 29 | 30 | metric.Reset(); 31 | })) 32 | .ToArray(); 33 | 34 | await Task.WhenAll(tasks); 35 | } 36 | 37 | private IUntyped CreateUntyped() 38 | { 39 | var config = new MetricConfiguration("test", string.Empty, Array.Empty(), false, TimeSpan.Zero); 40 | return new Untyped(config, Array.Empty()); 41 | } 42 | } 43 | --------------------------------------------------------------------------------