// SPDX-License-Identifier: GPL-3.0-or-later

#include "perflib-rrd.h"

#define COLLECTED_NUMBER_PRECISION 10000

RRDDIM *perflib_rrddim_add(RRDSET *st, const char *id, const char *name, collected_number multiplier, collected_number divider, COUNTER_DATA *cd) {
    RRD_ALGORITHM algorithm = RRD_ALGORITHM_ABSOLUTE;

    switch (cd->current.CounterType) {
        case PERF_COUNTER_COUNTER:
        case PERF_SAMPLE_COUNTER:
        case PERF_COUNTER_BULK_COUNT:
            // (N1 - N0) / ((D1 - D0) / F)
            // multiplier *= cd->current.Frequency / 10000000;
            // tested, the frequency is not that useful for netdata
            // we get right results without it.
            algorithm = RRD_ALGORITHM_INCREMENTAL;
            break;

        case PERF_COUNTER_QUEUELEN_TYPE:
        case PERF_COUNTER_100NS_QUEUELEN_TYPE:
        case PERF_COUNTER_OBJ_TIME_QUEUELEN_TYPE:
        case PERF_COUNTER_LARGE_QUEUELEN_TYPE:
        case PERF_AVERAGE_BULK:  // normally not displayed
            // (N1 - N0) / (D1 - D0)
            algorithm = RRD_ALGORITHM_INCREMENTAL;
            break;

        case PERF_OBJ_TIME_TIMER:
        case PERF_COUNTER_TIMER:
        case PERF_100NSEC_TIMER:
        case PERF_PRECISION_SYSTEM_TIMER:
        case PERF_PRECISION_100NS_TIMER:
        case PERF_PRECISION_OBJECT_TIMER:
        case PERF_SAMPLE_FRACTION:
            // 100 * (N1 - N0) / (D1 - D0)
            multiplier *= 100;
            algorithm = RRD_ALGORITHM_INCREMENTAL;
            break;

        case PERF_COUNTER_TIMER_INV:
        case PERF_100NSEC_TIMER_INV:
            // 100 * (1 - ((N1 - N0) / (D1 - D0)))
            divider *= COLLECTED_NUMBER_PRECISION;
            algorithm = RRD_ALGORITHM_ABSOLUTE;
            break;

        case PERF_COUNTER_MULTI_TIMER:
            // 100 * ((N1 - N0) / ((D1 - D0) / TB)) / B1
            divider *= COLLECTED_NUMBER_PRECISION;
            algorithm = RRD_ALGORITHM_ABSOLUTE;
            break;

        case PERF_100NSEC_MULTI_TIMER:
            // 100 * ((N1 - N0) / (D1 - D0)) / B1
            divider *= COLLECTED_NUMBER_PRECISION;
            algorithm = RRD_ALGORITHM_ABSOLUTE;
            break;

        case PERF_COUNTER_MULTI_TIMER_INV:
        case PERF_100NSEC_MULTI_TIMER_INV:
            // 100 * (B1 - ((N1 - N0) / (D1 - D0)))
            divider *= COLLECTED_NUMBER_PRECISION;
            algorithm = RRD_ALGORITHM_ABSOLUTE;
            break;

        case PERF_COUNTER_RAWCOUNT:
        case PERF_COUNTER_LARGE_RAWCOUNT:
            // N as decimal
            algorithm = RRD_ALGORITHM_ABSOLUTE;
            break;

        case PERF_COUNTER_RAWCOUNT_HEX:
        case PERF_COUNTER_LARGE_RAWCOUNT_HEX:
            // N as hexadecimal
            algorithm = RRD_ALGORITHM_ABSOLUTE;
            break;

        case PERF_COUNTER_DELTA:
        case PERF_COUNTER_LARGE_DELTA:
            // N1 - N0
            algorithm = RRD_ALGORITHM_ABSOLUTE;
            break;

        case PERF_RAW_FRACTION:
        case PERF_LARGE_RAW_FRACTION:
            // 100 * N / B
            algorithm = RRD_ALGORITHM_ABSOLUTE;
            divider *= COLLECTED_NUMBER_PRECISION;
            break;

        case PERF_AVERAGE_TIMER:
            // ((N1 - N0) / TB) / (B1 - B0)
            // divider *= cd->current.Frequency / 10000000;
            algorithm = RRD_ALGORITHM_INCREMENTAL;
            break;

        case PERF_ELAPSED_TIME:
            // (D0 - N0) / F
            algorithm = RRD_ALGORITHM_ABSOLUTE;
            break;

        case PERF_COUNTER_TEXT:
        case PERF_SAMPLE_BASE:
        case PERF_AVERAGE_BASE:
        case PERF_COUNTER_MULTI_BASE:
        case PERF_RAW_BASE:
        case PERF_COUNTER_NODATA:
        case PERF_PRECISION_TIMESTAMP:
        default:
            break;
    }

    return rrddim_add(st, id, name, multiplier, divider, algorithm);
}

