)]}'
{"/COMMIT_MSG":[{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"aa15643ed16f48f69ae25f1eeb8b929dd2436d80","unresolved":true,"context_lines":[{"line_number":6,"context_line":""},{"line_number":7,"context_line":"swift-manage-expirer: introduce summary and details"},{"line_number":8,"context_line":""},{"line_number":9,"context_line":"Introduce two subcommands to the swift-manage-expirer CLI for monitoring"},{"line_number":10,"context_line":"the expirer task queue:"},{"line_number":11,"context_line":""},{"line_number":12,"context_line":"- summary: Prints high-level statistics including total container and"},{"line_number":13,"context_line":"  object counts, plus per-day task counts for the selected time window."}],"source_content_type":"text/x-gerrit-commit-message","patch_set":1,"id":"1b3d0013_1d4e5781","line":10,"range":{"start_line":9,"start_character":0,"end_line":10,"end_character":23},"updated":"2026-03-22 22:05:05.000000000","message":"Better as\n\n\u003e Introduce a swift-manage-expirer CLI for monitoring the expirer task queue. It has two subcommands:","commit_id":"2ddf9dbbdaabc3feb0649b1bd62228a08e8d60e4"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"3df9d39c82c903ee5ec3be9e3a00a795b8e8a40f","unresolved":false,"context_lines":[{"line_number":6,"context_line":""},{"line_number":7,"context_line":"swift-manage-expirer: introduce summary and details"},{"line_number":8,"context_line":""},{"line_number":9,"context_line":"Introduce two subcommands to the swift-manage-expirer CLI for monitoring"},{"line_number":10,"context_line":"the expirer task queue:"},{"line_number":11,"context_line":""},{"line_number":12,"context_line":"- summary: Prints high-level statistics including total container and"},{"line_number":13,"context_line":"  object counts, plus per-day task counts for the selected time window."}],"source_content_type":"text/x-gerrit-commit-message","patch_set":1,"id":"e7a78686_964ea62c","line":10,"range":{"start_line":9,"start_character":0,"end_line":10,"end_character":23},"in_reply_to":"1b3d0013_1d4e5781","updated":"2026-04-14 21:57:41.000000000","message":"Done","commit_id":"2ddf9dbbdaabc3feb0649b1bd62228a08e8d60e4"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"aa15643ed16f48f69ae25f1eeb8b929dd2436d80","unresolved":true,"context_lines":[{"line_number":18,"context_line":"  vs async_delete), account, container, days until expiration, and grace"},{"line_number":19,"context_line":"  period status. Uses multiple workers for parallel processing of large queues."},{"line_number":20,"context_line":""},{"line_number":21,"context_line":"Both subcommands emit Prometheus-style metrics suitable for monitoring"},{"line_number":22,"context_line":"and integration with Prometheus/Grafana."},{"line_number":23,"context_line":""},{"line_number":24,"context_line":"Change-Id: Ie08499737d6a51a3ffa36bc74c2d38be27028af9"}],"source_content_type":"text/x-gerrit-commit-message","patch_set":1,"id":"03a3d9d6_8ac29569","line":21,"range":{"start_line":21,"start_character":17,"end_line":21,"end_character":46},"updated":"2026-03-22 22:05:05.000000000","message":"\"Emit\" how/where? Would it be accurate to say \"write Prometheus-style metrics to stdout ...\"?","commit_id":"2ddf9dbbdaabc3feb0649b1bd62228a08e8d60e4"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"3df9d39c82c903ee5ec3be9e3a00a795b8e8a40f","unresolved":false,"context_lines":[{"line_number":18,"context_line":"  vs async_delete), account, container, days until expiration, and grace"},{"line_number":19,"context_line":"  period status. Uses multiple workers for parallel processing of large queues."},{"line_number":20,"context_line":""},{"line_number":21,"context_line":"Both subcommands emit Prometheus-style metrics suitable for monitoring"},{"line_number":22,"context_line":"and integration with Prometheus/Grafana."},{"line_number":23,"context_line":""},{"line_number":24,"context_line":"Change-Id: Ie08499737d6a51a3ffa36bc74c2d38be27028af9"}],"source_content_type":"text/x-gerrit-commit-message","patch_set":1,"id":"890944b9_64c8f283","line":21,"range":{"start_line":21,"start_character":17,"end_line":21,"end_character":46},"in_reply_to":"03a3d9d6_8ac29569","updated":"2026-04-14 21:57:41.000000000","message":"Done","commit_id":"2ddf9dbbdaabc3feb0649b1bd62228a08e8d60e4"}],"/PATCHSET_LEVEL":[{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f1c39b1bb5f995670271aba8a24ba76635d0a742","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":1,"id":"7c51cfd6_769e7ff4","updated":"2026-03-31 18:49:10.000000000","message":"I\u0027d love to see another version of this that includes some rst docs on how to use it to get prometheus compatible reporting that SRE can use to monitor their expirer queues.\n\nI also think some of the testing that\u0027s trying to make assertions about the emitted metrics would be better if it used prometheus parsing to make semantic assertions about the exact metric keys and counts. e.g.\n\n975948: add prom metrics test helpers | https://review.opendev.org/c/openstack/swift/+/975948","commit_id":"2ddf9dbbdaabc3feb0649b1bd62228a08e8d60e4"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"03d6bcbafc86eb1e33a873236b4bb6e8f971f234","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":7,"id":"301e7b72_c399741a","updated":"2026-04-17 18:04:55.000000000","message":"this is all looking great!  just some minor nits on the style of assertions in the probe - use literals and make them as strong as you can.  I\u0027d like to see the unittests grow to cover more stuff (like delay reaping and various bytes that may or may not be in container listings like --verify-bytes!)\n\nEventually I think you should try to adapt the print() statements to use the dependent `PrometheusClient` and suggest some kind of flush(file\u003dsys.stdout) interface - it will be helpful to have some exhaustive tests since the prom client interface should generate exactly the same output as the current literal print statements.\n\nI need to revisit the multiprocessing queue behavior - we don\u0027t want the tool to deadlock when you give it 1000s of days with \u003e1M containers.","commit_id":"3b10df5ce1f7e0997e98e905386bf79561a41f2d"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":11,"id":"d4828ed7_cda72e05","updated":"2026-04-21 22:53:11.000000000","message":"automated behavioral/functional tests seems to be coming along - kudos\n\nI left a lot of comments; but I\u0027m not sure I found any mis-behaviors?\n\nI\u0027m out of time; and I need to spend more time thinking about the possible deadlock-at-scale situation:\n\nMaybe because workers only ever push back a single aggregated stats dict the feeder can block putting containers into the consumer queue and the workers will always eventually unblock it w/o ever any risk of they themselves getting blocked trying to feed data back to the producer.\n\n... maybe next step is some manual scale smoke testing","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"}],"doc/source/overview_expiring_objects.rst":[{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"03d6bcbafc86eb1e33a873236b4bb6e8f971f234","unresolved":true,"context_lines":[{"line_number":58,"context_line":"Monitoring the expirer queue (``swift-manage-expirer``)"},{"line_number":59,"context_line":"-----------------------------------------------------"},{"line_number":60,"context_line":""},{"line_number":61,"context_line":"The ``swift-manage-expirer`` command-line tool inspects the legacy"},{"line_number":62,"context_line":"``.expiring_objects`` task queue using Swift\u0027s internal client. It is"},{"line_number":63,"context_line":"intended for operators who want queue size, shape, and breakdowns suitable"},{"line_number":64,"context_line":"for logs or monitoring (it prints Prometheus-style lines on standard output)."}],"source_content_type":"text/x-rst","patch_set":2,"id":"c1d7cb7e_364cbe61","line":61,"updated":"2026-04-17 18:04:55.000000000","message":"don\u0027t call `.expiring_objects` account \"legacy\" - the surrounding docs were aspirational and that re-queue or whatever is not going to happen.","commit_id":"f609f5d1f39cacef5155aece007e2bb9025f423f"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"d996ce71709b3b82e5ede40cc51e1ccd737f8ad4","unresolved":false,"context_lines":[{"line_number":58,"context_line":"Monitoring the expirer queue (``swift-manage-expirer``)"},{"line_number":59,"context_line":"-----------------------------------------------------"},{"line_number":60,"context_line":""},{"line_number":61,"context_line":"The ``swift-manage-expirer`` command-line tool inspects the legacy"},{"line_number":62,"context_line":"``.expiring_objects`` task queue using Swift\u0027s internal client. It is"},{"line_number":63,"context_line":"intended for operators who want queue size, shape, and breakdowns suitable"},{"line_number":64,"context_line":"for logs or monitoring (it prints Prometheus-style lines on standard output)."}],"source_content_type":"text/x-rst","patch_set":2,"id":"d1074641_7ad133b9","line":61,"in_reply_to":"c1d7cb7e_364cbe61","updated":"2026-04-21 18:21:56.000000000","message":"Done","commit_id":"f609f5d1f39cacef5155aece007e2bb9025f423f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"03d6bcbafc86eb1e33a873236b4bb6e8f971f234","unresolved":true,"context_lines":[{"line_number":98,"context_line":""},{"line_number":99,"context_line":"    Metrics use names such as ``expiring_objects_entries``,"},{"line_number":100,"context_line":"    ``expiring_objects_bytes``, ``async_delete_entries``, and"},{"line_number":101,"context_line":"    ``async_delete_bytes``, each with ``host``, ``account``, ``container``,"},{"line_number":102,"context_line":"    ``days``, and ``grace`` labels. Byte totals are taken from the"},{"line_number":103,"context_line":"    ``swift_expirer_bytes`` parameter in the queue entry\u0027s content type when"},{"line_number":104,"context_line":"    present; with ``--verify-bytes``, the tool may issue HEAD requests to"}],"source_content_type":"text/x-rst","patch_set":2,"id":"18315eb3_09cb8753","line":101,"updated":"2026-04-17 18:04:55.000000000","message":"by convention all our labeled metrics (currently only statsd, but we want to carry that convention forward) should be prefixed w/ `swift_`\n\nthe current labeled metrics actually tend to current be prefixed with `swift_\u003cservice_name\u003e_` (e.g. `swift_proxy_server_...`, `swift_object_replicator_...`) - but I\u0027m not sure if `swift_manage_expirer_` is a great prefix\n\nI think EITHER `swift_expirer_` or `swift_manage_expirer_` would be fine - we should get some input from other people e.g. @yanxiao@nvidia.com \u0026 @shreeyad@nvidia.com","commit_id":"f609f5d1f39cacef5155aece007e2bb9025f423f"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"d996ce71709b3b82e5ede40cc51e1ccd737f8ad4","unresolved":true,"context_lines":[{"line_number":98,"context_line":""},{"line_number":99,"context_line":"    Metrics use names such as ``expiring_objects_entries``,"},{"line_number":100,"context_line":"    ``expiring_objects_bytes``, ``async_delete_entries``, and"},{"line_number":101,"context_line":"    ``async_delete_bytes``, each with ``host``, ``account``, ``container``,"},{"line_number":102,"context_line":"    ``days``, and ``grace`` labels. Byte totals are taken from the"},{"line_number":103,"context_line":"    ``swift_expirer_bytes`` parameter in the queue entry\u0027s content type when"},{"line_number":104,"context_line":"    present; with ``--verify-bytes``, the tool may issue HEAD requests to"}],"source_content_type":"text/x-rst","patch_set":2,"id":"b999eddd_46639342","line":101,"in_reply_to":"18315eb3_09cb8753","updated":"2026-04-21 18:21:56.000000000","message":"Done for now; but after incoporating feedback from @shreeyad@nvidia.com or Yan we can democratically arrive at which metric name sounds appropriate since this particular part of the feedback involves quite a bit of refactoring","commit_id":"f609f5d1f39cacef5155aece007e2bb9025f423f"}],"swift/cli/manage_expirer.py":[{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"aa15643ed16f48f69ae25f1eeb8b929dd2436d80","unresolved":true,"context_lines":[{"line_number":29,"context_line":""},{"line_number":30,"context_line":"EXPIRING_ACCOUNT \u003d \".expiring_objects\""},{"line_number":31,"context_line":"EXPR_BYTES_HINT \u003d re.compile(r\"swift_expirer_bytes\u003d(\\d+)\")"},{"line_number":32,"context_line":"EXIT \u003d (\u0027__EXIT__\u0027,)"},{"line_number":33,"context_line":""},{"line_number":34,"context_line":""},{"line_number":35,"context_line":"@contextlib.contextmanager"}],"source_content_type":"text/x-python","patch_set":1,"id":"aca15efe_544a84f7","line":32,"updated":"2026-03-22 22:05:05.000000000","message":"Should we just use `None` as the sentinel? If w stick with a named sentinel, I tend to prefer something like `EXIT \u003d object()`, which is especially useful if we could be passing around arbitrary values.","commit_id":"2ddf9dbbdaabc3feb0649b1bd62228a08e8d60e4"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f1c39b1bb5f995670271aba8a24ba76635d0a742","unresolved":true,"context_lines":[{"line_number":29,"context_line":""},{"line_number":30,"context_line":"EXPIRING_ACCOUNT \u003d \".expiring_objects\""},{"line_number":31,"context_line":"EXPR_BYTES_HINT \u003d re.compile(r\"swift_expirer_bytes\u003d(\\d+)\")"},{"line_number":32,"context_line":"EXIT \u003d (\u0027__EXIT__\u0027,)"},{"line_number":33,"context_line":""},{"line_number":34,"context_line":""},{"line_number":35,"context_line":"@contextlib.contextmanager"}],"source_content_type":"text/x-python","patch_set":1,"id":"a921c0c5_c0ec394d","line":32,"in_reply_to":"aca15efe_544a84f7","updated":"2026-03-31 18:49:10.000000000","message":"I also prefer `EXIT \u003d object()` as a sentinel\n\n```\n\u003e\u003e\u003e MY_SPECIAL_OBJECT \u003d object()\n\u003e\u003e\u003e MY_SPECIAL_OBJECT is object()\nFalse\n\u003e\u003e\u003e MY_SPECIAL_OBJECT \u003d\u003d object()\nFalse\n```\n\n... although technically a tuple *is* an object; unfortunately it also has a default equality comparison:\n\n```\n\u003e\u003e\u003e from swift.cli.manage_expirer import EXIT\n\u003e\u003e\u003e EXIT is (\u0027__EXIT__\u0027,)\n\u003cstdin\u003e:1: SyntaxWarning: \"is\" with a literal. Did you mean \"\u003d\u003d\"?\nFalse\n\u003e\u003e\u003e EXIT \u003d\u003d (\u0027__EXIT__\u0027,)\nTrue\n```","commit_id":"2ddf9dbbdaabc3feb0649b1bd62228a08e8d60e4"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"aa15643ed16f48f69ae25f1eeb8b929dd2436d80","unresolved":true,"context_lines":[{"line_number":120,"context_line":"    )"},{"line_number":121,"context_line":"    parser.add_argument("},{"line_number":122,"context_line":"        \"--expirer-conf-path\","},{"line_number":123,"context_line":"        default\u003d\"/etc/swift/object-expirer.conf.d\","},{"line_number":124,"context_line":"        help\u003d\"Path to object-expirer.conf\","},{"line_number":125,"context_line":"    )"},{"line_number":126,"context_line":""}],"source_content_type":"text/x-python","patch_set":1,"id":"a74a351b_19557db0","line":123,"updated":"2026-03-22 22:05:05.000000000","message":"Interesting -- I think this is the first time we\u0027ve defaulted to a conf.d-style conf!","commit_id":"2ddf9dbbdaabc3feb0649b1bd62228a08e8d60e4"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f1c39b1bb5f995670271aba8a24ba76635d0a742","unresolved":true,"context_lines":[{"line_number":120,"context_line":"    )"},{"line_number":121,"context_line":"    parser.add_argument("},{"line_number":122,"context_line":"        \"--expirer-conf-path\","},{"line_number":123,"context_line":"        default\u003d\"/etc/swift/object-expirer.conf.d\","},{"line_number":124,"context_line":"        help\u003d\"Path to object-expirer.conf\","},{"line_number":125,"context_line":"    )"},{"line_number":126,"context_line":""}],"source_content_type":"text/x-python","patch_set":1,"id":"b9c0d1ca_510d1734","line":123,"in_reply_to":"a74a351b_19557db0","updated":"2026-03-31 18:49:10.000000000","message":"probably borrowed from gizmo code which uses nv-style-deployment-defaults\n\n... there may be good reason to start moving in this direction; a `default\u003dNone` behavior that would \"work for either based on search order\" might be a nice-to-have.","commit_id":"2ddf9dbbdaabc3feb0649b1bd62228a08e8d60e4"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"3df9d39c82c903ee5ec3be9e3a00a795b8e8a40f","unresolved":false,"context_lines":[{"line_number":120,"context_line":"    )"},{"line_number":121,"context_line":"    parser.add_argument("},{"line_number":122,"context_line":"        \"--expirer-conf-path\","},{"line_number":123,"context_line":"        default\u003d\"/etc/swift/object-expirer.conf.d\","},{"line_number":124,"context_line":"        help\u003d\"Path to object-expirer.conf\","},{"line_number":125,"context_line":"    )"},{"line_number":126,"context_line":""}],"source_content_type":"text/x-python","patch_set":1,"id":"1ccf6a16_74b93ecb","line":123,"in_reply_to":"b9c0d1ca_510d1734","updated":"2026-04-14 21:57:41.000000000","message":"Acknowledged","commit_id":"2ddf9dbbdaabc3feb0649b1bd62228a08e8d60e4"},{"author":{"_account_id":7233,"name":"Matthew Oliver","email":"matt@oliver.net.au","username":"mattoliverau"},"change_message_id":"6ec970be28a5bef5f23f4f58f1dfc32ba5a8e7f1","unresolved":false,"context_lines":[{"line_number":293,"context_line":"                self.args, f\"{mp.current_process().name}-ic\") as ic:"},{"line_number":294,"context_line":"            for task_dt, container_info in iter_relevant_containers("},{"line_number":295,"context_line":"                    self.args, ic, self.current_dt, self.logger):"},{"line_number":296,"context_line":"                container_q.put(container_info)"},{"line_number":297,"context_line":"                container_count +\u003d 1"},{"line_number":298,"context_line":""},{"line_number":299,"context_line":"        print(f\"Found {container_count} containers to process with \""}],"source_content_type":"text/x-python","patch_set":1,"id":"de5afa01_15fc978d","line":296,"updated":"2026-03-24 06:30:23.000000000","message":"So this grabs EVERY container and pops it onto the queue, is there a chance that this might be _ALOT_ of containers? and if so this doesn\u0027t seem to memory efficient. The iter_relevant_containers code is using a generator, which is great, I wonder if we need some kind of producer-consumer pattern here, so things are added to the queue as required. OR maybe use a pool and starmap so there is a concurrency and and a new process is started for each container which can be popped off the generator one at a time?","commit_id":"2ddf9dbbdaabc3feb0649b1bd62228a08e8d60e4"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"3df9d39c82c903ee5ec3be9e3a00a795b8e8a40f","unresolved":false,"context_lines":[{"line_number":293,"context_line":"                self.args, f\"{mp.current_process().name}-ic\") as ic:"},{"line_number":294,"context_line":"            for task_dt, container_info in iter_relevant_containers("},{"line_number":295,"context_line":"                    self.args, ic, self.current_dt, self.logger):"},{"line_number":296,"context_line":"                container_q.put(container_info)"},{"line_number":297,"context_line":"                container_count +\u003d 1"},{"line_number":298,"context_line":""},{"line_number":299,"context_line":"        print(f\"Found {container_count} containers to process with \""}],"source_content_type":"text/x-python","patch_set":1,"id":"7e1dc19d_e3832076","line":296,"in_reply_to":"68bee3ef_d60affe7","updated":"2026-04-14 21:57:41.000000000","message":"Done","commit_id":"2ddf9dbbdaabc3feb0649b1bd62228a08e8d60e4"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f1c39b1bb5f995670271aba8a24ba76635d0a742","unresolved":true,"context_lines":[{"line_number":293,"context_line":"                self.args, f\"{mp.current_process().name}-ic\") as ic:"},{"line_number":294,"context_line":"            for task_dt, container_info in iter_relevant_containers("},{"line_number":295,"context_line":"                    self.args, ic, self.current_dt, self.logger):"},{"line_number":296,"context_line":"                container_q.put(container_info)"},{"line_number":297,"context_line":"                container_count +\u003d 1"},{"line_number":298,"context_line":""},{"line_number":299,"context_line":"        print(f\"Found {container_count} containers to process with \""}],"source_content_type":"text/x-python","patch_set":1,"id":"68bee3ef_d60affe7","line":296,"in_reply_to":"de5afa01_15fc978d","updated":"2026-03-31 18:49:10.000000000","message":"in fact the `mp.Queue` *probably* has a upper bound that it will actually hold and with enough containers this would actually just deadlock.\n\nWe need to start he listing_workers *before* we start populating the queue - so that if our \"producer\" fills the queue faster than the listing workers can \"consume\" it - they\u0027ll just block until the queue has room - we\u0027ll never have more than a page + queue-depth entries in memory at a time.","commit_id":"2ddf9dbbdaabc3feb0649b1bd62228a08e8d60e4"},{"author":{"_account_id":7233,"name":"Matthew Oliver","email":"matt@oliver.net.au","username":"mattoliverau"},"change_message_id":"6ec970be28a5bef5f23f4f58f1dfc32ba5a8e7f1","unresolved":false,"context_lines":[{"line_number":301,"context_line":""},{"line_number":302,"context_line":"        # Signal workers to exit after processing"},{"line_number":303,"context_line":"        for _ in range(self.args.listing_workers):"},{"line_number":304,"context_line":"            container_q.put(EXIT)"},{"line_number":305,"context_line":""},{"line_number":306,"context_line":"        # Start workers"},{"line_number":307,"context_line":"        workers \u003d []"}],"source_content_type":"text/x-python","patch_set":1,"id":"2e09a648_167e08df","line":304,"updated":"2026-03-24 06:30:23.000000000","message":"If we don\u0027t use a pool and something like pool.starmap, then I wonder if we could try JoinableQueue, they seem interesting and do something similar, ie, join back when every task in the queue has been completed.","commit_id":"2ddf9dbbdaabc3feb0649b1bd62228a08e8d60e4"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f1c39b1bb5f995670271aba8a24ba76635d0a742","unresolved":true,"context_lines":[{"line_number":301,"context_line":""},{"line_number":302,"context_line":"        # Signal workers to exit after processing"},{"line_number":303,"context_line":"        for _ in range(self.args.listing_workers):"},{"line_number":304,"context_line":"            container_q.put(EXIT)"},{"line_number":305,"context_line":""},{"line_number":306,"context_line":"        # Start workers"},{"line_number":307,"context_line":"        workers \u003d []"}],"source_content_type":"text/x-python","patch_set":1,"id":"b47dadd7_26766b50","line":304,"in_reply_to":"2e09a648_167e08df","updated":"2026-03-31 18:49:10.000000000","message":"IME the task_done handling in multiporcessing is slightly less efficient and can be more complex to get correct when dealing with eventlet monkey-patching.\n\nIn fact we might want to use a `mp.SimpleQueue` for portability.\n\nIIRC we have some existing working/battle-hardened code that uses this implementation which we\u0027re trying to upstream (more than \"optimize\"), but we could try to do some scale testing/verification of a `JoinableQueue` implementation as well IF we had such code - possibly as a follow-up maintainability improvement.","commit_id":"2ddf9dbbdaabc3feb0649b1bd62228a08e8d60e4"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"3df9d39c82c903ee5ec3be9e3a00a795b8e8a40f","unresolved":false,"context_lines":[{"line_number":301,"context_line":""},{"line_number":302,"context_line":"        # Signal workers to exit after processing"},{"line_number":303,"context_line":"        for _ in range(self.args.listing_workers):"},{"line_number":304,"context_line":"            container_q.put(EXIT)"},{"line_number":305,"context_line":""},{"line_number":306,"context_line":"        # Start workers"},{"line_number":307,"context_line":"        workers \u003d []"}],"source_content_type":"text/x-python","patch_set":1,"id":"8c185324_b8ae31a8","line":304,"in_reply_to":"b47dadd7_26766b50","updated":"2026-04-14 21:57:41.000000000","message":"Done","commit_id":"2ddf9dbbdaabc3feb0649b1bd62228a08e8d60e4"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"03d6bcbafc86eb1e33a873236b4bb6e8f971f234","unresolved":true,"context_lines":[{"line_number":221,"context_line":"                         exc_info\u003dTrue)"},{"line_number":222,"context_line":""},{"line_number":223,"context_line":""},{"line_number":224,"context_line":"class BaseExpirerStatus:"},{"line_number":225,"context_line":"    \"\"\""},{"line_number":226,"context_line":"    High-level object for status and stats reporting on expirer queue."},{"line_number":227,"context_line":"    Handles \u0027summary\u0027 and \u0027details\u0027 subcommands."}],"source_content_type":"text/x-python","patch_set":2,"id":"bbb01f71_f9d2f77a","line":224,"updated":"2026-04-17 18:04:55.000000000","message":"AFAICT this is not only a concrete class - it\u0027s the ONLY class - I don\u0027t think it\u0027s a good idea to call a concrete class a \"Base\" class - Base implies inheritance and I don\u0027t see that here.","commit_id":"f609f5d1f39cacef5155aece007e2bb9025f423f"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"d996ce71709b3b82e5ede40cc51e1ccd737f8ad4","unresolved":false,"context_lines":[{"line_number":221,"context_line":"                         exc_info\u003dTrue)"},{"line_number":222,"context_line":""},{"line_number":223,"context_line":""},{"line_number":224,"context_line":"class BaseExpirerStatus:"},{"line_number":225,"context_line":"    \"\"\""},{"line_number":226,"context_line":"    High-level object for status and stats reporting on expirer queue."},{"line_number":227,"context_line":"    Handles \u0027summary\u0027 and \u0027details\u0027 subcommands."}],"source_content_type":"text/x-python","patch_set":2,"id":"0d9c3774_21b13055","line":224,"in_reply_to":"bbb01f71_f9d2f77a","updated":"2026-04-21 18:21:56.000000000","message":"Acknowledged","commit_id":"f609f5d1f39cacef5155aece007e2bb9025f423f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"03d6bcbafc86eb1e33a873236b4bb6e8f971f234","unresolved":true,"context_lines":[{"line_number":265,"context_line":""},{"line_number":266,"context_line":"            for task_dt, container_info in iter_relevant_containers("},{"line_number":267,"context_line":"                    self.args, ic, self.current_dt, self.logger):"},{"line_number":268,"context_line":"                offset \u003d (task_dt - self.current_dt).days"},{"line_number":269,"context_line":"                container_day_counts[offset] +\u003d container_info.get(\"count\", 0)"},{"line_number":270,"context_line":"                processed_containers +\u003d 1"},{"line_number":271,"context_line":""}],"source_content_type":"text/x-python","patch_set":2,"id":"44c2430b_e7fbeb89","line":268,"updated":"2026-04-17 18:04:55.000000000","message":"please consider adding a debug log message (only shows up in verbose mode?) that outputs:\n\ncontainer[\u0027name\u0027], offset, count\n\n... for ever processed container\n\nI could use for some un-related debugging I need to do 😊","commit_id":"f609f5d1f39cacef5155aece007e2bb9025f423f"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"d996ce71709b3b82e5ede40cc51e1ccd737f8ad4","unresolved":false,"context_lines":[{"line_number":265,"context_line":""},{"line_number":266,"context_line":"            for task_dt, container_info in iter_relevant_containers("},{"line_number":267,"context_line":"                    self.args, ic, self.current_dt, self.logger):"},{"line_number":268,"context_line":"                offset \u003d (task_dt - self.current_dt).days"},{"line_number":269,"context_line":"                container_day_counts[offset] +\u003d container_info.get(\"count\", 0)"},{"line_number":270,"context_line":"                processed_containers +\u003d 1"},{"line_number":271,"context_line":""}],"source_content_type":"text/x-python","patch_set":2,"id":"b514ab8b_0e2fcaf1","line":268,"in_reply_to":"44c2430b_e7fbeb89","updated":"2026-04-21 18:21:56.000000000","message":"Acknowledged","commit_id":"f609f5d1f39cacef5155aece007e2bb9025f423f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"03d6bcbafc86eb1e33a873236b4bb6e8f971f234","unresolved":true,"context_lines":[{"line_number":305,"context_line":"            for task_dt, container_info in iter_relevant_containers("},{"line_number":306,"context_line":"                    self.args, ic, self.current_dt, self.logger):"},{"line_number":307,"context_line":"                container_q.put(container_info)"},{"line_number":308,"context_line":"                container_count +\u003d 1"},{"line_number":309,"context_line":""},{"line_number":310,"context_line":"        print(f\"Found {container_count} containers to process with \""},{"line_number":311,"context_line":"              f\"{self.args.listing_workers} workers\", file\u003dsys.stderr)"}],"source_content_type":"text/x-python","patch_set":2,"id":"d38ef633_71bb0962","line":308,"updated":"2026-04-17 18:04:55.000000000","message":"this loop will block if `num_containers \u003e container_q.size`\n\nit *could* deadlock - if a worker can\u0027t put it\u0027s result in the result queue\n\nSo... we need to be draining/aggregating the `result_q` even as we\u0027re filling the `container_q`\n\nYou could either do that \"inline\" from the \"fill container loop\" using a `reqsult_q.get(timeout)` OR move the result aggregation to a background thread.","commit_id":"f609f5d1f39cacef5155aece007e2bb9025f423f"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"cde00c2014896a39784ca5890a6ac2cdcbb1ea1d","unresolved":true,"context_lines":[{"line_number":305,"context_line":"            for task_dt, container_info in iter_relevant_containers("},{"line_number":306,"context_line":"                    self.args, ic, self.current_dt, self.logger):"},{"line_number":307,"context_line":"                container_q.put(container_info)"},{"line_number":308,"context_line":"                container_count +\u003d 1"},{"line_number":309,"context_line":""},{"line_number":310,"context_line":"        print(f\"Found {container_count} containers to process with \""},{"line_number":311,"context_line":"              f\"{self.args.listing_workers} workers\", file\u003dsys.stderr)"}],"source_content_type":"text/x-python","patch_set":2,"id":"fc020933_86b73c71","line":308,"in_reply_to":"9b260144_fc1c2d2c","updated":"2026-04-21 19:05:54.000000000","message":"Proposed fix in-place:\n- Moved aggregate_stats and _drain_results() above the container fill loop so they\u0027re available during filling.\n- Added _drain_results() call inside the fill loop -- after each container_q.put(), we non-blocking drain any available results. This keeps result_q from filling up while the main process is still enqueuing containers.\n- Replaced the final \"drain remaining\" block with a single _drain_results() call to avoid duplicating the logic.","commit_id":"f609f5d1f39cacef5155aece007e2bb9025f423f"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"d996ce71709b3b82e5ede40cc51e1ccd737f8ad4","unresolved":true,"context_lines":[{"line_number":305,"context_line":"            for task_dt, container_info in iter_relevant_containers("},{"line_number":306,"context_line":"                    self.args, ic, self.current_dt, self.logger):"},{"line_number":307,"context_line":"                container_q.put(container_info)"},{"line_number":308,"context_line":"                container_count +\u003d 1"},{"line_number":309,"context_line":""},{"line_number":310,"context_line":"        print(f\"Found {container_count} containers to process with \""},{"line_number":311,"context_line":"              f\"{self.args.listing_workers} workers\", file\u003dsys.stderr)"}],"source_content_type":"text/x-python","patch_set":2,"id":"9b260144_fc1c2d2c","line":308,"in_reply_to":"d38ef633_71bb0962","updated":"2026-04-21 18:21:56.000000000","message":"Deadlock Scenario:\n\n- container_q is a SimpleQueue (unbounded) -- so filling it won\u0027t block. That part is fine.\n- result_q is a Queue(maxsize\u003d...) -- it will block workers on result_q.put() when full.\n- The main process fills container_q entirely (lines 313-316), then sends EXIT sentinels (321-322), then starts draining result_q (329+).\n\nThe deadlock: if workers process containers fast and result_q fills up before the main process finishes the iter_relevant_containers loop, workers block on result_q.put(stats) (line 185 in _listing_worker_details). But the main process is stuck in the container loop and hasn\u0027t started draining result_q yet. Nobody makes progress.\n\nWith _result_depth \u003d max(listing_workers * 4, 16) and thousands/millions of containers.","commit_id":"f609f5d1f39cacef5155aece007e2bb9025f423f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"03d6bcbafc86eb1e33a873236b4bb6e8f971f234","unresolved":true,"context_lines":[{"line_number":358,"context_line":"                break"},{"line_number":359,"context_line":""},{"line_number":360,"context_line":"        print(f\"\\nCompleted: processed {results_received} containers\","},{"line_number":361,"context_line":"              file\u003dsys.stderr)"},{"line_number":362,"context_line":""},{"line_number":363,"context_line":"        # Output metrics"},{"line_number":364,"context_line":"        for (stat_type, account, container, days,"}],"source_content_type":"text/x-python","patch_set":2,"id":"f2c49580_d255a5b5","line":361,"updated":"2026-04-17 18:04:55.000000000","message":"I think printing the prom metrics to stdout correctly elevates the *primary* usage of the tool for machine communication using prometheus metrics and the \"status/info messages\" printed to stderr are a secondary non-functional requirement for human consumption.\n\nIF you used logging.info() w/ logging.basicConfig these \"status/info messages\" would go to stderr by default - and it *might* be easier to maintain the pattern/convention and not accidently add a `print()` to stdout that breaks prometheus parsing.","commit_id":"f609f5d1f39cacef5155aece007e2bb9025f423f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"03d6bcbafc86eb1e33a873236b4bb6e8f971f234","unresolved":true,"context_lines":[{"line_number":414,"context_line":"    details_parser.add_argument(\"--num-days\","},{"line_number":415,"context_line":"                                type\u003dint, default\u003d10,"},{"line_number":416,"context_line":"                                help\u003d\"Number of days to scan from \""},{"line_number":417,"context_line":"                                     \"start-day-offset\")"},{"line_number":418,"context_line":"    details_parser.add_argument(\"--listing-workers\","},{"line_number":419,"context_line":"                                type\u003dint, default\u003d20)"},{"line_number":420,"context_line":"    details_parser.add_argument(\"--verify-bytes\","}],"source_content_type":"text/x-python","patch_set":2,"id":"ba352e05_57efe1df","line":417,"updated":"2026-04-17 18:04:55.000000000","message":"my gut is that the \"window\" for evaulation defaults should be `(-3, 6)` - basically 3 days on either side of \"now\"\n\nThe reason I don\u0027t *love* `(-10, 10)` is that in a healthy cluster not using grace this should evaluate to essentially \"empty\" - or at most: \"stuff to be expired *today*\n\nIf we default to a small window on either side of now you should see *something* and then you can decide based on the output which way you want to slide the window (or increase it\u0027s size)","commit_id":"f609f5d1f39cacef5155aece007e2bb9025f423f"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"d996ce71709b3b82e5ede40cc51e1ccd737f8ad4","unresolved":false,"context_lines":[{"line_number":414,"context_line":"    details_parser.add_argument(\"--num-days\","},{"line_number":415,"context_line":"                                type\u003dint, default\u003d10,"},{"line_number":416,"context_line":"                                help\u003d\"Number of days to scan from \""},{"line_number":417,"context_line":"                                     \"start-day-offset\")"},{"line_number":418,"context_line":"    details_parser.add_argument(\"--listing-workers\","},{"line_number":419,"context_line":"                                type\u003dint, default\u003d20)"},{"line_number":420,"context_line":"    details_parser.add_argument(\"--verify-bytes\","}],"source_content_type":"text/x-python","patch_set":2,"id":"d8f79e50_f102b64a","line":417,"in_reply_to":"ba352e05_57efe1df","updated":"2026-04-21 18:21:56.000000000","message":"Done","commit_id":"f609f5d1f39cacef5155aece007e2bb9025f423f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"03d6bcbafc86eb1e33a873236b4bb6e8f971f234","unresolved":true,"context_lines":[{"line_number":420,"context_line":"    details_parser.add_argument(\"--verify-bytes\","},{"line_number":421,"context_line":"                                action\u003d\"store_true\","},{"line_number":422,"context_line":"                                help\u003d\"Use HEAD requests to verify object size\""},{"line_number":423,"context_line":"                                     \" if content_type lacks bytes\")"},{"line_number":424,"context_line":"    details_parser.add_argument(\"-v\", \"--verbose\","},{"line_number":425,"context_line":"                                action\u003d\"count\", default\u003d0)"},{"line_number":426,"context_line":""}],"source_content_type":"text/x-python","patch_set":2,"id":"0232ddbb_66b5922d","line":423,"updated":"2026-04-17 18:04:55.000000000","message":"to me it was ambiguous which part of the description the \"if\" applied to -since the HEAD is unconditional might say:\n\n\u003e make head request on all expiring objects to report extra metric `{prefix}_bytes_verified`, can be useful if not all queue entries have bytes in the content_type and the value reported as `{prefix}_bytes` is suspect","commit_id":"f609f5d1f39cacef5155aece007e2bb9025f423f"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"d996ce71709b3b82e5ede40cc51e1ccd737f8ad4","unresolved":false,"context_lines":[{"line_number":420,"context_line":"    details_parser.add_argument(\"--verify-bytes\","},{"line_number":421,"context_line":"                                action\u003d\"store_true\","},{"line_number":422,"context_line":"                                help\u003d\"Use HEAD requests to verify object size\""},{"line_number":423,"context_line":"                                     \" if content_type lacks bytes\")"},{"line_number":424,"context_line":"    details_parser.add_argument(\"-v\", \"--verbose\","},{"line_number":425,"context_line":"                                action\u003d\"count\", default\u003d0)"},{"line_number":426,"context_line":""}],"source_content_type":"text/x-python","patch_set":2,"id":"05670506_c3045801","line":423,"in_reply_to":"0232ddbb_66b5922d","updated":"2026-04-21 18:21:56.000000000","message":"Done","commit_id":"f609f5d1f39cacef5155aece007e2bb9025f423f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":81,"context_line":"            continue"},{"line_number":82,"context_line":"        ts \u003d int(name)"},{"line_number":83,"context_line":"        task_dt \u003d datetime.datetime.fromtimestamp("},{"line_number":84,"context_line":"            ts + args.expirer_conf.task_container_per_day"},{"line_number":85,"context_line":"        )"},{"line_number":86,"context_line":"        if task_dt \u003c start_cutoff:"},{"line_number":87,"context_line":"            continue"}],"source_content_type":"text/x-python","patch_set":11,"id":"a31185b7_86fa49b1","line":84,"updated":"2026-04-21 22:53:11.000000000","message":"FWIW I don\u0027t think `task_containers_per_day` is supposed to be configurable yet.\n\nI think this \"fromtimestamp + num_containers_per_day\" is trying to account for how we \"spin backwards\" when assigning expiration buckets:\n\n```\nvagrant@saio:~$ curl http://saio:8090/v1/.expiring_objects\n1776383905\n1776383908\n1776383954\n1776383959\n1776383962\n1776470335\n1776470352\nvagrant@saio:~$ date -d @1776383905\nThu Apr 16 11:58:25 PM UTC 2026\nvagrant@saio:~$ date -d @1776470352\nFri Apr 17 11:59:12 PM UTC 2026\nvagrant@saio:~$ date -d @1776383962\nThu Apr 16 11:59:22 PM UTC 2026\n```\n\ni.e. if a object expires on 4/16 it can get put in any of the buckets `1776297600` down to `1776211201`\n\nSo I think what you want to do is \"normalize\" to \"the day\"\n\n```\n\u003e\u003e\u003e date.fromtimestamp(1776383905 + 100)\ndatetime.date(2026, 4, 17)\n\u003e\u003e\u003e date.fromtimestamp(1776383905 + 10000)\ndatetime.date(2026, 4, 17)\n```\n\nThen when you calculate offset:\n\n```\noffset \u003d (task_date - self.current_date).days\n```\n\nfor \"expiring in 60 seconds object\" that gives me `offset\u003d0`\n\n```\n\u003e\u003e\u003e (datetime.date.fromtimestamp(1776729574 + 1000) - datetime.datetime.now().date()).days\n0\n```\n\n^ I think this makes sense because the expiration of `X-Delete-At: 1776803327` will always put it in a container \u003c\u003d `1776729600`\n\nI think the only real confusion might come up with `datetime.date.today()` WRT to UTC - definitely worth testing carefully to make sure `offset` is consistently calling `offset\u003d0` \"expires same day UTC\" and `offset\u003d1` not so much \"expires in \u003e\u003d24h\" but rather \"expires after UTC midnight today\"\n\nI think `offset\u003d-1` should consistently mean \"expired \u003e24 hours ago\"\n\nAlso consider the labels of `days` vs `offset` (I\u0027m not sure which would be more consistently obvious across the code and metrics) and consider annotating the details results with `grace` or `delay` based on the expirer configuration:\n\nhttps://docs.openstack.org/swift/2025.2/overview_expiring_objects.html#delay-reaping-of-objects-from-disk","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"5ae2dcd20b1ee92b15085121e1153e48c6df7e83","unresolved":true,"context_lines":[{"line_number":81,"context_line":"            continue"},{"line_number":82,"context_line":"        ts \u003d int(name)"},{"line_number":83,"context_line":"        task_dt \u003d datetime.datetime.fromtimestamp("},{"line_number":84,"context_line":"            ts + args.expirer_conf.task_container_per_day"},{"line_number":85,"context_line":"        )"},{"line_number":86,"context_line":"        if task_dt \u003c start_cutoff:"},{"line_number":87,"context_line":"            continue"}],"source_content_type":"text/x-python","patch_set":11,"id":"a9fbd866_dd273f3d","line":84,"in_reply_to":"a31185b7_86fa49b1","updated":"2026-05-06 22:46:43.000000000","message":"Semantics now match what you asked:\n\n- offset \u003d 0 → \"expires today (UTC)\"\n- offset \u003d 1 → \"expires after midnight UTC tonight\" (not \"\u003e\u003d 24h from now\")\n- offset \u003d -1 → \"expired more than 24h ago\"","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"038ce20786627c886cf2bf669fe78c65c3c68446","unresolved":false,"context_lines":[{"line_number":81,"context_line":"            continue"},{"line_number":82,"context_line":"        ts \u003d int(name)"},{"line_number":83,"context_line":"        task_dt \u003d datetime.datetime.fromtimestamp("},{"line_number":84,"context_line":"            ts + args.expirer_conf.task_container_per_day"},{"line_number":85,"context_line":"        )"},{"line_number":86,"context_line":"        if task_dt \u003c start_cutoff:"},{"line_number":87,"context_line":"            continue"}],"source_content_type":"text/x-python","patch_set":11,"id":"afc52a72_66ab5b04","line":84,"in_reply_to":"a9fbd866_dd273f3d","updated":"2026-05-06 22:47:34.000000000","message":"Done","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":154,"context_line":"            try:"},{"line_number":155,"context_line":"                # Batch object processing for better performance"},{"line_number":156,"context_line":"                object_batch \u003d []"},{"line_number":157,"context_line":"                batch_size \u003d 100  # Process 100 objects at a time"},{"line_number":158,"context_line":""},{"line_number":159,"context_line":"                for obj in ic.iter_objects(EXPIRING_ACCOUNT, container):"},{"line_number":160,"context_line":"                    object_batch.append(obj)"}],"source_content_type":"text/x-python","patch_set":11,"id":"04f3dddd_ae17dbad","line":157,"updated":"2026-04-21 22:53:11.000000000","message":"what is the point of this batching?  since `iter_objects` returns the objects one-at-a-time and abstracts all the pagination/continuation we can just process them into the aggregated stats as they come.","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"5ae2dcd20b1ee92b15085121e1153e48c6df7e83","unresolved":true,"context_lines":[{"line_number":154,"context_line":"            try:"},{"line_number":155,"context_line":"                # Batch object processing for better performance"},{"line_number":156,"context_line":"                object_batch \u003d []"},{"line_number":157,"context_line":"                batch_size \u003d 100  # Process 100 objects at a time"},{"line_number":158,"context_line":""},{"line_number":159,"context_line":"                for obj in ic.iter_objects(EXPIRING_ACCOUNT, container):"},{"line_number":160,"context_line":"                    object_batch.append(obj)"}],"source_content_type":"text/x-python","patch_set":11,"id":"c9858baf_a1841a9d","line":157,"in_reply_to":"04f3dddd_ae17dbad","updated":"2026-05-06 22:46:43.000000000","message":"Done:\n\n- Removed _process_object_batch (and its accumulation/flush dance in _listing_worker_details).\n- Added _process_object — same per-object logic, takes a single obj instead of a list.\n- _listing_worker_details now streams: for obj in ic.iter_objects(...): _process_object(obj, ...). No buffering.","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"d320070424256f2ce4408dc51c253b3141a0ec09","unresolved":false,"context_lines":[{"line_number":154,"context_line":"            try:"},{"line_number":155,"context_line":"                # Batch object processing for better performance"},{"line_number":156,"context_line":"                object_batch \u003d []"},{"line_number":157,"context_line":"                batch_size \u003d 100  # Process 100 objects at a time"},{"line_number":158,"context_line":""},{"line_number":159,"context_line":"                for obj in ic.iter_objects(EXPIRING_ACCOUNT, container):"},{"line_number":160,"context_line":"                    object_batch.append(obj)"}],"source_content_type":"text/x-python","patch_set":11,"id":"9043b589_cfcb3c2b","line":157,"in_reply_to":"c9858baf_a1841a9d","updated":"2026-05-06 22:47:44.000000000","message":"Acknowledged","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":231,"context_line":"        self.args \u003d args"},{"line_number":232,"context_line":"        self.hostname \u003d socket.gethostname()"},{"line_number":233,"context_line":"        self.current_dt \u003d datetime.datetime.now()"},{"line_number":234,"context_line":"        self.args.current_dt \u003d self.current_dt"},{"line_number":235,"context_line":"        conf \u003d readconf(args.expirer_conf_path, section_name\u003d\"object-expirer\")"},{"line_number":236,"context_line":"        delay_reaping_times \u003d read_conf_for_delay_reaping_times(conf)"},{"line_number":237,"context_line":"        self.expirer_conf \u003d ExpirerConfig(conf)"}],"source_content_type":"text/x-python","patch_set":11,"id":"de97e9d6_27ba5337","line":234,"updated":"2026-04-21 22:53:11.000000000","message":"does arg need this?\n\n```\ndiff --git a/swift/cli/manage_expirer.py b/swift/cli/manage_expirer.py\nindex 898b3e6257..5aa850bde4 100644\n--- a/swift/cli/manage_expirer.py\n+++ b/swift/cli/manage_expirer.py\n@@ -231,7 +231,6 @@ class ExpirerStatus:\n         self.args \u003d args\n         self.hostname \u003d socket.gethostname()\n         self.current_dt \u003d datetime.datetime.now()\n-        self.args.current_dt \u003d self.current_dt\n         conf \u003d readconf(args.expirer_conf_path, section_name\u003d\"object-expirer\")\n         delay_reaping_times \u003d read_conf_for_delay_reaping_times(conf)\n         self.expirer_conf \u003d ExpirerConfig(conf)\n```\n\n^ tests pass","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"5ae2dcd20b1ee92b15085121e1153e48c6df7e83","unresolved":false,"context_lines":[{"line_number":231,"context_line":"        self.args \u003d args"},{"line_number":232,"context_line":"        self.hostname \u003d socket.gethostname()"},{"line_number":233,"context_line":"        self.current_dt \u003d datetime.datetime.now()"},{"line_number":234,"context_line":"        self.args.current_dt \u003d self.current_dt"},{"line_number":235,"context_line":"        conf \u003d readconf(args.expirer_conf_path, section_name\u003d\"object-expirer\")"},{"line_number":236,"context_line":"        delay_reaping_times \u003d read_conf_for_delay_reaping_times(conf)"},{"line_number":237,"context_line":"        self.expirer_conf \u003d ExpirerConfig(conf)"}],"source_content_type":"text/x-python","patch_set":11,"id":"1dc76c49_e783e7ae","line":234,"in_reply_to":"de97e9d6_27ba5337","updated":"2026-05-06 22:46:43.000000000","message":"Done","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"16b5e48c476381088a575cb139234a676fc7876c","unresolved":false,"context_lines":[{"line_number":231,"context_line":"        self.args \u003d args"},{"line_number":232,"context_line":"        self.hostname \u003d socket.gethostname()"},{"line_number":233,"context_line":"        self.current_dt \u003d datetime.datetime.now()"},{"line_number":234,"context_line":"        self.args.current_dt \u003d self.current_dt"},{"line_number":235,"context_line":"        conf \u003d readconf(args.expirer_conf_path, section_name\u003d\"object-expirer\")"},{"line_number":236,"context_line":"        delay_reaping_times \u003d read_conf_for_delay_reaping_times(conf)"},{"line_number":237,"context_line":"        self.expirer_conf \u003d ExpirerConfig(conf)"}],"source_content_type":"text/x-python","patch_set":11,"id":"1e81698b_4ce1d999","line":234,"in_reply_to":"de97e9d6_27ba5337","updated":"2026-05-06 22:47:09.000000000","message":"Done","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"5ae2dcd20b1ee92b15085121e1153e48c6df7e83","unresolved":false,"context_lines":[{"line_number":231,"context_line":"        self.args \u003d args"},{"line_number":232,"context_line":"        self.hostname \u003d socket.gethostname()"},{"line_number":233,"context_line":"        self.current_dt \u003d datetime.datetime.now()"},{"line_number":234,"context_line":"        self.args.current_dt \u003d self.current_dt"},{"line_number":235,"context_line":"        conf \u003d readconf(args.expirer_conf_path, section_name\u003d\"object-expirer\")"},{"line_number":236,"context_line":"        delay_reaping_times \u003d read_conf_for_delay_reaping_times(conf)"},{"line_number":237,"context_line":"        self.expirer_conf \u003d ExpirerConfig(conf)"}],"source_content_type":"text/x-python","patch_set":11,"id":"97165963_539ae8c0","line":234,"in_reply_to":"de97e9d6_27ba5337","updated":"2026-05-06 22:46:43.000000000","message":"Done","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":241,"context_line":"        self.logger \u003d get_logger("},{"line_number":242,"context_line":"            conf,"},{"line_number":243,"context_line":"            name\u003d\"swift-manage-expirer-status\","},{"line_number":244,"context_line":"            log_route\u003d\"object-expirer\")"},{"line_number":245,"context_line":""},{"line_number":246,"context_line":"    def run_summary(self):"},{"line_number":247,"context_line":"        \"\"\""}],"source_content_type":"text/x-python","patch_set":11,"id":"24656e6a_545836ab","line":244,"updated":"2026-04-21 22:53:11.000000000","message":"This may not be what we want for a cli tool\n\nSwift logging is designed fro daemons, it\u0027s typically going to send messages to a log file.  For an \"interactive cli too\" - it\u0027s probably better to output to stderr\n\n... except we expect one of the common uses of s-m-e to actually be running in a cron to dump prom metrics, so the logger might be good.\n\nAlso there\u0027s the problem of serializing writing to stderr from multiple workers.\n\nAt a minimum I think it\u0027d be nice if there was some way for tests to override this with a debug_logger:\n\n```\nself.logger \u003d args.logger or get_logger(...)\n```","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":298,"context_line":""},{"line_number":299,"context_line":"        workers \u003d []"},{"line_number":300,"context_line":"        for i in range(self.args.listing_workers):"},{"line_number":301,"context_line":"            p \u003d mp.Process("},{"line_number":302,"context_line":"                name\u003df\"details-worker-{i}\","},{"line_number":303,"context_line":"                target\u003d_listing_worker_details,"},{"line_number":304,"context_line":"                args\u003d(container_q, result_q, self.args,"}],"source_content_type":"text/x-python","patch_set":11,"id":"72a17eee_ee87c3e6","line":301,"updated":"2026-04-21 22:53:11.000000000","message":"I\u0027m probably going to regret this b/c\n\n\u003e Special cases aren\u0027t special enough to break the rules.\n\n... but for test-ability I think it could be very powerful to have `--workers\u003d0` trigger an \"inline mode\" - where instead of farming out listing to workers via queue you connect `iter_containers` directly to the main business logic entry-point such that a new `_listing_detail_stats` always does essentially:\n\n```\nstats \u003d {}\nfor c_info in magic_container_iter:\n    for obj in ic.iter_obejcts(c_info[\u0027name\u0027]):\n        _udpate_stats(stats, obj)\nreturn stats\n```\n\nThen then mp \"glue\" is just:\n\n```\ndef magic_queue_iter():\n    while True:\n        c_info \u003d q.get()\n        if c_info \u003d\u003d EXIT:\n            raise StopIteration()\n            \ndef _worker_listing_details(q, result_q):\n    stats \u003d _listing_detail_stats(magic_queue_iter(q))\n    result_q.put(stats)\n```\n\nwe do this \"inline the single worker case\" tick in other places in swift b/c it helps a lot with testing and debugging and ultimately:\n\n\u003e Special cases aren\u0027t special enough to break the rules.\n\u003e Although practicality beats purity.","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"}],"test/probe/test_manage_expirer.py":[{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"aa15643ed16f48f69ae25f1eeb8b929dd2436d80","unresolved":true,"context_lines":[{"line_number":128,"context_line":"                cmd_args,"},{"line_number":129,"context_line":"                stderr\u003dsubprocess.STDOUT,"},{"line_number":130,"context_line":"            )"},{"line_number":131,"context_line":"        except subprocess.TimeoutExpired:"},{"line_number":132,"context_line":"            self.fail(\u0027swift-manage-expirer command timed out\u0027)"},{"line_number":133,"context_line":"        except Exception as exc:"},{"line_number":134,"context_line":"            try:"}],"source_content_type":"text/x-python","patch_set":1,"id":"b65bafd2_f8540d85","line":131,"updated":"2026-03-22 22:05:05.000000000","message":"Don\u0027t we need to to specify a timeout if we hope to catch `TimeoutExpired`?","commit_id":"2ddf9dbbdaabc3feb0649b1bd62228a08e8d60e4"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"aa15643ed16f48f69ae25f1eeb8b929dd2436d80","unresolved":true,"context_lines":[{"line_number":159,"context_line":""},{"line_number":160,"context_line":"        stdout_str \u003d stdout.decode(\u0027utf-8\u0027, \u0027replace\u0027)"},{"line_number":161,"context_line":"        # Verify output contains expected metrics"},{"line_number":162,"context_line":"        self.assertTrue(\u0027total_expirer_containers\u0027 in stdout_str,"},{"line_number":163,"context_line":"                        \u0027total_expirer_containers not found in output\u0027)"},{"line_number":164,"context_line":"        self.assertTrue(\u0027total_expirer_entries\u0027 in stdout_str,"},{"line_number":165,"context_line":"                        \u0027total_expirer_entries not found in output\u0027)"}],"source_content_type":"text/x-python","patch_set":1,"id":"3e5d6c19_eb246952","line":162,"updated":"2026-03-22 22:05:05.000000000","message":"`self.assertIn`? Here and elsewhere.","commit_id":"2ddf9dbbdaabc3feb0649b1bd62228a08e8d60e4"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f1c39b1bb5f995670271aba8a24ba76635d0a742","unresolved":true,"context_lines":[{"line_number":159,"context_line":""},{"line_number":160,"context_line":"        stdout_str \u003d stdout.decode(\u0027utf-8\u0027, \u0027replace\u0027)"},{"line_number":161,"context_line":"        # Verify output contains expected metrics"},{"line_number":162,"context_line":"        self.assertTrue(\u0027total_expirer_containers\u0027 in stdout_str,"},{"line_number":163,"context_line":"                        \u0027total_expirer_containers not found in output\u0027)"},{"line_number":164,"context_line":"        self.assertTrue(\u0027total_expirer_entries\u0027 in stdout_str,"},{"line_number":165,"context_line":"                        \u0027total_expirer_entries not found in output\u0027)"}],"source_content_type":"text/x-python","patch_set":1,"id":"c82d7110_ac4729ff","line":162,"in_reply_to":"3e5d6c19_eb246952","updated":"2026-03-31 18:49:10.000000000","message":"I\u0027d prefer we use a helper to parse the prometheus metrics into structured data and assert equality:\n\n```\nself.assertEqual({\n    metrics_key(\u0027name\u0027, label\u003d\u0027value\u0027): count,\n    ...\n}, parse(stdout_str))\n```\n\nI think you could use:\n\n975948: add prom metrics test helpers | https://review.opendev.org/c/openstack/swift/+/975948","commit_id":"2ddf9dbbdaabc3feb0649b1bd62228a08e8d60e4"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"d996ce71709b3b82e5ede40cc51e1ccd737f8ad4","unresolved":false,"context_lines":[{"line_number":159,"context_line":""},{"line_number":160,"context_line":"        stdout_str \u003d stdout.decode(\u0027utf-8\u0027, \u0027replace\u0027)"},{"line_number":161,"context_line":"        # Verify output contains expected metrics"},{"line_number":162,"context_line":"        self.assertTrue(\u0027total_expirer_containers\u0027 in stdout_str,"},{"line_number":163,"context_line":"                        \u0027total_expirer_containers not found in output\u0027)"},{"line_number":164,"context_line":"        self.assertTrue(\u0027total_expirer_entries\u0027 in stdout_str,"},{"line_number":165,"context_line":"                        \u0027total_expirer_entries not found in output\u0027)"}],"source_content_type":"text/x-python","patch_set":1,"id":"a8e6fea2_c0f35590","line":162,"in_reply_to":"c82d7110_ac4729ff","updated":"2026-04-21 18:21:56.000000000","message":"Done","commit_id":"2ddf9dbbdaabc3feb0649b1bd62228a08e8d60e4"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"03d6bcbafc86eb1e33a873236b4bb6e8f971f234","unresolved":true,"context_lines":[{"line_number":143,"context_line":"            try:"},{"line_number":144,"context_line":"                output \u003d exc.stdout + b\u0027\\n\u0027 + exc.stderr"},{"line_number":145,"context_line":"            except AttributeError:"},{"line_number":146,"context_line":"                raise"},{"line_number":147,"context_line":"            self.fail(\u0027%s with output:\\n%s\u0027 % (exc, output))"},{"line_number":148,"context_line":""},{"line_number":149,"context_line":"    def _swift_manage_expirer_cmd(self, subcommand, *extra_args):"}],"source_content_type":"text/x-python","patch_set":7,"id":"2adb4764_4315c276","line":146,"updated":"2026-04-17 18:04:55.000000000","message":"i tried to test this but hadn\u0027t run `reinstallswift` yet and got a bug here:\n\n```\n\u003e               output \u003d exc.stdout + b\u0027\\n\u0027 + exc.stderr\nE               AttributeError: \u0027FileNotFoundError\u0027 object has no attribute \u0027stdout\u0027\n```\n\nbasically some `exc` won\u0027t have a stdout attribute; I think it would be better if you could re-raise the *original* exception; I think you have use some sys module to capture it.","commit_id":"3b10df5ce1f7e0997e98e905386bf79561a41f2d"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":71,"context_line":"        try:"},{"line_number":72,"context_line":"            client.delete_container(self.url, self.token, self.container_name2)"},{"line_number":73,"context_line":"        except Exception:"},{"line_number":74,"context_line":"            pass"},{"line_number":75,"context_line":"        super(TestManageExpirer, self).tearDown()"},{"line_number":76,"context_line":""},{"line_number":77,"context_line":"    def _create_expiring_objects(self, count\u003d5, delete_after\u003d10):"}],"source_content_type":"text/x-python","patch_set":11,"id":"13830f1e_bf25082d","line":74,"updated":"2026-04-21 22:53:11.000000000","message":"don\u0027t do this - probetests call `resetswift` on start up to ensure they get a fresh start - so when theu fail/run/exit they should leave everything as-in in a state you can inspect and probe.\n\nAlso these always have objects in them so this always fails\n\n```\nvagrant@saio:~$ swift list\nexpirer-test-d19e900b-f976-44cf-ad3a-d24e1e254a98\nexpirer-test2-9433d7f8-398b-42f1-8344-51adb5b2988e\n```","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":89,"context_line":"        delete_at \u003d int(now + delete_after)"},{"line_number":90,"context_line":""},{"line_number":91,"context_line":"        # Create objects in first container"},{"line_number":92,"context_line":"        for i in range(count):"},{"line_number":93,"context_line":"            obj_name \u003d \u0027object-%d-%s\u0027 % (i, uuid.uuid4())"},{"line_number":94,"context_line":"            client.put_object("},{"line_number":95,"context_line":"                self.url, self.token, self.container_name, obj_name,"}],"source_content_type":"text/x-python","patch_set":11,"id":"ff271e6c_9d184a76","line":92,"updated":"2026-04-21 22:53:11.000000000","message":"oic, so count \u0026 delete_after work together","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"86b16ba422ce5fb48bcede7145d9296aa226a806","unresolved":false,"context_lines":[{"line_number":89,"context_line":"        delete_at \u003d int(now + delete_after)"},{"line_number":90,"context_line":""},{"line_number":91,"context_line":"        # Create objects in first container"},{"line_number":92,"context_line":"        for i in range(count):"},{"line_number":93,"context_line":"            obj_name \u003d \u0027object-%d-%s\u0027 % (i, uuid.uuid4())"},{"line_number":94,"context_line":"            client.put_object("},{"line_number":95,"context_line":"                self.url, self.token, self.container_name, obj_name,"}],"source_content_type":"text/x-python","patch_set":11,"id":"f5ef4f54_527efc09","line":92,"in_reply_to":"ff271e6c_9d184a76","updated":"2026-05-06 23:48:50.000000000","message":"Acknowledged","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":103,"context_line":""},{"line_number":104,"context_line":"        # Create a couple in second container with different delete time"},{"line_number":105,"context_line":"        delete_at2 \u003d delete_at + 86400  # +1 day"},{"line_number":106,"context_line":"        for i in range(2):"},{"line_number":107,"context_line":"            obj_name \u003d \u0027object2-%d-%s\u0027 % (i, uuid.uuid4())"},{"line_number":108,"context_line":"            client.put_object("},{"line_number":109,"context_line":"                self.url, self.token, self.container_name2, obj_name,"}],"source_content_type":"text/x-python","patch_set":11,"id":"5d621f38_a498904f","line":106,"updated":"2026-04-21 22:53:11.000000000","message":"but the helper also has a fixed \"couple in second container with different delete time\"","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":190,"context_line":"                       host\u003d\u0027saio\u0027): 7.0,"},{"line_number":191,"context_line":"            metric_key(\u0027swift_manage_expirer_processed_\u0027"},{"line_number":192,"context_line":"                       \u0027expirer_containers\u0027, host\u003d\u0027saio\u0027): 7.0,"},{"line_number":193,"context_line":"            metric_key(\u0027swift_manage_expirer_expirer_task_\u0027"},{"line_number":194,"context_line":"                       \u0027container_objects\u0027, host\u003d\u0027saio\u0027, days\u003d\u0027-1\u0027): 5.0,"},{"line_number":195,"context_line":"            metric_key(\u0027swift_manage_expirer_expirer_task_\u0027"},{"line_number":196,"context_line":"                       \u0027container_objects\u0027, host\u003d\u0027saio\u0027, days\u003d\u00270\u0027): 2.0,"}],"source_content_type":"text/x-python","patch_set":11,"id":"96f697fa_858940ad","line":193,"updated":"2026-04-21 22:53:11.000000000","message":"LOL `manage_expirer_expirer`","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":191,"context_line":"            metric_key(\u0027swift_manage_expirer_processed_\u0027"},{"line_number":192,"context_line":"                       \u0027expirer_containers\u0027, host\u003d\u0027saio\u0027): 7.0,"},{"line_number":193,"context_line":"            metric_key(\u0027swift_manage_expirer_expirer_task_\u0027"},{"line_number":194,"context_line":"                       \u0027container_objects\u0027, host\u003d\u0027saio\u0027, days\u003d\u0027-1\u0027): 5.0,"},{"line_number":195,"context_line":"            metric_key(\u0027swift_manage_expirer_expirer_task_\u0027"},{"line_number":196,"context_line":"                       \u0027container_objects\u0027, host\u003d\u0027saio\u0027, days\u003d\u00270\u0027): 2.0,"},{"line_number":197,"context_line":"        }, metrics)"}],"source_content_type":"text/x-python","patch_set":11,"id":"b207f34b_4b18eb74","line":194,"updated":"2026-04-21 22:53:11.000000000","message":"I don\u0027t think I understand why objects that expire 60s in the future are `days\u003d-1`","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":193,"context_line":"            metric_key(\u0027swift_manage_expirer_expirer_task_\u0027"},{"line_number":194,"context_line":"                       \u0027container_objects\u0027, host\u003d\u0027saio\u0027, days\u003d\u0027-1\u0027): 5.0,"},{"line_number":195,"context_line":"            metric_key(\u0027swift_manage_expirer_expirer_task_\u0027"},{"line_number":196,"context_line":"                       \u0027container_objects\u0027, host\u003d\u0027saio\u0027, days\u003d\u00270\u0027): 2.0,"},{"line_number":197,"context_line":"        }, metrics)"},{"line_number":198,"context_line":""},{"line_number":199,"context_line":"        # Stable assertion by day bucket, without relying on container names"}],"source_content_type":"text/x-python","patch_set":11,"id":"c6cb76cb_a6bc1266","line":196,"updated":"2026-04-21 22:53:11.000000000","message":"I think 5/2 make sense b/c we call `_create_expiring_objects(count\u003d5, delete_after\u003d60)` (and it always makes \"two extra\" for \"tomorrow\")","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":205,"context_line":"        }"},{"line_number":206,"context_line":""},{"line_number":207,"context_line":"        self.assertEqual(per_day_entries[\u00270\u0027], 2.0)"},{"line_number":208,"context_line":"        self.assertEqual(per_day_entries[\u0027-1\u0027], 5.0)"},{"line_number":209,"context_line":""},{"line_number":210,"context_line":"    def test_summary_respects_day_window(self):"},{"line_number":211,"context_line":"        self._create_expiring_objects(count\u003d5, delete_after\u003d30)"}],"source_content_type":"text/x-python","patch_set":11,"id":"bf665ccd_4c97c088","line":208,"updated":"2026-04-21 22:53:11.000000000","message":"I think this essentially restates what we can already see from the literal assertion.","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"86b16ba422ce5fb48bcede7145d9296aa226a806","unresolved":false,"context_lines":[{"line_number":205,"context_line":"        }"},{"line_number":206,"context_line":""},{"line_number":207,"context_line":"        self.assertEqual(per_day_entries[\u00270\u0027], 2.0)"},{"line_number":208,"context_line":"        self.assertEqual(per_day_entries[\u0027-1\u0027], 5.0)"},{"line_number":209,"context_line":""},{"line_number":210,"context_line":"    def test_summary_respects_day_window(self):"},{"line_number":211,"context_line":"        self._create_expiring_objects(count\u003d5, delete_after\u003d30)"}],"source_content_type":"text/x-python","patch_set":11,"id":"8b1ffcba_929732ad","line":208,"in_reply_to":"bf665ccd_4c97c088","updated":"2026-05-06 23:48:50.000000000","message":"Done","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":316,"context_line":""},{"line_number":317,"context_line":"    def test_details_with_multiple_containers(self):"},{"line_number":318,"context_line":"        self._create_expiring_objects(count\u003d5, delete_after\u003d30)"},{"line_number":319,"context_line":"        self._create_expiring_objects(count\u003d15, delete_after\u003d60)"},{"line_number":320,"context_line":"        client.put_container(self.url, self.token, self.container_name)"},{"line_number":321,"context_line":""},{"line_number":322,"context_line":"        now \u003d time.time()"}],"source_content_type":"text/x-python","patch_set":11,"id":"2fe09c90_ce6b1a63","line":319,"updated":"2026-04-21 22:53:11.000000000","message":"FWIW IMHO I think both of these objects should register as `days\u003d0` (i.e. they expire today) - i\u0027m not sure exactly when they should start reading as `days\u003d-1`\n\npresumably they wouldn\u0027t be `days\u003d-1` (i.e. \"late\") ... maybe not until the expirer gets a decent chance to actually expire them?","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":338,"context_line":"                \u0027details\u0027,"},{"line_number":339,"context_line":"                \u0027--start-day-offset\u0027, \u0027-1\u0027,"},{"line_number":340,"context_line":"                \u0027--num-days\u0027, \u00273\u0027,"},{"line_number":341,"context_line":"                \u0027--listing-workers\u0027, \u00272\u0027))"},{"line_number":342,"context_line":""},{"line_number":343,"context_line":"        stdout_str \u003d stdout.decode(\u0027utf-8\u0027, \u0027replace\u0027)"},{"line_number":344,"context_line":"        with tempfile.NamedTemporaryFile(mode\u003d\u0027w\u0027, suffix\u003d\u0027.prom\u0027,"}],"source_content_type":"text/x-python","patch_set":11,"id":"b73a27b1_4b9fef0e","line":341,"updated":"2026-04-21 22:53:11.000000000","message":"b/c it\u0027s so hard to do unittesting of mp code I think this probe test is really important - kudos!","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"86b16ba422ce5fb48bcede7145d9296aa226a806","unresolved":false,"context_lines":[{"line_number":338,"context_line":"                \u0027details\u0027,"},{"line_number":339,"context_line":"                \u0027--start-day-offset\u0027, \u0027-1\u0027,"},{"line_number":340,"context_line":"                \u0027--num-days\u0027, \u00273\u0027,"},{"line_number":341,"context_line":"                \u0027--listing-workers\u0027, \u00272\u0027))"},{"line_number":342,"context_line":""},{"line_number":343,"context_line":"        stdout_str \u003d stdout.decode(\u0027utf-8\u0027, \u0027replace\u0027)"},{"line_number":344,"context_line":"        with tempfile.NamedTemporaryFile(mode\u003d\u0027w\u0027, suffix\u003d\u0027.prom\u0027,"}],"source_content_type":"text/x-python","patch_set":11,"id":"8e44e446_85398fb1","line":341,"in_reply_to":"b73a27b1_4b9fef0e","updated":"2026-05-06 23:48:50.000000000","message":"Acknowledged","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":358,"context_line":"            metric_key(\u0027swift_manage_expirer_expiring_objects_bytes\u0027,"},{"line_number":359,"context_line":"                       host\u003d\u0027saio\u0027,"},{"line_number":360,"context_line":"                       account\u003dself.account, container\u003dself.container_name,"},{"line_number":361,"context_line":"                       days\u003d\u00270\u0027, grace\u003d\u00271\u0027): 305.0,"},{"line_number":362,"context_line":"            metric_key(\u0027swift_manage_expirer_expiring_objects_entries\u0027,"},{"line_number":363,"context_line":"                       host\u003d\u0027saio\u0027,"},{"line_number":364,"context_line":"                       account\u003dself.account, container\u003dself.container_name2,"}],"source_content_type":"text/x-python","patch_set":11,"id":"74e29a85_91457316","line":361,"updated":"2026-04-21 22:53:11.000000000","message":"why is this `305`\n\n`_create_expiring_objects` does\n\n```\ncontents\u003db\u0027test content %d\u0027 % i\n```\n\n^ we have 20 of those\n\nwe also did two `contents\u003db\u0027far future\u0027`\n\nwhoa!!!\n\n```\n\u003e\u003e\u003e len(\u0027\u0027.join(\u0027test content %d\u0027 % i for i in (list(range(5)) + list(range(15)))) + \u0027far future\u0027 * 2)\n305\n```","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":367,"context_line":"                       host\u003d\u0027saio\u0027,"},{"line_number":368,"context_line":"                       account\u003dself.account, container\u003dself.container_name2,"},{"line_number":369,"context_line":"                       days\u003d\u00271\u0027, grace\u003d\u00271\u0027): 64.0,"},{"line_number":370,"context_line":"        }, metrics)"},{"line_number":371,"context_line":""},{"line_number":372,"context_line":""},{"line_number":373,"context_line":"if __name__ \u003d\u003d \"__main__\":"}],"source_content_type":"text/x-python","patch_set":11,"id":"2c82e889_f3d42aa2","line":370,"updated":"2026-04-21 22:53:11.000000000","message":"Something smells off that these values change if I run `s-m-e` right after the test exits:\n\n```\n(Pdb) print(stdout_str)\nswift_manage_expirer_expiring_objects_entries{host\u003d\"saio\", account\u003d\"AUTH_test\", container\u003d\"expirer-test-aea65c85-d754-492c-b6d2-1fd8ce37e8b1\", days\u003d\"0\", grace\u003d\"1\"} 22\nswift_manage_expirer_expiring_objects_bytes{host\u003d\"saio\", account\u003d\"AUTH_test\", container\u003d\"expirer-test-aea65c85-d754-492c-b6d2-1fd8ce37e8b1\", days\u003d\"0\", grace\u003d\"1\"} 305\nswift_manage_expirer_expiring_objects_entries{host\u003d\"saio\", account\u003d\"AUTH_test\", container\u003d\"expirer-test2-e31641e8-4508-4a2d-952b-18f4440b18a3\", days\u003d\"1\", grace\u003d\"1\"} 4\nswift_manage_expirer_expiring_objects_bytes{host\u003d\"saio\", account\u003d\"AUTH_test\", container\u003d\"expirer-test2-e31641e8-4508-4a2d-952b-18f4440b18a3\", days\u003d\"1\", grace\u003d\"1\"} 64\n```\n\nwhat am I doing differently?\n\n```\nvagrant@saio:~$ swift-manage-expirer details --start-day-offset -1 --num-days 3 --listing-workers 2\nEnumerating expirer task containers...\nFound 24 containers to process with 2 workers\n\nCompleted: processed 24 containers\nswift_manage_expirer_expiring_objects_entries{host\u003d\"saio\", account\u003d\"AUTH_test\", container\u003d\"expirer-test-aea65c85-d754-492c-b6d2-1fd8ce37e8b1\", days\u003d\"-1\", grace\u003d\"0\"} 20\nswift_manage_expirer_expiring_objects_bytes{host\u003d\"saio\", account\u003d\"AUTH_test\", container\u003d\"expirer-test-aea65c85-d754-492c-b6d2-1fd8ce37e8b1\", days\u003d\"-1\", grace\u003d\"0\"} 285\nswift_manage_expirer_expiring_objects_entries{host\u003d\"saio\", account\u003d\"AUTH_test\", container\u003d\"expirer-test2-e31641e8-4508-4a2d-952b-18f4440b18a3\", days\u003d\"0\", grace\u003d\"1\"} 4\nswift_manage_expirer_expiring_objects_bytes{host\u003d\"saio\", account\u003d\"AUTH_test\", container\u003d\"expirer-test2-e31641e8-4508-4a2d-952b-18f4440b18a3\", days\u003d\"0\", grace\u003d\"1\"} 64\nswift_manage_expirer_expiring_objects_entries{host\u003d\"saio\", account\u003d\"AUTH_test\", container\u003d\"expirer-test-aea65c85-d754-492c-b6d2-1fd8ce37e8b1\", days\u003d\"0\", grace\u003d\"1\"} 2\nswift_manage_expirer_expiring_objects_bytes{host\u003d\"saio\", account\u003d\"AUTH_test\", container\u003d\"expirer-test-aea65c85-d754-492c-b6d2-1fd8ce37e8b1\", days\u003d\"0\", grace\u003d\"1\"} 20\n```\n\nah, it seems like it\u0027s the same 26 objects - the \"difference\" is if they are `days\u003d0` or `days\u003d-1`\n\n```\nvagrant@saio:~$ for c in $(curl -s http://saio:8090/v1/.expiring_objects); do curl -s http://saio:8090/v1/.expiring_objects/${c}; done\n1776811119-AUTH_test/expirer-test-aea65c85-d754-492c-b6d2-1fd8ce37e8b1/object-6-7659da15-9eaf-45aa-b9e1-460b2625d123\n1776811119-AUTH_test/expirer-test-aea65c85-d754-492c-b6d2-1fd8ce37e8b1/object-12-436e5588-188a-47e8-8c1a-56c1aaa0bf0b\n1776811119-AUTH_test/expirer-test-aea65c85-d754-492c-b6d2-1fd8ce37e8b1/object-2-e1173e6c-0c19-49dd-92f2-f9eb846437ae\n1776811119-AUTH_test/expirer-test-aea65c85-d754-492c-b6d2-1fd8ce37e8b1/object-13-8805b171-4f0b-4528-8ee4-4410f449f158\n1776811119-AUTH_test/expirer-test-aea65c85-d754-492c-b6d2-1fd8ce37e8b1/object-10-7a6bae83-e622-47eb-894f-db9b7be0d9fd\n1776811085-AUTH_test/expirer-test-aea65c85-d754-492c-b6d2-1fd8ce37e8b1/object-4-0c93cf77-3cbc-4c1f-b3e7-cad75728c4bd\n1776811119-AUTH_test/expirer-test-aea65c85-d754-492c-b6d2-1fd8ce37e8b1/object-11-5cccc21f-23bf-4e0d-876d-48183ec02862\n1776811119-AUTH_test/expirer-test-aea65c85-d754-492c-b6d2-1fd8ce37e8b1/object-9-5734f9f3-79ad-4a8e-9643-2a9d5b7c0a26\n1776811085-AUTH_test/expirer-test-aea65c85-d754-492c-b6d2-1fd8ce37e8b1/object-0-9fbe9f52-f4f6-4cc8-b62a-752421e2169d\n1776811119-AUTH_test/expirer-test-aea65c85-d754-492c-b6d2-1fd8ce37e8b1/object-1-c330414b-9ebe-4cff-ac72-3f0fb03991cc\n1776811085-AUTH_test/expirer-test-aea65c85-d754-492c-b6d2-1fd8ce37e8b1/object-1-0382ec57-ae49-4212-9016-a1f601a20c2d\n1776811119-AUTH_test/expirer-test-aea65c85-d754-492c-b6d2-1fd8ce37e8b1/object-14-deb50903-80e3-42d5-8f2d-01703d253e41\n1776811119-AUTH_test/expirer-test-aea65c85-d754-492c-b6d2-1fd8ce37e8b1/object-5-8638c936-f721-4f57-8188-7a313e6e59d5\n1776811085-AUTH_test/expirer-test-aea65c85-d754-492c-b6d2-1fd8ce37e8b1/object-2-16d02834-1944-4209-835c-5f4f9092f5e5\n1776811119-AUTH_test/expirer-test-aea65c85-d754-492c-b6d2-1fd8ce37e8b1/object-8-fbbd35f6-5fb5-434d-bd73-b4e624739760\n1776811119-AUTH_test/expirer-test-aea65c85-d754-492c-b6d2-1fd8ce37e8b1/object-3-7d695b98-7693-454b-b9bb-18615bbf307a\n1776811119-AUTH_test/expirer-test-aea65c85-d754-492c-b6d2-1fd8ce37e8b1/object-7-2878351c-7d0a-4e2c-83a2-1a1f1c2e692f\n1776811119-AUTH_test/expirer-test-aea65c85-d754-492c-b6d2-1fd8ce37e8b1/object-0-159f5540-e848-4dc7-9ea6-e69b393d7a60\n1776811119-AUTH_test/expirer-test-aea65c85-d754-492c-b6d2-1fd8ce37e8b1/object-4-1db3c432-93d4-459c-8067-ec29ac5bd355\n1776811085-AUTH_test/expirer-test-aea65c85-d754-492c-b6d2-1fd8ce37e8b1/object-3-42b65505-3c6c-48dd-922e-02428c03a431\n1776897485-AUTH_test/expirer-test2-e31641e8-4508-4a2d-952b-18f4440b18a3/object2-1-1916c36d-4ce7-4768-a23c-156dab970b3b\n1776897463-AUTH_test/expirer-test-aea65c85-d754-492c-b6d2-1fd8ce37e8b1/far-object-1\n1776897519-AUTH_test/expirer-test2-e31641e8-4508-4a2d-952b-18f4440b18a3/object2-1-895a685d-62d9-4d99-921a-f9372e5d627c\n1776897519-AUTH_test/expirer-test2-e31641e8-4508-4a2d-952b-18f4440b18a3/object2-0-06b57f05-3199-4c85-90fe-0d6161ab298e\n1776897463-AUTH_test/expirer-test-aea65c85-d754-492c-b6d2-1fd8ce37e8b1/far-object-0\n1776897485-AUTH_test/expirer-test2-e31641e8-4508-4a2d-952b-18f4440b18a3/object2-0-008bc793-ad8e-47d1-8ed0-82b32634a7a8\nvagrant@saio:~$ for c in $(curl -s http://saio:8090/v1/.expiring_objects); do curl -s http://saio:8090/v1/.expiring_objects/${c}; done | wc -l\n26\n```","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"}],"test/unit/cli/test_manage_expirer.py":[{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"aa15643ed16f48f69ae25f1eeb8b929dd2436d80","unresolved":true,"context_lines":[{"line_number":31,"context_line":"from test.debug_logger import debug_logger"},{"line_number":32,"context_line":""},{"line_number":33,"context_line":""},{"line_number":34,"context_line":"class FakeStoragePolicySwift(object):"},{"line_number":35,"context_line":"    def __init__(self):"},{"line_number":36,"context_line":"        self.storage_policy \u003d defaultdict("},{"line_number":37,"context_line":"            partial(helpers.FakeSwift, capture_unexpected_calls\u003dFalse))"}],"source_content_type":"text/x-python","patch_set":1,"id":"71ff19d0_986e404a","line":34,"updated":"2026-03-22 22:05:05.000000000","message":"Huh. Cribbed from `test/unit/container/test_reconciler.py` I guess...","commit_id":"2ddf9dbbdaabc3feb0649b1bd62228a08e8d60e4"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":31,"context_line":"from test.debug_logger import debug_logger"},{"line_number":32,"context_line":""},{"line_number":33,"context_line":""},{"line_number":34,"context_line":"class FakeStoragePolicySwift(object):"},{"line_number":35,"context_line":"    def __init__(self):"},{"line_number":36,"context_line":"        self.storage_policy \u003d defaultdict("},{"line_number":37,"context_line":"            partial(helpers.FakeSwift, capture_unexpected_calls\u003dFalse))"}],"source_content_type":"text/x-python","patch_set":1,"id":"c2ba0468_84e032c9","line":34,"in_reply_to":"521cc4cc_e609aee4","updated":"2026-04-21 22:53:11.000000000","message":"for now, get rid of it - it\u0027s not necessary for the way you\u0027re faking up ic","commit_id":"2ddf9dbbdaabc3feb0649b1bd62228a08e8d60e4"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"3df9d39c82c903ee5ec3be9e3a00a795b8e8a40f","unresolved":true,"context_lines":[{"line_number":31,"context_line":"from test.debug_logger import debug_logger"},{"line_number":32,"context_line":""},{"line_number":33,"context_line":""},{"line_number":34,"context_line":"class FakeStoragePolicySwift(object):"},{"line_number":35,"context_line":"    def __init__(self):"},{"line_number":36,"context_line":"        self.storage_policy \u003d defaultdict("},{"line_number":37,"context_line":"            partial(helpers.FakeSwift, capture_unexpected_calls\u003dFalse))"}],"source_content_type":"text/x-python","patch_set":1,"id":"521cc4cc_e609aee4","line":34,"in_reply_to":"71ff19d0_986e404a","updated":"2026-04-14 21:57:41.000000000","message":"yes it was. should i clean it up a lil more ?","commit_id":"2ddf9dbbdaabc3feb0649b1bd62228a08e8d60e4"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"aa15643ed16f48f69ae25f1eeb8b929dd2436d80","unresolved":true,"context_lines":[{"line_number":246,"context_line":"    def run_command(self, cmd_args\u003dNone):"},{"line_number":247,"context_line":"        \"\"\"Execute the summary command with given args\"\"\""},{"line_number":248,"context_line":"        parser \u003d manage_expirer.build_parser()"},{"line_number":249,"context_line":"        args \u003d parser.parse_args([\u0027summary\u0027] + (cmd_args or []))"},{"line_number":250,"context_line":"        with self.capture_io():"},{"line_number":251,"context_line":"            args.func(args)"},{"line_number":252,"context_line":""}],"source_content_type":"text/x-python","patch_set":1,"id":"8e2b2bb0_820c0622","line":249,"updated":"2026-03-22 22:05:05.000000000","message":"Should this include `\u0027--expirer-conf-path\u0027, self.config_dir` and `\u0027--internal-client-conf-path\u0027, self.internal_client_conf`? Seems to be why tests are failing.","commit_id":"2ddf9dbbdaabc3feb0649b1bd62228a08e8d60e4"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"aa15643ed16f48f69ae25f1eeb8b929dd2436d80","unresolved":true,"context_lines":[{"line_number":370,"context_line":"    Test the \u0027details\u0027 subcommand"},{"line_number":371,"context_line":"    \"\"\""},{"line_number":372,"context_line":""},{"line_number":373,"context_line":"    def run_command(self, cmd_args\u003dNone):"},{"line_number":374,"context_line":"        \"\"\"Execute the details command with given args\"\"\""},{"line_number":375,"context_line":"        parser \u003d manage_expirer.build_parser()"},{"line_number":376,"context_line":"        args \u003d parser.parse_args([\u0027details\u0027] + (cmd_args or []))"}],"source_content_type":"text/x-python","patch_set":1,"id":"2375e146_bc16b5b5","line":373,"updated":"2026-03-22 22:05:05.000000000","message":"Does `TestDetails` actually use this? Should `TestSummary` use the `get_fake_args` pattern like below?","commit_id":"2ddf9dbbdaabc3feb0649b1bd62228a08e8d60e4"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"03d6bcbafc86eb1e33a873236b4bb6e8f971f234","unresolved":true,"context_lines":[{"line_number":104,"context_line":"            self.app.register(\u0027GET\u0027, account_path, swob.HTTPNoContent,"},{"line_number":105,"context_line":"                              headers, \u0027\u0027)"},{"line_number":106,"context_line":"            self.app.register(\u0027HEAD\u0027, account_path, swob.HTTPNoContent,"},{"line_number":107,"context_line":"                              headers, \u0027\u0027)"},{"line_number":108,"context_line":""},{"line_number":109,"context_line":"        # Setup container listings"},{"line_number":110,"context_line":"        for (account, container), objects in self._container_data.items():"}],"source_content_type":"text/x-python","patch_set":7,"id":"948b0161_8dea206f","line":107,"updated":"2026-04-17 18:04:55.000000000","message":"if you move these register until AFTER you\u0027ve parsed the container_data - you could calculate the consistent responses that the account servers would return to list the declared containers with their respective object counts.","commit_id":"3b10df5ce1f7e0997e98e905386bf79561a41f2d"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"d996ce71709b3b82e5ede40cc51e1ccd737f8ad4","unresolved":false,"context_lines":[{"line_number":104,"context_line":"            self.app.register(\u0027GET\u0027, account_path, swob.HTTPNoContent,"},{"line_number":105,"context_line":"                              headers, \u0027\u0027)"},{"line_number":106,"context_line":"            self.app.register(\u0027HEAD\u0027, account_path, swob.HTTPNoContent,"},{"line_number":107,"context_line":"                              headers, \u0027\u0027)"},{"line_number":108,"context_line":""},{"line_number":109,"context_line":"        # Setup container listings"},{"line_number":110,"context_line":"        for (account, container), objects in self._container_data.items():"}],"source_content_type":"text/x-python","patch_set":7,"id":"7c49aaaf_4c1bd0eb","line":107,"in_reply_to":"948b0161_8dea206f","updated":"2026-04-21 18:21:56.000000000","message":"Done","commit_id":"3b10df5ce1f7e0997e98e905386bf79561a41f2d"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"d996ce71709b3b82e5ede40cc51e1ccd737f8ad4","unresolved":false,"context_lines":[{"line_number":104,"context_line":"            self.app.register(\u0027GET\u0027, account_path, swob.HTTPNoContent,"},{"line_number":105,"context_line":"                              headers, \u0027\u0027)"},{"line_number":106,"context_line":"            self.app.register(\u0027HEAD\u0027, account_path, swob.HTTPNoContent,"},{"line_number":107,"context_line":"                              headers, \u0027\u0027)"},{"line_number":108,"context_line":""},{"line_number":109,"context_line":"        # Setup container listings"},{"line_number":110,"context_line":"        for (account, container), objects in self._container_data.items():"}],"source_content_type":"text/x-python","patch_set":7,"id":"e48379d6_184335d3","line":107,"in_reply_to":"948b0161_8dea206f","updated":"2026-04-21 18:21:56.000000000","message":"Done","commit_id":"3b10df5ce1f7e0997e98e905386bf79561a41f2d"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"03d6bcbafc86eb1e33a873236b4bb6e8f971f234","unresolved":true,"context_lines":[{"line_number":275,"context_line":"                \u0027container_count\u0027: 5,"},{"line_number":276,"context_line":"                \u0027object_count\u0027: 50,"},{"line_number":277,"context_line":"            }"},{"line_number":278,"context_line":"        }"},{"line_number":279,"context_line":""},{"line_number":280,"context_line":"        fake_ic \u003d FakeInternalClient("},{"line_number":281,"context_line":"            account_data\u003daccount_data,"}],"source_content_type":"text/x-python","patch_set":7,"id":"a50b8c3e_bbdb597c","line":278,"updated":"2026-04-17 18:04:55.000000000","message":"I think this is redundant - all of this information could be derived from the container_data that\u0027s being pased in and parsed by the Fake","commit_id":"3b10df5ce1f7e0997e98e905386bf79561a41f2d"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"d996ce71709b3b82e5ede40cc51e1ccd737f8ad4","unresolved":false,"context_lines":[{"line_number":275,"context_line":"                \u0027container_count\u0027: 5,"},{"line_number":276,"context_line":"                \u0027object_count\u0027: 50,"},{"line_number":277,"context_line":"            }"},{"line_number":278,"context_line":"        }"},{"line_number":279,"context_line":""},{"line_number":280,"context_line":"        fake_ic \u003d FakeInternalClient("},{"line_number":281,"context_line":"            account_data\u003daccount_data,"}],"source_content_type":"text/x-python","patch_set":7,"id":"cd49de8c_86b9ec63","line":278,"in_reply_to":"a50b8c3e_bbdb597c","updated":"2026-04-21 18:21:56.000000000","message":"Acknowledged","commit_id":"3b10df5ce1f7e0997e98e905386bf79561a41f2d"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":76,"context_line":"            env, start_response)"},{"line_number":77,"context_line":""},{"line_number":78,"context_line":""},{"line_number":79,"context_line":"class FakeInternalClient(object):"},{"line_number":80,"context_line":"    \"\"\""},{"line_number":81,"context_line":"    Fake InternalClient for manage_expirer tests"},{"line_number":82,"context_line":"    \"\"\""}],"source_content_type":"text/x-python","patch_set":11,"id":"b1699554_b9bbdfda","line":79,"updated":"2026-04-21 22:53:11.000000000","message":"idk if such an example exists but for some kinds of error handling tests it might interesting to use a \"RealInternalClient\" that *uses* a `FakeSwift`\n\n... but this isn\u0027t that - this is a pure fake, it should drop the `FakeStoragePolicySwift` pretense, all the requests are to account/container resources anyway - it doesn\u0027t *need* different object responses depending on the storage-policy","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"86b16ba422ce5fb48bcede7145d9296aa226a806","unresolved":false,"context_lines":[{"line_number":76,"context_line":"            env, start_response)"},{"line_number":77,"context_line":""},{"line_number":78,"context_line":""},{"line_number":79,"context_line":"class FakeInternalClient(object):"},{"line_number":80,"context_line":"    \"\"\""},{"line_number":81,"context_line":"    Fake InternalClient for manage_expirer tests"},{"line_number":82,"context_line":"    \"\"\""}],"source_content_type":"text/x-python","patch_set":11,"id":"381477de_6a0994d8","line":79,"in_reply_to":"b1699554_b9bbdfda","updated":"2026-05-06 23:48:50.000000000","message":"Done","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":83,"context_line":""},{"line_number":84,"context_line":"    def __init__(self, account_data\u003dNone, container_data\u003dNone,"},{"line_number":85,"context_line":"                 object_data\u003dNone):"},{"line_number":86,"context_line":"        self.app \u003d FakeStoragePolicySwift()"},{"line_number":87,"context_line":"        self.user_agent \u003d \u0027fake-internal-client\u0027"},{"line_number":88,"context_line":"        self.request_tries \u003d 1"},{"line_number":89,"context_line":"        self.use_replication_network \u003d True"}],"source_content_type":"text/x-python","patch_set":11,"id":"76c93951_cafc7f18","line":86,"updated":"2026-04-21 22:53:11.000000000","message":"this is entirely un-needed and not used - this FakeInternalClient doesn\u0027t use FakeSwift to make registered calls at all - every method that s-m-e uses on the *real* InternalClient is overridden to return stub data directly.","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"86b16ba422ce5fb48bcede7145d9296aa226a806","unresolved":false,"context_lines":[{"line_number":83,"context_line":""},{"line_number":84,"context_line":"    def __init__(self, account_data\u003dNone, container_data\u003dNone,"},{"line_number":85,"context_line":"                 object_data\u003dNone):"},{"line_number":86,"context_line":"        self.app \u003d FakeStoragePolicySwift()"},{"line_number":87,"context_line":"        self.user_agent \u003d \u0027fake-internal-client\u0027"},{"line_number":88,"context_line":"        self.request_tries \u003d 1"},{"line_number":89,"context_line":"        self.use_replication_network \u003d True"}],"source_content_type":"text/x-python","patch_set":11,"id":"e5c20a7c_299c042b","line":86,"in_reply_to":"76c93951_cafc7f18","updated":"2026-05-06 23:48:50.000000000","message":"Done","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":118,"context_line":"            self.app.register(\u0027HEAD\u0027, obj_path, swob.HTTPOk, headers, \u0027\u0027)"},{"line_number":119,"context_line":""},{"line_number":120,"context_line":"    def iter_containers(self, account):"},{"line_number":121,"context_line":"        \"\"\"Mimic InternalClient.iter_containers\"\"\""},{"line_number":122,"context_line":"        for (acc, cont), objects in sorted(self._container_data.items()):"},{"line_number":123,"context_line":"            if acc \u003d\u003d account:"},{"line_number":124,"context_line":"                yield {"}],"source_content_type":"text/x-python","patch_set":11,"id":"78389466_1dbaa2ad","line":121,"updated":"2026-04-21 22:53:11.000000000","message":"wait a minute - since we override `iter_containers` do we even NEED to register the `GET /account` request?  We\u0027re hi-jacking it before it ever gets to the app","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":121,"context_line":"        \"\"\"Mimic InternalClient.iter_containers\"\"\""},{"line_number":122,"context_line":"        for (acc, cont), objects in sorted(self._container_data.items()):"},{"line_number":123,"context_line":"            if acc \u003d\u003d account:"},{"line_number":124,"context_line":"                yield {"},{"line_number":125,"context_line":"                    \u0027name\u0027: cont,"},{"line_number":126,"context_line":"                    \u0027count\u0027: len(objects),"},{"line_number":127,"context_line":"                    \u0027bytes\u0027: sum(o.get(\u0027bytes\u0027, 0) for o in objects),"}],"source_content_type":"text/x-python","patch_set":11,"id":"a8b91a54_02628c32","line":124,"updated":"2026-04-21 22:53:11.000000000","message":"you could argue that instead of just iterating them in the order they appear in _container_data that this should collect all the containers for an account and iterate them in sorted name order.","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"86b16ba422ce5fb48bcede7145d9296aa226a806","unresolved":true,"context_lines":[{"line_number":121,"context_line":"        \"\"\"Mimic InternalClient.iter_containers\"\"\""},{"line_number":122,"context_line":"        for (acc, cont), objects in sorted(self._container_data.items()):"},{"line_number":123,"context_line":"            if acc \u003d\u003d account:"},{"line_number":124,"context_line":"                yield {"},{"line_number":125,"context_line":"                    \u0027name\u0027: cont,"},{"line_number":126,"context_line":"                    \u0027count\u0027: len(objects),"},{"line_number":127,"context_line":"                    \u0027bytes\u0027: sum(o.get(\u0027bytes\u0027, 0) for o in objects),"}],"source_content_type":"text/x-python","patch_set":11,"id":"edaa95c1_ec55b668","line":124,"in_reply_to":"a8b91a54_02628c32","updated":"2026-05-06 23:48:50.000000000","message":"Two changes introduced:\n\nFilter first, then sort — only the requested account\u0027s containers participate in the sort.\nSort by container name only — matches what InternalClient.iter_containers actually returns from a real Swift cluster (account listings are name-ordered).","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":146,"context_line":"                    self._account_data[account].get(\u0027object_count\u0027, 0)),"},{"line_number":147,"context_line":"            }"},{"line_number":148,"context_line":"        return {\u0027x-account-container-count\u0027: \u00270\u0027,"},{"line_number":149,"context_line":"                \u0027x-account-object-count\u0027: \u00270\u0027}"},{"line_number":150,"context_line":""},{"line_number":151,"context_line":"    def get_object_metadata(self, account, container, obj, headers\u003dNone,"},{"line_number":152,"context_line":"                            acceptable_statuses\u003dNone):"}],"source_content_type":"text/x-python","patch_set":11,"id":"95667311_45b848c0","line":149,"updated":"2026-04-21 22:53:11.000000000","message":"FWIW I\u0027d probably write this:\n\n```\ndata \u003d self._account_data.get(account, {})\nreturn {\u0027x-blah-count\u0027: data.get(\u0027blah_count\u0027, 0), ...}\n```\n\nthe duplication of `x-account-container-count` and `x-account-container-count` literals is a smell that if you needed to change the stub metadata response to include more keys you\u0027d have to fix both the provided data stub AND the null stub - and so it\u0027d be better if that was just always the same code path and the fake would obviously always return consistently structured stub data.","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":189,"context_line":"                                                 \u0027internal-client.conf\u0027)"},{"line_number":190,"context_line":"        with open(self.internal_client_conf, \u0027w\u0027) as f:"},{"line_number":191,"context_line":"            f.write(\u0027[DEFAULT]\\n\u0027)"},{"line_number":192,"context_line":"            f.write(\u0027user \u003d test_user\\n\u0027)"},{"line_number":193,"context_line":""},{"line_number":194,"context_line":"    def tearDown(self):"},{"line_number":195,"context_line":"        shutil.rmtree(self.tempdir, ignore_errors\u003dTrue)"}],"source_content_type":"text/x-python","patch_set":11,"id":"0259f4c6_6e417ec2","line":192,"updated":"2026-04-21 22:53:11.000000000","message":"oic, not ALL tests consistently mock `get_internal_client_ctx`","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":218,"context_line":"        try:"},{"line_number":219,"context_line":"            return read_metrics(metrics_file)"},{"line_number":220,"context_line":"        finally:"},{"line_number":221,"context_line":"            os.unlink(metrics_file)"},{"line_number":222,"context_line":""},{"line_number":223,"context_line":"    def get_fake_args(self, cmd_args\u003dNone):"},{"line_number":224,"context_line":"        \"\"\"Helper to build args object\"\"\""}],"source_content_type":"text/x-python","patch_set":11,"id":"8ccb6681_61dfd2b6","line":221,"updated":"2026-04-21 22:53:11.000000000","message":"hehe, it\u0027s a little funny we have to bounce the metrics through a stdout captured stringio object and down to disk before our helper can parse them\n\nprobably that helper should be split into `_parse_metrics(f)` that works on any \"line iterable\" - then you can just give it `metrics \u003d _parse_metrics(self.output.splitlines())`","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":285,"context_line":""},{"line_number":286,"context_line":"        with mock.patch(\u0027swift.cli.manage_expirer.i\u0027"},{"line_number":287,"context_line":"                        \u0027nternal_client_ctx\u0027) as mock_ctx:"},{"line_number":288,"context_line":"            mock_ctx.return_value.__enter__.return_value \u003d fake_ic"},{"line_number":289,"context_line":"            self.run_summary_command([\u0027--start-day-offset\u0027, \u00270\u0027, \u0027--num-days\u0027,"},{"line_number":290,"context_line":"                                      \u00275\u0027])"},{"line_number":291,"context_line":""}],"source_content_type":"text/x-python","patch_set":11,"id":"6e2fee06_f55d84bf","line":288,"updated":"2026-04-21 22:53:11.000000000","message":"`\u0027nternal` is an akward line break, maybe:\n\n```\ndiff --git a/test/unit/cli/test_manage_expirer.py b/test/unit/cli/test_manage_expirer.py\nindex 9ede557b21..b1a00b2876 100644\n--- a/test/unit/cli/test_manage_expirer.py\n+++ b/test/unit/cli/test_manage_expirer.py\n@@ -283,8 +283,8 @@ class TestSummary(BaseTestCase):\n             account_data\u003daccount_data,\n             container_data\u003dcontainer_data)\n \n-        with mock.patch(\u0027swift.cli.manage_expirer.i\u0027\n-                        \u0027nternal_client_ctx\u0027) as mock_ctx:\n+        with mock.patch(\n+                \u0027swift.cli.manage_expirer.internal_client_ctx\u0027) as mock_ctx:\n             mock_ctx.return_value.__enter__.return_value \u003d fake_ic\n             self.run_summary_command([\u0027--start-day-offset\u0027, \u00270\u0027, \u0027--num-days\u0027,\n                                       \u00275\u0027])\n```\n\nOR\n\n```\ndiff --git a/test/unit/cli/test_manage_expirer.py b/test/unit/cli/test_manage_expirer.py\nindex 9ede557b21..b247379ca1 100644\n--- a/test/unit/cli/test_manage_expirer.py\n+++ b/test/unit/cli/test_manage_expirer.py\n@@ -283,8 +283,8 @@ class TestSummary(BaseTestCase):\n             account_data\u003daccount_data,\n             container_data\u003dcontainer_data)\n \n-        with mock.patch(\u0027swift.cli.manage_expirer.i\u0027\n-                        \u0027nternal_client_ctx\u0027) as mock_ctx:\n+        with mock.patch(\u0027swift.cli.manage_expirer\u0027\n+                        \u0027.internal_client_ctx\u0027) as mock_ctx:\n             mock_ctx.return_value.__enter__.return_value \u003d fake_ic\n             self.run_summary_command([\u0027--start-day-offset\u0027, \u00270\u0027, \u0027--num-days\u0027,\n                                       \u00275\u0027])\n```","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"86b16ba422ce5fb48bcede7145d9296aa226a806","unresolved":false,"context_lines":[{"line_number":285,"context_line":""},{"line_number":286,"context_line":"        with mock.patch(\u0027swift.cli.manage_expirer.i\u0027"},{"line_number":287,"context_line":"                        \u0027nternal_client_ctx\u0027) as mock_ctx:"},{"line_number":288,"context_line":"            mock_ctx.return_value.__enter__.return_value \u003d fake_ic"},{"line_number":289,"context_line":"            self.run_summary_command([\u0027--start-day-offset\u0027, \u00270\u0027, \u0027--num-days\u0027,"},{"line_number":290,"context_line":"                                      \u00275\u0027])"},{"line_number":291,"context_line":""}],"source_content_type":"text/x-python","patch_set":11,"id":"1f0a0534_f34d22db","line":288,"in_reply_to":"6e2fee06_f55d84bf","updated":"2026-05-06 23:48:50.000000000","message":"Done","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":316,"context_line":"        self.assertEqual("},{"line_number":317,"context_line":"            metrics[metric_key(\u0027swift_manage_expirer_expirer_\u0027"},{"line_number":318,"context_line":"                               \u0027task_container_objects\u0027, host\u003dhost,"},{"line_number":319,"context_line":"                               days\u003d\u00273\u0027)], 10.0)"},{"line_number":320,"context_line":""},{"line_number":321,"context_line":"    def test_summary_empty_account(self):"},{"line_number":322,"context_line":"        \"\"\"Test summary with no expiring objects\"\"\""}],"source_content_type":"text/x-python","patch_set":11,"id":"ca223460_0d0d2a09","line":319,"updated":"2026-04-21 22:53:11.000000000","message":"the reason I prefer the literal:\n\n```\ndiff --git a/test/unit/cli/test_manage_expirer.py b/test/unit/cli/test_manage_expirer.py\nindex 9ede557b21..664dbe7adb 100644\n--- a/test/unit/cli/test_manage_expirer.py\n+++ b/test/unit/cli/test_manage_expirer.py\n@@ -291,32 +291,26 @@ class TestSummary(BaseTestCase):\n \n         metrics \u003d self.parse_output_metrics()\n \n-        self.assertEqual(\n-            metrics[metric_key(\u0027swift_manage_expirer_\u0027\n-                               \u0027total_expirer_containers\u0027, host\u003dhost)], 5.0)\n-        self.assertEqual(\n-            metrics[metric_key(\u0027swift_manage_expirer_\u0027\n-                               \u0027total_expirer_entries\u0027, host\u003dhost)], 50)\n-        self.assertEqual(\n-            metrics[metric_key(\u0027swift_manage_expirer_\u0027\n-                               \u0027processed_expirer_containers\u0027, host\u003dhost)],\n-            4.0)\n-        self.assertEqual(\n-            metrics[metric_key(\u0027swift_manage_expirer_expirer_\u0027\n-                               \u0027task_container_objects\u0027, host\u003dhost,\n-                               days\u003d\u00270\u0027)], 10.0)\n-        self.assertEqual(\n-            metrics[metric_key(\u0027swift_manage_expirer_expirer_\u0027\n-                               \u0027task_container_objects\u0027, host\u003dhost,\n-                               days\u003d\u00271\u0027)], 10.0)\n-        self.assertEqual(\n-            metrics[metric_key(\u0027swift_manage_expirer_expirer_\u0027\n-                               \u0027task_container_objects\u0027, host\u003dhost,\n-                               days\u003d\u00272\u0027)], 10.0)\n-        self.assertEqual(\n-            metrics[metric_key(\u0027swift_manage_expirer_expirer_\u0027\n-                               \u0027task_container_objects\u0027, host\u003dhost,\n-                               days\u003d\u00273\u0027)], 10.0)\n+        self.assertEqual({\n+            metric_key(\u0027swift_manage_expirer_\u0027\n+                       \u0027total_expirer_containers\u0027, host\u003dhost): 5.0,\n+            metric_key(\u0027swift_manage_expirer_\u0027\n+                       \u0027total_expirer_entries\u0027, host\u003dhost): 50,\n+            metric_key(\u0027swift_manage_expirer_\u0027\n+                       \u0027processed_expirer_containers\u0027, host\u003dhost): 4.0,\n+            metric_key(\u0027swift_manage_expirer_expirer_\u0027\n+                       \u0027task_container_objects\u0027, host\u003dhost,\n+                       days\u003d\u00270\u0027): 10.0,\n+            metric_key(\u0027swift_manage_expirer_expirer_\u0027\n+                       \u0027task_container_objects\u0027, host\u003dhost,\n+                       days\u003d\u00271\u0027): 10.0,\n+            metric_key(\u0027swift_manage_expirer_expirer_\u0027\n+                       \u0027task_container_objects\u0027, host\u003dhost,\n+                       days\u003d\u00272\u0027): 10.0,\n+            metric_key(\u0027swift_manage_expirer_expirer_\u0027\n+                       \u0027task_container_objects\u0027, host\u003dhost,\n+                       days\u003d\u00273\u0027): 11.0,\n+        }, metrics)\n \n```\n\nit because it says something MORE than the per-key assertions do not, it says:\n\n\"this results in THESE metrics AND NO MORE\"","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":328,"context_line":"            }"},{"line_number":329,"context_line":"        }"},{"line_number":330,"context_line":""},{"line_number":331,"context_line":"        fake_ic \u003d FakeInternalClient(account_data\u003daccount_data)"},{"line_number":332,"context_line":""},{"line_number":333,"context_line":"        with mock.patch(\u0027swift.cli.manage_expirer.\u0027"},{"line_number":334,"context_line":"                        \u0027internal_client_ctx\u0027) as mock_ctx:"}],"source_content_type":"text/x-python","patch_set":11,"id":"241ecfef_cc10f8f5","line":331,"updated":"2026-04-21 22:53:11.000000000","message":"ok, and sense container_data is not provided it uses an empty default","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"86b16ba422ce5fb48bcede7145d9296aa226a806","unresolved":false,"context_lines":[{"line_number":328,"context_line":"            }"},{"line_number":329,"context_line":"        }"},{"line_number":330,"context_line":""},{"line_number":331,"context_line":"        fake_ic \u003d FakeInternalClient(account_data\u003daccount_data)"},{"line_number":332,"context_line":""},{"line_number":333,"context_line":"        with mock.patch(\u0027swift.cli.manage_expirer.\u0027"},{"line_number":334,"context_line":"                        \u0027internal_client_ctx\u0027) as mock_ctx:"}],"source_content_type":"text/x-python","patch_set":11,"id":"58ba3121_5596e8c1","line":331,"in_reply_to":"241ecfef_cc10f8f5","updated":"2026-05-06 23:48:50.000000000","message":"Acknowledged","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":339,"context_line":""},{"line_number":340,"context_line":"        self.assertEqual("},{"line_number":341,"context_line":"            metrics[metric_key(\u0027swift_manage_expirer_\u0027"},{"line_number":342,"context_line":"                               \u0027total_expirer_containers\u0027, host\u003dhost)], 0)"},{"line_number":343,"context_line":"        self.assertEqual("},{"line_number":344,"context_line":"            metrics[metric_key(\u0027swift_manage_expirer_\u0027"},{"line_number":345,"context_line":"                               \u0027total_expirer_entries\u0027, host\u003dhost)], 0)"}],"source_content_type":"text/x-python","patch_set":11,"id":"6dce5eb5_81243ace","line":342,"updated":"2026-04-21 22:53:11.000000000","message":"I think this test might be more interesting if it the HEAD response returned different counts that the listing responses.\n\ni.e. demonstrate that `total_expirer_containers` comes from HEAD metadata, while `processed_expirer_containers` comes from the loop over the GET response data.","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":391,"context_line":"                               \u0027total_expirer_containers\u0027, host\u003dhost)], 41.0)"},{"line_number":392,"context_line":"        self.assertEqual("},{"line_number":393,"context_line":"            metrics[metric_key(\u0027swift_manage_expirer_t\u0027"},{"line_number":394,"context_line":"                               \u0027otal_expirer_entries\u0027, host\u003dhost)], 205.0"},{"line_number":395,"context_line":"        )"},{"line_number":396,"context_line":"        self.assertEqual("},{"line_number":397,"context_line":"            metrics[metric_key(\u0027swift_manage_expirer_\u0027"}],"source_content_type":"text/x-python","patch_set":11,"id":"70710a4c_e4e277fc","line":394,"updated":"2026-04-21 22:53:11.000000000","message":"heh, I read this as `otel_expirer_entries` - akward line break","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"86b16ba422ce5fb48bcede7145d9296aa226a806","unresolved":false,"context_lines":[{"line_number":391,"context_line":"                               \u0027total_expirer_containers\u0027, host\u003dhost)], 41.0)"},{"line_number":392,"context_line":"        self.assertEqual("},{"line_number":393,"context_line":"            metrics[metric_key(\u0027swift_manage_expirer_t\u0027"},{"line_number":394,"context_line":"                               \u0027otal_expirer_entries\u0027, host\u003dhost)], 205.0"},{"line_number":395,"context_line":"        )"},{"line_number":396,"context_line":"        self.assertEqual("},{"line_number":397,"context_line":"            metrics[metric_key(\u0027swift_manage_expirer_\u0027"}],"source_content_type":"text/x-python","patch_set":11,"id":"8a9cc1c3_fcbbe856","line":394,"in_reply_to":"70710a4c_e4e277fc","updated":"2026-05-06 23:48:50.000000000","message":"Done","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":396,"context_line":"        self.assertEqual("},{"line_number":397,"context_line":"            metrics[metric_key(\u0027swift_manage_expirer_\u0027"},{"line_number":398,"context_line":"                               \u0027processed_expirer_containers\u0027, host\u003dhost)],"},{"line_number":399,"context_line":"            10.0"},{"line_number":400,"context_line":"        )"},{"line_number":401,"context_line":"        self.assertEqual("},{"line_number":402,"context_line":"            metrics[metric_key(\u0027swift_manage_expirer_\u0027"}],"source_content_type":"text/x-python","patch_set":11,"id":"789e8780_c61d0e15","line":399,"updated":"2026-04-21 22:53:11.000000000","message":"I like the consistency between `--num-days` and `processed_expirer_containers` (since this test setup does \"only one stub container per day\")","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"86b16ba422ce5fb48bcede7145d9296aa226a806","unresolved":false,"context_lines":[{"line_number":396,"context_line":"        self.assertEqual("},{"line_number":397,"context_line":"            metrics[metric_key(\u0027swift_manage_expirer_\u0027"},{"line_number":398,"context_line":"                               \u0027processed_expirer_containers\u0027, host\u003dhost)],"},{"line_number":399,"context_line":"            10.0"},{"line_number":400,"context_line":"        )"},{"line_number":401,"context_line":"        self.assertEqual("},{"line_number":402,"context_line":"            metrics[metric_key(\u0027swift_manage_expirer_\u0027"}],"source_content_type":"text/x-python","patch_set":11,"id":"2e505221_cbafd5f8","line":399,"in_reply_to":"789e8780_c61d0e15","updated":"2026-05-06 23:48:50.000000000","message":"Done","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":447,"context_line":"            metrics[metric_key(\u0027swift_manage_expirer_\u0027"},{"line_number":448,"context_line":"                               \u0027expirer_task_container_objects\u0027, host\u003dhost,"},{"line_number":449,"context_line":"                               days\u003d\u00274\u0027)], 5.0"},{"line_number":450,"context_line":"        )"},{"line_number":451,"context_line":""},{"line_number":452,"context_line":"        total_containers \u003d metrics[metric_key("},{"line_number":453,"context_line":"            \u0027swift_manage_expirer_total_expirer_containers\u0027, host\u003dhost)]"}],"source_content_type":"text/x-python","patch_set":11,"id":"e5eaaf5a_6c8eb61d","line":450,"updated":"2026-04-21 22:53:11.000000000","message":"I wonder if it\u0027d be easier to reason about this expectation using an iterator\n\n```\nfor day_value in range(-5, 4):\n    key \u003d metric_key(\u0027task_container_objects\u0027, days\u003dday_value)\n    # every day has 5 objects\n    self.assertEqual(metrics[key], 5)\n```\n\nOR you could do something like:\n\n```\nexpected \u003d {...}  # base values\nfor day_value in range(-5, 4):\n    ...\n    # every day has 5 objects\n    expected[key] \u003d 5\nself.assertEqual(expected, metrics)\n```","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"86b16ba422ce5fb48bcede7145d9296aa226a806","unresolved":false,"context_lines":[{"line_number":447,"context_line":"            metrics[metric_key(\u0027swift_manage_expirer_\u0027"},{"line_number":448,"context_line":"                               \u0027expirer_task_container_objects\u0027, host\u003dhost,"},{"line_number":449,"context_line":"                               days\u003d\u00274\u0027)], 5.0"},{"line_number":450,"context_line":"        )"},{"line_number":451,"context_line":""},{"line_number":452,"context_line":"        total_containers \u003d metrics[metric_key("},{"line_number":453,"context_line":"            \u0027swift_manage_expirer_total_expirer_containers\u0027, host\u003dhost)]"}],"source_content_type":"text/x-python","patch_set":11,"id":"ecbc0afc_665be77f","line":450,"in_reply_to":"e5eaaf5a_6c8eb61d","updated":"2026-05-06 23:48:50.000000000","message":"Done","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":460,"context_line":""},{"line_number":461,"context_line":"        with mock.patch(\u0027swift.cli.manage_expirer.\u0027"},{"line_number":462,"context_line":"                        \u0027internal_client_ctx\u0027) as mock_ctx:"},{"line_number":463,"context_line":"            mock_ctx.return_value.__enter__.return_value \u003d fake_ic"},{"line_number":464,"context_line":"            # Only look from day -21 to 20 (1 day in total)"},{"line_number":465,"context_line":"            self.run_summary_command([\u0027--start-day-offset\u0027, \u0027-21\u0027,"},{"line_number":466,"context_line":"                                      \u0027--num-days\u0027, \u00271\u0027])"}],"source_content_type":"text/x-python","patch_set":11,"id":"b2cb9936_8ebef3de","line":463,"updated":"2026-04-21 22:53:11.000000000","message":"I feel like `with mock_expirer_ic_ctx(fake_ic)` would be a nice little helper.","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":463,"context_line":"            mock_ctx.return_value.__enter__.return_value \u003d fake_ic"},{"line_number":464,"context_line":"            # Only look from day -21 to 20 (1 day in total)"},{"line_number":465,"context_line":"            self.run_summary_command([\u0027--start-day-offset\u0027, \u0027-21\u0027,"},{"line_number":466,"context_line":"                                      \u0027--num-days\u0027, \u00271\u0027])"},{"line_number":467,"context_line":""},{"line_number":468,"context_line":"        metrics \u003d self.parse_output_metrics()"},{"line_number":469,"context_line":"        self.assertEqual("}],"source_content_type":"text/x-python","patch_set":11,"id":"f9757c3c_b773c1d5","line":466,"updated":"2026-04-21 22:53:11.000000000","message":"I really like this style of assertion where we have a fixed setup \u0027days -20 to +20 (41 containers, 5 objs each)\" and then we throw different options/filters at it.\n\nI think if the test defined a `base_expected` sort of value it\u0027d make it more clear which keys are consistent from run-to-run regardless of filters vs the `processed_expirer_containers` and `task_container_objects` metrics that change based on the selected container filters.","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":465,"context_line":"            self.run_summary_command([\u0027--start-day-offset\u0027, \u0027-21\u0027,"},{"line_number":466,"context_line":"                                      \u0027--num-days\u0027, \u00271\u0027])"},{"line_number":467,"context_line":""},{"line_number":468,"context_line":"        metrics \u003d self.parse_output_metrics()"},{"line_number":469,"context_line":"        self.assertEqual("},{"line_number":470,"context_line":"            metrics[metric_key(\u0027swift_manage_expirer_\u0027"},{"line_number":471,"context_line":"                               \u0027total_expirer_containers\u0027, host\u003dhost)], 41.0"}],"source_content_type":"text/x-python","patch_set":11,"id":"67f4f3b0_df49e695","line":468,"updated":"2026-04-21 22:53:11.000000000","message":"I wonder if you should keep around `orig_metrics \u003d metrics` to draw some comparisions?\n\n```\n(Pdb) !{k[0] for k in orig_metrics.keys()}\n{\u0027swift_manage_expirer_total_expirer_containers\u0027, \u0027swift_manage_expirer_expirer_task_container_objects\u0027, \u0027swift_manage_expirer_total_expirer_entries\u0027, \u0027swift_manage_expirer_processed_expirer_containers\u0027}\n(Pdb) !{k[0] for k in metrics.keys()}\n{\u0027swift_manage_expirer_total_expirer_containers\u0027, \u0027swift_manage_expirer_expirer_task_container_objects\u0027, \u0027swift_manage_expirer_total_expirer_entries\u0027, \u0027swift_manage_expirer_processed_expirer_containers\u0027}\n```","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":492,"context_line":"        day should merge their counts under one days key."},{"line_number":493,"context_line":"        \"\"\""},{"line_number":494,"context_line":"        now \u003d datetime.datetime.now()"},{"line_number":495,"context_line":"        host \u003d socket.gethostname()"},{"line_number":496,"context_line":""},{"line_number":497,"context_line":"        # Anchor to midnight 2 days from now so both containers comfortably"},{"line_number":498,"context_line":"        # land in the same day bucket regardless of time-of-day."}],"source_content_type":"text/x-python","patch_set":11,"id":"aff35a7a_1275fdf3","line":495,"updated":"2026-04-21 22:53:11.000000000","message":"basically all the tests need this - just define `self.host` in test setup (maybe even `self.common_labels \u003d {\u0027host\u0027: socket.gethostname()}`\n\nYou can then do:\n\n```\nmetric_key(\u0027some_name\u0027, special\u003dvalue, **self.common_labels)\n```","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":500,"context_line":"            hour\u003d0, minute\u003d0, second\u003d0, microsecond\u003d0)"},{"line_number":501,"context_line":"        ts_a \u003d int(midnight.timestamp()) // 3600 * 3600"},{"line_number":502,"context_line":"        ts_b \u003d int((midnight + datetime.timedelta(hours\u003d6)).timestamp()) \\"},{"line_number":503,"context_line":"            // 3600 * 3600"},{"line_number":504,"context_line":""},{"line_number":505,"context_line":"        container_data \u003d {"},{"line_number":506,"context_line":"            (manage_expirer.EXPIRING_ACCOUNT, str(ts_a)): ["}],"source_content_type":"text/x-python","patch_set":11,"id":"6949b23c_1685a9b5","line":503,"updated":"2026-04-21 22:53:11.000000000","message":"FWIW OMM\n\n```\n(Pdb) !ts_a\n1776902400\n(Pdb) !ts_b\n1776924000\nvagrant@saio:~$ date -d @1776902400\nThu Apr 23 12:00:00 AM UTC 2026\nvagrant@saio:~$ date -d @1776924000\nThu Apr 23 06:00:00 AM UTC 2026\n```\n\n^ seems fine I guess?  I don\u0027t think I understand exactly what the `// 3600 * 3600` does - something about hours?  In reality the \"timestamp\" that a deletion key gets hashed into is always `\u003cmidnight-of-expiration\u003e - \u003csome_random_modulo_seconds\u003e` so typically if an object expires sometime on 4/21 it shows up in a bucket BEFORE 4/21 00:00:01","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":504,"context_line":""},{"line_number":505,"context_line":"        container_data \u003d {"},{"line_number":506,"context_line":"            (manage_expirer.EXPIRING_ACCOUNT, str(ts_a)): ["},{"line_number":507,"context_line":"                {\u0027name\u0027: \u0027obj%d\u0027 % i, \u0027bytes\u0027: 50} for i in range(4)"},{"line_number":508,"context_line":"            ],"},{"line_number":509,"context_line":"            (manage_expirer.EXPIRING_ACCOUNT, str(ts_b)): ["},{"line_number":510,"context_line":"                {\u0027name\u0027: \u0027obj%d\u0027 % i, \u0027bytes\u0027: 50} for i in range(6)"}],"source_content_type":"text/x-python","patch_set":11,"id":"8f949e05_355b1025","line":507,"updated":"2026-04-21 22:53:11.000000000","message":"I guess these stubs don\u0027t really have (need?) the expected key names:\n\n```\nvagrant@saio:~$ curl http://saio:8090/v1/.expiring_objects/1776729574\n1776803327-AUTH_test/test/test\n```","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":513,"context_line":"        account_data \u003d {"},{"line_number":514,"context_line":"            manage_expirer.EXPIRING_ACCOUNT: {"},{"line_number":515,"context_line":"                \u0027container_count\u0027: 2,"},{"line_number":516,"context_line":"                \u0027object_count\u0027: 10,"},{"line_number":517,"context_line":"            }"},{"line_number":518,"context_line":"        }"},{"line_number":519,"context_line":""}],"source_content_type":"text/x-python","patch_set":11,"id":"d0cee515_f89b4f1b","line":516,"updated":"2026-04-21 22:53:11.000000000","message":"i find it confusing how this `FakeInternalClient` will trump up `iter_container` responses based on the provided `container_data` but not the account metadata response\n\nI guess it allows you to setup tests where those results are different; but I wonder how many times we want to test that vs the normal case where it\u0027s easier to reason about the beahvior of the UUT assuming consistent results from the stub storage state.","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":529,"context_line":""},{"line_number":530,"context_line":"        metrics \u003d self.parse_output_metrics()"},{"line_number":531,"context_line":""},{"line_number":532,"context_line":"        # make assertions stricter and better"},{"line_number":533,"context_line":"        self.assertEqual("},{"line_number":534,"context_line":"            metrics[metric_key(\u0027swift_manage_expirer_\u0027"},{"line_number":535,"context_line":"                               \u0027processed_expirer_containers\u0027, host\u003dhost)], 2)"}],"source_content_type":"text/x-python","patch_set":11,"id":"f5287f20_b580ac6c","line":532,"updated":"2026-04-21 22:53:11.000000000","message":"I suggest you start with `self.assertEqual({}, metrics)` and only deviate from that when necessary.\n\nFor my money I think:\n\n```\ndiff --git a/test/unit/cli/test_manage_expirer.py b/test/unit/cli/test_manage_expirer.py\nindex 9ede557b21..4a4b3e5f12 100644\n--- a/test/unit/cli/test_manage_expirer.py\n+++ b/test/unit/cli/test_manage_expirer.py\n@@ -530,17 +530,18 @@ class TestSummary(BaseTestCase):\n         metrics \u003d self.parse_output_metrics()\n \n         # make assertions stricter and better\n-        self.assertEqual(\n-            metrics[metric_key(\u0027swift_manage_expirer_\u0027\n-                               \u0027processed_expirer_containers\u0027, host\u003dhost)], 2)\n-        # Both containers\u0027 counts merge into a single day bucket\n-        day_metrics \u003d {k: v for k, v in metrics.items()\n-                       if k[0] \u003d\u003d (\u0027swift_manage_expirer_\u0027\n-                                   \u0027expirer_task_container_objects\u0027)}\n-        self.assertEqual(len(day_metrics), 1,\n-                         \u0027Expected exactly one day bucket, got %s\u0027\n-                         % day_metrics)\n-        self.assertEqual(list(day_metrics.values())[0], 10)\n+        common_labels \u003d {\u0027host\u0027: host}\n+        self.assertEqual({\n+            metric_key(\u0027swift_manage_expirer_total_expirer_containers\u0027,\n+                       **common_labels): 2.0,\n+            metric_key(\u0027swift_manage_expirer_processed_expirer_containers\u0027,\n+                       **common_labels): 2.0,\n+            metric_key(\u0027swift_manage_expirer_total_expirer_entries\u0027,\n+                       **common_labels): 10.0,\n+            # Both containers\u0027 counts merge into a single day bucket\n+            metric_key(\u0027swift_manage_expirer_expirer_task_container_objects\u0027,\n+                       days\u003d\u00271\u0027, **common_labels): 10.0,\n+        }, metrics)\n \n     def test_summary_account_metadata_diverges_from_scan_window(self):\n         \"\"\"\n```\n\n... is about as good as it gets!","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"86b16ba422ce5fb48bcede7145d9296aa226a806","unresolved":false,"context_lines":[{"line_number":529,"context_line":""},{"line_number":530,"context_line":"        metrics \u003d self.parse_output_metrics()"},{"line_number":531,"context_line":""},{"line_number":532,"context_line":"        # make assertions stricter and better"},{"line_number":533,"context_line":"        self.assertEqual("},{"line_number":534,"context_line":"            metrics[metric_key(\u0027swift_manage_expirer_\u0027"},{"line_number":535,"context_line":"                               \u0027processed_expirer_containers\u0027, host\u003dhost)], 2)"}],"source_content_type":"text/x-python","patch_set":11,"id":"342c74f2_4e537931","line":532,"in_reply_to":"f5287f20_b580ac6c","updated":"2026-05-06 23:48:50.000000000","message":"Done","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":546,"context_line":"        \"\"\""},{"line_number":547,"context_line":"        Account reports many containers but only a few fall in the scan"},{"line_number":548,"context_line":"        window. total_expirer_* comes from account metadata while"},{"line_number":549,"context_line":"        processed_expirer_containers comes from iteration."},{"line_number":550,"context_line":"        \"\"\""},{"line_number":551,"context_line":"        now \u003d datetime.datetime.now()"},{"line_number":552,"context_line":"        host \u003d socket.gethostname()"}],"source_content_type":"text/x-python","patch_set":11,"id":"ba09d4c4_f7575202","line":549,"updated":"2026-04-21 22:53:11.000000000","message":"oh this is good!  this actually speaks to exactly the case where account_data and container_data would be different.","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"86b16ba422ce5fb48bcede7145d9296aa226a806","unresolved":false,"context_lines":[{"line_number":546,"context_line":"        \"\"\""},{"line_number":547,"context_line":"        Account reports many containers but only a few fall in the scan"},{"line_number":548,"context_line":"        window. total_expirer_* comes from account metadata while"},{"line_number":549,"context_line":"        processed_expirer_containers comes from iteration."},{"line_number":550,"context_line":"        \"\"\""},{"line_number":551,"context_line":"        now \u003d datetime.datetime.now()"},{"line_number":552,"context_line":"        host \u003d socket.gethostname()"}],"source_content_type":"text/x-python","patch_set":11,"id":"d741d171_fcc61a07","line":549,"in_reply_to":"ba09d4c4_f7575202","updated":"2026-05-06 23:48:50.000000000","message":"Acknowledged","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":564,"context_line":"        # outside our scan window)"},{"line_number":565,"context_line":"        account_data \u003d {"},{"line_number":566,"context_line":"            manage_expirer.EXPIRING_ACCOUNT: {"},{"line_number":567,"context_line":"                \u0027container_count\u0027: 500,"},{"line_number":568,"context_line":"                \u0027object_count\u0027: 12000,"},{"line_number":569,"context_line":"            }"},{"line_number":570,"context_line":"        }"}],"source_content_type":"text/x-python","patch_set":11,"id":"e4776720_29ee3beb","line":567,"updated":"2026-04-21 22:53:11.000000000","message":"heh, and there you go 500 \u003e 1 😀","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"86b16ba422ce5fb48bcede7145d9296aa226a806","unresolved":false,"context_lines":[{"line_number":564,"context_line":"        # outside our scan window)"},{"line_number":565,"context_line":"        account_data \u003d {"},{"line_number":566,"context_line":"            manage_expirer.EXPIRING_ACCOUNT: {"},{"line_number":567,"context_line":"                \u0027container_count\u0027: 500,"},{"line_number":568,"context_line":"                \u0027object_count\u0027: 12000,"},{"line_number":569,"context_line":"            }"},{"line_number":570,"context_line":"        }"}],"source_content_type":"text/x-python","patch_set":11,"id":"29b31aaa_06bc623c","line":567,"in_reply_to":"e4776720_29ee3beb","updated":"2026-05-06 23:48:50.000000000","message":"Done","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":630,"context_line":"                        \u0027internal_client_ctx\u0027) as mock_ctx:"},{"line_number":631,"context_line":"            mock_ctx.return_value.__enter__.return_value \u003d fake_ic"},{"line_number":632,"context_line":"            self.run_summary_command([\u0027--start-day-offset\u0027, \u00270\u0027,"},{"line_number":633,"context_line":"                                      \u0027--num-days\u0027, \u00273\u0027])"},{"line_number":634,"context_line":""},{"line_number":635,"context_line":"        metrics \u003d self.parse_output_metrics()"},{"line_number":636,"context_line":""}],"source_content_type":"text/x-python","patch_set":11,"id":"a1218423_1744d44d","line":633,"updated":"2026-04-21 22:53:11.000000000","message":"if you can find a way to plumb a debug_logger into here that self `logger.warning(f\"Skipping invalid container name: {name!r}\")` might be easier to assert.\n\nIt is NOT going to `self.errors`","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b6b592f9ee4f2fa7ae555c19fb123d070fbc287","unresolved":true,"context_lines":[{"line_number":948,"context_line":"        containers \u003d list(manage_expirer.iter_relevant_containers("},{"line_number":949,"context_line":"            args, fake_ic, now, logger))"},{"line_number":950,"context_line":""},{"line_number":951,"context_line":"        self.assertTrue(5 \u003c\u003d len(containers) \u003c\u003d 15,"},{"line_number":952,"context_line":"                        f\"Expected 5-15 containers, \""},{"line_number":953,"context_line":"                        f\"got {len(containers)}\")"},{"line_number":954,"context_line":""}],"source_content_type":"text/x-python","patch_set":11,"id":"332286c7_573580eb","line":951,"updated":"2026-04-21 22:53:11.000000000","message":"we create containers from `(-15, 16)` we ask for `(-5, +10)` - i\u0027m not really sure why it this would ever be anything OTHER than 10?\n\n```\n(Pdb) len(containers)\n10\n```\n\n^ just assert that!","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"86b16ba422ce5fb48bcede7145d9296aa226a806","unresolved":false,"context_lines":[{"line_number":948,"context_line":"        containers \u003d list(manage_expirer.iter_relevant_containers("},{"line_number":949,"context_line":"            args, fake_ic, now, logger))"},{"line_number":950,"context_line":""},{"line_number":951,"context_line":"        self.assertTrue(5 \u003c\u003d len(containers) \u003c\u003d 15,"},{"line_number":952,"context_line":"                        f\"Expected 5-15 containers, \""},{"line_number":953,"context_line":"                        f\"got {len(containers)}\")"},{"line_number":954,"context_line":""}],"source_content_type":"text/x-python","patch_set":11,"id":"ff35a5b4_3452b5de","line":951,"in_reply_to":"332286c7_573580eb","updated":"2026-05-06 23:48:50.000000000","message":"Done","commit_id":"2578b78cdeca08cdf31c5d0ab1a2995f51deed3f"}]}
