#!/usr/bin/env pmpython
#
# Copyright (C) 2017 Marko Myllynen <myllynen@redhat.com>
#
# 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.
#

# Last updated for: haproxy 1.8.0

# pylint: disable=invalid-name, line-too-long, no-self-use
# pylint: disable=too-many-instance-attributes, too-many-locals
# pylint: disable=bad-whitespace, broad-except, too-many-branches
# pylint: disable=too-many-return-statements, bad-continuation

""" PCP HAProxy Performance Metrics Domain Agent """

import sys
import socket
try:
    import ConfigParser
except ImportError:
    import configparser as ConfigParser
try:
    import urllib.request as httprequest
except Exception:
    import urllib2 as httprequest

from ctypes import c_int

from pcp.pmapi import pmUnits
from pcp.pmapi import pmContext as PCP
from pcp.pmda import PMDA, pmdaIndom, pmdaMetric
from cpmapi import PM_INDOM_NULL
from cpmapi import PM_TYPE_32, PM_TYPE_U32, PM_TYPE_U64, PM_TYPE_STRING
from cpmapi import PM_SEM_COUNTER, PM_SEM_INSTANT, PM_SEM_DISCRETE
from cpmapi import PM_COUNT_ONE, PM_SPACE_BYTE, PM_SPACE_MBYTE, PM_TIME_MSEC, PM_TIME_SEC
from cpmapi import PM_ERR_AGAIN, PM_ERR_PMID, PM_ERR_APPVERSION

if sys.version_info[0] >= 3:
    long = int # pylint: disable=redefined-builtin

DEFAULT_USER   = 'root'
DEFAULT_SOCKET = '/var/lib/haproxy/stats'

class HAProxyPMDA(PMDA):
    """ PCP HAProxy PMDA """
    def __init__(self, name, domain):
        """ Constructor """
        PMDA.__init__(self, name, domain)

        self.info = {}
        self.stat = {}
        self.user = DEFAULT_USER
        self.skt = DEFAULT_SOCKET
        self.url = None
        self.auth = None
        self.pasw = None
        self.request = None
        self.read_config()
        self.set_user(self.user)

        self.connect_pmcd()
        try:
            if not self.url:
                conn = self.skt
                stats = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
                stats.connect(self.skt)
                stats.close()
            else:
                conn = self.url
                self.request = self.setup_urllib(self.url, self.auth, self.pasw)
                req = self.request.urlopen(self.url)
                req.read()
                req.close()
        except Exception as error:
            self.log("Failed to connect HAProxy at %s: %s" % (conn, error))

        units_none  = pmUnits(0, 0, 0, 0, 0, 0)
        units_count = pmUnits(0, 0, 1, 0, 0, PM_COUNT_ONE)
        units_cpers = pmUnits(0,-1, 1, 0, PM_TIME_SEC, PM_COUNT_ONE)
        units_bytes = pmUnits(1, 0, 0, PM_SPACE_BYTE, 0, 0)
        units_mbyte = pmUnits(1, 0, 0, PM_SPACE_MBYTE, 0, 0)
        units_bpers = pmUnits(1,-1, 0, PM_SPACE_BYTE, PM_TIME_SEC, 0)
        units_msecs = pmUnits(0, 1, 0, 0, PM_TIME_MSEC, 0)
        units_secs  = pmUnits(0, 1, 0, 0, PM_TIME_SEC, 0)

        self.info_indom = PM_INDOM_NULL
        self.info_cluster = 0
        self.info_metrics = [
            # Name - type - semantics - units - help
            # See src/stats.c
            [ 'info.name',                        PM_TYPE_STRING, PM_SEM_DISCRETE, units_none,  'Name'                                                            ],
            [ 'info.version',                     PM_TYPE_STRING, PM_SEM_DISCRETE, units_none,  'Version'                                                         ],
            [ 'info.release_date',                PM_TYPE_STRING, PM_SEM_DISCRETE, units_none,  'Release_date'                                                    ],
            [ 'info.nbproc',                      PM_TYPE_U32,    PM_SEM_DISCRETE, units_count, 'Nbproc'                                                          ],
            [ 'info.process_num',                 PM_TYPE_U32,    PM_SEM_DISCRETE, units_none,  'Process_num'                                                     ],
            [ 'info.pid',                         PM_TYPE_U32,    PM_SEM_DISCRETE, units_none,  'Pid'                                                             ],
            [ 'info.uptime',                      PM_TYPE_STRING, PM_SEM_INSTANT,  units_none,  'Uptime'                                                          ],
            [ 'info.uptime_sec',                  PM_TYPE_U32,    PM_SEM_COUNTER,  units_secs,  'Uptime_sec'                                                      ],
            [ 'info.memmax_mb',                   PM_TYPE_U32,    PM_SEM_DISCRETE, units_mbyte, 'Memmax_MB'                                                       ],
            [ 'info.poolalloc_mb',                PM_TYPE_U32,    PM_SEM_INSTANT,  units_mbyte, 'PoolAlloc_MB'                                                    ],
            [ 'info.poolused_mb',                 PM_TYPE_U32,    PM_SEM_INSTANT,  units_mbyte, 'PoolUsed_MB'                                                     ],
            [ 'info.poolfailed',                  PM_TYPE_U32,    PM_SEM_COUNTER,  units_count, 'PoolFailed'                                                      ],
            [ 'info.ulimit_n',                    PM_TYPE_U32,    PM_SEM_DISCRETE, units_count, 'Ulimit-n'                                                        ],
            [ 'info.maxsock',                     PM_TYPE_U32,    PM_SEM_DISCRETE, units_count, 'Maxsock'                                                         ],
            [ 'info.maxconn',                     PM_TYPE_U32,    PM_SEM_DISCRETE, units_count, 'Maxconn'                                                         ],
            [ 'info.hard_maxconn',                PM_TYPE_U32,    PM_SEM_DISCRETE, units_count, 'Hard_maxconn'                                                    ],
            [ 'info.currconns',                   PM_TYPE_U32,    PM_SEM_INSTANT,  units_count, 'CurrConns'                                                       ],
            [ 'info.cumconns',                    PM_TYPE_U32,    PM_SEM_COUNTER,  units_count, 'CumConns'                                                        ],
            [ 'info.cumreq',                      PM_TYPE_U32,    PM_SEM_COUNTER,  units_count, 'CumReq'                                                          ],
            [ 'info.maxsslconns',                 PM_TYPE_U32,    PM_SEM_INSTANT,  units_count, 'MaxSslConns'                                                     ],
            [ 'info.currsslconns',                PM_TYPE_U32,    PM_SEM_INSTANT,  units_count, 'CurrSslConns'                                                    ],
            [ 'info.cumsslconns',                 PM_TYPE_U32,    PM_SEM_COUNTER,  units_count, 'CumSslConns'                                                     ],
            [ 'info.maxpipes',                    PM_TYPE_U32,    PM_SEM_DISCRETE, units_count, 'Maxpipes'                                                        ],
            [ 'info.pipesused',                   PM_TYPE_U32,    PM_SEM_INSTANT,  units_count, 'PipesUsed'                                                       ],
            [ 'info.pipesfree',                   PM_TYPE_U32,    PM_SEM_INSTANT,  units_count, 'PipesFree'                                                       ],
            [ 'info.connrate',                    PM_TYPE_U32,    PM_SEM_INSTANT,  units_count, 'ConnRate'                                                        ],
            [ 'info.connratelimit',               PM_TYPE_U32,    PM_SEM_DISCRETE, units_count, 'ConnRateLimit'                                                   ],
            [ 'info.maxconnrate',                 PM_TYPE_U32,    PM_SEM_INSTANT,  units_count, 'MaxConnRate'                                                     ],
            [ 'info.sessrate',                    PM_TYPE_U32,    PM_SEM_INSTANT,  units_count, 'SessRate'                                                        ],
            [ 'info.sessratelimit',               PM_TYPE_U32,    PM_SEM_DISCRETE, units_count, 'SessRateLimit'                                                   ],
            [ 'info.maxsessrate',                 PM_TYPE_U32,    PM_SEM_INSTANT,  units_count, 'MaxSessRate'                                                     ],
            [ 'info.sslrate',                     PM_TYPE_U32,    PM_SEM_INSTANT,  units_count, 'SslRate'                                                         ],
            [ 'info.sslratelimit',                PM_TYPE_U32,    PM_SEM_DISCRETE, units_count, 'SslRateLimit'                                                    ],
            [ 'info.maxsslrate',                  PM_TYPE_U32,    PM_SEM_INSTANT,  units_count, 'MaxSslRate'                                                      ],
            [ 'info.sslfrontendkeyrate',          PM_TYPE_U32,    PM_SEM_INSTANT,  units_count, 'SslFrontendKeyRate'                                              ],
            [ 'info.sslfrontendmaxkeyrate',       PM_TYPE_U32,    PM_SEM_INSTANT,  units_count, 'SslFrontendMaxKeyRate'                                           ],
            [ 'info.sslfrontendsessionreuse_pct', PM_TYPE_U32,    PM_SEM_INSTANT,  units_none,  'SslFrontendSessionReuse_pct'                                     ],
            [ 'info.sslbackendkeyrate',           PM_TYPE_U32,    PM_SEM_INSTANT,  units_count, 'SslBackendKeyRate'                                               ],
            [ 'info.sslbackendmaxkeyrate',        PM_TYPE_U32,    PM_SEM_INSTANT,  units_count, 'SslBackendMaxKeyRate'                                            ],
            [ 'info.sslcachelookups',             PM_TYPE_U32,    PM_SEM_COUNTER,  units_count, 'SslCacheLookups'                                                 ],
            [ 'info.sslcachemisses',              PM_TYPE_U32,    PM_SEM_COUNTER,  units_count, 'SslCacheMisses'                                                  ],
            [ 'info.compressbpsin',               PM_TYPE_U32,    PM_SEM_INSTANT,  units_bpers, 'CompressBpsIn'                                                   ],
            [ 'info.compressbpsout',              PM_TYPE_U32,    PM_SEM_INSTANT,  units_bpers, 'CompressBpsOut'                                                  ],
            [ 'info.compressbpsratelim',          PM_TYPE_U32,    PM_SEM_DISCRETE, units_bpers, 'CompressBpsRateLim'                                              ],
            [ 'info.zlibmemusage',                PM_TYPE_U32,    PM_SEM_INSTANT,  units_bytes, 'ZlibMemUsage'                                                    ],
            [ 'info.maxzlibmemusage',             PM_TYPE_U32,    PM_SEM_DISCRETE, units_bytes, 'MaxZlibMemUsage'                                                 ],
            [ 'info.tasks',                       PM_TYPE_U32,    PM_SEM_INSTANT,  units_count, 'Tasks'                                                           ],
            [ 'info.run_queue',                   PM_TYPE_U32,    PM_SEM_INSTANT,  units_count, 'Run_queue'                                                       ],
            [ 'info.idle_pct',                    PM_TYPE_U32,    PM_SEM_INSTANT,  units_none,  'Idle_pct'                                                        ],
            [ 'info.node',                        PM_TYPE_STRING, PM_SEM_DISCRETE, units_none,  'node'                                                            ],
            [ 'info.description',                 PM_TYPE_STRING, PM_SEM_DISCRETE, units_none,  'description'                                                     ],
        ]

        self.stat_indom = self.indom(0)
        self.stat_insts = pmdaIndom(self.stat_indom, {})
        self.add_indom(self.stat_insts)

        self.stat_cluster = 1
        self.stat_metrics = [
            # Name - type - semantics - units - help
            # See src/stats.c and https://www.haproxy.org/download/1.7/doc/management.txt
            [ 'stat.pxname',                      PM_TYPE_STRING, PM_SEM_DISCRETE, units_none,  'proxy name'                                                      ],
            [ 'stat.svname',                      PM_TYPE_STRING, PM_SEM_DISCRETE, units_none,  'service name'                                                    ],
            [ 'stat.qcur',                        PM_TYPE_U32,    PM_SEM_INSTANT,  units_count, 'current queued requests'                                         ],
            [ 'stat.qmax',                        PM_TYPE_U32,    PM_SEM_INSTANT,  units_count, 'max value of qcur'                                               ],
            [ 'stat.scur',                        PM_TYPE_U32,    PM_SEM_INSTANT,  units_count, 'current sessions'                                                ],
            [ 'stat.smax',                        PM_TYPE_U32,    PM_SEM_INSTANT,  units_count, 'max sessions'                                                    ],
            [ 'stat.slim',                        PM_TYPE_U32,    PM_SEM_DISCRETE, units_count, 'configured session limit'                                        ],
            [ 'stat.stot',                        PM_TYPE_U64,    PM_SEM_COUNTER,  units_count, 'cumulative number of sessions'                                   ],
            [ 'stat.bin',                         PM_TYPE_U64,    PM_SEM_COUNTER,  units_bytes, 'bytes in'                                                        ],
            [ 'stat.bout',                        PM_TYPE_U64,    PM_SEM_COUNTER,  units_bytes, 'bytes out'                                                       ],
            [ 'stat.dreq',                        PM_TYPE_U64,    PM_SEM_COUNTER,  units_count, 'requests denied because of security concerns'                    ],
            [ 'stat.dresp',                       PM_TYPE_U64,    PM_SEM_COUNTER,  units_count, 'responses denied because of security concerns'                   ],
            [ 'stat.ereq',                        PM_TYPE_U64,    PM_SEM_COUNTER,  units_count, 'request errors'                                                  ],
            [ 'stat.econ',                        PM_TYPE_U64,    PM_SEM_COUNTER,  units_count, 'connection errors'                                               ],
            [ 'stat.eresp',                       PM_TYPE_U64,    PM_SEM_COUNTER,  units_count, 'response errors'                                                 ],
            [ 'stat.wretr',                       PM_TYPE_U64,    PM_SEM_COUNTER,  units_count, 'number of times a connection to a server was retried'            ],
            [ 'stat.wredis',                      PM_TYPE_U64,    PM_SEM_COUNTER,  units_count, 'number of times a request was redispatched to another'           ],
            [ 'stat.status',                      PM_TYPE_STRING, PM_SEM_INSTANT,  units_none,  'status'                                                          ],
            [ 'stat.weight',                      PM_TYPE_U32,    PM_SEM_INSTANT,  units_count, 'total weight (backend), server weight (server)'                  ],
            [ 'stat.act',                         PM_TYPE_U32,    PM_SEM_INSTANT,  units_count, 'number of active servers (backend), server is active (server)'   ],
            [ 'stat.bck',                         PM_TYPE_U32,    PM_SEM_INSTANT,  units_count, 'number of backup servers (backend), server is backup (server)'   ],
            [ 'stat.chkfail',                     PM_TYPE_U64,    PM_SEM_COUNTER,  units_count, 'number of failed checks'                                         ],
            [ 'stat.chkdown',                     PM_TYPE_U64,    PM_SEM_COUNTER,  units_count, 'number of UP->DOWN transitions'                                  ],
            [ 'stat.lastchg',                     PM_TYPE_U32,    PM_SEM_INSTANT,  units_secs,  'number of seconds since the last UP<->DOWN transition'           ],
            [ 'stat.downtime',                    PM_TYPE_U32,    PM_SEM_COUNTER,  units_secs,  'total downtime'                                                  ],
            [ 'stat.qlimit',                      PM_TYPE_U32,    PM_SEM_DISCRETE, units_count, 'configured maxqueue for the server'                              ],
            [ 'stat.pid',                         PM_TYPE_U32,    PM_SEM_DISCRETE, units_none,  'process id'                                                      ],
            [ 'stat.iid',                         PM_TYPE_U32,    PM_SEM_DISCRETE, units_none,  'unique proxy id'                                                 ],
            [ 'stat.sid',                         PM_TYPE_U32,    PM_SEM_DISCRETE, units_none,  'server id'                                                       ],
            [ 'stat.throttle',                    PM_TYPE_U32,    PM_SEM_INSTANT,  units_none,  'current throttle percentage for the server'                      ],
            [ 'stat.lbtot',                       PM_TYPE_U64,    PM_SEM_COUNTER,  units_count, 'total number of times a server was selected'                     ],
            [ 'stat.tracked',                     PM_TYPE_STRING, PM_SEM_DISCRETE, units_none,  'id of proxy/server if tracking is enabled'                       ],
            [ 'stat.type',                        PM_TYPE_U32,    PM_SEM_DISCRETE, units_none,  '0=frontend, 1=backend, 2=server, 3=socket/listener'              ],
            [ 'stat.rate',                        PM_TYPE_U32,    PM_SEM_INSTANT,  units_cpers, 'number of sessions per second over last elapsed second'          ],
            [ 'stat.rate_lim',                    PM_TYPE_U32,    PM_SEM_DISCRETE, units_cpers, 'configured limit on new sessions per second'                     ],
            [ 'stat.rate_max',                    PM_TYPE_U32,    PM_SEM_INSTANT,  units_cpers, 'max number of new sessions per second'                           ],
            [ 'stat.check_status',                PM_TYPE_STRING, PM_SEM_INSTANT,  units_count, 'status of last health check'                                     ],
            [ 'stat.check_code',                  PM_TYPE_U32,    PM_SEM_INSTANT,  units_none,  'layer5-7 code'                                                   ],
            [ 'stat.check_duration',              PM_TYPE_U64,    PM_SEM_INSTANT,  units_msecs, 'time in ms took to finish last health check'                     ],
            [ 'stat.hrsp_1xx',                    PM_TYPE_U64,    PM_SEM_COUNTER,  units_count, 'http responses with 1xx code'                                    ],
            [ 'stat.hrsp_2xx',                    PM_TYPE_U64,    PM_SEM_COUNTER,  units_count, 'http responses with 2xx code'                                    ],
            [ 'stat.hrsp_3xx',                    PM_TYPE_U64,    PM_SEM_COUNTER,  units_count, 'http responses with 3xx code'                                    ],
            [ 'stat.hrsp_4xx',                    PM_TYPE_U64,    PM_SEM_COUNTER,  units_count, 'http responses with 4xx code'                                    ],
            [ 'stat.hrsp_5xx',                    PM_TYPE_U64,    PM_SEM_COUNTER,  units_count, 'http responses with 5xx code'                                    ],
            [ 'stat.hrsp_other',                  PM_TYPE_U64,    PM_SEM_COUNTER,  units_count, 'http responses with other codes'                                 ],
            [ 'stat.hanafail',                    PM_TYPE_U64,    PM_SEM_COUNTER,  units_count, 'failed health checks details'                                    ],
            [ 'stat.req_rate',                    PM_TYPE_U32,    PM_SEM_INSTANT,  units_cpers, 'HTTP requests per second over last elapsed second'               ],
            [ 'stat.req_rate_max',                PM_TYPE_U32,    PM_SEM_INSTANT,  units_cpers, 'max number of HTTP requests per second observed'                 ],
            [ 'stat.req_tot',                     PM_TYPE_U64,    PM_SEM_COUNTER,  units_count, 'total number of HTTP requests received'                          ],
            [ 'stat.cli_abrt',                    PM_TYPE_U64,    PM_SEM_COUNTER,  units_count, 'number of data transfers aborted by the client'                  ],
            [ 'stat.srv_abrt',                    PM_TYPE_U64,    PM_SEM_COUNTER,  units_count, 'number of data transfers aborted by the server'                  ],
            [ 'stat.comp_in',                     PM_TYPE_U64,    PM_SEM_COUNTER,  units_bytes, 'number of HTTP response bytes fed to the compressor'             ],
            [ 'stat.comp_out',                    PM_TYPE_U64,    PM_SEM_COUNTER,  units_bytes, 'number of HTTP response bytes emitted by the compressor'         ],
            [ 'stat.comp_byp',                    PM_TYPE_U64,    PM_SEM_COUNTER,  units_bytes, 'number of bytes that bypassed the HTTP compressor'               ],
            [ 'stat.comp_rsp',                    PM_TYPE_U64,    PM_SEM_COUNTER,  units_count, 'number of HTTP responses that were compressed'                   ],
            [ 'stat.lastsess',                    PM_TYPE_32,     PM_SEM_INSTANT,  units_secs,  'number of seconds since last session assigned to server/backend' ],
            [ 'stat.last_chk',                    PM_TYPE_STRING, PM_SEM_INSTANT,  units_none,  'last health check contents or textual error'                     ],
            [ 'stat.last_agt',                    PM_TYPE_STRING, PM_SEM_INSTANT,  units_none,  'last agent check contents or textual error'                      ],
            [ 'stat.qtime',                       PM_TYPE_U32,    PM_SEM_INSTANT,  units_msecs, 'the average queue time in ms over the 1024 last requests'        ],
            [ 'stat.ctime',                       PM_TYPE_U32,    PM_SEM_INSTANT,  units_msecs, 'the average connect time in ms over the 1024 last requests'      ],
            [ 'stat.rtime',                       PM_TYPE_U32,    PM_SEM_INSTANT,  units_msecs, 'the average response time in ms over the 1024 last requests'     ],
            [ 'stat.ttime',                       PM_TYPE_U32,    PM_SEM_INSTANT,  units_msecs, 'the average total session time in ms over the 1024 last'         ],
            [ 'stat.agent_status',                PM_TYPE_STRING, PM_SEM_INSTANT,  units_none,  'status of last agent check'                                      ],
            [ 'stat.agent_code',                  PM_TYPE_U32,    PM_SEM_INSTANT,  units_none,  'numeric code reported by agent'                                  ],
            [ 'stat.agent_duration',              PM_TYPE_U64,    PM_SEM_INSTANT,  units_msecs, 'time in ms taken to finish last check'                           ],
            [ 'stat.check_desc',                  PM_TYPE_STRING, PM_SEM_INSTANT,  units_none,  'short human-readable description of check_status'                ],
            [ 'stat.agent_desc',                  PM_TYPE_STRING, PM_SEM_INSTANT,  units_none,  'short human-readable description of agent_status'                ],
            [ 'stat.check_rise',                  PM_TYPE_U32,    PM_SEM_DISCRETE, units_none,  'server "rise" parameter used by checks'                          ],
            [ 'stat.check_fall',                  PM_TYPE_U32,    PM_SEM_DISCRETE, units_none,  'server "fall" parameter used by checks'                          ],
            [ 'stat.check_health',                PM_TYPE_U32,    PM_SEM_DISCRETE, units_none,  'server health parameter'                                         ],
            [ 'stat.agent_rise',                  PM_TYPE_U32,    PM_SEM_DISCRETE, units_none,  'agent "rise" parameter'                                          ],
            [ 'stat.agent_fall',                  PM_TYPE_U32,    PM_SEM_DISCRETE, units_none,  'agent "fall" parameter'                                          ],
            [ 'stat.agent_health',                PM_TYPE_U32,    PM_SEM_DISCRETE, units_none,  'agent health parameter'                                          ],
            [ 'stat.addr',                        PM_TYPE_STRING, PM_SEM_DISCRETE, units_none,  'address:port or "unix"'                                          ],
            [ 'stat.cookie',                      PM_TYPE_STRING, PM_SEM_DISCRETE, units_none,  'servers cookie value or backends cookie name'                    ],
            [ 'stat.mode',                        PM_TYPE_STRING, PM_SEM_DISCRETE, units_none,  'proxy mode'                                                      ],
            [ 'stat.algo',                        PM_TYPE_STRING, PM_SEM_DISCRETE, units_none,  'load balancing algorithm'                                        ],
            [ 'stat.conn_rate',                   PM_TYPE_U32,    PM_SEM_INSTANT,  units_count, 'number of connections over the last elapsed second'              ],
            [ 'stat.conn_rate_max',               PM_TYPE_U32,    PM_SEM_INSTANT,  units_count, 'highest known conn_rate'                                         ],
            [ 'stat.conn_tot',                    PM_TYPE_U64,    PM_SEM_COUNTER,  units_count, 'cumulative number of connections'                                ],
            [ 'stat.intercepted',                 PM_TYPE_U64,    PM_SEM_COUNTER,  units_count, 'cumulative number of intercepted connections'                    ],
            [ 'stat.dcon',                        PM_TYPE_U64,    PM_SEM_COUNTER,  units_count, 'requests denied by "tcp-request connection" rules'               ],
            [ 'stat.dses',                        PM_TYPE_U64,    PM_SEM_COUNTER,  units_count, 'requests denied by "tcp-request session" rules'                  ],
        ]

        for item in range(len(self.info_metrics)):
            self.add_metric(name + '.' + self.info_metrics[item][0], pmdaMetric(self.pmid(self.info_cluster, item),
                            self.info_metrics[item][1], self.info_indom, self.info_metrics[item][2],
                            self.info_metrics[item][3]), self.info_metrics[item][4], self.info_metrics[item][4])

        for item in range(len(self.stat_metrics)):
            self.add_metric(name + '.' + self.stat_metrics[item][0], pmdaMetric(self.pmid(self.stat_cluster, item),
                            self.stat_metrics[item][1], self.stat_indom, self.stat_metrics[item][2],
                            self.stat_metrics[item][3]), self.stat_metrics[item][4], self.stat_metrics[item][4])

        self.set_refresh(self.haproxy_refresh)
        self.set_fetch_callback(self.haproxy_fetch_callback)

    def read_config(self):
        """ Read configuration """
        conffile = PCP.pmGetConfig('PCP_PMDAS_DIR')
        conffile += '/' + self.read_name() + '/' + self.read_name() + '.conf'

        # Silently ignore missing file/section
        # Python < 3.2 compat
        if sys.version_info[0] >= 3 and sys.version_info[1] >= 2:
            config = ConfigParser.ConfigParser()
        else:
            config = ConfigParser.SafeConfigParser()
        config.read(conffile)
        if config.has_section('pmda'):
            for opt in config.options('pmda'):
                if opt == 'user':
                    self.user = config.get('pmda', opt)
                elif opt == 'socket':
                    self.skt = config.get('pmda', opt)
                elif opt == 'url':
                    self.url = config.get('pmda', opt)
                elif opt == 'auth':
                    self.auth = config.get('pmda', opt)
                elif opt == 'pass':
                    self.pasw = config.get('pmda', opt)
                else:
                    self.err("Invalid directive '%s' in %s." % (opt, conffile))
                    sys.exit(1)

    def setup_urllib(self, url, auth, pasw):
        """ Setup urllib """
        passman = httprequest.HTTPPasswordMgrWithDefaultRealm()
        passman.add_password(None, url, auth, pasw)
        authhandler = httprequest.HTTPBasicAuthHandler(passman)
        opener = httprequest.build_opener(authhandler)
        httprequest.install_opener(opener)
        return httprequest

    def read_socket(self, skt):
        """ Read data from socket """
        resp = ""
        while True:
            data = skt.recv(4096).decode('UTF-8')
            if not data:
                break
            resp += data
        return resp

    def haproxy_refresh(self, cluster):
        """ Refresh """
        try:
            if not self.url:
                conn = self.skt
                stats = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
                stats.connect(self.skt)
            else:
                conn = self.url
                req = self.request.urlopen(self.url)
        except Exception as error:
            self.log("Failed to connect to %s: %s" % (conn, error))
            if self.info:
                self.info = {}
            if self.stat:
                self.stat = {}
            return

        if cluster == self.info_cluster:
            if self.url:
                return
            self.info = {}
            stats.send(b"show info\n")
            for line in self.read_socket(stats).splitlines():
                if ': ' in line:
                    k, v = line.split(': ')
                    self.info[k] = v

        if cluster == self.stat_cluster:
            insts = {}
            self.stat = {}
            if not self.url:
                stats.send(b"show stat\n")
                data = self.read_socket(stats)
                stats.close()
            else:
                data = req.read().decode('UTF-8')
                req.close()
            if data.startswith("# pxname"):
                for line in data.splitlines():
                    if not line.startswith('#') and ',' in line:
                        name = line.split(',')[0] + "::" + line.split(',')[1]
                        self.stat[name] = line.split(',')
                        insts[name] = c_int(1)
            else:
                self.log("Unrecognized reply from HAProxy.")
                if self.url and data.startswith("<"):
                    self.log("Got HTML, expected CSV.")

            self.stat_insts.set_instances(self.stat_indom, insts)
            self.replace_indom(self.stat_indom, insts)

    def haproxy_fetch_callback(self, cluster, item, inst):
        """ Fetch callback """
        if cluster == self.info_cluster:
            if not self.info:
                return [PM_ERR_AGAIN, 0]
            try:
                if self.info_metrics[item][1] == PM_TYPE_STRING:
                    return [self.info[self.info_metrics[item][4]], 1]
                else:
                    return [long(self.info[self.info_metrics[item][4]]), 1]
            except Exception:
                return [PM_ERR_APPVERSION, 0]

        if cluster == self.stat_cluster:
            if not self.stat:
                return [PM_ERR_AGAIN, 0]
            try:
                name = self.stat_insts.inst_name_lookup(inst)
                if self.stat_metrics[item][1] == PM_TYPE_STRING:
                    return [self.stat[name][item], 1]
                else:
                    return [long(self.stat[name][item]), 1]
            except Exception:
                return [PM_ERR_APPVERSION, 0]

        return [PM_ERR_PMID, 0]

if __name__ == '__main__':
    HAProxyPMDA('haproxy', 145).run()
