Skip to content

Collect pod resource usage and cgroup metrics in must-gather#2494

Open
evanwang9x wants to merge 2 commits into
skupperproject:mainfrom
evanwang9x:Evan/Gather-Resources-Fix
Open

Collect pod resource usage and cgroup metrics in must-gather#2494
evanwang9x wants to merge 2 commits into
skupperproject:mainfrom
evanwang9x:Evan/Gather-Resources-Fix

Conversation

@evanwang9x

@evanwang9x evanwang9x commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Must gather script can now collect memory and CPU usage via container exec and also captures cgroup limits, requests, and throttling stats.
Outputs the info to "must-gather/namespaces/"namespace"/pods/"pod-name"/"container-name"/"container-name"/resources/". Creates files to track what each does

  1. memory.info: Tracks current memory usage and shows the memory limit.
  2. cpu.stat: Provides CPU stats on total usage time, user/syxstem breakdown, and nr_throttled which shows performance bottlenecks.
Screenshot 2026-06-10 at 2 41 54 PM
  1. cpu.max: maximum amount of CPU processing power allotted.
  2. last.terminated: Provides an error message on why the previous container crashed if applicable. Helpful for identifying errors like Out of memory.
  3. memory.pressure / cpu.pressure: Tracks how much time the pod spends waiting for CPU or memory to become available. This helps identify if the pod is slowing down because the node is too busy.

To run the script:

  1. podman build -t skupper-gather-test -f Dockerfile.must-gather .
  2. mkdir ./must-gather
  3. podman run --rm --net=host -v $HOME/.kube/config:/root/.kube/config:Z -v $(pwd)/must-gather:/must-gather:Z -e KUBECONFIG=/root/.kube/config skupper-gather-test

Summary by CodeRabbit

  • New Features
    • Added collection of per-pod resource usage details for Skupper-related workloads.
    • Included additional diagnostics for memory, CPU, and container termination history, with fallback messages when some data is unavailable.
    • Extended collection to cover relevant workloads in both Skupper Sites and AttachedConnectors namespaces.

Comment thread scripts/skupper-gather.sh Outdated
for flag in "${flags[@]}"; do
oc exec -n "${sitens}" "${routerName}" -c router -- skstat -"${flag}" > "${logPath}/skstat.${flag}" 2>&1
done
getPodResources "${sitens}" "${routerName}" "router"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest that resources be retrieved for all skupper containers not just the router

e.g. getSkupperResourcesInNamespace iterates over label app.kubernetes.io/part-of: skupper

that then calls to getPodResources and iterates across all containers in the pod...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with Andy, this is the only observation I have as well.
But the data being retrieved from the router container looks good to me.
Just add all the other possible containers into the list and we should be good to merge it.

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds resource collection functions to the Skupper diagnostic gather script. A new getPodResources function collects per-container memory, CPU, PSI pressure, and termination data via oc exec, and getSkupperResourcesInNamespace iterates Skupper-labeled pods/containers, invoked from main() for site and attachedconnector namespaces.

Changes

Skupper Gather Script Resource Collection

Layer / File(s) Summary
Pod resource collection function
scripts/skupper-gather.sh
Adds getPodResources(ns, podName, container) which creates a per-pod/container directory and collects memory usage/limits, last termination info, memory/CPU PSI pressure, cpu.stat, and cpu.max via oc exec, with fallback text when data is unavailable.
Namespace iteration and wiring
scripts/skupper-gather.sh
Adds getSkupperResourcesInNamespace(ns) to select pods labeled app.kubernetes.io/part-of=skupper, enumerate their containers, and call getPodResources for each; main() invokes this for both site and attachedconnector discovered namespaces.

Estimated code review effort: 2 (Simple) | ~12 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: collecting pod resource usage and cgroup metrics in must-gather.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
scripts/skupper-gather.sh (2)

50-50: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Reuse the already-computed podDirName instead of recomputing.

podDirName is computed at Line 23 with the same expression; recompute is redundant.

♻️ Proposed fix
-oc get pod "${podName##"pod/"}" -n "${ns}" -o jsonpath="{.status.containerStatuses[?(@.name=='${container}')].lastState}" > "${resourcePath}/last.terminated" 2>/dev/null
+oc get pod "${podDirName}" -n "${ns}" -o jsonpath="{.status.containerStatuses[?(@.name=='${container}')].lastState}" > "${resourcePath}/last.terminated" 2>/dev/null

