xref: /OpenGrok/opengrok-indexer/src/main/java/org/opengrok/indexer/util/Progress.java (revision 892e85b0307b0e825858bf4da59ab4bc5647c63f)
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