Private/Checks/VCenter/Test-VcfVcenterCheckDiskSpaceInodeAndHprof.ps1

# Copyright (c) 2026 Broadcom. All Rights Reserved.
# Broadcom Confidential. The term "Broadcom" refers to Broadcom Inc.
# and/or its subsidiaries.
#
# =============================================================================
#
# SOFTWARE LICENSE AGREEMENT
#
# Copyright (c) CA, Inc. All rights reserved.
#
# You are hereby granted a non-exclusive, worldwide, royalty-free license
# under CA, Inc.'s copyrights to use, copy, modify, and distribute this
# software in source code or binary form for use in connection with CA, Inc.
# products.
#
# This copyright 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.
#
# =============================================================================
function Test-VcfVcenterCheckDiskSpaceInodeAndHprof {

    <#
        .SYNOPSIS
        Checks disk space and inode utilization on the vCenter appliance's filesystems, and scans
        for leftover Java heap-dump (.hprof) files.

        .DESCRIPTION
        Executes guest commands via Invoke-VcfApplianceCommand on every vCenter appliance VM
        managed by SDDC Manager to inspect filesystem capacity, inode utilization, and leftover Java
        heap dump (.hprof) files in a single appliance execution.

        Disk Space and Inode Evaluation:
        - Executes `df -h` and `df -i` to measure disk space and inode usage.
        - Any filesystem reporting disk space or inode utilization at or above 80% returns a Warning status.

        Heap Dump Scanning:
        - Executes `find / -xdev -name '*.hprof' 2>/dev/null` to identify leftover heap dumps from prior
          service crashes.
        - Any detected .hprof files raise a Warning status and list the file paths in the result detail.

        Target Addressing:
        - Connects to and executes commands against vCenter appliance VMs via the management vCenter
          (Get-VcfCheckManagementVCenterFqdn), as all vCenter appliance VMs reside on management
          domain compute.
        - Evaluates each vCenter and delegates per-domain result aggregation to
          Invoke-VcfCheckPerVCenterCheck.

        .PARAMETER Context
        The VcfCheck.Context object. Must already be connected to SDDC Manager.

        .OUTPUTS
        [PSObject] a VcfCheck.Result.
    #>


    [CmdletBinding()]
    [OutputType([PSObject])]
    Param (
        [Parameter(Mandatory = $true)] [PSObject]$Context,
        [Parameter(Mandatory = $false)] [String]$DisplayName = ''
    )

    $startedAt = Get-Date
    $checkId = 'vcenter_disk_space_inode_and_hprof_check'
    $catalogEntry = (Get-VcfCheckCatalog)[$checkId]
    $displayName = if ([String]::IsNullOrEmpty($DisplayName)) { $catalogEntry.displayName } else { $DisplayName }

    try {
        $managementVCenterFqdn = Get-VcfCheckManagementVCenterFqdn -Context $Context
    } catch {
        return New-VcfCheckResult -CheckId $checkId -Area vCenter -Status Error `
            -Exception $_.Exception.Message -ValidationCriteria $catalogEntry.validationCriteria -Remediation $catalogEntry.remediation -StartedAt $startedAt -CompletedAt (Get-Date) -DisplayName $displayName
    }

    return Invoke-VcfCheckPerVCenterCheck -Context $Context -CheckId $checkId -Area vCenter -DisplayName $DisplayName -Body {
        param($Context, $VCenterFqdn)
        Connect-VcfCheckVCenter -Context $Context -Fqdn $managementVCenterFqdn
        $rootCredential = Get-VcfCheckComponentCredential -Context $Context -ResourceType VCENTER -AccountType USER -Fqdn $VCenterFqdn -Username 'root'
        $vmName = ($VCenterFqdn -split '\.')[0]

        $commandResult = Invoke-VcfApplianceCommand -VmName $vmName -Server $managementVCenterFqdn -Credential $rootCredential `
            -ScriptText 'df -h --output=target,pcent | tail -n +2; echo "---INODES---"; df -i --output=target,ipcent | tail -n +2; echo "---HPROF---"; find / -xdev -name "*.hprof" 2>/dev/null'

        if (-not $commandResult.Success) {
            if ($commandResult.ErrorCategory -eq 'ToolsNotRunning') {
                $detail = "This check cannot run without VMware Tools running on the target appliance - skipped for now. $($commandResult.ErrorMessage)"
                return [PSCustomObject]@{ Status = 'Skipped'; Detail = $detail; SkipReasonTag = 'VMware Tools not running'; Rows = @() }
            }
            return [PSCustomObject]@{ Status = 'Error'; Detail = $commandResult.ErrorMessage; Rows = @() }
        }

        $sections = $commandResult.ScriptOutput -split '---INODES---|---HPROF---'
        $filesystemData = @{}
        $flagged = [System.Collections.Generic.List[String]]::new()

        foreach ($line in ($sections[0] -split "`n")) {
            $line = $line.Trim()
            if (-not $line) { continue }
            $parts = $line -split '\s+'
            if ($parts.Count -lt 2) { continue }
            $mountPoint = $parts[-1]
            $percentStr = $parts[-2]
            $percent = 0
            if ([Int32]::TryParse(($percentStr -replace '%', ''), [ref]$percent)) {
                if (-not $filesystemData[$mountPoint]) { $filesystemData[$mountPoint] = @{} }
                $filesystemData[$mountPoint]['SpacePercent'] = $percent
                if ($percent -ge 80) {
                    $flagged.Add("$mountPoint (space): $percent%")
                }
            }
        }

        foreach ($line in ($sections[1] -split "`n")) {
            $line = $line.Trim()
            if (-not $line) { continue }
            $parts = $line -split '\s+'
            if ($parts.Count -lt 2) { continue }
            $mountPoint = $parts[0]
            $percent = 0
            if ([Int32]::TryParse(($parts[1] -replace '%', ''), [ref]$percent)) {
                if (-not $filesystemData[$mountPoint]) { $filesystemData[$mountPoint] = @{} }
                $filesystemData[$mountPoint]['InodePercent'] = $percent
                if ($percent -ge 80) {
                    $flagged.Add("$mountPoint (inode): $percent%")
                }
            }
        }

        $hprofFiles = @($sections[2] -split "`n" | Where-Object { -not [String]::IsNullOrWhiteSpace($_) } | ForEach-Object { $_.Trim() })

        $rows = @($filesystemData.Keys | ForEach-Object {
            $target = $_
            $data = $filesystemData[$target]
            $spacePercent = if ($data.ContainsKey('SpacePercent')) { $data['SpacePercent'] } else { 0 }
            $inodePercent = if ($data.ContainsKey('InodePercent')) { $data['InodePercent'] } else { 0 }
            $status = if ($spacePercent -ge 80) { 'Warning' } else { 'Pass' }
            [PSCustomObject]@{ MountedOn = $target; SpaceUsedPercent = $spacePercent; InodeUsedPercent = $inodePercent; Status = $status }
        })

        $detailParts = [System.Collections.Generic.List[String]]::new()
        if ($flagged.Count -gt 0) {
            $detailParts.Add("Filesystem(s) at or above 80% utilization: $($flagged -join '; ')")
        }
        if ($hprofFiles.Count -gt 0) {
            $detailParts.Add("Found $($hprofFiles.Count) heap dump file(s): $($hprofFiles -join '; ')")
        }

        if ($detailParts.Count -gt 0) {
            return [PSCustomObject]@{ Status = 'Warning'; Detail = ($detailParts -join ' '); Rows = $rows }
        }

        return [PSCustomObject]@{ Status = 'Pass'; Detail = 'Every filesystem is below 80% disk space and inode utilization, and no heap dump files were found.'; Rows = $rows }
    }
}