#!/usr/bin/env python
#coding=utf-8 

"""
Written by freedomkk-qfeng
Github: https://github.com/freedomkk-qfeng
Email: freedomkk_qfeng@qq.com
Script to get vSphere metrics and push to Open-Falcon
Version 0.2
"""

import atexit
from pyVmomi import vim, vmodl
from pyVim.connect import SmartConnectNoSSL, Disconnect
import sys
import copy
import requests
import time
import json
import config
from datetime import timedelta

def VmInfo(vm,content,vchtime,interval,perf_dict,tags):
    try:
        statInt = interval/20
        summary = vm.summary
        stats = summary.quickStats
        
        uptime = stats.uptimeSeconds
        add_data("vm.uptime",uptime,"GAUGE",tags)
        
        cpuUsage = 100 * float(stats.overallCpuUsage)/float(summary.runtime.maxCpuUsage)
        add_data("vm.cpu.usage",cpuUsage,"GAUGE",tags)
        
        memoryUsage = stats.guestMemoryUsage * 1024 * 1024
        add_data("vm.memory.usage",memoryUsage,"GAUGE",tags)
        
        memoryCapacity = summary.runtime.maxMemoryUsage * 1024 * 1024
        add_data("vm.memory.capacity",memoryCapacity,"GAUGE",tags)
        
        freeMemoryPercentage = 100 - (
            (float(memoryUsage) / memoryCapacity) * 100
        )
        add_data("vm.memory.freePercent",freeMemoryPercentage,"GAUGE",tags)
        
        statDatastoreRead = BuildQuery(content, vchtime, (perf_id(perf_dict, 'datastore.read.average')),"*", vm, interval)
        DatastoreRead = (float(sum(statDatastoreRead[0].value[0].value) * 1024) / statInt)
        add_data("vm.datastore.io.read_bytes",DatastoreRead,"GAUGE",tags)
        
        statDatastoreWrite = BuildQuery(content, vchtime, (perf_id(perf_dict, 'datastore.write.average')),"*", vm, interval)
        DatastoreWrite = (float(sum(statDatastoreWrite[0].value[0].value) * 1024) / statInt)
        add_data("vm.datastore.io.write_bytes",DatastoreWrite,"GAUGE",tags)
        
        statDatastoreIoRead = BuildQuery(content, vchtime, (perf_id(perf_dict, 'datastore.numberReadAveraged.average')),"*", vm, interval)
        DatastoreIoRead = (float(sum(statDatastoreIoRead[0].value[0].value)) / statInt)
        add_data("vm.datastore.io.read_numbers",DatastoreIoRead,"GAUGE",tags)
        
        statDatastoreIoWrite = BuildQuery(content, vchtime, (perf_id(perf_dict, 'datastore.numberWriteAveraged.average')),"*", vm, interval)
        DatastoreIoWrite = (float(sum(statDatastoreIoWrite[0].value[0].value)) / statInt)
        add_data("vm.datastore.io.write_numbers",DatastoreIoWrite,"GAUGE",tags)
        
        statDatastoreLatRead = BuildQuery(content, vchtime, (perf_id(perf_dict, 'datastore.totalReadLatency.average')), "*", vm, interval)
        DatastoreLatRead = (float(sum(statDatastoreLatRead[0].value[0].value)) / statInt)
        add_data("vm.datastore.io.read_latency",DatastoreLatRead,"GAUGE",tags)

        statDatastoreLatWrite = BuildQuery(content, vchtime, (perf_id(perf_dict, 'datastore.totalWriteLatency.average')), "*", vm, interval)
        DatastoreLatWrite = (float(sum(statDatastoreLatWrite[0].value[0].value)) / statInt)
        add_data("vm.datastore.io.write_latency",DatastoreLatWrite,"GAUGE",tags)

        statNetworkTx = BuildQuery(content, vchtime, (perf_id(perf_dict, 'net.transmitted.average')), "", vm, interval)
        if statNetworkTx != False:
            networkTx = (float(sum(statNetworkTx[0].value[0].value) * 8 * 1024) / statInt)
            add_data("vm.net.if.out",networkTx,"GAUGE",tags)
        statNetworkRx = BuildQuery(content, vchtime, (perf_id(perf_dict, 'net.received.average')), "", vm, interval)
        if statNetworkRx != False:
            networkRx = (float(sum(statNetworkRx[0].value[0].value) * 8 * 1024) / statInt)        
            add_data("vm.net.if.in",networkRx,"GAUGE",tags)      
        
    except Exception as error:
        print "Unable to access information for host: ", vm.name
        print error
        pass