#define VALID_DELTA(cd) \
    ((cd)->previous.Time > 0 && (cd)->current.Data >= (cd)->previous.Data && (cd)->current.Time > (cd)->previous.Time)

collected_number perflib_rrddim_set_by_pointer(RRDSET *st, RRDDIM *rd, COUNTER_DATA *cd) {
    ULONGLONG numerator = 0;
    LONGLONG denominator = 0;
    double doubleValue = 0.0;
    collected_number value;

    switch(cd->current.CounterType) {
        case PERF_COUNTER_COUNTER:
        case PERF_SAMPLE_COUNTER:
        case PERF_COUNTER_BULK_COUNT:
            // (N1 - N0) / ((D1 - D0) / F)
            value = (collected_number)cd->current.Data;
            break;

        case PERF_COUNTER_QUEUELEN_TYPE:
        case PERF_COUNTER_100NS_QUEUELEN_TYPE:
        case PERF_COUNTER_OBJ_TIME_QUEUELEN_TYPE:
        case PERF_COUNTER_LARGE_QUEUELEN_TYPE:
        case PERF_AVERAGE_BULK:  // normally not displayed
            // (N1 - N0) / (D1 - D0)
            value = (collected_number)cd->current.Data;
            break;

        case PERF_OBJ_TIME_TIMER:
        case PERF_COUNTER_TIMER:
        case PERF_100NSEC_TIMER:
        case PERF_PRECISION_SYSTEM_TIMER:
        case PERF_PRECISION_100NS_TIMER:
        case PERF_PRECISION_OBJECT_TIMER:
        case PERF_SAMPLE_FRACTION:
            // 100 * (N1 - N0) / (D1 - D0)
            value = (collected_number)cd->current.Data;
            break;

        case PERF_COUNTER_TIMER_INV:
        case PERF_100NSEC_TIMER_INV:
            // 100 * (1 - ((N1 - N0) / (D1 - D0)))
            if(!VALID_DELTA(cd)) return 0;
            numerator = cd->current.Data - cd->previous.Data;
            denominator = cd->current.Time - cd->previous.Time;
            doubleValue = 100.0 * (1.0 - ((double)numerator / (double)denominator));
            // printf("Display value is (timer-inv): %f%%\n", doubleValue);
            value = (collected_number)(doubleValue * COLLECTED_NUMBER_PRECISION);
            break;

        case PERF_COUNTER_MULTI_TIMER:
            // 100 * ((N1 - N0) / ((D1 - D0) / TB)) / B1
            if(!VALID_DELTA(cd)) return 0;
            numerator = cd->current.Data - cd->previous.Data;
            denominator = cd->current.Time - cd->previous.Time;
            denominator /= cd->current.Frequency;
            doubleValue = 100.0 * ((double)numerator / (double)denominator) / cd->current.MultiCounterData;
            // printf("Display value is (multi-timer): %f%%\n", doubleValue);
            value = (collected_number)(doubleValue * COLLECTED_NUMBER_PRECISION);
            break;

        case PERF_100NSEC_MULTI_TIMER:
            // 100 * ((N1 - N0) / (D1 - D0)) / B1
            if(!VALID_DELTA(cd)) return 0;
            numerator = cd->current.Data - cd->previous.Data;
            denominator = cd->current.Time - cd->previous.Time;
            doubleValue = 100.0 * ((double)numerator / (double)denominator) / (double)cd->current.MultiCounterData;
            // printf("Display value is (100ns multi-timer): %f%%\n", doubleValue);
            value = (collected_number)(doubleValue * COLLECTED_NUMBER_PRECISION);
            break;

        case PERF_COUNTER_MULTI_TIMER_INV:
        case PERF_100NSEC_MULTI_TIMER_INV:
            // 100 * (B1 - ((N1 - N0) / (D1 - D0)))
            if(!VALID_DELTA(cd)) return 0;
            numerator = cd->current.Data - cd->previous.Data;
            denominator = cd->current.Time - cd->previous.Time;
            doubleValue = 100.0 * ((double)cd->current.MultiCounterData - ((double)numerator / (double)denominator));
            // printf("Display value is (multi-timer-inv): %f%%\n", doubleValue);
            value = (collected_number)(doubleValue * COLLECTED_NUMBER_PRECISION);
            break;

        case PERF_COUNTER_RAWCOUNT:
        case PERF_COUNTER_LARGE_RAWCOUNT:
            // N as decimal
            value = (collected_number)cd->current.Data;
            break;

        case PERF_COUNTER_RAWCOUNT_HEX:
        case PERF_COUNTER_LARGE_RAWCOUNT_HEX:
            // N as hexadecimal
            value = (collected_number)cd->current.Data;
            break;

        case PERF_COUNTER_DELTA:
        case PERF_COUNTER_LARGE_DELTA:
            if(!VALID_DELTA(cd)) return 0;
            value = (collected_number)(cd->current.Data - cd->previous.Data);
            break;

        case PERF_RAW_FRACTION:
        case PERF_LARGE_RAW_FRACTION:
            // 100 * N / B
            if(!cd->current.Time) return 0;
            doubleValue = 100.0 * (double)cd->current.Data / (double)cd->current.Time;
            // printf("Display value is (fraction): %f%%\n", doubleValue);
            value = (collected_number)(doubleValue * COLLECTED_NUMBER_PRECISION);
            break;

        default:
            return 0;
    }

    return rrddim_set_by_pointer(st, rd, value);
}

