Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial support for Madmax plotter. #797

Merged
merged 21 commits into from
Jun 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
([#430](https://github.com/ericaltendorf/plotman/pull/430))
- `plotman logs` command to print and tail plot logs by their plot ID.
([#509](https://github.com/ericaltendorf/plotman/pull/509))
- Support the [madMAx plotter](https://github.com/madMAx43v3r/chia-plotter).
See the [configuration wiki page](https://github.com/ericaltendorf/plotman/wiki/Configuration#2-v05) for help setting it up.
([#797](https://github.com/ericaltendorf/plotman/pull/797))

## [0.4.1] - 2021-06-11
### Fixed
Expand Down
21 changes: 18 additions & 3 deletions src/plotman/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,18 @@ def analyze(logfilenames: typing.List[str], clipterminals: bool, bytmp: bool, by
else:
sl += '-bitfield'

# Phase timing. Sample log line:
# CHIA: Phase timing. Sample log line:
# Time for phase 1 = 22796.7 seconds. CPU (98%) Tue Sep 29 17:57:19 2020
for phase in ['1', '2', '3', '4']:
m = re.search(r'^Time for phase ' + phase + ' = (\d+.\d+) seconds..*', line)
if m:
phase_time[phase] = float(m.group(1))

# MADMAX: Phase timing. Sample log line: "Phase 2 took 2193.37 sec"
for phase in ['1', '2', '3', '4']:
m = re.search(r'^Phase ' + phase + ' took (\d+.\d+) sec.*', line)
if m:
phase_time[phase] = float(m.group(1))

# Uniform sort. Sample log line:
# Bucket 267 uniform sort. Ram: 0.920GiB, u_sort min: 0.688GiB, qs min: 0.172GiB.
Expand All @@ -82,7 +88,7 @@ def analyze(logfilenames: typing.List[str], clipterminals: bool, bytmp: bool, by
else:
print ('Warning: unrecognized sort ' + sorter)

# Job completion. Record total time in sliced data store.
# CHIA: Job completion. Record total time in sliced data store.
# Sample log line:
# Total time = 49487.1 seconds. CPU (97.26%) Wed Sep 30 01:22:10 2020
m = re.search(r'^Total time = (\d+.\d+) seconds.*', line)
Expand All @@ -94,13 +100,22 @@ def analyze(logfilenames: typing.List[str], clipterminals: bool, bytmp: bool, by
for phase in ['1', '2', '3', '4']:
data.setdefault(sl, {}).setdefault('phase ' + phase, []).append(phase_time[phase])
data.setdefault(sl, {}).setdefault('%usort', []).append(100 * n_uniform // n_sorts)

# MADMAX: Job completion. Record total time in sliced data store.
# Sample log line: "Total plot creation time was 2530.76 sec"
m = re.search(r'^Total plot creation time was (\d+.\d+) sec.*', line)
if m:
data.setdefault(sl, {}).setdefault('total time', []).append(float(m.group(1)))
for phase in ['1', '2', '3', '4']:
data.setdefault(sl, {}).setdefault('phase ' + phase, []).append(phase_time[phase])
data.setdefault(sl, {}).setdefault('%usort', []).append(0) # Not available for MADMAX

# Prepare report
tab = tt.Texttable()
all_measures = ['%usort', 'phase 1', 'phase 2', 'phase 3', 'phase 4', 'total time']
headings = ['Slice', 'n'] + all_measures
tab.header(headings)

for sl in data.keys():
row = [sl]

Expand Down
57 changes: 50 additions & 7 deletions src/plotman/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
# TODO: should be a desert.ib() but mypy doesn't understand it then, see below
import desert._make
import marshmallow
import marshmallow.fields
import marshmallow.validate
import pendulum
import yaml

Expand Down Expand Up @@ -67,6 +69,27 @@ def get_validated_configs(config_text: str, config_path: str, preset_target_defi
f"Config file at: '{config_path}' is malformed"
) from e

if loaded.plotting.type == "chia" and loaded.plotting.chia is None:
raise ConfigurationException(
"chia selected as plotter but plotting: chia: was not specified in the config",
)
elif loaded.plotting.type == "madmax":
if loaded.plotting.madmax is None:
raise ConfigurationException(
"madmax selected as plotter but plotting: madmax: was not specified in the config",
)

if loaded.plotting.farmer_pk is None:
raise ConfigurationException(
"madmax selected as plotter but no plotting: farmer_pk: was specified in the config",
)

if loaded.plotting.pool_pk is None:
raise ConfigurationException(
"madmax selected as plotter but no plotting: pool_pk: was specified in the config",
)


if loaded.archiving is not None:
preset_target_objects = yaml.safe_load(preset_target_definitions_text)
preset_target_schema = desert.schema(PresetTargetDefinitions)
Expand Down Expand Up @@ -273,17 +296,37 @@ class Scheduling:
tmpdir_stagger_phase_minor: int
tmpdir_stagger_phase_limit: int = 1 # If not explicit, "tmpdir_stagger_phase_limit" will default to 1

@attr.frozen
class ChiaPlotterOptions:
n_threads: int = 2
n_buckets: int = 128
k: Optional[int] = 32
e: Optional[bool] = False
job_buffer: Optional[int] = 3389
x: bool = False
pool_contract_address: Optional[str] = None

@attr.frozen
class MadmaxPlotterOptions:
n_threads: int = 4
n_buckets: int = 256

@attr.frozen
class Plotting:
k: int
e: bool
n_threads: int
n_buckets: int
job_buffer: int
farmer_pk: Optional[str] = None
pool_pk: Optional[str] = None
pool_contract_address: Optional[str] = None
x: bool = False
type: str = attr.ib(
default="chia",
metadata={
desert._make._DESERT_SENTINEL: {
'marshmallow_field': marshmallow.fields.String(
validate=marshmallow.validate.OneOf(choices=["chia", "madmax"]),
),
},
},
)
chia: Optional[ChiaPlotterOptions] = None
madmax: Optional[MadmaxPlotterOptions] = None

@attr.frozen
class UserInterface:
Expand Down
117 changes: 83 additions & 34 deletions src/plotman/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import pendulum
import psutil

from plotman import chia
from plotman import chia, madmax


def job_phases_for_tmpdir(d: str, all_jobs: typing.List["Job"]) -> typing.List["Phase"]:
Expand All @@ -30,14 +30,17 @@ def job_phases_for_dstdir(d: str, all_jobs: typing.List["Job"]) -> typing.List["
return sorted([j.progress() for j in all_jobs if j.dstdir == d])

def is_plotting_cmdline(cmdline: typing.List[str]) -> bool:
if cmdline and 'python' in cmdline[0].lower():
if cmdline and 'python' in cmdline[0].lower(): # Stock Chia plotter
cmdline = cmdline[1:]
return (
len(cmdline) >= 3
and 'chia' in cmdline[0]
and 'plots' == cmdline[1]
and 'create' == cmdline[2]
)
return (
len(cmdline) >= 3
and 'chia' in cmdline[0]
and 'plots' == cmdline[1]
and 'create' == cmdline[2]
)
elif cmdline and 'chia_plot' == cmdline[0].lower(): # Madmax plotter
return True
return False

def parse_chia_plot_time(s: str) -> pendulum.DateTime:
# This will grow to try ISO8601 as well for when Chia logs that way
Expand All @@ -50,14 +53,21 @@ def parse_chia_plots_create_command_line(
) -> "ParsedChiaPlotsCreateCommand":
command_line = list(command_line)
# Parse command line args
if 'python' in command_line[0].lower():
if 'python' in command_line[0].lower(): # Stock Chia plotter
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:]
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:]
# 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()
elif 'chia_plot' in command_line[0].lower(): # Madmax plotter
command_line = command_line[1:]
all_command_arguments = command_line[2:]
command = madmax._cli_c8121b9

# nice idea, but this doesn't include -h
# help_option_names = command.get_help_option_names(ctx=context)
Expand All @@ -69,10 +79,6 @@ def parse_chia_plots_create_command_line(
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:
Expand Down Expand Up @@ -146,6 +152,7 @@ class Job:
jobfile: str = ''
job_id: int = 0
plot_id: str = '--------'
plotter: str = ''
proc: psutil.Process
k: int
r: int
Expand Down Expand Up @@ -260,15 +267,24 @@ def __init__(
# 'nobitfield': False,
# 'exclude_final_dir': False,
# }

self.k = self.args['size'] # type: ignore[assignment]
self.r = self.args['num_threads'] # type: ignore[assignment]
self.u = self.args['buckets'] # type: ignore[assignment]
self.b = self.args['buffer'] # type: ignore[assignment]
self.n = self.args['num'] # type: ignore[assignment]
self.tmpdir = self.args['tmp_dir'] # type: ignore[assignment]
self.tmp2dir = self.args['tmp2_dir'] # type: ignore[assignment]
self.dstdir = self.args['final_dir'] # type: ignore[assignment]
if proc.name().startswith("chia_plot"): # MADMAX
self.k = 32
self.r = self.args['threads'] # type: ignore[assignment]
self.u = self.args['buckets'] # type: ignore[assignment]
self.b = 0
self.n = self.args['count'] # type: ignore[assignment]
self.tmpdir = self.args['tmpdir'] # type: ignore[assignment]
self.tmp2dir = self.args['tmpdir2'] # type: ignore[assignment]
self.dstdir = self.args['finaldir'] # type: ignore[assignment]
else: # CHIA
self.k = self.args['size'] # type: ignore[assignment]
self.r = self.args['num_threads'] # type: ignore[assignment]
self.u = self.args['buckets'] # type: ignore[assignment]
self.b = self.args['buffer'] # type: ignore[assignment]
self.n = self.args['num'] # type: ignore[assignment]
self.tmpdir = self.args['tmp_dir'] # type: ignore[assignment]
self.tmp2dir = self.args['tmp2_dir'] # type: ignore[assignment]
self.dstdir = self.args['final_dir'] # type: ignore[assignment]

plot_cwd: str = self.proc.cwd()
self.tmpdir = os.path.join(plot_cwd, self.tmpdir)
Expand Down Expand Up @@ -311,15 +327,24 @@ def init_from_logfile(self) -> None:
with contextlib.suppress(UnicodeDecodeError):
for line in f:
m = re.match('^ID: ([0-9a-f]*)', line)
if m:
if m: # CHIA
self.plot_id = m.group(1)
self.plotter = 'chia'
found_id = True
else:
m = re.match("^Plot Name: plot-k(\d+)-(\d+)-(\d+)-(\d+)-(\d+)-(\d+)-(\w+)$", line)
if m: # MADMAX
self.plot_id = m.group(7)
self.plotter = 'madmax'
found_id = True
m = re.match(r'^Starting phase 1/4:.*\.\.\. (.*)', line)
if m:
if m: # CHIA
# Mon Nov 2 08:39:53 2020
self.start_time = parse_chia_plot_time(m.group(1))
found_log = True
break # Stop reading lines in file
else: # MADMAX
self.start_time = pendulum.from_timestamp(os.path.getctime(self.logfile))

if found_id and found_log:
break # Stop trying
Expand Down Expand Up @@ -352,26 +377,50 @@ def set_phase_from_logfile(self) -> None:
with open(self.logfile, 'r') as f:
with contextlib.suppress(UnicodeDecodeError):
for line in f:
# "Starting phase 1/4: Forward Propagation into tmp files... Sat Oct 31 11:27:04 2020"
# CHIA: "Starting phase 1/4: Forward Propagation into tmp files... Sat Oct 31 11:27:04 2020"
m = re.match(r'^Starting phase (\d).*', line)
if m:
phase = int(m.group(1))
phase_subphases[phase] = 0

# MADMAX: "[P1]" or "[P2]" or "[P3]" or "[P4]"
m = re.match(r'^\[P(\d)\].*', line)
if m:
phase = int(m.group(1))
phase_subphases[phase] = 0

# Phase 1: "Computing table 2"
# CHIA: Phase 1: "Computing table 2"
m = re.match(r'^Computing table (\d).*', line)
if m:
phase_subphases[1] = max(phase_subphases[1], int(m.group(1)))

# MADMAX: Phase 1: "[P1] Table 2"
m = re.match(r'^\[P1\] Table (\d).*', line)
if m:
phase_subphases[1] = max(phase_subphases[1], int(m.group(1)))

# Phase 2: "Backpropagating on table 2"
# CHIA: Phase 2: "Backpropagating on table 2"
m = re.match(r'^Backpropagating on table (\d).*', line)
if m:
phase_subphases[2] = max(phase_subphases[2], 7 - int(m.group(1)))

# Phase 3: "Compressing tables 4 and 5"
# MADMAX: Phase 2: "[P2] Table 2"
m = re.match(r'^\[P2\] Table (\d).*', line)
if m:
phase_subphases[2] = max(phase_subphases[2], 7 - int(m.group(1)))

# CHIA: Phase 3: "Compressing tables 4 and 5"
m = re.match(r'^Compressing tables (\d) and (\d).*', line)
if m:
phase_subphases[3] = max(phase_subphases[3], int(m.group(1)))

# MADMAX: Phase 3: "[P3-1] Table 4"
m = re.match(r'^\[P3\-\d\] Table (\d).*', line)
if m:
if 3 in phase_subphases:
phase_subphases[3] = max(phase_subphases[3], int(m.group(1)))
else:
phase_subphases[3] = int(m.group(1))

# TODO also collect timing info:

Expand Down
Loading