// SPDX-License-Identifier: GPL-3.0-or-later
/*
 *  netdata freeipmi.plugin
 *  Copyright (C) 2017 Costa Tsaousis
 *  GPL v3+
 *
 *  Based on:
 *  ipmimonitoring-sensors.c,v 1.51 2016/11/02 23:46:24 chu11 Exp
 *  ipmimonitoring-sel.c,v 1.51 2016/11/02 23:46:24 chu11 Exp
 *
 *  Copyright (C) 2007-2015 Lawrence Livermore National Security, LLC.
 *  Copyright (C) 2006-2007 The Regents of the University of California.
 *  Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
 *  Written by Albert Chu <chu11@llnl.gov>
 *  UCRL-CODE-222073
 */

#include "libnetdata/libnetdata.h"
#include "libnetdata/required_dummies.h"

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <unistd.h>
#include <sys/time.h>

#define IPMI_PARSE_DEVICE_LAN_STR       "lan"
#define IPMI_PARSE_DEVICE_LAN_2_0_STR   "lan_2_0"
#define IPMI_PARSE_DEVICE_LAN_2_0_STR2  "lan20"
#define IPMI_PARSE_DEVICE_LAN_2_0_STR3  "lan_20"
#define IPMI_PARSE_DEVICE_LAN_2_0_STR4  "lan2_0"
#define IPMI_PARSE_DEVICE_LAN_2_0_STR5  "lanplus"
#define IPMI_PARSE_DEVICE_KCS_STR       "kcs"
#define IPMI_PARSE_DEVICE_SSIF_STR      "ssif"
#define IPMI_PARSE_DEVICE_OPENIPMI_STR  "openipmi"
#define IPMI_PARSE_DEVICE_OPENIPMI_STR2 "open"
#define IPMI_PARSE_DEVICE_SUNBMC_STR    "sunbmc"
#define IPMI_PARSE_DEVICE_SUNBMC_STR2   "bmc"
#define IPMI_PARSE_DEVICE_INTELDCMI_STR "inteldcmi"

#include <ipmi_monitoring.h>
#include <ipmi_monitoring_bitmasks.h>

/* Communication Configuration - Initialize accordingly */

/* Hostname, NULL for In-band communication, non-null for a hostname */
char *hostname = NULL;

/* In-band Communication Configuration */
int driver_type = -1; // IPMI_MONITORING_DRIVER_TYPE_KCS; /* or -1 for default */
int disable_auto_probe = 0;     /* probe for in-band device */
unsigned int driver_address = 0; /* not used if probing */
unsigned int register_spacing = 0; /* not used if probing */
char *driver_device = NULL;     /* not used if probing */

/* Out-of-band Communication Configuration */
int protocol_version = -1; //IPMI_MONITORING_PROTOCOL_VERSION_1_5; /* or -1 for default */
char *username = "foousername";
char *password = "foopassword";
unsigned char *ipmi_k_g = NULL;
unsigned int ipmi_k_g_len = 0;
int privilege_level = -1; // IPMI_MONITORING_PRIVILEGE_LEVEL_USER; /* or -1 for default */
int authentication_type = -1; // IPMI_MONITORING_AUTHENTICATION_TYPE_MD5; /* or -1 for default */
int cipher_suite_id = 0;        /* or -1 for default */
int session_timeout = 0;        /* 0 for default */
int retransmission_timeout = 0; /* 0 for default */

/* Workarounds - specify workaround flags if necessary */
unsigned int workaround_flags = 0;

/* Initialize w/ record id numbers to only monitor specific record ids */
unsigned int record_ids[] = {0};
unsigned int record_ids_length = 0;

/* Initialize w/ sensor types to only monitor specific sensor types
 * see ipmi_monitoring.h sensor types list.
 */
unsigned int sensor_types[] = {0};
unsigned int sensor_types_length = 0;

/* Set to an appropriate alternate if desired */
char *sdr_cache_directory = "/tmp";
char *sensor_config_file = NULL;

/* Set to 1 or 0 to enable these sensor reading flags
 * - See ipmi_monitoring.h for descriptions of these flags.
 */
int reread_sdr_cache = 0;
int ignore_non_interpretable_sensors = 0;
int bridge_sensors = 0;
int interpret_oem_data = 0;
int shared_sensors = 0;
int discrete_reading = 1;
int ignore_scanning_disabled = 0;
int assume_bmc_owner = 0;
int entity_sensor_names = 0;

/* Initialization flags
 *
 * Most commonly bitwise OR IPMI_MONITORING_FLAGS_DEBUG and/or
 * IPMI_MONITORING_FLAGS_DEBUG_IPMI_PACKETS for extra debugging
 * information.
 */
unsigned int ipmimonitoring_init_flags = 0;

int errnum;

// ----------------------------------------------------------------------------
// SEL only variables

/* Initialize w/ date range to only monitoring specific date range */
char *date_begin = NULL;        /* use MM/DD/YYYY format */
char *date_end = NULL;          /* use MM/DD/YYYY format */

int assume_system_event_record = 0;

char *sel_config_file = NULL;


// ----------------------------------------------------------------------------
// functions common to sensors and SEL

static void
_init_ipmi_config (struct ipmi_monitoring_ipmi_config *ipmi_config)
{
    fatal_assert(ipmi_config);

    ipmi_config->driver_type = driver_type;
    ipmi_config->disable_auto_probe = disable_auto_probe;
    ipmi_config->driver_address = driver_address;
    ipmi_config->register_spacing = register_spacing;
    ipmi_config->driver_device = driver_device;

    ipmi_config->protocol_version = protocol_version;
    ipmi_config->username = username;
    ipmi_config->password = password;
    ipmi_config->k_g = ipmi_k_g;
    ipmi_config->k_g_len = ipmi_k_g_len;
    ipmi_config->privilege_level = privilege_level;
    ipmi_config->authentication_type = authentication_type;
    ipmi_config->cipher_suite_id = cipher_suite_id;
    ipmi_config->session_timeout_len = session_timeout;
    ipmi_config->retransmission_timeout_len = retransmission_timeout;

    ipmi_config->workaround_flags = workaround_flags;
}

