@@ -443,17 +443,23 @@ def _fetch_rapl_files(self) -> None:
443443 except Exception as e :
444444 if isinstance (e , PermissionError ):
445445 logger .warning (
446- "Permission denied listing %s: %s" , entry_path , e
446+ "\t RAPL - Permission denied listing %s: %s" ,
447+ entry_path ,
448+ e ,
447449 )
448450 else :
449- logger .debug ("Cannot list %s: %s" , entry_path , e )
451+ logger .debug ("\t RAPL - Cannot list %s: %s" , entry_path , e )
450452 except Exception as e :
451453 if isinstance (e , PermissionError ):
452454 logger .warning (
453- "Permission denied scanning %s for RAPL domains: %s" , base , e
455+ "\t RAPL - Permission denied scanning %s for RAPL domains: %s" ,
456+ base ,
457+ e ,
454458 )
455459 else :
456- logger .debug ("Cannot scan %s for RAPL domains: %s" , base , e )
460+ logger .debug (
461+ "\t RAPL - Cannot scan %s for RAPL domains: %s" , base , e
462+ )
457463
458464 # Fallback: if none found and the configured path looks like it directly
459465 # contains domain entries, try listing it (preserves backward compatibility).
@@ -473,6 +479,12 @@ def _fetch_rapl_files(self) -> None:
473479 # Remove duplicates
474480 domain_dirs = list (dict .fromkeys (domain_dirs ))
475481
482+ # Build a list of successfully readable domains with their metadata
483+ # We'll deduplicate at the end, after we know which ones are readable
484+ readable_domains = (
485+ []
486+ ) # List of (name, domain_dir, is_mmio, rapl_file, rapl_file_max)
487+
476488 i = 0
477489 for domain_dir in domain_dirs :
478490 try :
@@ -485,13 +497,13 @@ def _fetch_rapl_files(self) -> None:
485497 except Exception as e :
486498 if isinstance (e , PermissionError ):
487499 logger .warning (
488- "Permission denied reading name file %s: %s" ,
500+ "\t RAPL - Permission denied reading name file %s: %s" ,
489501 name_path ,
490502 e ,
491503 )
492504 else :
493505 logger .debug (
494- "Unable to read name file %s: %s" , name_path , e
506+ "\t RAPL - Unable to read name file %s: %s" , name_path , e
495507 )
496508 if not name :
497509 # Use the domain directory basename as a fallback
@@ -519,40 +531,127 @@ def _fetch_rapl_files(self) -> None:
519531 if is_required_main :
520532 found_main_readable = True
521533 except PermissionError :
522- msg = f"Permission denied reading RAPL file { rapl_file } ."
534+ msg = f"\t RAPL - Permission denied reading RAPL file { rapl_file } ."
523535 suggestion = "You can grant read permission with: sudo chmod -R a+r /sys/class/powercap/*"
524536 logger .warning ("%s %s; skipping." , msg , suggestion )
525537 # do not raise; skip this domain
526538 continue
527539 except Exception as e :
528540 logger .debug (
529- "Skipping non-numeric or unreadable RAPL file %s: %s" ,
541+ "\t RAPL - Skipping non-numeric or unreadable RAPL file %s: %s" ,
530542 rapl_file ,
531543 e ,
532544 )
533545 continue
534546
535- try :
536- self ._rapl_files .append (
537- RAPLFile (name = name , path = rapl_file , max_path = rapl_file_max )
538- )
539- logger .debug ("We will read Intel RAPL files at %s" , rapl_file )
540- except PermissionError as e :
541- logger .warning (
542- "Permission denied while initializing RAPL file %s: %s" ,
543- rapl_file ,
544- e ,
545- )
546- continue
547- except Exception as e :
548- logger .debug (
549- "Unable to initialize RAPLFile for %s: %s" , rapl_file , e
550- )
551- continue
547+ # This domain is readable, add it to our list
548+ is_mmio = "intel-rapl-mmio" in domain_dir
549+ readable_domains .append (
550+ (name , domain_dir , is_mmio , rapl_file , rapl_file_max )
551+ )
552552 except Exception as e :
553553 # Log and continue on any per-domain failure; availability is
554554 # determined from whether a main/package counter was readable.
555- logger .warning ("Error processing RAPL domain %s: %s" , domain_dir , e )
555+ logger .warning (
556+ "\t RAPL - Error processing RAPL domain %s: %s" , domain_dir , e
557+ )
558+ continue
559+
560+ # Deduplicate readable domains with same name, preferring MMIO over MSR-based
561+ # This prevents double-counting when same domain appears in both
562+ # intel-rapl and intel-rapl-mmio (e.g., package-0)
563+
564+ # First, check if we have a psys (platform/system) domain
565+ # psys provides total platform power and already includes package, core, uncore, etc.
566+ # Using psys alone is the best way to avoid double-counting on modern Intel systems
567+ psys_domain = None
568+ for domain_tuple in readable_domains :
569+ name , domain_dir , is_mmio , rapl_file , rapl_file_max = domain_tuple
570+
571+ # Check if this is a psys domain
572+ try :
573+ name_path = os .path .join (domain_dir , "name" )
574+ if os .path .exists (name_path ):
575+ with open (name_path ) as f :
576+ domain_name = f .read ().strip ().lower ()
577+ if domain_name == "psys" :
578+ psys_domain = domain_tuple
579+ logger .info (
580+ "\t RAPL - Found psys (platform/system) domain - this provides "
581+ "total platform power and avoids double-counting"
582+ )
583+ break
584+ except Exception :
585+ pass
586+
587+ # If psys is available, use ONLY psys to avoid all double-counting
588+ if psys_domain :
589+ logger .info (
590+ "\t RAPL - Using only psys domain for power measurement to ensure accuracy. "
591+ "Other domains (package, core, uncore) are subsets of psys."
592+ )
593+ domain_map = {"psys" : psys_domain }
594+ else :
595+ # No psys available, fall back to deduplicating package/core/uncore domains
596+ logger .warning (
597+ "\t RAPL - No psys domain found, using individual domains (package, core, uncore)"
598+ )
599+ domain_map = (
600+ {}
601+ ) # name -> (name, domain_dir, is_mmio, rapl_file, rapl_file_max)
602+ for domain_tuple in readable_domains :
603+ name , domain_dir , is_mmio , rapl_file , rapl_file_max = domain_tuple
604+
605+ # Extract the base name (without "Processor Energy Delta_X" numbering)
606+ # to properly identify duplicates
607+ base_name = name
608+ if "Processor Energy" in name :
609+ # This is a package domain, use the original domain name for deduplication
610+ try :
611+ name_path = os .path .join (domain_dir , "name" )
612+ if os .path .exists (name_path ):
613+ with open (name_path ) as f :
614+ base_name = f .read ().strip ()
615+ except Exception :
616+ base_name = os .path .basename (domain_dir )
617+
618+ # If we haven't seen this base name, or we're replacing MSR with MMIO, keep it
619+ if base_name not in domain_map or (
620+ is_mmio and not domain_map [base_name ][2 ]
621+ ):
622+ domain_map [base_name ] = domain_tuple
623+
624+ logger .debug (
625+ "\t RAPL - Found %d unique RAPL domains after deduplication (from %d readable domains)" ,
626+ len (domain_map ),
627+ len (readable_domains ),
628+ )
629+
630+ # Now create RAPLFile objects for deduplicated domains
631+ for name , _ , is_mmio , rapl_file , rapl_file_max in domain_map .values ():
632+ try :
633+ # Determine interface type for logging
634+ interface_type = "MMIO" if is_mmio else "MSR"
635+ self ._rapl_files .append (
636+ RAPLFile (name = name , path = rapl_file , max_path = rapl_file_max )
637+ )
638+ logger .debug (
639+ "\t RAPL - Reading RAPL domain '%s' via %s interface at %s" ,
640+ name ,
641+ interface_type ,
642+ rapl_file ,
643+ )
644+ except PermissionError as e :
645+ logger .warning (
646+ "\t RAPL - Permission denied while initializing RAPL file %s: %s" ,
647+ rapl_file ,
648+ e ,
649+ )
650+ continue
651+ except Exception as e :
652+ logger .debug (
653+ "\t RAPL - Unable to initialize RAPLFile for %s: %s" , rapl_file , e
654+ )
556655 continue
557656
558657 # Save whether we found a readable main/package energy counter so
@@ -580,7 +679,7 @@ def get_cpu_details(self, duration: Time) -> Dict:
580679 )
581680 except Exception as e :
582681 logger .info (
583- "Unable to read Intel RAPL files at %s\n \
682+ "\t RAPL - Unable to read Intel RAPL files at %s\n \
584683 Exception occurred %s" ,
585684 self ._rapl_files ,
586685 e ,
0 commit comments