1

I need to test if there's any memory leak in our application and monitor to see if memory usage increases too much while processing the requests. I'm trying to develop some code to make multiple simultaneous calls to our api/webservice method. This api method is not asynchronous and takes some time to complete its operation.

I've made a lot of research about Tasks, Threads and Parallelism, but so far I had no luck. The problem is, even after trying all the below solutions, the result is always the same, it appears to be processing only two requests at the time.

Tried:

-> Creating tasks inside a simple for loop and starting them with and without setting them with TaskCreationOptions.LongRunning

-> Creating threads inside a simple for loop and starting them with and without high priority

-> Creating a list of actions on a simple for loop and starting them using

Parallel.Foreach(list, options, item => item.Invoke)

-> Running directly inside a Parallel.For loop (below)

-> Running TPL methods with and without Options and TaskScheduler

-> Tried with different values for MaxParallelism and maximum threads

-> Checked this post too, but it didn't help either. (Could I be missing something?)

-> Checked some other posts here in Stackoverflow, but with F# solutions that I don't know how to properly translate them to C#. (I never used F#...)

(Task Scheduler class taken from msdn)

Here's the basic structure that I have:

public class Test
{
    Data _data;
    String _url;

    public Test(Data data, string url)
    {
        _data = data;
        _url = url;
    }

    public ReturnData Execute()
    {
         ReturnData returnData;

         using(var ws = new WebService())
         {
              ws.Url = _url;
              ws.Timeout = 600000;

              var wsReturn = ws.LongRunningMethod(data);

              // Basically convert wsReturn to my method return, with some logic if/else etc
         }
         return returnData;
    }
}

sealed class ThreadTaskScheduler : TaskScheduler, IDisposable
    {
        // The runtime decides how many tasks to create for the given set of iterations, loop options, and scheduler's max concurrency level.
        // Tasks will be queued in this collection
        private BlockingCollection<Task> _tasks = new BlockingCollection<Task>();

        // Maintain an array of threads. (Feel free to bump up _n.)
        private readonly int _n = 100;
        private Thread[] _threads;

        public TwoThreadTaskScheduler()
        {
            _threads = new Thread[_n];

            // Create unstarted threads based on the same inline delegate
            for (int i = 0; i < _n; i++)
            {
                _threads[i] = new Thread(() =>
                {
                    // The following loop blocks until items become available in the blocking collection.
                    // Then one thread is unblocked to consume that item.
                    foreach (var task in _tasks.GetConsumingEnumerable())
                    {
                        TryExecuteTask(task);
                    }
                });

                // Start each thread
                _threads[i].IsBackground = true;
                _threads[i].Start();
            }
        }

        // This method is invoked by the runtime to schedule a task
        protected override void QueueTask(Task task)
        {
            _tasks.Add(task);
        }

        // The runtime will probe if a task can be executed in the current thread.
        // By returning false, we direct all tasks to be queued up.
        protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
        {
            return false;
        }

        public override int MaximumConcurrencyLevel { get { return _n; } }

        protected override IEnumerable<Task> GetScheduledTasks()
        {
            return _tasks.ToArray();
        }

        // Dispose is not thread-safe with other members.
        // It may only be used when no more tasks will be queued
        // to the scheduler.  This implementation will block
        // until all previously queued tasks have completed.
        public void Dispose()
        {
            if (_threads != null)
            {
                _tasks.CompleteAdding();

                for (int i = 0; i < _n; i++)
                {
                    _threads[i].Join();
                    _threads[i] = null;
                }
                _threads = null;
                _tasks.Dispose();
                _tasks = null;
            }
        }
   }

And the test code itself:

