#!/usr/bin/python
# -*- encoding: utf-8; py-indent-offset: 4 -*-
# +------------------------------------------------------------------+
# |             ____ _               _        __  __ _  __           |
# |            / ___| |__   ___  ___| | __   |  \/  | |/ /           |
# |           | |   | '_ \ / _ \/ __| |/ /   | |\/| | ' /            |
# |           | |___| | | |  __/ (__|   <    | |  | | . \            |
# |            \____|_| |_|\___|\___|_|\_\___|_|  |_|_|\_\           |
# |                                                                  |
# | Copyright Mathias Kettner 2014             mk@mathias-kettner.de |
# +------------------------------------------------------------------+
#
# This file is part of Check_MK.
# The official homepage is at http://mathias-kettner.de/check_mk.
#
# check_mk 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 in version 2.  check_mk is  distributed
# in the hope that it will be useful, but WITHOUT ANY WARRANTY;  with-
# out even the implied warranty of  MERCHANTABILITY  or  FITNESS FOR A
# PARTICULAR PURPOSE. See the  GNU General Public License for more de-
# tails.  You should have  received  a copy of the  GNU  General Public
# License along with GNU Make; see the file  COPYING.  If  not,  write
# to the Free Software Foundation, Inc., 51 Franklin St,  Fifth Floor,
# Boston, MA 02110-1301 USA.


# Old comments:
# Strange: Channel IDs seem to be not unique. But the second
# usage has '0' in the docsIfUpChannelFrequency...

# Info might look different: on the one hand the channel id is the connector
# on the other hand the OID. In some cases the channel id is not unique:

# [[[u'4', u'3', u'38000000']], [[u'3', u'541092', u'36', u'6', u'498']], []]

# [[[u'1337', u'1', u'20000000'],
#  [u'1338', u'2', u'32000000'],
#  [u'1339', u'3', u'38000000'],
#  [u'1361', u'1', u'0'],
#  [u'1362', u'2', u'0'],
#  [u'1363', u'3', u'0'],
#  [u'1364', u'4', u'0']],
# [[u'1337', u'2262114535', u'322661943', u'406110', u'293'],
#  [u'1338', u'2567058620', u'5306417', u'78105', u'328'],
#  [u'1339', u'4222307191', u'4132447', u'19600', u'339'],
#  [u'1361', u'0', u'0', u'0', u'0'],
#  [u'1362', u'0', u'0', u'0', u'0'],
#  [u'1363', u'0', u'0', u'0', u'0'],
#  [u'1364', u'0', u'0', u'0', u'0']],
# [[u'1337', u'9', u'9', u'9', u'5'],
#  [u'1338', u'10', u'10', u'10', u'61'],
#  [u'1339', u'10', u'10', u'10', u'4'],
#  [u'1361', u'0', u'0', u'0', u'0'],
#  [u'1362', u'0', u'0', u'0', u'0'],
#  [u'1363', u'0', u'0', u'0', u'0'],
#  [u'1364', u'0', u'0', u'0', u'0']]]

# [[[u'4', u'3', u'32400000'],
#  [u'80', u'1', u'25200000'],
#  [u'81', u'2', u'27600000'],
#  [u'82', u'4', u'38800000']],
# [[u'3', u'104052489', u'22364', u'23308', u'389']],
# []]


factory_settings["docsis_channels_upstream_default_levels"] = {
    "signal_noise"   : ( 10.0, 5.0 ), # dB
    "corrected"      : ( 5.0, 8.0 ), # Percent
    "uncorrectable"  : ( 1.0, 2.0 ), # Percent
}


def parse_docsis_channels_upstream(info):
    freq_info      = info[0]
    freq_info_dict = dict([ (x[0], x[1:]) for x in freq_info ])
    sig_info_dict  = dict([ (x[0], x[1:]) for x in info[1] ])
    cm_info_dict   = dict([ (x[0], x[1:]) for x in info[2] ])

    parsed = {}
    for endoid, (cid, freq_str) in freq_info_dict.items():
        unique_name = (len(freq_info) == len(set(map(lambda x: x[1], freq_info)))) and \
                       cid or "%s.%s" % (endoid, cid)

        data = []
        if endoid in sig_info_dict:
            data = sig_info_dict[endoid] + cm_info_dict.get(endoid, [])
        elif cid in sig_info_dict:
            data = sig_info_dict[cid] + cm_info_dict.get(cid, [])

        if data:
            parsed[unique_name] = [float(freq_str)] + data

    return parsed


def inventory_docsis_channels_upstream(parsed):
    for unique_name, entry in parsed.items():
        if entry[0] != '0' and entry[4] != '0':
            yield unique_name, {}


