dweiss commented on code in PR #15096: URL: https://github.com/apache/lucene/pull/15096#discussion_r2286228568
########## build-tools/build-infra/src/main/java/org/apache/lucene/gradle/plugins/hacks/DumpGradleStateOnStalledBuildsPlugin.java: ########## @@ -0,0 +1,282 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.lucene.gradle.plugins.hacks; + +import java.lang.management.LockInfo; +import java.lang.management.ManagementFactory; +import java.lang.management.MonitorInfo; +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; +import java.time.Duration; +import java.time.Instant; +import java.util.Locale; +import java.util.concurrent.TimeUnit; +import javax.inject.Inject; +import org.apache.lucene.gradle.SuppressForbidden; +import org.apache.lucene.gradle.plugins.LuceneGradlePlugin; +import org.gradle.api.Project; +import org.gradle.api.logging.Logger; +import org.gradle.api.logging.Logging; +import org.gradle.api.provider.Provider; +import org.gradle.api.services.BuildService; +import org.gradle.api.services.BuildServiceParameters; +import org.gradle.build.event.BuildEventsListenerRegistry; +import org.gradle.tooling.events.FinishEvent; +import org.gradle.tooling.events.OperationCompletionListener; + +/** + * This tracks the build progress (looking at task completion updates) and emits some diagnostics if + * no such progress can be observed. + */ +public class DumpGradleStateOnStalledBuildsPlugin extends LuceneGradlePlugin { + /** Check for stalled progress every minute. */ + static final long checkIntervalMillis = TimeUnit.MINUTES.toMillis(1); + + /** + * After the no-progress is detected, dump everything every 5 minutes (unless progress is + * restored). + */ + static final long recheckIntervalMillis = TimeUnit.MINUTES.toMillis(5); + + /** Consider 3 minutes of no-updates as no-progress. */ + static final long minDurationOfNoUpdates = TimeUnit.MINUTES.toNanos(3); + + public static final String OPT_TRACK_GRADLE_STATE = "lucene.trackGradleState"; + + private final BuildEventsListenerRegistry listenerRegistry; + + @Inject + public DumpGradleStateOnStalledBuildsPlugin(BuildEventsListenerRegistry listenerRegistry) { + this.listenerRegistry = listenerRegistry; + } + + @Override + public void apply(Project project) { + applicableToRootProjectOnly(project); + + var buildOptions = getBuildOptions(project); + var trackStateOption = + buildOptions.addBooleanOption( + OPT_TRACK_GRADLE_STATE, + "Track build progress and dump gradle jvm state diagnostics in case of no progress.", + false); + + if (!trackStateOption.get()) { + return; + } + + Provider<TrackProgressService> service = + project + .getGradle() + .getSharedServices() + .registerIfAbsent("trackProgressService", TrackProgressService.class, _ -> {}); + + listenerRegistry.onTaskCompletion(service); + } + + /** + * This service collects per-task durations and prints a summary when closed (at the end of the + * build). + */ + public abstract static class TrackProgressService + implements BuildService<BuildServiceParameters.None>, + OperationCompletionListener, + AutoCloseable { + private static final Logger LOGGER = Logging.getLogger(TrackProgressService.class); + + /** Execution time of all successful tasks. Keys are task paths, values are in millis. */ + private volatile long lastUpdateNanos = System.nanoTime(); + + private final Thread trackerThread; + private volatile boolean stop; + + public TrackProgressService() { + this.trackerThread = + new Thread("gradle-progress-tracker") { + @Override + public void run() { + while (!stop) { + try { + sleepNow(checkIntervalMillis); + + long noUpdateTime = System.nanoTime() - lastUpdateNanos; + if (noUpdateTime >= minDurationOfNoUpdates) { + dumpGradleState(noUpdateTime); + sleepNow(recheckIntervalMillis); + } + } catch (InterruptedException _) { + // ignore. + } + } + } + + @SuppressForbidden(reason = "legitimate sleep here.") + private void sleepNow(long millis) throws InterruptedException { + Thread.sleep(millis); + } + }; + trackerThread.setDaemon(true); + trackerThread.start(); + } + + private void dumpGradleState(long noUpdateTimeNanos) { + LOGGER.warn( + "{}: Gradle panic: no status update in {}" + " seconds?", + Instant.now(), + Duration.ofNanos(noUpdateTimeNanos).toSeconds()); + + String jvmDiags; + try { + jvmDiags = collectJvmDiagnostics(); + } catch (Throwable t) { + jvmDiags = "Failed to collect jvm diagnostics: " + t.getMessage(); + } + LOGGER.warn(jvmDiags); + } + + /** Collects heap stats and a full thread dump into a single String. */ + public static String collectJvmDiagnostics() { + StringBuilder sb = new StringBuilder(32_768); + + sb.append("====== GRADLE JVM DIAGNOSTICS ======\n"); Review Comment: I've generated this method using AI. The rest is written by hand. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: issues-unsubscr...@lucene.apache.org For queries about this service, please contact Infrastructure at: us...@infra.apache.org --------------------------------------------------------------------- To unsubscribe, e-mail: issues-unsubscr...@lucene.apache.org For additional commands, e-mail: issues-h...@lucene.apache.org