PSChiaPlotter.psm1

enum KSize{
    K32 = 32
    K33 = 33
    K34 = 34
    K35 = 35
}

class MaximizedKSize {
    [KSize]$KSize
    [int]$MaxPlots
    [Decimal]$RemainingBytes
    [Decimal]$KSizeBytes
    [int64]$TotalBytes

    static [Decimal]$K35 = 884.1 * 1gb
    static [Decimal]$K34 = 429.8 * 1gb
    static [Decimal]$K33 = 208.8 * 1gb
    static [Decimal]$K32 = 101.4 * 1gb

    MaximizedKSize(
        [KSize]$KSize,
        [int64]$TotalBytes
    ){
        $this.KSize = $Ksize
        $this.TotalBytes = $TotalBytes

        $this.KSizeBytes = switch ($this.KSize){
            "K35" {[MaximizedKSize]::K35}
            "K34" {[MaximizedKSize]::K34}
            "K33" {[MaximizedKSize]::K33}
            "K32" {[MaximizedKSize]::K32}
        }
        $this.MaxPlots = [math]::Floor([decimal]($this.TotalBytes / $this.KSizeBytes))
        $this.RemainingBytes = $Totalbytes - (([math]::Floor([decimal]($this.TotalBytes / $this.KSizeBytes))) * $this.KSizeBytes)
    }
}
class OptimizedKPlots {
    [int]$K35
    [int]$K34
    [int]$K33
    [int]$K32
    [decimal]$RemainingBytes
    [double]$RemainingGB

