PrometheusExporter: New concurrency handling for scrape middleware + http server#2610
Conversation
Codecov Report
@@ Coverage Diff @@
## main #2610 +/- ##
==========================================
- Coverage 80.27% 80.22% -0.06%
==========================================
Files 256 257 +1
Lines 8826 8844 +18
==========================================
+ Hits 7085 7095 +10
- Misses 1741 1749 +8
|
src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusCollectionManager.cs
Show resolved
Hide resolved
| } | ||
| } | ||
|
|
||
| private async Task ProcessRequestAsync(HttpListenerContext context) |
src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusCollectionManager.cs
Outdated
Show resolved
Hide resolved
| Interlocked.Increment(ref this.readerCount); | ||
| this.ExitGlobalLock(); | ||
| #if NETCOREAPP3_1_OR_GREATER | ||
| return new ValueTask<ArraySegment<byte>>(this.previousDataView); |
There was a problem hiding this comment.
nit: for troubleshooting purposes, i think it'd be good to add some logging to indicate whether we re-used previous view, or triggered new collection.
There was a problem hiding this comment.
Or maybe use the Last-Modified response header. I think we should exclude the change from this PR (to keep it small).
There was a problem hiding this comment.
OK I was working on a log message but I'll hold off and do this as a follow-up. I like the idea of using the header.
|
|
||
| for (var i = 0; i < keys.Length; i++) | ||
| int numberOfKeys = keys?.Length ?? 0; | ||
| if (numberOfKeys > 0) |
There was a problem hiding this comment.
@reyang I think this was a bug. We were getting nullref exception when keys/labels were not specified.
There was a problem hiding this comment.
Let me add a test case for it once this PR is merged.
| if (i > 0) | ||
| { | ||
| buffer[cursor++] = unchecked((byte)','); | ||
| } |
There was a problem hiding this comment.
@CodeBlanch Would there be benefit to moving this to above the for loop, starting i = 1 and also adding cursor = WriteLabel(buffer, cursor, keys[i], values[i]); or would the compiler already do this loop unrolling?
There was a problem hiding this comment.
Oops I misread the code. another idea would be to always add the comma character after the WriteLabel call and then decrement the cursor after the loop so the next statement that modifies the buffer will overwrite it.
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
| private bool ExecuteCollect() | ||
| { | ||
| this.exporter.OnExport = this.onCollectRef; |
There was a problem hiding this comment.
this.exporter.OnExport = this.OnCollect;
Would this not have worked?
There was a problem hiding this comment.
So what this is doing is preventing the allocation of a delegate. The only sure-fire way I know how to do that is using a field. If you do it locally, sometimes the compiler will generate a field and initialize it on the first invocation, other times it will just allocate a delegate for each call. Not totally sure how it decides. I can't remember if this particular case fell into the delegate path or if I was just being cautious. I did do some benchmarks over this when I was working on it so I probably saw allocations and used the field, but I can't remember exactly.
There was a problem hiding this comment.
Thanks for the explanation, I had a hunch it was for perf reasons
| if (value < 0) | ||
| { | ||
| throw new ArgumentOutOfRangeException(nameof(value), "Value should be greater than or equal to zero."); | ||
| } |
There was a problem hiding this comment.
use Guard.Range(value, nameof(value), min: 0)
Changes
Removed the semaphore and added logic that returns the same response to concurrent scrape requests and then caches the response for a configurable period of time (10 seconds by default).
Public API Changes
TODOs
CHANGELOG.mdupdated for non-trivial changesREADME.mdupdate for new setting