private void button2_Click(object sender, EventArgs e)
    {
        var maximum = 100;
        var options = new ParallelOptions
        {
             MaxDegreeOfParallelism = 100,
             TaskScheduler = new ThreadTaskScheduler()
        };

        // To prevent UI blocking
        Task.Factory.StartNew(() =>
        {
            Parallel.For(0, maximum, options, i =>
            {
                var data = new Data();
                // Fill data
                var test = new Test(data, _url); //_url is pre-defined
                var ret = test.Execute();

               // Check return and display on screen
               var now = DateTime.Now.ToString("HH:mm:ss");
               var newText = $"{Environment.NewLine}[{now}] - {ret.ReturnId}) {ret.ReturnDescription}";

               AppendTextBox(newText, ref resultTextBox);
           }
     }

    public void AppendTextBox(string value, ref TextBox textBox)
    {
        if (InvokeRequired)
        {
            this.Invoke(new ActionRef<string, TextBox>(AppendTextBox), value, textBox);
            return;
        }
        textBox.Text += value;
    }

And the result that I get is basically this:

[10:08:56] - (0) OK
[10:08:56] - (0) OK
[10:09:23] - (0) OK
[10:09:23] - (0) OK
[10:09:49] - (0) OK
[10:09:50] - (0) OK
[10:10:15] - (0) OK
[10:10:16] - (0) OK
etc

As far as I know there's no limitation on the server side. I'm relatively new to the Parallel/Multitasking world. Is there any other way to do this? Am I missing something?

(I simplified all the code for clearness and I believe that the provided code is enough to picture the mentioned scenarios. I also didn't post the application code, but it's a simple WinForms screen just to call and show results. If any code is somehow relevant, please let me know, I can edit and post it too.)

Thanks in advance!

EDIT1: I checked on the server logs that it's receiving the requests two by two, so it's indeed something related to sending them, not receiving. Could it be a network problem/limitation related to how the framework manages the requests/connections? Or something with the network at all (unrelated to .net)?

EDIT2: Forgot to mention, it's a SOAP webservice.

EDIT3: One of the properties that I send (inside data) needs to change for each request.

EDIT4: I noticed that there's always an interval of ~25 secs between each pair of request, if it's relevant.

Community
  • 1
  • 1
Matheus Lemos
  • 578
  • 4
  • 13
  • Have you tried using a [Web Test](https://msdn.microsoft.com/en-us/library/ms182536(v=vs.90).aspx)? You can record a particular request then have this run as part of a [VS Load Test](https://www.visualstudio.com/en-us/docs/test/performance-testing/getting-started/getting-started-with-performance-testing). – G0dsquad Feb 08 '17 at 13:17
  • Thanks, I didn't tried that, will take a look at it. But I need to test only the webmethod, there no UI. Is it possible to achieve that using Web Tests? And is it possible to run it a LOT of times, simultaneously? Because I need the requests to reach the server simultaneously, or as closest to that. – Matheus Lemos Feb 08 '17 at 13:36
  • Sure, if the WebMethod has a HTTP endpoint you can record a request (e.g. a data POST) with the Web Test. You can then configure the Load Test to do say, 50 parallel users for 10 minutes with realistic think-times. – G0dsquad Feb 08 '17 at 13:47
  • I would agree with other suggestions to use a tool/service rather than build one yourself. I use JMeter it's open source and highly configurable, it also supports parallel requests. You can run it via a GUI or headless to suit your needs. - http://jmeter.apache.org/ – donners45 Feb 08 '17 at 13:53
  • The thing is, some of our clients develop integrations to their systems based on our webservice, and I was trying to simulate what they usually do for calling it. Can I achieve that whith those tools? Because I need not just to send the requests, but check its return and analyze it, as it have different return codes and descriptions. For example, it could return some a mesage informing a failure on business logic, sucessfull code/message, an exception (e.g. OutOfMemoryException), etc. – Matheus Lemos Feb 08 '17 at 15:53
  • I edited the question, there's another thing: for each request I need to change one of the properties of the object that I send. Can I achieve that with those tools? (I'm still reading the docs) – Matheus Lemos Feb 08 '17 at 16:03
  • 1st point: Yes you can check the response of requests for patterns, http codes, content, headers, response times. . . pretty much anything - If there's nothing built in you can of course write your own response handler with a bit of Java. You can then set up a test report to analyse total requests, successful and failed requests. It'll also generate some fancy graphs if that's your thing. 2nd point: Yes, but this will probably require some custom Java code- One case for me was generating a unique nonce for every request which was achieved via a Beanshell preprocessor, all built into Jmeter – donners45 Feb 08 '17 at 16:49

4 Answers4

4

I would recommend not to reinvent the wheel and just use one of the existing solutions:

  1. Most obvious choice: if your Visual Studio license allows you can use MS Load Testing Framework, most likely you won't even have to write a single line of code: How to: Create a Web Service Test
  2. SoapUI is a free and open source web services testing tool, it has some limited load testing capabilities
  3. If for some reasons SoapUI is not suitable (i.e. you need to run load tests in clustered mode from several hosts or you need more enhanced reporting) you can use Apache JMeter - free and open source multiprotocol load testing tool which supports web services load testing as well.
Dmitri T
  • 159,985
  • 5
  • 83
  • 133
  • Thanks for the reply! I edited the question, there's another thing: for each request I need to change one of the properties of the object that I send. Can I achieve that with those tools? (I'm still reading the docs) – Matheus Lemos Feb 08 '17 at 16:04
  • It is possible for all 3 options, with Visual Studio you have all the power of .NET framework, for SoapUI and JMeter you have [Groovy language](https://www.blazemeter.com/blog/groovy-new-black) as a scripting solution, also in JMeter you can configure everything using GUI (randomize the data or get it from CSV files, database, whatever). – Dmitri T Feb 08 '17 at 16:21
  • I managed to make it work by running it on a server, with a way more powerful CPU. It looks like my computer wasn't able to launch as many threads as I wanted, it worked as I excpeted then. But these are some good solutions, I'll keep that in mind for the next times. Thanks! – Matheus Lemos Feb 08 '17 at 18:49
2

A good solution to create load tests without write a own project is use this service https://loader.io/targets

It is free for small tests, you can POST Parameters, Header,... and you have a nice reporting.

Tom Baires
  • 381
  • 3
  • 16
  • Thanks for the response, but we need to make some relatively big tests and our testing environments are not open to the internet. I was looking for something more flexible/controlable, that's why we're trying to make our own test project. – Matheus Lemos Feb 08 '17 at 13:59
2

My favorite load testing library is NBomber. It has an easy and powerful API, realistic user simulations, and provides you with nice HTML reports about latency and requests per second. I used it to test my API and wrote an article about how I did it.

alexcodes
  • 404
  • 3
  • 11
0

Isnt the "two requests at a time" the result of the default maxconnection=2 limit on connectionManagement?

<configuration>  
  <system.net>  
    <connectionManagement>  
      <add address = "http://www.contoso.com" maxconnection = "4" />  
      <add address = "*" maxconnection = "2" />  
    </connectionManagement>  
  </system.net>  
</configuration> 
Reinhard
  • 36
  • 5