protected override Task StartServer(CancellationToken cancel) { // Kick off the actual processing to a new thread and return a Task for the processing thread. return(Task.Run(async delegate { while (true) { // We schedule approximately at the configured interval. There may be some small accumulation for the // part of the loop we do not measure but it is close enough to be acceptable for all practical scenarios. var duration = Stopwatch.StartNew(); try { using (var stream = new MemoryStream()) { var serializer = new TextSerializer(stream); await _registry.CollectAndSerializeAsync(serializer, cancel); stream.Position = 0; // StreamContent takes ownership of the stream. var response = await _httpClient.PostAsync(_targetUrl, new StreamContent(stream)); // If anything goes wrong, we want to get at least an entry in the trace log. response.EnsureSuccessStatusCode(); } } catch (ScrapeFailedException ex) { Trace.WriteLine($"Skipping metrics push due to failed scrape: {ex.Message}"); } catch (Exception ex) when(!(ex is OperationCanceledException)) { Trace.WriteLine(string.Format("Error in MetricPusher: {0}", ex)); } // We stop only after pushing metrics, to ensure that the latest state is flushed when told to stop. if (cancel.IsCancellationRequested) { break; } var sleepTime = _pushInterval - duration.Elapsed; // Sleep until the interval elapses or the pusher is asked to shut down. if (sleepTime > TimeSpan.Zero) { try { await Task.Delay(sleepTime, cancel); } catch (OperationCanceledException) { // The task was cancelled. // We continue the loop here to ensure final state gets pushed. continue; } } } })); }
public async Task Invoke(HttpContext context) { var response = context.Response; try { // We first touch the response.Body only in the callback because touching // it means we can no longer send headers (the status code). var serializer = new TextSerializer(delegate { response.ContentType = PrometheusConstants.ExporterContentType; response.StatusCode = StatusCodes.Status200OK; return(response.Body); }); await _registry.CollectAndSerializeAsync(serializer, context.RequestAborted); } catch (OperationCanceledException) when(context.RequestAborted.IsCancellationRequested) { // The scrape was cancalled by the client. This is fine. Just swallow the exception to not generate pointless spam. } catch (ScrapeFailedException ex) { // This can only happen before any serialization occurs, in the pre-collect callbacks. // So it should still be safe to update the status code and write an error message. response.StatusCode = StatusCodes.Status503ServiceUnavailable; if (!string.IsNullOrWhiteSpace(ex.Message)) { using (var writer = new StreamWriter(response.Body, PrometheusConstants.ExportEncoding, bufferSize: -1, leaveOpen: true)) await writer.WriteAsync(ex.Message); } } }
/// <summary> /// Collects all metrics and exports them in text document format to the provided stream. /// /// This method is designed to be used with custom output mechanisms that do not use an IMetricServer. /// </summary> public void CollectAndExportAsText(Stream to) { if (to == null) { throw new ArgumentNullException(nameof(to)); } using (var serializer = new TextSerializer(to, leaveOpen: true)) CollectAndSerialize(serializer); }
public async Task Invoke(HttpContext context) { // We just handle the root URL (/metrics or whatnot). if (!string.IsNullOrWhiteSpace(context.Request.Path.Value.Trim('/'))) { await _next(context); return; } var request = context.Request; var response = context.Response; try { // We first touch the response.Body only in the callback because touching // it means we can no longer send headers (the status code). var serializer = new TextSerializer(delegate { response.ContentType = PrometheusConstants.ExporterContentType; response.StatusCode = StatusCodes.Status200OK; return(response.Body); }); await _registry.CollectAndSerializeAsync(serializer, default); response.Body.Dispose(); } catch (ScrapeFailedException ex) { // This can only happen before any serialization occurs, in the pre-collect callbacks. // So it should still be safe to update the status code and write an error message. response.StatusCode = StatusCodes.Status503ServiceUnavailable; if (!string.IsNullOrWhiteSpace(ex.Message)) { using (var writer = new StreamWriter(response.Body)) await writer.WriteAsync(ex.Message); } } }
protected override Task StartServer(CancellationToken cancel) { // This will ensure that any failures to start are nicely thrown from StartServerAsync. _httpListener.Start(); // Kick off the actual processing to a new thread and return a Task for the processing thread. return(Task.Factory.StartNew(delegate { try { while (!cancel.IsCancellationRequested) { // There is no way to give a CancellationToken to GCA() so, we need to hack around it a bit. var getContext = _httpListener.GetContextAsync(); getContext.Wait(cancel); var context = getContext.Result; var request = context.Request; var response = context.Response; try { try { using (var serializer = new TextSerializer(delegate { response.ContentType = PrometheusConstants.ExporterContentType; response.StatusCode = 200; return response.OutputStream; })) { _registry.CollectAndSerialize(serializer); } } catch (ScrapeFailedException ex) { // This can only happen before anything is written to the stream, so it // should still be safe to update the status code and report an error. response.StatusCode = 503; if (!string.IsNullOrWhiteSpace(ex.Message)) { using (var writer = new StreamWriter(response.OutputStream)) writer.Write(ex.Message); } } } catch (Exception ex) when(!(ex is OperationCanceledException)) { Trace.WriteLine(string.Format("Error in MetricsServer: {0}", ex)); try { response.StatusCode = 500; } catch { // Might be too late in request processing to set response code, so just ignore. } } finally { response.Close(); } } } finally { _httpListener.Stop(); _httpListener.Close(); } }, TaskCreationOptions.LongRunning)); }
protected override Task StartServer(CancellationToken cancel) { // This will ensure that any failures to start are nicely thrown from StartServerAsync. _httpListener.Start(); // Kick off the actual processing to a new thread and return a Task for the processing thread. return(Task.Factory.StartNew(delegate { try { Thread.CurrentThread.Name = "Metric Server"; //Max length 16 chars (Linux limitation) while (!cancel.IsCancellationRequested) { // There is no way to give a CancellationToken to GCA() so, we need to hack around it a bit. var getContext = _httpListener.GetContextAsync(); getContext.Wait(cancel); var context = getContext.Result; // Kick the request off to a background thread for processing. _ = Task.Factory.StartNew(async delegate { var request = context.Request; var response = context.Response; try { Thread.CurrentThread.Name = "Metric Process"; try { // We first touch the response.OutputStream only in the callback because touching // it means we can no longer send headers (the status code). var serializer = new TextSerializer(delegate { response.ContentType = PrometheusConstants.ExporterContentType; response.StatusCode = 200; return response.OutputStream; }); await _registry.CollectAndSerializeAsync(serializer, cancel); response.OutputStream.Dispose(); } catch (ScrapeFailedException ex) { // This can only happen before anything is written to the stream, so it // should still be safe to update the status code and report an error. response.StatusCode = 503; if (!string.IsNullOrWhiteSpace(ex.Message)) { using (var writer = new StreamWriter(response.OutputStream)) writer.Write(ex.Message); } } } catch (Exception ex) when(!(ex is OperationCanceledException)) { if (!_httpListener.IsListening) { return; // We were shut down. } Trace.WriteLine(string.Format("Error in {0}: {1}", nameof(MetricServer), ex)); try { response.StatusCode = 500; } catch { // Might be too late in request processing to set response code, so just ignore. } } finally { response.Close(); } }, TaskCreationOptions.LongRunning); } } finally { _httpListener.Stop(); // This should prevent any currently processed requests from finishing. _httpListener.Close(); } }, TaskCreationOptions.LongRunning)); }