nebius/SWE-rebench-V2-PRs huggingface dataset:
Repository: prometheus/client_python
Pull request #9 (2015-02-23 19:51:16)

Make histograms with labels work


Patch:
diff --git a/prometheus_client/__init__.py b/prometheus_client/__init__.py
index 1847e09..c650c6d 100644
--- a/prometheus_client/__init__.py
+++ b/prometheus_client/__init__.py
@@ -126,8 +126,9 @@ class _LabelWrapper(object):
     with self._lock:
       metrics = self._metrics.copy()
     for labels, metric in metrics.items():
-      for suffix, _, value in metric._samples():
-        yield (suffix, dict(zip(self._labelnames, labels)), value)
+      series_labels = list(dict(zip(self._labelnames, labels)).items())
+      for suffix, sample_labels, value in metric._samples():
+        yield (suffix, dict(series_labels + list(sample_labels.items())), value)
 
 
 def _MetricWrapper(cls):
diff --git a/setup.py b/setup.py
index 5bb0fdf..9a286d6 100644
--- a/setup.py
+++ b/setup.py
@@ -10,7 +10,7 @@ def read(fname):
 
 setup(
     name = "prometheus_client",
-    version = "0.0.6",
+    version = "0.0.7",
     author = "Brian Brazil",
     author_email = "brian.brazil@gmail.com",
     description = ("Python client for the Prometheus monitoring system."),


Test patch:
diff --git a/tests/test_client.py b/tests/test_client.py
index b3d4278..535f2e8 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -108,6 +108,7 @@ class TestHistogram(unittest.TestCase):
   def setUp(self):
     self.registry = CollectorRegistry()
     self.histogram = Histogram('h', 'help', registry=self.registry)
+    self.labels = Histogram('hl', 'help', ['l'], registry=self.registry)
 
   def test_histogram(self):
     self.assertEqual(0, self.registry.get_sample_value('h_bucket', {'le': '1.0'}))
@@ -152,6 +153,15 @@ class TestHistogram(unittest.TestCase):
     self.assertRaises(ValueError, Histogram, 'h', 'help', registry=None, buckets=[float("inf")])
     self.assertRaises(ValueError, Histogram, 'h', 'help', registry=None, buckets=[3, 1])
 
+  def test_labels(self):
+    self.labels.labels('a').observe(2)
+    self.assertEqual(0, self.registry.get_sample_value('hl_bucket', {'le': '1.0', 'l': 'a'}))
+    self.assertEqual(1, self.registry.get_sample_value('hl_bucket', {'le': '2.5', 'l': 'a'}))
+    self.assertEqual(1, self.registry.get_sample_value('hl_bucket', {'le': '5.0', 'l': 'a'}))
+    self.assertEqual(1, self.registry.get_sample_value('hl_bucket', {'le': '+Inf', 'l': 'a'}))
+    self.assertEqual(1, self.registry.get_sample_value('hl_count', {'l': 'a'}))
+    self.assertEqual(2, self.registry.get_sample_value('hl_sum', {'l': 'a'}))
+
   def test_function_decorator(self):
     self.assertEqual(0, self.registry.get_sample_value('h_count'))
     self.assertEqual(0, self.registry.get_sample_value('h_bucket', {'le': '+Inf'}))
Repository: prometheus/client_python
Pull request #15 (2015-03-22 11:50:49)

Escape double quotes, not single


Patch:
diff --git a/prometheus_client/__init__.py b/prometheus_client/__init__.py
index 2e2bea4..0407577 100644
--- a/prometheus_client/__init__.py
+++ b/prometheus_client/__init__.py
@@ -418,7 +418,7 @@ def generate_latest(registry=REGISTRY):
             if labels:
                 labelstr = '{{{0}}}'.format(','.join(
                     ['{0}="{1}"'.format(
-                     k, v.replace('\\', r'\\').replace('\n', r'\n').replace('\'', r'\''))
+                     k, v.replace('\\', r'\\').replace('\n', r'\n').replace('"', r'\"'))
                      for k, v in labels.items()]))
             else:
                 labelstr = ''