#ifdef NETDATA_COMMENTED
static const char *
_get_sensor_type_string (int sensor_type)
{
    switch (sensor_type)
    {
        case IPMI_MONITORING_SENSOR_TYPE_RESERVED:
            return ("Reserved");
        case IPMI_MONITORING_SENSOR_TYPE_TEMPERATURE:
            return ("Temperature");
        case IPMI_MONITORING_SENSOR_TYPE_VOLTAGE:
            return ("Voltage");
        case IPMI_MONITORING_SENSOR_TYPE_CURRENT:
            return ("Current");
        case IPMI_MONITORING_SENSOR_TYPE_FAN:
            return ("Fan");
        case IPMI_MONITORING_SENSOR_TYPE_PHYSICAL_SECURITY:
            return ("Physical Security");
        case IPMI_MONITORING_SENSOR_TYPE_PLATFORM_SECURITY_VIOLATION_ATTEMPT:
            return ("Platform Security Violation Attempt");
        case IPMI_MONITORING_SENSOR_TYPE_PROCESSOR:
            return ("Processor");
        case IPMI_MONITORING_SENSOR_TYPE_POWER_SUPPLY:
            return ("Power Supply");
        case IPMI_MONITORING_SENSOR_TYPE_POWER_UNIT:
            return ("Power Unit");
        case IPMI_MONITORING_SENSOR_TYPE_COOLING_DEVICE:
            return ("Cooling Device");
        case IPMI_MONITORING_SENSOR_TYPE_OTHER_UNITS_BASED_SENSOR:
            return ("Other Units Based Sensor");
        case IPMI_MONITORING_SENSOR_TYPE_MEMORY:
            return ("Memory");
        case IPMI_MONITORING_SENSOR_TYPE_DRIVE_SLOT:
            return ("Drive Slot");
        case IPMI_MONITORING_SENSOR_TYPE_POST_MEMORY_RESIZE:
            return ("POST Memory Resize");
        case IPMI_MONITORING_SENSOR_TYPE_SYSTEM_FIRMWARE_PROGRESS:
            return ("System Firmware Progress");
        case IPMI_MONITORING_SENSOR_TYPE_EVENT_LOGGING_DISABLED:
            return ("Event Logging Disabled");
        case IPMI_MONITORING_SENSOR_TYPE_WATCHDOG1:
            return ("Watchdog 1");
        case IPMI_MONITORING_SENSOR_TYPE_SYSTEM_EVENT:
            return ("System Event");
        case IPMI_MONITORING_SENSOR_TYPE_CRITICAL_INTERRUPT:
            return ("Critical Interrupt");
        case IPMI_MONITORING_SENSOR_TYPE_BUTTON_SWITCH:
            return ("Button/Switch");
        case IPMI_MONITORING_SENSOR_TYPE_MODULE_BOARD:
            return ("Module/Board");
        case IPMI_MONITORING_SENSOR_TYPE_MICROCONTROLLER_COPROCESSOR:
            return ("Microcontroller/Coprocessor");
        case IPMI_MONITORING_SENSOR_TYPE_ADD_IN_CARD:
            return ("Add In Card");
        case IPMI_MONITORING_SENSOR_TYPE_CHASSIS:
            return ("Chassis");
        case IPMI_MONITORING_SENSOR_TYPE_CHIP_SET:
            return ("Chip Set");
        case IPMI_MONITORING_SENSOR_TYPE_OTHER_FRU:
            return ("Other Fru");
        case IPMI_MONITORING_SENSOR_TYPE_CABLE_INTERCONNECT:
            return ("Cable/Interconnect");
        case IPMI_MONITORING_SENSOR_TYPE_TERMINATOR:
            return ("Terminator");
        case IPMI_MONITORING_SENSOR_TYPE_SYSTEM_BOOT_INITIATED:
            return ("System Boot Initiated");
        case IPMI_MONITORING_SENSOR_TYPE_BOOT_ERROR:
            return ("Boot Error");
        case IPMI_MONITORING_SENSOR_TYPE_OS_BOOT:
            return ("OS Boot");
        case IPMI_MONITORING_SENSOR_TYPE_OS_CRITICAL_STOP:
            return ("OS Critical Stop");
        case IPMI_MONITORING_SENSOR_TYPE_SLOT_CONNECTOR:
            return ("Slot/Connector");
        case IPMI_MONITORING_SENSOR_TYPE_SYSTEM_ACPI_POWER_STATE:
            return ("System ACPI Power State");
        case IPMI_MONITORING_SENSOR_TYPE_WATCHDOG2:
            return ("Watchdog 2");
        case IPMI_MONITORING_SENSOR_TYPE_PLATFORM_ALERT:
            return ("Platform Alert");
        case IPMI_MONITORING_SENSOR_TYPE_ENTITY_PRESENCE:
            return ("Entity Presence");
        case IPMI_MONITORING_SENSOR_TYPE_MONITOR_ASIC_IC:
            return ("Monitor ASIC/IC");
        case IPMI_MONITORING_SENSOR_TYPE_LAN:
            return ("LAN");
        case IPMI_MONITORING_SENSOR_TYPE_MANAGEMENT_SUBSYSTEM_HEALTH:
            return ("Management Subsystem Health");
        case IPMI_MONITORING_SENSOR_TYPE_BATTERY:
            return ("Battery");
        case IPMI_MONITORING_SENSOR_TYPE_SESSION_AUDIT:
            return ("Session Audit");
        case IPMI_MONITORING_SENSOR_TYPE_VERSION_CHANGE:
            return ("Version Change");
        case IPMI_MONITORING_SENSOR_TYPE_FRU_STATE:
            return ("FRU State");
    }

    return ("Unrecognized");
}
#endif // NETDATA_COMMENTED


// ----------------------------------------------------------------------------
// BEGIN NETDATA CODE

static int debug = 0;

static int netdata_update_every = 5; // this is the minimum update frequency
static int netdata_priority = 90000;
static int netdata_do_sel = 1;

static size_t netdata_sensors_updated = 0;
static size_t netdata_sensors_collected = 0;
static size_t netdata_sel_events = 0;
static size_t netdata_sensors_states_nominal = 0;
static size_t netdata_sensors_states_warning = 0;
static size_t netdata_sensors_states_critical = 0;

struct sensor {
    int record_id;
    int sensor_number;
    int sensor_type;
    int sensor_state;
    int sensor_units;
    char *sensor_name;

    int sensor_reading_type;
    union {
        uint8_t bool_value;
        uint32_t uint32_value;
        double double_value;
    } sensor_reading;

    int sent;
    int ignore;
    int exposed;
    int updated;
    struct sensor *next;
} *sensors_root = NULL;

static void netdata_mark_as_not_updated() {
    struct sensor *sn;
    for(sn = sensors_root; sn ;sn = sn->next)
        sn->updated = sn->sent = 0;

    netdata_sensors_updated = 0;
    netdata_sensors_collected = 0;
    netdata_sel_events = 0;

    netdata_sensors_states_nominal = 0;
    netdata_sensors_states_warning = 0;
    netdata_sensors_states_critical = 0;
}

static void send_chart_to_netdata_for_units(int units) {
    struct sensor *sn, *sn_stored;
    int dupfound, multiplier;

    switch(units) {
        case IPMI_MONITORING_SENSOR_UNITS_CELSIUS:
            printf("CHART ipmi.temperatures_c '' 'System Celsius Temperatures read by IPMI' 'Celsius' 'temperatures' 'ipmi.temperatures_c' 'line' %d %d\n"
                   , netdata_priority + 10
                   , netdata_update_every
            );
            break;

        case IPMI_MONITORING_SENSOR_UNITS_FAHRENHEIT:
            printf("CHART ipmi.temperatures_f '' 'System Fahrenheit Temperatures read by IPMI' 'Fahrenheit' 'temperatures' 'ipmi.temperatures_f' 'line' %d %d\n"
                   , netdata_priority + 11
                   , netdata_update_every
            );
            break;

        case IPMI_MONITORING_SENSOR_UNITS_VOLTS:
            printf("CHART ipmi.volts '' 'System Voltages read by IPMI' 'Volts' 'voltages' 'ipmi.voltages' 'line' %d %d\n"
                   , netdata_priority + 12
                   , netdata_update_every
            );
            break;

        case IPMI_MONITORING_SENSOR_UNITS_AMPS:
            printf("CHART ipmi.amps '' 'System Current read by IPMI' 'Amps' 'current' 'ipmi.amps' 'line' %d %d\n"
                   , netdata_priority + 13
                   , netdata_update_every
            );
            break;

        case IPMI_MONITORING_SENSOR_UNITS_RPM:
            printf("CHART ipmi.rpm '' 'System Fans read by IPMI' 'RPM' 'fans' 'ipmi.rpm' 'line' %d %d\n"
                   , netdata_priority + 14
                   , netdata_update_every
            );
            break;

        case IPMI_MONITORING_SENSOR_UNITS_WATTS:
            printf("CHART ipmi.watts '' 'System Power read by IPMI' 'Watts' 'power' 'ipmi.watts' 'line' %d %d\n"
                   , netdata_priority + 5
                   , netdata_update_every
            );
            break;

        case IPMI_MONITORING_SENSOR_UNITS_PERCENT:
            printf("CHART ipmi.percent '' 'System Metrics read by IPMI' '%%' 'other' 'ipmi.percent' 'line' %d %d\n"
                   , netdata_priority + 15
                   , netdata_update_every
            );
            break;

        default:
            for(sn = sensors_root; sn; sn = sn->next)
                if(sn->sensor_units == units)
                    sn->ignore = 1;
            return;
    }

    for(sn = sensors_root; sn; sn = sn->next) {
        dupfound = 0;
        if(sn->sensor_units == units && sn->updated && !sn->ignore) {
            sn->exposed = 1;
            multiplier = 1;

            switch(sn->sensor_reading_type) {
                case IPMI_MONITORING_SENSOR_READING_TYPE_DOUBLE:
                    multiplier = 1000;
                    // fallthrough
                case IPMI_MONITORING_SENSOR_READING_TYPE_UNSIGNED_INTEGER8_BOOL:
                case IPMI_MONITORING_SENSOR_READING_TYPE_UNSIGNED_INTEGER32:
                    for (sn_stored = sensors_root; sn_stored; sn_stored = sn_stored->next) {
                        if (sn_stored == sn) continue;
                        // If the name is a duplicate, append the sensor number
                        if ( !strcmp(sn_stored->sensor_name, sn->sensor_name) ) {
                            dupfound = 1;
                            printf("DIMENSION i%d_n%d_r%d '%s i%d' absolute 1 %d\n"
                                   , sn->sensor_number
                                   , sn->record_id
                                   , sn->sensor_reading_type
                                   , sn->sensor_name
                                   , sn->sensor_number
                                   , multiplier
                            );
                            break;
                        }
                    }
                    // No duplicate name was found, display it just with Name
                    if (!dupfound) {
                        // display without ID
                        printf("DIMENSION i%d_n%d_r%d '%s' absolute 1 %d\n"
                               , sn->sensor_number
                               , sn->record_id
                               , sn->sensor_reading_type
                               , sn->sensor_name
                               , multiplier
                        );
                    }
                    break;

                default:
                    sn->ignore = 1;
                    break;
            }
        }
    }
}