def HostInformation(host,datacenter_name,computeResource_name,content,perf_dict,vchtime,interval):
    try:
        statInt = interval/20
        summary = host.summary
        stats = summary.quickStats
        hardware = host.hardware

        tags = "datacenter=" + datacenter_name + ",cluster_name=" + computeResource_name + ",host=" + host.name

        uptime = stats.uptime
        add_data("esxi.uptime",uptime,"GAUGE",tags)

        cpuUsage = 100 * 1000 * 1000 * float(stats.overallCpuUsage) / float(hardware.cpuInfo.numCpuCores * hardware.cpuInfo.hz)
        add_data("esxi.cpu.usage",cpuUsage,"GAUGE",tags)

        memoryCapacity = hardware.memorySize
        add_data("esxi.memory.capacity",memoryCapacity,"GAUGE",tags)

        memoryUsage = stats.overallMemoryUsage * 1024 * 1024
        add_data("esxi.memory.usage",memoryUsage,"GAUGE",tags)

        freeMemoryPercentage = 100 - (
            (float(memoryUsage) / memoryCapacity) * 100
        )
        add_data("esxi.memory.freePercent",freeMemoryPercentage,"GAUGE",tags)

        statNetworkTx = BuildQuery(content, vchtime, (perf_id(perf_dict, 'net.transmitted.average')), "", host, interval)       
        networkTx = (float(sum(statNetworkTx[0].value[0].value) * 8 * 1024) / statInt)
        add_data("esxi.net.if.out",networkTx,"GAUGE",tags)
        
        statNetworkRx = BuildQuery(content, vchtime, (perf_id(perf_dict, 'net.received.average')), "", host, interval)
        networkRx = (float(sum(statNetworkRx[0].value[0].value) * 8 * 1024) / statInt)
        add_data("esxi.net.if.in",networkRx,"GAUGE",tags)

        add_data("esxi.alive",1,"GAUGE",tags)

    except Exception as error:
        print "Unable to access information for host: ", host.name
        print error
        pass

def perf_id(perf_dict, counter_name):
    counter_key = perf_dict[counter_name]
    return counter_key

def ComputeResourceInformation(computeResource,datacenter_name,content,perf_dict,vchtime,interval):
    try:
        hostList = computeResource.host
        computeResource_name = computeResource.name
        for host in hostList:
            if (host.name in config.esxi_names) or (len(config.esxi_names) == 0):
                HostInformation(host,datacenter_name,computeResource_name,content,perf_dict,vchtime,interval)
    except Exception as error:
        print "Unable to access information for compute resource: ",
        computeResource.name
        print error
        pass

def DatastoreInformation(datastore,datacenter_name):
    try:
        summary = datastore.summary
        name = summary.name
        TYPE = summary.type

        tags = "datacenter=" + datacenter_name + ",datastore=" + name + ",type=" + TYPE

        capacity = summary.capacity
        add_data("datastore.capacity",capacity,"GAUGE",tags)

        freeSpace = summary.freeSpace
        add_data("datastore.free",freeSpace,"GAUGE",tags)

        freeSpacePercentage = (float(freeSpace) / capacity) * 100
        add_data("datastore.freePercent",freeSpacePercentage,"GAUGE",tags)
        
    except Exception as error:
        print "Unable to access summary for datastore: ", datastore.name
        print error
        pass

