In the previous article in this series, we looked at how to work with unnamed resources and commands. In this article, we’ll focus on working with statistics.

Statistics abound throughout the BIG-IP, but we’ll narrow in on virtual and pool stats here.

Virtual Server

{
    "kind": "tm:ltm:virtual:virtualstats",
    "generation": 56,
    "selfLink": "https://localhost/mgmt/tm/ltm/virtual/testvip/stats?ver=13.1.0.5",
    "entries": {
        "https://localhost/mgmt/tm/ltm/virtual/testvip/~Common~testvip/stats": {
            "nestedStats": {
                "kind": "tm:ltm:virtual:virtualstats",
                "selfLink": "https://localhost/mgmt/tm/ltm/virtual/testvip/~Common~testvip/stats?ver=13.1.0.5",
                "entries": {
                    "clientside.bitsIn": {
                        "value": 0
                    },

Pool

{
    "kind": "tm:ltm:pool:poolstats",
    "generation": 1,
    "selfLink": "https://localhost/mgmt/tm/ltm/pool/testpool/stats?ver=13.1.0.5",
    "entries": {
        "https://localhost/mgmt/tm/ltm/pool/testpool/~Common~testpool/stats": {
            "nestedStats": {
                "kind": "tm:ltm:pool:poolstats",
                "selfLink": "https://localhost/mgmt/tm/ltm/pool/testpool/~Common~testpool/stats?ver=13.1.0.5",
                "entries": {
                    "activeMemberCnt": {
                        "value": 1
                    },

You can see structurally they are very similar, with a kind including the stats keyword, and a series of entries and nested stats fields. The pool member, however, goes another nested level deeper (see details toward the bottom of this article). Even starting with the virtual server and the pool, trying to access these statistics manually, well, that is not for the faint of heart. Consider the following code:

# Virtual Server
>>> vip = b.tm.ltm.virtuals.virtual.load(name='testvip')
>>> vipstats = vip.stats.load()
>>> for k, v in vipstats.entries['https://localhost/mgmt/tm/ltm/virtual/testvip/~Common~testvip/stats']['nestedStats']['entries'].iteritems():
...     if v.get('description') != None:
...         print '%s: %s' % (k, v.get('description'))
...     elif v.get('value') != None:
...         print '%s: %s' % (k, v.get('value'))
...
syncookie.accepts: 0
ephemeral.bitsOut: 0
clientside.bitsOut: 5002552
fiveMinAvgUsageRatio: 0
tmName: /Common/testvip
csMeanConnDur: 192
syncookie.swsyncookieInstance: 0
status.enabledState: enabled
...remaining fields removed

# Pool
>>> pool = b.tm.ltm.pools.pool.load(name='testpool')
>>> poolstats = pool.stats.load()
>>> for k, v in poolstats.entries['https://localhost/mgmt/tm/ltm/pool/testpool/~Common~testpool/stats']['nestedStats']['entries'].iteritems():
...     if v.get('description') != None:
...         print '%s: %s' % (k, v.get('description'))
...     elif v.get('value') != None:
...         print '%s: %s' % (k, v.get('value'))
...
connqAll.ageMax: 0
serverside.curConns: 0
minActiveMembers: 0
serverside.bitsIn: 90474272
connqAll.serviced: 0
serverside.pktsIn: 181780
memberCnt: 1
serverside.maxConns: 50
connq.depth: 0
connqAll.depth: 0
connqAll.ageHead: 0
curSessions: 0
status.availabilityState: available

A couple things to note here. You might think you could use the selfLink attribute instead of manually typing the entry ID, but the selfLink needs a little help. You can programmatically insert the ~Common~testpool in there and use that, but either way, to get to that list of items to iterate over is a chore! Another item of interest is that each stats field is a dictionary with a key of value or description, so that needs to be managed while pulling the values for storage or printing. At this point you must be thinking "there's got to be a better way!" Thankfully, there is.

Using the Stats Utility

The Stats utility is buried down deep in the utils directory tree in the handlers module. Once imported, you just wrap any of your stats method calls with that class. Focusing in on just the pool this time, that significantly cuts down the work necessary in your for loop:

>>> from f5.utils.responses.handlers import Stats
>>> pool = b.tm.ltm.pools.pool.load(name='testpool')
>>> pool stats = Stats(pool.stats.load())
>>> for k, v in poolstats.stat.iteritems():
...     if v.get('description') != None:
...         print '%s: %s' % (k, v.get('description'))
...     elif v.get('value') != None:
...         print '%s: %s' % (k, v.get('value'))
...

And voila, piece of cake!

One final note before closing up shop: this doesn't quite work with pool members. The resulting json payload from a pool member stats query to the API are pretty buried. Using the sdk to grab the stats (without the Stats module):

>>> pm = pool.members_s.members.load(name='192.168.103.20:80', partition='Common')
>>> pmstats = pm.stats.load()

You can see from this variable data in my IDE that the depth is well...very deep!

Pool Member Stats

The development team still has some work to do to iron out the Stats class, particularly for subcollections like pool members, but for top level objects it’s a time/life-saver! Keep an eye on issue 1440 on GitHub for enhancements.