)]}'
{"/COMMIT_MSG":[{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"e85fe82281bcd789539f2ed805ba98233bdd4225","unresolved":true,"context_lines":[{"line_number":6,"context_line":""},{"line_number":7,"context_line":"swift-manage-expirer: sub-commands summary, details, evaluate-balance, rebalance"},{"line_number":8,"context_line":""},{"line_number":9,"context_line":"CLI utility designed to inspect, manage and rebalance the swift object expirer queue. It introduces subcommands: summary, details, evaluate-balance and rebalance."},{"line_number":10,"context_line":""},{"line_number":11,"context_line":"1. The summary subcommand quickly evaluates task container-level metadata to give operators a high-level overview of expiration activity across a time window, using parallel workers and multiprocessing queues."},{"line_number":12,"context_line":""}],"source_content_type":"text/x-gerrit-commit-message","patch_set":3,"id":"093cee8c_9742d92a","line":9,"updated":"2025-07-01 20:34:28.000000000","message":"Please wrap commit messages at 72 characters, per the openstack commit message guidelines:\n\n\u003e Subsequent lines should be wrapped at 72 characters.\n\nhttps://wiki.openstack.org/wiki/GitCommitMessages#Summary_of_Git_commit_message_structure","commit_id":"9d41a445b5d627b671676f80b125b92e0a72eb40"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"c3684af438cfb8b7ff8a9dcdbc18cde03ec2d177","unresolved":false,"context_lines":[{"line_number":6,"context_line":""},{"line_number":7,"context_line":"swift-manage-expirer: sub-commands summary, details, evaluate-balance, rebalance"},{"line_number":8,"context_line":""},{"line_number":9,"context_line":"CLI utility designed to inspect, manage and rebalance the swift object expirer queue. It introduces subcommands: summary, details, evaluate-balance and rebalance."},{"line_number":10,"context_line":""},{"line_number":11,"context_line":"1. The summary subcommand quickly evaluates task container-level metadata to give operators a high-level overview of expiration activity across a time window, using parallel workers and multiprocessing queues."},{"line_number":12,"context_line":""}],"source_content_type":"text/x-gerrit-commit-message","patch_set":3,"id":"96bbeac0_dc5375d6","line":9,"in_reply_to":"093cee8c_9742d92a","updated":"2025-07-21 17:31:03.000000000","message":"Done","commit_id":"9d41a445b5d627b671676f80b125b92e0a72eb40"}],"/PATCHSET_LEVEL":[{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"e85fe82281bcd789539f2ed805ba98233bdd4225","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":3,"id":"5fcdc4e7_ee6b8be7","updated":"2025-07-01 20:34:28.000000000","message":"rebalance subcommand doesn\u0027t make sense with out task_container_per_day - you need to rebase this on 933373: Configurable expiring_objects_task_container_per_day | https://review.opendev.org/c/openstack/swift/+/933373\n\n... for now I guess just try to leave `swift-expirer-rebalancer \u003d swift.cli.expirer_rebalancer:main` alone - but eventually `s-m-e rebalance` should replace it.\n\nYou need to add a new entrypoint for swift-manage-expirer (s-m-e):\n\n```\ndiff --git a/setup.cfg b/setup.cfg\nindex 830268bb3..2eca67921 100644\n--- a/setup.cfg\n+++ b/setup.cfg\n@@ -73,6 +73,7 @@ console_scripts \u003d\n     swift-form-signature \u003d swift.cli.form_signature:main\n     swift-get-nodes \u003d swift.cli.get_nodes:main\n     swift-init \u003d swift.common.manager:main\n+    swift-manage-expirer \u003d swift.cli.manage_expirer:main\n     swift-manage-shard-ranges \u003d swift.cli.manage_shard_ranges:main\n     swift-object-auditor \u003d swift.obj.auditor:main\n     swift-object-expirer \u003d swift.obj.expirer:main\n```\n\nPlease create a new `test/unit/cli/test_manage_expirer.py` - maybe you forgot to `git add` it?  You might be able to use https://review.opendev.org/c/openstack/swift/+/933373/26/test/unit/cli/test_expirer_rebalancer.py as inspiration.","commit_id":"9d41a445b5d627b671676f80b125b92e0a72eb40"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"6c7e652c8c7809bcfdf3b7491abac7779264a349","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":4,"id":"757c05e4_f4d65129","updated":"2025-07-11 00:28:38.000000000","message":"Still a big work-in-progress","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"c02bb7189797043b445dd70d9979cab0f33e35f1","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":4,"id":"ab32c95e_1564f9b9","updated":"2025-07-11 18:21:23.000000000","message":"YES!  I think you have summary vs detail task iteration happening at the right level, the map-reduce pattern of detail seems correct, the --verify-bytes option LGTM!\n\nthere\u0027s regression with using ic to manage listing pagination tho, you need to put that back - or implement pagination and explain why the stock pattern/helper doesn\u0027t meet your requirements.\n\nI forgot about `is_async` - I think the current stats we send to SRE use a different key name but we should probably talk them into moving it to a just use a label.\n\n... otherwise I think summary/details/rebalance are all pretty much sort of mostly kind of doing the right thing???  Like... we\u0027ll have to polish/refactor/test and figure out what to do about logging (it\u0027s hard with multiprocessing) maybe there\u0027s still some argparse patterns to clean up - but I think this is great progress!  The bones of the change are good!!!\n\nI think the most FUN thing to do next tho will be to play with `evaluate-reblance`?  I\u0027d start writing some tests for that tho.  I wouldn\u0027t recommend trying to test at the level of \"parse user readable text output\" - we want a function like:\n\n```\nbalance_details \u003d evaluate_expirer_balance(iter_of_containers)\n```\n\n... then in tests we can just pass it lists of crazy [im]balanced stub task_containers and assert `balance_details[\u0027overall\u0027] ~\u003d 100` or `balance_details[\u0027offset\u0027][0][\u0027balance\u0027] ~\u003d 0` ... we may want top level summary keys like \"worst_offset\" or \"largest_real_variance\" for display later, but I think table stakes are getting a \"balance_by_day\" map and then attempting a translation of those values into a single number we can call \"overall_balance\" that we normalize between 0 (worst possible?) and 100 (somehow *perfect*).\n\nI don\u0027t even know how you define \"worst possible\"... if you how `task_container_per_day \u003d 2` and one container has `1` and one has `100` that\u0027s bad... but what if it\u0027s `10` and `1000` is that \"worse\"?  Is it worse to have 10 \"really imbalanced\" containers or 100 \"slightly imbalanced\" containers?  We get to decide!  SO FUN!","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"484abbc8eeae309b3b6d4ee3431dc4cd08adf55f","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":14,"id":"ed78ad0f_c3eb7706","updated":"2025-11-06 21:13:41.000000000","message":"aww shoot - i have comments left over from who knows when.  Probably most of them aren\u0027t relevant anymore.","commit_id":"0575a1c05c0834c9cb87b186b9b10768aae3ae86"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"753979f4370f3b7b60a02c9e0f49719aa77080bc","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":24,"id":"9ae3fc6f_a162ccbf","updated":"2026-01-23 20:40:32.000000000","message":"i still like the unittests - pls get them passing and try make stronger asserts; presumably any value that\u0027s *truly* interesting should be available as a prometheus compatible metric - which is a highly structured data exchange format and our unittests should be able to parse - maybe let\u0027s just extract the interesting bits from there and then we need only the most surface level tests to assert the text/human output looks sort of reasonable (or at least says roughly the same thing as the interesting/correct prometheus metrics that we *actually* care about being *correct*)\n\nthe probetests seems pretty surface level \"doesn\u0027t blow up\" kind of tests - and they DO blow up (OMM and in the gate).  I think we\u0027re on the right track and they just need some more testing and careful attention.","commit_id":"9b364f337c3bd8a094d4718e9c9e3d53391da062"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"12d0c795cfdc51bf450b26f5e52f3c05313477ad","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":25,"id":"853263c1_19503682","updated":"2026-03-19 19:08:05.000000000","message":"This needs to be modified into just evaluate-balance and rebalance","commit_id":"0ef3c919bd09cfc3487cf9ce572c9198bc7c0a0e"}],"swift/cli/manage_expirer.py":[{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"f06f71bce549e09e6c3c7cdf1e45788cf3b77e51","unresolved":true,"context_lines":[{"line_number":16,"context_line":"DEFAULT_NUM_DAYS \u003d 10"},{"line_number":17,"context_line":""},{"line_number":18,"context_line":""},{"line_number":19,"context_line":"class BaseExpirerStatus:"},{"line_number":20,"context_line":"    def __init__(self, args):"},{"line_number":21,"context_line":"        self.args \u003d args"},{"line_number":22,"context_line":"        self.hostname \u003d socket.gethostname()"}],"source_content_type":"text/x-python","patch_set":2,"id":"755538f9_900ebabb","line":19,"range":{"start_line":19,"start_character":6,"end_line":19,"end_character":10},"updated":"2025-06-26 22:09:44.000000000","message":"Is there a plan to have some subclass in a later patch or something?","commit_id":"ff1142b399db77b1ff73ea50b27cd59d9de886a1"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"f06f71bce549e09e6c3c7cdf1e45788cf3b77e51","unresolved":true,"context_lines":[{"line_number":60,"context_line":"                        line1 \u003d ("},{"line_number":61,"context_line":"                            f\u0027expiring_objects_entries{{host\u003d\"{self.hostname}\", account\u003d\"{account}\", \u0027"},{"line_number":62,"context_line":"                            f\u0027container\u003d\"{container}\", days\u003d\"{days}\", grace\u003d\"{grace}\"}} 1\u0027"},{"line_number":63,"context_line":"                        )"},{"line_number":64,"context_line":"                        line2 \u003d ("},{"line_number":65,"context_line":"                            f\u0027expiring_objects_bytes{{host\u003d\"{self.hostname}\", account\u003d\"{account}\", \u0027"},{"line_number":66,"context_line":"                            f\u0027container\u003d\"{container}\", days\u003d\"{days}\", grace\u003d\"{grace}\"}} {size}\u0027"}],"source_content_type":"text/x-python","patch_set":2,"id":"e0621f7c_e0b21796","line":63,"updated":"2025-06-26 22:09:44.000000000","message":"These look like labeled metrics or something... but we\u0027re putting sending them out to stdout, is that right? Should we make `_get_labeled_statsd_formatter` in `swift/common/statsd_client.py` more of a public function and make use of that?\n\nWhy do the formatting here, rather than passing around some data structure and formatting closer to emission? Even just something like\n```\nlabels \u003d {\u0027account\u0027: account, \u0027container\u0027: container, \u0027days\u0027: days, \u0027grace\u0027: grace}\nresult_q.put((\u0027expiring_objects_entries\u0027, labels, 1))\nresult_q.put((\u0027expiring_objects_bytes\u0027, labels, size))\n```\nseems way better to me. Plus it\u0027ll mean that we\u0027ll only have one place that we need to escape values -- what happens if `container` has a `\"` in it?\n\nDo operators even really *want* results on stdout, or would they be happier to have this pointed directly at some statsd endpoint?","commit_id":"ff1142b399db77b1ff73ea50b27cd59d9de886a1"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"c02bb7189797043b445dd70d9979cab0f33e35f1","unresolved":false,"context_lines":[{"line_number":60,"context_line":"                        line1 \u003d ("},{"line_number":61,"context_line":"                            f\u0027expiring_objects_entries{{host\u003d\"{self.hostname}\", account\u003d\"{account}\", \u0027"},{"line_number":62,"context_line":"                            f\u0027container\u003d\"{container}\", days\u003d\"{days}\", grace\u003d\"{grace}\"}} 1\u0027"},{"line_number":63,"context_line":"                        )"},{"line_number":64,"context_line":"                        line2 \u003d ("},{"line_number":65,"context_line":"                            f\u0027expiring_objects_bytes{{host\u003d\"{self.hostname}\", account\u003d\"{account}\", \u0027"},{"line_number":66,"context_line":"                            f\u0027container\u003d\"{container}\", days\u003d\"{days}\", grace\u003d\"{grace}\"}} {size}\u0027"}],"source_content_type":"text/x-python","patch_set":2,"id":"3cdd30b2_a11ed14e","line":63,"in_reply_to":"d14b0897_ad26eb6b","updated":"2025-07-11 18:21:23.000000000","message":"Done","commit_id":"ff1142b399db77b1ff73ea50b27cd59d9de886a1"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"e85fe82281bcd789539f2ed805ba98233bdd4225","unresolved":true,"context_lines":[{"line_number":60,"context_line":"                        line1 \u003d ("},{"line_number":61,"context_line":"                            f\u0027expiring_objects_entries{{host\u003d\"{self.hostname}\", account\u003d\"{account}\", \u0027"},{"line_number":62,"context_line":"                            f\u0027container\u003d\"{container}\", days\u003d\"{days}\", grace\u003d\"{grace}\"}} 1\u0027"},{"line_number":63,"context_line":"                        )"},{"line_number":64,"context_line":"                        line2 \u003d ("},{"line_number":65,"context_line":"                            f\u0027expiring_objects_bytes{{host\u003d\"{self.hostname}\", account\u003d\"{account}\", \u0027"},{"line_number":66,"context_line":"                            f\u0027container\u003d\"{container}\", days\u003d\"{days}\", grace\u003d\"{grace}\"}} {size}\u0027"}],"source_content_type":"text/x-python","patch_set":2,"id":"d14b0897_ad26eb6b","line":63,"in_reply_to":"e0621f7c_e0b21796","updated":"2025-07-01 20:34:28.000000000","message":"\u003e These look like labeled metrics or something\n\nit\u0027s prometheus compatible otel format, this format is an nvidia ops requirement - although in theory other formats could also potentially be useful.\n\n\u003e Do operators even really want results on stdout, or would they be happier to have this pointed directly at some statsd endpoint?\n\nIIRC the unix philosophy is tools should use stderr/out and let ops put that in a file with redirection; but we could alternatively add support for `--output` arg as long as it supported `--output -` (by default?)\n\n\u003e passing around some data structure and formatting closer to emission\n\nthis seems like solid design feedback: text is trickier than you think, data exchange formats are tricker than you think, plan for bugs \u0026 maintenance, do NOT repeat yourself.","commit_id":"ff1142b399db77b1ff73ea50b27cd59d9de886a1"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"f06f71bce549e09e6c3c7cdf1e45788cf3b77e51","unresolved":true,"context_lines":[{"line_number":79,"context_line":"                    return"},{"line_number":80,"context_line":"                container_name, count \u003d self.summarize_task_container("},{"line_number":81,"context_line":"                    ic,container_info)"},{"line_number":82,"context_line":"                result_q.put((container_name, count))"},{"line_number":83,"context_line":""},{"line_number":84,"context_line":"    def lookup_bytes(self, ic, account, container, _object):"},{"line_number":85,"context_line":"        # Use the given internal client ic to get the content-length of object"}],"source_content_type":"text/x-python","patch_set":2,"id":"6a0bda23_6d572f29","line":82,"updated":"2025-06-26 22:09:44.000000000","message":"Oh, yeah! More like this one!\n\nInteresting to me that we\u0027ve got two different methods with a signature like `def method(self, container_q, result_q):` but the `result_q` entries need to look very different... at the very least, we\u0027ll want some docstrings to help us keep our queues straight. Maybe we could also rename this one to something like `summary_result_q`?","commit_id":"ff1142b399db77b1ff73ea50b27cd59d9de886a1"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"f06f71bce549e09e6c3c7cdf1e45788cf3b77e51","unresolved":true,"context_lines":[{"line_number":156,"context_line":"            container_counts[offset] +\u003d count"},{"line_number":157,"context_line":"            print("},{"line_number":158,"context_line":"                f\u0027expirer_task_container_objects{{host\u003d\"{self.hostname}\",\u0027"},{"line_number":159,"context_line":"                f\u0027container\u003d\"{container_name}\", days\u003d\"{offset}\"}} {count}\u0027"},{"line_number":160,"context_line":"            )"},{"line_number":161,"context_line":"            total_containers +\u003d 1"},{"line_number":162,"context_line":"            total_entries +\u003d count"}],"source_content_type":"text/x-python","patch_set":2,"id":"897c5a67_c8ef53ae","line":159,"updated":"2025-06-26 22:09:44.000000000","message":"OK, and we didn\u0027t include `account` here because it\u0027s always `\".expiring_objects\"` -- but maybe it\u0027d be good to include it anyway, for consistency? Or rename this label to something like `expirer_bucket`.","commit_id":"ff1142b399db77b1ff73ea50b27cd59d9de886a1"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"c02bb7189797043b445dd70d9979cab0f33e35f1","unresolved":false,"context_lines":[{"line_number":156,"context_line":"            container_counts[offset] +\u003d count"},{"line_number":157,"context_line":"            print("},{"line_number":158,"context_line":"                f\u0027expirer_task_container_objects{{host\u003d\"{self.hostname}\",\u0027"},{"line_number":159,"context_line":"                f\u0027container\u003d\"{container_name}\", days\u003d\"{offset}\"}} {count}\u0027"},{"line_number":160,"context_line":"            )"},{"line_number":161,"context_line":"            total_containers +\u003d 1"},{"line_number":162,"context_line":"            total_entries +\u003d count"}],"source_content_type":"text/x-python","patch_set":2,"id":"b9bb5556_0d81f7ee","line":159,"in_reply_to":"7325ca7c_96e868e7","updated":"2025-07-11 18:21:23.000000000","message":"Done","commit_id":"ff1142b399db77b1ff73ea50b27cd59d9de886a1"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"e85fe82281bcd789539f2ed805ba98233bdd4225","unresolved":true,"context_lines":[{"line_number":156,"context_line":"            container_counts[offset] +\u003d count"},{"line_number":157,"context_line":"            print("},{"line_number":158,"context_line":"                f\u0027expirer_task_container_objects{{host\u003d\"{self.hostname}\",\u0027"},{"line_number":159,"context_line":"                f\u0027container\u003d\"{container_name}\", days\u003d\"{offset}\"}} {count}\u0027"},{"line_number":160,"context_line":"            )"},{"line_number":161,"context_line":"            total_containers +\u003d 1"},{"line_number":162,"context_line":"            total_entries +\u003d count"}],"source_content_type":"text/x-python","patch_set":2,"id":"7325ca7c_96e868e7","line":159,"in_reply_to":"897c5a67_c8ef53ae","updated":"2025-07-01 20:34:28.000000000","message":"I don\u0027t think it\u0027d be helpful to include the account, i\u0027m not 100% it\u0027s that useful to include the task_container...\n\nSRE is going to want to aggregate on the `days\u003d{offset}` label (and I think that\u0027s the most helpful to think about the information) - adding the container dimension just explodes the carnality of the labels and makes it more necessary to use a metrics aggregator to make sense of the report.","commit_id":"ff1142b399db77b1ff73ea50b27cd59d9de886a1"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"e85fe82281bcd789539f2ed805ba98233bdd4225","unresolved":true,"context_lines":[{"line_number":44,"context_line":"    def summarize_task_container(self, ic, container_info):"},{"line_number":45,"context_line":"        count \u003d 0"},{"line_number":46,"context_line":"        for _ in ic.iter_objects(EXPIRING_ACCOUNT, container_info[\"name\"]):"},{"line_number":47,"context_line":"            count +\u003d 1"},{"line_number":48,"context_line":"        return container_info[\"name\"], count"},{"line_number":49,"context_line":""},{"line_number":50,"context_line":"    def _listing_worker_details(self, container_q, result_q):"}],"source_content_type":"text/x-python","patch_set":3,"id":"962fe029_fb6459d1","line":47,"updated":"2025-07-01 20:34:28.000000000","message":"this is obviously throwing away a lot of information; in \"_listing_worker_detail\" we\u0027re doing this same expensive IO but doing a little bit more parsing/computation to get a TON more information.\n\nI think doing task-listing/object-iteration in the summary subcommand is wasteful.","commit_id":"9d41a445b5d627b671676f80b125b92e0a72eb40"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"c02bb7189797043b445dd70d9979cab0f33e35f1","unresolved":false,"context_lines":[{"line_number":44,"context_line":"    def summarize_task_container(self, ic, container_info):"},{"line_number":45,"context_line":"        count \u003d 0"},{"line_number":46,"context_line":"        for _ in ic.iter_objects(EXPIRING_ACCOUNT, container_info[\"name\"]):"},{"line_number":47,"context_line":"            count +\u003d 1"},{"line_number":48,"context_line":"        return container_info[\"name\"], count"},{"line_number":49,"context_line":""},{"line_number":50,"context_line":"    def _listing_worker_details(self, container_q, result_q):"}],"source_content_type":"text/x-python","patch_set":3,"id":"599eb93f_55fcebac","line":47,"in_reply_to":"962fe029_fb6459d1","updated":"2025-07-11 18:21:23.000000000","message":"Done","commit_id":"9d41a445b5d627b671676f80b125b92e0a72eb40"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"e85fe82281bcd789539f2ed805ba98233bdd4225","unresolved":true,"context_lines":[{"line_number":45,"context_line":"        count \u003d 0"},{"line_number":46,"context_line":"        for _ in ic.iter_objects(EXPIRING_ACCOUNT, container_info[\"name\"]):"},{"line_number":47,"context_line":"            count +\u003d 1"},{"line_number":48,"context_line":"        return container_info[\"name\"], count"},{"line_number":49,"context_line":""},{"line_number":50,"context_line":"    def _listing_worker_details(self, container_q, result_q):"},{"line_number":51,"context_line":"        with self.internal_client_ctx(\"swift-manage-expirer-\""}],"source_content_type":"text/x-python","patch_set":3,"id":"e3248e58_2d4ffc3d","line":48,"updated":"2025-07-01 20:34:28.000000000","message":"In a consistent system the value of `count` should be equal to `container_info[\u0027count\u0027]`\n\nThe difference is we got `container_info[\u0027count\u0027]` practically for free from the account listing - where as count needed to list the entire container.","commit_id":"9d41a445b5d627b671676f80b125b92e0a72eb40"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"c02bb7189797043b445dd70d9979cab0f33e35f1","unresolved":false,"context_lines":[{"line_number":45,"context_line":"        count \u003d 0"},{"line_number":46,"context_line":"        for _ in ic.iter_objects(EXPIRING_ACCOUNT, container_info[\"name\"]):"},{"line_number":47,"context_line":"            count +\u003d 1"},{"line_number":48,"context_line":"        return container_info[\"name\"], count"},{"line_number":49,"context_line":""},{"line_number":50,"context_line":"    def _listing_worker_details(self, container_q, result_q):"},{"line_number":51,"context_line":"        with self.internal_client_ctx(\"swift-manage-expirer-\""}],"source_content_type":"text/x-python","patch_set":3,"id":"2bf0ce80_8691a2c6","line":48,"in_reply_to":"e3248e58_2d4ffc3d","updated":"2025-07-11 18:21:23.000000000","message":"Done","commit_id":"9d41a445b5d627b671676f80b125b92e0a72eb40"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"e85fe82281bcd789539f2ed805ba98233bdd4225","unresolved":true,"context_lines":[{"line_number":75,"context_line":"                            f\u0027container\u003d\"{container}\", days\u003d\"{days}\", grace\u003d\"{grace}\"}} {size}\u0027"},{"line_number":76,"context_line":"                        )"},{"line_number":77,"context_line":"                        result_q.put(line1)"},{"line_number":78,"context_line":"                        result_q.put(line2)"},{"line_number":79,"context_line":"                    except Exception as e:"},{"line_number":80,"context_line":"                        logging.warning(f\"Failed to parse object {obj[\u0027name\u0027]}: {e}\")"},{"line_number":81,"context_line":""}],"source_content_type":"text/x-python","patch_set":3,"id":"8768b52b_90748545","line":78,"updated":"2025-07-01 20:34:28.000000000","message":"this is the wrong level of aggregation - these queues are not being drained while the workers are filling them - with two messages for each expirer task (!!??) I was able to get `s-m-e details` to deadlock with only 2K expired objects in my queue.\n\nSince we need to support billions of expirer tasks - we want each listing worker to emit a single mapped aggregate to the result queue:\n\nstat_name, account, container, days, delay \u003d\u003e \u003caggregate_count\u003e\n\neach task object should increment two \"stat_name\" entries in the map, the count and bytes:\n\n```\nstats \u003d defaultdict(int)\nfor obj in ic.iter_objects():\n    ts, account, container \u003d parse_task_object(obj[\u0027name\u0027])\n    # this helper should consider the expirer\u0027s delay_reaping configuration\n    days, delay \u003d self.parse_delay(ts, account, container)\n    stats[(\u0027count\u0027, account, container, days, delay)] +\u003d 1\n    bytes \u003d extract_expirer_bytes(obj[\u0027content_type\u0027])\n    stats[(\u0027bytes\u0027, account, container, days, delay)] +\u003d bytes\nresult_q.put(stats)\n```\n\nThen the parent method will join the container listings and reduce their stats before emitting the results.","commit_id":"9d41a445b5d627b671676f80b125b92e0a72eb40"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"c02bb7189797043b445dd70d9979cab0f33e35f1","unresolved":false,"context_lines":[{"line_number":75,"context_line":"                            f\u0027container\u003d\"{container}\", days\u003d\"{days}\", grace\u003d\"{grace}\"}} {size}\u0027"},{"line_number":76,"context_line":"                        )"},{"line_number":77,"context_line":"                        result_q.put(line1)"},{"line_number":78,"context_line":"                        result_q.put(line2)"},{"line_number":79,"context_line":"                    except Exception as e:"},{"line_number":80,"context_line":"                        logging.warning(f\"Failed to parse object {obj[\u0027name\u0027]}: {e}\")"},{"line_number":81,"context_line":""}],"source_content_type":"text/x-python","patch_set":3,"id":"32d09172_db89ccd1","line":78,"in_reply_to":"8768b52b_90748545","updated":"2025-07-11 18:21:23.000000000","message":"Done","commit_id":"9d41a445b5d627b671676f80b125b92e0a72eb40"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"e85fe82281bcd789539f2ed805ba98233bdd4225","unresolved":true,"context_lines":[{"line_number":94,"context_line":"    def lookup_bytes(self, ic, account, container, _object):"},{"line_number":95,"context_line":"        # Use the given internal client ic to get the content-length of object"},{"line_number":96,"context_line":"        try:"},{"line_number":97,"context_line":"            resp \u003d ic.get_object_metadata("},{"line_number":98,"context_line":"                account, container, _object,"},{"line_number":99,"context_line":"                headers\u003d{\u0027x-open-expired\u0027: \u0027true\u0027},"},{"line_number":100,"context_line":"                acceptable_statuses\u003d(404, 200,)"}],"source_content_type":"text/x-python","patch_set":3,"id":"6083a6b2_0cc21113","line":97,"updated":"2025-07-01 20:34:28.000000000","message":"doing a HEAD on every object in the expiry queue is *exorbitantly* expensive; the ability to opt-in to this level of validation may be useful, but it\u0027s not required - for modern objects the bytes is available from the task_container listing object_item by parsing the content_type field:\n\n```\n\u0027content_type\u0027: \u0027text/plain;swift_expirer_bytes\u003d3\u0027\n```\n\nthere\u0027s even a helper to parse it, see `swift.obj.expirer.extract_expirer_bytes_from_ctype`\n\n... if we want to support the option to `--verify-bytes` the option *has* to default to off","commit_id":"9d41a445b5d627b671676f80b125b92e0a72eb40"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"c02bb7189797043b445dd70d9979cab0f33e35f1","unresolved":false,"context_lines":[{"line_number":94,"context_line":"    def lookup_bytes(self, ic, account, container, _object):"},{"line_number":95,"context_line":"        # Use the given internal client ic to get the content-length of object"},{"line_number":96,"context_line":"        try:"},{"line_number":97,"context_line":"            resp \u003d ic.get_object_metadata("},{"line_number":98,"context_line":"                account, container, _object,"},{"line_number":99,"context_line":"                headers\u003d{\u0027x-open-expired\u0027: \u0027true\u0027},"},{"line_number":100,"context_line":"                acceptable_statuses\u003d(404, 200,)"}],"source_content_type":"text/x-python","patch_set":3,"id":"874ba681_b9a4880a","line":97,"in_reply_to":"6083a6b2_0cc21113","updated":"2025-07-11 18:21:23.000000000","message":"Done","commit_id":"9d41a445b5d627b671676f80b125b92e0a72eb40"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"e85fe82281bcd789539f2ed805ba98233bdd4225","unresolved":true,"context_lines":[{"line_number":112,"context_line":"        start_cutoff \u003d self.now + datetime.timedelta("},{"line_number":113,"context_line":"            days\u003dself.args.start_day_offset)"},{"line_number":114,"context_line":"        end_cutoff \u003d self.now + datetime.timedelta("},{"line_number":115,"context_line":"            days\u003dself.args.start_day_offset + DEFAULT_NUM_DAYS)"},{"line_number":116,"context_line":""},{"line_number":117,"context_line":"        container_q \u003d mp.Queue()"},{"line_number":118,"context_line":"        result_q \u003d mp.Queue()"}],"source_content_type":"text/x-python","patch_set":3,"id":"6ea5d278_aff1e94c","line":115,"updated":"2025-07-01 20:34:28.000000000","message":"I don\u0027t understand why summary\u0027s end_cutoff isn\u0027t configurable; granted it\u0027s fairly cheap to GET /account and list the containers; but that\u0027s probably a reason to want to evaluate ALL the container_info results (esp upcoming/future expiration) - not just the last 10 days.\n\nI setup a test rig to have some objects expire \"today\" and some \"tomorrow\" - it\u0027s weird for me that the \"default\" -10\u003d\u003e0 days only reports on half my containers.","commit_id":"9d41a445b5d627b671676f80b125b92e0a72eb40"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"c02bb7189797043b445dd70d9979cab0f33e35f1","unresolved":false,"context_lines":[{"line_number":112,"context_line":"        start_cutoff \u003d self.now + datetime.timedelta("},{"line_number":113,"context_line":"            days\u003dself.args.start_day_offset)"},{"line_number":114,"context_line":"        end_cutoff \u003d self.now + datetime.timedelta("},{"line_number":115,"context_line":"            days\u003dself.args.start_day_offset + DEFAULT_NUM_DAYS)"},{"line_number":116,"context_line":""},{"line_number":117,"context_line":"        container_q \u003d mp.Queue()"},{"line_number":118,"context_line":"        result_q \u003d mp.Queue()"}],"source_content_type":"text/x-python","patch_set":3,"id":"75176cfe_1ea25532","line":115,"in_reply_to":"6ea5d278_aff1e94c","updated":"2025-07-11 18:21:23.000000000","message":"Done","commit_id":"9d41a445b5d627b671676f80b125b92e0a72eb40"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"e85fe82281bcd789539f2ed805ba98233bdd4225","unresolved":true,"context_lines":[{"line_number":139,"context_line":"                container_q.put(container_info)"},{"line_number":140,"context_line":""},{"line_number":141,"context_line":"        workers \u003d []"},{"line_number":142,"context_line":"        for _ in range(self.args.listing_workers):"},{"line_number":143,"context_line":"            p \u003d mp.Process(target\u003dself.listing_worker_summary,"},{"line_number":144,"context_line":"                           args\u003d(container_q, result_q))"},{"line_number":145,"context_line":"            p.start()"}],"source_content_type":"text/x-python","patch_set":3,"id":"53faa541_60dc4859","line":142,"updated":"2025-07-01 20:34:28.000000000","message":"maybe I\u0027m mis-understanding the summary subcommand - I didn\u0027t expect to see listing workers.\n\nIt leads to an interesting behavioral quirk where `--listing-workers 0` complete\u0027s very fast but doesn\u0027t output a bunch of useful stats that it could parse from `container_info`","commit_id":"9d41a445b5d627b671676f80b125b92e0a72eb40"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"c02bb7189797043b445dd70d9979cab0f33e35f1","unresolved":false,"context_lines":[{"line_number":139,"context_line":"                container_q.put(container_info)"},{"line_number":140,"context_line":""},{"line_number":141,"context_line":"        workers \u003d []"},{"line_number":142,"context_line":"        for _ in range(self.args.listing_workers):"},{"line_number":143,"context_line":"            p \u003d mp.Process(target\u003dself.listing_worker_summary,"},{"line_number":144,"context_line":"                           args\u003d(container_q, result_q))"},{"line_number":145,"context_line":"            p.start()"}],"source_content_type":"text/x-python","patch_set":3,"id":"d17d1be6_a732bbf8","line":142,"in_reply_to":"53faa541_60dc4859","updated":"2025-07-11 18:21:23.000000000","message":"Done","commit_id":"9d41a445b5d627b671676f80b125b92e0a72eb40"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"e85fe82281bcd789539f2ed805ba98233bdd4225","unresolved":true,"context_lines":[{"line_number":208,"context_line":"            workers.append(p)"},{"line_number":209,"context_line":""},{"line_number":210,"context_line":"        for p in workers:"},{"line_number":211,"context_line":"            p.join()"},{"line_number":212,"context_line":""},{"line_number":213,"context_line":"        while True:"},{"line_number":214,"context_line":"            try:"}],"source_content_type":"text/x-python","patch_set":3,"id":"29dbf349_8299d86c","line":211,"updated":"2025-07-01 20:34:28.000000000","message":"the ctrl-c experience here w/ 20 workers is not very good - it can be very hard to kill this thing; please consider adding some basic error handling:\n\n```\ndiff --git a/swift/cli/manage_expirer.py b/swift/cli/manage_expirer.py\nindex 214f06283..7d11bba12 100644\n--- a/swift/cli/manage_expirer.py\n+++ b/swift/cli/manage_expirer.py\n@@ -8,6 +8,7 @@ import queue\n import datetime\n import eventlet\n import socket\n+import sys\n \n from swift.common.internal_client import InternalClient\n from swift.common.utils import readconf, distribute_evenly\n@@ -207,8 +208,13 @@ class BaseExpirerStatus:\n             p.start()\n             workers.append(p)\n \n-        for p in workers:\n-            p.join()\n+        try:\n+            for p in workers:\n+                p.join()\n+        except KeyboardInterrupt:\n+            print(\u0027shutting down; hang tight!\u0027, file\u003dsys.stderr)\n+            for p in workers:\n+                p.terminate()\n \n         while True:\n             try:\n\n```\n\nIf you add some base class for spawning/managing workers and tracking process state in a BaseManager class you might be able to do something more elegant with a finally down in `main` that calls some kind of `manager.terminate()` helper","commit_id":"9d41a445b5d627b671676f80b125b92e0a72eb40"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"74e437a54c0272552814b3002326908f009e57c9","unresolved":false,"context_lines":[{"line_number":208,"context_line":"            workers.append(p)"},{"line_number":209,"context_line":""},{"line_number":210,"context_line":"        for p in workers:"},{"line_number":211,"context_line":"            p.join()"},{"line_number":212,"context_line":""},{"line_number":213,"context_line":"        while True:"},{"line_number":214,"context_line":"            try:"}],"source_content_type":"text/x-python","patch_set":3,"id":"85205ae6_f2975150","line":211,"in_reply_to":"29dbf349_8299d86c","updated":"2025-07-23 02:11:51.000000000","message":"Done","commit_id":"9d41a445b5d627b671676f80b125b92e0a72eb40"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"e85fe82281bcd789539f2ed805ba98233bdd4225","unresolved":true,"context_lines":[{"line_number":234,"context_line":"        ts \u003d int(container_name)"},{"line_number":235,"context_line":"        dt \u003d datetime.datetime.fromtimestamp("},{"line_number":236,"context_line":"            ts + self.expirer_conf.task_container_per_day)"},{"line_number":237,"context_line":"        return (dt - self.now).days"},{"line_number":238,"context_line":""},{"line_number":239,"context_line":"    def _scan_task_containers(self):"},{"line_number":240,"context_line":"        container_q \u003d mp.Queue()"}],"source_content_type":"text/x-python","patch_set":3,"id":"f27f9dc3_37aabca4","line":237,"updated":"2025-07-01 20:34:28.000000000","message":"I like this helper - it should be part of a base manager class","commit_id":"9d41a445b5d627b671676f80b125b92e0a72eb40"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"6dbbf7f39f2dc8c409a69c49e90569b1ea01cc9f","unresolved":false,"context_lines":[{"line_number":234,"context_line":"        ts \u003d int(container_name)"},{"line_number":235,"context_line":"        dt \u003d datetime.datetime.fromtimestamp("},{"line_number":236,"context_line":"            ts + self.expirer_conf.task_container_per_day)"},{"line_number":237,"context_line":"        return (dt - self.now).days"},{"line_number":238,"context_line":""},{"line_number":239,"context_line":"    def _scan_task_containers(self):"},{"line_number":240,"context_line":"        container_q \u003d mp.Queue()"}],"source_content_type":"text/x-python","patch_set":3,"id":"fc65f123_6592aec8","line":237,"in_reply_to":"16ed68ba_3dab8fd7","updated":"2025-11-17 17:53:45.000000000","message":"Done","commit_id":"9d41a445b5d627b671676f80b125b92e0a72eb40"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"f297b72faad24545283980079227d5d697900a16","unresolved":true,"context_lines":[{"line_number":234,"context_line":"        ts \u003d int(container_name)"},{"line_number":235,"context_line":"        dt \u003d datetime.datetime.fromtimestamp("},{"line_number":236,"context_line":"            ts + self.expirer_conf.task_container_per_day)"},{"line_number":237,"context_line":"        return (dt - self.now).days"},{"line_number":238,"context_line":""},{"line_number":239,"context_line":"    def _scan_task_containers(self):"},{"line_number":240,"context_line":"        container_q \u003d mp.Queue()"}],"source_content_type":"text/x-python","patch_set":3,"id":"16ed68ba_3dab8fd7","line":237,"in_reply_to":"f27f9dc3_37aabca4","updated":"2025-07-23 02:35:01.000000000","message":"I actually remove that helper entirely since in another comment you mentioned how we could compute this inline!","commit_id":"9d41a445b5d627b671676f80b125b92e0a72eb40"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"e85fe82281bcd789539f2ed805ba98233bdd4225","unresolved":true,"context_lines":[{"line_number":251,"context_line":"            offset \u003d self._get_day_offset(container_info[\"name\"])"},{"line_number":252,"context_line":"            if offset \u003c self.args.start_day_offset:"},{"line_number":253,"context_line":"                continue"},{"line_number":254,"context_line":"            container_q.put(container_info)"},{"line_number":255,"context_line":""},{"line_number":256,"context_line":"        workers \u003d []"},{"line_number":257,"context_line":"        for _ in range(self.args.listing_workers):"}],"source_content_type":"text/x-python","patch_set":3,"id":"aa824db8_d90b0c82","line":254,"updated":"2025-07-01 20:34:28.000000000","message":"while this command will most frequently be run against *all* task_containers - I think it would be useful to support filtering based on `--start-day-offset` and `--num-days`\n\nIn production clusters where you have years worth of expirations and rebalance operations will take hours/days to complete you might want to start with:\n\n```\ns-m-e evaluate-rebalance --start-day-offset 7 --num-days 30\n```\n\nto see what the balance for \"next month\" looks like...","commit_id":"9d41a445b5d627b671676f80b125b92e0a72eb40"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"c02bb7189797043b445dd70d9979cab0f33e35f1","unresolved":false,"context_lines":[{"line_number":251,"context_line":"            offset \u003d self._get_day_offset(container_info[\"name\"])"},{"line_number":252,"context_line":"            if offset \u003c self.args.start_day_offset:"},{"line_number":253,"context_line":"                continue"},{"line_number":254,"context_line":"            container_q.put(container_info)"},{"line_number":255,"context_line":""},{"line_number":256,"context_line":"        workers \u003d []"},{"line_number":257,"context_line":"        for _ in range(self.args.listing_workers):"}],"source_content_type":"text/x-python","patch_set":3,"id":"2ae27347_6d4b4a93","line":254,"in_reply_to":"aa824db8_d90b0c82","updated":"2025-07-11 18:21:23.000000000","message":"Done","commit_id":"9d41a445b5d627b671676f80b125b92e0a72eb40"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"e85fe82281bcd789539f2ed805ba98233bdd4225","unresolved":true,"context_lines":[{"line_number":285,"context_line":"            except queue.Empty:"},{"line_number":286,"context_line":"                return"},{"line_number":287,"context_line":"            count \u003d sum(1 for _ in ic.iter_objects(EXPIRING_ACCOUNT,"},{"line_number":288,"context_line":"                                                   container_info[\"name\"]))"},{"line_number":289,"context_line":"            offset \u003d self._get_day_offset(container_info[\"name\"])"},{"line_number":290,"context_line":"            result_q.put((container_info[\"name\"], offset, count))"},{"line_number":291,"context_line":""}],"source_content_type":"text/x-python","patch_set":3,"id":"f8bc2b51_d437690e","line":288,"updated":"2025-07-01 20:34:28.000000000","message":"this is the same problem as summary - we don\u0027t need to list each task_container - if you just want the count use `container_info[\u0027count\u0027]` - we don\u0027t need listing workers we can get everything we need from just listing the account.","commit_id":"9d41a445b5d627b671676f80b125b92e0a72eb40"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"c02bb7189797043b445dd70d9979cab0f33e35f1","unresolved":false,"context_lines":[{"line_number":285,"context_line":"            except queue.Empty:"},{"line_number":286,"context_line":"                return"},{"line_number":287,"context_line":"            count \u003d sum(1 for _ in ic.iter_objects(EXPIRING_ACCOUNT,"},{"line_number":288,"context_line":"                                                   container_info[\"name\"]))"},{"line_number":289,"context_line":"            offset \u003d self._get_day_offset(container_info[\"name\"])"},{"line_number":290,"context_line":"            result_q.put((container_info[\"name\"], offset, count))"},{"line_number":291,"context_line":""}],"source_content_type":"text/x-python","patch_set":3,"id":"24a10977_710b2142","line":288,"in_reply_to":"f8bc2b51_d437690e","updated":"2025-07-11 18:21:23.000000000","message":"Done","commit_id":"9d41a445b5d627b671676f80b125b92e0a72eb40"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"e85fe82281bcd789539f2ed805ba98233bdd4225","unresolved":true,"context_lines":[{"line_number":302,"context_line":"                                   counts_values) / len(counts_values))"},{"line_number":303,"context_line":"            date_str \u003d (self.now + datetime.timedelta(days\u003doffset)).strftime("},{"line_number":304,"context_line":"                \"%Y-%m-%d\")"},{"line_number":305,"context_line":"            print(f\"Day {offset:+} ({date_str}) balance \u003d {stddev:.2f}:\")"},{"line_number":306,"context_line":""},{"line_number":307,"context_line":"            for cname, cnt in sorted(counts.items()):"},{"line_number":308,"context_line":"                dev \u003d abs(cnt - avg)"}],"source_content_type":"text/x-python","patch_set":3,"id":"43018621_ae9225b2","line":305,"updated":"2025-07-01 20:34:28.000000000","message":"I don\u0027t think we want to call \"balance\" \u003d\u003d \"stddev\"\n\nWe want to spit out a number that goes to \"100% balanced\" as the task containers with disproportionate task counts trends towards 0.\n\nWe need to perform an evaluation of \"balance\" that accounts for \"a few containers WAY outside of the stddev\" OR \"a large number of containers significantly outside of the stddev\"\n\nAs a first-swag, maybe ...\n\n```\nbad_containers \u003d [c for c in all_c if dev(c) \u003e cutoff]\nbalance \u003d (len(all_c) - len(bad_containers)) / len(all_c) * 100\n```\n\n... and then we figure out how to make balance even LOWER as the the `sum(dev(c) for c in bad_container)` gets more and more multiples of stddev away from the avg?","commit_id":"9d41a445b5d627b671676f80b125b92e0a72eb40"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"6dbbf7f39f2dc8c409a69c49e90569b1ea01cc9f","unresolved":false,"context_lines":[{"line_number":302,"context_line":"                                   counts_values) / len(counts_values))"},{"line_number":303,"context_line":"            date_str \u003d (self.now + datetime.timedelta(days\u003doffset)).strftime("},{"line_number":304,"context_line":"                \"%Y-%m-%d\")"},{"line_number":305,"context_line":"            print(f\"Day {offset:+} ({date_str}) balance \u003d {stddev:.2f}:\")"},{"line_number":306,"context_line":""},{"line_number":307,"context_line":"            for cname, cnt in sorted(counts.items()):"},{"line_number":308,"context_line":"                dev \u003d abs(cnt - avg)"}],"source_content_type":"text/x-python","patch_set":3,"id":"e30dbb38_294ded6a","line":305,"in_reply_to":"43018621_ae9225b2","updated":"2025-11-17 17:53:45.000000000","message":"Done","commit_id":"9d41a445b5d627b671676f80b125b92e0a72eb40"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"e85fe82281bcd789539f2ed805ba98233bdd4225","unresolved":true,"context_lines":[{"line_number":306,"context_line":""},{"line_number":307,"context_line":"            for cname, cnt in sorted(counts.items()):"},{"line_number":308,"context_line":"                dev \u003d abs(cnt - avg)"},{"line_number":309,"context_line":"                mark \u003d \"\u003c-- High skew\" if dev \u003e stddev * 2 else \"\""},{"line_number":310,"context_line":"                print(f\"  Container {cname}: {cnt} entries \""},{"line_number":311,"context_line":"                      f\"(dev: {dev:.2f}) {mark}\")"},{"line_number":312,"context_line":""}],"source_content_type":"text/x-python","patch_set":3,"id":"ea5ac939_6fc3d645","line":309,"updated":"2025-07-01 20:34:28.000000000","message":"this `stddev * 2` \"cutoff\" needs to be configurable; i\u0027m not even sure this makes sense:\n\n```\n  Container 1751414390: 32 entries (dev: 3.09) \n  Container 1751414391: 41 entries (dev: 12.09) \n  Container 1751414392: 42 entries (dev: 13.09) \u003c-- High skew\n  Container 1751414393: 35 entries (dev: 6.09) \n  Container 1751414394: 37 entries (dev: 8.09) \n```\n\nlike it\u0027s \"weird\" that some containers have 32 and some have 42 - but with 1K expired tasks per day that\u0027s how to fell out on my machine\n\nwe need to tweak cutoff so that the default \"balance\" when using the default config w/o changing task_container_per_day tends to look \"ok\" (i.e. \u003e90% balance)\n\nideally if we put 1K tasks into a 100 task_containers and then crank task_container_per_day down to 10 and put another 1K tasks into the same day a `s-m-e evaluate-balance` would say `~balance\u003d50%` and I guess get worse the more objects you write in with the new config?\n\nThis is probably something we\u0027ll have to iterate on with some unittests; so you might think about how to make it a testable interface:\n\n```\nself.assertEqual(100, get_balance(counts, cutoff))\n```","commit_id":"9d41a445b5d627b671676f80b125b92e0a72eb40"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"6dbbf7f39f2dc8c409a69c49e90569b1ea01cc9f","unresolved":false,"context_lines":[{"line_number":306,"context_line":""},{"line_number":307,"context_line":"            for cname, cnt in sorted(counts.items()):"},{"line_number":308,"context_line":"                dev \u003d abs(cnt - avg)"},{"line_number":309,"context_line":"                mark \u003d \"\u003c-- High skew\" if dev \u003e stddev * 2 else \"\""},{"line_number":310,"context_line":"                print(f\"  Container {cname}: {cnt} entries \""},{"line_number":311,"context_line":"                      f\"(dev: {dev:.2f}) {mark}\")"},{"line_number":312,"context_line":""}],"source_content_type":"text/x-python","patch_set":3,"id":"2ac7b133_db7e3f1d","line":309,"in_reply_to":"ea5ac939_6fc3d645","updated":"2025-11-17 17:53:45.000000000","message":"Acknowledged","commit_id":"9d41a445b5d627b671676f80b125b92e0a72eb40"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"e85fe82281bcd789539f2ed805ba98233bdd4225","unresolved":true,"context_lines":[{"line_number":310,"context_line":"                print(f\"  Container {cname}: {cnt} entries \""},{"line_number":311,"context_line":"                      f\"(dev: {dev:.2f}) {mark}\")"},{"line_number":312,"context_line":""},{"line_number":313,"context_line":"            if stddev \u003e avg * 0.2:"},{"line_number":314,"context_line":"                print(\"Imbalance detected. Recommend rebalance.\")"},{"line_number":315,"context_line":"            else:"},{"line_number":316,"context_line":"                print(\"Balance OK!\")"}],"source_content_type":"text/x-python","patch_set":3,"id":"f89d64f1_83769efc","line":313,"updated":"2025-07-01 20:34:28.000000000","message":"it\u0027s not obvious to me this is the same as \"one of the task-containers was marked high skew\" ... is the magic `0.2` in this math related to the magic `2` we use int he \"high skew\" math?  Can we generalize this to a \"cutoff\" that we could explain to ops?\n\nMy assumption is that with 100 tasks in 100 containers some might have 0 and some might have 2-3, but with 10,000 tasks in in 100 containers most will have \u003e 75 and few would have \u003e 125... so maybe the bounding expectations for \"balance\" have something to do with the total per-day?\n\nLike `cutoff\u003d25` means","commit_id":"9d41a445b5d627b671676f80b125b92e0a72eb40"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"e85fe82281bcd789539f2ed805ba98233bdd4225","unresolved":true,"context_lines":[{"line_number":317,"context_line":""},{"line_number":318,"context_line":"            print(f\"Expirer Task Queue Balance: {stddev:.2f}\\n\")"},{"line_number":319,"context_line":""},{"line_number":320,"context_line":"            if self.args.output_format \u003d\u003d \"prom\":"},{"line_number":321,"context_line":"                for cname, cnt in counts.items():"},{"line_number":322,"context_line":"                    print(f\u0027expirer_task_container_entries{{host\u003d\u0027"},{"line_number":323,"context_line":"                          f\u0027\"{self.hostname}\",\u0027"}],"source_content_type":"text/x-python","patch_set":3,"id":"cedcd851_b38b3386","line":320,"updated":"2025-07-01 20:34:28.000000000","message":"it seems like the options for `--output-format` is either \"text\" OR \"text AND prom\" - there\u0027s no option for \"just prom\"\n\nmaybe you could try to prefix all the text output with a `#` so they\u0027re treated as comments and it\u0027s at least valid prom?\n\n```\n#   Container 1751327999: 7 entries (dev: 1.23) \nexpirer_task_container_entries{host\u003d\"saio\",container\u003d\"1751327999\",days\u003d\"-1\"} 7\n#   Container 1751328000: 5 entries (dev: 0.77) \nexpirer_task_container_entries{host\u003d\"saio\",container\u003d\"1751328000\",days\u003d\"-1\"} 5\n```\n\n... might not look so bad?\n\nAlternatively maybe we skip prom output for evaluate balance; it doesn\u0027t seem like the kind of information you\u0027d need to collect as time series data.  Once you change the config and rebalance it\u0027ll stay balanced on it\u0027s own.","commit_id":"9d41a445b5d627b671676f80b125b92e0a72eb40"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"74e437a54c0272552814b3002326908f009e57c9","unresolved":false,"context_lines":[{"line_number":317,"context_line":""},{"line_number":318,"context_line":"            print(f\"Expirer Task Queue Balance: {stddev:.2f}\\n\")"},{"line_number":319,"context_line":""},{"line_number":320,"context_line":"            if self.args.output_format \u003d\u003d \"prom\":"},{"line_number":321,"context_line":"                for cname, cnt in counts.items():"},{"line_number":322,"context_line":"                    print(f\u0027expirer_task_container_entries{{host\u003d\u0027"},{"line_number":323,"context_line":"                          f\u0027\"{self.hostname}\",\u0027"}],"source_content_type":"text/x-python","patch_set":3,"id":"00a69093_fe3852e6","line":320,"in_reply_to":"cedcd851_b38b3386","updated":"2025-07-23 02:11:51.000000000","message":"Done","commit_id":"9d41a445b5d627b671676f80b125b92e0a72eb40"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"c02bb7189797043b445dd70d9979cab0f33e35f1","unresolved":true,"context_lines":[{"line_number":494,"context_line":"        \"--listing-workers\","},{"line_number":495,"context_line":"        type\u003dint,"},{"line_number":496,"context_line":"        default\u003d20,"},{"line_number":497,"context_line":"        help\u003d\"Number of parallel listing workers\","},{"line_number":498,"context_line":"    )"},{"line_number":499,"context_line":"    summary_parser.add_argument(\"-v\", \"--verbose\","},{"line_number":500,"context_line":"                                action\u003d\"count\", default\u003d0)"}],"source_content_type":"text/x-python","patch_set":3,"id":"62a8360a_cba3789f","line":497,"updated":"2025-07-11 18:21:23.000000000","message":"nice, this is not needed for summary - KUDOS","commit_id":"9d41a445b5d627b671676f80b125b92e0a72eb40"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"74e437a54c0272552814b3002326908f009e57c9","unresolved":false,"context_lines":[{"line_number":494,"context_line":"        \"--listing-workers\","},{"line_number":495,"context_line":"        type\u003dint,"},{"line_number":496,"context_line":"        default\u003d20,"},{"line_number":497,"context_line":"        help\u003d\"Number of parallel listing workers\","},{"line_number":498,"context_line":"    )"},{"line_number":499,"context_line":"    summary_parser.add_argument(\"-v\", \"--verbose\","},{"line_number":500,"context_line":"                                action\u003d\"count\", default\u003d0)"}],"source_content_type":"text/x-python","patch_set":3,"id":"7195a9e6_50aacdee","line":497,"in_reply_to":"62a8360a_cba3789f","updated":"2025-07-23 02:11:51.000000000","message":"Acknowledged","commit_id":"9d41a445b5d627b671676f80b125b92e0a72eb40"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"e85fe82281bcd789539f2ed805ba98233bdd4225","unresolved":true,"context_lines":[{"line_number":506,"context_line":"    details_parser.add_argument(\"--internal-client-conf-path\","},{"line_number":507,"context_line":"                                default\u003d\"/etc/swift/internal-client.conf\")"},{"line_number":508,"context_line":"    details_parser.add_argument(\"--expirer-conf-path\","},{"line_number":509,"context_line":"                                default\u003d\"/etc/swift/object-expirer.conf.d\")"},{"line_number":510,"context_line":"    details_parser.add_argument(\"--start-day-offset\","},{"line_number":511,"context_line":"                                type\u003dint, default\u003d-10)"},{"line_number":512,"context_line":"    details_parser.add_argument(\"--listing-workers\","}],"source_content_type":"text/x-python","patch_set":3,"id":"37e4913e_290c0677","line":509,"updated":"2025-07-01 20:34:28.000000000","message":"consider creating an `_add_conf_args(subparser)` function to DRY out the help messages for the conf related arguments\n\nalternatively you can add them to the `parser` object before you start creating subparsers - argparse is opionated about the order of the optional args but it works ok as:\n\n```\ns-m-e --conf /etc/swift/my.conf subcommand --subcommand-arg\n```","commit_id":"9d41a445b5d627b671676f80b125b92e0a72eb40"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"74e437a54c0272552814b3002326908f009e57c9","unresolved":false,"context_lines":[{"line_number":506,"context_line":"    details_parser.add_argument(\"--internal-client-conf-path\","},{"line_number":507,"context_line":"                                default\u003d\"/etc/swift/internal-client.conf\")"},{"line_number":508,"context_line":"    details_parser.add_argument(\"--expirer-conf-path\","},{"line_number":509,"context_line":"                                default\u003d\"/etc/swift/object-expirer.conf.d\")"},{"line_number":510,"context_line":"    details_parser.add_argument(\"--start-day-offset\","},{"line_number":511,"context_line":"                                type\u003dint, default\u003d-10)"},{"line_number":512,"context_line":"    details_parser.add_argument(\"--listing-workers\","}],"source_content_type":"text/x-python","patch_set":3,"id":"4d901516_a6d35c9c","line":509,"in_reply_to":"37e4913e_290c0677","updated":"2025-07-23 02:11:51.000000000","message":"Done","commit_id":"9d41a445b5d627b671676f80b125b92e0a72eb40"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"e85fe82281bcd789539f2ed805ba98233bdd4225","unresolved":true,"context_lines":[{"line_number":572,"context_line":""},{"line_number":573,"context_line":"    if args.subcommand \u003d\u003d \"summary\":"},{"line_number":574,"context_line":"        manager \u003d BaseExpirerStatus(args)"},{"line_number":575,"context_line":"        manager.run_summary()"},{"line_number":576,"context_line":"    elif args.subcommand \u003d\u003d \"details\":"},{"line_number":577,"context_line":"        manager \u003d BaseExpirerStatus(args)"},{"line_number":578,"context_line":"        manager.run_details()"}],"source_content_type":"text/x-python","patch_set":3,"id":"e926e727_2059e5c6","line":575,"updated":"2025-07-01 20:34:28.000000000","message":"the recommend way to dispatch subcommands with argparse is using the set_defaults helper:\n\n\u003e One particularly effective way of handling subcommands is to combine the use of the add_subparsers() method with calls to set_defaults() \n\nhttps://docs.python.org/3/library/argparse.html#sub-commands\n\nit makes defining the mapping of cli-subcommand-arg \u003d\u003e implementation_function declarative as opposed to imperative - generally when you want to favor readability and maintainability over control of the execution details you should prefer a declarative structure.\n\nI think in this case you\u0027ll either need each manager class to have a \"construct and run\" helper function - or just make all the manager\u0027s have a consistent entrypoint...\n\n\n```\nrebalance_parser.set_defaults(manager\u003dRebalanceManager)\n...\nargs \u003d parser.parse_args()\nargs.manager(args).run()\n```\n\n... might get a little gross in `BaseExpiredStatus` - you either have to look at args.subcommand and dispatch to `run_details` vs `run_summary` or maybe better for consistency with the evaluate-balance and rebalance subcommands you could stick with 1:1 subcommand:ManagerClass and just subclass BaseExpiredStatus to create concrete SummaryStatus and DetailsStatus subclasses who\u0027s run method is explicitly .","commit_id":"9d41a445b5d627b671676f80b125b92e0a72eb40"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"74e437a54c0272552814b3002326908f009e57c9","unresolved":false,"context_lines":[{"line_number":572,"context_line":""},{"line_number":573,"context_line":"    if args.subcommand \u003d\u003d \"summary\":"},{"line_number":574,"context_line":"        manager \u003d BaseExpirerStatus(args)"},{"line_number":575,"context_line":"        manager.run_summary()"},{"line_number":576,"context_line":"    elif args.subcommand \u003d\u003d \"details\":"},{"line_number":577,"context_line":"        manager \u003d BaseExpirerStatus(args)"},{"line_number":578,"context_line":"        manager.run_details()"}],"source_content_type":"text/x-python","patch_set":3,"id":"7d6c3d76_56da55a1","line":575,"in_reply_to":"e926e727_2059e5c6","updated":"2025-07-23 02:11:51.000000000","message":"Done","commit_id":"9d41a445b5d627b671676f80b125b92e0a72eb40"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"c02bb7189797043b445dd70d9979cab0f33e35f1","unresolved":true,"context_lines":[{"line_number":41,"context_line":""},{"line_number":42,"context_line":""},{"line_number":43,"context_line":"def parse_delay(x_delete_at_ts, account, container, expirer_conf, now):"},{"line_number":44,"context_line":"    delay_reaping \u003d getattr(expirer_conf, \u0027delay_reaping\u0027, 0)"},{"line_number":45,"context_line":"    delete_at \u003d datetime.datetime.fromtimestamp(float(x_delete_at_ts))"},{"line_number":46,"context_line":"    delta \u003d (delete_at - now).days"},{"line_number":47,"context_line":"    delay \u003d (now \u003c delete_at + datetime.timedelta(seconds\u003ddelay_reaping))"}],"source_content_type":"text/x-python","patch_set":4,"id":"c68ce2f8_f3549877","line":44,"updated":"2025-07-11 18:21:23.000000000","message":"on that\u0027s crazy, I thought expirer_conf was a dict - it\u0027s an ExpirerConfig\n\nYou want to use `get_delay_reaping`\n\nhttps://github.com/NVIDIA/swift/blob/master/swift/obj/expirer.py#L252","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"c3684af438cfb8b7ff8a9dcdbc18cde03ec2d177","unresolved":false,"context_lines":[{"line_number":41,"context_line":""},{"line_number":42,"context_line":""},{"line_number":43,"context_line":"def parse_delay(x_delete_at_ts, account, container, expirer_conf, now):"},{"line_number":44,"context_line":"    delay_reaping \u003d getattr(expirer_conf, \u0027delay_reaping\u0027, 0)"},{"line_number":45,"context_line":"    delete_at \u003d datetime.datetime.fromtimestamp(float(x_delete_at_ts))"},{"line_number":46,"context_line":"    delta \u003d (delete_at - now).days"},{"line_number":47,"context_line":"    delay \u003d (now \u003c delete_at + datetime.timedelta(seconds\u003ddelay_reaping))"}],"source_content_type":"text/x-python","patch_set":4,"id":"ad4ec2ea_a7d746e9","line":44,"in_reply_to":"c68ce2f8_f3549877","updated":"2025-07-21 17:31:03.000000000","message":"Done","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"c02bb7189797043b445dd70d9979cab0f33e35f1","unresolved":true,"context_lines":[{"line_number":79,"context_line":"                                   \"details-worker\") as ic:"},{"line_number":80,"context_line":"        while True:"},{"line_number":81,"context_line":"            try:"},{"line_number":82,"context_line":"                container_info \u003d container_q.get(timeout\u003d1)"},{"line_number":83,"context_line":"            except queue.Empty:"},{"line_number":84,"context_line":"                break  # Exit cleanly"},{"line_number":85,"context_line":""}],"source_content_type":"text/x-python","patch_set":4,"id":"39c3c8c6_750a8517","line":82,"updated":"2025-07-11 18:21:23.000000000","message":"I *think* a get from a multiprocess queue while w/i an eventlet context is exactly what can deadlock... I think if you move enter and exit the ic_ctx for each task_container to list it\u0027d be safer.\n\n\"better safe than sorry\" might suggest we want to try make this method look like this:\n\n```\nwhile true:\n    container_info \u003d q.get()\n    with ic_ctx as conn:\n        stats \u003d list_all_tasks(conn, container_info)\n    result_q.put(stats)\n```","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"74e437a54c0272552814b3002326908f009e57c9","unresolved":false,"context_lines":[{"line_number":79,"context_line":"                                   \"details-worker\") as ic:"},{"line_number":80,"context_line":"        while True:"},{"line_number":81,"context_line":"            try:"},{"line_number":82,"context_line":"                container_info \u003d container_q.get(timeout\u003d1)"},{"line_number":83,"context_line":"            except queue.Empty:"},{"line_number":84,"context_line":"                break  # Exit cleanly"},{"line_number":85,"context_line":""}],"source_content_type":"text/x-python","patch_set":4,"id":"1199e854_3fa4b015","line":82,"in_reply_to":"39c3c8c6_750a8517","updated":"2025-07-23 02:11:51.000000000","message":"Acknowledged","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"c02bb7189797043b445dd70d9979cab0f33e35f1","unresolved":true,"context_lines":[{"line_number":81,"context_line":"            try:"},{"line_number":82,"context_line":"                container_info \u003d container_q.get(timeout\u003d1)"},{"line_number":83,"context_line":"            except queue.Empty:"},{"line_number":84,"context_line":"                break  # Exit cleanly"},{"line_number":85,"context_line":""},{"line_number":86,"context_line":"            container \u003d container_info[\"name\"]"},{"line_number":87,"context_line":"            try:"}],"source_content_type":"text/x-python","patch_set":4,"id":"4cc75ec9_11910068","line":84,"updated":"2025-07-11 18:21:23.000000000","message":"I\u0027d prefer an explicit exit sentinel; you see a lot of python mp queue code do something like:\n\n```\nEXIT \u003d object()\n\ndef work_on_queue_till_done():\n    while True:\n        obj \u003d q.get()\n        if obj \u003d\u003d EXIT:\n           # we\u0027re done!\n           return\n        handle(obj)\n           \nworkers \u003d start_workers()\n# do some other setup that might take a minute\nfor i in do_lots_of_work_that_might_stall_occasionally():\n    q.put(i)\nfor _ in len(workers):\n    # let everyone know we\u0027re done\n    q.put(EXIT)\n```\n\nI can imagine a scenario where the queue might not get a new item in it for \u003e1s but that doesn\u0027t mean it won\u0027t get a new item *eventually* - we don\u0027t want our best workers quitting out early cause we didn\u0027t feed them tasks fast enough when there\u0027s still work to do!","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"74e437a54c0272552814b3002326908f009e57c9","unresolved":false,"context_lines":[{"line_number":81,"context_line":"            try:"},{"line_number":82,"context_line":"                container_info \u003d container_q.get(timeout\u003d1)"},{"line_number":83,"context_line":"            except queue.Empty:"},{"line_number":84,"context_line":"                break  # Exit cleanly"},{"line_number":85,"context_line":""},{"line_number":86,"context_line":"            container \u003d container_info[\"name\"]"},{"line_number":87,"context_line":"            try:"}],"source_content_type":"text/x-python","patch_set":4,"id":"7bbee126_ffb2b756","line":84,"in_reply_to":"4cc75ec9_11910068","updated":"2025-07-23 02:11:51.000000000","message":"Done","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"c02bb7189797043b445dd70d9979cab0f33e35f1","unresolved":true,"context_lines":[{"line_number":98,"context_line":"                    f\"Failed to list objects in container {container}: {e}\")"},{"line_number":99,"context_line":"                continue"},{"line_number":100,"context_line":""},{"line_number":101,"context_line":"            for obj in objects:"},{"line_number":102,"context_line":"                obj_name \u003d obj.get(\"name\")"},{"line_number":103,"context_line":"                try:"},{"line_number":104,"context_line":"                    ts, account, obj_container, obj_name \u003d parse_task_obj("}],"source_content_type":"text/x-python","patch_set":4,"id":"b1ef6280_4ad5d765","line":101,"updated":"2025-07-11 18:21:23.000000000","message":"similarly here task_containers with \u003e10K objects won\u0027t get processed without making *another* `GET /container` request with `marker\u003dobjects[-1][\u0027name\u0027]`\n\nPagination is handled for you in `iter_objects` - that\u0027s why internal_client is so great; use it!","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"a4592ef3e6b0d8f36a93ffc7c1a376045788369a","unresolved":false,"context_lines":[{"line_number":98,"context_line":"                    f\"Failed to list objects in container {container}: {e}\")"},{"line_number":99,"context_line":"                continue"},{"line_number":100,"context_line":""},{"line_number":101,"context_line":"            for obj in objects:"},{"line_number":102,"context_line":"                obj_name \u003d obj.get(\"name\")"},{"line_number":103,"context_line":"                try:"},{"line_number":104,"context_line":"                    ts, account, obj_container, obj_name \u003d parse_task_obj("}],"source_content_type":"text/x-python","patch_set":4,"id":"b96b435d_363ef898","line":101,"in_reply_to":"b1ef6280_4ad5d765","updated":"2025-07-23 18:33:22.000000000","message":"Done","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"c02bb7189797043b445dd70d9979cab0f33e35f1","unresolved":true,"context_lines":[{"line_number":106,"context_line":"                    days, delay \u003d parse_delay(ts, account, obj_container,"},{"line_number":107,"context_line":"                                              expirer_conf, now)"},{"line_number":108,"context_line":""},{"line_number":109,"context_line":"                    stats[(\"count\", account, obj_container, days, delay)] +\u003d 1"},{"line_number":110,"context_line":"                    content_type \u003d obj.get(\"content_type\") or \"\""},{"line_number":111,"context_line":"                    bytes_obj \u003d extract_expirer_bytes_from_ctype(content_type)"},{"line_number":112,"context_line":"                    stats[(\"bytes\", account, obj_container, days, delay)] +\u003d ("}],"source_content_type":"text/x-python","patch_set":4,"id":"74c6825f_2adb8367","line":109,"updated":"2025-07-11 18:21:23.000000000","message":"doh!  I forgot another label that we need here:\n\n```\nfrom swift.obj.expirer import ASYNC_DELETE_TYPE\n...\nis_async \u003d (object_item[\u0027content_type\u0027] \u003d\u003d ASYNC_DELETE_TYPE)\n```","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"74e437a54c0272552814b3002326908f009e57c9","unresolved":false,"context_lines":[{"line_number":106,"context_line":"                    days, delay \u003d parse_delay(ts, account, obj_container,"},{"line_number":107,"context_line":"                                              expirer_conf, now)"},{"line_number":108,"context_line":""},{"line_number":109,"context_line":"                    stats[(\"count\", account, obj_container, days, delay)] +\u003d 1"},{"line_number":110,"context_line":"                    content_type \u003d obj.get(\"content_type\") or \"\""},{"line_number":111,"context_line":"                    bytes_obj \u003d extract_expirer_bytes_from_ctype(content_type)"},{"line_number":112,"context_line":"                    stats[(\"bytes\", account, obj_container, days, delay)] +\u003d ("}],"source_content_type":"text/x-python","patch_set":4,"id":"04d5566b_76b1e05e","line":109,"in_reply_to":"74c6825f_2adb8367","updated":"2025-07-23 02:11:51.000000000","message":"Done","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"c02bb7189797043b445dd70d9979cab0f33e35f1","unresolved":true,"context_lines":[{"line_number":116,"context_line":"                        verified_bytes \u003d lookup_bytes(ic, account,"},{"line_number":117,"context_line":"                                                      obj_container, obj_name)"},{"line_number":118,"context_line":"                        stats[(\"bytes_verified\", account, obj_container, days,"},{"line_number":119,"context_line":"                               delay)] +\u003d verified_bytes"},{"line_number":120,"context_line":"                except Exception as e:"},{"line_number":121,"context_line":"                    logging.warning(f\"Failed to parse object {obj_name}: \""},{"line_number":122,"context_line":"                                    f\"{e}\")"}],"source_content_type":"text/x-python","patch_set":4,"id":"7ede4bd3_3d5afbf8","line":119,"updated":"2025-07-11 18:21:23.000000000","message":"oh I *really* like using a new key here for bytes_verified - KUDOS!","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"74e437a54c0272552814b3002326908f009e57c9","unresolved":false,"context_lines":[{"line_number":116,"context_line":"                        verified_bytes \u003d lookup_bytes(ic, account,"},{"line_number":117,"context_line":"                                                      obj_container, obj_name)"},{"line_number":118,"context_line":"                        stats[(\"bytes_verified\", account, obj_container, days,"},{"line_number":119,"context_line":"                               delay)] +\u003d verified_bytes"},{"line_number":120,"context_line":"                except Exception as e:"},{"line_number":121,"context_line":"                    logging.warning(f\"Failed to parse object {obj_name}: \""},{"line_number":122,"context_line":"                                    f\"{e}\")"}],"source_content_type":"text/x-python","patch_set":4,"id":"7a748480_8e2e4305","line":119,"in_reply_to":"76ee6521_1d8ea29b","updated":"2025-07-23 02:11:51.000000000","message":"Done","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"c3684af438cfb8b7ff8a9dcdbc18cde03ec2d177","unresolved":true,"context_lines":[{"line_number":116,"context_line":"                        verified_bytes \u003d lookup_bytes(ic, account,"},{"line_number":117,"context_line":"                                                      obj_container, obj_name)"},{"line_number":118,"context_line":"                        stats[(\"bytes_verified\", account, obj_container, days,"},{"line_number":119,"context_line":"                               delay)] +\u003d verified_bytes"},{"line_number":120,"context_line":"                except Exception as e:"},{"line_number":121,"context_line":"                    logging.warning(f\"Failed to parse object {obj_name}: \""},{"line_number":122,"context_line":"                                    f\"{e}\")"}],"source_content_type":"text/x-python","patch_set":4,"id":"76ee6521_1d8ea29b","line":119,"in_reply_to":"7ede4bd3_3d5afbf8","updated":"2025-07-21 17:31:03.000000000","message":"Thanks!","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"c02bb7189797043b445dd70d9979cab0f33e35f1","unresolved":true,"context_lines":[{"line_number":121,"context_line":"                    logging.warning(f\"Failed to parse object {obj_name}: \""},{"line_number":122,"context_line":"                                    f\"{e}\")"},{"line_number":123,"context_line":""},{"line_number":124,"context_line":"    result_q.put(stats)"},{"line_number":125,"context_line":""},{"line_number":126,"context_line":""},{"line_number":127,"context_line":"class BaseExpirerStatus:"}],"source_content_type":"text/x-python","patch_set":4,"id":"d0ccfa52_7113b50b","line":124,"updated":"2025-07-11 18:21:23.000000000","message":"maybe it\u0027s only the mp.queue.put that locks up - and at this point you\u0027ve exited the ic_ctx so maybe it\u0027s ok.","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"a4592ef3e6b0d8f36a93ffc7c1a376045788369a","unresolved":false,"context_lines":[{"line_number":121,"context_line":"                    logging.warning(f\"Failed to parse object {obj_name}: \""},{"line_number":122,"context_line":"                                    f\"{e}\")"},{"line_number":123,"context_line":""},{"line_number":124,"context_line":"    result_q.put(stats)"},{"line_number":125,"context_line":""},{"line_number":126,"context_line":""},{"line_number":127,"context_line":"class BaseExpirerStatus:"}],"source_content_type":"text/x-python","patch_set":4,"id":"b276add4_b8fb918d","line":124,"in_reply_to":"d0ccfa52_7113b50b","updated":"2025-07-23 18:33:22.000000000","message":"Done","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"c02bb7189797043b445dd70d9979cab0f33e35f1","unresolved":true,"context_lines":[{"line_number":162,"context_line":"                headers\u003d{},"},{"line_number":163,"context_line":"                acceptable_statuses\u003d(200,),"},{"line_number":164,"context_line":"                params\u003d{\u0027format\u0027: \u0027json\u0027},"},{"line_number":165,"context_line":"            )"},{"line_number":166,"context_line":"            containers \u003d json.loads(resp.body)"},{"line_number":167,"context_line":""},{"line_number":168,"context_line":"            for container in containers:"}],"source_content_type":"text/x-python","patch_set":4,"id":"27944c83_7ed77c8a","line":165,"updated":"2025-07-11 18:21:23.000000000","message":"no good!  use `iter_containers` - otherwise if you have to do the pagination on your own.\n\nI wonder what was the motivation for changing it.","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"c3684af438cfb8b7ff8a9dcdbc18cde03ec2d177","unresolved":true,"context_lines":[{"line_number":162,"context_line":"                headers\u003d{},"},{"line_number":163,"context_line":"                acceptable_statuses\u003d(200,),"},{"line_number":164,"context_line":"                params\u003d{\u0027format\u0027: \u0027json\u0027},"},{"line_number":165,"context_line":"            )"},{"line_number":166,"context_line":"            containers \u003d json.loads(resp.body)"},{"line_number":167,"context_line":""},{"line_number":168,"context_line":"            for container in containers:"}],"source_content_type":"text/x-python","patch_set":4,"id":"83f28f83_9c517145","line":165,"in_reply_to":"27944c83_7ed77c8a","updated":"2025-07-21 17:31:03.000000000","message":"The motivation was to see perf differences for \u003c 10K entries for one json manifest v/s iterating \u003c 10K entries, though it wasn\u0027t much, worth evaluating in a situation if we come up with a \"better\" paginator for the json response, although seems like more of a complicated task since we already have a powerful `InternalClient` to do it for us","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"74e437a54c0272552814b3002326908f009e57c9","unresolved":false,"context_lines":[{"line_number":162,"context_line":"                headers\u003d{},"},{"line_number":163,"context_line":"                acceptable_statuses\u003d(200,),"},{"line_number":164,"context_line":"                params\u003d{\u0027format\u0027: \u0027json\u0027},"},{"line_number":165,"context_line":"            )"},{"line_number":166,"context_line":"            containers \u003d json.loads(resp.body)"},{"line_number":167,"context_line":""},{"line_number":168,"context_line":"            for container in containers:"}],"source_content_type":"text/x-python","patch_set":4,"id":"20ef2b54_cd0611e3","line":165,"in_reply_to":"83f28f83_9c517145","updated":"2025-07-23 02:11:51.000000000","message":"Acknowledged","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"c02bb7189797043b445dd70d9979cab0f33e35f1","unresolved":true,"context_lines":[{"line_number":172,"context_line":"                        self.expirer_conf.task_container_per_day"},{"line_number":173,"context_line":"                    )"},{"line_number":174,"context_line":"                except (ValueError, TypeError):"},{"line_number":175,"context_line":"                    continue  # skip malformed names"},{"line_number":176,"context_line":""},{"line_number":177,"context_line":"                if ts \u003c start_cutoff or ts \u003e\u003d end_cutoff:"},{"line_number":178,"context_line":"                    continue"}],"source_content_type":"text/x-python","patch_set":4,"id":"95da2f2f_dde7ea1f","line":175,"updated":"2025-07-11 18:21:23.000000000","message":"like what!?","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"74e437a54c0272552814b3002326908f009e57c9","unresolved":false,"context_lines":[{"line_number":172,"context_line":"                        self.expirer_conf.task_container_per_day"},{"line_number":173,"context_line":"                    )"},{"line_number":174,"context_line":"                except (ValueError, TypeError):"},{"line_number":175,"context_line":"                    continue  # skip malformed names"},{"line_number":176,"context_line":""},{"line_number":177,"context_line":"                if ts \u003c start_cutoff or ts \u003e\u003d end_cutoff:"},{"line_number":178,"context_line":"                    continue"}],"source_content_type":"text/x-python","patch_set":4,"id":"efb08f0d_a3028f6d","line":175,"in_reply_to":"95da2f2f_dde7ea1f","updated":"2025-07-23 02:11:51.000000000","message":"Done","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"c02bb7189797043b445dd70d9979cab0f33e35f1","unresolved":true,"context_lines":[{"line_number":175,"context_line":"                    continue  # skip malformed names"},{"line_number":176,"context_line":""},{"line_number":177,"context_line":"                if ts \u003c start_cutoff or ts \u003e\u003d end_cutoff:"},{"line_number":178,"context_line":"                    continue"},{"line_number":179,"context_line":""},{"line_number":180,"context_line":"                offset \u003d (ts - self.now).days"},{"line_number":181,"context_line":"                container_day_counts[offset] +\u003d container.get(\"count\", 0)"}],"source_content_type":"text/x-python","patch_set":4,"id":"c609bb8c_0146cfd7","line":178,"updated":"2025-07-11 18:21:23.000000000","message":"I think if ts \u003e\u003d end_cutoff you can just break - they should be coming at your lexicographic ordered and since they\u0027re even width integers that should be good.  \n\nOther wise you could set an num_days \u003d\u003d 10 and still iterate through the next 100 years of expirations!","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"c3684af438cfb8b7ff8a9dcdbc18cde03ec2d177","unresolved":false,"context_lines":[{"line_number":175,"context_line":"                    continue  # skip malformed names"},{"line_number":176,"context_line":""},{"line_number":177,"context_line":"                if ts \u003c start_cutoff or ts \u003e\u003d end_cutoff:"},{"line_number":178,"context_line":"                    continue"},{"line_number":179,"context_line":""},{"line_number":180,"context_line":"                offset \u003d (ts - self.now).days"},{"line_number":181,"context_line":"                container_day_counts[offset] +\u003d container.get(\"count\", 0)"}],"source_content_type":"text/x-python","patch_set":4,"id":"d9293b88_1a428e99","line":178,"in_reply_to":"c609bb8c_0146cfd7","updated":"2025-07-21 17:31:03.000000000","message":"Done","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"c02bb7189797043b445dd70d9979cab0f33e35f1","unresolved":true,"context_lines":[{"line_number":177,"context_line":"                if ts \u003c start_cutoff or ts \u003e\u003d end_cutoff:"},{"line_number":178,"context_line":"                    continue"},{"line_number":179,"context_line":""},{"line_number":180,"context_line":"                offset \u003d (ts - self.now).days"},{"line_number":181,"context_line":"                container_day_counts[offset] +\u003d container.get(\"count\", 0)"},{"line_number":182,"context_line":"                processed_containers +\u003d 1"},{"line_number":183,"context_line":""}],"source_content_type":"text/x-python","patch_set":4,"id":"8229e169_f4b2b81b","line":180,"updated":"2025-07-11 18:21:23.000000000","message":"what\u0027s funny is that I normally think of \"ts\" as \"timestamp\" and \"now\" as `time.time()` - the fact that both of these are datetimes made me doubletake.","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"c3684af438cfb8b7ff8a9dcdbc18cde03ec2d177","unresolved":false,"context_lines":[{"line_number":177,"context_line":"                if ts \u003c start_cutoff or ts \u003e\u003d end_cutoff:"},{"line_number":178,"context_line":"                    continue"},{"line_number":179,"context_line":""},{"line_number":180,"context_line":"                offset \u003d (ts - self.now).days"},{"line_number":181,"context_line":"                container_day_counts[offset] +\u003d container.get(\"count\", 0)"},{"line_number":182,"context_line":"                processed_containers +\u003d 1"},{"line_number":183,"context_line":""}],"source_content_type":"text/x-python","patch_set":4,"id":"254803b5_c1351c0b","line":180,"in_reply_to":"8229e169_f4b2b81b","updated":"2025-07-21 17:31:03.000000000","message":"Done","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"c02bb7189797043b445dd70d9979cab0f33e35f1","unresolved":true,"context_lines":[{"line_number":178,"context_line":"                    continue"},{"line_number":179,"context_line":""},{"line_number":180,"context_line":"                offset \u003d (ts - self.now).days"},{"line_number":181,"context_line":"                container_day_counts[offset] +\u003d container.get(\"count\", 0)"},{"line_number":182,"context_line":"                processed_containers +\u003d 1"},{"line_number":183,"context_line":""},{"line_number":184,"context_line":"        print(f\u0027processed_expirer_containers{{host\u003d\"{self.hostname}\"}} \u0027"}],"source_content_type":"text/x-python","patch_set":4,"id":"32de97eb_a5774527","line":181,"updated":"2025-07-11 18:21:23.000000000","message":"probably this key should always be in the container info dict - unless maybe there\u0027s a bad test stub.","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"c3684af438cfb8b7ff8a9dcdbc18cde03ec2d177","unresolved":false,"context_lines":[{"line_number":178,"context_line":"                    continue"},{"line_number":179,"context_line":""},{"line_number":180,"context_line":"                offset \u003d (ts - self.now).days"},{"line_number":181,"context_line":"                container_day_counts[offset] +\u003d container.get(\"count\", 0)"},{"line_number":182,"context_line":"                processed_containers +\u003d 1"},{"line_number":183,"context_line":""},{"line_number":184,"context_line":"        print(f\u0027processed_expirer_containers{{host\u003d\"{self.hostname}\"}} \u0027"}],"source_content_type":"text/x-python","patch_set":4,"id":"5572981f_2c4b7806","line":181,"in_reply_to":"32de97eb_a5774527","updated":"2025-07-21 17:31:03.000000000","message":"Acknowledged","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"c02bb7189797043b445dd70d9979cab0f33e35f1","unresolved":true,"context_lines":[{"line_number":196,"context_line":"            start_cutoff \u003d self.now + datetime.timedelta("},{"line_number":197,"context_line":"                days\u003dself.args.start_day_offset)"},{"line_number":198,"context_line":"            end_cutoff \u003d self.now + datetime.timedelta("},{"line_number":199,"context_line":"                days\u003dself.args.start_day_offset + self.args.num_days)"},{"line_number":200,"context_line":""},{"line_number":201,"context_line":"            resp \u003d ic.make_request("},{"line_number":202,"context_line":"                method\u003d\u0027GET\u0027,"}],"source_content_type":"text/x-python","patch_set":4,"id":"dab97906_0d74801b","line":199,"updated":"2025-07-11 18:21:23.000000000","message":"this might be clearer as `start_cuffoff + timedelta(args.num_days)`\n\n... I *think* `now + offset + numdays` is the same thing tho?\n\nit\u0027d be like writing\n\n```\na \u003d b + c\nd \u003d b + c + e\n```\n\ninstead of\n\n```\na \u003d b + c\nd \u003d a + e\n```\n\nunless maybe datetimes \u0026 timedelta aren\u0027t always commutative, but I think they are:\n\n```\n\u003e\u003e\u003e now + timedelta(days\u003d1)\ndatetime.datetime(2025, 7, 12, 12, 20, 26, 344697)\n\u003e\u003e\u003e timedelta(days\u003d1) + now\ndatetime.datetime(2025, 7, 12, 12, 20, 26, 344697)\n```","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"c3684af438cfb8b7ff8a9dcdbc18cde03ec2d177","unresolved":false,"context_lines":[{"line_number":196,"context_line":"            start_cutoff \u003d self.now + datetime.timedelta("},{"line_number":197,"context_line":"                days\u003dself.args.start_day_offset)"},{"line_number":198,"context_line":"            end_cutoff \u003d self.now + datetime.timedelta("},{"line_number":199,"context_line":"                days\u003dself.args.start_day_offset + self.args.num_days)"},{"line_number":200,"context_line":""},{"line_number":201,"context_line":"            resp \u003d ic.make_request("},{"line_number":202,"context_line":"                method\u003d\u0027GET\u0027,"}],"source_content_type":"text/x-python","patch_set":4,"id":"9fb2763c_ea6da6f4","line":199,"in_reply_to":"dab97906_0d74801b","updated":"2025-07-21 17:31:03.000000000","message":"Done","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"c02bb7189797043b445dd70d9979cab0f33e35f1","unresolved":true,"context_lines":[{"line_number":207,"context_line":"            )"},{"line_number":208,"context_line":"            containers \u003d json.loads(resp.body)"},{"line_number":209,"context_line":""},{"line_number":210,"context_line":"            for container_info in containers:"},{"line_number":211,"context_line":"                try:"},{"line_number":212,"context_line":"                    ts \u003d datetime.datetime.fromtimestamp("},{"line_number":213,"context_line":"                        int(container_info[\"name\"]) +"}],"source_content_type":"text/x-python","patch_set":4,"id":"d4dcf04b_c82fb43c","line":210,"updated":"2025-07-11 18:21:23.000000000","message":"use of `iter_containers` is required for \u003e10K task containers","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"74e437a54c0272552814b3002326908f009e57c9","unresolved":false,"context_lines":[{"line_number":207,"context_line":"            )"},{"line_number":208,"context_line":"            containers \u003d json.loads(resp.body)"},{"line_number":209,"context_line":""},{"line_number":210,"context_line":"            for container_info in containers:"},{"line_number":211,"context_line":"                try:"},{"line_number":212,"context_line":"                    ts \u003d datetime.datetime.fromtimestamp("},{"line_number":213,"context_line":"                        int(container_info[\"name\"]) +"}],"source_content_type":"text/x-python","patch_set":4,"id":"ebdfbcdd_4c4cd9f5","line":210,"in_reply_to":"d4dcf04b_c82fb43c","updated":"2025-07-23 02:11:51.000000000","message":"Done","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"c02bb7189797043b445dd70d9979cab0f33e35f1","unresolved":true,"context_lines":[{"line_number":214,"context_line":"                        self.expirer_conf.task_container_per_day"},{"line_number":215,"context_line":"                    )"},{"line_number":216,"context_line":"                except (ValueError, TypeError):"},{"line_number":217,"context_line":"                    continue  # skip malformed names"},{"line_number":218,"context_line":""},{"line_number":219,"context_line":"                if ts \u003c start_cutoff or ts \u003e\u003d end_cutoff:"},{"line_number":220,"context_line":"                    continue"}],"source_content_type":"text/x-python","patch_set":4,"id":"eedf26a9_504d912c","line":217,"updated":"2025-07-11 18:21:23.000000000","message":"`import this` says \"Errors should never pass silently.\"\n\nwhat malformed names are we expecting?","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"74e437a54c0272552814b3002326908f009e57c9","unresolved":false,"context_lines":[{"line_number":214,"context_line":"                        self.expirer_conf.task_container_per_day"},{"line_number":215,"context_line":"                    )"},{"line_number":216,"context_line":"                except (ValueError, TypeError):"},{"line_number":217,"context_line":"                    continue  # skip malformed names"},{"line_number":218,"context_line":""},{"line_number":219,"context_line":"                if ts \u003c start_cutoff or ts \u003e\u003d end_cutoff:"},{"line_number":220,"context_line":"                    continue"}],"source_content_type":"text/x-python","patch_set":4,"id":"39667c17_093cc0eb","line":217,"in_reply_to":"eedf26a9_504d912c","updated":"2025-07-23 02:11:51.000000000","message":"Done","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"c02bb7189797043b445dd70d9979cab0f33e35f1","unresolved":true,"context_lines":[{"line_number":328,"context_line":"                    return"},{"line_number":329,"context_line":"                count \u003d container_info.get(\"count\", 0)"},{"line_number":330,"context_line":"                offset \u003d self._get_day_offset(container_info[\"name\"])"},{"line_number":331,"context_line":"                result_q.put((container_info[\"name\"], offset, count))"},{"line_number":332,"context_line":""},{"line_number":333,"context_line":"    def run_evaluation(self):"},{"line_number":334,"context_line":"        container_day_counts \u003d self._scan_task_containers()"}],"source_content_type":"text/x-python","patch_set":4,"id":"16aa16f3_886d0581","line":331,"updated":"2025-07-11 18:21:23.000000000","message":"this doesn\u0027t look right - we don\u0027t need a worker to lookup a key and do some math - just move this inline with the account-GET/container-listing same as summary.","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"74e437a54c0272552814b3002326908f009e57c9","unresolved":false,"context_lines":[{"line_number":328,"context_line":"                    return"},{"line_number":329,"context_line":"                count \u003d container_info.get(\"count\", 0)"},{"line_number":330,"context_line":"                offset \u003d self._get_day_offset(container_info[\"name\"])"},{"line_number":331,"context_line":"                result_q.put((container_info[\"name\"], offset, count))"},{"line_number":332,"context_line":""},{"line_number":333,"context_line":"    def run_evaluation(self):"},{"line_number":334,"context_line":"        container_day_counts \u003d self._scan_task_containers()"}],"source_content_type":"text/x-python","patch_set":4,"id":"6641671d_5a3533a2","line":331,"in_reply_to":"16aa16f3_886d0581","updated":"2025-07-23 02:11:51.000000000","message":"Done","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"c02bb7189797043b445dd70d9979cab0f33e35f1","unresolved":true,"context_lines":[{"line_number":407,"context_line":"                continue  # skip malformed names"},{"line_number":408,"context_line":""},{"line_number":409,"context_line":"            if container_ts \u003c start_cutoff or container_ts \u003e\u003d end_cutoff:"},{"line_number":410,"context_line":"                continue"},{"line_number":411,"context_line":""},{"line_number":412,"context_line":"            offset_days \u003d (container_ts - self.now).days"},{"line_number":413,"context_line":"            container[\"rebalance_day_delta\"] \u003d offset_days"}],"source_content_type":"text/x-python","patch_set":4,"id":"30d2a9c6_d7fdd715","line":410,"updated":"2025-07-11 18:21:23.000000000","message":"this seems similar to summary every other container listing - and should use `iter_containers` ... in *fact* you might want to extract to helper!\n\n```\nfor task_dt, container_info in iter_relevant_containers(args, conn):\n    day_delta \u003d (task_dt - self.now).days\n    container_info[\u0027day_delta\u0027] \u003d day_delta\n    q.put(container_info)\n```\n\nin addition to being a little tighter to read, DRY means you only have to fix the implementation in one spot!","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"f297b72faad24545283980079227d5d697900a16","unresolved":false,"context_lines":[{"line_number":407,"context_line":"                continue  # skip malformed names"},{"line_number":408,"context_line":""},{"line_number":409,"context_line":"            if container_ts \u003c start_cutoff or container_ts \u003e\u003d end_cutoff:"},{"line_number":410,"context_line":"                continue"},{"line_number":411,"context_line":""},{"line_number":412,"context_line":"            offset_days \u003d (container_ts - self.now).days"},{"line_number":413,"context_line":"            container[\"rebalance_day_delta\"] \u003d offset_days"}],"source_content_type":"text/x-python","patch_set":4,"id":"3785144c_251cf53c","line":410,"in_reply_to":"30d2a9c6_d7fdd715","updated":"2025-07-23 02:35:01.000000000","message":"Done","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"c02bb7189797043b445dd70d9979cab0f33e35f1","unresolved":true,"context_lines":[{"line_number":579,"context_line":"    details_parser.add_argument(\"--listing-workers\","},{"line_number":580,"context_line":"                                type\u003dint, default\u003d20)"},{"line_number":581,"context_line":"    details_parser.add_argument(\"--verify-bytes\","},{"line_number":582,"context_line":"                                action\u003d\"store_true\","},{"line_number":583,"context_line":"                                help\u003d\"Use HEAD requests to verify object size\""},{"line_number":584,"context_line":"                                     \" if content_type lacks bytes\")"},{"line_number":585,"context_line":"    details_parser.add_argument(\"-v\", \"--verbose\","}],"source_content_type":"text/x-python","patch_set":4,"id":"e008e507_33f77b7d","line":582,"updated":"2025-07-11 18:21:23.000000000","message":"verify_bytes default False is correct - KUDOS","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"74e437a54c0272552814b3002326908f009e57c9","unresolved":false,"context_lines":[{"line_number":579,"context_line":"    details_parser.add_argument(\"--listing-workers\","},{"line_number":580,"context_line":"                                type\u003dint, default\u003d20)"},{"line_number":581,"context_line":"    details_parser.add_argument(\"--verify-bytes\","},{"line_number":582,"context_line":"                                action\u003d\"store_true\","},{"line_number":583,"context_line":"                                help\u003d\"Use HEAD requests to verify object size\""},{"line_number":584,"context_line":"                                     \" if content_type lacks bytes\")"},{"line_number":585,"context_line":"    details_parser.add_argument(\"-v\", \"--verbose\","}],"source_content_type":"text/x-python","patch_set":4,"id":"4bf8e36e_f1ee7542","line":582,"in_reply_to":"e008e507_33f77b7d","updated":"2025-07-23 02:11:51.000000000","message":"Acknowledged","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"c02bb7189797043b445dd70d9979cab0f33e35f1","unresolved":true,"context_lines":[{"line_number":581,"context_line":"    details_parser.add_argument(\"--verify-bytes\","},{"line_number":582,"context_line":"                                action\u003d\"store_true\","},{"line_number":583,"context_line":"                                help\u003d\"Use HEAD requests to verify object size\""},{"line_number":584,"context_line":"                                     \" if content_type lacks bytes\")"},{"line_number":585,"context_line":"    details_parser.add_argument(\"-v\", \"--verbose\","},{"line_number":586,"context_line":"                                action\u003d\"count\", default\u003d0)"},{"line_number":587,"context_line":""}],"source_content_type":"text/x-python","patch_set":4,"id":"6578156b_9725aeb7","line":584,"updated":"2025-07-11 18:21:23.000000000","message":"is this HEAD conditional on what it\u0027s content type, tho?  Would we even *want* that - I like the implementation just providing both - so you can *verify*\n\nmaybe this help text transitions from \"what it does\" to \"why you\u0027d want it\" in the same sentence.  I think go ahead and be verbose here; it\u0027s a weird and expensive behavior - probably reasonable to error on the side of TMI","commit_id":"67b5c886a5779b37edd38e2ccdb0113229774ba6"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"484abbc8eeae309b3b6d4ee3431dc4cd08adf55f","unresolved":true,"context_lines":[{"line_number":159,"context_line":"                                   days, delay)] +\u003d verified"},{"line_number":160,"context_line":"                    except Exception as e:"},{"line_number":161,"context_line":"                        logging.warning("},{"line_number":162,"context_line":"                            f\"Failed to parse object {obj_name}: {e}\")"},{"line_number":163,"context_line":""},{"line_number":164,"context_line":"            except Exception as e:"},{"line_number":165,"context_line":"                logging.warning("}],"source_content_type":"text/x-python","patch_set":8,"id":"4a5269c2_89ca8ee8","line":162,"updated":"2025-11-06 21:13:41.000000000","message":"for free functions, esp those created via mp.Process - they can just call `swift.common.logs.get_logger` directly and use `logger.msg`\n\nthis removes the need to configure a global root handler that can survive the fork - which we don\u0027t really have a green-SyslogHandler that can do that anyway.","commit_id":"2c99d500538d5e7d35c8f2706932103013fe4af3"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"6dbbf7f39f2dc8c409a69c49e90569b1ea01cc9f","unresolved":false,"context_lines":[{"line_number":159,"context_line":"                                   days, delay)] +\u003d verified"},{"line_number":160,"context_line":"                    except Exception as e:"},{"line_number":161,"context_line":"                        logging.warning("},{"line_number":162,"context_line":"                            f\"Failed to parse object {obj_name}: {e}\")"},{"line_number":163,"context_line":""},{"line_number":164,"context_line":"            except Exception as e:"},{"line_number":165,"context_line":"                logging.warning("}],"source_content_type":"text/x-python","patch_set":8,"id":"22951b59_95052762","line":162,"in_reply_to":"4a5269c2_89ca8ee8","updated":"2025-11-17 17:53:45.000000000","message":"Done","commit_id":"2c99d500538d5e7d35c8f2706932103013fe4af3"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"484abbc8eeae309b3b6d4ee3431dc4cd08adf55f","unresolved":true,"context_lines":[{"line_number":164,"context_line":"            except Exception as e:"},{"line_number":165,"context_line":"                logging.warning("},{"line_number":166,"context_line":"                    f\"Failed to list objects in container {container}: {e}\")"},{"line_number":167,"context_line":"                result_q.put(stats)"},{"line_number":168,"context_line":"                continue"},{"line_number":169,"context_line":""},{"line_number":170,"context_line":"        # Reduce step: send this container’s map back to the parent"}],"source_content_type":"text/x-python","patch_set":8,"id":"8a8caf6e_d85cd1a4","line":167,"updated":"2025-11-06 21:13:41.000000000","message":"probably just wait to put all the stats int he result_q at the end","commit_id":"2c99d500538d5e7d35c8f2706932103013fe4af3"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"484abbc8eeae309b3b6d4ee3431dc4cd08adf55f","unresolved":true,"context_lines":[{"line_number":241,"context_line":"            print(f\u0027expirer_task_container_objects{{host\u003d\"{self.hostname}\", \u0027"},{"line_number":242,"context_line":"                  f\u0027days\u003d\"{offset}\"}} {total}\u0027)"},{"line_number":243,"context_line":""},{"line_number":244,"context_line":"    def run_details(self):"},{"line_number":245,"context_line":"        container_q \u003d mp.Queue()"},{"line_number":246,"context_line":"        result_q \u003d mp.Queue()"},{"line_number":247,"context_line":""}],"source_content_type":"text/x-python","patch_set":8,"id":"eaefe17f_008a696c","line":244,"updated":"2025-11-06 21:13:41.000000000","message":"```\nclass TestDetails():\n\n    def run(self, cmd_args):\n        parser \u003d build_parser()\n        args \u003d parser.parse_args([\u0027details\u0027] + cmd_args)\n        with self.capture_io():\n            args.func(args)\n```\n\n```\nclass TestDetails():\n    def get_reporter(self, cmd_args):\n        parser \u003d build_parser()\n        args \u003d parser.parse_args([\u0027details\u0027] + cmd_args)\n        return args.hanlder(args, ...)\n```","commit_id":"2c99d500538d5e7d35c8f2706932103013fe4af3"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"484abbc8eeae309b3b6d4ee3431dc4cd08adf55f","unresolved":true,"context_lines":[{"line_number":344,"context_line":"        return container_day_counts"},{"line_number":345,"context_line":""},{"line_number":346,"context_line":"    def run_evaluation(self):"},{"line_number":347,"context_line":"        container_day_counts \u003d self._scan_task_containers()"},{"line_number":348,"context_line":"        frac_cutoff \u003d self.args.imbalance_fraction"},{"line_number":349,"context_line":"        skew_mul \u003d self.args.high_skew_multiplier"},{"line_number":350,"context_line":""}],"source_content_type":"text/x-python","patch_set":8,"id":"a84d186c_c1630e99","line":347,"updated":"2025-11-06 21:13:41.000000000","message":"```\nstub_day_counts \u003d {\n   -1: {\n      self.yesterday[0]: 1,\n      self.yesterday[1]: 11,\n   }\n}\nevaluation \u003d self.evaluator.evaluate(stub_day_counts)\nself.assertEqual(evaluation[\u0027ok\u0027], True)\n...\nself.evaulator.args.imbalance_fraction \u003d 0.00000001\nevaluation \u003d self.evaluator.evaluate(stub_day_counts)\nself.assertEqual(evaluation[\u0027ok\u0027], True)\n```","commit_id":"2c99d500538d5e7d35c8f2706932103013fe4af3"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"484abbc8eeae309b3b6d4ee3431dc4cd08adf55f","unresolved":true,"context_lines":[{"line_number":490,"context_line":"        except Exception as e:"},{"line_number":491,"context_line":"            logging.warning("},{"line_number":492,"context_line":"                f\"Failed to list objects in container {container[\u0027name\u0027]}: {e}\""},{"line_number":493,"context_line":"            )"},{"line_number":494,"context_line":"            return"},{"line_number":495,"context_line":""},{"line_number":496,"context_line":"    def process_move_q(self, args, move_q, container_ring, stats):"}],"source_content_type":"text/x-python","patch_set":8,"id":"d561321b_658e3dfa","line":493,"updated":"2025-11-06 21:13:41.000000000","message":"depending on what kind of excetpion we expect - you might want `logging.exception` or `logging.warning(..., sys_exc\u003de)`","commit_id":"2c99d500538d5e7d35c8f2706932103013fe4af3"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"484abbc8eeae309b3b6d4ee3431dc4cd08adf55f","unresolved":true,"context_lines":[{"line_number":629,"context_line":"def setup_logging(verbosity):"},{"line_number":630,"context_line":"    import logging"},{"line_number":631,"context_line":"    level \u003d logging.DEBUG if verbosity else logging.INFO"},{"line_number":632,"context_line":"    logging.basicConfig(level\u003dlevel)"},{"line_number":633,"context_line":""},{"line_number":634,"context_line":""},{"line_number":635,"context_line":"def main():"}],"source_content_type":"text/x-python","patch_set":8,"id":"52c3f143_1c6f26a6","line":632,"updated":"2025-11-06 21:13:41.000000000","message":"ok, so I think when your call logging.msg from multiple mp.Process their output to stdout will get mixed up\n\nI would suggest you use swift\u0027s `utils.logs.get_logger` to get a logger object you can attatch to a class instance, and use as `self.logger.msg` - this is the same how all swift deamons and servers do logging (and some cli tools).  However, b/c we use multiprocessing - i\u0027m pretty sure the green-SyslogHandler socket wont\u0027 survive the fork correctly - so each worker should call `get_logger` post-fork before using self.logger.","commit_id":"2c99d500538d5e7d35c8f2706932103013fe4af3"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"484abbc8eeae309b3b6d4ee3431dc4cd08adf55f","unresolved":true,"context_lines":[{"line_number":636,"context_line":"    parser \u003d build_parser()"},{"line_number":637,"context_line":"    args \u003d parser.parse_args()"},{"line_number":638,"context_line":"    setup_logging(args.verbose)"},{"line_number":639,"context_line":"    args.func(args)"},{"line_number":640,"context_line":""},{"line_number":641,"context_line":""},{"line_number":642,"context_line":"if __name__ \u003d\u003d \"__main__\":"}],"source_content_type":"text/x-python","patch_set":8,"id":"7f0eb076_1177aebe","line":639,"updated":"2025-11-06 21:13:41.000000000","message":"in tests:\n\n```\nargs \u003d parser.parse_args([\u0027details\u0027])\nself.assertTrue(isinstance(args.handler, DetailsReporter))\n```\n\nin code:\n\n```\nargs \u003d parser.parse_args()\nhandler \u003d args.handler(args)\nhandler.run()\n```","commit_id":"2c99d500538d5e7d35c8f2706932103013fe4af3"}],"test/probe/test_manage_expirer.py":[{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"753979f4370f3b7b60a02c9e0f49719aa77080bc","unresolved":true,"context_lines":[{"line_number":47,"context_line":"        conf_files \u003d []"},{"line_number":48,"context_line":"        for server in self.expirer.servers:"},{"line_number":49,"context_line":"            conf_files.extend(server.conf_files())"},{"line_number":50,"context_line":"        self.expirer_conf_file \u003d conf_files[0]"},{"line_number":51,"context_line":""},{"line_number":52,"context_line":"        conf_dir \u003d os.path.dirname(self.expirer_conf_file)"},{"line_number":53,"context_line":"        self.internal_client_conf \u003d os.path.join("}],"source_content_type":"text/x-python","patch_set":24,"id":"124776f3_fc5f0a48","line":50,"updated":"2026-01-23 20:40:32.000000000","message":"this seems to work OMM:\n\n```\n-\u003e self.client \u003d InternalClient(self.expirer_conf_file, \u0027probe-test\u0027, 3)\n(Pdb) self.expirer_conf_file\n\u0027/etc/swift/object-expirer.conf.d\u0027\n(Pdb) self.internal_client_conf\n\u0027/etc/swift/internal-client.conf\u0027\n(Pdb) !conf_dir\n\u0027/etc/swift\u0027\n(Pdb) conf_files\n[\u0027/etc/swift/object-expirer.conf.d\u0027]\n```\n\nbut, interestingly this fails in the gate:\n\n```\nE       OSError: Unable to read config from /etc/swift/object-expirer.conf.d\n```","commit_id":"9b364f337c3bd8a094d4718e9c9e3d53391da062"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"753979f4370f3b7b60a02c9e0f49719aa77080bc","unresolved":true,"context_lines":[{"line_number":124,"context_line":""},{"line_number":125,"context_line":"    def _run_manage_expirer_command(self, subcommand, extra_args\u003dNone):"},{"line_number":126,"context_line":"        cmd \u003d ["},{"line_number":127,"context_line":"            sys.executable, \u0027-m\u0027, \u0027swift.cli.manage_expirer\u0027,"},{"line_number":128,"context_line":"            \u0027--internal-client-conf-path\u0027, self.internal_client_conf,"},{"line_number":129,"context_line":"            \u0027--expirer-conf-path\u0027, self.expirer_conf_file,"},{"line_number":130,"context_line":"            subcommand"}],"source_content_type":"text/x-python","patch_set":24,"id":"911943a6_8f0429c3","line":127,"updated":"2026-01-23 20:40:32.000000000","message":"this doesn\u0027t work on my machine\n\n```\nvagrant@saio:~$ /usr/bin/python3 -m swift.cli.manage_expirer --help\nusage: manage_expirer.py [-h] [--internal-client-conf-path INTERNAL_CLIENT_CONF_PATH] [--expirer-conf-path EXPIRER_CONF_PATH] {summary,details,evaluate-balance,rebalance} ...\n\nSwift Manage Expirer CLI\n\npositional arguments:\n  {summary,details,evaluate-balance,rebalance}\n    summary             Summarize expirer container metadata\n    details             Detailed object-level analysis of expirer queue\n    evaluate-balance    Evaluate expirer task queue balance using RMSE from ideal. 100% \u003d perfect balance, 0% \u003d critical imbalance\n    rebalance           Rebalance misplaced expirer task entries\n\noptions:\n  -h, --help            show this help message and exit\n  --internal-client-conf-path INTERNAL_CLIENT_CONF_PATH\n                        Path to internal-client.conf\n  --expirer-conf-path EXPIRER_CONF_PATH\n                        Path to object-expirer.conf\nvagrant@saio:~$ /usr/bin/python3 -m swift.cli.manage_expirer details\nEnumerating expirer task containers...\nTraceback (most recent call last):\n  File \"/usr/lib/python3.10/runpy.py\", line 196, in _run_module_as_main\n    return _run_code(code, main_globals, None,\n  File \"/usr/lib/python3.10/runpy.py\", line 86, in _run_code\n    exec(code, run_globals)\n  File \"/vagrant/swift/swift/cli/manage_expirer.py\", line 880, in \u003cmodule\u003e\n    main()\n  File \"/vagrant/swift/swift/cli/manage_expirer.py\", line 876, in main\n    args.func(args)\n  File \"/vagrant/swift/swift/cli/manage_expirer.py\", line 799, in \u003clambda\u003e\n    func\u003dlambda args: BaseExpirerStatus(args).run_details())\n  File \"/vagrant/swift/swift/cli/manage_expirer.py\", line 296, in run_details\n    with internal_client_ctx(\n  File \"/usr/lib/python3.10/contextlib.py\", line 135, in __enter__\n    return next(self.gen)\n  File \"/vagrant/swift/swift/cli/manage_expirer.py\", line 46, in internal_client_ctx\n    ic \u003d InternalClient(\n  File \"/vagrant/swift/swift/common/internal_client.py\", line 158, in __init__\n    self.app \u003d app or loadapp(conf_path, global_conf\u003dglobal_conf,\n  File \"/vagrant/swift/swift/common/wsgi.py\", line 352, in loadapp\n    ctx \u003d loadcontext(loadwsgi.APP, conf_file, global_conf\u003dglobal_conf)\n  File \"/vagrant/swift/swift/common/wsgi.py\", line 332, in loadcontext\n    return loadwsgi.loadcontext(object_type, add_conf_type(uri), name\u003dname,\n  File \"/usr/local/lib/python3.10/dist-packages/paste/deploy/loadwsgi.py\", line 293, in loadcontext\n    return _loaders[scheme](\n  File \"/usr/local/lib/python3.10/dist-packages/paste/deploy/loadwsgi.py\", line 324, in _loadconfig\n    return loader.get_context(object_type, name, global_conf)\n  File \"/vagrant/swift/swift/common/wsgi.py\", line 70, in get_context\n    context \u003d super(NamedConfigLoader, self).get_context(\n  File \"/usr/local/lib/python3.10/dist-packages/paste/deploy/loadwsgi.py\", line 449, in get_context\n    context \u003d self._pipeline_app_context(\n  File \"/usr/local/lib/python3.10/dist-packages/paste/deploy/loadwsgi.py\", line 571, in _pipeline_app_context\n    context.app_context \u003d self.get_context(APP, pipeline[-1], global_conf)\n  File \"/vagrant/swift/swift/common/wsgi.py\", line 70, in get_context\n    context \u003d super(NamedConfigLoader, self).get_context(\n  File \"/usr/local/lib/python3.10/dist-packages/paste/deploy/loadwsgi.py\", line 458, in get_context\n    context \u003d self._context_from_use(\n  File \"/usr/local/lib/python3.10/dist-packages/paste/deploy/loadwsgi.py\", line 485, in _context_from_use\n    context \u003d self.get_context(object_type, name\u003duse, global_conf\u003dglobal_conf)\n  File \"/vagrant/swift/swift/common/wsgi.py\", line 70, in get_context\n    context \u003d super(NamedConfigLoader, self).get_context(\n  File \"/usr/local/lib/python3.10/dist-packages/paste/deploy/loadwsgi.py\", line 400, in get_context\n    return loadcontext(\n  File \"/usr/local/lib/python3.10/dist-packages/paste/deploy/loadwsgi.py\", line 293, in loadcontext\n    return _loaders[scheme](\n  File \"/usr/local/lib/python3.10/dist-packages/paste/deploy/loadwsgi.py\", line 332, in _loadegg\n    return loader.get_context(object_type, name, global_conf)\n  File \"/usr/local/lib/python3.10/dist-packages/paste/deploy/loadwsgi.py\", line 635, in get_context\n    entry_point, protocol, ep_name \u003d self.find_egg_entry_point(\n  File \"/usr/local/lib/python3.10/dist-packages/paste/deploy/loadwsgi.py\", line 661, in find_egg_entry_point\n    possible.append((entry.load(), protocol, entry.name))\n  File \"/usr/lib/python3.10/importlib/metadata/__init__.py\", line 171, in load\n    module \u003d import_module(match.group(\u0027module\u0027))\n  File \"/usr/lib/python3.10/importlib/__init__.py\", line 126, in import_module\n    return _bootstrap._gcd_import(name[level:], package, level)\n  File \"\u003cfrozen importlib._bootstrap\u003e\", line 1050, in _gcd_import\n  File \"\u003cfrozen importlib._bootstrap\u003e\", line 1027, in _find_and_load\n  File \"\u003cfrozen importlib._bootstrap\u003e\", line 1006, in _find_and_load_unlocked\n  File \"\u003cfrozen importlib._bootstrap\u003e\", line 688, in _load_unlocked\n  File \"\u003cfrozen importlib._bootstrap_external\u003e\", line 883, in exec_module\n  File \"\u003cfrozen importlib._bootstrap\u003e\", line 241, in _call_with_frames_removed\n  File \"/vagrant/swift/swift/proxy/server.py\", line 29, in \u003cmodule\u003e\n    from swift import __canonical_version__ as swift_version\nImportError: cannot import name \u0027__canonical_version__\u0027 from \u0027swift\u0027 (unknown location)\n```\n\nlike the `-m swift.cli.manage_expirer` part is ok, but for some reason trying to loadwsgi/InternalClient w/i that context blows up?  this traceback is exactly what I get when I run the probetests.\n\nUsing the command as expected works fine:\n\n```\nvagrant@saio:~$ swift-manage-expirer details\nEnumerating expirer task containers...\nFound 0 containers to process with 20 workers\n\nCompleted: processed 0 containers\n```\n\nI think we should just spell this `swift-manage-expirer` same as you\u0027d do for real:\n\n```\ndiff --git a/test/probe/test_manage_expirer.py b/test/probe/test_manage_expirer.py\nindex e90d6cb7d..32660c88b 100644\n--- a/test/probe/test_manage_expirer.py\n+++ b/test/probe/test_manage_expirer.py\n@@ -124,7 +124,7 @@ class TestManageExpirer(ReplProbeTest):\n \n     def _run_manage_expirer_command(self, subcommand, extra_args\u003dNone):\n         cmd \u003d [\n-            sys.executable, \u0027-m\u0027, \u0027swift.cli.manage_expirer\u0027,\n+            \u0027swift-manage-expirer\u0027,\n             \u0027--internal-client-conf-path\u0027, self.internal_client_conf,\n             \u0027--expirer-conf-path\u0027, self.expirer_conf_file,\n             subcommand\n```\n\n... same as we do for `swift-manage-shard-ranges` in `probe/test_sharder.py`\n\nWhere did you come up with this trick?  Does it work on your machine?","commit_id":"9b364f337c3bd8a094d4718e9c9e3d53391da062"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"2cc7dd35dd1e2dda44cecbd80403c3a6870e7a8f","unresolved":false,"context_lines":[{"line_number":124,"context_line":""},{"line_number":125,"context_line":"    def _run_manage_expirer_command(self, subcommand, extra_args\u003dNone):"},{"line_number":126,"context_line":"        cmd \u003d ["},{"line_number":127,"context_line":"            sys.executable, \u0027-m\u0027, \u0027swift.cli.manage_expirer\u0027,"},{"line_number":128,"context_line":"            \u0027--internal-client-conf-path\u0027, self.internal_client_conf,"},{"line_number":129,"context_line":"            \u0027--expirer-conf-path\u0027, self.expirer_conf_file,"},{"line_number":130,"context_line":"            subcommand"}],"source_content_type":"text/x-python","patch_set":24,"id":"78da378e_12a6c7a9","line":127,"in_reply_to":"911943a6_8f0429c3","updated":"2026-03-06 19:02:06.000000000","message":"Acknowledged","commit_id":"9b364f337c3bd8a094d4718e9c9e3d53391da062"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"753979f4370f3b7b60a02c9e0f49719aa77080bc","unresolved":true,"context_lines":[{"line_number":346,"context_line":"            client.put_object("},{"line_number":347,"context_line":"                self.url, self.token, self.container_name, obj_name,"},{"line_number":348,"context_line":"                headers\u003d{\u0027X-Delete-At\u0027: str(delete_at_far)},"},{"line_number":349,"context_line":"                contents\u003db\u0027far future\u0027"},{"line_number":350,"context_line":"            )"},{"line_number":351,"context_line":""},{"line_number":352,"context_line":"        Manager([\u0027object-updater\u0027]).once()"}],"source_content_type":"text/x-python","patch_set":24,"id":"37df0ea6_98c19d96","line":349,"updated":"2026-01-23 20:40:32.000000000","message":"i get a 404 here b/c `self.container_name` hasn\u0027t been created yet\n\n```\nE           swiftclient.exceptions.ClientException: Object PUT failed: http://saio:8080/v1/AUTH_test/expirer-test-55f8a198-f13c-469d-99ce-bb314e1ceec8/far-object-0 404 Not Found  [first 60 chars of response] b\u0027\u003chtml\u003e\u003ch1\u003eNot Found\u003c/h1\u003e\u003cp\u003eThe resource could not be found.\u003c\u0027 (txn: tx057909f31df347d6b9102-006973d85b)\n\n/vagrant/python-swiftclient/swiftclient/client.py:1445: ClientException\n```","commit_id":"9b364f337c3bd8a094d4718e9c9e3d53391da062"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"2cc7dd35dd1e2dda44cecbd80403c3a6870e7a8f","unresolved":false,"context_lines":[{"line_number":346,"context_line":"            client.put_object("},{"line_number":347,"context_line":"                self.url, self.token, self.container_name, obj_name,"},{"line_number":348,"context_line":"                headers\u003d{\u0027X-Delete-At\u0027: str(delete_at_far)},"},{"line_number":349,"context_line":"                contents\u003db\u0027far future\u0027"},{"line_number":350,"context_line":"            )"},{"line_number":351,"context_line":""},{"line_number":352,"context_line":"        Manager([\u0027object-updater\u0027]).once()"}],"source_content_type":"text/x-python","patch_set":24,"id":"d54ed910_35e4cfc7","line":349,"in_reply_to":"37df0ea6_98c19d96","updated":"2026-03-06 19:02:06.000000000","message":"Done","commit_id":"9b364f337c3bd8a094d4718e9c9e3d53391da062"}],"test/unit/cli/test_manage_expirer.py":[{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"753979f4370f3b7b60a02c9e0f49719aa77080bc","unresolved":true,"context_lines":[{"line_number":620,"context_line":"                \u0027c2\u0027: 101,  # Only 1% difference"},{"line_number":621,"context_line":"                \u0027c3\u0027: 100,"},{"line_number":622,"context_line":"            }"},{"line_number":623,"context_line":"        }"},{"line_number":624,"context_line":""},{"line_number":625,"context_line":"        with mock.patch.object(evaluator, \u0027_scan_task_containers\u0027,"},{"line_number":626,"context_line":"                               return_value\u003dstub_day_counts):"}],"source_content_type":"text/x-python","patch_set":24,"id":"f1763f7a_a2f6a296","line":623,"updated":"2026-01-23 20:40:32.000000000","message":"this seems like it\u0027s sort doing the right kind of testing, I like being able to drop into a debugger and see the output:\n\n```\n(Pdb) print(self.output)\n\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\nExpirer Task Queue Balance Evaluation\nConfigured task_container_per_day: 100\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nDay +0 (2026-01-23):\n  Total tasks:              301\n  Observed containers:      3\n  Ideal containers:         100\n  Expected per container:   3.0\n  RMSE from ideal:          97.32\n  Balance Score:            0.0% - CRITICAL - Rebalance urgently needed\n\n  Top 5 worst-balanced containers:\n    1. c2: 101 tasks (deviation: 98.0, 3255% off ideal)\n    2. c1: 100 tasks (deviation: 97.0, 3222% off ideal)\n    3. c3: 100 tasks (deviation: 97.0, 3222% off ideal)\n\nexpirer_balance_score{host\u003d\"saio\", days\u003d\"0\", date\u003d\"2026-01-23\"} 0.00\nexpirer_balance_rmse{host\u003d\"saio\", days\u003d\"0\", date\u003d\"2026-01-23\"} 97.32\nexpirer_balance_total_tasks{host\u003d\"saio\", days\u003d\"0\", date\u003d\"2026-01-23\"} 301\nexpirer_balance_expected_per_container{host\u003d\"saio\", days\u003d\"0\", date\u003d\"2026-01-23\"} 3.01\n```\n\n... but IIUC this output doesn\u0027t match the expected.\n\nOur \"evaluator\" seems to think that with 301 tasks we only expect *3* tasks per container - suggesting maybe it thinks there should be 100 task containers per day?\n\nmaybe try something like this?\n\n```\ndiff --git a/test/unit/cli/test_manage_expirer.py b/test/unit/cli/test_manage_expirer.py\nindex 839551e3a..3ca4bec50 100644\n--- a/test/unit/cli/test_manage_expirer.py\n+++ b/test/unit/cli/test_manage_expirer.py\n@@ -612,6 +612,7 @@ class TestEvaluateBalance(BaseTestCase):\n     def test_evaluate_high_imbalance_fraction(self):\n         \"\"\"Test that small imbalance is detected with high sensitivity\"\"\"\n         evaluator \u003d self.get_evaluator()\n+        evaluator.expirer_conf.task_container_per_day \u003d 3\n \n         # Very slight imbalance\n         stub_day_counts \u003d {\n```","commit_id":"9b364f337c3bd8a094d4718e9c9e3d53391da062"},{"author":{"_account_id":34892,"name":"ASHWIN A NAIR","display_name":"indianwhocodes","email":"nairashwin952013@gmail.com","username":"indianwhocodes","status":"Nvidia"},"change_message_id":"2cc7dd35dd1e2dda44cecbd80403c3a6870e7a8f","unresolved":false,"context_lines":[{"line_number":620,"context_line":"                \u0027c2\u0027: 101,  # Only 1% difference"},{"line_number":621,"context_line":"                \u0027c3\u0027: 100,"},{"line_number":622,"context_line":"            }"},{"line_number":623,"context_line":"        }"},{"line_number":624,"context_line":""},{"line_number":625,"context_line":"        with mock.patch.object(evaluator, \u0027_scan_task_containers\u0027,"},{"line_number":626,"context_line":"                               return_value\u003dstub_day_counts):"}],"source_content_type":"text/x-python","patch_set":24,"id":"27f131a4_b090e95f","line":623,"in_reply_to":"f1763f7a_a2f6a296","updated":"2026-03-06 19:02:06.000000000","message":"Fixed; sorta did it a diff way with in L618:\n```\n        expirer_conf \u003d os.path.join(self.config_dir, \u002720_settings.conf\u0027)\n        with open(expirer_conf, \u0027w\u0027) as f:\n            f.write(\u0027[object-expirer]\\n\u0027)\n            f.write(\u0027expiring_objects_task_container_per_day \u003d 3\\n\u0027)\n            f.write(\u0027reclaim_age \u003d 604800\\n\u0027)\n```","commit_id":"9b364f337c3bd8a094d4718e9c9e3d53391da062"}]}
