How to delay execution of the first request of a w

2019-08-05 17:20发布

问题:

In a Visual Studio 2013 Web Performance test, how can I delay sending the very first request in a ".webtest" for a period of time that I specify? My web test is part of a load test. The first request of the web test should be issued after a delay period specified by a data source field. Determining when the request should be issued is simply:

delayPeriod = dataSourceValue - ( timeNow - loadTestStartTime )

Writing this delayPeriod into the think time for a request causes the correct delay. Unfortunately, think times are applied after the response to a request is received. Hence it is easy to delay the second request of the web test until the desired time. I want to delay before the first request.

As a workaround, I have included a dummy first request to http://localhost/ and set the expected status code result to 404. The required delay is set as a think time on this request. But this add an unwanted request to localhost.

Background: We have a log file from a web server. It gives the URL and the time for each request. We want to repeat the requests in that log file at the same rate as when they were recorded.

回答1:

OK, another shot at it. Bare bones though, no error checking.

The LoadTestStartTime load test plugin puts a reference to the load test itself and the load test start time into the web test context.

public class LoadTestStartTime : ILoadTestPlugin
{
    public const string LoadTestContextParameterName = "$LoadTest";
    public const string LoadTestStartTimeContextParameterName = "$LoadTestStartTime";

    public void Initialize(LoadTest loadTest)
    {
        DateTime startTime = default(DateTime);

        loadTest.LoadTestStarting += (sender, e) => { startTime = DateTime.Now; };

        loadTest.TestStarting += (sender,e) => {
            e.TestContextProperties[LoadTestContextParameterName] = loadTest;
            e.TestContextProperties[LoadTestStartTimeContextParameterName] = startTime;
        };
    }
}

The DelayWebTest web test plugin must be attached to a new web test containing one dummy request (the dummy request will not be executed). This new web test must be added to the Load Test Scenario test mix as the "Initialize Test".

The plugin reads the delay time from a context parameter, then sets the scenario's current load to 0. It then sets up a timer which calls an event handler after the properly calculated delay, which restores the load profile allowing the test to proceed. It uses locking so that only the first virtual user will execute the load profile logic while the others wait.

public class DelayWebTest : WebTestPlugin
{
    public string ContextParameterName { get; set; }

    private bool completed = false;
    private object _lock = new object();

    public override void PreRequest(object sender, PreRequestEventArgs e)
    {
        e.Instruction = WebTestExecutionInstruction.Skip;
        if (!completed)
        {
            lock (_lock)
            {
                if (!completed)
                {
                    if (e.WebTest.Context.WebTestUserId > 0) return;
                    LoadTest loadTest = (LoadTest)e.WebTest.Context[LoadTestStartTime.LoadTestContextParameterName];
                    DateTime startTime = (DateTime)e.WebTest.Context[LoadTestStartTime.LoadTestStartTimeContextParameterName];
                    TimeSpan delay = TimeSpan.Parse(e.WebTest.Context[ContextParameterName].ToString());
                    TimeSpan timer = delay - (DateTime.Now - startTime);
                    if (timer > default(TimeSpan))
                    {
                        var loadProfile = loadTest.Scenarios[0].LoadProfile.Copy();
                        loadTest.Scenarios[0].CurrentLoad = 0;
                        Timer t = new Timer() { AutoReset = false, Enabled = true, Interval = timer.TotalMilliseconds };
                        t.Elapsed += (_sender, _e) =>
                        {
                            loadTest.Scenarios[0].LoadProfile = loadProfile;
                            t.Stop();
                        };
                        t.Start();
                    }
                    completed = true;
                }
            }
        }
    }
}

Seems to work. 50 users with 30 second delay:



回答2:

Your solution of the dummy request to localhost is as good as it gets as far as I know. But here's an alternative shot at it.

Two parts:

(1) A load test plugin to copy the load test start time to the web test context:

[DisplayName("Load Test Start Time")]
[Description("Record the load test start time in the context of new web test iterations")]
public class LoadTestStartTime : ILoadTestPlugin
{
    public const string LoadTestStartTimeContextParameterName = "$LoadTestStartTime";

    public void Initialize(LoadTest loadTest)
    {
        DateTime startTime = DateTime.Now;

        // Copy the load test start time into the context of each new test
        loadTest.TestStarting += (sender, e) =>
        {
            e.TestContextProperties[LoadTestStartTimeContextParameterName] = startTime;
        };
    }
}

(2) A web test plugin that gets its delay time from a property which can be a static value or a context parameter, and can be HH:MM:SS.SSS or integer milliseconds:

[DisplayName("Delay WebTest")]
[Description("Delay the start of this WebTest by a certain amount of time.")]
public class DelayWebTest : WebTestPlugin
{
    [Description("Time to delay this WebTest, in HH:MM:SS.SSS time format or in ms (e.g. 00:01:23.456 or 83456 for 1m 23.456s delay)")]
    public string Delay { get; set; }

    public override void PreWebTest(object sender, PreWebTestEventArgs e)
    {
        DateTime start;
        // Make sure this works when not in a load test
        if (!e.WebTest.Context.ContainsKey("$LoadTestUserContext"))
            start = DateTime.Now;
        else
            start = (DateTime)(e.WebTest.Context[LoadTestStartTime.LoadTestStartTimeContextParameterName]);

        TimeSpan elapsed = DateTime.Now - start;
        TimeSpan delay;
        // Support {{context parameter}} substitutions
        if (Delay.StartsWith("{{") && Delay.EndsWith("}}"))
        {
            string key = Delay.Substring(2, Delay.Length - 4);
            object value = e.WebTest.Context[key];
            if (value is TimeSpan) delay = (TimeSpan)value;
            else if (value is int) delay = new TimeSpan(0, 0, 0, 0, (int)value);
            else if (value is string) delay = ParseDelayFromString(value as string);
            else throw new ArgumentException("Delay value must be TimeSpan, int or string.  Found: " + value.GetType());
        }
        else delay = ParseDelayFromString(Delay);

        TimeSpan sleep = delay - elapsed;
        if (sleep > new TimeSpan(0))
        {
            e.WebTest.AddCommentToResult("Current time: " + DateTime.Now);
            e.WebTest.AddCommentToResult("About to sleep for " + sleep);
            System.Threading.Thread.Sleep(sleep);
            e.WebTest.AddCommentToResult("Current time: " + DateTime.Now);
        }
    }

    public static TimeSpan ParseDelayFromString(string s)
    {
        TimeSpan ts;
        if (TimeSpan.TryParse(s, out ts)) return ts;
        int i;
        if (int.TryParse(s, out i)) return new TimeSpan(0, 0, 0, 0, (int)i);
        throw new FormatException("Could not parse value as TimeSpan or int: " + s);
    }
}

This seems to work for me.

However it uses Thread.Sleep which "...is NOT recommended for web tests is because it is a blocking API and more than one web test can share a thread, therefore it can adversely affect more than one vuser." (See Visual Studio Performance Testing Quick Reference Guide) page 187.

So this would certainly be OK for a single-user load test and maybe for small loads where virtual users don't outnumber threads*, but chances are it will screw up the results for larger loads.

(* I don't claim to know how the threading model works however)