LogicMonitor + Catchpoint: Enter the New Era of Autonomous IT

Learn more

LogicMonitor offers out-of-the-box monitoring for the Nutanix Prism Central. This monitoring suite leverages the Nutanix Prism Central v4 REST APIs to monitor Nutanix clusters, hypervisor hosts, and virtual machines.

For more information about Nutanix APIs, see Nutanix v4 APIs from Nutanix.  

LogicMonitor’s Nutanix Prism Central monitoring package is compatible with the following versions: 

  • Nutanix PC (Prism Central) 7.3 and later
  • Nutanix AOS (Acropolis Operating System) 7.3 and later
  • Nutanix AHV (Acropolis Hypervisor) 10.3 and later

Requirements for Nutanix Prism Central Monitoring

To enable Nutanix Prism Central Monitoring, you need the following:

  • LogicMonitor_Collector_Snippets DataSource
  • Access to Nutanix Prism Central API (port 9440 by default)
  • Local Nutanix Prism Central user credentials with read-only permissions

Recommendation: Use a local account with “Prism Viewer” permissions. For more information, see Built-in Roles List from Nutanix.

Adding Resources into Monitoring

Nutanix Prism Central resources can be added into monitoring using the following methods:

  • Automatically using an Advanced NetScan (recommended)
  • Manually, by adding resources and assigning required properties

Using NetScan is strongly recommended for larger or dynamic Nutanix environments, as it automates discovery and maintains hierarchical resource organization.

Warning: To avoid overages, verify that you have sufficient LogicMonitor licenses before running the NetScan. Test NetScan filters carefully to avoid unintentionally adding unwanted Nutanix resources.

For more information about using Advanced NetScan, see Enhanced Script Netscan.

  1. In LogicMonitor, navigate to  Modules > Exchange.
  2. Locate and install the Nutanix Prism Central LogicModules.
    For a list of modules, see Monitoring Modules.
  3. Navigate to Resources > Add > Several resources > Advanced NetScan.
  4. On the Add Advanced NetScan page, enter a descriptive name you want to associate with this NetScan (for example, Nutanix Prism Central Resources).
  5. Select the Collector to execute the NetScan.
    For more information, see Collector Assignment in Enhanced Script Netscan.
  6. Select “Enhanced Script NetScan” from the Method drop-down list.
  7. From the Enhanced Script section, select Resource Credentials > Use custom credentials for this scan.
  8. Add the following properties that provide the NetScan with the required Nutanix Prism Central credentials and change how and where the NetScan creates and organizes resources:
PropertyValueRequired
nutanix.prism.central.userPrism Central username with Viewer role (for example, logicmonitor_viewer)Yes
nutanix.prism.central.passPrism Central passwordYes
nutanix.prism.central.hostHostname or IP of Prism Central (for example, 10.10.10.100 or prism.company.com)Yes
nutanix.prism.central.portAPI port (default is 9440)No
lmaccess.id OR logicmonitor.access.idA LogicMonitor API access id to search for duplicate resources in the NetScan prior to adding them to the portal.Yes
lmaccess.key OR logicmonitor.access.keyA LogicMonitor API key to search for duplicate resources in the NetScan prior to adding them to the portal.Yes
hostname.sourceEnables the selection of the source of the hostname that is used by the NetScan. Note: This is useful in preventing duplicate resource creation when conflicts are found for existing resources. For more information, see NetScan Troubleshooting. No
skip.device.dedupeEnables you to skip device deduplication checks, making LogicMonitor API credentials not required.No
rootFolderThe name of the LogicMonitor Resource Group that this NetScan creates or uses if already existing. The value can be a nested child folder. For example, Nutanix/Production or DataCenter/Nutanix. Default is “Nutanix Prism Central Resources”.No
lmapi.timelimit.secSet to configure the amount of time dedicated to making LM API queries. Default 60, allowable range 30-120.No

Recommendation: To verify desired functionality, start with a specific filter of some Resources you want to monitor, then incrementally change the filter operations or values to onboard monitored resources in stages. For example, you can configure the filters to only discover or import one Nutanix Cluster at a time until you are confident that your Properties and Filters are properly defined to achieve your desired outcome.

