1
0
mirror of https://github.com/esphome/esphome.git synced 2025-10-21 19:23:45 +01:00
This commit is contained in:
J. Nick Koston
2025-10-17 14:52:07 -10:00
parent f87c969b43
commit e2101f5a20
2 changed files with 82 additions and 61 deletions

View File

@@ -564,26 +564,14 @@ jobs:
echo "Compiling with test_build_components.py..." echo "Compiling with test_build_components.py..."
# Find most recent build directory for detailed analysis # Run build and extract memory with auto-detection of build directory for detailed analysis
build_dir=$(find ~/.esphome/build -type d -maxdepth 1 -mindepth 1 -printf '%T@ %p\n' 2>/dev/null | sort -rn | head -1 | cut -d' ' -f2- || echo "")
# Run build and extract memory, with optional detailed analysis
if [ -n "$build_dir" ]; then
python script/test_build_components.py \ python script/test_build_components.py \
-e compile \ -e compile \
-c "$component_list" \ -c "$component_list" \
-t "$platform" 2>&1 | \ -t "$platform" 2>&1 | \
python script/ci_memory_impact_extract.py \ python script/ci_memory_impact_extract.py \
--output-env \ --output-env \
--build-dir "$build_dir" \
--output-json memory-analysis-target.json --output-json memory-analysis-target.json
else
python script/test_build_components.py \
-e compile \
-c "$component_list" \
-t "$platform" 2>&1 | \
python script/ci_memory_impact_extract.py --output-env
fi
- name: Upload memory analysis JSON - name: Upload memory analysis JSON
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with: with:
@@ -631,26 +619,14 @@ jobs:
echo "Compiling with test_build_components.py..." echo "Compiling with test_build_components.py..."
# Find most recent build directory for detailed analysis # Run build and extract memory with auto-detection of build directory for detailed analysis
build_dir=$(find ~/.esphome/build -type d -maxdepth 1 -mindepth 1 -printf '%T@ %p\n' 2>/dev/null | sort -rn | head -1 | cut -d' ' -f2- || echo "")
# Run build and extract memory, with optional detailed analysis
if [ -n "$build_dir" ]; then
python script/test_build_components.py \ python script/test_build_components.py \
-e compile \ -e compile \
-c "$component_list" \ -c "$component_list" \
-t "$platform" 2>&1 | \ -t "$platform" 2>&1 | \
python script/ci_memory_impact_extract.py \ python script/ci_memory_impact_extract.py \
--output-env \ --output-env \
--build-dir "$build_dir" \
--output-json memory-analysis-pr.json --output-json memory-analysis-pr.json
else
python script/test_build_components.py \
-e compile \
-c "$component_list" \
-t "$platform" 2>&1 | \
python script/ci_memory_impact_extract.py --output-env
fi
- name: Upload memory analysis JSON - name: Upload memory analysis JSON
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with: with:

View File

