From 6452f1dac620119601e0537302d10534d008f316 Mon Sep 17 00:00:00 2001
From: Lloyd Hilaiel <lloyd@hilaiel.com>
Date: Tue, 16 Aug 2011 19:54:18 +0300
Subject: [PATCH] initial implementation of structure of load generator,
 including encoding probabalistic launching of activities

---
 performance/README.md           |   2 +-
 performance/lib/add_email.js    |   4 ++
 performance/lib/include_only.js |   4 ++
 performance/lib/reauth.js       |   4 ++
 performance/lib/reset_pass.js   |   4 ++
 performance/lib/signin.js       |   4 ++
 performance/lib/signup.js       |   4 ++
 performance/run.js              | 115 +++++++++++++++++++++++++++++++-
 8 files changed, 139 insertions(+), 2 deletions(-)
 create mode 100644 performance/lib/add_email.js
 create mode 100644 performance/lib/include_only.js
 create mode 100644 performance/lib/reauth.js
 create mode 100644 performance/lib/reset_pass.js
 create mode 100644 performance/lib/signin.js
 create mode 100644 performance/lib/signup.js

diff --git a/performance/README.md b/performance/README.md
index d218ae94c..be276d457 100644
--- a/performance/README.md
+++ b/performance/README.md
@@ -28,7 +28,7 @@ The final bit of assumption is growth rate, what percentage of active
 users in a unit of time are using browserid for the first time.  This
 is interesting as different types of requests (with different costs)
 are made during initial user signup.  We start by assuming a 20/80 split
-of new to returning users.
+of new to returning users per month.
 
 The next bit of guesswork required is to explain the behaviors of these
 sites (RPs) that a user visits.  The average RP will set authentication
diff --git a/performance/lib/add_email.js b/performance/lib/add_email.js
new file mode 100644
index 000000000..651bffdac
--- /dev/null
+++ b/performance/lib/add_email.js
@@ -0,0 +1,4 @@
+exports.startFunc = function(cfg, cb) {
+  // XXX: write me
+  setTimeout(function() { cb(); }, 10); 
+};
diff --git a/performance/lib/include_only.js b/performance/lib/include_only.js
new file mode 100644
index 000000000..651bffdac
--- /dev/null
+++ b/performance/lib/include_only.js
@@ -0,0 +1,4 @@
+exports.startFunc = function(cfg, cb) {
+  // XXX: write me
+  setTimeout(function() { cb(); }, 10); 
+};
diff --git a/performance/lib/reauth.js b/performance/lib/reauth.js
new file mode 100644
index 000000000..651bffdac
--- /dev/null
+++ b/performance/lib/reauth.js
@@ -0,0 +1,4 @@
+exports.startFunc = function(cfg, cb) {
+  // XXX: write me
+  setTimeout(function() { cb(); }, 10); 
+};
diff --git a/performance/lib/reset_pass.js b/performance/lib/reset_pass.js
new file mode 100644
index 000000000..651bffdac
--- /dev/null
+++ b/performance/lib/reset_pass.js
@@ -0,0 +1,4 @@
+exports.startFunc = function(cfg, cb) {
+  // XXX: write me
+  setTimeout(function() { cb(); }, 10); 
+};
diff --git a/performance/lib/signin.js b/performance/lib/signin.js
new file mode 100644
index 000000000..651bffdac
--- /dev/null
+++ b/performance/lib/signin.js
@@ -0,0 +1,4 @@
+exports.startFunc = function(cfg, cb) {
+  // XXX: write me
+  setTimeout(function() { cb(); }, 10); 
+};
diff --git a/performance/lib/signup.js b/performance/lib/signup.js
new file mode 100644
index 000000000..651bffdac
--- /dev/null
+++ b/performance/lib/signup.js
@@ -0,0 +1,4 @@
+exports.startFunc = function(cfg, cb) {
+  // XXX: write me
+  setTimeout(function() { cb(); }, 10); 
+};
diff --git a/performance/run.js b/performance/run.js
index e29d65664..9e8d4f4d2 100755
--- a/performance/run.js
+++ b/performance/run.js
@@ -48,7 +48,9 @@ var argv = require('optimist')
 .describe('h', 'display this usage message')
 .alias('m', 'max')
 .describe('m', 'maximum active users to simulate (0 == infinite)')