Warning: If filters are not configured, the NetScan attempts to add all discovered Nutanix hosts and VMs as individual LogicMonitor resources.

  1. Select Embed a Groovy script and embed the following the NetScan script:
/*******************************************************************************
 * © 2007-2025 - LogicMonitor, Inc. All rights reserved.
 ******************************************************************************/

import com.santaba.agent.groovy.utils.GroovyScriptHelper as GSH
import com.logicmonitor.mod.Snippets
import groovy.json.JsonSlurper

Boolean debug = false
Boolean log = false

// Set props object based on whether or not we are running inside a netscan or debug console
def props
def prismCentralHost
try {
    hostProps.get("system.hostname")
    props = hostProps
    prismCentralHost = props.get("nutanix.prism.central.host", props.get("system.hostname", ""))
    debug = true  // set debug to true so that we can ensure we do not print sensitive properties
}
catch (MissingPropertyException) {
    props = netscanProps
    prismCentralHost = props.get("nutanix.prism.central.host", "")
}


def modLoader = GSH.getInstance(GroovySystem.version).getScript("Snippets", Snippets.getLoader()).withBinding(getBinding())
def lmDebugSnip = modLoader.load("lm.debug", "1")
def lmDebug = lmDebugSnip.debugSnippetFactory(out, debug, log, "Nutanix_PrismCentral_Netscan")
def emit = modLoader.load("lm.emit", "1")
def httpSnip = modLoader.load("proto.http", "0")
def http = httpSnip.httpSnippetFactory(props)
def nutanixPrismCentralSnip = modLoader.load("nutanix.prism.central", "0")
def nutanixPrismCentral = nutanixPrismCentralSnip.nutanixPrismCentralSnippetFactory(props, lmDebug, http)

// Configuration options
def rootFolder = props.get("rootFolder") ?: "Nutanix Prism Central Resources"
Boolean skipDeviceDedupe = props.get("skip.device.dedupe", "false").toBoolean()
String hostnameSource = props.get("hostname.source", "")?.toLowerCase()?.trim()

// Initialize LM API for device deduplication if not skipped
def lmApi
if (!skipDeviceDedupe) {
    def lmApiSnippet = modLoader.load("lm.api", "0")
    lmApi = lmApiSnippet.lmApiSnippetFactory(props, http, lmDebug)
}

// Get information about devices that already exist in LM portal
List fields = ["name", "currentCollectorId", "displayName", "id", "customProperties"]
Map args = ["size": 1000, "fields": fields.join(",")]
def lmDevices
def pathFlag, portalInfo, timeLimitSec, timeLimitMs
if (!skipDeviceDedupe) {
    portalInfo = lmApi.apiCallInfo("Devices", args)
    timeLimitSec = props.get("lmapi.timelimit.sec", "60").toInteger()
    timeLimitMs = (timeLimitSec) ? Math.min(Math.max(timeLimitSec, 30), 120) * 1000 : 60000

    if (portalInfo.timeEstimateMs > timeLimitMs) {
        lmDebug.LMDebugPrint("Estimate indicates LM API calls would take longer than time limit configured. Proceeding with individual queries by display name for each device to add.")
        lmDebug.LMDebugPrint("\t${portalInfo}\n\tNOTE: Time limit is set to ${timeLimitSec} seconds. Adjust this limit by setting the property lmapi.timelimit.sec. Max 120 seconds, min 30 seconds.")
        pathFlag = "ind"
    } else {
        lmDebug.LMDebugPrint("Response time indicates LM API calls will complete in a reasonable time range. Proceeding to collect info on all devices to cross reference and prevent duplicate device creation.\n\t${portalInfo}")
        pathFlag = "all"
        lmDevices = lmApi.getPortalDevices(args)
    }
}

List<Map> resources = []

def now = new Date()
def dateFormat = "yyyy-MM-dd'T'HH:mm:ss.s z"
TimeZone tz = TimeZone.getDefault()
Map duplicateResources = [
        "date"     : now.format(dateFormat, tz),
        "message"  : "Duplicate device names and display names, keyed by display name that would be assigned by the netscan, found within LogicMonitor portal. Refer to documentation for how to resolve name collisions using 'hostname.source' netscan property.",
        "total"    : 0,
        "resources": []
]