static void send_metrics_to_netdata_for_units(int units) {
    struct sensor *sn;

    switch(units) {
        case IPMI_MONITORING_SENSOR_UNITS_CELSIUS:
            printf("BEGIN ipmi.temperatures_c\n");
            break;

        case IPMI_MONITORING_SENSOR_UNITS_FAHRENHEIT:
            printf("BEGIN ipmi.temperatures_f\n");
            break;

        case IPMI_MONITORING_SENSOR_UNITS_VOLTS:
            printf("BEGIN ipmi.volts\n");
            break;

        case IPMI_MONITORING_SENSOR_UNITS_AMPS:
            printf("BEGIN ipmi.amps\n");
            break;

        case IPMI_MONITORING_SENSOR_UNITS_RPM:
            printf("BEGIN ipmi.rpm\n");
            break;

        case IPMI_MONITORING_SENSOR_UNITS_WATTS:
            printf("BEGIN ipmi.watts\n");
            break;

        case IPMI_MONITORING_SENSOR_UNITS_PERCENT:
            printf("BEGIN ipmi.percent\n");
            break;

        default:
            for(sn = sensors_root; sn; sn = sn->next)
                if(sn->sensor_units == units)
                    sn->ignore = 1;
            return;
    }

    for(sn = sensors_root; sn; sn = sn->next) {
        if(sn->sensor_units == units && sn->updated && !sn->sent && !sn->ignore) {
            netdata_sensors_updated++;

            sn->sent = 1;

            switch(sn->sensor_reading_type) {
                case IPMI_MONITORING_SENSOR_READING_TYPE_UNSIGNED_INTEGER8_BOOL:
                    printf("SET i%d_n%d_r%d = %u\n"
                           , sn->sensor_number
                           , sn->record_id
                           , sn->sensor_reading_type
                           , sn->sensor_reading.bool_value
                    );
                    break;

                case IPMI_MONITORING_SENSOR_READING_TYPE_UNSIGNED_INTEGER32:
                    printf("SET i%d_n%d_r%d = %u\n"
                           , sn->sensor_number
                           , sn->record_id
                           , sn->sensor_reading_type
                           , sn->sensor_reading.uint32_value
                    );
                    break;

                case IPMI_MONITORING_SENSOR_READING_TYPE_DOUBLE:
                    printf("SET i%d_n%d_r%d = %lld\n"
                           , sn->sensor_number
                           , sn->record_id
                           , sn->sensor_reading_type
                           , (long long int)(sn->sensor_reading.double_value * 1000)
                    );
                    break;

                default:
                    sn->ignore = 1;
                    break;
            }
        }
    }

    printf("END\n");
}

static void send_metrics_to_netdata() {
    static int sel_chart_generated = 0, sensors_states_chart_generated = 0;
    struct sensor *sn;

    if(netdata_do_sel && !sel_chart_generated) {
        sel_chart_generated = 1;
        printf("CHART ipmi.events '' 'IPMI Events' 'events' 'events' ipmi.sel area %d %d\n"
               , netdata_priority + 2
               , netdata_update_every
        );
        printf("DIMENSION events '' absolute 1 1\n");
    }

    if(!sensors_states_chart_generated) {
        sensors_states_chart_generated = 1;
        printf("CHART ipmi.sensors_states '' 'IPMI Sensors State' 'sensors' 'states' ipmi.sensors_states line %d %d\n"
               , netdata_priority + 1
               , netdata_update_every
        );
        printf("DIMENSION nominal '' absolute 1 1\n");
        printf("DIMENSION critical '' absolute 1 1\n");
        printf("DIMENSION warning '' absolute 1 1\n");
    }

    // generate the CHART/DIMENSION lines, if we have to
    for(sn = sensors_root; sn; sn = sn->next)
        if(sn->updated && !sn->exposed && !sn->ignore)
            send_chart_to_netdata_for_units(sn->sensor_units);

    if(netdata_do_sel) {
        printf(
                "BEGIN ipmi.events\n"
                "SET events = %zu\n"
                "END\n"
                , netdata_sel_events
        );
    }

    printf(
           "BEGIN ipmi.sensors_states\n"
           "SET nominal = %zu\n"
           "SET warning = %zu\n"
           "SET critical = %zu\n"
           "END\n"
           , netdata_sensors_states_nominal
           , netdata_sensors_states_warning
           , netdata_sensors_states_critical
    );

    // send metrics to netdata
    for(sn = sensors_root; sn; sn = sn->next)
        if(sn->updated && sn->exposed && !sn->sent && !sn->ignore)
            send_metrics_to_netdata_for_units(sn->sensor_units);

}

static int *excluded_record_ids = NULL;
size_t excluded_record_ids_length = 0;

static void excluded_record_ids_parse(const char *s) {
    if(!s) return;

    while(*s) {
        while(*s && !isdigit(*s)) s++;

        if(isdigit(*s)) {
            char *e;
            unsigned long n = strtoul(s, &e, 10);
            s = e;

            if(n != 0) {
                excluded_record_ids = realloc(excluded_record_ids, (excluded_record_ids_length + 1) * sizeof(int));
                if(!excluded_record_ids) {
                    fprintf(stderr, "freeipmi.plugin: failed to allocate memory. Exiting.");
                    exit(1);
                }
                excluded_record_ids[excluded_record_ids_length++] = (int)n;
            }
        }
    }

    if(debug) {
        fprintf(stderr, "freeipmi.plugin: excluded record ids:");
        size_t i;
        for(i = 0; i < excluded_record_ids_length; i++) {
            fprintf(stderr, " %d", excluded_record_ids[i]);
        }
        fprintf(stderr, "\n");
    }
}

static int *excluded_status_record_ids = NULL;
size_t excluded_status_record_ids_length = 0;

static void excluded_status_record_ids_parse(const char *s) {
    if(!s) return;

    while(*s) {
        while(*s && !isdigit(*s)) s++;

        if(isdigit(*s)) {
            char *e;
            unsigned long n = strtoul(s, &e, 10);
            s = e;

            if(n != 0) {
                excluded_status_record_ids = realloc(excluded_status_record_ids, (excluded_status_record_ids_length + 1) * sizeof(int));
                if(!excluded_status_record_ids) {
                    fprintf(stderr, "freeipmi.plugin: failed to allocate memory. Exiting.");
                    exit(1);
                }
                excluded_status_record_ids[excluded_status_record_ids_length++] = (int)n;
            }
        }
    }

    if(debug) {
        fprintf(stderr, "freeipmi.plugin: excluded status record ids:");
        size_t i;
        for(i = 0; i < excluded_status_record_ids_length; i++) {
            fprintf(stderr, " %d", excluded_status_record_ids[i]);
        }
        fprintf(stderr, "\n");
    }
}


static int excluded_record_ids_check(int record_id) {
    size_t i;

    for(i = 0; i < excluded_record_ids_length; i++) {
        if(excluded_record_ids[i] == record_id)
            return 1;
    }

    return 0;
}

static int excluded_status_record_ids_check(int record_id) {
    size_t i;

    for(i = 0; i < excluded_status_record_ids_length; i++) {
        if(excluded_status_record_ids[i] == record_id)
            return 1;
    }

    return 0;
}

