0

Notifications d’état de la mémoire sous windows : LOW, STEADY, HIGH

twitterlinkedinmail

Avant de commencer un article un peu profond sur Resource Monitor, j’ai dû faire quelques tests sur le fonctionnement des états mémoire et des notifications dans Windows.

Windows propose depuis sa version XP/2003 et à travers une API, à toute application qui le souhaite de questionner l’état de la mémoire physique, ceci afin de pouvoir prendre des décisions en connaissance de l’état du système, par exemple vérifier qu’il reste de la mémoire avant de procéder à une allocation significative, etc…

L’API consiste en 2 primitives win32: CreateMemoryResourceNotification et QueryMemoryResourceNotification.

On sait que SQL Server depuis la version 2005 fait usage de ce mécanisme pour répondre à une pression mémoire externe (IndicatorsSystem) ou interne (Indicatorsprocess). Dans les faits de nombreuses applications utilisent ce mécanisme. Si je regarde sur mon PC quels sont les processus qui déclarent un objet du noyau qui permet d’interroger ces notifications (via ProcessExplorer):

 

 

 

 

 

 

 

 

 

 

 

 

SQL Server est loin d’être tout seul à écouter.

On parlera de comment ces notifications sont gérées par SQL Server dans un second article. Dans celui-ci, on va s’intéresser au fonctionnement basique de ces notifications dans Windows.

Interroger l’état en utilisant l’API:

Avant de pouvoir demander à windows quel est l’état de la mémoire, il faut initialiser un objet par type d’état que l’on souhaite tester : un pour HIGH et un pour LOW. Ces objets sont de type MEMORY_RESOURCE_NOTIFICATION_TYPE, un ENUM qui ne contient donc que deux valeurs possibles, et on utilisera CreateMemoryResourceNotification pour les initialiser:

	
HANDLE LMhdl, HMhdl ;
MEMORY_RESOURCE_NOTIFICATION_TYPE Low = LowMemoryResourceNotification;
MEMORY_RESOURCE_NOTIFICATION_TYPE High = HighMemoryResourceNotification;
BOOL StateLow, StateHigh;
(...)
// Create des hdl pour le QMRN --------------------------------------------------------------------
LMhdl = CreateMemoryResourceNotification(Low);
if (LMhdl == NULL)
{
	printf_s("Erreur lors de CreateMemoryResourceNotification(Low), lasterrror:%d",GetLastError()) ;
	return -1;
}

HMhdl = CreateMemoryResourceNotification(High);
if (HMhdl == NULL)
{
	printf_s("Erreur lors de CreateMemoryResourceNotification(High), lasterrror:%d",GetLastError()) ;
	return -1;
}

Ensuite on interroge les deux états :

if (QueryMemoryResourceNotification(HMhdl,&StateHigh)) {
	if (StateHigh) {
		printf_s("- L'etat systeme courant est : HIGH\n");
	} 
} else {
	printf_s("Erreur lors de QueryMemoryResourceNotification(High), lasterrror:%d",GetLastError()) ;
}
if (QueryMemoryResourceNotification(LMhdl,&StateLow)) {
	if (StateLow) {
		printf_s("- L'etat systeme courant est : LOW\n");
	} 

} else {
	printf_s("Erreur lors de QueryMemoryResourceNotification(Low), lasterrror:%d",GetLastError()) ;
}

Il existe un troisième état qui se trouve entre HIGH et LOW : STEADY. Par défaut, on sait que les valeurs de seuil sont 64Mb libre pour LOW et HIGH = 3 x LOW, soit 192Mb. Lorsqu’il reste moins de 192Mb mais plus de 64Mb, la mémoire est à l’état STEADY. Il n’y a pas de notification spécifique pour cet état, il est simplement déduit lorsque les deux tests (HIGH et LOW) retournent False:

if ((!StateHigh) && (!StateLow))
{
	printf_s("- L'etat systeme courant est : STEADY \n");
}

