Visualising Citrix performance with Grafana
Visualising Citrix performance with Grafana

I have written a Python script to get some Citrix performance data and use it to create graph with Grafana. The result looks like this :
ctxmon

The Python script uses the linux wmic client you can find here

The software requirements are the following :

  • a debian / ubuntu system (you can choose another os but prefer those ones if you need help :))
  • influxdb database
  • grafana
  • python (for me I have the version 2.7.3)
  • InfluxDBClient for python
  • wmic tool

For the moment, there are two Python script : one to get Citrix site info like number of Active/Disconnected sessions and one for performance info (user latency, session bandwidth,…)
In this example, you will have to create two influxdb databases called ctxmon and ctxusr

Here we go…

[config]
FORESTNAME=domain.local
WMIC=/usr/bin/wmic
WMIUSR=wmiusername
WMIPWD=wmipassword
CDCSRV=10.11.0.1
DBSRVIP=10.11.0.2
DBSRVUSR=dbusr
DBSRVPWD=dbpwd
DB_USR_NAME=ctxusr
WMICLASS=Win32_PerfFormattedData_CitrixICA_ICASession
WMIOBJECT=InputClipboardBandwidt,InputDriveBandwidth,InputPrinterBandwidth,InputSessionBandwidth,InputSessionCompression,InputSessionLineSpeed,LatencyLastRecorded,LatencySessionAverage,LatencySessionDeviation,OutputPrinterBandwidth,OutputSeamlessBandwidth,OutputSessionBandwidth,OutputSessionCompression,OutputSessionLineSpeed,OutputThinWireBandwidth
WMINAMESPACE=Root/CIMV2
#!/usr/bin/python

from __future__ import division
import socket
import ConfigParser
import os
import subprocess
import threading
import re
import time
from influxdb import InfluxDBClient

config = ConfigParser.RawConfigParser()
config.read(os.path.dirname(os.path.realpath(__file__))+'/configusr.ini')
forestname = config.get('config', 'FORESTNAME')
wmic = config.get('config', 'WMIC')
wmiusr = config.get('config', 'WMIUSR')
wmipwd = config.get('config', 'WMIPWD')
cdcsrv = config.get('config', 'CDCSRV')
dbsrvip = config.get('config', 'DBSRVIP')
dbsrvusr = config.get('config', 'DBSRVUSR')
dbsrvpwd = config.get('config', 'DBSRVPWD')
db_usr_name = config.get('config', 'DB_USR_NAME')
wmiclass = config.get('config', 'WMICLASS')
wmiobject = config.get('config', 'WMIOBJECT')
wminamespace_ini = config.get('config', 'WMINAMESPACE')
sct = re.compile("^mon---", re.IGNORECASE)

client = InfluxDBClient(dbsrvip, 8096, dbsrvusr, dbsrvpwd, db_usr_name)

monsct = filter(sct.search, config.sections())

threadLimiter = threading.BoundedSemaphore(value=200)

class myThread (threading.Thread):
    def __init__(self, threadID, name, ctxuser, wmicmd, wmimoninfo):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.ctxuser = ctxuser
        self.wmicmd = wmicmd
        self.wmimoninfo = wmimoninfo
    def run(self):
        threadLimiter.acquire()
        try:
                print_adrepl(self.name, self.ctxuser, self.wmicmd, self.wmimoninfo )
        finally:
                threadLimiter.release()

def print_adrepl(threadName, ctxuser, wmicmd, wmimoninfo):
        json_body = [{
                        "points": [
                            [int(time.time()), ctxuser, int(wmicmd)]
                        ],
                        "name": wmimoninfo,
                        "columns": ["time", "user", "value"]
                    }]
        client.write_points(json_body, time_precision='s')

threads = []

cred = '-U'+forestname+'/'+wmiusr+'%'+wmipwd
wmidstip = "//"+cdcsrv
wmiselect = "select * from Citrix_Server"
wminamespace = "--namespace=Root/Citrix"
wmidelimiter = "--delimiter=;"
wmicmd = [wmic , cred , wmidstip  , wmiselect , wminamespace , wmidelimiter ]
proc = subprocess.Popen(wmicmd,stdout=subprocess.PIPE, stderr=subprocess.PIPE)
outputlines = filter(lambda x:len(x)>0,(line.strip() for line in proc.stdout))
ctxlist = []
for ctxinfo in outputlines[5:]:
        if ctxinfo.split(';')[6] == zoneranking:
                ctxlist.append([ctxinfo.split(';')[2], ctxinfo.split(';')[4]])