-.default('m', 100)
+.default('m', 1000)
+.describe('o', 'maximum *outstanding* activities to allow')
+.default('o', 100)
 .alias('s', 'server')
 .describe('s', 'base URL to browserid server')
 .demand('s')
@@ -62,3 +64,114 @@ if (args.h) {
   process.exit(1);
 }
 
+// global configuration
+const configuration = {
+  verifier: argv.v ? argv.v : argv.s + "/verify",
+  browserid: argv.s
+};
+
+// last time we updated stats and added work if necc.
+var lastPoll = new Date();
+
+// average active users simulated over the last second, 5s, and 60s
+var averages = [
+  0.0,
+  0.0,
+  0.0
+];
+
+// activities complete since the last poll
+var completed = {
+};
+
+// activities
+var activity = { 
+  "signup": {
+    // a %20 montly growth rate means there's a 20% probability of
+    // the monthly activity generated by an active user being a
+    // new user signup
+    probability: (1.0 / (40 * 28 * .2))
+  },
+  "reset_pass": { 
+    // users forget their password once every 4 weeks
+    probability: (1.0 / (40 * 28.0))
+  },
+  "add_email": {
+    // users add a new email address once every 2 weeks
+    probability: (1.0 / (40 * 14.0))
+  },
+  "reauth": {
+    // users must re-authenticate to browser id once a week
+    // (once every two weeks per device)
+    probability: (1.0 / (40 * 7.0))
+  },
+  "signin": {
+    // users sign in using existing authentication material
+    // 8 times a day (once ever six hours per device)
+    probability: (8 / 40.0)
+  },
+  
+  "include_only": {
+    // most of the time, users are already authenticated to their
+    // RPs, so the hit on our servers is simply resource (include.js)
+    // inclusion.  The strict probability is 100% - sum of above
+    // probabilities.  We round to 31 / 40.
+    probability: (31 / 40.0)
+  }
+};
+
+// now attach "start functions" to the activity map by including
+// the implementation of each activity
+Object.keys(activity).forEach(function(k) {
+  activity[k].startFunc = require("./lib/" + k).startFunc;
+});
+
+// probs is a 2d array mapping normalized probabilities from 0-1 to
+// activities, used when determining what activity to perform next
+var probs = [];
+Object.keys(activity).forEach(function(k) {
+  var sum = 0;
+  if (probs.length) sum = probs[probs.length - 1][0];
+  sum += activity[k].probability;
+  probs.push([sum, k]);
+});
+
+// and normalize probs into 0..1
+(function() {
+  var max = probs[probs.length - 1][0];
+  for (var i = 0; i < probs.length; i++) {
+    probs[i][0] /= max;
+  }
+})();
+
+function poll() {
+  function startNewActivity() {
+    // what type of activity is this?
+    var n = Math.random();
+    var act = undefined;
+    for (var i = 0; i < probs.length; i++) {
+      if (n <= probs[i][0]) {
+        act = probs[i][1];
+        break;
+      }
+    }
+    // start the activity!
+    activity[act].startFunc(configuration, function() {
+      console.log(act, "complete");
+    });
+  }
+
+  // XXX: next work to be done is here.  upon each call to poll we must:
+  // 1. update running averages based on activites completed while we
+  //    were sleeping.
+  // 2. 
+  // 3. determine how many activities to start based on throttling,
+  //    current outstanding, and current active users being simulated
+  // 4. start those activities
+  // 5. schedule another poll 1s from the time the last was started
+
+  // XXX: test...
+  for (var i = 0; i < 100; i++) startNewActivity();
+}
+
+poll();
-- 
GitLab