Lorsque Windows entre dans l’état STEADY, on remarque qu’il va devenir de plus en plus agressif sur la pagination, et plus on s’approche de la valeur de LOW, plus il va tout mettre en oeuvre pour s’en écarter quitte à paginer ce qui se trouve dans le Paged Pool, donc une partie du système, notamment certains drivers 3rd party et tout le registre, en plus des processus en usermode. Dans les faits Windows est tellement protectionniste qu’il est difficile de le forcer à descendre sous les 64Mb de libre en mémoire physique, tant qu’il lui reste de la place dans son fichier d’échange.

Le source complet de querymemrn.cpp:

Disclaimer : évidemment ce programme est un exemple et ne doit pas être déployé en environnement de production. Il a été écrit pour illustrer un cas de figure et non pour être utilisé comme un outil.

	
// querymemrn.cpp : Defines the entry point for the console application.
// On rappelle par defaut:
// - LowMemoryThreshold (DWORD) = 64Mb
// - HighMemoryThreshold (DWORD) = 3 * LMT = 192Mb
// - entre les deux, Steady state.
// Possibilite de modifier les valeurs sous HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\SessionManager\MemoryManagement
//

#include "stdafx.h"
#include "stdio .h"
#include "stdlib.h"
#include "windows .h"

#define headeroutput "# ----------------------------------------------------------------------------"
#define ONEMEGABYTE 1048576

int _tmain(int argc, _TCHAR* argv[])
{
	// Variables --------------------------------------------------------------------------------------
	MEMORYSTATUSEX msx;
	HANDLE LMhdl, HMhdl ;
	MEMORY_RESOURCE_NOTIFICATION_TYPE Low = LowMemoryResourceNotification, High = HighMemoryResourceNotification;
	BOOL StateLow, StateHigh;

	// En tête ----------------------------------------------------------------------------------------
	printf_s("\n%s\n",headeroutput);
	printf_s("USAGE : querymemrn : Verifie si l'etat de la memoire physique est LOW ou HIGH\n\n");

	msx.dwLength = sizeof(msx);	// Initialisation de la struct
	if (!GlobalMemoryStatusEx(&msx)){
		printf_s("Erreur lors de l apel a GlobalMemoryStatusEx, lasterror:%d",GetLastError());
		return -1;
	} else {
		printf_s("- Memoire physique totale (Mb): %llu\n",msx.ullTotalPhys/ONEMEGABYTE);
		printf_s("- Memoire physique libre  (Mb): %llu\n",msx.ullAvailPhys/ONEMEGABYTE);
		printf_s("- Swap total  (Mb): %llu\n",msx.ullTotalPageFile/ONEMEGABYTE);
		printf_s("- Swap libre  (Mb): %llu\n",msx.ullAvailPageFile/ONEMEGABYTE);
	}

	// Create des hdl pour le QMRN --------------------------------------------------------------------
	LMhdl = CreateMemoryResourceNotification(Low);
	if (LMhdl == NULL)
	{
		printf_s("Erreur lors de CreateMemoryResourceNotification(Low), lasterrror:%d",GetLastError()) ;
		return -1;
	}

	HMhdl = CreateMemoryResourceNotification(High);
	if (LMhdl == NULL)
	{
		printf_s("Erreur lors de CreateMemoryResourceNotification(High), lasterrror:%d",GetLastError()) ;
		return -1;
	}

	if (QueryMemoryResourceNotification(HMhdl,&StateHigh)) {
		if (StateHigh) {
			printf_s("- L'etat systeme courant est : HIGH\n");
		} 
	} else {
		printf_s("Erreur lors de QueryMemoryResourceNotification(High), lasterrror:%d",GetLastError()) ;
	}

	if (QueryMemoryResourceNotification(LMhdl,&StateLow)) {
		if (StateLow) {
			printf_s("- L'etat systeme courant est : LOW\n");
		} 

	} else {
		printf_s("Erreur lors de QueryMemoryResourceNotification(Low), lasterrror:%d",GetLastError()) ;
	}

	// Ni l'un ni l'autre alors state = STEADY
	if ((!StateHigh) && (!StateLow))
	{
		printf_s("- L'etat systeme courant est : STEADY \n");
	}

	printf_s("\n%s\n",headeroutput);

	CloseHandle(HMhdl);
	CloseHandle(HMhdl);
	return 0;
}

