ROOT logo
/**
 * @file   GridWatch.C
 * @author Christian Holm Christensen <cholm@master.hehi.nbi.dk>
 * @date   Thu Jan 24 23:06:08 2013
 * 
 * @brief Script to watch master jobs and automatically submit
 * terminate jobs
 * 
 * 
 * @ingroup pwglf_forward_trains_helper
 */
#ifndef __CINT__
# include <TString.h>
# include <TGrid.h>
# include <TSystem.h>
# include <TObjArray.h>
# include <iostream>
# include <fstream>
# include <TError.h>
# include <TDatime.h>
# include <TEnv.h>
#else 
class TString;
#endif
#include <TArrayI.h>

/** 
 * Create token name 
 * 
 * @param name   Name
 * @param ext    Extension
 * @param merge  Merge state or not 
 * 
 * @return Formatted string
 * @ingroup pwglf_forward_trains_helper
 */
TString CacheFileName(const TString& name, 
		      const TString& ext, 
		      Bool_t merge=false)
{
  return TString::Format("%s%s.%s", name.Data(), 
			 (merge ? "_merge" : ""), ext.Data());
}
/** 
 * Check if we have a particular file 
 * 
 * @param name   Base name 
 * @param ext    Extension 
 * @param merge  Merging stage or not 
 * 
 * @return true if file exits
 * @ingroup pwglf_forward_trains_helper
 */
Bool_t CheckCacheFile(const TString& name, 
		      const TString& ext, 
		      Bool_t merge=false)
{
  // TSystem::AccessPathName return false if file is there 
  return !gSystem->AccessPathName(CacheFileName(name, ext, merge));
}
/** 
 * Remove a token file 
 * 
 * @param name   Base name 
 * @param ext    Extension 
 * @param merge  Merging stage or not 
 * @ingroup pwglf_forward_trains_helper
 */
void RemoveCacheFile(const TString& name, 
		  const TString& ext, 
		  Bool_t merge=false)
{
  gSystem->Unlink(CacheFileName(name, ext, merge));
}

/** 
 * Read one line of text from file and return tokens.
 * 
 * @param name   Base name 
 * @param ext    Extension
 * @param merge  If true append "_merge" to name
 * 
 * @return Array of tokens or null
 *
 * @ingroup pwglf_forward_trains_helper
 */
TObjArray* ReadCacheFile(const TString& name, 
		      const TString& ext, 
		      bool merge=false) 
{
  TString fn = TString::Format("%s%s.%s", name.Data(), 
			       (merge ? "_merge" : ""), ext.Data());
  std::ifstream in(fn.Data());
  if (!in) { 
    Error("ReadCacheFile", "Failed to open %s", fn.Data());
    return 0;
  }
  TString ln;
  ln.ReadLine(in);
  in.close();
  
  if (ln.IsNull()) return 0;
  return ln.Tokenize(" \t");
}
  
/** 
 * Read list of job IDs from file 
 * 
 * @param name   Base name 
 * @param merge  If true append "_merge" to name
 * 
 * @return Array of job IDs or null
 *
 * @ingroup pwglf_forward_trains_helper
 */
TObjArray* ReadJobIDs(const TString& name, bool merge=false)
{
  return ReadCacheFile(name, "jobid", merge);
}

/** 
 * Read list of job stages from file 
 * 
 * @param name   Base name 
 * @param merge  If true append "_merge" to name
 * 
 * @return Array of job stages or null
 *
 * @ingroup pwglf_forward_trains_helper
 */
TObjArray* ReadStages(const TString& name, bool merge=false)
{
  return ReadCacheFile(name, "stage", merge);
}

/** 
 * Parse the job IDs into an array of integers 
 * 
 * @param jobIds List of jobs
 * @param ret    Return array
 * 
 * @return true on success
 *
 * @ingroup pwglf_forward_trains_helper
 */
Bool_t ParseJobIDs(const TObjArray* jobIds, TArrayI& ret)
{
  if (!jobIds) return false;

  Int_t n = jobIds->GetEntries();
  ret.Set(n);
  ret.Reset(-1);

  for (Int_t i = 0; i < n; i++) { 
    TObjString*    id = static_cast<TObjString*>(jobIds->At(i));
    const TString& s  = id->String();
    ret.SetAt(s.Atoi(), i);
  }
  return true;
}

