rda-python-dsquasar 3.0.13__tar.gz → 3.0.15__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (20) hide show
  1. {rda_python_dsquasar-3.0.13/src/rda_python_dsquasar.egg-info → rda_python_dsquasar-3.0.15}/PKG-INFO +1 -1
  2. {rda_python_dsquasar-3.0.13 → rda_python_dsquasar-3.0.15}/pyproject.toml +1 -1
  3. {rda_python_dsquasar-3.0.13 → rda_python_dsquasar-3.0.15}/src/rda_python_dsquasar/dsquasar.py +53 -18
  4. {rda_python_dsquasar-3.0.13 → rda_python_dsquasar-3.0.15/src/rda_python_dsquasar.egg-info}/PKG-INFO +1 -1
  5. {rda_python_dsquasar-3.0.13 → rda_python_dsquasar-3.0.15}/LICENSE +0 -0
  6. {rda_python_dsquasar-3.0.13 → rda_python_dsquasar-3.0.15}/MANIFEST.in +0 -0
  7. {rda_python_dsquasar-3.0.13 → rda_python_dsquasar-3.0.15}/README.md +0 -0
  8. {rda_python_dsquasar-3.0.13 → rda_python_dsquasar-3.0.15}/setup.cfg +0 -0
  9. {rda_python_dsquasar-3.0.13 → rda_python_dsquasar-3.0.15}/src/rda_python_dsquasar/__init__.py +0 -0
  10. {rda_python_dsquasar-3.0.13 → rda_python_dsquasar-3.0.15}/src/rda_python_dsquasar/ds_quasar.py +0 -0
  11. {rda_python_dsquasar-3.0.13 → rda_python_dsquasar-3.0.15}/src/rda_python_dsquasar/dsquasar.usg +0 -0
  12. {rda_python_dsquasar-3.0.13 → rda_python_dsquasar-3.0.15}/src/rda_python_dsquasar/dstacc.py +0 -0
  13. {rda_python_dsquasar-3.0.13 → rda_python_dsquasar-3.0.15}/src/rda_python_dsquasar/taccrec.py +0 -0
  14. {rda_python_dsquasar-3.0.13 → rda_python_dsquasar-3.0.15}/src/rda_python_dsquasar/tacctar.py +0 -0
  15. {rda_python_dsquasar-3.0.13 → rda_python_dsquasar-3.0.15}/src/rda_python_dsquasar.egg-info/SOURCES.txt +0 -0
  16. {rda_python_dsquasar-3.0.13 → rda_python_dsquasar-3.0.15}/src/rda_python_dsquasar.egg-info/dependency_links.txt +0 -0
  17. {rda_python_dsquasar-3.0.13 → rda_python_dsquasar-3.0.15}/src/rda_python_dsquasar.egg-info/entry_points.txt +0 -0
  18. {rda_python_dsquasar-3.0.13 → rda_python_dsquasar-3.0.15}/src/rda_python_dsquasar.egg-info/requires.txt +0 -0
  19. {rda_python_dsquasar-3.0.13 → rda_python_dsquasar-3.0.15}/src/rda_python_dsquasar.egg-info/top_level.txt +0 -0
  20. {rda_python_dsquasar-3.0.13 → rda_python_dsquasar-3.0.15}/test/test_dsquasar.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rda_python_dsquasar
3
- Version: 3.0.13
3
+ Version: 3.0.15
4
4
  Summary: RDA Python package to backup and recover RDA data archives to and from GLOBUS Quasar backup server
5
5
  Author-email: Zaihua Ji <zji@ucar.edu>
6
6
  Project-URL: Homepage, https://github.com/NCAR/rda-python-dsquasar
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "rda_python_dsquasar"
7
- version = "3.0.13"
7
+ version = "3.0.15"
8
8
  authors = [
9
9
  { name="Zaihua Ji", email="zji@ucar.edu" },
10
10
  ]
@@ -104,7 +104,7 @@ class DsQuasar(PgCMD, PgSplit):
104
104
  # for -A 3 but tar files for -A 2/-A 4.
105
105
  self.MAXWORKERS = 2 # default maximum concurrent workers per command (-W)
