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 sigint_handler(self, a, b): Log().add('info', 'interrupt received, cleaning up') Miners().poll() Miners().get_device_state(self.device) if self.device.state == 'calibrating': self.device.stop(True) self.unset_pin(self.device.id) sys.exit(0)
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 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 check_calibrated_algorithms(self, devices): device_classes = [] for device in devices: if not device.dclass in device_classes: device_classes.append(device.dclass) total_algorithms = Miners().get_miner_algorithm_count() * len(device_classes) calibrated_algorithms = 0 classes = [] for device in devices: calibration = device.get_calibrated_algorithms() for miner_name in calibration.keys(): calibrated_algorithms += len(calibration[miner_name]) if not device.dclass in classes or len(calibration) == 0: self.display_calibrated_algorithms_for_device(device, calibration) classes.append(device.dclass) else: device.log('info', ' as above') if calibrated_algorithms == 0: Log().add('fatal', 'you have no calibration data, unable to mine. please see --calibrate') if calibrated_algorithms >= total_algorithms: Log().add('info', 'you have calibration data for all supported algorithms :)') else: Log().add('warning', 'you only have calibration data for %d/%d supported algorithm/device combinations' % (calibrated_algorithms, total_algorithms))
def sigint_handler(self, a, b): Log().add('info', 'interrupt received, cleaning up') try: Miners().cleanup_workers() except: pass sys.exit(0)
def get_best_miner_for_algorithm(self, algorithm, supported_miners=[]): calibration_data = Calibration().get_hashrates(self) best_hashrate = 0 best_miner = None if calibration_data: for miner_name in calibration_data.keys(): if algorithm in calibration_data[miner_name].keys( ) and calibration_data[miner_name][algorithm][ "hashrate"] > best_hashrate: if Miners().enabled(miner_name) and Miners().is_up( miner_name): if supported_miners == [] or miner_name in supported_miners: best_hashrate = calibration_data[miner_name][ algorithm]["hashrate"] best_miner = miner_name return best_miner
def initialise(self): self.banner() Log().add("info", "initialising") self.check_for_update() Pools() Miners() Log().add("info", "scanning devices") self.scan_devices()
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 start(self, silent=False): return Miners().start_device(self, silent)
def stop(self, silent=False): return Miners().stop_device(self, silent)
def update(self): self.update_bests() if self.ignored(): if self.state != 'ignored': self.log('info', 'added to ignore list') if self.state == 'active': self.stop() self.state = 'ignored' self.changed = datetime.datetime.now() return else: if self.state == 'ignored': self.log('info', 'removed from ignore list') previous_state = self.state Miners().get_device_state(self) if self.state == 'active': profile = self.get_profile_for_algo(self.algos[0]['algo']) self.profile = profile.name if self.state == 'calibrating' and previous_state != 'calibrating': self.log('info', 'being used for calibration - ignoring') return if previous_state == 'calibrating' and self.state != 'calibrating': self.log('info', 'no longer being used for calibration') return pin = self.get_pin() if pin: if "idle" in pin.keys(): if not self.pin or not "idle" in self.pin.keys(): self.log('info', 'pinned to IDLE') self.pin = pin if self.state == 'active': Miners().schedule('stop', self) elif "calibration" in pin.keys(): if not self.pin or not "calibration" in self.pin.keys(): if self.state == 'active': Miners().schedule('stop', self) self.log('info', 'being used for calibration - ignoring') self.pin = pin elif not self.pin or 'idle' in self.pin.keys( ) or 'calibration' in self.pin.keys( ) or self.pin['pool_name'] != pin['pool_name'] or self.pin[ 'algorithm'] != pin['algorithm'] or self.pin[ 'region'] != pin['region'] or self.pin[ 'miner_name'] != pin['miner_name']: self.log( 'info', 'pinned to miner=%s pool=%s algorithm=%s region=%s' % (pin['miner_name'], pin['pool_name'], pin['algorithm'], pin['region'])) self.pin = pin if self.state == 'active' and ( self.algos[0]['pool'] != pin['pool_name'] or self.algos[0]['algo'] != pin['algorithm'] or self.algos[0]['region'] != pin['region'] or self.algos[0]['miner'] != pin['miner_name']): Miners().schedule('restart', self) else: if self.pin: self.log('info', 'unpinned') self.pin = False if self.state == 'active': Miners().schedule('restart', self) elif self.state == 'inactive': Miners().schedule('start', self)
def start(self, device, pool_name, miner, algorithm, region, quick=False, force=False): self.miner = miner self.device = device self.default_profile = Config().get('device_profiles.default') Miners().reload_config() device.pin = { 'pool_name': pool_name, 'miner_name': miner.name, 'algorithm': algorithm, 'region': region } if not Config().get('calibration'): Log().add('fatal', "calibration config missing from config file") for key in [ "hashrate_stabilisation_timeout_mins", "hashrate_stabilisation_tolerance", "hashrate_stabilisation_consecutive_readings_required", "algorithm_start_timeout" ]: if Config().get('calibration.%s' % (key)) == None: Log().add('fatal', "missing config option: calibration.%s" % (key)) if Config().get('calibration.power_tuning.enable'): for key in ["decrement_watts", "acceptable_loss_percent"]: if not Config().get('calibration.power_tuning.%s' % (key)): Log().add( 'fatal', "missing config option: calibration.power_tuning.%s" % (key)) Config().set('pools.%s.append_device_id_to_worker_name' % (pool_name), False) if pool_name == 'miningpoolhub': if Config().get('pools.miningpoolhub.hub_workers.%s' % (algorithm))[-5:] != 'CALIB': Config().set( 'pools.miningpoolhub.hub_workers.%s' % (algorithm), Config().get('pools.miningpoolhub.hub_workers.%s' % (algorithm)) + 'CALIB') else: if Config().get('pools.%s.worker_name' % (pool_name))[-5:] != 'CALIB': Config().set( 'pools.%s.worker_name' % (pool_name), Config().get('pools.%s.worker_name' % (pool_name)) + "CALIB") if quick: Config().set('calibration.power_tuning.enable', False) profile = Profiles().get_for_device_algo(device, algorithm) Log().add('info', ' device id: %d' % (device.id)) Log().add('info', ' pool: %s' % (pool_name)) Log().add('info', ' device class: %s' % (device.dclass)) Log().add('info', 'device profile: %s' % (profile.name)) Log().add('info', ' miner: %s' % (miner.name)) Log().add('info', ' algorithm: %s' % (algorithm)) Log().add('info', ' region: %s' % (region)) Miners().poll() Miners().get_device_state(device) if device.state != 'inactive': if device.state == 'calibrating': device.log( 'fatal', 'this device is already being used for calibration') if force: device.log('info', 'terminating existing worker') if not device.stop(True): device.log('fatal', 'failed to stop existing worker') else: device.log( 'fatal', 'unable to start calibration - device is in use (use --force to override)' ) self.set_pin(device.id) default_power_limit = device.default_power_limit_f min_power_limit = device.min_power_limit_f max_power_limit = device.max_power_limit_f if max_power_limit == 0: device.log('info', 'device does not support power monitoring') else: device.log('info', 'max power limit: %d W' % (max_power_limit)) device.log( 'info', 'stabilisation tolerance: %.2f%%' % (Config().get('calibration.hashrate_stabilisation_tolerance'))) if Config().get('debug'): device.log('debug', 'loading default profile') Nvidia().set_default_profile(device) if Config().get('debug'): device.log('debug', 'initialising pools') Pools() sample_timeout = Config().get( 'calibration.hashrate_stabilisation_timeout_mins') if default_power_limit != None and Config().get( 'calibration.power_tuning.enable'): device.log( 'info', 'starting initial run at max power limit [timeout=%dmins]' % (sample_timeout)) else: device.log( 'info', 'starting single run [timeout=%dmins]' % (sample_timeout)) Miners().poll() Miners().get_device_state(device) if device.state != 'calibrating': if not device.start(True): device.log('fatal', 'worker failed to start') if Config().get('calibration.power_tuning.enable'): if not device.power_supported(): device.log('info', 'device does not support power monitoring') else: device.set_power_limit(max_power_limit) initial_hashrate, initial_max_power_draw = self.get_max_hashrate_and_power_draw( miner, device, sample_timeout * 60) if initial_hashrate == None: device.log( 'info', 'skipping algorithm as we failed to get a stable reading') print "" return if initial_max_power_draw != None and initial_max_power_draw > max_power_limit: initial_max_power_draw = max_power_limit device.log( 'info', 'benchmark hashrate: %s' % (Units().hashrate_str(initial_hashrate))) if initial_max_power_draw != None: device.log('info', 'max power draw: %.2f' % (initial_max_power_draw)) initial_power_limit = int(math.ceil(initial_max_power_draw)) hashrate = initial_hashrate if initial_max_power_draw == None: power_limit = None elif Config().get('calibration.power_tuning.enable'): if max_power_limit == 0 or min_power_limit == 0 or default_power_limit == 0: device.log( 'error', 'device did not give us sane values for its min/max/default power limits' ) device.log( 'error', 'unable to proceed with power calibration - device may not support changing the power limit' ) power_limit = default_power_limit else: power_limit = initial_power_limit device.log('info', 'tuning power limit for optimum efficiency') hashrate, power_limit = self.do_power_calibration( device, power_limit, miner, hashrate, min_power_limit) else: power_limit = default_power_limit Nvidia().set_default_profile(device) self.unset_pin(device.id) device.log('info', 'storing calibration data') Calibration().update_calibration_data(device.dclass, miner.name, algorithm, hashrate, power_limit) if not device.stop(True): device.log('fatal', "failed to stop the miner process") print "" device.log( 'info', ' calibrated hashrate: %s' % (Units().hashrate_str(hashrate))) if power_limit != None: device.log('info', 'calibrated power limit: %.2f W' % (power_limit)) print ""
def get_max_hashrate_and_power_draw(self, miner, device, time_limit): max_power_draw = 0 timeout = 30 count = 0 while len(device.algos) == 0: time.sleep(1) Miners.poll() count += 1 if count >= 30: device.log('fatal', 'timed out waiting for worker to start') algorithm = device.algos[0]['algo'] if "_" in algorithm: readings_a = [] readings_b = [] else: readings = [] time_start = time.time() count = 0 err = 0 reading_count = 0 stable = False variance_pc = None while stable == False and (time.time() - time_start) <= time_limit: time.sleep(1) if count % 5 == 0: Miners().poll() if not Miners().is_up(miner.name): device.log('fatal', 'miner API went away, aborting') Miners().get_device_state(device) if len(device.algos) == 0: device.log('fatal', 'worker was stopped by an external process') rate_a, rate_b = device.algos[0]['hashrate'] if "_" in algorithm: r_len = len(readings_a) else: r_len = len(readings) if r_len > 0: if "_" in algorithm: variance_pc, n = Calibration().get_variance( readings_a + [rate_a], readings_b + [rate_b]) else: variance_pc, n = Calibration().get_variance(readings + [rate_a]) device.log( 'info', 'hashrate: %s (variance %.2f%%)' % (device.hashrate_str(), variance_pc)) else: device.log('info', 'hashrate: %s' % (device.hashrate_str())) if rate_a == 0: if reading_count >= 3: device.log('warning', 'hashrate dropped to 0!') if device.state != 'calibrating': device.log( 'fatal', 'our worker was stopped by an external process, aborting.' ) err += 1 if err >= 10: device.stop() device.log( 'fatal', 'failed to get a hashrate reading from the worker' ) else: if "_" in algorithm: readings_a.append(rate_a) readings_b.append(rate_b) else: readings.append(rate_a) if variance_pc != None and Calibration().is_stable( variance_pc, n): device.log('info', 'hashrate stabilised') stable = True reading_count += 1 count += 1 metrics = Nvidia().get_nvidia_metrics_for_device(device.id) if metrics['power_f']: max_power_draw = metrics['power_f'] else: max_power_draw = None if stable == False: device.stop(True) device.log( 'error', 'hashrate failed to stabilise after %d minutes\ntry increasing the timeout or loosen the stabilisation tolerance by increasing the "hashrate_stabilisation_tolerance" parameter' % (time_limit / 60)) return [None, None] if "_" in algorithm: hashrate = [ Calibration().get_nominal_hashrate_from_range(readings_a), Calibration().get_nominal_hashrate_from_range(readings_b) ] else: hashrate = Calibration().get_nominal_hashrate_from_range(readings) return [hashrate, max_power_draw]
def get_local_data(self, minimal=False): data = [] devices = nvidia.Nvidia().get_nvidia_devices(1, True) Miners().poll() if not minimal and os.path.exists( self.power_file) and os.path.getsize(self.power_file) > 0: power_draw_readings = pickle.loads(open(self.power_file).read()) else: power_draw_readings = [] if not minimal and os.path.exists( self.profitability_file) and os.path.getsize( self.profitability_file) > 0: profitability_readings = pickle.loads( open(self.profitability_file).read()) else: profitability_readings = [] Calibration().load() total_mbtc = 0 total_power = 0 total_power_limit = 0 power_values = {} power_limit_values = {} for device in devices: device.algos = [] Miners().get_device_state(device) mbtc_per_day_values = [0] algos = device.algos if len(algos) == 0: algos = ['IDLE'] for algo_i in range(0, len(algos)): algo = algos[algo_i] if algo_i == 0: omit_fields = False else: omit_fields = True if algo == 'IDLE': algo = "IDLE" algo1 = "IDLE" rate_s = "-" mbtc_per_day = 0 miner = '-' region = '-' pool = '-' _time = '-' else: algo1 = algo['algo'] miner = algo['miner'] region = algo['region'] pool = algo['pool'] _pool = Pools().pools[pool] if os.path.exists("/tmp/.minotaur.%d" % (device.id)): _time = Units().to_timestr( int(time.time() - os.stat("/tmp/.minotaur.%d" % (device.id)).st_mtime)) else: _time = '-' rate_a, rate_b = algo['hashrate'] if algo['algo'] in Config().get('algorithms')['double']: rate_s = Units().hashrate_str(rate_a) rate_s2 = Units().hashrate_str(rate_b) benchmarks = {algo['algo']: [rate_a, rate_b]} algo1, algo2 = algo['algo'].split("_") mbtc_per_day_values = [ _pool.mbtc_per_day( benchmarks, 'cached')[algo['region']][algo['algo']] ] else: rate_s = Units().hashrate_str(rate_a) benchmarks = {algo['algo']: rate_a} if algo['algo'] in _pool.mbtc_per_day( benchmarks, 'cached')[algo['region']].keys(): mbtc_per_day_values = [ _pool.mbtc_per_day( benchmarks, 'cached')[algo['region']][algo['algo']] ] else: mbtc_per_day_values = [0] if pool != '-': pool = _pool.shortened(region) metrics = device.to_hash() metrics.pop('changed', None) metrics['miner'] = miner metrics['region'] = region metrics['pool'] = pool metrics['time'] = _time metrics['omit_fields'] = omit_fields if metrics['fan']: metrics['fan'] = str(metrics['fan']).rjust(3) + " %" if metrics['miner']: metrics['miner'] = metrics['miner'][0:5] else: metrics['miner'] = '-' if algo == "IDLE": if metrics['gpu_u_i'] and metrics['gpu_u_i'] >= 90: algo = ".pending." algo1 = ".pending." rate_s = ".." mbtc_per_day = mbtc_per_day_values[0] metrics['host'] = 'local' metrics['id'] = device.id metrics[" algo"] = algo1 metrics["rate"] = rate_s metrics[" mBTC/day"] = ("%.2f" % (mbtc_per_day)).rjust(5) if not metrics['region']: metrics['region'] = '-' total_mbtc += sum(mbtc_per_day_values) if metrics['power_f']: power_values[metrics['id']] = metrics['power_f'] margin = self.calculate_profit_margin_for_card( sum(mbtc_per_day_values), metrics['power_f']) net_mbtc = (mbtc_per_day / 100) * margin if margin < 0: margin = 0 margin_s = "%d%%" % (int(margin)) metrics[" mBTC/day"] += "/" metrics[" mBTC/day"] += ("%.2f" % (net_mbtc)).rjust(5) metrics[" mBTC/day"] += " %s" % (margin_s.rjust(4)) else: margin = 0 if margin > 0: metrics["margin"] = "%.1f%%" % (margin) else: metrics["margin"] = "-" if metrics['limit_f']: power_limit_values[metrics['id']] = metrics['limit_f'] if device.state == 'calibrating': metrics[' algo'] = '*' + metrics[' algo'] elif device.get_pin() and 'algorithm' in device.get_pin().keys( ) and device.get_pin()['algorithm'] == metrics[' algo']: metrics[' algo'] = '+' + metrics[' algo'] elif device.get_pin() and 'algorithm' in device.get_pin().keys( ) and '_' in device.get_pin()['algorithm'] and metrics[ ' algo'] in device.get_pin()['algorithm'].split('_'): metrics[' algo'] = '+' + metrics[' algo'] else: metrics[' algo'] = ' ' + metrics[' algo'] if metrics['gpu_f']: match = re.match("^([\d]+)", metrics['gpu_f']) if match: metrics['gpu_f'] = match.group(1) else: metrics['gpu_f'] = '-' else: metrics['gpu_f'] = '-' if metrics['mem_f']: match = re.match("^([\d]+)", metrics['mem_f']) if match: metrics['mem_f'] = match.group(1) else: metrics['mem_f'] = '-' else: metrics['mem_f'] = '-' metrics['ps clocks'] = "%s %s/%s MHz" % ( metrics['ps'], metrics['gpu_f'], metrics['mem_f']) power = re.match("^([\d]+)", metrics['power']) limit = re.match("^([\d]+)", metrics['limit']) if power and limit: metrics['power'] = "%s/%s W" % (power.group(1), limit.group(1)) else: metrics['power'] = '-' data.append(metrics) if algo not in [ 'IDLE', '.pending.' ] and algo['algo'] in Config().get('algorithms.double'): mbtc_per_day = mbtc_per_day_values[1] metrics2 = metrics.copy() metrics2[" algo"] = algo2 metrics2["rate"] = rate_s2 metrics2[" mBTC/day"] = ("%.2f" % (mbtc_per_day)).rjust(5) net_mbtc = (mbtc_per_day / 100) * margin metrics2[" mBTC/day"] += "/" metrics2[" mBTC/day"] += ("%.2f" % (net_mbtc)).rjust(5) metrics2[" mBTC/day"] += " %s" % (margin_s.rjust(4)) if device.state == 'calibrating': metrics2[" algo"] = '*' + metrics2[' algo'] elif device.get_pin() and '_' in device.get_pin( )['algorithm'] and metrics2[' algo'] in device.get_pin( )['algorithm'].split('_'): metrics2[" algo"] = '+' + metrics2[' algo'] else: metrics2[" algo"] = ' ' + metrics2[' algo'] data.append(metrics2) total_power = sum(power_values.values()) total_power_limit = sum(power_limit_values.values()) electric_cost_mbtc = self.get_electricity_cost( total_power + Config().get('system_draw_watts'), True, Config().get('electricity_per_kwh')) net_mbtc = total_mbtc - electric_cost_mbtc return [ data, total_mbtc, net_mbtc, total_power, total_power_limit, power_draw_readings, profitability_readings ]