RRDTool.psm1

$RRDTool = '{0}\bin\rrdtool.exe' -f $PSScriptRoot

try {
    & $RRDTool | Out-Null
} catch {
    Write-Error ('rrdtool.exe not in module {0}\bin folder' -f $PSScriptRoot)
    break
}

Function Invoke-NativeCommand {
    Param(
        [String] $Command,
        [switch] $SuppressOutput
    )
    $cmd = '{0} 2>&1' -f $Command
    $Result = Invoke-Expression -Command $cmd
    $Result | %{ $err = '' } {
        if ($_.WriteErrorStream) {
            $err += [string] $_
        } else {
            if (-not $SuppressOutput) {$_}
        }
    } {
        if ($err -ne '') {
            Write-Error $err
        }
    }
}

Function New-RRD {
    Param(
        [Parameter(Mandatory)]
        [String] $FileName,
        [DateTime] $Start = ((Get-Date).AddSeconds(-10)),
        [int] $Step = 300,
        [Parameter(Mandatory)]
        [PSCustomObject[]] $DS,
        [Parameter(Mandatory)]
        [PSCustomObject[]] $RRA
    )

    $StartUnixTime = [int][double]::Parse((Get-Date $Start.ToUniversalTime() -UFormat %s))
    $DSs = $DS -join ' '
    $RRAs = $RRA -join ' '
    $cmd = '{0} create {1} --step {2} --start {3} {4} {5}' -f $RRDTool, $FileName, $Step, $StartUnixTime, $DSs, $RRAs

    Write-Verbose $cmd

    try {
        Invoke-NativeCommand -Command $cmd -SuppressOutput
    } catch {
        Write-Error $Error
    }
}

Function New-RRDDataSource {
    Param(
        [Parameter(Mandatory)]
        [ValidatePattern('^[a-zA-Z0-9_]{1,19}$')]
        [String] $Name,
        [Parameter(Mandatory)]
        [ValidateSet('GAUGE', 'COUNTER', 'DERIVE', 'DCOUNTER', 'DDERIVE', 'COMPUTE')]
        [alias('DST')]
        [String] $DataSourceType,
        [Parameter(ParameterSetName='NoCompute', Mandatory)]
        [int] $Heartbeat,
        [Parameter(ParameterSetName='NoCompute')]
        $Min = 'U',
        [Parameter(ParameterSetName='NoCompute')]
        $Max = 'U',
        [Parameter(ParameterSetName='Compute', Mandatory)]
        [Scriptblock] $Expression
    )

    $DS = [ordered] @{
        Name = $Name
        DST = $DataSourceType
    }

    if ($PSCmdlet.ParameterSetName -eq 'NoCompute') {
        $DS['Heartbeat'] = $Heartbeat
        $DS['Min'] = $Min
        $DS['Max'] = $Max
    } elseif ($PSCmdlet.ParameterSetName -eq 'Compute') {
        $DS['Expression'] = $Expression
    }

    Write-Output (ConvertFrom-RRD -Object $DS -Type 'DS:')
}

Function New-RRDRoundRobinArchive {
    Param(
        [Parameter(Mandatory)]
        [ValidateSet('AVERAGE', 'MIN', 'MAX', 'LAST')]
        [alias('CF')]
        [String] $ConsolidationFunction,
        [ValidateRange(0,1)]
        [float] $XFilesFactor = 0.5,
        [Parameter(Mandatory)]
        [int] $Steps,
        [Parameter(Mandatory)]
        [int] $Rows
    )

    $RRA = [ordered] @{
        CF = $ConsolidationFunction
        XFF = "$XFilesFactor"
        Steps = $Steps
        Rows = $Rows
    }

    Write-Output (ConvertFrom-RRD -Object $RRA -Type 'RRA:')
}

Function New-RRDPrint {
    Param(
        [Parameter(Mandatory)]
        [String] $Name,
        [Parameter(Mandatory)]
        [String] $Format
    )

    $PRINT = [ordered] @{
        Name = $Name
        Format = '"{0}"' -f ($Format -replace ':', '\:')
    }

    Write-Output (ConvertFrom-RRD -Object $PRINT -Type 'PRINT:')
}

Function New-RRDGPrint {
    Param(
        [Parameter(Mandatory)]
        [String] $Name,
        [Parameter(Mandatory)]
        [String] $Format
    )

    $GPRINT = [ordered] @{
        Name = $Name
        Format = '"{0}"' -f ($Format -replace ':', '\:')
    }

    Write-Output (ConvertFrom-RRD -Object $GPRINT -Type 'GPRINT:')
}