    OptimizedKPlots (
        [int]$K35,
        [int]$K34,
        [int]$K33,
        [int64]$Totalbytes
    ){
        $sizeremaining = $TotalBytes - (($K35 * [MaximizedKSize]::K35) + ($K34 * [MaximizedKSize]::K34) + ($K33 * [MaximizedKSize]::K33))
        $k32max = Get-MaxKSize -Totalbytes $sizeremaining -KSize "K32"
        $this.K35 = $K35
        $this.K34 = $K34
        $this.K33 = $K33
        $this.K32 = $k32max.MaxPlots
        $this.RemainingBytes = $k32max.RemainingBytes
        $this.RemainingGB = [math]::Round($k32max.RemainingBytes / 1gb,2)
    }
}
function ConvertTo-FriendlyTimeSpan {
    [CmdletBinding()]
    param(
        [int32]$Seconds
    )

    $TimeSpan = New-TimeSpan -Seconds $Seconds
    switch ($TimeSpan){
        {$_.Days -ge 1} {return "$([math]::Round($TimeSpan.TotalDays,2)) days";break}
        {$_.Hours -ge 1} {return "$([math]::Round($TimeSpan.TotalHours,2)) hrs";break}
        {$_.Minutes -ge 1} {return "$([math]::Round($TimeSpan.TotalMinutes,2)) mins";break}
        {$_.seconds -ge 1} {return "$([math]::Round($TimeSpan.TotalSeconds,2)) sec";break}
    }
}
function Get-ChiaKPlotCombination{
    [CmdletBinding(DefaultParameterSetName = "DriveLetter")]
    param(
        [Parameter(ParameterSetName="FreeSpace")]
        [int64[]]$FreeSpace,
        [Parameter(ParameterSetName="DriveLetter")]
        [string[]]$DriveLetter = (Get-Volume).DriveLetter
    )

    if ($PSCmdlet.ParameterSetName -eq "FreeSpace"){
        foreach ($space in $FreeSpace){
            $Max = Get-MaxKSize -TotalBytes $space
            $AllCombos = Get-OptimizedKSizePlotNumbers $Max | sort RemainingBytes
            $AllCombos | Add-Member -MemberType NoteProperty -Name "StartingFreeSpace" -Value $space
            $AllCombos
        }
    }
    elseif ($PSCmdlet.ParameterSetName -eq "DriveLetter"){
        foreach ($letter in $DriveLetter){
            $Drive = Get-Volume -DriveLetter $letter
            $Max = Get-MaxKSize -TotalBytes $Drive.SizeRemaining
            $AllCombos = Get-OptimizedKSizePlotNumbers $Max | sort RemainingBytes
            $AllCombos | Add-Member -NotePropertyMembers @{
                DriveLetter = $letter
                FriendlyName = $Drive.FileSystemLabel
            }
            $AllCombos | foreach {$_.psobject.TypeNames.Insert(0,"PSChiaPlotter.KSizeCombination")}
            $AllCombos
        }
    }
}
function Get-ChiaMaxParallelCount {
    [CmdletBinding()]
    param(
        [Parameter()]
        [ValidateRange(1,128)]
        [int]$ThreadCount = 2,
        [Parameter()]
        [ValidateRange(1, [int]::MaxValue)]
        [int]$BufferMiB = 3390
    )

    if (!$PSBoundParameters.ContainsKey("ThreadCount") -and !$PSBoundParameters.ContainsKey("BufferMiB")){
        Write-Warning "All calculations based on plotting k32 plot size only. SSD TB suggestion rounded up to the nearest TB."
    }
    else{
        Write-Warning "SSD TB suggestion rounded up to the nearest TB."
    }
    $Processor = Get-CimInstance -ClassName Win32_Processor
    $Threads = ($Processor | measure -Property ThreadCount -Sum).Sum
    $MaxParallelCountCPU = [math]::Floor($Threads / $ThreadCount)
    #1mb = 1048576 bytes
    $RAM = (Get-CimInstance -ClassName Win32_PhysicalMemory | measure -Property Capacity -Sum).Sum / 1mb
    $MaxParallelCountRAM = [Math]::Floor([decimal]($RAM / $BufferMiB))

    $SystemDisk = Get-CimInstance -Namespace ROOT/Microsoft/Windows/Storage -ClassName MSFT_Disk -Filter "IsSystem=True"
    $SSDs = Get-CimInstance -Namespace root/microsoft/windows/storage -ClassName MSFT_PhysicalDisk -Filter "MediaType=4" #4 -eq SSD
    $SSDs = $SSDs | where UniqueId -ne $SystemDisk.UniqueId | select 
    $One_TB = 1000000000000
    $One_GB = 1000000000
    $TotalSSDspace = ($SSDs | measure -Property Size -Sum).Sum
    $SSD_Count = ($SSDs | Measure-Object).Count
    if ($SSD_Count -eq 0){
        Write-Warning "No non-system SSD found, therefore Current_MaxParallelPlots will be 0. (Ignore if using mutiple HDDs)"
    }

    if ($Threads -gt ($Processor.NumberOfCores * 2)){
        Write-Warning "Threads may actually only be half what is reported and therefore all calculations are off."
    }

    $SSD_MAX = [math]::Floor([decimal]($TotalSSDspace / (256.6 * $One_GB)))

    if ($MaxParallelCountCPU -le $MaxParallelCountRAM){
        $MAXCount = $MaxParallelCountCPU
        $BottleNeck = "CPU"        
    }
    else{
        $MAXCount = $MaxParallelCountRAM
        $BottleNeck = "RAM"        
    }
    $Suggested_SSD_TB = [math]::Ceiling([decimal](256.6 * $MAXCount) / 1000)

    if ($SSD_MAX -le $MAXCount){
        $CurrentMax = $SSD_MAX
        $BottleNeck = "SSD"
    }
    else{
        $CurrentMax = $MAXCount
    }

    $Suggested_SSD_TB = [math]::Ceiling([decimal](256.6 * $MAXCount) / 1000)

    [PSCustomObject]@{
        ThreadCount = $ThreadCount
        Buffer = $BufferMiB
        CPUTotalThreads = $Threads
        CPUCores = ($Processor | Measure -Property NumberOfCores -Sum).Sum
        NumberOfProcessors = ($Processor | measure).Count
        TotalRAM_MiB = $RAM
        BottleNeck = $BottleNeck
        Current_SSD_SPACE_TB = [math]::Round(($TotalSSDspace / $One_TB),2)
        Current_SSD_Count = $SSD_Count
        Suggested_SSD_SPACE_TB = $Suggested_SSD_TB
        Current_MaxParallelPlots = $CurrentMax
        Potential_MAXParallelPlots = $MAXCount
    }
}
function Get-ChiaPlottingStatistic {
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline,ValueFromPipelineByPropertyName)]
        [string[]]$Path = (Get-ChildItem -Path $env:USERPROFILE\.chia\mainnet\plotter\ | sort CreationTime -Descending).FullName
    )

    Process{
        foreach ($log in $path){
            if (Test-Path $log){
                $Content = Get-Content -Path $log | Select-String "Time for phase","Total time","Plot size","Buffer size","threads of stripe","Copy time","Copied final file from","Starting plotting progress into temporary dirs" | foreach {$_.ToString()}
                foreach ($line in $Content){
                    switch -Wildcard ($line){
                        "Plot size*" {$PlotSize = $line.split(' ') | select -Skip 3} #using select for these since indexing will error if empty
                        "Buffer Size*" {$BufferSize = ($line.Split(' ') | select -Skip 3).split("M") | select -First 1}
                        "*threads*" {$ThreadCount = $line.split(' ') | select -First 1 -Skip 1}
                        "*phase 1*" {$Phase_1 = $line.Split(' ') | select -First 1 -Skip 5}
                        "*phase 2*" {$Phase_2 = $line.Split(' ') | select -First 1 -Skip 5}
                        "*phase 3*" {$Phase_3 = $line.Split(' ') | select -First 1 -Skip 5}
                        "*phase 4*" {$phase_4 = $line.Split(' ') | select -First 1 -Skip 5}
                        "Total time*" {$TotalTime = $line.Split(' ') | select -First 1 -Skip 3}
                        "Copy time*" {$CopyTime = $line.Split(' ') | select -First 1 -Skip 3}
                        "Starting plotting progress into temporary dirs*" {$TempDrive = ($line.Split(' ') | select -First 1 -Skip 6).Split('\') | select -First 1 }
                        "Copied final file from*" {$FinalDrive = ($line.Split(' ') | select -First 1 -Skip 6).Split('\').Replace('"', '') | select -First 1}
                        default {Write-Information "Could not match line: $line"}
                    }
                }
                [PSCustomObject]@{
                    PSTypeName = "PSChiaPlotter.ChiaPlottingStatistic"
                    KSize = $PlotSize
                    "RAM(MiB)" = $BufferSize
                    Threads = $ThreadCount
                    "Phase_1_sec" = [int]$Phase_1
                    "Phase_2_sec" = [int]$Phase_2
                    "Phase_3_sec" = [int]$Phase_3
                    "Phase_4_sec" = [int]$phase_4
                    "TotalTime_sec" = [int]$TotalTime
                    "CopyTime_sec" = [int]$CopyTime
                    "PlotAndCopyTime_sec" = ([int]$CopyTime + [int]$TotalTime)
                    "Time_Started" = (Get-Item -Path $log).CreationTime 
                    "Temp_drive" = $TempDrive
                    "Final_drive" = $FinalDrive
                }
                Clear-Variable -Name "Phase_1","Phase_2","Phase_3","Phase_4","TotalTime","CopyTime" -ErrorAction SilentlyContinue
            }
        }
    }
}
function Get-ChiaRAMInfo {
    [CmdletBinding()]
    param(
    )

    Begin{

    } #Begin

    Process{
    
        $Array = Get-CimInstance -Class Win32_PhysicalMemoryArray
        $CurrentRAM = Get-CimInstance -Class Win32_PhysicalMemory

        [PSCustomObject]@{
            PSTypeName = "PSChiaPlotter.RAMInfo"
            ComputerName = $ENV:COMPUTERNAME
            SlotsInUse = ($CurrentRAM | Measure).Count
            SlotsFree = $Array.MemoryDevices - ($CurrentRAM | Measure).Count
            CurrentSize_GB = (($CurrentRAM).Capacity | Measure -Sum).Sum / 1gb
            MaxSize_GB = $Array.MaxCapacityEx / 1mb
            PartNumber = ($CurrentRAM.PartNumber | Select -Unique | foreach {$_.Trim()})
            Manufacturer = ($CurrentRAM.Manufacturer | Select -Unique | foreach {$_.Trim()})
            TotalSlots = $Array.MemoryDevices
            RAMDevices = $CurrentRAM
        }
    } #Process
}
function Get-MaxKSize {
    [CmdletBinding()]
    param(
        [ValidateSet("K32","K33","K34","K35")]
        [string[]]$KSize = ("K32","K33","K34","K35"),

        [Parameter(Mandatory)]
        [int64]$TotalBytes
    )

    foreach ($size in $KSize){
        [MaximizedKSize]::new($size,$TotalBytes)
    } #foreach
}
function Get-OptimizedKSizePlotNumbers {
    [CmdletBinding()]
    param(
        [MaximizedKSize[]]$MaximizedKSize
    )

    foreach ($size in $MaximizedKSize){
        switch ($size.KSize){
            "K32" {
                [OptimizedKPlots]::new(0,0,0,$Size.TotalBytes)
            }

            "K33" {
                for ($K33Count = 1; $K33Count -le $size.MaxPlots; $K33Count++){
                    [OptimizedKPlots]::new(0,0,$K33Count,$Size.TotalBytes)
                } #for
            }
            "K34" {
                for ($K34Count = 1; $K34Count -le $size.maxplots; $K34Count++){
                    [OptimizedKPlots]::new(0,$K34Count,0,$Size.TotalBytes)

                    $k34sizeremaining = $Size.TotalBytes - ($K34Count * $size.KSizeBytes)
                    $K33Max = Get-MaxKSize -TotalBytes $k34sizeremaining -KSize "K33"
                    for ($k33 = 1; $k33 -le $k33max.MaxPlots; $k33++){
                        [OptimizedKPlots]::new(0,$K34Count,$k33,$Size.TotalBytes)
                    } #for 33
                } #for 34
            } #34

            "K35" {
                for ($k35count = 1; $k35count -le $size.maxplots; $k35count++){

                    [OptimizedKPlots]::new($k35count,0,0,$Size.TotalBytes)

                    $k35sizeremaining = $Size.TotalBytes - ($k35count * $size.KSizeBytes)
                    $k33max = Get-MaxKSize -Totalbytes $k35sizeremaining -KSize "K33"

                    for ($k33 = 1; $k33 -le $k33max.MaxPlots; $k33++){
                        [OptimizedKPlots]::new($k35count,0,$k33,$Size.TotalBytes)
                    } #for 33

                    $k34max = Get-MaxKSize -Totalbytes $k35sizeremaining -KSize "K34"
                    for ($k34 = 1; $k34 -le $k34max.maxplots; $k34++){
                        [OptimizedKPlots]::new($k35count,$k34,0,$Size.TotalBytes)

                        $sizeremaining = $Size.TotalBytes - (($k35count * $size.KSizeBytes) + ($k34 * $k34max.KSizeBytes))
                        $K33max = Get-MaxKSize -TotalBytes $sizeremaining -KSize "K33"

                        for ($k33 = 1;$k33 -le $k33max.maxplots; $k33++){
                            [OptimizedKPlots]::new($k35count,$k34,$k33,$Size.TotalBytes)
                        }
                    }
                }
            }
        } #switch
    } #foreach
}
Export-ModuleMember -function ConvertTo-FriendlyTimeSpan, Get-ChiaKPlotCombination, Get-ChiaMaxParallelCount, Get-ChiaPlottingStatistic, Get-ChiaRAMInfo