static void netdata_get_sensor(
          int record_id
        , int sensor_number
        , int sensor_type
        , int sensor_state
        , int sensor_units
        , int sensor_reading_type
        , char *sensor_name
        , void *sensor_reading
) {
    // find the sensor record
    struct sensor *sn;
    for(sn = sensors_root; sn ;sn = sn->next)
        if(     sn->record_id           == record_id &&
                sn->sensor_number       == sensor_number &&
                sn->sensor_reading_type == sensor_reading_type &&
                sn->sensor_units        == sensor_units &&
                !strcmp(sn->sensor_name, sensor_name)
                )
            break;

    if(!sn) {
        // not found, create it
        // check if it is excluded
        if(excluded_record_ids_check(record_id)) {
            if(debug) fprintf(stderr, "Sensor '%s' is excluded by excluded_record_ids_check()\n", sensor_name);
            return;
        }

        if(debug) fprintf(stderr, "Allocating new sensor data record for sensor '%s', id %d, number %d, type %d, state %d, units %d, reading_type %d\n", sensor_name, record_id, sensor_number, sensor_type, sensor_state, sensor_units, sensor_reading_type);

        sn = calloc(1, sizeof(struct sensor));
        if(!sn) {
            fatal("cannot allocate %zu bytes of memory.", sizeof(struct sensor));
        }

        sn->record_id = record_id;
        sn->sensor_number = sensor_number;
        sn->sensor_type = sensor_type;
        sn->sensor_state = sensor_state;
        sn->sensor_units = sensor_units;
        sn->sensor_reading_type = sensor_reading_type;
        sn->sensor_name = strdup(sensor_name);
        if(!sn->sensor_name) {
            fatal("cannot allocate %zu bytes of memory.", strlen(sensor_name));
        }

        sn->next = sensors_root;
        sensors_root = sn;
    }
    else {
        if(debug) fprintf(stderr, "Reusing sensor record for sensor '%s', id %d, number %d, type %d, state %d, units %d, reading_type %d\n", sensor_name, record_id, sensor_number, sensor_type, sensor_state, sensor_units, sensor_reading_type);
    }

    switch(sensor_reading_type) {
        case IPMI_MONITORING_SENSOR_READING_TYPE_UNSIGNED_INTEGER8_BOOL:
            sn->sensor_reading.bool_value = *((uint8_t *)sensor_reading);
            sn->updated = 1;
            netdata_sensors_collected++;
            break;

        case IPMI_MONITORING_SENSOR_READING_TYPE_UNSIGNED_INTEGER32:
            sn->sensor_reading.uint32_value = *((uint32_t *)sensor_reading);
            sn->updated = 1;
            netdata_sensors_collected++;
            break;

        case IPMI_MONITORING_SENSOR_READING_TYPE_DOUBLE:
            sn->sensor_reading.double_value = *((double *)sensor_reading);
            sn->updated = 1;
            netdata_sensors_collected++;
            break;

        default:
            if(debug) fprintf(stderr, "Unknown reading type - Ignoring sensor record for sensor '%s', id %d, number %d, type %d, state %d, units %d, reading_type %d\n", sensor_name, record_id, sensor_number, sensor_type, sensor_state, sensor_units, sensor_reading_type);
            sn->ignore = 1;
            break;
    }

    // check if it is excluded
    if(excluded_status_record_ids_check(record_id)) {
        if(debug) fprintf(stderr, "Sensor '%s' is excluded for status check, by excluded_status_record_ids_check()\n", sensor_name);
        return;
    }

    switch(sensor_state) {
        case IPMI_MONITORING_STATE_NOMINAL:
            netdata_sensors_states_nominal++;
            break;

        case IPMI_MONITORING_STATE_WARNING:
            netdata_sensors_states_warning++;
            break;

        case IPMI_MONITORING_STATE_CRITICAL:
            netdata_sensors_states_critical++;
            break;

        default:
            break;
    }
}

static void netdata_get_sel(
          int record_id
        , int record_type_class
        , int sel_state
) {
    (void)record_id;
    (void)record_type_class;
    (void)sel_state;

    netdata_sel_events++;
}


// END NETDATA CODE
// ----------------------------------------------------------------------------


