)]}'
{"/COMMIT_MSG":[{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"94af78cac5977ac70e611aaaf52dd8cd3d8cec79","unresolved":true,"context_lines":[{"line_number":4,"context_line":"Commit:     Shreeya Deshpande \u003cshreeyad@nvidia.com\u003e"},{"line_number":5,"context_line":"CommitDate: 2026-01-29 10:55:59 -0600"},{"line_number":6,"context_line":""},{"line_number":7,"context_line":"PrometheousClient"},{"line_number":8,"context_line":""},{"line_number":9,"context_line":"Change-Id: I7c12b4893855c3731a825ae7eefc8ee0b6e3f280"},{"line_number":10,"context_line":"Signed-off-by: Shreeya Deshpande \u003cshreeyad@nvidia.com\u003e"}],"source_content_type":"text/x-gerrit-commit-message","patch_set":7,"id":"5d10744f_fffbe130","line":7,"updated":"2026-01-30 17:34:38.000000000","message":"The spelling is \"prometheus\" (no second \u0027o\u0027)\nhttps://prometheus.io/","commit_id":"1395affd541c81ac1de954bf43fefab455ddd104"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"8ea433753461363516daad2f0a75b32496d00d18","unresolved":false,"context_lines":[{"line_number":4,"context_line":"Commit:     Shreeya Deshpande \u003cshreeyad@nvidia.com\u003e"},{"line_number":5,"context_line":"CommitDate: 2026-01-29 10:55:59 -0600"},{"line_number":6,"context_line":""},{"line_number":7,"context_line":"PrometheousClient"},{"line_number":8,"context_line":""},{"line_number":9,"context_line":"Change-Id: I7c12b4893855c3731a825ae7eefc8ee0b6e3f280"},{"line_number":10,"context_line":"Signed-off-by: Shreeya Deshpande \u003cshreeyad@nvidia.com\u003e"}],"source_content_type":"text/x-gerrit-commit-message","patch_set":7,"id":"97474e68_55af86e9","line":7,"in_reply_to":"5d10744f_fffbe130","updated":"2026-02-06 01:20:27.000000000","message":"Acknowledged","commit_id":"1395affd541c81ac1de954bf43fefab455ddd104"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"972b28563a02fa2ef2d7a8bf90e14b0818ffa7d4","unresolved":true,"context_lines":[{"line_number":4,"context_line":"Commit:     Shreeya Deshpande \u003cshreeyad@nvidia.com\u003e"},{"line_number":5,"context_line":"CommitDate: 2026-05-07 21:58:48 +0000"},{"line_number":6,"context_line":""},{"line_number":7,"context_line":"PrometheusClient"},{"line_number":8,"context_line":""},{"line_number":9,"context_line":"Change-Id: I7c12b4893855c3731a825ae7eefc8ee0b6e3f280"},{"line_number":10,"context_line":"Signed-off-by: Clay Gerrard \u003cclay.gerrard@gmail.com\u003e"}],"source_content_type":"text/x-gerrit-commit-message","patch_set":26,"id":"568940d5_81d77612","line":7,"range":{"start_line":7,"start_character":0,"end_line":7,"end_character":16},"updated":"2026-05-15 23:54:38.000000000","message":"This patch deserves a descriptive commit message","commit_id":"5e0d0d3c8322ec5a4e8d2fdb9c9c5be3ab246a07"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"a04019e2e8bb266360d9d0f587f3ed5868404632","unresolved":false,"context_lines":[{"line_number":4,"context_line":"Commit:     Shreeya Deshpande \u003cshreeyad@nvidia.com\u003e"},{"line_number":5,"context_line":"CommitDate: 2026-05-07 21:58:48 +0000"},{"line_number":6,"context_line":""},{"line_number":7,"context_line":"PrometheusClient"},{"line_number":8,"context_line":""},{"line_number":9,"context_line":"Change-Id: I7c12b4893855c3731a825ae7eefc8ee0b6e3f280"},{"line_number":10,"context_line":"Signed-off-by: Clay Gerrard \u003cclay.gerrard@gmail.com\u003e"}],"source_content_type":"text/x-gerrit-commit-message","patch_set":26,"id":"fc20e7bb_7f75835b","line":7,"range":{"start_line":7,"start_character":0,"end_line":7,"end_character":16},"in_reply_to":"568940d5_81d77612","updated":"2026-05-19 03:19:45.000000000","message":"Done","commit_id":"5e0d0d3c8322ec5a4e8d2fdb9c9c5be3ab246a07"}],"/PATCHSET_LEVEL":[{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"8fb8665fbef2e8aa06ac54c1eaf90bc2dcbf1ca7","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":2,"id":"a4ee90d9_1dc37f8a","updated":"2025-12-17 15:54:43.000000000","message":"great start!","commit_id":"e66be0e8192282a9a92a4cc878e1fa3b1a48189d"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1f89dae221ca224f992884d4d85d0f14cb377f9f","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":5,"id":"796b74f4_e6132dba","updated":"2026-01-15 20:22:16.000000000","message":"I can\u0027t find what I would consider to be a GREAT example of a test that I\u0027d like to see used as a pattern for the prometheus client tests.\n\nThe statsd_client tests are a pretty starting point tho:\n\n```\n    def test_label_values_to_str(self):\n        # verify that simple non-str types can be passed as label values\n        conf \u003d {\n            \u0027log_statsd_host\u0027: \u0027myhost1\u0027,\n            \u0027log_statsd_port\u0027: 1235,\n            \u0027statsd_label_mode\u0027: \u0027librato\u0027,\n        }\n        client \u003d statsd_client.get_labeled_statsd_client(conf)\n        labels \u003d {\u0027bool\u0027: True, \u0027number\u0027: 42.1, \u0027null\u0027: None}\n        with mock.patch.object(client, \u0027_send_line\u0027) as mocked:\n            client.update_stats(\u0027metric\u0027, \u002710\u0027, labels\u003dlabels)\n        self.assertEqual(\n            [mock.call(\u0027metric#bool\u003dTrue,null\u003dNone,number\u003d42.1:10|c\u0027)],\n            mocked.call_args_list)\n```\n\nthey follow the standard unittest pattern of \"setup, crank turn, assert\" - simple and to the point!\n\nFor the prometheus-client tests we\u0027ll need a similar \"setup, crank turn, assert\" pattern - where e.g.\n\n * setup \u003d\u003e \"prepare the temp directory where the prom file will go \u0026 create/configure a prom client to write there\"\n * crank turn \u003d\u003e \"call `update_stats(metricname, value, labels\u003dlabels)` a couple times \u0026 then client dumps it\u0027s accumulated stats to a file\"\n * assert \u003d\u003e \"read the file and assert 1) it\u0027s valid prom 2) it has the expected stats\"\n \nThere\u0027s some relinker unit-tests that make assertions on recon data that *sort of* follow that pattern of \"configure recon_cache_path, turn the crank, assert recon looks as expected\":\n\nhttps://github.com/NVIDIA/swift/blob/master/test/unit/cli/test_relinker.py#L2642-L2686","commit_id":"460c16e21099714aa2972623cd888a158824f13b"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"94af78cac5977ac70e611aaaf52dd8cd3d8cec79","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":7,"id":"4f4833e1_999174e8","updated":"2026-01-30 17:34:38.000000000","message":"I\u0027m not sure what the status of this patch is so I\u0027ve only left a few initial comments","commit_id":"1395affd541c81ac1de954bf43fefab455ddd104"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"6859e20067084277d9dd05383c8f3913bccb0bc7","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":10,"id":"b003f57d_9e298c1e","updated":"2026-02-12 01:42:12.000000000","message":"recheck","commit_id":"25579692835717672c7b2db372c66785b4a895ba"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"81ef57a92e060fb191cf1f5a45694c2aef25f9c6","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":11,"id":"fae8b2bd_3c34b072","updated":"2026-02-12 15:43:51.000000000","message":"recheck","commit_id":"1bb5b822b37bb309193ae5549f05ee92fe6ac625"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"795c971ab91775835a923645c604f184ea3f9df3","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":11,"id":"93d1a1f1_f2c1c663","updated":"2026-02-13 20:04:07.000000000","message":"this was in better shape than I thought - i was super confused by the \"metric\" input being a formatted string; but I think we see a path forward!","commit_id":"1bb5b822b37bb309193ae5549f05ee92fe6ac625"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"bb10abec2f1db98ebceb473dfcf63d3600208e8f","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":14,"id":"8117675d_792f348c","updated":"2026-03-10 23:04:54.000000000","message":"please go through and try to ack any comments you\u0027ve addressed as you deal with them; if in doing so you notice that some are not addressed please do another change set and try to ack all comments.\n\nIf you\u0027re not clear on what you should be addressed on any particular comment you can add a comment back asking for the information youneed.","commit_id":"1999c38dab00890ce9f2226c398d0ceb5b0774ec"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"5e1ceba9e9f643701ee4b8f894a6e5d0c06d6df0","unresolved":true,"context_lines":[],"source_content_type":"","patch_set":20,"id":"0790c879_de857b02","updated":"2026-04-12 00:38:07.000000000","message":"Not entirely done with the review; i might have more comments later on as well","commit_id":"16cb7e13309e42b272d67b3ac2bc8b6c762e3497"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"3b9dd1e8218ecea13dd3ef8ac6814cf4467dcca4","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":20,"id":"8d6a9d6f_866de685","in_reply_to":"0790c879_de857b02","updated":"2026-04-20 20:58:16.000000000","message":"Done","commit_id":"16cb7e13309e42b272d67b3ac2bc8b6c762e3497"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"dc8f5366b4f25b474339ef7650d4e30727ed3508","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":21,"id":"d49eca87_e784e209","updated":"2026-04-14 19:10:20.000000000","message":"looks great - those improvements are awesome, I think I was not sufficiently clear about how I expect the `self._stats` accumulation and `self.flush()` behavior to work.\n\nI definitely don\u0027t want each call to the update stats methods to open and append to the filename\n\nI didn\u0027t look at tests","commit_id":"81b11074c834ebbc1fe521d6f15ee70c5f468fb3"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"6b828f105ebe2501d80931b112050931b95064db","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":22,"id":"26761a49_caac34c2","updated":"2026-04-21 20:05:35.000000000","message":"I\u0027d suggest PromClient should have two public methods to pull the data out of `self._stats` - basically two methods `dump` and `write` similar to json|pickle serializers `dumps` and `dump` methods:\n\n```\nall_stats \u003d client.dump_stats()  # return a multi-line string\n```\n\nand\n\n```\ndef write_stats(self, filename\u003dNone):\n    filename \u003d filename or self.filename\n    if not filename:\n        raise ValueError(\u0027where should I write stats!?\u0027)\n    all_stats \u003d self.dump_stats()\n    _atomic_write(all_stats, filename)\n```\n\nN.B. `_atomic_write` doesn\u0027t need `all_stats` nor `filename` to be optional - they\u0027re both required strings and no longer depend on internal object state.","commit_id":"1eb2486a2b5322c030db7f3e4129d7d3b84b59b5"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"972b28563a02fa2ef2d7a8bf90e14b0818ffa7d4","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":26,"id":"f7ff8811_cbd25bf9","updated":"2026-05-15 23:54:38.000000000","message":"I think I\u0027m missing some context to better review this patch. I wasn\u0027t able to tell how one would use all methods in this module, especially `parse_metrics` and `format_metrics_batch`. Perhaps a description in a commit message would be helpful.","commit_id":"5e0d0d3c8322ec5a4e8d2fdb9c9c5be3ab246a07"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"527e3ee49ac796e2322943a074744d9939f2f613","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":26,"id":"ea082009_ceb7a621","updated":"2026-05-07 22:02:19.000000000","message":"recheck","commit_id":"5e0d0d3c8322ec5a4e8d2fdb9c9c5be3ab246a07"}],"swift/common/prom_metrics.py":[{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"558a201133e17190fc7a46457a2d50a3381d6c11","unresolved":true,"context_lines":[{"line_number":15,"context_line":""},{"line_number":16,"context_line":"\"\"\" Prometheous Metrics \"\"\""},{"line_number":17,"context_line":""},{"line_number":18,"context_line":"from test import metric_key"},{"line_number":19,"context_line":""},{"line_number":20,"context_line":""},{"line_number":21,"context_line":"def parse_metrics(line):"}],"source_content_type":"text/x-python","patch_set":20,"id":"43429e47_8f74de1a","line":18,"updated":"2026-04-12 00:01:38.000000000","message":"Weird to see a dependency on a test module.\n\ninstead why not DEFINE a private helper inside swift/common/prom_metrics.py (e.g. _prom_key(name, **labels)) or something with a better name which has same return value, so behavior matches read_metrics() without having production code having a `test/` dependency","commit_id":"16cb7e13309e42b272d67b3ac2bc8b6c762e3497"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"5caa9f5a203bdca5ca37c018ea381307cbd3ba3c","unresolved":true,"context_lines":[{"line_number":15,"context_line":""},{"line_number":16,"context_line":"\"\"\" Prometheous Metrics \"\"\""},{"line_number":17,"context_line":""},{"line_number":18,"context_line":"from test import metric_key"},{"line_number":19,"context_line":""},{"line_number":20,"context_line":""},{"line_number":21,"context_line":"def parse_metrics(line):"}],"source_content_type":"text/x-python","patch_set":20,"id":"73e40fc7_08dbdd6b","line":18,"in_reply_to":"43429e47_8f74de1a","updated":"2026-04-14 16:53:37.000000000","message":"i think we wanted to have a common metric_key to use. \nClay and i wrote this to 975948: add prom metrics test helpers | https://review.opendev.org/c/openstack/swift/+/975948 \n@clay.gerrard@gmail.com is there a better place to put this? Do we anticipate using metric_key just for prom metrics?","commit_id":"16cb7e13309e42b272d67b3ac2bc8b6c762e3497"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"7abd8df05b8081785b9541f05ff02331685d4b3f","unresolved":false,"context_lines":[{"line_number":15,"context_line":""},{"line_number":16,"context_line":"\"\"\" Prometheous Metrics \"\"\""},{"line_number":17,"context_line":""},{"line_number":18,"context_line":"from test import metric_key"},{"line_number":19,"context_line":""},{"line_number":20,"context_line":""},{"line_number":21,"context_line":"def parse_metrics(line):"}],"source_content_type":"text/x-python","patch_set":20,"id":"28c108a9_59ec47b3","line":18,"in_reply_to":"54ba9939_129c4cf3","updated":"2026-05-05 15:26:31.000000000","message":"Done","commit_id":"16cb7e13309e42b272d67b3ac2bc8b6c762e3497"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"dc8f5366b4f25b474339ef7650d4e30727ed3508","unresolved":true,"context_lines":[{"line_number":15,"context_line":""},{"line_number":16,"context_line":"\"\"\" Prometheous Metrics \"\"\""},{"line_number":17,"context_line":""},{"line_number":18,"context_line":"from test import metric_key"},{"line_number":19,"context_line":""},{"line_number":20,"context_line":""},{"line_number":21,"context_line":"def parse_metrics(line):"}],"source_content_type":"text/x-python","patch_set":20,"id":"54ba9939_129c4cf3","line":18,"in_reply_to":"73e40fc7_08dbdd6b","updated":"2026-04-14 19:10:20.000000000","message":"I agree supa weird to see `swift.common` import from `test` - i\u0027m not even sure prod *packages* the test tree - i\u0027m sort of surprised this worked (?)\n\n\u003e behavior matches read_metrics()\n\nright, let\u0027s not duplicate.  Given that prom_metrics needs a helper to read metrics *already* I think it WOULD make sense to \"just\" define it `common.prom_metrics` AND \"re-use THAT\" in tests\n\nLet\u0027s remove the dependency on 975948: add prom metrics test helpers | https://review.opendev.org/c/openstack/swift/+/975948 - move (a refactored more flexible version of?) that code into `common.prom_metrics` in THIS change - and test and merge it as part of introducing `PrometheusClient`","commit_id":"16cb7e13309e42b272d67b3ac2bc8b6c762e3497"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"5e1ceba9e9f643701ee4b8f894a6e5d0c06d6df0","unresolved":true,"context_lines":[{"line_number":61,"context_line":"    \"\"\""},{"line_number":62,"context_line":"    labels \u003d labels or {}"},{"line_number":63,"context_line":""},{"line_number":64,"context_line":"    if not isinstance(name, str):"},{"line_number":65,"context_line":"        raise ValueError(f\"Metric name must be string, got {type(name)}\")"},{"line_number":66,"context_line":""},{"line_number":67,"context_line":"    if labels and isinstance(labels, dict):"}],"source_content_type":"text/x-python","patch_set":20,"id":"cfb2dacf_f0d2f59c","line":64,"updated":"2026-04-12 00:38:07.000000000","message":"This check could come before we assign labels such that:\n```\n    if not isinstance(name, str):\n        raise ValueError(f\"Metric name must be string, got {type(name)}\")\n    labels \u003d labels or {}\n```","commit_id":"16cb7e13309e42b272d67b3ac2bc8b6c762e3497"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"5caa9f5a203bdca5ca37c018ea381307cbd3ba3c","unresolved":false,"context_lines":[{"line_number":61,"context_line":"    \"\"\""},{"line_number":62,"context_line":"    labels \u003d labels or {}"},{"line_number":63,"context_line":""},{"line_number":64,"context_line":"    if not isinstance(name, str):"},{"line_number":65,"context_line":"        raise ValueError(f\"Metric name must be string, got {type(name)}\")"},{"line_number":66,"context_line":""},{"line_number":67,"context_line":"    if labels and isinstance(labels, dict):"}],"source_content_type":"text/x-python","patch_set":20,"id":"4b54c80b_2450fc73","line":64,"in_reply_to":"cfb2dacf_f0d2f59c","updated":"2026-04-14 16:53:37.000000000","message":"Acknowledged","commit_id":"16cb7e13309e42b272d67b3ac2bc8b6c762e3497"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"5e1ceba9e9f643701ee4b8f894a6e5d0c06d6df0","unresolved":true,"context_lines":[{"line_number":64,"context_line":"    if not isinstance(name, str):"},{"line_number":65,"context_line":"        raise ValueError(f\"Metric name must be string, got {type(name)}\")"},{"line_number":66,"context_line":""},{"line_number":67,"context_line":"    if labels and isinstance(labels, dict):"},{"line_number":68,"context_line":"        # Sort labels for consistent output"},{"line_number":69,"context_line":"        labels_str \u003d \u0027,\u0027.join("},{"line_number":70,"context_line":"            f\u0027{k}\u003d\"{v}\"\u0027 for k, v in sorted(labels.items())"}],"source_content_type":"text/x-python","patch_set":20,"id":"4fbe5172_dea271c4","line":67,"updated":"2026-04-12 00:38:07.000000000","message":"in L62 we have already initialized lablels as \"labels or {}\" so `isinstance(labels, dict)` seems like a redundant check!","commit_id":"16cb7e13309e42b272d67b3ac2bc8b6c762e3497"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"5caa9f5a203bdca5ca37c018ea381307cbd3ba3c","unresolved":false,"context_lines":[{"line_number":64,"context_line":"    if not isinstance(name, str):"},{"line_number":65,"context_line":"        raise ValueError(f\"Metric name must be string, got {type(name)}\")"},{"line_number":66,"context_line":""},{"line_number":67,"context_line":"    if labels and isinstance(labels, dict):"},{"line_number":68,"context_line":"        # Sort labels for consistent output"},{"line_number":69,"context_line":"        labels_str \u003d \u0027,\u0027.join("},{"line_number":70,"context_line":"            f\u0027{k}\u003d\"{v}\"\u0027 for k, v in sorted(labels.items())"}],"source_content_type":"text/x-python","patch_set":20,"id":"0ce110f9_9055b299","line":67,"in_reply_to":"4fbe5172_dea271c4","updated":"2026-04-14 16:53:37.000000000","message":"true true","commit_id":"16cb7e13309e42b272d67b3ac2bc8b6c762e3497"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"5e1ceba9e9f643701ee4b8f894a6e5d0c06d6df0","unresolved":false,"context_lines":[{"line_number":74,"context_line":"        return name + \u0027 \u0027 + str(value) + \u0027\\n\u0027"},{"line_number":75,"context_line":""},{"line_number":76,"context_line":""},{"line_number":77,"context_line":"def format_metrics_batch(metrics_list):"},{"line_number":78,"context_line":"    \"\"\""},{"line_number":79,"context_line":"    Format multiple metrics into a Prometheus text format string."},{"line_number":80,"context_line":""}],"source_content_type":"text/x-python","patch_set":20,"id":"03491edb_db92f735","line":77,"updated":"2026-04-12 00:38:07.000000000","message":"nice!","commit_id":"16cb7e13309e42b272d67b3ac2bc8b6c762e3497"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"dc8f5366b4f25b474339ef7650d4e30727ed3508","unresolved":true,"context_lines":[{"line_number":42,"context_line":"    return name, labels, value"},{"line_number":43,"context_line":""},{"line_number":44,"context_line":""},{"line_number":45,"context_line":"def format_metric(name, value, labels\u003dNone):"},{"line_number":46,"context_line":"    \"\"\""},{"line_number":47,"context_line":"    Format a single metric into Prometheus text format string."},{"line_number":48,"context_line":""}],"source_content_type":"text/x-python","patch_set":21,"id":"d851fe29_c513c86b","line":45,"updated":"2026-04-14 19:10:20.000000000","message":"in `common.statsd_client` we call this something like `_build_line_parts` - we might want to keep the naming convention consistent-ish?","commit_id":"81b11074c834ebbc1fe521d6f15ee70c5f468fb3"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"ff8bbcd7be70afe80dd41554e2d66eae83fe1cc8","unresolved":false,"context_lines":[{"line_number":42,"context_line":"    return name, labels, value"},{"line_number":43,"context_line":""},{"line_number":44,"context_line":""},{"line_number":45,"context_line":"def format_metric(name, value, labels\u003dNone):"},{"line_number":46,"context_line":"    \"\"\""},{"line_number":47,"context_line":"    Format a single metric into Prometheus text format string."},{"line_number":48,"context_line":""}],"source_content_type":"text/x-python","patch_set":21,"id":"9a06d3d3_8c6aeef7","line":45,"in_reply_to":"d851fe29_c513c86b","updated":"2026-04-28 04:49:45.000000000","message":"Done","commit_id":"81b11074c834ebbc1fe521d6f15ee70c5f468fb3"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"dc8f5366b4f25b474339ef7650d4e30727ed3508","unresolved":true,"context_lines":[{"line_number":57,"context_line":"    :param name: Metric name (string)"},{"line_number":58,"context_line":"    :param value: Metric value (int or float)"},{"line_number":59,"context_line":"    :param labels: Optional dict of label key-value pairs"},{"line_number":60,"context_line":"    :returns: Prometheus-formatted metric string"},{"line_number":61,"context_line":"    \"\"\""},{"line_number":62,"context_line":""},{"line_number":63,"context_line":"    if not isinstance(name, str):"}],"source_content_type":"text/x-python","patch_set":21,"id":"16a160c1_94d53013","line":60,"updated":"2026-04-14 19:10:20.000000000","message":"... terminated in a newline\n\nI\u0027m ok with this b/c personally I like files that have even a single line in them to be terminated with a newline","commit_id":"81b11074c834ebbc1fe521d6f15ee70c5f468fb3"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"ff8bbcd7be70afe80dd41554e2d66eae83fe1cc8","unresolved":false,"context_lines":[{"line_number":57,"context_line":"    :param name: Metric name (string)"},{"line_number":58,"context_line":"    :param value: Metric value (int or float)"},{"line_number":59,"context_line":"    :param labels: Optional dict of label key-value pairs"},{"line_number":60,"context_line":"    :returns: Prometheus-formatted metric string"},{"line_number":61,"context_line":"    \"\"\""},{"line_number":62,"context_line":""},{"line_number":63,"context_line":"    if not isinstance(name, str):"}],"source_content_type":"text/x-python","patch_set":21,"id":"8bcb6461_bd51d276","line":60,"in_reply_to":"16a160c1_94d53013","updated":"2026-04-28 04:49:45.000000000","message":"Acknowledged","commit_id":"81b11074c834ebbc1fe521d6f15ee70c5f468fb3"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"dc8f5366b4f25b474339ef7650d4e30727ed3508","unresolved":true,"context_lines":[{"line_number":80,"context_line":"    Format multiple metrics into a Prometheus text format string."},{"line_number":81,"context_line":""},{"line_number":82,"context_line":"    :param metrics_list: List of tuples (name, value) or"},{"line_number":83,"context_line":"                         (name, value, labels_dict)"},{"line_number":84,"context_line":"    :returns: Multi-line Prometheus-formatted string"},{"line_number":85,"context_line":"    \"\"\""},{"line_number":86,"context_line":"    lines \u003d []"}],"source_content_type":"text/x-python","patch_set":21,"id":"e7e61ac7_bd4cf53c","line":83,"updated":"2026-04-14 19:10:20.000000000","message":"nit: the \"length implies format\" is not *ideal* - maybe better to try and take advantage of the \"structured semantic coupling\" of `metrics_name` and `metrics_value` using the `metrics_key` helper (or something like it?)\n\nThen the list values are ALWAYS two tuples, `(metrics_key, metrics_value)` and `metrics_key` ALWAYS has a `metrics_name` and *sometimes* `metrics_key` has non-empty `metrics_labels`?","commit_id":"81b11074c834ebbc1fe521d6f15ee70c5f468fb3"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"ccfc40fce0c2d3d8e0bb87dbdc88aaf7ea4a150b","unresolved":false,"context_lines":[{"line_number":80,"context_line":"    Format multiple metrics into a Prometheus text format string."},{"line_number":81,"context_line":""},{"line_number":82,"context_line":"    :param metrics_list: List of tuples (name, value) or"},{"line_number":83,"context_line":"                         (name, value, labels_dict)"},{"line_number":84,"context_line":"    :returns: Multi-line Prometheus-formatted string"},{"line_number":85,"context_line":"    \"\"\""},{"line_number":86,"context_line":"    lines \u003d []"}],"source_content_type":"text/x-python","patch_set":21,"id":"85c7ef4e_49e159cb","line":83,"in_reply_to":"e7e61ac7_bd4cf53c","updated":"2026-05-07 21:59:18.000000000","message":"Done","commit_id":"81b11074c834ebbc1fe521d6f15ee70c5f468fb3"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"dc8f5366b4f25b474339ef7650d4e30727ed3508","unresolved":true,"context_lines":[{"line_number":94,"context_line":"        else:"},{"line_number":95,"context_line":"            raise ValueError(f\"Invalid metric tuple: {metric_data}\")"},{"line_number":96,"context_line":""},{"line_number":97,"context_line":"    return \u0027\u0027.join(lines)"},{"line_number":98,"context_line":""},{"line_number":99,"context_line":""},{"line_number":100,"context_line":"def get_prometheus_client(conf, logger\u003dNone):"}],"source_content_type":"text/x-python","patch_set":21,"id":"94109df3_3bf576a9","line":97,"updated":"2026-04-14 19:10:20.000000000","message":"maybe worth a comment that `format_metrics` returns lines with the new-line attached\n\nalternatively if `format_metric` did NOT append a newline; we could return:\n\n```\n\u0027\\n\u0027.join(lines) + \u0027\\n\u0027\n```\n\n^ to ensure that even a single line file always gets the new line at the end.","commit_id":"81b11074c834ebbc1fe521d6f15ee70c5f468fb3"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"ff8bbcd7be70afe80dd41554e2d66eae83fe1cc8","unresolved":false,"context_lines":[{"line_number":94,"context_line":"        else:"},{"line_number":95,"context_line":"            raise ValueError(f\"Invalid metric tuple: {metric_data}\")"},{"line_number":96,"context_line":""},{"line_number":97,"context_line":"    return \u0027\u0027.join(lines)"},{"line_number":98,"context_line":""},{"line_number":99,"context_line":""},{"line_number":100,"context_line":"def get_prometheus_client(conf, logger\u003dNone):"}],"source_content_type":"text/x-python","patch_set":21,"id":"dc34de78_45c02055","line":97,"in_reply_to":"94109df3_3bf576a9","updated":"2026-04-28 04:49:45.000000000","message":"maybe worth a comment that format_metrics returns lines with the new-line attached - DONE\nthere will always be a new line in the end! all other called functions ensure that including render amd gen_metrics","commit_id":"81b11074c834ebbc1fe521d6f15ee70c5f468fb3"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"dc8f5366b4f25b474339ef7650d4e30727ed3508","unresolved":true,"context_lines":[{"line_number":100,"context_line":"def get_prometheus_client(conf, logger\u003dNone):"},{"line_number":101,"context_line":"    \"\"\""},{"line_number":102,"context_line":"        Get an instance of PrometheousClient using config settings."},{"line_number":103,"context_line":"    \"\"\""},{"line_number":104,"context_line":"    conf \u003d conf or {}"},{"line_number":105,"context_line":"    _stats \u003d {}"},{"line_number":106,"context_line":"    return PrometheusClient(conf, _stats, logger)"}],"source_content_type":"text/x-python","patch_set":21,"id":"c637e977_6274bb40","line":103,"updated":"2026-04-14 19:10:20.000000000","message":"please be thinking about a \"default labels\" configuration similar to how we support that for labeled statsd clients:\n\nhttps://github.com/NVIDIA/swift/blob/master/swift/common/statsd_client.py#L169-L195","commit_id":"81b11074c834ebbc1fe521d6f15ee70c5f468fb3"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"ff8bbcd7be70afe80dd41554e2d66eae83fe1cc8","unresolved":false,"context_lines":[{"line_number":100,"context_line":"def get_prometheus_client(conf, logger\u003dNone):"},{"line_number":101,"context_line":"    \"\"\""},{"line_number":102,"context_line":"        Get an instance of PrometheousClient using config settings."},{"line_number":103,"context_line":"    \"\"\""},{"line_number":104,"context_line":"    conf \u003d conf or {}"},{"line_number":105,"context_line":"    _stats \u003d {}"},{"line_number":106,"context_line":"    return PrometheusClient(conf, _stats, logger)"}],"source_content_type":"text/x-python","patch_set":21,"id":"54c3031e_28dcced3","line":103,"in_reply_to":"c637e977_6274bb40","updated":"2026-04-28 04:49:45.000000000","message":"Acknowledged","commit_id":"81b11074c834ebbc1fe521d6f15ee70c5f468fb3"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"dc8f5366b4f25b474339ef7650d4e30727ed3508","unresolved":true,"context_lines":[{"line_number":102,"context_line":"        Get an instance of PrometheousClient using config settings."},{"line_number":103,"context_line":"    \"\"\""},{"line_number":104,"context_line":"    conf \u003d conf or {}"},{"line_number":105,"context_line":"    _stats \u003d {}"},{"line_number":106,"context_line":"    return PrometheusClient(conf, _stats, logger)"},{"line_number":107,"context_line":""},{"line_number":108,"context_line":""}],"source_content_type":"text/x-python","patch_set":21,"id":"b994bea6_582791d4","line":105,"updated":"2026-04-14 19:10:20.000000000","message":"I don\u0027t think callers should have to pass this in(?) - the whole `metrics_key \u003d\u003e value` data structure is sort of an implementation detail?","commit_id":"81b11074c834ebbc1fe521d6f15ee70c5f468fb3"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"ff8bbcd7be70afe80dd41554e2d66eae83fe1cc8","unresolved":false,"context_lines":[{"line_number":102,"context_line":"        Get an instance of PrometheousClient using config settings."},{"line_number":103,"context_line":"    \"\"\""},{"line_number":104,"context_line":"    conf \u003d conf or {}"},{"line_number":105,"context_line":"    _stats \u003d {}"},{"line_number":106,"context_line":"    return PrometheusClient(conf, _stats, logger)"},{"line_number":107,"context_line":""},{"line_number":108,"context_line":""}],"source_content_type":"text/x-python","patch_set":21,"id":"ad2c4044_49f2cd9e","line":105,"in_reply_to":"b994bea6_582791d4","updated":"2026-04-28 04:49:45.000000000","message":"Acknowledged","commit_id":"81b11074c834ebbc1fe521d6f15ee70c5f468fb3"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"dc8f5366b4f25b474339ef7650d4e30727ed3508","unresolved":true,"context_lines":[{"line_number":108,"context_line":""},{"line_number":109,"context_line":"class AbstractPrometheusClient:"},{"line_number":110,"context_line":"    def __init__(self, conf, _stats, logger\u003dNone):"},{"line_number":111,"context_line":"        self.conf \u003d conf"},{"line_number":112,"context_line":"        self._stats \u003d {}"},{"line_number":113,"context_line":"        self.logger \u003d logger"},{"line_number":114,"context_line":""}],"source_content_type":"text/x-python","patch_set":21,"id":"5b918b14_1060f4ea","line":111,"updated":"2026-04-14 19:10:20.000000000","message":"we should do something like:\n\n```\nself.filename \u003d conf.get(\u0027metrics_filename\u0027)\n```\n\nThen you *can* specify filename in the conf; but it\u0027s not required.","commit_id":"81b11074c834ebbc1fe521d6f15ee70c5f468fb3"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"3b9dd1e8218ecea13dd3ef8ac6814cf4467dcca4","unresolved":false,"context_lines":[{"line_number":108,"context_line":""},{"line_number":109,"context_line":"class AbstractPrometheusClient:"},{"line_number":110,"context_line":"    def __init__(self, conf, _stats, logger\u003dNone):"},{"line_number":111,"context_line":"        self.conf \u003d conf"},{"line_number":112,"context_line":"        self._stats \u003d {}"},{"line_number":113,"context_line":"        self.logger \u003d logger"},{"line_number":114,"context_line":""}],"source_content_type":"text/x-python","patch_set":21,"id":"31a41180_282edb42","line":111,"in_reply_to":"5b918b14_1060f4ea","updated":"2026-04-20 20:58:16.000000000","message":"Done","commit_id":"81b11074c834ebbc1fe521d6f15ee70c5f468fb3"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"dc8f5366b4f25b474339ef7650d4e30727ed3508","unresolved":true,"context_lines":[{"line_number":109,"context_line":"class AbstractPrometheusClient:"},{"line_number":110,"context_line":"    def __init__(self, conf, _stats, logger\u003dNone):"},{"line_number":111,"context_line":"        self.conf \u003d conf"},{"line_number":112,"context_line":"        self._stats \u003d {}"},{"line_number":113,"context_line":"        self.logger \u003d logger"},{"line_number":114,"context_line":""},{"line_number":115,"context_line":""}],"source_content_type":"text/x-python","patch_set":21,"id":"799fd949_89841652","line":112,"updated":"2026-04-14 19:10:20.000000000","message":"maybe don\u0027t accept/require this arg (since it has no effect anyway) - maybe for tests if needed?  sort of like `logger` where sometimes we pass in a `debug_logger`?  We don\u0027t use it (successfully?) right now, so I\u0027d be inclined to try and get rid of it - YAGNI.\n\nI have a notion that sometimes a client is going to want to get \"initialized\" from an existing metrics file (???) - but maybe most of the time not?  I think we could start with supporting only the use-case of \"the next time you call flush we overwrite the existing metric filenames with whatever you\u0027ve loaded into your prom client brain\" - and then if a daemon *needs* to \"keep metrics from previous run\" it can decide how to reload (some of?) that state (and maybe if a enough common use-case emerge we could consolidate support here)","commit_id":"81b11074c834ebbc1fe521d6f15ee70c5f468fb3"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"ff8bbcd7be70afe80dd41554e2d66eae83fe1cc8","unresolved":false,"context_lines":[{"line_number":109,"context_line":"class AbstractPrometheusClient:"},{"line_number":110,"context_line":"    def __init__(self, conf, _stats, logger\u003dNone):"},{"line_number":111,"context_line":"        self.conf \u003d conf"},{"line_number":112,"context_line":"        self._stats \u003d {}"},{"line_number":113,"context_line":"        self.logger \u003d logger"},{"line_number":114,"context_line":""},{"line_number":115,"context_line":""}],"source_content_type":"text/x-python","patch_set":21,"id":"2bbee81b_2668fd17","line":112,"in_reply_to":"799fd949_89841652","updated":"2026-04-28 04:49:45.000000000","message":"We can add this easily and work towards a buffer when needed in future. As of now, we probably don\u0027t need to reload existing metrics","commit_id":"81b11074c834ebbc1fe521d6f15ee70c5f468fb3"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"dc8f5366b4f25b474339ef7650d4e30727ed3508","unresolved":true,"context_lines":[{"line_number":110,"context_line":"    def __init__(self, conf, _stats, logger\u003dNone):"},{"line_number":111,"context_line":"        self.conf \u003d conf"},{"line_number":112,"context_line":"        self._stats \u003d {}"},{"line_number":113,"context_line":"        self.logger \u003d logger"},{"line_number":114,"context_line":""},{"line_number":115,"context_line":""},{"line_number":116,"context_line":"class PrometheusClient(AbstractPrometheusClient):"}],"source_content_type":"text/x-python","patch_set":21,"id":"843ea5a0_687d92cf","line":113,"updated":"2026-04-14 19:10:20.000000000","message":"since we only have a single concrete class - let\u0027s avoid the indirection of an abstract base class.\n\nThe statsd-client pattern makes sense b/c there\u0027s two concrete classes - one with labels and a legacy one w/o - we don\u0027t plan to have a prom client that doesn\u0027t support labels so we only need one class for now!","commit_id":"81b11074c834ebbc1fe521d6f15ee70c5f468fb3"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"3b9dd1e8218ecea13dd3ef8ac6814cf4467dcca4","unresolved":false,"context_lines":[{"line_number":110,"context_line":"    def __init__(self, conf, _stats, logger\u003dNone):"},{"line_number":111,"context_line":"        self.conf \u003d conf"},{"line_number":112,"context_line":"        self._stats \u003d {}"},{"line_number":113,"context_line":"        self.logger \u003d logger"},{"line_number":114,"context_line":""},{"line_number":115,"context_line":""},{"line_number":116,"context_line":"class PrometheusClient(AbstractPrometheusClient):"}],"source_content_type":"text/x-python","patch_set":21,"id":"099d2729_1d52b71a","line":113,"in_reply_to":"843ea5a0_687d92cf","updated":"2026-04-20 20:58:16.000000000","message":"Acknowledged","commit_id":"81b11074c834ebbc1fe521d6f15ee70c5f468fb3"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"dc8f5366b4f25b474339ef7650d4e30727ed3508","unresolved":true,"context_lines":[{"line_number":160,"context_line":"        else:"},{"line_number":161,"context_line":"            return f\u0027{name} {value}\\n\u0027"},{"line_number":162,"context_line":""},{"line_number":163,"context_line":"    def write_stats(self, metric, filename\u003dNone):"},{"line_number":164,"context_line":"        prom_metric \u003d self._gen_str_metrics(metric)"},{"line_number":165,"context_line":""},{"line_number":166,"context_line":"        # Update internal stats dictionary"}],"source_content_type":"text/x-python","patch_set":21,"id":"b8e8c080_0e5223ea","line":163,"updated":"2026-04-14 19:10:20.000000000","message":"`wirte_stats` is called with a SINGLE `metric` - it\u0027s more like `write_stat` 🤔\n\nPlease put doc-strings on the methods that are part of the public API for this client.  Please prefix \"private\" implementation detail helper methods with `_` - they should not be expected to be called by clients, and only from with-in other \"public\" methods (that have doc strings).\n\nIn particular the poloy-morphic nature of the `metrics` param deserves a doc-string even if the method is \"private\"","commit_id":"81b11074c834ebbc1fe521d6f15ee70c5f468fb3"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"3b9dd1e8218ecea13dd3ef8ac6814cf4467dcca4","unresolved":false,"context_lines":[{"line_number":160,"context_line":"        else:"},{"line_number":161,"context_line":"            return f\u0027{name} {value}\\n\u0027"},{"line_number":162,"context_line":""},{"line_number":163,"context_line":"    def write_stats(self, metric, filename\u003dNone):"},{"line_number":164,"context_line":"        prom_metric \u003d self._gen_str_metrics(metric)"},{"line_number":165,"context_line":""},{"line_number":166,"context_line":"        # Update internal stats dictionary"}],"source_content_type":"text/x-python","patch_set":21,"id":"f63c4c99_cd0f8a62","line":163,"in_reply_to":"b8e8c080_0e5223ea","updated":"2026-04-20 20:58:16.000000000","message":"Done","commit_id":"81b11074c834ebbc1fe521d6f15ee70c5f468fb3"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"dc8f5366b4f25b474339ef7650d4e30727ed3508","unresolved":true,"context_lines":[{"line_number":166,"context_line":"        # Update internal stats dictionary"},{"line_number":167,"context_line":"        if isinstance(metric, tuple) and len(metric) \u003d\u003d 3:"},{"line_number":168,"context_line":"            name, labels, value \u003d metric"},{"line_number":169,"context_line":"            metric_key_str \u003d metric_key(name, **labels)"},{"line_number":170,"context_line":"            self._stats[metric_key_str] \u003d value"},{"line_number":171,"context_line":"        elif isinstance(metric, str):"},{"line_number":172,"context_line":"            # Parse metric string to update internal state"}],"source_content_type":"text/x-python","patch_set":21,"id":"fd867e29_8bf87eec","line":169,"updated":"2026-04-14 19:10:20.000000000","message":"this isn\u0027t a `_str` - it\u0027s a `metrics_key` two-tuple","commit_id":"81b11074c834ebbc1fe521d6f15ee70c5f468fb3"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"3b9dd1e8218ecea13dd3ef8ac6814cf4467dcca4","unresolved":false,"context_lines":[{"line_number":166,"context_line":"        # Update internal stats dictionary"},{"line_number":167,"context_line":"        if isinstance(metric, tuple) and len(metric) \u003d\u003d 3:"},{"line_number":168,"context_line":"            name, labels, value \u003d metric"},{"line_number":169,"context_line":"            metric_key_str \u003d metric_key(name, **labels)"},{"line_number":170,"context_line":"            self._stats[metric_key_str] \u003d value"},{"line_number":171,"context_line":"        elif isinstance(metric, str):"},{"line_number":172,"context_line":"            # Parse metric string to update internal state"}],"source_content_type":"text/x-python","patch_set":21,"id":"c9c0b62a_fc198e3e","line":169,"in_reply_to":"fd867e29_8bf87eec","updated":"2026-04-20 20:58:16.000000000","message":"Acknowledged","commit_id":"81b11074c834ebbc1fe521d6f15ee70c5f468fb3"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"dc8f5366b4f25b474339ef7650d4e30727ed3508","unresolved":true,"context_lines":[{"line_number":183,"context_line":"        if filename is None:"},{"line_number":184,"context_line":"            # If no filename provided,"},{"line_number":185,"context_line":"            # return the metric string without writing"},{"line_number":186,"context_line":"            return prom_metric"},{"line_number":187,"context_line":"        with open(filename, \"a\") as file:"},{"line_number":188,"context_line":"            file.write(prom_metric)"},{"line_number":189,"context_line":"        return"}],"source_content_type":"text/x-python","patch_set":21,"id":"ed5627af_0322cd4f","line":186,"updated":"2026-04-14 19:10:20.000000000","message":"who uses this?  Can filename be provided via configuration?","commit_id":"81b11074c834ebbc1fe521d6f15ee70c5f468fb3"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"3b9dd1e8218ecea13dd3ef8ac6814cf4467dcca4","unresolved":false,"context_lines":[{"line_number":183,"context_line":"        if filename is None:"},{"line_number":184,"context_line":"            # If no filename provided,"},{"line_number":185,"context_line":"            # return the metric string without writing"},{"line_number":186,"context_line":"            return prom_metric"},{"line_number":187,"context_line":"        with open(filename, \"a\") as file:"},{"line_number":188,"context_line":"            file.write(prom_metric)"},{"line_number":189,"context_line":"        return"}],"source_content_type":"text/x-python","patch_set":21,"id":"c0b37d31_22cdac15","line":186,"in_reply_to":"ed5627af_0322cd4f","updated":"2026-04-20 20:58:16.000000000","message":"Done","commit_id":"81b11074c834ebbc1fe521d6f15ee70c5f468fb3"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"dc8f5366b4f25b474339ef7650d4e30727ed3508","unresolved":true,"context_lines":[{"line_number":185,"context_line":"            # return the metric string without writing"},{"line_number":186,"context_line":"            return prom_metric"},{"line_number":187,"context_line":"        with open(filename, \"a\") as file:"},{"line_number":188,"context_line":"            file.write(prom_metric)"},{"line_number":189,"context_line":"        return"},{"line_number":190,"context_line":""},{"line_number":191,"context_line":"    def write_all_stats(self, stats_list, filename\u003dNone):"}],"source_content_type":"text/x-python","patch_set":21,"id":"473701ad_042f95d8","line":188,"updated":"2026-04-14 19:10:20.000000000","message":"\"append\" is a weird behavior - if a client calls `gauge` twice with the same metric name and different values it ends up in the file TWICE\n\nI\u0027m not sure what a prometheus scraper would do in that case - but I don\u0027t think it\u0027s the interface/beahavior we want to support/encourage.","commit_id":"81b11074c834ebbc1fe521d6f15ee70c5f468fb3"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"3b9dd1e8218ecea13dd3ef8ac6814cf4467dcca4","unresolved":false,"context_lines":[{"line_number":185,"context_line":"            # return the metric string without writing"},{"line_number":186,"context_line":"            return prom_metric"},{"line_number":187,"context_line":"        with open(filename, \"a\") as file:"},{"line_number":188,"context_line":"            file.write(prom_metric)"},{"line_number":189,"context_line":"        return"},{"line_number":190,"context_line":""},{"line_number":191,"context_line":"    def write_all_stats(self, stats_list, filename\u003dNone):"}],"source_content_type":"text/x-python","patch_set":21,"id":"3256e67a_2addef5f","line":188,"in_reply_to":"473701ad_042f95d8","updated":"2026-04-20 20:58:16.000000000","message":"Agree, gauge no longer writes in the file","commit_id":"81b11074c834ebbc1fe521d6f15ee70c5f468fb3"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"dc8f5366b4f25b474339ef7650d4e30727ed3508","unresolved":true,"context_lines":[{"line_number":195,"context_line":"        :param stats_list: List of metrics to write. Each metric can be:"},{"line_number":196,"context_line":"                          - Tuple: (name, value) or (name, value, labels_dict)"},{"line_number":197,"context_line":"                          - Dict: {\"name\": ..., \"labels\": {...}, \"value\": ...}"},{"line_number":198,"context_line":"                          - String: Prometheus format string"},{"line_number":199,"context_line":"        :param filename: Output file path"},{"line_number":200,"context_line":"                         (if not provided, metrics are not written to file)"},{"line_number":201,"context_line":"        :returns: Count of metrics written"}],"source_content_type":"text/x-python","patch_set":21,"id":"0c823a6c_36944c88","line":198,"updated":"2026-04-14 19:10:20.000000000","message":"oh wow, the list of values is polymorphic!?\n\nA *really useful* signature to support would be something like:\n\n```\nprom_client.write_all_stats(prom_client._stats.values(), filename\u003d\u0027/tmp/check.prom\u0027)\n```\n\n^ Actually I think there should be a kind of \"flush\" public API that handles the `self._stats.values()` part FOR you.\n\nThe `filename\u003d` part could maybe also come from configuration:\n\n```\ndaemon.metrics \u003d PromClient({\u0027filename\u0027\u003d\u0027/opt/ss/support/prom/daemon.prom\u0027})\ndaemon.metirics.gauge(\u0027swift_something\u0027, 10, labels\u003d{\u0027event\u0027: \u0027foo\u0027})\ndaemon.metrics.flush()\n```\n\nN.B. if you call `flush` and w/o specifying filename AND it\u0027s not configured that should error?\n\n```\nfilename \u003d filename or self.filename\nif not filename:\n   raise RuntimeError(\u0027you must specify filename\u0027)  # ???\n```","commit_id":"81b11074c834ebbc1fe521d6f15ee70c5f468fb3"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"3b9dd1e8218ecea13dd3ef8ac6814cf4467dcca4","unresolved":false,"context_lines":[{"line_number":195,"context_line":"        :param stats_list: List of metrics to write. Each metric can be:"},{"line_number":196,"context_line":"                          - Tuple: (name, value) or (name, value, labels_dict)"},{"line_number":197,"context_line":"                          - Dict: {\"name\": ..., \"labels\": {...}, \"value\": ...}"},{"line_number":198,"context_line":"                          - String: Prometheus format string"},{"line_number":199,"context_line":"        :param filename: Output file path"},{"line_number":200,"context_line":"                         (if not provided, metrics are not written to file)"},{"line_number":201,"context_line":"        :returns: Count of metrics written"}],"source_content_type":"text/x-python","patch_set":21,"id":"2eaf05d5_32246df8","line":198,"in_reply_to":"0c823a6c_36944c88","updated":"2026-04-20 20:58:16.000000000","message":"Done","commit_id":"81b11074c834ebbc1fe521d6f15ee70c5f468fb3"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"dc8f5366b4f25b474339ef7650d4e30727ed3508","unresolved":true,"context_lines":[{"line_number":204,"context_line":"        for metric in stats_list:"},{"line_number":205,"context_line":"            self.write_stats(metric, filename\u003dfilename)"},{"line_number":206,"context_line":"            metrics_written +\u003d 1"},{"line_number":207,"context_line":"        return metrics_written"},{"line_number":208,"context_line":""},{"line_number":209,"context_line":"    def gauge(self, name, value, labels\u003dNone, filename\u003dNone):"},{"line_number":210,"context_line":"        \"\"\""}],"source_content_type":"text/x-python","patch_set":21,"id":"37fefc45_0c6c6b85","line":207,"updated":"2026-04-14 19:10:20.000000000","message":"if `filename\u003dNone` it seems weird to return `metrics_written \u003d len(stats_list)`\n\nAlso can *individual* metrics fail?\n\nI think we want this method to only open the file once and then write all the lines to the file.\n\nIdeally we\u0027d have temporary atomic renames:\n\n```\nfilename \u003d filename or self.filename\nstats_data \u003d self._render_all_stats()\nwith open(tempfile, \u0027w\u0027) as f:\n    f.write(statsd_data)\nos.rename(tempfile, filename)\n```\n\nSo that you never get a \"partically complete\" metrics file @ `self.filename` - it\u0027s all or nothing!  single `open`, single `write`, atomic `rename`","commit_id":"81b11074c834ebbc1fe521d6f15ee70c5f468fb3"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"3b9dd1e8218ecea13dd3ef8ac6814cf4467dcca4","unresolved":false,"context_lines":[{"line_number":204,"context_line":"        for metric in stats_list:"},{"line_number":205,"context_line":"            self.write_stats(metric, filename\u003dfilename)"},{"line_number":206,"context_line":"            metrics_written +\u003d 1"},{"line_number":207,"context_line":"        return metrics_written"},{"line_number":208,"context_line":""},{"line_number":209,"context_line":"    def gauge(self, name, value, labels\u003dNone, filename\u003dNone):"},{"line_number":210,"context_line":"        \"\"\""}],"source_content_type":"text/x-python","patch_set":21,"id":"6f538054_3d52ba7c","line":207,"in_reply_to":"37fefc45_0c6c6b85","updated":"2026-04-20 20:58:16.000000000","message":"Acknowledged","commit_id":"81b11074c834ebbc1fe521d6f15ee70c5f468fb3"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"dc8f5366b4f25b474339ef7650d4e30727ed3508","unresolved":true,"context_lines":[{"line_number":206,"context_line":"            metrics_written +\u003d 1"},{"line_number":207,"context_line":"        return metrics_written"},{"line_number":208,"context_line":""},{"line_number":209,"context_line":"    def gauge(self, name, value, labels\u003dNone, filename\u003dNone):"},{"line_number":210,"context_line":"        \"\"\""},{"line_number":211,"context_line":"        Record a gauge metric (current value)."},{"line_number":212,"context_line":""}],"source_content_type":"text/x-python","patch_set":21,"id":"08c9696d_f4220400","line":209,"updated":"2026-04-14 19:10:20.000000000","message":"I don\u0027t think that \"record\" functions should need/want to individually override/specify the metrics `fillename`\n\nIt seems like you\u0027d mostly want to create a prometheus_client from a configured filename - and then use the client to update the metrics; and occasionally flush the updated metrics back to disk.","commit_id":"81b11074c834ebbc1fe521d6f15ee70c5f468fb3"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"ff8bbcd7be70afe80dd41554e2d66eae83fe1cc8","unresolved":false,"context_lines":[{"line_number":206,"context_line":"            metrics_written +\u003d 1"},{"line_number":207,"context_line":"        return metrics_written"},{"line_number":208,"context_line":""},{"line_number":209,"context_line":"    def gauge(self, name, value, labels\u003dNone, filename\u003dNone):"},{"line_number":210,"context_line":"        \"\"\""},{"line_number":211,"context_line":"        Record a gauge metric (current value)."},{"line_number":212,"context_line":""}],"source_content_type":"text/x-python","patch_set":21,"id":"a4e2a459_7a250acd","line":209,"in_reply_to":"08c9696d_f4220400","updated":"2026-04-28 04:49:45.000000000","message":"added filename \u003d filename or self.filename","commit_id":"81b11074c834ebbc1fe521d6f15ee70c5f468fb3"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"dc8f5366b4f25b474339ef7650d4e30727ed3508","unresolved":true,"context_lines":[{"line_number":217,"context_line":"        \"\"\""},{"line_number":218,"context_line":"        labels \u003d labels or {}"},{"line_number":219,"context_line":"        metric_data \u003d (name, labels, value)"},{"line_number":220,"context_line":"        self.write_stats(metric_data, filename\u003dfilename)"},{"line_number":221,"context_line":"        return self._gen_str_metrics(metric_data)"},{"line_number":222,"context_line":""},{"line_number":223,"context_line":"    def counter(self, name, value, labels\u003dNone, filename\u003dNone):"}],"source_content_type":"text/x-python","patch_set":21,"id":"a83231e8_2526071f","line":220,"updated":"2026-04-14 19:10:20.000000000","message":"I don\u0027t think we should (ever need to?) do disk io by default for every call to `gauge`\n\nBy default, we should just `record_stats` in `self._stats` ... and leave the caller/owner of the `prom_client` to deicde when it should flush to the filename.","commit_id":"81b11074c834ebbc1fe521d6f15ee70c5f468fb3"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"3b9dd1e8218ecea13dd3ef8ac6814cf4467dcca4","unresolved":false,"context_lines":[{"line_number":217,"context_line":"        \"\"\""},{"line_number":218,"context_line":"        labels \u003d labels or {}"},{"line_number":219,"context_line":"        metric_data \u003d (name, labels, value)"},{"line_number":220,"context_line":"        self.write_stats(metric_data, filename\u003dfilename)"},{"line_number":221,"context_line":"        return self._gen_str_metrics(metric_data)"},{"line_number":222,"context_line":""},{"line_number":223,"context_line":"    def counter(self, name, value, labels\u003dNone, filename\u003dNone):"}],"source_content_type":"text/x-python","patch_set":21,"id":"ac0f08dc_58aed7cf","line":220,"in_reply_to":"a83231e8_2526071f","updated":"2026-04-20 20:58:16.000000000","message":"Done","commit_id":"81b11074c834ebbc1fe521d6f15ee70c5f468fb3"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"6b828f105ebe2501d80931b112050931b95064db","unresolved":true,"context_lines":[{"line_number":40,"context_line":"        labels \u003d {}"},{"line_number":41,"context_line":"        value_str \u003d parts[1]"},{"line_number":42,"context_line":"    value \u003d float(value_str)"},{"line_number":43,"context_line":"    metrics[metric_key(name, **labels)] \u003d value"},{"line_number":44,"context_line":"    return name, labels, value"},{"line_number":45,"context_line":""},{"line_number":46,"context_line":""}],"source_content_type":"text/x-python","patch_set":22,"id":"5334c418_28c0b2e4","line":43,"updated":"2026-04-21 20:05:35.000000000","message":"why do we build the `metrics` dict - it\u0027s not used or returned?","commit_id":"1eb2486a2b5322c030db7f3e4129d7d3b84b59b5"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"ff8bbcd7be70afe80dd41554e2d66eae83fe1cc8","unresolved":false,"context_lines":[{"line_number":40,"context_line":"        labels \u003d {}"},{"line_number":41,"context_line":"        value_str \u003d parts[1]"},{"line_number":42,"context_line":"    value \u003d float(value_str)"},{"line_number":43,"context_line":"    metrics[metric_key(name, **labels)] \u003d value"},{"line_number":44,"context_line":"    return name, labels, value"},{"line_number":45,"context_line":""},{"line_number":46,"context_line":""}],"source_content_type":"text/x-python","patch_set":22,"id":"d7d4c10e_af7d9893","line":43,"in_reply_to":"5334c418_28c0b2e4","updated":"2026-04-28 04:49:45.000000000","message":"right! never used!","commit_id":"1eb2486a2b5322c030db7f3e4129d7d3b84b59b5"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"6b828f105ebe2501d80931b112050931b95064db","unresolved":true,"context_lines":[{"line_number":126,"context_line":"        3. Dict: {\"name\": ..., \"labels\": {...}, \"value\": ...}"},{"line_number":127,"context_line":""},{"line_number":128,"context_line":"        :param metric: Metric data (string, tuple, or dict)"},{"line_number":129,"context_line":"        :returns: a string with prometheus format"},{"line_number":130,"context_line":"        \"\"\""},{"line_number":131,"context_line":"        # Handle string input - parse it"},{"line_number":132,"context_line":"        if isinstance(metric, str):"}],"source_content_type":"text/x-python","patch_set":22,"id":"112c5f34_8f85948c","line":129,"updated":"2026-04-21 20:05:35.000000000","message":"how can this not re-use `format_metrics` - we\u0027ve implemented this twice?","commit_id":"1eb2486a2b5322c030db7f3e4129d7d3b84b59b5"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"ff8bbcd7be70afe80dd41554e2d66eae83fe1cc8","unresolved":false,"context_lines":[{"line_number":126,"context_line":"        3. Dict: {\"name\": ..., \"labels\": {...}, \"value\": ...}"},{"line_number":127,"context_line":""},{"line_number":128,"context_line":"        :param metric: Metric data (string, tuple, or dict)"},{"line_number":129,"context_line":"        :returns: a string with prometheus format"},{"line_number":130,"context_line":"        \"\"\""},{"line_number":131,"context_line":"        # Handle string input - parse it"},{"line_number":132,"context_line":"        if isinstance(metric, str):"}],"source_content_type":"text/x-python","patch_set":22,"id":"38bf09b9_8192fbb7","line":129,"in_reply_to":"112c5f34_8f85948c","updated":"2026-04-28 04:49:45.000000000","message":"Absolutely! Reused!","commit_id":"1eb2486a2b5322c030db7f3e4129d7d3b84b59b5"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"6b828f105ebe2501d80931b112050931b95064db","unresolved":true,"context_lines":[{"line_number":174,"context_line":"            labels_dict \u003d dict(labels_frozenset) if labels_frozenset else {}"},{"line_number":175,"context_line":"            line \u003d self._gen_str_metrics((name, labels_dict, value))"},{"line_number":176,"context_line":"            lines.append(line)"},{"line_number":177,"context_line":"        return \u0027\u0027.join(lines)"},{"line_number":178,"context_line":""},{"line_number":179,"context_line":"    def _update_stat_internal(self, metric):"},{"line_number":180,"context_line":"        \"\"\""}],"source_content_type":"text/x-python","patch_set":22,"id":"7c03d93c_1d25ff35","line":177,"updated":"2026-04-21 20:05:35.000000000","message":"FWIW an empty `self._stats` dict should return an empty string here; which may provide exactly the \"clear stale stats\" behavior I assume we want.","commit_id":"1eb2486a2b5322c030db7f3e4129d7d3b84b59b5"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"ff8bbcd7be70afe80dd41554e2d66eae83fe1cc8","unresolved":false,"context_lines":[{"line_number":174,"context_line":"            labels_dict \u003d dict(labels_frozenset) if labels_frozenset else {}"},{"line_number":175,"context_line":"            line \u003d self._gen_str_metrics((name, labels_dict, value))"},{"line_number":176,"context_line":"            lines.append(line)"},{"line_number":177,"context_line":"        return \u0027\u0027.join(lines)"},{"line_number":178,"context_line":""},{"line_number":179,"context_line":"    def _update_stat_internal(self, metric):"},{"line_number":180,"context_line":"        \"\"\""}],"source_content_type":"text/x-python","patch_set":22,"id":"0282823c_3138d039","line":177,"in_reply_to":"7c03d93c_1d25ff35","updated":"2026-04-28 04:49:45.000000000","message":"Acknowledged","commit_id":"1eb2486a2b5322c030db7f3e4129d7d3b84b59b5"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"6b828f105ebe2501d80931b112050931b95064db","unresolved":true,"context_lines":[{"line_number":183,"context_line":"        :param metric: Metric data in one of these formats:"},{"line_number":184,"context_line":"                      - Tuple: (name, labels_dict, value)"},{"line_number":185,"context_line":"                      - String: Prometheus format string"},{"line_number":186,"context_line":"                      - Dict: {\"name\": ..., \"labels\": {...}, \"value\": ...}"},{"line_number":187,"context_line":"        \"\"\""},{"line_number":188,"context_line":"        if isinstance(metric, tuple) and len(metric) \u003d\u003d 3:"},{"line_number":189,"context_line":"            name, labels, value \u003d metric"}],"source_content_type":"text/x-python","patch_set":22,"id":"b2ed5378_1ba03598","line":186,"updated":"2026-04-21 20:05:35.000000000","message":"this polymorphism sits funny with me; I\u0027m not sure why we need it.\n\n```\n\u003e\u003e\u003e import this\nThe Zen of Python, by Tim Peters\n```\n\n\u003e There should be one-- and preferably only one --obvious way to do it.\n\nI also don\u0027t love:\n```\n        metric_data \u003d (name, labels, value)\n        self._update_stat_internal(metric_data)\n```\n\ncompared to just:\n\n```\nself._stats[metric_key(name, **labels)] \u003d value\n```","commit_id":"1eb2486a2b5322c030db7f3e4129d7d3b84b59b5"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"ff8bbcd7be70afe80dd41554e2d66eae83fe1cc8","unresolved":false,"context_lines":[{"line_number":183,"context_line":"        :param metric: Metric data in one of these formats:"},{"line_number":184,"context_line":"                      - Tuple: (name, labels_dict, value)"},{"line_number":185,"context_line":"                      - String: Prometheus format string"},{"line_number":186,"context_line":"                      - Dict: {\"name\": ..., \"labels\": {...}, \"value\": ...}"},{"line_number":187,"context_line":"        \"\"\""},{"line_number":188,"context_line":"        if isinstance(metric, tuple) and len(metric) \u003d\u003d 3:"},{"line_number":189,"context_line":"            name, labels, value \u003d metric"}],"source_content_type":"text/x-python","patch_set":22,"id":"0aade15e_5b130aa1","line":186,"in_reply_to":"b2ed5378_1ba03598","updated":"2026-04-28 04:49:45.000000000","message":"Done","commit_id":"1eb2486a2b5322c030db7f3e4129d7d3b84b59b5"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"ff8bbcd7be70afe80dd41554e2d66eae83fe1cc8","unresolved":false,"context_lines":[{"line_number":233,"context_line":"        stats_data \u003d self._render_all_stats()"},{"line_number":234,"context_line":"        "},{"line_number":235,"context_line":"        # Write atomically using temp file"},{"line_number":236,"context_line":"        temp_fd, tempfile_path \u003d tempfile.mkstemp(dir\u003dos.path.dirname(filename))"},{"line_number":237,"context_line":"        try:"},{"line_number":238,"context_line":"            with os.fdopen(temp_fd, \u0027w\u0027) as f:"},{"line_number":239,"context_line":"                f.write(stats_data)"}],"source_content_type":"text/x-python","patch_set":22,"id":"9e7669b6_49745ca2","line":236,"in_reply_to":"16d0101a_f376744f","updated":"2026-04-28 04:49:45.000000000","message":"Done","commit_id":"1eb2486a2b5322c030db7f3e4129d7d3b84b59b5"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"6b828f105ebe2501d80931b112050931b95064db","unresolved":true,"context_lines":[{"line_number":233,"context_line":"        stats_data \u003d self._render_all_stats()"},{"line_number":234,"context_line":"        "},{"line_number":235,"context_line":"        # Write atomically using temp file"},{"line_number":236,"context_line":"        temp_fd, tempfile_path \u003d tempfile.mkstemp(dir\u003dos.path.dirname(filename))"},{"line_number":237,"context_line":"        try:"},{"line_number":238,"context_line":"            with os.fdopen(temp_fd, \u0027w\u0027) as f:"},{"line_number":239,"context_line":"                f.write(stats_data)"}],"source_content_type":"text/x-python","patch_set":22,"id":"16d0101a_f376744f","line":236,"in_reply_to":"8c9845cb_1f72567f","updated":"2026-04-21 20:05:35.000000000","message":"I\u0027d recommend `NamedTemporaryFile` as a context manager:\n\n```\ntf \u003d None\ntry:\n   with temp as tf:\n       tf.write()\n   renamer(tf.name, filename)\nfinally:\n   if tf:\n       try:\n           os.unlink(tf.name)\n       except FileNotFoundError:\n           pass\n```\n\nread the implemention of `swift.common.utils.dump_recon_cache` for inspiration\n\nN.B. We don\u0027t currently need/want the locking, but I couldn\u0027t find another example of \"dump to temp file and rename into place\" - honestly you could probably extract that as a common primitive `swift.common.utils.atomic_dump`","commit_id":"1eb2486a2b5322c030db7f3e4129d7d3b84b59b5"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"6b828f105ebe2501d80931b112050931b95064db","unresolved":true,"context_lines":[{"line_number":252,"context_line":"        \"\"\""},{"line_number":253,"context_line":"        # If there are no stats to write, don\u0027t write to file"},{"line_number":254,"context_line":"        if not self._stats:"},{"line_number":255,"context_line":"            return"},{"line_number":256,"context_line":"        "},{"line_number":257,"context_line":"        # Render all accumulated stats"},{"line_number":258,"context_line":"        stats_data \u003d self._render_all_stats()"}],"source_content_type":"text/x-python","patch_set":22,"id":"82bb76d5_a73f5576","line":255,"updated":"2026-04-21 20:05:35.000000000","message":"I\u0027m not sure \"do nothing\" is more correct than \"truncate file\"\n\n... if you create a client and call dump_stats I wouldn\u0027t expect the \"stale\" stats from a previous run should stay there on disk.","commit_id":"1eb2486a2b5322c030db7f3e4129d7d3b84b59b5"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"ff8bbcd7be70afe80dd41554e2d66eae83fe1cc8","unresolved":false,"context_lines":[{"line_number":252,"context_line":"        \"\"\""},{"line_number":253,"context_line":"        # If there are no stats to write, don\u0027t write to file"},{"line_number":254,"context_line":"        if not self._stats:"},{"line_number":255,"context_line":"            return"},{"line_number":256,"context_line":"        "},{"line_number":257,"context_line":"        # Render all accumulated stats"},{"line_number":258,"context_line":"        stats_data \u003d self._render_all_stats()"}],"source_content_type":"text/x-python","patch_set":22,"id":"609c769a_7a18ff8d","line":255,"in_reply_to":"82bb76d5_a73f5576","updated":"2026-04-28 04:49:45.000000000","message":"we might use write_all_stats multiple times write periodically. Using NamedTemporaryFile with delete\u003dFalse and os.rename() ensures the file is never in a partial/corrupted state. The temp file is written completely, then atomically renamed over the old file.\nAnd we also write stats everytime (not append) which is similar behavior to truncate!","commit_id":"1eb2486a2b5322c030db7f3e4129d7d3b84b59b5"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"6b828f105ebe2501d80931b112050931b95064db","unresolved":true,"context_lines":[{"line_number":300,"context_line":"                        os.remove(tempfile_path)"},{"line_number":301,"context_line":"                    raise"},{"line_number":302,"context_line":"        "},{"line_number":303,"context_line":"        return self._gen_str_metrics(metric_data)"},{"line_number":304,"context_line":""},{"line_number":305,"context_line":"    def counter(self, name, value, labels\u003dNone, filename\u003dNone):"},{"line_number":306,"context_line":"        \"\"\""}],"source_content_type":"text/x-python","patch_set":22,"id":"11cf65ad_796c0803","line":303,"updated":"2026-04-21 20:05:35.000000000","message":"I don\u0027t think this method needs to return anything - the statsd client somewhat arbitrarily returns the number of bytes sent to the socket\n\nThe disadvantage of baking this return value is two-fold\n\n1) it establishes an interface that we may be obliged to support even if the format of the rendered string evolves to include type annotations\n2) it\u0027s computational effort not-needed at this time; the only time we need to gen_str_metrics is in dump - and we don\u0027t want to dump every call to gauage\n\nThe best anaology I can think of in stdlib is a plain old dict udpate:\n\n```\n\u003e\u003e\u003e d \u003d {\u0027foo\u0027: \u0027bar\u0027}\n\u003e\u003e\u003e d.update({\u0027baz\u0027: \u0027buz\u0027})\n\u003e\u003e\u003e d\n{\u0027foo\u0027: \u0027bar\u0027, \u0027baz\u0027: \u0027buz\u0027}\n```\n\n`d.update` doesn\u0027t *return* anything - because it\u0027s updating in place","commit_id":"1eb2486a2b5322c030db7f3e4129d7d3b84b59b5"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"7abd8df05b8081785b9541f05ff02331685d4b3f","unresolved":false,"context_lines":[{"line_number":300,"context_line":"                        os.remove(tempfile_path)"},{"line_number":301,"context_line":"                    raise"},{"line_number":302,"context_line":"        "},{"line_number":303,"context_line":"        return self._gen_str_metrics(metric_data)"},{"line_number":304,"context_line":""},{"line_number":305,"context_line":"    def counter(self, name, value, labels\u003dNone, filename\u003dNone):"},{"line_number":306,"context_line":"        \"\"\""}],"source_content_type":"text/x-python","patch_set":22,"id":"98935426_31522144","line":303,"in_reply_to":"11cf65ad_796c0803","updated":"2026-05-05 15:26:31.000000000","message":"yep, totally!","commit_id":"1eb2486a2b5322c030db7f3e4129d7d3b84b59b5"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"6b828f105ebe2501d80931b112050931b95064db","unresolved":true,"context_lines":[{"line_number":312,"context_line":"        :param filename: Optional filename to write all accumulated stats to"},{"line_number":313,"context_line":"        \"\"\""},{"line_number":314,"context_line":"        labels \u003d labels or {}"},{"line_number":315,"context_line":"        metric_data \u003d (name, labels, value)"},{"line_number":316,"context_line":"        self._update_stat_internal(metric_data)"},{"line_number":317,"context_line":"        "},{"line_number":318,"context_line":"        if filename:"}],"source_content_type":"text/x-python","patch_set":22,"id":"a283cd68_700d8bf2","line":315,"updated":"2026-04-21 20:05:35.000000000","message":"how can this implementation be the same as `gauge` - where is the \"monotonically increasing\" part?\n\nperhaps:\n\n```\n        key \u003d metric_key(name, **labels)\n        value \u003d self._stats.get(key, 0) + value\n        self._stats[key] \u003d value\n```\n\n... I\u0027m not actually sure what the expected behavior should be; presumably this and you replace increment/decrement w/ `self.counter(name, [+/-]1, labels\u003dlabels)`?\n\nI guess classically a prometheus style \"counter\" should only ever go up - something like `http_requests_total`\n\n```\n# TYPE http_requests_total counter\nhttp_requests_total{method\u003d\"GET\"} 12345\n```\n\n^ would start a `zero` and only ever go up (getting reset to zero on process restart, which I guess prometheus collectors pay attention to and understand different compared to the value of a gauge)\n\nIn otel-land the API requires you to declare the type of a metric and it\u0027s affordances change the provided methods:\n\n```\nGauage.record(value)\nCounter.add(value)\nUpDownCounter.add(value)  # value can be negative\n```\n\nUltimately the only difference in how they\u0027re rendered is in the `TYPE` comment; so I think we can just rename our interface to follow otel convention:\n\n```\ngauge \u003d\u003e record\ncounter \u003d\u003e add\n```\n\n`increment` and `decrement` can stay for compatibility/familiarity with statsd_client - but should just wrap `self.add([+|-]1)`","commit_id":"1eb2486a2b5322c030db7f3e4129d7d3b84b59b5"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"ff8bbcd7be70afe80dd41554e2d66eae83fe1cc8","unresolved":false,"context_lines":[{"line_number":312,"context_line":"        :param filename: Optional filename to write all accumulated stats to"},{"line_number":313,"context_line":"        \"\"\""},{"line_number":314,"context_line":"        labels \u003d labels or {}"},{"line_number":315,"context_line":"        metric_data \u003d (name, labels, value)"},{"line_number":316,"context_line":"        self._update_stat_internal(metric_data)"},{"line_number":317,"context_line":"        "},{"line_number":318,"context_line":"        if filename:"}],"source_content_type":"text/x-python","patch_set":22,"id":"b3685d4e_3abb5bb4","line":315,"in_reply_to":"a283cd68_700d8bf2","updated":"2026-04-28 04:49:45.000000000","message":"Done","commit_id":"1eb2486a2b5322c030db7f3e4129d7d3b84b59b5"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"6b828f105ebe2501d80931b112050931b95064db","unresolved":true,"context_lines":[{"line_number":360,"context_line":"                except Exception:"},{"line_number":361,"context_line":"                    if os.path.exists(tempfile_path):"},{"line_number":362,"context_line":"                        os.remove(tempfile_path)"},{"line_number":363,"context_line":"                    raise"},{"line_number":364,"context_line":"        "},{"line_number":365,"context_line":"        return self._gen_str_metrics(metric_data)"},{"line_number":366,"context_line":""}],"source_content_type":"text/x-python","patch_set":22,"id":"cf9ee47b_d43e89fc","line":363,"updated":"2026-04-21 20:05:35.000000000","message":"this behavior is duplicated three times - in addition to `write_all_stats` (which is the only place I expect to see it) - that\u0027s a smell\n\nI don\u0027t think we need `histogram_bucket` et al to support a `filename\u003d` kwarg, nor dump to stats immediately after adding the metrics.\n\nIf that behavior is needed I think callers could simply:\n\n```\nclient \u003d PromClient(filename\u003dmy_file)\nclient.histogram_bucket(\u0027my_hist\u0027, 5, 10)\nclient.dump_stats()\n```","commit_id":"1eb2486a2b5322c030db7f3e4129d7d3b84b59b5"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"ff8bbcd7be70afe80dd41554e2d66eae83fe1cc8","unresolved":false,"context_lines":[{"line_number":360,"context_line":"                except Exception:"},{"line_number":361,"context_line":"                    if os.path.exists(tempfile_path):"},{"line_number":362,"context_line":"                        os.remove(tempfile_path)"},{"line_number":363,"context_line":"                    raise"},{"line_number":364,"context_line":"        "},{"line_number":365,"context_line":"        return self._gen_str_metrics(metric_data)"},{"line_number":366,"context_line":""}],"source_content_type":"text/x-python","patch_set":22,"id":"f50b47c1_755f2ad4","line":363,"in_reply_to":"cf9ee47b_d43e89fc","updated":"2026-04-28 04:49:45.000000000","message":"Do we not even want filename\u003dxyz for other ones like write ot flush?\nSafely, I\u0027ve added filename \u003d filename or self.filename everywhere!","commit_id":"1eb2486a2b5322c030db7f3e4129d7d3b84b59b5"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"6b828f105ebe2501d80931b112050931b95064db","unresolved":true,"context_lines":[{"line_number":365,"context_line":"        return self._gen_str_metrics(metric_data)"},{"line_number":366,"context_line":""},{"line_number":367,"context_line":"    def update_stats(self, metric, update_val, filename\u003dNone):"},{"line_number":368,"context_line":"        \"\"\"Update a metric with a new value.\"\"\""},{"line_number":369,"context_line":"        name, labels, value \u003d parse_metrics(metric)"},{"line_number":370,"context_line":"        metric_key_str \u003d metric_key(name, **labels)"},{"line_number":371,"context_line":"        self._stats[metric_key_str] \u003d update_val"}],"source_content_type":"text/x-python","patch_set":22,"id":"5eaacc28_41a501c0","line":368,"updated":"2026-04-21 20:05:35.000000000","message":"i don\u0027t know what this method is for - why would you ever have a \"metric_str\" already in hand from the caller?  This whole abstraction is so that callers don\u0027t have to think about rendered metrics and can instead just think in `(name, labels)`","commit_id":"1eb2486a2b5322c030db7f3e4129d7d3b84b59b5"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"ff8bbcd7be70afe80dd41554e2d66eae83fe1cc8","unresolved":false,"context_lines":[{"line_number":365,"context_line":"        return self._gen_str_metrics(metric_data)"},{"line_number":366,"context_line":""},{"line_number":367,"context_line":"    def update_stats(self, metric, update_val, filename\u003dNone):"},{"line_number":368,"context_line":"        \"\"\"Update a metric with a new value.\"\"\""},{"line_number":369,"context_line":"        name, labels, value \u003d parse_metrics(metric)"},{"line_number":370,"context_line":"        metric_key_str \u003d metric_key(name, **labels)"},{"line_number":371,"context_line":"        self._stats[metric_key_str] \u003d update_val"}],"source_content_type":"text/x-python","patch_set":22,"id":"19d6107c_ad6c0416","line":368,"in_reply_to":"5eaacc28_41a501c0","updated":"2026-04-28 04:49:45.000000000","message":"Done","commit_id":"1eb2486a2b5322c030db7f3e4129d7d3b84b59b5"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"6b828f105ebe2501d80931b112050931b95064db","unresolved":true,"context_lines":[{"line_number":373,"context_line":"        prom_metric \u003d self._gen_str_metrics(metric_data)"},{"line_number":374,"context_line":"        self._write_stat(prom_metric, filename\u003dfilename)"},{"line_number":375,"context_line":""},{"line_number":376,"context_line":"    def increment(self, metric, filename\u003dNone):"},{"line_number":377,"context_line":"        \"\"\"Increment a metric value by 1.\"\"\""},{"line_number":378,"context_line":"        name, labels, value \u003d parse_metrics(metric)"},{"line_number":379,"context_line":"        metric_key_str \u003d metric_key(name, **labels)"}],"source_content_type":"text/x-python","patch_set":22,"id":"660e3c36_62c7f6ea","line":376,"updated":"2026-04-21 20:05:35.000000000","message":"remove the filename kwarg, add a label kwarg.\n\n`metric` here should be the `metric_name` not a `metric_str`","commit_id":"1eb2486a2b5322c030db7f3e4129d7d3b84b59b5"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"ff8bbcd7be70afe80dd41554e2d66eae83fe1cc8","unresolved":false,"context_lines":[{"line_number":373,"context_line":"        prom_metric \u003d self._gen_str_metrics(metric_data)"},{"line_number":374,"context_line":"        self._write_stat(prom_metric, filename\u003dfilename)"},{"line_number":375,"context_line":""},{"line_number":376,"context_line":"    def increment(self, metric, filename\u003dNone):"},{"line_number":377,"context_line":"        \"\"\"Increment a metric value by 1.\"\"\""},{"line_number":378,"context_line":"        name, labels, value \u003d parse_metrics(metric)"},{"line_number":379,"context_line":"        metric_key_str \u003d metric_key(name, **labels)"}],"source_content_type":"text/x-python","patch_set":22,"id":"cf7dbef2_29e9df43","line":376,"in_reply_to":"660e3c36_62c7f6ea","updated":"2026-04-28 04:49:45.000000000","message":"reused this filename \u003d filename or self.filename!","commit_id":"1eb2486a2b5322c030db7f3e4129d7d3b84b59b5"},{"author":{"_account_id":36606,"name":"Yan Xiao","display_name":"Yan","email":"yanxiao@nvidia.com","username":"yanxiao"},"change_message_id":"9af175fc7017025ca53e038365aeeccc2afae5c8","unresolved":true,"context_lines":[{"line_number":109,"context_line":"    Format a single metric into Prometheus text format string."},{"line_number":110,"context_line":""},{"line_number":111,"context_line":"    Examples:"},{"line_number":112,"context_line":"        format_metric(\u0027http_requests_total\u0027, 42)"},{"line_number":113,"context_line":"        # Returns: \u0027http_requests_total 42\\n\u0027"},{"line_number":114,"context_line":""},{"line_number":115,"context_line":"        format_metric(\u0027http_requests_total\u0027, 42,"}],"source_content_type":"text/x-python","patch_set":26,"id":"82e5cdc5_31ce0a32","line":112,"updated":"2026-05-15 19:59:23.000000000","message":"should rename this in examples?","commit_id":"5e0d0d3c8322ec5a4e8d2fdb9c9c5be3ab246a07"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"a04019e2e8bb266360d9d0f587f3ed5868404632","unresolved":false,"context_lines":[{"line_number":109,"context_line":"    Format a single metric into Prometheus text format string."},{"line_number":110,"context_line":""},{"line_number":111,"context_line":"    Examples:"},{"line_number":112,"context_line":"        format_metric(\u0027http_requests_total\u0027, 42)"},{"line_number":113,"context_line":"        # Returns: \u0027http_requests_total 42\\n\u0027"},{"line_number":114,"context_line":""},{"line_number":115,"context_line":"        format_metric(\u0027http_requests_total\u0027, 42,"}],"source_content_type":"text/x-python","patch_set":26,"id":"4ec6859d_adefdc31","line":112,"in_reply_to":"82e5cdc5_31ce0a32","updated":"2026-05-19 03:19:45.000000000","message":"Acknowledged","commit_id":"5e0d0d3c8322ec5a4e8d2fdb9c9c5be3ab246a07"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"972b28563a02fa2ef2d7a8bf90e14b0818ffa7d4","unresolved":true,"context_lines":[{"line_number":137,"context_line":"        return name + \u0027 \u0027 + str(value) + \u0027\\n\u0027"},{"line_number":138,"context_line":""},{"line_number":139,"context_line":""},{"line_number":140,"context_line":"def format_metrics_batch(metrics_list):"},{"line_number":141,"context_line":"    \"\"\""},{"line_number":142,"context_line":"    Format multiple metrics into a Prometheus text format string."},{"line_number":143,"context_line":""}],"source_content_type":"text/x-python","patch_set":26,"id":"5b300bf2_2f7bd077","line":140,"updated":"2026-05-15 23:54:38.000000000","message":"How is this method different from `PrometheusClient._render_all_stats`? It seems that both output the same thing but using slightly different input.","commit_id":"5e0d0d3c8322ec5a4e8d2fdb9c9c5be3ab246a07"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"a04019e2e8bb266360d9d0f587f3ed5868404632","unresolved":false,"context_lines":[{"line_number":137,"context_line":"        return name + \u0027 \u0027 + str(value) + \u0027\\n\u0027"},{"line_number":138,"context_line":""},{"line_number":139,"context_line":""},{"line_number":140,"context_line":"def format_metrics_batch(metrics_list):"},{"line_number":141,"context_line":"    \"\"\""},{"line_number":142,"context_line":"    Format multiple metrics into a Prometheus text format string."},{"line_number":143,"context_line":""}],"source_content_type":"text/x-python","patch_set":26,"id":"eb7f9cfd_092f43fd","line":140,"in_reply_to":"5b300bf2_2f7bd077","updated":"2026-05-19 03:19:45.000000000","message":"yes, totally! Ive modified write_all_stats to use format_metrics_batch","commit_id":"5e0d0d3c8322ec5a4e8d2fdb9c9c5be3ab246a07"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"972b28563a02fa2ef2d7a8bf90e14b0818ffa7d4","unresolved":true,"context_lines":[{"line_number":170,"context_line":"        prom_user_label_hostname\u003dmyhost"},{"line_number":171,"context_line":"        prom_user_label_region\u003dus-west"},{"line_number":172,"context_line":""},{"line_number":173,"context_line":"    Default labels are automatically added to all metrics as \u0027user_*\u0027 labels."},{"line_number":174,"context_line":""},{"line_number":175,"context_line":"    :param conf: Configuration dictionary"},{"line_number":176,"context_line":"    :returns: PrometheusClient instance"}],"source_content_type":"text/x-python","patch_set":26,"id":"ffe4bdf4_948a9e97","line":173,"updated":"2026-05-15 23:54:38.000000000","message":"It took me a minute to realize that user labels and default labels are the same thing. I vote we stick to one of those throughout.","commit_id":"5e0d0d3c8322ec5a4e8d2fdb9c9c5be3ab246a07"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"a04019e2e8bb266360d9d0f587f3ed5868404632","unresolved":false,"context_lines":[{"line_number":170,"context_line":"        prom_user_label_hostname\u003dmyhost"},{"line_number":171,"context_line":"        prom_user_label_region\u003dus-west"},{"line_number":172,"context_line":""},{"line_number":173,"context_line":"    Default labels are automatically added to all metrics as \u0027user_*\u0027 labels."},{"line_number":174,"context_line":""},{"line_number":175,"context_line":"    :param conf: Configuration dictionary"},{"line_number":176,"context_line":"    :returns: PrometheusClient instance"}],"source_content_type":"text/x-python","patch_set":26,"id":"1968e011_e4b03535","line":173,"in_reply_to":"ffe4bdf4_948a9e97","updated":"2026-05-19 03:19:45.000000000","message":"Acknowledged","commit_id":"5e0d0d3c8322ec5a4e8d2fdb9c9c5be3ab246a07"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"972b28563a02fa2ef2d7a8bf90e14b0818ffa7d4","unresolved":true,"context_lines":[{"line_number":182,"context_line":"    # Extract default labels from configuration"},{"line_number":183,"context_line":"    default_labels \u003d {}"},{"line_number":184,"context_line":"    for k, v in conf.items():"},{"line_number":185,"context_line":"        if not k.startswith(PROM_CONF_USER_LABEL_PREFIX):"},{"line_number":186,"context_line":"            continue"},{"line_number":187,"context_line":"        conf_label \u003d k[len(PROM_CONF_USER_LABEL_PREFIX):]"},{"line_number":188,"context_line":"        result \u003d USER_LABEL_PATTERN.search(conf_label)"}],"source_content_type":"text/x-python","patch_set":26,"id":"e21e38af_18544436","line":185,"updated":"2026-05-15 23:54:38.000000000","message":"It took me a minute to realize that user labels and default labels are the same thing. I vote we stick to one of those throughout.","commit_id":"5e0d0d3c8322ec5a4e8d2fdb9c9c5be3ab246a07"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"a04019e2e8bb266360d9d0f587f3ed5868404632","unresolved":false,"context_lines":[{"line_number":182,"context_line":"    # Extract default labels from configuration"},{"line_number":183,"context_line":"    default_labels \u003d {}"},{"line_number":184,"context_line":"    for k, v in conf.items():"},{"line_number":185,"context_line":"        if not k.startswith(PROM_CONF_USER_LABEL_PREFIX):"},{"line_number":186,"context_line":"            continue"},{"line_number":187,"context_line":"        conf_label \u003d k[len(PROM_CONF_USER_LABEL_PREFIX):]"},{"line_number":188,"context_line":"        result \u003d USER_LABEL_PATTERN.search(conf_label)"}],"source_content_type":"text/x-python","patch_set":26,"id":"1f318665_97d9ad7c","line":185,"in_reply_to":"e21e38af_18544436","updated":"2026-05-19 03:19:45.000000000","message":"Done","commit_id":"5e0d0d3c8322ec5a4e8d2fdb9c9c5be3ab246a07"},{"author":{"_account_id":36606,"name":"Yan Xiao","display_name":"Yan","email":"yanxiao@nvidia.com","username":"yanxiao"},"change_message_id":"c757555c05ceda476908f7253accf8f9e86b3ebf","unresolved":true,"context_lines":[{"line_number":212,"context_line":"    def _render_all_stats(self):"},{"line_number":213,"context_line":"        \"\"\""},{"line_number":214,"context_line":"        Render all accumulated stats as a Prometheus-formatted string."},{"line_number":215,"context_line":""},{"line_number":216,"context_line":"        Each metric line ends with a newline character. If stats exist,"},{"line_number":217,"context_line":"        the returned string will always end with a newline."},{"line_number":218,"context_line":""}],"source_content_type":"text/x-python","patch_set":26,"id":"0eff02a6_c9fd770e","line":215,"updated":"2026-05-18 19:16:18.000000000","message":"IIRC from prometheus document, the #TYPE metadata comment is optional, although not sure if the counter or gauge types are required to load the text format","commit_id":"5e0d0d3c8322ec5a4e8d2fdb9c9c5be3ab246a07"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"972b28563a02fa2ef2d7a8bf90e14b0818ffa7d4","unresolved":true,"context_lines":[{"line_number":220,"context_line":"                  (or empty string if no stats accumulated)"},{"line_number":221,"context_line":"        \"\"\""},{"line_number":222,"context_line":"        lines \u003d []"},{"line_number":223,"context_line":"        for metrics_key, value in sorted(self._stats.items()):"},{"line_number":224,"context_line":"            # metrics_key is (name, frozenset(labels))"},{"line_number":225,"context_line":"            name, labels_frozenset \u003d metrics_key"},{"line_number":226,"context_line":"            labels_dict \u003d dict(labels_frozenset) if labels_frozenset else {}"}],"source_content_type":"text/x-python","patch_set":26,"id":"c4b75279_9891f327","line":223,"updated":"2026-05-15 23:54:38.000000000","message":"Tests pass for me when we drop `sort`:\n```\ndiff --git a/swift/common/prom_metrics.py b/swift/common/prom_metrics.py\nindex 7a4114d80..c20d95a6c 100644\n--- a/swift/common/prom_metrics.py\n+++ b/swift/common/prom_metrics.py\n@@ -223 +223 @@ class PrometheusClient:\n-        for metrics_key, value in sorted(self._stats.items()):\n+        for metrics_key, value in self._stats.items():\n```","commit_id":"5e0d0d3c8322ec5a4e8d2fdb9c9c5be3ab246a07"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"a04019e2e8bb266360d9d0f587f3ed5868404632","unresolved":false,"context_lines":[{"line_number":220,"context_line":"                  (or empty string if no stats accumulated)"},{"line_number":221,"context_line":"        \"\"\""},{"line_number":222,"context_line":"        lines \u003d []"},{"line_number":223,"context_line":"        for metrics_key, value in sorted(self._stats.items()):"},{"line_number":224,"context_line":"            # metrics_key is (name, frozenset(labels))"},{"line_number":225,"context_line":"            name, labels_frozenset \u003d metrics_key"},{"line_number":226,"context_line":"            labels_dict \u003d dict(labels_frozenset) if labels_frozenset else {}"}],"source_content_type":"text/x-python","patch_set":26,"id":"cd709003_d16ddd3c","line":223,"in_reply_to":"c4b75279_9891f327","updated":"2026-05-19 03:19:45.000000000","message":"Acknowledged","commit_id":"5e0d0d3c8322ec5a4e8d2fdb9c9c5be3ab246a07"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"972b28563a02fa2ef2d7a8bf90e14b0818ffa7d4","unresolved":true,"context_lines":[{"line_number":223,"context_line":"        for metrics_key, value in sorted(self._stats.items()):"},{"line_number":224,"context_line":"            # metrics_key is (name, frozenset(labels))"},{"line_number":225,"context_line":"            name, labels_frozenset \u003d metrics_key"},{"line_number":226,"context_line":"            labels_dict \u003d dict(labels_frozenset) if labels_frozenset else {}"},{"line_number":227,"context_line":"            line \u003d _build_line_parts(name, value, labels_dict)"},{"line_number":228,"context_line":"            lines.append(line)"},{"line_number":229,"context_line":"        return \u0027\u0027.join(lines)"}],"source_content_type":"text/x-python","patch_set":26,"id":"40ce8e73_7ef11f76","line":226,"updated":"2026-05-15 23:54:38.000000000","message":"I would\u0027ve imagined `else None` since that\u0027s what is implied by `_build_line_parts`\u0027s signature when we don\u0027t have labels.","commit_id":"5e0d0d3c8322ec5a4e8d2fdb9c9c5be3ab246a07"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"a04019e2e8bb266360d9d0f587f3ed5868404632","unresolved":false,"context_lines":[{"line_number":223,"context_line":"        for metrics_key, value in sorted(self._stats.items()):"},{"line_number":224,"context_line":"            # metrics_key is (name, frozenset(labels))"},{"line_number":225,"context_line":"            name, labels_frozenset \u003d metrics_key"},{"line_number":226,"context_line":"            labels_dict \u003d dict(labels_frozenset) if labels_frozenset else {}"},{"line_number":227,"context_line":"            line \u003d _build_line_parts(name, value, labels_dict)"},{"line_number":228,"context_line":"            lines.append(line)"},{"line_number":229,"context_line":"        return \u0027\u0027.join(lines)"}],"source_content_type":"text/x-python","patch_set":26,"id":"d7a8a66a_dc96f541","line":226,"in_reply_to":"40ce8e73_7ef11f76","updated":"2026-05-19 03:19:45.000000000","message":"Done","commit_id":"5e0d0d3c8322ec5a4e8d2fdb9c9c5be3ab246a07"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"972b28563a02fa2ef2d7a8bf90e14b0818ffa7d4","unresolved":true,"context_lines":[{"line_number":232,"context_line":"        self._stats[metric_key(name, **(labels or {}))] \u003d value"},{"line_number":233,"context_line":""},{"line_number":234,"context_line":"    def write_all_stats(self, filename\u003dNone):"},{"line_number":235,"context_line":"        filename \u003d filename or self.filename"},{"line_number":236,"context_line":"        if not filename or not self._stats:"},{"line_number":237,"context_line":"            return"},{"line_number":238,"context_line":"        stats_data \u003d self._render_all_stats()"}],"source_content_type":"text/x-python","patch_set":26,"id":"bfd4044a_11b66c92","line":235,"updated":"2026-05-15 23:54:38.000000000","message":"This seems to be the only method that can be used to extract meaningful information out of this class. I don\u0027t see why you would to support `filename \u003d\u003d None`.","commit_id":"5e0d0d3c8322ec5a4e8d2fdb9c9c5be3ab246a07"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"a04019e2e8bb266360d9d0f587f3ed5868404632","unresolved":false,"context_lines":[{"line_number":232,"context_line":"        self._stats[metric_key(name, **(labels or {}))] \u003d value"},{"line_number":233,"context_line":""},{"line_number":234,"context_line":"    def write_all_stats(self, filename\u003dNone):"},{"line_number":235,"context_line":"        filename \u003d filename or self.filename"},{"line_number":236,"context_line":"        if not filename or not self._stats:"},{"line_number":237,"context_line":"            return"},{"line_number":238,"context_line":"        stats_data \u003d self._render_all_stats()"}],"source_content_type":"text/x-python","patch_set":26,"id":"afaac5d6_70bcaca9","line":235,"in_reply_to":"bfd4044a_11b66c92","updated":"2026-05-19 03:19:45.000000000","message":"Acknowledged","commit_id":"5e0d0d3c8322ec5a4e8d2fdb9c9c5be3ab246a07"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"972b28563a02fa2ef2d7a8bf90e14b0818ffa7d4","unresolved":true,"context_lines":[{"line_number":233,"context_line":""},{"line_number":234,"context_line":"    def write_all_stats(self, filename\u003dNone):"},{"line_number":235,"context_line":"        filename \u003d filename or self.filename"},{"line_number":236,"context_line":"        if not filename or not self._stats:"},{"line_number":237,"context_line":"            return"},{"line_number":238,"context_line":"        stats_data \u003d self._render_all_stats()"},{"line_number":239,"context_line":"        tf \u003d None"}],"source_content_type":"text/x-python","patch_set":26,"id":"ec93795c_12431d01","line":236,"updated":"2026-05-15 23:54:38.000000000","message":"Why would we check `self._stats` here?","commit_id":"5e0d0d3c8322ec5a4e8d2fdb9c9c5be3ab246a07"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"a04019e2e8bb266360d9d0f587f3ed5868404632","unresolved":false,"context_lines":[{"line_number":233,"context_line":""},{"line_number":234,"context_line":"    def write_all_stats(self, filename\u003dNone):"},{"line_number":235,"context_line":"        filename \u003d filename or self.filename"},{"line_number":236,"context_line":"        if not filename or not self._stats:"},{"line_number":237,"context_line":"            return"},{"line_number":238,"context_line":"        stats_data \u003d self._render_all_stats()"},{"line_number":239,"context_line":"        tf \u003d None"}],"source_content_type":"text/x-python","patch_set":26,"id":"58ba0323_b46295a9","line":236,"in_reply_to":"ec93795c_12431d01","updated":"2026-05-19 03:19:45.000000000","message":"just checking if there are any stats to write.\nmodified it to:\nif not self._stats:\n            raise RuntimeError(\u0027no stats to write\u0027)","commit_id":"5e0d0d3c8322ec5a4e8d2fdb9c9c5be3ab246a07"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"972b28563a02fa2ef2d7a8bf90e14b0818ffa7d4","unresolved":true,"context_lines":[{"line_number":250,"context_line":"                except FileNotFoundError:"},{"line_number":251,"context_line":"                    pass"},{"line_number":252,"context_line":""},{"line_number":253,"context_line":"    def record(self, name, value, labels\u003dNone):"},{"line_number":254,"context_line":"        all_labels \u003d dict(self.default_labels)"},{"line_number":255,"context_line":"        if labels is not None:"},{"line_number":256,"context_line":"            all_labels.update(labels)"}],"source_content_type":"text/x-python","patch_set":26,"id":"9f0b4bf4_8b82d32a","line":253,"updated":"2026-05-15 23:54:38.000000000","message":"Are we OK with this method overwriting the value of an existing key? I think we need more tests to assert expected behavior.","commit_id":"5e0d0d3c8322ec5a4e8d2fdb9c9c5be3ab246a07"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"a04019e2e8bb266360d9d0f587f3ed5868404632","unresolved":false,"context_lines":[{"line_number":250,"context_line":"                except FileNotFoundError:"},{"line_number":251,"context_line":"                    pass"},{"line_number":252,"context_line":""},{"line_number":253,"context_line":"    def record(self, name, value, labels\u003dNone):"},{"line_number":254,"context_line":"        all_labels \u003d dict(self.default_labels)"},{"line_number":255,"context_line":"        if labels is not None:"},{"line_number":256,"context_line":"            all_labels.update(labels)"}],"source_content_type":"text/x-python","patch_set":26,"id":"f160976a_73c402cb","line":253,"in_reply_to":"9f0b4bf4_8b82d32a","updated":"2026-05-19 03:19:45.000000000","message":"we are okay for now to overwrite the value of existing key","commit_id":"5e0d0d3c8322ec5a4e8d2fdb9c9c5be3ab246a07"}],"swift/common/utils/prom_metrics.py":[{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"8fb8665fbef2e8aa06ac54c1eaf90bc2dcbf1ca7","unresolved":true,"context_lines":[{"line_number":40,"context_line":"        \"\"\""},{"line_number":41,"context_line":"        return \u0027%s{hostname\u003d\"%s\", policy\u003d\"%s\"} %d\\n\u0027 % ("},{"line_number":42,"context_line":"            metricname,"},{"line_number":43,"context_line":"            socket.gethostname(),"},{"line_number":44,"context_line":"            policy,"},{"line_number":45,"context_line":"            value,"},{"line_number":46,"context_line":"        )"}],"source_content_type":"text/x-python","patch_set":1,"id":"a174e6d8_0b5a2185","line":43,"updated":"2025-12-17 15:54:43.000000000","message":"the instance should probably cache this, IIRC the way we handle hostname for labeled statsd metrics is using a \"user label\"","commit_id":"fd1d11deca9bbfce218bbbb0e06c6b2b68f9b938"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"d28f060d253fa45bd118c68808834739ea47a8a0","unresolved":false,"context_lines":[{"line_number":40,"context_line":"        \"\"\""},{"line_number":41,"context_line":"        return \u0027%s{hostname\u003d\"%s\", policy\u003d\"%s\"} %d\\n\u0027 % ("},{"line_number":42,"context_line":"            metricname,"},{"line_number":43,"context_line":"            socket.gethostname(),"},{"line_number":44,"context_line":"            policy,"},{"line_number":45,"context_line":"            value,"},{"line_number":46,"context_line":"        )"}],"source_content_type":"text/x-python","patch_set":1,"id":"e456d271_b708fa88","line":43,"in_reply_to":"a174e6d8_0b5a2185","updated":"2026-02-12 01:17:54.000000000","message":"Acknowledged","commit_id":"fd1d11deca9bbfce218bbbb0e06c6b2b68f9b938"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"8fb8665fbef2e8aa06ac54c1eaf90bc2dcbf1ca7","unresolved":true,"context_lines":[{"line_number":41,"context_line":"        return \u0027%s{hostname\u003d\"%s\", policy\u003d\"%s\"} %d\\n\u0027 % ("},{"line_number":42,"context_line":"            metricname,"},{"line_number":43,"context_line":"            socket.gethostname(),"},{"line_number":44,"context_line":"            policy,"},{"line_number":45,"context_line":"            value,"},{"line_number":46,"context_line":"        )"},{"line_number":47,"context_line":""}],"source_content_type":"text/x-python","patch_set":1,"id":"5faf3b5a_7b006772","line":44,"updated":"2025-12-17 15:54:43.000000000","message":"at this low level policy should be a generic label - same as method, or account or any other arbitrary label.","commit_id":"fd1d11deca9bbfce218bbbb0e06c6b2b68f9b938"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"b4dbb83a9c6164c1821f5c9a05aeb315f2b653ff","unresolved":false,"context_lines":[{"line_number":41,"context_line":"        return \u0027%s{hostname\u003d\"%s\", policy\u003d\"%s\"} %d\\n\u0027 % ("},{"line_number":42,"context_line":"            metricname,"},{"line_number":43,"context_line":"            socket.gethostname(),"},{"line_number":44,"context_line":"            policy,"},{"line_number":45,"context_line":"            value,"},{"line_number":46,"context_line":"        )"},{"line_number":47,"context_line":""}],"source_content_type":"text/x-python","patch_set":1,"id":"ba62561f_42f83383","line":44,"in_reply_to":"5faf3b5a_7b006772","updated":"2026-01-05 21:46:11.000000000","message":"Acknowledged","commit_id":"fd1d11deca9bbfce218bbbb0e06c6b2b68f9b938"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"8fb8665fbef2e8aa06ac54c1eaf90bc2dcbf1ca7","unresolved":true,"context_lines":[{"line_number":54,"context_line":"        logging.info(\"\u003d\" * 50)"},{"line_number":55,"context_line":"        metricname, policy, value \u003d stats.split(\u0027 \u0027)"},{"line_number":56,"context_line":"        self._gen_metrics(metricname, policy, value)"},{"line_number":57,"context_line":"        # write in file? show graphically?"}],"source_content_type":"text/x-python","patch_set":1,"id":"ebfd0294_50444f55","line":57,"updated":"2025-12-17 15:54:43.000000000","message":"yes, the main interface of the client should be accumulate stats/counters or track a guage set and then consumers will want to periodically \"output\" or \"dump_stats\" to an output file.","commit_id":"fd1d11deca9bbfce218bbbb0e06c6b2b68f9b938"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"b4dbb83a9c6164c1821f5c9a05aeb315f2b653ff","unresolved":false,"context_lines":[{"line_number":54,"context_line":"        logging.info(\"\u003d\" * 50)"},{"line_number":55,"context_line":"        metricname, policy, value \u003d stats.split(\u0027 \u0027)"},{"line_number":56,"context_line":"        self._gen_metrics(metricname, policy, value)"},{"line_number":57,"context_line":"        # write in file? show graphically?"}],"source_content_type":"text/x-python","patch_set":1,"id":"cfb3ebf8_829ec99f","line":57,"in_reply_to":"ebfd0294_50444f55","updated":"2026-01-05 21:46:11.000000000","message":"Done","commit_id":"fd1d11deca9bbfce218bbbb0e06c6b2b68f9b938"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1f89dae221ca224f992884d4d85d0f14cb377f9f","unresolved":true,"context_lines":[{"line_number":36,"context_line":"class AbstractPrometheousClient:"},{"line_number":37,"context_line":"    def __init__(self, env, metricname, value, conf\u003dNone, logger\u003dNone):"},{"line_number":38,"context_line":"        self.metricname \u003d metricname"},{"line_number":39,"context_line":"        self.value \u003d value"},{"line_number":40,"context_line":"        self.env \u003d env"},{"line_number":41,"context_line":"        self.conf \u003d conf"},{"line_number":42,"context_line":"        self.logger \u003d logger"}],"source_content_type":"text/x-python","patch_set":5,"id":"508b2aec_44852374","line":39,"updated":"2026-01-15 20:22:16.000000000","message":"metricname and value seem like weird state for the *client* object to hold.  You could maybe *try* to make a dataclass object to represent an individual metric+labels \"key\" but I might recommend trying to see how far you can get with a stdlib datastructure like `(metricname, frozenset)` as the key as I think any custom class will need to be basically a wrapper on that anyway.\n\nIf you look at a statsd client the metric name and value are provided as arguments to the update_stats method - while the client state holds information about e.g. \"where to send the udp packets\".\n\nIf you translate the design of the statsd-client faithfully to a prometheus-client - I think the client should be initialized with \"the path/file where it writes stats\" and have an update_stats method that takes metric name and value.\n\n```\ndef update_stats(metricname, value, lables\u003dNone):\n    labels \u003d labels or {}\n    key \u003d (metricname, frozenset(labels.items())\n    self._stats[key] \u003d value\n```","commit_id":"460c16e21099714aa2972623cd888a158824f13b"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"8ea433753461363516daad2f0a75b32496d00d18","unresolved":false,"context_lines":[{"line_number":36,"context_line":"class AbstractPrometheousClient:"},{"line_number":37,"context_line":"    def __init__(self, env, metricname, value, conf\u003dNone, logger\u003dNone):"},{"line_number":38,"context_line":"        self.metricname \u003d metricname"},{"line_number":39,"context_line":"        self.value \u003d value"},{"line_number":40,"context_line":"        self.env \u003d env"},{"line_number":41,"context_line":"        self.conf \u003d conf"},{"line_number":42,"context_line":"        self.logger \u003d logger"}],"source_content_type":"text/x-python","patch_set":5,"id":"79872286_eba1ec72","line":39,"in_reply_to":"508b2aec_44852374","updated":"2026-02-06 01:20:27.000000000","message":"Done","commit_id":"460c16e21099714aa2972623cd888a158824f13b"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1f89dae221ca224f992884d4d85d0f14cb377f9f","unresolved":true,"context_lines":[{"line_number":53,"context_line":"        \"\"\""},{"line_number":54,"context_line":"        return \u0027%s %d\\n\u0027 % (metricname, value)"},{"line_number":55,"context_line":""},{"line_number":56,"context_line":"    def display_stats(self, env, stats, args):"},{"line_number":57,"context_line":"        logging.info(\"\u003d\" * 50)"},{"line_number":58,"context_line":"        metricname, policy, value \u003d stats.split(\u0027 \u0027) #?"},{"line_number":59,"context_line":"        prom_metric \u003d self._gen_metrics(metricname, value)"}],"source_content_type":"text/x-python","patch_set":5,"id":"f9238714_ddf27246","line":56,"updated":"2026-01-15 20:22:16.000000000","message":"I don\u0027t understand this signature - maybe try to a write a doc string for your methods so it\u0027s more clear what we should think this method is supposed to do?","commit_id":"460c16e21099714aa2972623cd888a158824f13b"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"ec30e831ea7cb2dda7eadbfa78029491992f0527","unresolved":false,"context_lines":[{"line_number":53,"context_line":"        \"\"\""},{"line_number":54,"context_line":"        return \u0027%s %d\\n\u0027 % (metricname, value)"},{"line_number":55,"context_line":""},{"line_number":56,"context_line":"    def display_stats(self, env, stats, args):"},{"line_number":57,"context_line":"        logging.info(\"\u003d\" * 50)"},{"line_number":58,"context_line":"        metricname, policy, value \u003d stats.split(\u0027 \u0027) #?"},{"line_number":59,"context_line":"        prom_metric \u003d self._gen_metrics(metricname, value)"}],"source_content_type":"text/x-python","patch_set":5,"id":"da9380ab_af117af7","line":56,"in_reply_to":"f9238714_ddf27246","updated":"2026-01-29 16:56:40.000000000","message":"Acknowledged","commit_id":"460c16e21099714aa2972623cd888a158824f13b"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1f89dae221ca224f992884d4d85d0f14cb377f9f","unresolved":true,"context_lines":[{"line_number":57,"context_line":"        logging.info(\"\u003d\" * 50)"},{"line_number":58,"context_line":"        metricname, policy, value \u003d stats.split(\u0027 \u0027) #?"},{"line_number":59,"context_line":"        prom_metric \u003d self._gen_metrics(metricname, value)"},{"line_number":60,"context_line":"        with open(env[\u0027input\u0027], \"a\") as file:"},{"line_number":61,"context_line":"            env[\u0027input\u0027].file \u003d file"},{"line_number":62,"context_line":"            file.write(prom_metric)"},{"line_number":63,"context_line":"        return"}],"source_content_type":"text/x-python","patch_set":5,"id":"17ce4156_78b8806c","line":60,"updated":"2026-01-15 20:22:16.000000000","message":"the name of the file where the stats go is obviously one of the things the client is expected to provide as input - I think that would be more clear if you asked for it explicitly - the nesting in an \"env\" dict seems\n\n1) unnecessarily obfuscating (Flat is better than nested https://peps.python.org/pep-0020/)\n2) confusing conceptual naming overlap with swob.Request.environ\n\nI\u0027d be curious to learn more about how you came to the conceptualization for this idea...","commit_id":"460c16e21099714aa2972623cd888a158824f13b"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"ec30e831ea7cb2dda7eadbfa78029491992f0527","unresolved":false,"context_lines":[{"line_number":57,"context_line":"        logging.info(\"\u003d\" * 50)"},{"line_number":58,"context_line":"        metricname, policy, value \u003d stats.split(\u0027 \u0027) #?"},{"line_number":59,"context_line":"        prom_metric \u003d self._gen_metrics(metricname, value)"},{"line_number":60,"context_line":"        with open(env[\u0027input\u0027], \"a\") as file:"},{"line_number":61,"context_line":"            env[\u0027input\u0027].file \u003d file"},{"line_number":62,"context_line":"            file.write(prom_metric)"},{"line_number":63,"context_line":"        return"}],"source_content_type":"text/x-python","patch_set":5,"id":"971a9e6e_6372eb81","line":60,"in_reply_to":"17ce4156_78b8806c","updated":"2026-01-29 16:56:40.000000000","message":"Gotcha! I just thought of who shares variables in test and swift. env was the first one to pop up since we send env with needed info! :))\n\nUsing @with_tempdir now!","commit_id":"460c16e21099714aa2972623cd888a158824f13b"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1f89dae221ca224f992884d4d85d0f14cb377f9f","unresolved":true,"context_lines":[{"line_number":58,"context_line":"        metricname, policy, value \u003d stats.split(\u0027 \u0027) #?"},{"line_number":59,"context_line":"        prom_metric \u003d self._gen_metrics(metricname, value)"},{"line_number":60,"context_line":"        with open(env[\u0027input\u0027], \"a\") as file:"},{"line_number":61,"context_line":"            env[\u0027input\u0027].file \u003d file"},{"line_number":62,"context_line":"            file.write(prom_metric)"},{"line_number":63,"context_line":"        return"},{"line_number":64,"context_line":""}],"source_content_type":"text/x-python","patch_set":5,"id":"46229c3c_689d2bf5","line":61,"updated":"2026-01-15 20:22:16.000000000","message":"wait what, what do we think this is doing exactly... is `env[\u0027input\u0027]` supposed to be the a filename as string?  a file object?  a *Path* object!?","commit_id":"460c16e21099714aa2972623cd888a158824f13b"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"ec30e831ea7cb2dda7eadbfa78029491992f0527","unresolved":false,"context_lines":[{"line_number":58,"context_line":"        metricname, policy, value \u003d stats.split(\u0027 \u0027) #?"},{"line_number":59,"context_line":"        prom_metric \u003d self._gen_metrics(metricname, value)"},{"line_number":60,"context_line":"        with open(env[\u0027input\u0027], \"a\") as file:"},{"line_number":61,"context_line":"            env[\u0027input\u0027].file \u003d file"},{"line_number":62,"context_line":"            file.write(prom_metric)"},{"line_number":63,"context_line":"        return"},{"line_number":64,"context_line":""}],"source_content_type":"text/x-python","patch_set":5,"id":"3e46d379_3bcf6076","line":61,"in_reply_to":"46229c3c_689d2bf5","updated":"2026-01-29 16:56:40.000000000","message":"Done","commit_id":"460c16e21099714aa2972623cd888a158824f13b"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1f89dae221ca224f992884d4d85d0f14cb377f9f","unresolved":true,"context_lines":[{"line_number":59,"context_line":"        prom_metric \u003d self._gen_metrics(metricname, value)"},{"line_number":60,"context_line":"        with open(env[\u0027input\u0027], \"a\") as file:"},{"line_number":61,"context_line":"            env[\u0027input\u0027].file \u003d file"},{"line_number":62,"context_line":"            file.write(prom_metric)"},{"line_number":63,"context_line":"        return"},{"line_number":64,"context_line":""},{"line_number":65,"context_line":"    def _build_line_parts(metric, value, metric_type, sample_rate):"}],"source_content_type":"text/x-python","patch_set":5,"id":"285ef3a5_61c71c2f","line":62,"updated":"2026-01-15 20:22:16.000000000","message":"I think \"display\" is a weird name for a method that writes to a file - maybe better as \"write_metrics\"","commit_id":"460c16e21099714aa2972623cd888a158824f13b"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"ec30e831ea7cb2dda7eadbfa78029491992f0527","unresolved":false,"context_lines":[{"line_number":59,"context_line":"        prom_metric \u003d self._gen_metrics(metricname, value)"},{"line_number":60,"context_line":"        with open(env[\u0027input\u0027], \"a\") as file:"},{"line_number":61,"context_line":"            env[\u0027input\u0027].file \u003d file"},{"line_number":62,"context_line":"            file.write(prom_metric)"},{"line_number":63,"context_line":"        return"},{"line_number":64,"context_line":""},{"line_number":65,"context_line":"    def _build_line_parts(metric, value, metric_type, sample_rate):"}],"source_content_type":"text/x-python","patch_set":5,"id":"2be2a327_ace21939","line":62,"in_reply_to":"285ef3a5_61c71c2f","updated":"2026-01-29 16:56:40.000000000","message":"Acknowledged","commit_id":"460c16e21099714aa2972623cd888a158824f13b"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"94af78cac5977ac70e611aaaf52dd8cd3d8cec79","unresolved":true,"context_lines":[{"line_number":21,"context_line":"PROM_USER_LABEL_NAMESPACE \u003d \u0027user_\u0027"},{"line_number":22,"context_line":""},{"line_number":23,"context_line":""},{"line_number":24,"context_line":"def get_prometheous_client(env, metricname, labels, value, filename, conf\u003dNone, logger\u003dNone):"},{"line_number":25,"context_line":"    \"\"\""},{"line_number":26,"context_line":"        Get an instance of PrometheousClient using config settings."},{"line_number":27,"context_line":"    \"\"\""}],"source_content_type":"text/x-python","patch_set":7,"id":"4961e5a7_36902b61","line":24,"updated":"2026-01-30 17:34:38.000000000","message":"we have\n```\ndef get_statsd_client(conf\u003dNone, tail_prefix\u003d\u0027\u0027, logger\u003dNone)\n```\nand\n```\ndef get_swift_logger(conf, name\u003dNone, log_to_console\u003dFalse, log_route\u003dNone,\n                     fmt\u003d\"%(server)s: %(message)s\"):\n```\nwhere the first arg is the conf dict.\n\nCould be worth sticking with that pattern or \"first arg is conf\"","commit_id":"1395affd541c81ac1de954bf43fefab455ddd104"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"8ea433753461363516daad2f0a75b32496d00d18","unresolved":false,"context_lines":[{"line_number":21,"context_line":"PROM_USER_LABEL_NAMESPACE \u003d \u0027user_\u0027"},{"line_number":22,"context_line":""},{"line_number":23,"context_line":""},{"line_number":24,"context_line":"def get_prometheous_client(env, metricname, labels, value, filename, conf\u003dNone, logger\u003dNone):"},{"line_number":25,"context_line":"    \"\"\""},{"line_number":26,"context_line":"        Get an instance of PrometheousClient using config settings."},{"line_number":27,"context_line":"    \"\"\""}],"source_content_type":"text/x-python","patch_set":7,"id":"3f1f1bbd_be67cac6","line":24,"in_reply_to":"4961e5a7_36902b61","updated":"2026-02-06 01:20:27.000000000","message":"Acknowledged","commit_id":"1395affd541c81ac1de954bf43fefab455ddd104"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"94af78cac5977ac70e611aaaf52dd8cd3d8cec79","unresolved":true,"context_lines":[{"line_number":26,"context_line":"        Get an instance of PrometheousClient using config settings."},{"line_number":27,"context_line":"    \"\"\""},{"line_number":28,"context_line":"    conf \u003d conf or {}"},{"line_number":29,"context_line":"    metricname \u003d metricname"},{"line_number":30,"context_line":"    labels \u003d dict(sorted(labels.items()))"},{"line_number":31,"context_line":"    labels \u003d frozenset(labels.items())"},{"line_number":32,"context_line":"    key \u003d (metricname, labels)"}],"source_content_type":"text/x-python","patch_set":7,"id":"7548df38_c2d6855d","line":29,"updated":"2026-01-30 17:34:38.000000000","message":"there\u0027s no need to reassign these variables (metricname, value, logger)","commit_id":"1395affd541c81ac1de954bf43fefab455ddd104"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"8ea433753461363516daad2f0a75b32496d00d18","unresolved":false,"context_lines":[{"line_number":26,"context_line":"        Get an instance of PrometheousClient using config settings."},{"line_number":27,"context_line":"    \"\"\""},{"line_number":28,"context_line":"    conf \u003d conf or {}"},{"line_number":29,"context_line":"    metricname \u003d metricname"},{"line_number":30,"context_line":"    labels \u003d dict(sorted(labels.items()))"},{"line_number":31,"context_line":"    labels \u003d frozenset(labels.items())"},{"line_number":32,"context_line":"    key \u003d (metricname, labels)"}],"source_content_type":"text/x-python","patch_set":7,"id":"a6defbd1_adc10455","line":29,"in_reply_to":"7548df38_c2d6855d","updated":"2026-02-06 01:20:27.000000000","message":"Acknowledged","commit_id":"1395affd541c81ac1de954bf43fefab455ddd104"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"94af78cac5977ac70e611aaaf52dd8cd3d8cec79","unresolved":true,"context_lines":[{"line_number":38,"context_line":"    return PrometheousClient(env, key, value, filename, conf, logger)"},{"line_number":39,"context_line":""},{"line_number":40,"context_line":""},{"line_number":41,"context_line":"class AbstractPrometheousClient:"},{"line_number":42,"context_line":"    def __init__(self, env, key, value, fileneme, conf\u003dNone, logger\u003dNone):"},{"line_number":43,"context_line":"        self.key \u003d key"},{"line_number":44,"context_line":"        self.value \u003d value"}],"source_content_type":"text/x-python","patch_set":7,"id":"08a471b9_b88062ca","line":41,"updated":"2026-01-30 17:34:38.000000000","message":"I\u0027m wondering why there is an abstract superclass - do you have thoughts for what other subclasses there will be?","commit_id":"1395affd541c81ac1de954bf43fefab455ddd104"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"8ea433753461363516daad2f0a75b32496d00d18","unresolved":true,"context_lines":[{"line_number":38,"context_line":"    return PrometheousClient(env, key, value, filename, conf, logger)"},{"line_number":39,"context_line":""},{"line_number":40,"context_line":""},{"line_number":41,"context_line":"class AbstractPrometheousClient:"},{"line_number":42,"context_line":"    def __init__(self, env, key, value, fileneme, conf\u003dNone, logger\u003dNone):"},{"line_number":43,"context_line":"        self.key \u003d key"},{"line_number":44,"context_line":"        self.value \u003d value"}],"source_content_type":"text/x-python","patch_set":7,"id":"4acb8a76_ae7da871","line":41,"in_reply_to":"08a471b9_b88062ca","updated":"2026-02-06 01:20:27.000000000","message":"Just this one, I noticed this pattern in statsd_client! No particular reason to add an abstract class in this","commit_id":"1395affd541c81ac1de954bf43fefab455ddd104"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"d28f060d253fa45bd118c68808834739ea47a8a0","unresolved":false,"context_lines":[{"line_number":38,"context_line":"    return PrometheousClient(env, key, value, filename, conf, logger)"},{"line_number":39,"context_line":""},{"line_number":40,"context_line":""},{"line_number":41,"context_line":"class AbstractPrometheousClient:"},{"line_number":42,"context_line":"    def __init__(self, env, key, value, fileneme, conf\u003dNone, logger\u003dNone):"},{"line_number":43,"context_line":"        self.key \u003d key"},{"line_number":44,"context_line":"        self.value \u003d value"}],"source_content_type":"text/x-python","patch_set":7,"id":"9cfbdd77_2b1538cc","line":41,"in_reply_to":"4acb8a76_ae7da871","updated":"2026-02-12 01:17:54.000000000","message":"Done","commit_id":"1395affd541c81ac1de954bf43fefab455ddd104"},{"author":{"_account_id":7847,"name":"Alistair Coles","email":"alistairncoles@gmail.com","username":"acoles"},"change_message_id":"94af78cac5977ac70e611aaaf52dd8cd3d8cec79","unresolved":true,"context_lines":[{"line_number":80,"context_line":"            metric \u003d self._prefix + metric"},{"line_number":81,"context_line":"            line \u003d self._build_line_parts("},{"line_number":82,"context_line":"                metric, value, metric_type, adjusted_sample_rate)"},{"line_number":83,"context_line":"            return self._send_line(line)"},{"line_number":84,"context_line":""},{"line_number":85,"context_line":"    def _update_stats(self, metric, value, **kwargs):"},{"line_number":86,"context_line":"        return self._send(metric, value, \u0027c\u0027, **kwargs)"}],"source_content_type":"text/x-python","patch_set":7,"id":"ca07b125_e3fcc324","line":83,"range":{"start_line":83,"start_character":19,"end_line":83,"end_character":40},"updated":"2026-01-30 17:34:38.000000000","message":"I can\u0027t see a _send_line method...but this all looks very similar to the statsd client ... is this still WIP and needing to be further adapted for the PrometheusClient","commit_id":"1395affd541c81ac1de954bf43fefab455ddd104"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"8ea433753461363516daad2f0a75b32496d00d18","unresolved":false,"context_lines":[{"line_number":80,"context_line":"            metric \u003d self._prefix + metric"},{"line_number":81,"context_line":"            line \u003d self._build_line_parts("},{"line_number":82,"context_line":"                metric, value, metric_type, adjusted_sample_rate)"},{"line_number":83,"context_line":"            return self._send_line(line)"},{"line_number":84,"context_line":""},{"line_number":85,"context_line":"    def _update_stats(self, metric, value, **kwargs):"},{"line_number":86,"context_line":"        return self._send(metric, value, \u0027c\u0027, **kwargs)"}],"source_content_type":"text/x-python","patch_set":7,"id":"52be0013_62750d1a","line":83,"range":{"start_line":83,"start_character":19,"end_line":83,"end_character":40},"in_reply_to":"ca07b125_e3fcc324","updated":"2026-02-06 01:20:27.000000000","message":"It was WIP, not needed anymore","commit_id":"1395affd541c81ac1de954bf43fefab455ddd104"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"795c971ab91775835a923645c604f184ea3f9df3","unresolved":true,"context_lines":[{"line_number":33,"context_line":"        Get an instance of PrometheousClient using config settings."},{"line_number":34,"context_line":"    \"\"\""},{"line_number":35,"context_line":"    conf \u003d conf or {}"},{"line_number":36,"context_line":"    name, labels, value \u003d parse_metrics(metric)"},{"line_number":37,"context_line":"    _stats \u003d {}"},{"line_number":38,"context_line":"    with open(filename, \"a\"):"},{"line_number":39,"context_line":"        pass"}],"source_content_type":"text/x-python","patch_set":11,"id":"bb697203_af166bbc","line":36,"updated":"2026-02-13 20:04:07.000000000","message":"let\u0027s get rid of the \"metric\" argument entirely - when I create a new prometheus client I should just have a blank slate.","commit_id":"1bb5b822b37bb309193ae5549f05ee92fe6ac625"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"fe3175dffa8e10748f1a5bea4adf6536c696df08","unresolved":false,"context_lines":[{"line_number":33,"context_line":"        Get an instance of PrometheousClient using config settings."},{"line_number":34,"context_line":"    \"\"\""},{"line_number":35,"context_line":"    conf \u003d conf or {}"},{"line_number":36,"context_line":"    name, labels, value \u003d parse_metrics(metric)"},{"line_number":37,"context_line":"    _stats \u003d {}"},{"line_number":38,"context_line":"    with open(filename, \"a\"):"},{"line_number":39,"context_line":"        pass"}],"source_content_type":"text/x-python","patch_set":11,"id":"9f1af57e_9fe06785","line":36,"in_reply_to":"bb697203_af166bbc","updated":"2026-03-19 18:47:20.000000000","message":"Done","commit_id":"1bb5b822b37bb309193ae5549f05ee92fe6ac625"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"795c971ab91775835a923645c604f184ea3f9df3","unresolved":true,"context_lines":[{"line_number":72,"context_line":"        return"},{"line_number":73,"context_line":""},{"line_number":74,"context_line":"    def update_stats(self, metric, update_val):"},{"line_number":75,"context_line":"        name, labels, value \u003d parse_metrics(metric)"},{"line_number":76,"context_line":"        self._stats[str(name) + str(labels)] \u003d update_val"},{"line_number":77,"context_line":"        self.write_stats()"},{"line_number":78,"context_line":""}],"source_content_type":"text/x-python","patch_set":11,"id":"6ba02893_79d44fa1","line":75,"updated":"2026-02-13 20:04:07.000000000","message":"I think instead of parse_metrics here I think the input should look like statsd:\n\nhttps://github.com/NVIDIA/swift/blob/master/swift/common/statsd_client.py#L569C46-L569C52\n\nthe \"metric\" argument is required - it\u0027s the name of the metrics - and any \"labels\" would come in as a dict.","commit_id":"1bb5b822b37bb309193ae5549f05ee92fe6ac625"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"fe3175dffa8e10748f1a5bea4adf6536c696df08","unresolved":false,"context_lines":[{"line_number":72,"context_line":"        return"},{"line_number":73,"context_line":""},{"line_number":74,"context_line":"    def update_stats(self, metric, update_val):"},{"line_number":75,"context_line":"        name, labels, value \u003d parse_metrics(metric)"},{"line_number":76,"context_line":"        self._stats[str(name) + str(labels)] \u003d update_val"},{"line_number":77,"context_line":"        self.write_stats()"},{"line_number":78,"context_line":""}],"source_content_type":"text/x-python","patch_set":11,"id":"6b96a2f8_4fd8ff23","line":75,"in_reply_to":"6ba02893_79d44fa1","updated":"2026-03-19 18:47:20.000000000","message":"Acknowledged","commit_id":"1bb5b822b37bb309193ae5549f05ee92fe6ac625"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"795c971ab91775835a923645c604f184ea3f9df3","unresolved":true,"context_lines":[{"line_number":74,"context_line":"    def update_stats(self, metric, update_val):"},{"line_number":75,"context_line":"        name, labels, value \u003d parse_metrics(metric)"},{"line_number":76,"context_line":"        self._stats[str(name) + str(labels)] \u003d update_val"},{"line_number":77,"context_line":"        self.write_stats()"},{"line_number":78,"context_line":""},{"line_number":79,"context_line":"    def _update_stats(self, key, count):"},{"line_number":80,"context_line":"        return self.update_stats(key, self._stats[self.key] + count)"}],"source_content_type":"text/x-python","patch_set":11,"id":"4ca2ccd4_afb0e3df","line":77,"updated":"2026-02-13 20:04:07.000000000","message":"There is probably a \"batch\" optimization here where you might want to add all the metrics with multiple calls to udpate_stats/increment etc and then call \"write_stats\" once at the end (or periodically from another thread with syncronization or something)","commit_id":"1bb5b822b37bb309193ae5549f05ee92fe6ac625"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"969839b5ec3d5ae322ddb7888758e5a8e33dd831","unresolved":false,"context_lines":[{"line_number":74,"context_line":"    def update_stats(self, metric, update_val):"},{"line_number":75,"context_line":"        name, labels, value \u003d parse_metrics(metric)"},{"line_number":76,"context_line":"        self._stats[str(name) + str(labels)] \u003d update_val"},{"line_number":77,"context_line":"        self.write_stats()"},{"line_number":78,"context_line":""},{"line_number":79,"context_line":"    def _update_stats(self, key, count):"},{"line_number":80,"context_line":"        return self.update_stats(key, self._stats[self.key] + count)"}],"source_content_type":"text/x-python","patch_set":11,"id":"5e667e1d_7146834f","line":77,"in_reply_to":"4ca2ccd4_afb0e3df","updated":"2026-03-24 18:40:08.000000000","message":"Acknowledged","commit_id":"1bb5b822b37bb309193ae5549f05ee92fe6ac625"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"795c971ab91775835a923645c604f184ea3f9df3","unresolved":true,"context_lines":[{"line_number":77,"context_line":"        self.write_stats()"},{"line_number":78,"context_line":""},{"line_number":79,"context_line":"    def _update_stats(self, key, count):"},{"line_number":80,"context_line":"        return self.update_stats(key, self._stats[self.key] + count)"},{"line_number":81,"context_line":""},{"line_number":82,"context_line":"    def increment(self, key):"},{"line_number":83,"context_line":"        return self._update_stats(key, 1)"}],"source_content_type":"text/x-python","patch_set":11,"id":"119857f6_ce67fb98","line":80,"updated":"2026-02-13 20:04:07.000000000","message":"this is not the normal order I expect with method naming....\n\nI would have expected that \"update_stats\" uses \"_update_stats\" not the other way.\n\nin statsd client we always send `self._send(metric, value, \u0027c\u0027, **kwargs)` so it\u0027s a `c` unit - i.e. a *relative* change (a counter); however statsd *does* support \u0027g\u0027 (a guage)\n\nhttps://github.com/statsd/statsd/blob/master/docs/metric_types.md#gauges\n\nwe should look at an otel client to align their method naming - having two \"update_stats\" where one is a gauge/set and the other is an relative/increment/offset/counter is going to be confusing.\n\nOtel has separate primitives for counters/gauges.\n\nTo change a counter you use the \"add\" method:\n\nhttps://opentelemetry.io/docs/specs/otel/metrics/api/#counter-creation\n\nTo change a gauge you use the \"record\" method:\n\nhttps://opentelemetry.io/docs/specs/otel/metrics/api/#gauge-creation\n\nso maybe \"_add_counter\" and \"record_[stat|gauge]\" ????","commit_id":"1bb5b822b37bb309193ae5549f05ee92fe6ac625"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"969839b5ec3d5ae322ddb7888758e5a8e33dd831","unresolved":false,"context_lines":[{"line_number":77,"context_line":"        self.write_stats()"},{"line_number":78,"context_line":""},{"line_number":79,"context_line":"    def _update_stats(self, key, count):"},{"line_number":80,"context_line":"        return self.update_stats(key, self._stats[self.key] + count)"},{"line_number":81,"context_line":""},{"line_number":82,"context_line":"    def increment(self, key):"},{"line_number":83,"context_line":"        return self._update_stats(key, 1)"}],"source_content_type":"text/x-python","patch_set":11,"id":"e1debb1f_2f2e837d","line":80,"in_reply_to":"119857f6_ce67fb98","updated":"2026-03-24 18:40:08.000000000","message":"Done","commit_id":"1bb5b822b37bb309193ae5549f05ee92fe6ac625"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"bb10abec2f1db98ebceb473dfcf63d3600208e8f","unresolved":true,"context_lines":[{"line_number":62,"context_line":"    labels \u003d labels or {}"},{"line_number":63,"context_line":""},{"line_number":64,"context_line":"    if not isinstance(name, str):"},{"line_number":65,"context_line":"        raise ValueError(f\"Metric name must be string, got {type(name)}\")"},{"line_number":66,"context_line":""},{"line_number":67,"context_line":"    if labels and isinstance(labels, dict):"},{"line_number":68,"context_line":"        # Sort labels for consistent output"}],"source_content_type":"text/x-python","patch_set":14,"id":"469007c2_9f7ffb35","line":65,"updated":"2026-03-10 23:04:54.000000000","message":"this seems like a reasonable thing to do if the type of the argument isn\u0027t what we expect","commit_id":"1999c38dab00890ce9f2226c398d0ceb5b0774ec"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"fe3175dffa8e10748f1a5bea4adf6536c696df08","unresolved":false,"context_lines":[{"line_number":62,"context_line":"    labels \u003d labels or {}"},{"line_number":63,"context_line":""},{"line_number":64,"context_line":"    if not isinstance(name, str):"},{"line_number":65,"context_line":"        raise ValueError(f\"Metric name must be string, got {type(name)}\")"},{"line_number":66,"context_line":""},{"line_number":67,"context_line":"    if labels and isinstance(labels, dict):"},{"line_number":68,"context_line":"        # Sort labels for consistent output"}],"source_content_type":"text/x-python","patch_set":14,"id":"d6f386cc_295301c4","line":65,"in_reply_to":"469007c2_9f7ffb35","updated":"2026-03-19 18:47:20.000000000","message":"Done","commit_id":"1999c38dab00890ce9f2226c398d0ceb5b0774ec"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"bb10abec2f1db98ebceb473dfcf63d3600208e8f","unresolved":true,"context_lines":[{"line_number":69,"context_line":"        labels_str \u003d \u0027,\u0027.join("},{"line_number":70,"context_line":"            f\u0027{k}\u003d\"{v}\"\u0027 for k, v in sorted(labels.items())"},{"line_number":71,"context_line":"        )"},{"line_number":72,"context_line":"        return f\u0027{name}{{{labels_str}}} {value}\\n\u0027"},{"line_number":73,"context_line":"    else:"},{"line_number":74,"context_line":"        return f\u0027{name} {value}\\n\u0027"},{"line_number":75,"context_line":""}],"source_content_type":"text/x-python","patch_set":14,"id":"5f396d84_2ad21882","line":72,"updated":"2026-03-10 23:04:54.000000000","message":"the `{{{label_str}}}` sort of stinks IMHO - maybe use another format method to avoid conflating the literal `{}` chars?\n\n```\n\u003e\u003e\u003e \u0027%(name)s{%(label_str)s} %(value)s\u0027 % {\u0027name\u0027:\u0027foo\u0027, \u0027label_str\u0027: \u0027k1\u003dv1,k2\u003dv2\u0027, \u0027value\u0027: 3}\n\u0027foo{k1\u003dv1,k2\u003dv2} 3\u0027\n```\n\nor maybe even:\n\n```\n\u003e\u003e\u003e name + \u0027{\u0027 + label_str + \u0027} \u0027 + str(value)\n\u0027foo{k1\u003dv1,k2\u003dv2} 3\u0027\n```","commit_id":"1999c38dab00890ce9f2226c398d0ceb5b0774ec"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"969839b5ec3d5ae322ddb7888758e5a8e33dd831","unresolved":false,"context_lines":[{"line_number":69,"context_line":"        labels_str \u003d \u0027,\u0027.join("},{"line_number":70,"context_line":"            f\u0027{k}\u003d\"{v}\"\u0027 for k, v in sorted(labels.items())"},{"line_number":71,"context_line":"        )"},{"line_number":72,"context_line":"        return f\u0027{name}{{{labels_str}}} {value}\\n\u0027"},{"line_number":73,"context_line":"    else:"},{"line_number":74,"context_line":"        return f\u0027{name} {value}\\n\u0027"},{"line_number":75,"context_line":""}],"source_content_type":"text/x-python","patch_set":14,"id":"4e595a48_d5cda28c","line":72,"in_reply_to":"5f396d84_2ad21882","updated":"2026-03-24 18:40:08.000000000","message":"Done","commit_id":"1999c38dab00890ce9f2226c398d0ceb5b0774ec"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"bb10abec2f1db98ebceb473dfcf63d3600208e8f","unresolved":true,"context_lines":[{"line_number":71,"context_line":"        )"},{"line_number":72,"context_line":"        return f\u0027{name}{{{labels_str}}} {value}\\n\u0027"},{"line_number":73,"context_line":"    else:"},{"line_number":74,"context_line":"        return f\u0027{name} {value}\\n\u0027"},{"line_number":75,"context_line":""},{"line_number":76,"context_line":""},{"line_number":77,"context_line":"def format_metrics_batch(metrics_list):"}],"source_content_type":"text/x-python","patch_set":14,"id":"f730a2e0_21df483b","line":74,"updated":"2026-03-10 23:04:54.000000000","message":"if labels is a non-empty non-dict I\u0027m not sure WHAT we should do - but I don\u0027t think \"silently ignore\" it is the best answer.","commit_id":"1999c38dab00890ce9f2226c398d0ceb5b0774ec"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"5caa9f5a203bdca5ca37c018ea381307cbd3ba3c","unresolved":false,"context_lines":[{"line_number":71,"context_line":"        )"},{"line_number":72,"context_line":"        return f\u0027{name}{{{labels_str}}} {value}\\n\u0027"},{"line_number":73,"context_line":"    else:"},{"line_number":74,"context_line":"        return f\u0027{name} {value}\\n\u0027"},{"line_number":75,"context_line":""},{"line_number":76,"context_line":""},{"line_number":77,"context_line":"def format_metrics_batch(metrics_list):"}],"source_content_type":"text/x-python","patch_set":14,"id":"a0a171f0_2d6d6c1e","line":74,"in_reply_to":"6bf1fb02_f68d1dfb","updated":"2026-04-14 16:53:37.000000000","message":"Done","commit_id":"1999c38dab00890ce9f2226c398d0ceb5b0774ec"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"fe3175dffa8e10748f1a5bea4adf6536c696df08","unresolved":true,"context_lines":[{"line_number":71,"context_line":"        )"},{"line_number":72,"context_line":"        return f\u0027{name}{{{labels_str}}} {value}\\n\u0027"},{"line_number":73,"context_line":"    else:"},{"line_number":74,"context_line":"        return f\u0027{name} {value}\\n\u0027"},{"line_number":75,"context_line":""},{"line_number":76,"context_line":""},{"line_number":77,"context_line":"def format_metrics_batch(metrics_list):"}],"source_content_type":"text/x-python","patch_set":14,"id":"6bf1fb02_f68d1dfb","line":74,"in_reply_to":"f730a2e0_21df483b","updated":"2026-03-19 18:47:20.000000000","message":"As an edge case, i assumed labels can be empty! Would they never be empty?","commit_id":"1999c38dab00890ce9f2226c398d0ceb5b0774ec"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"bb10abec2f1db98ebceb473dfcf63d3600208e8f","unresolved":true,"context_lines":[{"line_number":96,"context_line":"    return \u0027\u0027.join(lines)"},{"line_number":97,"context_line":""},{"line_number":98,"context_line":""},{"line_number":99,"context_line":"def get_prometheus_client(conf, env, filename, logger\u003dNone):"},{"line_number":100,"context_line":"    \"\"\""},{"line_number":101,"context_line":"        Get an instance of PrometheousClient using config settings."},{"line_number":102,"context_line":"    \"\"\""}],"source_content_type":"text/x-python","patch_set":14,"id":"91d056a3_03bcbe50","line":99,"updated":"2026-03-10 23:04:54.000000000","message":"why is env passed in here?\n\nuse the same signature as `get_labeled_statsd_client`\n\n```\ndef get_labeled_statsd_client(conf\u003dNone, logger\u003dNone):\n```\n\nhttps://github.com/NVIDIA/swift/blob/master/swift/common/statsd_client.py#L141","commit_id":"1999c38dab00890ce9f2226c398d0ceb5b0774ec"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"969839b5ec3d5ae322ddb7888758e5a8e33dd831","unresolved":false,"context_lines":[{"line_number":96,"context_line":"    return \u0027\u0027.join(lines)"},{"line_number":97,"context_line":""},{"line_number":98,"context_line":""},{"line_number":99,"context_line":"def get_prometheus_client(conf, env, filename, logger\u003dNone):"},{"line_number":100,"context_line":"    \"\"\""},{"line_number":101,"context_line":"        Get an instance of PrometheousClient using config settings."},{"line_number":102,"context_line":"    \"\"\""}],"source_content_type":"text/x-python","patch_set":14,"id":"bab62a03_badd3eb2","line":99,"in_reply_to":"91d056a3_03bcbe50","updated":"2026-03-24 18:40:08.000000000","message":"Acknowledged","commit_id":"1999c38dab00890ce9f2226c398d0ceb5b0774ec"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"bb10abec2f1db98ebceb473dfcf63d3600208e8f","unresolved":true,"context_lines":[{"line_number":110,"context_line":"class AbstractPrometheusClient:"},{"line_number":111,"context_line":"    def __init__(self, conf, env, _stats, filename, logger\u003dNone):"},{"line_number":112,"context_line":"        self.conf \u003d conf"},{"line_number":113,"context_line":"        self.env \u003d env"},{"line_number":114,"context_line":"        self._stats \u003d {}"},{"line_number":115,"context_line":"        self.filename \u003d filename"},{"line_number":116,"context_line":"        self.logger \u003d logger"}],"source_content_type":"text/x-python","patch_set":14,"id":"c5fe1565_b3f7f735","line":113,"updated":"2026-03-10 23:04:54.000000000","message":"this is never used?  Is this supposed to be a stab at a base labels sort of configuration?","commit_id":"1999c38dab00890ce9f2226c398d0ceb5b0774ec"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"fe3175dffa8e10748f1a5bea4adf6536c696df08","unresolved":false,"context_lines":[{"line_number":110,"context_line":"class AbstractPrometheusClient:"},{"line_number":111,"context_line":"    def __init__(self, conf, env, _stats, filename, logger\u003dNone):"},{"line_number":112,"context_line":"        self.conf \u003d conf"},{"line_number":113,"context_line":"        self.env \u003d env"},{"line_number":114,"context_line":"        self._stats \u003d {}"},{"line_number":115,"context_line":"        self.filename \u003d filename"},{"line_number":116,"context_line":"        self.logger \u003d logger"}],"source_content_type":"text/x-python","patch_set":14,"id":"71f788f4_e47c75a5","line":113,"in_reply_to":"c5fe1565_b3f7f735","updated":"2026-03-19 18:47:20.000000000","message":"Incase this patch develops big, and there are multiple methods/ classes. Although, I agree that it\u0027s not been used as of now. If it\u0027s still unneeded in the following patch, I\u0027ll remove this","commit_id":"1999c38dab00890ce9f2226c398d0ceb5b0774ec"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"bb10abec2f1db98ebceb473dfcf63d3600208e8f","unresolved":true,"context_lines":[{"line_number":222,"context_line":""},{"line_number":223,"context_line":"    def increment(self, metric):"},{"line_number":224,"context_line":"        \"\"\"Increment a metric value by 1.\"\"\""},{"line_number":225,"context_line":"        name, labels, value \u003d parse_metrics(metric)"},{"line_number":226,"context_line":"        metric_key_str \u003d metric_key(name, **labels)"},{"line_number":227,"context_line":"        current_value \u003d self._stats.get(metric_key_str, 0)"},{"line_number":228,"context_line":"        return self.update_stats(metric, current_value + 1)"}],"source_content_type":"text/x-python","patch_set":14,"id":"d81bc450_13057a98","line":225,"updated":"2026-03-10 23:04:54.000000000","message":"this is not right, the method signature should look like the statsd client - the caller wants to provide a metric name w/ labels as an optional kwarg dict.\n\n```\ndef increment(self, metric, *, labels\u003dNone):\n```\n\nhttps://github.com/NVIDIA/swift/blob/master/swift/common/statsd_client.py#L581C5-L582C1","commit_id":"1999c38dab00890ce9f2226c398d0ceb5b0774ec"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"969839b5ec3d5ae322ddb7888758e5a8e33dd831","unresolved":false,"context_lines":[{"line_number":222,"context_line":""},{"line_number":223,"context_line":"    def increment(self, metric):"},{"line_number":224,"context_line":"        \"\"\"Increment a metric value by 1.\"\"\""},{"line_number":225,"context_line":"        name, labels, value \u003d parse_metrics(metric)"},{"line_number":226,"context_line":"        metric_key_str \u003d metric_key(name, **labels)"},{"line_number":227,"context_line":"        current_value \u003d self._stats.get(metric_key_str, 0)"},{"line_number":228,"context_line":"        return self.update_stats(metric, current_value + 1)"}],"source_content_type":"text/x-python","patch_set":14,"id":"ebd95008_d17647ed","line":225,"in_reply_to":"d81bc450_13057a98","updated":"2026-03-24 18:40:08.000000000","message":"Acknowledged","commit_id":"1999c38dab00890ce9f2226c398d0ceb5b0774ec"}],"test/unit/common/test_prom_metrics.py":[{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"b4dbb83a9c6164c1821f5c9a05aeb315f2b653ff","unresolved":false,"context_lines":[{"line_number":29,"context_line":"        # expirer_balance_score{host \u003d \"saio\", days \u003d \"-1\", date \u003d \"2025-11-05\"} 48.70"},{"line_number":30,"context_line":"        # expirer_balance_rmse{host \u003d \"saio\", days \u003d \"-1\", date \u003d \"2025-11-05\"} 1.54"},{"line_number":31,"context_line":"        # expirer_balance_total_tasks{host \u003d \"saio\", days \u003d \"-1\", date \u003d \"2025-11-05\"} 300"},{"line_number":32,"context_line":"        # expirer_balance_expected_per_container{host \u003d \"saio\", days \u003d \"-1\", date \u003d \"2025-11-05\"} 3.00"}],"source_content_type":"text/x-python","patch_set":1,"id":"1f886773_1b06477d","line":32,"in_reply_to":"41156bef_6c415986","updated":"2026-01-05 21:46:11.000000000","message":"Done","commit_id":"fd1d11deca9bbfce218bbbb0e06c6b2b68f9b938"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"8fb8665fbef2e8aa06ac54c1eaf90bc2dcbf1ca7","unresolved":true,"context_lines":[{"line_number":29,"context_line":"        # expirer_balance_score{host \u003d \"saio\", days \u003d \"-1\", date \u003d \"2025-11-05\"} 48.70"},{"line_number":30,"context_line":"        # expirer_balance_rmse{host \u003d \"saio\", days \u003d \"-1\", date \u003d \"2025-11-05\"} 1.54"},{"line_number":31,"context_line":"        # expirer_balance_total_tasks{host \u003d \"saio\", days \u003d \"-1\", date \u003d \"2025-11-05\"} 300"},{"line_number":32,"context_line":"        # expirer_balance_expected_per_container{host \u003d \"saio\", days \u003d \"-1\", date \u003d \"2025-11-05\"} 3.00"}],"source_content_type":"text/x-python","patch_set":1,"id":"41156bef_6c415986","line":32,"in_reply_to":"9ca03444_f32b72de","updated":"2025-12-17 15:54:43.000000000","message":"yes, this is perfect - a test to demonstrate how you can use the PrometheousClient to generate the kinds of metrics we need would be a great starting point.","commit_id":"fd1d11deca9bbfce218bbbb0e06c6b2b68f9b938"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1f89dae221ca224f992884d4d85d0f14cb377f9f","unresolved":true,"context_lines":[{"line_number":49,"context_line":"        }"},{"line_number":50,"context_line":"        blah \u003d get_prometheous_client(env, \u0027expirer_balance_score\u0027, 50, conf\u003dNone, logger\u003dNone)"},{"line_number":51,"context_line":"        self.assertEqual(type(blah), PrometheousClient)"},{"line_number":52,"context_line":"        self.assertEqual(blah.display_stats, read_stats(env))"}],"source_content_type":"text/x-python","patch_set":5,"id":"30b39795_b1c77a5b","line":52,"updated":"2026-01-15 20:22:16.000000000","message":"I have no idea what this is even attempting to achieve\n\n```\nAssertionError: \u003cbound method PrometheousClient.display_s[80 chars]3a0\u003e\u003e !\u003d None\n```","commit_id":"460c16e21099714aa2972623cd888a158824f13b"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"ec30e831ea7cb2dda7eadbfa78029491992f0527","unresolved":false,"context_lines":[{"line_number":49,"context_line":"        }"},{"line_number":50,"context_line":"        blah \u003d get_prometheous_client(env, \u0027expirer_balance_score\u0027, 50, conf\u003dNone, logger\u003dNone)"},{"line_number":51,"context_line":"        self.assertEqual(type(blah), PrometheousClient)"},{"line_number":52,"context_line":"        self.assertEqual(blah.display_stats, read_stats(env))"}],"source_content_type":"text/x-python","patch_set":5,"id":"4f57cfce_4b1f5dd3","line":52,"in_reply_to":"30b39795_b1c77a5b","updated":"2026-01-29 16:56:40.000000000","message":"Improvised!","commit_id":"460c16e21099714aa2972623cd888a158824f13b"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"795c971ab91775835a923645c604f184ea3f9df3","unresolved":true,"context_lines":[{"line_number":80,"context_line":"                        })): 3.0}"},{"line_number":81,"context_line":"        self.assertEqual(read_metrics(filename), exp_stats)"},{"line_number":82,"context_line":""},{"line_number":83,"context_line":"        blah.update_stats(metric, 50)"},{"line_number":84,"context_line":"        exp_stats \u003d {"},{"line_number":85,"context_line":"            (\u0027object_updater\u0027,"},{"line_number":86,"context_line":"             frozenset({(\u0027metrics_name\u0027, \"\u0027sweep\u0027\"),"}],"source_content_type":"text/x-python","patch_set":11,"id":"42c9fdb7_caf91037","line":83,"updated":"2026-02-13 20:04:07.000000000","message":"this should use `metric\u003d\u0027object_updater\u0027` (really `swift_object_updater_sweep_time`) and `labels\u003d{HOST\u003d\u0027s8k...\u0027, ...}`\n\n... similar to statsd client\u0027s interface:\n\nhttps://github.com/NVIDIA/swift/blob/master/swift/common/statsd_client.py#L569\n\nN.B. sample_rate doesn\u0027t matter for an in-memory prom client that flushes to disk when the consumer calls `blah.write_stats()`","commit_id":"1bb5b822b37bb309193ae5549f05ee92fe6ac625"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"969839b5ec3d5ae322ddb7888758e5a8e33dd831","unresolved":false,"context_lines":[{"line_number":80,"context_line":"                        })): 3.0}"},{"line_number":81,"context_line":"        self.assertEqual(read_metrics(filename), exp_stats)"},{"line_number":82,"context_line":""},{"line_number":83,"context_line":"        blah.update_stats(metric, 50)"},{"line_number":84,"context_line":"        exp_stats \u003d {"},{"line_number":85,"context_line":"            (\u0027object_updater\u0027,"},{"line_number":86,"context_line":"             frozenset({(\u0027metrics_name\u0027, \"\u0027sweep\u0027\"),"}],"source_content_type":"text/x-python","patch_set":11,"id":"66b0af29_c3c0c01e","line":83,"in_reply_to":"42c9fdb7_caf91037","updated":"2026-03-24 18:40:08.000000000","message":"Done","commit_id":"1bb5b822b37bb309193ae5549f05ee92fe6ac625"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"795c971ab91775835a923645c604f184ea3f9df3","unresolved":true,"context_lines":[{"line_number":85,"context_line":"            (\u0027object_updater\u0027,"},{"line_number":86,"context_line":"             frozenset({(\u0027metrics_name\u0027, \"\u0027sweep\u0027\"),"},{"line_number":87,"context_line":"                        (\u0027HOST\u0027, \"\u0027s8k-sjc11-a25-ac-01.nsv.sjc11.nvmetal.net\u0027\")"},{"line_number":88,"context_line":"                        })): 50.0}"},{"line_number":89,"context_line":"        self.assertEqual(read_metrics(filename), exp_stats)"},{"line_number":90,"context_line":""},{"line_number":91,"context_line":"        blah.increment(metric)"}],"source_content_type":"text/x-python","patch_set":11,"id":"48744cd9_837c0c48","line":88,"updated":"2026-02-13 20:04:07.000000000","message":"use metric_key here to make the assertion a little more readable.","commit_id":"1bb5b822b37bb309193ae5549f05ee92fe6ac625"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"969839b5ec3d5ae322ddb7888758e5a8e33dd831","unresolved":false,"context_lines":[{"line_number":85,"context_line":"            (\u0027object_updater\u0027,"},{"line_number":86,"context_line":"             frozenset({(\u0027metrics_name\u0027, \"\u0027sweep\u0027\"),"},{"line_number":87,"context_line":"                        (\u0027HOST\u0027, \"\u0027s8k-sjc11-a25-ac-01.nsv.sjc11.nvmetal.net\u0027\")"},{"line_number":88,"context_line":"                        })): 50.0}"},{"line_number":89,"context_line":"        self.assertEqual(read_metrics(filename), exp_stats)"},{"line_number":90,"context_line":""},{"line_number":91,"context_line":"        blah.increment(metric)"}],"source_content_type":"text/x-python","patch_set":11,"id":"25bcdd7b_2708cc65","line":88,"in_reply_to":"48744cd9_837c0c48","updated":"2026-03-24 18:40:08.000000000","message":"Acknowledged","commit_id":"1bb5b822b37bb309193ae5549f05ee92fe6ac625"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"bb10abec2f1db98ebceb473dfcf63d3600208e8f","unresolved":true,"context_lines":[{"line_number":11,"context_line":"# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or"},{"line_number":12,"context_line":"# implied."},{"line_number":13,"context_line":"# See the License for the specific language governing permissions and"},{"line_number":14,"context_line":"# limitations under the License."},{"line_number":15,"context_line":"import os"},{"line_number":16,"context_line":"import unittest"},{"line_number":17,"context_line":""}],"source_content_type":"text/x-python","patch_set":14,"id":"f3529c2c_85ca7c6b","line":14,"updated":"2026-03-10 23:04:54.000000000","message":"why isn\u0027t this file `test.unit.common.utils.test_prom_metrics` to match the new module `common.utils.prom_metrics`","commit_id":"1999c38dab00890ce9f2226c398d0ceb5b0774ec"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"969839b5ec3d5ae322ddb7888758e5a8e33dd831","unresolved":false,"context_lines":[{"line_number":11,"context_line":"# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or"},{"line_number":12,"context_line":"# implied."},{"line_number":13,"context_line":"# See the License for the specific language governing permissions and"},{"line_number":14,"context_line":"# limitations under the License."},{"line_number":15,"context_line":"import os"},{"line_number":16,"context_line":"import unittest"},{"line_number":17,"context_line":""}],"source_content_type":"text/x-python","patch_set":14,"id":"830915f6_78e7050e","line":14,"in_reply_to":"f3529c2c_85ca7c6b","updated":"2026-03-24 18:40:08.000000000","message":"moved the other way, similar to how statsd is!","commit_id":"1999c38dab00890ce9f2226c398d0ceb5b0774ec"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"bb10abec2f1db98ebceb473dfcf63d3600208e8f","unresolved":true,"context_lines":[{"line_number":49,"context_line":"        env \u003d {"},{"line_number":50,"context_line":"            \u0027REQUEST_METHOD\u0027: \u0027GET\u0027,"},{"line_number":51,"context_line":"            \u0027HTTP_AUTHORIZATION\u0027: \u0027AWS X:Y:Z\u0027,"},{"line_number":52,"context_line":"        }"},{"line_number":53,"context_line":"        filename \u003d os.path.join(tempdir, \u0027example.txt\u0027)"},{"line_number":54,"context_line":"        # Use tuple format to write metric with proper labels"},{"line_number":55,"context_line":"        conf \u003d {}"}],"source_content_type":"text/x-python","patch_set":14,"id":"0c43b49a_48285c6f","line":52,"updated":"2026-03-10 23:04:54.000000000","message":"what on earth is this structure?","commit_id":"1999c38dab00890ce9f2226c398d0ceb5b0774ec"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"969839b5ec3d5ae322ddb7888758e5a8e33dd831","unresolved":false,"context_lines":[{"line_number":49,"context_line":"        env \u003d {"},{"line_number":50,"context_line":"            \u0027REQUEST_METHOD\u0027: \u0027GET\u0027,"},{"line_number":51,"context_line":"            \u0027HTTP_AUTHORIZATION\u0027: \u0027AWS X:Y:Z\u0027,"},{"line_number":52,"context_line":"        }"},{"line_number":53,"context_line":"        filename \u003d os.path.join(tempdir, \u0027example.txt\u0027)"},{"line_number":54,"context_line":"        # Use tuple format to write metric with proper labels"},{"line_number":55,"context_line":"        conf \u003d {}"}],"source_content_type":"text/x-python","patch_set":14,"id":"0045c102_58b6988d","line":52,"in_reply_to":"0c43b49a_48285c6f","updated":"2026-03-24 18:40:08.000000000","message":"Done","commit_id":"1999c38dab00890ce9f2226c398d0ceb5b0774ec"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"dc8f5366b4f25b474339ef7650d4e30727ed3508","unresolved":true,"context_lines":[{"line_number":36,"context_line":"            content \u003d f.read()"},{"line_number":37,"context_line":"            return content"},{"line_number":38,"context_line":"    except FileNotFoundError:"},{"line_number":39,"context_line":"        print(\"The file was not found.\")"},{"line_number":40,"context_line":""},{"line_number":41,"context_line":""},{"line_number":42,"context_line":"class BaseTestPrometheusMetrics(unittest.TestCase):"}],"source_content_type":"text/x-python","patch_set":21,"id":"13c54dfa_1bb1101a","line":39,"updated":"2026-04-14 19:10:20.000000000","message":"\u003e Errors should never pass silently.\n\nI wouldn\u0027t want to merge this, I would try to let the error get raised and see if tests fail - if anyone is *depending* on this behavior; maybe we can bake it into the test helper function name like `failsafe_read_stats` and throw a doc-string on for \"returns None if filename does not exist\"\n\n... also this is a pretty thin/opionated wrapper around `with open(): f.read()` - do we open files other than prom metrics?  invalid prom metrics maybe!?","commit_id":"81b11074c834ebbc1fe521d6f15ee70c5f468fb3"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"ff8bbcd7be70afe80dd41554e2d66eae83fe1cc8","unresolved":false,"context_lines":[{"line_number":36,"context_line":"            content \u003d f.read()"},{"line_number":37,"context_line":"            return content"},{"line_number":38,"context_line":"    except FileNotFoundError:"},{"line_number":39,"context_line":"        print(\"The file was not found.\")"},{"line_number":40,"context_line":""},{"line_number":41,"context_line":""},{"line_number":42,"context_line":"class BaseTestPrometheusMetrics(unittest.TestCase):"}],"source_content_type":"text/x-python","patch_set":21,"id":"dcec80da_688131fd","line":39,"in_reply_to":"13c54dfa_1bb1101a","updated":"2026-04-28 04:49:45.000000000","message":"done! Also raised errors if we open a wrong file or invalid prom metrics","commit_id":"81b11074c834ebbc1fe521d6f15ee70c5f468fb3"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"6b828f105ebe2501d80931b112050931b95064db","unresolved":true,"context_lines":[{"line_number":26,"context_line":""},{"line_number":27,"context_line":"from test.debug_logger import debug_logger"},{"line_number":28,"context_line":""},{"line_number":29,"context_line":"from test import read_metrics, metric_key"},{"line_number":30,"context_line":"from test.unit import with_tempdir"},{"line_number":31,"context_line":""},{"line_number":32,"context_line":""}],"source_content_type":"text/x-python","patch_set":22,"id":"1d533f94_25093431","line":29,"updated":"2026-04-21 20:05:35.000000000","message":"what\u0027s the difference between `read_metrics` and `parse_metrics`?\n\nI think you should get rid of 975948: add prom metrics test helpers | https://review.opendev.org/c/openstack/swift/+/975948 and collapse the implemention into `common.prom_metrics` and the tests into here.","commit_id":"1eb2486a2b5322c030db7f3e4129d7d3b84b59b5"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"7abd8df05b8081785b9541f05ff02331685d4b3f","unresolved":false,"context_lines":[{"line_number":26,"context_line":""},{"line_number":27,"context_line":"from test.debug_logger import debug_logger"},{"line_number":28,"context_line":""},{"line_number":29,"context_line":"from test import read_metrics, metric_key"},{"line_number":30,"context_line":"from test.unit import with_tempdir"},{"line_number":31,"context_line":""},{"line_number":32,"context_line":""}],"source_content_type":"text/x-python","patch_set":22,"id":"d87869fb_96a897f5","line":29,"in_reply_to":"1d533f94_25093431","updated":"2026-05-05 15:26:31.000000000","message":"Acknowledged","commit_id":"1eb2486a2b5322c030db7f3e4129d7d3b84b59b5"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"6b828f105ebe2501d80931b112050931b95064db","unresolved":true,"context_lines":[{"line_number":64,"context_line":"            (\u0027object_updater\u0027,"},{"line_number":65,"context_line":"             frozenset({(\u0027metrics_name\u0027, \u0027sweep\u0027),"},{"line_number":66,"context_line":"                        (\u0027HOST\u0027, \u0027s8k-sjc11-a25-ac-01.nsv.sjc11.nvmetal.net\u0027)"},{"line_number":67,"context_line":"                        })): 3.0}"},{"line_number":68,"context_line":"        self.assertEqual(read_metrics(filename), exp_stats)"},{"line_number":69,"context_line":""},{"line_number":70,"context_line":"    @with_tempdir"}],"source_content_type":"text/x-python","patch_set":22,"id":"28364b32_8c5d5b68","line":67,"updated":"2026-04-21 20:05:35.000000000","message":"personally I find the `metric_key` helper sufficiently literal and quite readable:\n\n```\ndiff --git a/test/unit/common/test_prom_metrics.py b/test/unit/common/test_prom_metrics.py\nindex a2b8cdb1d2..2179484fb0 100644\n--- a/test/unit/common/test_prom_metrics.py\n+++ b/test/unit/common/test_prom_metrics.py\n@@ -60,12 +60,10 @@ class BaseTestPrometheusMetrics(unittest.TestCase):\n         blah._write_stat(metric_data)\n         self.assertIs(type(blah), PrometheusClient)\n \n-        exp_stats \u003d {\n-            (\u0027object_updater\u0027,\n-             frozenset({(\u0027metrics_name\u0027, \u0027sweep\u0027),\n-                        (\u0027HOST\u0027, \u0027s8k-sjc11-a25-ac-01.nsv.sjc11.nvmetal.net\u0027)\n-                        })): 3.0}\n-        self.assertEqual(read_metrics(filename), exp_stats)\n+        self.assertEqual({\n+            metric_key(\u0027object_updater\u0027, metrics_name\u003d\u0027sweep\u0027,\n+                       HOST\u003d\u0027s8k-sjc11-a25-ac-01.nsv.sjc11.nvmetal.net\u0027): 3.0,\n+        }, read_metrics(filename))\n \n     @with_tempdir\n     def test_update_stats(self, tempdir):\n```","commit_id":"1eb2486a2b5322c030db7f3e4129d7d3b84b59b5"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"7abd8df05b8081785b9541f05ff02331685d4b3f","unresolved":false,"context_lines":[{"line_number":64,"context_line":"            (\u0027object_updater\u0027,"},{"line_number":65,"context_line":"             frozenset({(\u0027metrics_name\u0027, \u0027sweep\u0027),"},{"line_number":66,"context_line":"                        (\u0027HOST\u0027, \u0027s8k-sjc11-a25-ac-01.nsv.sjc11.nvmetal.net\u0027)"},{"line_number":67,"context_line":"                        })): 3.0}"},{"line_number":68,"context_line":"        self.assertEqual(read_metrics(filename), exp_stats)"},{"line_number":69,"context_line":""},{"line_number":70,"context_line":"    @with_tempdir"}],"source_content_type":"text/x-python","patch_set":22,"id":"299881ec_a23e96a0","line":67,"in_reply_to":"28364b32_8c5d5b68","updated":"2026-05-05 15:26:31.000000000","message":"Done","commit_id":"1eb2486a2b5322c030db7f3e4129d7d3b84b59b5"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"6b828f105ebe2501d80931b112050931b95064db","unresolved":true,"context_lines":[{"line_number":80,"context_line":"        blah._write_stat(metric_str)"},{"line_number":81,"context_line":""},{"line_number":82,"context_line":"        # Update stats with new value"},{"line_number":83,"context_line":"        blah.update_stats(metric_str, 50)"},{"line_number":84,"context_line":"        exp_stats \u003d {"},{"line_number":85,"context_line":"            (\u0027object_updater\u0027,"},{"line_number":86,"context_line":"             frozenset({(\u0027metrics_name\u0027, \u0027sweep\u0027),"}],"source_content_type":"text/x-python","patch_set":22,"id":"89cdb76b_49b93c32","line":83,"updated":"2026-04-21 20:05:35.000000000","message":"this `update_stats` interface needs to go:\n\n```\n-\u003e blah.update_stats(metric_str, 50)\n(Pdb) !metric_str\n\u0027object_updater{HOST\u003d\"s8k-sjc11-a25-ac-01.nsv.sjc11.nvmetal.net\",metrics_name\u003d\"sweep\"} 3.0\u0027\n```\n\nsince `metric_str` already has a value I think it\u0027s a strange interface, compared to better interfaces that already exist:\n\n```\n(Pdb) blah._stats\n{(\u0027object_updater\u0027, frozenset({(\u0027metrics_name\u0027, \u0027sweep\u0027), (\u0027HOST\u0027, \u0027s8k-sjc11-a25-ac-01.nsv.sjc11.nvmetal.net\u0027)})): 3.0}\n(Pdb) blah.gauge(\u0027object_updater\u0027, 50, labels\u003d{\u0027metrics_name\u0027: \u0027sweep\u0027, \u0027HOST\u0027: \u0027s8k-sjc11-a25-ac-01.nsv.sjc11.nvmetal.net\u0027})\n\u0027object_updater{HOST\u003d\"s8k-sjc11-a25-ac-01.nsv.sjc11.nvmetal.net\",metrics_name\u003d\"sweep\"} 50\\n\u0027\n(Pdb) blah._stats\n{(\u0027object_updater\u0027, frozenset({(\u0027metrics_name\u0027, \u0027sweep\u0027), (\u0027HOST\u0027, \u0027s8k-sjc11-a25-ac-01.nsv.sjc11.nvmetal.net\u0027)})): 50}\n```","commit_id":"1eb2486a2b5322c030db7f3e4129d7d3b84b59b5"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"ccfc40fce0c2d3d8e0bb87dbdc88aaf7ea4a150b","unresolved":false,"context_lines":[{"line_number":80,"context_line":"        blah._write_stat(metric_str)"},{"line_number":81,"context_line":""},{"line_number":82,"context_line":"        # Update stats with new value"},{"line_number":83,"context_line":"        blah.update_stats(metric_str, 50)"},{"line_number":84,"context_line":"        exp_stats \u003d {"},{"line_number":85,"context_line":"            (\u0027object_updater\u0027,"},{"line_number":86,"context_line":"             frozenset({(\u0027metrics_name\u0027, \u0027sweep\u0027),"}],"source_content_type":"text/x-python","patch_set":22,"id":"5be06a0d_2af29c18","line":83,"in_reply_to":"89cdb76b_49b93c32","updated":"2026-05-07 21:59:18.000000000","message":"Acknowledged","commit_id":"1eb2486a2b5322c030db7f3e4129d7d3b84b59b5"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"6b828f105ebe2501d80931b112050931b95064db","unresolved":true,"context_lines":[{"line_number":113,"context_line":""},{"line_number":114,"context_line":"        self.assertEqual(name, \u0027http_requests_total\u0027)"},{"line_number":115,"context_line":"        self.assertEqual(labels, {\u0027endpoint\u0027: \u0027/api\u0027, \u0027method\u0027: \u0027GET\u0027})"},{"line_number":116,"context_line":"        self.assertEqual(value, 42.0)"},{"line_number":117,"context_line":""},{"line_number":118,"context_line":"    def test_parse_metrics_without_labels(self):"},{"line_number":119,"context_line":"        \"\"\"Test parsing simple metrics without labels\"\"\""}],"source_content_type":"text/x-python","patch_set":22,"id":"48761674_6463f228","line":116,"updated":"2026-04-21 20:05:35.000000000","message":"I\u0027d encourage you to put all the tests for parsing/reading metrics into their own `TestPrometheusParsing` separate from the tests of `TestPrometheusClient`","commit_id":"1eb2486a2b5322c030db7f3e4129d7d3b84b59b5"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"ff8bbcd7be70afe80dd41554e2d66eae83fe1cc8","unresolved":false,"context_lines":[{"line_number":113,"context_line":""},{"line_number":114,"context_line":"        self.assertEqual(name, \u0027http_requests_total\u0027)"},{"line_number":115,"context_line":"        self.assertEqual(labels, {\u0027endpoint\u0027: \u0027/api\u0027, \u0027method\u0027: \u0027GET\u0027})"},{"line_number":116,"context_line":"        self.assertEqual(value, 42.0)"},{"line_number":117,"context_line":""},{"line_number":118,"context_line":"    def test_parse_metrics_without_labels(self):"},{"line_number":119,"context_line":"        \"\"\"Test parsing simple metrics without labels\"\"\""}],"source_content_type":"text/x-python","patch_set":22,"id":"cdf8805f_fc1ffeef","line":116,"in_reply_to":"48761674_6463f228","updated":"2026-04-28 04:49:45.000000000","message":"Done","commit_id":"1eb2486a2b5322c030db7f3e4129d7d3b84b59b5"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"6b828f105ebe2501d80931b112050931b95064db","unresolved":true,"context_lines":[{"line_number":198,"context_line":"        self.assertEqual("},{"line_number":199,"context_line":"            result,"},{"line_number":200,"context_line":"            \u0027room_temperature{location\u003d\"room1\",unit\u003d\"celsius\"} 22.0\\n\u0027"},{"line_number":201,"context_line":"        )"},{"line_number":202,"context_line":"        exp_stats \u003d {"},{"line_number":203,"context_line":"            metric_key(\u0027room_temperature\u0027, location\u003d\u0027room1\u0027,"},{"line_number":204,"context_line":"                       unit\u003d\u0027celsius\u0027): 22.0"}],"source_content_type":"text/x-python","patch_set":22,"id":"4adffb67_9feccfa0","line":201,"updated":"2026-04-21 20:05:35.000000000","message":"this is why I don\u0027t think it\u0027s a good idea to define the interface as returning this string - it makes it harder to change later b/c callers will come to expect it.","commit_id":"1eb2486a2b5322c030db7f3e4129d7d3b84b59b5"},{"author":{"_account_id":35790,"name":"Shreeya Deshpande","email":"shreeyad@nvidia.com","username":"shreeyad"},"change_message_id":"7abd8df05b8081785b9541f05ff02331685d4b3f","unresolved":false,"context_lines":[{"line_number":198,"context_line":"        self.assertEqual("},{"line_number":199,"context_line":"            result,"},{"line_number":200,"context_line":"            \u0027room_temperature{location\u003d\"room1\",unit\u003d\"celsius\"} 22.0\\n\u0027"},{"line_number":201,"context_line":"        )"},{"line_number":202,"context_line":"        exp_stats \u003d {"},{"line_number":203,"context_line":"            metric_key(\u0027room_temperature\u0027, location\u003d\u0027room1\u0027,"},{"line_number":204,"context_line":"                       unit\u003d\u0027celsius\u0027): 22.0"}],"source_content_type":"text/x-python","patch_set":22,"id":"3bf1acb2_0e664e0d","line":201,"in_reply_to":"4adffb67_9feccfa0","updated":"2026-05-05 15:26:31.000000000","message":"Done! used metric_key to get by!","commit_id":"1eb2486a2b5322c030db7f3e4129d7d3b84b59b5"}]}
