Script para controlar el swap

He creado un pequeño script que activa o desactiva el swap según la cantidad de memoria física libre.
Ahora mismo está pensado para tenerlo en autoarranque. Funciona mediante un bucle que, cada cierto tiempo, comprueba la memoria disponible y ejecuta las acciones correspondientes.
Se pueden configurar el tiempo y los estados de memoria libre editando unas variables dentro del propio script; no requiere archivos de configuración ni permite (aún) parámetros externos, y no está pensado para que sea interactivo (se supone que debe ejecutarse solo). Ahora mismo está configurado para repetirse cada 30 segundos, activar el swap cuando queden menos de 200MB libres y desactivarlo cuando queden más de 1000MB.
Para reactivar el swap, por defecto lee la configuración de fstab (es lo más sencillo y estándar, pues swapon tiene un parámetro para ello), pero también puede restablecer el estado en el que se encontraba antes de ejecutar el script descomentando algunas líneas, para entornos más… peculiares.
Requiere añadir permisos de ejecución sin contraseña para swapon y swapoff (instrucciones en los comentarios del script; también se puede hacer editando sudoers).
Aún no está terminado. Los comentarios los he escrito en inglés, pero no soy muy bueno en idiomas, así que aceptaría críticas también sobre eso.

¿Qué os parece? ¿Qué cambiaríais? ¿Qué se puede mejorar?

VERSIÓN 2:

Mostrar código
#!/bin/bash
# -*- ENCODING: UTF-8 -*-

# Permissions for swap:
# sudo chmod 4755 /usr/sbin/swapon
# sudo chmod 4755 /usr/sbin/swapoff

# Repeat each (s):
seconds=30

# Memory free (MB) limits:
tope=1000
critico=200

# On first execution, store the active swap locations (not needed mounting swap using fstab)
swapon_pre=$(swapon| tail -n +2)
# Then, disable the swap
swapoff -a
swaped=false
# state 1: normal, state 2: warning, state 3: critical. Initialised to "normal"
state=1
sleep 3

# Infinite loop:
while true
do

libre=$(free -m | awk '/Mem/{print $7}')

# Actions to do when RAM is critical low:
	if [ $libre -lt $critico ] && [ $state != 3 ]; then
		state=3
		zenity --notification --text="Memoria crítca: "$libre"MB"
		
		if [ $swaped == false ]; then
# If the swap partition is in fstab:
			swapon -a
			zenity --notification --text="Swap on";
# If not, this lines restore the previous swap partitions:
#			cat <<<$swapon_pre | while read line ; do
#					location=$(echo $line | cut -d " " -f 1);
#					swapon $location;
#					zenity --notification --text="Swap on "$location;
#				done
			swaped=true
		fi

# Actions to do when RAM is warning:
	elif [ $libre -gt $critico ] && [ $libre -lt $tope ] && [ $state != 2 ]; then
		state=2
		zenity --notification --text="Memoria baja: "$libre"MB"

# Actions to do when RAM normal:
	elif [ $libre -gt $tope ] && [ $state != 1 ]; then
		state=1
		zenity --notification --text="Memoria normal: "$libre"MB"
		if [ $swaped == true ]; then
			swapoff -a
			zenity --notification --text="Swap off";
			swaped=false
		fi
		

	fi
	sleep $seconds

done

VERSIÓN 3:

Mostrar código
#!/bin/bash
# -*- ENCODING: UTF-8 -*-

# Execution permissions for swapon:
# sudo chmod 4755 /usr/sbin/swapon

# Repeat each (s):
seconds=30

# Memory free (MB) limits:
tope=1000
critico=500

# Languages:
lang=$(locale | grep LANGUAGE= | cut -b 10-15)
echo $lang
case $lang in
	"es_"*)
		txt_normal='Memoria normal: '
		txt_low='Memoria baja: '
		txt_critic='Memoria crítica: '
		txt_swapon_p='Swap activado'
		txt_swapon_f='Swap activado en '
		txt_swapoff='Swap desactivado'
	;;

	*)
		txt_normal='Normal memory:'
		txt_low='Low memory:'
		txt_critic='Critic memory'
		txt_swapon_p='Swap on'
		txt_swapon_f='Swap on for'
		txt_swapoff='Swap off'
	;;
esac

# On first execution, check if swap is configured on fstab.
# If not, store the active swap locations before disabling the swap
fstabswap=$(cat /etc/fstab | grep swap | grep -v was | cut -d " " -f 1)
if [ -n "$fstabswap"  ]
	then
		use_fstab=true
	else
		swapon_pre=$(swapon| tail -n +2)
		use_fstab=false