// Get all hosts data with pagination
def hostsData = nutanixPrismCentral.httpGet("clustermgmt/v4.0/config/hosts?\$limit=100")
def allHosts = []
def hostsCurrentPage = 0
def hostsPageSize = 100

if (hostsData && hostsData.data) {
    allHosts.addAll(hostsData.data)
    lmDebug.LMDebugPrint("Hosts first page: Found ${hostsData.data.size()} hosts")

    // Check if there are more pages to process
    def hostsHasMore = hostsData.metadata?.links?.find { it.rel == "next" } != null

    if (hostsHasMore) {
        lmDebug.LMDebugPrint("Hosts pagination detected. Total available: ${hostsData.totalAvailableResults ?: 'unknown'}")

        // Continue fetching remaining pages
        while (hostsHasMore) {
            hostsCurrentPage++
            lmDebug.LMDebugPrint("Fetching hosts page ${hostsCurrentPage + 1}...")

            def hostsPageData = nutanixPrismCentral.httpGet("clustermgmt/v4.0/config/hosts?\$limit=${hostsPageSize}&\$page=${hostsCurrentPage}")

            if (hostsPageData && hostsPageData.data && hostsPageData.data.size() > 0) {
                allHosts.addAll(hostsPageData.data)
                lmDebug.LMDebugPrint("Hosts page ${hostsCurrentPage + 1}: Found ${hostsPageData.data.size()} hosts")

                // Check if there's a next page
                hostsHasMore = hostsPageData.metadata?.links?.find { it.rel == "next" } != null
            } else {
                lmDebug.LMDebugPrint("No more hosts data found, stopping pagination")
                hostsHasMore = false
            }

            // Safety check to prevent infinite loops
            if (hostsCurrentPage > 50) {
                lmDebug.LMDebugPrint("Reached maximum hosts page limit (50), stopping pagination")
                break
            }
        }
    }
}

lmDebug.LMDebugPrint("Total hosts discovered: ${allHosts.size()}")

// Process each host
allHosts.each { host ->

    def hostExtId = host.extId
    def hostName = host.hostName
    def serial = host.blockSerial

    lmDebug.LMDebug("Processing host: ${hostName} (extId: ${hostExtId})")

    if (!hostExtId || !hostName) {
        lmDebug.LMDebug("ERROR: Host missing required fields (extId: ${hostExtId}, name: ${hostName}), skipping")
        return
    }

    String displayName = hostName
    def hostname = hostName
    def ip = hostName

    // Try to get IP from hypervisor external address
    if (host.hypervisor?.externalAddress?.ipv4?.value) {
        ip = host.hypervisor.externalAddress.ipv4.value
        hostname = ip

        // Try reverse DNS lookup
        try {
            def reverseDnsName = InetAddress.getByName(ip)?.getHostName()
            if (reverseDnsName && reverseDnsName != ip) {
                hostname = reverseDnsName
            }
        } catch (Exception e) {
            lmDebug.LMDebug("DEBUG: Failed to resolve reverse DNS for ${ip}: ${e.message}")
        }
    }

    // Check for existing device in LM portal
    def deviceMatch = false
    if (!skipDeviceDedupe) {
        if (pathFlag == "ind") {
            deviceMatch = lmApi.findPortalDevice(displayName, args)
        } else if (pathFlag == "all") {
            deviceMatch = lmApi.checkExistingDevices(displayName, lmDevices)
        }
    }

    if (deviceMatch) {
        if (hostname != deviceMatch?.name) {
            // Log duplicates that would cause additional devices to be created; unless these entries are resolved, they will not be added to resources for netscan output
            def collisionInfo = [
                    (displayName): [
                            "Netscan" : [
                                    "hostname"   : hostname,
                                    "displayName": displayName
                            ],
                            "LM"      : [
                                    "hostname"   : deviceMatch?.name,
                                    "collectorId": deviceMatch?.currentCollectorId,
                                    "displayName": deviceMatch?.displayName
                            ],
                            "Resolved": false
                    ]
            ]

            // Resolve hostname collision based on user preference
            if (hostnameSource == "lm" || hostnameSource == "logicmonitor") {
                hostname = deviceMatch?.name
                displayName = deviceMatch?.displayName
                deviceMatch = false
                if (collisionInfo && collisionInfo[displayName]) {
                    collisionInfo[displayName]["Resolved"] = true
                }
            } else if (hostnameSource == "netscan") {
                if (collisionInfo && collisionInfo[displayName]) {
                    collisionInfo[displayName]["Resolved"] = true
                }
                displayName = "${displayName} - ${ip}"
                deviceMatch = false
            }

            if (collisionInfo) {
                duplicateResources["resources"].add(collisionInfo)
            }
        }
        // Don't worry about matches where the hostname values are the same
        // These will update via normal NetScan processing and should be ignored
        else {
            deviceMatch = false
        }
    }
    // Determine device group based on cluster
    def deviceGroups = []
    deviceGroups << "${rootFolder}"

    // Resource host properties
    Map hostProps = [
            "system.categories"         : "Nutanix_Hypervisor",
            "nutanix.prism.central.host": prismCentralHost,
            "nutanix.prism.central.user": props.get("nutanix.prism.central.user"),
            "nutanix.prism.central.pass": props.get("nutanix.prism.central.pass"),
            "nutanix.hypervisor.serial" : serial

    ]


    def resource = [
            "hostname"   : hostname,
            "displayname": displayName,
            "hostProps"  : hostProps,
            "groupName"  : deviceGroups
    ]

    lmDebug.LMDebugPrint("Adding host: ${displayName} (${hostname})")
    if (!deviceMatch) resources.add(resource)
}

