mcgillij / amdfan

Updated AMD Fan control utility forked from amdgpu-fan and updated.
https://mcgillij.dev/pages/amdfan.html
GNU General Public License v2.0
33 stars 7 forks source link

AttributeError: 'Card' object has no attribute '_monitor' #28

Closed a-crate closed 7 months ago

a-crate commented 9 months ago

I'm running on gentoo built with python3.11 but could reproduce this on latest Arch. Full stack trace:

# amdfan --daemon
╭──────────────────────────── Traceback (most recent call last) ─────────────────────────────╮
│ /usr/lib/python-exec/python3.11/amdfan:8 in <module>                                       │
│                                                                                            │
│   5 from amdfan.amdfan import cli                                                          │
│   6 if __name__ == "__main__":                                                             │
│   7 │   sys.argv[0] = re.sub(r"(-script\.pyw|\.exe)?$", "", sys.argv[0])                   │
│ ❱ 8 │   sys.exit(cli())                                                                    │
│   9                                                                                        │
│                                                                                            │
│ /usr/lib/python3.11/site-packages/click/core.py:1157 in __call__                           │
│                                                                                            │
│   1154 │                                                                                   │
│   1155 │   def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Any:                     │
│   1156 │   │   """Alias for :meth:`main`."""                                               │
│ ❱ 1157 │   │   return self.main(*args, **kwargs)                                           │
│   1158                                                                                     │
│   1159                                                                                     │
│   1160 class Command(BaseCommand):                                                         │
│                                                                                            │
│ /usr/lib/python3.11/site-packages/click/core.py:1078 in main                               │
│                                                                                            │
│   1075 │   │   try:                                                                        │
│   1076 │   │   │   try:                                                                    │
│   1077 │   │   │   │   with self.make_context(prog_name, args, **extra) as ctx:            │
│ ❱ 1078 │   │   │   │   │   rv = self.invoke(ctx)                                           │
│   1079 │   │   │   │   │   if not standalone_mode:                                         │
│   1080 │   │   │   │   │   │   return rv                                                   │
│   1081 │   │   │   │   │   # it's not safe to `ctx.exit(rv)` here!                         │
│                                                                                            │
│ /usr/lib/python3.11/site-packages/click/core.py:1434 in invoke                             │
│                                                                                            │
│   1431 │   │   │   echo(style(message, fg="red"), err=True)                                │
│   1432 │   │                                                                               │
│   1433 │   │   if self.callback is not None:                                               │
│ ❱ 1434 │   │   │   return ctx.invoke(self.callback, **ctx.params)                          │
│   1435 │                                                                                   │
│   1436 │   def shell_complete(self, ctx: Context, incomplete: str) -> t.List["CompletionIt │
│   1437 │   │   """Return a list of completions for the incomplete value. Looks             │
│                                                                                            │
│ /usr/lib/python3.11/site-packages/click/core.py:783 in invoke                              │
│                                                                                            │
│    780 │   │                                                                               │
│    781 │   │   with augment_usage_errors(__self):                                          │
│    782 │   │   │   with ctx:                                                               │
│ ❱  783 │   │   │   │   return __callback(*args, **kwargs)                                  │
│    784 │                                                                                   │
│    785 │   def forward(                                                                    │
│    786 │   │   __self, __cmd: "Command", *args: t.Any, **kwargs: t.Any  # noqa: B902       │
│                                                                                            │
│ /usr/lib/python3.11/site-packages/amdfan/amdfan.py:344 in cli                              │
│                                                                                            │
│   341 │   daemon: bool, monitor: bool, manual: bool, configuration: bool, service: bool    │
│   342 ) -> None:                                                                           │
│   343 │   if daemon:                                                                       │
│ ❱ 344 │   │   run_as_daemon()                                                              │
│   345 │   elif monitor:                                                                    │
│   346 │   │   monitor_cards()                                                              │
│   347 │   elif manual:                                                                     │
│                                                                                            │
│ /usr/lib/python3.11/site-packages/amdfan/amdfan.py:372 in run_as_daemon                    │
│                                                                                            │
│   369 │   │                                                                                │
│   370 │   │   config = load_config(CONFIG_LOCATIONS[-1])                                   │
│   371 │                                                                                    │
│ ❱ 372 │   FanController(config).main()                                                     │
│   373                                                                                      │
│   374                                                                                      │
│   375 def show_table(cards: Dict) -> Table:                                                │
│                                                                                            │
│ /usr/lib/python3.11/site-packages/amdfan/amdfan.py:206 in __init__                         │
│                                                                                            │
│   203 │   """Used to apply the curve at regular intervals"""                               │
│   204 │                                                                                    │
│   205 │   def __init__(self, config) -> None:                                              │
│ ❱ 206 │   │   self._scanner = Scanner(config.get("cards"))                                 │
│   207 │   │   if len(self._scanner.cards) < 1:                                             │
│   208 │   │   │   LOGGER.error("no compatible cards found, exiting")                       │
│   209 │   │   │   sys.exit(1)                                                              │
│                                                                                            │
│ /usr/lib/python3.11/site-packages/amdfan/amdfan.py:179 in __init__                         │
│                                                                                            │
│   176 │   CARD_REGEX: str = r"^card\d$"                                                    │
│   177 │                                                                                    │
│   178 │   def __init__(self, cards=None) -> None:                                          │
│ ❱ 179 │   │   self.cards = self._get_cards(cards)                                          │
│   180 │                                                                                    │
│   181 │   def _get_cards(self, cards_to_scan):                                             │
│   182 │   │   """                                                                          │
│                                                                                            │
│ /usr/lib/python3.11/site-packages/amdfan/amdfan.py:194 in _get_cards                       │
│                                                                                            │
│   191 │   │   │   │   ]:                                                                   │
│   192 │   │   │   │   │   continue                                                         │
│   193 │   │   │   │   try:                                                                 │
│ ❱ 194 │   │   │   │   │   cards[node] = Card(node)                                         │
│   195 │   │   │   │   except FileNotFoundError:                                            │
│   196 │   │   │   │   │   # if card lacks hwmon or required devfs files, its not           │
│   197 │   │   │   │   │   # amdgpu, and definitely not compatible with this software       │
│                                                                                            │
│ /usr/lib/python3.11/site-packages/amdfan/amdfan.py:106 in __init__                         │
│                                                                                            │
│   103 │   │   for node in os.listdir(os.path.join(ROOT_DIR, self._id, HWMON_DIR)):         │
│   104 │   │   │   if re.match(self.HWMON_REGEX, node):                                     │
│   105 │   │   │   │   self._monitor = node                                                 │
│ ❱ 106 │   │   self._endpoints = self._load_endpoints()                                     │
│   107 │                                                                                    │
│   108 │   def _verify_card(self) -> None:                                                  │
│   109 │   │   for endpoint in self.AMD_FIELDS:                                             │
│                                                                                            │
│ /usr/lib/python3.11/site-packages/amdfan/amdfan.py:116 in _load_endpoints                  │
│                                                                                            │
│   113 │                                                                                    │
│   114 │   def _load_endpoints(self) -> Dict:                                               │
│   115 │   │   _endpoints = {}                                                              │
│ ❱ 116 │   │   _dir = os.path.join(ROOT_DIR, self._id, HWMON_DIR, self._monitor)            │
│   117 │   │   for endpoint in os.listdir(_dir):                                            │
│   118 │   │   │   if endpoint not in ("device", "power", "subsystem", "uevent"):           │
│   119 │   │   │   │   _endpoints[endpoint] = os.path.join(_dir, endpoint)                  │
╰────────────────────────────────────────────────────────────────────────────────────────────╯
AttributeError: 'Card' object has no attribute '_monitor'
#
mcgillij commented 8 months ago

You may need to go check that your configuration match's what your system actually reports.

ls /sys/class/drm/ it could be trying to find card0 or card1 etc depending on what you set in the config.

a-crate commented 8 months ago

card0 is my Intel iGPU and card1 is an amd gpu. I tried setting card1 only in the config and got the same results.

a-crate commented 8 months ago

I've found the root cause: the conditional on line 107 never triggers because the HWMON_REGEX on line 91 assumes that all hwmon IDs will be single digits. Mine is not, it's hwmon12. I likely have unusual hwmon IDs because I have an eGPU setup. Changing line 91 of amdfan.py from HWMON_REGEX: str = r"^hwmon\d$" to HWMON_REGEX: str = r"^hwmon\d+$" is the fix.

mcgillij commented 8 months ago

Cool, sounds good I'll fire up a patch when I have a chance, glad you got it working.