)]}'
{"doc/source/deployment_guide.rst":[{"author":{"_account_id":7233,"name":"Matthew Oliver","email":"matt@oliver.net.au","username":"mattoliverau"},"change_message_id":"9e410e43866d5727f2de5788665c6e7c2e991c69","unresolved":false,"context_lines":[{"line_number":696,"context_line":"                                                       run per worker process"},{"line_number":697,"context_line":"replication_workers          0                         Number of worker processes to use."},{"line_number":698,"context_line":"                                                       No matter how big this number is,"},{"line_number":699,"context_line":"\t\t\t\t\t\t       at most one worker per disk will"},{"line_number":700,"context_line":"\t\t\t\t\t\t       be used. The default value of 0"},{"line_number":701,"context_line":"\t\t\t\t\t\t       means no forking; all work is done"},{"line_number":702,"context_line":"\t\t\t\t\t\t       in the main process."}],"source_content_type":"text/x-rst","patch_set":2,"id":"bf659307_27f8a147","line":699,"range":{"start_line":699,"start_character":0,"end_line":699,"end_character":6},"updated":"2018-04-09 06:05:27.000000000","message":"I\u0027m surprised the docs job was ok with the tabs here.","commit_id":"16a1149612e21817cd1bb7ec68fb57b06f2bb921"},{"author":{"_account_id":7233,"name":"Matthew Oliver","email":"matt@oliver.net.au","username":"mattoliverau"},"change_message_id":"e07eea3b8a282eb6d3996c76e1ccfc154b5944fd","unresolved":false,"context_lines":[{"line_number":694,"context_line":"                                                       replication passes"},{"line_number":695,"context_line":"concurrency                  1                         Number of replication jobs to"},{"line_number":696,"context_line":"                                                       run per worker process"},{"line_number":697,"context_line":"replication_workers          0                         Number of worker processes to use."},{"line_number":698,"context_line":"                                                       No matter how big this number is,"},{"line_number":699,"context_line":"                                                       at most one worker per disk will"},{"line_number":700,"context_line":"                                                       be used. The default value of 0"}],"source_content_type":"text/x-rst","patch_set":4,"id":"9f6a8fd7_030b9623","line":697,"range":{"start_line":697,"start_character":0,"end_line":697,"end_character":19},"updated":"2018-04-24 04:03:44.000000000","message":"According to the code this is actually meant to be \u0027replicator_workers\u0027 not \u0027replication_workers\u0027.\n\nI found this out the hard way trying to figure out what I was doing wrong when recreating this on a new SAIO.","commit_id":"bcd394e860c1ae00e6069a89b056d9fe694ef2f1"}],"etc/object-server.conf-sample":[{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"d4990851a90a43dc35a0298a4d0e3fb83b8ed0f3","unresolved":false,"context_lines":[{"line_number":233,"context_line":"# Number of worker processes to use. No matter how big this number is,"},{"line_number":234,"context_line":"# at most one worker per disk will be used. 0 means no forking; all work"},{"line_number":235,"context_line":"# is done in the main process."},{"line_number":236,"context_line":"# replicator_workers \u003d 0"},{"line_number":237,"context_line":"#"},{"line_number":238,"context_line":"# stats_interval \u003d 300"},{"line_number":239,"context_line":"#"}],"source_content_type":"application/octet-stream","patch_set":3,"id":"bf659307_e578482c","line":236,"updated":"2018-04-10 18:23:07.000000000","message":"Is there any advantage to using 1? Or would you expect this to always be either 0 or some N \u003e\u003d 2?","commit_id":"efa6fb81c92eaf468e6ad19a4ca77a3c3a45be43"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"9641e3261a2dd5ad8bc71d71334641df068a2369","unresolved":false,"context_lines":[{"line_number":233,"context_line":"# Number of worker processes to use. No matter how big this number is,"},{"line_number":234,"context_line":"# at most one worker per disk will be used. 0 means no forking; all work"},{"line_number":235,"context_line":"# is done in the main process."},{"line_number":236,"context_line":"# replicator_workers \u003d 0"},{"line_number":237,"context_line":"#"},{"line_number":238,"context_line":"# stats_interval \u003d 300"},{"line_number":239,"context_line":"#"}],"source_content_type":"application/octet-stream","patch_set":3,"id":"bf659307_59aadf70","line":236,"in_reply_to":"bf659307_3a9624f2","updated":"2018-04-12 21:01:56.000000000","message":"having a special keyword \"auto\" to mean \"number of disks\" would be a great follow up.  There is a mechanical difference between 0 and 1 - it\u0027s just not useful.  Having more processes than disks might at some point be supported and useful (i.e. once we can shard out work per-partition, or if we have dedicated handoff_only workers)","commit_id":"efa6fb81c92eaf468e6ad19a4ca77a3c3a45be43"},{"author":{"_account_id":597,"name":"Pete Zaitcev","email":"zaitcev@kotori.zaitcev.us","username":"zaitcev"},"change_message_id":"0af1adc46a7875c4ff863d87cc47cd52048bd30f","unresolved":false,"context_lines":[{"line_number":233,"context_line":"# Number of worker processes to use. No matter how big this number is,"},{"line_number":234,"context_line":"# at most one worker per disk will be used. 0 means no forking; all work"},{"line_number":235,"context_line":"# is done in the main process."},{"line_number":236,"context_line":"# replicator_workers \u003d 0"},{"line_number":237,"context_line":"#"},{"line_number":238,"context_line":"# stats_interval \u003d 300"},{"line_number":239,"context_line":"#"}],"source_content_type":"application/octet-stream","patch_set":3,"id":"bf659307_3a9624f2","line":236,"in_reply_to":"bf659307_c99bc863","updated":"2018-04-11 19:41:39.000000000","message":"That\u0027s why I wish that 1 were the default; 0 meant \"the number of disks\", and any other number was obeyed, even if it\u0027s 100. I reflexively hate on parameters that are clipped without a bypass. What if I want more processes for some kind of test?","commit_id":"efa6fb81c92eaf468e6ad19a4ca77a3c3a45be43"},{"author":{"_account_id":2622,"name":"Samuel Merritt","email":"spam+launchpad@andcheese.org","username":"torgomatic"},"change_message_id":"cec22f36fe5d54aa894234c59f4f469a2ed3089d","unresolved":false,"context_lines":[{"line_number":233,"context_line":"# Number of worker processes to use. No matter how big this number is,"},{"line_number":234,"context_line":"# at most one worker per disk will be used. 0 means no forking; all work"},{"line_number":235,"context_line":"# is done in the main process."},{"line_number":236,"context_line":"# replicator_workers \u003d 0"},{"line_number":237,"context_line":"#"},{"line_number":238,"context_line":"# stats_interval \u003d 300"},{"line_number":239,"context_line":"#"}],"source_content_type":"application/octet-stream","patch_set":3,"id":"bf659307_c99bc863","line":236,"in_reply_to":"bf659307_e578482c","updated":"2018-04-10 20:05:52.000000000","message":"No, using 1 is sort of pointless.","commit_id":"efa6fb81c92eaf468e6ad19a4ca77a3c3a45be43"}],"swift/common/utils.py":[{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"d4990851a90a43dc35a0298a4d0e3fb83b8ed0f3","unresolved":false,"context_lines":[{"line_number":3256,"context_line":"                        if err.errno !\u003d errno.ENOENT:"},{"line_number":3257,"context_line":"                            raise"},{"line_number":3258,"context_line":"    except (Exception, Timeout) as err:"},{"line_number":3259,"context_line":"        logger.exception(\u0027Exception dumping recon cache: %s\u0027 % err)"},{"line_number":3260,"context_line":""},{"line_number":3261,"context_line":""},{"line_number":3262,"context_line":"def load_recon_cache(cache_file):"}],"source_content_type":"text/x-python","patch_set":3,"id":"bf659307_c5cbc450","line":3259,"range":{"start_line":3259,"start_character":15,"end_line":3259,"end_character":24},"updated":"2018-04-10 18:23:07.000000000","message":"I thought we use exception() so we *didn\u0027t* have to shove the error into a string ourselves :-/\n\nMeh, either way.","commit_id":"efa6fb81c92eaf468e6ad19a4ca77a3c3a45be43"},{"author":{"_account_id":2622,"name":"Samuel Merritt","email":"spam+launchpad@andcheese.org","username":"torgomatic"},"change_message_id":"cec22f36fe5d54aa894234c59f4f469a2ed3089d","unresolved":false,"context_lines":[{"line_number":3256,"context_line":"                        if err.errno !\u003d errno.ENOENT:"},{"line_number":3257,"context_line":"                            raise"},{"line_number":3258,"context_line":"    except (Exception, Timeout) as err:"},{"line_number":3259,"context_line":"        logger.exception(\u0027Exception dumping recon cache: %s\u0027 % err)"},{"line_number":3260,"context_line":""},{"line_number":3261,"context_line":""},{"line_number":3262,"context_line":"def load_recon_cache(cache_file):"}],"source_content_type":"text/x-python","patch_set":3,"id":"bf659307_697ddc1a","line":3259,"range":{"start_line":3259,"start_character":15,"end_line":3259,"end_character":24},"in_reply_to":"bf659307_c5cbc450","updated":"2018-04-10 20:05:52.000000000","message":"exception() just gives us the stack trace, not the exception text, I think.","commit_id":"efa6fb81c92eaf468e6ad19a4ca77a3c3a45be43"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"56df55f8197033e8d35c15244af2e08dfccc198f","unresolved":false,"context_lines":[{"line_number":3262,"context_line":"def load_recon_cache(cache_file):"},{"line_number":3263,"context_line":"    \"\"\""},{"line_number":3264,"context_line":"    Load a recon cache file. Treats missing file as empty."},{"line_number":3265,"context_line":"    \"\"\""},{"line_number":3266,"context_line":"    try:"},{"line_number":3267,"context_line":"        with open(cache_file) as fh:"},{"line_number":3268,"context_line":"            return json.load(fh)"}],"source_content_type":"text/x-python","patch_set":3,"id":"bf659307_42d24a5e","line":3265,"updated":"2018-04-09 22:54:28.000000000","message":"i know it\u0027s silly, but it\u0027d be nice to have a test failure in utils for this bad boy...\n    diff --git a/test/unit/common/test_utils.py b/test/unit/common/test_utils.py\n    index 893bb18..0d6057f 100644\n    --- a/test/unit/common/test_utils.py\n    +++ b/test/unit/common/test_utils.py\n    @@ -1532,6 +1532,15 @@ class TestUtils(unittest.TestCase):\n             finally:\n                 rmtree(testdir_base)\n     \n    +    def test_load_recon_cache(self):\n    +        stub_data \u003d {\u0027test\u0027: \u0027foo\u0027}\n    +        with NamedTemporaryFile() as f:\n    +            json.dump(stub_data, f)\n    +            f.flush()\n    +            self.assertEqual(stub_data, utils.load_recon_cache(f.name))\n    +        self.assertFalse(os.path.exists(f.name))  # sanity\n    +        self.assertEqual({}, utils.load_recon_cache(f.name))\n    +\n         def test_get_logger(self):\n             sio \u003d StringIO()\n             logger \u003d logging.getLogger(\u0027server\u0027)","commit_id":"efa6fb81c92eaf468e6ad19a4ca77a3c3a45be43"},{"author":{"_account_id":2622,"name":"Samuel Merritt","email":"spam+launchpad@andcheese.org","username":"torgomatic"},"change_message_id":"7467d491b916781dbff57e9ba8b7461dfafa8a75","unresolved":false,"context_lines":[{"line_number":3262,"context_line":"def load_recon_cache(cache_file):"},{"line_number":3263,"context_line":"    \"\"\""},{"line_number":3264,"context_line":"    Load a recon cache file. Treats missing file as empty."},{"line_number":3265,"context_line":"    \"\"\""},{"line_number":3266,"context_line":"    try:"},{"line_number":3267,"context_line":"        with open(cache_file) as fh:"},{"line_number":3268,"context_line":"            return json.load(fh)"}],"source_content_type":"text/x-python","patch_set":3,"id":"bf659307_055c5c7c","line":3265,"in_reply_to":"bf659307_42d24a5e","updated":"2018-04-10 01:10:40.000000000","message":"Done","commit_id":"efa6fb81c92eaf468e6ad19a4ca77a3c3a45be43"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"56df55f8197033e8d35c15244af2e08dfccc198f","unresolved":false,"context_lines":[{"line_number":3266,"context_line":"    try:"},{"line_number":3267,"context_line":"        with open(cache_file) as fh:"},{"line_number":3268,"context_line":"            return json.load(fh)"},{"line_number":3269,"context_line":"    except IOError as e:"},{"line_number":3270,"context_line":"        if e.errno \u003d\u003d errno.ENOENT:"},{"line_number":3271,"context_line":"            return {}"},{"line_number":3272,"context_line":"        else:"}],"source_content_type":"text/x-python","patch_set":3,"id":"bf659307_02f3f20b","line":3269,"updated":"2018-04-09 22:54:28.000000000","message":"ValueError?\n\n      File \"/home/vagrant/swift/swift/common/utils.py\", line 3268, in load_recon_cache\n        return json.load(fh)\n      File \"/usr/lib/python2.7/json/__init__.py\", line 291, in load\n        **kw)\n      File \"/usr/lib/python2.7/json/__init__.py\", line 339, in loads\n        return _default_decoder.decode(s)\n      File \"/usr/lib/python2.7/json/decoder.py\", line 364, in decode\n        obj, end \u003d self.raw_decode(s, idx\u003d_w(s, 0).end())\n      File \"/usr/lib/python2.7/json/decoder.py\", line 382, in raw_decode\n        raise ValueError(\"No JSON object could be decoded\")\n    ValueError: No JSON object could be decoded","commit_id":"efa6fb81c92eaf468e6ad19a4ca77a3c3a45be43"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"d4990851a90a43dc35a0298a4d0e3fb83b8ed0f3","unresolved":false,"context_lines":[{"line_number":3266,"context_line":"    try:"},{"line_number":3267,"context_line":"        with open(cache_file) as fh:"},{"line_number":3268,"context_line":"            return json.load(fh)"},{"line_number":3269,"context_line":"    except IOError as e:"},{"line_number":3270,"context_line":"        if e.errno \u003d\u003d errno.ENOENT:"},{"line_number":3271,"context_line":"            return {}"},{"line_number":3272,"context_line":"        else:"}],"source_content_type":"text/x-python","patch_set":3,"id":"bf659307_85c5cc3e","line":3269,"in_reply_to":"bf659307_02f3f20b","updated":"2018-04-10 18:23:07.000000000","message":"No worse than before, anyway.","commit_id":"efa6fb81c92eaf468e6ad19a4ca77a3c3a45be43"},{"author":{"_account_id":2622,"name":"Samuel Merritt","email":"spam+launchpad@andcheese.org","username":"torgomatic"},"change_message_id":"7467d491b916781dbff57e9ba8b7461dfafa8a75","unresolved":false,"context_lines":[{"line_number":3266,"context_line":"    try:"},{"line_number":3267,"context_line":"        with open(cache_file) as fh:"},{"line_number":3268,"context_line":"            return json.load(fh)"},{"line_number":3269,"context_line":"    except IOError as e:"},{"line_number":3270,"context_line":"        if e.errno \u003d\u003d errno.ENOENT:"},{"line_number":3271,"context_line":"            return {}"},{"line_number":3272,"context_line":"        else:"}],"source_content_type":"text/x-python","patch_set":3,"id":"bf659307_6563d8b2","line":3269,"in_reply_to":"bf659307_02f3f20b","updated":"2018-04-10 01:10:40.000000000","message":"Probably a good idea. Better to overwrite a borked recon file than to blow up the replicator until an operator shows up to fix the darn thing.","commit_id":"efa6fb81c92eaf468e6ad19a4ca77a3c3a45be43"}],"swift/obj/reconstructor.py":[{"author":{"_account_id":7233,"name":"Matthew Oliver","email":"matt@oliver.net.au","username":"mattoliverau"},"change_message_id":"b632cc2dde67d6f74d33674237089010d331bf20","unresolved":false,"context_lines":[{"line_number":111,"context_line":""},{"line_number":112,"context_line":"    # Keys \u0027override_devices\u0027 and \u0027override_partitions\u0027 will be present if"},{"line_number":113,"context_line":"    # we\u0027re a worker process. The results of get_worker_args() are passed to"},{"line_number":114,"context_line":"    # run() or run_once(), but unpacked into keyword arguments. In that"},{"line_number":115,"context_line":"    # case, we ignore \u0027devices\u0027 and \u0027partitions\u0027 since the parent"},{"line_number":116,"context_line":"    # reconstructor process has already dealt with them."},{"line_number":117,"context_line":"    return {"}],"source_content_type":"text/x-python","patch_set":3,"id":"bf659307_a3032542","line":114,"updated":"2018-04-10 06:28:32.000000000","message":"NIT: it\u0027s passed to run_forever() or run_once()","commit_id":"efa6fb81c92eaf468e6ad19a4ca77a3c3a45be43"},{"author":{"_account_id":2622,"name":"Samuel Merritt","email":"spam+launchpad@andcheese.org","username":"torgomatic"},"change_message_id":"cec22f36fe5d54aa894234c59f4f469a2ed3089d","unresolved":false,"context_lines":[{"line_number":111,"context_line":""},{"line_number":112,"context_line":"    # Keys \u0027override_devices\u0027 and \u0027override_partitions\u0027 will be present if"},{"line_number":113,"context_line":"    # we\u0027re a worker process. The results of get_worker_args() are passed to"},{"line_number":114,"context_line":"    # run() or run_once(), but unpacked into keyword arguments. In that"},{"line_number":115,"context_line":"    # case, we ignore \u0027devices\u0027 and \u0027partitions\u0027 since the parent"},{"line_number":116,"context_line":"    # reconstructor process has already dealt with them."},{"line_number":117,"context_line":"    return {"}],"source_content_type":"text/x-python","patch_set":3,"id":"bf659307_e4d27134","line":114,"in_reply_to":"bf659307_a3032542","updated":"2018-04-10 20:05:52.000000000","message":"Handled in followup","commit_id":"efa6fb81c92eaf468e6ad19a4ca77a3c3a45be43"}],"swift/obj/replicator.py":[{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"adfe97679cb96f1c1681c257f11043da3f95a52b","unresolved":false,"context_lines":[{"line_number":41,"context_line":"from swift.common.http import HTTP_OK, HTTP_INSUFFICIENT_STORAGE"},{"line_number":42,"context_line":"from swift.obj import ssync_sender"},{"line_number":43,"context_line":"from swift.obj.diskfile import get_data_dir, get_tmp_dir, DiskFileRouter"},{"line_number":44,"context_line":"from swift.obj.reconstructor import parse_override_options"},{"line_number":45,"context_line":"from swift.common.storage_policy import POLICIES, REPL_POLICY"},{"line_number":46,"context_line":""},{"line_number":47,"context_line":"DEFAULT_RSYNC_TIMEOUT \u003d 900"}],"source_content_type":"text/x-python","patch_set":1,"id":"bf659307_f2b0688e","line":44,"updated":"2018-03-23 20:11:55.000000000","message":"there\u0027s utils.parse_overrides too - but... maybe that\u0027s too different and not worth unifying...","commit_id":"0de1caf6672cdc48b940e4a0f0af38f312a86d0a"},{"author":{"_account_id":2622,"name":"Samuel Merritt","email":"spam+launchpad@andcheese.org","username":"torgomatic"},"change_message_id":"9c60df1fb5623be10d65c77314213c0f28171926","unresolved":false,"context_lines":[{"line_number":41,"context_line":"from swift.common.http import HTTP_OK, HTTP_INSUFFICIENT_STORAGE"},{"line_number":42,"context_line":"from swift.obj import ssync_sender"},{"line_number":43,"context_line":"from swift.obj.diskfile import get_data_dir, get_tmp_dir, DiskFileRouter"},{"line_number":44,"context_line":"from swift.obj.reconstructor import parse_override_options"},{"line_number":45,"context_line":"from swift.common.storage_policy import POLICIES, REPL_POLICY"},{"line_number":46,"context_line":""},{"line_number":47,"context_line":"DEFAULT_RSYNC_TIMEOUT \u003d 900"}],"source_content_type":"text/x-python","patch_set":1,"id":"bf659307_3f6f88c6","line":44,"in_reply_to":"bf659307_f2b0688e","updated":"2018-03-23 21:22:26.000000000","message":"I\u0027ll try it in a followup patch. It\u0027s similar, but the return value of utils.Everything() versus None may get tricky.","commit_id":"0de1caf6672cdc48b940e4a0f0af38f312a86d0a"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"adfe97679cb96f1c1681c257f11043da3f95a52b","unresolved":false,"context_lines":[{"line_number":120,"context_line":"                this_ip_failures[dev] \u003d ("},{"line_number":121,"context_line":"                    self_devs.get(dev, 0) + other_devs.get(dev, 0))"},{"line_number":122,"context_line":"            total.failure_nodes[ip] \u003d this_ip_failures"},{"line_number":123,"context_line":"        return total"},{"line_number":124,"context_line":""},{"line_number":125,"context_line":"    def add_failure_stats(self, failures):"},{"line_number":126,"context_line":"        \"\"\""}],"source_content_type":"text/x-python","patch_set":1,"id":"bf659307_92180cd4","line":123,"updated":"2018-03-23 20:11:55.000000000","message":"oh, wow - so this is how the .values() aggregation in total_stats works - that\u0027s SLICK!","commit_id":"0de1caf6672cdc48b940e4a0f0af38f312a86d0a"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"adfe97679cb96f1c1681c257f11043da3f95a52b","unresolved":false,"context_lines":[{"line_number":203,"context_line":"        self._child_process_reaper_queue \u003d queue.LightQueue()"},{"line_number":204,"context_line":""},{"line_number":205,"context_line":"    def _zero_stats(self):"},{"line_number":206,"context_line":"        self.stats \u003d defaultdict(Stats)"},{"line_number":207,"context_line":""},{"line_number":208,"context_line":"    def _stats_for_device(self, device):"},{"line_number":209,"context_line":"        return self.stats[device]"}],"source_content_type":"text/x-python","patch_set":1,"id":"bf659307_d2d684f2","line":206,"updated":"2018-03-23 20:11:55.000000000","message":"I love self.stats_by_dev - I missed it on my first read","commit_id":"0de1caf6672cdc48b940e4a0f0af38f312a86d0a"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"adfe97679cb96f1c1681c257f11043da3f95a52b","unresolved":false,"context_lines":[{"line_number":211,"context_line":"    @property"},{"line_number":212,"context_line":"    def total_stats(self):"},{"line_number":213,"context_line":"        return functools.reduce("},{"line_number":214,"context_line":"            lambda x, y: x + y, self.stats.values(), Stats())"},{"line_number":215,"context_line":""},{"line_number":216,"context_line":"    def _get_my_replication_ips(self):"},{"line_number":217,"context_line":"        my_replication_ips \u003d set()"}],"source_content_type":"text/x-python","patch_set":1,"id":"bf659307_b2ef50e8","line":214,"updated":"2018-03-23 20:11:55.000000000","message":"there\u0027s \"operator.add\" which... should really be a __builtin__ because who likes to have to import operator [1]\n\nSo the Stats() is an initializer... could you use sum?\n\n    diff --git a/swift/obj/replicator.py b/swift/obj/replicator.py\n    index fe3e537..4086264 100644\n    --- a/swift/obj/replicator.py\n    +++ b/swift/obj/replicator.py\n    @@ -210,8 +210,7 @@ class ObjectReplicator(Daemon):\n     \n         @property\n         def total_stats(self):\n    -        return functools.reduce(\n    -            lambda x, y: x + y, self.stats.values(), Stats())\n    +        return sum(self.stats.values(), Stats())\n     \n         def _get_my_replication_ips(self):\n             my_replication_ips \u003d set()\n\n\n^ tests pass?\n\n1. https://docs.python.org/2/library/operator.html","commit_id":"0de1caf6672cdc48b940e4a0f0af38f312a86d0a"},{"author":{"_account_id":2622,"name":"Samuel Merritt","email":"spam+launchpad@andcheese.org","username":"torgomatic"},"change_message_id":"9c60df1fb5623be10d65c77314213c0f28171926","unresolved":false,"context_lines":[{"line_number":211,"context_line":"    @property"},{"line_number":212,"context_line":"    def total_stats(self):"},{"line_number":213,"context_line":"        return functools.reduce("},{"line_number":214,"context_line":"            lambda x, y: x + y, self.stats.values(), Stats())"},{"line_number":215,"context_line":""},{"line_number":216,"context_line":"    def _get_my_replication_ips(self):"},{"line_number":217,"context_line":"        my_replication_ips \u003d set()"}],"source_content_type":"text/x-python","patch_set":1,"id":"bf659307_4d3f39d6","line":214,"in_reply_to":"bf659307_b2ef50e8","updated":"2018-03-23 21:22:26.000000000","message":"That\u0027s much nicer. Done.","commit_id":"0de1caf6672cdc48b940e4a0f0af38f312a86d0a"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"adfe97679cb96f1c1681c257f11043da3f95a52b","unresolved":false,"context_lines":[{"line_number":284,"context_line":"            }"},{"line_number":285,"context_line":"            for _ in range(n_workers)]"},{"line_number":286,"context_line":"        for index, device in enumerate(devices_to_replicate):"},{"line_number":287,"context_line":"            worker_args[index % n_workers][\u0027override_devices\u0027].append(device)"},{"line_number":288,"context_line":"        return worker_args"},{"line_number":289,"context_line":""},{"line_number":290,"context_line":"    def is_healthy(self):"}],"source_content_type":"text/x-python","patch_set":1,"id":"bf659307_12ccfc87","line":287,"updated":"2018-03-23 20:11:55.000000000","message":"this is so much better that what the reconstrutor does - please tell me you\u0027re going to fix the reconstructor in a follow up?","commit_id":"0de1caf6672cdc48b940e4a0f0af38f312a86d0a"},{"author":{"_account_id":2622,"name":"Samuel Merritt","email":"spam+launchpad@andcheese.org","username":"torgomatic"},"change_message_id":"9c60df1fb5623be10d65c77314213c0f28171926","unresolved":false,"context_lines":[{"line_number":284,"context_line":"            }"},{"line_number":285,"context_line":"            for _ in range(n_workers)]"},{"line_number":286,"context_line":"        for index, device in enumerate(devices_to_replicate):"},{"line_number":287,"context_line":"            worker_args[index % n_workers][\u0027override_devices\u0027].append(device)"},{"line_number":288,"context_line":"        return worker_args"},{"line_number":289,"context_line":""},{"line_number":290,"context_line":"    def is_healthy(self):"}],"source_content_type":"text/x-python","patch_set":1,"id":"bf659307_4d189980","line":287,"in_reply_to":"bf659307_12ccfc87","updated":"2018-03-23 21:22:26.000000000","message":"Sure, I\u0027ll make a note to go do that after.","commit_id":"0de1caf6672cdc48b940e4a0f0af38f312a86d0a"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"adfe97679cb96f1c1681c257f11043da3f95a52b","unresolved":false,"context_lines":[{"line_number":604,"context_line":""},{"line_number":605,"context_line":"        :param job: a dict containing info about the partition to be replicated"},{"line_number":606,"context_line":"        \"\"\""},{"line_number":607,"context_line":"        stats \u003d self._stats_for_device(job[\u0027device\u0027])"},{"line_number":608,"context_line":"        stats.attempted +\u003d 1"},{"line_number":609,"context_line":"        self.logger.increment(\u0027partition.update.count.%s\u0027 % (job[\u0027device\u0027],))"},{"line_number":610,"context_line":"        headers \u003d dict(self.default_headers)"}],"source_content_type":"text/x-python","patch_set":1,"id":"bf659307_52b6f4db","line":607,"updated":"2018-03-23 20:11:55.000000000","message":"brilliant","commit_id":"0de1caf6672cdc48b940e4a0f0af38f312a86d0a"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"adfe97679cb96f1c1681c257f11043da3f95a52b","unresolved":false,"context_lines":[{"line_number":991,"context_line":"            # If it weren\u0027t for the failure_nodes field, we could do this as"},{"line_number":992,"context_line":"            # a bunch of shared memory using multiprocessing.Value, which"},{"line_number":993,"context_line":"            # would be nice because it\u0027d avoid dealing with existing data"},{"line_number":994,"context_line":"            # during an upgrade."},{"line_number":995,"context_line":"            update \u003d {"},{"line_number":996,"context_line":"                \u0027object_replication_per_disk\u0027: {"},{"line_number":997,"context_line":"                    od: {\u0027replication_stats\u0027:"}],"source_content_type":"text/x-python","patch_set":1,"id":"bf659307_32e90080","line":994,"updated":"2018-03-23 20:11:55.000000000","message":"meh, anything that starts up eventlet\u0027s hub and then later touches multiprocessing is scary for me","commit_id":"0de1caf6672cdc48b940e4a0f0af38f312a86d0a"},{"author":{"_account_id":2622,"name":"Samuel Merritt","email":"spam+launchpad@andcheese.org","username":"torgomatic"},"change_message_id":"9c60df1fb5623be10d65c77314213c0f28171926","unresolved":false,"context_lines":[{"line_number":991,"context_line":"            # If it weren\u0027t for the failure_nodes field, we could do this as"},{"line_number":992,"context_line":"            # a bunch of shared memory using multiprocessing.Value, which"},{"line_number":993,"context_line":"            # would be nice because it\u0027d avoid dealing with existing data"},{"line_number":994,"context_line":"            # during an upgrade."},{"line_number":995,"context_line":"            update \u003d {"},{"line_number":996,"context_line":"                \u0027object_replication_per_disk\u0027: {"},{"line_number":997,"context_line":"                    od: {\u0027replication_stats\u0027:"}],"source_content_type":"text/x-python","patch_set":1,"id":"bf659307_ff96f094","line":994,"in_reply_to":"bf659307_32e90080","updated":"2018-03-23 21:22:26.000000000","message":"Heh, true. At least multiprocessing.Value is shared memory, not some fork-pickle-and-pipe thing like multiprocessing.Manager.\n\nAt least IPC-via-object.recon gives you some visibility when it breaks.","commit_id":"0de1caf6672cdc48b940e4a0f0af38f312a86d0a"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"adfe97679cb96f1c1681c257f11043da3f95a52b","unresolved":false,"context_lines":[{"line_number":1107,"context_line":"    def post_multiprocess_run(self):"},{"line_number":1108,"context_line":"        # This method is called after run_once using multiple workers."},{"line_number":1109,"context_line":"        update \u003d self.aggregate_recon_update()"},{"line_number":1110,"context_line":"        dump_recon_cache(update, self.rcache, self.logger)"}],"source_content_type":"text/x-python","patch_set":1,"id":"bf659307_d2a7644f","line":1110,"updated":"2018-03-23 20:11:55.000000000","message":"why doesn\u0027t the reconstructor need one of these - this looks great!","commit_id":"0de1caf6672cdc48b940e4a0f0af38f312a86d0a"},{"author":{"_account_id":2622,"name":"Samuel Merritt","email":"spam+launchpad@andcheese.org","username":"torgomatic"},"change_message_id":"9c60df1fb5623be10d65c77314213c0f28171926","unresolved":false,"context_lines":[{"line_number":1107,"context_line":"    def post_multiprocess_run(self):"},{"line_number":1108,"context_line":"        # This method is called after run_once using multiple workers."},{"line_number":1109,"context_line":"        update \u003d self.aggregate_recon_update()"},{"line_number":1110,"context_line":"        dump_recon_cache(update, self.rcache, self.logger)"}],"source_content_type":"text/x-python","patch_set":1,"id":"bf659307_1f3884d1","line":1110,"in_reply_to":"bf659307_d2a7644f","updated":"2018-03-23 21:22:26.000000000","message":"The reconstructor might need one. I\u0027m not sure if it manages to produce aggregated stats at the end of a run-once invocation. Probably worth looking into.","commit_id":"0de1caf6672cdc48b940e4a0f0af38f312a86d0a"},{"author":{"_account_id":7233,"name":"Matthew Oliver","email":"matt@oliver.net.au","username":"mattoliverau"},"change_message_id":"9e410e43866d5727f2de5788665c6e7c2e991c69","unresolved":false,"context_lines":[{"line_number":1037,"context_line":""},{"line_number":1038,"context_line":"        # If all devices have reported in, then produce an aggregate stat"},{"line_number":1039,"context_line":"        # update."},{"line_number":1040,"context_line":"        if not set(self.all_local_devices) - set(per_disk_stats):"},{"line_number":1041,"context_line":"            for local_device in self.all_local_devices:"},{"line_number":1042,"context_line":"                if local_device not in per_disk_stats:"},{"line_number":1043,"context_line":"                    break"},{"line_number":1044,"context_line":"        return recon_update"},{"line_number":1045,"context_line":""},{"line_number":1046,"context_line":"    def run_once(self, is_multiprocess_worker\u003dFalse, *args, **kwargs):"}],"source_content_type":"text/x-python","patch_set":2,"id":"bf659307_aa5c78ed","line":1043,"range":{"start_line":1040,"start_character":8,"end_line":1043,"end_character":25},"updated":"2018-04-09 06:05:27.000000000","message":"What\u0027s this loop for? As far as I can tell, it doesn\u0027t actually end up doing anything.","commit_id":"16a1149612e21817cd1bb7ec68fb57b06f2bb921"},{"author":{"_account_id":2622,"name":"Samuel Merritt","email":"spam+launchpad@andcheese.org","username":"torgomatic"},"change_message_id":"bb5b0166208c30649f1463d793af598c9c5135c6","unresolved":false,"context_lines":[{"line_number":1037,"context_line":""},{"line_number":1038,"context_line":"        # If all devices have reported in, then produce an aggregate stat"},{"line_number":1039,"context_line":"        # update."},{"line_number":1040,"context_line":"        if not set(self.all_local_devices) - set(per_disk_stats):"},{"line_number":1041,"context_line":"            for local_device in self.all_local_devices:"},{"line_number":1042,"context_line":"                if local_device not in per_disk_stats:"},{"line_number":1043,"context_line":"                    break"},{"line_number":1044,"context_line":"        return recon_update"},{"line_number":1045,"context_line":""},{"line_number":1046,"context_line":"    def run_once(self, is_multiprocess_worker\u003dFalse, *args, **kwargs):"}],"source_content_type":"text/x-python","patch_set":2,"id":"bf659307_7c2135fb","line":1043,"range":{"start_line":1040,"start_character":8,"end_line":1043,"end_character":25},"in_reply_to":"bf659307_aa5c78ed","updated":"2018-04-09 21:11:46.000000000","message":"Whoops, that\u0027s a vestigial leftover. It used to have some useful code in it, but it moved up to the aggregate-some-stats block above.","commit_id":"16a1149612e21817cd1bb7ec68fb57b06f2bb921"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"56df55f8197033e8d35c15244af2e08dfccc198f","unresolved":false,"context_lines":[{"line_number":64,"context_line":"        s.suffix_hash \u003d dct[\u0027suffix_hash\u0027]"},{"line_number":65,"context_line":"        s.suffix_sync \u003d dct[\u0027suffix_sync\u0027]"},{"line_number":66,"context_line":"        s.failure_nodes \u003d dct[\u0027failure_nodes\u0027]"},{"line_number":67,"context_line":"        return s"},{"line_number":68,"context_line":""},{"line_number":69,"context_line":"    def to_recon(self):"},{"line_number":70,"context_line":"        return {"}],"source_content_type":"text/x-python","patch_set":3,"id":"bf659307_dff393e6","line":67,"updated":"2018-04-09 22:54:28.000000000","message":"the way this all worked out I\u0027d prefer:\n\n    diff --git a/swift/obj/replicator.py b/swift/obj/replicator.py\n    index 9c542c9..8965e89 100644\n    --- a/swift/obj/replicator.py\n    +++ b/swift/obj/replicator.py\n    @@ -53,18 +53,7 @@ def _do_listdir(partition, replication_cycle):\n     class Stats(object):\n         @classmethod\n         def from_recon(cls, dct):\n    -        s \u003d cls()\n    -        s.attempted \u003d dct[\u0027attempted\u0027]\n    -        s.failure \u003d dct[\u0027failure\u0027]\n    -        s.hashmatch \u003d dct[\u0027hashmatch\u0027]\n    -        s.remove \u003d dct[\u0027remove\u0027]\n    -        s.rsync \u003d dct[\u0027rsync\u0027]\n    -        s.success \u003d dct[\u0027success\u0027]\n    -        s.suffix_count \u003d dct[\u0027suffix_count\u0027]\n    -        s.suffix_hash \u003d dct[\u0027suffix_hash\u0027]\n    -        s.suffix_sync \u003d dct[\u0027suffix_sync\u0027]\n    -        s.failure_nodes \u003d dct[\u0027failure_nodes\u0027]\n    -        return s\n    +        return cls(**dct)\n     \n         def to_recon(self):\n             return {\n    @@ -82,7 +71,7 @@ class Stats(object):\n     \n         def __init__(self, attempted\u003d0, failure\u003d0, hashmatch\u003d0, remove\u003d0, rsync\u003d0,\n                      success\u003d0, suffix_count\u003d0, suffix_hash\u003d0,\n    -                 suffix_sync\u003d0, failure_nodes\u003dNone):\n    +                 suffix_sync\u003d0, failure_nodes\u003dNone, **kwargs):\n             if failure_nodes is None:\n                 failure_nodes \u003d {}","commit_id":"efa6fb81c92eaf468e6ad19a4ca77a3c3a45be43"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"d4990851a90a43dc35a0298a4d0e3fb83b8ed0f3","unresolved":false,"context_lines":[{"line_number":64,"context_line":"        s.suffix_hash \u003d dct[\u0027suffix_hash\u0027]"},{"line_number":65,"context_line":"        s.suffix_sync \u003d dct[\u0027suffix_sync\u0027]"},{"line_number":66,"context_line":"        s.failure_nodes \u003d dct[\u0027failure_nodes\u0027]"},{"line_number":67,"context_line":"        return s"},{"line_number":68,"context_line":""},{"line_number":69,"context_line":"    def to_recon(self):"},{"line_number":70,"context_line":"        return {"}],"source_content_type":"text/x-python","patch_set":3,"id":"bf659307_452b34ea","line":67,"in_reply_to":"bf659307_dff393e6","updated":"2018-04-10 18:23:07.000000000","message":"+1 -- shorter *and* it would mean we wouldn\u0027t have to remember to use .get() later when we decide we need another stat.","commit_id":"efa6fb81c92eaf468e6ad19a4ca77a3c3a45be43"},{"author":{"_account_id":2622,"name":"Samuel Merritt","email":"spam+launchpad@andcheese.org","username":"torgomatic"},"change_message_id":"7467d491b916781dbff57e9ba8b7461dfafa8a75","unresolved":false,"context_lines":[{"line_number":64,"context_line":"        s.suffix_hash \u003d dct[\u0027suffix_hash\u0027]"},{"line_number":65,"context_line":"        s.suffix_sync \u003d dct[\u0027suffix_sync\u0027]"},{"line_number":66,"context_line":"        s.failure_nodes \u003d dct[\u0027failure_nodes\u0027]"},{"line_number":67,"context_line":"        return s"},{"line_number":68,"context_line":""},{"line_number":69,"context_line":"    def to_recon(self):"},{"line_number":70,"context_line":"        return {"}],"source_content_type":"text/x-python","patch_set":3,"id":"bf659307_050e9c8c","line":67,"in_reply_to":"bf659307_dff393e6","updated":"2018-04-10 01:10:40.000000000","message":"I did something similar to this, but without the addition of **kwargs to __init__. I\u0027ve typed \"suffix_sycn\" and \"failure_ndoes\" too many times to make that a silent failure.","commit_id":"efa6fb81c92eaf468e6ad19a4ca77a3c3a45be43"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"d4990851a90a43dc35a0298a4d0e3fb83b8ed0f3","unresolved":false,"context_lines":[{"line_number":84,"context_line":"                 success\u003d0, suffix_count\u003d0, suffix_hash\u003d0,"},{"line_number":85,"context_line":"                 suffix_sync\u003d0, failure_nodes\u003dNone):"},{"line_number":86,"context_line":"        if failure_nodes is None:"},{"line_number":87,"context_line":"            failure_nodes \u003d {}"},{"line_number":88,"context_line":""},{"line_number":89,"context_line":"        self.attempted \u003d attempted"},{"line_number":90,"context_line":"        self.failure \u003d failure"}],"source_content_type":"text/x-python","patch_set":3,"id":"bf659307_451494a0","line":87,"updated":"2018-04-10 18:23:07.000000000","message":"...and here we *do* get an empty dict in recon, right? we don\u0027t go trying to pop the key?","commit_id":"efa6fb81c92eaf468e6ad19a4ca77a3c3a45be43"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"56df55f8197033e8d35c15244af2e08dfccc198f","unresolved":false,"context_lines":[{"line_number":130,"context_line":"        self.failure +\u003d len(failures)"},{"line_number":131,"context_line":"        for ip, device in failures:"},{"line_number":132,"context_line":"            dev_failures \u003d self.failure_nodes.setdefault(ip, {})"},{"line_number":133,"context_line":"            dev_failures[device] \u003d dev_failures.get(device, 0) + 1"},{"line_number":134,"context_line":""},{"line_number":135,"context_line":""},{"line_number":136,"context_line":"class ObjectReplicator(Daemon):"}],"source_content_type":"text/x-python","patch_set":3,"id":"bf659307_9fee7bb2","line":133,"updated":"2018-04-09 22:54:28.000000000","message":"meh, I think a defaultdict would be pretty good...\n\n    diff --git a/swift/obj/replicator.py b/swift/obj/replicator.py\n    index 9c542c9..fb52152 100644\n    --- a/swift/obj/replicator.py\n    +++ b/swift/obj/replicator.py\n    @@ -95,7 +95,8 @@ class Stats(object):\n             self.suffix_count \u003d suffix_count\n             self.suffix_hash \u003d suffix_hash\n             self.suffix_sync \u003d suffix_sync\n    -        self.failure_nodes \u003d failure_nodes\n    +        self.failure_nodes \u003d defaultdict(lambda: defaultdict(int),\n    +                                         failure_nodes)\n     \n         def __add__(self, other):\n             total \u003d type(self)()\n    @@ -129,8 +130,7 @@ class Stats(object):\n             \"\"\"\n             self.failure +\u003d len(failures)\n             for ip, device in failures:\n    -            dev_failures \u003d self.failure_nodes.setdefault(ip, {})\n    -            dev_failures[device] \u003d dev_failures.get(device, 0) + 1\n    +            self.failure_nodes[ip][device] +\u003d 1\n     \n     \n     class ObjectReplicator(Daemon):","commit_id":"efa6fb81c92eaf468e6ad19a4ca77a3c3a45be43"},{"author":{"_account_id":2622,"name":"Samuel Merritt","email":"spam+launchpad@andcheese.org","username":"torgomatic"},"change_message_id":"7467d491b916781dbff57e9ba8b7461dfafa8a75","unresolved":false,"context_lines":[{"line_number":130,"context_line":"        self.failure +\u003d len(failures)"},{"line_number":131,"context_line":"        for ip, device in failures:"},{"line_number":132,"context_line":"            dev_failures \u003d self.failure_nodes.setdefault(ip, {})"},{"line_number":133,"context_line":"            dev_failures[device] \u003d dev_failures.get(device, 0) + 1"},{"line_number":134,"context_line":""},{"line_number":135,"context_line":""},{"line_number":136,"context_line":"class ObjectReplicator(Daemon):"}],"source_content_type":"text/x-python","patch_set":3,"id":"bf659307_05b69cfb","line":133,"in_reply_to":"bf659307_9fee7bb2","updated":"2018-04-10 01:10:40.000000000","message":"Nice. That\u0027s definitely cleaner.","commit_id":"efa6fb81c92eaf468e6ad19a4ca77a3c3a45be43"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"56df55f8197033e8d35c15244af2e08dfccc198f","unresolved":false,"context_lines":[{"line_number":205,"context_line":"        self.stats \u003d defaultdict(Stats)"},{"line_number":206,"context_line":""},{"line_number":207,"context_line":"    def _stats_for_device(self, device):"},{"line_number":208,"context_line":"        return self.stats[device]"},{"line_number":209,"context_line":""},{"line_number":210,"context_line":"    @property"},{"line_number":211,"context_line":"    def total_stats(self):"}],"source_content_type":"text/x-python","patch_set":3,"id":"bf659307_9fdf5bd5","line":208,"updated":"2018-04-09 22:54:28.000000000","message":"I don\u0027t understand using the getter method here?  Curious what let you this way...\n\n    diff --git a/swift/obj/replicator.py b/swift/obj/replicator.py\n    index 9c542c9..73901af 100644\n    --- a/swift/obj/replicator.py\n    +++ b/swift/obj/replicator.py\n    @@ -202,14 +202,11 @@ class ObjectReplicator(Daemon):\n             self._child_process_reaper_queue \u003d queue.LightQueue()\n     \n         def _zero_stats(self):\n    -        self.stats \u003d defaultdict(Stats)\n    -\n    -    def _stats_for_device(self, device):\n    -        return self.stats[device]\n    +        self.stats_per_dev \u003d defaultdict(Stats)\n     \n         @property\n         def total_stats(self):\n    -        return sum(self.stats.values(), Stats())\n    +        return sum(self.stats_per_dev.values(), Stats())\n     \n         def _get_my_replication_ips(self):\n             my_replication_ips \u003d set()\n    @@ -482,7 +479,7 @@ class ObjectReplicator(Daemon):\n                 return [suff for suff in os.listdir(path)\n                         if len(suff) \u003d\u003d 3 and isdir(join(path, suff))]\n     \n    -        stats \u003d self._stats_for_device(job[\u0027device\u0027])\n    +        stats \u003d self.stats_per_dev[job[\u0027device\u0027]]\n             stats.attempted +\u003d 1\n             self.logger.increment(\u0027partition.delete.count.%s\u0027 % (job[\u0027device\u0027],))\n             headers \u003d dict(self.default_headers)\n    @@ -602,7 +599,7 @@ class ObjectReplicator(Daemon):\n     \n             :param job: a dict containing info about the partition to be replicated\n             \"\"\"\n    -        stats \u003d self._stats_for_device(job[\u0027device\u0027])\n    +        stats \u003d self.stats_per_dev[job[\u0027device\u0027]]\n             stats.attempted +\u003d 1\n             self.logger.increment(\u0027partition.update.count.%s\u0027 % (job[\u0027device\u0027],))\n             headers \u003d dict(self.default_headers)\n    @@ -790,7 +787,7 @@ class ObjectReplicator(Daemon):\n                 found_local \u003d True\n                 dev_path \u003d check_drive(self.devices_dir, local_dev[\u0027device\u0027],\n                                        self.mount_check)\n    -            local_dev_stats \u003d self._stats_for_device(local_dev[\u0027device\u0027])\n    +            local_dev_stats \u003d self.stats_per_dev[local_dev[\u0027device\u0027]]\n                 if not dev_path:\n                     local_dev_stats.add_failure_stats(\n                         [(failure_dev[\u0027replication_ip\u0027],\n    @@ -926,7 +923,7 @@ class ObjectReplicator(Daemon):\n                                          override_partitions\u003doverride_partitions,\n                                          override_policies\u003doverride_policies)\n                 for job in jobs:\n    -                dev_stats \u003d self._stats_for_device(job[\u0027device\u0027])\n    +                dev_stats \u003d self.stats_per_dev[job[\u0027device\u0027]]\n                     num_jobs +\u003d 1\n                     current_nodes \u003d job[\u0027nodes\u0027]\n                     dev_path \u003d check_drive(self.devices_dir, job[\u0027device\u0027],\n    @@ -993,7 +990,7 @@ class ObjectReplicator(Daemon):\n                 update \u003d {\n                     \u0027object_replication_per_disk\u0027: {\n                         od: {\u0027replication_stats\u0027:\n    -                         self._stats_for_device(od).to_recon(),\n    +                         self.stats_per_dev[od].to_recon(),\n                              \u0027replication_time\u0027: total,\n                              \u0027replication_last\u0027: end_time,\n                              \u0027object_replication_time\u0027: total,\n    @@ -1041,7 +1038,7 @@ class ObjectReplicator(Daemon):\n             self.is_multiprocess_worker \u003d is_multiprocess_worker\n             rsync_reaper \u003d eventlet.spawn(self._child_process_reaper)\n     \n    -        self.stats \u003d defaultdict(Stats)\n    +        self._zero_stats()\n             self.logger.info(_(\"Running object replicator in script mode.\"))\n     \n             overrides \u003d parse_override_options(**kwargs)\n    diff --git a/test/unit/obj/test_replicator.py b/test/unit/obj/test_replicator.py\n    index 028687b..ab1d271 100644\n    --- a/test/unit/obj/test_replicator.py\n    +++ b/test/unit/obj/test_replicator.py\n    @@ -2156,7 +2156,7 @@ class TestMultiProcessReplicator(unittest.TestCase):\n             # Faked-out replicate() method. Just updates the stats, but doesn\u0027t\n             # do any work.\n             for device in override_devices:\n    -            stats \u003d self.replicator._stats_for_device(device)\n    +            stats \u003d self.replicator.stats_per_dev[device]\n                 if device \u003d\u003d \u0027sda\u0027:\n                     stats.attempted \u003d 1\n                     stats.success \u003d 10","commit_id":"efa6fb81c92eaf468e6ad19a4ca77a3c3a45be43"},{"author":{"_account_id":2622,"name":"Samuel Merritt","email":"spam+launchpad@andcheese.org","username":"torgomatic"},"change_message_id":"7467d491b916781dbff57e9ba8b7461dfafa8a75","unresolved":false,"context_lines":[{"line_number":205,"context_line":"        self.stats \u003d defaultdict(Stats)"},{"line_number":206,"context_line":""},{"line_number":207,"context_line":"    def _stats_for_device(self, device):"},{"line_number":208,"context_line":"        return self.stats[device]"},{"line_number":209,"context_line":""},{"line_number":210,"context_line":"    @property"},{"line_number":211,"context_line":"    def total_stats(self):"}],"source_content_type":"text/x-python","patch_set":3,"id":"bf659307_e58368a8","line":208,"in_reply_to":"bf659307_9fdf5bd5","updated":"2018-04-10 01:10:40.000000000","message":"Done","commit_id":"efa6fb81c92eaf468e6ad19a4ca77a3c3a45be43"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"56df55f8197033e8d35c15244af2e08dfccc198f","unresolved":false,"context_lines":[{"line_number":209,"context_line":""},{"line_number":210,"context_line":"    @property"},{"line_number":211,"context_line":"    def total_stats(self):"},{"line_number":212,"context_line":"        return sum(self.stats.values(), Stats())"},{"line_number":213,"context_line":""},{"line_number":214,"context_line":"    def _get_my_replication_ips(self):"},{"line_number":215,"context_line":"        my_replication_ips \u003d set()"}],"source_content_type":"text/x-python","patch_set":3,"id":"bf659307_7f592758","line":212,"updated":"2018-04-09 22:54:28.000000000","message":"this little property is powerful.  love it!","commit_id":"efa6fb81c92eaf468e6ad19a4ca77a3c3a45be43"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"56df55f8197033e8d35c15244af2e08dfccc198f","unresolved":false,"context_lines":[{"line_number":278,"context_line":"            {"},{"line_number":279,"context_line":"                \u0027override_devices\u0027: [],"},{"line_number":280,"context_line":"                \u0027override_partitions\u0027: override_partitions,"},{"line_number":281,"context_line":"                \u0027is_multiprocess_worker\u0027: True,"},{"line_number":282,"context_line":"            }"},{"line_number":283,"context_line":"            for _ in range(n_workers)]"},{"line_number":284,"context_line":"        for index, device in enumerate(devices_to_replicate):"}],"source_content_type":"text/x-python","patch_set":3,"id":"bf659307_5f1d4371","line":281,"updated":"2018-04-09 22:54:28.000000000","message":"omg, having the args pass back this explicit flag is *brilliant*","commit_id":"efa6fb81c92eaf468e6ad19a4ca77a3c3a45be43"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"56df55f8197033e8d35c15244af2e08dfccc198f","unresolved":false,"context_lines":[{"line_number":282,"context_line":"            }"},{"line_number":283,"context_line":"            for _ in range(n_workers)]"},{"line_number":284,"context_line":"        for index, device in enumerate(devices_to_replicate):"},{"line_number":285,"context_line":"            worker_args[index % n_workers][\u0027override_devices\u0027].append(device)"},{"line_number":286,"context_line":"        return worker_args"},{"line_number":287,"context_line":""},{"line_number":288,"context_line":"    def is_healthy(self):"}],"source_content_type":"text/x-python","patch_set":3,"id":"bf659307_ffe9b77f","line":285,"updated":"2018-04-09 22:54:28.000000000","message":"so good!","commit_id":"efa6fb81c92eaf468e6ad19a4ca77a3c3a45be43"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"56df55f8197033e8d35c15244af2e08dfccc198f","unresolved":false,"context_lines":[{"line_number":537,"context_line":"                delete_handoff \u003d len(responses) \u003d\u003d len(job[\u0027nodes\u0027]) and \\"},{"line_number":538,"context_line":"                    all(responses)"},{"line_number":539,"context_line":"            if delete_handoff:"},{"line_number":540,"context_line":"                stats.remove +\u003d 1"},{"line_number":541,"context_line":"                if (self.conf.get(\u0027sync_method\u0027, \u0027rsync\u0027) \u003d\u003d \u0027ssync\u0027 and"},{"line_number":542,"context_line":"                        delete_objs is not None):"},{"line_number":543,"context_line":"                    self.logger.info(_(\"Removing %s objects\"),"}],"source_content_type":"text/x-python","patch_set":3,"id":"bf659307_3f4c0f9d","line":540,"updated":"2018-04-09 22:54:28.000000000","message":"i\u0027m loving how this diff is working out - great design!","commit_id":"efa6fb81c92eaf468e6ad19a4ca77a3c3a45be43"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"56df55f8197033e8d35c15244af2e08dfccc198f","unresolved":false,"context_lines":[{"line_number":715,"context_line":"        \"\"\""},{"line_number":716,"context_line":"        Logs various stats for the currently running replication pass."},{"line_number":717,"context_line":"        \"\"\""},{"line_number":718,"context_line":"        stats \u003d self.total_stats"},{"line_number":719,"context_line":"        replication_count \u003d stats.attempted"},{"line_number":720,"context_line":"        if replication_count \u003e self.last_replication_count:"},{"line_number":721,"context_line":"            self.last_replication_count \u003d replication_count"}],"source_content_type":"text/x-python","patch_set":3,"id":"bf659307_9f543b23","line":718,"updated":"2018-04-09 22:54:28.000000000","message":"*magic* - love it!","commit_id":"efa6fb81c92eaf468e6ad19a4ca77a3c3a45be43"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"56df55f8197033e8d35c15244af2e08dfccc198f","unresolved":false,"context_lines":[{"line_number":1066,"context_line":"        total \u003d (end_time - start_time) / 60"},{"line_number":1067,"context_line":"        self.logger.info("},{"line_number":1068,"context_line":"            _(\"Object replication complete (once). (%.02f minutes)\"), total)"},{"line_number":1069,"context_line":"        if (self.is_multiprocess_worker or ("},{"line_number":1070,"context_line":"                not override_partitions or override_devices)):"},{"line_number":1071,"context_line":"            self.update_recon(total, end_time, override_devices)"},{"line_number":1072,"context_line":""}],"source_content_type":"text/x-python","patch_set":3,"id":"bf659307_42ffaa00","line":1069,"updated":"2018-04-09 22:54:28.000000000","message":"that explicitness works out *really* well here!","commit_id":"efa6fb81c92eaf468e6ad19a4ca77a3c3a45be43"},{"author":{"_account_id":597,"name":"Pete Zaitcev","email":"zaitcev@kotori.zaitcev.us","username":"zaitcev"},"change_message_id":"0af1adc46a7875c4ff863d87cc47cd52048bd30f","unresolved":false,"context_lines":[{"line_number":1066,"context_line":"        total \u003d (end_time - start_time) / 60"},{"line_number":1067,"context_line":"        self.logger.info("},{"line_number":1068,"context_line":"            _(\"Object replication complete (once). (%.02f minutes)\"), total)"},{"line_number":1069,"context_line":"        if (self.is_multiprocess_worker or ("},{"line_number":1070,"context_line":"                not override_partitions or override_devices)):"},{"line_number":1071,"context_line":"            self.update_recon(total, end_time, override_devices)"},{"line_number":1072,"context_line":""}],"source_content_type":"text/x-python","patch_set":3,"id":"bf659307_a641bd3b","line":1069,"in_reply_to":"bf659307_42ffaa00","updated":"2018-04-11 19:41:39.000000000","message":"The  not (A or B)  turned into  not A or B   is fixed-up in patch I574e5fd5c22bfb52f78f876b9704fe18ff951c63","commit_id":"efa6fb81c92eaf468e6ad19a4ca77a3c3a45be43"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"9641e3261a2dd5ad8bc71d71334641df068a2369","unresolved":false,"context_lines":[{"line_number":1067,"context_line":"        self.logger.info("},{"line_number":1068,"context_line":"            _(\"Object replication complete (once). (%.02f minutes)\"), total)"},{"line_number":1069,"context_line":"        if (self.is_multiprocess_worker or ("},{"line_number":1070,"context_line":"                not override_partitions or override_devices)):"},{"line_number":1071,"context_line":"            self.update_recon(total, end_time, override_devices)"},{"line_number":1072,"context_line":""},{"line_number":1073,"context_line":"        # Give rsync processes one last chance to exit, then bail out and"}],"source_content_type":"text/x-python","patch_set":3,"id":"bf659307_d996ef3e","line":1070,"updated":"2018-04-12 21:01:56.000000000","message":"Pete is 100% correct - error in translation here","commit_id":"efa6fb81c92eaf468e6ad19a4ca77a3c3a45be43"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"4129b78188b27eb4876c014a309087bfd5d5e3e0","unresolved":false,"context_lines":[{"line_number":1067,"context_line":"        self.logger.info("},{"line_number":1068,"context_line":"            _(\"Object replication complete (once). (%.02f minutes)\"), total)"},{"line_number":1069,"context_line":"        if (self.is_multiprocess_worker or ("},{"line_number":1070,"context_line":"                not override_partitions or override_devices)):"},{"line_number":1071,"context_line":"            self.update_recon(total, end_time, override_devices)"},{"line_number":1072,"context_line":""},{"line_number":1073,"context_line":"        # Give rsync processes one last chance to exit, then bail out and"}],"source_content_type":"text/x-python","patch_set":3,"id":"bf659307_d9ceef2b","line":1070,"in_reply_to":"bf659307_d996ef3e","updated":"2018-04-12 21:08:55.000000000","message":"Are we sure that\u0027s not (somewhat) intentional? Now that update_recon can do per-disk results... maybe the logic *does* need to change...\n\nBut we\u0027d most-likely want something like\n\n if self.is_multiprocess_worker or (\n         override_devices and not override_partitions):\n\nhere and\n\n  if self.is_multiprocess_worker or override_devices:\n\nup around 988.","commit_id":"efa6fb81c92eaf468e6ad19a4ca77a3c3a45be43"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"1b170a222ca5d7f83e0eb5995e8b0430532f0743","unresolved":false,"context_lines":[{"line_number":263,"context_line":"                \u0027override_devices\u0027: [],"},{"line_number":264,"context_line":"                \u0027override_partitions\u0027: override_opts.partitions,"},{"line_number":265,"context_line":"                \u0027override_policies\u0027: override_opts.policies,"},{"line_number":266,"context_line":"                \u0027have_overrides\u0027: have_overrides,"},{"line_number":267,"context_line":"                \u0027multiprocess_worker_index\u0027: i,"},{"line_number":268,"context_line":"            }"},{"line_number":269,"context_line":"            for i in range(self.replicator_workers)]"}],"source_content_type":"text/x-python","patch_set":4,"id":"9f6a8fd7_e26ce509","line":266,"updated":"2018-04-17 19:35:13.000000000","message":"wow, I don\u0027t think I really appreciated the complexity of the command line over-rides for multi-process worker case","commit_id":"bcd394e860c1ae00e6069a89b056d9fe694ef2f1"}],"test/unit/obj/test_replicator.py":[{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"adfe97679cb96f1c1681c257f11043da3f95a52b","unresolved":false,"context_lines":[{"line_number":2112,"context_line":""},{"line_number":2113,"context_line":"@patch_policies([StoragePolicy(0, \u0027zero\u0027, False),"},{"line_number":2114,"context_line":"                 StoragePolicy(1, \u0027one\u0027, True)])"},{"line_number":2115,"context_line":"class TestMultiProcessReplicator(unittest.TestCase):"},{"line_number":2116,"context_line":"    def setUp(self):"},{"line_number":2117,"context_line":"        # recon cache path"},{"line_number":2118,"context_line":"        self.recon_cache \u003d tempfile.mkdtemp()"}],"source_content_type":"text/x-python","patch_set":1,"id":"bf659307_f275c8cd","line":2115,"updated":"2018-03-23 20:11:55.000000000","message":"this acctually covers a lot of ground - Kudos!","commit_id":"0de1caf6672cdc48b940e4a0f0af38f312a86d0a"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"adfe97679cb96f1c1681c257f11043da3f95a52b","unresolved":false,"context_lines":[{"line_number":2411,"context_line":"                for pd in recon_data[\u0027object_replication_per_disk\u0027].values()))"},{"line_number":2412,"context_line":""},{"line_number":2413,"context_line":""},{"line_number":2414,"context_line":"class TestReplicatorStats(unittest.TestCase):"},{"line_number":2415,"context_line":"    def test_to_recon(self):"},{"line_number":2416,"context_line":"        st \u003d object_replicator.Stats("},{"line_number":2417,"context_line":"            attempted\u003d1, failure\u003d2, hashmatch\u003d3, remove\u003d4,"}],"source_content_type":"text/x-python","patch_set":1,"id":"bf659307_326ca007","line":2414,"updated":"2018-03-23 20:11:55.000000000","message":"nice!","commit_id":"0de1caf6672cdc48b940e4a0f0af38f312a86d0a"}]}