// Get Prism Central resource information
lmDebug.LMDebugPrint("Fetching Prism Central resource...")
def domainManagerData = nutanixPrismCentral.httpGet("prism/v4.0/config/domain-managers")

if (domainManagerData && domainManagerData.data && domainManagerData.data.size() > 0) {
    def domainManager = domainManagerData.data[0]

    lmDebug.LMDebugPrint("Processing Prism Central resource")

    // Perform nslookup on nutanix.prism.central.host
    String prismHostForLookup = prismCentralHost
    String prismHostIP = null
    String prismHostDNS = prismCentralHost

    try {
        def inetAddress = InetAddress.getByName(prismHostForLookup)
        prismHostIP = inetAddress.getHostAddress()
        prismHostDNS = inetAddress.getHostName()

        lmDebug.LMDebug("DEBUG: DNS lookup for ${prismHostForLookup}: IP=${prismHostIP}, DNS=${prismHostDNS}")
    } catch (Exception e) {
        lmDebug.LMDebug("ERROR: Failed to resolve ${prismHostForLookup}: ${e.message}")
        // If lookup fails, use the original value
        prismHostIP = prismCentralHost
        prismHostDNS = prismCentralHost
    }

    // Determine hostname based on nslookup result
    String pcHostname = prismHostIP

    // Determine displayname from config.name
    String pcConfigName = domainManager.config?.name ?: "Unnamed"
    String pcDisplayName = pcConfigName

    // If config.name is "Unnamed", use DNS name
    if (pcConfigName == "Unnamed") {
        pcDisplayName = prismHostDNS
        lmDebug.LMDebug("DEBUG: Config name is 'Unnamed', using DNS name: ${pcDisplayName}")
    }

    // Check for existing device in LM portal
    def pcDeviceMatch = false
    if (!skipDeviceDedupe) {
        if (pathFlag == "ind") {
            pcDeviceMatch = lmApi.findPortalDevice(pcDisplayName, args)
        } else if (pathFlag == "all") {
            pcDeviceMatch = lmApi.checkExistingDevices(pcDisplayName, lmDevices)
        }
    }

    if (pcDeviceMatch) {
        if (pcHostname != pcDeviceMatch?.name) {
            // Log duplicates that would cause additional devices to be created; unless these entries are resolved, they will not be added to resources for netscan output
            def pcCollisionInfo = [
                    (pcDisplayName): [
                            "Netscan" : [
                                    "hostname"   : pcHostname,
                                    "displayName": pcDisplayName
                            ],
                            "LM"      : [
                                    "hostname"   : pcDeviceMatch?.name,
                                    "collectorId": pcDeviceMatch?.currentCollectorId,
                                    "displayName": pcDeviceMatch?.displayName
                            ],
                            "Resolved": false
                    ]
            ]

            // Resolve hostname collision based on user preference
            if (hostnameSource == "lm" || hostnameSource == "logicmonitor") {
                pcHostname = pcDeviceMatch?.name
                pcDisplayName = pcDeviceMatch?.displayName
                pcDeviceMatch = false

                if (pcCollisionInfo && pcCollisionInfo[pcDisplayName]) {
                    pcCollisionInfo[pcDisplayName]["Resolved"] = true
                }
            } else if (hostnameSource == "netscan") {
                pcDisplayName = "${pcDisplayName} - ${pcHostname}"
                pcDeviceMatch = false

                if (pcCollisionInfo && pcCollisionInfo[pcDisplayName]) {
                    pcCollisionInfo[pcDisplayName]["Resolved"] = true
                }
            }

            if (pcCollisionInfo) {
                duplicateResources["resources"].add(pcCollisionInfo)
            }
        }
        // Don't worry about matches where the hostname values are the same
        // These will update via normal netscan processing and should be ignored
        else {
            pcDeviceMatch = false
        }
    }

    if (!pcDeviceMatch) {
        // Prism Central resource properties
        Map pcHostProps = [
                "system.categories"         : "Nutanix_PrismCentral",
                "nutanix.prism.central.user": props.get("nutanix.prism.central.user"),
                "nutanix.prism.central.pass": props.get("nutanix.prism.central.pass")
        ]

        def pcDeviceGroups = []
        pcDeviceGroups << "${rootFolder}"

        def pcResource = [
                "hostname"   : pcHostname,
                "displayname": pcDisplayName,
                "hostProps"  : pcHostProps,
                "groupName"  : pcDeviceGroups
        ]

        lmDebug.LMDebugPrint("Adding Prism Central resource: ${pcDisplayName} (${pcHostname})")
        resources.add(pcResource)
    } else {
        lmDebug.LMDebug("DEBUG: Skipping Prism Central resource - already exists in portal")
    }
} else {
    lmDebug.LMDebug("WARNING: No Prism Central domain manager data found")
}

