master
in 352 lines 8.32 KB
Raw
1 #!/usr/bin/env bash
2 # SPDX-License-Identifier: GPL-3.0-or-later
3 #
4 # This script is a helper to allow netdata collect tc data.
5 # tc output parsing has been implemented in C, inside netdata
6 # This script allows setting names to dimensions.
7
8 export PATH="${PATH}:/sbin:/usr/sbin:/usr/local/sbin:@sbindir_POST@"
9 export LC_ALL=C
10
11 cmd_line="'${0}' $(printf "'%s' " "${@}")"
12
13 # -----------------------------------------------------------------------------
14 # logging
15
16 PROGRAM_NAME="$(basename "${0}")"
17 PROGRAM_NAME="${PROGRAM_NAME/.plugin/}"
18
19 # these should be the same with syslog() priorities
20 NDLP_EMERG=0 # system is unusable
21 NDLP_ALERT=1 # action must be taken immediately
22 NDLP_CRIT=2 # critical conditions
23 NDLP_ERR=3 # error conditions
24 NDLP_WARN=4 # warning conditions
25 NDLP_NOTICE=5 # normal but significant condition
26 NDLP_INFO=6 # informational
27 NDLP_DEBUG=7 # debug-level messages
28
29 # the max (numerically) log level we will log
30 LOG_LEVEL=$NDLP_INFO
31
32 set_log_min_priority() {
33 case "${NETDATA_LOG_LEVEL,,}" in
34 "emerg" | "emergency")
35 LOG_LEVEL=$NDLP_EMERG
36 ;;
37
38 "alert")
39 LOG_LEVEL=$NDLP_ALERT
40 ;;
41
42 "crit" | "critical")
43 LOG_LEVEL=$NDLP_CRIT
44 ;;
45
46 "err" | "error")
47 LOG_LEVEL=$NDLP_ERR
48 ;;
49
50 "warn" | "warning")
51 LOG_LEVEL=$NDLP_WARN
52 ;;
53
54 "notice")
55 LOG_LEVEL=$NDLP_NOTICE
56 ;;
57
58 "info")
59 LOG_LEVEL=$NDLP_INFO
60 ;;
61
62 "debug")
63 LOG_LEVEL=$NDLP_DEBUG
64 ;;
65 esac
66 }
67
68 set_log_min_priority
69
70 log() {
71 local level="${1}"
72 shift 1
73
74 [[ -n "$level" && -n "$LOG_LEVEL" && "$level" -gt "$LOG_LEVEL" ]] && return
75
76 systemd-cat-native --log-as-netdata <<EOFLOG
77 INVOCATION_ID=${NETDATA_INVOCATION_ID}
78 SYSLOG_IDENTIFIER=${PROGRAM_NAME}
79 PRIORITY=${level}
80 THREAD_TAG=tc-qos-helper
81 ND_LOG_SOURCE=collector
82 ND_REQUEST=${cmd_line}
83 MESSAGE=${*//$'\n'/\\n}
84
85 EOFLOG
86 # AN EMPTY LINE IS NEEDED ABOVE
87 }
88
89 info() {
90 log "$NDLP_INFO" "${@}"
91 }
92
93 warning() {
94 log "$NDLP_WARN" "${@}"
95 }
96
97 error() {
98 log "$NDLP_ERR" "${@}"
99 }
100
101 fatal() {
102 log "$NDLP_ALERT" "${@}"
103 exit 1
104 }
105
106 debug() {
107 log "$NDLP_DEBUG" "${@}"
108 }
109
110 # -----------------------------------------------------------------------------
111 # find /var/run/fireqos
112
113 # the default
114 fireqos_run_dir="/var/run/fireqos"
115
116 function realdir() {
117 local r
118 local t
119 r="$1"
120 t="$(readlink "$r")"
121
122 while [ "$t" ]; do
123 r=$(cd "$(dirname "$r")" && cd "$(dirname "$t")" && pwd -P)/$(basename "$t")
124 t=$(readlink "$r")
125 done
126
127 dirname "$r"
128 }
129
130 if [ ! -d "${fireqos_run_dir}" ]; then
131
132 # the fireqos executable - we will use it to find its config
133 fireqos="$(command -v fireqos 2>/dev/null)"
134
135 if [ -n "${fireqos}" ]; then
136
137 fireqos_exec_dir="$(realdir "${fireqos}")"
138
139 if [ -n "${fireqos_exec_dir}" ] && [ "${fireqos_exec_dir}" != "." ] && [ -f "${fireqos_exec_dir}/install.config" ]; then
140 LOCALSTATEDIR=
141 #shellcheck source=/dev/null
142 source "${fireqos_exec_dir}/install.config"
143
144 if [ -d "${LOCALSTATEDIR}/run/fireqos" ]; then
145 fireqos_run_dir="${LOCALSTATEDIR}/run/fireqos"
146 else
147 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)."
148 fi
149 else
150 warning "Although FireQOS is installed on this system as '${fireqos}', I cannot find/read its installation configuration at '${fireqos_exec_dir}/install.config'."
151 fi
152 else
153 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/src/collectors/tc.plugin#tcplugin"
154 fi
155 fi
156
157 # -----------------------------------------------------------------------------
158
159 [ -z "${NETDATA_PLUGINS_DIR}" ] && NETDATA_PLUGINS_DIR="$(dirname "${0}")"
160 [ -z "${NETDATA_USER_CONFIG_DIR}" ] && NETDATA_USER_CONFIG_DIR="@configdir_POST@"
161 [ -z "${NETDATA_STOCK_CONFIG_DIR}" ] && NETDATA_STOCK_CONFIG_DIR="@libconfigdir_POST@"
162
163 plugins_dir="${NETDATA_PLUGINS_DIR}"
164 tc="$(command -v tc 2>/dev/null)"
165
166 # -----------------------------------------------------------------------------
167 # user configuration
168
169 # time in seconds to refresh QoS class/qdisc names
170 qos_get_class_names_every=120
171
172 # time in seconds to exit - netdata will restart the script
173 qos_exit_every=3600
174
175 # what to use? classes or qdiscs?
176 tc_show="qdisc" # can also be "class"
177
178 # -----------------------------------------------------------------------------
179 # check if we have a valid number for interval
180
181 t=${1}
182 update_every=$((t))
183 [ $((update_every)) -lt 1 ] && update_every=${NETDATA_UPDATE_EVERY}
184 [ $((update_every)) -lt 1 ] && update_every=1
185
186 # -----------------------------------------------------------------------------
187 # allow the user to override our defaults
188
189 for CONFIG in "${NETDATA_STOCK_CONFIG_DIR}/tc-qos-helper.conf" "${NETDATA_USER_CONFIG_DIR}/tc-qos-helper.conf"; do
190 if [ -f "${CONFIG}" ]; then
191 info "Loading config file '${CONFIG}'..."
192 #shellcheck source=/dev/null
193 source "${CONFIG}" || error "Failed to load config file '${CONFIG}'."
194 else
195 warning "Cannot find file '${CONFIG}'."
196 fi
197 done
198
199 case "${tc_show}" in
200 qdisc | class) ;;
201
202 *)
203 error "tc_show variable can be either 'qdisc' or 'class' but is set to '${tc_show}'. Assuming it is 'qdisc'."
204 tc_show="qdisc"
205 ;;
206 esac
207
208 # -----------------------------------------------------------------------------
209 # default sleep function
210
211 LOOPSLEEPMS_LASTWORK=0
212 loopsleepms() {
213 sleep "$1"
214 }
215
216 # if found and included, this file overwrites loopsleepms()
217 # with a high resolution timer function for precise looping.
218 #shellcheck source=/dev/null
219 . "${plugins_dir}/loopsleepms.sh.inc"
220
221 # -----------------------------------------------------------------------------
222 # final checks we can run
223
224 if [ -z "${tc}" ] || [ ! -x "${tc}" ]; then
225 fatal "cannot find command 'tc' in this system."
226 fi
227
228 tc_devices=
229 fix_names=
230
231 # -----------------------------------------------------------------------------
232
233 setclassname() {
234 if [ "${tc_show}" = "qdisc" ]; then
235 echo "SETCLASSNAME $4 $2"
236 else
237 echo "SETCLASSNAME $3 $2"
238 fi
239 }
240
241 show_tc_cls() {
242 [ "${tc_show}" = "qdisc" ] && return 1
243
244 local x="${1}"
245
246 if [ -f /etc/iproute2/tc_cls ]; then
247 local classid name rest
248 while read -r classid name rest; do
249 if [ -z "${classid}" ] ||
250 [ -z "${name}" ] ||
251 [ "${classid}" = "#" ] ||
252 [ "${name}" = "#" ] ||
253 [ "${classid:0:1}" = "#" ] ||
254 [ "${name:0:1}" = "#" ]; then
255 continue
256 fi
257 setclassname "" "${name}" "${classid}"
258 done </etc/iproute2/tc_cls
259 return 0
260 fi
261 return 1
262 }
263
264 show_fireqos_names() {
265 local x="${1}" name n interface_dev interface_classes_monitor
266
267 if [ -f "${fireqos_run_dir}/ifaces/${x}" ]; then
268 name="$(<"${fireqos_run_dir}/ifaces/${x}")"
269 echo "SETDEVICENAME ${name}" || exit
270
271 #shellcheck source=/dev/null
272 source "${fireqos_run_dir}/${name}.conf"
273 for n in ${interface_classes_monitor}; do
274 # shellcheck disable=SC2086
275 setclassname ${n//|/ }
276 done
277 [ -n "${interface_dev}" ] && echo "SETDEVICEGROUP ${interface_dev}" || exit
278
279 return 0
280 fi
281
282 return 1
283 }
284
285 show_tc() {
286 local x="${1}"
287
288 echo "BEGIN ${x}" || exit
289
290 # netdata can parse the output of tc
291 ${tc} -s ${tc_show} show dev "${x}"
292
293 # check FireQOS names for classes
294 if [ -n "${fix_names}" ]; then
295 show_fireqos_names "${x}" || show_tc_cls "${x}"
296 fi
297
298 echo "END ${x}" || exit
299 }
300
301 find_tc_devices() {
302 local count=0 devs dev rest l
303
304 # find all the devices in the system
305 # without forking
306 while IFS=":| " read -r dev rest; do
307 count=$((count + 1))
308 [ ${count} -le 2 ] && continue
309 devs="${devs} ${dev}"
310 done </proc/net/dev
311
312 # from all the devices find the ones
313 # that have QoS defined
314 # unfortunately, one fork per device cannot be avoided
315 tc_devices=
316 for dev in ${devs}; do
317 l="$(${tc} class show dev "${dev}" 2>/dev/null)"
318 [ -n "${l}" ] && tc_devices="${tc_devices} ${dev}"
319 done
320 }
321
322 # update devices and class names
323 # once every 2 minutes
324 names_every=$((qos_get_class_names_every / update_every))
325
326 # exit this script every hour
327 # it will be restarted automatically
328 exit_after=$((qos_exit_every / update_every))
329
330 c=0
331 gc=0
332 while true; do
333 fix_names=
334 c=$((c + 1))
335 gc=$((gc + 1))
336
337 if [ ${c} -le 1 ] || [ ${c} -ge ${names_every} ]; then
338 c=1
339 fix_names="YES"
340 find_tc_devices
341 fi
342
343 for d in ${tc_devices}; do
344 show_tc "${d}"
345 done
346
347 echo "WORKTIME ${LOOPSLEEPMS_LASTWORK}" || exit
348
349 loopsleepms "${update_every}"
350
351 [ ${gc} -gt ${exit_after} ] && exit 0
352 done