Function New-RRDComment {
    Param(
        [Parameter(Mandatory)]
        [String] $Text
    )
    
    $COMMENT = [ordered] @{
        Text = '"{0}"' -f ($Text -replace ':', '\:')
    }

    Write-Output (ConvertFrom-RRD -Object $COMMENT -Type 'COMMENT:')
}

Function New-RRDShift {
    Param(
        [Parameter(Mandatory)]
        [String] $Name,
        [Parameter(Mandatory)]
        [int] $Offset
    )

    $SHIFT = [ordered] @{
        Name = $Name
        Offset = $Offset
    }

    Write-Output (ConvertFrom-RRD -Object $SHIFT -Type 'SHIFT:')
}

Function New-RRDTick {
    Param(
        [Parameter(Mandatory)]
        [String] $Name,
        [Parameter(Mandatory)]
        [ValidatePattern('^#[\da-fA-F]{6}([\da-fA-F]{2})?$')]
        [String] $Color,
        [float] $Fraction,
        [String] $Legend
    )

    $TICK = [ordered] @{
        Name = '{0}{1}' -f $Name, $Color
    }

    if ($Fraction) {$TICK['Fraction'] = $Fraction}
    if ($Legend -and -not $Fraction) {
        $TICK['Fraction'] = $null
        $TICK['Legend'] = $Legend
    } elseif ($Legend) {
        $TICK['Legend'] = $Legend
    }

    Write-Output (ConvertFrom-RRD -Object $TICK -Type 'TICK:')
}

Function New-RRDHRule {
    Param(
        [Parameter(Mandatory)]
        [String] $Value,
        [Parameter(Mandatory)]
        [ValidatePattern('^#[\da-fA-F]{6}([\da-fA-F]{2})?$')]
        [String] $Color,
        [String] $Legend
    )

    $HRULE = [ordered] @{
        Value = '{0}{1}' -f $Value, $Color
    }
    if ($Legend) {$HRULE['Legend'] = $Legend}

    Write-Output (ConvertFrom-RRD -Object $HRULE -Type 'HRULE:')
}

Function New-RRDVRule {
    Param(
        [Parameter(Mandatory)]
        [String] $Value,
        [Parameter(Mandatory)]
        [ValidatePattern('^#[\da-fA-F]{6}([\da-fA-F]{2})?$')]
        [String] $Color,
        [String] $Legend
    )
    
    $VRULE = [ordered] @{
        Value = '{0}{1}' -f $Value, $Color
    }
    if ($Legend) {$VRULE['Legend'] = $Legend}

    Write-Output (ConvertFrom-RRD -Object $VRULE -Type 'VRULE:')
}

Function New-RRDLine {
    Param(
        [Parameter(Mandatory)]
        [String] $Name,
        [float] $Width = 1,
        [ValidatePattern('^#[\da-fA-F]{6}([\da-fA-F]{2})?$')]
        [String] $Color,
        [String] $Legend,
        [switch] $Stack
    )

    $LINE = [ordered] @{
        Width = "$Width"
        Name = $Name
    }
    
    if ($Color) {$LINE['Name'] += $Color}
    if ($Legend) {$LINE['Legend'] = '"{0}"' -f $Legend}
    if ($Stack -and -not $Legend) {
        $LINE['Legend'] = $null
        $LINE['Stack'] = 'STACK'
    } elseif ($Stack) {
        $LINE['Stack'] = 'STACK'
    }

    Write-Output (ConvertFrom-RRD -Object $LINE -Type 'LINE')
}

Function New-RRDArea {
    Param(
        [Parameter(Mandatory)]
        [String] $Name,
        [ValidatePattern('^#[\da-fA-F]{6}([\da-fA-F]{2})?$')]
        [String] $Color,
        [String] $Legend,
        [switch] $Stack
    )

    $AREA = [ordered] @{
        Name = $Name
    }
    
    if ($Color) {$AREA['Name'] += $Color}
    if ($Legend) {$AREA['Legend'] = '"{0}"' -f $Legend}
    if ($Stack -and -not $Legend) {
        $AREA['Legend'] = $null
        $AREA['Stack'] = 'STACK'
    } elseif ($Stack) {
        $AREA['Stack'] = 'STACK'
    }

    Write-Output (ConvertFrom-RRD -Object $AREA -Type 'AREA:')
}