56-62: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate PSI-check/limit pattern across three blocks.

Memory pressure, cpu.max, and cpu.pressure each re-implement a similar "check file exists, else print PSI-unavailable message" structure via separate oc exec calls. Extracting a small helper (taking the sysfs path and output file) would reduce duplication and make future cgroup additions easier.

Also applies to: 93-103, 106-112


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b8d4179f-3155-4a80-860d-11ec3162c41c

📥 Commits

Reviewing files that changed from the base of the PR and between 4035c0e and 976865f.

📒 Files selected for processing (1)
  • scripts/skupper-gather.sh

Comment thread scripts/skupper-gather.sh
Comment on lines +30 to +47
# Memory Info
oc exec -n "${ns}" "${podName}" -c "${container}" -- bash -c '
curr=$(cat /sys/fs/cgroup/memory.current)
max=$(cat /sys/fs/cgroup/memory.max)
curr_kb=$((curr / 1024))
curr_mb=$((curr / 1024 / 1024))

if [ "$max" = "max" ]; then
limit_str="max (uncapped)"
else
max_kb=$((max / 1024))
max_mb=$((max / 1024 / 1024))
limit_str="$max bytes ($max_kb KiB / $max_mb MiB)"
fi

echo "Usage: $curr bytes ($curr_kb KiB / $curr_mb MiB) - Limit: $limit_str" > /tmp/memory.info
' 2>/dev/null
oc exec -n "${ns}" "${podName}" -c "${container}" -- cat /tmp/memory.info > "${resourcePath}/memory.info" 2>&1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Avoid the intermediate /tmp file and second oc exec round trip.

Unlike the other cgroup blocks (pressure, cpu.stat, cpu.max) which write directly to stdout and are captured via output redirection in a single oc exec, memory.info is written to a hardcoded /tmp/memory.info inside the container and then read back with a second oc exec. Static analysis flags this as an insecure/predictable temp file (CWE-377), and it also doubles the exec calls needlessly.

🛡️ Proposed fix: write directly to stdout like the other blocks
   # Memory Info
-  oc exec -n "${ns}" "${podName}" -c "${container}" -- bash -c '
+  oc exec -n "${ns}" "${podName}" -c "${container}" -- bash -c '
     curr=$(cat /sys/fs/cgroup/memory.current)
     max=$(cat /sys/fs/cgroup/memory.max)
     curr_kb=$((curr / 1024))
     curr_mb=$((curr / 1024 / 1024))

     if [ "$max" = "max" ]; then
       limit_str="max (uncapped)"
     else
       max_kb=$((max / 1024))
       max_mb=$((max / 1024 / 1024))
       limit_str="$max bytes ($max_kb KiB / $max_mb MiB)"
     fi

-    echo "Usage: $curr bytes ($curr_kb KiB / $curr_mb MiB) - Limit: $limit_str" > /tmp/memory.info
-  ' 2>/dev/null
-  oc exec -n "${ns}" "${podName}" -c "${container}" -- cat /tmp/memory.info > "${resourcePath}/memory.info" 2>&1
+    echo "Usage: $curr bytes ($curr_kb KiB / $curr_mb MiB) - Limit: $limit_str"
+  ' > "${resourcePath}/memory.info" 2>&1
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Memory Info
oc exec -n "${ns}" "${podName}" -c "${container}" -- bash -c '
curr=$(cat /sys/fs/cgroup/memory.current)
max=$(cat /sys/fs/cgroup/memory.max)
curr_kb=$((curr / 1024))
curr_mb=$((curr / 1024 / 1024))
if [ "$max" = "max" ]; then
limit_str="max (uncapped)"
else
max_kb=$((max / 1024))
max_mb=$((max / 1024 / 1024))
limit_str="$max bytes ($max_kb KiB / $max_mb MiB)"
fi
echo "Usage: $curr bytes ($curr_kb KiB / $curr_mb MiB) - Limit: $limit_str" > /tmp/memory.info
' 2>/dev/null
oc exec -n "${ns}" "${podName}" -c "${container}" -- cat /tmp/memory.info > "${resourcePath}/memory.info" 2>&1
# Memory Info
oc exec -n "${ns}" "${podName}" -c "${container}" -- bash -c '
curr=$(cat /sys/fs/cgroup/memory.current)
max=$(cat /sys/fs/cgroup/memory.max)
curr_kb=$((curr / 1024))
curr_mb=$((curr / 1024 / 1024))
if [ "$max" = "max" ]; then
limit_str="max (uncapped)"
else
max_kb=$((max / 1024))
max_mb=$((max / 1024 / 1024))
limit_str="$max bytes ($max_kb KiB / $max_mb MiB)"
fi
echo "Usage: $curr bytes ($curr_kb KiB / $curr_mb MiB) - Limit: $limit_str"
' > "${resourcePath}/memory.info" 2>&1
🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 46-46: Writing to or reading from a hardcoded, predictable path under /tmp is vulnerable to symlink and TOCTOU attacks: a local attacker can pre-create the file (or a symlink pointing elsewhere) and hijack or corrupt the contents. Generate a unique, unpredictable temporary file with mktemp instead, e.g. tmpfile="$(mktemp)" (or mktemp -d for directories) and reference "$tmpfile".
Context: /tmp/memory.info
Note: [CWE-377] Insecure Temporary File.

