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