static int
_ipmimonitoring_sensors (struct ipmi_monitoring_ipmi_config *ipmi_config)
{
    ipmi_monitoring_ctx_t ctx = NULL;
    unsigned int sensor_reading_flags = 0;
    int i;
    int sensor_count;
    int rv = -1;

    if (!(ctx = ipmi_monitoring_ctx_create ())) {
        error("ipmi_monitoring_ctx_create()");
        goto cleanup;
    }

    if (sdr_cache_directory)
    {
        if (ipmi_monitoring_ctx_sdr_cache_directory (ctx,
                sdr_cache_directory) < 0)
        {
            error("ipmi_monitoring_ctx_sdr_cache_directory(): %s\n",
                    ipmi_monitoring_ctx_errormsg (ctx));
            goto cleanup;
        }
    }

    /* Must call otherwise only default interpretations ever used */
    if (sensor_config_file)
    {
        if (ipmi_monitoring_ctx_sensor_config_file (ctx,
                sensor_config_file) < 0)
        {
            error( "ipmi_monitoring_ctx_sensor_config_file(): %s\n",
                    ipmi_monitoring_ctx_errormsg (ctx));
            goto cleanup;
        }
    }
    else
    {
        if (ipmi_monitoring_ctx_sensor_config_file (ctx, NULL) < 0)
        {
            error( "ipmi_monitoring_ctx_sensor_config_file(): %s\n",
                    ipmi_monitoring_ctx_errormsg (ctx));
            goto cleanup;
        }
    }

    if (reread_sdr_cache)
        sensor_reading_flags |= IPMI_MONITORING_SENSOR_READING_FLAGS_REREAD_SDR_CACHE;

    if (ignore_non_interpretable_sensors)
        sensor_reading_flags |= IPMI_MONITORING_SENSOR_READING_FLAGS_IGNORE_NON_INTERPRETABLE_SENSORS;

    if (bridge_sensors)
        sensor_reading_flags |= IPMI_MONITORING_SENSOR_READING_FLAGS_BRIDGE_SENSORS;

    if (interpret_oem_data)
        sensor_reading_flags |= IPMI_MONITORING_SENSOR_READING_FLAGS_INTERPRET_OEM_DATA;

    if (shared_sensors)
        sensor_reading_flags |= IPMI_MONITORING_SENSOR_READING_FLAGS_SHARED_SENSORS;

    if (discrete_reading)
        sensor_reading_flags |= IPMI_MONITORING_SENSOR_READING_FLAGS_DISCRETE_READING;

    if (ignore_scanning_disabled)
        sensor_reading_flags |= IPMI_MONITORING_SENSOR_READING_FLAGS_IGNORE_SCANNING_DISABLED;

    if (assume_bmc_owner)
        sensor_reading_flags |= IPMI_MONITORING_SENSOR_READING_FLAGS_ASSUME_BMC_OWNER;

#ifdef IPMI_MONITORING_SENSOR_READING_FLAGS_ENTITY_SENSOR_NAMES
    if (entity_sensor_names)
        sensor_reading_flags |= IPMI_MONITORING_SENSOR_READING_FLAGS_ENTITY_SENSOR_NAMES;
#endif // IPMI_MONITORING_SENSOR_READING_FLAGS_ENTITY_SENSOR_NAMES

    if (!record_ids_length && !sensor_types_length)
    {
        if ((sensor_count = ipmi_monitoring_sensor_readings_by_record_id (ctx,
                hostname,
                ipmi_config,
                sensor_reading_flags,
                NULL,
                0,
                NULL,
                NULL)) < 0)
        {
            error( "ipmi_monitoring_sensor_readings_by_record_id(): %s",
                    ipmi_monitoring_ctx_errormsg (ctx));
            goto cleanup;
        }
    }
    else if (record_ids_length)
    {
        if ((sensor_count = ipmi_monitoring_sensor_readings_by_record_id (ctx,
                hostname,
                ipmi_config,
                sensor_reading_flags,
                record_ids,
                record_ids_length,
                NULL,
                NULL)) < 0)
        {
            error( "ipmi_monitoring_sensor_readings_by_record_id(): %s",
                    ipmi_monitoring_ctx_errormsg (ctx));
            goto cleanup;
        }
    }
    else
    {
        if ((sensor_count = ipmi_monitoring_sensor_readings_by_sensor_type (ctx,
                hostname,
                ipmi_config,
                sensor_reading_flags,
                sensor_types,
                sensor_types_length,
                NULL,
                NULL)) < 0)
        {
            error( "ipmi_monitoring_sensor_readings_by_sensor_type(): %s",
                    ipmi_monitoring_ctx_errormsg (ctx));
            goto cleanup;
        }
    }

#ifdef NETDATA_COMMENTED
    printf ("%s, %s, %s, %s, %s, %s, %s, %s, %s, %s\n",
            "Record ID",
            "Sensor Name",
            "Sensor Number",
            "Sensor Type",
            "Sensor State",
            "Sensor Reading",
            "Sensor Units",
            "Sensor Event/Reading Type Code",
            "Sensor Event Bitmask",
            "Sensor Event String");
#endif // NETDATA_COMMENTED

    for (i = 0; i < sensor_count; i++, ipmi_monitoring_sensor_iterator_next (ctx))
    {
        int record_id, sensor_number, sensor_type, sensor_state, sensor_units,
                sensor_reading_type;

#ifdef NETDATA_COMMENTED
        int sensor_bitmask_type, sensor_bitmask, event_reading_type_code;
        char **sensor_bitmask_strings = NULL;
        const char *sensor_type_str;
        const char *sensor_state_str;
#endif // NETDATA_COMMENTED

        char *sensor_name = NULL;
        void *sensor_reading;

        if ((record_id = ipmi_monitoring_sensor_read_record_id (ctx)) < 0)
        {
            error( "ipmi_monitoring_sensor_read_record_id(): %s",
                    ipmi_monitoring_ctx_errormsg (ctx));
            goto cleanup;
        }

        if ((sensor_number = ipmi_monitoring_sensor_read_sensor_number (ctx)) < 0)
        {
            error( "ipmi_monitoring_sensor_read_sensor_number(): %s",
                    ipmi_monitoring_ctx_errormsg (ctx));
            goto cleanup;
        }

        if ((sensor_type = ipmi_monitoring_sensor_read_sensor_type (ctx)) < 0)
        {
            error( "ipmi_monitoring_sensor_read_sensor_type(): %s",
                    ipmi_monitoring_ctx_errormsg (ctx));
            goto cleanup;
        }

        if (!(sensor_name = ipmi_monitoring_sensor_read_sensor_name (ctx)))
        {
            error( "ipmi_monitoring_sensor_read_sensor_name(): %s",
                    ipmi_monitoring_ctx_errormsg (ctx));
            goto cleanup;
        }

        if ((sensor_state = ipmi_monitoring_sensor_read_sensor_state (ctx)) < 0)
        {
            error( "ipmi_monitoring_sensor_read_sensor_state(): %s",
                    ipmi_monitoring_ctx_errormsg (ctx));
            goto cleanup;
        }

        if ((sensor_units = ipmi_monitoring_sensor_read_sensor_units (ctx)) < 0)
        {
            error( "ipmi_monitoring_sensor_read_sensor_units(): %s",
                    ipmi_monitoring_ctx_errormsg (ctx));
            goto cleanup;
        }

#ifdef NETDATA_COMMENTED
        if ((sensor_bitmask_type = ipmi_monitoring_sensor_read_sensor_bitmask_type (ctx)) < 0)
        {
            error( "ipmi_monitoring_sensor_read_sensor_bitmask_type(): %s",
                    ipmi_monitoring_ctx_errormsg (ctx));
            goto cleanup;
        }
        if ((sensor_bitmask = ipmi_monitoring_sensor_read_sensor_bitmask (ctx)) < 0)
        {
            error(
                   "ipmi_monitoring_sensor_read_sensor_bitmask(): %s",
                    ipmi_monitoring_ctx_errormsg (ctx));
            goto cleanup;
        }

         /* it's ok for this to be NULL, i.e. sensor_bitmask ==
          * IPMI_MONITORING_SENSOR_BITMASK_TYPE_UNKNOWN
          */
        sensor_bitmask_strings = ipmi_monitoring_sensor_read_sensor_bitmask_strings (ctx);
        
        
        
#endif // NETDATA_COMMENTED

        if ((sensor_reading_type = ipmi_monitoring_sensor_read_sensor_reading_type (ctx)) < 0)
        {
            error( "ipmi_monitoring_sensor_read_sensor_reading_type(): %s",
                    ipmi_monitoring_ctx_errormsg (ctx));
            goto cleanup;
        }

        sensor_reading = ipmi_monitoring_sensor_read_sensor_reading (ctx);

#ifdef NETDATA_COMMENTED
        if ((event_reading_type_code = ipmi_monitoring_sensor_read_event_reading_type_code (ctx)) < 0)
        {
            error( "ipmi_monitoring_sensor_read_event_reading_type_code(): %s",
                    ipmi_monitoring_ctx_errormsg (ctx));
            goto cleanup;
        }
#endif // NETDATA_COMMENTED

        netdata_get_sensor(
                record_id
                , sensor_number
                , sensor_type
                , sensor_state
                , sensor_units
                , sensor_reading_type
                , sensor_name
                , sensor_reading
        );

#ifdef NETDATA_COMMENTED
        if (!strlen (sensor_name))
            sensor_name = "N/A";

        sensor_type_str = _get_sensor_type_string (sensor_type);

        printf ("%d, %s, %d, %s",
                record_id,
                sensor_name,
                sensor_number,
                sensor_type_str);

        if (sensor_state == IPMI_MONITORING_STATE_NOMINAL)
            sensor_state_str = "Nominal";
        else if (sensor_state == IPMI_MONITORING_STATE_WARNING)
            sensor_state_str = "Warning";
        else if (sensor_state == IPMI_MONITORING_STATE_CRITICAL)
            sensor_state_str = "Critical";
        else
            sensor_state_str = "N/A";

        printf (", %s", sensor_state_str);

        if (sensor_reading)
        {
            const char *sensor_units_str;

            if (sensor_reading_type == IPMI_MONITORING_SENSOR_READING_TYPE_UNSIGNED_INTEGER8_BOOL)
                printf (", %s",
                        (*((uint8_t *)sensor_reading) ? "true" : "false"));
            else if (sensor_reading_type == IPMI_MONITORING_SENSOR_READING_TYPE_UNSIGNED_INTEGER32)
                printf (", %u",
                        *((uint32_t *)sensor_reading));
            else if (sensor_reading_type == IPMI_MONITORING_SENSOR_READING_TYPE_DOUBLE)
                printf (", %.2f",
                        *((double *)sensor_reading));
            else
                printf (", N/A");

            if (sensor_units == IPMI_MONITORING_SENSOR_UNITS_CELSIUS)
                sensor_units_str = "C";
            else if (sensor_units == IPMI_MONITORING_SENSOR_UNITS_FAHRENHEIT)
                sensor_units_str = "F";
            else if (sensor_units == IPMI_MONITORING_SENSOR_UNITS_VOLTS)
                sensor_units_str = "V";
            else if (sensor_units == IPMI_MONITORING_SENSOR_UNITS_AMPS)
                sensor_units_str = "A";
            else if (sensor_units == IPMI_MONITORING_SENSOR_UNITS_RPM)
                sensor_units_str = "RPM";
            else if (sensor_units == IPMI_MONITORING_SENSOR_UNITS_WATTS)
                sensor_units_str = "W";
            else if (sensor_units == IPMI_MONITORING_SENSOR_UNITS_PERCENT)
                sensor_units_str = "%";
            else
                sensor_units_str = "N/A";

            printf (", %s", sensor_units_str);
        }
        else
            printf (", N/A, N/A");

        printf (", %Xh", event_reading_type_code);

        /* It is possible you may want to monitor specific event
         * conditions that may occur.  If that is the case, you may want
         * to check out what specific bitmask type and bitmask events
         * occurred.  See ipmi_monitoring_bitmasks.h for a list of
         * bitmasks and types.
         */

        if (sensor_bitmask_type != IPMI_MONITORING_SENSOR_BITMASK_TYPE_UNKNOWN)
            printf (", %Xh", sensor_bitmask);
        else
            printf (", N/A");

        if (sensor_bitmask_type != IPMI_MONITORING_SENSOR_BITMASK_TYPE_UNKNOWN
            && sensor_bitmask_strings)
        {
            unsigned int i = 0;

            printf (",");

            while (sensor_bitmask_strings[i])
            {
                printf (" ");

                printf ("'%s'",
                        sensor_bitmask_strings[i]);

                i++;
            }
        }
        else
            printf (", N/A");

        printf ("\n");
#endif // NETDATA_COMMENTED
    }

    rv = 0;
    cleanup:
    if (ctx)
        ipmi_monitoring_ctx_destroy (ctx);
    return (rv);
}