def check_docsis_channels_upstream(item, params, parsed):
    if item in parsed:
        entry = parsed[item]
        mhz, unerroreds, correcteds, uncorrectables, signal_noise = entry[:5]

        # Signal Noise
        noise_db   = float(signal_noise) / 10
        infotext   = "Signal/Noise ratio: %.2f dB" % noise_db
        warn, crit = params['signal_noise']

        state = 0
        if noise_db < crit:
            state = 2
        elif noise_db < warn:
            state = 1

        if state:
            infotext += " (warn/crit below %.1f/%.1f dB)" % (warn, crit)

        yield state, infotext, [ ('signal_noise', noise_db, warn, crit ) ]

        fields = [( "frequency", float(mhz) / 1000000, "Frequency", "%.2f", " MHz" )]
        if len(entry) >= 6:
            total, active, registered, avg_util = entry[5:9]
            fields += [
                ( "total",      int(total),      "Modems total",         "%d", "" ),
                ( "active",     int(active),     "Active",               "%d", "" ),
                ( "registered", int(registered), "Registered",           "%d", "" ),
                ( "util",       int(avg_util),   "Aaverage utilization", "%d", "%" )]

        for varname, value, title, form, unit in fields:
            yield 0, title + ": " + (form + "%s") % (value, unit), [ (varname, value) ]

        # Handle codewords. These are counters.
        now   = time.time()
        rates = {}
        total_rate = 0.0
        for what, counter in [
            ( "unerrored",     int(unerroreds),   ),
            ( "corrected",     int(correcteds),   ),
            ( "uncorrectable", int(uncorrectables))]:
            rate = get_rate("docsis_channels_upstream.%s.%s" % (item, what), now, counter)
            rates[what] = rate
            total_rate += rate

        if total_rate:
            for what, title in [
                ( "corrected",     "corrected errors",     ),
                ( "uncorrectable", "uncorrectable errors", )]:
                ratio      = rates[what] / total_rate
                perc       = 100.0 * ratio
                warn, crit = params[what]
                infotext   = "%s %s" % (get_percent_human_readable(perc), title)

                if perc >= crit:
                    state = 2
                elif perc >= crit:
                    state = 1

                if state:
                    infotext += " (warn/crit at %.1f/%.1f%%)" % (warn, crit)

                yield state, infotext, [ ("codewords_" + what, ratio, warn/100.0, crit/100.0) ]


check_info["docsis_channels_upstream"] = {
    "parse_function"          : parse_docsis_channels_upstream,
    "inventory_function"      : inventory_docsis_channels_upstream,
    "check_function"          : check_docsis_channels_upstream,
    "service_description"     : "Upstream Channel %s",
    "has_perfdata"            : True,
    "snmp_scan_function"      : docsis_scan_function,
    "snmp_info"               : [( ".1.3.6.1.2.1.10.127.1.1.2.1", [
                                        OID_END,
                                        "1",    # DOCS-IF-MIB::docsIfUpChannelId
                                        "2",    # DOCS-IF-MIB::docsIfUpChannelFrequency
                                 ]),
                                 ( ".1.3.6.1.2.1.10.127.1.1.4.1", [
                                        OID_END,
                                        "2",    # DOCS-IF-MIB::docsIfSigQUnerroreds:
                                                # "Codewords received on this channel without error.
                                                # This includes all codewords, whether or not they
                                                # were part of frames destined for this device."

                                        "3",    # DOCS-IF-MIB::docsIfSigQCorrecteds:
                                                # "Codewords received on this channel with correctable
                                                # errors. This includes all codewords, whether or not
                                                # they were part of frames destined for this device."

                                        "4",    # DOCS-IF-MIB::docsIfSigQUncorrectables:
                                                # "Codewords received on this channel with uncorrectable
                                                # errors. This includes all codewords, whether or not
                                                # they were part of frames destined for this device."

                                        "5",    # DOCS-IF-MIB::docsIfSigQSignalNoise
                                 ]),
                                 ( ".1.3.6.1.4.1.9.9.116.1.4.1.1" , [
                                        OID_END,
                                        "3",    # CISCO-DOCS-EXT-MIB::cdxIfUpChannelCmTotal
                                        "4",    # CISCO-DOCS-EXT-MIB::cdxIfUpChannelCmActive
                                        "5",    # CISCO-DOCS-EXT-MIB::cdxIfUpChannelCmRegistered
                                        "7",    # CISCO-DOCS-EXT-MIB::cdxIfUpChannelAvgUtil
                                ])],
    "default_levels_variable" : "docsis_channels_upstream_default_levels",
    "group"                   : "docsis_channels_upstream",
    "includes"                : [ "docsis.include" ],
}
