Procd process handling

I know nothing about mosquitto or Home Assitant, but at least for awk, this is not how it would normally be used in a pipeline. Normally it operates on a finite input, and when it reads EOF it exits. No need for an explicit signal to kill it.

I think in the present situation, what would help is some explicit "marker" line in awk's input, together with a pattern matching in the awk program that makes it exit when it sees the marker.

1 Like

Since I’m passing the output of tail from a continually updating file, I wonder what form that marker could take?

So otherwise is no EOF received when tail exits?

e.g. tail -100 to show the last 100 lines ?

I meant make the marker appear in the file.

So have we tried the plan with a shell wrapper script foo.sh taking the signal from procd, and having in foo.sh a trap hook for the signal, which hook kills the pipeline? I don't see why that shouldn't work. And killing the front process of the pipe should be enough in that case.

1 Like

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.

1 Like