diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/IPhoneBuilder.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/IPhoneBuilder.java index 13184339a9..973c78e39b 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/IPhoneBuilder.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/IPhoneBuilder.java @@ -2194,6 +2194,20 @@ public void stop() { summary.append("----------------------\n"); summary.append(String.format("%-40s : %d ms", "Total Time", totalDuration)); log(summary.toString()); + String statsFile = System.getenv("CN1_BUILD_STATS_FILE"); + if (statsFile != null && statsFile.length() > 0) { + try { + File f = new File(statsFile); + if (f.getParentFile() != null) { + f.getParentFile().mkdirs(); + } + try (FileOutputStream fos = new FileOutputStream(f)) { + fos.write(summary.toString().getBytes("UTF-8")); + } + } catch (Exception ex) { + log("Failed to write build stats to file " + statsFile + ": " + ex.getMessage()); + } + } } } diff --git a/scripts/build-ios-app.sh b/scripts/build-ios-app.sh index e88710f21b..4990495e80 100755 --- a/scripts/build-ios-app.sh +++ b/scripts/build-ios-app.sh @@ -63,6 +63,12 @@ xcodebuild -version bia_log "Building iOS Xcode project using Codename One port" cd $APP_DIR VM_START=$(date +%s) + +ARTIFACTS_DIR="${ARTIFACTS_DIR:-$REPO_ROOT/artifacts}" +mkdir -p "$ARTIFACTS_DIR" + +export CN1_BUILD_STATS_FILE="$ARTIFACTS_DIR/iphone-builder-stats.txt" + ./mvnw package \ -DskipTests \ -Dcodename1.platform=ios \ @@ -73,11 +79,19 @@ VM_END=$(date +%s) VM_TIME=$((VM_END - VM_START)) cd ../.. -ARTIFACTS_DIR="${ARTIFACTS_DIR:-$REPO_ROOT/artifacts}" -mkdir -p "$ARTIFACTS_DIR" echo "$VM_TIME" > "$ARTIFACTS_DIR/vm_time.txt" bia_log "VM translation time: ${VM_TIME}s (saved to $ARTIFACTS_DIR/vm_time.txt)" +# Calculate Maven overhead if stats file exists +if [ -f "$ARTIFACTS_DIR/iphone-builder-stats.txt" ]; then + TOTAL_BUILDER_TIME_MS=$(grep "Total Time" "$ARTIFACTS_DIR/iphone-builder-stats.txt" | awk -F ':' '{print $2}' | tr -d ' ms') + if [ -n "$TOTAL_BUILDER_TIME_MS" ]; then + TOTAL_BUILDER_TIME_SEC=$((TOTAL_BUILDER_TIME_MS / 1000)) + MAVEN_OVERHEAD=$((VM_TIME - TOTAL_BUILDER_TIME_SEC)) + echo "Maven Overhead : ${MAVEN_OVERHEAD}000 ms" >> "$ARTIFACTS_DIR/iphone-builder-stats.txt" + fi +fi + IOS_TARGET_DIR="$APP_DIR/ios/target" if [ ! -d "$IOS_TARGET_DIR" ]; then bia_log "iOS target directory not found at $IOS_TARGET_DIR" >&2 @@ -101,6 +115,7 @@ bia_log "Found generated iOS project at $PROJECT_DIR" # CocoaPods (project contains a Podfile but usually empty — fine) if [ -f "$PROJECT_DIR/Podfile" ]; then bia_log "Installing CocoaPods dependencies" + POD_START=$(date +%s) ( cd "$PROJECT_DIR" if ! pod install --repo-update; then @@ -108,6 +123,9 @@ if [ -f "$PROJECT_DIR/Podfile" ]; then pod install fi ) + POD_END=$(date +%s) + POD_TIME=$((POD_END - POD_START)) + echo "CocoaPods Install (Script) : ${POD_TIME}000 ms" >> "$ARTIFACTS_DIR/iphone-builder-stats.txt" else bia_log "Podfile not found in generated project; skipping pod install" fi diff --git a/scripts/common/java/RenderScreenshotReport.java b/scripts/common/java/RenderScreenshotReport.java index b245dd7390..10d72106ae 100644 --- a/scripts/common/java/RenderScreenshotReport.java +++ b/scripts/common/java/RenderScreenshotReport.java @@ -32,11 +32,40 @@ public static void main(String[] args) throws Exception { CoverageSummary coverage = loadCoverage(arguments.coverageSummary, arguments.coverageHtmlUrl); - SummaryAndComment output = buildSummaryAndComment(data, title, marker, successMessage, coverage, arguments.vmTime, arguments.compilationTime); + Map extraStats = new LinkedHashMap<>(); + if (arguments.extraStats != null) { + for (Path p : arguments.extraStats) { + if (Files.isRegularFile(p)) { + parseStatsFile(p, extraStats); + } + } + } + + SummaryAndComment output = buildSummaryAndComment(data, title, marker, successMessage, coverage, arguments.vmTime, arguments.compilationTime, extraStats); writeLines(arguments.summaryOut, output.summaryLines); writeLines(arguments.commentOut, output.commentLines); } + private static void parseStatsFile(Path p, Map extraStats) { + try { + List lines = Files.readAllLines(p, StandardCharsets.UTF_8); + for (String line : lines) { + line = line.trim(); + if (line.isEmpty() || line.startsWith("-")) { + continue; + } + int colon = line.indexOf(':'); + if (colon > 0) { + String key = line.substring(0, colon).trim(); + String val = line.substring(colon + 1).trim(); + extraStats.put(key, val); + } + } + } catch (IOException e) { + System.err.println("Failed to read stats file " + p + ": " + e.getMessage()); + } + } + private static void writeLines(Path path, List lines) throws IOException { StringBuilder sb = new StringBuilder(); for (int i = 0; i < lines.size(); i++) { @@ -51,7 +80,7 @@ private static void writeLines(Path path, List lines) throws IOException Files.writeString(path, sb.toString(), StandardCharsets.UTF_8); } - private static SummaryAndComment buildSummaryAndComment(Map data, String title, String marker, String successMessage, CoverageSummary coverage, Long vmTime, Long compilationTime) { + private static SummaryAndComment buildSummaryAndComment(Map data, String title, String marker, String successMessage, CoverageSummary coverage, Long vmTime, Long compilationTime, Map extraStats) { List summaryLines = new ArrayList<>(); List commentLines = new ArrayList<>(); Object resultsObj = data.get("results"); @@ -161,7 +190,7 @@ private static SummaryAndComment buildSummaryAndComment(Map data } // Add benchmark results at the end - appendBenchmarkResults(commentLines, vmTime, compilationTime); + appendBenchmarkResults(commentLines, vmTime, compilationTime, extraStats); if (commentLines.isEmpty() || (commentLines.size() == 1 && commentLines.get(0).isEmpty())) { // This fallback block might be redundant now, but kept for safety. @@ -231,8 +260,8 @@ private static void appendCoverageSummary(List summaryLines, CoverageSum } } - private static void appendBenchmarkResults(List commentLines, Long vmTime, Long compilationTime) { - if (vmTime == null && compilationTime == null) { + private static void appendBenchmarkResults(List commentLines, Long vmTime, Long compilationTime, Map extraStats) { + if (vmTime == null && compilationTime == null && (extraStats == null || extraStats.isEmpty())) { return; } if (!commentLines.isEmpty() && !commentLines.get(commentLines.size() - 1).isEmpty()) { @@ -246,6 +275,15 @@ private static void appendBenchmarkResults(List commentLines, Long vmTim if (compilationTime != null) { commentLines.add(String.format("- **Compilation Time:** %d seconds", compilationTime)); } + if (extraStats != null && !extraStats.isEmpty()) { + commentLines.add(""); + commentLines.add("#### Detailed Performance Metrics"); + commentLines.add("| Metric | Duration |"); + commentLines.add("| --- | --- |"); + for (Map.Entry entry : extraStats.entrySet()) { + commentLines.add(String.format("| %s | %s |", entry.getKey(), entry.getValue())); + } + } commentLines.add(""); } @@ -450,8 +488,9 @@ private static class Arguments { final String coverageHtmlUrl; final Long vmTime; final Long compilationTime; + final List extraStats; - private Arguments(Path compareJson, Path commentOut, Path summaryOut, String marker, String title, String successMessage, Path coverageSummary, String coverageHtmlUrl, Long vmTime, Long compilationTime) { + private Arguments(Path compareJson, Path commentOut, Path summaryOut, String marker, String title, String successMessage, Path coverageSummary, String coverageHtmlUrl, Long vmTime, Long compilationTime, List extraStats) { this.compareJson = compareJson; this.commentOut = commentOut; this.summaryOut = summaryOut; @@ -462,6 +501,7 @@ private Arguments(Path compareJson, Path commentOut, Path summaryOut, String mar this.coverageHtmlUrl = coverageHtmlUrl; this.vmTime = vmTime; this.compilationTime = compilationTime; + this.extraStats = extraStats; } static Arguments parse(String[] args) { @@ -475,6 +515,7 @@ static Arguments parse(String[] args) { String coverageHtmlUrl = null; Long vmTime = null; Long compilationTime = null; + List extraStats = new ArrayList<>(); for (int i = 0; i < args.length; i++) { String arg = args[i]; switch (arg) { @@ -558,6 +599,13 @@ static Arguments parse(String[] args) { return null; } } + case "--extra-stats" -> { + if (++i >= args.length) { + System.err.println("Missing value for --extra-stats"); + return null; + } + extraStats.add(Path.of(args[i])); + } default -> { System.err.println("Unknown argument: " + arg); return null; @@ -568,7 +616,7 @@ static Arguments parse(String[] args) { System.err.println("--compare-json, --comment-out, and --summary-out are required"); return null; } - return new Arguments(compare, comment, summary, marker, title, successMessage, coverageSummary, coverageHtmlUrl, vmTime, compilationTime); + return new Arguments(compare, comment, summary, marker, title, successMessage, coverageSummary, coverageHtmlUrl, vmTime, compilationTime, extraStats); } } diff --git a/scripts/lib/cn1ss.sh b/scripts/lib/cn1ss.sh index e054bd16dc..d62921b28c 100644 --- a/scripts/lib/cn1ss.sh +++ b/scripts/lib/cn1ss.sh @@ -361,6 +361,15 @@ cn1ss_process_and_report() { render_args+=(--compilation-time "$CN1SS_COMPILATION_TIME") fi + # Pass any stats files found in artifacts + if [ -n "$artifacts_dir" ] && [ -d "$artifacts_dir" ]; then + for stats_file in "$artifacts_dir"/iphone-builder-stats.txt "$artifacts_dir"/ios-test-stats.txt; do + if [ -f "$stats_file" ]; then + render_args+=(--extra-stats "$stats_file") + fi + done + fi + if ! cn1ss_java_run "$CN1SS_RENDER_CLASS" "${render_args[@]}"; then cn1ss_log "FATAL: Failed to render screenshot summary/comment" return 14 diff --git a/scripts/run-ios-ui-tests.sh b/scripts/run-ios-ui-tests.sh index 2b319798a4..20d2490376 100755 --- a/scripts/run-ios-ui-tests.sh +++ b/scripts/run-ios-ui-tests.sh @@ -335,8 +335,11 @@ SIM_DESTINATION="$(normalize_destination "$SIM_DESTINATION")" SIM_UDID="$(printf '%s\n' "$SIM_DESTINATION" | sed -n 's/.*id=\([^,]*\).*/\1/p' | tr -d '\r[:space:]')" if [ -n "$SIM_UDID" ]; then ri_log "Booting simulator $SIM_UDID" + BOOT_START=$(date +%s) xcrun simctl boot "$SIM_UDID" >/dev/null 2>&1 || true xcrun simctl bootstatus "$SIM_UDID" -b + BOOT_END=$(date +%s) + echo "Simulator Boot : $(( (BOOT_END - BOOT_START) * 1000 )) ms" >> "$ARTIFACTS_DIR/ios-test-stats.txt" SIM_DESTINATION="id=$SIM_UDID" fi ri_log "Running DeviceRunner on destination '$SIM_DESTINATION'" @@ -435,8 +438,11 @@ APP_PROCESS_NAME="${WRAPPER_NAME%.app}" if [ -n "$SIM_DEVICE_ID" ]; then ri_log "Booting simulator $SIM_DEVICE_ID" + BOOT_START=$(date +%s) xcrun simctl boot "$SIM_DEVICE_ID" >/dev/null 2>&1 || true xcrun simctl bootstatus "$SIM_DEVICE_ID" -b + BOOT_END=$(date +%s) + echo "Simulator Boot (Run) : $(( (BOOT_END - BOOT_START) * 1000 )) ms" >> "$ARTIFACTS_DIR/ios-test-stats.txt" else ri_log "Warning: simulator UDID not resolved; relying on default booted device" xcrun simctl bootstatus booted -b || true @@ -470,25 +476,36 @@ APP_PROCESS_NAME="${WRAPPER_NAME%.app}" sleep 2 ri_log "Installing simulator app bundle" + INSTALL_START=$(date +%s) if [ -n "$SIM_DEVICE_ID" ]; then if ! xcrun simctl install "$SIM_DEVICE_ID" "$APP_BUNDLE_PATH"; then ri_log "FATAL: simctl install failed" exit 11 fi + INSTALL_END=$(date +%s) + + LAUNCH_START=$(date +%s) if ! xcrun simctl launch "$SIM_DEVICE_ID" "$BUNDLE_IDENTIFIER" >/dev/null 2>&1; then ri_log "FATAL: simctl launch failed" exit 11 fi + LAUNCH_END=$(date +%s) else if ! xcrun simctl install booted "$APP_BUNDLE_PATH"; then ri_log "FATAL: simctl install failed" exit 11 fi + INSTALL_END=$(date +%s) + + LAUNCH_START=$(date +%s) if ! xcrun simctl launch booted "$BUNDLE_IDENTIFIER" >/dev/null 2>&1; then ri_log "FATAL: simctl launch failed" exit 11 fi + LAUNCH_END=$(date +%s) fi + echo "App Install : $(( (INSTALL_END - INSTALL_START) * 1000 )) ms" >> "$ARTIFACTS_DIR/ios-test-stats.txt" + echo "App Launch : $(( (LAUNCH_END - LAUNCH_START) * 1000 )) ms" >> "$ARTIFACTS_DIR/ios-test-stats.txt" END_MARKER="CN1SS:SUITE:FINISHED" TIMEOUT_SECONDS=300 @@ -506,6 +523,8 @@ while true; do fi sleep 5 done +END_TIME=$(date +%s) +echo "Test Execution : $(( (END_TIME - START_TIME) * 1000 )) ms" >> "$ARTIFACTS_DIR/ios-test-stats.txt" sleep 3