Browse Source

Run_experiments does a diagnostic run, then sets the parameters (epoch_duration, num_WN_to_precompute, enclave memory) for the actual experiment based on these parameters

Sajin 1 year ago
parent
commit
9d3ee2a3ee
8 changed files with 327 additions and 176 deletions
  1. 14 4
      App/launch
  2. 19 5
      App/start.cpp
  3. 2 0
      App/start.hpp
  4. 15 3
      App/teems.cpp
  5. 21 7
      gen_enclave_config.py
  6. 8 7
      gen_manifest.py
  7. 141 89
      logs_to_csv.py
  8. 107 61
      run_experiments.py

+ 14 - 4
App/launch

@@ -19,7 +19,7 @@ PUBKEYS = "pubkeys.yaml"
 # The TEEMS binary
 TEEMS = "./teems"
 
-def launch(node, manifest, config, cmd, log_folder, epoch_time):
+def launch(node, manifest, config, cmd, log_folder, epoch_time, num_epochs, num_WN):
     manifestdata = manifest[node]
     cmdline = ''
     if 'launchprefix' in manifestdata:
@@ -27,7 +27,13 @@ def launch(node, manifest, config, cmd, log_folder, epoch_time):
     cmdline += TEEMS + " -k %s -n %s" % (manifestdata['sprvfile'], node)
 
     if(epoch_time):
-        cmdline+= ' -e ' + str(epoch_time)
+        cmdline+= ' -d ' + str(epoch_time)
+
+    if(num_epochs):
+        cmdline+= ' -e ' + str(num_epochs)
+
+    if(num_WN):
+        cmdline+= ' -w ' + str(num_WN)
 
     if 'args' in manifestdata:
         cmdline += ' ' + manifestdata['args']
@@ -76,8 +82,12 @@ if __name__ == "__main__":
         help='override max number of incoming public messages per user per epoch')
     aparse.add_argument('-l', default=None,
         help='log folder to store logs of each server in an experiment')
-    aparse.add_argument('-e', default=None,
+    aparse.add_argument('-d', default=None,
         help='Set epoch interval time in seconds')
+    aparse.add_argument('-e', default=None,
+        help='Set number of epochs')
+    aparse.add_argument('-w', default=None,
+        help='Set number of Waksman Networks to precompute before starting epochs')
     aparse.add_argument('-r', default=None,
         help='override if routing private channel messages (or public)')
     aparse.add_argument('-n', nargs='*', help='nodes to include')
@@ -114,7 +124,7 @@ if __name__ == "__main__":
         if node == "params":
             continue
         thread = threading.Thread(target=launch,
-            args=(node, manifest, config, args.cmd, args.l, args.e))
+            args=(node, manifest, config, args.cmd, args.l, args.d, args.e, args.w))
         thread.start()
         threadlist.append(thread)
 

+ 19 - 5
App/start.cpp

@@ -5,8 +5,14 @@
 #include "Untrusted.hpp"
 #include "start.hpp"
 
-// epoch_duration is set to 5 seconds by default
+// Default 4 epochs
+int num_epochs = 4;
+// Default epoch_duration of 5 seconds
 int epoch_duration = 5;