/** 
 * Parse string representing status and return human-readable string 
 * 
 * @param status Return from ps command
 * @param out    Output
 * 
 * @return true on success
 *
 * @ingroup pwglf_forward_trains_helper
 */
Bool_t ParseState(const TString& status, TString& out)
{
  switch (status[0]) { 
  case 'D': out = "DONE"; break;
  case 'E': out = "ERROR"; break;
  case 'R': out = "RUNNING"; break; 
  case 'W': out = "WAITING"; break;
  case 'O': out = "WAITING_QUOTA"; break;
  case 'A': out = "ASSIGNED"; break;
  case 'S': out = "STARTED" ; break;
  case 'I': out = "INSERTING"; break;
  case 'K': out = "KILLED"; break;
  default:  out = "UNKNOWN"; return false;
  }
  if (status[1] != '\0' && 
      (status[0] != 'O' || status[0] != 'S')) { 
    out.Append("_");
    switch (status[1]) { 
    case 'S': out.Append(status[0] == 'E' ? "SUBMIT" : "SPLIT"); break;
    case 'X': out.Append("EXPIRED"); break;
    case 'A': out.Append("ASSIGNING"); break;
    case 'E': out.Append("EXECUTING"); break;
    case 'V': 
      if (status[0] == 'S') out = "SAVING"; 
      else out.Append("VALIDATING"); 
      break;
    case 'd': 
      if (status[0] == 'I') {
	out = "INTERACTIVE_IDLE";
	break;
      } // Fall through on else 
    case 'a': 
      if (status[0] == 'I') {
	out = "INTERACTIVE_USED";
	break;
      } // Fall through on else
    default:  out.Append("UNKNOWN"); return false;
    }
    if (status[2] != '\0') { 
      switch (status[2]) {
      case 'V': if (status[0] == 'E') out.ReplaceAll("SUBMIT", "SAVING");
	break;
      default: out.Append("_UNKNOWN");
      }
    }
  }
  return true;
}  

/** 
 * Do a PS on the grid 
 * 
 * @param tmp The file generated 
 * 
 * @return true on success
 */
Bool_t GridPs(TString& tmp)
{
  tmp = "gridMonitor";
  FILE* fp = gSystem->TempFileName(tmp);

#if 0
  // Here, we'd ideally use TGrid::Ps but that doesn't work, so we use
  // the shell instead. 
  gSystem->RedirectOutput(fn);
  gGrid->Command("ps -Ax");
  gGrid->Stdout();
  gSystem->RedirectOutput(0);
  gGrid->Stderr();
  fclose(fp);
#else
  fclose(fp);
  
  // Printf("Using gbbox ps -Ax >> %s", tmp.Data());
  gSystem->Exec(Form("gbbox ps -Ax >> %s", tmp.Data()));
#endif
  return true;
}

/** 
 * Get the job state 
 * 
 * @param jobId Job status 
 * @param out   Output status string 
 * 
 * @return true on success
 *
 * @ingroup pwglf_forward_trains_helper
 */
Bool_t GetJobState(Int_t jobId, TString& out) 
{
  out = "MISSING";

  TString fn;
  GridPs(fn);

  std::ifstream in(fn.Data());

  while (!in.eof()) { 
    TString l;
    l.ReadLine(in);
    if (in.bad()) break;

    TObjArray* tokens = l.Tokenize(" \t");
    if (tokens->GetEntries() < 2) break;

    //TString  user   = tokens->At(0)->GetName(); 
    TString    sjid   = tokens->At(1)->GetName(); // Job ID
    TString    stat   = tokens->At(2)->GetName(); // State 
    Int_t      jid    = sjid.Atoi();
    
    if (jid != jobId) continue;

    ParseState(stat, out);
    break;
  }

  in.close();
  gSystem->Unlink(fn);

  return true;
}

/** 
 * Get the job states
 * 
 * @param jobs   List of job IDs
 * @param states On return the states
 * 
 * @return true on success
 *
 * @ingroup pwglf_forward_trains_helper
 */
