1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * See LICENSE.txt included in this distribution for the specific 9 * language governing permissions and limitations under the License. 10 * 11 * When distributing Covered Code, include this CDDL HEADER in each 12 * file and include the License file at LICENSE.txt. 13 * If applicable, add the following below this CDDL HEADER, with the 14 * fields enclosed by brackets "[]" replaced with your own identifying 15 * information: Portions Copyright [yyyy] [name of copyright owner] 16 * 17 * CDDL HEADER END 18 */ 19 20 /* 21 * Copyright (c) 2007, 2021, Oracle and/or its affiliates. All rights reserved. 22 * Portions Copyright (c) 2017, 2020, Chris Fraire <cfraire@me.com>. 23 */ 24 package org.opengrok.indexer.util; 25 26 import org.opengrok.indexer.configuration.RuntimeEnvironment; 27 28 import java.util.HashMap; 29 import java.util.Map; 30 import java.util.concurrent.atomic.AtomicLong; 31 import java.util.logging.Level; 32 import java.util.logging.Logger; 33 34 public class Progress implements AutoCloseable { 35 private final Logger logger; 36 private final long totalCount; 37 private final String suffix; 38 39 private final AtomicLong currentCount = new AtomicLong(); 40 private Thread loggerThread = null; 41 private volatile boolean run; 42 43 private final Object sync = new Object(); 44 45 /** 46 * @param logger logger instance 47 * @param suffix string suffix to identify the operation 48 * @param totalCount total count 49 */ Progress(Logger logger, String suffix, long totalCount)50 public Progress(Logger logger, String suffix, long totalCount) { 51 this.logger = logger; 52 this.suffix = suffix; 53 this.totalCount = totalCount; 54 55 // Assuming printProgress configuration setting cannot be changed on the fly. 56 if (totalCount > 0 && RuntimeEnvironment.getInstance().isPrintProgress()) { 57 // spawn a logger thread. 58 run = true; 59 loggerThread = new Thread(this::logLoop, 60 "progress-thread-" + suffix.replaceAll(" ", "_")); 61 loggerThread.start(); 62 } 63 } 64 65 // for testing getLoggerThread()66 Thread getLoggerThread() { 67 return loggerThread; 68 } 69 70 /** 71 * Increment counter. The actual logging will be done eventually. 72 */ increment()73 public void increment() { 74 this.currentCount.incrementAndGet(); 75 76 if (loggerThread != null) { 77 // nag the thread. 78 synchronized (sync) { 79 sync.notifyAll(); 80 } 81 } 82 } 83 logLoop()84 private void logLoop() { 85 long cachedCount = 0; 86 Map<Level, Long> lastLoggedChunk = new HashMap<>(); 87 Map<Level, Integer> levelCount = Map.of(Level.INFO, 100, 88 Level.FINE, 50, 89 Level.FINER, 10, 90 Level.FINEST, 1); 91 92 while (true) { 93 long currentCount = this.currentCount.get(); 94 Level currentLevel = Level.FINEST; 95 96 // Do not log if there was no progress. 97 if (cachedCount < currentCount) { 98 if (currentCount <= 1 || currentCount == totalCount) { 99 currentLevel = Level.INFO; 100 } else { 101 if (lastLoggedChunk.getOrDefault(Level.INFO, -1L) < 102 currentCount / levelCount.get(Level.INFO)) { 103 currentLevel = Level.INFO; 104 } else if (lastLoggedChunk.getOrDefault(Level.FINE, -1L) < 105 currentCount / levelCount.get(Level.FINE)) { 106 currentLevel = Level.FINE; 107 } else if (lastLoggedChunk.getOrDefault(Level.FINER, -1L) < 108 currentCount / levelCount.get(Level.FINER)) { 109 currentLevel = Level.FINER; 110 } 111 } 112 113 if (logger.isLoggable(currentLevel)) { 114 lastLoggedChunk.put(currentLevel, currentCount / levelCount.get(currentLevel)); 115 logger.log(currentLevel, "Progress: {0} ({1}%) for {2}", 116 new Object[]{currentCount, currentCount * 100.0f / totalCount, suffix}); 117 } 118 } 119 120 if (!run) { 121 return; 122 } 123 124 cachedCount = currentCount; 125 126 // wait for event 127 try { 128 synchronized (sync) { 129 if (!run) { 130 // Loop once more to do the final logging. 131 continue; 132 } 133 sync.wait(); 134 } 135 } catch (InterruptedException e) { 136 logger.log(Level.WARNING, "logger thread interrupted"); 137 } 138 } 139 } 140 141 @Override close()142 public void close() { 143 if (loggerThread == null) { 144 return; 145 } 146 147 try { 148 run = false; 149 synchronized (sync) { 150 sync.notifyAll(); 151 } 152 loggerThread.join(); 153 } catch (InterruptedException e) { 154 logger.log(Level.WARNING, "logger thread interrupted"); 155 } 156 } 157 } 158