#
# Copyright (c) 2015 Martins Innus.  All Rights Reserved.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
# for more details.
#

'''
Python implementation of an Intel "mic" Performance Metrics Domain Agent.
'''

import sys, os
from ctypes import c_int, c_uint, c_float, c_ulonglong, POINTER, cast, Structure
import cpmapi as c_api
from pcp.pmda import PMDA, pmdaMetric, pmdaIndom, pmdaInstid
from pcp.pmapi import pmUnits, pmContext as PCP

mpss_python_dir = os.environ.get('PMDAMIC_MPSS_PYTHON_DIR')

if mpss_python_dir != None:
    sys.path.insert(1, mpss_python_dir)

from micmgmt import *

class MicDevInfo(Structure):
    ''' Metric values for mic device indom (one per card) '''
# Info
    _fields_ = [("num_cores", c_uint),
                ("num_threads", c_uint),
                ("dev_id", c_uint),
                ("stepping", c_uint),
                ("frequency", c_float),
# Temp
                ("cpu_temp", c_float),
                ("mem_temp", c_float),
                ("fanin_temp", c_float),
                ("fanout_temp", c_float),
                ("core_rail_temp", c_float),
                ("uncore_rail_temp", c_float),
                ("memory_rail_temp", c_float),
# Power
                ("power", c_float),
                ("power_w0", c_float),
                ("power_w1", c_float),
                ("window0", c_float),
                ("window1", c_float),
                ("low_power_limit", c_float),
                ("high_power_limit", c_float),
                ("phys_power_limit", c_float),
# Mem in KB
                ("free_mem", c_ulonglong),
                ("total_mem", c_ulonglong),
                ("used_mem", c_ulonglong),
# Core
                ("tick_count", c_uint),
                ("all_usr", c_ulonglong),
                ("all_sys", c_ulonglong),
                ("all_nice", c_ulonglong),
                ("all_idle", c_ulonglong)]

    def __init__(self, cores):
        self.num_cores = cores

class MicPerCoreInfo(Structure):
    ''' Metric values for mic percore indom (one per card/core pair) '''
# Percore
    _fields_ = [ ("cpu_usr", c_ulonglong),
                ("cpu_sys", c_ulonglong),
                ("cpu_nice", c_ulonglong),
                ("cpu_idle", c_ulonglong)]