106
106
  self.WORKGRACE = 6*3600 # 6 hours before a running job is judged on progress
107
- self.MINWDONE = 0.01 # done fraction after WORKGRACE that counts as progressing
107
+ self.MINWPROJ = 0.9 # projected done fraction at the walltime that counts as finishing
108
108
  self.PGBACK = {
109
109
  'workdir' : "{}/{}/quasar_backup".format(self.PGLOG['GDEXWORK'], self.PGLOG['COMMONUSER']),
110
110
  'mproc' : 1,
@@ -114,6 +114,8 @@ class DsQuasar(PgCMD, PgSplit):
114
114
  'backflag' : None,
115
115
  'actmsg' : None,
116
116
  'pstep' : 0, # record progress step for under dscheck control
117
+ 'dcnt' : 0, # files done so far, recorded as dscheck.dcount every pstep
118
+ 'dsize' : 0,
117
119
  'errcnt' : 0,
118
120
  'bckcnt' : 0,
119
121
  'maxcnt' : 10,
@@ -399,6 +401,17 @@ class DsQuasar(PgCMD, PgSplit):
399
401
  return tcnt
400
402
  return self.pgget('bfile', '', bcnd, self.LGWNEX)
401
403
 
404
+ # describe how much of the registered work this run has finished. the tar counts alone
405
+ # do not show it: -A 3 tars each input file right after creating it, so the 'N' count
406
+ # stays near zero exactly while building is the busy phase, which reads as almost done
407
+ # when the run is barely started. dcount/fcount is the honest measure. the live dcnt is
408
+ # used rather than the recorded dcount, which is only flushed to RDADB every pstep files
409
+ def batch_done_count(self):
410
+ fcnt = self.DSCHK['fcount'] if 'fcount' in self.DSCHK else 0
411
+ dcnt = self.PGBACK['dcnt']
412
+ if not fcnt: return "{} file(s) done".format(dcnt)
413
+ return "{} of {} file(s) done({}%)".format(dcnt, fcnt, int(100*dcnt/fcnt))
414
+
402
415
  # look up the dscheck record registered for a worker's argv, using the same lookup key
403
416
  # init_dscheck builds
404
417
  def lookup_worker_dscheck(self, argv):
@@ -409,18 +422,29 @@ class DsQuasar(PgCMD, PgSplit):
409
422
  dargv = dargv[0:100]
410
423
  return self.get_dscheck("dsquasar", dargv, self.PGBACK['workdir'], self.PGLOG['CURUID'], dargx, self.LOGWRN)
411
424
 
412
- # a running batch job counts as stalled once it has run longer than WORKGRACE and has
413
- # still completed no more than MINWDONE of its recorded work; anything further along
414
- # than that is progressing and is left alone. the done fraction dcount/fcount is used
415
- # instead of a raw count because dcount counts GDEX files for -A 3 but tar files for
416
- # -A 2/-A 4. a job that is not locked, not started yet, or whose process is gone is not
417
- # stalled: init_dscheck already restarts a dead one.
425
+ # a running batch job counts as stalled once it has run longer than WORKGRACE and its
426
+ # progress so far projects to less than MINWPROJ of its recorded work by the walltime:
427
+ # such a job cannot finish this attempt however long it is left alone, so an extra
428
+ # worker is what helps it, not more retries.
429
+ # the test is on the RATE, not on an absolute done fraction as it first was. a fixed
430
+ # 1% threshold only catches a job that is nearly frozen, and reads a job that needs
431
+ # weeks as progressing: the real case was 2% done after 13H35M of a 24 hour walltime,
432
+ # which is above 1% yet projects to 3.5% by the walltime and ~28 days to finish, and so
433
+ # never got a second worker.
434
+ # the done fraction dcount/fcount is used instead of a raw count because dcount counts
435
+ # GDEX files for -A 3 but tar files for -A 2/-A 4. a job that is not locked, not started
436
+ # yet, or whose process is gone is not stalled: init_dscheck already restarts a dead one.
437
+ # WALLTIME is the 24 hours asked for at submit time, since only the submitting process
438
+ # runs this and set_walltime_deadline reads the granted walltime back in the batch job.
439
+ # when the daemon caps it to a shorter queue limit the projection is optimistic, which
440
+ # errs towards leaving the job alone.
418
441
  def worker_progress_stalled(self, pgrec):
419
442
  if not (pgrec['pid'] and pgrec['stttime']): return False
420
443
  if self.check_host_pid(pgrec['lockhost'], pgrec['pid']) <= 0: return False
421
- if (tm() - pgrec['stttime']) < self.WORKGRACE: return False
444
+ elapsed = tm() - pgrec['stttime']
445
+ if elapsed < self.WORKGRACE: return False
422
446
  done = pgrec['dcount']/pgrec['fcount'] if pgrec['fcount'] else 0
423
- return done <= self.MINWDONE
447
+ return (done*self.WALLTIME/elapsed) < self.MINWPROJ
424
448
 
425
449
  # whether more than one worker may run the current action. two workers stay off each
426
450
  # other's files only through dataset locking, so the action must lock what it works on:
@@ -566,6 +590,7 @@ class DsQuasar(PgCMD, PgSplit):
566
590
  'size' : 0, 'infiles' : [], 'instr' : '', 'qdsids' : [], 'abids' : [],
567
591
  'dslocks' : [], 'qfcnt' : 0, 'qsize' : 0, 'qcnt' : 0}
