123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356 |
- #!/usr/bin/env bash
- # netdata
- # real-time performance and health monitoring, done right!
- # (C) 2023 Netdata Inc.
- # SPDX-License-Identifier: GPL-3.0-or-later
- #
- # This script is a helper to allow netdata collect tc data.
- # tc output parsing has been implemented in C, inside netdata
- # This script allows setting names to dimensions.
- export PATH="${PATH}:/sbin:/usr/sbin:/usr/local/sbin:@sbindir_POST@"
- export LC_ALL=C
- cmd_line="'${0}' $(printf "'%s' " "${@}")"
- # -----------------------------------------------------------------------------
- # logging
- PROGRAM_NAME="$(basename "${0}")"
- PROGRAM_NAME="${PROGRAM_NAME/.plugin/}"
- # these should be the same with syslog() priorities
- NDLP_EMERG=0 # system is unusable
- NDLP_ALERT=1 # action must be taken immediately
- NDLP_CRIT=2 # critical conditions
- NDLP_ERR=3 # error conditions
- NDLP_WARN=4 # warning conditions
- NDLP_NOTICE=5 # normal but significant condition
- NDLP_INFO=6 # informational
- NDLP_DEBUG=7 # debug-level messages
- # the max (numerically) log level we will log
- LOG_LEVEL=$NDLP_INFO
- set_log_min_priority() {
- case "${NETDATA_LOG_LEVEL,,}" in
- "emerg" | "emergency")
- LOG_LEVEL=$NDLP_EMERG
- ;;
- "alert")
- LOG_LEVEL=$NDLP_ALERT
- ;;
- "crit" | "critical")
- LOG_LEVEL=$NDLP_CRIT
- ;;
- "err" | "error")
- LOG_LEVEL=$NDLP_ERR
- ;;
- "warn" | "warning")
- LOG_LEVEL=$NDLP_WARN
- ;;
- "notice")
- LOG_LEVEL=$NDLP_NOTICE
- ;;
- "info")
- LOG_LEVEL=$NDLP_INFO
- ;;
- "debug")
- LOG_LEVEL=$NDLP_DEBUG
- ;;
- esac
- }
- set_log_min_priority
- log() {
- local level="${1}"
- shift 1
- [[ -n "$level" && -n "$LOG_LEVEL" && "$level" -gt "$LOG_LEVEL" ]] && return
- systemd-cat-native --log-as-netdata --newline="--NEWLINE--" <<EOFLOG
- INVOCATION_ID=${NETDATA_INVOCATION_ID}
- SYSLOG_IDENTIFIER=${PROGRAM_NAME}
- PRIORITY=${level}
- THREAD_TAG=tc-qos-helper
- ND_LOG_SOURCE=collector
- ND_REQUEST=${cmd_line}
- MESSAGE=${*//\\n/--NEWLINE--}
- EOFLOG
- # AN EMPTY LINE IS NEEDED ABOVE
- }
- info() {
- log "$NDLP_INFO" "${@}"
- }
- warning() {
- log "$NDLP_WARN" "${@}"
- }
- error() {
- log "$NDLP_ERR" "${@}"
- }
- fatal() {
- log "$NDLP_ALERT" "${@}"
- exit 1
- }
- debug() {
- log "$NDLP_DEBUG" "${@}"
- }
- # -----------------------------------------------------------------------------
- # find /var/run/fireqos
- # the default
- fireqos_run_dir="/var/run/fireqos"
- function realdir() {
- local r
- local t
- r="$1"
- t="$(readlink "$r")"
- while [ "$t" ]; do
- r=$(cd "$(dirname "$r")" && cd "$(dirname "$t")" && pwd -P)/$(basename "$t")
- t=$(readlink "$r")
- done
- dirname "$r"
- }
- if [ ! -d "${fireqos_run_dir}" ]; then
- # the fireqos executable - we will use it to find its config
- fireqos="$(command -v fireqos 2>/dev/null)"
- if [ -n "${fireqos}" ]; then
- fireqos_exec_dir="$(realdir "${fireqos}")"
- if [ -n "${fireqos_exec_dir}" ] && [ "${fireqos_exec_dir}" != "." ] && [ -f "${fireqos_exec_dir}/install.config" ]; then
- LOCALSTATEDIR=
- #shellcheck source=/dev/null
- source "${fireqos_exec_dir}/install.config"
- if [ -d "${LOCALSTATEDIR}/run/fireqos" ]; then
- fireqos_run_dir="${LOCALSTATEDIR}/run/fireqos"
- else
- warning "FireQOS is installed as '${fireqos}', its installation config at '${fireqos_exec_dir}/install.config' specifies local state data at '${LOCALSTATEDIR}/run/fireqos', but this directory is not found or is not readable (check the permissions of its parents)."
- fi
- else
- warning "Although FireQOS is installed on this system as '${fireqos}', I cannot find/read its installation configuration at '${fireqos_exec_dir}/install.config'."
- fi
- else
- warning "FireQOS is not installed on this system. Use FireQOS to apply traffic QoS and expose the class names to netdata. Check https://github.com/netdata/netdata/tree/master/collectors/tc.plugin#tcplugin"
- fi
- fi
- # -----------------------------------------------------------------------------
- [ -z "${NETDATA_PLUGINS_DIR}" ] && NETDATA_PLUGINS_DIR="$(dirname "${0}")"
- [ -z "${NETDATA_USER_CONFIG_DIR}" ] && NETDATA_USER_CONFIG_DIR="@configdir_POST@"
- [ -z "${NETDATA_STOCK_CONFIG_DIR}" ] && NETDATA_STOCK_CONFIG_DIR="@libconfigdir_POST@"
- plugins_dir="${NETDATA_PLUGINS_DIR}"
- tc="$(command -v tc 2>/dev/null)"
- # -----------------------------------------------------------------------------
- # user configuration
- # time in seconds to refresh QoS class/qdisc names
- qos_get_class_names_every=120
- # time in seconds to exit - netdata will restart the script
- qos_exit_every=3600
- # what to use? classes or qdiscs?
- tc_show="qdisc" # can also be "class"
- # -----------------------------------------------------------------------------
- # check if we have a valid number for interval
- t=${1}
- update_every=$((t))
- [ $((update_every)) -lt 1 ] && update_every=${NETDATA_UPDATE_EVERY}
- [ $((update_every)) -lt 1 ] && update_every=1
- # -----------------------------------------------------------------------------
- # allow the user to override our defaults
- for CONFIG in "${NETDATA_STOCK_CONFIG_DIR}/tc-qos-helper.conf" "${NETDATA_USER_CONFIG_DIR}/tc-qos-helper.conf"; do
- if [ -f "${CONFIG}" ]; then
- info "Loading config file '${CONFIG}'..."
- #shellcheck source=/dev/null
- source "${CONFIG}" || error "Failed to load config file '${CONFIG}'."
- else
- warning "Cannot find file '${CONFIG}'."
- fi
- done
- case "${tc_show}" in
- qdisc | class) ;;
- *)
- error "tc_show variable can be either 'qdisc' or 'class' but is set to '${tc_show}'. Assuming it is 'qdisc'."
- tc_show="qdisc"
- ;;
- esac
- # -----------------------------------------------------------------------------
- # default sleep function
- LOOPSLEEPMS_LASTWORK=0
- loopsleepms() {
- sleep "$1"
- }
- # if found and included, this file overwrites loopsleepms()
- # with a high resolution timer function for precise looping.
- #shellcheck source=/dev/null
- . "${plugins_dir}/loopsleepms.sh.inc"
- # -----------------------------------------------------------------------------
- # final checks we can run
- if [ -z "${tc}" ] || [ ! -x "${tc}" ]; then
- fatal "cannot find command 'tc' in this system."
- fi
- tc_devices=
- fix_names=
- # -----------------------------------------------------------------------------
- setclassname() {
- if [ "${tc_show}" = "qdisc" ]; then
- echo "SETCLASSNAME $4 $2"
- else
- echo "SETCLASSNAME $3 $2"
- fi
- }
- show_tc_cls() {
- [ "${tc_show}" = "qdisc" ] && return 1
- local x="${1}"
- if [ -f /etc/iproute2/tc_cls ]; then
- local classid name rest
- while read -r classid name rest; do
- if [ -z "${classid}" ] ||
- [ -z "${name}" ] ||
- [ "${classid}" = "#" ] ||
- [ "${name}" = "#" ] ||
- [ "${classid:0:1}" = "#" ] ||
- [ "${name:0:1}" = "#" ]; then
- continue
- fi
- setclassname "" "${name}" "${classid}"
- done </etc/iproute2/tc_cls
- return 0
- fi
- return 1
- }
- show_fireqos_names() {
- local x="${1}" name n interface_dev interface_classes_monitor
- if [ -f "${fireqos_run_dir}/ifaces/${x}" ]; then
- name="$(<"${fireqos_run_dir}/ifaces/${x}")"
- echo "SETDEVICENAME ${name}" || exit
- #shellcheck source=/dev/null
- source "${fireqos_run_dir}/${name}.conf"
- for n in ${interface_classes_monitor}; do
- # shellcheck disable=SC2086
- setclassname ${n//|/ }
- done
- [ -n "${interface_dev}" ] && echo "SETDEVICEGROUP ${interface_dev}" || exit
- return 0
- fi
- return 1
- }
- show_tc() {
- local x="${1}"
- echo "BEGIN ${x}" || exit
- # netdata can parse the output of tc
- ${tc} -s ${tc_show} show dev "${x}"
- # check FireQOS names for classes
- if [ -n "${fix_names}" ]; then
- show_fireqos_names "${x}" || show_tc_cls "${x}"
- fi
- echo "END ${x}" || exit
- }
- find_tc_devices() {
- local count=0 devs dev rest l
- # find all the devices in the system
- # without forking
- while IFS=":| " read -r dev rest; do
- count=$((count + 1))
- [ ${count} -le 2 ] && continue
- devs="${devs} ${dev}"
- done </proc/net/dev
- # from all the devices find the ones
- # that have QoS defined
- # unfortunately, one fork per device cannot be avoided
- tc_devices=
- for dev in ${devs}; do
- l="$(${tc} class show dev "${dev}" 2>/dev/null)"
- [ -n "${l}" ] && tc_devices="${tc_devices} ${dev}"
- done
- }
- # update devices and class names
- # once every 2 minutes
- names_every=$((qos_get_class_names_every / update_every))
- # exit this script every hour
- # it will be restarted automatically
- exit_after=$((qos_exit_every / update_every))
- c=0
- gc=0
- while true; do
- fix_names=
- c=$((c + 1))
- gc=$((gc + 1))
- if [ ${c} -le 1 ] || [ ${c} -ge ${names_every} ]; then
- c=1
- fix_names="YES"
- find_tc_devices
- fi
- for d in ${tc_devices}; do
- show_tc "${d}"
- done
- echo "WORKTIME ${LOOPSLEEPMS_LASTWORK}" || exit
- loopsleepms "${update_every}"
- [ ${gc} -gt ${exit_after} ] && exit 0
- done
|