383ff66de9a1a37635a1d079cccdeb9842056524 braney Tue Sep 8 09:22:26 2026 -0700 ottoMonitor: correct the job counts in the README and the survey header, refs #38101 The README's "WHAT IT ASKS" section said fifteen jobs write a log or a named file, nine leave only a directory mtime, and eight leave nothing. Those add to 32, but the monitor watches 40. The real split is twenty-four, eight and eight, which is what ottoMonitor.py -v reports. The old "fifteen" counted only the external jobs with a positive stamp and dropped the nine internal ones that also write a per-run file. The old "nine" came from counting the "dir mtime" notes in ottoMonitorStamps.tsv, which catches ottoGitVsHive, but that job is blind, not directory-mtime. The same off-by-one is fixed in the traps section. The two surveys cover all 47 jobs in otto.crontab while the monitor watches 40, so the README now says where the other seven went: they are the Cell Browser jobs, marked monitor=no in ottoOwners.tsv. ottoFailureSignatures.tsv opened by saying the 18 internal jobs were not surveyed yet, three sections above a fully surveyed section for exactly those 18 jobs. The sentence was left over from the draft before that section was added. Also drop a dead clause in sourceIsUp(). "code == 226 or code == 0 and False" reduces to "code == 226", because and binds tighter than or, so it read as if a curl that could not connect were handled specially when it was not. Behavior is unchanged: curl() returns 0 when curl itself failed, and 0 means the source did not answer. These are the two items the 2026-09-08 code review asked for, both in files that tell a future editor to read them before touching a stamp glob. diff --git src/hg/utils/otto/ottoMonitor/ottoMonitor.py src/hg/utils/otto/ottoMonitor/ottoMonitor.py index 947f6c5b136..618587b0e10 100755 --- src/hg/utils/otto/ottoMonitor/ottoMonitor.py +++ src/hg/utils/otto/ottoMonitor/ottoMonitor.py @@ -193,32 +193,34 @@ path = sourceUrl[len("file:"):] if not os.access(path, os.R_OK): return(None, "cannot read " + path) if path.endswith("curl.config"): code = curl(["-K", path, "-r", "0-0"]) return(code == 200 or code == 206, "curl -K %s -> %d" % (path, code)) with open(path) as fh: for line in fh: line = line.strip() if line.startswith("http") or line.startswith("ftp"): code = curl(["-L", "-r", "0-0", line]) return(code in (200, 206), "config url -> %d" % code) return(None, "no url found in " + path) if sourceUrl.startswith("ftp:"): + # An ftp listing has no HTTP code; curl reports 226 for a completed + # transfer. Anything else, 0 included, means the source did not answer. code = curl([sourceUrl]) - return(code == 226 or code == 0 and False, "ftp -> %d" % code) + return(code == 226, "ftp -> %d" % code) code = curl(["-I", sourceUrl]) if code == 200: return(True, "HEAD -> 200") # HEAD is refused by clinGenCspec, insight and lovd, and g2p and omim # redirect. A ranged GET answers all five. code2 = curl(["-L", "-r", "0-0", sourceUrl]) return(code2 in (200, 206), "HEAD -> %d, GET -> %d" % (code, code2)) def readTable(path, wanted): """Read a tab separated table with a leading comment block. Returns a dict keyed on the first column, plus the comment lines, so the ottoOnDuty header can be found without a second pass.""" rows = {}