Dell ECS Network Statistics Version 3.6+ Flux Query
Dell made some changes to their ECS offering in version 3.6 where system level statistics such as CPU, Memory and Network were removed from the dashboard API and Flux Queries needed to be used to retrieve the data, below are two discovery and collection scripts one for CPU and Memory and one for Network Level statistics that utilize the flux query to retrieve the relevant metrics.
Important note: this is for all versions above 3.6 of the Dell EMC ECS Solution all versions before are supported fully by the existing LogicMonitor out of the box packages.
The Network statistics will return "0" values from time to time, I am still troubleshooting this, as in the previous script I have found a minimum of 5 minutes works best.
Due to 20000 character limitation on a post the cpu and memory stats can be found here.
Discovery Script:
/*******************************************************************************
* Dell ECS Network Interface Discovery script.
******************************************************************************/
import groovy.json.JsonSlurper
import groovy.json.JsonOutput
import groovy.json.JsonBuilder
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import com.santaba.agent.groovyapi.http.*;
import com.santaba.agent.util.Settings
hostname = hostProps.get("system.hostname")
user = hostProps.get("ecs.user")
pass = hostProps.get("ecs.pass")
collectorplatform = hostProps.get("system.collectorplatform")
debug = false
def success = false
def token = login()
if (token) {
//Retrieve data for all nodes for CPU and Memory
def encoded_instance_props_array = []
def NETresponse = getNetwork(token)
// Work through table to retrieve the Node Name and Id to build out the instance level properties.
// Internal Note used the methods in "Arista Campus PSU Collection Script"
//Process Network Statistics
if (debug) println "Net Response: "+ NETresponse
def NETJson = new JsonSlurper().parseText(NETresponse)
if (debug) println "\n\n Network Values: "+NETJson.Series.Values[0]
NETJson.Series.Values[0].each { ifaceEntry ->
def nodeId = ifaceEntry[9]
def nodeName = ifaceEntry[7]
def nodeIfaceName = ifaceEntry[8]
wildvalue = "${nodeId}-${nodeIfaceName}"
wildalias = "${nodeName}-${nodeIfaceName}"
description = "${nodeId}/${nodeIfaceName}"
def instance_props = [
"auto.node.id": nodeId,
"auto.node.name": nodeName,
"auto.node.interface":nodeIfaceName,
"auto.node.interface.speed":ifaceEntry[4]
]
encoded_instance_props_array = instance_props.collect() { property, value ->
URLEncoder.encode(property.toString()) + "=" +
URLEncoder.encode(value.toString())
}
println "${wildvalue}##${wildalias}##${description}####${encoded_instance_props_array.join("&")}"
}
} else if (debug) { println "Bad API response: ${response}"}
return 0
def login() {
if (debug) println "in login"
// Fetch new token using Basic authentication, set in cache file and return
if (debug) println "Checking provided ${user} creds at /login.json..."
def userCredentials = "${user}:${pass}"
def basicAuthStringEnc = new String(Base64.getEncoder().encode(userCredentials.getBytes()))
def loginUrl = "https://${hostname}:4443/login.json".toURL()
def loginConnection = loginUrl.openConnection()
loginConnection.setRequestProperty("Authorization", "Basic " + basicAuthStringEnc)
def loginResponseBody = loginConnection.getInputStream()?.text
def loginResponseCode = loginConnection.getResponseCode()
def loginResponseToken = loginConnection.getHeaderField("X-SDS-AUTH-TOKEN")
if (debug) println loginResponseCode
if (loginResponseCode == 200 && loginResponseToken) {
if (debug) println "Retrieved token: ${loginResponseToken}"
return loginResponseToken
} else {
println "STATUS CODE:\n${loginResponseCode}\n\nRESPONSE:\n${loginResponseBody}"
println "Unable to fetch token with ${user} creds at /login.json"
}
println "Something unknown went wrong when logging in"
}
def getNetwork(token) {
def slurper = new JsonSlurper()
def dataUrl = "https://"+hostname+":4443/flux/api/external/v2/query"
if (debug) println "Trying to fetch data from ${dataUrl}"
def flux = 'from(bucket:\"monitoring_op\") |> range(start: -5m) |> filter(fn: (r) => r._measurement == \"net\" and r._field == \"speed\")'
def jsonBody = groovy.json.JsonOutput.toJson(["query":flux])
if (debug) println "Raw JSON Body "+jsonBody
if (debug) println "Json Body "+JsonOutput.prettyPrint(jsonBody)+" Type "+jsonBody.getClass()
def dataHeader = ["X-SDS-AUTH-TOKEN": token,"Content-Type":"application/json","Accept":"application/json"]
if (debug) println("Sent Header: "+dataHeader)
// Now we can retrieve the data.
def httpClient = Client.open (hostname,4443);
httpClient.post(dataUrl,jsonBody,dataHeader);
if ( !(httpClient.getStatusCode() =~ /200/))
{
println "Failed to retrieve data "+httpClient.getStatusCode()
println "Header: "+httpClient.getHeader
return(1)
}
String dataContent = httpClient.getResponseBody()
if (debug) println "Status Code "+httpClient.getStatusCode()
if (debug) println "Data in response Body "+dataContent
return dataContent
}Collection Script:
/*******************************************************************************
* Dell ECS Flux Query Network Statistics
******************************************************************************/
import groovy.json.JsonSlurper
import groovy.json.JsonOutput
import groovy.json.JsonBuilder
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import com.santaba.agent.groovyapi.http.*;
import com.santaba.agent.util.Settings
hostname = hostProps.get("system.hostname")
user = hostProps.get("ecs.user")
pass = hostProps.get("ecs.pass")
collectorplatform = hostProps.get("system.collectorplatform")
debug = false
def success = false
def token = login()
if (token) {
//Retrieve data for all nodes for CPU and Memory
def encoded_instance_props_array = []
def NETresponse = getNetwork(token)
// Work through table to retrieve the Node Name and Id to build out the instance level properties.
// Internal Note used the methods in "Arista Campus PSU Collection Script"
//Process Network Statistics
if (debug) println "Net Response: "+ NETresponse
def NETJson = new JsonSlurper().parseText(NETresponse)
if (debug) println "\n\n Net Values: "+NETJson.Series.Values[0]
NETJson.Series.Values[0].each { ifaceEntry ->
def nodeId = ifaceEntry[9]
def nodeName = ifaceEntry[7]
def nodeIfaceName = ifaceEntry[8]
// Get the _field value so we know which metric we are collecting.
def fieldName = ifaceEntry[5]
def fieldValue = ifaceEntry[4]
wildvalue = "${nodeId}-${nodeIfaceName}"
wildalias = "${nodeName}-${nodeIfaceName}"
description = "${nodeName}/${nodeIfaceName}"
println "${wildvalue}.${fieldName}=${fieldValue}"
}
} else if (debug) { println "Bad API response: ${response}"}
return 0
def login() {
if (debug) println "in login"
// Fetch new token using Basic authentication, set in cache file and return
if (debug) println "Checking provided ${user} creds at /login.json..."
def userCredentials = "${user}:${pass}"
def basicAuthStringEnc = new String(Base64.getEncoder().encode(userCredentials.getBytes()))
def loginUrl = "https://${hostname}:4443/login.json".toURL()
def loginConnection = loginUrl.openConnection()
loginConnection.setRequestProperty("Authorization", "Basic " + basicAuthStringEnc)
def loginResponseBody = loginConnection.getInputStream()?.text
def loginResponseCode = loginConnection.getResponseCode()
def loginResponseToken = loginConnection.getHeaderField("X-SDS-AUTH-TOKEN")
if (debug) println loginResponseCode
if (loginResponseCode == 200 && loginResponseToken) {
if (debug) println "Retrieved token: ${loginResponseToken}"
return loginResponseToken
} else {
println "STATUS CODE:\n${loginResponseCode}\n\nRESPONSE:\n${loginResponseBody}"
println "Unable to fetch token with ${user} creds at /login.json"
}
println "Something unknown went wrong when logging in"
}
def getNetwork(token) {
def slurper = new JsonSlurper()
def dataUrl = "https://"+hostname+":4443/flux/api/external/v2/query"
if (debug) println "Trying to fetch data from ${dataUrl}"
def flux = 'from(bucket:\"monitoring_op\") |> range(start: -5m) |> filter(fn: (r) => r._measurement == \"net\")'
def jsonBody = groovy.json.JsonOutput.toJson(["query":flux])
if (debug) println "Raw JSON Body "+jsonBody
if (debug) println "Json Body "+JsonOutput.prettyPrint(jsonBody)+" Type "+jsonBody.getClass()
def dataHeader = ["X-SDS-AUTH-TOKEN": token,"Content-Type":"application/json","Accept":"application/json"]
if (debug) println("Sent Header: "+dataHeader)
// Now we can retrieve the data.
def httpClient = Client.open (hostname,4443);
httpClient.post(dataUrl,jsonBody,dataHeader);
if ( !(httpClient.getStatusCode() =~ /200/))
{
println "Failed to retrieve data "+httpClient.getStatusCode()
println "Header: "+httpClient.getHeader
return(1)
}
String dataContent = httpClient.getResponseBody()
if (debug) println "Status Code "+httpClient.getStatusCode()
if (debug) println "Data in response Body "+dataContent
return dataContent
}
Data Source Configuration:
In both cases I have set up the "applies to" setting to "hasCategory("EMC_ECS_Cluster")"
The discovery schedule is daily
The collection schedule as stated is every 5 minutes.
The collection is configured as batch script in both.
Additional comments and notes:
One of the biggest challenges I had solving this change from the Dashboard API to the Flux API was that I was receiving a HTTP 401 initially I thought this was the flux query however it turned out to be the saving of the token to the file as per the original data sources, once I removed this and made it the same as my Python script which worked with out issue I resolved this issue.
I have an additional request for the Latency statistics, I will share these in a separate post once done.
Hope this helps.