Rest performance: It’s a measure of how much rest you can get in a given span of time.
REST performance: It’s a measure of how much REST you can get in a given span of time.
Similar, but subtly and importantly different. My tool will measure the second, but not the first.
https://github.com/lkolbly/perf-rest
Do, check it out. Here’s the sample perf.def.js file, broken up into bite-sized chunks with explanations.
var perf_utils = require(PERF_UTILS_PATH); var mkpoisson = perf_utils.mkpoisson;
Note that the .def.js file is just a NodeJS file, like any other. It gets called with the global PERF_UTILS_PATH variable, which defines the path to the perf utils file. This file contains several one useful utilities, such as mkpoisson. You’ll see how this is useful later.
Instances
var ClientInstance = function() { this.client_id = ""; };
This defines what consistant state is. perf-rest works by maintaining a whole bunch of instances, and moving them between different states according to rules that we’ll define below. One way to think of this is sort of like cookies on a web browser - your web browser may make multiple requests to a web site, but the cookies stay roughly constant. For example here, I’ve defined a variable “client_id” which is a unique ID which the server gives to us.
Requests
var ClientRequests = { "base": { hostname: "localhost", port: 3000, headers: { "Content-Type": "application/json" } },
This is where we start defining the different possible requests that can be made to the REST server in question. Note that this is not a complete request: it is only a base request which other requests build upon. Much like an object-oriented language (I realize that my audience is going to be more and more Java-oriented, sadly, so you can think of it like Java where classes can subclass other classes).
"authenticated": { oncall: function(state, params) { this.headers = {client_id: state.client_id}; }, parent: "base" // The parent request },
Woah! I was just talking about subclassing requests! And here we are. An “authenticated” request has all of the fields of a “base” request.
It also has the oncall function defined. This function is run when the request is run, and is given the instance state. This state is used to create a header. After the oncall function is run, the headers are merged with the headers defined in the base request. Headers are the only thing that are merged; everything else is overwritten. For instance, if the authenticated request had specified a hostname, then it would overwrite the one in it’s parents.
"login": { parent: "base", path: "/login", method: "GET", onfinish: function(res, state) { state.client_id = res.client_id; //console.log("We are client "+state.client_id+"!"); } },
Notice the new onfinish function. This function is called with the parsed response from the request. Note that it’s parsed as JSON: If the remote server doesn’t respond in JSON, then that’s so sad for you. File an issue on Github and I’ll fix it.
The onfinish function, as you can see here, can be used to update state from the server’s response.
"perform_expensive_action": { oncall: function(state, params) { var delayTime = Math.random()*10.0; // Look! You can specify these in the oncall function this.path = "/delay"; this.body = {delaytime: delayTime}; }, parent: "authenticated", // path is specified in the oncall function method: "POST" } };
Hey, you figure it out. Let’s move on to how these requests apply.
States
…not like Alabama or Texas. Like Markov.
var ClientStates = { "preinit": { // Stores the delay for before init is called delay: mkpoisson(60.0) },
There are three special states, preinit, init, and exit. You will see each of these.
Preinit specifies the delay between when the program starts and when the init state is called. Note here the mkpoisson we noticed before. “delay” can be either a number or a function: If it’s a number, then that exact literal number of seconds is used as the delay. If it’s a function, then that function is called and the return value is used as the number of seconds in the delay. What mkpoisson(x) does is return a function which returns a poisson random number centered on x. That is, by setting delay to be “mkpoisson(60.0)”, we have said that the delay for preinit is a poisson-distributed random number centered on 60.0.
"init": { request: "login", transition: [{ // Specify the transition after the request prob: 1.0, dest: "delay_action" }] },
The second of the three special states. When an instance enters the “init” state it is said to be “active” or “alive”, and appears in the output’s instance (“client”) count.
That’s the only special thing about init. Otherwise, it’s just a normal state. “request” specifies which of the above requests gets used. The request gets sent once, when the state is entered. If you don’t want to send a request, use the placeholder “noop”.
“transition” defines which state we transition to after the request is completed. It is a list of probabilities, delays, and destinations. One of these objects is picked out of the list based on the flat probability distribution defined by the “prob”. Note that the sum of the “prob” fields must add to 1, or else undefined behavior occurs.
Once one of the paths is selected, a delay is inserted according to the “delay” field. Obviously, in this instance, the “delay” field is omitted, so the delay is taken to be zero (that is, an immediate transfer to the next state). The “destination” field specifies the name of the state to move to.
"delay_action": { request: "perform_expensive_action", transition: [{ prob: 0.95, delay: mkpoisson(30.0), dest: "delay_action" }, { prob: 0.05, dest: "exit" }] } };
Here, we can see the delay field in use. After the request “perform_expensive_action” is performed, there is a 95% chance that after a delay (defined by a poisson distributed random number centered at 30.0) the instance in question will move to the “delay_action” state. Which, by happenchance, is this state. So it’s a loop. With a 5% probability, the instance will immediately move to the “exit” state. While fairly self-explanatory, I feel I should spell it out. When the exit state is reached, the instance is no longer included in the output’s count of instances/clients, and nothing more can be done.
One last thing before I finish this file.
module.exports = { Requests: ClientRequests, States: ClientStates, Instance: ClientInstance, numClients: 1000 };
This exports the requests, states, instance definition, and the number of clients.
Conclusion
Here I’ve described in pretty good detail my perf-rest project’s definition files. The actual usage of the program is described on the Github page. If you have any questions or comments, please feel free to email me or track me down or file an issue with the Github issue reporter. Also, pull requests are cool. Or requests for support. Or just drop a line and say you used it.