def curses_main(stdscr):
    # TODO: figure out how to pass the configs in from plotman.py instead of
    # duplicating the code here.
    with open('config.yaml', 'r') as ymlfile:
        cfg = yaml.load(ymlfile, Loader=yaml.FullLoader)
    dir_cfg = cfg['directories']
    sched_cfg = cfg['scheduling']
    plotting_cfg = cfg['plotting']

    log = Log()

    plotting_active = True
    archiving_configured = 'archive' in dir_cfg
    archiving_active = archiving_configured

    (n_rows, n_cols) = map(int, stdscr.getmaxyx())

    # Page layout.  Currently requires at least ~40 rows.
    # TODO: make everything dynamically figure to best use available space
    header_height = 3
    jobs_height = 10
    dirs_height = 14
    logscreen_height = n_rows - (header_height + jobs_height + dirs_height)

    header_pos = 0
    jobs_pos = header_pos + header_height
    dirs_pos = jobs_pos + jobs_height
    logscreen_pos = dirs_pos + dirs_height

    plotting_status = '<startup>'  # todo rename these msg?
    archiving_status = '<startup>'

    refresh_period = int(sched_cfg['polling_time_s'])

    stdscr.nodelay(True)  # make getch() non-blocking
    stdscr.timeout(2000)

    header_win = curses.newwin(header_height, n_cols, header_pos, 0)
    log_win = curses.newwin(logscreen_height, n_cols, logscreen_pos, 0)
    jobs_win = curses.newwin(jobs_height, n_cols, jobs_pos, 0)
    dirs_win = curses.newwin(dirs_height, n_cols, dirs_pos, 0)

    jobs = Job.get_running_jobs(dir_cfg['log'])
    last_refresh = datetime.datetime.now()

    pressed_key = ''  # For debugging

    while True:

        # TODO: handle resizing.  Need to (1) figure out how to reliably get
        # the terminal size -- the recommended method doesn't seem to work:
        #    (n_rows, n_cols) = [int(v) for v in stdscr.getmaxyx()]
        # Consider instead:
        #    ...[int(v) for v in os.popen('stty size', 'r').read().split()]
        # and then (2) implement the logic to resize all the subwindows as above

        # stdscr.clear()
        linecap = n_cols - 1
        logscreen_height = n_rows - (header_height + jobs_height + dirs_height)

        elapsed = (datetime.datetime.now() - last_refresh).total_seconds()

        # A full refresh scans for and reads info for running jobs from
        # scratch (i.e., reread their logfiles).  Otherwise we'll only
        # initialize new jobs, and mostly rely on cached info.
        do_full_refresh = elapsed >= refresh_period

        if not do_full_refresh:
            jobs = Job.get_running_jobs(dir_cfg['log'], cached_jobs=jobs)

        else:
            last_refresh = datetime.datetime.now()
            jobs = Job.get_running_jobs(dir_cfg['log'])

            if plotting_active:
                (started,
                 msg) = manager.maybe_start_new_plot(dir_cfg, sched_cfg,
                                                     plotting_cfg)
                if (started):
                    log.log(msg)
                    plotting_status = '<just started job>'
                    jobs = Job.get_running_jobs(dir_cfg['log'],
                                                cached_jobs=jobs)
                else:
                    plotting_status = msg

            if archiving_configured and archiving_active:
                # Look for running archive jobs.  Be robust to finding more than one
                # even though the scheduler should only run one at a time.
                arch_jobs = archive.get_running_archive_jobs(
                    dir_cfg['archive'])
                if arch_jobs:
                    archiving_status = 'pid: ' + ', '.join(map(str, arch_jobs))
                else:
                    (should_start,
                     status_or_cmd) = archive.archive(dir_cfg, jobs)
                    if not should_start:
                        archiving_status = status_or_cmd
                    else:
                        cmd = status_or_cmd
                        log.log('Starting archive: ' + cmd)

                        # TODO: do something useful with output instead of DEVNULL
                        p = subprocess.Popen(cmd,
                                             shell=True,
                                             stdout=subprocess.DEVNULL,
                                             stderr=subprocess.STDOUT,
                                             start_new_session=True)

        # Directory prefixes, for abbreviation
        tmp_prefix = ''  #os.path.commonpath(dir_cfg['tmp'])
        dst_prefix = ''  #os.path.commonpath(dir_cfg['dst'])
        if archiving_configured:
            arch_prefix = dir_cfg['archive']['rsyncd_path']

        # Header
        header_win.addnstr(0, 0, 'Plotman', linecap, curses.A_BOLD)
        timestamp = datetime.datetime.now().strftime("%H:%M:%S")
        refresh_msg = "now" if do_full_refresh else f"{int(elapsed)}s/{refresh_period}"
        header_win.addnstr(f" {timestamp} (refresh {refresh_msg})", linecap)
        header_win.addnstr('  |  <P>lotting: ', linecap, curses.A_BOLD)
        header_win.addnstr(
            plotting_status_msg(plotting_active, plotting_status), linecap)
        header_win.addnstr(' <A>rchival: ', linecap, curses.A_BOLD)
        header_win.addnstr(
            archiving_status_msg(archiving_configured, archiving_active,
                                 archiving_status), linecap)

        # Oneliner progress display
        header_win.addnstr(1, 0, 'Jobs (%d): ' % len(jobs), linecap)
        header_win.addnstr('[' + reporting.job_viz(jobs) + ']', linecap)

        # These are useful for debugging.
        # header_win.addnstr('  term size: (%d, %d)' % (n_rows, n_cols), linecap)  # Debuggin
        # if pressed_key:
        # header_win.addnstr(' (keypress %s)' % str(pressed_key), linecap)
        header_win.addnstr(2, 0, 'Prefixes:', linecap, curses.A_BOLD)
        header_win.addnstr('  tmp=', linecap, curses.A_BOLD)
        header_win.addnstr(tmp_prefix, linecap)
        header_win.addnstr('  dst=', linecap, curses.A_BOLD)
        header_win.addnstr(dst_prefix, linecap)
        if archiving_configured:
            header_win.addnstr('  archive=', linecap, curses.A_BOLD)
            header_win.addnstr(arch_prefix, linecap)
        header_win.addnstr(' (remote)', linecap)

        # Jobs
        jobs_win.addstr(
            0, 0,
            reporting.status_report(jobs, n_cols, jobs_height, tmp_prefix,
                                    dst_prefix))
        jobs_win.chgat(0, 0, curses.A_REVERSE)

        # Dirs.  Collect reports as strings, then lay out.
        n_tmpdirs = len(dir_cfg['tmp'])
        n_tmpdirs_half = int(n_tmpdirs / 2)
        tmp_report_1 = reporting.tmp_dir_report(jobs, dir_cfg['tmp'],
                                                sched_cfg, n_cols, 0,
                                                n_tmpdirs_half, tmp_prefix)
        tmp_report_2 = reporting.tmp_dir_report(jobs, dir_cfg['tmp'],
                                                sched_cfg, n_cols,
                                                n_tmpdirs_half, n_tmpdirs,
                                                tmp_prefix)

        dst_report = reporting.dst_dir_report(jobs, dir_cfg['dst'], n_cols,
                                              dst_prefix)

        if archiving_configured:
            arch_report = reporting.arch_dir_report(
                archive.get_archdir_freebytes(dir_cfg['archive']), n_cols,
                arch_prefix)
            if not arch_report:
                arch_report = '<no archive dir info>'
        else:
            arch_report = '<archiving not configured>'

        tmp_h = max(len(tmp_report_1.splitlines()),
                    len(tmp_report_2.splitlines()))
        tmp_w = len(
            max(tmp_report_1.splitlines() + tmp_report_2.splitlines(),
                key=len)) + 1
        dst_h = len(dst_report.splitlines())
        dst_w = len(max(dst_report.splitlines(), key=len)) + 1
        arch_h = len(arch_report.splitlines()) + 1
        arch_w = n_cols

        tmpwin_12_gutter = 3
        tmpwin_dstwin_gutter = 6

        maxtd_h = max([tmp_h, dst_h])

        tmpwin_1 = curses.newwin(tmp_h, tmp_w, dirs_pos + int(
            (maxtd_h - tmp_h) / 2), 0)
        tmpwin_1.addstr(tmp_report_1)

        tmpwin_2 = curses.newwin(tmp_h, tmp_w, dirs_pos + int(
            (maxtd_h - tmp_h) / 2), tmp_w + tmpwin_12_gutter)
        tmpwin_2.addstr(tmp_report_2)

        tmpwin_1.chgat(0, 0, curses.A_REVERSE)
        tmpwin_2.chgat(0, 0, curses.A_REVERSE)

        dstwin = curses.newwin(
            dst_h, dst_w, dirs_pos + int((maxtd_h - dst_h) / 2),
            2 * tmp_w + tmpwin_12_gutter + tmpwin_dstwin_gutter)
        dstwin.addstr(dst_report)
        dstwin.chgat(0, 0, curses.A_REVERSE)

        #archwin = curses.newwin(arch_h, arch_w, dirs_pos + maxtd_h, 0)
        #archwin.addstr(0, 0, 'Archive dirs free space', curses.A_REVERSE)
        #archwin.addstr(1, 0, arch_report)

        # Log.  Could use a pad here instead of managing scrolling ourselves, but
        # this seems easier.
        log_win.addnstr(
            0, 0,
            ('Log: %d (<up>/<down>/<end> to scroll)\n' % log.get_cur_pos()),
            linecap, curses.A_REVERSE)
        for i, logline in enumerate(log.cur_slice(logscreen_height - 1)):
            log_win.addnstr(i + 1, 0, logline, linecap)

        stdscr.noutrefresh()
        header_win.noutrefresh()
        jobs_win.noutrefresh()
        tmpwin_1.noutrefresh()
        tmpwin_2.noutrefresh()
        dstwin.noutrefresh()
        #archwin.noutrefresh()
        log_win.noutrefresh()
        curses.doupdate()

        key = stdscr.getch()
        if key == curses.KEY_UP:
            log.shift_slice(-1)
            pressed_key = 'up'
        elif key == curses.KEY_DOWN:
            log.shift_slice(1)
            pressed_key = 'dwn'
        elif key == curses.KEY_END:
            log.shift_slice_to_end()
            pressed_key = 'end'
        elif key == ord('p'):
            plotting_active = not plotting_active
            pressed_key = 'p'
        elif key == ord('a'):
            archiving_active = not archiving_active
            pressed_key = 'a'
        elif key == ord('q'):
            break
        else:
            pressed_key = key
Exemple #2
0
    pm_parser = PlotmanArgParser()
    args = pm_parser.parse_args()

    with open('config.yaml', 'r') as ymlfile:
        cfg = yaml.load(ymlfile, Loader=yaml.FullLoader)
    dir_cfg = cfg['directories']
    sched_cfg = cfg['scheduling']
    plotting_cfg = cfg['plotting']

    #
    # Stay alive, spawning plot jobs
    #
    if args.cmd == 'plot':
        print('...starting plot loop')
        while True:
            wait_reason = manager.maybe_start_new_plot(dir_cfg, sched_cfg,
                                                       plotting_cfg)

            # TODO: report this via a channel that can be polled on demand, so we don't spam the console
            sleep_s = int(sched_cfg['polling_time_s'])
            if wait_reason:
                print('...sleeping %d s: %s' % (sleep_s, wait_reason))

            time.sleep(sleep_s)

    #
    # Analysis of completed jobs
    #
    elif args.cmd == 'analyze':
        analyzer = analyzer.LogAnalyzer()
        analyzer.analyze(args.logfile)