568
592
  for dsid in bfiles:
593
+ self.check_batch_deadline()
569
594
  if len(qinfo['dsids']) == self.DSCNT: self.process_one_backup_file(qinfo, True, False)
570
595
  if self.PGBACK['dolock'] and dsid not in qinfo['dslocks']:
571
596
  if self.lock_dataset(dsid, 1, self.LOGERR) < 1: continue
@@ -682,6 +707,7 @@ class DsQuasar(PgCMD, PgSplit):
682
707
  if self.lock_dataset(dsid, 1, self.LOGERR) < 1: continue
683
708
  qinfo['dslocks'].append(dsid)
684
709
  for bid in bfiles[dsid]:
710
+ self.check_batch_deadline()
685
711
  # the status 'N' records were gathered before this dataset was locked, so they
686
712
  # can be hours old; confirm each one is still untarred before spending a tar on
687
713
  # it. holding the dataset lock makes this check race free against a second
@@ -729,6 +755,7 @@ class DsQuasar(PgCMD, PgSplit):
729
755
  'size' : 0, 'fromfiles' : [], 'tofiles' : [], 'qdsids' : [],
730
756
  'qfcnt' : 0, 'qsize' : 0, 'qcnt' : 0}
731
757
  for bid in bfiles:
758
+ self.check_batch_deadline()
732
759
  binfo = bfiles[bid]
733
760
  for dsid in binfo['dsids']:
734
761
  if dsid not in qinfo['dsids']: qinfo['dsids'].append(dsid)
@@ -840,21 +867,27 @@ class DsQuasar(PgCMD, PgSplit):
840
867
  self.pglog("Cannot clean the progress report of {}; it is emailed again".format(cnd), self.LOGWRN)
841
868
  return estat
842
869
 
843
- # guard the long PBS batch jobs (-A 2/3/4/6) against the walltime: once past MAXRUNTIME
870
+ # guard the long PBS batch jobs (-A 2/3/4/6/7) against the walltime: once past MAXRUNTIME
844
871
  # the run keeps going, but a progress report is parked in dscheck.einfo so that
845
872
  # something is reported even if PBS kills the job at the walltime. the final report
846
873
  # follows and drops the parked one if the run does finish in time. the live email
847
874
  # buffers are saved and put back, so the final report still carries everything logged
848
875
  # before the cutoff.
849
- # both queue depths are reported rather than the current phase's: -A 3 and -A 6 run the
850
- # build and the transfer phase in one run, and -A 3 tars each input file right after
851
- # creating it, so its status 'N' count stays near zero while that is the busy phase.
876
+ # both queue depths are reported rather than the current phase's, because -A 3 tars each
877
+ # input file right after creating it, so its status 'N' count stays near zero while that
878
+ # is the busy phase. they are worded as STATES ('left to build', 'tarred') and not as
879
+ # work this run will do: only -A 4 and -A 6 transfer, so calling the status 'T' count
880
+ # 'to transfer' in an -A 2 or -A 3 report claims work that run never performs.
852
881
  # a no-op for command-line runs, other actions, before the cutoff, or once a progress
