Skip to content
Snippets Groups Projects
monitor.py 11.1 KiB
Newer Older
  • Learn to ignore specific revisions
  • #! /usr/bin/env python
    
    """Monitor """
    from os import path
    import sys
    # ensure geomag is on the path before importing
    try:
        import geomagio  # noqa (tells linter to ignore this line.)
    except:
        script_dir = path.dirname(path.abspath(__file__))
        sys.path.append(path.normpath(path.join(script_dir, '..')))
    
    import argparse
    import sys
    from obspy.core import UTCDateTime
    import geomagio.TimeseriesUtility as TimeseriesUtility
    import geomagio.edge as edge
    
    
    def calculate_warning_threshold(warning_threshold, interval):
        """Calculate warning_threshold for the giving interval
        Parameters
        ----------
        warning_threshold: int
            the warning_threshold from the command line.
        interval: string
            the interval being warned against
        """
        if interval == 'minute':
            warning_threshold *= 60
        elif interval == 'second':
            warning_threshold *= 3600
        return warning_threshold
    
    
    def calculate_gap_percentage(total, trace):
        """Calculate the percentage of missing values
        Parameters
        ----------
        total: int
            Total number of missing values
        trace: obspy.core.Trace
            a stream containing a single channel of data
        """
        return (float(total) / float(trace.stats.npts)) * 100.0, trace.stats.npts
    
    
    def format_time(date):
        """Print UTCDateTime in YYYY-MM-DD HH:MM:SS format
        Parameters
        ----------
        date: UTCDateTime
        """
        return date.datetime.strftime("%Y-%m-%d %H:%M:%S")
    
    
    def get_gaps(gaps):
        """Print gaps for a given channel into a html string.
        gaps: array
            Array of gaps
        """
        gap_string = ''
        if len(gaps):
            for gap in gaps:
                gap_string += '&nbsp;&nbsp;&nbsp;&nbsp; %s to %s <br>\n' % \
                    (format_time(gap[0]),
                     format_time(gap[1]))
        else:
            gap_string = '&nbsp;&nbsp;&nbsp;&nbsp;None<br>'
        return gap_string
    
    
    def get_gap_total(gaps, interval):
        """Get total length of time for all gaps in a channel
        Parameters
        ----------
        gaps: array
            Array of gaps
        interval: string
            the interval being warned against
        """
        total = 0
        divisor = 1
        if interval == 'minute':
            divisor = 60
        for gap in gaps:
    
            total += (int(gap[2] - gap[0]) / divisor)
    
    def get_last_time(gaps, endtime):
        """ Return the last time that a channel has in it.
        Parameters
        ----------
        gaps: array
            Array of gaps
        endtime: UTCDateTime
            The endtime specified in the arguments
        """
        length = len(gaps) - 1
        if length > -1 and gaps[length][2] >= endtime:
            return gaps[length][0]
        else:
            return endtime
    
    
    def get_table_header():
        return '<table style="border-collapse: collapse;">\n' + \
            '<thead>\n' + \
                '<tr>\n' + \
                    '<th style="border:1px solid black; padding: 2px;">' +\
                        '</th>\n' + \
                    '<th style="border:1px solid black; padding: 2px;">' +\
                        '</th>\n' + \
                    '<th colspan=3 style="border:1px solid black; padding: 2px;">' +\
                        'Gap</th>\n' + \
                    '<th style="border:1px solid black; padding: 2px;">' +\
                        '</th>\n' + \
                  '</tr>\n' + \
                  '<tr>\n' + \
                    '<th style="border:1px solid black; padding: 2px;">' +\
                        'Channel</th>\n' + \
                    '<th style="border:1px solid black; padding: 2px;">' +\
                        'Last Time Value</th>\n' + \
                    '<th style="border:1px solid black; padding: 2px;">' +\
                        'Count</th>\n' + \
                    '<th style="border:1px solid black; padding: 2px;">' +\
                        'Total Time</th>\n' + \
                    '<th style="border:1px solid black; padding: 2px;">' +\
                        'Percentage</th>\n' + \
                    '<th style="border:1px solid black; padding: 2px;">' +\
                        'Total Values</th>\n' + \
                '</tr>\n' + \
            '</thead>\n' + \
            '<tbody>\n'
    
    
    def has_gaps(gaps):
        """ Returns True if gaps dictionary has gaps in it.
        Parameters
        ----------
        gaps: dictionary
            Dictionary of Channel:gaps arrays
        """
        for channel in gaps:
            if len(gaps[channel]):
                return True
        return False
    
    
    def print_html_header(starttime, endtime, title):
        """Prints the html header, and title
        Parameters
        ----------
        starttime: UTCDateTime
            The starttime of the data we are analyzing
        endtime: UTCDateTime
            The endtime of the data we are analyzing
        title: string
            The title passed in by the user
        """
        print '<!DOCTYPE html>\n' + \
        '<html>\n' + \
            '<head>\n' + \
                '<title> %s \n to %s \n</title>' % \
                        (format_time(starttime), format_time(endtime)) + \
            '</head>\n' + \
            '<body>\n' + \
                '<style type="text/css">\n' + \
                    'table {border-collapse: collapse;}\n' + \
                    'th {border:1px solid black; padding: 2px;}\n' + \
                    'td {text-align:center;}\n' + \
                '</style>\n' +\
                title + '<br>\n'\
                '%s to %s ' % \
                    (format_time(starttime), format_time(endtime))
    
    
    def print_observatories(args):
        """Print all the observatories
        Parameters
        ---------
        args: dictionary
            Holds all the command line arguments. See parse_args
    
        Returns
        -------
        Boolean: if a warning was issued.
    
        """
        intervals = args.intervals
        channels = args.channels
        starttime = args.starttime
        endtime = args.endtime
        host = args.edge_host
        table_header = get_table_header()
        warning_issued = False
        table_end = \
            '</tbody>\n' + \
            '</table>\n'
    
        for observatory in args.observatories:
            summary_table = ''
            gap_details = ''
            print_it = False
            summary_header = '<p>Observatory: %s </p>\n' % observatory
            summary_table += table_header
            for interval in intervals:
                factory = edge.EdgeFactory(
                        host=host,
                        port=2060,
                        observatory=observatory,
                        type=args.type,
                        channels=channels,
                        locationCode=args.locationcode,
    
                        interval=interval)
    
    
                timeseries = factory.get_timeseries(
                        starttime=starttime,
                        endtime=endtime)
                gaps = TimeseriesUtility.get_stream_gaps(timeseries)
                if args.gaps_only and not has_gaps(gaps):
                    continue
                else:
                    print_it = True
    
                warning = ''
                warning_threshold = calculate_warning_threshold(
    
                        args.warning_threshold, interval)
    
    
                summary_table += '<tr>'
                summary_table += '<td style="text-align:center;">'
                summary_table += ' %sS \n </td></tr>\n' % interval.upper()
                gap_details += '&nbsp;&nbsp;%sS <br>\n' % interval.upper()
                for channel in channels:
                    gap = gaps[channel]
                    trace = timeseries.select(channel=channel)[0]
                    total = get_gap_total(gap, interval)
    
                    percentage, count = calculate_gap_percentage(total, trace)
    
                    last = get_last_time(gap, endtime)
                    summary_table += '<tr>\n'
                    summary_table += '<td style="text-align:center;">%s</td>' % \
                            channel
                    summary_table += '<td style="text-align:center;">%s</td>' % \
                            format_time(last)
                    summary_table += '<td style="text-align:center;">%d</td>' % \
                            len(gap)
                    summary_table += '<td style="text-align:center;">%d %s</td>' \
                            % (total, interval)
                    summary_table += '<td style="text-align:center;">%0.2f%%</td>'\
                            % percentage
                    summary_table += '<td style="text-align:center;">%d</td>' \
                            % count
                    summary_table += '</tr>\n'
                    if endtime - last > warning_threshold:
                        warning += '%s ' % channel
                        warning_issued = True
                    # Gap Detail
                    gap_details += '&nbsp;&nbsp;Channel: %s <br>\n' % channel
                    gap_details += get_gaps(gap) + '\n'
                if len(warning):
                    summary_header += 'Warning: Channels older then ' + \
                        'warning-threshold ' + \
                        '%s %ss<br>\n' % (warning, interval)
            summary_table += table_end
            if print_it:
                print summary_header
                print summary_table
                print gap_details
    
            return warning_issued
    
    
    def main(args):
        """command line tool for building geomag monitoring reports
    
        Inputs
        ------
        use monitor.py --help to see inputs, or see parse_args.
    
        Notes
        -----
        parses command line options using argparse
        Output is in HTML.
        """
        print_html_header(args.starttime, args.endtime, args.title)
    
        warning_issued = print_observatories(args)
        print '</body>\n' + \
              '</html>\n'
    
        sys.exit(warning_issued)
    
    
    def parse_args(args):
        """parse input arguments
    
        Parameters
        ----------
        args : list of strings
    
        Returns
        -------
        argparse.Namespace
            dictionary like object containing arguments.
        """
        parser = argparse.ArgumentParser(
            description='Use @ to read commands from a file.',
            fromfile_prefix_chars='@')
    
        parser.add_argument('--starttime',
                required=True,
                type=UTCDateTime,
                default=None,
                help='UTC date YYYY-MM-DD HH:MM:SS')
        parser.add_argument('--endtime',
                required=True,
                type=UTCDateTime,
                default=None,
                help='UTC date YYYY-MM-DD HH:MM:SS')
        parser.add_argument('--edge-host',
                required=True,
                help='IP/URL for edge connection')
        parser.add_argument('--observatories',
                required=True,
                nargs='*',
                help='Observatory code ie BOU, CMO, etc')
        parser.add_argument('--channels',
                nargs='*',
                default=['H', 'E', 'Z', 'F'],
                help='Channels H, E, Z, etc')
        parser.add_argument('--intervals',
                nargs='*',
                default=['minute'],
                choices=['hourly', 'minute', 'second'])
        parser.add_argument('--locationcode',
                default='R0',
                choices=['R0', 'R1', 'RM', 'Q0', 'D0', 'C0'])
        parser.add_argument('--type',
                default='variation',
                choices=['variation', 'quasi-definitive', 'definitive'])
        parser.add_argument('--warning-threshold',
                type=int,
                default=60,
                help='How many time slices should pass before a warning is issued')
        parser.add_argument('--gaps-only',
                action='store_true',
                default=True,
                help='Only print Observatories with gaps.')
        parser.add_argument('--title',
                default='',
                help='Title for the top of the report')
    
        return parser.parse_args(args)
    
    
    if __name__ == '__main__':
        args = parse_args(sys.argv[1:])
        main(args)