Exemple #1
0
class Minotaur:
    def __init__(self):
        self.data_path = os.getenv("HOME") + "/.minotaur"
        if not os.path.exists(self.data_path):
            os.mkdir(self.data_path)

        self.power_file = self.data_path + "/power.dat"
        self.profitability_file = self.data_path + "/profitability.dat"
        self.currency_rate_file = self.data_path + "/currencies.dat"

        self.startup_time = time.time()
        Config().load()

    def already_running(self):
        if not os.path.exists("/var/run/minotaur"):
            try:
                ok.mkdir("/var/run/minotaur", 0755)
            except:
                Log().add('fatal', 'unable to create /var/run/minotaur')

        if os.path.exists("/var/run/minotaur/minotaur.pid"):
            pid = open("/var/run/minotaur/minotaur.pid").read().rstrip()

            return pid.isdigit() and os.path.exists("/proc/%s" % (pid))

    def create_pid_file(self):
        if self.already_running():
            Log().add(
                'fatal',
                'unable to start, there is another minotaur process running')

        with open("/var/run/minotaur/minotaur.pid", "w") as f:
            f.write("%d" % (os.getpid()))

    def banner(self):
        version = Version().get()

        line = "-" * (20 + len(version))

        print
        print "  /%s/" % (line)
        print " / minotaur %s by m4rkw /" % (version)
        print "/%s/" % (line)
        print

    def usage(self):
        self.banner()
        print "usage: %s [command] [options]" % (sys.argv[0])
        print
        print "## quickstart"
        print
        print " %s --quickstart" % (sys.argv[0])
        print
        print "quickstart mode will calibrate the 5 most profitable Nicehash algorithms"
        print "in fast mode (no power tuning) and then start mining. See the README for"
        print "more information."
        print
        print "## calibration"
        print
        print " %s --calibrate <device id / name / class | all> <pool (nicehash/ethermine/all)> <miner> <algorithms | all> [region (eu/usa)] [--quick] [--overwrite] [--force]" % (
            sys.argv[0])
        print
        print "     --quick : skip power calibration and just do the initial run at default power"
        print " --overwrite : replace existing calibration data"
        print "     --force : take-over a device from minotaur and use it for calibration"
        print
        print "## mining"
        print
        print " %s --mine" % (sys.argv[0])
        print
        print "## misc"
        print
        print " %s --devices                                         # list devices" % (
            sys.argv[0])
        print " %s --algos                                           # list algorithms and calibrated hashrates" % (
            sys.argv[0])
        print " %s --cleanup                                         # stop workers (not calibration runs)" % (
            sys.argv[0])
        print " %s --cleanup <device id>                             # stop workers for device" % (
            sys.argv[0])
        print " %s --cleanup-all                                     # stop all workers (including calibration runs)" % (
            sys.argv[0])
        print " %s --pin <device id> <pool> <miner> <algo> [region]  # pin a device to a specific miner+algorithm" % (
            sys.argv[0])
        print " %s --pin <device id> idle                            # pin a device to idle (ie don't use)" % (
            sys.argv[0])
        print " %s --unpin <device id>                               # unpin a device from a specific miner+algorithm" % (
            sys.argv[0])
        print " %s --stats [hours]                                   # show stats (default last 24 hours)" % (
            sys.argv[0])
        print " %s --help                                            # display this help page" % (
            sys.argv[0])
        print
        sys.exit()

    def initialise(self):
        self.banner()

        Log().add("info", "initialising")

        self.check_for_update()

        Pools()
        Miners()

        Log().add("info", "scanning devices")

        self.scan_devices()

    def check_for_update(self):
        try:
            opener = urllib2.build_opener(NoRedirection,
                                          urllib2.HTTPCookieProcessor())

            resp = opener.open(
                "https://github.com/m4rkw/minotaur/releases/latest")

            latest_version = resp.headers['Location'].split('/')[-1][1:]

            if self.is_newer(latest_version, Version().get()):
                Log().add('info',
                          'new version available: v%s' % (latest_version))
        except:
            Log().add('warning', 'failed to check for updates')
            pass

    def is_newer(self, latest_version, current_version):
        latest_version = latest_version.replace('v', '').split(".")
        current_version = current_version.replace('v', '').split(".")

        if int(latest_version[0]) > int(current_version[0]):
            return True
        elif int(latest_version[0]) < int(current_version[0]):
            return False

        if int(latest_version[1]) > int(current_version[1]):
            return True
        elif int(latest_version[1]) < int(current_version[1]):
            return False

        if len(latest_version) > 2 and len(current_version) < 3:
            return True

        if len(latest_version) > 2 and len(current_version) < 3:
            return True

        if len(latest_version) > 2 and len(current_version) > 2:
            return int(latest_version[2]) > int(current_version[2])

        return False

    def sigint_handler(self, a, b):
        Log().add('info', 'interrupt received, cleaning up')

        try:
            Miners().cleanup_workers()
        except:
            pass

        sys.exit(0)

    def cleanup(self, all=0, device_id=None):
        if all:
            Miners().cleanup_workers(True)
        elif not all and not device_id:
            Miners().cleanup_workers(False)
        else:
            device = Device({"id": device_id})

            Nvidia().set_default_profile(device)
            Miners().stop_device(device)

        sys.exit(0)

    def scan_devices(self):
        self.devices = Nvidia().get_nvidia_devices()

        Calibration().load()

        if len(self.devices) < 1:
            Log().add('fatal', "no nvidia GPUs found")

        Log().add("info", "found %d nvidia GPUs" % (len(self.devices)))

        Calibration().check_calibrated_algorithms(self.devices)

        Log().add('info', 'retrieving state from miner backends')

        Miners().poll()

        for device in self.devices:
            device.update()

            if device.state == 'active':
                device.log(
                    'info',
                    'currently running algorithm %s with %s [profile=%s] [region=%s]'
                    % (device.algos[0]['algo'], device.algos[0]['miner'],
                       device.profile, device.algos[0]['region']))
                device.apply_profile()
            elif device.state == 'calibrating':
                device.log(
                    'info',
                    'calibration in progress with algorithm %s using %s [region=%s]'
                    % (device.algos[0]['algo'], device.algos[0]['miner'],
                       device.algos[0]['region']))

            device.period_start = self.startup_time
            device.grubtime = device.period_start + (
                60 * random.randrange(15, 1424))

    def run(self):
        self.create_pid_file()
        self.initialise()

        signal.signal(signal.SIGINT, self.sigint_handler)
        signal.signal(signal.SIGTERM, self.sigint_handler)
        signal.signal(signal.SIGHUP, self.sighup_handler)

        power_draw_readings = self.load_power_draw_readings()
        profitability_readings = self.load_profitability_readings()

        main_loop_index = 0

        self.miner_state = {}
        self.pool_refresh = None

        while True:
            if main_loop_index == 0:

                if self.pool_refresh == None or (
                        time.time() - self.pool_refresh
                ) >= Config().get('pool_refresh_interval'):
                    Pools().refresh()
                    self.pool_refresh = time.time()

                show_mbtc_total = False

                Miners().poll()

                for i in range(0, len(self.devices)):
                    device = self.devices[i]

                    device.update()
                    device.log_stats()

                    if not device.can_run():
                        continue

                    #device.grubtime = self.startup_time += (60 * random.randrange(15, 1424))


