Yes this indeed works, as follows.
procd service script:
root@OpenWrt:~# cat /etc/init.d/mqtt-publisher
#!/bin/sh /etc/rc.common
# metadata
START=98
STOP=10
USE_PROCD=1
start_service()
{
procd_open_instance
# command runs the wrapper script; respawn if it exits
procd_set_param command /root/cake-autorate/mqtt-publisher.sh
procd_set_param respawn # default respawn forever
procd_close_instance
}
stop_service()
{
# procd sends SIGTERM to the tracked PID; wrapper trap handles cleanup
:
}
reload_service()
{
stop
start
}
And the wrapper script:
root@OpenWrt:~# cat /root/cake-autorate/mqtt-publisher.sh
#!/usr/bin/env bash
CPU_CORES=2
MQTT_HOST="192.168.x.x"
MQTT_PORT="1883"
MQTT_USER="xx"
MQTT_PASS="xx"
MQTT_TOPIC="cake-autorate"
DISC_PREFIX="homeassistant"
DEVICE_ID="openwrt"
DEVICE_NAME="OpenWrt"
MIN_INTERVAL_S=1
set -m
cleanup()
{
trap - INT TERM EXIT
for pid in "${publish_stats_pids[@]}"
do
kill -- -"$pid" 2>/dev/null || true
done
exit 0
}
publish_config()
{
mosquitto_pub \
-h "$MQTT_HOST" -p "$MQTT_PORT" \
-u "$MQTT_USER" -P "$MQTT_PASS" \
-r -q 1 \
-t "$DISC_PREFIX/sensor/$DEVICE_ID/$1/config" \
-m "$2"
}
publish_discovery()
{
publish_config "dl_achieved_rate_kbps" \
'{"name":"CAKE DL Achieved Rate","state_topic":"cake-autorate","value_template":"{{ value_json.dl_achieved_rate_kbps }}","unit_of_measurement":"kbps","unique_id":"openwrt_dl_achieved_rate","device":{"identifiers":["openwrt"],"name":"OpenWrt"}}'
publish_config "ul_achieved_rate_kbps" \
'{"name":"CAKE UL Achieved Rate","state_topic":"cake-autorate","value_template":"{{ value_json.ul_achieved_rate_kbps }}","unit_of_measurement":"kbps","unique_id":"openwrt_ul_achieved_rate","device":{"identifiers":["openwrt"],"name":"OpenWrt"}}'
publish_config "cake_dl_rate_kbps" \
'{"name":"CAKE DL Rate","state_topic":"cake-autorate","value_template":"{{ value_json.cake_dl_rate_kbps }}","unit_of_measurement":"kbps","unique_id":"openwrt_cake_dl_rate","device":{"identifiers":["openwrt"],"name":"OpenWrt"}}'
publish_config "cake_ul_rate_kbps" \
'{"name":"CAKE UL Rate","state_topic":"cake-autorate","value_template":"{{ value_json.cake_ul_rate_kbps }}","unit_of_measurement":"kbps","unique_id":"openwrt_cake_ul_rate","device":{"identifiers":["openwrt"],"name":"OpenWrt"}}'
publish_config "dl_sum_delays" \
'{"name":"DL Delay Sum","state_topic":"cake-autorate","value_template":"{{ value_json.dl_sum_delays }}","unit_of_measurement":"us","unique_id":"openwrt_dl_delay","device":{"identifiers":["openwrt"],"name":"OpenWrt"}}'
publish_config "ul_sum_delays" \
'{"name":"UL Delay Sum","state_topic":"cake-autorate","value_template":"{{ value_json.ul_sum_delays }}","unit_of_measurement":"us","unique_id":"openwrt_ul_delay","device":{"identifiers":["openwrt"],"name":"OpenWrt"}}'
publish_config "dl_avg_owd_delta_us" \
'{"name":"DL OWD Delta","state_topic":"cake-autorate","value_template":"{{ value_json.dl_avg_owd_delta_us }}","unit_of_measurement":"us","unique_id":"openwrt_dl_owd","device":{"identifiers":["openwrt"],"name":"OpenWrt"}}'
publish_config "ul_avg_owd_delta_us" \
'{"name":"UL OWD Delta","state_topic":"cake-autorate","value_template":"{{ value_json.ul_avg_owd_delta_us }}","unit_of_measurement":"us","unique_id":"openwrt_ul_owd","device":{"identifiers":["openwrt"],"name":"OpenWrt"}}'
publish_config "dl_load_condition" \
'{"name":"DL Load Condition","state_topic":"cake-autorate","value_template":"{{ value_json.dl_load_condition }}","unique_id":"openwrt_dl_condition","device":{"identifiers":["openwrt"],"name":"OpenWrt"}}'
publish_config "ul_load_condition" \
'{"name":"UL Load Condition","state_topic":"cake-autorate","value_template":"{{ value_json.ul_load_condition }}","unique_id":"openwrt_ul_condition","device":{"identifiers":["openwrt"],"name":"OpenWrt"}}'
for c in $(seq 0 "$CPU_CORES"); do
publish_config "cpu_core$c" \
"{\"name\":\"CPU Core $c\",\"state_topic\":\"cake-autorate\",\"value_template\":\"{{ value_json.cpu_core$c }}\",\"unit_of_measurement\":\"%\",\"unique_id\":\"openwrt_cpu_$c\",\"device\":{\"identifiers\":[\"openwrt\"],\"name\":\"OpenWrt\"}}"
done
}
publish_stats()
{
local log_file_path="$1"
publish_discovery
while true; do
tail -F "$log_file_path" 2>/dev/null | \
awk -F'; ' -v min_int="$MIN_INTERVAL_S" '
BEGIN {
last_emit = 0.0
dl_achieved_rate_kbps = ul_achieved_rate_kbps = 0
dl_sum_delays = ul_sum_delays = 0
dl_avg_owd_delta_us = ul_avg_owd_delta_us = 0
dl_load_condition = ul_load_condition = "unknown"
cake_dl_rate_kbps = cake_ul_rate_kbps = 0
cpu_core0 = cpu_core1 = cpu_core2 = 0
summary_epoch = cpu_epoch = 0
}
$1=="SUMMARY" && NF>=13 {
summary_epoch = $3 + 0
dl_achieved_rate_kbps = ($4~/^[0-9.]+$/)?$4:0
ul_achieved_rate_kbps = ($5~/^[0-9.]+$/)?$5:0
dl_sum_delays = ($6~/^[0-9.]+$/)?$6:0
ul_sum_delays = ($7~/^[0-9.]+$/)?$7:0
dl_avg_owd_delta_us = ($8~/^[0-9.]+$/)?$8:0
ul_avg_owd_delta_us = ($9~/^[0-9.]+$/)?$9:0
dl_load_condition = ($10!="")?$10:"unknown"
ul_load_condition = ($11!="")?$11:"unknown"
cake_dl_rate_kbps = ($12~/^[0-9.]+$/)?$12:0
cake_ul_rate_kbps = ($13~/^[0-9.]+$/)?$13:0
}
$1=="CPU" && NF>=7 {
cpu_epoch = $3 + 0
cpu_core0 = ($5~/^[0-9.]+$/)?$5:0
cpu_core1 = ($6~/^[0-9.]+$/)?$6:0
cpu_core2 = ($7~/^[0-9.]+$/)?$7:0
}
{
event_epoch = (summary_epoch > cpu_epoch) ? summary_epoch : cpu_epoch
if (event_epoch > 0 && event_epoch - last_emit >= min_int) {
last_emit = event_epoch
printf "{\"event_epoch\":%0.6f,\"dl_achieved_rate_kbps\":%s,\"ul_achieved_rate_kbps\":%s,\"dl_sum_delays\":%s,\"ul_sum_delays\":%s,\"dl_avg_owd_delta_us\":%s,\"ul_avg_owd_delta_us\":%s,\"dl_load_condition\":\"%s\",\"ul_load_condition\":\"%s\",\"cake_dl_rate_kbps\":%s,\"cake_ul_rate_kbps\":%s,\"cpu_core0\":%s,\"cpu_core1\":%s,\"cpu_core2\":%s}\n",
event_epoch,
dl_achieved_rate_kbps,
ul_achieved_rate_kbps,
dl_sum_delays,
ul_sum_delays,
dl_avg_owd_delta_us,
ul_avg_owd_delta_us,
dl_load_condition,
ul_load_condition,
cake_dl_rate_kbps,
cake_ul_rate_kbps,
cpu_core0,
cpu_core1,
cpu_core2
}
}
' | mosquitto_pub \
-h "$MQTT_HOST" -p "$MQTT_PORT" \
-u "$MQTT_USER" -P "$MQTT_PASS" \
-t "$MQTT_TOPIC" -l -q 1 -r
sleep 5
done
}
trap cleanup INT TERM EXIT
publish_stats_pids=()
for log_file_path in /var/log/cake-autorate.*.log; do
( publish_stats "$log_file_path" ) &
publish_stats_pids+=($!)
done
wait
Enabling job control, using subshell when backgrounding the wrapper process and storing the PID of the latter ensures that:
kill -- -"$pid" 2>/dev/null || true
inside the cleanup trap of the backgrounded process, kills all the children of that wrapper process, even within procd context.