diff --git a/dot_config/quickshell/scripts/executable_k8s-metrics.sh b/dot_config/quickshell/scripts/executable_k8s-metrics.sh new file mode 100644 index 0000000..ed87e6d --- /dev/null +++ b/dot_config/quickshell/scripts/executable_k8s-metrics.sh @@ -0,0 +1,166 @@ +#!/usr/bin/env bash +# Outputs per-pod CPU/MEM metrics and namespace quota as JSON. +# Usage: k8s-metrics.sh +set -euo pipefail + +NS="${1:-tenant-5}" + +# ── Unit normalization ─────────────────────────────────────────────────────── + +# Normalize CPU string to millicores integer +normalize_cpu() { + local val="$1" + if [[ "$val" =~ ^([0-9]+)m$ ]]; then + echo "${BASH_REMATCH[1]}" + elif [[ "$val" =~ ^([0-9]+(\.[0-9]+)?)$ ]]; then + awk "BEGIN { printf \"%d\", ${BASH_REMATCH[1]} * 1000 }" + else + echo 0 + fi +} + +# Normalize memory string to MiB integer +normalize_mem() { + local val="$1" + if [[ "$val" =~ ^([0-9]+)Gi$ ]]; then + echo $(( ${BASH_REMATCH[1]} * 1024 )) + elif [[ "$val" =~ ^([0-9]+)Mi$ ]]; then + echo "${BASH_REMATCH[1]}" + elif [[ "$val" =~ ^([0-9]+)Ki$ ]]; then + echo $(( ${BASH_REMATCH[1]} / 1024 )) + elif [[ "$val" =~ ^([0-9]+)$ ]]; then + echo $(( ${BASH_REMATCH[1]} / 1048576 )) + else + echo 0 + fi +} + +# Format millicores for display ("420m" or "1.5c") +fmt_cpu() { + local m="$1" + if [[ $m -lt 1000 ]]; then + echo "${m}m" + else + awk "BEGIN { printf \"%.1fc\", $m / 1000 }" + fi +} + +# Format MiB for display ("343Mi" or "1.50Gi") +fmt_mem() { + local mib="$1" + if [[ $mib -lt 1024 ]]; then + echo "${mib}Mi" + else + awk "BEGIN { printf \"%.2fGi\", $mib / 1024 }" + fi +} + +# ── Fetch data ─────────────────────────────────────────────────────────────── + +pods_json=$(kubectl get pods -n "$NS" -o json 2>/dev/null) || exit 0 +top_output=$(kubectl top pods -n "$NS" --no-headers 2>/dev/null) || top_output="" +quota_json=$(kubectl get resourcequota -n "$NS" -o json 2>/dev/null) || exit 0 + +# ── Build podName → app map ────────────────────────────────────────────────── + +declare -A name_to_app +while IFS= read -r line; do + pod_name=$(echo "$line" | jq -r '.name') + app=$(echo "$line" | jq -r '.app') + name_to_app["$pod_name"]="$app" +done < <(echo "$pods_json" | jq -c '.items[] | {name: .metadata.name, app: (.metadata.labels["app.kubernetes.io/instance"] // .metadata.name)}') + +# ── Parse kubectl top ──────────────────────────────────────────────────────── + +declare -A top_cpu # app → cpuM +declare -A top_mem # app → memMi +cpu_actual=0 +mem_actual=0 + +while IFS= read -r line; do + [[ -z "$line" ]] && continue + read -r pod_name cpu_str mem_str <<< "$line" + app="${name_to_app[$pod_name]:-}" + [[ -z "$app" ]] && continue + cpu_m=$(normalize_cpu "$cpu_str") + mem_mib=$(normalize_mem "$mem_str") + top_cpu["$app"]=$cpu_m + top_mem["$app"]=$mem_mib + cpu_actual=$(( cpu_actual + cpu_m )) + mem_actual=$(( mem_actual + mem_mib )) +done <<< "$top_output" + +# ── Build podMetrics JSON array ────────────────────────────────────────────── + +pod_metrics="[" +first=1 +while IFS= read -r line; do + app=$(echo "$line" | jq -r '.app') + cpu_m="${top_cpu[$app]:-"-1"}" + mem_mib="${top_mem[$app]:-"-1"}" + [[ $first -eq 0 ]] && pod_metrics+="," + pod_metrics+=$(jq -nc --arg app "$app" --argjson cpu "$cpu_m" --argjson mem "$mem_mib" \ + '{app: $app, cpuM: $cpu, memMi: $mem}') + first=0 +done < <(echo "$pods_json" | jq -c '.items[] | {app: (.metadata.labels["app.kubernetes.io/instance"] // .metadata.name)}') +pod_metrics+="]" + +# ── Parse resourcequota ────────────────────────────────────────────────────── + +q_cpu_req_used=$(echo "$quota_json" | jq -r '.items[0].status.used["requests.cpu"] // "0"') +q_cpu_req_hard=$(echo "$quota_json" | jq -r '.items[0].status.hard["requests.cpu"] // "0"') +q_cpu_lim_used=$(echo "$quota_json" | jq -r '.items[0].status.used["limits.cpu"] // "0"') +q_cpu_lim_hard=$(echo "$quota_json" | jq -r '.items[0].status.hard["limits.cpu"] // "0"') +q_mem_req_used=$(echo "$quota_json" | jq -r '.items[0].status.used["requests.memory"] // "0"') +q_mem_req_hard=$(echo "$quota_json" | jq -r '.items[0].status.hard["requests.memory"] // "0"') +q_mem_lim_used=$(echo "$quota_json" | jq -r '.items[0].status.used["limits.memory"] // "0"') +q_mem_lim_hard=$(echo "$quota_json" | jq -r '.items[0].status.hard["limits.memory"] // "0"') + +cpu_req_used_m=$(normalize_cpu "$q_cpu_req_used") +cpu_req_hard_m=$(normalize_cpu "$q_cpu_req_hard") +cpu_lim_used_m=$(normalize_cpu "$q_cpu_lim_used") +cpu_lim_hard_m=$(normalize_cpu "$q_cpu_lim_hard") +mem_req_used_mib=$(normalize_mem "$q_mem_req_used") +mem_req_hard_mib=$(normalize_mem "$q_mem_req_hard") +mem_lim_used_mib=$(normalize_mem "$q_mem_lim_used") +mem_lim_hard_mib=$(normalize_mem "$q_mem_lim_hard") + +cpu_req_pct=$(awk "BEGIN { printf \"%.4f\", $cpu_req_used_m / ($cpu_req_hard_m > 0 ? $cpu_req_hard_m : 1) }") +cpu_lim_pct=$(awk "BEGIN { printf \"%.4f\", $cpu_lim_used_m / ($cpu_lim_hard_m > 0 ? $cpu_lim_hard_m : 1) }") +mem_req_pct=$(awk "BEGIN { printf \"%.4f\", $mem_req_used_mib / ($mem_req_hard_mib > 0 ? $mem_req_hard_mib : 1) }") +mem_lim_pct=$(awk "BEGIN { printf \"%.4f\", $mem_lim_used_mib / ($mem_lim_hard_mib > 0 ? $mem_lim_hard_mib : 1) }") + +cpu_req_label="$(fmt_cpu $cpu_req_used_m) / $(fmt_cpu $cpu_req_hard_m)" +cpu_lim_label="$(fmt_cpu $cpu_lim_used_m) / $(fmt_cpu $cpu_lim_hard_m)" +mem_req_label="$(fmt_mem $mem_req_used_mib) / $(fmt_mem $mem_req_hard_mib)" +mem_lim_label="$(fmt_mem $mem_lim_used_mib) / $(fmt_mem $mem_lim_hard_mib)" + +# ── Output ─────────────────────────────────────────────────────────────────── + +jq -nc \ + --argjson podMetrics "$pod_metrics" \ + --argjson cpuActualM "$cpu_actual" \ + --argjson memActualMi "$mem_actual" \ + --argjson cpuReqPct "$cpu_req_pct" \ + --argjson cpuLimPct "$cpu_lim_pct" \ + --argjson memReqPct "$mem_req_pct" \ + --argjson memLimPct "$mem_lim_pct" \ + --arg cpuReqLabel "$cpu_req_label" \ + --arg cpuLimLabel "$cpu_lim_label" \ + --arg memReqLabel "$mem_req_label" \ + --arg memLimLabel "$mem_lim_label" \ + '{ + podMetrics: $podMetrics, + quota: { + cpuActualM: $cpuActualM, + memActualMi: $memActualMi, + cpuReqPct: $cpuReqPct, + cpuLimPct: $cpuLimPct, + memReqPct: $memReqPct, + memLimPct: $memLimPct, + cpuReqLabel: $cpuReqLabel, + cpuLimLabel: $cpuLimLabel, + memReqLabel: $memReqLabel, + memLimLabel: $memLimLabel + } + }'