static int
_ipmimonitoring_sel (struct ipmi_monitoring_ipmi_config *ipmi_config)
{
    ipmi_monitoring_ctx_t ctx = NULL;
    unsigned int sel_flags = 0;
    int i;
    int sel_count;
    int rv = -1;

    if (!(ctx = ipmi_monitoring_ctx_create ()))
    {
        error("ipmi_monitoring_ctx_create()");
        goto cleanup;
    }

    if (sdr_cache_directory)
    {
        if (ipmi_monitoring_ctx_sdr_cache_directory (ctx,
                sdr_cache_directory) < 0)
        {
            error( "ipmi_monitoring_ctx_sdr_cache_directory(): %s",
                    ipmi_monitoring_ctx_errormsg (ctx));
            goto cleanup;
        }
    }

    /* Must call otherwise only default interpretations ever used */
    if (sel_config_file)
    {
        if (ipmi_monitoring_ctx_sel_config_file (ctx,
                sel_config_file) < 0)
        {
            error( "ipmi_monitoring_ctx_sel_config_file(): %s",
                    ipmi_monitoring_ctx_errormsg (ctx));
            goto cleanup;
        }
    }
    else
    {
        if (ipmi_monitoring_ctx_sel_config_file (ctx, NULL) < 0)
        {
            error( "ipmi_monitoring_ctx_sel_config_file(): %s",
                    ipmi_monitoring_ctx_errormsg (ctx));
            goto cleanup;
        }
    }

    if (reread_sdr_cache)
        sel_flags |= IPMI_MONITORING_SEL_FLAGS_REREAD_SDR_CACHE;

    if (interpret_oem_data)
        sel_flags |= IPMI_MONITORING_SEL_FLAGS_INTERPRET_OEM_DATA;

    if (assume_system_event_record)
        sel_flags |= IPMI_MONITORING_SEL_FLAGS_ASSUME_SYSTEM_EVENT_RECORD;

#ifdef IPMI_MONITORING_SEL_FLAGS_ENTITY_SENSOR_NAMES
    if (entity_sensor_names)
        sel_flags |= IPMI_MONITORING_SEL_FLAGS_ENTITY_SENSOR_NAMES;
#endif // IPMI_MONITORING_SEL_FLAGS_ENTITY_SENSOR_NAMES

    if (record_ids_length)
    {
        if ((sel_count = ipmi_monitoring_sel_by_record_id (ctx,
                hostname,
                ipmi_config,
                sel_flags,
                record_ids,
                record_ids_length,
                NULL,
                NULL)) < 0)
        {
            error( "ipmi_monitoring_sel_by_record_id(): %s",
                    ipmi_monitoring_ctx_errormsg (ctx));
            goto cleanup;
        }
    }
    else if (sensor_types_length)
    {
        if ((sel_count = ipmi_monitoring_sel_by_sensor_type (ctx,
                hostname,
                ipmi_config,
                sel_flags,
                sensor_types,
                sensor_types_length,
                NULL,
                NULL)) < 0)
        {
            error( "ipmi_monitoring_sel_by_sensor_type(): %s",
                    ipmi_monitoring_ctx_errormsg (ctx));
            goto cleanup;
        }
    }
    else if (date_begin
             || date_end)
    {
        if ((sel_count = ipmi_monitoring_sel_by_date_range (ctx,
                hostname,
                ipmi_config,
                sel_flags,
                date_begin,
                date_end,
                NULL,
                NULL)) < 0)
        {
            error( "ipmi_monitoring_sel_by_sensor_type(): %s",
                    ipmi_monitoring_ctx_errormsg (ctx));
            goto cleanup;
        }
    }
    else
    {
        if ((sel_count = ipmi_monitoring_sel_by_record_id (ctx,
                hostname,
                ipmi_config,
                sel_flags,
                NULL,
                0,
                NULL,
                NULL)) < 0)
        {
            error( "ipmi_monitoring_sel_by_record_id(): %s",
                    ipmi_monitoring_ctx_errormsg (ctx));
            goto cleanup;
        }
    }

#ifdef NETDATA_COMMENTED
    printf ("%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s\n",
            "Record ID",
            "Record Type",
            "SEL State",
            "Timestamp",
            "Sensor Name",
            "Sensor Type",
            "Event Direction",
            "Event Type Code",
            "Event Data",
            "Event Offset",
            "Event Offset String");
#endif // NETDATA_COMMENTED

    for (i = 0; i < sel_count; i++, ipmi_monitoring_sel_iterator_next (ctx))
    {
        int record_id, record_type, sel_state, record_type_class;
#ifdef NETDATA_COMMENTED
        int sensor_type, sensor_number, event_direction,
                event_offset_type, event_offset, event_type_code, manufacturer_id;
        unsigned int timestamp, event_data1, event_data2, event_data3;
        char *event_offset_string = NULL;
        const char *sensor_type_str;
        const char *event_direction_str;
        const char *sel_state_str;
        char *sensor_name = NULL;
        unsigned char oem_data[64];
        int oem_data_len;
        unsigned int j;
#endif // NETDATA_COMMENTED

        if ((record_id = ipmi_monitoring_sel_read_record_id (ctx)) < 0)
        {
            error( "ipmi_monitoring_sel_read_record_id(): %s",
                    ipmi_monitoring_ctx_errormsg (ctx));
            goto cleanup;
        }

        if ((record_type = ipmi_monitoring_sel_read_record_type (ctx)) < 0)
        {
            error( "ipmi_monitoring_sel_read_record_type(): %s",
                    ipmi_monitoring_ctx_errormsg (ctx));
            goto cleanup;
        }

        if ((record_type_class = ipmi_monitoring_sel_read_record_type_class (ctx)) < 0)
        {
            error( "ipmi_monitoring_sel_read_record_type_class(): %s",
                    ipmi_monitoring_ctx_errormsg (ctx));
            goto cleanup;
        }

        if ((sel_state = ipmi_monitoring_sel_read_sel_state (ctx)) < 0)
        {
            error( "ipmi_monitoring_sel_read_sel_state(): %s",
                    ipmi_monitoring_ctx_errormsg (ctx));
            goto cleanup;
        }

        netdata_get_sel(
                  record_id
                , record_type_class
                , sel_state
        );

#ifdef NETDATA_COMMENTED
        if (sel_state == IPMI_MONITORING_STATE_NOMINAL)
            sel_state_str = "Nominal";
        else if (sel_state == IPMI_MONITORING_STATE_WARNING)
            sel_state_str = "Warning";
        else if (sel_state == IPMI_MONITORING_STATE_CRITICAL)
            sel_state_str = "Critical";
        else
            sel_state_str = "N/A";

        printf ("%d, %d, %s",
                record_id,
                record_type,
                sel_state_str);

        if (record_type_class == IPMI_MONITORING_SEL_RECORD_TYPE_CLASS_SYSTEM_EVENT_RECORD
            || record_type_class == IPMI_MONITORING_SEL_RECORD_TYPE_CLASS_TIMESTAMPED_OEM_RECORD)
        {

            if (ipmi_monitoring_sel_read_timestamp (ctx, &timestamp) < 0)
            {
                error( "ipmi_monitoring_sel_read_timestamp(): %s",
                        ipmi_monitoring_ctx_errormsg (ctx));
                goto cleanup;
            }

            /* XXX: This should be converted to a nice date output using
             * your favorite timestamp -> string conversion functions.
             */
            printf (", %u", timestamp);
        }
        else
            printf (", N/A");

        if (record_type_class == IPMI_MONITORING_SEL_RECORD_TYPE_CLASS_SYSTEM_EVENT_RECORD)
        {
            /* If you are integrating ipmimonitoring SEL into a monitoring application,
             * you may wish to count the number of times a specific error occurred
             * and report that to the monitoring application.
             *
             * In this particular case, you'll probably want to check out
             * what sensor type each SEL event is reporting, the
             * event offset type, and the specific event offset that occurred.
             *
             * See ipmi_monitoring_offsets.h for a list of event offsets
             * and types.
             */

            if (!(sensor_name = ipmi_monitoring_sel_read_sensor_name (ctx)))
            {
                error( "ipmi_monitoring_sel_read_sensor_name(): %s",
                        ipmi_monitoring_ctx_errormsg (ctx));
                goto cleanup;
            }

            if ((sensor_type = ipmi_monitoring_sel_read_sensor_type (ctx)) < 0)
            {
                error( "ipmi_monitoring_sel_read_sensor_type(): %s",
                        ipmi_monitoring_ctx_errormsg (ctx));
                goto cleanup;
            }

            if ((sensor_number = ipmi_monitoring_sel_read_sensor_number (ctx)) < 0)
            {
                error( "ipmi_monitoring_sel_read_sensor_number(): %s",
                        ipmi_monitoring_ctx_errormsg (ctx));
                goto cleanup;
            }

            if ((event_direction = ipmi_monitoring_sel_read_event_direction (ctx)) < 0)
            {
                error( "ipmi_monitoring_sel_read_event_direction(): %s",
                        ipmi_monitoring_ctx_errormsg (ctx));
                goto cleanup;
            }

            if ((event_type_code = ipmi_monitoring_sel_read_event_type_code (ctx)) < 0)
            {
                error( "ipmi_monitoring_sel_read_event_type_code(): %s",
                        ipmi_monitoring_ctx_errormsg (ctx));
                goto cleanup;
            }

            if (ipmi_monitoring_sel_read_event_data (ctx,
                    &event_data1,
                    &event_data2,
                    &event_data3) < 0)
            {
                error( "ipmi_monitoring_sel_read_event_data(): %s",
                        ipmi_monitoring_ctx_errormsg (ctx));
                goto cleanup;
            }

            if ((event_offset_type = ipmi_monitoring_sel_read_event_offset_type (ctx)) < 0)
            {
                error( "ipmi_monitoring_sel_read_event_offset_type(): %s",
                        ipmi_monitoring_ctx_errormsg (ctx));
                goto cleanup;
            }

            if ((event_offset = ipmi_monitoring_sel_read_event_offset (ctx)) < 0)
            {
                error( "ipmi_monitoring_sel_read_event_offset(): %s",
                        ipmi_monitoring_ctx_errormsg (ctx));
                goto cleanup;
            }

            if (!(event_offset_string = ipmi_monitoring_sel_read_event_offset_string (ctx)))
            {
                error( "ipmi_monitoring_sel_read_event_offset_string(): %s",
                        ipmi_monitoring_ctx_errormsg (ctx));
                goto cleanup;
            }

            if (!strlen (sensor_name))
                sensor_name = "N/A";

            sensor_type_str = _get_sensor_type_string (sensor_type);

            if (event_direction == IPMI_MONITORING_SEL_EVENT_DIRECTION_ASSERTION)
                event_direction_str = "Assertion";
            else
                event_direction_str = "Deassertion";

            printf (", %s, %s, %d, %s, %Xh, %Xh-%Xh-%Xh",
                    sensor_name,
                    sensor_type_str,
                    sensor_number,
                    event_direction_str,
                    event_type_code,
                    event_data1,
                    event_data2,
                    event_data3);

            if (event_offset_type != IPMI_MONITORING_EVENT_OFFSET_TYPE_UNKNOWN)
                printf (", %Xh", event_offset);
            else
                printf (", N/A");

            if (event_offset_type != IPMI_MONITORING_EVENT_OFFSET_TYPE_UNKNOWN)
                printf (", %s", event_offset_string);
            else
                printf (", N/A");
        }
        else if (record_type_class == IPMI_MONITORING_SEL_RECORD_TYPE_CLASS_TIMESTAMPED_OEM_RECORD
                 || record_type_class == IPMI_MONITORING_SEL_RECORD_TYPE_CLASS_NON_TIMESTAMPED_OEM_RECORD)
        {
            if (record_type_class == IPMI_MONITORING_SEL_RECORD_TYPE_CLASS_TIMESTAMPED_OEM_RECORD)
            {
                if ((manufacturer_id = ipmi_monitoring_sel_read_manufacturer_id (ctx)) < 0)
                {
                    error( "ipmi_monitoring_sel_read_manufacturer_id(): %s",
                            ipmi_monitoring_ctx_errormsg (ctx));
                    goto cleanup;
                }

                printf (", Manufacturer ID = %Xh", manufacturer_id);
            }

            if ((oem_data_len = ipmi_monitoring_sel_read_oem_data (ctx, oem_data, 1024)) < 0)
            {
                error( "ipmi_monitoring_sel_read_oem_data(): %s",
                        ipmi_monitoring_ctx_errormsg (ctx));
                goto cleanup;
            }

            printf (", OEM Data = ");

            for (j = 0; j < oem_data_len; j++)
                printf ("%02Xh ", oem_data[j]);
        }
        else
            printf (", N/A, N/A, N/A, N/A, N/A, N/A, N/A");

        printf ("\n");
#endif // NETDATA_COMMENTED
    }

    rv = 0;
    cleanup:
    if (ctx)
        ipmi_monitoring_ctx_destroy (ctx);
    return (rv);
}