Bool_t GetJobStates(const TArrayI& jobs, TObjArray& states)
{
  Int_t n = jobs.GetSize();
  states.Expand(n);
  for (Int_t i = 0; i < n; i++) {
    TObjString* s = static_cast<TObjString*>(states.At(i));
    if (!s) states.AddAt(s = new TObjString(""), i);
    s->SetString("MISSING");
  }

  TString fn;
  GridPs(fn);

  std::ifstream in(fn.Data());

  while (!in.eof()) {
    TString l;
    l.ReadLine(in);
    if (in.bad()) break;
    if (l.IsNull()) continue;

    TObjArray* tokens = l.Tokenize(" \t");
    if (tokens->GetEntries() < 3) { 
      Warning("GetJobStates", "Got too few tokens (%d): %s", 
	      tokens->GetEntries(), l.Data());
      tokens->Print();
      break;
    }

    //TString  user   = tokens->At(0)->GetName(); 
    TString    sjid   = tokens->At(1)->GetName(); // Job ID
    TString    stat   = tokens->At(2)->GetName(); // State 
    Int_t      jid    = sjid.Atoi();
    
    for (Int_t i = 0; i < n; i++) { 
      if (jid != jobs.At(i)) continue;
      TObjString* s = static_cast<TObjString*>(states.At(i));
      TString out;
      if (!ParseState(stat, out)) continue;
      s->SetString(out);
    }
  }

  in.close();
  gSystem->Unlink(fn);

  return true;
}

/** 
 * Check if the AliEn token is valid 
 * 
 * 
 * @return true if it is 
 */
Bool_t CheckAlienToken()
{
  Int_t ret = gSystem->Exec("alien-token-info > /dev/null 2>&1");
  if (ret != 0) {
    Printf("=== AliEn token not valid");
    return false;
  }
  return true;
}

/** 
 * Refersh the grid token every 6th hour
 * 
 * @param now 
 * @param force 
 */
#if 0
void RefreshAlienToken(UInt_t, Bool_t f=false)
{}
#else
void RefreshAlienToken(UInt_t now, Bool_t force=false)
{
  Bool_t renew = force;
  if (!renew && !CheckAlienToken()) renew = true;

  if (!renew) {
    TString l = gSystem->GetFromPipe(Form("cat /tmp/gclient_token_%d",
					  gSystem->GetUid()));
    TObjArray*  lines  = l.Tokenize("\n");
    TObjString* sline  = 0;
    UInt_t      expire = 0;
    TIter       next(lines);
    while ((sline = static_cast<TObjString*>(next()))) {
      TString& line = sline->String();
      if (!line.BeginsWith("Expiretime")) continue;

      Size_t  eq      = line.Index("=");
      TString sdatime = line(eq+2, line.Length()-eq-2);
      expire          = sdatime.Atoi();
      break;
    }
    lines->Delete();
    // If the expiration date/time has passed or is less than 30 min
    // away, we refresh
    Int_t diff = (expire - now);
    if (now > expire || diff < 30*60) renew = true;

    Printf("=== Now: %d, Expires: %d, in %03d:%02d:%02d -> %s", 
	   now, expire, diff/60/60, (diff/60 % 60), (diff % 60), 
	    (renew ? "renew" : "nothing"));
	   
  }

  if (!renew) return;

  // Reset the start time 
  Printf("=== Refreshing AliEn token");
  gSystem->Exec("alien-token-init");
  Printf("=== Done refreshing AliEn token");
}
#endif


/** 
 * Wait of jobs to finish 
 * 
 * @param jobs    List of jobs
 * @param stages  Stages
 * @param delay   Delay for check
 * @param batch   If true, do not prompt 
 * 
 * @return true on success, false otherwise
 *
 * @ingroup pwglf_forward_trains_helper
 */
