Added plugin repo-repack-tracker Added a groovy script plugin to check if a repack process is running for a list of projects, exposing as a Gerrit metric for each project. Change-Id: I940777e5503b05d0c070bed9fd72b72402999b31
diff --git a/admin/README.md b/admin/README.md index 7c0ba9f..d798ab2 100644 --- a/admin/README.md +++ b/admin/README.md
@@ -12,3 +12,4 @@ * [readonly-1.0.groovy](/admin/readonly-1.0.groovy) - Set all Gerrit projects in read-only mode during maintenance * [stale-packed-refs-1.0.groovy](/admin/stale-packed-refs-1.0.groovy) - Check all specified projects and expose metric with age of `packed-refs.lock` files * [track-and-disable-inactive-users.groovy](/admin/track-and-disable-inactive-users.groovy) - Tracks users login in `track-active-users_cache` and automatically disables inactive users +* [repo-repack-tracker-1.0.groovy](/admin/repo-repack-tracker-1.0.groovy) - Check if a repack process is running for a list of projects
diff --git a/admin/repo-repack-tracker-1.0.groovy b/admin/repo-repack-tracker-1.0.groovy new file mode 100644 index 0000000..6a4553a --- /dev/null +++ b/admin/repo-repack-tracker-1.0.groovy
@@ -0,0 +1,142 @@ +// Copyright (C) 2025 The Android Open Source Project +// +// Licensed 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. + +import com.google.common.flogger.FluentLogger +import com.google.gerrit.entities.Project +import com.google.gerrit.extensions.annotations.* +import com.google.gerrit.extensions.events.LifecycleListener +import com.google.gerrit.lifecycle.LifecycleModule +import com.google.gerrit.metrics.* +import com.google.gerrit.server.config.* +import com.google.gerrit.server.git.LocalDiskRepositoryManager +import com.google.inject.Singleton +import org.eclipse.jgit.lib.Constants + +import javax.inject.Inject +import java.util.concurrent.TimeUnit + +import static groovy.io.FileType.FILES + +@Singleton +@Listen +class RepoRepackTracker implements LifecycleListener { + + private static final FluentLogger logger = FluentLogger.forEnclosingClass() + private static final NAME = "is_repack_running_per_project" + private static final DESCRIPTION = "Check repack running for the project" + private static final GIT_PACK_FOLDER = "objects/pack" + private static final TMP_PREFIX = "tmp_" + private static final TMP_SUFFIX = ".tmp" + + @Inject + @PluginName + String pluginName + @Inject + PluginConfigFactory configFactory + @Inject + MetricMaker metrics + @Inject + LocalDiskRepositoryManager repoMgr + + private def tmpFilter = ~/^(${TMP_PREFIX}.*|.*${TMP_SUFFIX})$/ + private long considerStaleAfterMs + CallbackMetric1<String, Long> projectsAndGcMetric + List<String> projects + + @Override + void start() { + PluginConfig pluginConfig = configFactory.getFromGerritConfig(pluginName) + long considerStaleAfter = ConfigUtil.getTimeUnit( + pluginConfig.getString("considerStaleAfter", "60 minutes"), + 60L, + TimeUnit.MINUTES) + considerStaleAfterMs = TimeUnit.MILLISECONDS.convert(considerStaleAfter, TimeUnit.MINUTES) + + projects = pluginConfig.getStringList("project") + projectsAndGcMetric = createCallbackMetric(NAME, DESCRIPTION) + addMetricsTrigger(projectsAndGcMetric, projects) + + logger.atInfo().log("Plugin %s started (staleAfter %d minutes)", + pluginName, + considerStaleAfter) + } + + CallbackMetric1<String, Long> createCallbackMetric(String name, String description) { + metrics.newCallbackMetric( + name, + Long.class, + new Description(description).setGauge(), + Field.ofProjectName("repository_name") + .description(description) + .build() + ) + } + + void addMetricsTrigger(CallbackMetric1<String, Long> projectsAndGcMetric, List<String> projects) { + metrics.newTrigger( + projectsAndGcMetric, { + if (projects.isEmpty()) { + projectsAndGcMetric.forceCreate("") + } else { + projects.each { e -> + projectsAndGcMetric.set(e, checkRepackingRunningForProject(e)) + } + projectsAndGcMetric.prune() + } + }) + } + + long checkRepackingRunningForProject(String projectName) { + def isRepackRunning = 0L + try { + def repoDir = getRepoDir(projectName) + def packDir = new File(repoDir, GIT_PACK_FOLDER) + if (packDir.exists() && hasRepackTmpFiles(packDir)) { + isRepackRunning = 1L + } + } catch (Exception e) { + logger.atSevere().withCause(e).log("Could not check project %s", projectName) + } + isRepackRunning + } + + File getRepoDir(String projectName) { + def name = Project.nameKey(projectName) + def path = repoMgr.getBasePath(name) + return path.resolve("${name.get()}${Constants.DOT_GIT_EXT}").toFile() + } + + boolean hasRepackTmpFiles(folder) { + def modifiedCutoffTime = System.currentTimeMillis() - considerStaleAfterMs + def tmpFileFound = false + + folder.traverse(type: FILES, nameFilter: tmpFilter) { file -> + if (file.lastModified() >= modifiedCutoffTime) { + tmpFileFound = true + } + } + return tmpFileFound + } + + @Override + void stop() {} +} + +class RepoRepackTrackerModule extends LifecycleModule { + protected void configure() { + listener().to(RepoRepackTracker) + } +} + +modules = [RepoRepackTrackerModule]
diff --git a/admin/repo-repack-tracker.md b/admin/repo-repack-tracker.md new file mode 100644 index 0000000..00f4b96 --- /dev/null +++ b/admin/repo-repack-tracker.md
@@ -0,0 +1,47 @@ +Repo Repack Tracker +============================== + +DESCRIPTION +----------- +Check for each project configured if a repack process is running. + +Configuration +========================= + +The repo-repack-tracker plugin is configured in +$site_path/etc/gerrit.config` files, example: + +```text +[plugins "repo-repack-tracker"] + considerStaleAfter = 1h + project = test +``` + +Configuration parameters +--------------------- + +======= +```plugins.repo-repack-tracker.considerStaleAfter``` +: If any of the files checked for determining if the repack is running has the modified date older than this value, then +the repack is considered stale (not running). If a time unit suffix is not specified, `minutes` is assumed. + +Default: 1 hour. + +```plugins.repo-repack-tracker.project``` +: The name of the repository to check. + May be specified more than once to specify multiple projects, for example: + + ``` + project = foo + project = bar + ``` + +Metrics +--------------------- +Currently, the metrics exposed are the following: + +```groovy_repo_gc_tracker_is_repack_running_per_project_<repo_name>``` +: Indicates if the repack is currently running for the <repo_name>. +The <repo_name> is sanitised to prevent the introduction of invalid characters for a metric name and to remove +the risk of collisions (between the sanitized metric names). +Repack is considered running when its value is greater than 0 .