#!@@GOODSH@@
# -*- sh -*-

: << =cut

=head1 NAME

cpuspeed - Plugin to monitor CPU speed

=head1 APPLICABLE SYSTEMS

Linux systems with a working CPU speed governor/stats kernel module
that can be accessed through the /sys filesystem.

=head1 CONFIGURATION

None needed by default.

You can set one environment variable to modify the plugins behaviour:

  [cpuspeed]
     env.scaleto100 yes

Show the frequency as a percentage instead of absolute frequency.
If set the "yes" the plugin sets up a CDEF to change the speed in Hz
to percent.

If you set or unset this the whole time series will be shown in the
same way, either as Hz or percent (as the graphs are updated).

=head1 INTERPRETATION

The plugin supports two sources of information.

The optimal source of information is the "acpi-cpufreq" kernel module.
It provides access to the accumulated time a CPU spent in a specific
speed state (see /sys/devices/system/cpu/*/cpufreq/stats/time_in_state).
This value is fine-grained and represents the *average* CPU speed for
every data collection period.
This data is available only for non-Intel CPUs.

An alternative (sub-optimal) source of information is provided by the
"intel_pstate" driver (typically built into a kernel).
This driver delivers only *instant* information about the CPU speed
(at the time of the munin data collection). This is not necessarily
representative for the real CPU speed history.
The "pstate" driver conflicts with "acpi-cpufreq".  Thus hosts with an
Intel CPU are usually stuck with "intel_pstate" as a less optimal
source of frequency information.

=head1 BUGS

Nah.

=head1 AUTHOR

Nicolai Langfeldt

=head1 LICENSE

GPLv2

=head1 MAGIC MARKERS

 #%# family=auto
 #%# capabilities=autoconf

=cut

. "$MUNIN_LIBDIR/plugins/plugin.sh"

scaleto100=${scaleto100:-no}
ACPI_CPUFREQ_INDICATOR_FILENAME=/sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state
INTEL_PSTATE_INDICATOR_FILENAME=/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq


if [ "$1" = "autoconf" ]; then
    if [ -r "$ACPI_CPUFREQ_INDICATOR_FILENAME" ]; then
        # "acpi-cpufreq" is available
        echo yes
    elif [ -r "$INTEL_PSTATE_INDICATOR_FILENAME" ]; then
        # the "intel_pstate" driver is available (no averaging, but at least snapshot values)
        echo yes
    else
        echo "no (neither $ACPI_CPUFREQ_INDICATOR_FILENAME nor $INTEL_PSTATE_INDICATOR_FILENAME is readable)"
    fi
    exit 0
fi


if [ "$1" = "config" ]; then
    echo "graph_title CPU frequency scaling"
    echo "graph_args --base 1000"
    echo "graph_category system"

    if [ "$scaleto100" = "yes" ]; then
        echo "graph_vlabel %"
        echo "graph_scale no"
    else
        echo "graph_vlabel Hz"
    fi
    # the visualized data depends on the available data source
    if [ -r "$ACPI_CPUFREQ_INDICATOR_FILENAME" ]; then
        graph_info="This graph shows the average running speed of each CPU."
        field_type="DERIVE"
    elif [ -r "$INTEL_PSTATE_INDICATOR_FILENAME" ]; then
        graph_info="This graph shows the current speed of the CPU at the time of the data retrieval (not its average). This is a limitiation of the 'intel_pstate' driver."
        field_type="GAUGE"
    else
        graph_info="The properties of this data source are not documented."
        field_type="DERIVE"
    fi
    echo "graph_info $graph_info"

    for c in /sys/devices/system/cpu/cpu[0-9]*; do
        if [ ! -r "$c/cpufreq/stats/time_in_state" ] && [ ! -r "$c/cpufreq/scaling_cur_freq" ]; then
            continue
        fi

        N=${c##*/cpu}

        echo "cpu$N.label CPU $N"
        echo "cpu$N.type $field_type"

        if [ -r "$c/cpufreq/cpuinfo_max_freq" ]; then

            MAXHZ=$(cat "$c/cpufreq/cpuinfo_max_freq")
            # Adding 10% to $MAXHZ, to cope with polling jitters
            # See bug D#615957
            MAXHZ=$(( MAXHZ + MAXHZ / 10 ))
            echo "cpu$N.max $MAXHZ"

            if [ "$scaleto100" = "yes" ]; then
                echo "cpu$N.cdef cpu$N,1000,*,$MAXHZ,/"
            else
                echo "cpu$N.cdef cpu$N,1000,*"
            fi
        fi

        if [ -r "$c/cpufreq/cpuinfo_min_freq" ]; then
            MINHZ=$(cat "$c/cpufreq/cpuinfo_min_freq")
            echo "cpu$N.min $MINHZ"
        fi

        print_warning "cpu$N"
        print_critical "cpu$N"

    done | sort -V
    if [ "${MUNIN_CAP_DIRTYCONFIG:-0}" != 1 ]; then exit 0; fi
fi

for c in /sys/devices/system/cpu/cpu[0-9]*; do
    N=${c##*/cpu}
    if [ -r "$c/cpufreq/stats/time_in_state" ]; then
        value=$(awk '{ cycles += $1 * $2 } END { printf("%.0f", cycles / 100); }' "$c/cpufreq/stats/time_in_state")
    elif [ -r "$c/cpufreq/scaling_cur_freq" ]; then
        value=$(cat "$c/cpufreq/scaling_cur_freq")
    else
        continue
    fi
    printf 'cpu%d.value %s\n' "$N" "$value"
done
