diff --git a/scripts/wk/clone/ddrescue.py b/scripts/wk/clone/ddrescue.py index 482a94bc..2bc1ad93 100644 --- a/scripts/wk/clone/ddrescue.py +++ b/scripts/wk/clone/ddrescue.py @@ -119,6 +119,7 @@ class BlockPair(): self.map_data: dict[str, bool | int] = {} self.map_path: pathlib.Path = pathlib.Path() self.size: int = source_dev.size + self.stats = {} self.status: dict[str, float | int | str] = { 'read-skip': 'Pending', 'read-full': 'Pending', @@ -345,6 +346,7 @@ class State(): self.ost = osticket.osTicket() self.progress_out: pathlib.Path = self.log_dir.joinpath('progress.out') self.mode: str = '?' + self.notes = [] self.source: hw_disk.Disk | None = None self.working_dir: pathlib.Path | None = None self.ui: tui.TUI = tui.TUI('Source') @@ -584,6 +586,13 @@ class State(): def generate_report(self) -> list[str]: """Generate report of overall and per block_pair results, returns list.""" report = [] + stats_str = ( + '\tnon-trimmed: {non-trimmed}, ' + 'non-scraped: {non-scraped}, ' + 'bad-sectors: {bad-sector}, ' + 'slow reads: {slow reads}, ' + 'run time: {run time}' + ) # Header report.append(f'{self.mode.title()} Results:') @@ -604,8 +613,16 @@ class State(): report.append(f'Overall rescued: {percent}, error size: {error_size_str}') # Block-Pairs - if len(self.block_pairs) > 1: - report.append(' ') + if len(self.block_pairs) == 1: + stats = self.block_pairs[0].stats + if stats: + try: + report.append(stats_str.format(**stats)) + except KeyError: + # Ignore and omit stats + pass + else: + # Two or more block_pairs for pair in self.block_pairs: error_size = pair.get_error_size() error_size_str = std.bytes_to_string(error_size, decimals=2) @@ -614,11 +631,25 @@ class State(): pair_size = std.bytes_to_string(pair.size, decimals=2) percent = pair.get_percent_recovered() percent = format_status_string(percent, width=0) + report.append(' ') report.append( f'{pair.source.name} ({pair_size}) ' f'rescued: {percent}, ' f'error size: {error_size_str}' ) + stats = pair.stats + if not stats: + continue + try: + report.append(stats_str.format(**stats)) + except KeyError: + # Ignore and omit stats + pass + + # Notes + if self.notes: + report.append(' ') + report.extend(self.notes) # Done return report @@ -1654,6 +1685,24 @@ def get_etoc() -> str: return etoc +def get_stats() -> dict[str, Any]: + """Get stats from ddrescue output, returns dict.""" + output = tmux.capture_pane() + stats = {} + temp = [] + for line in output[output.find('ipos:'):].splitlines(): + temp.extend(line.split(',')) + for line in temp: + line = line.strip() + try: + key, value = line.split(':') + stats[key] = value.strip() + except ValueError: + # ignore + pass + return stats + + def finalize_recovery(state: State, dry_run: bool = True) -> None: """Show recovery finalization options.""" zero_fill_destination(state, dry_run=dry_run) @@ -2096,6 +2145,9 @@ def relocate_backup_gpt(state: State, dry_run: bool = True) -> None: if proc.returncode: cli.print_error('ERROR: Failed to relocate backup GPT.') LOG.error('sfdisk result: %s, %s', proc.stdout, proc.stderr) + state.notes.append('NOTE: Failed to relocated backup GPT') + else: + state.notes.append('NOTE: Relocated backup GPT to the end of the disk.') def run_ddrescue(state, block_pair, pass_name, settings, dry_run=True) -> None: @@ -2183,6 +2235,9 @@ def run_ddrescue(state, block_pair, pass_name, settings, dry_run=True) -> None: # Update SMART pane _update_smart_panes() + # Stats + block_pair.stats.update(get_stats()) + # Check destination warning_message = check_destination_health(state.destination) if warning_message: @@ -2430,9 +2485,13 @@ def zero_fill_destination(state: State, dry_run: bool = True) -> None: # Re-run ddrescue to zero-fill gaps proc = exe.run_program(cmd, check=False, pipe=False) + LOG.info('Zero-fill result: %s', proc) if proc.returncode: cli.print_error('ERROR: Failed to zero-fill: {block_pair.destination}') LOG.error('zero-fill error: %s, %s', proc.stdout, proc.stderr) + state.notes.append('NOTE: Failed to zero-fill destination') + else: + state.notes.append('NOTE: Zero-filled gaps and extra space on destination.') if __name__ == '__main__':