lundi 22 décembre 2008

JMeter et asp.net

Voici comment faire pour pour gérer envoyer des tests de charge sur une application ASP.net sous JMeter et gérer correctement le ViewState, et autres ....
Une fois votre plan de test enregistré (cf : Intro à JMeter et Enregistrement du scénario)

Prenons l'exemple d'une page de connexion :


La page est affichée une première fois puis l'utilisateur rempli un formulaire contenant son login et son password.

Il valide et les champs du formulaire (incluant les champs cachés comme le viewstate) sont renvoyés vers la même page.

Il est important pour la cohérence du test que les champs cachés qui sont générés à chaque exécution de la page soient renvoyés conformément. Ajoutez donc deux post processeurs (RegEx Extractor) à votre premier appel à la page de login Ces deux processeurs vont se charger d'extraire la valeur du ViewState et de l'EventValidation et de les enregistrer dans des variables que nous pourront ensuite utiliser comme paramètres du prochain appel de la page.

Paramétrez l'extracteur du ViewState comme suit :
- Nom de référence : nom dans la variable dans laquelle vas être stockée la valeur.
- Expression régulière : <input.*?name=\"__VIEWSTATE\".*?value=\"(.+?)\".*?/>
- Canevas : Numéro de l'occurence à extraire : 1 dans notre cas.

Après l'appel de la première page de login les post processeurs vont charger les valeurs dans les variables et il n'y a ensuite plus qu'à paramétrer l'utilisation de ces mêmes variables dans l'appel suivant de la page.

mardi 9 décembre 2008

Introduction à JMeter

J'ai eu du mal à trouver de la documentation francophone sur l'utilisation de JMeter, c'est pourquoi je propose ici une traduction personnelle des parties de la documentation officielle qui m'ont parues les plus pertinentes pour commencer avec cet outils.


Installation

Un dézippe suffit


Démarrage

Lancer jmeter.bat


Bases

Un plan de test JMeter contient un ou plusieurs groupes de thread, controller logique, sampler, auditeurs, timers, vérifications.
On peut ajouter ou enlever des éléments du plan de test par click droit dans l'arbre de test. De la même manière et à n'importe quel endroit du plan de test on peut sauvegarder un élément par click droit / enregistrer dessus, ou on peut ajouter un élément enfant par click droit / ouvrir dessus.
Sur la partie de droite de l'interface se situe les éléments de configuration de l'élément en cours de sélection.

Pour lancer un plan de test utilisez le menu Lancer / Démarrer
Pour stopper immédiatement l'exécution du plan de test : Lancer / Stop
Pour stopper les threads à la fin de leur tâche courante : Lancer / Eteindre

L'arbre de test contient des éléments à la fois classés par hiérarchie et ordonnés. Certains sont strictement hiérarchiques (Listeners, Config Elements, Post-Procesors, Pre-Processors, Assertions, Timers), et d'autre sont d'abord ordonnés (controllers, samplers). En créant votre plan de test vous créez une liste ordonnée de requêtes (Samplers) qui représente une étape à exécuter. Ces requêtes sont souvent organisées en controllers qui sont ordonnés eux aussi. Il peut y avoir des temps d'attentes (Timers) entre les requêtes, en ajoutant un compteur de temps constant vous affectez le temps d'attente entre toutes les requêtes soeurs et petites soeurs du timers (les enfants de son parent).


Les éléments d'un plan de test

Groupe de threads
Les thread group sont les points d'entrée des plan de test. Tous les controleurs et samplers doivent être enfants d'un groupe de thread. Comme leur nom l'indique, ils controlent un nombre de thread que JMeter utilise pour le test.
Tous les threads exécutent le plan entièrement et indépendamment des autres threads. Les multiples threads simulent les accès concurrents au server.

Controleurs
Il y a deux types de controleurs dans JMeter, Les controleurs d'échantillons et les controleurs logiques (Samplers / Logical). Ils controllent l'exécution d'un test.
Les samplers envoient une requête au server (HTTP Request Sampler -> envois une requête http).
Vous pouvez aussi customiser un sampler en ajoutant des éléments de configuration.
Les controleurs logiques vous permettent de decider quand envoyer la requête. Par exemple, vous pouvez ajouter un controleur logique "interleave" pour alterner en 2 requêtes.

Auditeurs
Les auditeurs fournissent un accès à l'information collecté pendant les test. Ils collectent les informations de tous les éléments de leur niveau ou plus bas.

Timers
Par défaut, les thread envoient les requêtes sans pause entre chaque. Il est recommandé d'ajouter un delay entre les requêtes en ajoutant un timer. Sans cela JMeter peut submerger votre serveur en envoyant trop de requêtes en un très court laps de temps.
Si plusieurs timers sont présents sur les même éléments d'application, leur delais sont additionnés.

Vérifications
Les vérifications vous permettent de vérifier des réponses du serveur. Avec une vérification (Assert) vous vous assurez essentiellement que votre application produit le resultat escompté.
Vous pouvez ajouter des vérifications à n'importe quel sampler, en cas d'échec de la vérification la requête est marqué comme échouée (ce qui fait grimper le pourcentage d'échec dans les auditeurs).

Configurations
Un élément de configuration travail en coopération avec un sampler, il n'envoit pas de requête mais il peut modifier la requête envoyée.
Un élément de configuration est accessible (comme le reste) seulement à l'intérieur de son élément parent. Un élément de configuration placé plus bas dans la hiérarchie est prioritaire par rapport au même élément de configuration placé sur une branche supérieur de l'arbre de test.

Pre Processeurs
Un pre processeur exécute une action avant qu'un sampler soit exécuté. Si un pre processeur est attaché à un sampler, il sera exécuté avant qu'il soit lancé (le sampler !).
Un pre processeur est le plus souvent utiliser pour modifier les paramètres d'une requête juste avant qu'elle tourne.

Post Processeurs
Un post processeur exécute une action après qu'un sampler ait été exécuté. Un post process est le plus souvent utilisé pour analyser les donnée de réponse, souvent pour en extraire des valeurs.


L'ordre d'exécution

  1. Elements de configuration
  2. Pre processeurs
  3. Timers
  4. Sampler
  5. Post processeur (sauf si le resultat du sample est null)
  6. Vérifications (sauf si le resultat du sample est null)
  7. Auditeurs (sauf si le resultat du sample est null)


Créer un plan de test Web

Le shema d'un plan de test tout simple :
  • Plan de test
    • Groupe de thread
      • HTTP Request Defaults (Configure l'URL racine de l'application web)
      • HTTP Cookie Manager (permet la gestion des cookies de l'application web)
      • HTTP Request (Echantillon requête)
      • HTTP Request (Requête 2 vers autre page)
      • Graph Result (Affiche les resultat des tests sous forme de graphique)

Pour un plan de test plus compliqué ajouter HTTP URL Re-writing Modifier, Header Manager ....


Bonnes pratiques

Limiter le nombre des threads
Surcharger le nombre des threads gérés par JMeter peut submerger votre propre machine. Surtout si votre application répond vite, éviter de gérer trop de threads. De plus, si JMeter est surchargé le calcul des informations de timing perdra en exactitude. Si vous avez besoin de tests à plus grand échelle préférez lancer des tests distribués sur plusieurs machines.
tutorial : http://jakarta.apache.org/jmeter/usermanual/jmeter_distributed_testing_step_by_step.pdf

Utilisez le serveur Proxy
Pour préparamétrer un plan de test il vous est possible d'utiliser le serveur proxy intégré à JMeter.
Sous réserve d'un bon préparamétrage ainsi que quelques modifications ultérieures votre plan de test sera déjà créé par la simple manipulation de votre application.
http://jakarta.apache.org/jmeter/usermanual/component_reference.html#HTTP_Proxy_Server
tutorial : http://jakarta.apache.org/jmeter/usermanual/jmeter_proxy_step_by_step.pdf

Variables utilisateur
Certaines applications nécéssitent d'utiliser différentes valeurs pour différent utilisateurs (threads). Par exemple vous pourriez vouloir tester une séquence qui nécéssaite un login unique pour chaque utilisateur. Dans ce cas par exemple :
- créez un fichier text des utilisateurs / mots de passe séparé par des virgule.
- mettez le dans le même répertoire que le plan de test.
- Ajouter un Element de configuration CSV DataSet, nommez les variables USER et PASS
- Remplacez le login original par ${USER} et ${PASS} sur la requête appropriée

Réduire le ressources requises
- Passez en ligne de commande
- utilisez le moins d'auditeurs possible
- plutôt que d'utiliser beaucoup de sampler similaires, utilisez le même dans un controleur de boucle,
et utilisez des variables utilisateur pour varier l'échantillon
- n'utilisez pas le mode fonctionnel
- utilisez des fichiers CSV plutôt que XML
- Ne sauvegardez que les données dont vous avez besoin
- utiliser le moins de vérifications possible

A l'aide ! Mon patron veut que je test notre application

Les question à poser :
- Combien d'utilisateurs en moyenne ?
- Combien d'utilisateurs au maximum ?
- Quand doit on tester (garder en tête que ça peut vraiment crasher un ou plusieurs serveurs) ?
- Est-ce que l'application gère des états (cookies, session, url rewriting, autres ...)

Quels types de test ?
- tests unitaires : peu de volume, juste pour voir si ça marche
- benchmark : volume moyen d'utilisateurs
- tests de charge : maximum d'utilisateurs
- test destructifs : quelle est la limite ?


Site Intéréssants
http://fr.wikipedia.org/wiki/JMeter
http://blog.milamberspace.net/index.php/jmeter-test-de-charges-dun-site-web-mode-demploi
http://fr.wikipedia.org/wiki/JMeter#Liens_externe
http://groups.google.com/group/jmeter-fr
http://jakarta.apache.org/jmeter/usermanual/

lundi 9 juin 2008

MgmtClassGen

Il y'a vraiment quelque chose d'énervant à découvrir un outils pratique qui se trouvait devant notre nez depuis toujours et qu'on a cherché si longuement. MgmtClassGen en fait partie, c'est un des outils de Visual Studio qui permet de générer des classes 'wrapper' fortements typées des objets WMI utilisables depuis le namespace System.Management.

C'est donc en tombant sur cet article :
http://blogs.msdn.com/saveenr/archive/2005/11/18/494366.aspx
que je me suis rendu compte qu'on devrait en préambule au développement sous visual studio, prendre connaissance des quelques outils qui l'accompagne.
http://msdn.microsoft.com/fr-fr/library/d9kh6s92.aspx

l'utilisation est on ne peux plus simple :
La commande "mgmtclassgen.exe Win32_Printer" génére le fichier Printer.CS qui contient une classe très bien commentée permettant d'utiliser l'objet WMI sous-jacent.

Pour celui qui savait déjà rien de neuf, pour moi ce fut une révolution.

mercredi 4 juin 2008

Implémentation d'un objet de gestion d'une transaction sur les fichiers

Implémentation de FileTransaction, un objet simple de sauvegarde et de restauration des fichiers sur lesquels on travail.

/// <summary>
///
Se charge du backup et de la restauration des fichiers qui sont ajoute9 e0 sa Gestion.
/// ne sauvegarde pas 2 fois le meame fichier.
/// </summary>
public class FileTransaction : IDisposable
{
const string DELETE_FILE_MAP = "DELETE";
string BackupFolder;

#region Constructeurs
/// <summary>
///
En utilisant ce constructeur c'est un re9pertoire alle9atoire qui est utilise9 pour la sauvegarde
/// </summary>
public FileTransaction()
: this(
Path.Combine(
Environment.GetEnvironmentVariable("TMP", EnvironmentVariableTarget.Machine),
Path.GetRandomFileName()
)
) { }
/// <summary>
/// </summary>
/// <param name="backupFolder">
Spe9cifie le re9pertoire qui sera utilise9 pour les sauvegardes</param>
public FileTransaction(string backupFolder)
{
if (Directory.Exists(backupFolder)) Directory.Delete(backupFolder, true);
BackupFolder = Directory.CreateDirectory(backupFolder).FullName;
}
#endregion

private readonly
Dictionary<String, String> File_Map = new Dictionary<string, string>();

#region Ajout de fichier e0 la transaction
/// <summary>
///
Ajoute une liste de fichier e0 la transaction
/// </summary>
/// <param name="files"></param>
public void AddFiles(IEnumerable<String> files)
{
foreach (string file in files) AddFile(file);
}

/// <summary>
///
Ajout un fichier e0 la transaction
/// </summary>
/// <param name="file">
Chemin du fichier (relatif ou absolu)</param>
public void AddFile(string file)
{
string saved_file = Path.GetFullPath(file);
// Pas 2 fois le meame fichier quand meame !
if (File_Map.ContainsKey(saved_file)) return;

if (!File.Exists(saved_file))
{
// En cas de RollBack on supprimera le fichier
File_Map.Add(saved_file, DELETE_FILE_MAP);
}
else
{
string renamed_backup_filename = Path.GetFileName(saved_file) + "." + Path.GetRandomFileName();
string backup_file = Path.Combine(BackupFolder, renamed_backup_filename);

// On s'assure que le re9pertoire de copie existe
Directory.CreateDirectory(Path.GetDirectoryName(backup_file));

// c'est parti
File.Copy(saved_file, backup_file);
File_Map.Add(saved_file, backup_file);
}
}
#endregion

#region
Commit
/// <summary>
///
Efface la transaction et la possibilite9 de RollBack
/// </summary>
public void Commit() { Commit(true); }
public void Commit(bool destroyBackup)
{
Clear_File_Map();
if (destroyBackup) DestroyBackup();
}
#endregion

#region
RollBack
/// <summary>
///
Remet tous les fichier sauvegarde9 e0 leur place d'origine
/// </summary>
public void RollBack() { RollBack(true); }
public void RollBack(bool destroyBackup)
{
RestoreFiles();
Clear_File_Map();
if (destroyBackup) DestroyBackup();
}
#endregion

#region
Me9thodes prive9es
void Clear_File_Map()
{
File_Map.Clear();
}

void RestoreFiles()
{
foreach (string file in this.File_Map.Keys)
{
string backup = File_Map[file];

if (backup == DELETE_FILE_MAP)
{
if (File.Exists(file)) File.Delete(file);
}
else
{
string dir = Path.GetDirectoryName(file);
if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
File.Copy(backup, file, true);
}
}
}

void DestroyBackup() { DestroyBackup(this.BackupFolder); }
void DestroyBackup(string backupFolder)
{
if (Directory.Exists(backupFolder)) Directory.Delete(backupFolder, true);
}
#endregion

#region
IDisposable Membres

public void Dispose()
{
if (File_Map.Count != 0) RollBack();
else Clear_File_Map();
}

#endregion
}


Utilisation :

FileTransaction FT = new FileTransaction();
String filename = "MonFichierSuperImportant.dat";
FT.AddFile(filename);
File.Delete(filename);
FT.RollBack();
// le fichier est restauré


Autre utilisation :

using (FileTransaction FT = new FileTransaction())
{
try
{
FT.AddFiles(new string[]{
"fichier1.txt",
"fichier2.txt",
"fichier3.txt"
});

// Traitement atomique : tout ou rien

// Helas quelque chose tourne mal
throw new Exception("TEST");

FT.Commit();
}
catch (Exception ex)
{
FT.RollBack();
}
}


FileTransaction implémente IDispose, ce qui lui donne des possibilités de restauration des fichiers en cas de destruction de l'objet avant le Commit.

string TestFile = "Mon fichier";
using (FileTransaction FT = new FileTransaction())
{
FT.AddFile(TestFile);
File.Delete(TestFile);
}// Dispose ;-)


Ici le fichier est restauré à la fin du using, automatiquement.

Yin Yang Xaml

<Grid>
<
Path Style="{DynamicResource Yin}">
<
Path.Data>
<
PathGeometry>
<
PathGeometry.Figures>
<
PathFigure StartPoint="100,0" IsClosed="False">
<
ArcSegment Point="100,100" Size="25,25" SweepDirection="Clockwise" />
<
ArcSegment Point="100,200" Size="25,25" SweepDirection="Counterclockwise" />
<
ArcSegment Point="100,0" Size="50,50" SweepDirection="Clockwise" />
</
PathFigure>
</
PathGeometry.Figures>
</
PathGeometry>
</
Path.Data>
</
Path>
<
Path Style="{DynamicResource Yang}">
<
Path.Data>
<
PathGeometry>
<
PathGeometry.Figures>
<
PathFigure StartPoint="100,200" IsClosed="False">
<
ArcSegment Point="100,100" Size="25,25" SweepDirection="Clockwise"/>
<
ArcSegment Point="100,0" Size="25,25" SweepDirection="Counterclockwise" />
<
ArcSegment Point="100,200" Size="50,50" SweepDirection="Clockwise" />
</
PathFigure>
</
PathGeometry.Figures>
</
PathGeometry>
</
Path.Data>
</
Path>
<
Path Style="{DynamicResource Yin}">
<
Path.Data>
<
PathGeometry>
<
PathGeometry.Figures>
<
PathFigure StartPoint="100,150" IsClosed="False">
<
ArcSegment Point="100,149" Size="15,15" IsLargeArc="True" SweepDirection="Clockwise" />
</
PathFigure>
</
PathGeometry.Figures>
</
PathGeometry>
</
Path.Data>
</
Path>
<
Path Style="{DynamicResource Yang}">
<
Path.Data>
<
PathGeometry>
<
PathGeometry.Figures>
<
PathFigure StartPoint="100,50" IsClosed="False">
<
ArcSegment Point="100,51" Size="15,15" IsLargeArc="True" SweepDirection="Clockwise" />
</
PathFigure>
</
PathGeometry.Figures>
</
PathGeometry>
</
Path.Data>
</
Path>
</
Grid>

Sans commentaires ....

mercredi 16 avril 2008

System.Net.NetworkInformation

Quelques investigations sur le namespace System.Net.NetworkInformation

Récupérer les adresses Ip de la machine en cours :


static void
Main(string[] args)
{
foreach (NetworkInterface interf in NetworkInterface.GetAllNetworkInterfaces())
{
// Nom et description de l'interface
Console.WriteLine("- {0} ({1})", interf.Name, interf.Description);

// Type et vitesse
Console.WriteLine(
"Type : {0}, Vitesse : {1}, ReceiveOnly : {2}",
interf.NetworkInterfaceType, // Ethernet, AsymmetricDsl, GigabitEthernet, Wireless80211, Unknown ...
interf.Speed,
interf.IsReceiveOnly
);

// Adresse MAC
Console.WriteLine("Mac adress : {0}", interf.GetPhysicalAddress());

// Les Adresses Ip de l'interface
IPInterfaceProperties ipprops = interf.GetIPProperties();
foreach (UnicastIPAddressInformation ip in ipprops.UnicastAddresses)
{
Console.WriteLine("IP adress : {0}/{1}", ip.Address, ip.IPv4Mask);
int DNSnum = 0;
// Et les DNS
foreach (IPAddress dns in ipprops.DnsAddresses)
Console.WriteLine("DNS {0} : {1}", ++DNSnum, dns);
}

Console.WriteLine();
}

Console.ReadKey();
}


Ce qui donne sur ma machine :

- Connexion au réseau local (Broadcom NetXtreme 57xx Gigabit Controller)
Type : Ethernet, Vitesse : 100000000, ReceiveOnly : False
Mac adress : 001#####03E5
IP adress : 192.168.#.##1/255.255.255.0
DNS 1 : ##5.2.0.#0
DNS 2 : ##6.2.0.#0

- Hamachi (Hamachi Network Interface)
Type : Ethernet, Vitesse : 100000000, ReceiveOnly : False
Mac adress : 7##90######2
IP adress : 5.161.##.##/255.0.0.0

- MS TCP Loopback interface (MS TCP Loopback interface)
Type : Loopback, Vitesse : 10000000, ReceiveOnly : False
Mac adress :
IP adress : 127.0.0.1/

(les # sont là pour l'anonymat)