Repository: grayfallstown/Chia-Plot-Status
Branch: main
Commit: 7a513e54842c
Files: 86
Total size: 423.8 KB
Directory structure:
gitextract_7ffcgm84/
├── .github/
│ └── workflows/
│ └── dotnet.yml
├── .gitignore
├── ChiaPlotStatus.csproj
├── ChiaPlotStatus.sln
├── ChiaPlotStatusCli/
│ ├── CLI/
│ │ ├── CLI.cs
│ │ └── CliOptions.cs
│ └── ChiaPlotStatusCli.csproj
├── ChiaPlotStatusGUI/
│ ├── ChiaPlotStatusGUI.csproj
│ ├── ChiaPlotStatusGUI.csproj.user
│ ├── Directory.Build.props
│ ├── GUI/
│ │ ├── App.axaml
│ │ ├── App.axaml.cs
│ │ ├── Assets/
│ │ │ └── en.yaml
│ │ ├── Models/
│ │ │ ├── HighlightedText.cs
│ │ │ ├── PlotCounts.cs
│ │ │ └── Translation.cs
│ │ ├── Program.cs
│ │ ├── Utils/
│ │ │ └── Utils.cs
│ │ ├── ViewLocator.cs
│ │ ├── ViewModels/
│ │ │ ├── MainWindowViewModel.cs
│ │ │ └── ViewModelBase.cs
│ │ ├── Views/
│ │ │ ├── ChiaPlotterDialog.axaml
│ │ │ ├── ChiaPlotterDialog.axaml.cs
│ │ │ ├── DonationDialog.axaml
│ │ │ ├── DonationDialog.axaml.cs
│ │ │ ├── HarvestDialog.axaml
│ │ │ ├── HarvestDialog.axaml.cs
│ │ │ ├── MainWindow.axaml
│ │ │ ├── MainWindow.axaml.cs
│ │ │ ├── MarkOfDeathDialog.axaml
│ │ │ ├── MarkOfDeathDialog.axaml.cs
│ │ │ ├── NoteDialog.axaml
│ │ │ ├── NoteDialog.axaml.cs
│ │ │ ├── StatisticsDialog.axaml
│ │ │ ├── StatisticsDialog.axaml.cs
│ │ │ ├── UpdateDialog.axaml
│ │ │ └── UpdateDialog.axaml.cs
│ │ └── nuget.config
│ └── chia-plot-status.desktop
├── ChiaPlotStatusLib/
│ ├── ChiaPlotStatusLib.csproj
│ └── Logic/
│ ├── ChiaPlotStatus.cs
│ ├── Models/
│ │ ├── CPPlotLog.cs
│ │ ├── CPPlotLogReadable.cs
│ │ ├── Columns.cs
│ │ ├── Filter.cs
│ │ ├── Health.cs
│ │ ├── MarkOfDeath.cs
│ │ ├── Note.cs
│ │ ├── PlotLog.cs
│ │ ├── PlotLogReadable.cs
│ │ └── Settings.cs
│ ├── Parser/
│ │ ├── CPPlotLogFileParser.cs
│ │ ├── PlotLogFileParser.cs
│ │ ├── PlotParserCache.cs
│ │ └── TailLineEmitter.cs
│ ├── Statistics/
│ │ ├── CPPlottingStatistics.cs
│ │ ├── Harvest/
│ │ │ ├── Harvest.cs
│ │ │ ├── HarvestParser.cs
│ │ │ ├── HarvestSummary.cs
│ │ │ └── HarvestSummeryReadable.cs
│ │ ├── PlottingStatistics.cs
│ │ ├── PlottingStatisticsDay.cs
│ │ ├── PlottingStatisticsDayReadable.cs
│ │ ├── PlottingStatisticsFull.cs
│ │ ├── PlottingStatisticsFullReadable.cs
│ │ ├── PlottingStatisticsHolder.cs
│ │ ├── PlottingStatisticsID.cs
│ │ └── PlottingStatisticsIdRelevanceWeights.cs
│ └── Utils/
│ ├── Exporter.cs
│ ├── Formatter.cs
│ ├── SearchFilter.cs
│ └── Sorter.cs
├── Directory.Build.props
├── InstallerConfig.ifp
├── LICENSE
├── Logo/
│ └── Icon - Readme.txt
├── NuGet.Config
├── Properties/
│ ├── PublishProfiles/
│ │ ├── FolderProfile.pubxml
│ │ └── FolderProfile.pubxml.user
│ ├── Resources.Designer.cs
│ └── Resources.resx
├── README.md
├── grayfallstown.pfx
├── next-minor.ps1
├── next-patch.ps1
└── release.ps1
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/dotnet.yml
================================================
name: .NET
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
strategy:
matrix:
platform: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v2
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: 5.0.x
#- name: Show Working Directory # does not work on windows as we are not in a powershell
# run: ls -lah
- name: Restore dependencies
run: dotnet restore --configfile NuGet.Config ChiaPlotStatus.sln
- name: Build
run: dotnet build --no-restore --configuration .\ChiaPlotStatus.sln /p:Configuration=Release /p:Platform="Any CPU"
- name: Test
run: dotnet test --no-build --verbosity normal --configuration .\ChiaPlotStatus.sln /p:Configuration=Release /p:Platform="Any CPU"
#- name: tree
# run: tree
# - uses: actions/upload-artifact@v2
# with:
# name: binaries-${{ matrix.platform }}
# path: bin/Release/net5.0
================================================
FILE: .gitignore
================================================
.vs
ChiaPlotStatus.csproj.user
obj
ChiaPlotStatusLib/obj
ChiaPlotStatusLib/bin
ChiaPlotStatusLib/.vs
ChiaPlotStatusGUI/obj
ChiaPlotStatusGUI/bin
ChiaPlotStatusGUI/.vs
ChiaPlotStatusCli/obj
ChiaPlotStatusCli/bin
ChiaPlotStatusCli/.vs
release
Notes.txt
website
Packaging
Properties/PlotProgress.cs
================================================
FILE: ChiaPlotStatus.csproj
================================================
Library
net5.0
win7-x64;ubuntu.16.10-x64;linux-x64
enable
false
grayfallstown.pfx
false
false
grayfallstown
none
0.11.11
5
Component
True
True
Resources.resx
ResXFileCodeGenerator
Resources.Designer.cs
================================================
FILE: ChiaPlotStatus.sln
================================================
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31129.286
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChiaPlotStatusCli", "ChiaPlotStatusCli\ChiaPlotStatusCli.csproj", "{677D6D3F-3693-4EA7-9FE0-AE64167BE82E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChiaPlotStatusGUI", "ChiaPlotStatusGUI\ChiaPlotStatusGUI.csproj", "{38F6EA6C-7E72-439B-B548-AFB5591DCEE3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChiaPlotStatusLib", "ChiaPlotStatusLib\ChiaPlotStatusLib.csproj", "{1CD172CB-184B-4C41-BB7A-4F68B61E80DE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{677D6D3F-3693-4EA7-9FE0-AE64167BE82E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{677D6D3F-3693-4EA7-9FE0-AE64167BE82E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{677D6D3F-3693-4EA7-9FE0-AE64167BE82E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{677D6D3F-3693-4EA7-9FE0-AE64167BE82E}.Release|Any CPU.Build.0 = Release|Any CPU
{38F6EA6C-7E72-439B-B548-AFB5591DCEE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{38F6EA6C-7E72-439B-B548-AFB5591DCEE3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{38F6EA6C-7E72-439B-B548-AFB5591DCEE3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{38F6EA6C-7E72-439B-B548-AFB5591DCEE3}.Release|Any CPU.Build.0 = Release|Any CPU
{1CD172CB-184B-4C41-BB7A-4F68B61E80DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1CD172CB-184B-4C41-BB7A-4F68B61E80DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1CD172CB-184B-4C41-BB7A-4F68B61E80DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1CD172CB-184B-4C41-BB7A-4F68B61E80DE}.Release|Any CPU.Build.0 = Release|Any CPU
{91BD97DE-BA91-4BED-97C6-1AC8B306FFA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{91BD97DE-BA91-4BED-97C6-1AC8B306FFA1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{91BD97DE-BA91-4BED-97C6-1AC8B306FFA1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{91BD97DE-BA91-4BED-97C6-1AC8B306FFA1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {75F20C1E-B71F-4C68-9945-E3C53E1C74D1}
EndGlobalSection
EndGlobal
================================================
FILE: ChiaPlotStatusCli/CLI/CLI.cs
================================================
using ChiaPlotStatus.Logic.Models;
using ChiaPlotStatus.Logic.Utils;
using CommandLine;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ChiaPlotStatus.CLI
{
public class ChiaPlotStatusCLI
{
public static void Main(string[] args)
{
Parser parser = new((parserSettings) => {
parserSettings.CaseSensitive = false;
parserSettings.AutoVersion = false;
parserSettings.AutoHelp = true;
parserSettings.HelpWriter = Console.Out;
});
parser.ParseArguments(args)
.WithParsed(options =>
{
do
{
GenerateReport(options);
Console.Out.WriteLine("File '" + options.File + "' written");
if (options.KeepUpdating)
{
int seconds = 10;
Console.Out.WriteLine("Updating file in " + seconds + " seconds...");
Thread.Sleep(seconds * 1000);
}
}
while (options.KeepUpdating);
});
}
public static void GenerateReport(CliOptions options)
{
ChiaPlotStatus PlotManager = SetupChiaPlotStatus(options);
Filter filter = SetupFilter(options);
string sortProperty = options.SortProperty;
if (string.IsNullOrEmpty(sortProperty))
sortProperty = PlotManager.Settings.SortProperty;
Console.Out.WriteLine("Sorting by " + sortProperty);
List<(PlotLog, PlotLogReadable)> plotLogs = PlotManager.PollPlotLogs(options.SortProperty, options.SortAsc, options.Search, filter);
ExportToFile(options, plotLogs);
}
private static ChiaPlotStatus SetupChiaPlotStatus(CliOptions options)
{
var configFolder = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + Path.DirectorySeparatorChar;
Settings Settings = new Settings(configFolder + "ChiaPlotStatu.config.json");
Settings.Load();
ChiaPlotStatus PlotManager = new(Settings);
List folders = options.LogFolders.ToList();
if (folders.Count() == 0)
{
if (PlotManager.Settings.LogDirectories.Count == 0)
PlotManager.AddDefaultLogFolders();
}
else
{
// override LogFolders from settings file
PlotManager.Settings.LogDirectories.Clear();
foreach (var folder in folders)
PlotManager.AddLogFolder(folder);
}
return PlotManager;
}
private static Filter SetupFilter(CliOptions options)
{
Filter filter = new();
filter.HideHealthy = options.HideHealthy;
filter.HideFinished = options.HideFinished;
filter.HidePossiblyDead = options.HidePossiblyDead;
filter.HideConfirmedDead = options.HideConfirmedDead;
return filter;
}
private static void ExportToFile(CliOptions options, List<(PlotLog, PlotLogReadable)> plotLogs)
{
Exporter exporter = new Exporter(plotLogs);
try
{
switch (options.Format.ToLower())
{
case "json":
exporter.ToJson(options.File, options.Raw);
break;
case "yaml":
exporter.ToYaml(options.File, options.Raw);
break;
case "csv":
exporter.ToCsv(options.File, options.Raw);
break;
default:
throw new NotImplementedException("File format '" + options.Format + "' not supported");
}
}
catch (Exception e)
{
Console.Error.WriteLine("Could not write file '" + options.File + "': " + e.Message);
Console.Error.WriteLine(e.StackTrace);
System.Environment.Exit(1);
}
}
}
}
================================================
FILE: ChiaPlotStatusCli/CLI/CliOptions.cs
================================================
using CommandLine;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ChiaPlotStatus.CLI
{
public class CliOptions
{
[Option('o', "outfile", Required = true, HelpText = "The file to write to")]
public string File { get; set; }
[Option('f', "format", Required = true, HelpText = "The format to use while writing the file. Valid values are json, yaml and csv")]
public string Format { get; set; }
[Option('r', "raw", Required = false, HelpText = "Write raw, unformatted values instead of human readable ones.")]
public bool Raw { get; set; }
[Option('l', "log-folders", Required = false, Separator = ',', HelpText = "The folders where logs can be found, comma separated. Uses default folder when empty")]
public IEnumerable LogFolders { get; set; }
[Option('s', "sort-property", Required = false, HelpText = "The property you want the plotlogs sorted by")]
public string SortProperty { get; set; }
[Option('a', "sort-asc", Required = false, HelpText = "Sort ascending")]
public bool SortAsc { get; set; }
[Option("search", Required = false, HelpText = "Filter plotlogs by this search terms. You filter for your temp1 folder for example.")]
public string Search { get; set; }
[Option("hide-finished", Required = false, HelpText = "Hide finished plots")]
public bool HideFinished { get; set; }
[Option("hide-possibly-dead", Required = false, HelpText = "Hide possibly dead plots")]
public bool HidePossiblyDead { get; set; }
[Option("hide-confirmed-dead", Required = false, HelpText = "Hide confirmed dead plots")]
public bool HideConfirmedDead { get; set; }
[Option("hide-healthy", Required = false, HelpText = "Hide healthy plots")]
public bool HideHealthy { get; set; }
[Option("keep-updating", Required = false, HelpText = "Keep updating the file every 10 seconds")]
public bool KeepUpdating { get; set; }
}
}
================================================
FILE: ChiaPlotStatusCli/ChiaPlotStatusCli.csproj
================================================
Exe
net5.0
win7-x64;ubuntu.16.10-x64;linux-x64
ChiaPlotStatus.CLI.ChiaPlotStatusCLI
Icon - Color changed.ico
grayfallstown
grayfallstown
0.11.11
================================================
FILE: ChiaPlotStatusGUI/ChiaPlotStatusGUI.csproj
================================================
WinExe
net5.0
win7-x64;ubuntu.16.10-x64;linux-x64
enable
ChiaPlotStatus.Program
ChiaPlotStatus
ChiaPlotStatusGUI
grayfallstown
grayfallstown
Icon - Color changed.ico
0.11.11
/usr/share/applications/chia-plot-status.desktop
/usr/share/pixmaps/chia-plot-status.png
%(Filename)
%(Filename)
%(Filename)
HarvestDialog.axaml
NoteDialog.axaml
ChiaPlotterDialog.axaml
StatisticsDialog.axaml
%(Filename)
%(Filename)
================================================
FILE: ChiaPlotStatusGUI/ChiaPlotStatusGUI.csproj.user
================================================
================================================
FILE: ChiaPlotStatusGUI/Directory.Build.props
================================================
0.1.189-*
all
================================================
FILE: ChiaPlotStatusGUI/GUI/App.axaml
================================================
================================================
FILE: ChiaPlotStatusGUI/GUI/App.axaml.cs
================================================
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using ChiaPlotStatus.ViewModels;
using ChiaPlotStatus.Views;
namespace ChiaPlotStatus
{
public class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow
{
DataContext = new MainWindowViewModel(),
};
}
base.OnFrameworkInitializationCompleted();
}
}
}
================================================
FILE: ChiaPlotStatusGUI/GUI/Assets/en.yaml
================================================
Name: English
Fields:
Search: "Search"
Light: "Light"
Dark: "Dark"
LogFolder: "Log Folders"
RawExport: "Raw Export"
HideHealthy: "Hide healthy"
HideFinished: "Hide finished"
HidePossiblyDead: "Hide possibly Dead"
HideConfirmedDead: "Hide confirmed Dead"
Buttons:
Add: "Add"
Remove: "Remove"
Json: "Json"
Yaml: "Yaml"
CSV: "CSV"
MarkAsDead: "Mark as dead"
SelectAllConcerningCommand: "Select concerning"
SelectAllPossiblyDeadCommand: "Selected possibly dead"
MarkSelectionAsDead: "Mark selected logs as dead"
UnmarkAsDead: "Unmark as dead"
Abort: "Cancel"
Copy: "Copy"
Save: "Save"
Columns:
RuntimeMinutes: "Time Frame Minutes"
PoolPuzzleHash: "Pool Puzzle / Address"
Tmp1Drive: "Tmp1"
Tmp2Drive: "Tmp2"
DestDrive: "Destination"
Errors: "Errors"
PID: "PID"
Progress: "Progress"
TimeRemaining: "Remaining 🕐"
RunTimeSeconds: "Runtime 🕐"
ETA: "ETA"
CurrentTable: "Cur. Table"
CurrentBucket: "Cur. Bucket"
CurrentPhase: "Cur. Phase"
Phase1Cpu: "P1 CPU"
Phase2Cpu: "P2 CPU"
Phase3Cpu: "P3 CPU"
Phase4Cpu: "P4 CPU"
Phase1Seconds: "Phase 1 🕐"
Phase2Seconds: "Phase 2 🕐"
Phase3Seconds: "Phase 3 🕐"
Phase4Seconds: "Phase 4 🕐"
CopyTimeSeconds: "Copy 🕐"
TotalSeconds: "Total 🕐"
PlotSize: "K-Size"
Threads: "Threads"
Buffer: "Buffer"
Buckets: "Buckets"
StartDate: "Started"
FinishDate: "Finished"
PlotName: "Plot Name"
LogFolder: "Log Folder"
LogFile: "Log File"
ApproximateWorkingSpace: "Workingspace"
FinalFileSize: "Final Size"
Health: "Health"
LastLogLine: "Last Log Line"
PlaceInLogFile: "Nr"
Phase1AvgTimeNeed: "Phase 1 avg 🕐"
Phase2AvgTimeNeed: "Phase 2 avg 🕐"
Phase3AvgTimeNeed: "Phase 3 avg 🕐"
Phase4AvgTimeNeed: "Phase 4 avg 🕐"
CopyTimeAvgTimeNeed: "Copy avg 🕐"
TotalAvgTimeNeed: "Total avg 🕐"
Phase1Completed: "Sample Size"
Note: "Note/Tags"
TotalEligiblePlots: "Eligible Plots"
FoundProofs: "Found Proofs"
BestLookupTime: "Best Lookup Time"
WorstLookupTime: "Worst Lookup Time"
AvgLookupTime: "Avg Lookup Time"
FilterRatio: "Filter Ratio"
TotalPlots: "Total Plots"
ChallengesPerMinute: "Challenges per Minute"
AvgHeat: "Avg Heat"
MaxHeat: "Max Heat"
MinHeat: "Min Heat"
AvgEligiblePlots: "Avg eligible Plots"
Tooltips:
RuntimeMinutes: "This statistic uses the last n minutes from the log for its calculations"
PoolPuzzleHash: "The Pool Puzzle Hash if you plot with madmax and the pool address if you still use chiapos."
JsonExport: "Export shown data to Json. Check if you want plain unformatted numbers."
YamlExport: "Export shown data to Yaml. Check if you want plain unformatted numbers."
CsvExport: "Export shown data to Csv. Check if you want plain unformatted numbers."
RawExport: "Export shown data as plain unformatted numbers."
Tmp1Drive: null
Tmp2Drive: null
DestDrive: "Directory where the plot file was/will be written to."
Errors: >
Errors that occured in the plotting process while reading from or writing data to disk.
The chia plotter will retry in case of such an error after a short Seconds. One is not that bad.
If you use external harddrives or network attached Storage this usally means the connection
dropped. Reattach the device and chia will continue. If the error count keeps rising you need to troubleshoot.
PID: >
Process ID if the running plotter process. You can use this nr to find and kill the process if it misbehaves.
Progress: >
Shows the progression defined by the number of steps done vs the number of steps left to completion.
The number of steps tells you nothing about how long the process will take to completion.
TimeRemaining: >
An estimate on how long it will take for the plot to complete. It is based on the plots progress and all
the data extracted from your finished plots. It will try to use the data of those plots that match the
configuration of that plot most closely to make a better prediction if you mix hard disks, ssd, internal
and external drives and even network attached storage.
RunTimeSeconds: >
"How long the process is running already"
ETA: >
An estimate on when the plotting process completes formatted MM/dd HH:mm. It is based on the plots
progress and all the data extracted from your finished plots. It will try to use the data of those plots
that match the configuration of that plot most closely to make a better prediction if you mix hard disks,
ssd, internal and external drives and even network attached storage.
CurrentTable: >
The table number currently worked on. On phase 1 and 3 it will start with table 1 and work its way up to 7. In
Phase 2 it will start with table 7 and work its way back to table 1. An arrow shows in which way it currently
progresses.
CurrentBucket: >
The Chia plotter will work through each table in the phases 1 and 3 and will process each bucket in each table.
This shows which bucket in the current table is currently worked on.
CurrentPhase: >
Shows in which of the four phases the plotting process is. It does not go back to a previous phase.
Phase1Cpu: >
"CPU Usage during phase 1."
Phase2Cpu: >
"CPU Usage during phase 2."
Phase3Cpu: >
"CPU Usage during phase 3."
Phase4Cpu: >
"CPU Usage during phase 4."
Phase1Seconds: >
The time this plot process took to complete phase 1.
This is the only phase with multithreading enabled and the only one you can optimize with the threads option.
Phase2Seconds: >
The time this plot process took to complete phase 2
Phase3Seconds: >
The time this plot process took to complete phase 3
Phase4Seconds: >
The time this plot process took to complete phase 4
CopyTimeSeconds: >
Unofficial phase 5.
The time this plot process took to copy the final file to the destination.
It gets copied from temp folder 2 to destination unless temp folder 2 and the destination are identical.
If you are using a remote destination you can optimize this process by using a local ssd or even just a
hdd as destination and then moving the plot to its remote destination by other means.
This way your plotting process is completed before the plot slowly gets moved to its final destination
and the plotter can already begin the next plot.
Google "windows task scheduler" or "linux chron jobs" to see how to set up automatic jobs that move the
files for you.
TotalSeconds: >
The time this plot process took to fully complete
PlotSize: >
The k-size of the plot. Usally 32, which produces a 100GB plot file. Apparently there is very little
reason to use a larger k-size for now.
Threads: >
The number of threads used to complete phase 1. As of now the other phases are not multithreaded
and will only use one cpu core, tweaking this only affects phase 1
Buffer: >
This buffer size the plotter uses. It is used to sort the data within the buckets in memory.
It will resort to a different sort algorithm which is slower on most tables. Too small buffer
sizes will slow down the plotter, too large ones will bring no additional benefit, but lower
the number on plotting processes you can run in parallel with the ram installed in your
system.
Buckets: >
The number of parts a table will be split into. Each bucket is sorted individually. Each bucket
gets written in its own file.
StartDate: >
When the plotting process was started.
FinishDate: >
When the plotting process finished.
PlotName: >
Name of the final plot file.
LogFolder: >
The folder in which this log file in stored. If you have multiple log folder for multiple rigs,
this tells you which rig it is from.
LogFile: >
The name of the logfile.
ApproximateWorkingSpace: >
An estimate on how much temporary space was used during the creation of the plot.
FinalFileSize: >
The size of the finished plot file. Varies little over the same k-size.
Health: >
Read catefully! Don't be rushed into making a decision here!
This feature requires existing logs of finished plots from the same log directory
to run correctly or it uses default warning thresholds that are pretty much diced at the moment!
If your system does not update "last modfied at" on files (some deactivate it on SSDs) ignore
the warnings with time in it. Chia Plot Status doesn't know any better.
An Estimate on the processes health. In some cases it is not clear if the process is still running,
this can happen especially in phase 4.
"⚠ Temp Errors": Usually not so serious, but you need to take action.
It means the last line in the logfile is a read or write error (look at Errors column).
You might be out of disk space for temp1, temp2 or the destination, so free up some space.
Another reason could be your external hard disk / network attached storage is detached or the
connection is flaky.
The plotter retries every 5 minutes, so it continues once you fixed the error.
"⚠ Slow 20m/15m": Usually not serious, just have an eye on it.
It means last update to the logfile was 20 minutes ago, but Chia Plot Status expected a new log line
within 15 minutes.
Process is likely still running.
Maybe you gave your temp drives more IO than they can handle this time?
If you only have finished plots that ran on SSDs and this one is on a HDD now, it is expected to be
slower and throw that warning.
Chia Plot Status will learn that this drive is slower once this plot is done.
"⚠ Dead? 40m/10m": This is scary. It means last update to the logFile was way too long ago.
Process might be still running!
If your system is not really slow/overtaxed at the moment the process could be dead (check to be sure).
Chia Plot Status considers it possibly dead until it logs something again.
"✗ Dead (m)": You manually marked this process as dead.
"✗ Dead": Ouch. The chia plotter produced a fatal error. There will be something like "Caught plotting
error" in your logfile or it means the processes is not done and a new process appeared in the same logfile.
If your logging is not messed up and logging multiple processes in parallel to the same file this means
the process is dead.
LastLogLine: >
Last line in this plots log file
PlaceInLogFile: >
When using --num 20 to plot 20 plots in a row this shows you the current plot nr on the left and the
size of the queue on the right
Note: >
Here you can add notes and tags to group plot logs and compare performance.
TotalEligiblePlots: >
Plots that could have provided proofs for the challanges (for a reward).
1/512 chance per plot, per challenge.
Maybe they did actually contain a proof, maybe not.
Even if, this does not mean you earned Chia as the best proof provided by anyone gets rewarded, not everyone who delivers a proof.
AvgEligiblePlots: >
Average number of plots that can provide proofs for the challanges (for a reward) given in the timeframe from begin to end of your logfiles.
1/512 chance per plot, therefor should be somewhere around 0.001953125 times the nr of total plots.
If the number is higher, lucky you.
If it is a lot lower, well something is wrong unless you do not have many plots, like less than 50 or so or if you do not have a lot of data.
if you have just set the log level to INFO, this means practically nothing.
Wait a few hours to see any relevant value.
The less plots you have, the longer it takes for this value to become relevant.
Do you have a thousand plots? This value could become meaningfull after one to three hours.
Do you have less than 100 plots? Wait at least a day, maybe a week.
FoundProofs: >
Number of Proofs found.
A found proof does not necessarily mean you won chia, as the best submitted proof is picked.
BestLookupTime: >
Lookup time increases when a plot does actually contain proofs.
Best lookup time does therefor not tell you much unless it is high.
WorstLookupTime: >
Lookup time increases when a plot does actually contain proofs.
You have 30 seconds to submit a proof once a challenge is started.
Keep in mind that the proof needs to be transfered to a lot of nodes to be successfull.
Because of this the worst lookup time should not be larger that 5 seconds.
AvgLookupTime: >
Lookup time increases when a plot does actually contain proofs.
You have 30 seconds to submit a proof once a challenge is started.
Keep in mind that the proof needs to be transfered to a lot of nodes to be successfull.
Because of this the avg lookup time should not be larger that ~2 seconds.
FilterRatio: >
Number of elgible plots divided by the total nr of plots.
Shout be somewhere around 1/512 (0.001953125).
Higher is better, way lower means something is seriously wrong.
TotalPlots: >
Total nr of plots that harvester uses to harvest.
ChallengesPerMinute: >
Number of challenges this harvester handled per minute
AvgHeat: >
The nearer this gets to 5 (or even above 5) the worse your harvester works.
NAS does not work well with chia.
MaxHeat: >
The nearer this gets to 5 (or even above 5) the worse your harvester works, but Avg Heat is more important.
NAS does not work well with chia.
MinHeat: >
Should be as low as possible.
The nearer this gets to 5 (or even above 5) the worse your harvester works, but Avg and Max Heat are more important.
NAS does not work well with chia.
================================================
FILE: ChiaPlotStatusGUI/GUI/Models/HighlightedText.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ChiaPlotStatus.GUI.Models
{
public class HighlightedText
{
public string Text { get; set; }
public bool Level0 { get; set; }
public bool Level1 { get; set; }
public bool Level2 { get; set; }
public bool Level3 { get; set; }
public bool Level4 { get; set; }
public bool Level5 { get; set; }
public HighlightedText(string Text, int level)
{
this.Text = Text;
this.Level0 = level == 0;
this.Level1 = level == 1;
this.Level2 = level == 2;
this.Level3 = level == 3;
this.Level4 = level == 4;
this.Level5 = level == 5;
}
}
}
================================================
FILE: ChiaPlotStatusGUI/GUI/Models/PlotCounts.cs
================================================
using ChiaPlotStatus;
using ChiaPlotStatus.Logic.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ChiaPlotStatusGUI.GUI.Models
{
public class PlotCounts
{
public int PlotsInPhase1 { get; set; } = 0;
public int PlotsInPhase2 { get; set; } = 0;
public int PlotsInPhase3 { get; set; } = 0;
public int PlotsInPhase4 { get; set; } = 0;
public int PlotsInPhase5 { get; set; } = 0;
public int Finished { get; set; } = 0;
public int Running { get; set; } = 0;
public int Concerning { get; set; } = 0;
public int Failed { get; set; } = 0;
public PlotCounts() { }
public PlotCounts(List<(PlotLog, PlotLogReadable)> plotLogs)
{
foreach (var tuple in plotLogs)
{
var plotLog = tuple.Item1;
var done = tuple.Item1.CurrentPhase == 6;
switch (plotLog.Health)
{
case Healthy:
if (done)
Finished++;
else
handlePhase(plotLog);
break;
case TempError:
case Concerning c:
Concerning++;
handlePhase(plotLog);
break;
case PossiblyDead p:
Concerning++;
Failed++;
handlePhase(plotLog);
break;
case ConfirmedDead c:
Failed++;
break;
}
}
}
private void handlePhase(PlotLog plotLog)
{
Running++;
switch (plotLog.CurrentPhase)
{
case 1:
PlotsInPhase1++;
break;
case 2:
PlotsInPhase2++;
break;
case 3:
PlotsInPhase3++;
break;
case 4:
PlotsInPhase4++;
break;
case 5:
PlotsInPhase5++;
break;
}
}
}
}
================================================
FILE: ChiaPlotStatusGUI/GUI/Models/Translation.cs
================================================
using Avalonia;
using Avalonia.Platform;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using YamlDotNet.Serialization;
namespace ChiaPlotStatus.GUI.Models
{
public static class Translation
{
public static Dictionary LoadLanguages()
{
var deserializer = new DeserializerBuilder().Build();
Dictionary langs = new();
var assets = AvaloniaLocator.Current.GetService();
foreach (var lang2 in new string[] { "en" })
{
System.IO.Stream filestream = assets.Open(new Uri("avares://ChiaPlotStatus/GUI/Assets/" + lang2 + ".yaml"));
StreamReader reader = new StreamReader(filestream);
string yaml = reader.ReadToEnd();
var lang = deserializer.Deserialize(yaml);
langs.Add(lang.Name, lang);
}
return langs;
}
}
public class Language
{
public string Name { get; set; }
public Tooltips Tooltips { get; set; }
public Columns Columns { get; set; }
public Fields Fields { get; set; }
public Buttons Buttons { get; set; }
}
public class Fields
{
public string Search { get; set; } = "";
public string Light { get; set; } = "";
public string Dark { get; set; } = "";
public string LogFolder { get; set; } = "";
public string RawExport { get; set; } = "";
public string HideHealthy { get; set; } = "";
public string HideFinished { get; set; } = "";
public string HidePossiblyDead { get; set; } = "";
public string HideConfirmedDead { get; set; } = "";
}
public class Buttons
{
public string Add { get; set; } = "";
public string Remove { get; set; } = "";
public string Json { get; set; } = "";
public string Yaml { get; set; } = "";
public string CSV { get; set; } = "";
public string MarkAsDead { get; set; } = "";
public string SelectAllConcerningCommand { get; set; } = "";
public string SelectAllPossiblyDeadCommand { get; set; } = "";
public string MarkSelectionAsDead { get; set; } = "";
public string UnmarkAsDead { get; set; } = "";
public string Abort { get; set; } = "";
public string Copy { get; set; } = "";
public string Save { get; set; } = "";
}
public class Tooltips
{
public string JsonExport { get; set; } = "";
public string YamlExport { get; set; } = "";
public string CsvExport { get; set; } = "";
public string RawExport { get; set; } = "";
public string Tmp1Drive { get; set; } = "";
public string Tmp2Drive { get; set; } = "";
public string DestDrive { get; set; } = "";
public string Errors { get; set; } = "";
public string PID { get; set; } = "";
public string Progress { get; set; } = "";
public string TimeRemaining { get; set; } = "";
public string RunTimeSeconds { get; set; } = "";
public string ETA { get; set; } = "";
public string CurrentTable { get; set; } = "";
public string CurrentBucket { get; set; } = "";
public string CurrentPhase { get; set; } = "";
public string Phase1Cpu { get; set; } = "";
public string Phase2Cpu { get; set; } = "";
public string Phase3Cpu { get; set; } = "";
public string Phase4Cpu { get; set; } = "";
public string Phase1Seconds { get; set; } = "";
public string Phase2Seconds { get; set; } = "";
public string Phase3Seconds { get; set; } = "";
public string Phase4Seconds { get; set; } = "";
public string CopyTimeSeconds { get; set; } = "";
public string TotalSeconds { get; set; } = "";
public string PlotSize { get; set; } = "";
public string Threads { get; set; } = "";
public string Buffer { get; set; } = "";
public string Buckets { get; set; } = "";
public string StartDate { get; set; } = "";
public string FinishDate { get; set; } = "";
public string PlotName { get; set; } = "";
public string LogFolder { get; set; } = "";
public string LogFile { get; set; } = "";
public string ApproximateWorkingSpace { get; set; } = "";
public string FinalFileSize { get; set; } = "";
public string Health { get; set; } = "";
public string LastLogLine { get; set; } = "";
public string PlaceInLogFile { get; set; } = "";
public string Note { get; set; } = "";
public string TotalEligiblePlots { get; set; } = "";
public string AvgEligiblePlots { get; set; } = "";
public string FoundProofs { get; set; } = "";
public string BestLookupTime { get; set; } = "";
public string WorstLookupTime { get; set; } = "";
public string AvgLookupTime { get; set; } = "";
public string FilterRatio { get; set; } = "";
public string TotalPlots { get; set; } = "";
public string ChallengesPerMinute { get; set; } = "";
public string AvgHeat { get; set; } = "";
public string MaxHeat { get; set; } = "";
public string MinHeat { get; set; } = "";
public string PoolPuzzleHash { get; set; } = "";
public string RuntimeMinutes { get; set; } = "";
}
public class Columns
{
public string Tmp1Drive { get; set; } = "";
public string Tmp2Drive { get; set; } = "";
public string DestDrive { get; set; } = "";
public string Errors { get; set; } = "";
public string PID { get; set; } = "";
public string Progress { get; set; } = "";
public string TimeRemaining { get; set; } = "";
public string RunTimeSeconds { get; set; } = "";
public string ETA { get; set; } = "";
public string CurrentTable { get; set; } = "";
public string CurrentBucket { get; set; } = "";
public string CurrentPhase { get; set; } = "";
public string Phase1Cpu { get; set; } = "";
public string Phase2Cpu { get; set; } = "";
public string Phase3Cpu { get; set; } = "";
public string Phase4Cpu { get; set; } = "";
public string Phase1Seconds { get; set; } = "";
public string Phase2Seconds { get; set; } = "";
public string Phase3Seconds { get; set; } = "";
public string Phase4Seconds { get; set; } = "";
public string CopyTimeSeconds { get; set; } = "";
public string TotalSeconds { get; set; } = "";
public string PlotSize { get; set; } = "";
public string Threads { get; set; } = "";
public string Buffer { get; set; } = "";
public string Buckets { get; set; } = "";
public string StartDate { get; set; } = "";
public string FinishDate { get; set; } = "";
public string PlotName { get; set; } = "";
public string LogFolder { get; set; } = "";
public string LogFile { get; set; } = "";
public string ApproximateWorkingSpace { get; set; } = "";
public string FinalFileSize { get; set; } = "";
public string Health { get; set; } = "";
public string LastLogLine { get; set; } = "";
public string PlaceInLogFile { get; set; } = "";
public string Phase1AvgTimeNeed { get; set; } = "";
public string Phase2AvgTimeNeed { get; set; } = "";
public string Phase3AvgTimeNeed { get; set; } = "";
public string Phase4AvgTimeNeed { get; set; } = "";
public string CopyTimeAvgTimeNeed { get; set; } = "";
public string TotalAvgTimeNeed { get; set; } = "";
public string Phase1Completed { get; set; } = "";
public string Note { get; set; } = "";
public string TotalEligiblePlots { get; set; } = "";
public string AvgEligiblePlots { get; set; } = "";
public string FoundProofs { get; set; } = "";
public string BestLookupTime { get; set; } = "";
public string WorstLookupTime { get; set; } = "";
public string AvgLookupTime { get; set; } = "";
public string FilterRatio { get; set; } = "";
public string TotalPlots { get; set; } = "";
public string ChallengesPerMinute { get; set; } = "";
public string AvgHeat { get; set; } = "";
public string MaxHeat { get; set; } = "";
public string MinHeat { get; set; } = "";
public string PoolPuzzleHash { get; set; } = "";
public string RuntimeMinutes { get; set; } = "";
}
}
================================================
FILE: ChiaPlotStatusGUI/GUI/Program.cs
================================================
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.ReactiveUI;
using System;
namespace ChiaPlotStatus
{
class Program
{
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
public static void Main(string[] args) => BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure()
.UsePlatformDetect()
.LogToTrace()
.UseReactiveUI();
}
}
================================================
FILE: ChiaPlotStatusGUI/GUI/Utils/Utils.cs
================================================
using Avalonia.Controls;
using Avalonia.Markup.Xaml.Styling;
using ChiaPlotStatus.Views;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace ChiaPlotStatusGUI.GUI.Utils
{
public class Utils
{
/**
* Open URL in default browser
*/
public static void OpenUrl(string url)
{
try
{
Process.Start(url);
}
catch
{
// workaround because of this: https://github.com/dotnet/corefx/issues/10361
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
url = url.Replace("&", "^&");
Process.Start(new ProcessStartInfo("cmd", $"/c start {url}") { CreateNoWindow = true });
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
Process.Start("xdg-open", url);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
Process.Start("open", url);
}
else
{
throw;
}
}
}
public static void OpenLogFile(string url)
{
try
{
Process.Start(url);
}
catch
{
// hack because of this: https://github.com/dotnet/corefx/issues/10361
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
url = url.Replace("&", "^&");
// force notepad or it will not open log files of running plots
Process.Start(new ProcessStartInfo("cmd", $"/c start notepad {url}") { CreateNoWindow = true });
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
Process.Start("xdg-open", url);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
Process.Start("open", url);
}
else
{
throw;
}
}
}
public static void SetTheme(Window window, string theme)
{
var light = new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
Source = new Uri("resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default")
};
var dark = new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
Source = new Uri("resm:Avalonia.Themes.Default.Accents.BaseDark.xaml?assembly=Avalonia.Themes.Default")
};
switch (theme)
{
case "Dark":
window.Styles[0] = dark;
break;
default:
window.Styles[0] = light;
break;
}
}
}
}
================================================
FILE: ChiaPlotStatusGUI/GUI/ViewLocator.cs
================================================
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using ChiaPlotStatus.ViewModels;
using System;
namespace ChiaPlotStatus
{
public class ViewLocator : IDataTemplate
{
public bool SupportsRecycling => false;
public IControl Build(object data)
{
var name = data.GetType().FullName!.Replace("ViewModel", "View");
var type = Type.GetType(name);
if (type != null)
{
return (Control)Activator.CreateInstance(type)!;
}
else
{
return new TextBlock { Text = "Not Found: " + name };
}
}
public bool Match(object data)
{
return data is ViewModelBase;
}
}
}
================================================
FILE: ChiaPlotStatusGUI/GUI/ViewModels/MainWindowViewModel.cs
================================================
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml.Styling;
using Avalonia.Threading;
using ChiaPlotStatus;
using ChiaPlotStatus.Logic.Utils;
using ChiaPlotStatus.GUI.Models;
using ChiaPlotStatus.Views;
using ReactiveUI;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Reactive;
using System.Text;
using System.Threading.Tasks;
using ChiaPlotStatus.Logic.Models;
using Avalonia;
using System.Runtime.InteropServices;
using ChiaPlotStatusGUI.GUI.Models;
using ChiaPlotStatusGUI.GUI.Utils;
using System.ComponentModel;
namespace ChiaPlotStatus.ViewModels
{
public class MainWindowViewModel : ViewModelBase
{
public ChiaPlotStatus PlotManager { get; internal set; }
public ObservableCollection PlotLogs { get; set; } = new();
public List<(PlotLog, PlotLogReadable)> PlotLogTuples { get; set; } = new();
public PlotCounts PlotCounts { get; set; }
public string? Search { get; set; } = null;
public ObservableCollection SortProperties = new();
public Language Language { get; set; }
public Dictionary Languages { get; set; }
public ReactiveCommand ExportJsonCommand { get; set; }
public bool RawExport { get; set; } = false;
public ReactiveCommand ExportYamlCommand { get; set; }
public ReactiveCommand ExportCsvCommand { get; set; }
public ReactiveCommand AddFolderCommand { get; set; }
public ReactiveCommand RemoveFolderCommand { get; set; }
public ReactiveCommand IncreaseFontSizeCommand { get; set; }
public ReactiveCommand DecreaseFontSizeCommand { get; set; }
public ReactiveCommand MarkAsDeadCommand { get; set; }
public ReactiveCommand NoteCommand { get; set; }
public ReactiveCommand MarkSelectionAsDeadCommand { get; set; }
public ReactiveCommand SelectAllPossiblyDeadCommand { get; set; }
public ReactiveCommand SelectAllConcerningCommand { get; set; }
public DispatcherTimer RefreshTimer { get; set; }
public MainWindowViewModel()
{
if (MainWindow.Instance.Find("RefreshPauseButton") == null)
{
// TODO: rewrite in proper MVVM or similar pattern to get rid of this
return;
}
// cleanup installation files after update
UpdateDialog.DeleteUpdateTempDirectory();
foreach (var property in typeof(PlotLogReadable).GetProperties())
SortProperties.Add(property.Name);
Languages = Translation.LoadLanguages();
Language = Languages["English"];
InitializeChiaPlotStatus();
InitializeButtons();
InitializeSearchBox();
KeepGridScrollbarOnScreen();
InitializeThemeSwitcher();
InitializeRefreshInterval();
InitializeRefreshPauseButton();
InitializeSelection();
InitializeFilterUpdates();
SortColumns();
HandleColumnWidths();
RegisterOnCloseHandler();
}
private void HandleFinishDateVisibility()
{
var logDataGrid = MainWindow.Instance.Find("LogDataGrid");
foreach (var col in logDataGrid.Columns)
{
if (col.SortMemberPath == "FinishDate") {
col.IsVisible = !PlotManager.Settings.Filter.HideFinished;
break;
}
}
}
private void SortColumns()
{
var logDataGrid = MainWindow.Instance.Find("LogDataGrid");
List columns = new();
foreach (var col in logDataGrid.Columns)
columns.Add(col);
logDataGrid.Columns.Clear();
columns.Sort((a, b) =>
{
return PlotManager.Settings.Columns.IndexOf(a.SortMemberPath).CompareTo(
PlotManager.Settings.Columns.IndexOf(b.SortMemberPath));
});
for (int i = 0; i < columns.Count; i++)
{
columns[i].DisplayIndex = i;
logDataGrid.Columns.Add(columns[i]);
}
}
private void HandleColumnWidths()
{
var logDataGrid = MainWindow.Instance.Find("LogDataGrid");
foreach (var column in logDataGrid.Columns)
{
string name = column.SortMemberPath;
if (this.PlotManager.Settings.Columns.Widths.ContainsKey(name))
{
column.Width = new DataGridLength(this.PlotManager.Settings.Columns.Widths[name]);
//column.Width = new DataGridLength(3333);
}
/*
column.WhenAnyValue(x => x.ActualWidth).Subscribe(x => {
this.PlotManager.Settings.Columns.Widths.Add(name, x);
this.PlotManager.Settings.Persist();
});
*/
}
MainWindow.Instance.Closing += (sender, e) =>
{
foreach (var column in logDataGrid.Columns)
{
string name = column.SortMemberPath;
this.PlotManager.Settings.Columns.Widths[name] = (int) column.ActualWidth;
}
this.PlotManager.Settings.Persist();
};
}
private void InitializeRefreshPauseButton()
{
var button = MainWindow.Instance.Find("RefreshPauseButton");
if (button != null)
{
button.Click += (sender, e) => SetPauseRefreshState(null);
}
}
private void SetPauseRefreshState(bool? pause)
{
if (pause == null)
{
pause = RefreshTimer.IsEnabled;
}
var button = MainWindow.Instance.Find("RefreshPauseButton");
if (pause == true)
{
Debug.WriteLine("pausing refresh");
button.Content = "Refresh ■";
RefreshTimer.Stop();
} else {
Debug.WriteLine("continuing refresh");
button.Content = "Refresh ▶";
RefreshTimer.Start();
LoadPlotLogs();
}
}
private void InitializeSelection()
{
MainWindow.Instance.SelectionChangedAction = () => SetPauseRefreshState(true);
this.SelectAllPossiblyDeadCommand = ReactiveCommand.Create(() => Select(true, false));
this.SelectAllConcerningCommand = ReactiveCommand.Create(() => Select(false, true));
void Select(bool possiblyDead, bool concerning)
{
foreach (var plotLogReadable in PlotLogs)
{
plotLogReadable.IsSelected = false;
var plotLog = getPlotLogByReadable(plotLogReadable);
switch (plotLog.Health)
{
case PossiblyDead pd:
if (possiblyDead)
plotLogReadable.IsSelected = true;
break;
case Concerning c:
if (concerning)
plotLogReadable.IsSelected = true;
break;
default:
break;
}
}
// work around avalonia datagrid not updating the checkbox even
// with TwoWay Binding as I refuse to re-implement plotLog and
// plotLogReadable as ReactiveObjects and copy the data from
// plotLog/plotLogReadable to its reactive counterparts (thats
// not dry!) or even worse, make plotLog itself reactive and
// thereby adding gui code and dependencies to the non-gui
// library and cli
ObservableCollection plotLogsTemp = new();
foreach (var plotLogReadable in PlotLogs)
plotLogsTemp.Add(plotLogReadable);
PlotLogs = plotLogsTemp;
this.RaisePropertyChanged("PlotLogs");
MainWindow.Instance.SelectionChangedAction();
}
this.MarkSelectionAsDeadCommand = ReactiveCommand.Create(() =>
{
foreach (var plotLog in PlotLogs)
{
if (plotLog.IsSelected)
{
var mark = new MarkOfDeath(plotLog);
if (!PlotManager.Settings.MarksOfDeath.Contains(mark))
PlotManager.Settings.MarksOfDeath.Add(mark);
}
}
PlotManager.Settings.Persist();
SetPauseRefreshState(false);
});
}
private PlotLog getPlotLogByReadable(PlotLogReadable plotLogReadable)
{
foreach (var tuple in PlotLogTuples)
if (tuple.Item2 == plotLogReadable)
return tuple.Item1;
throw new Exception("getPlotLogByReadable did not find the match");
}
private void InitializeThemeSwitcher()
{
MainWindow.Instance.ThemeSwitchWorkaround = (theme) =>
{
PlotManager.Settings.Theme = theme;
PlotManager.Settings.Persist();
Utils.SetTheme(MainWindow.Instance, theme);
switch (theme)
{
case "Dark":
MainWindow.Instance.Find("Themes").SelectedIndex = 1;
MainWindow.Instance.Find("SearchBox").Foreground = Avalonia.Media.Brushes.LightGray;
MainWindow.Instance.Find("Themes").Foreground = Avalonia.Media.Brushes.LightGray;
break;
default:
MainWindow.Instance.Find("Themes").SelectedIndex = 0;
MainWindow.Instance.Find("SearchBox").Foreground = Avalonia.Media.Brushes.Black;
MainWindow.Instance.Find("Themes").Foreground = Avalonia.Media.Brushes.Black;
break;
}
return true;
};
Dispatcher.UIThread.InvokeAsync(() =>
{
MainWindow.Instance.ThemeSwitchWorkaround(PlotManager.Settings.Theme);
});
}
private void KeepGridScrollbarOnScreen()
{
MainWindow.Instance.WhenAnyValue(x => x.Height)
.Subscribe(x =>
{
var dataGrid = MainWindow.Instance.Find("LogDataGrid");
if (dataGrid != null)
dataGrid.Height = x - 150;
});
}
public void InitializeChiaPlotStatus()
{
var folder = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + Path.DirectorySeparatorChar;
Settings Settings = new Settings(folder + "ChiaPlotStatu.config.json");
Settings.Load();
PlotManager = new(Settings);
if (PlotManager.Settings.LogDirectories.Count == 0)
PlotManager.AddDefaultLogFolders();
LoadPlotLogs();
}
public void LoadPlotLogs()
{
MainWindow.Instance.Find("LogDataGrid").BeginBatchUpdate();
PlotLogs.Clear();
PlotLogTuples = new();
foreach (var plotLog in PlotManager.PollPlotLogs(PlotManager.Settings.SortProperty, (bool)PlotManager.Settings.SortAsc, Search, PlotManager.Settings.Filter))
{
PlotLogs.Add(plotLog.Item2);
PlotLogTuples.Add(plotLog);
}
PlotCounts = new(PlotLogTuples);
HandleFinishDateVisibility();
MainWindow.Instance.Find("LogDataGrid").EndBatchUpdate();
this.RaisePropertyChanged("PlotCounts");
}
public void InitializeButtons()
{
AddFolderCommand = ReactiveCommand.Create(AddFolder);
RemoveFolderCommand = ReactiveCommand.Create(RemoveFolder);
MainWindow.Instance.BtnClickWorkaround = (folder) =>
{
RemoveFolder(folder);
return true;
};
MainWindow.Instance.SortChangeWorkaround = (headerText) =>
{
var oldSortProperty = PlotManager.Settings.SortProperty;
foreach (var property in typeof(Columns).GetProperties())
{
string translation = (string)property.GetValue(Language.Columns);
if (string.Equals(headerText, translation))
{
PlotManager.Settings.SortProperty = property.Name;
break;
}
}
if (string.Equals(PlotManager.Settings.SortProperty, oldSortProperty))
PlotManager.Settings.SortAsc = !PlotManager.Settings.SortAsc;
// Debug.WriteLine("SearchProperty: " + SortProperty + ", ASC: " + SortAsc);
LoadPlotLogs();
PlotManager.Settings.Persist();
return true;
};
IncreaseFontSizeCommand = ReactiveCommand.Create(() =>
{
this.PlotManager.Settings.FontSize += 0.3;
this.PlotManager.Settings.Persist();
});
DecreaseFontSizeCommand = ReactiveCommand.Create(() =>
{
this.PlotManager.Settings.FontSize -= 0.3;
this.PlotManager.Settings.Persist();
});
ExportJsonCommand = ReactiveCommand.Create(() =>
{
Exporter exporter = new Exporter(PlotLogTuples);
SaveFileDialog picker = new SaveFileDialog();
picker.Title = "Save as Json";
if (RawExport)
picker.InitialFileName = "ChiaPlotStatus-raw.json";
else
picker.InitialFileName = "ChiaPlotStatus.json";
picker.Filters = new List
{
new FileDialogFilter
{
Name = "Json (.json)", Extensions = new List {"json"}
},
new FileDialogFilter
{
Name = "All files",
Extensions = new List {"*"}
}
};
Task.Run(async () =>
{
var result = await picker.ShowAsync(MainWindow.Instance);
if (result != null)
exporter.ToJson(result, RawExport);
});
});
ExportYamlCommand = ReactiveCommand.Create(() =>
{
Exporter exporter = new Exporter(PlotLogTuples);
SaveFileDialog picker = new SaveFileDialog();
picker.Title = "Save as Yaml";
if (RawExport)
picker.InitialFileName = "ChiaPlotStatus-raw.yaml";
else
picker.InitialFileName = "ChiaPlotStatus.yaml";
picker.Filters = new List
{
new FileDialogFilter
{
Name = "Yaml (.yaml)", Extensions = new List {"yaml"}
},
new FileDialogFilter
{
Name = "All files",
Extensions = new List {"*"}
}
};
Task.Run(async () =>
{
var result = await picker.ShowAsync(MainWindow.Instance);
if (result != null)
exporter.ToYaml(result, RawExport);
});
});
ExportCsvCommand = ReactiveCommand.Create(() =>
{
Exporter exporter = new Exporter(PlotLogTuples);
SaveFileDialog picker = new SaveFileDialog();
picker.Title = "Save as Csv";
if (RawExport)
picker.InitialFileName = "ChiaPlotStatus-raw.csv";
else
picker.InitialFileName = "ChiaPlotStatus.csv";
picker.Filters = new List
{
new FileDialogFilter
{
Name = "Yaml (.csv)", Extensions = new List {"csv"}
},
new FileDialogFilter
{
Name = "All files",
Extensions = new List {"*"}
}
};
Task.Run(async () =>
{
var result = await picker.ShowAsync(MainWindow.Instance);
if (result != null)
exporter.ToCsv(result, RawExport);
});
});
MarkAsDeadCommand = ReactiveCommand.Create((plotLogReadable) =>
{
var dialog = new MarkOfDeathDialog(plotLogReadable, this.Language, this.PlotManager.Settings, this.PlotManager.Settings.Theme, LoadPlotLogs);
dialog.Show();
});
NoteCommand = ReactiveCommand.Create((plotLogReadable) =>
{
var dialog = new NoteDialog(plotLogReadable, this.Language, this.PlotManager.Settings, this.PlotManager.Settings.Theme, LoadPlotLogs);
dialog.Show();
});
var updateButton = MainWindow.Instance.Find("UpdateButton");
if (updateButton != null)
updateButton.Command = ReactiveCommand.Create(() =>
{
var dialog = new UpdateDialog(this.Language, this.PlotManager.Settings.Theme);
dialog.Show();
});
var donateButton = MainWindow.Instance.Find("DonateButton");
if (donateButton != null)
donateButton.Command = ReactiveCommand.Create(() =>
{
var dialog = new DonationDialog(this.Language, this.PlotManager.Settings.Theme);
dialog.Show();
});
var statisticsButton = MainWindow.Instance.Find("StatisticsButton");
if (statisticsButton != null)
statisticsButton.Command = ReactiveCommand.Create(() =>
{
var dialog = new StatisticsDialog(this.PlotManager, this.Language, this.PlotManager.Settings.Theme);
dialog.Show();
});
var harvestersButton = MainWindow.Instance.Find("HarvestersButton");
if (harvestersButton != null)
harvestersButton.Command = ReactiveCommand.Create(() =>
{
var dialog = new HarvestDialog(this.Language, this.PlotManager.Settings, this.PlotManager.Settings.Theme);
dialog.Show();
});
var chiaPlotterButton = MainWindow.Instance.Find("ChiaPlotterButton");
if (chiaPlotterButton != null)
chiaPlotterButton.Command = ReactiveCommand.Create(() =>
{
var dialog = new ChiaPlotterDialog(this.PlotManager, this.Language, this.PlotManager.Settings.Theme);
dialog.Show();
});
}
public void InitializeSearchBox()
{
MainWindow.Instance.TextChangeWorkaround = (text) =>
{
Search = text;
LoadPlotLogs();
return true;
};
}
public async void AddFolder()
{
OpenFolderDialog picker = new OpenFolderDialog();
var result = await picker.ShowAsync(MainWindow.Instance);
AddFolder(result);
}
public void AddFolder(string folder)
{
if (!PlotManager.Settings.LogDirectories.Contains(folder))
{
PlotManager.AddLogFolder(folder);
PlotManager.Settings.Persist();
LoadPlotLogs();
}
}
public void RemoveFolder(string folder)
{
PlotManager.RemoveLogFolder(folder);
PlotManager.Settings.Persist();
LoadPlotLogs();
}
public void InitializeRefreshInterval()
{
RefreshTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(30) };
RefreshTimer.Tick += (sender, e) =>
{
// Somehow this ticks twice
Debug.WriteLine("Refresh " + DateTime.Now);
LoadPlotLogs();
};
RefreshTimer.Start();
}
public void InitializeFilterUpdates()
{
/*
this.WhenAnyValue(x => x.Filter.HideHealthy)
.Subscribe((x) => LoadPlotLogs());
this.WhenAnyValue(x => x.Filter.HidePossiblyDead)
.Subscribe((x) => LoadPlotLogs());
this.WhenAnyValue(x => x.Filter.HideConfirmedDead)
.Subscribe((x) => LoadPlotLogs());
*/
var checkBox = MainWindow.Instance.Find("HideConfirmedDead");
if (checkBox != null)
{
void update()
{
PlotManager.Settings.Persist();
LoadPlotLogs();
}
checkBox.WhenAnyValue(x => x.IsChecked)
.Subscribe((x) => update());
MainWindow.Instance.Find("HidePossiblyDead").WhenAnyValue(x => x.IsChecked)
.Subscribe((x) => update());
MainWindow.Instance.Find("HideHealthy").WhenAnyValue(x => x.IsChecked)
.Subscribe((x) => update());
MainWindow.Instance.Find("HideFinished").WhenAnyValue(x => x.IsChecked)
.Subscribe((x) => update());
}
}
public void RegisterOnCloseHandler()
{
MainWindow.Instance.Closing += (sender, e) =>
{
PlotManager.Persist();
};
}
}
}
================================================
FILE: ChiaPlotStatusGUI/GUI/ViewModels/ViewModelBase.cs
================================================
using ReactiveUI;
using System;
using System.Collections.Generic;
using System.Text;
namespace ChiaPlotStatus.ViewModels
{
public class ViewModelBase : ReactiveObject
{
}
}
================================================
FILE: ChiaPlotStatusGUI/GUI/Views/ChiaPlotterDialog.axaml
================================================
================================================
FILE: ChiaPlotStatusGUI/GUI/Views/ChiaPlotterDialog.axaml.cs
================================================
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.Styling;
using ChiaPlotStatus.GUI.Models;
using ChiaPlotStatus.Logic.Models;
using ChiaPlotStatus.Logic.Utils;
using ChiaPlotStatusGUI.GUI.Utils;
using ChiaPlotStatusGUI.GUI.ViewModels;
using ChiaPlotStatusLib.Logic.Models;
using ReactiveUI;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Reactive;
using System.Text.RegularExpressions;
namespace ChiaPlotStatus.Views
{
public class ChiaPlotterDialog : Window
{
public Language Language { get; set; }
public List<(CPPlotLog, CPPlotLogReadable)> PlotLogTuples { get; set; }
public ObservableCollection PlotLogs { get; set; } = new();
public ChiaPlotStatus PlotManager { get; set; }
public string SortProperty { get; set; } = "Tmp1Drive";
public string Search { get; set; } = "";
public bool SortAsc { get; set; } = true;
public ChiaPlotterDialog()
{
}
public ChiaPlotterDialog(ChiaPlotStatus plotManager, Language language, string theme)
{
this.DataContext = this;
this.Language = language;
this.PlotManager = plotManager;
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
KeepGridScrollbarOnScreen();
LoadData();
this.WindowState = WindowState.Maximized;
Utils.SetTheme(this, theme);
this.Focus();
}
private void LoadData()
{
this.Find("LogDataGrid").BeginBatchUpdate();
PlotLogs.Clear();
PlotLogTuples = new();
foreach (var plotLog in PlotManager.PollCPPlotLogs(PlotManager.Settings.SortProperty, (bool)PlotManager.Settings.SortAsc, Search, PlotManager.Settings.Filter))
{
PlotLogs.Add(plotLog.Item2);
PlotLogTuples.Add(plotLog);
}
// PlotCounts = new(PlotLogTuples);
// HandleFinishDateVisibility();
this.Find("LogDataGrid").EndBatchUpdate();
//this.RaisePropertyChanged("PlotCounts");
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public void OnSelectionChanged(object sender, RoutedEventArgs e)
{
// TODO: SelectionChangedAction();
}
public void OpenLogViewerWindow(object sender, RoutedEventArgs e)
{
var plotLogReadable = (PlotLogReadable)((Button)sender).Tag;
var path = plotLogReadable.LogFolder + Path.DirectorySeparatorChar + plotLogReadable.LogFile;
Utils.OpenLogFile(path);
}
public void LogDataGridHeaderClick(object sender, RoutedEventArgs e)
{
Button header = ((Button)sender);
string headerText = (string)header.Content;
var oldSortProperty = SortProperty;
foreach (var property in typeof(GUI.Models.Columns).GetProperties())
{
string translation = (string)property.GetValue(Language.Columns);
if (string.Equals(headerText, translation))
{
SortProperty = property.Name;
break;
}
}
if (string.Equals(SortProperty, oldSortProperty))
SortAsc = !SortAsc;
Sorter.Sort(SortProperty, SortAsc, PlotLogTuples);
PlotLogs.Clear();
foreach (var tuple in PlotLogTuples)
PlotLogs.Add(tuple.Item2);
// Debug.WriteLine("SearchProperty: " + SortProperty + ", ASC: " + SortAsc);
}
private void KeepGridScrollbarOnScreen()
{
this.WhenAnyValue(x => x.Height)
.Subscribe(x =>
{
var logDataGrid = this.Find("LogDataGrid");
logDataGrid.Height = x - 145;
});
}
}
}
================================================
FILE: ChiaPlotStatusGUI/GUI/Views/DonationDialog.axaml
================================================
================================================
FILE: ChiaPlotStatusGUI/GUI/Views/DonationDialog.axaml.cs
================================================
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.Styling;
using ChiaPlotStatus.GUI.Models;
using ChiaPlotStatus.Logic.Models;
using ChiaPlotStatusGUI.GUI.Utils;
using ReactiveUI;
using System;
using System.Diagnostics;
using System.Drawing;
using System.Reactive;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
namespace ChiaPlotStatus.Views
{
public class DonationDialog : Window
{
public Language Language { get; set; }
public string ChiaAddress { get; set; } = "xch15p8swrrdt5ujv0dxy4hwjrvpjseyvuquwtfwnrjhxqt6ws9uf90qzq4axl";
public string PaypalURL { get; set; } = "https://www.paypal.com/donate?hosted_button_id=PDLLVF5XVMJPC";
public string LiberapayURL { get; set; } = "https://liberapay.com/grayfallstown/donate";
public DonationDialog() { }
public DonationDialog(Language language, string theme)
{
this.DataContext = this;
this.Language = language;
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
this.Find("Thx").IsVisible = false;
Utils.SetTheme(this, theme);
this.Focus();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public void CopyToClipboard(object sender, RoutedEventArgs e)
{
string text = (string)(((Button)sender).Tag);
Application.Current.Clipboard.SetTextAsync(text);
this.Find("Thx").IsVisible = true;
}
public void OpenLink(object sender, RoutedEventArgs e)
{
string url = (string)(((Button)sender).Tag);
Utils.OpenUrl(url);
this.Find("Thx").IsVisible = true;
}
}
}
================================================
FILE: ChiaPlotStatusGUI/GUI/Views/HarvestDialog.axaml
================================================
================================================
FILE: ChiaPlotStatusGUI/GUI/Views/HarvestDialog.axaml.cs
================================================
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.Styling;
using Avalonia.Threading;
using ChiaPlotStatus.GUI.Models;
using ChiaPlotStatus.Logic.Models;
using ChiaPlotStatusGUI.GUI.Utils;
using ChiaPlotStatusLib.Logic.Models;
using ChiaPlotStatusLib.Logic.Statistics.Harvest;
using ReactiveUI;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Reactive;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace ChiaPlotStatus.Views
{
/**
* TODO:
* - FolderList
* - AddFolder
* - RemoveFolder
* - Summary List
* - Heatmaps in Details (either tabs or sub-dialogs)
*/
public class HarvestDialog : Window
{
public Settings Settings { get; set; }
public Language Language { get; set; }
public List>?> Results { get; set; }
public ObservableCollection Summaries { get; set; } = new();
public ObservableCollection PathsWithoutResults { get; set; } = new();
public HarvestDialog()
{
}
public HarvestDialog(Language language, Settings settings, string theme)
{
this.DataContext = this;
this.Language = language;
this.Settings = settings;
foreach (var path in HarvestParser.DefaultPaths())
this.Settings.HarvesterLogDirectories.Add(path);
InitializeComponent();
KeepGridScrollbarOnScreen();
#if DEBUG
this.AttachDevTools();
#endif
Utils.SetTheme(this, theme);
//Task.Run(async () =>
//{
LoadData();
Dispatcher.UIThread.InvokeAsync(() =>
{
this.Find("Loading").IsVisible = false;
this.Find("Loaded").IsVisible = true;
});
//});
this.WindowState = WindowState.Maximized;
this.Focus();
}
public void AddFolder(string folder)
{
if (!Settings.HarvesterLogDirectories.Contains(folder))
{
Settings.HarvesterLogDirectories.Add(folder);
Settings.Persist();
LoadData();
}
}
public void AddFolder(object sender, RoutedEventArgs e)
{
Task.Run(async () =>
{
OpenFolderDialog picker = new OpenFolderDialog();
var result = await picker.ShowAsync(this);
Dispatcher.UIThread.InvokeAsync(() => AddFolder(result));
});
}
public void RemoveFolder (object sender, RoutedEventArgs e) {
string folder = (string)((Button)sender).Tag;
Settings.HarvesterLogDirectories.Remove(folder);
Settings.Persist();
LoadData();
}
private void LoadData()
{
Summaries.Clear();
PathsWithoutResults.Clear();
Results = new HarvestParser().ParseLogs(new List(this.Settings.HarvesterLogDirectories),
(double)this.Settings.MaxHarvestLookupSeconds, 500);
// assume all are missing until a result is found
foreach (var path in this.Settings.HarvesterLogDirectories)
PathsWithoutResults.Add(path);
foreach (var triplet in Results)
{
if (triplet != null)
{
Summaries.Add(new HarvestSummeryReadable(triplet.Item2));
PathsWithoutResults.Remove(triplet.Item1);
}
}
}
private void KeepGridScrollbarOnScreen()
{
this.WhenAnyValue(x => x.Height)
.Subscribe(x =>
{
var summeriesDataGrid = this.Find("SummeriesDataGrid");
summeriesDataGrid.Height = x - 100;
});
}
public void DataGridHeaderClick(object sender, RoutedEventArgs e)
{
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}
================================================
FILE: ChiaPlotStatusGUI/GUI/Views/MainWindow.axaml
================================================
Running:
Finished:
Concerning:
Failed:
Phase 1:
Phase 2:
Phase 3:
Phase 4:
Phase 5:
Logo made by charlie on freeicons.io under License Creative Commons(Attribution 3.0 unported)
================================================
FILE: ChiaPlotStatusGUI/GUI/Views/MainWindow.axaml.cs
================================================
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.Styling;
using ChiaPlotStatusGUI.GUI.Utils;
using ReactiveUI;
using System;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Reactive;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace ChiaPlotStatus.Views
{
public class MainWindow : Window
{
public static MainWindow? Instance { get; private set; }
public Func BtnClickWorkaround { get; set; }
public Func TextChangeWorkaround { get; set; }
public Func SortChangeWorkaround { get; set; }
public Func ThemeSwitchWorkaround { get; set; }
public Action SelectionChangedAction { get; set; }
public MainWindow()
{
Instance = this;
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
ThemeSwitcher();
this.WindowState = WindowState.Maximized;
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
// FIXME: RemoveFolderCommand does not trigger. Why is button.Command null?
public void RemoveFolderWorkaround(object sender, RoutedEventArgs e)
{
Button button = (Button)sender;
string folder = (string)button.CommandParameter;
ReactiveCommand command = (ReactiveCommand)button.Command;
if (command == null)
{
command = (ReactiveCommand)button.Tag;
}
// button.Command.Execute(folder);
BtnClickWorkaround.Invoke(folder);
}
// FIXME: apparently avalonia cannot tell me when a textbox text changes in my MainWindowViewModel
public void OnKeyPressUp(object sender, KeyEventArgs e)
{
TextChangeWorkaround(((TextBox)sender).Text);
}
public void LogDataGridHeaderClick(object sender, RoutedEventArgs e)
{
Button header = ((Button)sender);
string headerText = (string) header.Content;
SortChangeWorkaround(headerText);
}
public void ThemeSwitcher()
{
var themes = this.Find("Themes");
themes.SelectionChanged += (sender, e) =>
{
switch (themes.SelectedIndex)
{
case 1:
ThemeSwitchWorkaround("Dark");
break;
default:
ThemeSwitchWorkaround("Light");
break;
}
};
}
public void OnSelectionChanged(object sender, RoutedEventArgs e)
{
SelectionChangedAction();
}
public void OpenLogViewerWindow(object sender, RoutedEventArgs e)
{
var plotLogReadable = (PlotLogReadable)((Button)sender).Tag;
var path = plotLogReadable.LogFolder + Path.DirectorySeparatorChar + plotLogReadable.LogFile;
Utils.OpenLogFile(path);
}
}
}
================================================
FILE: ChiaPlotStatusGUI/GUI/Views/MarkOfDeathDialog.axaml
================================================
================================================
FILE: ChiaPlotStatusGUI/GUI/Views/MarkOfDeathDialog.axaml.cs
================================================
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.Styling;
using ChiaPlotStatus.GUI.Models;
using ChiaPlotStatus.Logic.Models;
using ChiaPlotStatusGUI.GUI.Utils;
using ReactiveUI;
using System;
using System.Diagnostics;
using System.Drawing;
using System.Reactive;
using System.Text.RegularExpressions;
namespace ChiaPlotStatus.Views
{
public class MarkOfDeathDialog : Window
{
public Settings Settings { get; set; }
public Language Language { get; set; }
public Action OnUpdate { get; set; }
public bool IsDead { get; set; }
public bool IsAlive { get; set; }
public PlotLogReadable plotLogReadable { get; }
public MarkOfDeathDialog()
{
}
public MarkOfDeathDialog(PlotLogReadable plotLogReadable, Language language, Settings settings, string theme, Action onUpdate)
{
this.DataContext = this;
this.Language = language;
this.Settings = settings;
this.plotLogReadable = plotLogReadable;
this.OnUpdate = onUpdate;
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
foreach (var markOfDeath in Settings.MarksOfDeath)
if (markOfDeath.IsMatch(plotLogReadable))
this.IsDead = true;
this.IsAlive = !this.IsDead;
Utils.SetTheme(this, theme);
this.Focus();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public void MarkAsDead(object sender, RoutedEventArgs e)
{
var mark = new MarkOfDeath(this.plotLogReadable);
if (!this.Settings.MarksOfDeath.Contains(mark))
{
this.Settings.MarksOfDeath.Add(mark);
this.Settings.Persist();
this.OnUpdate();
}
this.Close();
}
public void UnmarkAsDead(object sender, RoutedEventArgs e)
{
var mark = new MarkOfDeath(this.plotLogReadable);
if (this.Settings.MarksOfDeath.Contains(mark))
{
this.Settings.MarksOfDeath.Remove(mark);
this.Settings.Persist();
this.OnUpdate();
}
this.Close();
}
public void Abort(object sender, RoutedEventArgs e)
{
this.Close();
}
}
}
================================================
FILE: ChiaPlotStatusGUI/GUI/Views/NoteDialog.axaml
================================================
================================================
FILE: ChiaPlotStatusGUI/GUI/Views/NoteDialog.axaml.cs
================================================
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.Styling;
using ChiaPlotStatus.GUI.Models;
using ChiaPlotStatus.Logic.Models;
using ChiaPlotStatusGUI.GUI.Utils;
using ChiaPlotStatusLib.Logic.Models;
using ReactiveUI;
using System;
using System.Diagnostics;
using System.Drawing;
using System.Reactive;
using System.Text.RegularExpressions;
namespace ChiaPlotStatus.Views
{
public class NoteDialog : Window
{
public Settings Settings { get; set; }
public Language Language { get; set; }
public Action OnUpdate { get; set; }
public PlotLogReadable plotLogReadable { get; }
public string Note { get; set; }
public NoteDialog()
{
}
public NoteDialog(PlotLogReadable plotLogReadable, Language language, Settings settings, string theme, Action onUpdate)
{
this.DataContext = this;
this.Language = language;
this.Settings = settings;
this.plotLogReadable = plotLogReadable;
this.OnUpdate = onUpdate;
this.Note = plotLogReadable.Note;
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
Utils.SetTheme(this, theme);
this.Focus();
// does not work for some reason:
this.Find("Input").SelectionStart = 0;
this.Find("Input").SelectionEnd = plotLogReadable.Note.Length;
this.Find("Input").Focus();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public void Save(object sender, RoutedEventArgs e)
{
var note = new Note(this.plotLogReadable);
note.text = this.Note;
if (this.Settings.Notes.Contains(note))
this.Settings.Notes.Remove(note);
this.Settings.Notes.Add(note);
this.Settings.Persist();
this.OnUpdate();
this.Close();
}
public void Abort(object sender, RoutedEventArgs e)
{
this.Close();
}
}
}
================================================
FILE: ChiaPlotStatusGUI/GUI/Views/StatisticsDialog.axaml
================================================
================================================
FILE: ChiaPlotStatusGUI/GUI/Views/StatisticsDialog.axaml.cs
================================================
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.Styling;
using ChiaPlotStatus.GUI.Models;
using ChiaPlotStatus.Logic.Models;
using ChiaPlotStatus.Logic.Utils;
using ChiaPlotStatusGUI.GUI.Utils;
using ChiaPlotStatusGUI.GUI.ViewModels;
using ReactiveUI;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Drawing;
using System.Reactive;
using System.Text.RegularExpressions;
namespace ChiaPlotStatus.Views
{
public class StatisticsDialog : Window
{
public Language Language { get; set; }
public List<(PlottingStatisticsFull, PlottingStatisticsFullReadable)> StatsTuples { get; set; }
public ObservableCollection Stats { get; set; } = new();
public List DailyStats { get; set; }
public ChiaPlotStatus PlotManager { get; set; }
public string SortProperty { get; set; } = "Tmp1Drive";
public bool SortAsc { get; set; } = true;
public StatisticsDialog()
{
}
public StatisticsDialog(ChiaPlotStatus plotManager, Language language, string theme)
{
this.DataContext = this;
this.Language = language;
this.PlotManager = plotManager;
LoadData();
InitDailyStatsTable();
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
KeepGridScrollbarOnScreen();
this.WindowState = WindowState.Maximized;
Utils.SetTheme(this, theme);
this.Focus();
}
private void LoadData()
{
this.StatsTuples = PlotManager.Statistics.AllStatistics();
Sorter.Sort(SortProperty, SortAsc, StatsTuples);
Stats.Clear();
foreach (var tuple in this.StatsTuples)
Stats.Add(tuple.Item2);
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public void DataGridHeaderClick(object sender, RoutedEventArgs e)
{
Button header = ((Button)sender);
string headerText = (string)header.Content;
var oldSortProperty = SortProperty;
foreach (var property in typeof(Columns).GetProperties())
{
string translation = (string)property.GetValue(Language.Columns);
if (string.Equals(headerText, translation))
{
SortProperty = property.Name;
break;
}
}
if (string.Equals(SortProperty, oldSortProperty))
SortAsc = !SortAsc;
Sorter.Sort(SortProperty, SortAsc, StatsTuples);
Stats.Clear();
foreach (var tuple in StatsTuples)
Stats.Add(tuple.Item2);
// Debug.WriteLine("SearchProperty: " + SortProperty + ", ASC: " + SortAsc);
}
private void KeepGridScrollbarOnScreen()
{
this.WhenAnyValue(x => x.Height)
.Subscribe(x =>
{
var statsDataGrid = this.Find("StatsDataGrid");
var dailyStatsDataGrid = this.Find("DailyStatsDataGrid");
statsDataGrid.Height = x / 2;
dailyStatsDataGrid.Height = x / 2;
});
}
public void InitDailyStatsTable()
{
Dictionary dailyStats = PlotManager.Statistics.GetDailyStats();
List days = new(dailyStats.Values);
days.Sort((a, b) => -1 * a.Day.CompareTo(b.Day));
DailyStats = new();
foreach (var stat in days)
DailyStats.Add(new(stat));
}
/*
private void BuildPlotModel()
{
Dictionary dailyStats = PlotManager.Statistics.GetDailyStats();
Dictionary lineSeries = new();
LineSeries getLineSeries(string name)
{
if (!lineSeries.ContainsKey(name))
lineSeries.Add(name, new LineSeries());
return lineSeries[name];
}
var plotModel = new PlotModel
{
Title = "test1",
TitleToolTip = "test2"
};
DateTime twoWeeksAgo = DateTime.Now.AddDays(-14);
plotModel.Axes.Add(new DateTimeAxis {
Position = AxisPosition.Bottom,
Minimum = DateTimeAxis.ToDouble(twoWeeksAgo),
Maximum = DateTimeAxis.ToDouble(DateTime.Now),
StringFormat = "MMM dd" });
List stats = new(dailyStats.Values);
stats.Sort((a, b)=> a.Day.CompareTo(b.Day));
foreach (var stat in stats)
{
getLineSeries("Phase1").Points.Add(new DataPoint(DateTimeAxis.ToDouble(stat.Day), stat.Phase1));
getLineSeries("Phase2").Points.Add(new DataPoint(DateTimeAxis.ToDouble(stat.Day), stat.Phase2));
getLineSeries("Phase3").Points.Add(new DataPoint(DateTimeAxis.ToDouble(stat.Day), stat.Phase3));
getLineSeries("Phase4").Points.Add(new DataPoint(DateTimeAxis.ToDouble(stat.Day), stat.Phase4));
getLineSeries("Phase5").Points.Add(new DataPoint(DateTimeAxis.ToDouble(stat.Day), stat.Phase5));
getLineSeries("Finished").Points.Add(new DataPoint(DateTimeAxis.ToDouble(stat.Day), stat.Finished));
getLineSeries("Died").Points.Add(new DataPoint(DateTimeAxis.ToDouble(stat.Day), stat.Died));
}
foreach (var series in lineSeries.Values)
plotModel.Series.Add(series);
this.PlotModel = plotModel;
}
*/
}
}
================================================
FILE: ChiaPlotStatusGUI/GUI/Views/UpdateDialog.axaml
================================================
================================================
FILE: ChiaPlotStatusGUI/GUI/Views/UpdateDialog.axaml.cs
================================================
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.Styling;
using Avalonia.Platform;
using Avalonia.Threading;
using ChiaPlotStatus.GUI.Models;
using ChiaPlotStatus.Logic.Models;
using ChiaPlotStatusGUI.GUI.Utils;
using Octokit;
using ReactiveUI;
using System;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Net;
using System.Reactive;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using YamlDotNet.Serialization;
namespace ChiaPlotStatus.Views
{
public enum InstallationPackageType
{
EXE,
DEB,
RPM,
PKG
}
public class UpdateDialog : Window
{
public GUI.Models.Language Language { get; set; }
public Release Latest { get; set; }
public string Current { get; set; }
public string current { get; set; }
public UpdateDialog() { }
public UpdateDialog(GUI.Models.Language language, string theme)
{
this.DataContext = this;
this.Language = language;
this.Current = LoadCurrentRelease();
this.Latest = LoadLatestRelease();
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
this.Find("UpToDate").IsVisible = string.Equals(this.Latest.TagName, this.Current);
this.Find("DownloadButtons").IsVisible = !string.Equals(this.Latest.TagName, this.Current);
Utils.SetTheme(this, theme);
this.Focus();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private Release LoadLatestRelease()
{
var client = new GitHubClient(new ProductHeaderValue("Chia-Plot-Status"));
Release? latest = null;
var task = Task.Run(async () =>
{
var result = await client.Repository.Release.GetAll("grayfallstown", "Chia-Plot-Status");
latest = result[0];
});
task.Wait();
return latest;
}
private string LoadCurrentRelease()
{
return System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(3);
}
public void DownloadWindows(object sender, RoutedEventArgs e)
{
Update(InstallationPackageType.EXE);
}
public void DownloadDeb(object sender, RoutedEventArgs e)
{
Update(InstallationPackageType.DEB);
}
public void DownloadRpm(object sender, RoutedEventArgs e)
{
Update(InstallationPackageType.RPM);
}
/**
* It was requested to rename Setup.exe in something that contains the name of the application.
* Problem is the download url and therefor the file name is hardcoded to Setup.exe.
* Old applications would no longer be able to find the update if the name was changed.
* Solving this by using a fallback name now and once a few updates passed I can change
* the name without breaking too many installations.
*/
public static (string, string?) GetInstallationPackageName(InstallationPackageType type)
{
switch (type)
{
case InstallationPackageType.EXE:
return ("ChiaPlotStatus.windows-Setup.exe", "Setup.exe");
case InstallationPackageType.DEB:
return ("ChiaPlotStatus.linux-x64.deb", null);
case InstallationPackageType.RPM:
return ("ChiaPlotStatus.linux-x64.rpm", null);
case InstallationPackageType.PKG:
return ("ChiaPlotStatus.mac.pkg", null);
default:
throw new NotImplementedException("unknown InstallationPackageType " + type);
}
}
public void Update(InstallationPackageType type)
{
this.Find("DownloadButtons").IsVisible = false;
this.Find("DownloadNotice").IsVisible = true;
var fileName = GetInstallationPackageName(type);
Task.Run(async () =>
{
if (DownloadInstallationPackage(fileName))
{
void StartInstallationAndExit(string installerPath)
{
// starts a detached process that does not get terminated
// when we close Chia Plot Status, as we need to close
// it to update the files (on windows and maybe on mac too)
var startInfo = new ProcessStartInfo(installerPath);
startInfo.UseShellExecute = true;
Process.Start(startInfo);
Environment.Exit(0);
}
var paths = GetFullInstallationPackagePath(type);
if (File.Exists(paths.Item1))
{
StartInstallationAndExit(paths.Item1);
return;
}
if (paths.Item2 != null && File.Exists(paths.Item2))
{
StartInstallationAndExit(paths.Item2);
return;
}
else
Debug.WriteLine("Both installation package paths lead nowhere, falling back to manual installation");
}
else
Debug.WriteLine("Download of installation package failed, falling back to manual installation");
// fallback to manual download via browser
Utils.OpenUrl("https://github.com/grayfallstown/Chia-Plot-Status/releases/latest/");
});
}
public bool DownloadInstallationPackage((string, string?) fileName)
{
using (var client = new WebClient())
{
try
{
string baseUrl = "https://github.com/grayfallstown/Chia-Plot-Status/releases/latest/download/";
string updateDir = EnsureUpdateTempDirectoryExists(); ;
client.DownloadFile(baseUrl + fileName.Item1, updateDir + fileName.Item1);
return true;
}
catch (System.Net.WebException e)
{
Debug.WriteLine("download of update file " + fileName + " failed " + e);
if (fileName.Item2 != null)
return DownloadInstallationPackage((fileName.Item2, null));
}
}
return false;
}
public static string GetUpdateTempDirectory()
{
return Path.GetTempPath() + "ChiaPlotStatusUpdate" + Path.DirectorySeparatorChar;
}
public static (string, string?) GetFullInstallationPackagePath(InstallationPackageType type)
{
(string, string?) packageName = GetInstallationPackageName(type);
string basePath = GetUpdateTempDirectory() + Path.DirectorySeparatorChar;
if (packageName.Item2 == null)
return (basePath + packageName.Item1, null);
else
return (basePath + packageName.Item1, basePath + packageName.Item2);
}
public static string EnsureUpdateTempDirectoryExists()
{
var dir = GetUpdateTempDirectory();
if (!Directory.Exists(dir))
Directory.CreateDirectory(dir);
return dir;
}
public static void DeleteUpdateTempDirectory()
{
try
{
var dir = GetUpdateTempDirectory();
if (Directory.Exists(dir))
Directory.Delete(dir, true);
} catch (Exception e)
{
Debug.WriteLine("DeleteUpdateTempDirectory failed: " + e);
}
}
}
}
================================================
FILE: ChiaPlotStatusGUI/GUI/nuget.config
================================================
================================================
FILE: ChiaPlotStatusGUI/chia-plot-status.desktop
================================================
[Desktop Entry]
Name=Chia Plot Status
Comment=Tool to Monitor and Analyse Chia Plotting log files, show health and progress of running plots and estimated time to completion
GenericName=Chia Plot Status
Exec=/usr/local/bin/ChiaPlotStatus %U
Icon=chia-plot-status
Type=Application
StartupNotify=true
Categories=GNOME;GTK;Utility;
================================================
FILE: ChiaPlotStatusLib/ChiaPlotStatusLib.csproj
================================================
net5.0
win7-x64;ubuntu.16.10-x64;linux-x64
grayfallstown
grayfallstown
ChiaPlotStatusLib
0.11.11
================================================
FILE: ChiaPlotStatusLib/Logic/ChiaPlotStatus.cs
================================================
using ChiaPlotStatus.Logic.Models;
using ChiaPlotStatus.Logic.Utils;
using ChiaPlotStatusLib.Logic;
using ChiaPlotStatusLib.Logic.Models;
using ChiaPlotStatusLib.Logic.Parser;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ChiaPlotStatus
{
/**
* Core Object of this tool.
* Knows everything, does everything.
*/
public class ChiaPlotStatus
{
public Settings Settings { get; }
private PlotParserCache cache;
private Dictionary PlotLogFiles { get; } = new();
private Dictionary CPPlotLogFiles { get; } = new();
public PlottingStatisticsHolder Statistics { get; set; }
public ChiaPlotStatus(Settings settings) {
this.Settings = settings;
Statistics = new PlottingStatisticsHolder(new List(), Settings.Weigths, new List());
string cachePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + Path.DirectorySeparatorChar +
"ChiaPlotStatus.cache.json";
cache = new PlotParserCache(cachePath);
}
public void AddDefaultLogFolders()
{
void add(string blockchain)
{
string path = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)
+ Path.DirectorySeparatorChar + "." + blockchain + Path.DirectorySeparatorChar + "mainnet" +
Path.DirectorySeparatorChar + "plotter";
AddLogFolder(path);
}
add("chia");
add("flax");
add("chaingreen");
add("seno2");
add("chiarose");
add("goji-blockchain");
add("spare-blockchain");
}
public void AddLogFolder(string path)
{
Settings.LogDirectories.Add(path);
}
public void RemoveLogFolder(string path)
{
Settings.LogDirectories.Remove(path);
// drop plotlogs from that folder
foreach (var plotLogFile in PlotLogFiles.Values)
{
var folder = plotLogFile.LogFolder;
if (string.Equals(folder, path))
PlotLogFiles.Remove(plotLogFile.LogFile);
folder = folder + "\\";
if (string.Equals(folder, path))
PlotLogFiles.Remove(plotLogFile.LogFile);
}
foreach (var plotLogFile in CPPlotLogFiles.Values)
{
var folder = plotLogFile.LogFolder;
if (string.Equals(folder, path))
CPPlotLogFiles.Remove(plotLogFile.LogFile);
folder = folder + "\\";
if (string.Equals(folder, path))
CPPlotLogFiles.Remove(plotLogFile.LogFile);
}
}
public List<(PlotLog, PlotLogReadable)> PollPlotLogs(string sortPropertyName, bool sortAsc, string? searchString, Filter filter)
{
SearchForNewLogFiles();
ConcurrentBag plotLogs = ParseTheLogs();
foreach (var cpPlotLog in ParseTheCPLogs())
plotLogs.Add(cpPlotLog);
if (Settings.AlwaysDoFullRead == true) // nullable, so == true
PlotLogFiles.Clear();
HandleStatistics(plotLogs.ToList());
List<(PlotLog, PlotLogReadable)> plusReadable = new();
foreach (var plotLog in plotLogs)
{
foreach (var markOfDeath in Settings.MarksOfDeath)
if (markOfDeath.IsMatch(plotLog))
plotLog.Health = new ConfirmedDead(true);
foreach (var note in Settings.Notes)
if (note.IsMatch(plotLog))
plotLog.Note = note.text;
if (!plotLog.IsRunning())
plotLog.RunTimeSeconds = 0;
else if (plotLog.StartDate != null)
plotLog.RunTimeSeconds = (int)((TimeSpan)(DateTime.Now - plotLog.StartDate)).TotalSeconds;
if (typeof(CPPlotLog) == plotLog.GetType())
plusReadable.Add((plotLog, new CPPlotLogReadable((CPPlotLog)plotLog)));
else
plusReadable.Add((plotLog, new PlotLogReadable(plotLog)));
}
List<(PlotLog, PlotLogReadable)> result = Filter(searchString, filter, plusReadable);
SortPlotLogs(sortPropertyName, sortAsc, result);
return result;
}
public List<(CPPlotLog, CPPlotLogReadable)> PollCPPlotLogs(string sortPropertyName, bool sortAsc, string? searchString, Filter filter)
{
SearchForNewLogFiles();
ConcurrentBag cpPlotLogs = ParseTheCPLogs();
if (Settings.AlwaysDoFullRead == true) // nullable, so == true
CPPlotLogFiles.Clear();
HandleStatistics(new(cpPlotLogs.ToArray()));
List<(CPPlotLog, CPPlotLogReadable)> plusReadable = new();
foreach (var cpPlotLog in cpPlotLogs)
{
foreach (var markOfDeath in Settings.MarksOfDeath)
if (markOfDeath.IsMatch(cpPlotLog))
cpPlotLog.Health = new ConfirmedDead(true);
foreach (var note in Settings.Notes)
if (note.IsMatch(cpPlotLog))
cpPlotLog.Note = note.text;
if (!cpPlotLog.IsRunning())
cpPlotLog.RunTimeSeconds = 0;
else if (cpPlotLog.StartDate != null)
cpPlotLog.RunTimeSeconds = (int)((TimeSpan)(DateTime.Now - cpPlotLog.StartDate)).TotalSeconds;
plusReadable.Add((cpPlotLog, new CPPlotLogReadable(cpPlotLog)));
}
List<(CPPlotLog, CPPlotLogReadable)> result = Filter(searchString, filter, plusReadable);
SortPlotLogs(sortPropertyName, sortAsc, result);
return result;
}
private void SearchForNewLogFiles()
{
foreach (var directory in Settings.LogDirectories)
{
// when a logfolder gets deleted that was added to Settings Chia Plot Status failed to start
if (Directory.Exists(directory))
{
foreach (var filePath in Directory.GetFiles(directory))
{
if (!PlotLogFiles.ContainsKey(filePath) && LooksLikeAPlotLog(filePath))
{
PlotLogFiles[filePath] = new PlotLogFileParser(filePath, Settings.AlwaysDoFullRead == true, ref cache);
}
else if (!CPPlotLogFiles.ContainsKey(filePath) && LooksLikeACPPlotLog(filePath))
{
CPPlotLogFiles[filePath] = new CPPlotLogFileParser(filePath, Settings.AlwaysDoFullRead == true);
}
}
}
}
}
private ConcurrentBag ParseTheLogs()
{
ConcurrentBag plotLogs = new ConcurrentBag();
Parallel.ForEach(PlotLogFiles.Values, (plotLogFile) =>
{
foreach (var plotLog in plotLogFile.ParsePlotLog())
plotLogs.Add(plotLog);
});
return plotLogs;
}
private ConcurrentBag ParseTheCPLogs()
{
ConcurrentBag cpPlotLogs = new ConcurrentBag();
Parallel.ForEach(CPPlotLogFiles.Values, (cpPlotLogFile) => {
foreach (var cpPlotLog in cpPlotLogFile.ParseCPPlotLog())
cpPlotLogs.Add(cpPlotLog);
});
return cpPlotLogs;
}
private List<(A, B)> Filter (string? searchString, Filter filter, List<(A, B)> plotLogs) where A : PlotLog
{
List<(A, B)> searchResults = SearchFilter.Search (searchString, plotLogs);
List<(A, B)> filterResults = new();
foreach (var tuple in searchResults)
{
if (filter.HideFinished && tuple.Item1.CurrentPhase == 6)
continue;
switch (tuple.Item1.Health)
{
case Healthy:
if (!filter.HideHealthy || (tuple.Item1.CurrentPhase == 6 && !filter.HideFinished)) filterResults.Add(tuple);
break;
case TempError:
// well, you have to take action here.
filterResults.Add(tuple);
break;
case Concerning:
// would one want to hide those?
filterResults.Add(tuple);
break;
case PossiblyDead:
if (!filter.HidePossiblyDead) filterResults.Add(tuple);
break;
case ConfirmedDead:
if (!filter.HideConfirmedDead) filterResults.Add(tuple);
break;
}
}
return filterResults;
}
private static void SortPlotLogs(string propertyName, bool sortAsc, List<(PlotLog, PlotLogReadable)> plotLogs)
{
Sorter.Sort(propertyName, sortAsc, plotLogs);
}
private static void SortPlotLogs(string propertyName, bool sortAsc, List<(CPPlotLog, CPPlotLogReadable)> plotLogs)
{
Sorter.Sort(propertyName, sortAsc, plotLogs);
}
private void HandleStatistics(List plotLogs)
{
Statistics = new PlottingStatisticsHolder(plotLogs, Settings.Weigths, Settings.MarksOfDeath);
foreach (var plotLog in plotLogs)
{
PlottingStatistics stats = Statistics.GetMostRelevantStatistics(plotLog);
plotLog.UpdateEta(stats);
plotLog.UpdateHealth(stats);
}
}
private bool LooksLikeAPlotLog(string file)
{
byte[] buffer = new byte[4096];
try
{
using (FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
var bytes_read = fs.Read(buffer, 0, buffer.Length);
fs.Close();
if (bytes_read > 63)
{
string asString = GetEncoding(file).GetString(buffer);
bool hasNewLine = asString.Contains("\n");
bool hasStartingSentence = asString.Contains("Starting plotting progress into temporary dirs");
bool hasHarvesterDuplicateWarning = asString.Contains("already exists for harvester, please remove it manually");
if (hasNewLine && (hasStartingSentence || hasHarvesterDuplicateWarning))
return true;
}
}
} catch (Exception e)
{
Debug.WriteLine(e);
return false;
}
// Debug.WriteLine("File " + file + " was detected as a non plotlog file and will not be parsed as such");
return false;
}
private bool LooksLikeACPPlotLog(string file)
{
byte[] buffer = new byte[4096];
try
{
using (FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
var bytes_read = fs.Read(buffer, 0, buffer.Length);
fs.Close();
if (bytes_read > 63)
{
string asString = GetEncoding(file).GetString(buffer);
bool hasNewLine = asString.Contains("\n");
bool hasPlotterName = asString.Contains("Multi-threaded pipelined Chia k32 plotter");
bool hasFinalDirectory = asString.Contains("Final Directory: ");
bool hasThreads = asString.Contains("Number of Threads: ");
bool hasBuckets = asString.Contains("Number of Sort Buckets: ");
bool hasDirectory = asString.Contains("Working Directory:");
bool hasStartingSentence = asString.Contains("Number of Threads: ");
if (hasNewLine && (hasPlotterName || hasThreads || hasBuckets || hasDirectory || hasFinalDirectory))
return true;
}
}
}
catch (Exception e)
{
Debug.WriteLine(e);
return false;
}
// Debug.WriteLine("File " + file + " was detected as a non cpPlotlog file and will not be parsed as such");
return false;
}
private static Encoding GetEncoding(string filename)
{
// Read the BOM
var bom = new byte[4];
using (var file = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
file.Read(bom, 0, 4);
}
// Analyze the BOM
// if (bom[0] == 0x2b && bom[1] == 0x2f && bom[2] == 0x76) return Encoding.UTF7;
if (bom[0] == 0xef && bom[1] == 0xbb && bom[2] == 0xbf) return Encoding.UTF8;
if (bom[0] == 0xff && bom[1] == 0xfe && bom[2] == 0 && bom[3] == 0) return Encoding.UTF32; //UTF-32LE
if (bom[0] == 0xff && bom[1] == 0xfe) return Encoding.Unicode; //UTF-16LE
if (bom[0] == 0xfe && bom[1] == 0xff) return Encoding.BigEndianUnicode; //UTF-16BE
if (bom[0] == 0 && bom[1] == 0 && bom[2] == 0xfe && bom[3] == 0xff) return new UTF32Encoding(true, true); //UTF-32BE
// We actually have no idea what the encoding is if we reach this point, so
// you may wish to return null instead of defaulting to ASCII
return Encoding.ASCII;
}
public void Persist()
{
this.Settings.Persist();
this.cache.Persist();
}
}
}
================================================
FILE: ChiaPlotStatusLib/Logic/Models/CPPlotLog.cs
================================================
using ChiaPlotStatus;
using ChiaPlotStatus.Logic.Models;
using ChiaPlotStatusLib.Logic.Models;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace ChiaPlotStatusLib.Logic.Models
{
public class CPPlotLog: PlotLog
{
public static int P1PARTS = 12;
public static int P2PARTS = 15;
public static int P3PARTS = 14;
public static int P4PARTS = 6;
public int CurrentPhasePart { get; set; } = 1;
public float P1 { get; set; } = 0.0f;
public float P2 { get; set; } = 0.0f;
public float P3 { get; set; } = 0.0f;
public long P3Entries { get; set; } = 0L;
public float P4 { get; set; } = 0.0f;
public float P1Table1 { get; set; } = 0.0f;
public long P1Table1Found { get; set; } = 0L;
public long P1Table1Lost { get; set; } = 0L;
public float P1Table2 { get; set; } = 0.0f;
public long P1Table2Found { get; set; } = 0L;
public long P1Table2Lost { get; set; } = 0L;
public float P1Table3 { get; set; } = 0.0f;
public long P1Table3Found { get; set; } = 0L;
public long P1Table3Lost { get; set; } = 0L;
public float P1Table4 { get; set; } = 0.0f;
public long P1Table4Found { get; set; } = 0L;
public long P1Table4Lost { get; set; } = 0L;
public float P1Table5 { get; set; } = 0.0f;
public long P1Table5Found { get; set; } = 0L;
public long P1Table5Lost { get; set; } = 0L;
public float P1Table6 { get; set; } = 0.0f;
public long P1Table6Found { get; set; } = 0L;
public long P1Table6Lost { get; set; } = 0L;
public float P1Table7 { get; set; } = 0.0f;
public long P1Table7Found { get; set; } = 0L;
public long P1Table7Lost { get; set; } = 0L;
public long P2MaxTableSize { get; set; } = 0L;
public float P2Table1Scan { get; set; } = 0.0f;
public float P2Table2Scan { get; set; } = 0.0f;
public float P2Table3Scan { get; set; } = 0.0f;
public float P2Table4Scan { get; set; } = 0.0f;
public float P2Table5Scan { get; set; } = 0.0f;
public float P2Table6Scan { get; set; } = 0.0f;
public float P2Table7Scan { get; set; } = 0.0f;
public float P2Table1Rewrite { get; set; } = 0.0f;
public float P2Table2Rewrite { get; set; } = 0.0f;
public float P2Table3Rewrite { get; set; } = 0.0f;
public float P2Table4Rewrite { get; set; } = 0.0f;
public float P2Table5Rewrite { get; set; } = 0.0f;
public float P2Table6Rewrite { get; set; } = 0.0f;
public float P2Table7Rewrite { get; set; } = 0.0f;
public long P2Table1Lost { get; set; } = 0L;
public long P2Table2Lost { get; set; } = 0L;
public long P2Table3Lost { get; set; } = 0L;
public long P2Table4Lost { get; set; } = 0L;
public long P2Table5Lost { get; set; } = 0L;
public long P2Table6Lost { get; set; } = 0L;
public long P2Table7Lost { get; set; } = 0L;
public float P31Table2 { get; set; } = 0.0f;
public long P31Table2Entries { get; set; } = 0L;
public float P31Table3 { get; set; } = 0.0f;
public long P31Table3Entries { get; set; } = 0L;
public float P31Table4 { get; set; } = 0.0f;
public long P31Table4Entries { get; set; } = 0L;
public float P31Table5 { get; set; } = 0.0f;
public long P31Table5Entries { get; set; } = 0L;
public float P31Table6 { get; set; } = 0.0f;
public long P31Table6Entries { get; set; } = 0L;
public float P31Table7 { get; set; } = 0.0f;
public long P31Table7Entries { get; set; } = 0L;
public float P32Table1 { get; set; } = 0.0f;
public long P32Table1Entries { get; set; } = 0L;
public float P32Table2 { get; set; } = 0.0f;
public long P32Table2Entries { get; set; } = 0L;
public float P32Table3 { get; set; } = 0.0f;
public long P32Table3Entries { get; set; } = 0L;
public float P32Table4 { get; set; } = 0.0f;
public long P32Table4Entries { get; set; } = 0L;
public float P32Table5 { get; set; } = 0.0f;
public long P32Table5Entries { get; set; } = 0L;
public float P32Table6 { get; set; } = 0.0f;
public long P32Table6Entries { get; set; } = 0L;
public float P32Table7 { get; set; } = 0.0f;
public long P32Table7Entries { get; set; } = 0L;
public CPPlotLog()
{
UsedPlotter = "chia-plotter";
}
public void EnterPhase(int phase)
{
//Debug.WriteLine("XXXXXX " + this.CurrentPhase + ": " + this.CurrentPhasePart);
this.CurrentPhase = phase;
this.CurrentPhasePart = 0;
}
public void NextPhasePart()
{
this.CurrentPhasePart++;
}
public override void UpdateProgress()
{
float part = 0;
switch (CurrentPhase)
{
case 6:
part = 1 + CPPlotLog.P4PARTS + CPPlotLog.P3PARTS + CPPlotLog.P2PARTS + CPPlotLog.P1PARTS;
break;
case 5:
part = CPPlotLog.P4PARTS + CPPlotLog.P3PARTS + CPPlotLog.P2PARTS + CPPlotLog.P1PARTS;
break;
case 4:
part = part = CPPlotLog.P3PARTS + CPPlotLog.P2PARTS + CPPlotLog.P1PARTS;
break;
case 3:
int totalTablesIn3 = 7;
part = part = CPPlotLog.P2PARTS + CPPlotLog.P1PARTS;
break;
case 2:
part = part = CPPlotLog.P1PARTS;
break;
case 1:
part = 0;
break;
}
Progress = (part + CurrentPhasePart) / (1 + CPPlotLog.P4PARTS + CPPlotLog.P3PARTS + CPPlotLog.P2PARTS + CPPlotLog.P1PARTS) * 100;
if (Double.IsNaN(Progress))
Progress = 0;
}
public override void UpdateEta(PlottingStatistics stats)
{
this.TimeRemaining = 0;
if (CurrentPhase == 6)
{
this.TimeRemaining = 0;
}
if (CurrentPhase <= 5)
{
float factor = 1;
if (CurrentPhase == 5)
{
// TODO: if more can be known
}
this.TimeRemaining += (int)(factor * (float)stats.CopyTimeAvgTimeNeed);
}
if (CurrentPhase <= 4)
{
float factor = 1;
if (CurrentPhase == 4)
factor = (float)this.CurrentPhasePart / CPPlotLog.P4PARTS;
this.TimeRemaining += (int)(factor * stats.Phase4AvgTimeNeed);
}
if (CurrentPhase <= 3)
{
float factor = 1;
if (CurrentPhase == 3)
factor = (float)this.CurrentPhasePart / CPPlotLog.P3PARTS;
this.TimeRemaining += (int)(factor * stats.Phase3AvgTimeNeed);
}
if (CurrentPhase <= 2)
{
float factor = 1;
if (CurrentPhase == 2)
factor = (float)this.CurrentPhasePart / CPPlotLog.P2PARTS;
this.TimeRemaining += (int)(factor * stats.Phase2AvgTimeNeed);
}
if (CurrentPhase == 1)
{
var factor = (float)this.CurrentPhasePart / CPPlotLog.P1PARTS;
this.TimeRemaining += (int)(factor * stats.Phase2AvgTimeNeed);
}
if (this.TimeRemaining > 0)
{
DateTime dt = DateTime.Now;
dt = dt.AddSeconds(this.TimeRemaining);
this.ETA = dt;
}
else
this.ETA = null;
}
public override void UpdateHealth(PlottingStatistics stats)
{
// TODO
base.UpdateHealth(stats);
}
}
}
================================================
FILE: ChiaPlotStatusLib/Logic/Models/CPPlotLogReadable.cs
================================================
using ChiaPlotStatus;
using ChiaPlotStatusLib.Logic.Utils;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ChiaPlotStatusLib.Logic.Models
{
public class CPPlotLogReadable: PlotLogReadable
{
public string CurrentPhasePart { get; set; } = "";
public string P3Entries { get; set; } = "";
public string P1Table1 { get; set; } = "";
public string P1Table1Found { get; set; } = "";
public string P1Table1Lost { get; set; } = "";
public string P1Table2 { get; set; } = "";
public string P1Table2Found { get; set; } = "";
public string P1Table2Lost { get; set; } = "";
public string P1Table3 { get; set; } = "";
public string P1Table3Found { get; set; } = "";
public string P1Table3Lost { get; set; } = "";
public string P1Table4 { get; set; } = "";
public string P1Table4Found { get; set; } = "";
public string P1Table4Lost { get; set; } = "";
public string P1Table5 { get; set; } = "";
public string P1Table5Found { get; set; } = "";
public string P1Table5Lost { get; set; } = "";
public string P1Table6 { get; set; } = "";
public string P1Table6Found { get; set; } = "";
public string P1Table6Lost { get; set; } = "";
public string P1Table7 { get; set; } = "";
public string P1Table7Found { get; set; } = "";
public string P1Table7Lost { get; set; } = "";
public string P2MaxTableSize { get; set; } = "";
public string P2Table1Scan { get; set; } = "";
public string P2Table2Scan { get; set; } = "";
public string P2Table3Scan { get; set; } = "";
public string P2Table4Scan { get; set; } = "";
public string P2Table5Scan { get; set; } = "";
public string P2Table6Scan { get; set; } = "";
public string P2Table7Scan { get; set; } = "";
public string P2Table1Rewrite { get; set; } = "";
public string P2Table2Rewrite { get; set; } = "";
public string P2Table3Rewrite { get; set; } = "";
public string P2Table4Rewrite { get; set; } = "";
public string P2Table5Rewrite { get; set; } = "";
public string P2Table6Rewrite { get; set; } = "";
public string P2Table7Rewrite { get; set; } = "";
public string P2Table1Lost { get; set; } = "";
public string P2Table2Lost { get; set; } = "";
public string P2Table3Lost { get; set; } = "";
public string P2Table4Lost { get; set; } = "";
public string P2Table5Lost { get; set; } = "";
public string P2Table6Lost { get; set; } = "";
public string P2Table7Lost { get; set; } = "";
public string P31Table2 { get; set; } = "";
public string P31Table2Entries { get; set; } = "";
public string P31Table3 { get; set; } = "";
public string P31Table3Entries { get; set; } = "";
public string P31Table4 { get; set; } = "";
public string P31Table4Entries { get; set; } = "";
public string P31Table5 { get; set; } = "";
public string P31Table5Entries { get; set; } = "";
public string P31Table6 { get; set; } = "";
public string P31Table6Entries { get; set; } = "";
public string P31Table7 { get; set; } = "";
public string P31Table7Entries { get; set; } = "";
public string P32Table1 { get; set; } = "";
public string P32Table1Entries { get; set; } = "";
public string P32Table2 { get; set; } = "";
public string P32Table2Entries { get; set; } = "";
public string P32Table3 { get; set; } = "";
public string P32Table3Entries { get; set; } = "";
public string P32Table4 { get; set; } = "";
public string P32Table4Entries { get; set; } = "";
public string P32Table5 { get; set; } = "";
public string P32Table5Entries { get; set; } = "";
public string P32Table6 { get; set; } = "";
public string P32Table6Entries { get; set; } = "";
public string P32Table7 { get; set; } = "";
public string P32Table7Entries { get; set; } = "";
public CPPlotLogReadable(CPPlotLog cpPlotLog): base(cpPlotLog)
{
this.CurrentBucket = "";
if (cpPlotLog.CurrentTable == 0)
this.CurrentTable = "";
else
this.CurrentTable = cpPlotLog.CurrentTable + "/7";
switch (cpPlotLog.CurrentPhase)
{
case 1:
this.CurrentPhase = "1/5";
this.CurrentPhasePart = cpPlotLog.CurrentPhasePart + "/" + CPPlotLog.P1PARTS;
break;
case 2:
this.CurrentPhase = "2/5";
this.CurrentPhasePart = cpPlotLog.CurrentPhasePart + "/" + CPPlotLog.P2PARTS;
break;
case 3:
this.CurrentPhase = "3/5";
this.CurrentPhasePart = cpPlotLog.CurrentPhasePart + "/" + CPPlotLog.P3PARTS;
break;
case 4:
this.CurrentPhase = "4/5";
this.CurrentPhasePart = cpPlotLog.CurrentPhasePart + "/" + CPPlotLog.P4PARTS;
break;
case 5:
this.CurrentPhase = "5/5";
this.CurrentPhasePart = "";
break;
case 6:
this.CurrentPhase = "";
this.CurrentPhasePart = "";
break;
}
}
}
}
================================================
FILE: ChiaPlotStatusLib/Logic/Models/Columns.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace ChiaPlotStatusLib.Logic.Models
{
/**
* This is code for the GUI, but needs to here to be part of Settings.
* Well, it could be used to render an ascii table / cli ui, too.
*/
public class Columns
{
public List Order { get; set; } = new();
public Dictionary Widths { get; set; } = new();
public Columns() { }
public int IndexOf(string columnName)
{
for (int i = 0; i < Order.Count; i++)
if (columnName == Order[i])
return i;
return -1;
}
public void FixAddedAndRemovedColumns() {
var defaultColumns = Default();
// add columns that are new in this release
foreach (var defaultCol in defaultColumns.Order)
if (!this.Order.Contains(defaultCol))
this.Order.Add(defaultCol);
// remove saved columns that were removed in this release
List colsToRemove = new();
foreach (var col in this.Order)
if (!defaultColumns.Order.Contains(col))
colsToRemove.Add(col);
foreach (var col in colsToRemove)
this.Order.Remove(col);
}
public static Columns Default()
{
Columns columns = new();
columns.Order.Add("Note");
columns.Order.Add("Tmp1Drive");
columns.Order.Add("Tmp2Drive");
columns.Order.Add("DestDrive");
columns.Order.Add("StartDate");
columns.Order.Add("FinishDate");
columns.Order.Add("Health");
columns.Order.Add("Errors");
columns.Order.Add("Progress");
columns.Order.Add("ETA");
columns.Order.Add("TimeRemaining");
columns.Order.Add("RunTimeSeconds");
columns.Order.Add("CurrentPhase");
columns.Order.Add("CurrentTable");
columns.Order.Add("CurrentBucket");
columns.Order.Add("Phase1Seconds");
columns.Order.Add("Phase1Cpu");
columns.Order.Add("Phase2Seconds");
columns.Order.Add("Phase2Cpu");
columns.Order.Add("Phase3Seconds");
columns.Order.Add("Phase3Cpu");
columns.Order.Add("Phase4Seconds");
columns.Order.Add("Phase4Cpu");
columns.Order.Add("CopyTimeSeconds");
columns.Order.Add("TotalSeconds");
columns.Order.Add("Buffer");
columns.Order.Add("Buckets");
columns.Order.Add("Threads");
columns.Order.Add("LogFolder");
columns.Order.Add("LogFile");
columns.Order.Add("PlaceInLogFile");
columns.Order.Add("PID");
columns.Order.Add("ApproximateWorkingSpace");
columns.Order.Add("FinalFileSize");
columns.Order.Add("LastLogLine");
columns.Order.Add("PoolPuzzleHash");
return columns;
}
}
}
================================================
FILE: ChiaPlotStatusLib/Logic/Models/Filter.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ChiaPlotStatus.Logic.Models
{
public class Filter
{
public bool HideFinished { get; set; } = false;
public bool HidePossiblyDead { get; set; } = false;
public bool HideConfirmedDead { get; set; } = false;
public bool HideHealthy { get; set; } = false;
}
}
================================================
FILE: ChiaPlotStatusLib/Logic/Models/Health.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ChiaPlotStatus.Logic.Models
{
public interface HealthIndicator
{
public string Name { get; }
public int SortIndex { get; }
}
public class Healthy : HealthIndicator
{
public string Name { get; } = "Healthy";
public int SortIndex { get; } = 1;
private Healthy() { }
public static Healthy Instance = new();
}
public class TempError : HealthIndicator
{
public string Name { get; } = "TempError";
public int SortIndex { get; } = 2;
private TempError() { }
public static TempError Instance = new();
}
public class Concerning : HealthIndicator
{
public string Name { get; } = "Concerning";
public int SortIndex { get; } = 3;
public float Minutes { get; set; }
public float ExpectedMinutes { get; set; }
public Concerning(float minutes, float expectedMinutes) {
this.Minutes = minutes;
this.ExpectedMinutes = expectedMinutes;
}
}
public class PossiblyDead : HealthIndicator
{
public string Name { get; } = "PossiblyDead";
public int SortIndex { get; } = 4;
public float Minutes { get; set; }
public float ExpectedMinutes { get; set; }
public PossiblyDead(float minutes, float expectedMinutes)
{
this.Minutes = minutes;
this.ExpectedMinutes = expectedMinutes;
}
}
public class ConfirmedDead : HealthIndicator
{
public string Name { get; } = "ConfirmedDead";
public int SortIndex { get; } = 5;
public bool Manual { get; set; }
public ConfirmedDead(bool manual)
{
this.Manual = manual;
}
}
/**
* // apparently csharp cannot do this? Which lang did tho? Scala?
* public enum Health
{
HEALTHY,
TEMP_ERROR,
CONCERNING(float minutes),
POSSIBLY_DEAD(float minutes),
CONFIRMED_DEAD(bool manual)
}
*/
}
================================================
FILE: ChiaPlotStatusLib/Logic/Models/MarkOfDeath.cs
================================================
using ChiaPlotStatusLib.Logic.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ChiaPlotStatus.Logic.Models
{
/**
* Manually marks a PlogLog as dead so it can be hidden
*/
public class MarkOfDeath
{
public string LogFolder { get; set; }
public string LogFile { get; set; }
public int PlaceInLogFile { get; set; }
public DateTime? DiedAt { get; set; }
public MarkOfDeath() { }
public MarkOfDeath(PlotLogReadable plotLogReadable) {
this.LogFolder = plotLogReadable.LogFolder;
this.LogFile = plotLogReadable.LogFile;
this.PlaceInLogFile = int.Parse(plotLogReadable.PlaceInLogFile.Split("/")[0]);
// here and not in field definition to stay compatible to pre DiedAt versions
this.DiedAt = DateTime.Now;
}
public MarkOfDeath(CPPlotLogReadable plotLogReadable)
{
this.LogFolder = plotLogReadable.LogFolder;
this.LogFile = plotLogReadable.LogFile;
this.PlaceInLogFile = 1;
this.DiedAt = DateTime.Now;
}
public bool IsMatch(PlotLog plotLog)
{
if (!string.Equals(this.LogFolder, plotLog.LogFolder)) return false;
string logFileName = plotLog.LogFile.Substring(plotLog.LogFile.LastIndexOf(Path.DirectorySeparatorChar) + 1);
if (!string.Equals(this.LogFile, logFileName)) return false;
if (this.PlaceInLogFile != plotLog.PlaceInLogFile) return false;
return true;
}
public bool IsMatch(PlotLogReadable plotLog)
{
if (!string.Equals(this.LogFolder, plotLog.LogFolder)) return false;
if (!string.Equals(this.LogFile, plotLog.LogFile)) return false;
if (!string.Equals(this.PlaceInLogFile, plotLog.PlaceInLogFile)) return false;
return true;
}
public bool IsMatch(CPPlotLog plotLog)
{
if (!string.Equals(this.LogFolder, plotLog.LogFolder)) return false;
string logFileName = plotLog.LogFile.Substring(plotLog.LogFile.LastIndexOf(Path.DirectorySeparatorChar) + 1);
if (!string.Equals(this.LogFile, logFileName)) return false;
return true;
}
public bool IsMatch(CPPlotLogReadable plotLog)
{
if (!string.Equals(this.LogFolder, plotLog.LogFolder)) return false;
if (!string.Equals(this.LogFile, plotLog.LogFile)) return false;
return true;
}
public override bool Equals(object? obj)
{
return obj is MarkOfDeath death &&
LogFolder == death.LogFolder &&
LogFile == death.LogFile &&
PlaceInLogFile == death.PlaceInLogFile;
}
public override int GetHashCode()
{
return HashCode.Combine(LogFolder, LogFile, PlaceInLogFile);
}
}
}
================================================
FILE: ChiaPlotStatusLib/Logic/Models/Note.cs
================================================
using ChiaPlotStatus;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ChiaPlotStatusLib.Logic.Models
{
/**
* A little notice or a collection of tags you can attach to a plot log
* It affects all plot logs in the same log file, which allows to add the
* GUI Plotter Queue Name to Chia Plot Status.
*/
public class Note
{
public string LogFolder { get; set; }
public string LogFile { get; set; }
public string text { get; set; }
public Note() { }
public Note(PlotLogReadable plotLogReadable)
{
this.LogFolder = plotLogReadable.LogFolder;
this.LogFile = plotLogReadable.LogFile;
this.text = plotLogReadable.Note;
}
public bool IsMatch(PlotLog plotLog)
{
if (!string.Equals(this.LogFolder, plotLog.LogFolder)) return false;
string logFileName = plotLog.LogFile.Substring(plotLog.LogFile.LastIndexOf(Path.DirectorySeparatorChar) + 1);
if (!string.Equals(this.LogFile, logFileName)) return false;
return true;
}
public bool IsMatch(PlotLogReadable plotLog)
{
if (!string.Equals(this.LogFolder, plotLog.LogFolder)) return false;
if (!string.Equals(this.LogFile, plotLog.LogFile)) return false;
return true;
}
public bool IsMatch(CPPlotLog plotLog)
{
if (!string.Equals(this.LogFolder, plotLog.LogFolder)) return false;
string logFileName = plotLog.LogFile.Substring(plotLog.LogFile.LastIndexOf(Path.DirectorySeparatorChar) + 1);
if (!string.Equals(this.LogFile, logFileName)) return false;
return true;
}
public bool IsMatch(CPPlotLogReadable plotLog)
{
if (!string.Equals(this.LogFolder, plotLog.LogFolder)) return false;
if (!string.Equals(this.LogFile, plotLog.LogFile)) return false;
return true;
}
public override bool Equals(object obj)
{
return obj is Note note &&
LogFolder == note.LogFolder &&
LogFile == note.LogFile;
}
public override int GetHashCode()
{
return HashCode.Combine(LogFolder, LogFile);
}
}
}
================================================
FILE: ChiaPlotStatusLib/Logic/Models/PlotLog.cs
================================================
using ChiaPlotStatus.Logic.Models;
using ChiaPlotStatusLib.Logic.Models;
using System;
using System.Diagnostics;
using System.Text.Json.Serialization;
namespace ChiaPlotStatus
{
/**
* Stores informations about a plotting process.
* This is the object shown in the ui table.
*/
public class PlotLog
{
public string UsedPlotter { get; set; } = "chiapos";
public string? Tmp1Drive { get; set; }
public string? Tmp2Drive { get; set; }
public string? DestDrive { get; set; }
public int Errors { get; set; } = 0;
public int PID { get; set; } = 0;
public float Progress { get; set; } = 0;
public int TimeRemaining { get; set; } = 0;
public DateTime? ETA { get; set; }
public int CurrentTable { get; set; } = 0;
// Phase 6 is done
public int CurrentPhase { get; set; } = 0;
public int CurrentBucket { get; set; } = 0;
public int Phase1Table { get; set; } = 0;
public int Phase2Table { get; set; } = 0;
public int Phase3Table { get; set; } = 0;
public int Phase1Seconds { get; set; } = 0;
public int Phase2Seconds { get; set; } = 0;
public int Phase3Seconds { get; set; } = 0;
public int Phase4Seconds { get; set; } = 0;
public double Phase1Cpu { get; set; } = 0;
public double Phase2Cpu { get; set; } = 0;
public double Phase3Cpu { get; set; } = 0;
public double Phase4Cpu { get; set; } = 0;
public int CopyTimeSeconds { get; set; } = 0;
public int TotalSeconds { get; set; } = 0;
public int PlotSize { get; set; } = 0;
public int Threads { get; set; } = 0;
public int Buffer { get; set; } = 0;
public int Buckets { get; set; } = 0;
public DateTime? StartDate { get; set; }
public DateTime? FinishDate { get; set; }
public string PlotName { get; set; } = "";
public string LogFolder { get; set; } = "";
public string LogFile { get; set; } = "";
public string ApproximateWorkingSpace { get; set; } = "";
public string FinalFileSize { get; set; } = "";
public DateTime? FileLastWritten { get; set; }
[JsonIgnore]
public HealthIndicator Health { get; set; } = Healthy.Instance;
public bool IsLastInLogFile { get; set; } = true;
public bool IsLastLineTempError { get; set; } = false;
public int QueueSize { get; set; } = 1;
public int PlaceInLogFile { get; set; } = 1;
public int RunTimeSeconds { get; set; } = 0;
public bool CaughtPlottingError { get; set; } = false;
public string LastLogLine { get; set; } = "";
public string Note { get; set; } = "Notice / Tags";
public string PoolPuzzleHash { get; set; } = "";
public virtual void UpdateProgress()
{
float part = 0;
// 22 parts total:
// 7 tables in phase 1
// 7 tables in phase 2
// 7 tables in phase 3
// 1 phase 4
// 1 phase 5
float subpart = 0;
switch(CurrentPhase)
{
case 6:
part = 23;
break;
case 5:
part = 22;
break;
case 4:
part = 20;
subpart = (float)this.CurrentBucket / this.Buckets;
break;
case 3:
int totalTablesIn3 = 7;
part = 20 - (totalTablesIn3 - Phase3Table);
subpart = (float)CurrentBucket / Buckets;
break;
case 2:
part = 14 - Phase2Table;
subpart = (float)CurrentBucket / Buckets;
break;
case 1:
int totalTablesIn1 = 7;
part = 7 - (totalTablesIn1 - Phase1Table) - 1;
subpart = (float)CurrentBucket / Buckets;
break;
}
Progress = (part + subpart) / 23 * 100;
if (Double.IsNaN(Progress))
Progress = 0;
}
/**
* Run UpdateHealth before calling
*/
public virtual bool IsRunning()
{
switch (Health)
{
case Healthy:
case Concerning:
case TempError:
case PossiblyDead:
return CurrentPhase != 6;
default:
return false;
}
}
public virtual void UpdateEta(PlottingStatistics stats)
{
this.TimeRemaining = 0;
if (this.Buckets == 0)
{
// log too short to know anything yet
this.ETA = null;
this.TimeRemaining = 0;
return;
}
if (CurrentPhase == 6)
{
this.TimeRemaining = 0;
}
if (CurrentPhase <= 5)
{
float factor = 1;
if (CurrentPhase == 5)
{
DateTime copyStart = ((DateTime)this.StartDate)
.AddSeconds(this.Phase1Seconds)
.AddSeconds(this.Phase2Seconds)
.AddSeconds(this.Phase3Seconds)
.AddSeconds(this.Phase4Seconds);
int elapsed = (int)(DateTime.Now - copyStart).TotalSeconds;
factor = (float)1 - (float)((float)elapsed / stats.CopyTimeAvgTimeNeed);
if (factor < 0.01f)
factor = 0.01f;
}
this.TimeRemaining += (int)(factor * (float)stats.CopyTimeAvgTimeNeed);
}
if (CurrentPhase <= 4)
{
float factor = 1;
if (CurrentPhase == 4)
{
factor = (float)1 - ((float)((float)this.CurrentBucket / this.Buckets));
}
this.TimeRemaining += (int)(factor * stats.Phase4AvgTimeNeed);
}
if (CurrentPhase <= 3)
{
float factor = 1;
if (CurrentPhase == 3)
{
factor = (float)1 - ((float)(((float)this.Phase3Table - 1) + ((float)this.CurrentBucket / this.Buckets)) / 7);
}
this.TimeRemaining += (int)(factor * stats.Phase3AvgTimeNeed);
}
if (CurrentPhase <= 2)
{
float factor = 1;
if (CurrentPhase == 2)
{
factor = (float)1 - ((float)((float) 7 - this.Phase2Table) / 7);
}
this.TimeRemaining += (int)(factor * stats.Phase2AvgTimeNeed);
}
if (CurrentPhase == 1)
{
var factor = (float)1 - ((float)(((float)this.Phase3Table - 1) + ((float)this.CurrentBucket / this.Buckets)) / 7);
this.TimeRemaining += (int)(factor * stats.Phase2AvgTimeNeed);
}
if (this.TimeRemaining > 0)
{
DateTime dt = DateTime.Now;
dt = dt.AddSeconds(this.TimeRemaining);
this.ETA = dt;
}
else
this.ETA = null;
}
public virtual void UpdateHealth(PlottingStatistics stats)
{
int lastModifiedAtWarningThreashold = 0;
int lastModifiedAtErrorThreashold = 0;
switch (this.CurrentPhase)
{
case 1:
if (this.CurrentTable == 1)
lastModifiedAtWarningThreashold = (int)(((float)stats.Phase1AvgTimeNeed / 60 / 7) * 3);
else
lastModifiedAtWarningThreashold = (int)(((float)stats.Phase1AvgTimeNeed / 60 / 7 / this.Buckets) * 3);
if (lastModifiedAtWarningThreashold == 0)
lastModifiedAtWarningThreashold = 15;
break;
case 2:
lastModifiedAtWarningThreashold = (int)((float)stats.Phase2AvgTimeNeed / 60 / 7 * 3);
if (lastModifiedAtWarningThreashold == 0)
lastModifiedAtWarningThreashold = 10;
break;
case 3:
lastModifiedAtWarningThreashold = (int)(((float)stats.Phase3AvgTimeNeed / 60 / 7 / this.Buckets) * 3);
if (lastModifiedAtWarningThreashold == 0)
lastModifiedAtWarningThreashold = 15;
break;
case 4:
lastModifiedAtWarningThreashold = (int)(((float)stats.Phase3AvgTimeNeed / 60 / this.Buckets) * 3);
if (lastModifiedAtWarningThreashold == 0)
lastModifiedAtWarningThreashold = 15;
if (this.CurrentBucket == this.Buckets)
lastModifiedAtWarningThreashold = 20;
break;
case 5:
case 6:
Health = Healthy.Instance;
return;
}
if (lastModifiedAtWarningThreashold < 10)
lastModifiedAtWarningThreashold = 10;
lastModifiedAtWarningThreashold = (int)((float)lastModifiedAtWarningThreashold * 1.2f);
lastModifiedAtErrorThreashold = lastModifiedAtWarningThreashold * 4;
// Debug.WriteLine("lastModifiedAtWarningThreashold: " + lastModifiedAtWarningThreashold);
// Debug.WriteLine("lastModifiedAtErrorThreashold: " + lastModifiedAtErrorThreashold);
bool notLastAndNotDone = this.Progress < 100d && !this.IsLastInLogFile;
bool lastModfiedAtWarning = false;
bool lastModfiedAtError = false;
bool lastLineError = this.IsLastLineTempError;
TimeSpan? fileLastWrittenAge = null;
int lastWriteMinutesAgo = 0;
if (this.FileLastWritten != null)
{
fileLastWrittenAge = DateTime.Now - ((DateTime)this.FileLastWritten);
if (((TimeSpan)fileLastWrittenAge).TotalMinutes > (lastModifiedAtWarningThreashold + 1))
lastModfiedAtWarning = true;
if (((TimeSpan)fileLastWrittenAge).TotalMinutes > (lastModifiedAtErrorThreashold + 1))
lastModfiedAtError = true;
lastWriteMinutesAgo = (int)((TimeSpan)fileLastWrittenAge).TotalMinutes;
}
// manual is set to false for now, it will be overwritten in ChiaPlotStatus.cs if necessary
bool manual = false;
if (notLastAndNotDone || this.CaughtPlottingError)
this.Health = new ConfirmedDead(manual);
else if (lastModfiedAtError)
this.Health = new PossiblyDead(lastWriteMinutesAgo, lastModifiedAtWarningThreashold);
else if (lastModfiedAtWarning)
this.Health = new Concerning(lastWriteMinutesAgo, lastModifiedAtWarningThreashold);
else if (lastLineError)
this.Health = TempError.Instance;
else
this.Health = Healthy.Instance;
}
}
}
================================================
FILE: ChiaPlotStatusLib/Logic/Models/PlotLogReadable.cs
================================================
using ChiaPlotStatus.Logic.Models;
using ChiaPlotStatusLib.Logic.Models;
using ChiaPlotStatusLib.Logic.Utils;
using System;
using System.Diagnostics;
using System.IO;
namespace ChiaPlotStatus
{
/**
* Stores readable informations about a plotting process.
* This is the object shown in the ui table.
*/
public class PlotLogReadable
{
public string Tmp1Drive { get; set; } = "";
public string Tmp2Drive { get; set; } = "";
public string DestDrive { get; set; }
public string Errors { get; set; } = "";
public string PID { get; set; } = "";
public string Progress { get; set; } = "";
public string TimeRemaining { get; set; } = "";
public string ETA { get; set; } = "";
public string CurrentTable { get; set; } = "";
public string CurrentBucket { get; set; } = "";
public string CurrentPhase { get; set; } = "";
public string Phase1Cpu { get; set; } = "";
public string Phase2Cpu { get; set; } = "";
public string Phase3Cpu { get; set; } = "";
public string Phase4Cpu { get; set; } = "";
public string Phase1Seconds { get; set; } = "";
public string Phase2Seconds { get; set; } = "";
public string Phase3Seconds { get; set; } = "";
public string Phase4Seconds { get; set; } = "";
public string CopyTimeSeconds { get; set; } = "";
public string TotalSeconds { get; set; } = "";
public string PlotSize { get; set; } = "";
public string Threads { get; set; } = "";
public string Buffer { get; set; } = "";
public string Buckets { get; set; } = "";
public string StartDate { get; set; } = "";
public string FinishDate { get; set; } = "";
public string PlotName { get; set; } = "";
public string LogFolder { get; set; } = "";
public string LogFile { get; set; } = "";
public string ApproximateWorkingSpace { get; set; } = "";
public string FinalFileSize { get; set; } = "";
public string Health { get; set; } = "";
public string PlaceInLogFile { get; set; } = "";
public string RunTimeSeconds { get; set; } = "";
public string LastLogLine { get; set; } = "";
public bool IsSelected { get; set; } = false;
public string Note { get; set; } = "";
public string PoolPuzzleHash { get; set; } = "";
public PlotLogReadable(PlotLog plotLog)
{
this.Tmp1Drive = plotLog.Tmp1Drive;
this.Tmp2Drive = plotLog.Tmp2Drive;
if (plotLog.Errors > 0)
this.Errors = plotLog.Errors.ToString();
this.Progress = string.Format("{0:0.00}", plotLog.Progress) + "%";
if (string.Equals(this.Progress, "NaN%")) this.Progress = "";
switch(plotLog.CurrentPhase)
{
case 1:
this.CurrentPhase = "1/5";
this.CurrentTable = plotLog.Phase1Table + "/7 ↑";
break;
case 2:
this.CurrentPhase = "2/5";
this.CurrentTable = plotLog.Phase2Table + "/7 ↓";
break;
case 3:
this.CurrentPhase = "3/5";
this.CurrentTable = plotLog.Phase3Table + "/7 ↑";
break;
case 4:
this.CurrentPhase = "4/5";
this.CurrentTable = "1/1";
break;
case 5:
this.CurrentPhase = "5/5";
this.CurrentTable = "";
break;
case 6: // done
this.CurrentPhase = "";
this.CurrentTable = "";
break;
}
this.ETA = Formatter.formatDateTime(plotLog.ETA);
this.StartDate = Formatter.formatDateTime(plotLog.StartDate);
this.FinishDate = Formatter.formatDateTime(plotLog.FinishDate);
this.TimeRemaining = Formatter.formatSeconds(plotLog.TimeRemaining, true);
this.Phase1Cpu = Formatter.formatCpuUsage(plotLog.Phase1Cpu);
this.Phase2Cpu = Formatter.formatCpuUsage(plotLog.Phase2Cpu);
this.Phase3Cpu = Formatter.formatCpuUsage(plotLog.Phase3Cpu);
this.Phase4Cpu = Formatter.formatCpuUsage(plotLog.Phase4Cpu);
this.Phase1Seconds = Formatter.formatSeconds(plotLog.Phase1Seconds, true);
this.Phase2Seconds = Formatter.formatSeconds(plotLog.Phase2Seconds, true);
this.Phase3Seconds = Formatter.formatSeconds(plotLog.Phase3Seconds, true);
this.Phase4Seconds = Formatter.formatSeconds(plotLog.Phase4Seconds, false);
this.CopyTimeSeconds = Formatter.formatSeconds(plotLog.CopyTimeSeconds, false);
this.TotalSeconds = Formatter.formatSeconds(plotLog.TotalSeconds, true);
this.RunTimeSeconds = Formatter.formatSeconds(plotLog.RunTimeSeconds, true);
this.ApproximateWorkingSpace = plotLog.ApproximateWorkingSpace;
this.DestDrive = plotLog.DestDrive;
this.FinalFileSize = plotLog.FinalFileSize;
this.Buckets = plotLog.Buckets.ToString();
this.Threads = plotLog.Threads.ToString();
this.Buffer = plotLog.Buffer + " MB";
this.PoolPuzzleHash = plotLog.PoolPuzzleHash;
this.LastLogLine = plotLog.LastLogLine;
if (plotLog.PID != 0)
this.PID = "" + plotLog.PID;
switch (plotLog.CurrentPhase)
{
case 1:
case 3:
case 4:
this.CurrentBucket = plotLog.CurrentBucket + "/" + plotLog.Buckets;
break;
case 2:
case 5:
case 6:
this.CurrentBucket = "";
break;
}
this.PlotName = plotLog.PlotName;
this.LogFolder = plotLog.LogFolder;
this.LogFile = plotLog.LogFile.Substring(plotLog.LogFile.LastIndexOf(Path.DirectorySeparatorChar) + 1);
this.Health = Formatter.formatHealth(plotLog.Health);
this.PlaceInLogFile = plotLog.PlaceInLogFile + "/" + plotLog.QueueSize;
this.Note = plotLog.Note;
}
}
}
================================================
FILE: ChiaPlotStatusLib/Logic/Models/Settings.cs
================================================
using ChiaPlotStatus.Logic.Models;
using ChiaPlotStatusLib.Logic.Models;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace ChiaPlotStatus.Logic.Models
{
public class Settings
{
private string SettingsFile;
public bool? AlwaysDoFullRead { get; set; } = false;
public string? Theme { get; set; } = "Light";
public ObservableCollection LogDirectories { get; set; } = new();
public double? MaxHarvestLookupSeconds { get; set; } = 5d;
public ObservableCollection HarvesterLogDirectories { get; set; } = new();
public Columns Columns { get; set; } = Columns.Default();
public PlottingStatisticsIdRelevanceWeights Weigths { get; set; } = new();
public Filter Filter { get; set; } = new();
public string? SortProperty { get; set; } = "Progress";
public bool? SortAsc { get; set; } = true;
public List? Notes { get; set; } = new();
public List? MarksOfDeath { get; set; } = new();
// no longer supported, but needs to stay or JsonDeserializer is not happy with old config files
public double? FontSize { get; set; } = null;
public Settings()
{
this.SettingsFile = "ChiaPlotStatusSettings";
}
public Settings(string settingsFile)
{
this.SettingsFile = settingsFile;
}
public bool Load()
{
if (File.Exists(this.SettingsFile))
{
string json = File.ReadAllText(this.SettingsFile);
Settings? fromFile = JsonSerializer.Deserialize(json);
if (fromFile != null)
{
if (fromFile.LogDirectories != null)
this.LogDirectories = fromFile.LogDirectories;
if (fromFile.FontSize != null && fromFile.FontSize > 6)
this.FontSize = fromFile.FontSize;
if (fromFile.Theme != null)
this.Theme = fromFile.Theme;
if (fromFile.MarksOfDeath != null)
this.MarksOfDeath = fromFile.MarksOfDeath;
if (fromFile.AlwaysDoFullRead != null)
this.AlwaysDoFullRead = fromFile.AlwaysDoFullRead;
if (fromFile.Filter != null)
this.Filter = fromFile.Filter;
if (fromFile.SortProperty != null)
this.SortProperty = fromFile.SortProperty;
if (fromFile.SortAsc != null)
this.SortAsc = fromFile.SortAsc;
if (fromFile.Weigths != null)
this.Weigths = fromFile.Weigths;
if (fromFile.Notes != null)
this.Notes = fromFile.Notes;
if (fromFile.HarvesterLogDirectories != null)
this.HarvesterLogDirectories = fromFile.HarvesterLogDirectories;
if (fromFile.MaxHarvestLookupSeconds != null)
this.MaxHarvestLookupSeconds = fromFile.MaxHarvestLookupSeconds;
if (fromFile.Columns != null)
{
this.Columns = fromFile.Columns;
this.Columns.FixAddedAndRemovedColumns();
}
return true;
}
}
return false;
}
public void Persist()
{
JsonSerializerOptions options = new JsonSerializerOptions();
options.AllowTrailingCommas = true;
options.MaxDepth = 9999999;
options.DefaultBufferSize = 64 * 1024;
options.WriteIndented = true;
string json = JsonSerializer.Serialize(this, options);
File.WriteAllText(SettingsFile, json);
}
}
}
================================================
FILE: ChiaPlotStatusLib/Logic/Parser/CPPlotLogFileParser.cs
================================================
using ChiaPlotStatus;
using ChiaPlotStatus.Logic.Models;
using ChiaPlotStatusLib.Logic.Models;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace ChiaPlotStatusLib.Logic.Parser
{
class CPPlotLogFileParser
{
private TailLineEmitter TailLineEmitter { get; }
protected List PlotLogs { get; } = new();
public string LogFile;
public string LogFolder;
public bool closed = false;
public bool firstRead = false;
public bool lineRead = false;
public DateTime? lastGrown;
public CPPlotLogFileParser(string path, bool closeOnEndOfFile)
{
this.LogFile = path;
this.LogFolder = path.Substring(0, path.LastIndexOf(Path.DirectorySeparatorChar));
CurrentPlotLog().StartDate = File.GetCreationTime(path);
CurrentPlotLog().LogFile = this.LogFile;
CurrentPlotLog().LogFolder = this.LogFolder;
CurrentPlotLog().CurrentPhase = 1;
this.TailLineEmitter = new TailLineEmitter(path, closeOnEndOfFile, (line) =>
{
lineRead = true;
CurrentPlotLog().LastLogLine = line.Trim();
if (CurrentPlotLog().CurrentTable == 0)
CurrentPlotLog().CurrentTable = 1;
if (!firstRead)
lastGrown = DateTime.Now;
MatchCollection matches;
switch (line)
{
case var _ when copyTimeRG.IsMatch(line):
matches = copyTimeRG.Matches(line);
CurrentPlotLog().CopyTimeSeconds = int.Parse(matches[0].Groups[1].Value);
break;
case var _ when poolPuzzleHashRg.IsMatch(line):
matches = poolPuzzleHashRg.Matches(line);
CurrentPlotLog().PoolPuzzleHash = matches[0].Groups[1].Value;
break;
case var _ when threadsRg.IsMatch(line):
matches = threadsRg.Matches(line);
if (CurrentPlotLog().Threads != 0)
NextPlotLog();
CurrentPlotLog().Threads = int.Parse(matches[0].Groups[1].Value);
CurrentPlotLog().EnterPhase(1);
break;
case var _ when bucketsRg.IsMatch(line):
matches = bucketsRg.Matches(line);
CurrentPlotLog().Buckets = int.Parse(matches[0].Groups[1].Value);
break;
case var _ when buckets2Rg.IsMatch(line):
matches = buckets2Rg.Matches(line);
CurrentPlotLog().Buckets = int.Parse(matches[0].Groups[1].Value);
break;
case var _ when plotNameRg.IsMatch(line):
matches = plotNameRg.Matches(line);
CurrentPlotLog().PlotName = matches[0].Groups[1].Value;
break;
case var _ when tmp1Rg.IsMatch(line):
matches = tmp1Rg.Matches(line);
CurrentPlotLog().Tmp1Drive = matches[0].Groups[1].Value;
break;
case var _ when tmp2Rg.IsMatch(line):
matches = tmp2Rg.Matches(line);
CurrentPlotLog().Tmp2Drive = matches[0].Groups[1].Value;
break;
case var _ when destRg.IsMatch(line):
matches = destRg.Matches(line);
CurrentPlotLog().DestDrive = matches[0].Groups[1].Value;
break;
case var _ when pidRg.IsMatch(line):
matches = pidRg.Matches(line);
CurrentPlotLog().PID = int.Parse(matches[0].Groups[1].Value);
break;
case var _ when lossRg.IsMatch(line):
matches = lossRg.Matches(line);
long lost = long.Parse(matches[0].Groups[1].Value);
switch (CurrentPlotLog().CurrentTable)
{
case 1:
CurrentPlotLog().P1Table1Lost = lost;
break;
case 2:
CurrentPlotLog().P1Table2Lost = lost;
break;
case 3:
CurrentPlotLog().P1Table3Lost = lost;
break;
case 4:
CurrentPlotLog().P1Table4Lost = lost;
break;
case 5:
CurrentPlotLog().P1Table5Lost = lost;
break;
case 6:
CurrentPlotLog().P1Table6Lost = lost;
break;
case 7:
CurrentPlotLog().P1Table7Lost = lost;
break;
}
if (CurrentPlotLog().P1Table1Lost == 0)
CurrentPlotLog().P1Table1Lost = lost;
else if (CurrentPlotLog().P1Table2Lost == 0)
CurrentPlotLog().P1Table2Lost = lost;
else if (CurrentPlotLog().P1Table3Lost == 0)
CurrentPlotLog().P1Table3Lost = lost;
else if (CurrentPlotLog().P1Table4Lost == 0)
CurrentPlotLog().P1Table4Lost = lost;
else if (CurrentPlotLog().P1Table5Lost == 0)
CurrentPlotLog().P1Table5Lost = lost;
else if (CurrentPlotLog().P1Table6Lost == 0)
CurrentPlotLog().P1Table6Lost = lost;
else if (CurrentPlotLog().P1Table7Lost == 0)
CurrentPlotLog().P1Table7Lost = lost;
CurrentPlotLog().NextPhasePart();
break;
case var _ when p1table1Rg.IsMatch(line):
matches = p1table1Rg.Matches(line);
CurrentPlotLog().P1Table1 = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().CurrentTable = 2;
CurrentPlotLog().NextPhasePart();
break;
case var _ when p1table2Rg.IsMatch(line):
matches = p1table2Rg.Matches(line);
CurrentPlotLog().P1Table2 = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().P1Table2Found = long.Parse(matches[0].Groups[2].Value);
CurrentPlotLog().CurrentTable = 3;
CurrentPlotLog().NextPhasePart();
break;
case var _ when p1table3Rg.IsMatch(line):
matches = p1table3Rg.Matches(line);
CurrentPlotLog().P1Table3 = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().P1Table3Found = long.Parse(matches[0].Groups[2].Value);
CurrentPlotLog().CurrentTable = 4;
CurrentPlotLog().NextPhasePart();
break;
case var _ when p1table4Rg.IsMatch(line):
matches = p1table4Rg.Matches(line);
CurrentPlotLog().P1Table4 = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().P1Table4Found = long.Parse(matches[0].Groups[2].Value);
CurrentPlotLog().CurrentTable = 5;
CurrentPlotLog().NextPhasePart();
break;
case var _ when p1table5Rg.IsMatch(line):
matches = p1table5Rg.Matches(line);
CurrentPlotLog().P1Table5 = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().P1Table5Found = long.Parse(matches[0].Groups[2].Value);
CurrentPlotLog().CurrentTable = 6;
CurrentPlotLog().NextPhasePart();
break;
case var _ when p1table6Rg.IsMatch(line):
matches = p1table6Rg.Matches(line);
CurrentPlotLog().P1Table6 = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().P1Table6Found = long.Parse(matches[0].Groups[2].Value);
CurrentPlotLog().CurrentTable = 7;
CurrentPlotLog().NextPhasePart();
break;
case var _ when p1table7Rg.IsMatch(line):
matches = p1table7Rg.Matches(line);
CurrentPlotLog().P1Table7 = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().P1Table7Found = long.Parse(matches[0].Groups[2].Value);
CurrentPlotLog().NextPhasePart();
break;
case var _ when p1timeRg.IsMatch(line):
matches = p1timeRg.Matches(line);
CurrentPlotLog().P1 = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().Phase1Seconds = (int)float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().EnterPhase(2);
break;
case var _ when p2timeRg.IsMatch(line):
matches = p2timeRg.Matches(line);
CurrentPlotLog().P2 = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().Phase2Seconds = (int)float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().EnterPhase(3);
break;
case var _ when p3timeRg.IsMatch(line):
matches = p3timeRg.Matches(line);
CurrentPlotLog().P3 = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().Phase3Seconds = (int)float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().P3Entries = long.Parse(matches[0].Groups[2].Value);
CurrentPlotLog().EnterPhase(4);
break;
case var _ when p4timeRg.IsMatch(line):
matches = p4timeRg.Matches(line);
CurrentPlotLog().P4 = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().Phase4Seconds = (int)float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().FinalFileSize = (long.Parse(matches[0].Groups[2].Value) / 1024 / 1024) + " MB";
CurrentPlotLog().EnterPhase(5);
break;
case var _ when totaltimeRg.IsMatch(line):
matches = totaltimeRg.Matches(line);
CurrentPlotLog().TotalSeconds = (int)float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().FinishDate = CurrentPlotLog().StartDate.Value.AddSeconds(CurrentPlotLog().TotalSeconds);
if (CurrentPlotLog().FinishDate > DateTime.Now)
{
using (var file = File.AppendText(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + Path.DirectorySeparatorChar +
"ChiaPlotStatus.chia-plotter-finishdate.log"))
{
file.WriteLine((DateTime.Now - CurrentPlotLog().FinishDate.Value).TotalSeconds + "\n");
}
CurrentPlotLog().FinishDate = DateTime.Now;
}
CurrentPlotLog().EnterPhase(6);
// TODO:
//if (CurrentPlotLog().QueueSize == 1 || CurrentPlotLog().QueueSize == 0)
// this.Close();
break;
// phase 2
case var _ when p2MaxTableSize.IsMatch(line):
matches = p2MaxTableSize.Matches(line);
CurrentPlotLog().P2MaxTableSize = long.Parse(matches[0].Groups[1].Value);
CurrentPlotLog().NextPhasePart();
break;
case var _ when p2table1scanRg.IsMatch(line):
matches = p2table1scanRg.Matches(line);
CurrentPlotLog().P2Table1Scan = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().CurrentTable = 1;
CurrentPlotLog().NextPhasePart();
break;
case var _ when p2table1rewriteRg.IsMatch(line):
matches = p2table1rewriteRg.Matches(line);
CurrentPlotLog().P2Table1Rewrite = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().P2Table2Lost = long.Parse(matches[0].Groups[2].Value);
CurrentPlotLog().CurrentTable = 1;
CurrentPlotLog().NextPhasePart();
break;
case var _ when p2table2scanRg.IsMatch(line):
matches = p2table2scanRg.Matches(line);
CurrentPlotLog().P2Table2Scan = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().CurrentTable = 2;
CurrentPlotLog().NextPhasePart();
break;
case var _ when p2table2rewriteRg.IsMatch(line):
matches = p2table2rewriteRg.Matches(line);
CurrentPlotLog().P2Table2Rewrite = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().P2Table2Lost = long.Parse(matches[0].Groups[2].Value);
CurrentPlotLog().CurrentTable = 1;
CurrentPlotLog().NextPhasePart();
break;
case var _ when p2table3scanRg.IsMatch(line):
matches = p2table3scanRg.Matches(line);
CurrentPlotLog().P2Table3Scan = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().CurrentTable = 3;
CurrentPlotLog().NextPhasePart();
break;
case var _ when p2table3rewriteRg.IsMatch(line):
matches = p2table3rewriteRg.Matches(line);
CurrentPlotLog().P2Table3Rewrite = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().P2Table3Lost = long.Parse(matches[0].Groups[2].Value);
CurrentPlotLog().CurrentTable = 2;
CurrentPlotLog().NextPhasePart();
break;
case var _ when p2table4scanRg.IsMatch(line):
matches = p2table4scanRg.Matches(line);
CurrentPlotLog().P2Table4Scan = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().CurrentTable = 4;
CurrentPlotLog().NextPhasePart();
break;
case var _ when p2table4rewriteRg.IsMatch(line):
matches = p2table4rewriteRg.Matches(line);
CurrentPlotLog().P2Table4Rewrite = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().P2Table4Lost = long.Parse(matches[0].Groups[2].Value);
CurrentPlotLog().CurrentTable = 3;
CurrentPlotLog().NextPhasePart();
break;
case var _ when p2table5scanRg.IsMatch(line):
matches = p2table5scanRg.Matches(line);
CurrentPlotLog().P2Table5Scan = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().CurrentTable = 5;
CurrentPlotLog().NextPhasePart();
break;
case var _ when p2table5rewriteRg.IsMatch(line):
matches = p2table5rewriteRg.Matches(line);
CurrentPlotLog().P2Table5Rewrite = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().P2Table5Lost = long.Parse(matches[0].Groups[2].Value);
CurrentPlotLog().CurrentTable = 4;
CurrentPlotLog().NextPhasePart();
break;
case var _ when p2table6scanRg.IsMatch(line):
matches = p2table6scanRg.Matches(line);
CurrentPlotLog().P2Table6Scan = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().CurrentTable = 6;
CurrentPlotLog().NextPhasePart();
break;
case var _ when p2table6rewriteRg.IsMatch(line):
matches = p2table6rewriteRg.Matches(line);
CurrentPlotLog().P2Table6Rewrite = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().P2Table6Lost = long.Parse(matches[0].Groups[2].Value);
CurrentPlotLog().CurrentTable = 5;
CurrentPlotLog().NextPhasePart();
break;
case var _ when p2table7scanRg.IsMatch(line):
matches = p2table7scanRg.Matches(line);
CurrentPlotLog().P2Table7Scan = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().CurrentTable = 7;
CurrentPlotLog().NextPhasePart();
break;
case var _ when p2table7rewriteRg.IsMatch(line):
matches = p2table7rewriteRg.Matches(line);
CurrentPlotLog().P2Table7Rewrite = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().P2Table7Lost = long.Parse(matches[0].Groups[2].Value);
CurrentPlotLog().CurrentTable = 6;
CurrentPlotLog().NextPhasePart();
break;
// phase 3
case var _ when p31table2Rg.IsMatch(line):
matches = p31table2Rg.Matches(line);
CurrentPlotLog().P31Table2 = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().P31Table2Entries = long.Parse(matches[0].Groups[2].Value);
CurrentPlotLog().NextPhasePart();
CurrentPlotLog().CurrentTable = 3;
break;
case var _ when p31table3Rg.IsMatch(line):
matches = p31table3Rg.Matches(line);
CurrentPlotLog().P31Table3 = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().P31Table3Entries = long.Parse(matches[0].Groups[2].Value);
CurrentPlotLog().NextPhasePart();
CurrentPlotLog().CurrentTable = 4;
break;
case var _ when p31table4Rg.IsMatch(line):
matches = p31table4Rg.Matches(line);
CurrentPlotLog().P31Table4 = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().P31Table4Entries = long.Parse(matches[0].Groups[2].Value);
CurrentPlotLog().NextPhasePart();
CurrentPlotLog().CurrentTable = 5;
break;
case var _ when p31table5Rg.IsMatch(line):
matches = p31table5Rg.Matches(line);
CurrentPlotLog().P31Table5 = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().P31Table5Entries = long.Parse(matches[0].Groups[2].Value);
CurrentPlotLog().NextPhasePart();
CurrentPlotLog().CurrentTable = 6;
break;
case var _ when p31table6Rg.IsMatch(line):
matches = p31table6Rg.Matches(line);
CurrentPlotLog().P31Table6 = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().P31Table6Entries = long.Parse(matches[0].Groups[2].Value);
CurrentPlotLog().NextPhasePart();
CurrentPlotLog().CurrentTable = 7;
break;
case var _ when p31table7Rg.IsMatch(line):
matches = p31table7Rg.Matches(line);
CurrentPlotLog().P31Table7 = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().P31Table7Entries = long.Parse(matches[0].Groups[2].Value);
CurrentPlotLog().NextPhasePart();
CurrentPlotLog().CurrentTable = 1;
break;
case var _ when p31table2Rg.IsMatch(line):
matches = p31table2Rg.Matches(line);
CurrentPlotLog().P31Table2 = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().P31Table2Entries = long.Parse(matches[0].Groups[2].Value);
CurrentPlotLog().NextPhasePart();
break;
case var _ when p32table2Rg.IsMatch(line):
matches = p32table2Rg.Matches(line);
CurrentPlotLog().P32Table2 = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().P32Table2Entries = long.Parse(matches[0].Groups[2].Value);
CurrentPlotLog().NextPhasePart();
break;
case var _ when p32table3Rg.IsMatch(line):
matches = p32table3Rg.Matches(line);
CurrentPlotLog().P32Table3 = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().P32Table3Entries = long.Parse(matches[0].Groups[2].Value);
CurrentPlotLog().NextPhasePart();
break;
case var _ when p32table4Rg.IsMatch(line):
matches = p32table4Rg.Matches(line);
CurrentPlotLog().P32Table4 = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().P32Table4Entries = long.Parse(matches[0].Groups[2].Value);
CurrentPlotLog().NextPhasePart();
break;
case var _ when p32table5Rg.IsMatch(line):
matches = p32table5Rg.Matches(line);
CurrentPlotLog().P32Table5 = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().P32Table5Entries = long.Parse(matches[0].Groups[2].Value);
CurrentPlotLog().NextPhasePart();
break;
case var _ when p32table6Rg.IsMatch(line):
matches = p32table6Rg.Matches(line);
CurrentPlotLog().P32Table6 = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().P32Table6Entries = long.Parse(matches[0].Groups[2].Value);
CurrentPlotLog().NextPhasePart();
break;
case var _ when p32table7Rg.IsMatch(line):
matches = p32table7Rg.Matches(line);
CurrentPlotLog().P32Table7 = float.Parse(matches[0].Groups[1].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().P32Table7Entries = long.Parse(matches[0].Groups[2].Value);
CurrentPlotLog().NextPhasePart();
CurrentPlotLog().CurrentTable = 1;
break;
case var _ when p4start1Rg.IsMatch(line):
matches = p4start1Rg.Matches(line);
CurrentPlotLog().NextPhasePart();
CurrentPlotLog().CurrentTable = 1;
break;
case var _ when p4finish1Rg.IsMatch(line):
matches = p4finish1Rg.Matches(line);
CurrentPlotLog().NextPhasePart();
CurrentPlotLog().CurrentTable = 2;
break;
case var _ when p4start2Rg.IsMatch(line):
matches = p4start2Rg.Matches(line);
CurrentPlotLog().NextPhasePart();
CurrentPlotLog().CurrentTable = 2;
break;
case var _ when p4finish2Rg.IsMatch(line):
matches = p4finish2Rg.Matches(line);
CurrentPlotLog().NextPhasePart();
CurrentPlotLog().CurrentTable = 1;
break;
// errors
case var _ when segFaultRg.IsMatch(line):
case var _ when coreDumpRg.IsMatch(line):
case var _ when catchExceptionRg.IsMatch(line):
case var _ when invalidArgumentRg.IsMatch(line):
case var _ when terminateRg.IsMatch(line):
case var _ when failedRg.IsMatch(line):
CurrentPlotLog().CaughtPlottingError = true;
CurrentPlotLog().Health = new ConfirmedDead(false);
break;
}
CurrentPlotLog().UpdateProgress();
});
}
/**
* Can be called as often as needed as it does not reparse what was already processed.
*/
public List ParseCPPlotLog()
{
if (closed)
return PlotLogs;
lineRead = false;
this.TailLineEmitter.ReadMore();
CurrentPlotLog().FileLastWritten = File.GetLastWriteTime(this.LogFile);
if (CurrentPlotLog().FileLastWritten != null && ((DateTime.Now - (DateTime)CurrentPlotLog().FileLastWritten).TotalDays > 1d))
Close();
if (lineRead && !firstRead)
lastGrown = DateTime.Now;
if (lastGrown != null)
CurrentPlotLog().FileLastWritten = lastGrown;
firstRead = false;
return PlotLogs;
}
private CPPlotLog CurrentPlotLog()
{
if (PlotLogs.Count == 0)
PlotLogs.Add(new());
return PlotLogs[PlotLogs.Count - 1];
}
private CPPlotLog NextPlotLog()
{
var oldPlotLog = CurrentPlotLog();
oldPlotLog.IsLastInLogFile = false;
var newPlotLog = new CPPlotLog();
// when plot create --num n is used parameters stay the same
newPlotLog.Buckets = oldPlotLog.Buckets;
newPlotLog.Threads = oldPlotLog.Threads;
newPlotLog.Buffer = oldPlotLog.Buffer;
newPlotLog.Tmp1Drive = oldPlotLog.Tmp1Drive;
newPlotLog.Tmp2Drive = oldPlotLog.Tmp2Drive;
newPlotLog.DestDrive = oldPlotLog.DestDrive;
newPlotLog.LogFile = oldPlotLog.LogFile;
newPlotLog.LogFolder = oldPlotLog.LogFolder;
newPlotLog.PlaceInLogFile = oldPlotLog.PlaceInLogFile + 1;
newPlotLog.QueueSize = oldPlotLog.QueueSize;
newPlotLog.StartDate = oldPlotLog.FinishDate;
PlotLogs.Add(newPlotLog);
return newPlotLog;
}
private void Close()
{
if (closed)
return;
Debug.WriteLine("CPPlotLog " + LogFile + " has not been updated for more than a day. Closing file.");
this.TailLineEmitter.Close();
closed = true;
}
static Regex copyTimeRG = new Regex("Copy to .* finished, took (\\d+).?\\d* sec,", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex poolPuzzleHashRg = new Regex("Pool Puzzle Hash:\\s+(.+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex threadsRg = new Regex("Number of Threads: (\\d+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex bucketsRg = new Regex("Number of Sort Buckets: 2\\^\\d+ \\((\\d+)\\)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex buckets2Rg = new Regex("Number of Buckets P1:\\s*2\\^\\d+ \\((\\d+)\\)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex plotNameRg = new Regex("Plot Name: (.*)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex tmp1Rg = new Regex("Working Directory:\\s+(.*)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex tmp2Rg = new Regex("Working Directory 2:\\s+(.*)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex destRg = new Regex("Final Directory:\\s+(.*)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex pidRg = new Regex("Process ID:\\s+(\\d+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p1table1Rg = new Regex("\\[P1\\] Table 1 took ([0-9.]+) sec", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p1table2Rg = new Regex("\\[P1\\] Table 2 took ([0-9.]+) sec, found (\\d+) matches", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p1table3Rg = new Regex("\\[P1\\] Table 3 took ([0-9.]+) sec, found (\\d+) matches", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p1table4Rg = new Regex("\\[P1\\] Table 4 took ([0-9.]+) sec, found (\\d+) matches", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p1table5Rg = new Regex("\\[P1\\] Table 5 took ([0-9.]+) sec, found (\\d+) matches", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p1table6Rg = new Regex("\\[P1\\] Table 6 took ([0-9.]+) sec, found (\\d+) matches", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p1table7Rg = new Regex("\\[P1\\] Table 7 took ([0-9.]+) sec, found (\\d+) matches", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex lossRg = new Regex("\\[P1\\] Lost (\\d+) matches due to 32-bit overflow.", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p1timeRg = new Regex("Phase 1 took ([0-9.]+) sec", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p2timeRg = new Regex("Phase 2 took ([0-9.]+) sec", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p3timeRg = new Regex("Phase 3 took ([0-9.]+) sec, wrote (\\d+) entries to final plot", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p4timeRg = new Regex("Phase 4 took ([0-9.]+) sec, final plot size is (\\d+) bytes", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex totaltimeRg = new Regex("Total plot creation time was ([0-9.]+) sec", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p2MaxTableSize = new Regex("\\[P2\\] max_table_size = (\\d+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p2table1scanRg = new Regex("\\[P2\\] Table 1 scan took ([0-9.]+) sec", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p2table2scanRg = new Regex("\\[P2\\] Table 2 scan took ([0-9.]+) sec", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p2table3scanRg = new Regex("\\[P2\\] Table 3 scan took ([0-9.]+) sec", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p2table4scanRg = new Regex("\\[P2\\] Table 4 scan took ([0-9.]+) sec", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p2table5scanRg = new Regex("\\[P2\\] Table 5 scan took ([0-9.]+) sec", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p2table6scanRg = new Regex("\\[P2\\] Table 6 scan took ([0-9.]+) sec", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p2table7scanRg = new Regex("\\[P2\\] Table 7 scan took ([0-9.]+) sec", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p2table1rewriteRg = new Regex("\\[P2\\] Table 1 rewrite took ([0-9.]+) sec, dropped (\\d+) entries \\(([0-9.]+) %\\)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p2table2rewriteRg = new Regex("\\[P2\\] Table 2 rewrite took ([0-9.]+) sec, dropped (\\d+) entries \\(([0-9.]+) %\\)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p2table3rewriteRg = new Regex("\\[P2\\] Table 3 rewrite took ([0-9.]+) sec, dropped (\\d+) entries \\(([0-9.]+) %\\)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p2table4rewriteRg = new Regex("\\[P2\\] Table 4 rewrite took ([0-9.]+) sec, dropped (\\d+) entries \\(([0-9.]+) %\\)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p2table5rewriteRg = new Regex("\\[P2\\] Table 5 rewrite took ([0-9.]+) sec, dropped (\\d+) entries \\(([0-9.]+) %\\)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p2table6rewriteRg = new Regex("\\[P2\\] Table 6 rewrite took ([0-9.]+) sec, dropped (\\d+) entries \\(([0-9.]+) %\\)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p2table7rewriteRg = new Regex("\\[P2\\] Table 7 rewrite took ([0-9.]+) sec, dropped (\\d+) entries \\(([0-9.]+) %\\)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p31table2Rg = new Regex("\\[P3-1\\] Table 2 took ([0-9.]+) sec, wrote (\\d+) right entries", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p31table3Rg = new Regex("\\[P3-1\\] Table 3 took ([0-9.]+) sec, wrote (\\d+) right entries", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p31table4Rg = new Regex("\\[P3-1\\] Table 4 took ([0-9.]+) sec, wrote (\\d+) right entries", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p31table5Rg = new Regex("\\[P3-1\\] Table 5 took ([0-9.]+) sec, wrote (\\d+) right entries", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p31table6Rg = new Regex("\\[P3-1\\] Table 6 took ([0-9.]+) sec, wrote (\\d+) right entries", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p31table7Rg = new Regex("\\[P3-1\\] Table 7 took ([0-9.]+) sec, wrote (\\d+) right entries", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p32table2Rg = new Regex("\\[P3-2\\] Table 2 took ([0-9.]+) sec, wrote (\\d+) left entries, (\\d+) final", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p32table3Rg = new Regex("\\[P3-2\\] Table 3 took ([0-9.]+) sec, wrote (\\d+) left entries, (\\d+) final", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p32table4Rg = new Regex("\\[P3-2\\] Table 4 took ([0-9.]+) sec, wrote (\\d+) left entries, (\\d+) final", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p32table5Rg = new Regex("\\[P3-2\\] Table 5 took ([0-9.]+) sec, wrote (\\d+) left entries, (\\d+) final", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p32table6Rg = new Regex("\\[P3-2\\] Table 6 took ([0-9.]+) sec, wrote (\\d+) left entries, (\\d+) final", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p32table7Rg = new Regex("\\[P3-2\\] Table 7 took ([0-9.]+) sec, wrote (\\d+) left entries, (\\d+) final", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p4start1Rg = new Regex("\\[P4\\] Starting to write C1 and C3 tables", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p4finish1Rg = new Regex("\\[P4\\] Finished to write C1 and C3 tables", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p4start2Rg = new Regex("\\[P4\\] Starting to write C2 tables", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex p4finish2Rg = new Regex("\\[P4\\] Finished to write C2 tables", RegexOptions.Compiled | RegexOptions.IgnoreCase);
// errors
static Regex segFaultRg = new Regex("(Segmentation fault|SIGSEGV|segfault)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex coreDumpRg = new Regex("(core dumped|coredump)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex catchExceptionRg = new Regex("Error", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex invalidArgumentRg = new Regex("Invalid", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex terminateRg = new Regex("terminat", RegexOptions.Compiled | RegexOptions.IgnoreCase);
static Regex failedRg = new Regex("failed", RegexOptions.Compiled | RegexOptions.IgnoreCase);
}
}
================================================
FILE: ChiaPlotStatusLib/Logic/Parser/PlotLogFileParser.cs
================================================
using ChiaPlotStatusLib.Logic;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace ChiaPlotStatus
{
/**
* Parses a log file and generates PlotLogs for each plotting process in the log file
* (multiple PlotLogs per file if plot create --num n is used)
* Tails the log file if it is still being written. Call Parse() before each access
*/
public class PlotLogFileParser
{
private TailLineEmitter TailLineEmitter { get; }
protected List PlotLogs { get; } = new List();
public string LogFile;
public string LogFolder;
public bool firstRead = true;
public bool lineRead = true;
public bool closed = false;
public PlotParserCache cache;
public DateTime? lastGrown;
public PlotLogFileParser(string path, bool closeOnEndOfFile, ref PlotParserCache cache)
{
this.cache = cache;
this.LogFile = path;
this.LogFolder = path.Substring(0, path.LastIndexOf(Path.DirectorySeparatorChar));
this.TailLineEmitter = new TailLineEmitter(path, closeOnEndOfFile, (line) =>
{
CurrentPlotLog().IsLastLineTempError = false;
CurrentPlotLog().LastLogLine = line.Trim();
switch (line)
{
case var _ when plotSizeRg.IsMatch(line):
CurrentPlotLog().PlotSize = int.Parse(plotSizeRg.Matches(line)[0].Groups[1].Value);
break;
case var _ when poolPuzzleHashRg.IsMatch(line):
CurrentPlotLog().PoolPuzzleHash = poolPuzzleHashRg.Matches(line)[0].Groups[1].Value;
break;
case var _ when bufferSizeRg.IsMatch(line):
CurrentPlotLog().Buffer = int.Parse(bufferSizeRg.Matches(line)[0].Groups[1].Value);
break;
case var _ when bucketsRg.IsMatch(line):
CurrentPlotLog().Buckets = int.Parse(bucketsRg.Matches(line)[0].Groups[1].Value);
break;
case var _ when currentBucketRg.IsMatch(line):
CurrentPlotLog().CurrentBucket = 1 + int.Parse(currentBucketRg.Matches(line)[0].Groups[1].Value);
break;
case var _ when threadsRg.IsMatch(line):
CurrentPlotLog().Threads = int.Parse(threadsRg.Matches(line)[0].Groups[1].Value);
break;
case var _ when pidRG.IsMatch(line):
CurrentPlotLog().PID = int.Parse(pidRG.Matches(line)[0].Groups[1].Value);
break;
case var _ when startDateRg.IsMatch(line):
var dateTimeStr = startDateRg.Matches(line)[0].Groups[1].Value;
CurrentPlotLog().StartDate = DateTime.ParseExact(dateTimeStr, "ddd MMM d HH:mm:ss yyyy", new System.Globalization.CultureInfo("en-US"), DateTimeStyles.AllowWhiteSpaces);
break;
case var _ when plotNameRg.IsMatch(line):
CurrentPlotLog().PlotName = plotNameRg.Matches(line)[0].Groups[1].Value;
break;
case var _ when phase1Rg.IsMatch(line):
CurrentPlotLog().Phase1Seconds = int.Parse(phase1Rg.Matches(line)[0].Groups[1].Value);
CurrentPlotLog().Phase1Cpu = double.Parse(phase1Rg.Matches(line)[0].Groups[2].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().CurrentBucket = 0;
break;
case var _ when phase1Table.IsMatch(line):
CurrentPlotLog().Phase1Table = int.Parse(phase1Table.Matches(line)[0].Groups[1].Value);
CurrentPlotLog().CurrentTable = CurrentPlotLog().Phase1Table;
CurrentPlotLog().CurrentPhase = 1;
break;
case var _ when phase2Rg.IsMatch(line):
CurrentPlotLog().Phase2Seconds = int.Parse(phase2Rg.Matches(line)[0].Groups[1].Value);
CurrentPlotLog().Phase2Cpu = double.Parse(phase2Rg.Matches(line)[0].Groups[2].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().CurrentBucket = 0;
break;
case var _ when phase2Table.IsMatch(line):
CurrentPlotLog().Phase2Table = int.Parse(phase2Table.Matches(line)[0].Groups[1].Value);
CurrentPlotLog().CurrentTable = CurrentPlotLog().Phase2Table;
CurrentPlotLog().CurrentPhase = 2;
break;
case var _ when phase3Rg.IsMatch(line):
CurrentPlotLog().Phase3Seconds = int.Parse(phase3Rg.Matches(line)[0].Groups[1].Value);
CurrentPlotLog().Phase3Cpu = double.Parse(phase3Rg.Matches(line)[0].Groups[2].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().CurrentBucket = 0;
CurrentPlotLog().CurrentPhase = 4;
break;
case var _ when phase3Table.IsMatch(line):
CurrentPlotLog().Phase3Table = int.Parse(phase3Table.Matches(line)[0].Groups[1].Value);
CurrentPlotLog().CurrentTable = CurrentPlotLog().Phase3Table;
CurrentPlotLog().CurrentPhase = 3;
break;
case var _ when phase4Rg.IsMatch(line):
CurrentPlotLog().Phase4Seconds = int.Parse(phase4Rg.Matches(line)[0].Groups[1].Value);
CurrentPlotLog().Phase4Cpu = double.Parse(phase4Rg.Matches(line)[0].Groups[2].Value, CultureInfo.InvariantCulture);
CurrentPlotLog().CurrentBucket = 0;
CurrentPlotLog().CurrentPhase = 5;
break;
case var _ when copyTime.IsMatch(line):
CurrentPlotLog().CopyTimeSeconds = int.Parse(copyTime.Matches(line)[0].Groups[1].Value);
break;
case var _ when totalTimeRg.IsMatch(line):
var curPlot = CurrentPlotLog();
curPlot.TotalSeconds = int.Parse(totalTimeRg.Matches(line)[0].Groups[1].Value);
curPlot.CurrentPhase = 6;
if (curPlot.StartDate != null)
curPlot.FinishDate = ((DateTime)curPlot.StartDate).AddSeconds(curPlot.TotalSeconds);
break;
case var _ when approximateWorkingSpace.IsMatch(line):
CurrentPlotLog().ApproximateWorkingSpace = approximateWorkingSpace.Matches(line)[0].Groups[1].Value;
break;
case var _ when destinationDirectory.IsMatch(line):
CurrentPlotLog().DestDrive = destinationDirectory.Matches(line)[0].Groups[1].Value;
break;
case var _ when finalFileSize.IsMatch(line):
CurrentPlotLog().FinalFileSize = finalFileSize.Matches(line)[0].Groups[1].Value;
break;
case var _ when queueSize.IsMatch(line):
CurrentPlotLog().QueueSize = int.Parse(queueSize.Matches(line)[0].Groups[1].Value);
break;
case var _ when writePloblemRg.IsMatch(line):
case var _ when readPloblemRg.IsMatch(line):
case var _ when copyPloblemRg.IsMatch(line):
case var _ when couldNotOpenFile.IsMatch(line):
CurrentPlotLog().IsLastLineTempError = true;
CurrentPlotLog().Errors++;
break;
case var _ when caughtPlottingError.IsMatch(line):
CurrentPlotLog().CaughtPlottingError = true;
break;
case var _ when tmpFolders.IsMatch(line):
var match = tmpFolders.Matches(line)[0];
var plotLog = CurrentPlotLog();
if (plotLog.Tmp1Drive != null) // this is a new plot in the same logfile (--num n used)
{
plotLog = NextPlotLog();
}
plotLog.Tmp1Drive = match.Groups[1].Value;
plotLog.Tmp2Drive = match.Groups[2].Value;
break;
default:
break;
}
var cPlotLog = CurrentPlotLog();
cPlotLog.LogFile = this.LogFile;
cPlotLog.LogFolder = this.LogFolder;
cPlotLog.UpdateProgress();
lineRead = true;
});
}
/**
* Can be called as often as needed as it does not reparse what was already processed.
*/
public List ParsePlotLog()
{
if (closed)
return PlotLogs;
var fromCache = cache.Get(this);
if (fromCache != null)
{
Debug.WriteLine("Plotlog was already in cache. Not reading again.");
firstRead = false;
Close();
foreach (var plotLog in fromCache)
PlotLogs.Add(plotLog);
return PlotLogs;
}
lineRead = false;
this.TailLineEmitter.ReadMore();
CurrentPlotLog().FileLastWritten = File.GetLastWriteTime(this.LogFile);
if (CurrentPlotLog().FileLastWritten != null && ((DateTime.Now - (DateTime)CurrentPlotLog().FileLastWritten).TotalDays > 1d))
{
cache.Add(this, PlotLogs);
Close();
}
if (lineRead && !firstRead)
lastGrown = DateTime.Now;
if (lastGrown != null)
CurrentPlotLog().FileLastWritten = lastGrown;
firstRead = false;
return PlotLogs;
}
private void Close()
{
if (closed)
return;
Debug.WriteLine("PlotLog " + LogFile + " has not been updated for more than a day. Closing file.");
this.TailLineEmitter.Close();
closed = true;
}
private PlotLog CurrentPlotLog()
{
if (PlotLogs.Count == 0)
PlotLogs.Add(new PlotLog());
return PlotLogs[PlotLogs.Count - 1];
}
private PlotLog NextPlotLog()
{
var oldPlotLog = CurrentPlotLog();
oldPlotLog.IsLastInLogFile = false;
var newPlotLog = new PlotLog();
// when plot create --num n is used parameters stay the same
newPlotLog.Buckets = oldPlotLog.Buckets;
newPlotLog.Threads = oldPlotLog.Threads;
newPlotLog.Buffer = oldPlotLog.Buffer;
newPlotLog.Tmp1Drive = oldPlotLog.Tmp1Drive;
newPlotLog.Tmp2Drive = oldPlotLog.Tmp2Drive;
newPlotLog.DestDrive = oldPlotLog.DestDrive;
newPlotLog.LogFile = oldPlotLog.LogFile;
newPlotLog.LogFolder = oldPlotLog.LogFolder;
newPlotLog.PlaceInLogFile = oldPlotLog.PlaceInLogFile + 1;
newPlotLog.QueueSize = oldPlotLog.QueueSize;
PlotLogs.Add(newPlotLog);
return newPlotLog;
}
// interesting data from logfiles as regex
public static Regex poolPuzzleHashRg = new Regex("pool contract address:\\s+([a-z0-9]+)\\s+", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static Regex plotSizeRg = new Regex("^Plot size is: (\\d+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static Regex bufferSizeRg = new Regex("^Buffer size is: (\\d+)MiB", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static Regex bucketsRg = new Regex("^Using (\\d+) buckets", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static Regex threadsRg = new Regex("^Using (\\d+) threads of stripe size (\\d+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static Regex pidRG = new Regex("^Process ID is: (\\d+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static Regex startDateRg = new Regex("^Starting phase 1/4: Forward Propagation into tmp files\\.\\.\\. (.+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static Regex phase1Rg = new Regex("^Time for phase 1 = (\\d+)\\.\\d+ seconds. CPU \\((\\d+\\.\\d+)%\\) ", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static Regex phase2Rg = new Regex("^Time for phase 2 = (\\d+)\\.\\d+ seconds. CPU \\((\\d+\\.\\d+)%\\) ", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static Regex phase3Rg = new Regex("^Time for phase 3 = (\\d+)\\.\\d+ seconds. CPU \\((\\d+\\.\\d+)%\\) ", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static Regex phase4Rg = new Regex("^Time for phase 4 = (\\d+)\\.\\d+ seconds. CPU \\((\\d+\\.\\d+)%\\) ", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static Regex copyTime = new Regex("^Copy time = (\\d+)\\.\\d+ seconds", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static Regex totalTimeRg = new Regex("^Total time = (\\d+)\\.\\d+ seconds", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static Regex plotNameRg = new Regex("^Renamed final file from \".+\" to (\".+\")", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static Regex currentBucketRg = new Regex("^\\tBucket (\\d+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static Regex phase1Table = new Regex("^Computing table (\\d+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static Regex phase2Table = new Regex("^Backpropagating on table (\\d+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static Regex phase3Table = new Regex("^Compressing tables (\\d+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static Regex tmpFolders = new Regex("^Starting plotting progress into temporary dirs: (.*) and (.*)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static Regex writePloblemRg = new Regex("^Only wrote \\d+ of \\d+ bytes at", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static Regex couldNotOpenFile = new Regex("^Could not open", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static Regex readPloblemRg = new Regex("^Only read \\d+ of \\d+ bytes at", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static Regex copyPloblemRg = new Regex("^Could not copy ", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static Regex approximateWorkingSpace = new Regex("^Approximate working space used \\(without final file\\): (\\d+\\.\\d+ .*)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static Regex finalFileSize = new Regex("^Final File size: (\\d+\\.\\d+ .*)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static Regex destinationDirectory = new Regex("^Final Directory is: (.*)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static Regex caughtPlottingError = new Regex("^Caught plotting error: .*", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static Regex queueSize = new Regex("Starting plot \\d+.(\\d+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
}
}
================================================
FILE: ChiaPlotStatusLib/Logic/Parser/PlotParserCache.cs
================================================
using ChiaPlotStatus;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace ChiaPlotStatusLib.Logic
{
public class PlotParserCache
{
public Dictionary> CachedPlots { get; set; } = new();
private readonly object lockObject = new object();
private string path;
public PlotParserCache(string path)
{
this.path = path;
try
{
Debug.WriteLine("Trying to read cache");
if (File.Exists(path))
CachedPlots = JsonSerializer.Deserialize>>(File.ReadAllText(path));
Debug.WriteLine("Loaded " + CachedPlots.Count + " entries from cache");
} catch (Exception e)
{
Debug.WriteLine("Could not load PlotParserCache: " + e);
}
}
private string GetKey(PlotLogFileParser parser)
{
return parser.LogFolder + " # " + parser.LogFile;
}
public void Add(PlotLogFileParser parser, List plotLogs)
{
lock(lockObject)
{
CachedPlots[GetKey(parser)] = plotLogs;
}
}
public List? Get(PlotLogFileParser parser)
{
lock (lockObject)
{
List result;
if (CachedPlots.TryGetValue(GetKey(parser), out result))
return result;
return null;
}
}
public void Persist()
{
lock (lockObject)
{
try
{
Debug.WriteLine("Persisting Cache");
string json = JsonSerializer.Serialize(CachedPlots);
File.WriteAllText(path, json);
}
catch (Exception e)
{
Debug.WriteLine("Could not persist PlotParserCache: " + e);
}
}
}
}
}
================================================
FILE: ChiaPlotStatusLib/Logic/Parser/TailLineEmitter.cs
================================================
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ChiaPlotStatus
{
public delegate void TailLineEmitterCallback(string line);
/**
* Opens a given File for read even if it is currently written by a different process and
* emits each line to the given TailLineEmitterCallback in correct order. When the end of
* the file is reached it pauses until ReadMore() is called again and it tries to continues
* reading from the spot it paused at.
*/
public class TailLineEmitter
{
StreamReader StreamReader { get; set; }
TailLineEmitterCallback Callback { get; set; }
private string file;
private bool closeOnEndOfFile;
private bool firstRead = true;
private bool initialized = false;
private bool closed = false;
public TailLineEmitter(string file, bool closeOnEndOfFile, TailLineEmitterCallback callback)
{
this.file = file;
this.Callback = callback;
this.closeOnEndOfFile = closeOnEndOfFile;
}
~TailLineEmitter()
{
Close();
}
public void Close()
{
if (!closed)
{
closed = true;
if (this.StreamReader != null)
this.StreamReader.Close();
}
}
/**
* Read and emit each line from last read point on until current end of file is reached
*/
public void ReadMore()
{
if (!initialized)
{
var fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
var bufferedFs = new BufferedStream(fs, 128 * 1024);
this.StreamReader = new StreamReader(bufferedFs);
initialized = true;
}
if (!closeOnEndOfFile ||firstRead)
{
firstRead = false;
try
{
string? line = "";
do
{
line = this.StreamReader.ReadLine();
if (line != null)
{
this.Callback(line);
}
} while (line != null && !closed);
}
catch (Exception e)
{
Debug.WriteLine(e);
}
if (closeOnEndOfFile)
Close();
}
}
}
}
================================================
FILE: ChiaPlotStatusLib/Logic/Statistics/CPPlottingStatistics.cs
================================================
using ChiaPlotStatusLib.Logic.Models;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ChiaPlotStatus
{
/**
* Collects Statistics on plotting processes such as avarage time spend on phases.
* Used for ETA.
*/
public class CPPlottingStatistics: PlottingStatistics
{
public CPPlottingStatistics(List plotLogs): base(plotLogs)
{
// TODO: implement details like table times
}
}
}
================================================
FILE: ChiaPlotStatusLib/Logic/Statistics/Harvest/Harvest.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ChiaPlotStatusLib.Logic.Statistics.Harvest
{
public class Harvest
{
public string LogFolder { get; set; }
public DateTime DateTime { get; set; }
public int ElgiblePlots { get; set; }
public int FoundProofs { get; set; }
public double LookupTime { get; set; }
public double FilterRatio { get; set; }
public int TotalPlots { get; set; }
public double Heat { get; set; }
}
}
================================================
FILE: ChiaPlotStatusLib/Logic/Statistics/Harvest/HarvestParser.cs
================================================
using ChiaPlotStatus;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace ChiaPlotStatusLib.Logic.Statistics.Harvest
{
// partially ported from https://github.com/MrPig91/PSChiaPlotter/blob/main/PSChiaPlotter/Public/Get-ChiaHarvesterActivity.ps1
public class HarvestParser
{
// public static string DefaultPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)
// + Path.DirectorySeparatorChar + ".chia" + Path.DirectorySeparatorChar + "mainnet" +
/// Path.DirectorySeparatorChar + "log";
///
public static List DefaultPaths()
{
List result = new();
void add(string blockchain)
{
result.Add(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)
+ Path.DirectorySeparatorChar + "." + blockchain + Path.DirectorySeparatorChar + "mainnet" +
Path.DirectorySeparatorChar + "log");
}
add("chia");
add("flax");
add("chaingreen");
add("seno2");
add("chiarose");
add("goji-blockchain");
add("spare-blockchain");
return result;
}
public List>?> ParseLogs(List paths, double maxAllowedLookupTime, int nrOfRecentEntries)
{
ConcurrentBag>?> results = new();
Parallel.ForEach(paths, (path) => results.Add(ParseLogs(path, maxAllowedLookupTime, nrOfRecentEntries)));
return results.ToList();
}
public Tuple>? ParseLogs(string path, double maxAllowedLookupTime, int nrOfRecentEntries)
{
string[] debugLogFiles = { };
if (Directory.Exists(path))
debugLogFiles = Directory.GetFiles(path, "debug.log*");
var regex = new Regex("([0-9:.\\-T]*) harvester .*.harvester.harvester: INFO\\s*([0-9]*) " +
"plots were eligible for farming ([a-z0-9.]*) Found ([0-9]*) proofs. " +
"Time: ([0-9.]*) s. Total ([0-9]*) plots", RegexOptions.Compiled | RegexOptions.IgnoreCase);
ConcurrentBag harvestsBag = new();
Parallel.ForEach(debugLogFiles, (file) =>
{
try
{
// for now close after each read as I am not sure how wallets / harvesters
// react if they run or get restarted while we keep a shared lock on those files
new TailLineEmitter(file, true, (line) =>
{
if (regex.IsMatch(line))
{
var matches = regex.Matches(line)[0];
var harvest = new Harvest
{
LogFolder = path,
DateTime = DateTime.Parse(matches.Groups[1].Value),
ElgiblePlots = int.Parse(matches.Groups[2].Value, CultureInfo.InvariantCulture),
FoundProofs = int.Parse(matches.Groups[4].Value, CultureInfo.InvariantCulture),
LookupTime = double.Parse(matches.Groups[5].Value, CultureInfo.InvariantCulture),
TotalPlots = int.Parse(matches.Groups[6].Value, CultureInfo.InvariantCulture),
FilterRatio = double.Parse(matches.Groups[2].Value) / int.Parse(matches.Groups[6].Value, CultureInfo.InvariantCulture),
Heat = double.Parse(matches.Groups[5].Value, CultureInfo.InvariantCulture) == 0 ? 0 :
(double.Parse(matches.Groups[5].Value, CultureInfo.InvariantCulture) / maxAllowedLookupTime),
};
harvestsBag.Add(harvest);
}
}).ReadMore();
} catch (Exception e)
{
Debug.WriteLine("ERROR: parsing harvester log " + path + " failed. skipping it");
}
});
List harvests = harvestsBag.ToList();
if (harvests.Count == 0)
{
Debug.WriteLine("No harvests found! Set log level at least to info!");
return null;
}
else if (harvests.Count == 1)
{
Debug.WriteLine("Only found a single harvest! Need at least two!");
return null;
}
harvests.Sort((a, b) => a.DateTime.CompareTo(b.DateTime));
harvests = new(harvests.Skip(Math.Max(0, harvests.Count() - nrOfRecentEntries)));
var first = harvests[0];
var last = harvests[harvests.Count - 1];
var runtimeMinutes = (last.DateTime - first.DateTime).TotalMinutes;
var summary = new HarvestSummary
{
LogFolder = path,
WorstLookupTime = harvests.Aggregate((a, b) => a.LookupTime > b.LookupTime ? a : b).LookupTime,
BestLookupTime = harvests.Aggregate((a, b) => a.LookupTime < b.LookupTime ? a : b).LookupTime,
AvgLookupTime = harvests.Average(a => a.LookupTime),
AvgEligiblePlots = harvests.Average(a => a.ElgiblePlots),
TotalPlots = last.TotalPlots,
FoundProofs = (int)harvests.Sum(a => a.FoundProofs),
FilterRatio = harvests.Average(a => a.FilterRatio),
ChallengesPerMinute = harvests.Count / runtimeMinutes,
AvgHeat = harvests.Average(a => a.Heat),
MaxHeat = harvests.Aggregate((a, b) => a.LookupTime > b.LookupTime ? a : b).LookupTime,
MinHeat = harvests.Aggregate((a, b) => a.LookupTime < b.LookupTime ? a : b).LookupTime,
RuntimeMinutes = runtimeMinutes,
};
return new(path, summary, harvests);
}
}
}
================================================
FILE: ChiaPlotStatusLib/Logic/Statistics/Harvest/HarvestSummary.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ChiaPlotStatusLib.Logic.Statistics.Harvest
{
public class HarvestSummary
{
public string LogFolder { get; set; }
public double AvgEligiblePlots { get; set; }
public int FoundProofs { get; set; }
public double BestLookupTime { get; set; }
public double WorstLookupTime { get; set; }
public double AvgLookupTime { get; set; }
public double FilterRatio { get; set; }
public int TotalPlots { get; set; }
public double ChallengesPerMinute { get; set; }
public double AvgHeat { get; set; }
public double MaxHeat { get; set; }
public double MinHeat { get; set; }
public double RuntimeMinutes { get; set; }
}
}
================================================
FILE: ChiaPlotStatusLib/Logic/Statistics/Harvest/HarvestSummeryReadable.cs
================================================
using ChiaPlotStatusLib.Logic.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ChiaPlotStatusLib.Logic.Statistics.Harvest
{
public class HarvestSummeryReadable
{
public string LogFolder { get; set; }
public string AvgEligiblePlots { get; set; }
public int FoundProofs { get; set; }
public string BestLookupTime { get; set; }
public string WorstLookupTime { get; set; }
public string AvgLookupTime { get; set; }
public string FilterRatio { get; set; }
public int TotalPlots { get; set; }
public string ChallengesPerMinute { get; set; }
public string AvgHeat { get; set; }
public string MaxHeat { get; set; }
public string MinHeat { get; set; }
public string RuntimeMinutes { get; set; }
public HarvestSummeryReadable(HarvestSummary summery)
{
this.LogFolder = summery.LogFolder;
this.AvgEligiblePlots = Formatter.formatDouble(summery.AvgEligiblePlots, 5, null);
this.FoundProofs = summery.FoundProofs;
this.BestLookupTime = Formatter.formatDouble(summery.BestLookupTime, 5, "s");
this.WorstLookupTime = Formatter.formatDouble(summery.WorstLookupTime, 5, "s");
this.AvgLookupTime = Formatter.formatDouble(summery.AvgLookupTime, 5, "s");
this.FilterRatio = Formatter.formatDouble(summery.FilterRatio, 5, null);
this.TotalPlots = summery.TotalPlots;
this.ChallengesPerMinute = Formatter.formatDouble(summery.ChallengesPerMinute, 5, null);
this.AvgHeat = Formatter.formatDouble(summery.AvgHeat, 5, null);
this.MaxHeat = Formatter.formatDouble(summery.MaxHeat, 5, null);
this.MinHeat = Formatter.formatDouble(summery.MinHeat, 5, null);
this.RuntimeMinutes = Formatter.formatDouble(summery.RuntimeMinutes, 2, null);
}
}
}
================================================
FILE: ChiaPlotStatusLib/Logic/Statistics/PlottingStatistics.cs
================================================
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ChiaPlotStatus
{
/**
* Collects Statistics on plotting processes such as avarage time spend on phases.
* Used for ETA.
*/
public class PlottingStatistics
{
private List plotLogs;
// in seconds
public int Phase1AvgTimeNeed { get; set; }
public int Phase1Completed { get; set; }
public int Phase2AvgTimeNeed { get; set; }
public int Phase2Completed { get; set; }
public int Phase3AvgTimeNeed { get; set; }
public int Phase3Completed { get; set; }
public int Phase4AvgTimeNeed { get; set; }
public int Phase4Completed { get; set; }
public int CopyTimeAvgTimeNeed { get; set; }
public int CopyTimeCompleted { get; set; }
public PlottingStatistics(List plotLogs)
{
this.plotLogs = plotLogs;
foreach (var plotLog in plotLogs)
{
if (plotLog.Phase1Seconds != 0)
{
Phase1Completed++;
Phase1AvgTimeNeed += plotLog.Phase1Seconds;
}
if (plotLog.Phase2Seconds != 0)
{
Phase2Completed++;
Phase2AvgTimeNeed += plotLog.Phase2Seconds;
}
if (plotLog.Phase3Seconds != 0)
{
Phase3Completed++;
Phase3AvgTimeNeed += plotLog.Phase3Seconds;
}
if (plotLog.Phase4Seconds != 0)
{
Phase4Completed++;
Phase4AvgTimeNeed += plotLog.Phase4Seconds;
}
if (plotLog.CopyTimeSeconds != 0)
{
CopyTimeCompleted++;
CopyTimeAvgTimeNeed += plotLog.CopyTimeSeconds;
}
}
if (Phase1Completed > 0) Phase1AvgTimeNeed /= Phase1Completed;
if (Phase2Completed > 0) Phase2AvgTimeNeed /= Phase2Completed;
if (Phase3Completed > 0) Phase3AvgTimeNeed /= Phase3Completed;
if (Phase4Completed > 0) Phase4AvgTimeNeed /= Phase4Completed;
if (CopyTimeCompleted > 0) CopyTimeAvgTimeNeed /= CopyTimeCompleted;
}
public override bool Equals(object obj)
{
return obj is PlottingStatistics statistics &&
Phase1AvgTimeNeed == statistics.Phase1AvgTimeNeed &&
Phase1Completed == statistics.Phase1Completed &&
Phase2AvgTimeNeed == statistics.Phase2AvgTimeNeed &&
Phase2Completed == statistics.Phase2Completed &&
Phase3AvgTimeNeed == statistics.Phase3AvgTimeNeed &&
Phase3Completed == statistics.Phase3Completed &&
Phase4AvgTimeNeed == statistics.Phase4AvgTimeNeed &&
Phase4Completed == statistics.Phase4Completed &&
CopyTimeAvgTimeNeed == statistics.CopyTimeAvgTimeNeed &&
CopyTimeCompleted == statistics.CopyTimeCompleted;
}
public override int GetHashCode()
{
HashCode hash = new HashCode();
hash.Add(Phase1AvgTimeNeed);
hash.Add(Phase1Completed);
hash.Add(Phase2AvgTimeNeed);
hash.Add(Phase2Completed);
hash.Add(Phase3AvgTimeNeed);
hash.Add(Phase3Completed);
hash.Add(Phase4AvgTimeNeed);
hash.Add(Phase4Completed);
hash.Add(CopyTimeAvgTimeNeed);
hash.Add(CopyTimeCompleted);
return hash.ToHashCode();
}
}
}
================================================
FILE: ChiaPlotStatusLib/Logic/Statistics/PlottingStatisticsDay.cs
================================================
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ChiaPlotStatus
{
public class PlottingStatisticsDay
{
public DateTime Day { get; set; }
public int Phase1 { get; set; } = 0;
public int Phase2 { get; set; } = 0;
public int Phase3 { get; set; } = 0;
public int Phase4 { get; set; } = 0;
public int Phase5 { get; set; } = 0;
public int Finished { get; set; } = 0;
public int Died { get; set; } = 0;
public PlottingStatisticsDay(DateTime day)
{
this.Day = day;
}
}
}
================================================
FILE: ChiaPlotStatusLib/Logic/Statistics/PlottingStatisticsDayReadable.cs
================================================
using ChiaPlotStatusLib.Logic.Utils;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ChiaPlotStatus
{
public class PlottingStatisticsDayReadable
{
public string Day { get; set; }
public int Phase1 { get; set; } = 0;
public int Phase2 { get; set; } = 0;
public int Phase3 { get; set; } = 0;
public int Phase4 { get; set; } = 0;
public int Phase5 { get; set; } = 0;
public int Finished { get; set; } = 0;
public int Died { get; set; } = 0;
public PlottingStatisticsDayReadable(PlottingStatisticsDay psd)
{
this.Day = Formatter.formatDate(psd.Day);
this.Phase1 = psd.Phase1;
this.Phase2 = psd.Phase2;
this.Phase3 = psd.Phase3;
this.Phase4 = psd.Phase4;
this.Phase5 = psd.Phase5;
this.Finished = psd.Finished;
this.Died = psd.Died;
}
}
}
================================================
FILE: ChiaPlotStatusLib/Logic/Statistics/PlottingStatisticsFull.cs
================================================
using ChiaPlotStatus;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ChiaPlotStatusGUI.GUI.ViewModels
{
public class PlottingStatisticsFull
{
public string LogFolder { get; set; }
public string Tmp1Drive { get; set; }
public string Tmp2Drive { get; set; }
public int PlotSize { get; set; }
public int Threads { get; set; }
public int Buffer { get; set; }
public int Buckets { get; set; }
public int Phase1AvgTimeNeed { get; set; }
public int Phase1Completed { get; set; }
public int Phase2AvgTimeNeed { get; set; }
public int Phase2Completed { get; set; }
public int Phase3AvgTimeNeed { get; set; }
public int Phase3Completed { get; set; }
public int Phase4AvgTimeNeed { get; set; }
public int Phase4Completed { get; set; }
public int CopyTimeAvgTimeNeed { get; set; }
public int CopyTimeCompleted { get; set; }
public int TotalAvgTimeNeed { get; set; }
public PlottingStatisticsFull(PlottingStatisticsID id, PlottingStatistics stats)
{
this.LogFolder = id.LogFolder;
this.Tmp1Drive = id.Tmp1Drive;
this.Tmp2Drive = id.Tmp2Drive;
this.PlotSize = id.PlotSize;
this.Threads = id.Threads;
this.Buffer = id.Buffer;
this.Buckets = id.Buckets;
this.Phase1AvgTimeNeed = stats.Phase1AvgTimeNeed;
this.Phase1Completed = stats.Phase1Completed;
this.Phase2AvgTimeNeed = stats.Phase2AvgTimeNeed;
this.Phase2Completed = stats.Phase2Completed;
this.Phase3AvgTimeNeed = stats.Phase3AvgTimeNeed;
this.Phase3Completed = stats.Phase3Completed;
this.Phase4AvgTimeNeed = stats.Phase4AvgTimeNeed;
this.Phase4Completed = stats.Phase4Completed;
this.CopyTimeAvgTimeNeed = stats.CopyTimeAvgTimeNeed;
this.CopyTimeCompleted = stats.CopyTimeCompleted;
this.TotalAvgTimeNeed = stats.Phase1AvgTimeNeed + stats.Phase2AvgTimeNeed +
stats.Phase3AvgTimeNeed + stats.Phase4AvgTimeNeed + stats.CopyTimeAvgTimeNeed;
}
}
}
================================================
FILE: ChiaPlotStatusLib/Logic/Statistics/PlottingStatisticsFullReadable.cs
================================================
using ChiaPlotStatus;
using ChiaPlotStatusLib.Logic.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ChiaPlotStatusGUI.GUI.ViewModels
{
public class PlottingStatisticsFullReadable
{
public string LogFolder { get; set; }
public string Tmp1Drive { get; set; }
public string Tmp2Drive { get; set; }
public int PlotSize { get; set; }
public int Threads { get; set; }
public string Buffer { get; set; }
public int Buckets { get; set; }
public string Phase1AvgTimeNeed { get; set; }
public int Phase1Completed { get; set; }
public string Phase2AvgTimeNeed { get; set; }
public int Phase2Completed { get; set; }
public string Phase3AvgTimeNeed { get; set; }
public int Phase3Completed { get; set; }
public string Phase4AvgTimeNeed { get; set; }
public int Phase4Completed { get; set; }
public string CopyTimeAvgTimeNeed { get; set; }
public int CopyTimeCompleted { get; set; }
public string TotalAvgTimeNeed { get; set; }
public PlottingStatisticsFullReadable(PlottingStatisticsFull stats)
{
this.LogFolder = stats.LogFolder;
this.Tmp1Drive = stats.Tmp1Drive;
this.Tmp2Drive = stats.Tmp2Drive;
this.PlotSize = stats.PlotSize;
this.Threads = stats.Threads;
this.Buffer = stats.Buffer + "MB";
this.Buckets = stats.Buckets;
this.Phase1AvgTimeNeed = Formatter.formatSeconds(stats.Phase1AvgTimeNeed, true);
this.Phase1Completed = stats.Phase1Completed;
this.Phase2AvgTimeNeed = Formatter.formatSeconds(stats.Phase2AvgTimeNeed, true);
this.Phase2Completed = stats.Phase2Completed;
this.Phase3AvgTimeNeed = Formatter.formatSeconds(stats.Phase3AvgTimeNeed, true);
this.Phase3Completed = stats.Phase3Completed;
this.Phase4AvgTimeNeed = Formatter.formatSeconds(stats.Phase4AvgTimeNeed, false);
this.Phase4Completed = stats.Phase4Completed;
this.CopyTimeAvgTimeNeed = Formatter.formatSeconds(stats.CopyTimeAvgTimeNeed, false);
this.CopyTimeCompleted = stats.CopyTimeCompleted;
this.TotalAvgTimeNeed = Formatter.formatSeconds(stats.TotalAvgTimeNeed, true);
}
}
}
================================================
FILE: ChiaPlotStatusLib/Logic/Statistics/PlottingStatisticsHolder.cs
================================================
using ChiaPlotStatus.Logic.Models;
using ChiaPlotStatusGUI.GUI.ViewModels;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ChiaPlotStatus
{
/**
* Collects Statistics on plotting processes and can give you a statistic most
* relevant to your PlogLog
*/
public class PlottingStatisticsHolder
{
private readonly ConcurrentBag FinishedPlotLogs = new ConcurrentBag();
private readonly PlottingStatisticsIdRelevanceWeights weights;
private readonly Dictionary Days = new();
public PlottingStatisticsHolder(List plotLogs, PlottingStatisticsIdRelevanceWeights weights, List markOfDeaths)
{
this.weights = weights;
PlottingStatisticsDay GetOrCreatePlottingStatisticsDay(DateTime? dateTime)
{
if (dateTime == null)
// silently give them an instance that gets thrown away
return new PlottingStatisticsDay(DateTime.Now.Date);
DateTime day = ((DateTime)dateTime).Date;
if (!Days.ContainsKey(day))
Days.Add(day, new PlottingStatisticsDay(day));
return Days[day];
}
foreach (var plotLog in plotLogs)
{
GetOrCreatePlottingStatisticsDay(plotLog.StartDate).Phase1++;
GetOrCreatePlottingStatisticsDay(plotLog.FinishDate).Finished++;
// max reached moment the process was likely still running. Not very precise when --num is used.
// could use currentTable + currentBucket and ETA-like Calculation to improve estimate in that case.
// Should be a noticable improvement on HDDs
DateTime? max = plotLog.StartDate;
if (plotLog.Phase1Seconds != 0)
{
var phase2 = plotLog.StartDate.Value.AddSeconds(plotLog.Phase1Seconds);
max = phase2;
GetOrCreatePlottingStatisticsDay(phase2).Phase2++;
if (plotLog.Phase2Seconds != 0)
{
var phase3 = phase2.AddSeconds(plotLog.Phase2Seconds);
max = phase3;
GetOrCreatePlottingStatisticsDay(phase3).Phase3++;
if (plotLog.Phase3Seconds != 0)
{
var phase4 = phase3.AddSeconds(plotLog.Phase3Seconds);
max = phase4;
GetOrCreatePlottingStatisticsDay(phase4).Phase4++;
if (plotLog.Phase4Seconds != 0)
{
var phase5 = phase4.AddSeconds(plotLog.Phase4Seconds);
max = phase5;
GetOrCreatePlottingStatisticsDay(phase5).Phase5++;
}
}
}
}
foreach (var mark in markOfDeaths)
{
if (mark.IsMatch(plotLog) && mark.DiedAt != null)
{
max = mark.DiedAt.Value;
break;
}
}
if (plotLog.Health is ConfirmedDead)
{
// if --num is not used then lastModifiedAt property on the log file should be very
// near our time of death.
// if --num is used we can only assume the above if this was the last plot in queue
// plotLog.PlaceInLogFile == plotLog.QueueSize catches both cases
if (plotLog.PlaceInLogFile == plotLog.QueueSize)
{
max = File.GetLastWriteTime(plotLog.LogFile);
}
GetOrCreatePlottingStatisticsDay(max).Died++;
}
if (plotLog.TotalSeconds > 0)
FinishedPlotLogs.Add(plotLog);
}
}
public PlottingStatistics GetMostRelevantStatistics(PlotLog plotLog)
{
PlottingStatisticsID id = new PlottingStatisticsID(plotLog);
return GetMostRelevantStatistics(id);
}
public PlottingStatistics GetMostRelevantStatistics(PlottingStatisticsID id)
{
Dictionary> byRelevance = new Dictionary>();
foreach (var fromAll in FinishedPlotLogs)
{
int relevance = id.CalcRelevance(new PlottingStatisticsID(fromAll), weights);
// Debug.WriteLine(relevance);
if (!byRelevance.ContainsKey(relevance))
byRelevance.Add(relevance, new List());
byRelevance[relevance].Add(fromAll);
}
int highestRelevance = -1;
foreach (var relevance in byRelevance.Keys)
if (relevance > highestRelevance)
highestRelevance = relevance;
if (highestRelevance > -1)
{
List plotLogs = byRelevance[highestRelevance];
if (id.UsedPlotter == "chia-plotter")
return new CPPlottingStatistics(plotLogs.ToList());
else
return new PlottingStatistics(plotLogs.ToList());
}
if (FinishedPlotLogs.Count > 0)
if (id.UsedPlotter == "chia-plotter")
return new CPPlottingStatistics(FinishedPlotLogs.ToList());
else
return new PlottingStatistics(FinishedPlotLogs.ToList());
else
return MagicNumbers(id.UsedPlotter);
}
/**
* An average over some plotlogs I had on my drive.
* This way we can display ETA and Time Remaining even
* when the user does not have finished plotlogs (will
* be imprecise tho).
* That way we get better warning and error thresholds
* from the beginning too, as they were diced before.
* See Issue #23
*/
private PlottingStatistics MagicNumbers(string usedPlotter)
{
List plotLogs = new List();
var magicPlotLog = new PlotLog();
if (usedPlotter == "chiapos")
{
magicPlotLog.Phase1Seconds = 31573;
magicPlotLog.Phase2Seconds = 12298;
magicPlotLog.Phase3Seconds = 34925;
magicPlotLog.Phase4Seconds = 3024;
magicPlotLog.CopyTimeSeconds = 934;
}
else
{
// from chia-plotter readme times 3 as most people will use ssds and not ramdisk
magicPlotLog.Phase1Seconds = 1143 * 3;
magicPlotLog.Phase2Seconds = 635 * 3;
magicPlotLog.Phase3Seconds = 946 * 3;
magicPlotLog.Phase4Seconds = 80 * 3;
magicPlotLog.CopyTimeSeconds = 2804 * 3;
}
plotLogs.Add(magicPlotLog);
return new PlottingStatistics(plotLogs);
}
public List<(PlottingStatisticsFull, PlottingStatisticsFullReadable)> AllStatistics()
{
Dictionary statsDict = new();
foreach (var plotLog in FinishedPlotLogs)
{
var id = new PlottingStatisticsID(plotLog);
if (!statsDict.ContainsKey(id))
statsDict.Add(id, this.GetMostRelevantStatistics(plotLog));
}
List<(PlottingStatisticsFull, PlottingStatisticsFullReadable)> statsFull = new();
foreach (var entry in statsDict) {
var stat = new PlottingStatisticsFull(entry.Key, entry.Value);
statsFull.Add((stat, new PlottingStatisticsFullReadable(stat)));
}
return statsFull;
}
public Dictionary GetDailyStats()
{
return new(Days);
}
}
}
================================================
FILE: ChiaPlotStatusLib/Logic/Statistics/PlottingStatisticsID.cs
================================================
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ChiaPlotStatus
{
/**
* Key used to determine best matches / relevance on finished plotting processes for a given PlotLog
* Relevance is calculated by summing up weigths assigned to the Properties that are equal between
* PlotLogs represented by this PlottingStatisticsID
*/
public class PlottingStatisticsID
{
public string UsedPlotter { get; set; }
public string LogFolder { get; set; }
public string Tmp1Drive { get; set; }
public string Tmp2Drive { get; set; }
public int PlotSize { get; set; }
public int Threads { get; set; }
public int Buffer { get; set; }
public int Buckets { get; set; }
public PlottingStatisticsID(PlotLog plotLog)
{
UsedPlotter = plotLog.UsedPlotter;
LogFolder = plotLog.LogFolder;
Tmp1Drive = plotLog.Tmp1Drive;
Tmp2Drive = plotLog.Tmp2Drive;
PlotSize = plotLog.PlotSize;
Threads = plotLog.Threads;
Buffer = plotLog.Buffer;
Tmp2Drive = plotLog.Tmp2Drive;
Buckets = plotLog.Buckets;
}
public int CalcRelevance(PlottingStatisticsID other, PlottingStatisticsIdRelevanceWeights weights)
{
var relevance = 0;
relevance += UsedPlotterRelevance(other) * (weights.TmpDir + weights.ComputeConfiguration) * 10;
relevance += PlotSizeRelevance(other) * weights.PlotSize;
relevance += LogFolderRelevance(other) * weights.LogFolder;
relevance += TmpDirRelevance(other) * weights.TmpDir;
relevance += ComputeConfiguration(other) * weights.ComputeConfiguration;
return relevance;
}
private int UsedPlotterRelevance(PlottingStatisticsID other)
{
if (this.UsedPlotter == other.UsedPlotter)
return 1;
return 0;
}
private int PlotSizeRelevance(PlottingStatisticsID other)
{
if (this.PlotSize == other.PlotSize)
return 1;
return 0;
}
private int LogFolderRelevance(PlottingStatisticsID other)
{
if (string.Equals(this.LogFolder, other.LogFolder))
return 1;
return 0;
}
private int TmpDirRelevance(PlottingStatisticsID other)
{
bool tmp1Equal = string.Equals(this.Tmp1Drive, other.Tmp1Drive);
bool tmp2Equal = string.Equals(this.Tmp2Drive, other.Tmp2Drive);
if (tmp1Equal && tmp2Equal)
return 2;
if (!tmp1Equal && !tmp2Equal)
return 0;
return 1;
}
private int ComputeConfiguration(PlottingStatisticsID other)
{
int relevance = 0;
if (this.Threads == other.Threads) relevance++;
if (this.Buckets == other.Buckets) relevance++;
if (this.Buffer == other.Buffer) relevance++;
return relevance;
}
public override bool Equals(object obj)
{
return obj is PlottingStatisticsID iD &&
LogFolder == iD.LogFolder &&
Tmp1Drive == iD.Tmp1Drive &&
Tmp2Drive == iD.Tmp2Drive &&
PlotSize == iD.PlotSize &&
Threads == iD.Threads &&
Buffer == iD.Buffer &&
Buckets == iD.Buckets;
}
public override int GetHashCode()
{
return HashCode.Combine(LogFolder, Tmp1Drive, Tmp2Drive, PlotSize, Threads, Buffer, Buckets);
}
}
}
================================================
FILE: ChiaPlotStatusLib/Logic/Statistics/PlottingStatisticsIdRelevanceWeights.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ChiaPlotStatus
{
/**
* When trying to find the best matched finished PlotLogs for a given
* PlotLog this is used to define relevance.
*/
public class PlottingStatisticsIdRelevanceWeights
{
/**
* k32, k33, k34...
*/
public int PlotSize { get; set; } = 10000;
/**
* from which folder the PlogLogs originate
*/
public int LogFolder { get; set; } = 1000;
/**
* Partially or exact match on tmp1 and tmp2 dirs
*/
public int TmpDir { get; set; } = 200;
/**
* Similarity on Threads, Buffer, Buckets
*/
public int ComputeConfiguration { get; set; } = 100;
}
}
================================================
FILE: ChiaPlotStatusLib/Logic/Utils/Exporter.cs
================================================
using CsvHelper;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using YamlDotNet.Serialization;
namespace ChiaPlotStatus.Logic.Utils
{
public class Exporter
{
private List<(PlotLog, PlotLogReadable)> plotLogTuples = new();
private List plotLogs = new();
private List plotLogReadables = new();
public Exporter(IEnumerable<(PlotLog, PlotLogReadable)> plotLogs)
{
foreach (var tuple in plotLogs)
{
this.plotLogTuples.Add(tuple);
this.plotLogs.Add(tuple.Item1);
this.plotLogReadables.Add(tuple.Item2);
}
}
public void ToJson(string file, bool raw)
{
JsonSerializerOptions options = new JsonSerializerOptions();
options.WriteIndented = true;
string? json = null;
try {
if (raw)
json = JsonSerializer.Serialize(this.plotLogs, options);
else
json = JsonSerializer.Serialize(this.plotLogReadables, options);
File.WriteAllText(file, json);
} catch (Exception e)
{
Debug.WriteLine(e);
}
}
public void ToYaml(string file, bool raw)
{
var serializer = new SerializerBuilder().Build();
string? yaml = null;
if (raw)
yaml = serializer.Serialize(this.plotLogs);
else
yaml = serializer.Serialize(this.plotLogReadables);
File.WriteAllText(file, yaml);
}
public void ToCsv(string file, bool raw)
{
using (var writer = new StreamWriter(file))
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
if (raw)
csv.WriteRecords(this.plotLogs);
else
csv.WriteRecords(this.plotLogReadables);
}
}
}
================================================
FILE: ChiaPlotStatusLib/Logic/Utils/Formatter.cs
================================================
using ChiaPlotStatus.Logic.Models;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ChiaPlotStatusLib.Logic.Utils
{
public class Formatter
{
public static string formatCpuUsage(double usage)
{
if (usage < 0.01d)
return "";
else
return usage.ToString("0.0 '%'");
}
public static string formatSeconds(int seconds, bool hideSeconds)
{
var secondsPart = "\\ ss\\s";
if (hideSeconds)
secondsPart = "";
if (seconds == 0)
return "";
else if (seconds > 24 * 60 * 60)
return TimeSpan.FromSeconds(seconds).ToString(@"dd\d\ hh\h\ mm\m" + secondsPart);
else if (seconds > 60 * 60)
return TimeSpan.FromSeconds(seconds).ToString(@"hh\h\ mm\m" + secondsPart);
else if (seconds > 60)
return TimeSpan.FromSeconds(seconds).ToString(@"mm\m" + secondsPart);
else
return TimeSpan.FromSeconds(seconds).ToString(@"ss\s");
}
public static string formatDouble(double value, int decimalPlaces, string? suffix)
{
var format = "0";
if (decimalPlaces > 0)
format += ".";
for (int i = 0; i < decimalPlaces; i++) format += "0";
var val = value.ToString(format, CultureInfo.CurrentCulture);
if (suffix == null)
return val;
else return val + suffix;
}
public static string formatDateTime(DateTime? dateTime)
{
if (dateTime == null)
return "";
else {
CultureInfo culture = CultureInfo.CurrentCulture;
// forced to cast it to a non nullable or it does not find ToString(format)
return ((DateTime)dateTime).ToString("m", culture) + " " + ((DateTime)dateTime).ToString("t", culture);
}
}
public static string formatDate(DateTime? dateTime)
{
if (dateTime == null)
return "";
else
{
CultureInfo culture = CultureInfo.CurrentCulture;
// forced to cast it to a non nullable or it does not find ToString(format)
return ((DateTime)dateTime).ToString("m", culture);
}
}
public static string formatHealth(HealthIndicator health)
{
switch (health)
{
case Healthy:
return "✓";
case TempError:
return "⚠ Temp Errors";
case Concerning c:
return "⚠ Slow " + c.Minutes + " / " + c.ExpectedMinutes + "m";
case PossiblyDead p:
return "⚠ Dead? " + p.Minutes + " / " + p.ExpectedMinutes + "m";
case ConfirmedDead c:
return "✗ Dead" + (c.Manual ? " (m)" : "");
default:
throw new NotImplementedException("formatHealth (HealthIndicator " + health + ")");
}
}
}
}
================================================
FILE: ChiaPlotStatusLib/Logic/Utils/SearchFilter.cs
================================================
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace ChiaPlotStatus.Logic.Utils
{
public static class SearchFilter
{
/**
* Splits searchString into keywords and each keyword must be contained in at least one
* property of an item for the item to be included in the return list.
* Uses Reflection, which is more than fast enough for our usecase.
*/
public static List Search(string? searchString, List items)
{
if (items.Count == 0 || string.IsNullOrEmpty(searchString))
{
return items;
}
List result = new();
string[] keywords = searchString.ToLower().Split(" ");
PropertyInfo[] properties = typeof(T).GetProperties();
foreach (var item in items)
{
bool match = true;
foreach (var keyword in keywords)
{
bool keywordMatch = false;
foreach (PropertyInfo property in properties)
{
object? value = property.GetValue(item);
if (value != null)
{
string? valueStr = value.ToString().ToLower();
if (!string.IsNullOrEmpty(valueStr) && valueStr.Contains(keyword))
{
keywordMatch = true;
break;
}
}
}
if (!keywordMatch)
{
match = false;
break;
}
}
if (match)
result.Add(item);
}
return result;
}
public static List<(A, B)> Search(string? searchString, List<(A, B)> items)
{
if (items.Count == 0 || string.IsNullOrEmpty(searchString))
{
return items;
}
List itemsA = new();
List itemsB = new();
foreach (var item in items)
{
itemsA.Add(item.Item1);
itemsB.Add(item.Item2);
}
itemsA = Search(searchString, itemsA);
itemsB = Search(searchString, itemsB);
List<(A, B)> result = new();
foreach (var item in items)
if (itemsA.Contains(item.Item1) || itemsB.Contains(item.Item2))
result.Add(item);
return result;
}
}
}
================================================
FILE: ChiaPlotStatusLib/Logic/Utils/Sorter.cs
================================================
using ChiaPlotStatus.Logic.Models;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace ChiaPlotStatus.Logic.Utils
{
public static class Sorter
{
public static void Sort(string propertyName, bool sortAsc, List<(A, B)> items)
{
PropertyInfo[] propertiesA = typeof(A).GetProperties();
PropertyInfo[] propertiesB = typeof(B).GetProperties();
PropertyInfo? propertyA = null;
PropertyInfo? propertyB = null;
foreach (var prop in propertiesA)
if (string.Equals(propertyName, prop.Name))
propertyA = prop;
foreach (var prop in propertiesB)
if (string.Equals(propertyName, prop.Name))
propertyB = prop;
PropertyInfo? property = propertyA;
bool sortOverLeftTupleItem = true;
if (property == null)
{
property = propertyB;
sortOverLeftTupleItem = false;
}
items.Sort((a, b) =>
{
int sortIndex = 0;
object? valueA = null;
object? valueB = null;
if (sortOverLeftTupleItem)
{
// property not found, sort by first property on A whatever it is
if (property == null)
property = propertiesA[0];
valueA = property.GetValue(a.Item1);
valueB = property.GetValue(b.Item1);
}
else
{
// property not found, sort by first property on A whatever it is
if (property == null)
property = propertiesB[0];
valueA = property.GetValue(a.Item2);
valueB = property.GetValue(b.Item2);
}
string valueAStr = "";
string valueBStr = "";
TypeCode typeCode = Type.GetTypeCode(property.PropertyType);
switch (typeCode)
{
case TypeCode.Int32:
sortIndex = compare((int?)valueA, (int?)valueB);
break;
case TypeCode.Single:
sortIndex = compare((Single?)valueA, (Single?)valueB);
break;
case TypeCode.Double:
sortIndex = compare((Double?)valueA, (Double?)valueB);
break;
case TypeCode.Decimal:
sortIndex = compare((Decimal?)valueA, (Decimal?)valueB);
break;
case TypeCode.DateTime:
case TypeCode.Object:
if (valueA is HealthIndicator)
sortIndex = ((HealthIndicator)valueA).SortIndex.CompareTo(((HealthIndicator)valueB).SortIndex);
else
sortIndex = compare((DateTime?)valueA, (DateTime?)valueB);
break;
case TypeCode.String:
valueAStr = "";
valueBStr = "";
if (valueA != null)
valueAStr = valueA.ToString().ToLower();
if (valueB != null)
valueBStr = valueB.ToString().ToLower();
sortIndex = string.Compare(valueAStr, valueBStr);
break;
default:
Debug.WriteLine("TypeCode " + typeCode);
break;
}
// keep empty values on the bottom of the table
if (propertyB != null)
{
// we sort over plotLog first but might hide useless information in plotLogReadable
// the sorting does not see this values as empty as it looks into plotLogs first since
// the data there is more easily sortable
// but on the table we stil see empty values on top of the table as they are hidden.
// That is why we sort on propertyB (plotLogReadable/item2) here
var valueAPropB = propertyB.GetValue(a.Item2);
var valueBPropB = propertyB.GetValue(b.Item2);
var valueAProbANullOrEmpty = valueA == null || string.IsNullOrEmpty("" + valueA);
var valueBProbANullOrEmpty = valueB == null || string.IsNullOrEmpty("" + valueB);
var valueAProbBNullOrEmpty = valueAPropB == null || string.IsNullOrEmpty("" + valueAPropB);
var valueBProbBNullOrEmpty = valueBPropB == null || string.IsNullOrEmpty("" + valueBPropB);
if ((!valueAProbANullOrEmpty && valueAProbBNullOrEmpty) || (!valueBProbANullOrEmpty && valueBProbBNullOrEmpty))
{
// this is to sort by propb when values are null in plotlogreadable but not in plotlog
if (valueAProbBNullOrEmpty && !valueBProbBNullOrEmpty)
{
if (sortAsc)
sortIndex = 1;
else
sortIndex = -1;
}
if (!valueAProbBNullOrEmpty && valueBProbBNullOrEmpty)
{
if (sortAsc)
sortIndex = -1;
else
sortIndex = 1;
}
}
else
{
// this is to sort by propa when values are null in plotlog
if (valueAProbANullOrEmpty && !valueBProbANullOrEmpty)
{
if (sortAsc)
sortIndex = 1;
else
sortIndex = -1;
}
if (!valueAProbANullOrEmpty && valueBProbANullOrEmpty)
{
if (sortAsc)
sortIndex = -1;
else
sortIndex = 1;
}
}
}
// stabilize sorting so a refresh or sort does not put plot logs with equal values at random
// positions causing visual "jumping" of table entries.
// this way sorting stays consistent on equal values across the runtime of the program
if (sortIndex == 0)
{
sortIndex = a.Item1.GetHashCode().CompareTo(b.Item1.GetHashCode());
}
if (!sortAsc)
{
sortIndex *= -1;
}
return sortIndex;
});
}
private static int compare(Single? a, Single? b)
{
if (a == null && b != null)
return +1;
if (a != null && b == null)
return -1;
if (a == null && b == null)
return 0;
if (a > b)
return +1;
if (a < b)
return -1;
return 0;
}
private static int compare(Double? a, Double? b)
{
if (a == null && b != null)
return +1;
if (a != null && b == null)
return -1;
if (a == null && b == null)
return 0;
if (a > b)
return +1;
if (a < b)
return -1;
return 0;
}
private static int compare(Decimal? a, Decimal? b)
{
if (a == null && b != null)
return +1;
if (a != null && b == null)
return -1;
if (a == null && b == null)
return 0;
if (a > b)
return +1;
if (a < b)
return -1;
return 0;
}
private static int compare(int? a, int? b)
{
if (a == null && b != null)
return +1;
if (a != null && b == null)
return -1;
if (a == null && b == null)
return 0;
if (a > b)
return +1;
if (a < b)
return -1;
return 0;
}
private static int compare(DateTime? a, DateTime? b)
{
if (a == null && b != null)
return +1;
if (a != null && b == null)
return -1;
if (a == null && b == null)
return 0;
if (a > b)
return +1;
if (a < b)
return -1;
return 0;
}
}
}
================================================
FILE: Directory.Build.props
================================================
0.1.189-*
all
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2021 grayfallstown
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: Logo/Icon - Readme.txt
================================================
Image downloaded from https://freeicons.io/plant-icon-set/leaf-leaves-green-fall-spring-nature-tree-icon-41188
Artist Charlie https://www.tanhuiyingdesign.com/ https://freeicons.io/profile/740
License Creative Commons(Attribution 3.0 unported)
Color was changed
================================================
FILE: NuGet.Config
================================================
================================================
FILE: Properties/PublishProfiles/FolderProfile.pubxml
================================================
Release
Any CPU
bin\Release\net5.0\publish\
FileSystem
net5.0
win-x86
true
False
False
False
================================================
FILE: Properties/PublishProfiles/FolderProfile.pubxml.user
================================================
True|2021-05-02T17:58:29.8523802Z;True|2021-05-02T19:49:14.6691365+02:00;
================================================
FILE: Properties/Resources.Designer.cs
================================================
//------------------------------------------------------------------------------
//
// Dieser Code wurde von einem Tool generiert.
// Laufzeitversion:4.0.30319.42000
//
// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn
// der Code erneut generiert wird.
//
//------------------------------------------------------------------------------
namespace ChiaPlotStatus.Properties {
using System;
///
/// Eine stark typisierte Ressourcenklasse zum Suchen von lokalisierten Zeichenfolgen usw.
///
// Diese Klasse wurde von der StronglyTypedResourceBuilder automatisch generiert
// -Klasse über ein Tool wie ResGen oder Visual Studio automatisch generiert.
// Um einen Member hinzuzufügen oder zu entfernen, bearbeiten Sie die .ResX-Datei und führen dann ResGen
// mit der /str-Option erneut aus, oder Sie erstellen Ihr VS-Projekt neu.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
///
/// Gibt die zwischengespeicherte ResourceManager-Instanz zurück, die von dieser Klasse verwendet wird.
///
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ChiaPlotStatus.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
///
/// Überschreibt die CurrentUICulture-Eigenschaft des aktuellen Threads für alle
/// Ressourcenzuordnungen, die diese stark typisierte Ressourcenklasse verwenden.
///
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}
================================================
FILE: Properties/Resources.resx
================================================
text/microsoft-resx
1.3
System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
================================================
FILE: README.md
================================================




[](https://www.paypal.com/donate?hosted_button_id=PDLLVF5XVMJPC)
# [Chia Plot Status](https://grayfallstown.github.io/Chia-Plot-Status/)
GUI Tool for beginners and experts to Monitor and Analyse Chia Plotting log files, show health and progress of running plots and estimated time to completion. No setup, configuration or installation of python or whatever required. Just install and enjoy.


## Features
- Support for Chia, Flax, Spare and ChainGreen
- Monitor Progress of running plots
- Show estimated time to completion based on your already finished plots best matching your current plot config
- Monitor Health of plotting processes
- Already compatible with madMAx43v3r/chia-plotter (currently getting improved)
- Compatible with all plotters and plotting managers that use or are based on the official chia plotter (see Troubleshooting if something does not work from the get go)
- Show important information from log file in easy to read table
- Multiple folders with log files supported (multiple plotting rigs, anyone?)
- Multiple plots per log file supported (plot create --num n)
- Export of readable or raw data to Json, Yaml and CSV
## See Chia Plot Status in action:
### On Upside Down Crypto (YouTube):
### On Patro TV (YouTube):
## How it works
Chia Plot Status observes the log folders you give it to monitor which can be local or connected via network. By this it supports monitoring multiple plotting rigs as you can access them on your desktop even if your plotting rigs are headless. It regulary checks for new Log files in those folders and analyses them.
On basis of finished plots it builds a local statistic (on your machine, no data is send anywhere or pulled from any cloud) and uses them to calculate ETA / time remaining and warning thresholds for the Health of your plotting processes.
## Working with many distributed plotting rigs
**Recommended way:** Use sshfs (with [sshfs-win](https://github.com/billziss-gh/sshfs-win) for Windows) to securely mount the log dirs of your plotting rigs on your desktop via highly encrypted network connection, where it is your desktop that initiates the mount. This can be set up so that the desktop can only access the log dirs and only has read access. Even if you use remote plotting rigs that you access over the internet this is the most secure way and you most likely access your remote servers via ssh already.
Other Options: Mount the log folders of all rigs as network shares (via samba on linux) if all your plotting rigs are in the local network or connected via VPN. Another way would be to make a cronjob on your plotting rigs that uses scp or rsync in append mode to copy the log dir to your desktop where you run Chia Plot Status, but if you can manage to set this up you should set up sshfs instead. Last, least preferred option: collect them with cloud apps like Google Drive (Chia Plot Status does not talk to any cloud services for you, you have to install those apps and mount your log folders in them yourself if you want to use them).
**Best Practice:**
- Only delete log files of finished plots if your hardware or the way you plot has significantly changed. Chia Plot Status uses finished plots to calculate ETA/Time Remaining as well as warning/error thresholds. If you delete finished log files the quality of those values decreases significantly.
- Use SSHFS to access the log directories of your plotting rigs
- Each plotting rig should have its own log folder, so they don't mix and mess up estimates and warning thresholds for each other.
- Always log locally. If you log directly to a network share / NAS your plotting processes will crash if the connection becomes flaky. Prefer connecting your host machine (which runs Chia Plot Status) to networkshares on the plotting rigs, not the other way around.
## Security / Trustworthiness
See [a reddit comment made by the Chia Plot Status Core Developer, summarized in the following:](https://www.reddit.com/r/chia/comments/nlmwk7/safety_of_chiabot_from_joaquimguimaraes_on_github/gzn4xu3/?utm_source=reddit&utm_medium=web2x&context=3)
### There are multiple attack vectors to consider:
##### 1. The possibility that the core developer (me) is or becomes malicious
There is a saying: Where is a money trail, there is a way to sue/prosecute someone.
Chia Plot Status has buttons to donate via paypal both in the application itself and on the website.
Should the core developer (me) turn malicious, people could easily sue the core developer (me) and by that get the necessary details as in full name, address and day of birth, IBAN/Bic, everything from paypal.
If the core developer (me) becomes malicious this would be basically a how to get imprisoned speedrun (any %)
Even if you think you would not sue the core developer as he (me) might sit in a different country (germany), someone will as the Chia Plot Status Github Repository has between 2k to 4k visits daily and currently 24k downloads.
This should be more than enough to deter the core developer (me) from doing anything malicious.
#### 2. The core developer (me) merges a pull request (code changes made by someone else) which contains malicious code without noticing
As seen on [https://github.com/grayfallstown/Chia-Plot-Status/graphs/contributors](https://github.com/grayfallstown/Chia-Plot-Status/graphs/contributors) there is only one other person who contributed a pull request so far and that wasn't code but a documentation change.
The core developer (me) will check each pull request before merging as he (me) would have to run the code himself to check if the application works properly after merging that pull request and by that he (I) would get attacked by any malicious code that was contained in that pull request.
#### 3. External Dependencies (as in libraries / code written by someone else) the application uses to do certain things (like to create the graphical user interface) become malicious.
Well, this is a tough one as even the core developer (me) has very little means to check external binaries for malicious code. The core developer (me) and every other developer using those libraries will get attacked by any malicious code in those libraries before they (we) distribute a new version of their (our) software containing that library to the users of their (our) softwares, as they (we) generally test their (our) applications before each release.
The core developer (me) takes the following precautions to mitigate that risk:
- External dependencies are kept at a minimum to reduce this attack vector (chia-blockchain devs do the same)
- Every release build is build on the same system and previously downloaded dependencies are never deleted/redownloaded. This prevents pulling in malicious code if the external dependency version used gets replaced with malicious code. But it also prevents reproducible builds that everyone can follow and reproduce step by step on their system, if the external dependency version actually does get changed. Well, this should raise concern anyway and in any case.
- Updating Dependencies (external libraries / code written by someone else) is delayed (possibly indefinitely) until an update is required to implement a feature or to fix a bug. This gives anti virus providers time to determine if that library version is malicious, which would prevent an update.
## Installation / Download

Windows: [Download latest version](https://github.com/grayfallstown/Chia-Plot-Status/releases/latest/download/Setup.exe)
You will get a blue warning saying this was published by an unknown developer.
Linux: First install [dotnet 5.0 SDK](https://dotnet.microsoft.com/download/dotnet/5.0), then either the Chia Plot Status [deb](https://github.com/grayfallstown/Chia-Plot-Status/releases/latest/download/ChiaPlotStatus.linux-x64.deb) or [rpm](https://github.com/grayfallstown/Chia-Plot-Status/releases/latest/download/ChiaPlotStatus.linux-x64.rpm) package depending on your linux distribution (deb for ubuntu)
For Mac you currently have to [build it](#Build-it-).
## Getting Log Files from PowerShell
```
$Temp1="D:\PlotTemp"
$Temp2="D:\PlotTemp"
...
chia.exe plots create --tmp_dir "$TEMP1" --tmp2_dir "$TEMP2" [and so on] 2>&1 | % ToString | Tee-Object -FilePath "C:\Users\$USERNAME\.chia\mainnet\plotter\$([GUID]::NewGUID().ToString('D')).log"
```
The last part with `2>&1 | % ToString | Tee-Object` writes the log to the PowerShell and to a file with a unique name for each plotting process.
You can download a [full example script with Tee-Object](https://gist.github.com/grayfallstown/8530acb84eb131d3dae074e4be23badb) as well.
## Getting Log Files from madMAx43v3r/chia-plotter
On Windows without WSL:
Take the same command you are currently using and just add ` 2>&1 | % ToString | Tee-Object -FilePath "C:\Users\$env:UserName\.chia\mainnet\plotter\$([GUID]::NewGUID().ToString('D')).log"` at the end and run it in PowerShell, not CMD.
Note: there must be a whitespace between your command and this and there is nothing to be replaced in this line. Just leave it as it is.
On Windows with WSL:
```
# make sure you have got uuid installed (sudo apt install uuid -y)
# use 'chia keys show' to get this keys:
export POOLKEY="replace-me"; \
export FARMERKEY="replace-me"; \
export WINDOWS_USERNAME="replace-me" \
export THREADS="$(expr $(nproc) / 2)"; \
/home/mk/chia-plotter/build2/chia_plot \
--poolkey=$POOLKEY \
--farmerkey=$FARMERKEY \
--tmpdir2=replace-me \
--tmpdir=replace-me \
--finaldir=replace-me/ \
--count=-1 \
--threads=$THREADS \
--buckets=7 \
2>&1 | tee /mnt/c/Users/$WINDOWS_USERNAME/.chia/mainnet/plotter/chia-plotter-$(uuid).log
```
Note: There is nothing to be replaced in the last line. Just leave it as it is.
On Linux directly:
```
# make sure you have got uuid installed (sudo apt install uuid -y)
# use 'chia keys show' to get this keys:
export POOLKEY="replace-me"; \
export FARMERKEY="replace-me"; \
export THREADS="$(expr $(nproc) / 2)"; \
/home/mk/chia-plotter/build2/chia_plot \
--poolkey=$POOLKEY \
--farmerkey=$FARMERKEY \
--tmpdir2=replace-me \
--tmpdir=replace-me \
--finaldir=replace-me/ \
--count=-1 \
--threads=$THREADS \
--buckets=7 \
2>&1 | tee /home/$(whoami)/.chia/mainnet/plotter/chia-plotter-$(uuid).log;
```
Note: There is nothing to be replaced in the last line. Just leave it as it is.
## Need the columns in a different order?
See https://github.com/grayfallstown/Chia-Plot-Status/issues/36#issuecomment-843351280
## Troubleshooting
If you use Cloud Sync Services, rsync/scp cronjobs or tools like Syncthing to collect your log files you might run into an issue with the files not properly syncing. Sonething like `The process cannot access the file because it is being used by another process.`. See [Issue #40](https://github.com/grayfallstown/Chia-Plot-Status/issues/40#issuecomment-841025993) for how to fix that, or even better, use sshfs instead.
The same works if you use harry plotter as plotting manager.
If Chia Plot Status does no longer start, try renaming `ChiaPlotStatu.config.json` to `ChiaPlotStatu.config.json.backup`. The file is located in your home directory at `C:\Users\\ChiaPlotStatu.config.json` on windows, `/home//ChiaPlotStatu.config.json` on linux and `/ChiaPlotStatu.config.json` on mac.
## Custom tools / Home automation
You can export plot logs to json, yaml or csv both via the gui or the console for automation:
```
"C:\Program Files (x86)\ChiaPlotStatus\ChiaPlotStatus\ChiaPlotStatusCli.exe" --help
Copyright (C) 2021 grayfallstown
-o, --outfile Required. The file to write to
-f, --format Required. The format to use while writing the file. Valid values are json, yaml and csv
-l, --log-folders The folders where logs can be found, comma separated. Uses default folder when empty
-s, --sort-property The property you want the plotlogs sorted by
-a, --sort-asc Sort ascending
--search Filter plotlogs by this search terms. You filter for your temp1 folder for example.
--hide-finished Hide finished plots
--hide-possibly-dead Hide possibly dead plots
--hide-confirmed-dead Hide confirmed dead plots
--hide-healthy Hide healthy plots
--keep-updating Keep updating the file every 10 seconds
--help Display this help screen.
"C:\Program Files (x86)\ChiaPlotStatus\ChiaPlotStatus\ChiaPlotStatusCli.exe" -o test.json -f json
Sorting by Progress
File 'test.json' written
```
Note: Write your tools or home automation in a way that new fields/properties/columns added to the exported files do not crash it.
## Open Source
MIT opensource licence, free to keep or change.
## Build it yourself
This **should** work on x86_64bit, x86_32bit, ARM 64bit and ARM 32 bit Systems. If not, open an [Issue](https://github.com/grayfallstown/Chia-Plot-Status/issues/new) to tell me whats wrong.
Download and install [dotnet 5.0 SDK](https://dotnet.microsoft.com/download/dotnet/5.0) and [git](https://git-scm.com/).
On the console, clone/download this repository:
`git clone https://github.com/grayfallstown/Chia-Plot-Status.git`
Build it:
`cd Chia-Plot-Status`
`dotnet build --configuration .\ChiaPlotStatus.sln /p:Configuration=Release /p:Platform="Any CPU"`
Chia-Plot-Status can now be started with
windows: `.\ChiaPlotStatusGUI\bin\Release\net5.0\ChiaPlotStatus.exe`
linux: `./ChiaPlotStatusGUI/bin/Release/net5.0/ChiaPlotStatus`
macOS: `dotnet ./ChiaPlotStatusGUI/bin/Release/net5.0/ChiaPlotStatus.dll` (thanks @mahdi-ninja)
alternatively try `dotnet run --build`.
## Donations
PayPal: https://www.paypal.com/donate?hosted_button_id=PDLLVF5XVMJPC
Bitcoin/BTC bc1qy2fvr0js9xunlcgndlz9m9yu2qrydtlnlh4fgm
Etherium/ETH 0x0e07b5A73F571a98bAf19Fc42EBDE15d6B1664f0
Chia/XCH xch15p8swrrdt5ujv0dxy4hwjrvpjseyvuquwtfwnrjhxqt6ws9uf90qzq4axl
Tether/USDT 0x0e07b5A73F571a98bAf19Fc42EBDE15d6B1664f0
Binance Coin/BNB bnb1pes46l2jkw8dd9tw6j7sc4r9muzv5kuepnal0r
Cardano/ADA addr1q86sat0hpuvg5mp85qwke5kk6rjf6yntvr7epaz8j3k2g784p6klwrcc3fkz0gqadnfdd58yn5fxkc8ajr6y09rv53uqzjzv2r
XRP r9Wrpa2JsHh74nKqmcjJduy9GXDgmX44ic
USD Coin/USDC 0x0e07b5A73F571a98bAf19Fc42EBDE15d6B1664f0
Polkadot/DOT 16Mp3siK7qFJ9567R6ynPMNf3W9SYcumgUMXacMkH9UC7817
Uniswap/UNI 0x0e07b5A73F571a98bAf19Fc42EBDE15d6B1664f0
Litecoin/LTC LdbWCtQu7L9J9sYeDcp3M1qTWyA6Acck28
Bitcoin Cash/BCH qrxfzf3dact3crqu8l8uvxdp639scekw9qn20akrdc
Chainlink/LINK 0x0e07b5A73F571a98bAf19Fc42EBDE15d6B1664f0
Stellar/XLM GAIEQ6N7V77WLDYNFBESCGONNURKGCQZ2GS6THEENOWUSBVHQBBFL4XJ
VeChain/VET 0xBf8AC5799333a8B14A51228Be9E02629e034A039
Tron/TRX TKMnrH4P2L4PigJVNijZz9BQiSewrABosc
Dai/DAI 0x0e07b5A73F571a98bAf19Fc42EBDE15d6B1664f0
Neo/NEO ANZiysMZZS3PgzKpqTs8jATRcrpujZTBsB
Tezos/XTZ tz1XhtmaVr6RnqbTGPmPvD1XgsK7kHt8rUuH
Cosmos/ATOM cosmos1mu89q07xv9m8furg06f7tsw3u32da553wh0uv2
NEM/XEM NDM7SU2CVAN6CNOEFRMPPOIZSXQVYHIQ3BVJGH7T
0x/ZRX 0x0e07b5A73F571a98bAf19Fc42EBDE15d6B1664f0
Monero/XMR 43RbqKt37UUgY62ow4N3ptTT263zK1sfC88szAhThihU6bQKeURF3TrYLDumSak5gkX8Bj2FNzeWiduoEcPjLppHHSoBQi5
Etherium Classic/ETC 0x9347727443e8808c14062A7Fb95625B0284F2F8d
Flax/XFX xfx1622u30na4n83rya7nxkn5rpm78f4988e7cscl47ypct7wgnd7wus747nmk
ChainGreen/CGN cgn1zsfna95ju03juzpfcgyrfg4ljj2tq4ydpsdfxaegxx5dd6k3rjlqjgu9rr
## Special Thanks
- [@charlie](https://freeicons.io/profile/740) on [freeicons.io](https://freeicons.io) for the Logo [(details)](https://github.com/grayfallstown/Chia-Plot-Status/blob/main/Logo/Icon%20-%20Readme.txt)
- @ใครๆก็ทําได้ DiY
- @Alpha One
- @Çağlar
- @Cuello
- @DazEB2
- @DjMuffin_top
- @Dujapan
- @Gridjump
- @@getchiaplot
- @Hellfall1
- @Jazeon
- @Jonesyj83
- @JoseAngelB
- @KJP Gaming
- @Lucky_Length2676
- @Lyushen
- @Manic!
- @Mr pq
- @Ok-Studio5311
- @Oguzhan Oda
- @Patro TV
- @R3htribution
- @RaySmalley
- @RedxLus
- @SERVAK
- @TormodSan
- @Upside Down Crypto
- @Waloumi
- @WeAreNotAngels
- @Worldly-Mind3108
- @Zubon102
- @aDilly
- @badi95
- @bathrobehero
- @bestq8.com
- @blood5322
- @buettgenbach
- @c-pool
- @carfnann
- @chefsas
- @chiaxch
- @cyperbg
- @darkfriend77
- @djdookie81
- @dorofino
- @douwebusschops
- @dvlzgrmz
- @j.spracher
- @jcmarco
- @jimshank
- @johnamtl
- @jonnnny
- @kata32
- @littleneko
- @magallanesrafa
- @magnusmyklebost
- @massimo de rovere
- @mmoingame
- @ouoam
- @ozulu
- @puperinoo
- @raf-cr
- @revlisoft
- @rsegrest77
- @rul3s
- @sirdeekay
- @tajchert
- @tiberiu puscas
- @toddouimet
- @whitetechnologies
- @Vera Toro
- @whoismos3s
- @wild9
- @wizbowes
- @z.ostroff
- @zeroarst
- @The Malware Analysts of Microsoft and Malwarebytes for checking Chia Plot Status after every false positive
For contributing to Chia Plot Status either by [donating](https://www.paypal.com/donate?hosted_button_id=PDLLVF5XVMJPC) or otherwise.
================================================
FILE: next-minor.ps1
================================================
dotnet version --no-git --minor
cd ChiaPlotStatusGUI
dotnet version --no-git --minor
cd ..\ChiaPlotStatusCli
dotnet version --no-git --minor
cd ..\ChiaPlotStatusLib
dotnet version --no-git --minor
cd ..
================================================
FILE: next-patch.ps1
================================================
dotnet version --no-git --patch
cd ChiaPlotStatusGUI
dotnet version --no-git --patch
cd ..\ChiaPlotStatusCli
dotnet version --no-git --patch
cd ..\ChiaPlotStatusLib
dotnet version --no-git --patch
cd ..
================================================
FILE: release.ps1
================================================
Echo "Set Version!"
Echo ".\next-patch.ps1"
Echo ".\next-minor.ps1"
pause
Echo "Killing running ChiaPlotStatus.exe"
taskkill /IM "ChiaPlotStatus.exe" /F
taskkill /IM "ChiaPlotStatus.exe" /F
Echo "Cleaning release folder"
rm -r release\
mkdir release\
mkdir release\ChiaPlotStatus\
rm -r ChiaPlotStatusGUI\bin
rm -r ChiaPlotStatusCLI\bin
rm -r ChiaPlotStatusLIB\bin
Echo "Building linux deb and rpm packages"
cd ChiaPlotStatusGUI
dotnet deb -r linux-x64 -f net5.0 -c Release
dotnet rpm -r linux-x64 -f net5.0 -c Release
# dotnet publish -c Release -f net5.0 -r ubuntu.16.04-x64
# cd ..\ChiaPlotStatusGUI
# dotnet deb -r ubuntu.16.04-x64 -f net5.0 -c Release
# cp ..\ChiaPlotStatusCLI\bin\Release\net5.0\linux-x64\publish\ChiaPlotStatusCLI* .\bin\Release\net5.0\ubuntu.16.04-x64\publish\
# dotnet rpm -r ubuntu.16.04-x64 -f net5.0 -c Release
# cp bin\Release\net5.0\ubuntu.16.04-x64\ChiaPlotStatus.*.ubuntu.16.04-x64.rpm ..\release\ChiaPlotStatus.linux-x64.rpm
# dotnet deb -r ubuntu.16.04-x64 -f net5.0 -c Release
# dotnet deb -c Release
# cp bin\Release\net5.0\ChiaPlotStatus.*.deb ..\release\ChiaPlotStatus.linux-x64.deb
cp bin\Release\net5.0\linux-x64\ChiaPlotStatus*.deb ..\release\ChiaPlotStatus.linux-x64.deb
cp bin\Release\net5.0\linux-x64\ChiaPlotStatus*.rpm ..\release\ChiaPlotStatus.linux-x64.rpm
cd ..
# Echo "Building for MacOS"
# dotnet publish -r osx-x64 --configuration .\ChiaPlotStatus.sln /p:Configuration=Release /p:Platform="Any CPU"
# dotnet pkg -r osx-x64 -f net5.0 -c Release
Echo "Building for Windows"
dotnet publish -r win-x64 --configuration .\ChiaPlotStatus.sln /p:Configuration=Release /p:Platform="Any CPU"
Echo ""
Echo "The warnings are normal. Only formal flaws in the code"
Echo ""
Echo "Copying Windows build to release folder"
xcopy /sy C:\Users\mk\IdeaProjects\ChiaPlotStatus\ChiaPlotStatusLib\bin\Release\net5.0\win-x64\publish\* release\ChiaPlotStatus\
xcopy /sy C:\Users\mk\IdeaProjects\ChiaPlotStatus\ChiaPlotStatusGUI\bin\Release\net5.0\win-x64\publish\* release\ChiaPlotStatus\
xcopy /sy C:\Users\mk\IdeaProjects\ChiaPlotStatus\ChiaPlotStatusCli\bin\Release\net5.0\win-x64\publish\* release\ChiaPlotStatus\
Echo "Now open and build setup using InstallForge and InstallerConfig.ifp as config"