/*
double perflibCalculateValue(RAW_DATA *current, RAW_DATA *previous) {
    ULONGLONG numerator = 0;
    LONGLONG denominator = 0;
    double doubleValue = 0.0;
    DWORD dwordValue = 0;

    if (NULL == previous) {
        // Return error if the counter type requires two samples to calculate the value.
        switch (current->CounterType) {
            default:
                if (PERF_DELTA_COUNTER != (current->CounterType & PERF_DELTA_COUNTER))
                    break;
                __fallthrough;
                // fallthrough

            case PERF_AVERAGE_TIMER: // Special case.
            case PERF_AVERAGE_BULK:  // Special case.
                // printf(" > The counter type requires two samples but only one sample was provided.\n");
                return NAN;
        }
    }
    else {
        if (current->CounterType != previous->CounterType) {
            // printf(" > The samples have inconsistent counter types.\n");
            return NAN;
        }

        // Check for integer overflow or bad data from provider (the data from
        // sample 2 must be greater than the data from sample 1).
        if (current->Data < previous->Data)
        {
            // Can happen for various reasons. Commonly occurs with the Process counterset when
            // multiple processes have the same name and one of them starts or stops.
            // Normally you'll just drop the older sample and continue.
            // printf("> current (%llu) is smaller than previous (%llu).\n", current->Data, previous->Data);
            return NAN;
        }
    }

    switch (current->CounterType) {
        case PERF_COUNTER_COUNTER:
        case PERF_SAMPLE_COUNTER:
        case PERF_COUNTER_BULK_COUNT:
            // (N1 - N0) / ((D1 - D0) / F)
            numerator = current->Data - previous->Data;
            denominator = current->Time - previous->Time;
            dwordValue = (DWORD)(numerator / ((double)denominator / current->Frequency));
            //printf("Display value is (counter): %lu%s\n", (unsigned long)dwordValue,
            //       (previous->CounterType == PERF_SAMPLE_COUNTER) ? "" : "/sec");
            return (double)dwordValue;

        case PERF_COUNTER_QUEUELEN_TYPE:
        case PERF_COUNTER_100NS_QUEUELEN_TYPE:
        case PERF_COUNTER_OBJ_TIME_QUEUELEN_TYPE:
        case PERF_COUNTER_LARGE_QUEUELEN_TYPE:
        case PERF_AVERAGE_BULK:  // normally not displayed
            // (N1 - N0) / (D1 - D0)
            numerator = current->Data - previous->Data;
            denominator = current->Time - previous->Time;
            doubleValue = (double)numerator / denominator;
            if (previous->CounterType != PERF_AVERAGE_BULK) {
                // printf("Display value is (queuelen): %f\n", doubleValue);
                return doubleValue;
            }
            return NAN;

        case PERF_OBJ_TIME_TIMER:
        case PERF_COUNTER_TIMER:
        case PERF_100NSEC_TIMER:
        case PERF_PRECISION_SYSTEM_TIMER:
        case PERF_PRECISION_100NS_TIMER:
        case PERF_PRECISION_OBJECT_TIMER:
        case PERF_SAMPLE_FRACTION:
            // 100 * (N1 - N0) / (D1 - D0)
            numerator = current->Data - previous->Data;
            denominator = current->Time - previous->Time;
            doubleValue = (double)(100 * numerator) / denominator;
            // printf("Display value is (timer): %f%%\n", doubleValue);
            return doubleValue;

        case PERF_COUNTER_TIMER_INV:
            // 100 * (1 - ((N1 - N0) / (D1 - D0)))
            numerator = current->Data - previous->Data;
            denominator = current->Time - previous->Time;
            doubleValue = 100 * (1 - ((double)numerator / denominator));
            // printf("Display value is (timer-inv): %f%%\n", doubleValue);
            return doubleValue;

        case PERF_100NSEC_TIMER_INV:
            // 100 * (1- (N1 - N0) / (D1 - D0))
            numerator = current->Data - previous->Data;
            denominator = current->Time - previous->Time;
            doubleValue = 100 * (1 - (double)numerator / denominator);
            // printf("Display value is (100ns-timer-inv): %f%%\n", doubleValue);
            return doubleValue;

        case PERF_COUNTER_MULTI_TIMER:
            // 100 * ((N1 - N0) / ((D1 - D0) / TB)) / B1
            numerator = current->Data - previous->Data;
            denominator = current->Time - previous->Time;
            denominator /= current->Frequency;
            doubleValue = 100 * ((double)numerator / denominator) / current->MultiCounterData;
            // printf("Display value is (multi-timer): %f%%\n", doubleValue);
            return doubleValue;

        case PERF_100NSEC_MULTI_TIMER:
            // 100 * ((N1 - N0) / (D1 - D0)) / B1
            numerator = current->Data - previous->Data;
            denominator = current->Time - previous->Time;
            doubleValue = 100 * ((double)numerator / (double)denominator) / (double)current->MultiCounterData;
            // printf("Display value is (100ns multi-timer): %f%%\n", doubleValue);
            return doubleValue;

        case PERF_COUNTER_MULTI_TIMER_INV:
        case PERF_100NSEC_MULTI_TIMER_INV:
            // 100 * (B1 - ((N1 - N0) / (D1 - D0)))
            numerator = current->Data - previous->Data;
            denominator = current->Time - previous->Time;
            doubleValue = 100.0 * ((double)current->MultiCounterData - ((double)numerator / (double)denominator));
            // printf("Display value is (multi-timer-inv): %f%%\n", doubleValue);
            return doubleValue;

        case PERF_COUNTER_RAWCOUNT:
        case PERF_COUNTER_LARGE_RAWCOUNT:
            // N as decimal
            // printf("Display value is (rawcount): %llu\n", current->Data);
            return (double)current->Data;

        case PERF_COUNTER_RAWCOUNT_HEX:
        case PERF_COUNTER_LARGE_RAWCOUNT_HEX:
            // N as hexadecimal
            // printf("Display value is (hex): 0x%llx\n", current->Data);
            return (double)current->Data;

        case PERF_COUNTER_DELTA:
        case PERF_COUNTER_LARGE_DELTA:
            // N1 - N0
            // printf("Display value is (delta): %llu\n", current->Data - previous->Data);
            return (double)(current->Data - previous->Data);

        case PERF_RAW_FRACTION:
        case PERF_LARGE_RAW_FRACTION:
            // 100 * N / B
            doubleValue = 100.0 * (double)current->Data / (double)current->Time;
            // printf("Display value is (fraction): %f%%\n", doubleValue);
            return doubleValue;

        case PERF_AVERAGE_TIMER:
            // ((N1 - N0) / TB) / (B1 - B0)
            numerator = current->Data - previous->Data;
            denominator = current->Time - previous->Time;
            doubleValue = (double)numerator / (double)current->Frequency / (double)denominator;
            // printf("Display value is (average timer): %f seconds\n", doubleValue);
            return doubleValue;

        case PERF_ELAPSED_TIME:
            // (D0 - N0) / F
            doubleValue = (double)(current->Time - current->Data) / (double)current->Frequency;
            // printf("Display value is (elapsed time): %f seconds\n", doubleValue);
            return doubleValue;

        case PERF_COUNTER_TEXT:
        case PERF_SAMPLE_BASE:
        case PERF_AVERAGE_BASE:
        case PERF_COUNTER_MULTI_BASE:
        case PERF_RAW_BASE:
        case PERF_COUNTER_NODATA:
        case PERF_PRECISION_TIMESTAMP:
            // printf(" > Non-printing counter type: 0x%08x\n", current->CounterType);
            return NAN;
            break;

        default:
            // printf(" > Unrecognized counter type: 0x%08x\n", current->CounterType);
            return NAN;
            break;
    }
}
*/
