diff --git a/LICENSE-chia-blockchain b/LICENSE-chia-blockchain new file mode 100644 index 00000000..ee81ae2a --- /dev/null +++ b/LICENSE-chia-blockchain @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2021 Chia Network + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/MAINTENANCE.md b/MAINTENANCE.md new file mode 100644 index 00000000..b8b17c2d --- /dev/null +++ b/MAINTENANCE.md @@ -0,0 +1,19 @@ +# Maintenance + +## Overview + +This document holds guidance on maintaining aspects of plotman. + +## The `chia plots create` CLI parsing code + +In [src/plotman/chia.py](src/plotman/chia.py) there is code copied from the `chia plots create` subcommand's CLI parser definition. +When new versions of `chia-blockchain` are released, their interface code should be added to plotman. +plotman commit [1b5db4e](https://github.com/ericaltendorf/plotman/commit/1b5db4e342b9ec1f7910663a453aec3a97ba51a6) provides an example of adding a new version. + +In many cases, copying code is a poor choice. +It is believed that in this case it is appropriate since the chia code that plotman could import is not necessarily the code that is parsing the plotting process command lines anyways. +The chia command could come from another Python environment, a system package, a `.dmg`, etc. +This approach also offers future potential of using the proper version of parsing for the specific plot process being inspected. +Finally, this alleviates dealing with the dependency on the `chia-blockchain` package. +In generally, using dependencies is good. +This seems to be an exceptional case. diff --git a/MANIFEST.in b/MANIFEST.in index c6146883..8a034f2d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,5 @@ include CHANGELOG.md -include LICENSE +include LICENSE* include README.md include *.md include VERSION diff --git a/setup.cfg b/setup.cfg index 092ae2b3..ed151143 100644 --- a/setup.cfg +++ b/setup.cfg @@ -37,6 +37,7 @@ package_dir= packages=find: install_requires = appdirs + click desert marshmallow pendulum diff --git a/src/plotman/_tests/job_test.py b/src/plotman/_tests/job_test.py index ce6ff5e0..25840723 100644 --- a/src/plotman/_tests/job_test.py +++ b/src/plotman/_tests/job_test.py @@ -54,3 +54,88 @@ def test_job_parses_time_with_non_english_locale(logfile_path, locale_name): job.Job.init_from_logfile(self=faux_job_with_logfile) assert faux_job_with_logfile.start_time == log_file_time + + +@pytest.mark.parametrize( + argnames=['arguments'], + argvalues=[ + [['-h']], + [['--help']], + [['-k', '32']], + [['-k32']], + [['-k', '32', '--help']], + ], + ids=str, +) +def test_chia_plots_create_parsing_does_not_fail(arguments): + job.parse_chia_plots_create_command_line( + command_line=['python', 'chia', 'plots', 'create', *arguments], + ) + + +@pytest.mark.parametrize( + argnames=['arguments'], + argvalues=[ + [['-h']], + [['--help']], + [['-k', '32', '--help']], + ], + ids=str, +) +def test_chia_plots_create_parsing_detects_help(arguments): + parsed = job.parse_chia_plots_create_command_line( + command_line=['python', 'chia', 'plots', 'create', *arguments], + ) + + assert parsed.help + + +@pytest.mark.parametrize( + argnames=['arguments'], + argvalues=[ + [[]], + [['-k32']], + [['-k', '32']], + ], + ids=str, +) +def test_chia_plots_create_parsing_detects_not_help(arguments): + parsed = job.parse_chia_plots_create_command_line( + command_line=['python', 'chia', 'plots', 'create', *arguments], + ) + + assert not parsed.help + + +@pytest.mark.parametrize( + argnames=['arguments'], + argvalues=[ + [[]], + [['-k32']], + [['-k', '32']], + [['--size', '32']], + ], + ids=str, +) +def test_chia_plots_create_parsing_handles_argument_forms(arguments): + parsed = job.parse_chia_plots_create_command_line( + command_line=['python', 'chia', 'plots', 'create', *arguments], + ) + + assert parsed.parameters['size'] == 32 + + +@pytest.mark.parametrize( + argnames=['arguments'], + argvalues=[ + [['--size32']], + [['--not-an-actual-option']], + ], + ids=str, +) +def test_chia_plots_create_parsing_identifies_errors(arguments): + parsed = job.parse_chia_plots_create_command_line( + command_line=['python', 'chia', 'plots', 'create', *arguments], + ) + + assert parsed.error is not None diff --git a/src/plotman/chia.py b/src/plotman/chia.py new file mode 100644 index 00000000..a9e4f9ef --- /dev/null +++ b/src/plotman/chia.py @@ -0,0 +1,140 @@ +import functools + +import click +from pathlib import Path + + +class Commands: + def __init__(self): + self.by_version = {} + + def register(self, version): + if version in self.by_version: + raise Exception(f'Version already registered: {version!r}') + if not isinstance(version, tuple): + raise Exception(f'Version must be a tuple: {version!r}') + + return functools.partial(self._decorator, version=version) + + def _decorator(self, command, *, version): + self.by_version[version] = command + # self.by_version = dict(sorted(self.by_version.items())) + + def __getitem__(self, item): + return self.by_version[item] + + def latest_command(self): + return max(self.by_version.items())[1] + + +commands = Commands() + + +@commands.register(version=(1, 1, 2)) +@click.command() +# https://github.com/Chia-Network/chia-blockchain/blob/v1.1.2/LICENSE +# https://github.com/Chia-Network/chia-blockchain/blob/v1.1.2/chia/cmds/plots.py#L39-L83 +# start copied code +@click.option("-k", "--size", help="Plot size", type=int, default=32, show_default=True) +@click.option("--override-k", help="Force size smaller than 32", default=False, show_default=True, is_flag=True) +@click.option("-n", "--num", help="Number of plots or challenges", type=int, default=1, show_default=True) +@click.option("-b", "--buffer", help="Megabytes for sort/plot buffer", type=int, default=4608, show_default=True) +@click.option("-r", "--num_threads", help="Number of threads to use", type=int, default=2, show_default=True) +@click.option("-u", "--buckets", help="Number of buckets", type=int, default=128, show_default=True) +@click.option( + "-a", + "--alt_fingerprint", + type=int, + default=None, + help="Enter the alternative fingerprint of the key you want to use", +) +@click.option( + "-c", + "--pool_contract_address", + type=str, + default=None, + help="Address of where the pool reward will be sent to. Only used if alt_fingerprint and pool public key are None", +) +@click.option("-f", "--farmer_public_key", help="Hex farmer public key", type=str, default=None) +@click.option("-p", "--pool_public_key", help="Hex public key of pool", type=str, default=None) +@click.option( + "-t", + "--tmp_dir", + help="Temporary directory for plotting files", + type=click.Path(), + default=Path("."), + show_default=True, +) +@click.option("-2", "--tmp2_dir", help="Second temporary directory for plotting files", type=click.Path(), default=None) +@click.option( + "-d", + "--final_dir", + help="Final directory for plots (relative or absolute)", + type=click.Path(), + default=Path("."), + show_default=True, +) +@click.option("-i", "--plotid", help="PlotID in hex for reproducing plots (debugging only)", type=str, default=None) +@click.option("-m", "--memo", help="Memo in hex for reproducing plots (debugging only)", type=str, default=None) +@click.option("-e", "--nobitfield", help="Disable bitfield", default=False, is_flag=True) +@click.option( + "-x", "--exclude_final_dir", help="Skips adding [final dir] to harvester for farming", default=False, is_flag=True +) +# end copied code +def _cli(): + pass + + +@commands.register(version=(1, 1, 3)) +@click.command() +# https://github.com/Chia-Network/chia-blockchain/blob/v1.1.3/LICENSE +# https://github.com/Chia-Network/chia-blockchain/blob/v1.1.3/chia/cmds/plots.py#L39-L83 +# start copied code +@click.option("-k", "--size", help="Plot size", type=int, default=32, show_default=True) +@click.option("--override-k", help="Force size smaller than 32", default=False, show_default=True, is_flag=True) +@click.option("-n", "--num", help="Number of plots or challenges", type=int, default=1, show_default=True) +@click.option("-b", "--buffer", help="Megabytes for sort/plot buffer", type=int, default=4608, show_default=True) +@click.option("-r", "--num_threads", help="Number of threads to use", type=int, default=2, show_default=True) +@click.option("-u", "--buckets", help="Number of buckets", type=int, default=128, show_default=True) +@click.option( + "-a", + "--alt_fingerprint", + type=int, + default=None, + help="Enter the alternative fingerprint of the key you want to use", +) +@click.option( + "-c", + "--pool_contract_address", + type=str, + default=None, + help="Address of where the pool reward will be sent to. Only used if alt_fingerprint and pool public key are None", +) +@click.option("-f", "--farmer_public_key", help="Hex farmer public key", type=str, default=None) +@click.option("-p", "--pool_public_key", help="Hex public key of pool", type=str, default=None) +@click.option( + "-t", + "--tmp_dir", + help="Temporary directory for plotting files", + type=click.Path(), + default=Path("."), + show_default=True, +) +@click.option("-2", "--tmp2_dir", help="Second temporary directory for plotting files", type=click.Path(), default=None) +@click.option( + "-d", + "--final_dir", + help="Final directory for plots (relative or absolute)", + type=click.Path(), + default=Path("."), + show_default=True, +) +@click.option("-i", "--plotid", help="PlotID in hex for reproducing plots (debugging only)", type=str, default=None) +@click.option("-m", "--memo", help="Memo in hex for reproducing plots (debugging only)", type=str, default=None) +@click.option("-e", "--nobitfield", help="Disable bitfield", default=False, is_flag=True) +@click.option( + "-x", "--exclude_final_dir", help="Skips adding [final dir] to harvester for farming", default=False, is_flag=True +) +# end copied code +def _cli(): + pass diff --git a/src/plotman/job.py b/src/plotman/job.py index 97115be1..8150d400 100644 --- a/src/plotman/job.py +++ b/src/plotman/job.py @@ -11,9 +11,12 @@ from enum import Enum, auto from subprocess import call +import click import pendulum import psutil +from plotman import chia + def job_phases_for_tmpdir(d, all_jobs): '''Return phase 2-tuples for jobs running on tmpdir d''' @@ -33,45 +36,67 @@ def is_plotting_cmdline(cmdline): and 'create' == cmdline[2] ) -# This is a cmdline argument fix for https://github.com/ericaltendorf/plotman/issues/41 -def cmdline_argfix(cmdline): - known_keys = 'krbut2dnea' - for i in cmdline: - # If the argument starts with dash and a known key and is longer than 2, - # then an argument is passed with no space between its key and value. - # This is POSIX compliant but the arg parser was tripping over it. - # In these cases, splitting that item up in separate key and value - # elements results in a `cmdline` list that is correctly formatted. - if i[0]=='-' and i[1] in known_keys and len(i)>2: - yield i[0:2] # key - yield i[2:] # value - else: - yield i - def parse_chia_plot_time(s): # This will grow to try ISO8601 as well for when Chia logs that way return pendulum.from_format(s, 'ddd MMM DD HH:mm:ss YYYY', locale='en', tz=None) +def parse_chia_plots_create_command_line(command_line): + command_line = list(command_line) + # Parse command line args + if 'python' in command_line[0].lower(): + command_line = command_line[1:] + assert len(command_line) >= 3 + assert 'chia' in command_line[0] + assert 'plots' == command_line[1] + assert 'create' == command_line[2] + + all_command_arguments = command_line[3:] + + # nice idea, but this doesn't include -h + # help_option_names = command.get_help_option_names(ctx=context) + help_option_names = {'--help', '-h'} + + command_arguments = [ + argument + for argument in all_command_arguments + if argument not in help_option_names + ] + + # TODO: We could at some point do chia version detection and pick the + # associated command. For now we'll just use the latest one we have + # copied. + command = chia.commands.latest_command() + try: + context = command.make_context(info_name='', args=list(command_arguments)) + except click.ClickException as e: + error = e + params = {} + else: + error = None + params = context.params + + return ParsedChiaPlotsCreateCommand( + error=error, + help=len(all_command_arguments) > len(command_arguments), + parameters=params, + ) + +class ParsedChiaPlotsCreateCommand: + def __init__(self, error, help, parameters): + self.error = error + self.help = help + self.parameters = parameters + # TODO: be more principled and explicit about what we cache vs. what we look up # dynamically from the logfile class Job: 'Represents a plotter job' - # These are constants, not updated during a run. - k = 0 - r = 0 - u = 0 - b = 0 - n = 0 # probably not used - tmpdir = '' - tmp2dir = '' - dstdir = '' logfile = '' jobfile = '' job_id = 0 plot_id = '--------' proc = None # will get a psutil.Process - help = False # These are dynamic, cached, and need to be udpated periodically phase = (None, None) # Phase/subphase @@ -91,73 +116,85 @@ def get_running_jobs(logroot, cached_jobs=()): if proc.pid in cached_jobs_by_pid.keys(): jobs.append(cached_jobs_by_pid[proc.pid]) # Copy from cache else: - job = Job(proc, logroot) - if not job.help: + with proc.oneshot(): + parsed_command = parse_chia_plots_create_command_line( + command_line=proc.cmdline(), + ) + if parsed_command.error is not None: + continue + job = Job( + proc=proc, + parsed_command=parsed_command, + logroot=logroot, + ) + if job.help: + continue jobs.append(job) return jobs - def __init__(self, proc, logroot): + def __init__(self, proc, parsed_command, logroot): '''Initialize from an existing psutil.Process object. must know logroot in order to understand open files''' self.proc = proc - with self.proc.oneshot(): - # Parse command line args - args = self.proc.cmdline() - if 'python' in args[0].lower(): - args = args[1:] - assert len(args) > 4 - assert 'chia' in args[0] - assert 'plots' == args[1] - assert 'create' == args[2] - args_iter = iter(cmdline_argfix(args[3:])) - for arg in args_iter: - val = None if arg in {'-e', '--nobitfield', '-h', '--help', '--override-k'} else next(args_iter) - if arg in {'-k', '--size'}: - self.k = val - elif arg in {'-r', '--num_threads'}: - self.r = val - elif arg in {'-b', '--buffer'}: - self.b = val - elif arg in {'-u', '--buckets'}: - self.u = val - elif arg in {'-t', '--tmp_dir'}: - self.tmpdir = val - elif arg in {'-2', '--tmp2_dir'}: - self.tmp2dir = val - elif arg in {'-d', '--final_dir'}: - self.dstdir = val - elif arg in {'-n', '--num'}: - self.n = val - elif arg in {'-h', '--help'}: - self.help = True - elif arg in {'-e', '--nobitfield', '-f', '--farmer_public_key', '-p', '--pool_public_key', '-a', '--alt_fingerprint'}: - pass - # TODO: keep track of these - elif arg == '--override-k': - pass + self.help = parsed_command.help + self.args = parsed_command.parameters + + # an example as of 1.0.5 + # { + # 'size': 32, + # 'num_threads': 4, + # 'buckets': 128, + # 'buffer': 6000, + # 'tmp_dir': '/farm/yards/901', + # 'final_dir': '/farm/wagons/801', + # 'override_k': False, + # 'num': 1, + # 'alt_fingerprint': None, + # 'pool_contract_address': None, + # 'farmer_public_key': None, + # 'pool_public_key': None, + # 'tmp2_dir': None, + # 'plotid': None, + # 'memo': None, + # 'nobitfield': False, + # 'exclude_final_dir': False, + # } + + self.k = self.args['size'] + self.r = self.args['num_threads'] + self.u = self.args['buckets'] + self.b = self.args['buffer'] + self.n = self.args['num'] + self.tmpdir = self.args['tmp_dir'] + self.tmp2dir = self.args['tmp2_dir'] + self.dstdir = self.args['final_dir'] + + plot_cwd = self.proc.cwd() + self.tmpdir = os.path.join(plot_cwd, self.tmpdir) + if self.tmp2dir is not None: + self.tmp2dir = os.path.join(plot_cwd, self.tmp2dir) + self.dstdir = os.path.join(plot_cwd, self.dstdir) + + # Find logfile (whatever file is open under the log root). The + # file may be open more than once, e.g. for STDOUT and STDERR. + for f in self.proc.open_files(): + if logroot in f.path: + if self.logfile: + assert self.logfile == f.path else: - print('Warning: unrecognized args: %s %s' % (arg, val)) + self.logfile = f.path + break - # Find logfile (whatever file is open under the log root). The - # file may be open more than once, e.g. for STDOUT and STDERR. + if self.logfile: + # Initialize data that needs to be loaded from the logfile + self.init_from_logfile() + else: + print('Found plotting process PID {pid}, but could not find ' + 'logfile in its open files:'.format(pid = self.proc.pid)) for f in self.proc.open_files(): - if logroot in f.path: - if self.logfile: - assert self.logfile == f.path - else: - self.logfile = f.path - break - - if self.logfile: - # Initialize data that needs to be loaded from the logfile - self.init_from_logfile() - else: - print('Found plotting process PID {pid}, but could not find ' - 'logfile in its open files:'.format(pid = self.proc.pid)) - for f in self.proc.open_files(): - print(f.path) + print(f.path)