Initial metrics skill with aggregation

This commit is contained in:
Azat
2026-02-02 22:29:24 +01:00
commit 09fe677f36
3 changed files with 304 additions and 0 deletions

124
scripts/autorun.sh Normal file
View File

@@ -0,0 +1,124 @@
#!/bin/bash
set -e
NODE_EXPORTER_VERSION="${NODE_EXPORTER_VERSION:-1.7.0}"
SKILLS_DIR="${SKILLS_DIR:-/skills}"
# Idempotent node_exporter installation
install_node_exporter() {
if command -v node_exporter &>/dev/null; then
echo "node_exporter already installed"
return 0
fi
echo "Installing node_exporter v${NODE_EXPORTER_VERSION}..."
local arch
case "$(uname -m)" in
x86_64) arch="amd64" ;;
aarch64) arch="arm64" ;;
armv7l) arch="armv7" ;;
*)
echo "Unsupported architecture: $(uname -m)"
exit 1
;;
esac
local url="https://github.com/prometheus/node_exporter/releases/download/v${NODE_EXPORTER_VERSION}/node_exporter-${NODE_EXPORTER_VERSION}.linux-${arch}.tar.gz"
curl -sSL "$url" -o /tmp/node_exporter.tar.gz
tar -xzf /tmp/node_exporter.tar.gz -C /tmp
mv /tmp/node_exporter-*/node_exporter /usr/local/bin/
rm -rf /tmp/node_exporter*
echo "node_exporter installed"
}
# Discover skills with metrics ports
discover_metrics_targets() {
local targets_file="/tmp/metrics_targets.txt"
echo "# Auto-discovered metrics targets" > "$targets_file"
for skill_dir in "$SKILLS_DIR"/*/; do
local skill_name=$(basename "$skill_dir")
local skill_md="$skill_dir/SKILL.md"
# Skip self
[ "$skill_name" = "metrics" ] && continue
# Check for metrics-port in SKILL.md
if [ -f "$skill_md" ]; then
local port=$(yq -r '.metadata.vibestack."metrics-port" // empty' < <(sed -n '/^---$/,/^---$/p' "$skill_md" | sed '1d;$d') 2>/dev/null)
if [ -n "$port" ] && [ "$port" != "null" ]; then
echo "$skill_name localhost:$port" >> "$targets_file"
echo " $skill_name: metrics on port $port"
fi
fi
# Check for METRICS_PORT_skillname env var
local env_var="METRICS_PORT_${skill_name//-/_}"
local env_port="${!env_var}"
if [ -n "$env_port" ]; then
echo "$skill_name localhost:$env_port" >> "$targets_file"
echo " $skill_name: metrics on port $env_port (from env)"
fi
done
echo "Targets written to $targets_file"
}
# Configure Caddy if present
configure_caddy() {
local caddy_dir="$SKILLS_DIR/caddy"
local metrics_port="${METRICS_PORT:-9090}"
local metrics_domain="${METRICS_DOMAIN:-}"
if [ ! -d "$caddy_dir" ]; then
echo "Caddy not found - metrics on port $metrics_port"
return 0
fi
echo "Caddy detected - configuring reverse proxy..."
mkdir -p "$caddy_dir/snippets.d"
local snippet="$caddy_dir/snippets.d/metrics.caddy"
if [ -n "$metrics_domain" ]; then
cat > "$snippet" << EOF
# Auto-generated by metrics skill
$metrics_domain {
reverse_proxy localhost:$metrics_port
}
EOF
echo "Caddy config: $metrics_domain -> localhost:$metrics_port"
else
cat > "$snippet" << EOF
# Auto-generated by metrics skill
# Add to your site block:
# handle /metrics {
# reverse_proxy localhost:$metrics_port
# }
EOF
echo "Caddy snippet created (manual config needed)"
fi
}
# Install socat for HTTP server
install_socat() {
if command -v socat &>/dev/null; then
echo "socat already installed"
return 0
fi
echo "Installing socat..."
apt-get update
apt-get install -y socat
echo "socat installed"
}
install_node_exporter
install_socat
discover_metrics_targets
configure_caddy
echo "Metrics setup complete"

107
scripts/run.sh Normal file
View File

@@ -0,0 +1,107 @@
#!/bin/bash
set -e
SCRIPT_DIR="$(dirname "$0")"
METRICS_PORT="${METRICS_PORT:-9090}"
NODE_EXPORTER_PORT="${NODE_EXPORTER_PORT:-9100}"
TARGETS_FILE="/tmp/metrics_targets.txt"
# Start node_exporter in background
start_node_exporter() {
echo "Starting node_exporter on port $NODE_EXPORTER_PORT..."
node_exporter --web.listen-address=":$NODE_EXPORTER_PORT" &
NODE_EXPORTER_PID=$!
sleep 2
if ! kill -0 $NODE_EXPORTER_PID 2>/dev/null; then
echo "Failed to start node_exporter"
exit 1
fi
echo "node_exporter running (PID $NODE_EXPORTER_PID)"
}
# Create the aggregator CGI script
create_aggregator_script() {
cat > /tmp/aggregate.sh << 'SCRIPT'
#!/bin/bash
NODE_EXPORTER_PORT="${NODE_EXPORTER_PORT:-9100}"
TARGETS_FILE="/tmp/metrics_targets.txt"
# Collect all metrics
{
# System metrics
echo "# HELP vibestack_up Whether the metrics aggregator is up"
echo "# TYPE vibestack_up gauge"
echo "vibestack_up 1"
echo ""
# node_exporter metrics
curl -s "http://localhost:$NODE_EXPORTER_PORT/metrics" 2>/dev/null || true
# Skill metrics
if [ -f "$TARGETS_FILE" ]; then
while IFS=' ' read -r skill_name target || [ -n "$skill_name" ]; do
[[ "$skill_name" =~ ^#.*$ ]] && continue
[ -z "$skill_name" ] && continue
[ -z "$target" ] && continue
echo ""
echo "# Metrics from skill: $skill_name"
curl -s "http://${target}/metrics" 2>/dev/null || true
done < "$TARGETS_FILE"
fi
}
SCRIPT
chmod +x /tmp/aggregate.sh
}
# Serve metrics using socat
serve_metrics() {
echo "Starting metrics aggregator on port $METRICS_PORT..."
if ! command -v socat &>/dev/null; then
echo "Installing socat..."
apt-get update && apt-get install -y socat
fi
# Use socat to handle HTTP requests
exec socat TCP-LISTEN:$METRICS_PORT,reuseaddr,fork EXEC:"/tmp/metrics_handler.sh"
}
# Create HTTP handler script
create_handler_script() {
cat > /tmp/metrics_handler.sh << 'HANDLER'
#!/bin/bash
# Read HTTP request (we ignore it, always serve /metrics)
read -r request_line
while read -r header; do
[ "$header" = $'\r' ] && break
done
# Get metrics
metrics=$(/tmp/aggregate.sh)
content_length=${#metrics}
# Send HTTP response
printf "HTTP/1.1 200 OK\r\n"
printf "Content-Type: text/plain; charset=utf-8\r\n"
printf "Content-Length: %d\r\n" "$content_length"
printf "Connection: close\r\n"
printf "\r\n"
printf "%s" "$metrics"
HANDLER
chmod +x /tmp/metrics_handler.sh
}
# Cleanup on exit
cleanup() {
echo "Shutting down..."
[ -n "$NODE_EXPORTER_PID" ] && kill $NODE_EXPORTER_PID 2>/dev/null
exit 0
}
trap cleanup SIGTERM SIGINT
start_node_exporter
create_aggregator_script
create_handler_script
serve_metrics