class MicPMDA(PMDA):
    '''
    A mic Performance Metrics Domain Agent.
    Install it and make basic use of it, as follows:

    # $PCP_PMDAS_DIR/mic/Install
    # pminfo -f mic
    '''

    # Clusters
    GLOBAL_CLUSTER = 0
    INFO_CLUSTER = 1
    TEMP_CLUSTER = 2
    POWER_CLUSTER = 3
    MEM_CLUSTER = 4
    CORE_CLUSTER = 5
    PERCORE_CLUSTER = 6

    # Card indom
    numcards = 0
    cards = {}

    # Percore indom
    numcardcores = 0
    cardcores = {}

    # As far as I can tell, this is hard coded.
    HZ = 100

    def init_mic(self):
        self.numcards = mic_get_ndevices()
        
    def update_mic(self):
        '''
            Update the mic stats. Serial "get" calls don't actually
            refresh unless you call the update functions in between.
        '''
        self.cards.clear()
        self.cardcores.clear()

        for device in range(self.numcards):
            mic = MicDevice(device)

            mic.mic_update_core_util()
            mic.mic_update_thermal_info()
            mic.mic_update_memory_util()
            mic.mic_update_power_utilization_info()

            devinfo = MicDevInfo( mic.mic_get_cores_count() )
            cardname = mic.mic_get_device_name()

            # Info / Freq

            devinfo.dev_id = mic.mic_get_device_id()
            devinfo.stepping = mic.mic_get_processor_steppingid()
            # Convert from KHz to MHz
            devinfo.frequency = mic.mic_get_cores_frequency() / 1000.0

            # Power (Need to check the status for the readings)
            # The readings come in as uWatts but the limits as Watts
            devinfo.power = mic.mic_get_inst_power_readings() / 1000000.0
            if( mic.mic_get_inst_power_sensor_sts() < 0 ):
                devinfo.power = -1

            devinfo.power_w0 = mic.mic_get_total_power_readings_w0() / 1000000.0
            if( mic.mic_get_total_power_sensor_sts_w0() < 0 ):
                devinfo.power_w0 = -1

            devinfo.power_w1 = mic.mic_get_total_power_readings_w1() / 1000000.0
            if( mic.mic_get_total_power_sensor_sts_w1() < 0 ):
                devinfo.power_w1 = -1

            devinfo.window0 = mic.mic_get_time_window0()
            devinfo.window1 = mic.mic_get_time_window1()

            devinfo.low_power_limit = mic.mic_get_power_lmrk()
            devinfo.high_power_limit = mic.mic_get_power_hmrk()
            devinfo.phys_power_limit = mic.mic_get_power_phys_limit()

            # Temperature (need to check if they are valid after read)
            devinfo.cpu_temp = mic.mic_get_die_temp()
            if( not mic.mic_is_die_temp_valid() ):
                devinfo.cpu_temp = -1
    
            devinfo.mem_temp = mic.mic_get_gddr_temp()
            if( not mic.mic_is_gddr_temp_valid() ):
                devinfo.mem_temp = -1
    
            devinfo.fanin_temp = mic.mic_get_fanin_temp()
            if( not mic.mic_is_fanin_temp_valid() ):
                devinfo.fanin_temp = -1
    
            devinfo.fanout_temp = mic.mic_get_fanout_temp()
            if( not mic.mic_is_fanout_temp_valid() ):
                devinfo.fanout_temp = -1
    
            devinfo.core_rail_temp = mic.mic_get_vccp_temp()
            if( not mic.mic_is_vccp_temp_valid() ):
                devinfo.core_rail_temp = -1
    
            devinfo.uncore_rail_temp = mic.mic_get_vddg_temp()
            if( not mic.mic_is_vddg_temp_valid() ):
                devinfo.uncore_rail_temp = -1
    
            devinfo.memory_rail_temp = mic.mic_get_vddq_temp()
            if( not mic.mic_is_vddq_temp_valid() ):
                devinfo.memory_rail_temp = -1

            # Memory, reported in KB
            devinfo.free_mem = mic.mic_get_available_memory_size()
            devinfo.total_mem = mic.mic_get_total_memory_size()
            devinfo.used_mem = devinfo.total_mem - devinfo.free_mem

            # Core Metrics
            devinfo.num_threads = mic.mic_get_threads_core()

            # All core metrics are converted to ms per thread from jiffies
            millis = devinfo.num_threads * self.HZ

            # Overall metrics
            devinfo.all_usr = int(1000 * mic.mic_get_user_sum() / millis)
            devinfo.all_sys = int(1000 * mic.mic_get_sys_sum() / millis)
            devinfo.all_nice = int(1000 * mic.mic_get_nice_sum() / millis)
            devinfo.all_idle = int(1000 * mic.mic_get_idle_sum() / millis)

            # Per core metrics
            core_usr = mic.mic_get_user_counters()
            core_sys = mic.mic_get_sys_counters()
            core_nice = mic.mic_get_nice_counters()
            core_idle = mic.mic_get_idle_counters()

            cnt = 0
            for usr, sys, nice, idle in zip(core_usr, core_sys, core_nice, core_idle):
                cpuinfo = MicPerCoreInfo()
                cpuinfo.cpu_usr = int(1000 * usr /  millis)
                cpuinfo.cpu_sys = int(1000 * sys / millis)
                cpuinfo.cpu_nice = int(1000 * nice / millis)
                cpuinfo.cpu_idle = int(1000 * idle / millis)

                coreinst = "%s/core%d" % (cardname, cnt)

                self.cardcores[coreinst] = cpuinfo
                cnt += 1

            # Not sure if this is useful but we report it
            devinfo.tick_count = mic.mic_get_tick_count()

            self.cards[cardname] = devinfo

        self.replace_indom(self.card_indom, self.cards)
        self.replace_indom(self.cardcore_indom, self.cardcores)

    def mic_instance(self, serial):
        ''' Called once per "instance request" PDU '''
        # self.log("instance update for %d" % serial)
        self.update_mic()


    def mic_fetch(self):
        ''' Called once per "fetch" PDU, before callbacks '''
        self.update_mic()

    def mic_fetch_global_callback(self, item, inst):
        '''
        Returns a list of value,status (single pair) for global cluster
        Helper for the fetch callback
        '''
        if (item == 0):
            if (inst != c_api.PM_IN_NULL):
                return [c_api.PM_ERR_INST, 0]
            return [self.numcards, 1]
        else:
            return [c_api.PM_ERR_PMID, 0]

    def mic_fetch_info_callback(self, item, inst):
        '''
        Returns a list of value,status (single pair) for info cluster
        Helper for the fetch callback
        '''
        
        voidp = self.inst_lookup(self.card_indom, inst)

        if(voidp == None):
            return [c_api.PM_ERR_INST, 0]

        cache = cast(voidp, POINTER(MicDevInfo) )
        micdata = cache.contents

        if (item == 0):
            return [micdata.num_cores, 1]
        elif (item == 1):
            return [micdata.num_threads, 1]
        elif (item == 2):
            return [micdata.dev_id, 1]
        elif (item == 3):
            return [micdata.stepping, 1]
        elif (item == 4):
            return [micdata.frequency, 1]
        else:
            return [c_api.PM_ERR_PMID, 0]

    def mic_fetch_temp_callback(self, item, inst):
        '''
        Returns a list of value,status (single pair) for temp cluster
        Helper for the fetch callback
        '''
        
        voidp = self.inst_lookup(self.card_indom, inst)

        if(voidp == None):
            return [c_api.PM_ERR_INST, 0]

        cache = cast(voidp, POINTER(MicDevInfo) )
        micdata = cache.contents

        if (item == 0):
            return [micdata.cpu_temp, 1] if micdata.cpu_temp>=0 else [c_api.PM_ERR_PMID, 0]
        elif (item == 1):
            return [micdata.mem_temp, 1] if micdata.mem_temp>=0 else [c_api.PM_ERR_PMID, 0]
        elif (item == 2):
            return [micdata.fanin_temp, 1] if micdata.fanin_temp>=0 else [c_api.PM_ERR_PMID, 0]
        elif (item == 3):
            return [micdata.fanout_temp, 1] if micdata.fanout_temp>=0 else [c_api.PM_ERR_PMID, 0]
        elif (item == 4):
            return [micdata.core_rail_temp, 1] if micdata.core_rail_temp>=0 else [c_api.PM_ERR_PMID, 0]
        elif (item == 5):
            return [micdata.uncore_rail_temp, 1] if micdata.uncore_rail_temp>=0 else [c_api.PM_ERR_PMID, 0]
        elif (item == 6):
            return [micdata.memory_rail_temp, 1] if micdata.memory_rail_temp>=0 else [c_api.PM_ERR_PMID, 0]
        else:
            return [c_api.PM_ERR_PMID, 0]

    def mic_fetch_power_callback(self, item, inst):
        '''
        Returns a list of value,status (single pair) for power cluster
        Helper for the fetch callback
        '''
        
        voidp = self.inst_lookup(self.card_indom, inst)

        if(voidp == None):
            return [c_api.PM_ERR_INST, 0]

        cache = cast(voidp, POINTER(MicDevInfo) )
        micdata = cache.contents

        if (item == 0):
            return [micdata.power , 1] if micdata.power>=0 else [c_api.PM_ERR_PMID, 0]
        elif (item == 1):
            return [micdata.power_w0 , 1] if micdata.power_w0>=0 else [c_api.PM_ERR_PMID, 0]
        elif (item == 2):
            return [micdata.power_w1 , 1] if micdata.power_w1>=0 else [c_api.PM_ERR_PMID, 0]
        elif (item == 3):
            return [micdata.window0 , 1]
        elif (item == 4):
            return [micdata.window1 , 1]
        elif (item == 5):
            return [micdata.low_power_limit , 1]
        elif (item == 6):
            return [micdata.high_power_limit , 1]
        elif (item == 7):
            return [micdata.phys_power_limit , 1]
        else:
            return [c_api.PM_ERR_PMID, 0]

    def mic_fetch_mem_callback(self, item, inst):
        '''
        Returns a list of value,status (single pair) for mem cluster
        Helper for the fetch callback
        '''
        
        voidp = self.inst_lookup(self.card_indom, inst)

        if(voidp == None):
            return [c_api.PM_ERR_INST, 0]

        cache = cast(voidp, POINTER(MicDevInfo) )
        micdata = cache.contents

        if (item == 0):
            return [micdata.total_mem, 1]
        elif (item == 1):
            return [micdata.free_mem, 1]
        elif (item == 2):
            return [micdata.used_mem, 1]
        else:
            return [c_api.PM_ERR_PMID, 0]

    def mic_fetch_core_callback(self, item, inst):
        '''
        Returns a list of value,status (single pair) for core cluster
        Helper for the fetch callback
        '''
        
        voidp = self.inst_lookup(self.card_indom, inst)

        if(voidp == None):
            return [c_api.PM_ERR_INST, 0]

        cache = cast(voidp, POINTER(MicDevInfo) )
        micdata = cache.contents

        if (item == 0):
            return [micdata.tick_count, 1]
        elif (item == 1):
            return [micdata.all_usr, 1]
        elif (item == 2):
            return [micdata.all_sys, 1]
        elif (item == 3):
            return [micdata.all_nice, 1]
        elif (item == 4):
            return [micdata.all_idle, 1]
        else:
            return [c_api.PM_ERR_PMID, 0]

    def mic_fetch_percore_callback(self, item, inst):
        '''
        Returns a list of value,status (single pair) for percore cluster
        Helper for the fetch callback
        '''
        
        voidp = self.inst_lookup(self.cardcore_indom, inst)

        if(voidp == None):
            return [c_api.PM_ERR_INST, 0]

        cache = cast(voidp, POINTER(MicPerCoreInfo) )
        percoredata = cache.contents

        if( item < 4 ):
            if( item == 0 ):
                return [percoredata.cpu_usr, 1]
            elif( item == 1 ):
                return [percoredata.cpu_sys, 1]
            elif( item == 2 ):
                return [percoredata.cpu_nice, 1]
            else:
                return [percoredata.cpu_idle, 1]
        else:
            return [c_api.PM_ERR_PMID, 0]

    def mic_fetch_callback(self, cluster, item, inst):
        '''
        Main fetch callback, defers to helpers for each cluster.
        Returns a list of value,status (single pair) for requested pmid/inst
        '''
        #self.log("fetch callback for %d.%d[%d]" % (cluster, item, inst))
        if (cluster == self.GLOBAL_CLUSTER):
            return self.mic_fetch_global_callback(item, inst)
        elif (cluster == self.INFO_CLUSTER):
            return self.mic_fetch_info_callback(item, inst)
        elif (cluster == self.TEMP_CLUSTER):
            return self.mic_fetch_temp_callback(item, inst)
        elif (cluster == self.POWER_CLUSTER):
            return self.mic_fetch_power_callback(item, inst)
        elif (cluster == self.MEM_CLUSTER):
            return self.mic_fetch_mem_callback(item, inst)
        elif (cluster == self.CORE_CLUSTER):
            return self.mic_fetch_core_callback(item, inst)
        elif (cluster == self.PERCORE_CLUSTER):
            return self.mic_fetch_percore_callback(item, inst)
        return [c_api.PM_ERR_PMID, 0]

    def __init__(self, name, domain):
        PMDA.__init__(self, name, domain)

        self.connect_pmcd()

        self.init_mic()

        self.card_indom = self.indom(0)
        self.cardcore_indom = self.indom(1)

        self.add_indom(pmdaIndom(self.card_indom, self.cards))
        self.add_indom(pmdaIndom(self.cardcore_indom, self.cardcores))

        # Global
        self.add_metric(name + '.mgmt.numcards', pmdaMetric(self.pmid(self.GLOBAL_CLUSTER, 0),
                    c_api.PM_TYPE_U32, c_api.PM_INDOM_NULL, c_api.PM_SEM_INSTANT,
                    pmUnits(0, 0, 0, 0, 0, 0)),
                    "Number of cards in the host system")

        # Info / Freq
        self.add_metric(name + '.mgmt.info.numcores', pmdaMetric(self.pmid(self.INFO_CLUSTER,0),
                    c_api.PM_TYPE_U32, self.card_indom, c_api.PM_SEM_INSTANT,
                    pmUnits(0, 0, 0, 0, 0, 0)),
                    "Number of cores in the mic card")

        self.add_metric(name + '.mgmt.info.numthreads', pmdaMetric(self.pmid(self.INFO_CLUSTER,1),
                    c_api.PM_TYPE_U32, self.card_indom, c_api.PM_SEM_INSTANT,
                    pmUnits(0, 0, 0, 0, 0, 0)),
                    "Number of threads per core")

        self.add_metric(name + '.mgmt.info.deviceid', pmdaMetric(self.pmid(self.INFO_CLUSTER,2),
                    c_api.PM_TYPE_U32, self.card_indom, c_api.PM_SEM_INSTANT,
                    pmUnits(0, 0, 0, 0, 0, 0)),
                    "The 16-bit PCI Device ID")

        self.add_metric(name + '.mgmt.info.stepping', pmdaMetric(self.pmid(self.INFO_CLUSTER,3),
                    c_api.PM_TYPE_U32, self.card_indom, c_api.PM_SEM_INSTANT,
                    pmUnits(0, 0, 0, 0, 0, 0)),
                    "Stepping and substepping id")

        self.add_metric(name + '.mgmt.info.frequency', pmdaMetric(self.pmid(self.INFO_CLUSTER,4),
                    c_api.PM_TYPE_FLOAT, self.card_indom, c_api.PM_SEM_INSTANT,
                    pmUnits(0, 0, 0, 0, 0, 0)),
                    "Core frequency")

        # Temperature
        self.add_metric(name + '.mgmt.temp.cpu',  pmdaMetric(self.pmid(self.TEMP_CLUSTER,0),
                    c_api.PM_TYPE_FLOAT, self.card_indom, c_api.PM_SEM_INSTANT,
                    pmUnits(0, 0, 0, 0, 0, 0)),
                    "Die temperature in degrees Celsius")

        self.add_metric(name + '.mgmt.temp.mem',  pmdaMetric(self.pmid(self.TEMP_CLUSTER,1),
                    c_api.PM_TYPE_FLOAT, self.card_indom, c_api.PM_SEM_INSTANT,
                    pmUnits(0, 0, 0, 0, 0, 0)),
                    "GDDR temperature in degrees Celsius")

        self.add_metric(name + '.mgmt.temp.fanin',  pmdaMetric(self.pmid(self.TEMP_CLUSTER,2),
                    c_api.PM_TYPE_FLOAT, self.card_indom, c_api.PM_SEM_INSTANT,
                    pmUnits(0, 0, 0, 0, 0, 0)),
                    "Fan in temperature in degrees Celsius")

        self.add_metric(name + '.mgmt.temp.fanout',  pmdaMetric(self.pmid(self.TEMP_CLUSTER,3),
                    c_api.PM_TYPE_FLOAT, self.card_indom, c_api.PM_SEM_INSTANT,
                    pmUnits(0, 0, 0, 0, 0, 0)),
                    "Fan out temperature in degrees Celsius")

        self.add_metric(name + '.mgmt.temp.corerail',  pmdaMetric(self.pmid(self.TEMP_CLUSTER,4),
                    c_api.PM_TYPE_FLOAT, self.card_indom, c_api.PM_SEM_INSTANT,
                    pmUnits(0, 0, 0, 0, 0, 0)),
                    "VCCP temperature in degrees Celsius")

        self.add_metric(name + '.mgmt.temp.uncorerail',  pmdaMetric(self.pmid(self.TEMP_CLUSTER,5),
                    c_api.PM_TYPE_FLOAT, self.card_indom, c_api.PM_SEM_INSTANT,
                    pmUnits(0, 0, 0, 0, 0, 0)),
                    "VDDG temperature in degrees Celsius")

        self.add_metric(name + '.mgmt.temp.memrail',  pmdaMetric(self.pmid(self.TEMP_CLUSTER,6),
                    c_api.PM_TYPE_FLOAT, self.card_indom, c_api.PM_SEM_INSTANT,
                    pmUnits(0, 0, 0, 0, 0, 0)),
                    "VDDQ temperature in degrees Celsius")

        # Power utilization and limits
        self.add_metric(name + '.mgmt.power.inst',  pmdaMetric(self.pmid(self.POWER_CLUSTER,0),
                    c_api.PM_TYPE_FLOAT, self.card_indom, c_api.PM_SEM_INSTANT,
                    pmUnits(0, 0, 0, 0, 0, 0)),
                    "Instantaneous power utilization")

        self.add_metric(name + '.mgmt.power.w0',  pmdaMetric(self.pmid(self.POWER_CLUSTER,1),
                    c_api.PM_TYPE_FLOAT, self.card_indom, c_api.PM_SEM_INSTANT,
                    pmUnits(0, 0, 0, 0, 0, 0)),
                    "Power utilized in Window 0")

        self.add_metric(name + '.mgmt.power.w1',  pmdaMetric(self.pmid(self.POWER_CLUSTER,2),
                    c_api.PM_TYPE_FLOAT, self.card_indom, c_api.PM_SEM_INSTANT,
                    pmUnits(0, 0, 0, 0, 0, 0)),
                    "Power utilized in Window 1")

        self.add_metric(name + '.mgmt.power.t0',  pmdaMetric(self.pmid(self.POWER_CLUSTER,3),
                    c_api.PM_TYPE_FLOAT, self.card_indom, c_api.PM_SEM_INSTANT,
                    pmUnits(0, 0, 0, 0, 0, 0)),
                    "Time window level 0")

        self.add_metric(name + '.mgmt.power.t1',  pmdaMetric(self.pmid(self.POWER_CLUSTER,4),
                    c_api.PM_TYPE_FLOAT, self.card_indom, c_api.PM_SEM_INSTANT,
                    pmUnits(0, 0, 0, 0, 0, 0)),
                    "Time window level 1")

        self.add_metric(name + '.mgmt.power.low_limit',  pmdaMetric(self.pmid(self.POWER_CLUSTER,5),
                    c_api.PM_TYPE_FLOAT, self.card_indom, c_api.PM_SEM_INSTANT,
                    pmUnits(0, 0, 0, 0, 0, 0)),
                    "Limit above which PM initiates basic cooling activities such as increasing the fan speed.")

        self.add_metric(name + '.mgmt.power.high_limit',  pmdaMetric(self.pmid(self.POWER_CLUSTER,6),
                    c_api.PM_TYPE_FLOAT, self.card_indom, c_api.PM_SEM_INSTANT,
                    pmUnits(0, 0, 0, 0, 0, 0)),
                    "Limit above which PM performs aggressive cooling activities such as throttling the cores and maximizing fan speed")

        self.add_metric(name + '.mgmt.power.phys_limit',  pmdaMetric(self.pmid(self.POWER_CLUSTER,7),
                    c_api.PM_TYPE_FLOAT, self.card_indom, c_api.PM_SEM_INSTANT,
                    pmUnits(0, 0, 0, 0, 0, 0)),
                    "Also called the shutdown limit. Above this limit, the PM starts shutting down the coprocessor.")

        # Mem
        self.add_metric(name + '.mgmt.mem.total', pmdaMetric(self.pmid(self.MEM_CLUSTER, 0),
                c_api.PM_TYPE_U64, self.card_indom, c_api.PM_SEM_INSTANT,
                pmUnits(1, 0, 0, c_api.PM_SPACE_KBYTE, 0, 0)),
                "Total Memory")

        self.add_metric(name + '.mgmt.mem.free', pmdaMetric(self.pmid(self.MEM_CLUSTER, 1),
                c_api.PM_TYPE_U64, self.card_indom, c_api.PM_SEM_INSTANT,
                pmUnits(1, 0, 0, c_api.PM_SPACE_KBYTE, 0, 0)),
                "Free Memory")

        self.add_metric(name + '.mgmt.mem.used', pmdaMetric(self.pmid(self.MEM_CLUSTER, 2),
                c_api.PM_TYPE_U64, self.card_indom, c_api.PM_SEM_INSTANT,
                pmUnits(1, 0, 0, c_api.PM_SPACE_KBYTE, 0, 0)),
                "Used Memory")

        # Core
        self.add_metric(name + '.mgmt.cores.tickcount', pmdaMetric(self.pmid(self.CORE_CLUSTER,0),
                    c_api.PM_TYPE_U32, self.card_indom, c_api.PM_SEM_INSTANT,
                    pmUnits(0, 0, 0, 0, 0, 0)),
                    "The tick value is for measuring internal system time")

        self.add_metric(name + ".mgmt.cores.usr", pmdaMetric(self.pmid(self.CORE_CLUSTER, 1),
                    c_api.PM_TYPE_U64,  self.card_indom, c_api.PM_SEM_COUNTER,
                    pmUnits(0, 1, 0, 0, c_api.PM_TIME_MSEC, 0)),
                    "User time for all cores")

        self.add_metric(name + ".mgmt.cores.sys", pmdaMetric(self.pmid(self.CORE_CLUSTER, 2),
                    c_api.PM_TYPE_U64,  self.card_indom, c_api.PM_SEM_COUNTER,
                    pmUnits(0, 1, 0, 0, c_api.PM_TIME_MSEC, 0)),
                    "System time for all cores")

        self.add_metric(name + ".mgmt.cores.nice", pmdaMetric(self.pmid(self.CORE_CLUSTER, 3),
                    c_api.PM_TYPE_U64,  self.card_indom, c_api.PM_SEM_COUNTER,
                    pmUnits(0, 1, 0, 0, c_api.PM_TIME_MSEC, 0)),
                    "Nice time for all cores")

        self.add_metric(name + ".mgmt.cores.idle", pmdaMetric(self.pmid(self.CORE_CLUSTER, 4),
                    c_api.PM_TYPE_U64,  self.card_indom, c_api.PM_SEM_COUNTER,
                    pmUnits(0, 1, 0, 0, c_api.PM_TIME_MSEC, 0)),
                    "Idle time for all cores")

        # Percore metrics. Instances are cardname/coreY
        self.add_metric(name + ".mgmt.percore.usr", pmdaMetric(self.pmid(self.PERCORE_CLUSTER, 0),
            c_api.PM_TYPE_U64,  self.cardcore_indom, c_api.PM_SEM_COUNTER,
            pmUnits(0, 1, 0, 0, c_api.PM_TIME_MSEC, 0)),
            "User time per core")

        self.add_metric(name + ".mgmt.percore.sys", pmdaMetric(self.pmid(self.PERCORE_CLUSTER, 1),
            c_api.PM_TYPE_U64,  self.cardcore_indom, c_api.PM_SEM_COUNTER,
            pmUnits(0, 1, 0, 0, c_api.PM_TIME_MSEC, 0)),
            "System time per core")
    
        self.add_metric(name + ".mgmt.percore.nice", pmdaMetric(self.pmid(self.PERCORE_CLUSTER, 2),
            c_api.PM_TYPE_U64,  self.cardcore_indom, c_api.PM_SEM_COUNTER,
            pmUnits(0, 1, 0, 0, c_api.PM_TIME_MSEC, 0)),
            "Nice time per core")
    
        self.add_metric(name + ".mgmt.percore.idle", pmdaMetric(self.pmid(self.PERCORE_CLUSTER, 3),
            c_api.PM_TYPE_U64,  self.cardcore_indom, c_api.PM_SEM_COUNTER,
            pmUnits(0, 1, 0, 0, c_api.PM_TIME_MSEC, 0)),
            "Idle time per core")

        self.set_fetch(self.mic_fetch)
        self.set_instance(self.mic_instance)
        self.set_fetch_callback(self.mic_fetch_callback)
        # So far all the stats seem to be available to any user
        self.set_user(PCP.pmGetConfig('PCP_USER'))
        self.update_mic()

if __name__ == '__main__':
    MicPMDA('mic', 138).run()