Test patch:
diff --git a/tests/test_client.py b/tests/test_client.py
index 0faaab5..a28f3f4 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -262,8 +262,8 @@ class TestGenerateText(unittest.TestCase):
 
     def test_escaping(self):
         c = Counter('cc', 'A\ncount\\er', ['a'], registry=self.registry)
-        c.labels('\\x\n').inc(1)
-        self.assertEqual(b'# HELP cc A\\ncount\\\\er\n# TYPE cc counter\ncc{a="\\\\x\\n"} 1.0\n', generate_latest(self.registry))
+        c.labels('\\x\n"').inc(1)
+        self.assertEqual(b'# HELP cc A\\ncount\\\\er\n# TYPE cc counter\ncc{a="\\\\x\\n\\""} 1.0\n', generate_latest(self.registry))
 
 
 if __name__ == '__main__':
Repository: prometheus/client_python
Pull request #24 (2015-04-06 13:15:21)

Add support for callbacks for gauges.


Patch:
diff --git a/README.md b/README.md
index cc71faa..35e36a6 100644
--- a/README.md
+++ b/README.md
@@ -113,6 +113,14 @@ with g.track_inprogress():
   pass
 ```
 
+A Gauge can also take it's value from a callback:
+
+```python
+d = Gauge('data_objects', 'Number of objects')
+my_dict = {}
+d.set_function(lambda: len(my_dict))
+```
+
 ### Summary
 
 Summaries track the size and number of events.
diff --git a/prometheus_client/__init__.py b/prometheus_client/__init__.py
index 7e2896e..a0fd65c 100644
--- a/prometheus_client/__init__.py
+++ b/prometheus_client/__init__.py
@@ -8,6 +8,7 @@ import resource
 import os
 import time
 import threading
+import types
 try:
     from BaseHTTPServer import BaseHTTPRequestHandler
     from BaseHTTPServer import HTTPServer
@@ -279,6 +280,17 @@ class Gauge(object):
 
         return InprogressTracker(self)
 
+    def set_function(self, f):
+        '''Call the provided function to return the Gauge value.
+
+        The function must return a float, and may be called from
+        multiple threads.
+        All other methods of the Gauge become NOOPs.
+        '''
+        def samples(self):
+            return (('', {}, float(f())), )
+        self._samples = types.MethodType(samples, self)
+
     def _samples(self):
         with self._lock:
             return (('', {}, self._value), )


Test patch:
diff --git a/tests/test_client.py b/tests/test_client.py
index 3b113cb..4945131 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -87,6 +87,15 @@ class TestGauge(unittest.TestCase):
             self.assertEqual(1, self.registry.get_sample_value('g'))
         self.assertEqual(0, self.registry.get_sample_value('g'))
 
+    def test_gauge_function(self):
+        x = {}
+        self.gauge.set_function(lambda: len(x))
+        self.assertEqual(0, self.registry.get_sample_value('g'))
+        self.gauge.inc()
+        self.assertEqual(0, self.registry.get_sample_value('g'))
+        x['a'] = None
+        self.assertEqual(1, self.registry.get_sample_value('g'))
+
 
 class TestSummary(unittest.TestCase):
     def setUp(self):
Repository: prometheus/client_python
Pull request #133 (2017-01-31 11:07:56)

Support trailing commas in parser
These are permitted by the format.


Patch:
diff --git a/prometheus_client/parser.py b/prometheus_client/parser.py
index 67f5654..18e1a31 100644
--- a/prometheus_client/parser.py
+++ b/prometheus_client/parser.py
@@ -71,7 +71,7 @@ def _parse_sample(text):
                 value.append(char)
                 state = 'value'
         elif state == 'startoflabelname':
-            if char == ' ' or char == '\t':
+            if char == ' ' or char == '\t' or char == ',':
                 pass
             elif char == '}':
                 state = 'endoflabels'
@@ -121,7 +121,7 @@ def _parse_sample(text):
                 labelvalue.append('\\' + char)
         elif state == 'nextlabel':
             if char == ',':
-                state = 'labelname'
+                state = 'startoflabelname'
             elif char == '}':
                 state = 'endoflabels'
             elif char == ' ' or char == '\t':


Test patch:
diff --git a/tests/test_parser.py b/tests/test_parser.py
index e19794e..9ab45ee 100644
--- a/tests/test_parser.py
+++ b/tests/test_parser.py
@@ -135,6 +135,26 @@ a\t\t{\t\tfoo\t\t=\t\t"baz"\t\t}\t\t2
         metric_family.add_metric(["baz"], 2)
         self.assertEqual([metric_family], list(families))
 
+    def test_commas(self):
+        families = text_string_to_metric_families("""# TYPE a counter
+# HELP a help
+a{foo="bar",} 1
+# TYPE b counter
+# HELP b help
+b{,} 2
+""")
+        a = CounterMetricFamily("a", "help", labels=["foo"])
+        a.add_metric(["bar"], 1)
+        b = CounterMetricFamily("b", "help", value=2)
+        self.assertEqual([a, b], list(families))
+
+    def test_empty_brackets(self):
+        families = text_string_to_metric_families("""# TYPE a counter
+# HELP a help
+a{} 1
+""")
+        self.assertEqual([CounterMetricFamily("a", "help", value=1)], list(families))
+
     def test_nan(self):
         families = text_string_to_metric_families("""a NaN
 """)
Repository: prometheus/client_python
Pull request #147 (2017-03-20 22:55:19)

Fix error when initializing histogram with empty labels list
Docs demonstrate initializing a metric's labels with a list, but `labelnames` is internally represented as a tuple. If `labelnames` is truthy, it gets cast to a tuple (and a reasonable error is thrown if it can't be cast), but `_MetricWrapper` simply forwards `labelnames` as-is if it is falsy.

This is normally not a problem, but when initializing a histogram with an empty labels list:
```
from prometheus_client import Histogram
Histogram('h', 'help', [])
```

this error occurs:
```
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python2.7/dist-packages/prometheus_client/core.py", line 539, in init
    collector = cls(name, labelnames, (), **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/prometheus_client/core.py", line 835, in __init__
    bucket_labelnames = labelnames + ('le',)
TypeError: can only concatenate list (not "tuple") to list
```

I see no reason why any iterable that can be cast to a tuple shouldn't reasonably be expected to work here (use case for the empty list: building up a list of labels conditionally; it's inconvenient/inconsistent to check if empty, or to cast to tuple when lists are used for all other cases).

I think a reasonable fix is to set `labelnames` to an empty tuple if it evaluates to falsy. (Possible alternative: casting even the falsy value to tuple, in case you would like to throw a clear error if someone calls the function with a falsy non-iterable?)

The added test might be a bit simplistic, but it passes with this change and fails without it.


Patch:
diff --git a/prometheus_client/core.py b/prometheus_client/core.py
index f9334fa..2485725 100644
--- a/prometheus_client/core.py
+++ b/prometheus_client/core.py
@@ -536,7 +536,7 @@ def _MetricWrapper(cls):
                     raise ValueError('Reserved label metric name: ' + l)
             collector = _LabelWrapper(cls, name, labelnames, **kwargs)
         else:
-            collector = cls(name, labelnames, (), **kwargs)
+            collector = cls(name, (), (), **kwargs)
 
         if not _METRIC_NAME_RE.match(full_name):
             raise ValueError('Invalid metric name: ' + full_name)


Test patch:
diff --git a/tests/test_core.py b/tests/test_core.py
index 8088f8f..41bbe52 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -303,6 +303,10 @@ class TestMetricWrapper(unittest.TestCase):
         self.assertRaises(ValueError, Counter, 'c', '', labelnames=['__reserved'])
         self.assertRaises(ValueError, Summary, 'c', '', labelnames=['quantile'])
 
+    def test_empty_labels_list(self):
+        h = Histogram('h', 'help', [], registry=self.registry)
+        self.assertEqual(0, self.registry.get_sample_value('h_sum'))
+
 
 class TestMetricFamilies(unittest.TestCase):
     def setUp(self):
Repository: prometheus/client_python
Pull request #151 (2017-03-24 15:56:30)

Expose original metric classes via __wrapped__ attribute
Related to issue #150

brian-brazil: A small unittest would be a good idea I think.

Patch:
diff --git a/prometheus_client/core.py b/prometheus_client/core.py
index 2485725..adc012b 100644
--- a/prometheus_client/core.py
+++ b/prometheus_client/core.py
@@ -556,6 +556,7 @@ def _MetricWrapper(cls):
             registry.register(collector)
         return collector
 
+    init.__wrapped__ = cls
     return init
 
 


Test patch:
diff --git a/tests/test_core.py b/tests/test_core.py
index 41bbe52..f0eca1f 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -307,6 +307,9 @@ class TestMetricWrapper(unittest.TestCase):
         h = Histogram('h', 'help', [], registry=self.registry)
         self.assertEqual(0, self.registry.get_sample_value('h_sum'))
 
+    def test_wrapped_original_class(self):
+        self.assertEqual(Counter.__wrapped__, Counter('foo', 'bar').__class__)
+
 
 class TestMetricFamilies(unittest.TestCase):
     def setUp(self):
@@ -386,6 +389,7 @@ class TestMetricFamilies(unittest.TestCase):
         self.assertRaises(ValueError, HistogramMetricFamily, 'h', 'help', buckets={}, sum_value=1, labels=['a'])
         self.assertRaises(KeyError, HistogramMetricFamily, 'h', 'help', buckets={}, sum_value=1)
 
+
 class TestCollectorRegistry(unittest.TestCase):
     def test_duplicate_metrics_raises(self):
         registry = CollectorRegistry()
Repository: prometheus/client_python
Pull request #159 (2017-04-25 08:33:54)

Fix namespace and subsystem in multiprocess mode
See https://github.com/prometheus/client_python/issues/158 for details.

atollena: @brian-brazil here is the suggested fix for https://github.com/prometheus/client_python/issues/158.

Patch:
diff --git a/prometheus_client/core.py b/prometheus_client/core.py
index adc012b..b770f55 100644
--- a/prometheus_client/core.py
+++ b/prometheus_client/core.py
@@ -534,9 +534,9 @@ def _MetricWrapper(cls):
                     raise ValueError('Reserved label metric name: ' + l)
                 if l in cls._reserved_labelnames:
                     raise ValueError('Reserved label metric name: ' + l)
-            collector = _LabelWrapper(cls, name, labelnames, **kwargs)
+            collector = _LabelWrapper(cls, full_name, labelnames, **kwargs)
         else:
-            collector = cls(name, (), (), **kwargs)
+            collector = cls(full_name, (), (), **kwargs)
 
         if not _METRIC_NAME_RE.match(full_name):
             raise ValueError('Invalid metric name: ' + full_name)


Test patch:
diff --git a/tests/test_multiprocess.py b/tests/test_multiprocess.py
index f1dcee9..c05e457 100644
--- a/tests/test_multiprocess.py
+++ b/tests/test_multiprocess.py
@@ -110,6 +110,12 @@ class TestMultiProcess(unittest.TestCase):
         mark_process_dead(123, os.environ['prometheus_multiproc_dir'])
         self.assertEqual(2, self.registry.get_sample_value('g'))
 
+    def test_namespace_subsystem(self):
+         c1 = Counter('c', 'help', registry=None, namespace='ns', subsystem='ss')
+         c1.inc(1)
+         self.assertEqual(1, self.registry.get_sample_value('ns_ss_c'))
+
+
 class TestMmapedDict(unittest.TestCase):
     def setUp(self):
         fd, self.tempfile = tempfile.mkstemp()
Repository: prometheus/client_python
Pull request #206 (2017-10-25 14:16:03)

helpful error message when multiprocessing dir env is unset

On head it fails with:
```python
ERROR in app: Exception on /metrics [GET]
AttributeError: 'NoneType' object has no attribute 'endswith'
```
With this change:
```python
  File "build/bdist.linux-x86_64/egg/prometheus_client/multiprocess.py", line 16, in __init__
    raise ValueError('env prometheus_multiproc_dir is not set or not accessible')
ValueError: env prometheus_multiproc_dir is not set or not accessible
```

dbalan: @brian-brazil  I've changed the code to check for directories, I can squash the commits if you prefer.

Patch:
diff --git a/prometheus_client/multiprocess.py b/prometheus_client/multiprocess.py
index 2fa9716..a45d1ec 100644
--- a/prometheus_client/multiprocess.py
+++ b/prometheus_client/multiprocess.py
@@ -12,6 +12,8 @@ from . import core
 class MultiProcessCollector(object):
     """Collector for files for multi-process mode."""
     def __init__(self, registry, path=os.environ.get('prometheus_multiproc_dir')):
+        if not path or not os.path.isdir(path):
+            raise ValueError('env prometheus_multiproc_dir is not set or not a directory')
         self._path = path
         if registry:
           registry.register(self)


Test patch:
diff --git a/tests/test_multiprocess.py b/tests/test_multiprocess.py
index d122dbb..8f282d1 100644
--- a/tests/test_multiprocess.py
+++ b/tests/test_multiprocess.py
@@ -158,3 +158,28 @@ class TestMmapedDict(unittest.TestCase):
 
     def tearDown(self):
         os.unlink(self.tempfile)
+
+class TestUnsetEnv(unittest.TestCase):
+    def setUp(self):
+        self.registry = CollectorRegistry()
+        fp, self.tmpfl = tempfile.mkstemp()
+        os.close(fp)
+
+    def test_unset_syncdir_env(self):
+        self.assertRaises(
+            ValueError,
+            MultiProcessCollector,
+            self.registry
+        )
+
+    def test_file_syncpath(self):
+        registry = CollectorRegistry()
+        self.assertRaises(
+            ValueError,
+            MultiProcessCollector,
+            registry,
+            self.tmpfl
+        )
+
+    def tearDown(self):
+        os.remove(self.tmpfl)
Repository: prometheus/client_python
Pull request #329 (2018-10-18 11:38:09)

Limit memory reads for mmaped files
This is a follow on fix for the initialisation corruption fixed in #328.

When corruption of the master's mmaped file occured, it ended up reading the corrupted data as an large integer. It would then feed this into *struct.unpack_from* as the length to read, which would rightly raise an exception.

However, it also somehow caused a much bigger problem. After this had occurred, Gunicorn would for some unknown reason fail to successfully launch new workers, and thus exit. We saw this pattern of corruption and subsequent gunicorn death happen consistently over multiple machines and services. 
The issue was transient, and we didn't get coredumps, but the suspicion is that trying to read a large chunk of memory somehow broke gunicorn. 

So this change adds a reasonable bounds check on read length, and if corruption occurs in future, then it should blow up earlier, with a better error, and without breaking Gunicorn. 
 


Patch:
diff --git a/prometheus_client/core.py b/prometheus_client/core.py
index f730e09..964947a 100644
--- a/prometheus_client/core.py
+++ b/prometheus_client/core.py
@@ -562,6 +562,7 @@ class _MmapedDict(object):
 
     def __init__(self, filename, read_mode=False):
         self._f = open(filename, 'rb' if read_mode else 'a+b')
+        self._fname = filename
         if os.fstat(self._f.fileno()).st_size == 0:
             self._f.truncate(_INITIAL_MMAP_SIZE)
         self._capacity = os.fstat(self._f.fileno()).st_size
@@ -607,6 +608,10 @@ class _MmapedDict(object):
 
         while pos < used:
             encoded_len = _unpack_integer(data, pos)[0]
+            # check we are not reading beyond bounds
+            if encoded_len + pos > used:
+                msg = 'Read beyond file size detected, %s is corrupted.'
+                raise RuntimeError(msg % self._fname)
             pos += 4
             encoded = unpack_from(('%ss' % encoded_len).encode(), data, pos)[0]
             padded_len = encoded_len + (8 - (encoded_len + 4) % 8)


Test patch:
diff --git a/tests/test_multiprocess.py b/tests/test_multiprocess.py
index e2745b0..a479e64 100644
--- a/tests/test_multiprocess.py
+++ b/tests/test_multiprocess.py
@@ -296,6 +296,13 @@ class TestMmapedDict(unittest.TestCase):
             [('abc', 42.0), (key, 123.0), ('def', 17.0)],
             list(self.d.read_all_values()))
 
+    def test_corruption_detected(self):
+        self.d.write_value('abc', 42.0)
+        # corrupt the written data
+        self.d._m[8:16] = b'somejunk'
+        with self.assertRaises(RuntimeError):
+            list(self.d.read_all_values())
+
     def tearDown(self):
         os.unlink(self.tempfile)
Repository: prometheus/client_python
Pull request #442 (2019-07-25 13:44:55)

Support slashes in pgw grouping keys.
Signed-off-by: Brian Brazil <brian.brazil@robustperception.io>

@beorn7 


Patch:
diff --git a/prometheus_client/exposition.py b/prometheus_client/exposition.py
index 6a2db15..9b89c01 100644
--- a/prometheus_client/exposition.py
+++ b/prometheus_client/exposition.py
@@ -343,7 +343,7 @@ def _use_gateway(method, gateway, job, registry, grouping_key, timeout, handler)
     gateway_url = urlparse(gateway)
     if not gateway_url.scheme or (PYTHON26_OR_OLDER and gateway_url.scheme not in ['http', 'https']):
         gateway = 'http://{0}'.format(gateway)
-    url = '{0}/metrics/job/{1}'.format(gateway, quote_plus(job))
+    url = '{0}/metrics/{1}/{2}'.format(gateway, *_escape_grouping_key("job", job))
 
     data = b''
     if method != 'DELETE':
@@ -352,7 +352,7 @@ def _use_gateway(method, gateway, job, registry, grouping_key, timeout, handler)
     if grouping_key is None:
         grouping_key = {}
     url += ''.join(
-        '/{0}/{1}'.format(quote_plus(str(k)), quote_plus(str(v)))
+        '/{0}/{1}'.format(*_escape_grouping_key(str(k), str(v)))
         for k, v in sorted(grouping_key.items()))
 
     handler(
@@ -361,6 +361,14 @@ def _use_gateway(method, gateway, job, registry, grouping_key, timeout, handler)
     )()
 
 
+def _escape_grouping_key(k, v):
+    if '/' in v:
+        # Added in Pushgateway 0.9.0.
+        return k + "@base64", base64.urlsafe_b64encode(v.encode("utf-8")).decode("utf-8")
+    else:
+        return k, quote_plus(v)
+
+
 def instance_ip_grouping_key():
     """Grouping key with instance set to the IP Address of this host."""
     with closing(socket.socket(socket.AF_INET, socket.SOCK_DGRAM)) as s:


Test patch:
diff --git a/tests/test_exposition.py b/tests/test_exposition.py
index 00f39b4..de116a0 100644
--- a/tests/test_exposition.py
+++ b/tests/test_exposition.py
@@ -239,7 +239,14 @@ class TestPushGateway(unittest.TestCase):
     def test_push_with_complex_groupingkey(self):
         push_to_gateway(self.address, "my_job", self.registry, {'a': 9, 'b': 'a/ z'})
         self.assertEqual(self.requests[0][0].command, 'PUT')
-        self.assertEqual(self.requests[0][0].path, '/metrics/job/my_job/a/9/b/a%2F+z')
+        self.assertEqual(self.requests[0][0].path, '/metrics/job/my_job/a/9/b@base64/YS8geg==')
+        self.assertEqual(self.requests[0][0].headers.get('content-type'), CONTENT_TYPE_LATEST)
+        self.assertEqual(self.requests[0][1], b'# HELP g help\n# TYPE g gauge\ng 0.0\n')
+
+    def test_push_with_complex_job(self):
+        push_to_gateway(self.address, "my/job", self.registry)
+        self.assertEqual(self.requests[0][0].command, 'PUT')
+        self.assertEqual(self.requests[0][0].path, '/metrics/job@base64/bXkvam9i')
         self.assertEqual(self.requests[0][0].headers.get('content-type'), CONTENT_TYPE_LATEST)
         self.assertEqual(self.requests[0][1], b'# HELP g help\n# TYPE g gauge\ng 0.0\n')