Quelques Tests

On va forcer les changements d’état à l’aide d’un petit bout de code maison (allokatorr) qui va allouer pas à pas des blocs de mémoire et les verrouiller avec VirtualLock(*) pour empêcher Windows de pousser ces allocations dans le fichier d’échange.

E:\CAPDATA\DEV\CPP\allokatorr\x64\Debug>allokatorr
Usage : allokatorr
        size of allocation step in bytes
        memory to leave to OS in bytes
        interval between steps in ms
        time in seconds to stay at maximum allocation

Exemple (allocating on 100Mb units in 100ms steps, leaving 400Mb for the OS, waiting for 30 seconds before exiting) :
 allokatorr 104857600 419430400 100 30

Pour les paramètres d’allokatorr:
– Quantité de mémoire allouée et verrouillée en octets.
– Quantité de mémoire physique laissée libre. En pratique on va laisser cette valeur plus la valeur de la prochaine allocation, par exemple si on alloue par pas de 30Mb, qu’on laisse 50Mb et qu’il ne reste que 70Mb de libre, on va rester à 70Mb.
– Intervalle de temps entre 2 allocations en ms. Permet de simuler une surallocation rapide ou plus lente comme une fuite.
– Lorsque le process est arrivée en butée d’allocation, il va tenir cette allocation pendant une période en secondes. Permet simplement d’avoir le temps de visualiser les phénomènes côté système, SQL server, etc…

D’abord on va montrer le passage de HIGH à STEADY en allouant par pas de 30Mb et en ne laissant que 100Mb:

E:\CAPDATA\DEV\CPP\allokatorr\x64\Debug>allokatorr 31457280 104857600 300 120
-------------------------------------------------------------------------------------------
Size of dwpage:4096 bytes
Working Set Min : 41947136 octets
Working Set Max : 8550109184 octets
-------------------------------------------------------------------------------------------
(...)
-------------------------------------------------------------------------------------------
31461376 bytes committed
Verrouillage de 31461376 octets
Working Set Min : 6522990592 octets
Working Set Max : 8550109184 octets
Memoire restante apres allocation de 31461376 octets:222 Mb
-------------------------------------------------------------------------------------------

-------------------------------------------------------------------------------------------
31461376 bytes committed
Verrouillage de 31461376 octets
Working Set Min : 6554451968 octets
Working Set Max : 8550109184 octets
Memoire restante apres allocation de 31461376 octets:202 Mb
-------------------------------------------------------------------------------------------

-------------------------------------------------------------------------------------------
31461376 bytes committed
Verrouillage de 31461376 octets
Working Set Min : 6585913344 octets
Working Set Max : 8550109184 octets
Memoire restante apres allocation de 31461376 octets:172 Mb
-------------------------------------------------------------------------------------------

-------------------------------------------------------------------------------------------
31461376 bytes committed
Verrouillage de 31461376 octets
Working Set Min : 6617374720 octets
Working Set Max : 8550109184 octets
Memoire restante apres allocation de 31461376 octets:144 Mb
-------------------------------------------------------------------------------------------

-------------------------------------------------------------------------------------------
31461376 bytes committed
Verrouillage de 31461376 octets
Working Set Min : 6648836096 octets
Working Set Max : 8550109184 octets
Memoire restante apres allocation de 31461376 octets:114 Mb
-------------------------------------------------------------------------------------------

Attente de 120 secondes avant de rendre la memoire ...

Ça grimpe:


 
Et le résultat de la notification au moment où on passe le seuil des 192Mb:

E:\CAPDATA\DEV\CPP\querymemrn\x64\Debug>querymemrn.exe

# ----------------------------------------------------------------------------
USAGE : querymemrn : Verifie si l etat de la memoire physique est LOW ou HIGH