+// Default of 12 Waksman Networks (3 per private_route for 4 epochs)
+int num_WN_to_precompute = 12;
+
+#define CEILDIV(x,y) (((x)+(y)-1)/(y))
 
 class Epoch {
     NetIO &netio;
@@ -131,7 +137,6 @@ static void epoch(NetIO &netio, char **args) {
 }
 
 static void epoch_clients(NetIO &netio) {
-
     static uint32_t epoch_num = 1;
     Epoch epoch(netio, epoch_num);
     epoch.proceed();
@@ -157,11 +162,17 @@ static void route_clients_test(NetIO &netio)
     usleep(epoch_interval);
 
     // Precompute some WaksmanNetworks
-    const Config &config = netio.config();
     size_t num_sizes = ecall_precompute_sort(-2);
+    printf("Precompute num_sizes = %ld\n", num_sizes);
+    int num_WN_per_size = int(CEILDIV(num_WN_to_precompute, num_sizes));
+    if(num_WN_per_size <2) {
+        num_WN_per_size = 2;
+    }
+
     for (int i=0;i<int(num_sizes);++i) {
         std::vector<boost::thread> ts;
-        for (int j=0; j<config.nthreads; ++j) {
+        // num_WN_to_precompute = num_sizes * ceil(precompute_WN/epoch_time)
+        for (int j=0; j<num_WN_per_size; ++j) {
             ts.emplace_back([i] {
                 ecall_precompute_sort(i);
             });
@@ -172,7 +183,7 @@ static void route_clients_test(NetIO &netio)
     }
 
     // Run epoch
-    for (int i=1; i<=10; ++i) {
+    for (int i=1; i<=5; ++i) {
         struct timespec tp;
         clock_gettime(CLOCK_REALTIME_COARSE, &tp);
         unsigned long start = tp.tv_sec * 1000000 + tp.tv_nsec/1000;
@@ -254,6 +265,7 @@ static void route_test(NetIO &netio, char **args)
             usleep(epoch_interval_us - (useconds_t)diff);
         }
     }
+
     netio.close();
 }
 
@@ -269,6 +281,8 @@ void start(NetIO &netio, char **args)
 
     if (*args && !strcmp(*args, "route_clients")) {
         ++args;
+        printf("num_epochs = %d, epoch_duration = %d, num_WN_to_precompute = %d\n",
+            num_epochs, epoch_duration, num_WN_to_precompute);
         route_clients_test(netio);
         return;
     }

+ 2 - 0
App/start.hpp

@@ -8,4 +8,6 @@
 void start(NetIO &netio, char **args);
 
 extern int epoch_duration;
+extern int num_epochs;
+extern int num_WN_to_precompute;
 #endif

+ 15 - 3
App/teems.cpp

@@ -129,7 +129,7 @@ static void usage(const char *argv0)
 {
     fprintf(stderr, "Usage: %s --gen sealedprivkeyfile pubkeyfile\n",
         argv0);
-    fprintf(stderr, "or     %s -k sealedprivkeyfile -n myname [-t nthreads] [-e epoch_duration] [command] < config.json\n",
+    fprintf(stderr, "or     %s -k sealedprivkeyfile -n myname [-t nthreads] [-d epoch_duration (in s)] [-e number of epochs] [-w number of Waksman Networks to precompute before starting any epochs] [command] < config.json\n",
         argv0);
     exit(1);
 }
@@ -201,13 +201,25 @@ int main(int argc, char **argv)
                 usage(progname);
             }
             nthreads = uint16_t(atoi(argv[1]));
-            argv += 2;    
-        } else if (!strcmp(*argv, "-e")) {
+            argv += 2;
+        } else if (!strcmp(*argv, "-d")) {
             if (argv[1] == NULL) {
                 usage(progname);
             }
             epoch_duration = size_t(atoi(argv[1]));
             argv += 2;
+        } else if (!strcmp(*argv, "-e")) {
+            if (argv[1] == NULL) {
+                usage(progname);
+            }
+            num_epochs = size_t(atoi(argv[1]));
+            argv += 2;
+        } else if (!strcmp(*argv, "-w")) {
+            if (argv[1] == NULL) {
+                usage(progname);
+            }
+            num_WN_to_precompute = size_t(atoi(argv[1]));
+            argv += 2;
         } else {
             usage(progname);
         }

+ 21 - 7
gen_enclave_config.py

@@ -17,23 +17,37 @@ CONFIG_FILE = "Enclave/Enclave.config.xml"
     M: number of servers
     T: number of threads for each server
     B: msg_size
+    PRIVATE_ROUTE: Private (True) / Public (False) route
+    PRO: PRivate Out
+    PRI: PRivate In
+    PUO: PUblic Out
+    PUI: PUblic In
+    num_WN_to_precompute: The default num_WN_to_precompute is 12 in App/start.cpp
 '''
-def generate_config(N, M, T, B):
+
+def generate_config(N, M, T, B, PRIVATE_ROUTE=True, PRO=1, PRI=1, PUO=1, PUI=1, num_WN_to_precompute=12):
 
     cf = open(CONFIG_FILE, 'w+')
     clients_per_server = math.ceil(N/M)
 
     # Base heap of 2 MB per thread
     heap_size = 2000000 * T
-    # Note: This assumes priv_out = 1 , i.e. only 1 token per client in an epoch
+
+    num_out_mult = PRO
+    if(not(PRIVATE_ROUTE)):
+        num_out_mult = PUO
+
     # Storage and Ingestion data stored per_client = 52 bytes
     heap_size += clients_per_server * (B + 60)
+
     # 4 Buffers of clients_per_server items of B size each
-    heap_size += (clients_per_server * B * 5)
-    # 3 x WN
-    heap_size += (5 * T * (clients_per_server * math.ceil(math.log(clients_per_server,2)) * 8))
-    # 60 MB
-    #heap_size += 60000000
+    heap_size += (clients_per_server * B * 5) * num_out_mult
+
+    # num_WN_to_precompute times size of each WN
+    heap_size += (num_WN_to_precompute * num_out_mult * \
+        (clients_per_server * math.ceil(math.log(clients_per_server,2)) * 8))
+
+
     heap_size_page_aligned = math.ceil(heap_size/4096) * 4096
     hex_heap_size = hex(heap_size_page_aligned)
 

+ 8 - 7
gen_manifest.py

@@ -16,7 +16,7 @@ MANIFEST_FILE = "App/manifest.yaml"
     T: number of threads for each server
     B: msg_size
 '''
-def generate_manifest(N, M, T, B):
+def generate_manifest(N, M, T, B, PRIVATE_ROUTE = True, priv_out=1, priv_in=1, pub_out=1, pub_in=1):
 
     mf = open(MANIFEST_FILE, 'w+')
     manifest_params = '''params:
@@ -25,18 +25,19 @@ def generate_manifest(N, M, T, B):
   # The size of a message in bytes
   msg_size: {B}
   # The number of private messages each user can send per epoch
-  priv_out: 1
+  priv_out: {pro}
   # The number of private messages each user can receive per epoch
-  priv_in: 1
+  priv_in: {pri}
   # The number of public messages each user can send per epoch
-  pub_out: 1
+  pub_out: {puo}
   # The number of public messages each user can receive per epoch
-  pub_in: 1
+  pub_in: {pui}
   # Private or public routing protocol selection
-  private_routing: False
+  private_routing: {PRIVATE_ROUTE}
   # A hardcoded master secret for generating keys to bootstrap
   # client -> server communications
-  master_secret: \"AHardCodedAESKey\"\n'''.format(N = str(N), B = str(B))
+  master_secret: \"AHardCodedAESKey\"\n'''.format(N = str(N), B = str(B), PRIVATE_ROUTE=str(PRIVATE_ROUTE),\
+  pro = str(priv_out), pri = str(priv_in), puo = str(pub_out), pui = str(pub_in))
 
     # print (manifest_params)
     mf.write(manifest_params)

+ 141 - 89
logs_to_csv.py

@@ -6,7 +6,144 @@ import sys
 import math
 import numpy as np
 
-FILE_PARSE_STATE = ["ROUND1", "ESP", "BYTES", "PADDING", "EPOCH"]
+FILE_PARSE_STATE = ["ROUND1", "ESP", "BYTES", "PADDING", "EPOCH", "SCMT"]
+
+'''
+parse_output_logs is used in two ways:
+  (i) For logs_to_csv.py script which produces the output csv given the
+  LOGS_FOLDER with multiple experiment log folders within it. This is done when
+  this script is run directly
+
+  (ii) For run_experiments, which invokes parse_output_logs with (LOGS_FOLDER,
+  experiment_name), takes the LOGS_FOLDER pointing to a diagnostic output
+  folder to extract num_sizes of waksman networks, the WN precomputation time,
+  epoch time, and send_client_mailbox time.
+
+'''
+def parse_output_logs(LOGS_FOLDER, experiment_name, generate_csv = False, op_file = None):
+
+    params = experiment_name.split('_')
+    print (params)
+    n = int(params[0])
+    M = int(params[1])
+    t = int(params[2])
+    b = int(params[3].strip('/'))
+
+    expected_real = math.floor(n/M)
+    epoch_time = []
+    # scm = send_client_mailbox
+    scm_time = []
+    storage_time = []
+    # pwn = precompute_Waksman_Network
+    pwn_time = []
+    bytes_sent = 0
+    num_sizes = 0
+    state = FILE_PARSE_STATE[0]
+    for m in range(1,M+1):
+        EOF = False
+        f = open(os.path.join(LOGS_FOLDER, experiment_name, 's'+str(m)+'.log'),'r')
+        print(os.path.join(LOGS_FOLDER, experiment_name, 's'+str(m)+'.log'))
+        line_cnt = 0
+        while(1):
+            line = f.readline()
+            line_cnt+=1
+            if(line == ""):
+                break
+
+            if('end precompute Waksman Network' in line):
+                value = line.split(' ')[1]
+                value = value.strip('(')
+                value = value.strip(')')
+                pwn_time.append(float(value))
+
+            if('Precompute num_sizes' in line):
+                value = line.split(' ')[-1]
+                num_sizes = int(value)
+
+            elif(state == "ROUND1"):
+                if("Round 1" in line):
+                    state = "PADDING"
+                    #print("R1: " + str(line_cnt))
+
+            elif(state == "ESP"):
+                if("end storage processing" in line):
+                    value = line.split(' ')[1]
+                    value = value.strip('(')
+                    value = value.strip(')')
+                    stg_time = float(value)
+                    storage_time.append(stg_time)
+                    state = "BYTES"
+
+            elif(state == "PADDING"):
+                if('padding' in line):
+                    #print("PADDING: " + str(line_cnt))
+                    words = line.split(' ')
+                    log_real = int(words[0])
+                    if(log_real >= expected_real):
+                        state = "ESP"
+                    else:
+                        state = "ROUND1"
+
+            elif(state == "BYTES"):
+                if('bytes_sent' in line):
+                    words = line.split(' ')
+                    bytes_sent = int(words[-1])
+                    state = "EPOCH"
+
+            elif(state == "EPOCH"):
+                if('Epoch' in line and 'time' in line):
+                    #print("EPOCH: " + str(line_cnt))
+                    nwords = line.split(' ')
+                    epoch_time.append(float(nwords[-2]))
+                    state = "SCMT"
+
+            elif(state == "SCMT"):
+                if('send_client_mailbox time' in line):
+                    scm_time = float(line.split(' ')[-2])
+                    state = "ROUND1"
+
+
+    if(len(epoch_time)!=0):
+        route_time = []
+        for i in range(len(epoch_time)):
+            route_time.append(epoch_time[i] - storage_time[i])
+        epoch_mean = np.mean(epoch_time)
+        route_mean = np.mean(route_time)
+        storage_mean = np.mean(storage_time)
+        scm_mean = np.mean(scm_time)
+        pwn_mean = np.mean(pwn_time)
+
+        epoch_max = np.max(epoch_time)
+        scm_max = np.max(scm_time)
+        pwn_max = np.max(pwn_time)
+
+        epoch_stddev = np.std(epoch_time)
+        route_stddev = np.std(route_time)
+        storage_stddev = np.std(storage_time)
+        scm_stddev = np.std(scm_time)
+        pwn_stddev = np.mean(pwn_time)
+
+        epochs = int(len(epoch_time)/M)
+        print("Num epochs = %d" % epochs);
+        print("Route time = %f +/- %f" %(route_mean, route_stddev))
+        print("Storage time = %f +/- %f" %(storage_mean, storage_stddev))
+        print("Epoch time = %f +/- %f (Max epoch_time = %f)" %(epoch_mean, epoch_stddev, epoch_max))
+        print("PWN time = %f +/- %f (Max PWN = %f)" %(pwn_mean, pwn_stddev, pwn_max))
+        print("SCM time = %f +/- %f (Max SCM = %f)" %(scm_mean, scm_stddev, scm_max))
+
+        if(generate_csv):
+            # Insert it into the output csv file
+            op_line = str(n) + "," + str(M) + "," + str(t) + "," + str(b) + "," + str(epochs) + ","
+            op_line+= "{:.4f}".format(epoch_mean) + (",") + "{:.4f}".format(epoch_stddev) + ","
+            op_line+= "{:.4f}".format(route_mean) + (",") + "{:.4f}".format(route_stddev) + ","
+            op_line+= "{:.4f}".format(storage_mean) + (",") + "{:.4f}".format(storage_stddev) + ","
+            op_line+= str(bytes_sent) + "\n"
+            op_file.write(op_line)
+        else:
+            return(num_sizes, pwn_max, epoch_max, scm_max)
+    else:
+        print("No valid logs for %s" % name)
+
 
 if __name__ == "__main__":
 
@@ -22,92 +159,7 @@ if __name__ == "__main__":
     op_file.write("N,M,T,B,E,epoch_total_time,epoch_stddev,\
 route_time,route_stddev,storage_time,storage_stddev,bytes_sent\n")
 
-    # Iterate over each folder in LOGS_FOLDER
-    for name in os.listdir(LOGS_FOLDER):
-        #print(name)
-        params = name.split('_')
-        print (params)
-        n = int(params[0])
-        M = int(params[1])
-        t = int(params[2])
-        b = int(params[3])
-
-        expected_real = math.floor(n/M)
-        print(name)
-        epoch_time = []
-        storage_time = []
-        bytes_sent = 0
-        state = FILE_PARSE_STATE[0]
-        for m in range(1,M+1):
-            EOF = False
-            f = open(os.path.join(LOGS_FOLDER, name, 's'+str(m)+'.log'),'r')
-            print(os.path.join(LOGS_FOLDER, name, 's'+str(m)+'.log'))
-            line_cnt = 0
-            while(1):
-                line = f.readline()
-                line_cnt+=1
-                if(line == ""):
-                    break
-
-                elif(state == "ROUND1"):
-                    if("Round 1" in line):
-                        state = "PADDING"
-                        #print("R1: " + str(line_cnt))
-
-                elif(state == "ESP"):
-                    if("end storage processing" in line):
-                        value = line.split(' ')[1]
-                        value = value.strip('(')
-                        value = value.strip(')')
-                        stg_time = float(value)
-                        storage_time.append(stg_time)
-                        state = "BYTES"
-
-                elif(state == "PADDING"):
-                    if('padding' in line):
-                        #print("PADDING: " + str(line_cnt))
-                        words = line.split(' ')
-                        log_real = int(words[0])
-                        if(log_real >= expected_real):
-                            state = "ESP"
-                        else:
-                            state = "ROUND1"
-
-                elif(state == "BYTES"):
-                    if('bytes_sent' in line):
-                        words = line.split(' ')
-                        bytes_sent = int(words[-1])
-                        state = "EPOCH"
-
-                elif(state == "EPOCH"):
-                    if('Epoch' in line and 'time' in line):
-                        #print("EPOCH: " + str(line_cnt))
-                        nwords = line.split(' ')
-                        epoch_time.append(float(nwords[-2]))
-                        state = "ROUND1"
+    for exp_name in os.listdir(LOGS_FOLDER):
+        parse_output_logs(LOGS_FOLDER, exp_name, True, op_file)
 
-        if(len(epoch_time)!=0):
-            route_time = []
-            for i in range(len(epoch_time)):
-                route_time.append(epoch_time[i] - storage_time[i])
-            epoch_mean = np.mean(epoch_time)
-            route_mean = np.mean(route_time)
-            storage_mean = np.mean(storage_time)
-            epoch_stddev = np.std(epoch_time)
-            route_stddev = np.std(route_time)
-            storage_stddev = np.std(storage_time)
-            epochs = int(len(epoch_time)/M)
-            print("Num epochs = %d" % epochs);
-            print("Epoch time = %f +/- %f" %(epoch_mean, epoch_stddev))
-            print("Route time = %f +/- %f" %(route_mean, route_stddev))
-            print("Storage time = %f +/- %f" %(storage_mean, storage_stddev))
-
-            # Insert it into the output csv file
-            op_line = str(n) + "," + str(M) + "," + str(t) + "," + str(b) + "," + str(epochs) + ","
-            op_line+= "{:.4f}".format(epoch_mean) + (",") + "{:.4f}".format(epoch_stddev) + ","
-            op_line+= "{:.4f}".format(route_mean) + (",") + "{:.4f}".format(route_stddev) + ","
-            op_line+= "{:.4f}".format(storage_mean) + (",") + "{:.4f}".format(storage_stddev) + ","
-            op_line+= str(bytes_sent) + "\n"
-            op_file.write(op_line)
-        else:
-            print("No valid logs for %s" % name)
+    op_file.close()

+ 107 - 61
run_experiments.py

@@ -17,13 +17,21 @@ import sys
 import math
 from gen_manifest import generate_manifest
 from gen_enclave_config import generate_config
+from logs_to_csv import parse_output_logs
 
 ###############################################################################
 
 # CONFIGS TO SET:
 
 MANIFEST_FILE = "App/manifest.yaml"
-LOG_FOLDER = "Experiments_test/"
+LOG_FOLDER = "Test_diagnostic1/"
+
+NUM_EPOCHS = 5
+PRIVATE_ROUTE = True
+PRIV_OUT = 1
+PRIV_IN = 1
+PUB_OUT = 1
+PUB_IN = 1
 
 # N = number of clients
 # M = number of servers
@@ -35,9 +43,9 @@ LOG_FOLDER = "Experiments_test/"
 # T = [16, 8, 4, 2, 1]
 
 ## Figure 7
-N = [1<<15, 1<<16, 1<<17, 1<<18, 1<<19, 1<<20]
+N = [1<<17]
 M = [4]
-T = [1]
+T = [4]
 
 ## Figure 8
 # N = [1<<20]
@@ -56,27 +64,25 @@ M_MAX = {
 
 # B = message size (bytes)
 B = 256
+NUM_DIAGNOSTIC_EPOCHS = 4
 
 ###############################################################################
 
-def epoch_time(n, m, t, b):
-    # Base epoch time is 15 sec
-    etime_base = 15
+def epoch_time_estimate(n, m, t, b):
+    # Base epoch time is 5 sec
+    etime_base = 5
     clients_per_server = math.ceil(n/m)
-    # Using 1 sec for ~50K clients in compute time
     # Using 8 sec for 2^20 clients in route_compute time as the base for calculations below
-    # (About 1 sec actual route, 6.5 sec for storage generate_tokens
-    # and process_msgs)
     etime_route_compute = 0.8 * math.ceil(clients_per_server/100000)
-    etime_precompute = 1.5 * math.ceil(clients_per_server/100000)
+    etime_precompute = 1 * math.ceil(clients_per_server/100000)
 
     # Costs for Waksman network precompute
-    # Public routing needs 5 WN, private routing needs 3 WNs
+    # Public routing needs 7 WN, private routing needs 3 WNs
     etime_precompute *=5
 
     # Client time:
     # Takes about 30 sec for handling 2^20 clients
-    etime_client = math.ceil(clients_per_server/100000) * 5
+    etime_client = 3 * math.ceil(clients_per_server/100000)
 
     etime = etime_base + etime_precompute + etime_route_compute + etime_client
     return int(etime)
@@ -87,6 +93,10 @@ if __name__ == "__main__":
     if not os.path.exists(LOG_FOLDER):
         os.mkdir(LOG_FOLDER)
 
+    DIAGNOSTIC_FOLDER = LOG_FOLDER + "diagnostic/"
+    if not os.path.exists(DIAGNOSTIC_FOLDER):
+        os.mkdir(DIAGNOSTIC_FOLDER)
+
     for t in T:
         b = B
         m_start = 1
@@ -95,52 +105,88 @@ if __name__ == "__main__":
         for m in M:
             if(m <= m_end):
                 for n in N:
-                    print("\n\n Running Experiment t = %d, m = %d, n = %d \n\n" % (t, m, n))
-                    generate_manifest(n, m, t, b)
-                    generate_config(n, m, t, b)
-                    epoch_interval = epoch_time(n, m, t, b)
-                    print("Epoch_interval = %d" % epoch_interval)
-
-                    log_subfolder = str(n) + "_" + str(m) + "_" + str(t) + "_" + str(b) + "/"
-                    log_subfolder = LOG_FOLDER + log_subfolder
-                    if not os.path.exists(log_subfolder):
-                        os.mkdir(log_subfolder)
-                    # Since launch is invoked from App/ ; we add a ../ to subfolder before
-                    # passing it to slaunch
-                    log_subfolder = "../" + log_subfolder
-
-                    slaunch = []
-                    slaunch.append("./launch")
-                    slaunch.append("route_clients")
-                    slaunch.append("-l")
-                    slaunch.append(log_subfolder)
-                    slaunch.append("-n")
-                    nodes_to_include = ["-n"]
-                    for i in range(1, m+1):
-                        nodes_to_include.append("s"+str(i))
-                        slaunch.append("s"+str(i))
-                    slaunch.append("-e")
-                    slaunch.append(str(epoch_interval))
-
-                    os.chdir("./App")
-                    # Server outputs are captured by log files provided to each of the server
-                    # launch calls made by App/launch
-                    make = subprocess.call(["make", "-C", "..", "-j"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
-                    try:
-                        os.mkdir("keys")
-                    except:
-                        # It's OK if it already exists
-                        pass
-                    pubkeys = subprocess.call(["./getpubkeys"])
-                    server_process = subprocess.run(slaunch)
-
-                    claunch = []
-                    claunch.append("./clientlaunch")
-                    claunch.append("-t")
-                    claunch.append("8")
-                    claunch.append("-l")
-                    claunch.append(log_subfolder+"clients.log")
-                    os.chdir("./../Client/")
-                    client_process = subprocess.call(claunch)
-
-                    os.chdir("./../")
+                    for run in ["diagnostic", "experiment"]:
+                        num_WN_to_precompute = 0
+
+                        # Make the correct output folder for diagnostic/experiment
+                        experiment_name = str(n) + "_" + str(m) + "_" + str(t) + "_" + str(b) + "/"
+                        if(run == "diagnostic"):
+                            log_subfolder = DIAGNOSTIC_FOLDER
+                        elif(run == "experiment"):
+                            log_subfolder = LOG_FOLDER
+                        log_subfolder = log_subfolder + experiment_name
+                        if not os.path.exists(log_subfolder):
+                            os.mkdir(log_subfolder)
+
+                        if(run == "diagnostic"):
+                            print("\n\n   Running DIAGNOSTIC t = %d, m = %d, n = %d \n\n" % (t, m, n))
+                            # Manifest generated by diagnostic can be reused by the actual experiment
+                            generate_manifest(n, m, t, b, PRIVATE_ROUTE, PRIV_OUT, PRIV_IN, PUB_OUT, PUB_IN)
+                            generate_config(n, m, t, b, PRIVATE_ROUTE, PRIV_OUT, PRIV_IN, PUB_OUT, PUB_IN)
+                            epoch_param = epoch_time_estimate(n, m, t, b)
+                        elif(run == "experiment"):
+                            num_sizes, pwn_max, epoch_max, scm_max = parse_output_logs(DIAGNOSTIC_FOLDER, experiment_name)
+                            print("From logs_to_csv: num_sizes = %d, pwn_max = %f, epoch_max = %f, scm_max = %f"
+                            % (num_sizes, pwn_max, epoch_max, scm_max))
+                            print("\n\n   Running EXPERIMENT t = %d, m = %d, n = %d \n\n" % (t, m, n))
+                            num_WN_to_precompute = math.ceil((num_sizes * pwn_max)/epoch_max)
+                            print("num_WN_to_precompute = %d" %(num_WN_to_precompute))
+                            if(num_WN_to_precompute < 2 * num_sizes):
+                                num_WN_to_precompute = 2 * num_sizes
+                            print("num_WN_to_precompute (pushed up to min 2 sets) = %d" %(num_WN_to_precompute))
+                            epoch_param = epoch_max + 2 * scm_max
+                            generate_config(n, m, t, b, PRIVATE_ROUTE, PRIV_IN, PUB_OUT, PUB_IN, num_WN_to_precompute)
+
+                        # Either estimate from epoch_time_estimate for diagnostic
+                        # or the one we got from diagnostic run
+                        epoch_duration = math.ceil(epoch_param)
+                        print("Epoch_duration = %d" % epoch_duration)
+
+                        # Since launch is invoked from App/ ; we add a ../ to subfolder before
+                        # passing it to slaunch
+                        log_subfolder = "../" + log_subfolder
+
+                        slaunch = []
+                        slaunch.append("./launch")
+                        slaunch.append("route_clients")
+                        slaunch.append("-l")
+                        slaunch.append(log_subfolder)
+                        slaunch.append("-n")
+                        nodes_to_include = ["-n"]
+                        for i in range(1, m+1):
+                            nodes_to_include.append("s"+str(i))
+                            slaunch.append("s"+str(i))
+                        slaunch.append("-d")
+                        slaunch.append(str(epoch_duration))
+                        slaunch.append("-e")
+                        if(run == "experiment"):
+                            slaunch.append(str(NUM_EPOCHS))
+                        else:
+                            slaunch.append(str(NUM_DIAGNOSTIC_EPOCHS))
+                        if(run == "experiment"):
+                            slaunch.append("-w")
+                            slaunch.append(str(num_WN_to_precompute))
+
+
+                        os.chdir("./App")
+                        # Server outputs are captured by log files provided to each of the server
+                        # launch calls made by App/launch
+                        make = subprocess.call(["make", "-C", "..", "-j"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
+                        try:
+                            os.mkdir("keys")
+                        except:
+                            # It's OK if it already exists
+                            pass
+                        pubkeys = subprocess.call(["./getpubkeys"])
+                        server_process = subprocess.run(slaunch)
+
+                        claunch = []
+                        claunch.append("./clientlaunch")
+                        claunch.append("-t")
+                        claunch.append("8")
+                        claunch.append("-l")
+                        claunch.append(log_subfolder+"clients.log")
+                        os.chdir("./../Client/")
+                        client_process = subprocess.call(claunch)
+
+                        os.chdir("./../")