Bool_t WaitForJobs(TArrayI&   jobs, 
		   TObjArray* stages, 
		   Int_t      delay,
		   Bool_t     batch)
{
  if (!CheckAlienToken()) return false;
  // Bool_t stopped = false;
  TFileHandler h(0, 0x1);
  // RefreshAlienToken(0, true);
  do { 
    Bool_t allDone = true;
    TDatime t;
    Printf("--- %4d/%02d/%02d %02d:%02d:%02d [Press enter to pause] ---", 
	   t.GetYear(), t.GetMonth(), t.GetDay(), 
	   t.GetHour(), t.GetMinute(), t.GetSecond());
    UInt_t now = t.Convert(true);

    TObjArray states;
    GetJobStates(jobs, states);

    Int_t missing = 0;
    Int_t total   = jobs.GetSize();
    // Bool_t allAccounted = false;
    for (Int_t i = 0; i < total; i++) { 
      Int_t job = jobs.At(i);

      if (job < 0) continue;

      TObjString* obj = static_cast<TObjString*>(states.At(i));
      const TString& state = obj->String();
      
      if (state.BeginsWith("ERROR_"))
	jobs.SetAt(-1, i);
      else if (state.EqualTo("MISSING")) 
	missing++;
      else if (!state.EqualTo("DONE")) 
	allDone = false;
      

      Printf(" %d(%s)=%s", job, stages->At(i)->GetName(), state.Data());
      
    }
    RefreshAlienToken(now);

    if (allDone) break;
    if (missing >= total) {
      Error("GetJobStates", "Info on all jobs missing");
      break;
    }
    if (!batch) {
      if (gSystem->Select(&h, 1000*delay)) {
	// Got input on std::cin 
	std::string l;
	std::getline(std::cin, l);
	std::cout << "Do you want to terminate now [yN]? " << std::flush;
	std::getline(std::cin, l);
	if (l[0] == 'y' || l[0] == 'Y') { 
	  // stopped = true;
	  break;
	}
      }
    }
    else 
      gSystem->Sleep(1000*delay);

    // 
  } while (true);

  return true;
}
/** 
 * Watch Grid for termination of main job, and submit merging jobs as needed. 
 * 
 * @param name   Name of the job
 * @param batch  If true, do not prompt 
 * @param delay  Delay between updates in seconds
 *
 * @ingroup pwglf_forward_trains_helper
 */
