11e75da15SVladimir Kotal /* 21e75da15SVladimir Kotal * CDDL HEADER START 31e75da15SVladimir Kotal * 41e75da15SVladimir Kotal * The contents of this file are subject to the terms of the 51e75da15SVladimir Kotal * Common Development and Distribution License (the "License"). 61e75da15SVladimir Kotal * You may not use this file except in compliance with the License. 71e75da15SVladimir Kotal * 81e75da15SVladimir Kotal * See LICENSE.txt included in this distribution for the specific 91e75da15SVladimir Kotal * language governing permissions and limitations under the License. 101e75da15SVladimir Kotal * 111e75da15SVladimir Kotal * When distributing Covered Code, include this CDDL HEADER in each 121e75da15SVladimir Kotal * file and include the License file at LICENSE.txt. 131e75da15SVladimir Kotal * If applicable, add the following below this CDDL HEADER, with the 141e75da15SVladimir Kotal * fields enclosed by brackets "[]" replaced with your own identifying 151e75da15SVladimir Kotal * information: Portions Copyright [yyyy] [name of copyright owner] 161e75da15SVladimir Kotal * 171e75da15SVladimir Kotal * CDDL HEADER END 181e75da15SVladimir Kotal */ 191e75da15SVladimir Kotal 201e75da15SVladimir Kotal /* 21c6f0939bSAdam Hornacek * Copyright (c) 2007, 2021, Oracle and/or its affiliates. All rights reserved. 225d9f3aa0SAdam Hornáček * Portions Copyright (c) 2017, 2020, Chris Fraire <cfraire@me.com>. 231e75da15SVladimir Kotal */ 241e75da15SVladimir Kotal package org.opengrok.indexer.util; 251e75da15SVladimir Kotal 261e75da15SVladimir Kotal import org.opengrok.indexer.configuration.RuntimeEnvironment; 271e75da15SVladimir Kotal 28*892e85b0SVladimir Kotal import java.util.HashMap; 29*892e85b0SVladimir Kotal import java.util.Map; 301e75da15SVladimir Kotal import java.util.concurrent.atomic.AtomicLong; 311e75da15SVladimir Kotal import java.util.logging.Level; 321e75da15SVladimir Kotal import java.util.logging.Logger; 331e75da15SVladimir Kotal 341e75da15SVladimir Kotal public class Progress implements AutoCloseable { 351e75da15SVladimir Kotal private final Logger logger; 361e75da15SVladimir Kotal private final long totalCount; 371e75da15SVladimir Kotal private final String suffix; 381e75da15SVladimir Kotal 39c6f0939bSAdam Hornacek private final AtomicLong currentCount = new AtomicLong(); 401e75da15SVladimir Kotal private Thread loggerThread = null; 411e75da15SVladimir Kotal private volatile boolean run; 421e75da15SVladimir Kotal 431e75da15SVladimir Kotal private final Object sync = new Object(); 441e75da15SVladimir Kotal 451e75da15SVladimir Kotal /** 461e75da15SVladimir Kotal * @param logger logger instance 471e75da15SVladimir Kotal * @param suffix string suffix to identify the operation 481e75da15SVladimir Kotal * @param totalCount total count 491e75da15SVladimir Kotal */ Progress(Logger logger, String suffix, long totalCount)501e75da15SVladimir Kotal public Progress(Logger logger, String suffix, long totalCount) { 511e75da15SVladimir Kotal this.logger = logger; 521e75da15SVladimir Kotal this.suffix = suffix; 531e75da15SVladimir Kotal this.totalCount = totalCount; 541e75da15SVladimir Kotal 551e75da15SVladimir Kotal // Assuming printProgress configuration setting cannot be changed on the fly. 561e75da15SVladimir Kotal if (totalCount > 0 && RuntimeEnvironment.getInstance().isPrintProgress()) { 571e75da15SVladimir Kotal // spawn a logger thread. 581e75da15SVladimir Kotal run = true; 591e75da15SVladimir Kotal loggerThread = new Thread(this::logLoop, 601e75da15SVladimir Kotal "progress-thread-" + suffix.replaceAll(" ", "_")); 611e75da15SVladimir Kotal loggerThread.start(); 621e75da15SVladimir Kotal } 631e75da15SVladimir Kotal } 641e75da15SVladimir Kotal 651e75da15SVladimir Kotal // for testing getLoggerThread()661e75da15SVladimir Kotal Thread getLoggerThread() { 671e75da15SVladimir Kotal return loggerThread; 681e75da15SVladimir Kotal } 691e75da15SVladimir Kotal 701e75da15SVladimir Kotal /** 711e75da15SVladimir Kotal * Increment counter. The actual logging will be done eventually. 721e75da15SVladimir Kotal */ increment()731e75da15SVladimir Kotal public void increment() { 741e75da15SVladimir Kotal this.currentCount.incrementAndGet(); 751e75da15SVladimir Kotal 761e75da15SVladimir Kotal if (loggerThread != null) { 771e75da15SVladimir Kotal // nag the thread. 781e75da15SVladimir Kotal synchronized (sync) { 792173ed7bSChris Fraire sync.notifyAll(); 801e75da15SVladimir Kotal } 811e75da15SVladimir Kotal } 821e75da15SVladimir Kotal } 831e75da15SVladimir Kotal logLoop()841e75da15SVladimir Kotal private void logLoop() { 851e75da15SVladimir Kotal long cachedCount = 0; 86*892e85b0SVladimir Kotal Map<Level, Long> lastLoggedChunk = new HashMap<>(); 87*892e85b0SVladimir Kotal Map<Level, Integer> levelCount = Map.of(Level.INFO, 100, 88*892e85b0SVladimir Kotal Level.FINE, 50, 89*892e85b0SVladimir Kotal Level.FINER, 10, 90*892e85b0SVladimir Kotal Level.FINEST, 1); 911e75da15SVladimir Kotal 921e75da15SVladimir Kotal while (true) { 931e75da15SVladimir Kotal long currentCount = this.currentCount.get(); 94*892e85b0SVladimir Kotal Level currentLevel = Level.FINEST; 951e75da15SVladimir Kotal 961e75da15SVladimir Kotal // Do not log if there was no progress. 97*892e85b0SVladimir Kotal if (cachedCount < currentCount) { 98*892e85b0SVladimir Kotal if (currentCount <= 1 || currentCount == totalCount) { 99*892e85b0SVladimir Kotal currentLevel = Level.INFO; 100*892e85b0SVladimir Kotal } else { 101*892e85b0SVladimir Kotal if (lastLoggedChunk.getOrDefault(Level.INFO, -1L) < 102*892e85b0SVladimir Kotal currentCount / levelCount.get(Level.INFO)) { 103*892e85b0SVladimir Kotal currentLevel = Level.INFO; 104*892e85b0SVladimir Kotal } else if (lastLoggedChunk.getOrDefault(Level.FINE, -1L) < 105*892e85b0SVladimir Kotal currentCount / levelCount.get(Level.FINE)) { 106*892e85b0SVladimir Kotal currentLevel = Level.FINE; 107*892e85b0SVladimir Kotal } else if (lastLoggedChunk.getOrDefault(Level.FINER, -1L) < 108*892e85b0SVladimir Kotal currentCount / levelCount.get(Level.FINER)) { 109*892e85b0SVladimir Kotal currentLevel = Level.FINER; 110*892e85b0SVladimir Kotal } 111*892e85b0SVladimir Kotal } 112*892e85b0SVladimir Kotal 1131e75da15SVladimir Kotal if (logger.isLoggable(currentLevel)) { 114*892e85b0SVladimir Kotal lastLoggedChunk.put(currentLevel, currentCount / levelCount.get(currentLevel)); 1151e75da15SVladimir Kotal logger.log(currentLevel, "Progress: {0} ({1}%) for {2}", 116*892e85b0SVladimir Kotal new Object[]{currentCount, currentCount * 100.0f / totalCount, suffix}); 1171e75da15SVladimir Kotal } 1181e75da15SVladimir Kotal } 1191e75da15SVladimir Kotal 1201e75da15SVladimir Kotal if (!run) { 1211e75da15SVladimir Kotal return; 1221e75da15SVladimir Kotal } 1231e75da15SVladimir Kotal 1241e75da15SVladimir Kotal cachedCount = currentCount; 1251e75da15SVladimir Kotal 1261e75da15SVladimir Kotal // wait for event 1271e75da15SVladimir Kotal try { 1281e75da15SVladimir Kotal synchronized (sync) { 1291e75da15SVladimir Kotal if (!run) { 1301e75da15SVladimir Kotal // Loop once more to do the final logging. 1311e75da15SVladimir Kotal continue; 1321e75da15SVladimir Kotal } 1331e75da15SVladimir Kotal sync.wait(); 1341e75da15SVladimir Kotal } 1351e75da15SVladimir Kotal } catch (InterruptedException e) { 1361e75da15SVladimir Kotal logger.log(Level.WARNING, "logger thread interrupted"); 1371e75da15SVladimir Kotal } 1381e75da15SVladimir Kotal } 1391e75da15SVladimir Kotal } 1401e75da15SVladimir Kotal 1411e75da15SVladimir Kotal @Override close()1421e75da15SVladimir Kotal public void close() { 1431e75da15SVladimir Kotal if (loggerThread == null) { 1441e75da15SVladimir Kotal return; 1451e75da15SVladimir Kotal } 1461e75da15SVladimir Kotal 1471e75da15SVladimir Kotal try { 1481e75da15SVladimir Kotal run = false; 1491e75da15SVladimir Kotal synchronized (sync) { 1502173ed7bSChris Fraire sync.notifyAll(); 1511e75da15SVladimir Kotal } 1521e75da15SVladimir Kotal loggerThread.join(); 1531e75da15SVladimir Kotal } catch (InterruptedException e) { 1541e75da15SVladimir Kotal logger.log(Level.WARNING, "logger thread interrupted"); 1551e75da15SVladimir Kotal } 1561e75da15SVladimir Kotal } 1571e75da15SVladimir Kotal } 158