-
Notifications
You must be signed in to change notification settings - Fork 21
/
PrometheusTextReader.cs
134 lines (118 loc) · 4.33 KB
/
PrometheusTextReader.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Man.Dapr.Sidekick.AspNetCore.Metrics
{
public class PrometheusTextReader
{
private const string HelpPrefix = "# HELP";
private const string TypePrefix = "# TYPE";
private enum LineType
{
Metric = 0,
Help = 1,
Type = 2,
Empty = 3,
Unknown = 4
}
private readonly PrometheusModel _model;
private readonly PrometheusLabelEnricher _labelEnricher = null;
public PrometheusTextReader(PrometheusModel model, PrometheusLabelEnricher labelEnricher = null)
{
_model = model ?? throw new ArgumentNullException(nameof(model));
_labelEnricher = labelEnricher;
}
public async Task ReadAsync(Stream source, CancellationToken cancellationToken)
{
// Create a reader for the stream
using var reader = new StreamReader(source);
// Read each line
string sourceLine;
PrometheusModel.Metric currentMetric = null;
while ((sourceLine = await reader.ReadLineAsync()) != null)
{
// Check the cancellation token
cancellationToken.ThrowIfCancellationRequested();
// Identify it
var (lineType, name) = IdentifyLine(sourceLine);
if (string.IsNullOrEmpty(name) || lineType == LineType.Unknown || lineType == LineType.Empty)
{
// Unknown line
_model.Unknown.Add(sourceLine);
continue;
}
// If the current metric has the same name it can be reused for efficiency, else we'll need to add/retrieve another one.
if (currentMetric?.Name != name)
{
// Different name. Get the metric
currentMetric = _model.GetOrAddMetric(name);
}
// Set the value if not already specified
switch (lineType)
{
case LineType.Help:
currentMetric.HelpLine ??= sourceLine;
break;
case LineType.Type:
currentMetric.TypeLine ??= sourceLine;
break;
case LineType.Metric:
currentMetric.MetricLines.Add(_labelEnricher?.EnrichMetricLine(sourceLine) ?? sourceLine);
break;
}
}
}
private (LineType LineType, string Name) IdentifyLine(string line)
{
if (string.IsNullOrEmpty(line))
{
return (LineType.Empty, null);
}
if (line.StartsWith(HelpPrefix))
{
// HELP definition
return (LineType.Help, GetName(line, HelpPrefix.Length, ' '));
}
else if (line.StartsWith(TypePrefix))
{
// TYPE definition
return (LineType.Type, GetName(line, TypePrefix.Length, ' '));
}
else if (line[0] != '#')
{
// Metric definition
return (LineType.Metric, GetName(line, 0, ' ', '{'));
}
else
{
// Unknown
return (LineType.Unknown, null);
}
}
private string GetName(string line, int startIndex, params char[] terminators)
{
// Simple way of skipping over any leading spaces, faster than Trim
var firstNonSpace = startIndex;
while (line[firstNonSpace] == ' ')
{
firstNonSpace++;
}
// Check each character to see if it is a terminator, when it is return the substring up to that point.
for (var i = firstNonSpace; i < line.Length; i++)
{
if (terminators.Any(terminator => terminator == line[i]))
{
#if NETCOREAPP
return line[firstNonSpace..i];
#else
return line.Substring(firstNonSpace, i - firstNonSpace);
#endif
}
}
// Not found
return null;
}
}
}