#!/bin/sh
#
# /etc/cron.daily/fpm-daily-rollup
#
# Named to sort BEFORE fpm-metrics-prune in run-parts' alphabetical order ("d" < "m"),
# the same trick fpm-traffic-snapshot uses to beat logrotate. It matters only in one
# case, but that case is unrecoverable: if this job is broken for a while, days pile up
# unarchived, and a prune that ran first would delete them at 45 days before the rollup
# could ever catch up. Running first means any outage shorter than the retention window
# self-heals on the next successful run.
#
# Folds each FINISHED day of 15-second ticks into hourly rows and keeps them
# forever, so history outlives the raw files.
#
# Why this exists: fpm-metrics-prune deletes pool-YYYY-MM-DD.jsonl at 45 days, so
# "how did this month compare to three months ago" has no data to read no matter
# how fast the reader is. A day of raw ticks is 17,280 lines / ~10 MB; the same day
# as hourly rows is 72 rows / ~39 KB, which is ~14 MB per YEAR. Keeping that
# indefinitely costs less than two days of raw ticks.
#
# It is also the fast path for wide queries. Measured on this data shape, PHP
# aggregating raw ticks streams at ~3.2 us/line with flat 2 MB memory: 54 ms for a
# day, 392 ms for a week, 3.16 s for the full 45 days. So raw is fine for recent
# ranges and the rollup is what makes a multi-month range instant.
#
# The aggregation itself is pool-metrics-summary.pl --json, NOT a reimplementation.
# The reload/counter-reset semantics in that script are subtle (a decrease means the
# source reset, so the new value IS the count since the reset; PSS is doubled during
# a graceful reload because both worker generations are alive) and a second
# implementation would drift from the summary everyone reads by eye.
#
# Naming: rollup-YYYY-MM-DD.json, deliberately .json and NOT .jsonl. fpm-metrics-prune
# globs *.jsonl and *.log for both compression and deletion, so a .jsonl name would be
# pruned at 45 days -- the exact thing this job exists to prevent. The prune script
# also excludes rollup-* explicitly; keep BOTH guards, since either alone is a silent
# data-loss bug waiting for someone to rename a file.
#
set -eu

DIR="${DIR:-/var/log/php8.3-fpm/metrics}"
SUMMARY="${SUMMARY:-/usr/local/bin/pool-metrics-summary.pl}"

[ -d "$DIR" ]     || exit 0
[ -x "$SUMMARY" ] || exit 0

today=$(date -u +%Y-%m-%d)
made=0
skipped=0

# Every day present as raw ticks, compressed or not. Backfills on first run, so the
# 45 days already on disk become permanent history immediately instead of the archive
# starting empty tomorrow.
for src in "$DIR"/pool-*.jsonl "$DIR"/pool-*.jsonl.gz; do
	[ -f "$src" ] || continue

	base=${src##*/}
	day=${base#pool-}
	day=${day%.jsonl}
	day=${day%.jsonl.gz}

	# Today is still being written; rolling it up now would store a partial day
	# under a name that claims to be the whole one.
	[ "$day" = "$today" ] && continue

	out="$DIR/rollup-$day.json"
	if [ -e "$out" ]; then
		skipped=$((skipped + 1))
		continue
	fi

	# Write to a temp file in the same directory and rename, so a reader never sees a
	# half-written rollup and a failure part-way leaves no file to be skipped forever.
	# The grep is the real test, not [ -s ]. --json on an unreadable or corrupt source
	# still emits a well-formed document with an empty "hours" array and exits 0, so a
	# size check accepts it -- and because this loop skips any day that already has a
	# rollup, that empty file would mask the day permanently. Require at least one pool
	# row before the file is allowed to exist.
	tmp="$out.tmp.$$"
	if zcat -f "$src" | "$SUMMARY" --json - > "$tmp" 2>/dev/null \
		&& grep -q '"pool":' "$tmp"; then
		mv "$tmp" "$out"
		chgrp www-data "$out" 2>/dev/null || true
		chmod 640 "$out" 2>/dev/null || true
		made=$((made + 1))
	else
		rm -f "$tmp"
		logger -t fpm-daily-rollup "FAILED for $day (source $base)"
	fi
done

logger -t fpm-daily-rollup "created=$made existing=$skipped total=$(
	find "$DIR" -maxdepth 1 -name 'rollup-*.json' -type f | wc -l
) size=$(
	find "$DIR" -maxdepth 1 -name 'rollup-*.json' -type f -exec du -ch {} + 2>/dev/null \
		| tail -1 | cut -f1
)"