while 1:
        i = 1
        for ctxserver in ctxlist:
                wmimonvalue = 0
                ctxname = ctxserver[1]
                ctxip = ctxserver[0]
                response = os.system("ping -c 1 -t 2 " + ctxip)
                if response == 0:
                        wmidstip = "//" + ctxip
                        wmiselect = "select "+wmiobject+" from "+wmiclass
                        wminamespace = '--namespace='+wminamespace_ini
                        wmidelimiter = "--delimiter=;"
                        wmicmd = [wmic , cred , wmidstip  , wmiselect , wminamespace , wmidelimiter ]
                        print wmicmd
                        proc = subprocess.Popen(wmicmd,stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                        outputlines = filter(lambda x:len(x)>0,(line.strip() for line in proc.stdout))
                        if ((not any("ERROR" in s for s in outputlines)) and outputlines):
                                property_array = outputlines[1].split(";")
                                for ctxuserinfo in outputlines[2:]:
                                        if (re.search(r"\(([A-Za-z0-9_]+)\)", ctxuserinfo.split(';')[9])):
                                                m = re.search(r"\(([A-Za-z0-9_]+)\)", ctxuserinfo.split(';')[9])
                                                ctx_username = m.group(1)
                                        else:
                                                ctx_username = ctxuserinfo.split(';')[9].replace(" ", "")
                                        idx=2
                                        for property_value in property_array[2:]:
                                                wmimonvalue = ctxuserinfo.split(';')[idx]
                                                if (idx != 9):
                                                        #wmimoninfo = ctx_username + "." + ctxname + "." + property_value
                                                        wmimoninfo = ctxname + "." + property_value
                                                        thread = myThread(i, str(ctxname), ctx_username, wmimonvalue, wmimoninfo )
                                                        thread.start()
                                                        threads.append(thread)
                                                idx += 1
                                                i += 1
        for t in threads:
                t.join()
        print "Exiting Main Thread"
[config]
FORESTNAME=domain.local
WMIC=/usr/bin/wmic
WMIUSR=wmiusername
WMIPWD=wmipassword
CDCSRV=10.11.0.1
DBSRVIP=10.11.0.2
DBSRVUSR=dbusr
DBSRVPWD=dbpwd
DB_SESSION_NAME=ctxmon

[mon---1]
WMICLASS=MetaFrame_Server
WMIINSTANCE=
WMIOBJECT=NumberOfActiveSessions
WMINAMESPACE=Root/Citrix

[mon---2]
WMICLASS=MetaFrame_Server
WMIINSTANCE=
WMIOBJECT=NumberOfDisconnectedSessions
WMINAMESPACE=Root/Citrix

[mon---3]
WMICLASS=MetaFrame_Server
WMIINSTANCE=
WMIOBJECT=NumberOfSessions
WMINAMESPACE=Root/Citrix
#!/usr/bin/python

from __future__ import division
import socket
import ConfigParser
import os
import subprocess
import threading
import re
import time
from influxdb import InfluxDBClient

config = ConfigParser.RawConfigParser()
config.read(os.path.dirname(os.path.realpath(__file__))+'/config.ini')
forestname = config.get('config', 'FORESTNAME')
wmic = config.get('config', 'WMIC')
wmiusr = config.get('config', 'WMIUSR')
wmipwd = config.get('config', 'WMIPWD')
cdcsrv = config.get('config', 'CDCSRV')
dbsrvip = config.get('config', 'DBSRVIP')
dbsrvusr = config.get('config', 'DBSRVUSR')
dbsrvpwd = config.get('config', 'DBSRVPWD')
db_session_name = config.get('config', 'DB_SESSION_NAME')
sct = re.compile("^mon---", re.IGNORECASE)
client = InfluxDBClient(dbsrvip, 8096, dbsrvusr, dbsrvpwd, db_session_name)

monsct = filter(sct.search, config.sections())

threadLimiter = threading.BoundedSemaphore(value=200)

class myThread (threading.Thread):
    def __init__(self, threadID, name, wmicmd, wmimoninfo):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.wmicmd = wmicmd
        self.wmimoninfo = wmimoninfo
    def run(self):
        threadLimiter.acquire()
        try:
                print_adrepl(self.name, self.wmicmd, self.wmimoninfo )
        finally:
                threadLimiter.release()

def print_adrepl(threadName, wmicmd, wmimoninfo):
        timeserie = threadName + '.' + wmimoninfo
        proc = subprocess.Popen(wmicmd,stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        errorlines = filter(lambda x:len(x)>0,(line.strip() for line in proc.stderr))
        if errorlines :
                t = 1
        outputlines = filter(lambda x:len(x)>0,(line.strip() for line in proc.stdout))
        if ((len(outputlines)>2) and (int(re.findall(r'\b\d+\b', outputlines[2:][0])[0])!=196)):
                json_body = [{
                                "points": [
                                    [int(time.time()), int(re.findall(r'\b\d+\b', outputlines[2:][0])[0])]
                                ],
                                "name": timeserie,
                                "columns": ["time", "value"]
                            }]
                client.write_points(json_body, time_precision='s')

threads = []

cred = '-U'+forestname+'/'+wmiusr+'%'+wmipwd
wmidstip = "//"+cdcsrv
wmiselect = "select * from Citrix_Server"
wminamespace = "--namespace=Root/Citrix"
wmidelimiter = "--delimiter=;"
wmicmd = [wmic , cred , wmidstip  , wmiselect , wminamespace , wmidelimiter ]
proc = subprocess.Popen(wmicmd,stdout=subprocess.PIPE, stderr=subprocess.PIPE)
outputlines = filter(lambda x:len(x)>0,(line.strip() for line in proc.stdout))
ctxlist = []
for ctxinfo in outputlines[5:]:
	ctxlist.append([ctxinfo.split(';')[2], ctxinfo.split(';')[4]])

while 1:
        i = 1
        for ctxserver in ctxlist:
                ctxname = ctxserver[1]
                ctxip = ctxserver[0]
                response = os.system("ping -c 1 -t 2 " + ctxip)
                if response == 0:
                        wmidstip = "//" + ctxip
                        for monitor in monsct :
                                if config.get(monitor, 'WMIINSTANCE') != "" :
                                        wmiinstance = " where name='"+config.get(monitor, 'WMIINSTANCE')+"'"
                                else :
                                        wmiinstance = " "

                                wmiselect = "select " + config.get(monitor, 'WMIOBJECT') + " from " + config.get(monitor, 'WMICLASS') + wmiinstance
                                wminamespace = "--namespace=" + config.get(monitor, 'WMINAMESPACE')
                                wmidelimiter = "--delimiter=;"
                                wmicmd = [wmic , cred , wmidstip  , wmiselect , wminamespace , wmidelimiter ]
                                wmimoninfo = config.get(monitor, 'WMICLASS')+"_"+config.get(monitor, 'WMIOBJECT')
                                thread = myThread(i, str(ctxname), wmicmd, wmimoninfo )
                                thread.start()
                                threads.append(thread)
                                i += 1
        time.sleep(30)

        for t in threads:
                t.join()
        print "Exiting Main Thread"

Last step, build the graphs

I have used the templating feature in Grafana to filter data using variables : one variable for the Citrix server name and the other for the username

ctxusr

ctxsrv

You can download the following file and import it as a new dashboard :
Citrix-Session-Monitoring

A new feature has been added : annotation
ctxannotation

The annotation detail :
annot_dtl

Here an annotation is added when the Latency average is greater than 10s (=10000). You can adapt this value with your environment.

Do not hesitate to leave a comment if you need help…

<>

My Powershell script categories

Visualizing Citrix performance with Grafana

6 thoughts on “Visualizing Citrix performance with Grafana

  • August 14, 2015 at 6:52 am
    Permalink

    Hi Nicolas,

    I’m always getting exiting main thread when running one of the python scripts, but no data is sent to influxdb.
    Is there a way to debug the scripts? Already tried but am getting lost, since python is not a language I know very well.

    Thanks in advance Johannes

    Reply
  • August 14, 2015 at 6:52 am
    Permalink

    Hi Nicolas,

    I’m always getting exiting main thread when running one of the python scripts, but no data is sent to influxdb.
    Is there a way to debug the scripts? Already tried but am getting lost, since python is not a language I know very well.

    Thanks in advance Johannes

    Reply
  • February 13, 2019 at 10:07 pm
    Permalink

    Hi. I realize this post is quite old, but.. I am trying to run the first python script and receiving the following error message. Very new to python so not sure what I am missing. Would greatly appreciate any help.

    Traceback (most recent call last):
    File “./ctxsession.py”, line 68, in
    proc = subprocess.Popen(wmicmd,stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    File “/usr/lib/python2.7/subprocess.py”, line 394, in __init__
    errread, errwrite)
    File “/usr/lib/python2.7/subprocess.py”, line 1047, in _execute_child
    raise child_exception
    OSError: [Errno 2] No such file or directory

    Reply
  • February 13, 2019 at 10:07 pm
    Permalink

    Hi. I realize this post is quite old, but.. I am trying to run the first python script and receiving the following error message. Very new to python so not sure what I am missing. Would greatly appreciate any help.

    Traceback (most recent call last):
    File “./ctxsession.py”, line 68, in
    proc = subprocess.Popen(wmicmd,stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    File “/usr/lib/python2.7/subprocess.py”, line 394, in __init__
    errread, errwrite)
    File “/usr/lib/python2.7/subprocess.py”, line 1047, in _execute_child
    raise child_exception
    OSError: [Errno 2] No such file or directory

    Reply

Leave a Reply to Nicolas HAHANG Cancel reply

Your email address will not be published.