Aller au contenu

Métrique WorkingSet Size#

Disclaimer

La métrique WorkingSet Size est liée aux cgroups (et donc aux containers). Elle n'existe pas en dehors des cgroups.

Références#

La page https://mihai-albert.com/2022/02/13/out-of-memory-oom-in-kubernetes-part-1-intro-and-topics-discussed/ permet d'aller sur ces deux pages :

Par ailleurs, la page https://kubernetes.io/docs/concepts/scheduling-eviction/node-pressure-eviction/#eviction-signals-and-thresholds mentionne deux scripts :

Ces références servent à comprendre ce qui suit.

Explication sur la métrique#

La métrique workingset_size indique la quantité de mémoire minimale utilisée par le process en fonctionnement, soit une valeur proche de la RSS (Resident Set Size). Cette définition ne fonctionne que s'il n'y a qu'un seul process dans son cgroup (voir plus loin pour les cgroups).

C'est une métrique construite à partir de la différence entre deux autres métriques :

memory_working_set = memory_usage_in_bytes - memory_total_inactive_file

Si la différence est négative (si memory_usage_in_bytes est inférieur à memory_total_inactive_file), alors la valeur est nulle :

memory_working_set = 0

La signification de ces métriques (issue de cette page) est :

  • memory_usage_in_bytes : Size of overall memory used, regardless if it’s for mapping from disk or just allocating
  • memory_total_inactive_file : Number of bytes of file-backed memory on inactive LRU list

Cette même page indique que memory_working_set_bytes correspond à A heuristic for the minimum size of memory required for the app to work.

Obtention des métriques#

Si la métrique memory_working_set_bytes est une métrique calculée, il faut pouvoir obtenir les deux métriques qui servent au calcul.

Dans Kubernetes, ces métriques sont issues de cAdvisor, un composant du Kubelet.

Pour obtenir ces métriques en dehors de Kubernetes, il faut savoir la retrouver dans les cgroups.

Le chemin des cgroups s'obtient ainsi :

cat /proc/mounts |grep cgroup

Sur certains environnements, les deux versions de cgroup peuvent cohabiter. Dans tous les cas, il faudra une connaissance des cgroups pour aller rechercher le sous-répertoire contenant les fichiers et les métriques.

Avec la v1, les métriques s'obtiennent ainsi :

memory_usage_in_bytes=$(cat "${CGROUPSUBPATH}/memory.usage_in_bytes")
memory_total_inactive_file=$(cat "${CGROUPSUBPATH}/memory.stat | grep total_inactive_file | awk '{print $2}')

Avec la v2, les métriques s'obtiennent ainsi :

memory_usage_in_bytes=$(cat "${CGROUPSUBPATH}/memory.current")
memory_total_inactive_file=$(cat "${CGROUPSUBPATH}/memory.stat | grep inactive_file | awk '{print $2}')

Créer un cgroup pour un process#

Bien que les cgroups soient un outil puissant sur Linux, leur création n'est pas une tâche aisée. Cependant, des outils ont été créés.

Des outils bas-niveau ont pour préfixe cg* (comme cgcreate).

Mais un outil haut niveau a été créé pour simplifier cette tâche (entre autres) : Docker !

Autrement dit, la façon la plus simple d'isoler un process dans un cgroup dédié est de le lancer dans un container avec Docker.

Le chemin ${CGROUPSUBPATH} est alors plus facile à identifier. Il faudra d'abord obtenir l'ID du container :

ID=$(docker inspect <name> | jq -r '.[0].Id')

Puis avec les cgroups v1 :

ls "/sys/fs/cgroup/memory/docker/${ID}/"

Avec les cgroups v2 :

CGROUPPATH=$(cat /proc/mounts|grep cgroup2 | awk '{print $2}')
ls "${CGROUPPATH}/system.slice/docker-${ID}.scope/"

Cas d'usage : CAASINC-569 / Alertmanager#

Dans cet incident (voir l'issue Upstream), la consommation mémoire augmente lentement mais continuellement.

Afin de mesurer la consommation mémoire, Alertmanager est lancé avec Docker (pour l'incident, il y avait plus d'options sur la ligne de commande) :

docker run -d --name alertmanager --rm -it "quay.io/prometheus/alertmanager:v0.26.0"

Puis la mesure de la métrique s'effectue avec ce script :

#! /bin/bash

ID=$(docker inspect alertmanager | jq -r '.[0].Id')
CGROUP2=$(cat /proc/mounts|grep cgroup2 | awk '{print $2}')

if [ -d "${CGROUP2}/system.slice/docker-${ID}.scope" ]; then
    memory_usage_in_bytes=$(cat "${CGROUP2}/system.slice/docker-${ID}.scope/memory.current")
    memory_total_inactive_file=$(cat "${CGROUP2}/system.slice/docker-${ID}.scope/memory.stat" | grep inactive_file | awk '{print $2}')
elif [ -d "/sys/fs/cgroup/memory/docker/${ID}" ]; then
    memory_usage_in_bytes=$(cat "/sys/fs/cgroup/memory/docker/${ID}/memory.usage_in_bytes")
    memory_total_inactive_file=$(cat "/sys/fs/cgroup/memory/docker/${ID}/memory.stat" | grep total_inactive_file | awk '{print $2}')
else
    memory_usage_in_bytes=0
    memory_total_inactive_file=0
fi

memory_working_set=${memory_usage_in_bytes}
if [ "$memory_working_set" -lt "$memory_total_inactive_file" ];
then
    memory_working_set=0
else
    memory_working_set=$((memory_usage_in_bytes - memory_total_inactive_file))
fi

echo "memory_working_set $memory_working_set"