Wednesday, April 16, 2014

Averaging over JSON fields




    JSON has become very popular these days and many web services are based on JSON.  It's fairly common to see an interesting question related to JSON in StackOverflow.  This blog looks into a typical JSON test scenario. To paraphrase the question,  a server  sends a JSON response in the following format.

{
"global_id": 11111,
"name": "IMG_001.JPG",
"width": "1111",
"height": "1111",
"time_taken": {
  "segment_1_time": 1,
  "segment_2_time": 1,
  "segment_3_time": 27,
  "segment_4_time": 1,
  "segment_5_time": 56,
  "segment_6_time": 8,
  "total_time": 94
 }
}
  The test platform will need to emulate HTTP clients and:
  • Do a bunch of HTTP transactions.
  • Extract the values of the fields "segment_x_time" from the server response, where x ranges from 1 to 6 after each transaction.
  • Update the running total of each of the 6 fields.
  • Calculate the averages on the 6 fields by dividing the running total by the count at the end of the test.
  The net result is that there should be 6 averages, one for each of segment_1_time, segment_2_time, ... segment_6_time.

   It's raised as a question for JMeter platform, there are a few answers proposed. One post suggested using regular expression to extract the fields, which is not the right approach since it's error-prone.  Another post suggested using a Jmeter plugin for JSON path, but that would require some work on installation and the solution may not work for those without the plugin.

   Even after the values for the 6 fields are extracted, it is still unclear how to do a running total and how to calculate the average at the end of the test.

   It's a simple exercise on NetGend platform. The following short script will print the 6 averages at the end of test.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function userInit() {
    var totals = [];
    var counts = [];
}

function userEND() {
    for (i=1; i< totals.length; i++) {
        printf("average for ${i} is %d\n", totals[i] DIV counts[i]);
    }
}

function VUSER() {
    action(http,"http://www.example.com/test");
    x = fromJson(http.replyBody);
    for (i=1; i<=6; i++) {
        counts[i] ++;
        totals[i] += x.time_taken."segment_${i}_time";
    }

Here is a brief explanation on the script:

  • Lines 1 - 4,  userInit() function is called in the beginning of a test, before the first virtual user starts,  it will initialize two arrays, "totals" will hold the running totals for the fields "segment_x_time", "counts" will keep track of the counts.
  • Lines 6 - 10,  the function userEND() will be called at the end of the test and it will calculate the averages and print them out.
  • Line 13, do a transaction with the server to get the JSON message and store it in variable http.replyBody
  • Line 14, use fromJson() to parse the server response and create a variable x,  we can access any part of JSON message by using variable x,  for example, to get the value for field "global_id", we just need to use x.global_id
  • Line 15, create a simple loop with "i" going from 1 to 6
  • Line 16,  increment counts[i] by 1
  • Line 17,  increase totals[i] by the value of the field "segment_${i}_time"
   The solution looks simple, right?  With a little bit of change the script can even handle a more complex case where not all the 6 fields,  segment_x_time, are present in a server response.  The only change needed (see the bold line) is to check whether the field is present.


1
2
3
4
5
6
7
8
9
function VUSER() {
    action(http,"http://www.example.com/test");
    x = fromJson(http.replyBody);
    for (i=1; i<=6; i++) {
        if (x.time_taken."segment_${i}_time" == "") { continue; }
        counts[i] ++;
        totals[i] += x.time_taken."segment_${i}_time";
    }


   Some of the more astute readers may ask if it is a problem when multiple virtual users try to update the same global variable at the same time. This concern is reasonable because a thread based application may have a race condition when accessing a global variable from different threads.  The access to the data needs to be synchronized.  On the NetGend platform however, accessing to global variables from different VUsers is always synchronized under the covers.  Programmers do not have to worry about these tedious concerns.

    At NetGend, we are proud that we have the right architecture to make difficult test cases easy.  If you have complex test scenarios, you are welcome to give NetGend a try and save yourselves unnecessary headaches.

No comments:

Post a Comment