// ----------------------------------------------------------------------------
// MAIN PROGRAM FOR NETDATA PLUGIN

int ipmi_collect_data(struct ipmi_monitoring_ipmi_config *ipmi_config) {
    errno = 0;

    if (_ipmimonitoring_sensors(ipmi_config) < 0) return -1;

    if(netdata_do_sel) {
        if(_ipmimonitoring_sel(ipmi_config) < 0) return -2;
    }

    return 0;
}

int ipmi_detect_speed_secs(struct ipmi_monitoring_ipmi_config *ipmi_config) {
    int i, checks = 10;
    unsigned long long total = 0;

    for(i = 0 ; i < checks ; i++) {
        if(debug) fprintf(stderr, "freeipmi.plugin: checking data collection speed iteration %d of %d\n", i+1, checks);

        // measure the time a data collection needs
        unsigned long long start = now_realtime_usec();
        if(ipmi_collect_data(ipmi_config) < 0)
            fatal("freeipmi.plugin: data collection failed.");

        unsigned long long end = now_realtime_usec();

        if(debug) fprintf(stderr, "freeipmi.plugin: data collection speed was %llu usec\n", end - start);

        // add it to our total
        total += end - start;

        // wait the same time
        // to avoid flooding the IPMI processor with requests
        sleep_usec(end - start);
    }

    // so, we assume it needed 2x the time
    // we find the average in microseconds
    // and we round-up to the closest second

    return (int)(( total * 2 / checks / 1000000 ) + 1);
}

int parse_inband_driver_type (const char *str)
{
    fatal_assert(str);

    if (strcasecmp (str, IPMI_PARSE_DEVICE_KCS_STR) == 0)
        return (IPMI_MONITORING_DRIVER_TYPE_KCS);
    else if (strcasecmp (str, IPMI_PARSE_DEVICE_SSIF_STR) == 0)
        return (IPMI_MONITORING_DRIVER_TYPE_SSIF);
        /* support "open" for those that might be used to
		 * ipmitool.
		 */
    else if (strcasecmp (str, IPMI_PARSE_DEVICE_OPENIPMI_STR) == 0
             || strcasecmp (str, IPMI_PARSE_DEVICE_OPENIPMI_STR2) == 0)
        return (IPMI_MONITORING_DRIVER_TYPE_OPENIPMI);
        /* support "bmc" for those that might be used to
		 * ipmitool.
		 */
    else if (strcasecmp (str, IPMI_PARSE_DEVICE_SUNBMC_STR) == 0
             || strcasecmp (str, IPMI_PARSE_DEVICE_SUNBMC_STR2) == 0)
        return (IPMI_MONITORING_DRIVER_TYPE_SUNBMC);

    return (-1);
}

int parse_outofband_driver_type (const char *str)
{
    fatal_assert(str);

    if (strcasecmp (str, IPMI_PARSE_DEVICE_LAN_STR) == 0)
        return (IPMI_MONITORING_PROTOCOL_VERSION_1_5);
        /* support "lanplus" for those that might be used to ipmitool.
		 * support typo variants to ease.
		 */
    else if (strcasecmp (str, IPMI_PARSE_DEVICE_LAN_2_0_STR) == 0
             || strcasecmp (str, IPMI_PARSE_DEVICE_LAN_2_0_STR2) == 0
             || strcasecmp (str, IPMI_PARSE_DEVICE_LAN_2_0_STR3) == 0
             || strcasecmp (str, IPMI_PARSE_DEVICE_LAN_2_0_STR4) == 0
             || strcasecmp (str, IPMI_PARSE_DEVICE_LAN_2_0_STR5) == 0)
        return (IPMI_MONITORING_PROTOCOL_VERSION_2_0);

    return (-1);
}

int host_is_local(const char *host)
{
    if (host && (!strcmp(host, "localhost") || !strcmp(host, "127.0.0.1") || !strcmp(host, "::1")))
        return (1);

    return (0);
}