(predictable-tmp-file-bash)

Source: Linters/SAST tools

Comment thread scripts/skupper-gather.sh
Comment on lines +64 to +90
# CPU usage
oc exec -n "${ns}" "${podName}" -c "${container}" -- bash -c "
declare -A cpu_descriptions
cpu_descriptions=(
[usage_usec]='Total CPU time consumed (user + system)'
[user_usec]='Time spent in user-space (app logic)'
[system_usec]='Time spent in kernel-space (syscalls, I/O)'
[core_sched.force_idle_usec]='Time CPU forced idle for security (cross-HT side-channel protection)'
[nr_periods]='Number of CPU quota enforcement periods (0 = no quota set)'
[nr_throttled]='Times the container was throttled for exceeding CPU quota'
[throttled_usec]='Total time spent throttled/waiting for CPU quota'
[nr_bursts]='Times container used burst CPU capacity beyond quota'
[burst_usec]='Total time spent using burst CPU capacity'
)

cat /sys/fs/cgroup/cpu.stat | while read line; do
val=\$(echo \$line | awk '{print \$2}')
key=\$(echo \$line | awk '{print \$1}')
desc=\${cpu_descriptions[\$key]}
if [[ \$key == *_usec ]] && [ \$val -ge 1000000 ]; then
sec=\$(awk \"BEGIN {printf \\\"%.2f\\\", \$val/1000000}\")
echo \"\$line (\$sec s) | \$desc\"
else
echo \"\$line | \$desc\"
fi
done
" > "${resourcePath}/cpu.stat" 2>&1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect the Skupper container images used across pods to check shell availability assumptions.
rg -n 'FROM ' --type=dockerfile
fd -e Dockerfile

Repository: skupperproject/skupper

Length of output: 195


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== relevant call sites ==\n'
rg -n 'getSkupperResourcesInNamespace|getPodResources|bash -c|declare -A|cpu\.stat' scripts/skupper-gather.sh

printf '\n== file map ==\n'
ast-grep outline scripts/skupper-gather.sh --view expanded

printf '\n== container/image hints in repo ==\n'
rg -n 'Dockerfile\.(kube-adaptor|controller|cli|system-controller|network-observer)|FROM ' -g '!**/node_modules/**' .

Repository: skupperproject/skupper

Length of output: 2093


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,170p' scripts/skupper-gather.sh | cat -n

Repository: skupperproject/skupper

Length of output: 7333


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== script excerpt ==\n'
sed -n '1,170p' scripts/skupper-gather.sh | cat -n

Repository: skupperproject/skupper

Length of output: 7355


oc exec -- bash -c does not work for the scratch-based Skupper images.
getSkupperResourcesInNamespace now iterates every container in every app.kubernetes.io/part-of=skupper pod, and the final images for Dockerfile.cli, Dockerfile.controller, Dockerfile.kube-adaptor, Dockerfile.network-observer, and Dockerfile.system-controller are FROM scratch. Those containers won’t have bash, so resource collection fails for them; a POSIX sh path or another fallback is needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants