📜 ⬆️ ⬇️

How to use .NET from LoadRunner

Although LoadRunner has a good API for various text processing, sometimes it is still not enough, and then you have to expand it with self-written functions. Often, such implementations become the invention of the bicycle, since almost all the tasks, as you know, have already been solved by someone once. In addition, since I have a good background in C #, when solving a task, I often think that this task would be easily solved if I had the .NET Framework class library at hand. In principle, if I were a Java programmer, I would have similar thoughts about Java (where almost everything is also there), but as far as .NET is closer to me, then it will be about him. As a side effect, the article will be useful for those who want to learn how to call CLR code from native code. There is also a small study of the performance of this option and the Visual Studio work template and the LoadRunner script are attached.

.NET, and Java in LoadRunner


To begin, consider an attractive, but bad option. In principle, both .NET and Java work directly in LoadRunner. For each of these platforms, there are classes that represent wrappers over the standard LoadRunner API. They can be used right away by selecting, respectively, the .NET and Java Vuser modes . Let's say right away: these script development modes are created somewhat for another. The .NET mode allows you to record the activity of a .NET application and create a script that directly calls the methods of the application classes. Java Vuser has a full and documented API for Java, but it doesn’t have a recording mode at all (it has Java Record Replay in the sense of .NET ). For this reason, using them for the Web is very problematic, and in general, “using” in this case means “writing some kind of working code” and nothing more. When developing load scripts from a tool, it is important to be able to record traffic and convert it into a script, at least a rough one, which will later be refined. But here's the ill luck: recording web traffic ( Web mode - HTTP / HTML ) is possible only with conversion into C code. The community has been waiting for this for a long time, and I, frankly, hoped that at least in the new version 12.00 it would be possible to choose C # or Java, but this did not happen. In addition, the API for .NET is practically not documented, and you have to act when stepping on a rake in the dark (by the way, method signatures for .NET and Java wrappers are also different). If I can find the necessary wrapper method in the .NET classes relatively quickly, then I don’t know how to transfer the parameters to it.

For example:

namespace Script { public partial class VuserClass { public int Action() { web.url("ya.ru", "URL=http://ya.ru/", null, null); return 0; } } } 

')
This code works and implements a query equivalent to the web_url () C function. The method is attractive because you can connect any .NET libraries to the Run-Time Settings and use them immediately, however, questions immediately arise:



In general, this mode was not made for this purpose, therefore, despite the potential convenience of this option, we will not use it for other purposes and will remain on C. Still, the ability to quickly rewrite a piece of script is more substantial. At the same time, after performing a few simple steps, you can call .NET code from the LoadRunner C-script.

LoadRunner regularly supports calling native libraries, for this there is a function lr_load_dll () . In order to call .NET, you have to write a native layer and, in fact, the whole question comes down to calling the CLR code from the native code. Who knows how to do this, you can skip the next section, for the rest below I will tell you how to do it.

Native DLL with .NET access


Once I needed to decode strings like:

Создать заявку

This is a kind of encoding used internally in XML and HTML: each character is represented as UTF-8 code. I couldn’t do it on LoadRunner (if anyone knows how, please stick my nose). But in the HttpUtility class, there is a HtmlDecode () method, which does it perfectly. Let's see how you can zayuzat it.

Requirements

Naturally, you will need installed in the .NET Framework. Specifically, this method is in any version starting from 2.0 (or maybe earlier, but this is not important anymore), but remember that the native library indicates which version we are accessing, and different versions are not interchangeable. Also remember that Win7 / 2008 is already installed .NET FW 3.5, so if you use load stations on these OSs, then you do not need to install anything, you just need to specify inside the symplus library that we use .NET Framework 3.5.

We write DLL

For some time in Visual Studio, it has become very easy to access .NET classes from C ++ code. To do this, you need to set the Common Language Runtime Support mode in the project settings. Next you need to go to the project properties, Common Properties, Framework and References and add links to the assemblies that you want to use. In this case, we are interested in System.Web from Assemblies / Framework. After that, you can already refer to the .NET classes, however, in C ++ - syntax:

 #include "..\LR include 11.50\lrun.h" //... int xml_http_decode(const char* inputStr, const char* outputParam) { try { System::String^ temp = gcnew System::String(inputStr); System::String^ result = HttpUtility::HtmlDecode(temp); marshal_context^ context = gcnew marshal_context(); lr_save_string(context->marshal_as<const char*>(result), outputParam); } catch(char* message) { lr_save_string(message, outputParam); return LR_FAIL; } catch(...) { lr_save_string("!!! Unknown exception raised !!!", outputParam); return LR_FAIL; } return LR_PASS; } 

LoadRunner can only connect native dlls, so we declare a function with a C-compatible signature and a return value. Further, to declare .NET types and distinguish them from C ++ types, use the "^" sign. We need to create CLR strings from C strings to pass them to .NET methods. To create CLR objects, use the gcnew operator.

So that the function can be called from outside the dll, it needs to be exported. To do this, we write:

 extern "C" { __declspec( dllexport ) int xml_http_decode(const char* inputStr, const char* outputParam); } 

Next, we connect the library to LoadRunner and calmly call our function:

 lr_load_dll("hplr.dll"); // Native DLL  C++. xml_http_decode( "<span class=\"x11z\">&#1057;&#1086;&#1079;&#1076;&#1072;&#1090;&#1100; " "&#1079;&#1072;&#1103;&#1074;&#1082;&#1091;</span>", "p_decoded"); lr_output_message("%s", lr_eval_string("{p_decoded}")); //     . 

If the loadrunner function lr_load_dll () crashes with a confusing error “cannot find the file”, then the matter may not be in the plug-in DLL itself, but in its dependencies. To successfully connect the library compiled in Debug mode, you need to add the files msvcp110d.dll and msvcr110d.dll in System32 or in SysWOW64 for 32-bit and 64-bit OS, respectively. Other dependencies can be investigated using a Dependency Walker tool or similar tool. If the library is compiled in Release mode, then nothing extra is needed (remove also the additional #include and dependencies in the compiler settings).

Functions written in C ++ are available in the DLL right away (do not forget to make an export!). You can write in LoadRunner
relative path to the DLL, starting with the script folder. You do not need to restart VuGen or re-open the script.

So we can refer to the already ready .NET classes. But writing my own C ++ code to work with .NET is, in my opinion, somewhat inconvenient, there are more suitable languages ​​for this.
Note: C ++ also has a bunch of ready-made libraries (and they will work faster, by the way), but first, they need known experience and accuracy, and second, it is beyond the scope of this article.

Appeal to custom library on C #


Suppose we wrote more complex logic in C # and packed it into a separate assembly. How to contact her from LR?
In principle, everything is the same as in the previous case, only the link needs to be added to our assembly (via Solution or Browse) and the classes need to be called from our assembly.

C # DLL

For some reason, LoadRunner can search for regular expressions only in server responses, and how to find a regular expression in the C-string or in the parameter value is not clear (if anyone knows, stick my nose). To solve this problem, you can write the following function:

 // C#-,    input   pattern   //   nGroup    nMatch. namespace HplrCs { public static class HplrHelper { public static string GetRegexMatch(string input, string pattern, int nMatch, int nGroup) { try { var re = new Regex(pattern); var matches = re.Matches(input); if (matches.Count < nMatch + 1) return String.Empty; var match = matches[nMatch]; if (match.Success) { if (match.Groups.Count < nGroup + 1) return String.Empty; return match.Groups[nGroup].Value; } else return String.Empty; } catch (Exception ex) { return ex.ToString(); } } } } 

The assembly with this code we have to put in the Global Assembly Cache (GAC). To do this, you need to use the gacutil.exe utility, which is included with the Windows SDK, and also installed with Visual Studio. For .NET Framework 4.0 / 4.5, you need to use the appropriate version of gacutil.exe from the 8th version of the SDK, earlier versions will not be able to install build 4.0 / 4.5.

gacutil.exe -i HplrCs.dll

Installation in the GAC should be done under admin rights. You can make sure that the assembly is present in the cache like this:

gacutil.exe -l HplrCs

To replace the build version in the GAC, you need to install again, on top of the previous one. You do not need to delete the old version, but it is important to make sure that the installation was successful.

Writing a native wrapper

Again, similar to the first version, we will create a native library, which will be the link between LoadRunner and the .NET assembly:

 #include "..\LR include 11.50\lrun.h" //... extern "C" { __declspec( dllexport ) int get_regex_match( const char* inputStr, const char* pattern, const char* outputParam, int nMatch, int nGroup ); } int get_regex_match(const char* inputStr, const char* pattern, const char* outputParam, int nMatch, int nGroup) { try { System::String^ _inputStr = gcnew System::String(inputStr); System::String^ _pattern = gcnew System::String(pattern); System::String^ result = HplrHelper::GetRegexMatch(_inputStr, _pattern, nMatch, nGroup); marshal_context^ context = gcnew marshal_context(); lr_save_string(context->marshal_as<const char*>(result), outputParam); } catch(char* message) { lr_save_string(message, outputParam); return LR_FAIL; } catch(...) { lr_save_string("!!! Unknown exception raised !!!", outputParam); return LR_FAIL; } return LR_PASS; } 


LR Functions and Constants


You probably noticed that the above code uses functions and constants of LoadRunner. This is possible thanks to what we have done.

 #include "..\LR include 11.50\lrun.h" 


In order for this to gather correctly, you need to add the lrun50.lib library to the linker input. They lie, respectively, in

 C:\Program Files (x86)\HP\LoadRunner\include C:\Program Files (x86)\HP\LoadRunner\setup\dot_net\Vc9\VCWizards\LrCVuserDllLibrary\templates\1033 


With their help, you can call API functions of the LoadRunner in the same way as if you called them from a script.

Example


There is a completely ready template in the form of the MS Visual Studio 2012 project. You can take it by reference .

Archive content:


When you first open a project in Visual Studio, you will be prompted to update the version of the .NET Framework being used. You should not do this if you do not understand the meaning of what is happening. Upgrading the version will oblige you to build a .NET assembly with this version as the Target Framework (in the project properties). By the way, I did not find where in the interface you can change the version of the .NET Framework for a C ++ project. It is shown in the properties of a C ++ project, but only for viewing, it cannot be changed. But you can do this by opening the .vcxproj file in a text editor and finding the TargetFrameworkVersion XML element. Therefore, if you have updated the project, edit TargetFrameworkVersion to the one you need, or switch to using a different version of the .NET Framework.

Performance


The performance of the proposed approach will be the more attractive, the less you are going to use the built-in functions and the more you want to use the code in C LR. If for any purpose there is a built-in LoadRunner function, you should use it, since the native code is behind it, which means that it will work faster.

For example, let's try to find a substring in a string of approximately 32KB (longer to make it difficult, because this is the maximum that allows LoadRunner to allocate in the stack for local parameters). I specifically take the search for a substring, as one of the classical problems, because the algorithms for it, I think, should be optimized as much as possible.

  #define BUFF_SIZE 32700 char buff[BUFF_SIZE]; int i; lr_load_dll("hplr.dll"); // Native DLL. memset(buff, '-', BUFF_SIZE); buff[BUFF_SIZE - 1] = 0; strcpy(buff + BUFF_SIZE - 4, "+++"); lr_start_transaction("Find substring, internal function"); for (i = 0; i < 100000; i++) strstr(buff, "+++"); lr_end_transaction("Find substring, internal function", LR_AUTO); lr_start_transaction("Find substrings, C# function"); for (i = 0; i < 100000; i++) find_substr_net(buff, "+++"); lr_end_transaction("Find substrings, C# function", LR_AUTO); 


I will not give the code on .NET, there is a normal String.IndexOf () behind it. Measurements should be carried out only in the startup mode in the Controller, in VuGen the execution is two orders of magnitude slower.

 Find substring, internal function: 1,505 Find substring, C# function: 13,323 


Well, it is expected. The main execution time is actually the search for the substring, and here the native code gives a significant advantage. Also, the conversion of const char * to System.String slows down.
But we have what to answer. Let the interpreter work independently. To do this, we write a function that performs simple actions in integer arithmetic:

 int int_arithm_lr(int p) { int i; int s = p; for (i = 0; i < 10000; i++) { s += i * (i % 2 * 2 - 1); } return s; } 


The C # code is exactly the same, only with the words public static.
Let's compare the execution speed in both runtimes:

  lr_start_transaction("Integer arithmetics, LR function"); for (i = 0; i < 500; i++) int_arithm_lr(i); lr_end_transaction("Integer arithmetics, LR function", LR_AUTO); lr_start_transaction("Integer arithmetics, C# function"); for (i = 0; i < 500; i++) int_arithm_net(i); lr_end_transaction("Integer arithmetics, C# function", LR_AUTO); 

 Integer arithmetics, LR function: 45,772 Integer arithmetics, C# function: 0,013 


The difference is 3.5 thousand times.

Although the use of such calculations is not typical for load testing scenarios, it exposes the weak side of the LR interpreter: the low speed of its own code. Therefore, if you need to write something sophisticated, then in .NET languages ​​this will not only be more convenient to implement, but it will also work much faster than the C code in LR.

On this, perhaps, everything, thank you for your attention!

Source: https://habr.com/ru/post/231875/


All Articles