tc-qos-helper.sh.in 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. #!/usr/bin/env bash
  2. # netdata
  3. # real-time performance and health monitoring, done right!
  4. # (C) 2017 Costa Tsaousis <costa@tsaousis.gr>
  5. # SPDX-License-Identifier: GPL-3.0-or-later
  6. #
  7. # This script is a helper to allow netdata collect tc data.
  8. # tc output parsing has been implemented in C, inside netdata
  9. # This script allows setting names to dimensions.
  10. export PATH="${PATH}:/sbin:/usr/sbin:/usr/local/sbin"
  11. export LC_ALL=C
  12. # -----------------------------------------------------------------------------
  13. # logging functions
  14. PROGRAM_FILE="$0"
  15. PROGRAM_NAME="$(basename $0)"
  16. PROGRAM_NAME="${PROGRAM_NAME/.plugin}"
  17. logdate() {
  18. date "+%Y-%m-%d %H:%M:%S"
  19. }
  20. log() {
  21. local status="${1}"
  22. shift
  23. echo >&2 "$(logdate): ${PROGRAM_NAME}: ${status}: ${*}"
  24. }
  25. warning() {
  26. log WARNING "${@}"
  27. }
  28. error() {
  29. log ERROR "${@}"
  30. }
  31. info() {
  32. log INFO "${@}"
  33. }
  34. fatal() {
  35. log FATAL "${@}"
  36. exit 1
  37. }
  38. debug=0
  39. debug() {
  40. [ $debug -eq 1 ] && log DEBUG "${@}"
  41. }
  42. # -----------------------------------------------------------------------------
  43. # find /var/run/fireqos
  44. # the default
  45. fireqos_run_dir="/var/run/fireqos"
  46. function realdir {
  47. local r="$1"
  48. local t=$(readlink "$r")
  49. while [ "$t" ]
  50. do
  51. r=$(cd $(dirname "$r") && cd $(dirname "$t") && pwd -P)/$(basename "$t")
  52. t=$(readlink "$r")
  53. done
  54. dirname "$r"
  55. }
  56. if [ ! -d "${fireqos_run_dir}" ]
  57. then
  58. # the fireqos executable - we will use it to find its config
  59. fireqos="$(which fireqos 2>/dev/null || command -v fireqos 2>/dev/null)"
  60. if [ ! -z "${fireqos}" ]
  61. then
  62. fireqos_exec_dir="$(realdir ${fireqos})"
  63. if [ ! -z "${fireqos_exec_dir}" -a "${fireqos_exec_dir}" != "." -a -f "${fireqos_exec_dir}/install.config" ]
  64. then
  65. LOCALSTATEDIR=
  66. source "${fireqos_exec_dir}/install.config"
  67. if [ -d "${LOCALSTATEDIR}/run/fireqos" ]
  68. then
  69. fireqos_run_dir="${LOCALSTATEDIR}/run/fireqos"
  70. else
  71. 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)."
  72. fi
  73. else
  74. warning "Although FireQoS is installed on this system as '${fireqos}', I cannot find/read its installation configuration at '${fireqos_exec_dir}/install.config'."
  75. fi
  76. else
  77. 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"
  78. fi
  79. fi
  80. # -----------------------------------------------------------------------------
  81. [ -z "${NETDATA_PLUGINS_DIR}" ] && NETDATA_PLUGINS_DIR="$(dirname "${0}")"
  82. [ -z "${NETDATA_USER_CONFIG_DIR}" ] && NETDATA_USER_CONFIG_DIR="@configdir_POST@"
  83. [ -z "${NETDATA_STOCK_CONFIG_DIR}" ] && NETDATA_STOCK_CONFIG_DIR="@libconfigdir_POST@"
  84. plugins_dir="${NETDATA_PLUGINS_DIR}"
  85. tc="$(which tc 2>/dev/null || command -v tc 2>/dev/null)"
  86. # -----------------------------------------------------------------------------
  87. # user configuration
  88. # time in seconds to refresh QoS class/qdisc names
  89. qos_get_class_names_every=120
  90. # time in seconds to exit - netdata will restart the script
  91. qos_exit_every=3600
  92. # what to use? classes or qdiscs?
  93. tc_show="qdisc" # can also be "class"
  94. # -----------------------------------------------------------------------------
  95. # check if we have a valid number for interval
  96. t=${1}
  97. update_every=$((t))
  98. [ $((update_every)) -lt 1 ] && update_every=${NETDATA_UPDATE_EVERY}
  99. [ $((update_every)) -lt 1 ] && update_every=1
  100. # -----------------------------------------------------------------------------
  101. # allow the user to override our defaults
  102. for CONFIG in "${NETDATA_STOCK_CONFIG_DIR}/tc-qos-helper.conf" "${NETDATA_USER_CONFIG_DIR}/tc-qos-helper.conf"
  103. do
  104. if [ -f "${CONFIG}" ]
  105. then
  106. info "Loading config file '${CONFIG}'..."
  107. source "${CONFIG}"
  108. [ $? -ne 0 ] && error "Failed to load config file '${CONFIG}'."
  109. else
  110. warning "Cannot find file '${CONFIG}'."
  111. fi
  112. done
  113. case "${tc_show}" in
  114. qdisc|class)
  115. ;;
  116. *)
  117. error "tc_show variable can be either 'qdisc' or 'class' but is set to '${tc_show}'. Assuming it is 'qdisc'."
  118. tc_show="qdisc"
  119. ;;
  120. esac
  121. # -----------------------------------------------------------------------------
  122. # default sleep function
  123. LOOPSLEEPMS_LASTWORK=0
  124. loopsleepms() {
  125. sleep $1
  126. }
  127. # if found and included, this file overwrites loopsleepms()
  128. # with a high resolution timer function for precise looping.
  129. . "${plugins_dir}/loopsleepms.sh.inc"
  130. # -----------------------------------------------------------------------------
  131. # final checks we can run
  132. if [ -z "${tc}" -o ! -x "${tc}" ]
  133. then
  134. fatal "cannot find command 'tc' in this system."
  135. fi
  136. tc_devices=
  137. fix_names=
  138. # -----------------------------------------------------------------------------
  139. setclassname() {
  140. if [ "${tc_show}" = "qdisc" ]
  141. then
  142. echo "SETCLASSNAME $4 $2"
  143. else
  144. echo "SETCLASSNAME $3 $2"
  145. fi
  146. }
  147. show_tc_cls() {
  148. [ "${tc_show}" = "qdisc" ] && return 1
  149. local x="${1}"
  150. if [ -f /etc/iproute2/tc_cls ]
  151. then
  152. local classid name rest
  153. while read classid name rest
  154. do
  155. [ -z "${classid}" -o -z "${name}" -o "${classid}" = "#" -o "${name}" = "#" -o "${classid:0:1}" = "#" -o "${name:0:1}" = "#" ] && continue
  156. setclassname "" "${name}" "${classid}"
  157. done </etc/iproute2/tc_cls
  158. return 0
  159. fi
  160. return 1
  161. }
  162. show_fireqos_names() {
  163. local x="${1}" name n interface_dev interface_classes interface_classes_monitor
  164. if [ -f "${fireqos_run_dir}/ifaces/${x}" ]
  165. then
  166. name="$(<"${fireqos_run_dir}/ifaces/${x}")"
  167. echo "SETDEVICENAME ${name}"
  168. interface_dev=
  169. interface_classes=
  170. interface_classes_monitor=
  171. source "${fireqos_run_dir}/${name}.conf"
  172. for n in ${interface_classes_monitor}
  173. do
  174. setclassname ${n//|/ }
  175. done
  176. [ ! -z "${interface_dev}" ] && echo "SETDEVICEGROUP ${interface_dev}"
  177. return 0
  178. fi
  179. return 1
  180. }
  181. show_tc() {
  182. local x="${1}"
  183. echo "BEGIN ${x}"
  184. # netdata can parse the output of tc
  185. ${tc} -s ${tc_show} show dev ${x}
  186. # check FireQOS names for classes
  187. if [ ! -z "${fix_names}" ]
  188. then
  189. show_fireqos_names "${x}" || show_tc_cls "${x}"
  190. fi
  191. echo "END ${x}"
  192. }
  193. find_tc_devices() {
  194. local count=0 devs= dev rest l
  195. # find all the devices in the system
  196. # without forking
  197. while IFS=":| " read dev rest
  198. do
  199. count=$((count + 1))
  200. [ ${count} -le 2 ] && continue
  201. devs="${devs} ${dev}"
  202. done </proc/net/dev
  203. # from all the devices find the ones
  204. # that have QoS defined
  205. # unfortunately, one fork per device cannot be avoided
  206. tc_devices=
  207. for dev in ${devs}
  208. do
  209. l="$(${tc} class show dev ${dev} 2>/dev/null)"
  210. [ ! -z "${l}" ] && tc_devices="${tc_devices} ${dev}"
  211. done
  212. }
  213. # update devices and class names
  214. # once every 2 minutes
  215. names_every=$((qos_get_class_names_every / update_every))
  216. # exit this script every hour
  217. # it will be restarted automatically
  218. exit_after=$((qos_exit_every / update_every))
  219. c=0
  220. gc=0
  221. while [ 1 ]
  222. do
  223. fix_names=
  224. c=$((c + 1))
  225. gc=$((gc + 1))
  226. if [ ${c} -le 1 -o ${c} -ge ${names_every} ]
  227. then
  228. c=1
  229. fix_names="YES"
  230. find_tc_devices
  231. fi
  232. for d in ${tc_devices}
  233. do
  234. show_tc ${d}
  235. done
  236. echo "WORKTIME ${LOOPSLEEPMS_LASTWORK}"
  237. loopsleepms ${update_every}
  238. [ ${gc} -gt ${exit_after} ] && exit 0
  239. done