* Added a script (measurement_run.py) for running whitebox measurements
  to measure the amount of time Flows are added to the Network MAP.

* Added a script (measurement_process.py) to generate the CDF
  of the time to install a single Flow:
  cat result.txt | awk '{print $2}' | ./measurement_process.py > result_cdf.txt
diff --git a/web/measurement_process.py b/web/measurement_process.py
new file mode 100755
index 0000000..3187299
--- /dev/null
+++ b/web/measurement_process.py
@@ -0,0 +1,59 @@
+#! /usr/bin/env python
+# -*- Mode: python; py-indent-offset: 4; tab-width: 8; indent-tabs-mode: t; -*-
+
+import functools
+import math
+import sys
+
+## {{{ http://code.activestate.com/recipes/511478/ (r1)
+
+def percentile(N, percent, key=lambda x:x):
+    """
+    Find the percentile of a list of values.
+
+    @parameter N - is a list of values. Note N MUST BE already sorted.
+    @parameter percent - a float value from 0.0 to 1.0.
+    @parameter key - optional key function to compute value from each element of N.
+
+    @return - the percentile of the values
+    """
+    if not N:
+        return None
+    k = (len(N)-1) * percent
+    f = math.floor(k)
+    c = math.ceil(k)
+    if f == c:
+        return key(N[int(k)])
+    d0 = key(N[int(f)]) * (c-k)
+    d1 = key(N[int(c)]) * (k-f)
+    return d0+d1
+
+# median is 50th percentile.
+# median = functools.partial(percentile, percent=0.5)
+## end of http://code.activestate.com/recipes/511478/ }}}
+
+if __name__ == "__main__":
+
+    dict = {}
+
+    #
+    # Read the data from the stdin, and store it in a dictionary.
+    # The dictionary uses lists as values.
+    #
+    data = sys.stdin.readlines()
+    for line in data:
+	words = line.split()
+	thread_n = int(words[0])
+	msec = float(words[1])
+	dict.setdefault(thread_n, []).append(msec)
+
+    #
+    # Compute and print the values: median (50-th), 10-th, and 90-th
+    # percentile:
+    # <key> <median> <10-percentile> <90-percentile>
+    #
+    for key, val_list in sorted(dict.items()):
+	val_10 = percentile(sorted(val_list), 0.1)
+	val_50 = percentile(sorted(val_list), 0.5)
+	val_90 = percentile(sorted(val_list), 0.9)
+	print "%s %s %s %s" % (str(key), str(val_50), str(val_10), str(val_90))
diff --git a/web/measurement_run.py b/web/measurement_run.py
new file mode 100755
index 0000000..6a44512
--- /dev/null
+++ b/web/measurement_run.py
@@ -0,0 +1,101 @@
+#! /usr/bin/env python
+# -*- Mode: python; py-indent-offset: 4; tab-width: 8; indent-tabs-mode: t; -*-
+
+import os
+import string
+import subprocess
+import time
+
+threads_n = [1, 2, 3, 4, 5, 10, 20, 30, 40, 50, 100]
+# threads_n = [1]
+# flows_n = [42, 252, 420, 1008]
+# flow_n = 42
+# flow_n = 1008
+flow_n = 252
+# flow_n = 1
+iterations_n = 10
+# iterations_n = 100
+
+# threads_n = [1]
+# flowdef_filename = "web/flowdef_8node_1008.txt"
+# flowdef_filename = "web/flowdef_8node_252.txt"
+# flowdef_filename = "web/flowdef_8node_42.txt"
+# flowdef_filename = "web/flowdef_8node_1.txt"
+
+def run_command(cmd):
+    """
+    - Run an external command, and return a tuple: stdout as the
+    first argument, and stderr as the second argument.
+    - Returns None if error.
+    """
+    try:
+	pr = subprocess.Popen(cmd, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
+	ret_tuple = pr.communicate();
+	if pr.returncode:
+	    print "%s failed with error code: %s" % (cmd, str(pr.returncode))
+	return ret_tuple
+    except OSError:
+	print "OS Error running %s" % cmd
+
+def run_install_paths(flowdef_filename):
+    # Prepare the flows to measure
+    cmd = "web/measurement_store_flow.py -f " + flowdef_filename
+    os.system(cmd)
+
+def run_measurement(thread_n):
+    # Install the Flow Paths
+    cmd = ["web/measurement_install_paths.py", str(thread_n)]
+    run_command(cmd)
+
+    # Get the measurement data and print it
+    cmd = "web/measurement_get_install_paths_time_nsec.py"
+    r = run_command(cmd)		# Tuple: [<stdout>, <stderr>]
+    res = r[0].split()		# Tuple: [<num>, nsec]
+    nsec_str = res[0]
+    msec = float(nsec_str) / (1000 * 1000)
+
+    # Keep checking until all Flow Paths are installed
+    while True:
+	# time.sleep(3)
+	cmd = ["web/get_flow.py", "all"]
+	r = run_command(cmd)
+	if string.count(r[0], "FlowPath") != flow_n:
+	    continue
+	if string.find(r[0], "NOT") == -1:
+	    break
+
+    # Remove the installed Flow Paths
+    cmd = ["web/delete_flow.py", "all"]
+    run_command(cmd)
+
+    # Keep checking until all Flows are removed
+    while True:
+	# time.sleep(3)
+	cmd = ["web/get_flow.py", "all"]
+	r = run_command(cmd)
+	if r[0] == "":
+	    break
+
+    return msec
+
+
+if __name__ == "__main__":
+
+    # Initial cleanup
+    cmd = "web/measurement_clear_all_paths.py"
+    run_command(cmd)
+
+    # Install the Flow Paths to measure
+    flowdef_filename = "web/flowdef_8node_" + str(flow_n) + ".txt"
+    run_install_paths(flowdef_filename)
+
+    # Do the work
+    for thread_n in threads_n:
+	for n in range(iterations_n):
+	    msec = run_measurement(thread_n)
+	    # Format: <number of threads> <time in ms>
+	    print "%d %f" % (thread_n, msec / flow_n)
+
+    # Cleanup on exit
+    cmd = "web/measurement_clear_all_paths.py"
+    run_command(cmd)