#!/usr/bin/env python3 #************************************************************************** # Copyright (C) 2009-2018 by Walter Brisken * # * # This program is free software; you can redistribute it and/or modify * # it under the terms of the GNU General Public License as published by * # the Free Software Foundation; either version 3 of the License, or * # (at your option) any later version. * # * # This program is distributed in the hope that it will be useful, * # but WITHOUT ANY WARRANTY; without even the implied warranty of * # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * # GNU General Public License for more details. * # * # You should have received a copy of the GNU General Public License * # along with this program; if not, write to the * # Free Software Foundation, Inc., * # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * #************************************************************************** #=========================================================================== # SVN properties (DO NOT CHANGE) # # $Id$ # $HeadURL$ # $LastChangedRevision$ # $Author$ # $LastChangedDate$ # #============================================================================ program = 'checkdir' version = '0.9' author = 'Walter Brisken' verdate = '20191114' from sys import exit, argv from os.path import isfile, isdir from os import getenv, system from glob import glob from time import time import datetime dirPath = getenv("MARK5_DIR_PATH") mjd0 = datetime.datetime(1858, 11, 17, 0, 0) def usage(prog): print('\n%s ver %s %s %s' % (program, version, author, verdate)) print('A program to carefully inspect module directory files for problems.') print('\nUsage: %s [options] []' % prog) print(""" Options can include: --verbose -v Send more output to the screen (use -v -v for extra info) --quiet -q Be quieter in operation --help -h Print this help information and quit --all -a Run on all directory files --show -s Show the directory file --histogram -H Print a histogram of datarates If the "-a" or "--all" switch is not supplied, only those modules listed on the command line will be checked. You cannot use this switch together with a list of modules. This program looks in the directory defined by environment variable MARK5_DIR_PATH . If this variable is not defined this program will not function. Examples: checkdir -a # check all directories for problems checkdir -a -v -v # check all directories and show all problems found checkdir NRAO-024 NRAO+266 # check these two modules """) exit(0) def mjd2vex(mjd, dateonly=False): if mjd < 50001 or mjd > 99999: return '' md = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334] d = int(mjd) s = int((mjd - d)*86400.0 + 0.5) dt = datetime.timedelta(d, s) t = mjd0 + dt d = t.day + md[t.month-1] if t.year % 4 == 0 and t.month > 2: d += 1 if dateonly: return '%dy%03dd' % \ (t.year, d) else: return '%dy%03dd%02dh%02dm%02ds' % \ (t.year, d, t.hour, t.minute, t.second) class Scan: def __init__(self, text): self.error = None self.startMJD = 0.0 self.stopMJD = 0.0 self.endBytes = 0 s = text.split() if len(s) >= 11: try: self.startBytes = int(s[0]) self.bytes = int(s[1]) self.startDay = int(s[2]) self.startSec = int(s[3]) self.startFrame = int(s[4]) self.framesPerSec = int(s[5]) self.duration = float(s[6]) self.bytesPerFrame= int(s[7]) self.byteOffset = int(s[8]) self.nTrack = int(s[9]) self.format = int(s[10]) if len(s) > 11: self.name = s[11] else: self.name = 'Unknown' except ValueError: self.error = 'Conversion error' else: print('Deformed line in file: %s' % text) print('Cannot continue') exit(0) if len(s) < 12: self.error = 'Scan with no label' def dataRate(self): if self.duration < 1.0: return 0 R = 8.0*self.bytes/self.duration/1.0e6 r = 512 for i in range(10): if 1.1*R > r: return r else: r //= 2 return 0 def checkLength(self): if self.bytes == 0: return "Zero length scan" return None def checkDuration(self): if self.duration > 4200: return "Long scan (%d min). This is unusual and possibly problematic." % int(self.duration/60) return None def checkName(self): if self.name.find('UKN') >= 0: return "This scan was rescued and might consist of multiple scans. mk5map can be used to dissect it (for Mark5B format only)" return None def checkTime(self): if self.startDay < 51000 or self.startDay > 100000: return "MJD out of range" if self.startSec < 0 or self.startSec >= 86400: return "Seconds out of range" if self.framesPerSec < 1 or self.framesPerSec > 1000000: return "Frames per second out of range" if self.startFrame < 0 or self.startFrame >= self.framesPerSec: return "Start frame out of range" if self.duration < 0.0 or self.duration > 864000.0: return "Duration out of range" self.startMJD = self.startDay + (self.startSec + float(self.startFrame)/float(self.framesPerSec))/86400.0 self.stopMJD = self.startMJD + self.duration/86400.0 today = 40586.5 + time()/86400.0 if self.startMJD > today+1.0 or self.stopMJD > today+1.0: return "Recording is from %4.2f days in the future!" % (self.stopMJD-today) return None def checkFormat(self): if self.format < 0: return "Undecoded scan" elif self.format == 0: if not self.nTrack in [8,16,32,64]: return "VLBA format with illegal number of tracks (%d)" % self.nTrack if not self.bytesPerFrame in [20160, 40320, 80640, 161280]: return "VLBA format with illegal bytes per frame (%d)" % self.bytesPerFrame if not self.framesPerSec in [25, 50, 100, 200, 400, 800]: return "VLBA format with illegal frames per second (%d)" % self.framesPerSec elif self.format == 1: if not self.nTrack in [8,16,32,64]: return "MKIV format with illegal number of tracks (%d)" % self.nTrack if not self.bytesPerFrame in [20000, 40000, 80000, 160000]: return "MKIV format with illegal bytes per frame (%d)" % self.bytesPerFrame if not self.framesPerSec in [25, 50, 100, 200, 400, 800]: return "MKIV format with illegal frames per second (%d)" % self.framesPerSec elif self.format == 2: if not self.bytesPerFrame in [10016]: return "VLBA format with illegal bytes per frame (%d)" % self.bytesPerFrame if not self.framesPerSec in [25, 50, 100,200,400,800,1600,3200,6400,12800,25600]: return "Mark5B format with illegal frames per second (%d)" % self.framesPerSec elif self.format == 3: pass else: return "Unknown format (%d)" % self.format return None def checkBytes(self): self.endBytes = self.startBytes + self.bytes if self.endBytes < self.startBytes: return "startBytes > endBytes" if self.startBytes % 4 != 0: return "startBytes != 4n" if self.endBytes % 4 != 0: return "endBytes != 4n" return None def checkForOverlap(scans): n = len(scans) if n < 2: return None rv = '' for i in range(1, n): overlapTimeScans = '' overlapByteScans = '' for j in range(0, i): if min(scans[i].stopMJD, scans[j].stopMJD) > max(scans[i].startMJD, scans[j].startMJD): overlapTimeScans += ' %d' % (j+1) if min(scans[i].endBytes, scans[j].endBytes) > max(scans[i].startBytes, scans[j].startBytes): overlapByteScans += ' %d' % (j+1) if overlapTimeScans != '': rv += '\n Scans %d overlaps in time with scan(s)%s' % (i+1, overlapTimeScans) if overlapByteScans != '': rv += '\n Scans %d overlaps in bytes with scan(s)%s' % (i+1, overlapByteScans) if rv == '': return None else: return rv def checkForOrder(scans): n = len(scans) if n < 2: return None for i in range(1, n): if scans[i].startMJD < scans[i-1].startMJD: return 'Scans %d and %d are out of time order' % (i, i+1) if scans[i].startBytes < scans[i-1].startBytes: return 'Scans %d and %d are out of byte order' % (i, i+1) return None def checkFile(module, histogram, verbose=1): fn = dirPath + '/' + module + '.dir' if not isfile(fn): print('%s : No directory found' % module) return 0 text = open(fn).readlines() if len(text) < 1: print('%s : No data in directory file' % module) return 0 header = text[0].split() if len(header) < 4: print('%s : Header malformed' % module) return 0 if header[0] != module: print('%s : Module ID is incorrect (%s)' % (module, header[0])) return 0 try: nScan = int(header[1]) except ValueError: print('%s : Header number of scans value is malformed: %s' % (module, header[1])) return 0 if nScan > len(text) - 1: print('%s : Header number of scans is too large: %d > %d' % (module, nScan, len(text) - 1)) return 0 if nScan <= 0: print('%s : No scans on module. Try rereading the directory?' % module) return 0 scans = [] errors = [] mjdStart = 1.0e9 mjdStop = -1.0e9 for t in text[1:]: scan = Scan(t) scans.append(scan) durCheck = scan.checkDuration() if durCheck != None: print('%s scan %d: %s' % (module, len(scans), durCheck)) if scan.error == None: scan.error = scan.checkLength() if scan.error == None: scan.error = scan.checkTime() if scan.error == None: scan.error = scan.checkFormat() if scan.error == None: scan.error = scan.checkBytes() if scan.error == None: scan.error = scan.checkName() if scan.startMJD < mjdStart: mjdStart = scan.startMJD if scan.stopMJD > mjdStop: mjdStop = scan.stopMJD histogram[scan.dataRate()] += scan.duration if verbose > 3: print("MODULE: " + module) for s in range(len(scans)): if verbose > 3: print("Scan %i runs from %s to %s" % (s+1, mjd2vex(scans[s].startMJD), mjd2vex(scans[s].stopMJD))) if scans[s].error != None: errors.append('Scan %d : %s' % (s+1, scans[s].error)) if len(errors) > 0: if verbose > 1: print('%s : Module has errors' % module) for e in errors: print(' %s' % e) else: print('%s : %d / %d scans have errors' % (module, len(errors), len(scans))) return 0 err = checkForOverlap(scans) if err == None: err = checkForOrder(scans) else: print('%s : %s' % (module, err)) return 0 if verbose > 0: print('%s : %4d scans %s to %s' % (module, nScan, mjd2vex(mjdStart), mjd2vex(mjdStop))) return 0 def printHistogram(h): k = h.keys() k.sort() sum = 0.0 for key in k: sum += h[key] if sum < 1.0: return for key in k: print('%3d Mbps: %5.1f hours = %4.1f%%' % (key, h[key]/3600.0, 100.0*h[key]/sum)) # main if dirPath == None: print('Error: env. var. MARK5_DIR_PATH not defined.') exit(0) if not isdir(dirPath): print('Error: env. var. MARK5_DIR_PATH does not point to a directory.') exit(0) modules = [] verbose = 2 doAll = False show = False histo = False histogram = {0:0.0, 1:0.0, 2:0.0, 4:0.0, 8:0.0, 16:0.0, 32:0.0, 64:0.0, 128:0.0, 256:0.0, 512:0.0} if len(argv) == 1: print('Use -h option for help') exit(0) for a in argv[1:]: if a[0] == '-': if a in ['-v', '--verbose']: verbose += 1 elif a in ['-q', '--quiet']: verbose -= 1 elif a in ['-a', '--all']: doAll = True verbose -= 2 elif a in ['-s', '--show']: show = True elif a in ['-h', '--help']: usage(argv[0]) elif a in ['-H', '--histogram']: histo = True else: print('Unknown command line parameter : ', a) exit(0) else: modules.append(a[:8].upper()) if show: if len(modules) != 1: print('Exactly 1 module must be specified!') else: fn = '%s/%s.dir' % (dirPath, modules[0]) print('Displaying file: %s\n\n' % fn) system('cat %s' % fn) exit(0) if doAll: if len(modules) > 0: print('The doAll flag and individual modules were selected.') print('Only one can be set at a time.') files = glob(dirPath+'/????????.dir') if len(files) == 0: print('No module directories found in %s' % dirPath) exit(0) for f in files: g = f.split('/')[-1] if len(g) == 12 and g[-4:] == '.dir': modules.append(g[:8]) if len(modules) == 0: print('No module directories found in %s' % dirPath) exit(0) modules.sort() lastm = '' for m in modules: if m != lastm: checkFile(m, histogram, verbose) lastm = m if histo > 0: print('Histogram of data rate usage:') printHistogram(histogram)