lmDebug.LMDebugPrint("Duplicate Resources:")
if (duplicateResources && duplicateResources.resources) {
    duplicateResources.resources.each { collisionInfo ->
        if (collisionInfo) {
            collisionInfo.each { key, value ->
                lmDebug.LMDebugPrint("\t${key}: ${value}")
            }
        }
    }
}

lmDebug.LMDebugPrint("Summary: Found ${resources.size()} resource(s) to add (including Prism Central)")
if ((resources.size()) == 0) {
    lmDebug.LMDebugPrint("No resources to add. Exiting netscan.")
} else {
    emit.resource(resources, debug)
}

return 0
  1. In the Schedule section, select Run this NetScan on a schedule.
    For dynamic environments, you can schedule the NetScan to run as frequently as hourly.

Note: Subsequent NetScan runs will add or move resources or resource groups based on changes in Nutanix Prism Central. However, the NetScan does not have the ability to delete resources.

  1. Select Save or Save & Run.

After running the NetScan, review the history for the number of resources added, or for error messages if the NetScan does not create any resources.

Note: NetScans can add or move resources and resource groups, but cannot delete existing resources. For more information, see Dynamic Resource Groups.

Manually Adding Resources into Monitoring

  1. In LogicMonitor, navigate to Resources > Add.
  2. Enter the hostname or IP address of the Prism Central instance.
  3. Assign the resource to an appropriate Resource Group.
  4. Add the required properties listed below.
  5. Ensure the assigned Collector can reach Prism Central on port 9440 (or configured port).

To monitor AHV hypervisor hosts manually, add each host as an individual resource and assign the appropriate Prism Central credentials.

Prism Central Required Host Properties

Set the following properties on the Prism Central resource or a parent Resource Group:

