Querying a MySQL database with LoadRunner
Let’s imagine that you want to execute arbitary SELECT, INSERT, UPDATE and DELETE queries against a MySQL database from a VuGen script. Obviously it is easiest to use the JDBC libraries from a Java-based script, but most people aren’t licensed for any of the Java-based vuser types.
It is much more useful to be able to interact with MySQL from a C-based vuser script, such as the Web (HTTP/HTML) vuser type. JDS has already released code that allows you to use MySQL instead of the Virtual Table Server from a C-based script, but this code will allow you to run any query you like.
Assuming that you have already installed MySQL and created a user, you must then create a database schema and table(s) to use.
1 2 3 4 5 6 7 8 9 10 11 12 | CREATE DATABASE `loadrunner` ; USE `loadrunner`; CREATE TABLE `test_data` ( `order_id` BIGINT UNSIGNED NOT NULL COMMENT 'Order numbers. Must be unique.', `status` BOOL NOT NULL DEFAULT '0' COMMENT 'Whether data has been used or not. A value of 0 means FALSE.', `date_used` DATETIME NULL COMMENT 'Date/time that the data was used.', UNIQUE ( `order_id` ) ) ENGINE = innodb COMMENT = 'LoadRunner test data'; |
Now you are free to talk to the database from your VuGen script. Here is the example code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | // The MySQL 5.0 C API documentation is available from: // http://dev.mysql.com/doc/refman/5.0/en/c-api-functions.html // Note that this code may have problems with thread safety. // It is therefore best to run each vuser as a process rather than as a thread. Action() { int rc; // return code int db_connection; // Declaractions is a bit dodgy. Should really use MYSQL defined in mysql.h int query_result; // Declaractions is a bit dodgy. Should really use MYSQL_RES defined in mysql.h char** result_row; // Return data as array of strings. Declaractions is a bit dodgy. Should really use MYSQL_ROW defined in mysql.h char *server = "localhost"; char *user = "root"; char *password = ""; // very naughty to leave default root account with no password :) char *database = "loadrunner"; int port = 3306; // default MySQL port int unix_socket = NULL; // leave this as null int flags = 0; // no flags // You should be able to find the MySQL DLL somewhere in your MySQL install directory. rc = lr_load_dll("C:\\LoadRunner\\Lib\\libmysql.dll"); if (rc != 0) { lr_error_message("Could not load libmysql.dll"); lr_abort(); } // Allocate and initialise a new MySQL object db_connection = mysql_init(NULL); if (db_connection == NULL) { lr_error_message("Insufficient memory"); lr_abort(); } // Connect to the database rc = mysql_real_connect(db_connection, server, user, password, database, port, unix_socket, flags); if (rc == NULL) { lr_error_message("%s", mysql_error(db_connection)); mysql_close(db_connection); lr_abort(); } // INSERT a row into the database table lr_param_sprintf("paramInsertQuery", "INSERT INTO test_data (order_id) VALUE (%d)", time(NULL)); // use current time as order ID for this example rc = mysql_query(db_connection, lr_eval_string("{paramInsertQuery}")); if (rc != 0) { lr_error_message("%s", mysql_error(db_connection)); mysql_close(db_connection); lr_abort(); } // SELECT a single value from the database table, and print the result rc = mysql_query(db_connection, "SELECT order_id FROM test_data WHERE status IS FALSE LIMIT 1"); if (rc != 0) { lr_error_message("%s", mysql_error(db_connection)); mysql_close(db_connection); lr_abort(); } query_result = mysql_use_result(db_connection); if (query_result == NULL) { lr_error_message("%s", mysql_error(db_connection)); mysql_free_result(query_result); mysql_close(db_connection); lr_abort(); } result_row = (char **)mysql_fetch_row(query_result); // if the result set had multiple rows, we could keep calling mysql_fetch_row until it returned NULL to get all the rows. if (result_row == NULL) { lr_error_message("Did not expect the result set to be empty"); mysql_free_result(query_result); mysql_close(db_connection); lr_abort(); } lr_save_string(result_row[0], "paramOrderID"); // this parameter will be used when deleting the row. lr_output_message("Order ID is: %s", lr_eval_string("{paramOrderID}")); mysql_free_result(query_result); // SELECT and UPDATE a row in the same step (to avoid concurrency problems if more than 1 vuser is consuming this data). // Note that for transactions to work, your MySQL database must use the InnoDB engine. rc = mysql_query(db_connection, "BEGIN"); // begin the transaction if (rc != 0) { lr_error_message("%s", mysql_error(db_connection)); mysql_close(db_connection); lr_abort(); } rc = mysql_query(db_connection, "SELECT order_id FROM test_data WHERE status IS FALSE LIMIT 1 FOR UPDATE"); // note that "FOR UPDATE" locks the record for reading, so other vusers will not get this value. if (rc != 0) { lr_error_message("%s", mysql_error(db_connection)); mysql_close(db_connection); lr_abort(); } query_result = mysql_use_result(db_connection); if (query_result == NULL) { lr_error_message("%s", mysql_error(db_connection)); mysql_free_result(query_result); mysql_close(db_connection); lr_abort(); } result_row = (char **)mysql_fetch_row(query_result); // if the result set had multiple rows, we could keep calling mysql_fetch_row until it returned NULL to get all the rows. if (result_row == NULL) { lr_error_message("Did not expect the result set to be empty"); mysql_free_result(query_result); mysql_close(db_connection); lr_abort(); } lr_save_string(result_row[0], "paramOrderID"); // this parameter will be used when deleting the row. lr_output_message("Order ID is: %s", lr_eval_string("{paramOrderID}")); mysql_free_result(query_result); lr_param_sprintf("paramUpdateQuery", "UPDATE test_data SET status=TRUE, date_used=NOW() WHERE order_id='%s'", lr_eval_string("{paramOrderID}")); rc = mysql_query(db_connection, lr_eval_string("{paramUpdateQuery}")); // UPDATE row to indicate that data has been used if (rc != 0) { lr_error_message("%s", mysql_error(db_connection)); mysql_close(db_connection); lr_abort(); } rc = mysql_query(db_connection, "COMMIT"); // commit the transaction if (rc != 0) { lr_error_message("%s", mysql_error(db_connection)); mysql_close(db_connection); lr_abort(); } // SELECT a row from the table (this returns an empty record set if table was empty at the beginning) rc = mysql_query(db_connection, "SELECT order_id FROM test_data WHERE status IS FALSE LIMIT 1"); if (rc != 0) { lr_error_message("%s", mysql_error(db_connection)); mysql_close(db_connection); lr_abort(); } query_result = mysql_use_result(db_connection); if (query_result == NULL) { lr_error_message("%s", mysql_error(db_connection)); mysql_free_result(query_result); mysql_close(db_connection); lr_abort(); } result_row = (char **)mysql_fetch_row(query_result); // if the result set had multiple rows, we could keep calling mysql_fetch_row until it returned NULL to get all the rows. if (result_row == NULL) { lr_output_message("Result set is empty as expected"); mysql_free_result(query_result); } else { lr_error_message("Did not expect the result set to contain any rows"); mysql_free_result(query_result); mysql_close(db_connection); lr_abort(); } // DELETE a row from the table lr_param_sprintf("paramDeleteQuery", "DELETE FROM test_data WHERE order_id = '%s'", lr_eval_string("{paramOrderID}")); rc = mysql_query(db_connection, lr_eval_string("{paramDeleteQuery}")); if (rc != 0) { lr_error_message("%s", mysql_error(db_connection)); mysql_close(db_connection); lr_abort(); } // Free the MySQL object created by mysql_init mysql_close(db_connection); return 0; } |
I hope you find this useful. If you find any bugs in the code, please leave a comment.
Related posts:
- Persistent Data in VuGen with MySQL One of the main drawbacks with VuGen is the inability...
- How to handle HTTP POSTs with a changing number of name-value pairs Occasionally you will find that you need to create a...
- The “is it done yet” loop Occasionally you will find that you must write some code...
- Changing LoadRunner/VuGen log options at runtime LoadRunner has a whole bunch of logging options. These can...
- Think time that cannot be ignored Someone asked me once if there was a way of...
You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.
September 3rd, 2009 at 10:59 am
This is a very good idea. I am working on a project and would like to query a DB for the amount of time it takes to process documents. In order to use this code would I have to install MYSQL on each load generator? The max number of vusers for this test is 50. Thank you.
September 3rd, 2009 at 11:01 am
I forgot to mention in my last post that I came across your other post “vugen polling loop” and was going to use that method. However, I will have a margin of error (i.e think time in loop as well as waiting on the system to return a status “complete” while I am refreshing.