Function New-RRDDef {
    Param(
        [Parameter(Mandatory)]
        [String] $Name,
        [Parameter(Mandatory)]
        [String] $FileName,
        [Parameter(Mandatory)]
        [String] $DataSource,
        [Parameter(Mandatory)]
        [ValidateSet('AVERAGE', 'MIN', 'MAX', 'LAST')]
        [alias('CF')]
        [String] $ConsolidationFunction,
        [int] $Step,
        [DateTime] $Start,
        [DateTime] $End,
        [ValidateSet('AVERAGE', 'MIN', 'MAX', 'LAST')]
        [String] $Reduce
    )

    $DEF = [ordered] @{
        Name = '{0}={1}' -f $Name, ($FileName -replace ':', '\:')
        DS = $DataSource
        CF = $ConsolidationFunction
    }

    if ($Step) {$DEF['Step'] = 'step={0}' -f $Step}
    if ($Start) {$DEF['Start'] = 'start={0}' -f [int][double]::Parse((Get-Date $Start.ToUniversalTime() -UFormat %s))}
    if ($End) {$DEF['End'] = 'end={0}' -f [int][double]::Parse((Get-Date $End.ToUniversalTime() -UFormat %s))}
    if ($Reduce) {$DEF['Reduce'] = 'reduce={0}' -f $Reduce}

    Write-Output (ConvertFrom-RRD -Object $DEF -Type 'DEF:')
}

Function New-RRDVdef {
    Param(
        [Parameter(Mandatory)]
        [String] $Name,
        [Parameter(Mandatory)]
        [String] $RPNExpression
    )

    $VDEF = [ordered] @{
        Name = '{0}="{1}"' -f $Name, $RPNExpression
    }

    Write-Output (ConvertFrom-RRD -Object $VDEF -Type 'VDEF:')
}

Function New-RRDCdef {
    Param(
        [Parameter(Mandatory)]
        [String] $Name,
        [Parameter(Mandatory)]
        [String] $RPNExpression
    )

    $CDEF = [ordered] @{
        Name = '{0}={1}' -f $Name, $RPNExpression
    }

    Write-Output (ConvertFrom-RRD -Object $CDEF -Type 'CDEF:')
}

Function Update-RRD {
    Param(
        [Parameter(Mandatory)]
        [ValidateScript({Test-Path $_})]
        [String] $FileName,
        [DateTime] $Timestamp = (Get-Date),
        [object[]] $Value
    )

    $UnixTime = [int][double]::Parse((Get-Date $Timestamp.ToUniversalTime() -UFormat %s))
    $cmd = '{0} update {1} {2}:{3}' -f $RRDTool, $FileName, $UnixTime, ($Value -replace ',','.' -join ':')
    Write-Verbose $cmd

    try {
        Invoke-NativeCommand -Command $cmd -SuppressOutput
    } catch {
        Write-Error $Error
    }
}