int main (int argc, char **argv) {
    clocks_init();

    // ------------------------------------------------------------------------
    // initialization of netdata plugin

    program_name = "freeipmi.plugin";

    // disable syslog
    error_log_syslog = 0;

    // set errors flood protection to 100 logs per hour
    error_log_errors_per_period = 100;
    error_log_throttle_period = 3600;


    // ------------------------------------------------------------------------
    // parse command line parameters

    int i, freq = 0;
    for(i = 1; i < argc ; i++) {
        if(isdigit(*argv[i]) && !freq) {
            int n = str2i(argv[i]);
            if(n > 0 && n < 86400) {
                freq = n;
                continue;
            }
        }
        else if(strcmp("version", argv[i]) == 0 || strcmp("-version", argv[i]) == 0 || strcmp("--version", argv[i]) == 0 || strcmp("-v", argv[i]) == 0 || strcmp("-V", argv[i]) == 0) {
            printf("freeipmi.plugin %s\n", VERSION);
            exit(0);
        }
        else if(strcmp("debug", argv[i]) == 0) {
            debug = 1;
            continue;
        }
        else if(strcmp("sel", argv[i]) == 0) {
            netdata_do_sel = 1;
            continue;
        }
        else if(strcmp("no-sel", argv[i]) == 0) {
            netdata_do_sel = 0;
            continue;
        }
        else if(strcmp("-h", argv[i]) == 0 || strcmp("--help", argv[i]) == 0) {
            fprintf(stderr,
                    "\n"
                    " netdata freeipmi.plugin %s\n"
                    " Copyright (C) 2016-2017 Costa Tsaousis <costa@tsaousis.gr>\n"
                    " Released under GNU General Public License v3 or later.\n"
                    " All rights reserved.\n"
                    "\n"
                    " This program is a data collector plugin for netdata.\n"
                    "\n"
                    " Available command line options:\n"
                    "\n"
                    "  SECONDS                 data collection frequency\n"
                    "                          minimum: %d\n"
                    "\n"
                    "  debug                   enable verbose output\n"
                    "                          default: disabled\n"
                    "\n"
                    "  sel\n"
                    "  no-sel                  enable/disable SEL collection\n"
                    "                          default: %s\n"
                    "\n"
                    "  hostname HOST\n"
                    "  username USER\n"
                    "  password PASS           connect to remote IPMI host\n"
                    "                          default: local IPMI processor\n"
                    "\n"
                    "  noauthcodecheck         don't check the authentication codes returned\n"
                    "\n"
                    " driver-type IPMIDRIVER\n"
                    "                          Specify the driver type to use instead of doing an auto selection. \n"
                    "                          The currently available outofband drivers are LAN and  LAN_2_0,\n"
                    "                          which  perform  IPMI  1.5  and  IPMI  2.0 respectively. \n"
                    "                          The currently available inband drivers are KCS, SSIF, OPENIPMI and SUNBMC.\n"
                    "\n"
                    "  sdr-cache-dir PATH      directory for SDR cache files\n"
                    "                          default: %s\n"
                    "\n"
                    "  sensor-config-file FILE filename to read sensor configuration\n"
                    "                          default: %s\n"
                    "\n"
                    "  ignore N1,N2,N3,...     sensor IDs to ignore\n"
                    "                          default: none\n"
                    "\n"
                    "  ignore-status N1,N2,N3,... sensor IDs to ignore status (nominal/warning/critical)\n"
                    "                          default: none\n"
                    "\n"
                    "  -v\n"
                    "  -V\n"
                    "  version                 print version and exit\n"
                    "\n"
                    " Linux kernel module for IPMI is CPU hungry.\n"
                    " On Linux run this to lower kipmiN CPU utilization:\n"
                    " # echo 10 > /sys/module/ipmi_si/parameters/kipmid_max_busy_us\n"
                    "\n"
                    " or create: /etc/modprobe.d/ipmi.conf with these contents:\n"
                    " options ipmi_si kipmid_max_busy_us=10\n"
                    "\n"
                    " For more information:\n"
                    " https://github.com/netdata/netdata/tree/master/collectors/freeipmi.plugin\n"
                    "\n"
                    , VERSION
                    , netdata_update_every
                    , netdata_do_sel?"enabled":"disabled"
                    , sdr_cache_directory?sdr_cache_directory:"system default"
                    , sensor_config_file?sensor_config_file:"system default"
            );
            exit(1);
        }
        else if(i < argc && strcmp("hostname", argv[i]) == 0) {
            hostname = strdupz(argv[++i]);
            char *s = argv[i];
            // mask it be hidden from the process tree
            while(*s) *s++ = 'x';
            if(debug) fprintf(stderr, "freeipmi.plugin: hostname set to '%s'\n", hostname);
            continue;
        }
        else if(i < argc && strcmp("username", argv[i]) == 0) {
            username = strdupz(argv[++i]);
            char *s = argv[i];
            // mask it be hidden from the process tree
            while(*s) *s++ = 'x';
            if(debug) fprintf(stderr, "freeipmi.plugin: username set to '%s'\n", username);
            continue;
        }
        else if(i < argc && strcmp("password", argv[i]) == 0) {
            password = strdupz(argv[++i]);
            char *s = argv[i];
            // mask it be hidden from the process tree
            while(*s) *s++ = 'x';
            if(debug) fprintf(stderr, "freeipmi.plugin: password set to '%s'\n", password);
            continue;
        }
        else if(strcmp("driver-type", argv[i]) == 0) {
            if (hostname) {
                protocol_version=parse_outofband_driver_type(argv[++i]);
                if(debug) fprintf(stderr, "freeipmi.plugin: outband protocol version set to '%d'\n", protocol_version);
            }
            else {
                driver_type=parse_inband_driver_type(argv[++i]);
                if(debug) fprintf(stderr, "freeipmi.plugin: inband driver type set to '%d'\n", driver_type);
            }
            continue;
        } else if (i < argc && strcmp("noauthcodecheck", argv[i]) == 0) {
            if (!hostname || host_is_local(hostname)) {
                if (debug)
                    fprintf(
                        stderr,
                        "freeipmi.plugin: noauthcodecheck workaround flag is ignored for inband configuration\n");
            } else if (protocol_version < 0 || protocol_version == IPMI_MONITORING_PROTOCOL_VERSION_1_5) {
                workaround_flags |= IPMI_MONITORING_WORKAROUND_FLAGS_PROTOCOL_VERSION_1_5_NO_AUTH_CODE_CHECK;
                if (debug)
                    fprintf(stderr, "freeipmi.plugin: noauthcodecheck workaround flag enabled\n");
            } else {
                if (debug)
                    fprintf(
                        stderr,
                        "freeipmi.plugin: noauthcodecheck workaround flag is ignored for protocol version 2.0\n");
            }
            continue;
        }
        else if(i < argc && strcmp("sdr-cache-dir", argv[i]) == 0) {
            sdr_cache_directory = argv[++i];
            if(debug) fprintf(stderr, "freeipmi.plugin: SDR cache directory set to '%s'\n", sdr_cache_directory);
            continue;
        }
        else if(i < argc && strcmp("sensor-config-file", argv[i]) == 0) {
            sensor_config_file = argv[++i];
            if(debug) fprintf(stderr, "freeipmi.plugin: sensor config file set to '%s'\n", sensor_config_file);
            continue;
        }
        else if(i < argc && strcmp("ignore", argv[i]) == 0) {
            excluded_record_ids_parse(argv[++i]);
            continue;
        }
        else if(i < argc && strcmp("ignore-status", argv[i]) == 0) {
            excluded_status_record_ids_parse(argv[++i]);
            continue;
        }

        error("freeipmi.plugin: ignoring parameter '%s'", argv[i]);
    }

    errno = 0;

    if(freq >= netdata_update_every)
        netdata_update_every = freq;

    else if(freq)
        error("update frequency %d seconds is too small for IPMI. Using %d.", freq, netdata_update_every);


    // ------------------------------------------------------------------------
    // initialize IPMI

    struct ipmi_monitoring_ipmi_config ipmi_config;

    if(debug) fprintf(stderr, "freeipmi.plugin: calling _init_ipmi_config()\n");

    _init_ipmi_config(&ipmi_config);

    if(debug) {
        fprintf(stderr, "freeipmi.plugin: calling ipmi_monitoring_init()\n");
        ipmimonitoring_init_flags|=IPMI_MONITORING_FLAGS_DEBUG|IPMI_MONITORING_FLAGS_DEBUG_IPMI_PACKETS;
    }

    if(ipmi_monitoring_init(ipmimonitoring_init_flags, &errnum) < 0)
        fatal("ipmi_monitoring_init: %s", ipmi_monitoring_ctx_strerror(errnum));

    if(debug) fprintf(stderr, "freeipmi.plugin: detecting IPMI minimum update frequency...\n");
    freq = ipmi_detect_speed_secs(&ipmi_config);
    if(debug) fprintf(stderr, "freeipmi.plugin: IPMI minimum update frequency was calculated to %d seconds.\n", freq);

    if(freq > netdata_update_every) {
        info("enforcing minimum data collection frequency, calculated to %d seconds.", freq);
        netdata_update_every = freq;
    }


    // ------------------------------------------------------------------------
    // the main loop

    if(debug) fprintf(stderr, "freeipmi.plugin: starting data collection\n");

    time_t started_t = now_monotonic_sec();

    size_t iteration = 0;
    usec_t step = netdata_update_every * USEC_PER_SEC;

    heartbeat_t hb;
    heartbeat_init(&hb);
    for(iteration = 0; 1 ; iteration++) {
        usec_t dt = heartbeat_next(&hb, step);

        if(debug && iteration)
            fprintf(stderr, "freeipmi.plugin: iteration %zu, dt %llu usec, sensors collected %zu, sensors sent to netdata %zu \n"
                    , iteration
                    , dt
                    , netdata_sensors_collected
                    , netdata_sensors_updated
            );

        netdata_mark_as_not_updated();

        if(debug) fprintf(stderr, "freeipmi.plugin: calling ipmi_collect_data()\n");
        if(ipmi_collect_data(&ipmi_config) < 0)
            fatal("data collection failed.");

        if(debug) fprintf(stderr, "freeipmi.plugin: calling send_metrics_to_netdata()\n");
        send_metrics_to_netdata();
        fflush(stdout);

        // restart check (14400 seconds)
        if(now_monotonic_sec() - started_t > 14400) exit(0);
    }
}