def add_data(metric,value,conterType,tags):
    data = {"endpoint":endpoint,"metric":metric,"timestamp":ts,"step":interval,"value":value,"counterType":conterType,"tags":tags}
    payload.append(copy.copy(data))

def run(host,user,pwd,port,interval):
    try:
        si = SmartConnectNoSSL(host=host, user=user, pwd=pwd, port=port)
        atexit.register(Disconnect, si)
        content = si.RetrieveContent()
        vchtime = si.CurrentTime()

        perf_dict = {}
        perfList = content.perfManager.perfCounter
        for counter in perfList:
            counter_full = "{}.{}.{}".format(counter.groupInfo.key, counter.nameInfo.key, counter.rollupType)
            perf_dict[counter_full] = counter.key

        for datacenter in content.rootFolder.childEntity:
            datacenter_name = datacenter.name.encode("utf8")
            datastores = datacenter.datastore
            for ds in datastores:
                if (ds.name in config.datastore_names) or (len(config.datastore_names) == 0):
                    DatastoreInformation(ds,datacenter_name)

            if hasattr(datacenter.hostFolder, 'childEntity'):
                hostFolder = datacenter.hostFolder
                computeResourceList = []
                computeResourceList = getComputeResource(hostFolder,computeResourceList)
                for computeResource in computeResourceList:
                    ComputeResourceInformation(computeResource,datacenter_name,content,perf_dict,vchtime,interval)
         
        if config.vm_enable == True:
            obj = content.viewManager.CreateContainerView(content.rootFolder, [vim.VirtualMachine], True)
            for vm in obj.view:
                if (vm.name in config.vm_names) or (len(config.vm_names) == 0):
                    tags = "vm=" + vm.name
                    if vm.runtime.powerState == "poweredOn":
                        VmInfo(vm, content, vchtime, interval, perf_dict, tags)
                        add_data("vm.power",1,"GAUGE",tags)
                    else:
                        add_data("vm.power",0,"GAUGE",tags)               

    except vmodl.MethodFault as error:
        print "Caught vmodl fault : " + error.msg
        return False, error.msg
    return True, "ok"

def getComputeResource(Folder,computeResourceList):
    if hasattr(Folder, 'childEntity'):
        for computeResource in Folder.childEntity:
           getComputeResource(computeResource,computeResourceList)
    else:
        computeResourceList.append(Folder)
    return computeResourceList

def hello_vcenter(vchost,username,password,port):
    try:
        si = SmartConnectNoSSL(
            host=vchost,
            user=username,
            pwd=password,
            port=port)

        atexit.register(Disconnect, si)
        return True, "ok"
    except vmodl.MethodFault as error:
        return False, error.msg
    except Exception as e:
        return False, str(e)


def BuildQuery(content, vchtime, counterId, instance, entity, interval):
    perfManager = content.perfManager
    metricId = vim.PerformanceManager.MetricId(counterId=counterId, instance=instance)
    startTime = vchtime - timedelta(seconds=(interval + 60))
    endTime = vchtime - timedelta(seconds=60)
    query = vim.PerformanceManager.QuerySpec(intervalId=20, entity=entity, metricId=[metricId], startTime=startTime,
                                             endTime=endTime)
    perfResults = perfManager.QueryPerf(querySpec=[query])
    if perfResults:
        return perfResults
    else:
        return False

if __name__ == "__main__":
    host = config.host
    user = config.user
    pwd = config.pwd
    port = config.port
    
    endpoint = config.endpoint
    push_api = config.push_api
    interval = config.interval

    ts = int(time.time())
    payload = []

    success, msg = hello_vcenter(host,user,pwd,port)
    if success == False:
        print msg
        add_data("vcenter.alive",0,"GAUGE","")
        #print json.dumps(payload,indent=4)
        r= requests.post(push_api, data=json.dumps(payload))
        sys.exit(1)
        add_data("vcenter.alive",1,"GAUGE","")
    
    run(host,user,pwd,port,interval)
    #print json.dumps(payload,indent=4)
    r = requests.post(push_api, data=json.dumps(payload))