@@ -28,8 +28,10 @@ sys.path.insert(0, str(Path(__file__).parent.parent))
from script.ci_helpers import write_github_output from script.ci_helpers import write_github_output
def extract_from_compile_output(output_text: str) -> tuple[int | None, int | None]: def extract_from_compile_output(
"""Extract memory usage from PlatformIO compile output. output_text: str,
) -> tuple[int | None, int | None, str | None]:
"""Extract memory usage and build directory from PlatformIO compile output.
Supports multiple builds (for component groups or isolated components). Supports multiple builds (for component groups or isolated components).
When test_build_components.py creates multiple builds, this sums the When test_build_components.py creates multiple builds, this sums the
@@ -39,11 +41,14 @@ def extract_from_compile_output(output_text: str) -> tuple[int | None, int | Non
RAM: [==== ] 36.1% (used 29548 bytes from 81920 bytes) RAM: [==== ] 36.1% (used 29548 bytes from 81920 bytes)
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:
INFO Deleting /path/to/build/.esphome/build/componenttestesp8266ard/.pioenvs
Args: Args:
output_text: Compile output text (may contain multiple builds) output_text: Compile output text (may contain multiple builds)
Returns: Returns:
Tuple of (total_ram_bytes, total_flash_bytes) or (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 = re.findall(
@@ -54,13 +59,21 @@ def extract_from_compile_output(output_text: str) -> tuple[int | None, int | Non
) )
if not ram_matches or not flash_matches: if not ram_matches or not flash_matches:
return None, None return None, None, None
# Sum all builds (handles multiple component groups) # Sum all builds (handles multiple component groups)
total_ram = sum(int(match) for match in ram_matches) total_ram = sum(int(match) for match in ram_matches)
total_flash = sum(int(match) for match in flash_matches) total_flash = sum(int(match) for match in flash_matches)
return total_ram, total_flash # Extract build directory from ESPHome's delete messages
# Look for: INFO Deleting /path/to/build/.esphome/build/componenttest.../.pioenvs
build_dir = None
if match := re.search(
r"INFO Deleting (.+/\.esphome/build/componenttest[^/]+)/\.pioenvs", output_text
):
build_dir = match.group(1)
return total_ram, total_flash, build_dir
def run_detailed_analysis(build_dir: str) -> dict | None: def run_detailed_analysis(build_dir: str) -> dict | None:
@@ -94,18 +107,31 @@ def run_detailed_analysis(build_dir: str) -> dict | None:
print(f"firmware.elf not found in {build_dir}", file=sys.stderr) print(f"firmware.elf not found in {build_dir}", file=sys.stderr)
return None return None
# Find idedata.json # Find idedata.json - check multiple locations
device_name = build_path.name device_name = build_path.name
idedata_path = Path.home() / ".esphome" / "idedata" / f"{device_name}.json" idedata_candidates = [
# In .pioenvs for test builds
build_path / ".pioenvs" / device_name / "idedata.json",
# In .esphome/idedata for regular builds
Path.home() / ".esphome" / "idedata" / f"{device_name}.json",
# Check parent directories for .esphome/idedata (for test_build_components)
build_path.parent.parent.parent / "idedata" / f"{device_name}.json",
]
idedata = None idedata = None
for idedata_path in idedata_candidates:
if idedata_path.exists(): if idedata_path.exists():
try: try:
with open(idedata_path, encoding="utf-8") as f: with open(idedata_path, encoding="utf-8") as f:
raw_data = json.load(f) raw_data = json.load(f)
idedata = IDEData(raw_data) idedata = IDEData(raw_data)
print(f"Loaded idedata from: {idedata_path}", file=sys.stderr)
break
except (json.JSONDecodeError, OSError) as e: except (json.JSONDecodeError, OSError) as e:
print(f"Warning: Failed to load idedata: {e}", file=sys.stderr) print(
f"Warning: Failed to load idedata from {idedata_path}: {e}",
file=sys.stderr,
)
try: try:
analyzer = MemoryAnalyzer(elf_path, idedata=idedata) analyzer = MemoryAnalyzer(elf_path, idedata=idedata)
@@ -156,20 +182,26 @@ def main() -> int:
) )
parser.add_argument( parser.add_argument(
"--build-dir", "--build-dir",
help="Optional build directory for detailed memory analysis", help="Optional build directory for detailed memory analysis (overrides auto-detection)",
) )
parser.add_argument( parser.add_argument(
"--output-json", "--output-json",
help="Optional path to save detailed analysis JSON", help="Optional path to save detailed analysis JSON",
) )
parser.add_argument(
"--output-build-dir",
help="Optional path to write the detected build directory",
)
args = parser.parse_args() args = parser.parse_args()
# Read compile output from stdin # Read compile output from stdin
compile_output = sys.stdin.read() compile_output = sys.stdin.read()
# Extract memory usage # Extract memory usage and build directory
ram_bytes, flash_bytes = extract_from_compile_output(compile_output) ram_bytes, flash_bytes, detected_build_dir = extract_from_compile_output(
compile_output
)
if ram_bytes is None or flash_bytes is None: if ram_bytes is None or flash_bytes is None:
print("Failed to extract memory usage from compile output", file=sys.stderr) print("Failed to extract memory usage from compile output", file=sys.stderr)
@@ -200,11 +232,24 @@ def main() -> int:
print(f"Total RAM: {ram_bytes} bytes", file=sys.stderr) print(f"Total RAM: {ram_bytes} bytes", file=sys.stderr)
print(f"Total Flash: {flash_bytes} bytes", file=sys.stderr) print(f"Total Flash: {flash_bytes} bytes", file=sys.stderr)
# Run detailed analysis if build directory provided # Determine which build directory to use (explicit arg overrides auto-detection)
build_dir = args.build_dir or detected_build_dir
if detected_build_dir:
print(f"Detected build directory: {detected_build_dir}", file=sys.stderr)
# Write build directory to file if requested
if args.output_build_dir and build_dir:
build_dir_path = Path(args.output_build_dir)
build_dir_path.parent.mkdir(parents=True, exist_ok=True)
build_dir_path.write_text(build_dir)
print(f"Wrote build directory to {args.output_build_dir}", file=sys.stderr)
# Run detailed analysis if build directory available
detailed_analysis = None detailed_analysis = None
if args.build_dir: if build_dir:
print(f"Running detailed analysis on {args.build_dir}", file=sys.stderr) print(f"Running detailed analysis on {build_dir}", file=sys.stderr)
detailed_analysis = run_detailed_analysis(args.build_dir) detailed_analysis = run_detailed_analysis(build_dir)
# Save JSON output if requested # Save JSON output if requested
if args.output_json: if args.output_json: