#!/usr/bin/env pmpython
#
# Copyright (C) 2018 Red Hat.
# 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.
#

# pylint: disable=line-too-long, too-many-return-statements
# pylint: disable=too-many-statements, too-many-branches, too-many-lines
# pylint: disable=broad-except, too-many-instance-attributes

""" Performance Metrics Domain Agent exporting Elasticsearch metrics. """

import os
import sys
import json
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.pmda import PMDA, pmdaMetric, pmdaIndom
from pcp.pmapi import pmUnits
from pcp.pmapi import pmContext as PCP
from cpmapi import PM_INDOM_NULL
from cpmapi import PM_TYPE_32, PM_TYPE_U32, PM_TYPE_U64, PM_TYPE_STRING, PM_TYPE_FLOAT
from cpmapi import PM_SEM_COUNTER, PM_SEM_INSTANT, PM_SEM_DISCRETE
from cpmapi import PM_COUNT_ONE, PM_SPACE_BYTE, PM_TIME_MSEC
from cpmapi import PM_ERR_PMID, PM_ERR_APPVERSION
from cpmda import PMDA_FETCH_NOVALUES

DEFAULT_PORT = 9200
DEFAULT_URL = f"http://localhost:{DEFAULT_PORT}/"
DEFAULT_VERSION = 2
DEFAULT_USER = "root"


def _is_pmda_setup():
    """checks if PMDA is in setup state"""
    return os.environ.get('PCP_PYTHON_DOMAIN') or os.environ.get('PCP_PYTHON_PMNS')


def _setup_urllib(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


class ElasticsearchPMDA(PMDA):
    """ PCP Elasticsearch PMDA """
    def __init__(self, name, domain):
        """ Constructor """
        PMDA.__init__(self, name, domain)
        self.user = DEFAULT_USER
        self.baseurl = DEFAULT_URL
        self.auth = None
        self.password = None
        self.request = None
        self.error = False
        self.version = DEFAULT_VERSION
        self.read_config()
        # statistics dicts
        self.cluster = {}
        self.nodes = {}
        self.node_info = {}
        self.info = {}
        self.stat = {}
        self.search = {}
        self.perindex = {}
        self.master_node = None
        self.index = {}

        if self.user != DEFAULT_USER and not _is_pmda_setup():
            self.log(f"Switching to user '{self.user}'")
            self.set_user(self.user)

        self.connect_pmcd()

        if not _is_pmda_setup():
            try:
                connection = self.baseurl
                self.request = _setup_urllib(self.baseurl, self.auth, self.password)
                request = self.request.urlopen(self.baseurl)
                request.read()
                request.close()
            except (BaseException, Exception):
                self.log(f"Failed Elasticsearch connection attempt at {connection}")

        # metrics setup
        self.cluster_indom = PM_INDOM_NULL
        self.cluster_cluster = 0
        self.cluster_metrics = [
            # Name -                           type -          semantics -     units -               help
            [ 'cluster.cluster_name',          PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0), 'Name of the elasticsearch cluster'                                 ], #  0
            [ 'cluster.status.colour',         PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0), 'Status (green,yellow,red) of the elasticsearch cluster'            ], #  1
            [ 'cluster.timed_out',             PM_TYPE_U32,    PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0), 'Timed out status (0:false,1:true) of the elasticsearch cluster'    ], #  2
            [ 'cluster.number_of_nodes',       PM_TYPE_U32,    PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0), 'Number of nodes in the elasticsearch cluster'                      ], #  3
            [ 'cluster.number_of_data_nodes',  PM_TYPE_U32,    PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0), 'Number of data nodes in the elasticsearch cluster'                 ], #  4
            [ 'cluster.active_primary_shards', PM_TYPE_U32,    PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0), 'Number of active primary shards in the elasticsearch cluster'      ], #  5
            [ 'cluster.active_shards',         PM_TYPE_U32,    PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0), 'Number of primary shards in the elasticsearch cluster'             ], #  6
            [ 'cluster.relocating_shards',     PM_TYPE_U32,    PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0), 'Number of relocating shards in the elasticsearch cluster'          ], #  7
            [ 'cluster.initializing_shards',   PM_TYPE_U32,    PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0), 'Number of initializing shards in the elasticsearch cluster'        ], #  8
            [ 'cluster.unassigned_shards',     PM_TYPE_U32,    PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0), 'Number of unassigned shards in the elasticsearch cluster'          ], #  9
            [ 'cluster.status.code',           PM_TYPE_32,     PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0), 'Status code (0:green,1:yellow,2:red) of the elasticsearch cluster' ], # 10
        ]

        for item, metric in enumerate(self.cluster_metrics):
            self.add_metric(name + '.' + metric[0],
                            pmdaMetric(self.pmid(self.cluster_cluster, item),
                                       metric[1], self.cluster_indom,
                                       metric[2], metric[3]),
                            metric[4], metric[4])

        self.nodes_indom = self.indom(0)
        self.nodes_insts = pmdaIndom(self.nodes_indom, {})
        self.add_indom(self.nodes_insts, 'elasticsearch cluster nodes')
        self.nodes_cluster = 1
        self.nodes_metrics = [
            # Name -                                                                   type -          semantics -     units -                           help
            [ 'nodes.indices.size_in_bytes',                                           PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], #  0 - deprecated (now item 47)
            [ 'nodes.indices.docs.count',                                              PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),             '' ], #  1
            [ 'nodes.indices.docs.num_docs',                                           PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),             '' ], #  2 - bad metric
            [ 'nodes.indices.cache.field_evictions',                                   PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], #  3 - bad metric
            [ 'nodes.indices.cache.field_size_in_bytes',                               PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], #  4 - bad metric
            [ 'nodes.indices.cache.filter_count',                                      PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], #  5 - bad metric
            [ 'nodes.indices.cache.filter_evictions',                                  PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], #  6 - bad metric
            [ 'nodes.indices.cache.filter_size_in_bytes',                              PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], #  7 - bad metric
            [ 'nodes.indices.merges.current',                                          PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], #  8
            [ 'nodes.indices.merges.total',                                            PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], #  9
            [ 'nodes.indices.merges.total_time_in_millis',                             PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,1,0,0,PM_TIME_MSEC,0),  '' ], # 10
            [ 'nodes.jvm.uptime_in_millis',                                            PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(0,1,0,0,PM_TIME_MSEC,0),  '' ], # 11
            [ 'nodes.jvm.uptime',                                                      PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),             '' ], # 12 - bad metric
            [ 'nodes.jvm.mem.heap_used_in_bytes',                                      PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 13
            [ 'nodes.jvm.mem.heap_committed_in_bytes',                                 PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 14
            [ 'nodes.jvm.mem.non_heap_used_in_bytes',                                  PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 15
            [ 'nodes.jvm.mem.non_heap_committed_in_bytes',                             PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 16
            [ 'nodes.jvm.threads.count',                                               PM_TYPE_U32,    PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),             '' ], # 17
            [ 'nodes.jvm.threads.peak_count',                                          PM_TYPE_U32,    PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),             '' ], # 18
            [ 'nodes.jvm.gc.collection_count',                                         PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 19 - bad metric
            [ 'nodes.jvm.gc.collection_time_in_millis',                                PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,1,0,0,PM_TIME_MSEC,0),  '' ], # 20 - bad metric
            [ 'nodes.jvm.gc.collectors.Copy.collection_count',                         PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 21 - bad metric
            [ 'nodes.jvm.gc.collectors.Copy.collection_time_in_millis',                PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,1,0,0,PM_TIME_MSEC,0),  '' ], # 22 - bad metric
            [ 'nodes.jvm.gc.collectors.ParNew.collection_count',                       PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 23 - bad metric
            [ 'nodes.jvm.gc.collectors.ParNew.collection_time_in_millis',              PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,1,0,0,PM_TIME_MSEC,0),  '' ], # 24 - bad metric
            [ 'nodes.jvm.gc.collectors.ConcurrentMarkSweep.collection_count',          PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 25 - bad metric
            [ 'nodes.jvm.gc.collectors.ConcurrentMarkSweep.collection_time_in_millis', PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,1,0,0,PM_TIME_MSEC,0),  '' ], # 26 - bad metric
            [ 'nodes.indices.docs.deleted',                                            PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),             '' ], # 27
            [ 'nodes.indices.indexing.index_total',                                    PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 28
            [ 'nodes.indices.indexing.index_time_in_millis',                           PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,1,0,0,PM_TIME_MSEC,0),  '' ], # 29
            [ 'nodes.indices.indexing.delete_total',                                   PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 30
            [ 'nodes.indices.indexing.delete_time_in_millis',                          PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,1,0,0,PM_TIME_MSEC,0),  '' ], # 31
            [ 'nodes.indices.merges.current_docs',                                     PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 32
            [ 'nodes.indices.merges.current_size_in_bytes',                            PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 33
            [ 'nodes.indices.merges.total_docs',                                       PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 34
            [ 'nodes.indices.merges.total_size_in_bytes',                              PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 35
            [ 'nodes.indices.refresh.total',                                           PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 36
            [ 'nodes.indices.refresh.total_time_in_millis',                            PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,1,0,0,PM_TIME_MSEC,0),  '' ], # 37
            [ 'nodes.indices.flush.total',                                             PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 38
            [ 'nodes.indices.flush.total_time_in_millis',                              PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,1,0,0,PM_TIME_MSEC,0),  '' ], # 39
            [ 'nodes.process.timestamp',                                               PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(0,1,0,0,PM_TIME_MSEC,0),  '' ], # 40 - not usable
            [ 'nodes.process.open_file_descriptors',                                   PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),             '' ], # 41
            [ 'nodes.process.cpu.percent',                                             PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),             '' ], # 42
            [ 'nodes.process.cpu.sys_in_millis',                                       PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,1,0,0,PM_TIME_MSEC,0),  '' ], # 43
            [ 'nodes.process.cpu.user_in_millis',                                      PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,1,0,0,PM_TIME_MSEC,0),  '' ], # 44
            [ 'nodes.process.mem.resident_in_bytes',                                   PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 45
            [ 'nodes.process.mem.total_virtual_in_bytes',                              PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 46
            [ 'nodes.indices.store.size_in_bytes',                                     PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 47
            [ 'nodes.indices.get.total',                                               PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 48
            [ 'nodes.indices.get.time_in_millis',                                      PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,1,0,0,PM_TIME_MSEC,0),  '' ], # 49
            [ 'nodes.indices.get.exists_total',                                        PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 50
            [ 'nodes.indices.get.exists_time_in_millis',                               PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,1,0,0,PM_TIME_MSEC,0),  '' ], # 51
            [ 'nodes.indices.get.missing_total',                                       PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 52
            [ 'nodes.indices.get.missing_time_in_millis',                              PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,1,0,0,PM_TIME_MSEC,0),  '' ], # 53
            [ 'nodes.indices.search.query_total',                                      PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 54
            [ 'nodes.indices.search.query_time_in_millis',                             PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,1,0,0,PM_TIME_MSEC,0),  '' ], # 55
            [ 'nodes.indices.search.fetch_total',                                      PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 56
            [ 'nodes.indices.search.fetch_time_in_millis',                             PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,1,0,0,PM_TIME_MSEC,0),  '' ], # 57
            [ 'nodes.transport.server_open',                                           PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),             '' ], # 58
            [ 'nodes.transport.rx_count',                                              PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 59
            [ 'nodes.transport.rx_size_in_bytes',                                      PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 60
            [ 'nodes.transport.tx_count',                                              PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 61
            [ 'nodes.transport.tx_size_in_bytes',                                      PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 62
            [ 'nodes.http.current_open',                                               PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),             '' ], # 63
            [ 'nodes.http.total_opened',                                               PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 64
            [ 'nodes.jvm.mem.pools.old.max_in_bytes',                                  PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 65
            [ 'nodes.jvm.mem.pools.old.peak_max_in_bytes',                             PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 66
            [ 'nodes.jvm.mem.pools.old.peak_used_in_bytes',                            PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 67
            [ 'nodes.jvm.mem.pools.old.used_in_bytes',                                 PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 68
            [ 'nodes.jvm.mem.pools.survivor.max_in_bytes',                             PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 69
            [ 'nodes.jvm.mem.pools.survivor.peak_max_in_bytes',                        PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 70
            [ 'nodes.jvm.mem.pools.survivor.peak_used_in_bytes',                       PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 71
            [ 'nodes.jvm.mem.pools.survivor.used_in_bytes',                            PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 72
            [ 'nodes.jvm.mem.pools.young.max_in_bytes',                                PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 73
            [ 'nodes.jvm.mem.pools.young.peak_max_in_bytes',                           PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 74
            [ 'nodes.jvm.mem.pools.young.peak_used_in_bytes',                          PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 75
            [ 'nodes.jvm.mem.pools.young.used_in_bytes',                               PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 76
            [ 'nodes.jvm.gc.collectors.old.collection_count',                          PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 77
            [ 'nodes.jvm.gc.collectors.old.collection_time_in_millis',                 PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,1,0,0,PM_TIME_MSEC,0),  '' ], # 78
            [ 'nodes.jvm.gc.collectors.young.collection_count',                        PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 79
            [ 'nodes.jvm.gc.collectors.young.collection_time_in_millis',               PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,1,0,0,PM_TIME_MSEC,0),  '' ], # 80
            [ 'nodes.jvm.buffer_pools.direct.count',                                   PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),             '' ], # 81
            [ 'nodes.jvm.buffer_pools.direct.used_in_bytes',                           PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 82
            [ 'nodes.jvm.buffer_pools.direct.total_capacity_in_bytes',                 PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 83
            [ 'nodes.jvm.buffer_pools.mapped.count',                                   PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),             '' ], # 84
            [ 'nodes.jvm.buffer_pools.mapped.used_in_bytes',                           PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 85
            [ 'nodes.jvm.buffer_pools.mapped.total_capacity_in_bytes',                 PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 86
            [ 'nodes.indices.search.query_current',                                    PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),             '' ], # 87
            [ 'nodes.indices.search.fetch_current',                                    PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),             '' ], # 88
            [ 'nodes.indices.search.open_contexts',                                    PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),             '' ], # 89
            [ 'nodes.indices.get.current',                                             PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),             '' ], # 90
            [ 'nodes.indices.indexing.index_current',                                  PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),             '' ], # 91
            [ 'nodes.indices.indexing.delete_current',                                 PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),             '' ], # 92
            [ 'nodes.indices.indexing.noop_update_total',                              PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 93
            [ 'nodes.indices.indexing.is_throttled',                                   PM_TYPE_U32,    PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),             '' ], # 94
            [ 'nodes.indices.indexing.throttle_time_in_millis',                        PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,1,0,0,PM_TIME_MSEC,0),  '' ], # 95
            [ 'nodes.indices.store.throttle_time_in_millis',                           PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,1,0,0,PM_TIME_MSEC,0),  '' ], # 96
            [ 'nodes.indices.warmer.current',                                          PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),             '' ], # 97
            [ 'nodes.indices.warmer.total',                                            PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 98
            [ 'nodes.indices.warmer.total_time_in_millis',                             PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,1,0,0,PM_TIME_MSEC,0),  '' ], # 99
            [ 'nodes.indices.filter_cache.memory_size_in_bytes',                       PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 100
            [ 'nodes.indices.filter_cache.evictions',                                  PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 101
            [ 'nodes.indices.id_cache.memory_size_in_bytes',                           PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 102
            [ 'nodes.indices.fielddata.memory_size_in_bytes',                          PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 103
            [ 'nodes.indices.fielddata.evictions',                                     PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 104
            [ 'nodes.indices.percolate.total',                                         PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 105
            [ 'nodes.indices.percolate.time_in_millis',                                PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,1,0,0,PM_TIME_MSEC,0),  '' ], # 106
            [ 'nodes.indices.percolate.current',                                       PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),             '' ], # 107
            [ 'nodes.indices.percolate.memory_size_in_bytes',                          PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 108
            [ 'nodes.indices.percolate.queries',                                       PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),             '' ], # 109
            [ 'nodes.indices.completion.size_in_bytes',                                PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 110
            [ 'nodes.indices.segments.count',                                          PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),             '' ], # 111
            [ 'nodes.indices.segments.memory_in_bytes',                                PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 112
            [ 'nodes.indices.segments.index_writer_memory_in_bytes',                   PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 113
            [ 'nodes.indices.segments.index_writer_max_memory_in_bytes',               PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 114
            [ 'nodes.indices.segments.version_map_memory_in_bytes',                    PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 115
            [ 'nodes.indices.segments.fixed_bit_set_memory_in_bytes',                  PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 116
            [ 'nodes.indices.translog.operations',                                     PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 117
            [ 'nodes.indices.translog.size_in_bytes',                                  PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 118
            [ 'nodes.indices.suggest.total',                                           PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 119
            [ 'nodes.indices.suggest.time_in_millis',                                  PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,1,0,0,PM_TIME_MSEC,0),  '' ], # 120
            [ 'nodes.indices.suggest.current',                                         PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),             '' ], # 121
            [ 'nodes.indices.query_cache.memory_size_in_bytes',                        PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 122
            [ 'nodes.indices.query_cache.evictions',                                   PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 123
            [ 'nodes.indices.query_cache.hit_count',                                   PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 124
            [ 'nodes.indices.query_cache.miss_count',                                  PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 125
            [ 'nodes.indices.recovery.current_as_source',                              PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),             '' ], # 126
            [ 'nodes.indices.recovery.current_as_target',                              PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),             '' ], # 127
            [ 'nodes.indices.recovery.throttle_time_in_millis',                        PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,1,0,0,PM_TIME_MSEC,0),  '' ], # 128
            [ 'nodes.network.tcp.active_opens',                                        PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 129
            [ 'nodes.network.tcp.passive_opens',                                       PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 130
            [ 'nodes.network.tcp.curr_estab',                                          PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 131
            [ 'nodes.network.tcp.in_segs',                                             PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 132
            [ 'nodes.network.tcp.out_segs',                                            PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 133
            [ 'nodes.network.tcp.retrans_segs',                                        PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 134
            [ 'nodes.network.tcp.estab_resets',                                        PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 135
            [ 'nodes.network.tcp.attempt_fails',                                       PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 136
            [ 'nodes.network.tcp.in_errs',                                             PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 137
            [ 'nodes.network.tcp.out_rsts',                                            PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 138
            [ 'nodes.fs.total.total_in_bytes',                                         PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 139
            [ 'nodes.fs.total.free_in_bytes',                                          PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 140
            # Not sure difference between "free_in_bytes" and "available_in_bytes"
            [ 'nodes.fs.total.available_in_bytes',                                     PM_TYPE_U64,    PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 141
            [ 'nodes.fs.total.disk_reads',                                             PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 142
            [ 'nodes.fs.total.disk_writes',                                            PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 143
            [ 'nodes.fs.total.disk_io_op',                                             PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE),  '' ], # 144
            [ 'nodes.fs.total.disk_read_size_in_bytes',                                PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 145
            [ 'nodes.fs.total.disk_write_size_in_bytes',                               PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 146
            [ 'nodes.fs.total.disk_io_size_in_bytes',                                  PM_TYPE_U64,    PM_SEM_COUNTER, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 147
        ]
        self.network_map = {
            129: 'active_opens',
            130: 'passive_opens',
            131: 'curr_estab',
            132: 'in_segs',
            133: 'out_segs',
            134: 'retrans_segs',
            135: 'estab_resets',
            136: 'attempt_fails',
            137: 'in_errs',
            138: 'out_rsts',
        }
        self.fs_map = {
            139: 'total_in_bytes',
            140: 'free_in_bytes',
            141: 'available_in_bytes',
            142: 'disk_reads',
            143: 'disk_writes',
            144: 'disk_io_op',
            145: 'disk_read_size_in_bytes',
            146: 'disk_write_size_in_bytes',
            147: 'disk_io_size_in_bytes',
        }
        # 148 - 159 (3 x 4)
        idx = 148
        self.breaker_map = {}
        for breaker in [ "request", "fielddata", "parent" ]:
            metrics = [
                [ f'nodes.breakers.{breaker}.limit_size_in_bytes',     PM_TYPE_U64,   PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ],
                [ f'nodes.breakers.{breaker}.estimated_size_in_bytes', PM_TYPE_U64,   PM_SEM_INSTANT, pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ],
                [ f'nodes.breakers.{breaker}.overhead',                PM_TYPE_FLOAT, PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),             '' ],
                [ f'nodes.breakers.{breaker}.tripped',                 PM_TYPE_U32,   PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),             '' ],
            ]
            self.nodes_metrics.extend(metrics)
            self.breaker_map[idx]   = ( breaker, 'limit_size_in_bytes' )
            self.breaker_map[idx+1] = ( breaker, 'estimated_size_in_bytes' )
            self.breaker_map[idx+2] = ( breaker, 'overhead' )
            self.breaker_map[idx+3] = ( breaker, 'tripped' )
            idx += 4
        # 160 - 261 (17 x 6)
        self.thread_pool_map = {}
        for thread_pool in [ "generic", "index", "fetch_shard_store", "get", "snapshot", "merge", "suggest", "bulk", "optimize", "warmer", "flush", "search", "fetch_shard_started", "listener", "percolate", "management", "refresh" ]:
            metrics = [
                [ f'nodes.thread_pool.{thread_pool}.threads',   PM_TYPE_U32, PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),            '' ],
                [ f'nodes.thread_pool.{thread_pool}.queue',     PM_TYPE_U32, PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),            '' ],
                [ f'nodes.thread_pool.{thread_pool}.active',    PM_TYPE_U32, PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),            '' ],
                [ f'nodes.thread_pool.{thread_pool}.rejected',  PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), '' ],
                [ f'nodes.thread_pool.{thread_pool}.largest',   PM_TYPE_U32, PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),            '' ],
                [ f'nodes.thread_pool.{thread_pool}.completed', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), '' ],
            ]
            self.nodes_metrics.extend(metrics)
            self.thread_pool_map[idx]   = ( thread_pool, 'threads' )
            self.thread_pool_map[idx+1] = ( thread_pool, 'queue' )
            self.thread_pool_map[idx+2] = ( thread_pool, 'active' )
            self.thread_pool_map[idx+3] = ( thread_pool, 'rejected' )
            self.thread_pool_map[idx+4] = ( thread_pool, 'largest' )
            self.thread_pool_map[idx+5] = ( thread_pool, 'completed' )
            idx += 6

        for item, metric in enumerate(self.nodes_metrics):
            self.add_metric(name + '.' + metric[0],
                            pmdaMetric(self.pmid(self.nodes_cluster, item),
                                       metric[1], self.nodes_indom,
                                       metric[2], metric[3]),
                            metric[4], metric[4])
        self.node_info_indom = self.indom(1)
        self.node_info_insts = pmdaIndom(self.node_info_indom, {})
        self.add_indom(self.node_info_insts, 'elasticsearch cluster nodes')
        self.node_info_cluster = 2
        self.node_info_metrics = [
            # Name -                                  type -          semantics -      units -                           help
            [ 'nodes.jvm.pid',                        PM_TYPE_U32,    PM_SEM_DISCRETE, pmUnits(0,0,0,0,0,0),             '' ], # 0
            [ 'nodes.jvm.version',                    PM_TYPE_STRING, PM_SEM_INSTANT,  pmUnits(0,0,0,0,0,0),             '' ], # 1
            [ 'nodes.jvm.vm_name',                    PM_TYPE_STRING, PM_SEM_INSTANT,  pmUnits(0,0,0,0,0,0),             '' ], # 2
            [ 'nodes.jvm.vm_version',                 PM_TYPE_STRING, PM_SEM_INSTANT,  pmUnits(0,0,0,0,0,0),             '' ], # 3
            [ 'nodes.jvm.mem.heap_init_in_bytes',     PM_TYPE_U64,    PM_SEM_INSTANT,  pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 4
            [ 'nodes.jvm.mem.heap_max_in_bytes',      PM_TYPE_U64,    PM_SEM_INSTANT,  pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 5
            [ 'nodes.jvm.mem.non_heap_init_in_bytes', PM_TYPE_U64,    PM_SEM_INSTANT,  pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 6
            [ 'nodes.jvm.mem.non_heap_max_in_bytes',  PM_TYPE_U64,    PM_SEM_INSTANT,  pmUnits(1,0,0,PM_SPACE_BYTE,0,0), '' ], # 7
            [ 'nodes.process.max_file_descriptors',   PM_TYPE_U64,    PM_SEM_DISCRETE, pmUnits(0,0,0,0,0,0),             '' ], # 8
        ]
        for item, metric in enumerate(self.node_info_metrics):
            self.add_metric(name + '.' + metric[0],
                            pmdaMetric(self.pmid(self.node_info_cluster, item),
                                       metric[1], self.node_info_indom,
                                       metric[2], metric[3]),
                            metric[4], metric[4])

        self.version_indom = PM_INDOM_NULL
        self.version_cluster = 3
        self.version_metrics = [
            # Name -            type -          semantics -     units -               help
            [ 'version.number', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0), 'Version number of elasticsearch' ], # 0
        ]
        for item, metric in enumerate(self.version_metrics):
            self.add_metric(name + '.' + metric[0],
                            pmdaMetric(self.pmid(self.version_cluster, item),
                                       metric[1], self.version_indom,
                                       metric[2], metric[3]),
                            metric[4], metric[4])

        self.search_indom = PM_INDOM_NULL
        self.search_cluster = 4
        self.search_metrics = [
            # Name -                                              type -       semantics -     units -                          help
            [ 'search.shards.total',                              PM_TYPE_U64, PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),            'Number of shards in the elasticsearch cluster'                       ], #  0
            [ 'search.shards.successful',                         PM_TYPE_U64, PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),            'Number of successfully searched shards in the elasticsearch cluster' ], #  1
            [ 'search.shards.failed',                             PM_TYPE_U64, PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),            'Number of failed shards in searches in the elasticsearch cluster'    ], #  2
            [ 'search.all.primaries.search.query_total',          PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'Number of search queries to all primary shards'                      ], #  3
            [ 'search.all.primaries.search.query_time_in_millis', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,1,0,0,PM_TIME_MSEC,0), 'Time spent in search queries to all primary shards'                  ], #  4
            [ 'search.all.primaries.search.fetch_total',          PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'Number of search fetches to all primary shards'                      ], #  5
            [ 'search.all.total.search.query_total',              PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'Number of search queries to all index shards'                        ], #  6
            [ 'search.all.total.search.query_time_in_millis',     PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,1,0,0,PM_TIME_MSEC,0), 'Time spent in search queries to all index shards'                    ], #  7
            [ 'search.all.total.search.fetch_total',              PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'Number of search fetches to all index shards'                        ], #  8
            [ 'search.all.total.fetch_time_in_millis',            PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,1,0,0,PM_TIME_MSEC,0), 'Time spent in search fetches to all index shards (deprecated)'       ], #  9
            [ 'search.all.total.search.fetch_time_in_millis',     PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,1,0,0,PM_TIME_MSEC,0), 'Time spent in search fetches to all index shards'                    ], # 10
            [ 'search.all.primaries.search.fetch_time_in_millis', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,1,0,0,PM_TIME_MSEC,0), 'Time spent in search queries to all primary shards'                  ], # 11
        ]

        for item, metric in enumerate(self.search_metrics):
            self.add_metric(name + '.' + metric[0],
                            pmdaMetric(self.pmid(self.search_cluster, item),
                                       metric[1], self.search_indom,
                                       metric[2], metric[3]),
                            metric[4], metric[4])

        self.perindex_indom = self.indom(2)
        self.perindex_insts = pmdaIndom(self.perindex_indom, {})
        self.add_indom(self.perindex_insts, 'elasticsearch indices')
        self.perindex_cluster = 5
        self.perindex_metrics = [
            # Name -                                                   type -       semantics -     units -                          help
            [ 'search.perindex.primaries.search.query_total',          PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'Number of search queries to all index primary shards'         ], #  0
            [ 'search.perindex.primaries.search.query_time_in_millis', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,1,0,0,PM_TIME_MSEC,0), 'Time spent in search queries to all index primary shards'     ], #  1
            [ 'search.perindex.primaries.search.fetch_total',          PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'Number of search fetches to all index primary shards'         ], #  2
            [ 'search.perindex.primaries.search.fetch_time_in_millis', PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,1,0,0,PM_TIME_MSEC,0), 'Time spent in search fetches to all index primary shards'     ], #  3
            [ 'search.perindex.total.search.query_total',              PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'Number of search queries to all index shards'                 ], #  4
            [ 'search.perindex.total.search.query_time_in_millis',     PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,1,0,0,PM_TIME_MSEC,0), 'Time spent in search queries to all index shards'             ], #  5
            [ 'search.perindex.total.search.fetch_total',              PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,0,1,0,0,PM_COUNT_ONE), 'Number of search fetches to all index shards'                 ], #  6
            [ 'search.perindex.total.search.fetch_time_in_millis',     PM_TYPE_U64, PM_SEM_COUNTER, pmUnits(0,1,0,0,PM_TIME_MSEC,0), 'Time spent in search fetches to all index shards'             ], #  7
            [ 'search.perindex.primaries.search.open_contexts',        PM_TYPE_U64, PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),            'Current number of open search contexts for primary shards'    ], #  8
            [ 'search.perindex.primaries.search.query_current',        PM_TYPE_U64, PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),            'Current number of search queries to all index primary shards' ], #  9
            [ 'search.perindex.primaries.search.fetch_current',        PM_TYPE_U64, PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),            'Current number of search fetches to all index primary shards' ], # 10
            [ 'search.perindex.total.search.open_contexts',            PM_TYPE_U64, PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),            'Current number of open search contexts for all index shards'  ], # 11
            [ 'search.perindex.total.search.query_current',            PM_TYPE_U64, PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),            'Current number of search queries to all index shards'         ], # 12
            [ 'search.perindex.total.search.fetch_current',            PM_TYPE_U64, PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0),            'Current number of search fetches to all index shards'         ], # 13
        ]
        for item, metric in enumerate(self.perindex_metrics):
            self.add_metric(name + '.' + metric[0],
                            pmdaMetric(self.pmid(self.perindex_cluster, item),
                                       metric[1], self.perindex_indom,
                                       metric[2], metric[3]),
                            metric[4], metric[4])

        self.master_node_indom = PM_INDOM_NULL
        self.master_node_cluster = 6
        self.master_node_metrics = [
            # Name -                 type -          semantics -     units -               help
            [ 'cluster.master_node', PM_TYPE_STRING, PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0), 'Internal identifier of the master node of the cluster' ], # 0
        ]
        for item, metric in enumerate(self.master_node_metrics):
            self.add_metric(name + '.' + metric[0],
                            pmdaMetric(self.pmid(self.master_node_cluster, item),
                                       metric[1], self.master_node_indom,
                                       metric[2], metric[3]),
                            metric[4], metric[4])

        self.index_indom = self.indom(3)
        self.index_insts = pmdaIndom(self.index_indom, {})
        self.add_indom(self.index_insts, 'elasticsearch indices')
        self.index_cluster = 7
        self.index_metrics = [
            # Name -                                      type -       semantics -     units -               help
            [ 'index.settings.gateway_snapshot_interval', PM_TYPE_U64, PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0), 'Interval between gateway snapshots (deprecated)'          ], # 0
            [ 'index.settings.number_of_replicas',        PM_TYPE_U64, PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0), 'Number of replicas of shards index setting'               ], # 1
            [ 'index.settings.number_of_shards',          PM_TYPE_U64, PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0), 'Number of shards index setting'                           ], # 2
            [ 'index.settings.version_created',           PM_TYPE_U64, PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0), 'The version stamp at time of index creation (deprecated)' ], # 3
            [ 'index.settings.gateway.snapshot_interval', PM_TYPE_U64, PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0), 'Interval between gateway snapshots'                       ], # 4
            [ 'index.settings.version.created',           PM_TYPE_U64, PM_SEM_INSTANT, pmUnits(0,0,0,0,0,0), 'The version stamp at time of index creation'              ], # 5
        ]
        for item, metric in enumerate(self.index_metrics):
            self.add_metric(name + '.' + metric[0],
                            pmdaMetric(self.pmid(self.index_cluster, item),
                                       metric[1], self.index_indom,
                                       metric[2], metric[3]),
                            metric[4], metric[4])
        self.set_refresh(self.elasticsearch_refresh)
        self.set_fetch_callback(self.elasticsearch_fetch_callback)

    def read_config(self):
        """ Read Configuration file """

        configdir = PCP.pmGetConfig('PCP_PMDAS_DIR')
        configdir += '/' + self.read_name()
        # Retain es.conf name from original version of the PMDA
        configfile = configdir + '/' + 'es.conf'
        if not os.path.exists(configfile):
            configfile = configdir + '/' + 'elasticsearch.conf'
        # 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(configfile)
        if config.has_section('pmda'):
            for opt in config.options('pmda'):
                if opt == 'user':
                    self.user = config.get('pmda', opt)
                elif opt == 'baseurl':
                    self.baseurl = config.get('pmda', opt)
                elif opt == 'auth':
                    self.auth = config.get('pmda', opt)
                elif opt == 'password':
                    self.password = config.get('pmda', opt)
                else:
                    self.log(f"Ignoring directive '{opt}' in {configfile}.")

    def get_url(self, url):
        """ Perform HTTP GET """
        # self.log(f"url: {url}")
        req = None
        try:
            req = self.request.urlopen(url)
            self.error = False
        except (BaseException, Exception):
            if not self.error:
                self.log(f"Failed to get URL {url}")
            self.error = True
        if not req:
            if self.cluster:
                self.cluster = {}
            if self.nodes:
                self.nodes = {}
            if self.node_info:
                self.node_info = {}
            if self.info:
                self.info = {}
            if self.stat:
                self.stat = {}
            if self.search:
                self.search = {}
            if self.perindex:
                self.perindex = {}
            if self.master_node:
                self.master_node = None
            if self.index:
                self.index = {}
        return req

    def elasticsearch_refresh(self, cluster):
        """ Refresh """
        if cluster == self.cluster_cluster:
            request = self.get_url(self.baseurl + "_cluster/health")
            if request:
                self.cluster = json.loads(request.read())
                request.close()
            return

        if cluster == self.nodes_cluster:
            temp = {}
            insts = {}
            request = self.get_url(self.baseurl + "_nodes/stats")
            if request:
                temp = json.loads(request.read())
                request.close()
            try:
                self.nodes = {}
                for _id, stats in temp['nodes'].items():
                    name = stats['name']
                    insts[name] = c_int(1)
                    self.nodes[name] = stats
            except KeyError:
                pass
            self.nodes_insts.set_instances(self.nodes_indom, insts)
            self.replace_indom(self.nodes_indom, insts)
            return

        if cluster in (self.node_info_cluster, self.master_node_cluster):
            temp = {}
            insts = {}
            request = self.get_url(self.baseurl + "_nodes")
            if request:
                temp = json.loads(request.read())
                request.close()
            if cluster == self.master_node_cluster:
                self.master_node = None
                try:
                    for _id, info in temp['nodes'].items():
                        if info['attributes']['master'] == "true":
                            self.master_node = str(info['name'])
                except KeyError:
                    pass
            else:
                try:
                    self.node_info = {}
                    for _id, info in temp['nodes'].items():
                        name = info['name']
                        insts[name] = c_int(1)
                        self.node_info[name] = info
                except KeyError:
                    pass
                self.node_info_insts.set_instances(self.node_info_indom, insts)
                self.replace_indom(self.node_info_indom, insts)
            return

        if cluster == self.version_cluster:
            request = self.get_url(self.baseurl)
            if request:
                self.info = json.loads(request.read())
                request.close()
            return

        if cluster in (self.search_cluster, self.perindex_cluster):
            temp = {}
            insts = {}
            request = self.get_url(self.baseurl + "_stats/search")
            if request:
                temp = json.loads(request.read())
                request.close()
            if cluster == self.search_cluster:
                self.search = temp
            else:
                try:
                    self.perindex = temp['indices']
                    for index in temp['indices']:
                        insts[index] = c_int(1)
                except KeyError:
                    pass
                self.perindex_insts.set_instances(self.perindex_indom, insts)
                self.replace_indom(self.perindex_indom, insts)
            return

        if cluster == self.index_cluster:
            temp = {}
            insts = {}
            request = self.get_url(self.baseurl + "_cluster/state/metadata")
            if request:
                temp = json.loads(request.read())
                request.close()
            try:
                self.index = temp['metadata']['indices']
                for index in temp['metadata']['indices']:
                    insts[index] = c_int(1)
            except KeyError:
                pass
            self.index_insts.set_instances(self.index_indom, insts)
            self.replace_indom(self.index_indom, insts)

    def elasticsearch_fetch_callback(self, cluster, item, inst):
        """ Fetch Callback """
        if cluster == self.cluster_cluster:
            if not self.cluster:
                return [PMDA_FETCH_NOVALUES, 0]
            try:
                if item == 0:
                    return [str(self.cluster['cluster_name']), 1]
                if item == 1:
                    return [str(self.cluster['status']), 1]
                if item == 2:
                    return [self.cluster['timed_out'], 1]
                if item == 3:
                    return [self.cluster['number_of_nodes'], 1]
                if item == 4:
                    return [self.cluster['number_of_data_nodes'], 1]
                if item == 5:
                    return [self.cluster['active_primary_shards'], 1]
                if item == 6:
                    return [self.cluster['active_shards'], 1]
                if item == 7:
                    return [self.cluster['relocating_shards'], 1]
                if item == 8:
                    return [self.cluster['initializing_shards'], 1]
                if item == 9:
                    return [self.cluster['unassigned_shards'], 1]
                if item == 10:
                    if self.cluster['status'] == 'green':
                        return [0, 1]
                    if self.cluster['status'] == 'yellow':
                        return [1, 1]
                    if self.cluster['status'] == 'red':
                        return [2, 1]
            except KeyError:
                return [PM_ERR_APPVERSION, 0]

        elif cluster == self.nodes_cluster:
            if not self.nodes:
                return [PMDA_FETCH_NOVALUES, 0]
            try:
                node = self.nodes_insts.inst_name_lookup(inst)
                data = self.nodes[node]
                if item in (0, 47):
                    return [data['indices']['store']['size_in_bytes'], 1]
                if item == 1:
                    return [data['indices']['docs']['count'], 1]
                if item == 2:
                    # The 'nodes.indices.docs.num_docs', item #2, does not
                    # exist in the '_nodes/stats' API JSON payload
                    # (Elasticsearch V1 or V7).
                    #  [data['indices']['docs']['num_docs'], 1]
                    return [PM_ERR_APPVERSION, 0]
                if item in (3, 4, 5, 6, 7):
                    # The 'nodes.indices.cache.field_evictions',
                    # '.field_size_in_bytes', '.filter_count',
                    # '.filter_evictions', and '.filter_size_in_bytes', items
                    # #3 thru #7, do not exist in the '_nodes/stats' API JSON
                    # payload (Elasticsearch V1 or V7).
                    #   [data['indices']['cache']['field_evictions'], 1]
                    #   [data['indices']['cache']['field_size_in_bytes'], 1]
                    #   [data['indices']['cache']['filter_count'], 1]
                    #   [data['indices']['cache']['filter_evictions'], 1]
                    #   [data['indices']['cache']['filter_size_in_bytes'], 1]
                    return [PM_ERR_APPVERSION, 0]
                if item == 8:
                    return [data['indices']['merges']['current'], 1]
                if item == 9:
                    return [data['indices']['merges']['total'], 1]
                if item == 10:
                    return [data['indices']['merges']['total_time_in_millis'], 1]
                if item == 11:
                    return [data['jvm']['uptime_in_millis'], 1]
                if item == 12:
                    # The 'nodes.jvm.uptime', item #12, does not exist in
                    # the '_nodes/stats' API JSON payload (Elasticsearch V1 or V7).
                    return [PM_ERR_APPVERSION, 0]
                if item == 13:
                    return [data['jvm']['mem']['heap_used_in_bytes'], 1]
                if item == 14:
                    return [data['jvm']['mem']['heap_committed_in_bytes'], 1]
                if item == 15:
                    return [data['jvm']['mem']['non_heap_used_in_bytes'], 1]
                if item == 16:
                    return [data['jvm']['mem']['non_heap_committed_in_bytes'], 1]
                if item == 17:
                    return [data['jvm']['threads']['count'], 1]
                if item == 18:
                    return [data['jvm']['threads']['peak_count'], 1]
                if item in (19, 20, 21, 22, 23, 24, 25, 26):
                    # The 'nodes.jvm.gc.collection_count',
                    # '.collection_time_in_millis',
                    # '.collectors.{Copy|ParNew|ConcurrentMarkSweep}.collection_count',
                    # and
                    # '.collectors.{Copy|ParNew|ConcurrentMarkSweep}.collection_time_in_millis',
                    # items #19 thru #26, do not exist in the '_nodes/stats'
                    # API JSON payload (Elasticsearch V1 or V7).
                    #   [data['jvm']['gc']['collection_count'], 1]
                    #   [data['jvm']['gc']['collection_time_in_millis'], 1]
                    #   [data['jvm']['gc']['collectors']['Copy']['collection_count'], 1]
                    #   [data['jvm']['gc']['collectors']['Copy']['collection_time_in_millis'], 1]
                    #   [data['jvm']['gc']['collectors']['ParNew']['collection_count'], 1]
                    #   [data['jvm']['gc']['collectors']['ParNew']['collection_time_in_millis'], 1]
                    #   [data['jvm']['gc']['collectors']['ConcurrentMarkSweep']['collection_count'], 1]
                    #   [data['jvm']['gc']['collectors']['ConcurrentMarkSweep']['collection_time_in_millis'], 1]
                    return [PM_ERR_APPVERSION, 0]
                if item == 27:
                    return [data['indices']['docs']['deleted'], 1]
                if item == 28:
                    return [data['indices']['indexing']['index_total'], 1]
                if item == 29:
                    return [data['indices']['indexing']['index_time_in_millis'], 1]
                if item == 30:
                    return [data['indices']['indexing']['delete_total'], 1]
                if item == 31:
                    return [data['indices']['indexing']['delete_time_in_millis'], 1]
                if item == 32:
                    return [data['indices']['merges']['current_docs'], 1]
                if item == 33:
                    return [data['indices']['merges']['current_size_in_bytes'], 1]
                if item == 34:
                    return [data['indices']['merges']['total_docs'], 1]
                if item == 35:
                    return [data['indices']['merges']['total_size_in_bytes'], 1]
                if item == 36:
                    return [data['indices']['refresh']['total'], 1]
                if item == 37:
                    return [data['indices']['refresh']['total_time_in_millis'], 1]
                if item == 38:
                    return [data['indices']['flush']['total'], 1]
                if item == 39:
                    return [data['indices']['flush']['total_time_in_millis'], 1]
                if item == 40:
                    # The '_nodes/stats' JSON payload contains "timestamp"
                    # fields for each sub-section report marking the time at
                    # which each of those fields was actually gathered before
                    # placing into the JSON payload. This is not a usable metric
                    # and was never setup properly.
                    #
                    #   [data['process']['timestamp'], 1]
                    return [PM_ERR_APPVERSION, 0]
                if item == 41:
                    return [data['process']['open_file_descriptors'], 1]
                if item == 42:
                    return [data['process']['cpu']['percent'], 1]
                if item == 43:
                    return [data['process']['cpu']['sys_in_millis'], 1]
                if item == 44:
                    return [data['process']['cpu']['user_in_millis'], 1]
                if item == 45:
                    return [data['process']['mem']['resident_in_bytes'], 1]
                if item == 46:
                    return [data['process']['mem']['total_virtual_in_bytes'], 1]
                # Item 47 is the same as item 0.
                if item == 48:
                    return [data['indices']['get']['total'], 1]
                if item == 49:
                    return [data['indices']['get']['time_in_millis'], 1]
                if item == 50:
                    return [data['indices']['get']['exists_total'], 1]
                if item == 51:
                    return [data['indices']['get']['exists_time_in_millis'], 1]
                if item == 52:
                    return [data['indices']['get']['missing_total'], 1]
                if item == 53:
                    return [data['indices']['get']['missing_time_in_millis'], 1]
                if item == 54:
                    return [data['indices']['search']['query_total'], 1]
                if item == 55:
                    return [data['indices']['search']['query_time_in_millis'], 1]
                if item == 56:
                    return [data['indices']['search']['fetch_total'], 1]
                if item == 57:
                    return [data['indices']['search']['fetch_time_in_millis'], 1]
                if item == 58:
                    return [data['transport']['server_open'], 1]
                if item == 59:
                    return [data['transport']['rx_count'], 1]
                if item == 60:
                    return [data['transport']['rx_size_in_bytes'], 1]
                if item == 61:
                    return [data['transport']['tx_count'], 1]
                if item == 62:
                    return [data['transport']['tx_size_in_bytes'], 1]
                if item == 63:
                    return [data['http']['current_open'], 1]
                if item == 64:
                    return [data['http']['total_opened'], 1]
                if item == 65:
                    return [data['jvm']['mem']['pools']['old']['max_in_bytes'], 1]
                if item == 66:
                    return [data['jvm']['mem']['pools']['old']['peak_max_in_bytes'], 1]
                if item == 67:
                    return [data['jvm']['mem']['pools']['old']['peak_used_in_bytes'], 1]
                if item == 68:
                    return [data['jvm']['mem']['pools']['old']['used_in_bytes'], 1]
                if item == 69:
                    return [data['jvm']['mem']['pools']['survivor']['max_in_bytes'], 1]
                if item == 70:
                    return [data['jvm']['mem']['pools']['survivor']['peak_max_in_bytes'], 1]
                if item == 71:
                    return [data['jvm']['mem']['pools']['survivor']['peak_used_in_bytes'], 1]
                if item == 72:
                    return [data['jvm']['mem']['pools']['survivor']['used_in_bytes'], 1]
                if item == 73:
                    return [data['jvm']['mem']['pools']['young']['max_in_bytes'], 1]
                if item == 74:
                    return [data['jvm']['mem']['pools']['young']['peak_max_in_bytes'], 1]
                if item == 75:
                    return [data['jvm']['mem']['pools']['young']['peak_used_in_bytes'], 1]
                if item == 76:
                    return [data['jvm']['mem']['pools']['young']['used_in_bytes'], 1]
                if item == 77:
                    return [data['jvm']['gc']['collectors']['old']['collection_count'], 1]
                if item == 78:
                    return [data['jvm']['gc']['collectors']['old']['collection_time_in_millis'], 1]
                if item == 79:
                    return [data['jvm']['gc']['collectors']['young']['collection_count'], 1]
                if item == 80:
                    return [data['jvm']['gc']['collectors']['young']['collection_time_in_millis'], 1]
                if item == 81:
                    return [data['jvm']['buffer_pools']['direct']['count'], 1]
                if item == 82:
                    return [data['jvm']['buffer_pools']['direct']['used_in_bytes'], 1]
                if item == 83:
                    return [data['jvm']['buffer_pools']['direct']['total_capacity_in_bytes'], 1]
                if item == 84:
                    return [data['jvm']['buffer_pools']['mapped']['count'], 1]
                if item == 85:
                    return [data['jvm']['buffer_pools']['mapped']['used_in_bytes'], 1]
                if item == 86:
                    return [data['jvm']['buffer_pools']['mapped']['total_capacity_in_bytes'], 1]
                if item == 87:
                    return [data['indices']['search']['query_current'], 1]
                if item == 88:
                    return [data['indices']['search']['fetch_current'], 1]
                if item == 89:
                    return [data['indices']['search']['open_contexts'], 1]
                if item == 90:
                    return [data['indices']['get']['current'], 1]
                if item == 91:
                    return [data['indices']['indexing']['index_current'], 1]
                if item == 92:
                    return [data['indices']['indexing']['delete_current'], 1]
                if item == 93:
                    return [data['indices']['indexing']['noop_update_total'], 1]
                if item == 94:
                    return [data['indices']['indexing']['is_throttled'], 1]
                if item == 95:
                    return [data['indices']['indexing']['throttle_time_in_millis'], 1]
                if item == 96:
                    return [data['indices']['store']['throttle_time_in_millis'], 1]
                if item == 97:
                    return [data['indices']['warmer']['current'], 1]
                if item == 98:
                    return [data['indices']['warmer']['total'], 1]
                if item == 99:
                    return [data['indices']['warmer']['total_time_in_millis'], 1]
                if item == 100:
                    return [data['indices']['filter_cache']['memory_size_in_bytes'], 1]
                if item == 101:
                    return [data['indices']['filter_cache']['evictions'], 1]
                if item == 102:
                    return [data['indices']['id_cache']['memory_size_in_bytes'], 1]
                if item == 103:
                    return [data['indices']['fielddata']['memory_size_in_bytes'], 1]
                if item == 104:
                    return [data['indices']['fielddata']['evictions'], 1]
                if item == 105:
                    return [data['indices']['percolate']['total'], 1]
                if item == 106:
                    return [data['indices']['percolate']['time_in_millis'], 1]
                if item == 107:
                    return [data['indices']['percolate']['current'], 1]
                if item == 108:
                    return [data['indices']['percolate']['memory_size_in_bytes'], 1]
                if item == 109:
                    return [data['indices']['percolate']['queries'], 1]
                if item == 110:
                    return [data['indices']['completion']['size_in_bytes'], 1]
                if item == 111:
                    return [data['indices']['segments']['count'], 1]
                if item == 112:
                    return [data['indices']['segments']['memory_in_bytes'], 1]
                if item == 113:
                    return [data['indices']['segments']['index_writer_memory_in_bytes'], 1]
                if item == 114:
                    return [data['indices']['segments']['index_writer_max_memory_in_bytes'], 1]
                if item == 115:
                    return [data['indices']['segments']['version_map_memory_in_bytes'], 1]
                if item == 116:
                    return [data['indices']['segments']['fixed_bit_set_memory_in_bytes'], 1]
                if item == 117:
                    return [data['indices']['translog']['operations'], 1]
                if item == 118:
                    return [data['indices']['translog']['size_in_bytes'], 1]
                if item == 119:
                    return [data['indices']['suggest']['total'], 1]
                if item == 120:
                    return [data['indices']['suggest']['time_in_millis'], 1]
                if item == 121:
                    return [data['indices']['suggest']['current'], 1]
                if item == 122:
                    return [data['indices']['query_cache']['memory_size_in_bytes'], 1]
                if item == 123:
                    return [data['indices']['query_cache']['evictions'], 1]
                if item == 124:
                    return [data['indices']['query_cache']['hit_count'], 1]
                if item == 125:
                    return [data['indices']['query_cache']['miss_count'], 1]
                if item == 126:
                    return [data['indices']['recovery']['current_as_source'], 1]
                if item == 127:
                    return [data['indices']['recovery']['current_as_target'], 1]
                if item == 128:
                    return [data['indices']['recovery']['throttle_time_in_millis'], 1]
                if item in self.network_map:
                    metric = self.network_map[item]
                    return [data['network']['tcp'][metric], 1]
                if item in self.fs_map:
                    metric = self.fs_map[item]
                    return [data['fs']['total'][metric], 1]
                if item in self.breaker_map:
                    breaker, metric = self.breaker_map[item]
                    return [data['breakers'][breaker][metric], 1]
                if item in self.thread_pool_map:
                    thread_pool, metric = self.thread_pool_map[item]
                    return [data['thread_pool'][thread_pool][metric], 1]
            except KeyError:
                return [PM_ERR_APPVERSION, 0]

        elif cluster == self.node_info_cluster:
            if not self.node_info:
                return [PMDA_FETCH_NOVALUES, 0]
            try:
                node = self.node_info_insts.inst_name_lookup(inst)
                data = self.node_info[node]
                if item == 0:
                    return [data['jvm']['pid'], 1]
                if item == 1:
                    return [str(data['jvm']['version']), 1]
                if item == 2:
                    return [str(data['jvm']['vm_name']), 1]
                if item == 3:
                    return [str(data['jvm']['vm_version']), 1]
                if item == 4:
                    return [data['jvm']['mem']['heap_init_in_bytes'], 1]
                if item == 5:
                    return [data['jvm']['mem']['heap_max_in_bytes'], 1]
                if item == 6:
                    return [data['jvm']['mem']['non_heap_init_in_bytes'], 1]
                if item == 7:
                    return [data['jvm']['mem']['non_heap_max_in_bytes'], 1]
                if item == 8:
                    return [data['process']['max_file_descriptors'], 1]
            except KeyError:
                return [PM_ERR_APPVERSION, 0]

        elif cluster == self.version_cluster:
            if not self.info:
                return [PMDA_FETCH_NOVALUES, 0]
            try:
                if item == 0:
                    return [str(self.info['version']['number']), 1]
            except KeyError:
                return [PM_ERR_APPVERSION, 0]

        elif cluster == self.search_cluster:
            if not self.search:
                return [PMDA_FETCH_NOVALUES, 0]
            try:
                if item == 0:
                    return [self.search['_shards']['total'], 1]
                if item == 1:
                    return [self.search['_shards']['successful'], 1]
                if item == 2:
                    return [self.search['_shards']['failed'], 1]
                if item == 3:
                    return [self.search['_all']['primaries']['search']['query_total'], 1]
                if item == 4:
                    return [self.search['_all']['primaries']['search']['query_time_in_millis'], 1]
                if item == 5:
                    return [self.search['_all']['primaries']['search']['fetch_total'], 1]
                if item == 6:
                    return [self.search['_all']['total']['search']['query_total'], 1]
                if item == 7:
                    return [self.search['_all']['total']['search']['query_time_in_millis'], 1]
                if item == 8:
                    return [self.search['_all']['total']['search']['fetch_total'], 1]
                if item in (9, 10):
                    return [self.search['_all']['total']['search']['fetch_time_in_millis'], 1]
                if item == 11:
                    return [self.search['_all']['primaries']['search']['fetch_time_in_millis'], 1]
            except KeyError:
                return [PM_ERR_APPVERSION, 0]

        elif cluster == self.perindex_cluster:
            if not self.search:
                return [PMDA_FETCH_NOVALUES, 0]
            try:
                index = self.perindex_insts.inst_name_lookup(inst)
                data = self.perindex[index]
                if item == 0:
                    return [data['primaries']['search']['query_total'], 1]
                if item == 1:
                    return [data['primaries']['search']['query_time_in_millis'], 1]
                if item == 2:
                    return [data['primaries']['search']['fetch_total'], 1]
                if item == 3:
                    return [data['primaries']['search']['fetch_time_in_millis'], 1]
                if item == 4:
                    return [data['total']['search']['query_total'], 1]
                if item == 5:
                    return [data['total']['search']['query_time_in_millis'], 1]
                if item == 6:
                    return [data['total']['search']['fetch_total'], 1]
                if item == 7:
                    return [data['total']['search']['fetch_time_in_millis'], 1]
                if item == 8:
                    return [data['primaries']['search']['open_contexts'], 1]
                if item == 9:
                    return [data['primaries']['search']['query_current'], 1]
                if item == 10:
                    return [data['primaries']['search']['fetch_current'], 1]
                if item == 11:
                    return [data['total']['search']['open_contexts'], 1]
                if item == 12:
                    return [data['total']['search']['query_current'], 1]
                if item == 13:
                    return [data['total']['search']['fetch_current'], 1]
            except KeyError:
                return [PM_ERR_APPVERSION, 0]

        elif cluster == self.master_node_cluster:
            if not self.master_node:
                return [PMDA_FETCH_NOVALUES, 0]
            if item == 0:
                return [self.master_node, 1]

        elif cluster == self.index_cluster:
            if not self.index:
                return [PMDA_FETCH_NOVALUES, 0]
            try:
                index = self.index_insts.inst_name_lookup(inst)
                data = self.index[index]['settings']['index']
                if item in (0, 4):
                    return [int(data['gateway']['snapshot_interval']), 1]
                if item == 1:
                    return [int(data['number_of_replicas']), 1]
                if item == 2:
                    return [int(data['number_of_shards']), 1]
                if item in (3, 5):
                    return [int(data['version']['created']), 1]
            except KeyError:
                return [PM_ERR_APPVERSION, 0]

        return [PM_ERR_PMID, 0]


if __name__ == '__main__':
    ElasticsearchPMDA('elasticsearch', 108).run()
