package ru.andreymarkelov.atlas.plugins.prombitbucketexporter.manager; import com.atlassian.bitbucket.permission.Permission; import com.atlassian.bitbucket.project.ProjectService; import com.atlassian.bitbucket.pull.PullRequest; import com.atlassian.bitbucket.pull.PullRequestSearchRequest; import com.atlassian.bitbucket.pull.PullRequestService; import com.atlassian.bitbucket.repository.Repository; import com.atlassian.bitbucket.repository.RepositoryService; import com.atlassian.bitbucket.user.SecurityService; import com.atlassian.bitbucket.util.Operation; import com.atlassian.bitbucket.util.Page; import com.atlassian.bitbucket.util.PageRequest; import com.atlassian.bitbucket.util.PageRequestImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import javax.annotation.Nonnull; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import static com.atlassian.bitbucket.pull.PullRequestState.OPEN; import static java.lang.Thread.MIN_PRIORITY; import static java.util.concurrent.Executors.defaultThreadFactory; import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor; public class ScheduledMetricEvaluatorImpl implements ScheduledMetricEvaluator, DisposableBean, InitializingBean { private static final Logger log = LoggerFactory.getLogger(ScheduledMetricEvaluator.class); /** * Bitbucket components. */ private final ScrapingSettingsManager scrapingSettingsManager; private final RepositoryService repositoryService; private final ProjectService projectService; private final PullRequestService pullRequestService; private final SecurityService securityService; /** * Scheduled executor to grab metrics. */ private final ScheduledExecutorService executorService; private final AtomicLong lastExecutionTimestamp; private final Lock lock; private final AtomicInteger totalProjects; private final AtomicInteger totalRepositories; private final AtomicLong totalPullRequests; private ScheduledFuture<?> scraper; public ScheduledMetricEvaluatorImpl( ScrapingSettingsManager scrapingSettingsManager, RepositoryService repositoryService, ProjectService projectService, PullRequestService pullRequestService, SecurityService securityService) { this.scrapingSettingsManager = scrapingSettingsManager; this.repositoryService = repositoryService; this.projectService = projectService; this.pullRequestService = pullRequestService; this.securityService = securityService; this.executorService = newSingleThreadScheduledExecutor(new ThreadFactory() { @Override public Thread newThread(@Nonnull Runnable r) { Thread thread = defaultThreadFactory().newThread(r); thread.setPriority(MIN_PRIORITY); return thread; } }); this.lastExecutionTimestamp = new AtomicLong(-1); this.totalProjects = new AtomicInteger(0); this.totalRepositories = new AtomicInteger(0); this.totalPullRequests = new AtomicLong(0); this.lock = new ReentrantLock(); } @Override public long getLastExecutionTimestamp() { return lastExecutionTimestamp.get(); } @Override public void restartScraping(int newDelay) { lock.lock(); try{ stopScraping(); startScraping(newDelay); } finally { lock.unlock(); } } @Override public void afterPropertiesSet() { lock.lock(); try { startScraping(scrapingSettingsManager.getDelay()); } finally { lock.unlock(); } } @Override public void destroy() { executorService.shutdown(); try { if (!executorService.awaitTermination(1, TimeUnit.SECONDS)) { executorService.shutdownNow(); } } catch (InterruptedException e) { executorService.shutdownNow(); } } @Override public long getTotalProjects() { return totalProjects.get(); } @Override public long getTotalRepositories() { return totalRepositories.get(); } @Override public long getTotalPullRequests() { return totalPullRequests.get(); } private void startScraping(int delay) { scraper = executorService.scheduleWithFixedDelay(new Runnable() { @Override public void run() { calculateTotalProjects(); calculateTotalRepositories(); calculateTotalPullRequests(); lastExecutionTimestamp.set(System.currentTimeMillis()); } }, 0, delay, TimeUnit.MINUTES); } private void calculateTotalProjects() { try { securityService.withPermission(Permission.ADMIN, "Read all projects").call(new Operation<Object, Throwable>() { @Override public Object perform() { totalProjects.set(projectService.findAllKeys().size()); return null; } }); } catch (Throwable th) { log.error("Cannot read all projects", th); } } private void calculateTotalRepositories() { try { securityService.withPermission(Permission.ADMIN, "Read all repositories").call(new Operation<Object, Throwable>() { @Override public Object perform() { int repositories = 0; PageRequest nextPage = new PageRequestImpl(0, 10000); do { Page<Repository> repositoryPage = repositoryService.findAll(nextPage); repositories += repositoryPage.getSize(); nextPage = repositoryPage.getNextPageRequest(); } while (nextPage != null); totalRepositories.set(repositories); return null; } }); } catch (Throwable th) { log.error("Cannot read all repositories", th); } } private void calculateTotalPullRequests() { try { securityService.withPermission(Permission.ADMIN, "Read all pull requests").call(new Operation<Object, Throwable>() { @Override public Object perform() { int pullRequests = 0; PageRequest nextPage = new PageRequestImpl(0, 10000); do { Page<PullRequest> pullRequestPage = pullRequestService.search(new PullRequestSearchRequest.Builder().state(OPEN).build(), nextPage); pullRequests += pullRequestPage.getSize(); nextPage = pullRequestPage.getNextPageRequest(); } while (nextPage != null); totalPullRequests.set(pullRequests); return null; } }); } catch (Throwable th) { log.error("Cannot read all pull requests", th); } } private void stopScraping() { if (!scraper.cancel(true)) { log.debug("Unable to cancel scraping, typically because it has already completed."); } } }