#          if device.grub == False and time.time() >= device.grubtime and time.time() <= (device.grubtime + (60 * 15)):
#            device.log('info', 'starting 15min donation period to the author')
#            device.grub = True
#
#            Miners().schedule('restart', device)
#          elif device.grub == True and time.time() < device.grubtime or time.time() > (device.grubtime + (60 * 15)):
#            device.log('info', 'stopping donation period')
#            device.grub = False
#            device.period_start += 86400
#            device.grubtime = device.period_start + (60 * random.randrange(15, 1424))
#
#            while not self.grubtime_is_unique(device.grubtime, i):
#              device.grubtime = device.period_start + (60 * random.randrange(15, 1424))
#
#            Miners().schedule('restart', device)
#          else:
                    if True:
                        if not device.running():
                            if Config().get('debug'):
                                device.log(
                                    'debug',
                                    'device not running - starting worker')

                            Miners().schedule('start', device)
                        else:
                            switched = False

                            if not device.pin:
                                if Pools().should_switch(device):
                                    Miners().schedule('restart', device)
                                    switched = True

                            if not switched and Config(
                            ).get('calibration.update_calibration_data_over_time'
                                  ):
                                Calibration().handle_device_update(device)

                queued_device_ids = Miners().queue.keys()
                Miners().execute_queue()

                for device in self.devices:
                    if device.state == 'active' and device.id not in queued_device_ids and not device.warming_up(
                    ):
                        show_mbtc_total = True

                        device.log(
                            'info', '%s/%s[%s]: %s' %
                            (device.algos[0]['pool'], device.algos[0]['algo'],
                             device.algos[0]['miner'], device.hashrate_str()))
                        device.check_hashrate()

                total_power, total_power_limit, total_mbtc_per_day = Nvidia(
                ).get_device_metrics(self.devices)

                if show_mbtc_total:
                    Log().add(
                        'info', 'total profitability: %.2f mBTC/day' %
                        (total_mbtc_per_day))

                power_draw_readings = self.update_power_draw_readings(
                    power_draw_readings,
                    total_power + Config().get('system_draw_watts'))
                profitability_readings = self.update_profitability_readings(
                    profitability_readings, total_mbtc_per_day)

                # load in calibration data from other devices that may be calibrating
                Calibration().load()

            Miners().wait_for_queue()

            time.sleep(1)

            main_loop_index += 1

            if main_loop_index >= Config().get('refresh_interval'):
                main_loop_index = 0

    def grubtime_is_unique(self, grubtime, i):
        _from = grubtime
        _to = grubtime + (15 * 60)

        for j in range(0, len(self.devices)):
            if i != j:
                _from2 = self.devices[j].grubtime
                _to2 = _from2 + (15 * 60)

                for x in range(_from2, _to2):
                    if x in range(_from, _to):
                        return False

        return True

    def load_power_draw_readings(self):
        if os.path.exists(
                self.power_file) and os.path.getsize(self.power_file) > 0:
            return pickle.loads(open(self.power_file).read())

        return []

    def load_profitability_readings(self):
        if os.path.exists(self.profitability_file) and os.path.getsize(
                self.profitability_file) > 0:
            return pickle.loads(open(self.profitability_file).read())

        return []

    def trim_readings(self, readings, max_time):
        new_readings = []

        for reading in readings:
            if int(time.time()) - reading["timestamp"] <= max_time:
                new_readings.append(reading)

        return new_readings

    def update_power_draw_readings(self, power_draw_readings, total_power):
        power_draw_readings.append({
            "timestamp": int(time.time()),
            "reading": total_power
        })
        power_draw_readings = self.trim_readings(
            power_draw_readings,
            max(Config().get('live_data')['power_draw_averages']))

        with open(self.power_file + ".new", "w") as f:
            f.write(pickle.dumps(power_draw_readings))
        os.rename(self.power_file + ".new", self.power_file)

        return power_draw_readings

    def update_profitability_readings(self, profitability_readings,
                                      total_mbtc_per_day):
        profitability_readings.append({
            "timestamp": int(time.time()),
            "reading": total_mbtc_per_day
        })
        profitability_readings = self.trim_readings(
            profitability_readings,
            max(Config().get('live_data')['profitability_averages']))

        with open(self.profitability_file + ".new", "w") as f:
            f.write(pickle.dumps(profitability_readings))
        os.rename(self.profitability_file + ".new", self.profitability_file)

        return profitability_readings

    def sighup_handler(self, x, y):
        Log().add('info', 'SIGHUP caught, reloading config and benchmark data')

        Config().reload()
        Calibration().load()
        Miners().reload_config()

        for device in Nvidia().get_nvidia_devices():
            if not self.device_in_list(device):
                device.update()

                if device.state == 'active':
                    device.log(
                        'info',
                        'currently running algorithm %s with %s [profile=%s] [region=%s]'
                        % (device.algos[0]['algo'], device.algos[0]['miner'],
                           device.profile, device.algos[0]['region']))
                elif device.state == 'calibrating':
                    device.log(
                        'info',
                        'calibration in progress with algorithm %s using %s [region=%s]'
                        % (device.algos[0]['algo'], device.algos[0]['miner'],
                           device.algos[0]['region']))

                self.devices.append(device)

        for device in self.devices:
            if device.state == 'active':
                device.apply_profile()

        Log().add('info', 'reload complete')

    def device_in_list(self, device):
        for l_device in self.devices:
            if l_device.id == device.id:
                return True

        return False

    def calibration_banner(self):
        print "-" * 84
        Log().add(
            'info',
            'PLEASE NOTE: this process will not automatically overclock your card unless'
        )
        Log().add(
            'info',
            'you have enabled overclocking and configured a device profile with overclock'
        )
        Log().add(
            'info',
            'settings. You and you alone are responsible for any overclock settings that'
        )
        Log().add(
            'info',
            'you want to run this benchmark process with. We do not advise overclocking'
        )
        Log().add(
            'info',
            'and are not responsible for any hardware damage that may occur when using'
        )
        Log().add('info', 'this tool.')
        print "-" * 84

    def calibrate(self, device_params, pool, miner_name, algorithm, region,
                  quick, overwrite, force):
        devices = Nvidia().get_nvidia_devices(1)

        if pool == 'nicehash' and region not in [
                'eu', 'usa', 'hk', 'jp', 'in', 'br'
        ]:
            Log().add('fatal', 'a valid region is required for nicehash')

        devices_to_calibrate = []
        device_classes = []

        for device_param in device_params.split(','):
            if device_param.isdigit():
                if int(device_param) >= len(devices):
                    Log().add('fatal',
                              'device %d not found' % (int(device_param)))
                else:
                    devices_to_calibrate.append(devices[int(device_param)])
            else:
                found = False
                for device in devices:
                    if device.name == device_param:
                        devices_to_calibrate.append(device)
                        found = True
                    elif (device_param == 'all'
                          or device.dclass == device_param
                          ) and device.dclass not in device_classes:
                        devices_to_calibrate.append(device)
                        device_classes.append(device.dclass)
                        found = True

                if not found:
                    Log().add('fatal', 'device %s not found' % (device_param))

        log_dir = Config().get('logging.calibration_log_dir')

        if not log_dir:
            log_dir = "/var/log/minotaur"

        if miner_name == "all":
            miners = []

            for miner_name in Config().get('miners').keys():
                if Config().get('miners')[miner_name]['enable']:
                    miners.append(eval("%s()" % (miner_name.title())))
        else:
            if not miner_name in Config().get('miners').keys():
                Log().add('fatal', 'miner %s is not configured' % (miner_name))

            miners = [eval("%s()" % (miner_name.title()))]

        if len(miners) == 0:
            Log().add('fatal', "no miners available")

        if pool == 'all':
            pools = []
            for pool_name in Config().get('pools').keys():
                if Config().get('pools.%s.enable' % (pool_name)):
                    pools.append(pool_name)
        elif pool not in Config().get('pools').keys():
            Log().add('fatal', 'unknown pool: %s' % (pool))
        else:
            pools = [pool]

        algorithms = {}

        for pool_name in pools:
            algorithms[pool_name] = {}

            for miner in miners:
                if not pool_name in Pools().pools.keys():
                    Log().add('fatal', 'pool %s is not enabled' % (pool_name))

                pool = Pools().pools[pool_name]

                if miner.name not in pool.supported_miners:
                    continue

                if algorithm == "all":
                    algorithms[pool_name][
                        miner.name] = miner.supported_algorithms()
                else:
                    algorithms[pool_name][miner.name] = []

                    for algo_param in algorithm.split(","):
                        if algo_param == 'all':
                            algorithms[pool_name][
                                miner.name] = miner.supported_algorithms()
                        else:
                            if algo_param[0] == '!':
                                exclude_algo = algo_param[1:]

                                if miner.name in algorithms.keys(
                                ) and exclude_algo in algorithms[miner.name]:
                                    algorithms[pool_name][miner.name].remove(
                                        exclude_algo)
                            else:
                                if algo_param in miner.supported_algorithms():
                                    algorithms[pool_name][miner.name].append(
                                        algo_param)

        print ""
        self.calibration_banner()
        print ""

        n = 0

        for device in devices_to_calibrate:
            log_file = "%s/calibration_%d.log" % (log_dir, device.id)
            Log().set_log_file(log_file)

            for pool_name in algorithms.keys():
                for miner in miners:
                    if miner.name in algorithms[pool_name].keys():
                        for algorithm in algorithms[pool_name][miner.name]:
                            n += 1

                            if algorithm in Config(
                            ).get('algorithms.single') or algorithm in Config(
                            ).get('algorithms.double'):
                                Calibration().load()

                                if not overwrite and Calibration().get(
                                        '%s.%s.%s' %
                                    (device.dclass, miner.name, algorithm)):
                                    device.log(
                                        'info',
                                        'not overwriting existing calibration data for miner %s algorithm %s (use --overwrite to override)'
                                        % (miner.name, algorithm))
                                else:
                                    Calibrate().start(device, pool_name, miner,
                                                      algorithm, region, quick,
                                                      force)
                            else:
                                Log().add(
                                    'warning',
                                    'algorithm %s is not in the config file - skipping'
                                    % (algorithm))

        Log().add('info', 'nothing to do.')

    def print_devices(self):
        for device in Nvidia().get_nvidia_devices(1):
            print "%d: %s [class: %s]" % (device.id, device.name,
                                          device.dclass)

        sys.exit()

    def print_algorithms(self):
        algorithms = Config().get('algorithms.single') + Config().get(
            'algorithms.double')

        Calibration().load()

        device_classes = Nvidia().get_nvidia_device_classes()

        for device_class in device_classes:
            data = {}

            for algorithm in algorithms:
                data[algorithm] = {}

                for miner_name in Config().get('miners'):
                    miner = eval('%s()' % (miner_name.title()))

                    if algorithm in miner.supported_algorithms():
                        data[algorithm][miner_name] = Calibration(
                        ).get_miner_hashrate_for_algorithm_on_device(
                            miner_name, algorithm, device_class)
                    else:
                        data[algorithm][miner_name] = "-"

            self.display_device_algorithm_table(device_class, data)

        print "\nnote: - means not supported\n"

    def display_device_algorithm_table(self, device_class, data):
        print "\n"

        algo_width = self.get_width(data.keys()) + 2
        total_width = algo_width

        sys.stdout.write(device_class.ljust(algo_width))

        miner_widths = {}

        for miner_name in sorted(Config().get('miners').keys()):
            miner_widths[miner_name] = self.get_miner_width(data,
                                                            miner_name) + 2
            total_width += miner_widths[miner_name] + 2

            sys.stdout.write(miner_name.rjust(miner_widths[miner_name]))

        sys.stdout.write("\n")
        sys.stdout.write("-" * total_width)
        sys.stdout.write("\n")
        sys.stdout.flush()

        for algorithm in sorted(data.keys()):
            sys.stdout.write(algorithm.ljust(algo_width))

            for miner_name in sorted(data[algorithm].keys()):
                sys.stdout.write(data[algorithm][miner_name].rjust(
                    miner_widths[miner_name]))

            sys.stdout.write("\n")

    def get_width(self, values):
        width = 0

        for s in values:
            if len(s) > width:
                width = len(s)

        return width

    def get_miner_width(self, data, miner_name):
        width = len(miner_name)

        for a in data.keys():
            if len(data[a][miner_name]) > width:
                width = len(data[a][miner_name])

        return width

    def get_miners_for_algorithm(self, algorithm):
        miners = []

        for miner_name in Config().get('miners'):
            if Config().get('miners')[miner_name]['enable']:
                miner = eval('%s()' % (miner_name.title()))

                if algorithm in miner.supported_algorithms():
                    miners.append(miner_name)

        return miners

    def pin(self, device_id, pool_name, pin_miner_name, algorithm, region):
        device_ids = []

        if device_id == 'all':
            for device in Nvidia().get_nvidia_devices():
                device_ids.append(device.id)
        else:
            for device_id in device_id.split(","):
                device_ids.append(int(device_id))

        if len(device_ids) == 0:
            Log().add('fatal', 'no devices selected')

        for device_id in device_ids:
            device = Device({"id": int(device_id)})

            if pool_name not in Config().get('pools').keys():
                Log().add('fatal', 'unknown pool')

            if not algorithm in Config().get(
                    'algorithms.single') and not algorithm in Config().get(
                        'algorithms.double'):
                Log().add('fatal', 'unknown algorithm')

            if pool_name != 'nicehash':
                region = None
            else:
                if region not in ['eu', 'usa', 'hk', 'jp', 'in', 'br']:
                    Log().add('fatal', 'valid region is required for nicehash')

            Miners().poll()
            Miners().get_device_state(device)

            if device.state == "calibrating":
                Log().add(
                    'fatal', 'not pinning device %d - currently calibrating' %
                    (device.id))

            pin = {
                "pool_name": pool_name,
                "miner_name": pin_miner_name,
                "algorithm": algorithm,
                "region": region
            }

            with open("/var/run/minotaur/pin%d" % (device.id), "w") as f:
                f.write(yaml.dump(pin))

            Log().add(
                'info',
                'pinned device %d to miner: %s pool %s algorithm: %s region: %s'
                % (device.id, pin_miner_name, pool_name, algorithm, region))

    def pin_idle(self, device_id):
        pin = {"idle": True}

        device_ids = []

        if device_id == 'all':
            for device in Nvidia().get_nvidia_devices():
                device_ids.append(device.id)
        else:
            for device_id in device_id.split(","):
                device_ids.append(int(device_id))

        if len(device_ids) == 0:
            Log().add('fatal', 'no devices selected')

        for device_id in device_ids:
            device = Device({"id": int(device_id)})

            with open("/var/run/minotaur/pin%d" % (device.id), "w") as f:
                f.write(yaml.dump(pin))

            Log().add('info', 'pinned device %d to idle' % (device.id))

    def pin_calibration(self, device_id):
        pin = {"calibration": True}

        device_ids = []

        if device_id == 'all':
            for device in Nvidia().get_nvidia_devices():
                device_ids.append(device.id)
        else:
            for device_id in device_id.split(","):
                device_ids.append(int(device_id))

        if len(device_ids) == 0:
            Log().add('fatal', 'no devices selected')

        for device_id in device_ids:
            device = Device({"id": int(device_id)})

            with open("/var/run/minotaur/pin%d" % (device.id), "w") as f:
                f.write(yaml.dump(pin))

            Log().add('info', 'pinned device %d to calibration' % (device.id))

    def unpin(self, device_id):
        device_ids = []

        if device_id == 'all':
            for device in Nvidia().get_nvidia_devices():
                device_ids.append(device.id)
        else:
            for device_id in device_id.split(","):
                device_ids.append(int(device_id))

        if len(device_ids) == 0:
            Log().add('fatal', 'no devices selected')

        for device_id in device_ids:
            device_id = int(device_id)

            if os.path.exists("/var/run/minotaur/pin%d" % (device_id)):
                os.remove("/var/run/minotaur/pin%d" % (device_id))
                Log().add('info', 'unpinned device %d' % (device_id))
            else:
                Log().add('info', 'device %d is not pinned' % (device_id))

    def device_pinned(self, device_id):
        if os.path.exists("/var/run/minotaur/pin%d" % (device_id)):
            return yaml.load(
                open("/var/run/minotaur/pin%d" % (device_id)).read())

        return False

    def stats(self, hours=24):
        if hours == 1:
            suffix = ''
        else:
            suffix = 's'

        print "\nstats for the last %d hour%s:\n" % (hours, suffix)

        stats_file = "/var/log/minotaur/minotaur.csv"

        if not os.path.exists(stats_file):
            print "%s not found" % (stats_file)
            sys.exit(1)
        else:
            total = 0
            algos = {}
            devices = {}

            logs = Stats().get_logs_for_period(stats_file, hours)
            width = 0

            for item in logs:
                if len(item['algorithm']) > width:
                    width = len(item['algorithm'])

                if item['device_id'] not in devices.keys():
                    devices[item['device_id']] = item
                else:
                    time_on_algo = (
                        parser.parse(item['timestamp']) - parser.parse(
                            devices[item['device_id']]['timestamp'])).seconds
                    if devices[item['device_id']]['net_mbtc'] != '':
                        net = float(devices[item['device_id']]['net_mbtc'])

                    earning = (net / 86400) * time_on_algo

                    if item['algorithm'] not in algos.keys():
                        algos[item['algorithm']] = 0

                    total += earning
                    algos[item['algorithm']] += earning

                    devices[item['device_id']] = item

            print " from: %s" % (logs[0]['timestamp'])
            print "   to: %s\n" % (logs[len(logs) - 1]['timestamp'])

            total_s = "%.2f" % (total)

            print "total: %s mBTC" % (total_s.rjust(7))

            if os.path.exists(self.currency_rate_file):
                exchange_rate = pickle.loads(
                    open(self.currency_rate_file).read())

                fiat = (total / 1000) * float(exchange_rate['rate'])
                fiat_s = "%.2f" % (fiat)

                print "       %s  %s" % (fiat_s.rjust(7),
                                         Config().get('electricity_currency'))

            total_seconds = hours * 3600
            rate = ((total / total_seconds) * 86400)

            rate_s = "%.2f" % (rate)

            print "\n rate: %s mBTC/day" % (rate_s.rjust(7))

            if os.path.exists(self.currency_rate_file):
                exchange_rate = pickle.loads(
                    open(self.currency_rate_file).read())

                fiat = (rate / 1000) * float(exchange_rate['rate'])

                day_s = "%.2f" % (fiat)
                month_s = "%.2f" % (fiat * 30)

                print "       %s  %s/day" % (
                    day_s.rjust(7), Config().get('electricity_currency'))
                print "       %s  %s/month" % (
                    month_s.rjust(7), Config().get('electricity_currency'))

            print "\nincome by algorithm:\n"

            for w in sorted(algos, key=algos.get, reverse=True):
                if os.path.exists(self.currency_rate_file):
                    exchange_rate = pickle.loads(
                        open(self.currency_rate_file).read())

                    fiat = (algos[w] / 1000) * float(exchange_rate['rate'])

                    suffix = "  %.2f %s" % (
                        fiat, Config().get('electricity_currency'))
                else:
                    suffix = ""

                print "%s: %.4f mBTC%s" % (w.rjust(width), algos[w], suffix)

            print ""

            sys.exit()

    def get_latest_version(self):
        opener = urllib2.build_opener(NoRedirection,
                                      urllib2.HTTPCookieProcessor())
        resp = opener.open("https://github.com/m4rkw/minotaur/releases/latest")
        return resp.headers['Location'].split('/')[-1][1:]

    def upgrade(self):
        print "checking for update"

        latest_version = self.get_latest_version()
        current_version = Version().get
        current_version = '0.8.9'

        if self.is_newer(latest_version, current_version):
            print 'new version available: v%s' % (latest_version)

            sys.stdout.write('upgrade? (y/n) ')
            line = sys.stdin.readline()
            if line[0].lower() == 'y':
                self.do_upgrade()
        else:
            print 'you are already running the latest version.'

    def do_upgrade(self):
        if os.getuid() != 0:
            print 'elevating with sudo, you may need to enter your password below..'
            os.system("sudo %s --do-upgrade" % (sys.argv[0]))
            sys.exit(0)
        else:
            latest_version = self.get_latest_version()
            print "downloading minotaur-%s_centos7.tar.gz" % (latest_version)
            os.system(
                "curl -L -s -o /tmp/minotaur.tar.gz https://github.com/m4rkw/minotaur/releases/download/v%s/minotaur-%s_centos7.tar.gz"
                % (latest_version, latest_version))
            os.system("tar -C /tmp -zxf /tmp/minotaur.tar.gz")

            print "installing minotaur"
            copyfile("/tmp/minotaur-%s/minotaur" % (latest_version),
                     sys.argv[0])

            print "cleaning up"
            os.system("rm -rf /tmp/minotaur-%s /tmp/minotaur.tar.gz" %
                      (latest_version))

            print "done!"

        sys.exit(0)