void GridWatch(const TString& name, Bool_t batch=false, UShort_t delay=5*60)
{
#if 1
  // We use command line tools instead of ROOT interface - which is
  // broken so badly that it's hard to believe it ever worked.
  gEnv->SetValue("XSec.GSI.DelegProxy", "2");
  TGrid::Connect("alien:///");
  if (!gGrid) { 
    Error("GridWatch", "Failed to connect to the Grid");
    return;
  }
#endif

  TObjArray* jobIDs = ReadJobIDs(name, false);
  TObjArray* stages = ReadStages(name, false);

  if (!jobIDs || !stages) return;

  TArrayI jobs;
  if (!ParseJobIDs(jobIDs, jobs)) return;

  gSystem->Sleep(10*1000);
  if (!(CheckCacheFile(name, "jobid", true) && 
	CheckCacheFile(name, "stage", true))) 
    if (!WaitForJobs(jobs, stages, delay, batch)) return;

  delete jobIDs;
  delete stages;

  // return;
  do {
    if (!CheckCacheFile(name, "jobid", true) && 
	!CheckCacheFile(name, "stage", true)) {
      if (!CheckAlienToken()) return;
      Printf("Now executing terminate");
      gSystem->Exec("aliroot -l -b -q Terminate.C");
      gSystem->Sleep(10*1000);
    }

    Printf("Reading job ids");
    jobIDs = ReadJobIDs(name, true);
    stages = ReadStages(name, true);
    
    if (!ParseJobIDs(jobIDs, jobs)) {
      Error("GridWatch", "Failed to parse job ids %s", 
	    CacheFileName(name,"jobid",true).Data());
      return;
    }

    if (!WaitForJobs(jobs, stages, delay, batch)) return;
    
    Bool_t allFinal = true;
    for (Int_t i = 0; i < jobs.GetSize(); i++) {
      if (jobs.At(i) < 0) continue;

      const TString& s = static_cast<TObjString*>(stages->At(i))->String();
      if (!s.BeginsWith("final_")) allFinal = false;
    }
    
    delete jobIDs;
    delete stages;

    Printf("All jobs in final stage");
    if (allFinal) break;

    RemoveCacheFile(name, "jobid", true);
    RemoveCacheFile(name, "stage", true);
  } while (true);

  Printf("Finished");
}
//
// EOF
//

 GridWatch.C:1
 GridWatch.C:2
 GridWatch.C:3
 GridWatch.C:4
 GridWatch.C:5
 GridWatch.C:6
 GridWatch.C:7
 GridWatch.C:8
 GridWatch.C:9
 GridWatch.C:10
 GridWatch.C:11
 GridWatch.C:12
 GridWatch.C:13
 GridWatch.C:14
 GridWatch.C:15
 GridWatch.C:16
 GridWatch.C:17
 GridWatch.C:18
 GridWatch.C:19
 GridWatch.C:20
 GridWatch.C:21
 GridWatch.C:22
 GridWatch.C:23
 GridWatch.C:24
 GridWatch.C:25
 GridWatch.C:26
 GridWatch.C:27
 GridWatch.C:28
 GridWatch.C:29
 GridWatch.C:30
 GridWatch.C:31
 GridWatch.C:32
 GridWatch.C:33
 GridWatch.C:34
 GridWatch.C:35
 GridWatch.C:36
 GridWatch.C:37
 GridWatch.C:38
 GridWatch.C:39
 GridWatch.C:40
 GridWatch.C:41
 GridWatch.C:42
 GridWatch.C:43
 GridWatch.C:44
 GridWatch.C:45
 GridWatch.C:46
 GridWatch.C:47
 GridWatch.C:48
 GridWatch.C:49
 GridWatch.C:50
 GridWatch.C:51
 GridWatch.C:52
 GridWatch.C:53
 GridWatch.C:54
 GridWatch.C:55
 GridWatch.C:56
 GridWatch.C:57
 GridWatch.C:58
 GridWatch.C:59
 GridWatch.C:60
 GridWatch.C:61
 GridWatch.C:62
 GridWatch.C:63
 GridWatch.C:64
 GridWatch.C:65
 GridWatch.C:66
 GridWatch.C:67
 GridWatch.C:68
 GridWatch.C:69
 GridWatch.C:70
 GridWatch.C:71
 GridWatch.C:72
 GridWatch.C:73
 GridWatch.C:74
 GridWatch.C:75
 GridWatch.C:76
 GridWatch.C:77
 GridWatch.C:78
 GridWatch.C:79
 GridWatch.C:80
 GridWatch.C:81
 GridWatch.C:82
 GridWatch.C:83
 GridWatch.C:84
 GridWatch.C:85
 GridWatch.C:86
 GridWatch.C:87
 GridWatch.C:88
 GridWatch.C:89
 GridWatch.C:90
 GridWatch.C:91
 GridWatch.C:92
 GridWatch.C:93
 GridWatch.C:94
 GridWatch.C:95
 GridWatch.C:96
 GridWatch.C:97
 GridWatch.C:98
 GridWatch.C:99
 GridWatch.C:100
 GridWatch.C:101
 GridWatch.C:102
 GridWatch.C:103
 GridWatch.C:104
 GridWatch.C:105
 GridWatch.C:106
 GridWatch.C:107
 GridWatch.C:108
 GridWatch.C:109
 GridWatch.C:110
 GridWatch.C:111
 GridWatch.C:112
 GridWatch.C:113
 GridWatch.C:114
 GridWatch.C:115
 GridWatch.C:116
 GridWatch.C:117
 GridWatch.C:118
 GridWatch.C:119
 GridWatch.C:120
 GridWatch.C:121
 GridWatch.C:122
 GridWatch.C:123
 GridWatch.C:124
 GridWatch.C:125
 GridWatch.C:126
 GridWatch.C:127
 GridWatch.C:128
 GridWatch.C:129
 GridWatch.C:130
 GridWatch.C:131
 GridWatch.C:132
 GridWatch.C:133
 GridWatch.C:134
 GridWatch.C:135
 GridWatch.C:136
 GridWatch.C:137
 GridWatch.C:138
 GridWatch.C:139
 GridWatch.C:140
 GridWatch.C:141
 GridWatch.C:142
 GridWatch.C:143
 GridWatch.C:144
 GridWatch.C:145
 GridWatch.C:146
 GridWatch.C:147
 GridWatch.C:148
 GridWatch.C:149
 GridWatch.C:150
 GridWatch.C:151
 GridWatch.C:152
 GridWatch.C:153
 GridWatch.C:154
 GridWatch.C:155
 GridWatch.C:156
 GridWatch.C:157
 GridWatch.C:158
 GridWatch.C:159
 GridWatch.C:160
 GridWatch.C:161
 GridWatch.C:162
 GridWatch.C:163
 GridWatch.C:164
 GridWatch.C:165
 GridWatch.C:166
 GridWatch.C:167
 GridWatch.C:168
 GridWatch.C:169
 GridWatch.C:170
 GridWatch.C:171
 GridWatch.C:172
 GridWatch.C:173
 GridWatch.C:174
 GridWatch.C:175
 GridWatch.C:176
 GridWatch.C:177
 GridWatch.C:178
 GridWatch.C:179
 GridWatch.C:180
 GridWatch.C:181
 GridWatch.C:182
 GridWatch.C:183
 GridWatch.C:184
 GridWatch.C:185
 GridWatch.C:186
 GridWatch.C:187
 GridWatch.C:188
 GridWatch.C:189
 GridWatch.C:190
 GridWatch.C:191
 GridWatch.C:192
 GridWatch.C:193
 GridWatch.C:194
 GridWatch.C:195
 GridWatch.C:196
 GridWatch.C:197
 GridWatch.C:198
 GridWatch.C:199
 GridWatch.C:200
 GridWatch.C:201
 GridWatch.C:202
 GridWatch.C:203
 GridWatch.C:204
 GridWatch.C:205
 GridWatch.C:206
 GridWatch.C:207
 GridWatch.C:208
 GridWatch.C:209
 GridWatch.C:210
 GridWatch.C:211
 GridWatch.C:212
 GridWatch.C:213
 GridWatch.C:214
 GridWatch.C:215
 GridWatch.C:216
 GridWatch.C:217
 GridWatch.C:218
 GridWatch.C:219
 GridWatch.C:220
 GridWatch.C:221
 GridWatch.C:222
 GridWatch.C:223
 GridWatch.C:224
 GridWatch.C:225
 GridWatch.C:226
 GridWatch.C:227
 GridWatch.C:228
 GridWatch.C:229
 GridWatch.C:230
 GridWatch.C:231
 GridWatch.C:232
 GridWatch.C:233
 GridWatch.C:234
 GridWatch.C:235
 GridWatch.C:236
 GridWatch.C:237
 GridWatch.C:238
 GridWatch.C:239
 GridWatch.C:240
 GridWatch.C:241
 GridWatch.C:242
 GridWatch.C:243
 GridWatch.C:244
 GridWatch.C:245
 GridWatch.C:246
 GridWatch.C:247
 GridWatch.C:248
 GridWatch.C:249
 GridWatch.C:250
 GridWatch.C:251
 GridWatch.C:252
 GridWatch.C:253
 GridWatch.C:254
 GridWatch.C:255
 GridWatch.C:256
 GridWatch.C:257
 GridWatch.C:258
 GridWatch.C:259
 GridWatch.C:260
 GridWatch.C:261
 GridWatch.C:262
 GridWatch.C:263
 GridWatch.C:264
 GridWatch.C:265
 GridWatch.C:266
 GridWatch.C:267
 GridWatch.C:268
 GridWatch.C:269
 GridWatch.C:270
 GridWatch.C:271
 GridWatch.C:272
 GridWatch.C:273
 GridWatch.C:274
 GridWatch.C:275
 GridWatch.C:276
 GridWatch.C:277
 GridWatch.C:278
 GridWatch.C:279
 GridWatch.C:280
 GridWatch.C:281
 GridWatch.C:282
 GridWatch.C:283
 GridWatch.C:284
 GridWatch.C:285
 GridWatch.C:286
 GridWatch.C:287
 GridWatch.C:288
 GridWatch.C:289
 GridWatch.C:290
 GridWatch.C:291
 GridWatch.C:292
 GridWatch.C:293
 GridWatch.C:294
 GridWatch.C:295
 GridWatch.C:296
 GridWatch.C:297
 GridWatch.C:298
 GridWatch.C:299
 GridWatch.C:300
 GridWatch.C:301
 GridWatch.C:302
 GridWatch.C:303
 GridWatch.C:304
 GridWatch.C:305
 GridWatch.C:306
 GridWatch.C:307
 GridWatch.C:308
 GridWatch.C:309
 GridWatch.C:310
 GridWatch.C:311
 GridWatch.C:312
 GridWatch.C:313
 GridWatch.C:314
 GridWatch.C:315
 GridWatch.C:316
 GridWatch.C:317
 GridWatch.C:318
 GridWatch.C:319
 GridWatch.C:320
 GridWatch.C:321
 GridWatch.C:322
 GridWatch.C:323
 GridWatch.C:324
 GridWatch.C:325
 GridWatch.C:326
 GridWatch.C:327
 GridWatch.C:328
 GridWatch.C:329
 GridWatch.C:330
 GridWatch.C:331
 GridWatch.C:332
 GridWatch.C:333
 GridWatch.C:334
 GridWatch.C:335
 GridWatch.C:336
 GridWatch.C:337
 GridWatch.C:338
 GridWatch.C:339
 GridWatch.C:340
 GridWatch.C:341
 GridWatch.C:342
 GridWatch.C:343
 GridWatch.C:344
 GridWatch.C:345
 GridWatch.C:346
 GridWatch.C:347
 GridWatch.C:348
 GridWatch.C:349
 GridWatch.C:350
 GridWatch.C:351
 GridWatch.C:352
 GridWatch.C:353
 GridWatch.C:354
 GridWatch.C:355
 GridWatch.C:356
 GridWatch.C:357
 GridWatch.C:358
 GridWatch.C:359
 GridWatch.C:360
 GridWatch.C:361
 GridWatch.C:362
 GridWatch.C:363
 GridWatch.C:364
 GridWatch.C:365
 GridWatch.C:366
 GridWatch.C:367
 GridWatch.C:368
 GridWatch.C:369
 GridWatch.C:370
 GridWatch.C:371
 GridWatch.C:372
 GridWatch.C:373
 GridWatch.C:374
 GridWatch.C:375
 GridWatch.C:376
 GridWatch.C:377
 GridWatch.C:378
 GridWatch.C:379
 GridWatch.C:380
 GridWatch.C:381
 GridWatch.C:382
 GridWatch.C:383
 GridWatch.C:384
 GridWatch.C:385
 GridWatch.C:386
 GridWatch.C:387
 GridWatch.C:388
 GridWatch.C:389
 GridWatch.C:390
 GridWatch.C:391
 GridWatch.C:392
 GridWatch.C:393
 GridWatch.C:394
 GridWatch.C:395
 GridWatch.C:396
 GridWatch.C:397
 GridWatch.C:398
 GridWatch.C:399
 GridWatch.C:400
 GridWatch.C:401
 GridWatch.C:402
 GridWatch.C:403
 GridWatch.C:404
 GridWatch.C:405
 GridWatch.C:406
 GridWatch.C:407
 GridWatch.C:408
 GridWatch.C:409
 GridWatch.C:410
 GridWatch.C:411
 GridWatch.C:412
 GridWatch.C:413
 GridWatch.C:414
 GridWatch.C:415
 GridWatch.C:416
 GridWatch.C:417
 GridWatch.C:418
 GridWatch.C:419
 GridWatch.C:420
 GridWatch.C:421
 GridWatch.C:422
 GridWatch.C:423
 GridWatch.C:424
 GridWatch.C:425
 GridWatch.C:426
 GridWatch.C:427
 GridWatch.C:428
 GridWatch.C:429
 GridWatch.C:430
 GridWatch.C:431
 GridWatch.C:432
 GridWatch.C:433
 GridWatch.C:434
 GridWatch.C:435
 GridWatch.C:436
 GridWatch.C:437
 GridWatch.C:438
 GridWatch.C:439
 GridWatch.C:440
 GridWatch.C:441
 GridWatch.C:442
 GridWatch.C:443
 GridWatch.C:444
 GridWatch.C:445
 GridWatch.C:446
 GridWatch.C:447
 GridWatch.C:448
 GridWatch.C:449
 GridWatch.C:450
 GridWatch.C:451
 GridWatch.C:452
 GridWatch.C:453
 GridWatch.C:454
 GridWatch.C:455
 GridWatch.C:456
 GridWatch.C:457
 GridWatch.C:458
 GridWatch.C:459
 GridWatch.C:460
 GridWatch.C:461
 GridWatch.C:462
 GridWatch.C:463
 GridWatch.C:464
 GridWatch.C:465
 GridWatch.C:466
 GridWatch.C:467
 GridWatch.C:468
 GridWatch.C:469
 GridWatch.C:470
 GridWatch.C:471
 GridWatch.C:472
 GridWatch.C:473
 GridWatch.C:474
 GridWatch.C:475
 GridWatch.C:476
 GridWatch.C:477
 GridWatch.C:478
 GridWatch.C:479
 GridWatch.C:480
 GridWatch.C:481
 GridWatch.C:482
 GridWatch.C:483
 GridWatch.C:484
 GridWatch.C:485
 GridWatch.C:486
 GridWatch.C:487
 GridWatch.C:488
 GridWatch.C:489
 GridWatch.C:490
 GridWatch.C:491
 GridWatch.C:492
 GridWatch.C:493
 GridWatch.C:494
 GridWatch.C:495
 GridWatch.C:496
 GridWatch.C:497
 GridWatch.C:498
 GridWatch.C:499
 GridWatch.C:500
 GridWatch.C:501
 GridWatch.C:502
 GridWatch.C:503
 GridWatch.C:504
 GridWatch.C:505
 GridWatch.C:506
 GridWatch.C:507
 GridWatch.C:508
 GridWatch.C:509
 GridWatch.C:510
 GridWatch.C:511
 GridWatch.C:512
 GridWatch.C:513
 GridWatch.C:514
 GridWatch.C:515
 GridWatch.C:516
 GridWatch.C:517
 GridWatch.C:518
 GridWatch.C:519
 GridWatch.C:520
 GridWatch.C:521
 GridWatch.C:522
 GridWatch.C:523
 GridWatch.C:524
 GridWatch.C:525
 GridWatch.C:526
 GridWatch.C:527
 GridWatch.C:528
 GridWatch.C:529
 GridWatch.C:530
 GridWatch.C:531
 GridWatch.C:532
 GridWatch.C:533
 GridWatch.C:534
 GridWatch.C:535
 GridWatch.C:536
 GridWatch.C:537
 GridWatch.C:538
 GridWatch.C:539
 GridWatch.C:540
 GridWatch.C:541
 GridWatch.C:542
 GridWatch.C:543
 GridWatch.C:544
 GridWatch.C:545
 GridWatch.C:546
 GridWatch.C:547
 GridWatch.C:548
 GridWatch.C:549
 GridWatch.C:550
 GridWatch.C:551
 GridWatch.C:552
 GridWatch.C:553
 GridWatch.C:554
 GridWatch.C:555
 GridWatch.C:556
 GridWatch.C:557
 GridWatch.C:558
 GridWatch.C:559
 GridWatch.C:560
 GridWatch.C:561
 GridWatch.C:562
 GridWatch.C:563
 GridWatch.C:564
 GridWatch.C:565
 GridWatch.C:566
 GridWatch.C:567
 GridWatch.C:568
 GridWatch.C:569
 GridWatch.C:570
 GridWatch.C:571
 GridWatch.C:572
 GridWatch.C:573
 GridWatch.C:574
 GridWatch.C:575
 GridWatch.C:576
 GridWatch.C:577
 GridWatch.C:578
 GridWatch.C:579
 GridWatch.C:580
 GridWatch.C:581
 GridWatch.C:582
 GridWatch.C:583
 GridWatch.C:584
 GridWatch.C:585
 GridWatch.C:586
 GridWatch.C:587
 GridWatch.C:588
 GridWatch.C:589