fi

# Then, disable the swap
swapoff -a
swaped=false
# state 1: normal, state 2: warning, state 3: critical
state=1

# Infinite loop:
while true
do

libre=$(free -m | awk '/Mem/{print $7}')

#### For debug only ####
#echo "¿Memoria libre?"
#read libre

# Actions to do when RAM is critical low:
	if [ $libre -lt $critico ] && [ $state != 3 ]; then
		state=3
		zenity --notification --text="$txt_critic $libre""MB" 2>/dev/null
		if [ $swaped == false ] && [ $use_fstab == true ]; then
			swapon -a
			swaped=true
			zenity --notification --text="$txt_swapon_p" 2>/dev/null;
		elif [ $swaped == false ] && [ $use_fstab == false ]; then
			cat <<<$swapon_pre | while read line
				do
					location=$(echo $line | cut -d " " -f 1);
					swapon $location;
					zenity --notification --text="$txt_swapon_f $location" 2>/dev/null;
				done
			swaped=true;
		fi

# Actions to do when RAM is warning:
	elif [ $libre -gt $critico ] && [ $libre -lt $tope ] && [ $state != 2 ]; then
		state=2
		zenity --notification --text="$txt_low $libre""MB" 2>/dev/null

# Actions to do when RAM normal:
	elif [ $libre -gt $tope ] && [ $state != 1 ]; then
		state=1
		zenity --notification --text="$txt_normal $libre""MB" 2>/dev/null
		if [ $swaped == true ]; then
			swapoff -a
			zenity --notification --text="$txt_swapoff" 2>/dev/null;
			swaped=false
		fi
		

	fi
	sleep $seconds

done

VERSIÓN 4

Mostrar código
#!/bin/bash
# -*- ENCODING: UTF-8 -*-

# Execution permissions for swapon:
# sudo chmod 4755 /usr/sbin/swapon
# sudo chmod 4755 /usr/sbin/swapoff

# Repeat each (s):
seconds=30

# Memory free (MB) limits:
t_warning=1000
t_critic=500

# Languages:
lang=$(locale | grep LANGUAGE= | cut -b 10-15)
echo $lang
case $lang in
	"es_"*)
		txt_normal='Memoria normal: '
		txt_low='Memoria baja: '
		txt_critic='Memoria crítica: '
		txt_swapon_p='Swap activado'
		txt_swapon_f='Swap activado en '
		txt_swapoff='Swap desactivado'
	;;

	*)
		txt_normal='Normal memory:'
		txt_low='Low memory:'
		txt_critic='Critic memory'
		txt_swapon_p='Swap on'
		txt_swapon_f='Swap on for'
		txt_swapoff='Swap off'
	;;
esac

# Variables assigned by parameters:

seconds=30
t_warning=1000
t_critic=500

if [[ $1 ]]; then
	if [ $1 == "-h" ]; then
		echo -e "List of valid parameters:\n -t <seconds> \t Time in seconds for check free memory\n -w <MB> \t Amount of free memory to activate the warning alarm\n -c <MB> \t Amount of free memory to activate the critical actions"
		echo -e "Example: Check memory every 5 minutes (300 seconds) and activate swap when free memory is less than 100 MB:\n\t simpleswap -t 300 -c 100"
		exit
	fi

	while getopts t:w:c: flag
	do
		case "${flag}" in
		    t) seconds=${OPTARG};;
		    w) t_warning=${OPTARG};;
		    c) t_critic=${OPTARG};;
			*) echo -e "Valid arguments: -t <seconds> -w <MB> -c <MB> \nUse -h for help."
			exit;;
		esac
	done
fi
#echo "Seconds: $seconds";
#echo "Warning: $t_warning";
#echo "Critic: $t_critic";

# On first execution, check if swap is configured on fstab.
# If not, store the active swap locations before disabling the swap
fstabswap=$(cat /etc/fstab | grep swap | grep -v was | cut -d " " -f 1)
if [ -n "$fstabswap"  ]
	then
		use_fstab=true
	else
		swapon_pre=$(swapon| tail -n +2)
		use_fstab=false
fi

# Then, disable the swap
swapoff -a
swaped=false
# state 1: normal, state 2: warning, state 3: critical
state=1

# Infinite loop:
while true
do

libre=$(free -m | awk '/Mem/{print $7}')

#### For debug only ####
#echo "¿Memoria libre?"
#read libre