- Memoire physique totale (Mb): 8174
- Memoire physique libre  (Mb): 225
- Swap total  (Mb): 16346
- Swap libre  (Mb): 5059
- L etat systeme courant est : HIGH

# ----------------------------------------------------------------------------

E:\CAPDATA\DEV\CPP\querymemrn\x64\Debug>querymemrn.exe

# ----------------------------------------------------------------------------
USAGE : querymemrn : Verifie si l etat de la memoire physique est LOW ou HIGH

- Memoire physique totale (Mb): 8174
- Memoire physique libre  (Mb): 202
- Swap total  (Mb): 16346
- Swap libre  (Mb): 5030
- L etat systeme courant est : HIGH

# ----------------------------------------------------------------------------

E:\CAPDATA\DEV\CPP\querymemrn\x64\Debug>querymemrn.exe

# ----------------------------------------------------------------------------
USAGE : querymemrn : Verifie si l etat de la memoire physique est LOW ou HIGH

- Memoire physique totale (Mb): 8174
- Memoire physique libre  (Mb): 144
- Swap total  (Mb): 16346
- Swap libre  (Mb): 4970
- L etat systeme courant est : STEADY

# ----------------------------------------------------------------------------

E:\CAPDATA\DEV\CPP\querymemrn\x64\Debug>querymemrn.exe

# ----------------------------------------------------------------------------
USAGE : querymemrn : Verifie si l etat de la memoire physique est LOW ou HIGH

- Memoire physique totale (Mb): 8174
- Memoire physique libre  (Mb): 113
- Swap total  (Mb): 16346
- Swap libre  (Mb): 4942
- L etat systeme courant est : STEADY

A partir de là, j’entends mon disque dur qui gratte pour aller déplacer tout ce qui peut l’être (chrome, SSMS, Visual Studio, etc…) dans le pagefile…

Toujours plus bas:

Pour aller voir une notification à LOW sans avoir à freezer tout le système, on va tricher en configurant les valeurs de seuils via un DWORD dans le registre sous :

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\SessionManager\MemoryManagement

Nom des clés : LowMemoryThreshold ou HighMemoryThreshold.
Attention avant de lancer regedit, il faut bien se dire que si on monte LowMemoryThreshold trop haut, le système va entrer en mode STEADY plus tôt car HighMemoryThreshold vaut trois fois la valeur de LowMemoryThreshold.

Pour tester on va mettre LowMemoryThreshold à 200Mb:


       
On redémarre la machine pour prendre en compte et c’est reparti:
 

E:\CAPDATA\DEV\CPP\allokatorr\x64\Debug>allokatorr 31457280 104857600 300 120
-------------------------------------------------------------------------------------------
Size of dwpage:4096 bytes
Working Set Min : 41947136 octets
Working Set Max : 8550109184 octets
-------------------------------------------------------------------------------------------
(...)
-------------------------------------------------------------------------------------------
31461376 bytes committed
Verrouillage de 31461376 octets
Working Set Min : 6554451968 octets
Working Set Max : 8550109184 octets
Memoire restante apres allocation de 31461376 octets:172 Mb
-------------------------------------------------------------------------------------------

-------------------------------------------------------------------------------------------
31461376 bytes committed
Verrouillage de 31461376 octets
Working Set Min : 6585913344 octets
Working Set Max : 8550109184 octets
Memoire restante apres allocation de 31461376 octets:174 Mb
-------------------------------------------------------------------------------------------

-------------------------------------------------------------------------------------------
31461376 bytes committed
Verrouillage de 31461376 octets
Working Set Min : 6617374720 octets
Working Set Max : 8550109184 octets
Memoire restante apres allocation de 31461376 octets:144 Mb
-------------------------------------------------------------------------------------------

-------------------------------------------------------------------------------------------
31461376 bytes committed
Verrouillage de 31461376 octets
Working Set Min : 6648836096 octets
Working Set Max : 8550109184 octets
Memoire restante apres allocation de 31461376 octets:125 Mb
-------------------------------------------------------------------------------------------