Function Out-RRDGraph {
    Param(
        [Parameter(Mandatory)]
        [String] $FileName,
        [DateTime] $Start,
        [DateTime] $End,
        [String] $XGrid,
        [switch] $AltYGrid,
        [String] $YGrid,
        [String] $VerticalLabel,
        [int] $Width,
        [String] $RightAxis,
        [String] $RightAxisLabel,
        [String] $RightAxisFormat,
        [int] $Height,
        [switch] $Logarithmic,
        [double] $UpperLimit,
        [double] $LowerLimit,
        [switch] $Lazy,
        [switch] $Rigid,
        [switch] $NoLegend,
        [switch] $ForceRulesLegend,
        [switch] $OnlyGraph,
        [ValidatePattern('^(DEFAULT|TITLE|AXIS|UNIT|LEGEND):\d+(:.*)?$')]
        [String[]] $Font,
        [ValidateScript({$_ -gt 0})]
        [int] $Zoom,
        [switch] $AltAutoscale,
        [switch] $AltAutoscaleMax,
        [ValidateSet('NORMAL', 'LIGHT', 'MONO')]
        [String] $FontRenderMode,
        [float] $FontSmoothingThreshold,
        [switch] $SlopeMode,
        [switch] $NoGridfit,
        [ValidateSet(-18,-15,-12,-9,-6,-3,0,3,6,9,12,15,18)]
        [int] $UnitsExponent,
        [int] $UnitsLength,
        [int] $Step,
        [String] $Imginfo,
        [ValidateSet('PNG','SVG','EPS','PDF','XML','XMLENUM','JSON','JSONTIME','CSV','TSV','SSV')]
        [String] $Imgformat,
        [ValidatePattern('^(BACK|CANVAS|SHADEA|SHADEB|GRID|MGRID|FONT|AXIS|FRAME|ARROW)#[\da-fA-F]{6}([\da-fA-F]{2})?$')]
        [String[]] $Color,
        [String] $Title,
        [String] $Watermark,
        [Parameter(Mandatory)]
        [object[]] $Elements
    )

    $param = ''
    if ($Start -ne $null) {$param += ' --start {0}' -f ([int][double]::Parse((Get-Date $Start.ToUniversalTime() -UFormat %s)))}
    if ($End -ne $null) {$param += ' --end {0}' -f ([int][double]::Parse((Get-Date $End.ToUniversalTime() -UFormat %s)))}
    if ($XGrid) {$param += ' --x-grid "{0}"' -f $XGrid}
    if ($AltYGrid) {$param += ' --alt-y-grid'}
    if ($YGrid) {$param += ' --y-grid "{0}"' -f $YGrid}
    if ($VerticalLabel) {$param += ' --vertical-label "{0}"' -f $VerticalLabel}
    if ($Width) {$param += ' --width {0}' -f $Width}
    if ($RightAxis) {$param += ' --right-axis {0}' -f $RightAxis}
    if ($RightAxisLabel) {$param += ' --right-axis-label "{0}"' -f $RightAxisLabel}
    if ($RightAxisFormat) {$param += ' --right-axis-format "{0}"' -f $RightAxisFormat}
    if ($Height) {$param += ' --height {0}' -f $Height}
    if ($Logarithmic) {$param += ' --logarithmic'}
    if ($UpperLimit) {$param += ' --upper-limit {0}' -f $UpperLimit}
    if ($PSBoundParameters.ContainsKey('LowerLimit')) {$param += ' --lower-limit {0}' -f $LowerLimit}
    if ($Lazy) {$param += ' --lazy'}
    if ($Rigid) {$param += ' --rigid'}
    if ($NoLegend) {$param += ' --no-legend'}
    if ($ForceRulesLegend) {$param += ' --force-rules-legend'}
    if ($OnlyGraph) {$param += ' --only-graph'}
    if ($Font) {$param += $Font | %{' --font {0}' -f $_}}
    if ($Zoom) {$param += ' --zoom {0}' -f $Zoom}
    if ($AltAutoscale) {$param += ' --alt-autoscale'}
    if ($AltAutoscaleMax) {$param += ' --alt-autoscale-max'}
    if ($FontRenderMode) {$param += ' --font-render-mode {0}' -f $FontRenderMode}
    if ($FontSmoothingThreshold) {$param += ' --font-smoothing-threshold {0}' -f $FontSmoothingThreshold}
    if ($SlopeMode) {$param += ' --slope-mode'}
    if ($NoGridfit) {$param += ' --no-gridfit'}
    if ($UnitsExponent) {$param += ' --units-exponent {0}' -f $UnitsExponent}
    if ($UnitsLength) {$param += ' --units-length {0}' -f $UnitsLength}
    if ($Step) {$param += ' --step {0}' -f $Step}
    if ($Imginfo) {$param += ' --imginfo "{0}"' -f $Imginfo}
    if ($Imgformat) {$param += ' --imgformat "{0}"' -f $Imgformat}
    if ($Color) {$param += $Color | %{' --color "{0}"' -f $_}}
    if ($Title) {$param += ' --title "{0}"' -f $Title}
    if ($Watermark) {$param += ' --watermark "{0}"' -f $Watermark}

    $cmd = '{0} graph {1} {2} {3}' -f $RRDTool, $FileName, $param, ($Elements -join ' ')
    Write-Verbose $cmd

    try {
        Invoke-NativeCommand -Command $cmd -SuppressOutput
    } catch {
        Write-Error $Error
    }

}

Function ConvertFrom-RRD {
    Param(
        [Parameter(ValueFromPipeline)]
        [PSCustomObject[]] $Object,
        [ValidateSet('DS:','RRA:','DEF:','CDEF:','VDEF:','AREA:','COMMENT:','GPRINT:','HRULE:','LINE','PRINT:','SHIFT:','TICK:','VRULE:')]
        [String] $Type
    )

    PROCESS {
        foreach ($Obj in $Object) {
            $ret = @()
            foreach ($Val in $Obj.Values) {
                $ret += $Val
            }
            Write-Output ('{0}{1}' -f $Type, ($ret -join ':'))
        }
    }
}


Export-ModuleMember -Function New-RRD
Export-ModuleMember -Function New-RRDArea
Export-ModuleMember -Function New-RRDCDef
Export-ModuleMember -Function New-RRDComment
Export-ModuleMember -Function New-RRDDataSource
Export-ModuleMember -Function New-RRDDef
Export-ModuleMember -Function New-RRDGPrint
Export-ModuleMember -Function New-RRDHRule
Export-ModuleMember -Function New-RRDLine
Export-ModuleMember -Function New-RRDPrint
Export-ModuleMember -Function New-RRDRoundRobinArchive
Export-ModuleMember -Function New-RRDShift
Export-ModuleMember -Function New-RRDTick
Export-ModuleMember -Function New-RRDVDef
Export-ModuleMember -Function New-RRDVRule
Export-ModuleMember -Function Out-RRDGraph
Export-ModuleMember -Function Update-RRD