Main Page | Namespace List | Class Hierarchy | Class List | Directories | File List | Namespace Members | Class Members | File Members

RunToolHost.C

Go to the documentation of this file.
00001 // Copyright (c) 2000, Compaq Computer Corporation 
00002 // Copyright (C) 2001, Compaq Computer Corporation
00003 // 
00004 // This file is part of Vesta.
00005 // 
00006 // Vesta is free software; you can redistribute it and/or
00007 // modify it under the terms of the GNU Lesser General Public
00008 // License as published by the Free Software Foundation; either
00009 // version 2.1 of the License, or (at your option) any later version.
00010 // 
00011 // Vesta is distributed in the hope that it will be useful,
00012 // but WITHOUT ANY WARRANTY; without even the implied warranty of
00013 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014 // Lesser General Public License for more details.
00015 // 
00016 // You should have received a copy of the GNU Lesser General Public
00017 // License along with Vesta; if not, write to the Free Software
00018 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00019 
00020 // File: RunToolHost.C
00021 // Last modified on Mon May 23 23:25:03 EDT 2005 by ken@xorian.net         
00022 //      modified on Tue Apr 26 09:42:34 EDT 2005 by irina.furman@intel.com 
00023 //      modified on Wed Oct  4 12:51:53 PDT 2000 by mann  
00024 //      modified on Tue Apr 18 17:28:51 PDT 2000 by yuanyu
00025 
00026 #include <Basics.H>
00027 #include <VestaConfig.H>
00028 #include <RunToolClient.H>
00029 #include <fnmatch.h>
00030 #include "RunToolHost.H"
00031 #include "RunToolClient.H"
00032 #include "ThreadData.H"
00033 
00034 using std::cerr;
00035 using std::endl;
00036 
00037 #define DEBUG(s)
00038 /*#define DEBUG(s) s*/
00039 
00040 // Cached information about a host
00041 struct Host {
00042   Text name;         // hostname[:port] from list
00043   FP::Tag uniqueid;  // unique ID the host's runtool server has chosen
00044   int usecount;      // local threads using host
00045   bool bad;          // host appears to be down, does not match platform,
00046                      //  or is the same as another host on the list
00047   Host() : usecount(0), bad(false) { }
00048 };
00049 
00050 // Descriptor for a platform
00051 struct Platform {
00052   Platform* next;
00053   Text name;
00054   // glob patterns that the host must match to be this platform
00055   Text sysname, release, version, machine;
00056   // minimum hardware characteristics
00057   int cpus, cpuMHz, memKB;
00058   int nhosts;
00059   Host* hosts;
00060   // true if first host was given as "localhost"
00061   bool anchorfirst;
00062   // true if we've printed a message about having difficult finding a
00063   // runtool server slot for this platform.
00064   bool lowSlotsMessagePrinted;
00065 };
00066 
00067 // Data global to this file, protected by mu
00068 static Basics::mutex mu;
00069 static Platform* platforms;
00070 static bool initialized = false;
00071 static float negligibleExternalLoadPerCPU = 0.75;
00072 static unsigned int myhosthash;
00073 static Basics::cond toolDone;
00074 static unsigned int usecountTotal = 0;
00075 
00076 // Forward 
00077 static Platform* LookupPlatform(TextVC *platform, SrcLoc *loc);
00078 
00079 // 
00080 // Host selection for the _run_tool primitive.  
00081 //
00082 // Goals: 
00083 //
00084 // - Always select a host that can build for the specified platform.  
00085 //
00086 // - Try to use a different host for each parallel thread.
00087 //
00088 // - Optionally prefer the local host for the first thread.
00089 //
00090 // - When clients on several machines are all doing parallel builds,
00091 // try to spread the load fairly evenly among the available hosts.
00092 //
00093 // - But from any particular client, try to use the same set of hosts
00094 // repeatedly in order to increase NFS cache locality.
00095 //
00096 // - Try to avoid the following race condition: multiple threads in a
00097 // _par_map try the same host at once, they all find it is not busy,
00098 // and they all start tools on it.
00099 //
00100 // Algorithm:
00101 //
00102 //  1) Look up the platform in the Vesta configuration file to get a
00103 //  pattern that the runtool server's operating system and machine
00104 //  type info must match, and a list of candidate hosts.
00105 //
00106 //  2) Let p be a value that depends on the host where the evaluator
00107 //  is running.  Cyclically permute the list by p places, except that
00108 //  if the first host is the string "localhost", leave it in place and
00109 //  permute only the remainder of the list.
00110 //
00111 //  3) Iterate through the list.  For each host, if the host (A) is
00112 //  running a runtool server, (B) does not identify itself as being
00113 //  the same as another host on the list, (C) matches the pattern,
00114 //  (D) is currently running zero tools, and (E) reports a load average
00115 //  below the threshold configured in [Evaluator]MaxToolLoadPerCPU,
00116 //  then select it and return; otherwise continue.
00117 //  To avoid the race condition mentioned above, as soon as we select
00118 //  a host, we consider it to be running a tool by incrementing a local
00119 //  counter, even if the tool has not actually started yet.
00120 //
00121 //  4) If no host was selected in the previous step, then use the most
00122 //  lightly loaded host of those considered, where load is measured as
00123 //  the greater of the reported load average / number of CPUs or
00124 //  cur_tools/max_tools.  Break ties in favor of hosts considered
00125 //  earlier.
00126 //
00127 //  Note that in step 4, we perform a final check to make sure that we
00128 //  don't submit more tools to the RunToolServer than it's configured
00129 //  to accept.  If we would be, we wait for another thread to finish a
00130 //  tool invocation, then search again for a host to submit the tool
00131 //  to.
00132 //
00133 Text
00134 RunToolHost(TextVC *platform, SrcLoc *loc, /*OUT*/ void*& handle)
00135 {
00136   Host* host = NULL;
00137   mu.lock();
00138   try {
00139     // Look up the platform; see step (1) above.
00140     Platform* plat = LookupPlatform(platform, loc);
00141     int id = ThreadDataGet()->id;
00142     DEBUG(cout << "thread " << id << ": ");
00143 
00144     // Outer loop needed to handle the case where we can't find a slot
00145     // and need to wait.
00146     while(host == NULL)
00147       {
00148         // let's hope this is plenty big.  we really should use
00149         // numeric_limits<float>::infinity() but it doesn't exist in
00150         // our std_env
00151         float lightload = 3.4e38;
00152 
00153         int lighthost = 0;
00154         RunTool::Host_info light_hinfo;
00155 
00156         for (unsigned int ii=0; ii<plat->nhosts; ii++) {
00157           unsigned int i;
00158           // Permute host list; see step (2) above.
00159           if (plat->anchorfirst) {
00160             i = ii ? ((ii + myhosthash) % (plat->nhosts - 1)) + 1 : 0;
00161           } else {
00162             i = (ii + myhosthash) % plat->nhosts;
00163           }
00164           DEBUG(cout << "host " << i << " "); 
00165 
00166           // Consider host i; see step (3) above.
00167           if (plat->hosts[i].bad) continue;
00168           RunTool::Host_info hinfo;
00169           try {
00170             RunTool::get_info(plat->hosts[i].name, /*OUT*/hinfo);
00171           } catch (SRPC::failure f) {
00172             outputMu.lock();    
00173             cerr << "Warning: failed to contact runtool server on host \""
00174                  << plat->hosts[i].name << "\": " << f.msg << endl;
00175             outputMu.unlock();  
00176             plat->hosts[i].bad = true;
00177             continue;
00178           }
00179 
00180           // On first look at host i, check that it's not a duplicate.
00181           if (hinfo.uniqueid != plat->hosts[i].uniqueid) {
00182             int j;
00183             plat->hosts[i].uniqueid = hinfo.uniqueid;
00184             for (j=0; j<plat->nhosts; j++) {
00185               if (j != i && !plat->hosts[j].bad &&
00186                   plat->hosts[j].uniqueid == plat->hosts[i].uniqueid) {
00187                 DEBUG(cout << "(duplicate of " << j << ") ");
00188                 plat->hosts[i].bad = true;
00189                 break;
00190               }
00191             }
00192             if (plat->hosts[i].bad) continue;
00193           }
00194 
00195           // Check if platform description pattern matches this host
00196           if(fnmatch(plat->sysname.cchars(), hinfo.sysname.cchars(), 0) != 0 ||
00197              fnmatch(plat->release.cchars(), hinfo.release.cchars(), 0) != 0 ||
00198              fnmatch(plat->version.cchars(), hinfo.version.cchars(), 0) != 0 ||
00199              fnmatch(plat->machine.cchars(), hinfo.machine.cchars(), 0) != 0 ||
00200              hinfo.cpus < plat->cpus ||
00201              hinfo.cpuMHz < plat->cpuMHz ||
00202              hinfo.memKB < plat->memKB) {
00203 
00204             // No
00205             if (i > 0) {
00206               outputMu.lock();  
00207               cerr << "Warning: runtool server on host \""
00208                    << plat->hosts[i].name << "\" does not match platform \""
00209                    << plat->name << "\"." << endl;
00210               outputMu.unlock();          
00211             }
00212             plat->hosts[i].bad = true;
00213             continue;
00214           }     
00215 
00216           // Add usage by local threads to the load, since there is a race
00217           // condition between asking for the load and starting another
00218           // tool, and we're quite likely to race against other local
00219           // threads from the same _par_map.  This will cause us to
00220           // overestimate the load if the local threads have actually
00221           // gotten their tools started already, but that's much better
00222           // than undercounting it and dumping multiple tools on the same
00223           // machine at once.
00224           int cur_tools = hinfo.cur_tools + plat->hosts[i].usecount;
00225           DEBUG(cout << plat->hosts[i].name << " tools=" << cur_tools << endl);
00226 
00227           // If host has no running tool invocations and the reported
00228           // load is below the threshold, pick it immediatly.
00229           //
00230           float max_load = hinfo.cpus * negligibleExternalLoadPerCPU;
00231           if (cur_tools == 0 && hinfo.load < max_load) {
00232             host = &plat->hosts[i];
00233             break;
00234           }
00235 
00236           // Keep track of lightest load for step (3).
00237           float tool_load = ((float) cur_tools)/((float) hinfo.max_tools);
00238           float host_load = hinfo.load / hinfo.cpus;
00239           float load;
00240           if(host_load < tool_load) {
00241             load = tool_load;
00242           } else {
00243             load = host_load;
00244           }
00245           if (load < lightload) {
00246             lighthost = i;
00247             lightload = load;
00248             light_hinfo = hinfo;
00249           }
00250         }
00251 
00252         if (host == NULL) {
00253           if (plat->hosts[lighthost].bad) {
00254             // We get here if there are no hosts that are up and match the
00255             // pattern
00256             outputMu.lock();            
00257             Error("No runtool server found for platform ", loc);
00258             platform->PrintD(&cerr);
00259             ErrorDetail(".\n");
00260             outputMu.unlock();  
00261             throw(Evaluator::failure(Text("exiting"), false));
00262           }
00263 
00264           // Step 4: We didn't find an unloaded host; use the most
00265           // lightly loaded.
00266 
00267           // Compute the total number of tools we think may have been
00268           // submitted to this RunToolServer.  As noted above, this can be
00269           // an overestimate, but is needed to avoid a race.
00270           int total_tools = (light_hinfo.cur_tools + light_hinfo.cur_pending +
00271                              plat->hosts[lighthost].usecount);
00272 
00273           // If we think that submitting another tool may result in a "too
00274           // busy" error from the RunToolServer, wait for another thread
00275           // to finish its tool invocation and try again to select a
00276           // RunToolServer.
00277           if(total_tools >= (light_hinfo.max_tools + light_hinfo.max_pending))
00278             {
00279               // As long as there's at least one thread that's already
00280               // performing a tool invocation, we'll wait for one to
00281               // finish.
00282               if(usecountTotal > 0)
00283                 {
00284                   // If we haven't already printed a message about
00285                   // having trouble finding a RunToolServer slot for
00286                   // this platform, print one now.
00287                   if(!plat->lowSlotsMessagePrinted)
00288                     {
00289                       outputMu.lock();
00290                       cerr << "Warning:  Difficulty finding an available "
00291                            << "runtool server slot for platform \""
00292                            << plat->name << "\".   You may see less "
00293                            << "parallelism than expected." << endl;
00294                       outputMu.unlock();
00295                       plat->lowSlotsMessagePrinted = true;
00296                     }
00297 
00298                   // Wait for another thread to complete a tool invocation.
00299                   toolDone.wait(mu);
00300                 }
00301               else
00302                 {
00303                   // Error: we're not running any tools, so waiting for
00304                   // another thread to finish one would put us to sleep
00305                   // forever!
00306                   Text message =
00307                     Text("With no tools running, no available runtool server "
00308                          "slots for platform \"") +
00309                     plat->name + "\"";
00310                   outputMu.lock();
00311                   Error(message, loc);
00312                   outputMu.unlock();
00313                   throw(Evaluator::failure(Text("can't start tool"), false));
00314                 }
00315             }
00316           // We can safely submit another tool to this RunToolServer.
00317           else
00318             {
00319               host = &plat->hosts[lighthost];
00320             }
00321         }
00322       }
00323 
00324   } catch (...) {
00325     mu.unlock();
00326     throw;
00327   }
00328   host->usecount++;
00329   usecountTotal++;
00330   mu.unlock();
00331   handle = (void*) host;
00332   return host->name;
00333 }
00334 
00335 void
00336 RunToolDone(void* handle)
00337 {
00338   Host* host = (Host*) handle;
00339   mu.lock();
00340   host->usecount--;
00341   usecountTotal--;
00342   mu.unlock();
00343 
00344   // Wake up any threads that were waiting for an available
00345   // RunToolServer slot.  We use broadcast as the conditions may have
00346   // changed significantly enough that more than one waiting thread
00347   // can now get a slot to start a tool.
00348   toolDone.broadcast();
00349 }
00350 
00351 //
00352 // Look up a platform name.  mu must be held.
00353 //
00354 static Platform*
00355 LookupPlatform(TextVC *platform, SrcLoc *loc)
00356 {
00357   Text platname = platform->NDS().chars();
00358   Platform* plat = platforms;
00359 
00360   // Previously seen platform?
00361   while (plat) {
00362     if (platname == plat->name) return plat;
00363     plat = plat->next;
00364   }
00365 
00366   // No, look in config file
00367   plat = NEW(Platform);
00368   plat->name = platname;
00369   Text hosts;
00370   try {
00371     plat->sysname = VestaConfig::get_Text(platname, "sysname");
00372     plat->release = VestaConfig::get_Text(platname, "release");
00373     plat->version = VestaConfig::get_Text(platname, "version");
00374     plat->machine = VestaConfig::get_Text(platname, "machine");
00375     plat->cpus =    VestaConfig::get_int (platname, "cpus");
00376     plat->cpuMHz =  VestaConfig::get_int (platname, "cpuMHz");
00377     plat->memKB =   VestaConfig::get_int (platname, "memKB");
00378     hosts =         VestaConfig::get_Text(platname, "hosts");
00379   } catch (VestaConfig::failure f) {
00380     outputMu.lock();    
00381     Error("Unknown platform ", loc);
00382     platform->PrintD(&cerr);
00383     ErrorDetail(": " + f.msg + ".\n");
00384     outputMu.unlock();              
00385     throw(Evaluator::failure(Text("exiting"), false));
00386   }
00387 
00388   // Count hosts on list
00389   plat->nhosts = 0; 
00390   int len = hosts.Length();
00391   int i = 0;
00392   for (;;) {
00393     while (i < len && isspace(hosts[i])) i++;
00394     if (i >= len) break;
00395     plat->nhosts++;
00396     while (i < len && !isspace(hosts[i])) i++;
00397   }
00398       
00399   // Allocate and fill in data structure
00400   plat->hosts = NEW_ARRAY(Host, plat->nhosts);
00401   int h = 0;
00402   i = 0;
00403   for (;;) {
00404     while (i < len && isspace(hosts[i])) i++;
00405     if (i >= len) break;
00406     int j = i;
00407     while (j < len && !isspace(hosts[j])) j++;
00408     plat->hosts[h].name = hosts.Sub(i, j-i);
00409     i = j + 1;
00410     h++;
00411   }
00412 
00413   if (plat->anchorfirst = ((plat->nhosts > 0) &&
00414                            (plat->hosts[0].name == Text("localhost")))) {
00415     // Use canonical name for local host
00416     plat->hosts[0].name = TCP_sock::this_host();
00417   }
00418 
00419   plat->lowSlotsMessagePrinted = false;
00420 
00421   plat->next = platforms;
00422   platforms = plat;
00423   return plat;
00424 }
00425 
00426 //
00427 // Initialize module.
00428 //
00429 void
00430 RunToolHostInit()
00431 {
00432   mu.lock();
00433   if (!initialized) {
00434     initialized = true;
00435     Word full_hash = TCP_sock::this_host().Hash();
00436     myhosthash = (unsigned int) full_hash ^ (full_hash >> (sizeof(unsigned int)*8));
00437 
00438     try
00439       {
00440         // Set negligibleExternalLoadPerCPU from
00441         // [Evaluator]NegligibleExternalLoadPerCPU (if set in the config
00442         // file).
00443         if(VestaConfig::is_set("Evaluator", "NegligibleExternalLoadPerCPU"))
00444           {
00445             negligibleExternalLoadPerCPU = VestaConfig::get_float("Evaluator",
00446                                                                   "NegligibleExternalLoadPerCPU");
00447           }
00448       }
00449     catch (VestaConfig::failure f)
00450       {
00451         Error(f.msg);
00452       }
00453   }
00454   mu.unlock();
00455 }

Generated on Mon May 8 00:48:40 2006 for Vesta by  doxygen 1.4.2