PropertyDescriptionRequired
nutanix.prism.central.userPrism Central API usernameYes
nutanix.prism.central.passPrism Central API passwordYes
nutanix.prism.central.portPrism Central API port (default: 9440)No

AVH Hypervisor Required Host Properties

Set the following properties on AHV hypervisor resources or their parent Resource Group:

PropertyDescriptionRequired
nutanix.prism.central.userPrism Central API usernameYes
nutanix.prism.central.passPrism Central API passwordYes
nutanix.prism.central.hostPrism Central hostname or IPYes
nutanix.prism.central.portPrism Central API port (default: 9440)No

NetScan Troubleshooting

The NetScan for this suite can update existing resources in the portal to add relevant information retrieved from Nutanix’s API. It is also possible for the NetScan to create duplicate resources in the portal when a conflict exists where the display name is the same, but the value for system.hostname differs. To ensure resources are updated properly and duplicate resources are not created, this NetScan uses LogicMonitor’s API to query existing resources and report name conflicts discovered. This file can be accessed in the collector logs. For more information on retrieving collector logs, see Collector logging – Sending logs to LogicMonitor.

The resources in this report are not reported to LogicMonitor as part of the NetScan output unless the NetScan has been configured with the property hostname.source. This property allows a user to resolve the name conflicts discovered by selecting the source of the hostname that is used in the NetScan output. Two possible hostname sources are determined by the following values:

  • “lm” or “logicmonitor” resources with name conflicts use the existing system.hostname in LogicMonitor to ensure a device in the portal is updated using the NetScan. This setting does not create a new device.
  • “netscan” resources with name conflicts maintain system.hostname determined in the NetScan script and update the display name reported to append - <system.hostname> to ensure it is unique and can be added to the portal. This option is beneficial if you do not have strict naming conventions and have multiple resources with the same display name.

Note: NetScans are not able to update the value of system.hostname, however they may update display name, custom properties, and group assignments.

Import LogicModules

Install all Nutanix Prism Central LogicModules from the Exchange. If the modules are already installed, verify that you are running the most recent versions.

LogicModules in Package

The Nutanix Prism Central monitoring suite includes the following LogicModules:

Module NameTypeDescription
Nutanix_Hypervisor_CPUDataSourceMonitors Prism Central Hypervisor CPU Usage.
Nutanix_Hypervisor_ControllerPerformanceDataSourceMonitors Prism Central Hypervisor Controller Performance.
Nutanix_Hypervisor_InfoPropertySourceSets appropriate device properties for Nutanix Prism Central Hypervisors.
Nutanix_Hypervisor_MemoryDataSourceMonitors Prism Central Hypervisor Memory Usage.
Nutanix_Hypervisor_NetworkInterfacesDataSourceMonitors Prism Central Hypervisor Network Interface Status.
Nutanix_Hypervisor_NodeHealthDataSourceMonitors Prism Central Hypervisor Status and Health Check Score.
Nutanix_Hypervisor_StorageDataSourceMonitors Prism Central Hypervisor Storage Usage.
Nutanix_PrismCentral_ClustersDataSourceMonitors Prism Central Cluster Performance, Utilization, Total Hosts and Total VMs.
Nutanix_PrismCentral_HostsDataSourceMonitors Prism Central Host Status and Health Check Score.
Nutanix_PrismCentral_LicensingDataSourceMonitors Prism Central Licensing information and days until expiry.
Nutanix_PrismCentral_VMsDataSourceMonitors Prism Central Virtual Machine Performance and Utilization metrics.
addCategory_Nutanix_HypervisorPropertySourceSets appropriate category for Nutanix Prism Central Hypervisors.
addCategory_Nutanix_PrismCentralPropertySourceSets appropriate category for Nutanix Prism Central

When setting static datapoint thresholds on the various metrics tracked by this package’s DataSources, LogicMonitor follows the technology owner’s best practice KPI recommendations.

Recommendation: If necessary, LogicMonitor encourages you to adjust these predefined thresholds to meet the unique needs of your environment. For more information on tuning datapoint thresholds, see Static Thresholds for Datapoints.

14-day access to the full LogicMonitor platform