source: trunk/src/allmydata/test/test_openmetrics.py

Last change on this file was 1cfe843d, checked in by Alexandre Detiste <alexandre.detiste@…>, at 2024-02-22T23:40:25Z

more python2 removal

  • Property mode set to 100644
File size: 11.3 KB
Line 
1"""
2Tests for ``/statistics?t=openmetrics``.
3
4Ported to Python 3.
5"""
6
7from prometheus_client.openmetrics import parser
8
9from treq.testing import RequestTraversalAgent
10
11from twisted.web.http import OK
12from twisted.web.client import readBody
13from twisted.web.resource import Resource
14
15from testtools.twistedsupport import succeeded
16from testtools.matchers import (
17    AfterPreprocessing,
18    Equals,
19    MatchesAll,
20    MatchesStructure,
21    MatchesPredicate,
22)
23from testtools.content import text_content
24
25from allmydata.web.status import Statistics
26from allmydata.test.common import SyncTestCase
27
28
29class FakeStatsProvider(object):
30    """
31    A stats provider that hands backed a canned collection of performance
32    statistics.
33    """
34
35    def get_stats(self):
36        # Parsed into a dict from a running tahoe's /statistics?t=json
37        stats = {
38            "stats": {
39                "storage_server.latencies.get.99_9_percentile": None,
40                "storage_server.latencies.close.10_0_percentile": 0.00021910667419433594,
41                "storage_server.latencies.read.01_0_percentile": 2.8848648071289062e-05,
42                "storage_server.latencies.writev.99_9_percentile": None,
43                "storage_server.latencies.read.99_9_percentile": None,
44                "storage_server.latencies.allocate.99_0_percentile": 0.000988006591796875,
45                "storage_server.latencies.writev.mean": 0.00045332245070571654,
46                "storage_server.latencies.close.99_9_percentile": None,
47                "cpu_monitor.15min_avg": 0.00017592000079223033,
48                "storage_server.disk_free_for_root": 103289454592,
49                "storage_server.latencies.get.99_0_percentile": 0.000347137451171875,
50                "storage_server.latencies.get.mean": 0.00021158285060171353,
51                "storage_server.latencies.read.90_0_percentile": 8.893013000488281e-05,
52                "storage_server.latencies.write.01_0_percentile": 3.600120544433594e-05,
53                "storage_server.latencies.write.99_9_percentile": 0.00017690658569335938,
54                "storage_server.latencies.close.90_0_percentile": 0.00033211708068847656,
55                "storage_server.disk_total": 103497859072,
56                "storage_server.latencies.close.95_0_percentile": 0.0003509521484375,
57                "storage_server.latencies.readv.samplesize": 1000,
58                "storage_server.disk_free_for_nonroot": 103289454592,
59                "storage_server.latencies.close.mean": 0.0002715024480059103,
60                "storage_server.latencies.writev.95_0_percentile": 0.0007410049438476562,
61                "storage_server.latencies.readv.90_0_percentile": 0.0003781318664550781,
62                "storage_server.latencies.readv.99_0_percentile": 0.0004050731658935547,
63                "storage_server.latencies.allocate.mean": 0.0007128627429454784,
64                "storage_server.latencies.close.samplesize": 326,
65                "storage_server.latencies.get.50_0_percentile": 0.0001819133758544922,
66                "storage_server.latencies.write.50_0_percentile": 4.482269287109375e-05,
67                "storage_server.latencies.readv.01_0_percentile": 0.0002970695495605469,
68                "storage_server.latencies.get.10_0_percentile": 0.00015687942504882812,
69                "storage_server.latencies.allocate.90_0_percentile": 0.0008189678192138672,
70                "storage_server.latencies.get.samplesize": 472,
71                "storage_server.total_bucket_count": 393,
72                "storage_server.latencies.read.mean": 5.936201880959903e-05,
73                "storage_server.latencies.allocate.01_0_percentile": 0.0004208087921142578,
74                "storage_server.latencies.allocate.99_9_percentile": None,
75                "storage_server.latencies.readv.mean": 0.00034061360359191893,
76                "storage_server.disk_used": 208404480,
77                "storage_server.latencies.allocate.50_0_percentile": 0.0007410049438476562,
78                "storage_server.latencies.read.99_0_percentile": 0.00011992454528808594,
79                "node.uptime": 3805759.8545179367,
80                "storage_server.latencies.writev.10_0_percentile": 0.00035190582275390625,
81                "storage_server.latencies.writev.90_0_percentile": 0.0006821155548095703,
82                "storage_server.latencies.close.01_0_percentile": 0.00021505355834960938,
83                "storage_server.latencies.close.50_0_percentile": 0.0002579689025878906,
84                "cpu_monitor.1min_avg": 0.0002130000000003444,
85                "storage_server.latencies.writev.50_0_percentile": 0.0004138946533203125,
86                "storage_server.latencies.read.95_0_percentile": 9.107589721679688e-05,
87                "storage_server.latencies.readv.95_0_percentile": 0.0003859996795654297,
88                "storage_server.latencies.write.10_0_percentile": 3.719329833984375e-05,
89                "storage_server.accepting_immutable_shares": 1,
90                "storage_server.latencies.writev.samplesize": 309,
91                "storage_server.latencies.get.95_0_percentile": 0.0003190040588378906,
92                "storage_server.latencies.readv.10_0_percentile": 0.00032210350036621094,
93                "storage_server.latencies.get.90_0_percentile": 0.0002999305725097656,
94                "storage_server.latencies.get.01_0_percentile": 0.0001239776611328125,
95                "cpu_monitor.total": 641.4941180000001,
96                "storage_server.latencies.write.samplesize": 1000,
97                "storage_server.latencies.write.95_0_percentile": 9.489059448242188e-05,
98                "storage_server.latencies.read.50_0_percentile": 6.890296936035156e-05,
99                "storage_server.latencies.writev.01_0_percentile": 0.00033211708068847656,
100                "storage_server.latencies.read.10_0_percentile": 3.0994415283203125e-05,
101                "storage_server.latencies.allocate.10_0_percentile": 0.0004949569702148438,
102                "storage_server.reserved_space": 0,
103                "storage_server.disk_avail": 103289454592,
104                "storage_server.latencies.write.99_0_percentile": 0.00011301040649414062,
105                "storage_server.latencies.write.90_0_percentile": 9.083747863769531e-05,
106                "cpu_monitor.5min_avg": 0.0002370666691157502,
107                "storage_server.latencies.write.mean": 5.8008909225463864e-05,
108                "storage_server.latencies.readv.50_0_percentile": 0.00033020973205566406,
109                "storage_server.latencies.close.99_0_percentile": 0.0004038810729980469,
110                "storage_server.allocated": 0,
111                "storage_server.latencies.writev.99_0_percentile": 0.0007710456848144531,
112                "storage_server.latencies.readv.99_9_percentile": 0.0004780292510986328,
113                "storage_server.latencies.read.samplesize": 170,
114                "storage_server.latencies.allocate.samplesize": 406,
115                "storage_server.latencies.allocate.95_0_percentile": 0.0008411407470703125,
116            },
117            "counters": {
118                "storage_server.writev": 309,
119                "storage_server.bytes_added": 197836146,
120                "storage_server.close": 326,
121                "storage_server.readv": 14299,
122                "storage_server.allocate": 406,
123                "storage_server.read": 170,
124                "storage_server.write": 3775,
125                "storage_server.get": 472,
126            },
127        }
128        return stats
129
130
131class HackItResource(Resource, object):
132    """
133    A bridge between ``RequestTraversalAgent`` and ``MultiFormatResource``
134    (used by ``Statistics``).  ``MultiFormatResource`` expects the request
135    object to have a ``fields`` attribute but Twisted's ``IRequest`` has no
136    such attribute.  Create it here.
137    """
138
139    def getChildWithDefault(self, path, request):
140        request.fields = None
141        return Resource.getChildWithDefault(self, path, request)
142
143
144class OpenMetrics(SyncTestCase):
145    """
146    Tests for ``/statistics?t=openmetrics``.
147    """
148
149    def test_spec_compliance(self):
150        """
151        Does our output adhere to the `OpenMetrics <https://openmetrics.io/>` spec?
152        https://github.com/OpenObservability/OpenMetrics/
153        https://prometheus.io/docs/instrumenting/exposition_formats/
154        """
155        root = HackItResource()
156        root.putChild(b"", Statistics(FakeStatsProvider()))
157        rta = RequestTraversalAgent(root)
158        d = rta.request(b"GET", b"http://localhost/?t=openmetrics")
159        self.assertThat(d, succeeded(matches_stats(self)))
160
161
162def matches_stats(testcase):
163    """
164    Create a matcher that matches a response that confirms to the OpenMetrics
165    specification.
166
167    * The ``Content-Type`` is **application/openmetrics-text; version=1.0.0; charset=utf-8**.
168    * The status is **OK**.
169    * The body can be parsed by an OpenMetrics parser.
170    * The metric families in the body are grouped and sorted.
171    * At least one of the expected families appears in the body.
172
173    :param testtools.TestCase testcase: The case to which to add detail about the matching process.
174
175    :return: A matcher.
176    """
177    return MatchesAll(
178        MatchesStructure(
179            code=Equals(OK),
180            # "The content type MUST be..."
181            headers=has_header(
182                "content-type",
183                "application/openmetrics-text; version=1.0.0; charset=utf-8",
184            ),
185        ),
186        AfterPreprocessing(
187            readBodyText,
188            succeeded(
189                MatchesAll(
190                    MatchesPredicate(add_detail(testcase, "response body"), "%s dummy"),
191                    parses_as_openmetrics(),
192                )
193            ),
194        ),
195    )
196
197
198def add_detail(testcase, name):
199    """
200    Create a matcher that always matches and as a side-effect adds the matched
201    value as detail to the testcase.
202
203    :param testtools.TestCase testcase: The case to which to add the detail.
204
205    :return: A matcher.
206    """
207
208    def predicate(value):
209        testcase.addDetail(name, text_content(value))
210        return True
211
212    return predicate
213
214
215def readBodyText(response):
216    """
217    Read the response body and decode it using UTF-8.
218
219    :param twisted.web.iweb.IResponse response: The response from which to
220        read the body.
221
222    :return: A ``Deferred`` that fires with the ``str`` body.
223    """
224    d = readBody(response)
225    d.addCallback(lambda body: body.decode("utf-8"))
226    return d
227
228
229def has_header(name, value):
230    """
231    Create a matcher that matches a response object that includes the given
232    name / value pair.
233
234    :param str name: The name of the item in the HTTP header to match.
235    :param str value: The value of the item in the HTTP header to match by equality.
236
237    :return: A matcher.
238    """
239    return AfterPreprocessing(
240        lambda headers: headers.getRawHeaders(name),
241        Equals([value]),
242    )
243
244
245def parses_as_openmetrics():
246    """
247    Create a matcher that matches a ``str`` string that can be parsed as an
248    OpenMetrics response and includes a certain well-known value expected by
249    the tests.
250
251    :return: A matcher.
252    """
253    # The parser throws if it does not like its input.
254    # Wrapped in a list() to drain the generator.
255    return AfterPreprocessing(
256        lambda body: list(parser.text_string_to_metric_families(body)),
257        AfterPreprocessing(
258            lambda families: families[-1].name,
259            Equals("tahoe_stats_storage_server_total_bucket_count"),
260        ),
261    )
Note: See TracBrowser for help on using the repository browser.