# Actions to do when RAM is critical low:
	if [ $libre -lt $t_critic ] && [ $state != 3 ]; then
		state=3
		zenity --notification --text="$txt_critic $libre""MB" 2>/dev/null
		if [ $swaped == false ] && [ $use_fstab == true ]; then
			swapon -a
			swaped=true
			zenity --notification --text="$txt_swapon_p" 2>/dev/null;
		elif [ $swaped == false ] && [ $use_fstab == false ]; then
			cat <<<$swapon_pre | while read line
				do
					location=$(echo $line | cut -d " " -f 1);
					swapon $location;
					zenity --notification --text="$txt_swapon_f $location" 2>/dev/null;
				done
			swaped=true;
		fi

# Actions to do when RAM is warning:
	elif [ $libre -gt $t_critic ] && [ $libre -lt $t_warning ] && [ $state != 2 ]; then
		state=2
		zenity --notification --text="$txt_low $libre""MB" 2>/dev/null

# Actions to do when RAM normal:
	elif [ $libre -gt $t_warning ] && [ $state != 1 ]; then
		state=1
		zenity --notification --text="$txt_normal $libre""MB" 2>/dev/null
		if [ $swaped == true ]; then
			swapoff -a
			zenity --notification --text="$txt_swapoff" 2>/dev/null;
			swaped=false
		fi
		

	fi
	sleep $seconds

done
3 Me gusta

Antes de cambiarle algo y quebrar la ley de derechos de autor, liberaría el código bajo una licencia de software libre.

Ah claro porque segun las leyes de derechos de autor se aplica el todos los derechos reservados por defecto no?

Si no mal me acuerdo, todo lo que se publica en el foro está bajo CC BY-SA, véase las directrices, ahí se especifica. [1]


  1. Además, es un foro, si no se puede editar el contenido entonces no es un foro. ↩︎

En efecto, mi estimado. Todo código compartido en el foro es abierto.
Tiene libre distribución siempre y cuando se de crédito al creador por la parte que le corresponde.

Me faltó agregar que toda modificación recibe la misma licencia que el trabajo original.

1 me gusta

Sólo es un script para uso personal. Por supuesto que cualquiera puede copiarlo, usarlo, distribuirlo, modificarlo y venderlo. Por mí, el que quiera puede ponerle nombre y sacarlo a pasear, si le apetece.

1 me gusta

Bueno, el script lleva todo el día funcionando en mi ordenador, y le he estado dando caña al sistema, abriendo vídeos online, ejecutando tareas pesadas…
He aplicado unos valores muy conservadores: El aviso aparece al quedar menos de 3GB de RAM, y el swap se activa cuando quedan 2GB. Así puedo ver qué ocurre sin llegar a márgenes “delicados”.
De momento, todo funciona como era de esperar. Los avisos saltan cuando tienen que hacerlo, y el swap se activa y desactiva sin traumas según las necesidades del sistema. Como la mayor parte del tiempo hay memoria suficiente, el swap no almacena datos antiguos (se vacía en cuanto hay oportunidad, volcando todo a RAM física) y basta con cerrar algunas aplicaciones para que todo el sistema vuelva a ir fluido sin tener que reiniciar ni el sistema ni las aplicaciones, ni volcar el swap a RAM a mano.

Creo que éste es el script más complejo que he hecho para mi uso personal hasta ahora, y estoy muy contento con el resultado.


Ahora se me ocurren algunas mejoras que puedo hacer:

  • Que el script detecte si swap está configurado en fstab. Si lo está, que use ese método para montarlo; si no, que almacene el valor original y utilice éste.
  • Permitir que las variables se establezcan con parámetros, en lugar de editando el script, pero que se sigan usando las integradas si no se pasan parámetros.
  • Crear la opción de usar echo en lugar de zenity, para usar desde terminal.
  • Usar notify-send en lugar de zenity si no está disponible.
  • Cambiar el idioma de las notificaciones según la configuración “locale” del sistema. Añadir una cabecera con las traducciones de los textos, para que sea fácil añadir más idiomas o modificarlos. En principio, que use inglés por defecto si no tiene traducción para el idioma local.
  • ¿Cambiar la cadena de if anidados por un case? No sé si eso ayudaría o lo empeoraría. Tengo que probarlo.

Agradecería sugerencias técnicas. No soy programador y no suelo pelearme mucho con bash y sus amigos: seguro que mi código puede pulirse mucho y hacerse más eficiente.

Te recomendaría el case si fuese que en cada caso se realizan tareas más complejas o específicas, en este caso no importa, no te va a mejorar nada.

Muy buen trabajo con el script. Estoy probando lo de las notificaciones porque es muy útil y podría servir hasta para una aplicación de mensajería.

1 me gusta

Si, por defecto toda obra publicada tiene los derechos reservados por la ley del derecho (privilegio) de autor. La única excepción es cuando el autor de la obra la publica bajo una licencia determinada.

Entiendo, por lo que he leído tuyo en otros hilos, que el tema de la licencia es lo que más te atrae.
Por mi parte, tranquilo: creo en el software libre, y participo en lo que mis limitadas capacidades me lo permiten.
Esto no es un programa bancario, precisamente. Es sólo un script para automatizar una tarea cotidiana. Nadie se va a escandalizar por encontrarlo copiado en otro sitio, o porque alguien lo modifique.
Creo que, licencias aparte, para que un autor otorgue permisos no hace falta ningún documento legal, basta con que lo exprese públicamente. Vale, pues ahí voy: Cualquiera puede copiar este script, modificarlo, redistribuirlo, usarlo en otros proyectos, venderlo o atribuírselo (sí, renuncio incluso a ese derecho: a mí me da igual el reconocimiento).
Yo no me dedico a estas cosas profesionalmente. Esto es una afición mía, como montar cosas (Lego, Knex, Meccano…), el bricolaje o la cocina. Vengo a este foro a compartir mis experiencias con otros aficionados, eso es todo.

Te entiendo al 100% si fuera por mi tampoco habrían tales dificultades, pero las licencias son importantes para defendernos de la gran bota del Estado y del privilegio que otorga a algunos para restringir a otros.

Está interesante, pero prefiero emplear zram para crear la partición swap. Sin embargo, tengo una función de python que elimina un proceso que se conoce que puede llegar a usar el swap y lo cancela cuando el mismo emplea cierto porcentaje del swap.

Tu script lo emplearía en HDD, porque en SDD no es recomendable tener una partición swap si deseas que dure varios años.

2 Me gusta

Por lo que veo, zram básicamente comprime los datos en RAM, lo que permite más memoria ocupada a costa de mayor uso de procesador. Es interesante, no lo conocía.

Precisamente tengo el swap configurado en un HDD por eso.

Yo no uso swap para tener “RAM gratis”, sino únicamente para evitar desbordamientos. Si se usa constantemente y está en un SSD, las constantes reescrituras pueden dañar la unidad, pero el uso para el que está previsto el script es únicamente evitar desbordamientos en caso de que el usuario (yo) sea tan descuidado de llenar la memoria.
No quiero que me cierre ningún programa a las bravas, sólo que me avise de que queda poca memoria y, en caso de necesidad, active las “medidas de emergencia” para evitar males mayores.

Aparte está el tema de la duración de los SSD: aunque los primeros sí sufrían mucho por este motivo, hoy día no es tan problemático. De hecho, los sistemas operativos actuales detectan que el “disco” es un SSD y optimizan ciertas tareas para su uso, pero siguen usando paginación por defecto (ya sea en una partición, en un archivo…).

1 me gusta

Acabo de terminar la versión 3 del script (la 1 no la llegué a publicar, era muy básica).
Ya no hay que andar comentando líneas para configurar si el montaje de swap se hace según fstab o según el estado inicial: el script comprueba si fstab tiene información sobre swap; si la tiene, usa swapon -a; si no la tiene, usa el estado inicial.
Ahora tiene una lista con los textos de las notificaciones en distintos idiomas, detecta el que esté configurado en locale y muestra las notificaciones en el idioma correcto. Si no encuentra el idioma en su lista, usa el inglés por defecto. De momento sólo he añadido textos en español…

Lo publico en el primer post.

Lista la versión 4. Ahora acepta parámetros:

  • -h muestra un pequeño texto de ayuda.
  • -t (segundos) establece cada cuánto tiempo debe comprobarse la memoria libre
  • -w indica la cantidad de memoria libre límite entre los estados normal y peligro. Por debajo de esta cantidad, se desactiva el swap.
  • -c indica la cantidad de memoria libre límite entre los estados peligro y crítico. Por encima de esta cantidad, se activa el swap.
    Todos los parámetros son opcionales. Si falta alguno, se tomará el valor por defecto establecido en el script. Si se utiliza un parámetro incorrecto, el programa termina con “Use -h for help”.

Con esta versión es posible configurar el script sin tener que editarlo. Si se pone como autoarranque (con el sistema que sea), la línea ejecutable será script.sh -t segundos -w peligro -c crítico.
P.E.: ~/scripts/simpleswap.sh -t 60 -p 800 -c 100

Lo he publicado en el primer post.