853
882
  # report is cached.
883
+ # called from the top of every per-item loop, not only where a tar is dispatched: a long
884
+ # stretch of files that are skipped (already backed up, or not enough accumulated size to
885
+ # tar yet) would otherwise walk past the cutoff without ever reaching the check, which is
886
+ # how a 23h run reported nothing.
854
887
  def check_batch_deadline(self):
855
888
  act = self.PGBACK['action']
856
889
  if self.PGBACK['einfo'] or not (self.PGBACK['doemail'] and self.PGLOG['DSCHECK']): return
857
- if self.PGLOG['CURBID'] < 1 or act not in (self.TARACT, self.CTACTS, self.BCKACT, self.TBACTS): return
890
+ if self.PGLOG['CURBID'] < 1 or act not in (self.TARACT, self.CTACTS, self.BCKACT, self.TBACTS, self.CBACTS): return
858
891
  elapsed = tm() - self.PGBACK['starttime']
859
892
  if elapsed < self.MAXRUNTIME: return
860
893
  tcnt = self.batch_tar_count('N')
@@ -862,12 +895,13 @@ class DsQuasar(PgCMD, PgSplit):
862
895
  etime = self.seconds_to_string_time(int(elapsed))
863
896
  amsg = self.ACTMSG[act]
864
897
  bmsg = self.BACKMSG[self.PGBACK['backflag']] if self.PGBACK['backflag'] else 'backup'
865
- rmsg = "{} {} tar file(s) left to build and {} to transfer".format(tcnt, bmsg, bcnt)
866
- msg = "{}: Still running after {} of the {} PBS walltime, with {}".format(amsg, etime, self.seconds_to_string_time(self.WALLTIME), rmsg)
898
+ dmsg = self.batch_done_count()
899
+ rmsg = "{} {} tar file(s) left to build and {} tarred".format(tcnt, bmsg, bcnt)
900
+ msg = "{}: Still running after {} of the {} PBS walltime, {}, with {}".format(amsg, etime, self.seconds_to_string_time(self.WALLTIME), dmsg, rmsg)
867
901
  self.pglog(self.INDENT + msg, self.LOGACT)
868
902
  saved = {key : self.PGLOG[key] for key in ('EMLMSG', 'ERRMSG', 'ERRCNT', 'SUMMSG', 'PRGMSG')}
869
- self.set_email("{}: {} still in progress after {}, with {}!".format(self.PGBACK['cmd'], amsg, etime, rmsg), self.EMLTOP)
870
- title = "dsquasar: {} in progress ({} to build, {} to transfer)".format(amsg, tcnt, bcnt)
903
+ self.set_email("{}: {} still in progress after {}, {}, with {}!".format(self.PGBACK['cmd'], amsg, etime, dmsg, rmsg), self.EMLTOP)
904
+ title = "dsquasar: {} in progress ({})".format(amsg, dmsg)
871
905
  if self.PGBACK['errcnt']: title += " Error({})".format(self.PGBACK['errcnt'])
872
906
  self.report_dscheck_email(title, 1)
873
907
  self.PGLOG.update(saved)
@@ -1092,6 +1126,7 @@ class DsQuasar(PgCMD, PgSplit):
1092
1126
  fcate = filetype.lower()
1093
1127
  tname = fcate + 'file'
1094
1128
  for i in range(fcnt):
1129
+ self.check_batch_deadline()
1095
1130
  pgrec = recs[i]
1096
1131
  if not self.evaluate_file_stat(dsid, fcate, pgrec): continue
1097
1132
  fname = pgrec[tname]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rda_python_dsquasar
3
- Version: 3.0.13
3
+ Version: 3.0.15
4
4
  Summary: RDA Python package to backup and recover RDA data archives to and from GLOBUS Quasar backup server
5
5
  Author-email: Zaihua Ji <zji@ucar.edu>
6
6
  Project-URL: Homepage, https://github.com/NCAR/rda-python-dsquasar