Attente de 120 secondes avant de rendre la memoire ...

On note au passage le résultat de QueryMemoryResourceNotification lors du passage du nouveau seuil pour High (200*3 = 600Mb):

E:\CAPDATA\DEV\CPP\querymemrn\x64\Debug>querymemrn

# ----------------------------------------------------------------------------
USAGE : querymemrn : Verifie si l etat de la memoire physique est LOW ou HIGH

- Memoire physique totale (Mb): 8174
- Memoire physique libre  (Mb): 671
- Swap total  (Mb): 16346
- Swap libre  (Mb): 7649
- L etat systeme courant est : HIGH

# ----------------------------------------------------------------------------

E:\CAPDATA\DEV\CPP\querymemrn\x64\Debug>querymemrn

# ----------------------------------------------------------------------------
USAGE : querymemrn : Verifie si l etat de la memoire physique est LOW ou HIGH

- Memoire physique totale (Mb): 8174
- Memoire physique libre  (Mb): 623
- Swap total  (Mb): 16346
- Swap libre  (Mb): 7593
- L etat systeme courant est : HIGH

# ----------------------------------------------------------------------------

E:\CAPDATA\DEV\CPP\querymemrn\x64\Debug>querymemrn

# ----------------------------------------------------------------------------
USAGE : querymemrn : Verifie si l etat de la memoire physique est LOW ou HIGH

- Memoire physique totale (Mb): 8174
- Memoire physique libre  (Mb): 595
- Swap total  (Mb): 16346
- Swap libre  (Mb): 7563
- L etat systeme courant est : STEADY

 
Et enfin le passage en LOW sous les 200Mb :

# ----------------------------------------------------------------------------
USAGE : querymemrn : Verifie si l etat de la memoire physique est LOW ou HIGH

- Memoire physique totale (Mb): 8174
- Memoire physique libre  (Mb): 239
- Swap total  (Mb): 16346
- Swap libre  (Mb): 6957
- L etat systeme courant est : STEADY

# ----------------------------------------------------------------------------

E:\CAPDATA\DEV\CPP\querymemrn\x64\Debug>querymemrn

# ----------------------------------------------------------------------------
USAGE : querymemrn : Verifie si l etat de la memoire physique est LOW ou HIGH

- Memoire physique totale (Mb): 8174
- Memoire physique libre  (Mb): 201
- Swap total  (Mb): 16346
- Swap libre  (Mb): 6898
- L etat systeme courant est : STEADY

# ----------------------------------------------------------------------------

E:\CAPDATA\DEV\CPP\querymemrn\x64\Debug>querymemrn

# ----------------------------------------------------------------------------
USAGE : querymemrn : Verifie si l etat de la memoire physique est LOW ou HIGH

- Memoire physique totale (Mb): 8174
- Memoire physique libre  (Mb): 187
- Swap total  (Mb): 16346
- Swap libre  (Mb): 6894
- L etat systeme courant est : LOW

# ----------------------------------------------------------------------------

E:\CAPDATA\DEV\CPP\querymemrn\x64\Debug>querymemrn

# ----------------------------------------------------------------------------
USAGE : querymemrn : Verifie si l etat de la memoire physique est LOW ou HIGH

- Memoire physique totale (Mb): 8174
- Memoire physique libre  (Mb): 172
- Swap total  (Mb): 16346
- Swap libre  (Mb): 6876
- L etat systeme courant est : LOW

# ----------------------------------------------------------------------------

 
Cette fois c’est bon on va pouvoir commencer à générer quelques situations où la machine est à l’état LOW et voir l’interaction avec Resource Monitor dans SQL Server. Mais on garde tout ça pour un prochain épisode bien sûr 🙂

A+

(*) : Attention à ne pas confondre avec le mécanisme des Locked Pages utilisé notamment par SQL Server.

Continuez votre lecture sur le blog :

twitterlinkedinmail

David Baffaleuf

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.