| 1 | #!/usr/bin/env bash |
| 2 | |
| 3 | # SPDX-License-Identifier: GPL-3.0-or-later |
| 4 | |
| 5 | # This is a simple exporting proxy, written in BASH, using the nc command. |
| 6 | # Run the script without any parameters for help. |
| 7 | |
| 8 | MODE="${1}" |
| 9 | MY_PORT="${2}" |
| 10 | EXPORTING_HOST="${3}" |
| 11 | EXPORTING_PORT="${4}" |
| 12 | FILE="${NETDATA_NC_EXPORTING_DIR-/tmp}/netdata-nc-exporting-${MY_PORT}" |
| 13 | |
| 14 | log() { |
| 15 | logger --stderr --id=$$ --tag "netdata-nc-exporting" "${*}" |
| 16 | } |
| 17 | |
| 18 | mync() { |
| 19 | local ret |
| 20 | |
| 21 | log "Running: nc ${*}" |
| 22 | nc "${@}" |
| 23 | ret=$? |
| 24 | |
| 25 | log "nc stopped with return code ${ret}." |
| 26 | |
| 27 | return ${ret} |
| 28 | } |
| 29 | |
| 30 | listen_save_replay_forever() { |
| 31 | local file="${1}" port="${2}" real_exporting_host="${3}" real_exporting_port="${4}" ret delay=1 started ended |
| 32 | |
| 33 | while true |
| 34 | do |
| 35 | log "Starting nc to listen on port ${port} and save metrics to ${file}" |
| 36 | |
| 37 | started=$(date +%s) |
| 38 | mync -l -p "${port}" | tee -a -p --output-error=exit "${file}" |
| 39 | ended=$(date +%s) |
| 40 | |
| 41 | if [ -s "${file}" ] |
| 42 | then |
| 43 | if [ -n "${real_exporting_host}" ] && [ -n "${real_exporting_port}" ] |
| 44 | then |
| 45 | log "Attempting to send the metrics to the real external database at ${real_exporting_host}:${real_exporting_port}" |
| 46 | |
| 47 | mync "${real_exporting_host}" "${real_exporting_port}" <"${file}" |
| 48 | ret=$? |
| 49 | |
| 50 | if [ ${ret} -eq 0 ] |
| 51 | then |
| 52 | log "Successfully sent the metrics to ${real_exporting_host}:${real_exporting_port}" |
| 53 | mv "${file}" "${file}.old" |
| 54 | touch "${file}" |
| 55 | else |
| 56 | log "Failed to send the metrics to ${real_exporting_host}:${real_exporting_port} (nc returned ${ret}) - appending more data to ${file}" |
| 57 | fi |
| 58 | else |
| 59 | log "No external database configured - appending more data to ${file}" |
| 60 | fi |
| 61 | fi |
| 62 | |
| 63 | # prevent a CPU hungry infinite loop |
| 64 | # if nc cannot listen to port |
| 65 | if [ $((ended - started)) -lt 5 ] |
| 66 | then |
| 67 | log "nc has been stopped too fast." |
| 68 | delay=30 |
| 69 | else |
| 70 | delay=1 |
| 71 | fi |
| 72 | |
| 73 | log "Waiting ${delay} seconds before listening again for data." |
| 74 | sleep ${delay} |
| 75 | done |
| 76 | } |
| 77 | |
| 78 | if [ "${MODE}" = "start" ] |
| 79 | then |
| 80 | |
| 81 | # start the listener, in exclusive mode |
| 82 | # only one can use the same file/port at a time |
| 83 | { |
| 84 | flock -n 9 |
| 85 | # shellcheck disable=SC2181 |
| 86 | if [ $? -ne 0 ] |
| 87 | then |
| 88 | log "Cannot get exclusive lock on file ${FILE}.lock - Am I running multiple times?" |
| 89 | exit 2 |
| 90 | fi |
| 91 | |
| 92 | # save our PID to the lock file |
| 93 | echo "$$" >"${FILE}.lock" |
| 94 | |
| 95 | listen_save_replay_forever "${FILE}" "${MY_PORT}" "${EXPORTING_HOST}" "${EXPORTING_PORT}" |
| 96 | ret=$? |
| 97 | |
| 98 | log "listener exited." |
| 99 | exit ${ret} |
| 100 | |
| 101 | } 9>>"${FILE}.lock" |
| 102 | |
| 103 | # we can only get here if ${FILE}.lock cannot be created |
| 104 | log "Cannot create file ${FILE}." |
| 105 | exit 3 |
| 106 | |
| 107 | elif [ "${MODE}" = "stop" ] |
| 108 | then |
| 109 | |
| 110 | { |
| 111 | flock -n 9 |
| 112 | # shellcheck disable=SC2181 |
| 113 | if [ $? -ne 0 ] |
| 114 | then |
| 115 | pid=$(<"${FILE}".lock) |
| 116 | log "Killing process ${pid}..." |
| 117 | kill -TERM "-${pid}" |
| 118 | exit 0 |
| 119 | fi |
| 120 | |
| 121 | log "File ${FILE}.lock has been locked by me but it shouldn't. Is a collector running?" |
| 122 | exit 4 |
| 123 | |
| 124 | } 9<"${FILE}.lock" |
| 125 | |
| 126 | log "File ${FILE}.lock does not exist. Is a collector running?" |
| 127 | exit 5 |
| 128 | |
| 129 | else |
| 130 | |
| 131 | cat <<EOF |
| 132 | Usage: |
| 133 | |
| 134 | "${0}" start|stop PORT [EXPORTING_HOST EXPORTING_PORT] |
| 135 | |
| 136 | PORT The port this script will listen |
| 137 | (configure netdata to use this as an external database) |
| 138 | |
| 139 | EXPORTING_HOST The real host for the external database |
| 140 | EXPORTING_PORT The real port for the external database |
| 141 | |
| 142 | This script can act as fallback database for netdata. |
| 143 | It will receive metrics from netdata, save them to |
| 144 | ${FILE} |
| 145 | and once netdata reconnects to the real external database, |
| 146 | this script will push all metrics collected to the real |
| 147 | external database too and wait for a failure to happen again. |
| 148 | |
| 149 | Only one netdata can connect to this script at a time. |
| 150 | If you need fallback for multiple netdata, run this script |
| 151 | multiple times with different ports. |
| 152 | |
| 153 | You can run me in the background with this: |
| 154 | |
| 155 | screen -d -m "${0}" start PORT [EXPORTING_HOST EXPORTING_PORT] |
| 156 | EOF |
| 157 | exit 1 |
| 158 | fi |