mirror of
https://github.com/esphome/esphome.git
synced 2025-10-21 11:13:46 +01:00
preen
This commit is contained in:
@@ -77,7 +77,7 @@ class MemoryAnalyzer:
|
|||||||
readelf_path: str | None = None,
|
readelf_path: str | None = None,
|
||||||
external_components: set[str] | None = None,
|
external_components: set[str] | None = None,
|
||||||
idedata: "IDEData | None" = None,
|
idedata: "IDEData | None" = None,
|
||||||
):
|
) -> None:
|
||||||
"""Initialize memory analyzer.
|
"""Initialize memory analyzer.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -311,15 +311,13 @@ class MemoryAnalyzer:
|
|||||||
potential_cppfilt,
|
potential_cppfilt,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
_LOGGER.info(
|
_LOGGER.info("✗ Using system c++filt (objdump_path=%s)", self.objdump_path)
|
||||||
"✗ Using system c++filt (objdump_path=%s)", self.objdump_path
|
|
||||||
)
|
|
||||||
|
|
||||||
# Strip GCC optimization suffixes and prefixes before demangling
|
# Strip GCC optimization suffixes and prefixes before demangling
|
||||||
# Suffixes like $isra$0, $part$0, $constprop$0 confuse c++filt
|
# Suffixes like $isra$0, $part$0, $constprop$0 confuse c++filt
|
||||||
# Prefixes like _GLOBAL__sub_I_ need to be removed and tracked
|
# Prefixes like _GLOBAL__sub_I_ need to be removed and tracked
|
||||||
symbols_stripped = []
|
symbols_stripped: list[str] = []
|
||||||
symbols_prefixes = [] # Track removed prefixes
|
symbols_prefixes: list[str] = [] # Track removed prefixes
|
||||||
for symbol in symbols:
|
for symbol in symbols:
|
||||||
# Remove GCC optimization markers
|
# Remove GCC optimization markers
|
||||||
stripped = re.sub(r"\$(?:isra|part|constprop)\$\d+", "", symbol)
|
stripped = re.sub(r"\$(?:isra|part|constprop)\$\d+", "", symbol)
|
||||||
@@ -327,12 +325,11 @@ class MemoryAnalyzer:
|
|||||||
# Handle GCC global constructor/initializer prefixes
|
# Handle GCC global constructor/initializer prefixes
|
||||||
# _GLOBAL__sub_I_<mangled> -> extract <mangled> for demangling
|
# _GLOBAL__sub_I_<mangled> -> extract <mangled> for demangling
|
||||||
prefix = ""
|
prefix = ""
|
||||||
if stripped.startswith("_GLOBAL__sub_I_"):
|
for gcc_prefix in _GCC_PREFIX_ANNOTATIONS:
|
||||||
prefix = "_GLOBAL__sub_I_"
|
if stripped.startswith(gcc_prefix):
|
||||||
stripped = stripped[len(prefix) :]
|
prefix = gcc_prefix
|
||||||
elif stripped.startswith("_GLOBAL__sub_D_"):
|
|
||||||
prefix = "_GLOBAL__sub_D_"
|
|
||||||
stripped = stripped[len(prefix) :]
|
stripped = stripped[len(prefix) :]
|
||||||
|
break
|
||||||
|
|
||||||
symbols_stripped.append(stripped)
|
symbols_stripped.append(stripped)
|
||||||
symbols_prefixes.append(prefix)
|
symbols_prefixes.append(prefix)
|
||||||
@@ -405,17 +402,18 @@ class MemoryAnalyzer:
|
|||||||
if stripped == demangled and stripped.startswith("_Z"):
|
if stripped == demangled and stripped.startswith("_Z"):
|
||||||
failed_count += 1
|
failed_count += 1
|
||||||
if failed_count <= 5: # Only log first 5 failures
|
if failed_count <= 5: # Only log first 5 failures
|
||||||
_LOGGER.warning("Failed to demangle: %s", original[:100])
|
_LOGGER.warning("Failed to demangle: %s", original)
|
||||||
|
|
||||||
|
if failed_count == 0:
|
||||||
|
_LOGGER.info("Successfully demangled all %d symbols", len(symbols))
|
||||||
|
return
|
||||||
|
|
||||||
if failed_count > 0:
|
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"Failed to demangle %d/%d symbols using %s",
|
"Failed to demangle %d/%d symbols using %s",
|
||||||
failed_count,
|
failed_count,
|
||||||
len(symbols),
|
len(symbols),
|
||||||
cppfilt_cmd,
|
cppfilt_cmd,
|
||||||
)
|
)
|
||||||
else:
|
|
||||||
_LOGGER.warning("Successfully demangled all %d symbols", len(symbols))
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _restore_symbol_prefix(prefix: str, stripped: str, demangled: str) -> str:
|
def _restore_symbol_prefix(prefix: str, stripped: str, demangled: str) -> str:
|
||||||
|
@@ -83,7 +83,7 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
|||||||
total_ram = sum(c.ram_total for _, c in components)
|
total_ram = sum(c.ram_total for _, c in components)
|
||||||
|
|
||||||
# Build report
|
# Build report
|
||||||
lines = []
|
lines: list[str] = []
|
||||||
|
|
||||||
lines.append("=" * self.TABLE_WIDTH)
|
lines.append("=" * self.TABLE_WIDTH)
|
||||||
lines.append("Component Memory Analysis".center(self.TABLE_WIDTH))
|
lines.append("Component Memory Analysis".center(self.TABLE_WIDTH))
|
||||||
|
@@ -411,11 +411,11 @@ def find_existing_comment(pr_number: str) -> str | None:
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Comment numeric ID if found, None otherwise
|
Comment numeric ID if found, None otherwise
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
subprocess.CalledProcessError: If gh command fails
|
||||||
"""
|
"""
|
||||||
try:
|
print(f"DEBUG: Looking for existing comment on PR #{pr_number}", file=sys.stderr)
|
||||||
print(
|
|
||||||
f"DEBUG: Looking for existing comment on PR #{pr_number}", file=sys.stderr
|
|
||||||
)
|
|
||||||
|
|
||||||
# Use gh api to get comments directly - this returns the numeric id field
|
# Use gh api to get comments directly - this returns the numeric id field
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
@@ -470,27 +470,20 @@ def find_existing_comment(pr_number: str) -> str | None:
|
|||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
print(f"Error finding existing comment: {e}", file=sys.stderr)
|
|
||||||
if e.stderr:
|
|
||||||
print(f"stderr: {e.stderr.decode()}", file=sys.stderr)
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
def post_or_update_comment(pr_number: str, comment_body: str) -> None:
|
||||||
def post_or_update_comment(pr_number: str, comment_body: str) -> bool:
|
|
||||||
"""Post a new comment or update existing one.
|
"""Post a new comment or update existing one.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
pr_number: PR number
|
pr_number: PR number
|
||||||
comment_body: Comment body text
|
comment_body: Comment body text
|
||||||
|
|
||||||
Returns:
|
Raises:
|
||||||
True if successful, False otherwise
|
subprocess.CalledProcessError: If gh command fails
|
||||||
"""
|
"""
|
||||||
# Look for existing comment
|
# Look for existing comment
|
||||||
existing_comment_id = find_existing_comment(pr_number)
|
existing_comment_id = find_existing_comment(pr_number)
|
||||||
|
|
||||||
try:
|
|
||||||
if existing_comment_id and existing_comment_id != "None":
|
if existing_comment_id and existing_comment_id != "None":
|
||||||
# Update existing comment
|
# Update existing comment
|
||||||
print(
|
print(
|
||||||
@@ -527,21 +520,6 @@ def post_or_update_comment(pr_number: str, comment_body: str) -> bool:
|
|||||||
print(f"DEBUG: Post response: {result.stdout}", file=sys.stderr)
|
print(f"DEBUG: Post response: {result.stdout}", file=sys.stderr)
|
||||||
|
|
||||||
print("Comment posted/updated successfully", file=sys.stderr)
|
print("Comment posted/updated successfully", file=sys.stderr)
|
||||||
return True
|
|
||||||
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
print(f"Error posting/updating comment: {e}", file=sys.stderr)
|
|
||||||
if e.stderr:
|
|
||||||
print(
|
|
||||||
f"stderr: {e.stderr.decode() if isinstance(e.stderr, bytes) else e.stderr}",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
if e.stdout:
|
|
||||||
print(
|
|
||||||
f"stdout: {e.stdout.decode() if isinstance(e.stdout, bytes) else e.stdout}",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
|
@@ -27,6 +27,11 @@ sys.path.insert(0, str(Path(__file__).parent.parent))
|
|||||||
# pylint: disable=wrong-import-position
|
# pylint: disable=wrong-import-position
|
||||||
from script.ci_helpers import write_github_output
|
from script.ci_helpers import write_github_output
|
||||||
|
|
||||||
|
# Regex patterns for extracting memory usage from PlatformIO output
|
||||||
|
_RAM_PATTERN = re.compile(r"RAM:\s+\[.*?\]\s+\d+\.\d+%\s+\(used\s+(\d+)\s+bytes")
|
||||||
|
_FLASH_PATTERN = re.compile(r"Flash:\s+\[.*?\]\s+\d+\.\d+%\s+\(used\s+(\d+)\s+bytes")
|
||||||
|
_BUILD_PATH_PATTERN = re.compile(r"Build path: (.+)")
|
||||||
|
|
||||||
|
|
||||||
def extract_from_compile_output(
|
def extract_from_compile_output(
|
||||||
output_text: str,
|
output_text: str,
|
||||||
@@ -42,7 +47,7 @@ def extract_from_compile_output(
|
|||||||
Flash: [=== ] 34.0% (used 348511 bytes from 1023984 bytes)
|
Flash: [=== ] 34.0% (used 348511 bytes from 1023984 bytes)
|
||||||
|
|
||||||
Also extracts build directory from lines like:
|
Also extracts build directory from lines like:
|
||||||
INFO Deleting /path/to/build/.esphome/build/componenttestesp8266ard/.pioenvs
|
INFO Compiling app... Build path: /path/to/build
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
output_text: Compile output text (may contain multiple builds)
|
output_text: Compile output text (may contain multiple builds)
|
||||||
@@ -51,12 +56,8 @@ def extract_from_compile_output(
|
|||||||
Tuple of (total_ram_bytes, total_flash_bytes, build_dir) or (None, None, None) if not found
|
Tuple of (total_ram_bytes, total_flash_bytes, build_dir) or (None, None, None) if not found
|
||||||
"""
|
"""
|
||||||
# Find all RAM and Flash matches (may be multiple builds)
|
# Find all RAM and Flash matches (may be multiple builds)
|
||||||
ram_matches = re.findall(
|
ram_matches = _RAM_PATTERN.findall(output_text)
|
||||||
r"RAM:\s+\[.*?\]\s+\d+\.\d+%\s+\(used\s+(\d+)\s+bytes", output_text
|
flash_matches = _FLASH_PATTERN.findall(output_text)
|
||||||
)
|
|
||||||
flash_matches = re.findall(
|
|
||||||
r"Flash:\s+\[.*?\]\s+\d+\.\d+%\s+\(used\s+(\d+)\s+bytes", output_text
|
|
||||||
)
|
|
||||||
|
|
||||||
if not ram_matches or not flash_matches:
|
if not ram_matches or not flash_matches:
|
||||||
return None, None, None
|
return None, None, None
|
||||||
|
Reference in New Issue
Block a user