reworked logging, now supports log to file (fix #152 and #139)

This commit is contained in:
erpalma
2019-11-26 17:41:26 +01:00
parent b49d1b39d4
commit 763e948882

View File

@@ -2,6 +2,7 @@
from __future__ import print_function from __future__ import print_function
import argparse import argparse
import configparser
import glob import glob
import gzip import gzip
import os import os
@@ -10,13 +11,13 @@ import struct
import subprocess import subprocess
import sys import sys
from collections import defaultdict from collections import defaultdict
from datetime import datetime
from errno import EACCES, EPERM from errno import EACCES, EPERM
from multiprocessing import cpu_count from multiprocessing import cpu_count
from platform import uname from platform import uname
from threading import Event, Thread from threading import Event, Thread
from time import time from time import time
import configparser
import dbus import dbus
from dbus.mainloop.glib import DBusGMainLoop from dbus.mainloop.glib import DBusGMainLoop
from gi.repository import GLib from gi.repository import GLib
@@ -91,14 +92,33 @@ OK = bcolors.GREEN + bcolors.BOLD + 'OK' + bcolors.RESET
ERR = bcolors.RED + bcolors.BOLD + 'ERR' + bcolors.RESET ERR = bcolors.RED + bcolors.BOLD + 'ERR' + bcolors.RESET
LIM = bcolors.YELLOW + bcolors.BOLD + 'LIM' + bcolors.RESET LIM = bcolors.YELLOW + bcolors.BOLD + 'LIM' + bcolors.RESET
log_history = set()
def fatal(msg, code=1):
print('[E] {:s}'.format(msg), file=sys.stderr) def log(msg, oneshot=False, end='\n'):
outfile = args.log if args.log else sys.stdout
if msg.strip() not in log_history or oneshot is False:
tstamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
full_msg = '{:s}: {:s}'.format(tstamp, msg) if args.log else msg
print(full_msg, file=outfile, end=end)
log_history.add(msg.strip())
def fatal(msg, code=1, end='\n'):
outfile = args.log if args.log else sys.stderr
tstamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
full_msg = '{:s}: [E] {:s}'.format(tstamp, msg) if args.log else '[E] {:s}'.format(msg)
print(full_msg, file=outfile, end=end)
sys.exit(code) sys.exit(code)
def warning(msg): def warning(msg, oneshot=True, end='\n'):
print('[W] {:s}'.format(msg), file=sys.stderr) outfile = args.log if args.log else sys.stderr
if msg.strip() not in log_history or oneshot is False:
tstamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
full_msg = '{:s}: [W] {:s}'.format(tstamp, msg) if args.log else '[W] {:s}'.format(msg)
print(full_msg, file=outfile, end=end)
log_history.add(msg.strip())
def writemsr(msr, val): def writemsr(msr, val):
@@ -290,7 +310,7 @@ def undervolt(config):
read_value = get_undervolt(plane)[plane] read_value = get_undervolt(plane)[plane]
read_offset_mv = calc_undervolt_mv(read_value) read_offset_mv = calc_undervolt_mv(read_value)
match = OK if write_value == read_value else ERR match = OK if write_value == read_value else ERR
print( log(
'[D] Undervolt plane {:s} - write {:.0f} mV ({:#x}) - read {:.0f} mV ({:#x}) - match {}'.format( '[D] Undervolt plane {:s} - write {:.0f} mV ({:#x}) - read {:.0f} mV ({:#x}) - match {}'.format(
plane, write_offset_mv, write_value, read_offset_mv, read_value, match plane, write_offset_mv, write_value, read_offset_mv, read_value, match
) )
@@ -338,7 +358,7 @@ def set_icc_max(config):
read_value = get_icc_max(plane)[plane] read_value = get_icc_max(plane)[plane]
read_current_A = calc_icc_max_amp(read_value) read_current_A = calc_icc_max_amp(read_value)
match = OK if write_value == read_value else ERR match = OK if write_value == read_value else ERR
print( log(
'[D] IccMax plane {:s} - write {:.2f} A ({:#x}) - read {:.2f} A ({:#x}) - match {}'.format( '[D] IccMax plane {:s} - write {:.2f} A ({:#x}) - read {:.2f} A ({:#x}) - match {}'.format(
plane, write_current_amp, write_value, read_current_A, read_value, match plane, write_current_amp, write_value, read_current_A, read_value, match
) )
@@ -365,7 +385,7 @@ def load_config():
valid_trip_temp = min(TRIP_TEMP_RANGE[1], max(TRIP_TEMP_RANGE[0], trip_temp)) valid_trip_temp = min(TRIP_TEMP_RANGE[1], max(TRIP_TEMP_RANGE[0], trip_temp))
if trip_temp != valid_trip_temp: if trip_temp != valid_trip_temp:
config.set(power_source, 'Trip_Temp_C', str(valid_trip_temp)) config.set(power_source, 'Trip_Temp_C', str(valid_trip_temp))
print( log(
'[!] Overriding invalid "Trip_Temp_C" value in "{:s}": {:.1f} -> {:.1f}'.format( '[!] Overriding invalid "Trip_Temp_C" value in "{:s}": {:.1f} -> {:.1f}'.format(
power_source, trip_temp, valid_trip_temp power_source, trip_temp, valid_trip_temp
) )
@@ -379,7 +399,7 @@ def load_config():
valid_value = min(0, value) valid_value = min(0, value)
if value != valid_value: if value != valid_value:
config.set(key, plane, str(valid_value)) config.set(key, plane, str(valid_value))
print( log(
'[!] Overriding invalid "{:s}" value in "{:s}" voltage plane: {:.0f} -> {:.0f}'.format( '[!] Overriding invalid "{:s}" value in "{:s}" voltage plane: {:.0f} -> {:.0f}'.format(
key, plane, value, valid_value key, plane, value, valid_value
) )
@@ -413,7 +433,7 @@ def load_config():
raise ValueError raise ValueError
iccmax_enabled = True iccmax_enabled = True
except ValueError: except ValueError:
warning('Invalid value for {:s} in {:s}'.format(plane, key)) warning('Invalid value for {:s} in {:s}'.format(plane, key), oneshot=False)
config.remove_option(key, plane) config.remove_option(key, plane)
except configparser.NoOptionError: except configparser.NoOptionError:
pass pass
@@ -440,7 +460,7 @@ def calc_reg_values(platform_info, config):
trip_offset = int(round(critical_temp - Trip_Temp_C)) trip_offset = int(round(critical_temp - Trip_Temp_C))
regs[power_source]['MSR_TEMPERATURE_TARGET'] = trip_offset << 24 regs[power_source]['MSR_TEMPERATURE_TARGET'] = trip_offset << 24
else: else:
print('[I] {:s} trip temperature is disabled in config.'.format(power_source)) log('[I] {:s} trip temperature is disabled in config.'.format(power_source))
power_unit = get_power_unit() power_unit = get_power_unit()
@@ -453,26 +473,26 @@ def calc_reg_values(platform_info, config):
cur_pkg_power_limits = get_cur_pkg_power_limits() cur_pkg_power_limits = get_cur_pkg_power_limits()
if PL1_Tdp_W is None: if PL1_Tdp_W is None:
PL1 = cur_pkg_power_limits['PL1'] PL1 = cur_pkg_power_limits['PL1']
print('[I] {:s} PL1_Tdp_W disabled in config.'.format(power_source)) log('[I] {:s} PL1_Tdp_W disabled in config.'.format(power_source))
else: else:
PL1 = int(round(PL1_Tdp_W / power_unit)) PL1 = int(round(PL1_Tdp_W / power_unit))
if PL1_Duration_s is None: if PL1_Duration_s is None:
TW1 = cur_pkg_power_limits['TW1'] TW1 = cur_pkg_power_limits['TW1']
print('[I] {:s} PL1_Duration_s disabled in config.'.format(power_source)) log('[I] {:s} PL1_Duration_s disabled in config.'.format(power_source))
else: else:
Y, Z = calc_time_window_vars(PL1_Duration_s) Y, Z = calc_time_window_vars(PL1_Duration_s)
TW1 = Y | (Z << 5) TW1 = Y | (Z << 5)
if PL2_Tdp_W is None: if PL2_Tdp_W is None:
PL2 = cur_pkg_power_limits['PL2'] PL2 = cur_pkg_power_limits['PL2']
print('[I] {:s} PL2_Tdp_W disabled in config.'.format(power_source)) log('[I] {:s} PL2_Tdp_W disabled in config.'.format(power_source))
else: else:
PL2 = int(round(PL2_Tdp_W / power_unit)) PL2 = int(round(PL2_Tdp_W / power_unit))
if PL2_Duration_s is None: if PL2_Duration_s is None:
TW2 = cur_pkg_power_limits['TW2'] TW2 = cur_pkg_power_limits['TW2']
print('[I] {:s} PL2_Duration_s disabled in config.'.format(power_source)) log('[I] {:s} PL2_Duration_s disabled in config.'.format(power_source))
else: else:
Y, Z = calc_time_window_vars(PL2_Duration_s) Y, Z = calc_time_window_vars(PL2_Duration_s)
TW2 = Y | (Z << 5) TW2 = Y | (Z << 5)
@@ -481,15 +501,15 @@ def calc_reg_values(platform_info, config):
PL1 | (1 << 15) | (1 << 16) | (TW1 << 17) | (PL2 << 32) | (1 << 47) | (TW2 << 49) PL1 | (1 << 15) | (1 << 16) | (TW1 << 17) | (PL2 << 32) | (1 << 47) | (TW2 << 49)
) )
else: else:
print('[I] {:s} package power limits are disabled in config.'.format(power_source)) log('[I] {:s} package power limits are disabled in config.'.format(power_source))
# cTDP # cTDP
c_tdp_target_value = config.getint(power_source, 'cTDP', fallback=None) c_tdp_target_value = config.getint(power_source, 'cTDP', fallback=None)
if c_tdp_target_value is not None: if c_tdp_target_value is not None:
if platform_info['feature_programmable_tdp_limit'] != 1: if platform_info['feature_programmable_tdp_limit'] != 1:
print("[W] cTDP setting not supported by this CPU") log("[W] cTDP setting not supported by this CPU")
elif platform_info['number_of_additional_tdp_profiles'] < c_tdp_target_value: elif platform_info['number_of_additional_tdp_profiles'] < c_tdp_target_value:
print("[W] the configured cTDP profile is not supported by this CPU") log("[W] the configured cTDP profile is not supported by this CPU")
else: else:
valid_c_tdp_target_value = max(0, c_tdp_target_value) valid_c_tdp_target_value = max(0, c_tdp_target_value)
regs[power_source]['MSR_CONFIG_TDP_CONTROL'] = valid_c_tdp_target_value regs[power_source]['MSR_CONFIG_TDP_CONTROL'] = valid_c_tdp_target_value
@@ -505,7 +525,7 @@ def set_hwp():
if args.debug: if args.debug:
read_value = readmsr(0x774, from_bit=24, to_bit=31)[0] read_value = readmsr(0x774, from_bit=24, to_bit=31)[0]
match = OK if HWP_VALUE == read_value else ERR match = OK if HWP_VALUE == read_value else ERR
print('[D] HWP - write "{:#02x}" - read "{:#02x}" - match {}'.format(HWP_VALUE, read_value, match)) log('[D] HWP - write "{:#02x}" - read "{:#02x}" - match {}'.format(HWP_VALUE, read_value, match))
def power_thread(config, regs, exit_event): def power_thread(config, regs, exit_event):
@@ -518,12 +538,12 @@ def power_thread(config, regs, exit_event):
next_hwp_write = 0 next_hwp_write = 0
while not exit_event.is_set(): while not exit_event.is_set():
# print thermal status # log thermal status
if args.debug: if args.debug:
thermal_status = get_reset_thermal_status() thermal_status = get_reset_thermal_status()
for index, core_thermal_status in enumerate(thermal_status): for index, core_thermal_status in enumerate(thermal_status):
for key, value in core_thermal_status.items(): for key, value in core_thermal_status.items():
print('[D] core {} thermal status: {} = {}'.format(index, key.replace("_", " "), value)) log('[D] core {} thermal status: {} = {}'.format(index, key.replace("_", " "), value))
# switch back to sysfs polling # switch back to sysfs polling
if power['method'] == 'polling': if power['method'] == 'polling':
@@ -536,7 +556,7 @@ def power_thread(config, regs, exit_event):
if args.debug: if args.debug:
read_value = readmsr(0x1A2, 24, 29, flatten=True) read_value = readmsr(0x1A2, 24, 29, flatten=True)
match = OK if write_value >> 24 == read_value else ERR match = OK if write_value >> 24 == read_value else ERR
print( log(
'[D] TEMPERATURE_TARGET - write {:#x} - read {:#x} - match {}'.format( '[D] TEMPERATURE_TARGET - write {:#x} - read {:#x} - match {}'.format(
write_value >> 24, read_value, match write_value >> 24, read_value, match
) )
@@ -549,7 +569,7 @@ def power_thread(config, regs, exit_event):
if args.debug: if args.debug:
read_value = readmsr(0x64B, 0, 1, flatten=True) read_value = readmsr(0x64B, 0, 1, flatten=True)
match = OK if write_value == read_value else ERR match = OK if write_value == read_value else ERR
print( log(
'[D] CONFIG_TDP_CONTROL - write {:#x} - read {:#x} - match {}'.format( '[D] CONFIG_TDP_CONTROL - write {:#x} - read {:#x} - match {}'.format(
write_value, read_value, match write_value, read_value, match
) )
@@ -561,7 +581,7 @@ def power_thread(config, regs, exit_event):
if args.debug: if args.debug:
read_value = readmsr(0x610, 0, 55, flatten=True) read_value = readmsr(0x610, 0, 55, flatten=True)
match = OK if write_value == read_value else ERR match = OK if write_value == read_value else ERR
print( log(
'[D] MSR PACKAGE_POWER_LIMIT - write {:#x} - read {:#x} - match {}'.format( '[D] MSR PACKAGE_POWER_LIMIT - write {:#x} - read {:#x} - match {}'.format(
write_value, read_value, match write_value, read_value, match
) )
@@ -573,7 +593,7 @@ def power_thread(config, regs, exit_event):
if args.debug: if args.debug:
read_value = mchbar_mmio.read32(0) | (mchbar_mmio.read32(4) << 32) read_value = mchbar_mmio.read32(0) | (mchbar_mmio.read32(4) << 32)
match = OK if write_value == read_value else ERR match = OK if write_value == read_value else ERR
print( log(
'[D] MCHBAR PACKAGE_POWER_LIMIT - write {:#x} - read {:#x} - match {}'.format( '[D] MCHBAR PACKAGE_POWER_LIMIT - write {:#x} - read {:#x} - match {}'.format(
write_value, read_value, match write_value, read_value, match
) )
@@ -615,7 +635,7 @@ def check_kernel():
except (subprocess.CalledProcessError, IOError): except (subprocess.CalledProcessError, IOError):
pass pass
if kernel_config is None: if kernel_config is None:
print('[W] Unable to obtain and validate kernel config.') log('[W] Unable to obtain and validate kernel config.')
return return
elif not re.search('CONFIG_DEVMEM=y', kernel_config): elif not re.search('CONFIG_DEVMEM=y', kernel_config):
warning('Bad kernel config: you need CONFIG_DEVMEM=y.') warning('Bad kernel config: you need CONFIG_DEVMEM=y.')
@@ -649,7 +669,7 @@ def check_cpu():
if cpuinfo['cpu family'] != 6 or cpu_model is None: if cpuinfo['cpu family'] != 6 or cpu_model is None:
fatal('Your CPU model is not supported.') fatal('Your CPU model is not supported.')
print('[I] Detected CPU architecture: Intel {:s}'.format(cpu_model)) log('[I] Detected CPU architecture: Intel {:s}'.format(cpu_model))
except: except:
fatal('Unable to identify CPU model.') fatal('Unable to identify CPU model.')
@@ -677,13 +697,13 @@ def monitor(exit_event, wait):
undervolt_values = get_undervolt(convert=True) undervolt_values = get_undervolt(convert=True)
undervolt_output = ' | '.join('{:s}: {:.2f} mV'.format(plane, undervolt_values[plane]) for plane in VOLTAGE_PLANES) undervolt_output = ' | '.join('{:s}: {:.2f} mV'.format(plane, undervolt_values[plane]) for plane in VOLTAGE_PLANES)
print('[D] Undervolt offsets: {:s}'.format(undervolt_output)) log('[D] Undervolt offsets: {:s}'.format(undervolt_output))
iccmax_values = get_icc_max(convert=True) iccmax_values = get_icc_max(convert=True)
iccmax_output = ' | '.join('{:s}: {:.2f} A'.format(plane, iccmax_values[plane]) for plane in CURRENT_PLANES) iccmax_output = ' | '.join('{:s}: {:.2f} A'.format(plane, iccmax_values[plane]) for plane in CURRENT_PLANES)
print('[D] IccMax: {:s}'.format(iccmax_output)) log('[D] IccMax: {:s}'.format(iccmax_output))
print('[D] Realtime monitoring of throttling causes:\n') log('[D] Realtime monitoring of throttling causes:\n')
while not exit_event.is_set(): while not exit_event.is_set():
value = readmsr(IA32_THERM_STATUS, from_bit=0, to_bit=15, cpu=0) value = readmsr(IA32_THERM_STATUS, from_bit=0, to_bit=15, cpu=0)
offsets = {'Thermal': 0, 'Power': 10, 'Current': 12, 'Cross-domain (e.g. GPU)': 14} offsets = {'Thermal': 0, 'Power': 10, 'Current': 12, 'Cross-domain (e.g. GPU)': 14}
@@ -701,7 +721,11 @@ def monitor(exit_event, wait):
) )
stats2[power_plane] = '{:.1f} W'.format(energy_w) stats2[power_plane] = '{:.1f} W'.format(energy_w)
output2 = ('{:s}: {:s}'.format(label, stats2[label]) for label in stats2) output2 = ('{:s}: {:s}'.format(label, stats2[label]) for label in stats2)
print('[{}] {} || {}{}'.format(power['source'], ' - '.join(output), ' - '.join(output2), ' ' * 10), end='\r') terminator = '\n' if args.log else '\r'
log(
'[{}] {} || {}{}'.format(power['source'], ' - '.join(output), ' - '.join(output2), ' ' * 10),
end=terminator,
)
exit_event.wait(wait) exit_event.wait(wait)
@@ -721,20 +745,28 @@ def main():
) )
parser.add_argument('--config', default='/etc/lenovo_fix.conf', help='override default config file path') parser.add_argument('--config', default='/etc/lenovo_fix.conf', help='override default config file path')
parser.add_argument('--force', action='store_true', help='bypass compatibility checks (EXPERTS only)') parser.add_argument('--force', action='store_true', help='bypass compatibility checks (EXPERTS only)')
parser.add_argument('--log', metavar='/path/to/file', help='log to file instead of stdout')
args = parser.parse_args() args = parser.parse_args()
if args.log:
try:
args.log = open(args.log, 'w')
except:
args.log = None
fatal('Unable to write to the log file!')
if not args.force: if not args.force:
check_kernel() check_kernel()
check_cpu() check_cpu()
print('[I] Loading config file.') log('[I] Loading config file.')
config = load_config() config = load_config()
power['source'] = 'BATTERY' if is_on_battery(config) else 'AC' power['source'] = 'BATTERY' if is_on_battery(config) else 'AC'
platform_info = get_cpu_platform_info() platform_info = get_cpu_platform_info()
if args.debug: if args.debug:
for key, value in platform_info.items(): for key, value in platform_info.items():
print('[D] cpu platform info: {} = {}'.format(key.replace("_", " "), value)) log('[D] cpu platform info: {} = {}'.format(key.replace("_", " "), value))
regs = calc_reg_values(platform_info, config) regs = calc_reg_values(platform_info, config)
if not config.getboolean('GENERAL', 'Enabled'): if not config.getboolean('GENERAL', 'Enabled'):
@@ -778,7 +810,7 @@ def main():
path="/org/freedesktop/UPower/devices/line_power_AC", path="/org/freedesktop/UPower/devices/line_power_AC",
) )
print('[I] Starting main loop.') log('[I] Starting main loop.')
if args.monitor is not None: if args.monitor is not None:
monitor_thread = Thread(target=monitor, args=(exit_event, args.monitor)) monitor_thread = Thread(target=monitor, args=(exit_event, args.monitor))