• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

oracle / opengrok / #3630

20 Oct 2023 10:04AM UTC coverage: 65.943% (-9.8%) from 75.715%
#3630

push

web-flow
junit and docker file sonar issue fixes (#4447)

---------

Signed-off-by: Gino Augustine <ginoaugustine@gmail.com>
Co-authored-by: Vladimir Kotal <vladimir.kotal@oracle.com>

49 of 49 new or added lines in 9 files covered. (100.0%)

38645 of 58604 relevant lines covered (65.94%)

0.66 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

30.34
/suggester/src/main/java/org/opengrok/suggest/util/Progress.java
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, 2023, Oracle and/or its affiliates. All rights reserved.
22
 * Portions Copyright (c) 2017, 2020, Chris Fraire <cfraire@me.com>.
23
 */
24
package org.opengrok.suggest.util;
25

26
import org.jetbrains.annotations.VisibleForTesting;
27

28
import java.util.Arrays;
29
import java.util.Comparator;
30
import java.util.HashMap;
31
import java.util.List;
32
import java.util.Map;
33
import java.util.TreeMap;
34
import java.util.concurrent.atomic.AtomicLong;
35
import java.util.logging.Level;
36
import java.util.logging.Logger;
37

38
/**
39
 * Copy of {@code org.opengrok.indexer.util.Progress}.
40
 * <p>
41
 * Progress reporting via logging. The idea is that for anything that has a set of items
42
 * to go through, it will ping an instance of this class for each item completed.
43
 * This class will then log based on the number of pings. The bigger the progress,
44
 * the higher log level ({@link Level} value) will be used. The default base level is {@code Level.INFO}.
45
 * Regardless of the base level, maximum 4 log levels will be used.
46
 * </p>
47
 */
48
public class Progress implements AutoCloseable {
49
    private final Logger logger;
50
    private final Long totalCount;
51
    private final String suffix;
52

53
    private final AtomicLong currentCount = new AtomicLong();
1✔
54
    private final Map<Level, Integer> levelCountMap = new TreeMap<>(Comparator.comparingInt(Level::intValue).reversed());
1✔
55
    private Thread loggerThread = null;
1✔
56
    private volatile boolean run;
57

58
    private final Level baseLogLevel;
59

60
    private final Object sync = new Object();
1✔
61

62
    /**
63
     * @param logger logger instance
64
     * @param suffix string suffix to identify the operation
65
     * @param totalCount total count
66
     * @param logLevel base log level
67
     * @param isPrintProgress whether to print the progress
68
     */
69
    public Progress(Logger logger, String suffix, long totalCount, Level logLevel, boolean isPrintProgress) {
1✔
70
        this.logger = logger;
1✔
71
        this.suffix = suffix;
1✔
72
        this.baseLogLevel = logLevel;
1✔
73

74
        if (totalCount < 0) {
1✔
75
            this.totalCount = null;
×
76
        } else {
77
            this.totalCount = totalCount;
1✔
78
        }
79

80
        // Note: Level.CONFIG is missing as it does not make too much sense for progress reporting semantically.
81
        final List<Level> standardLevels = Arrays.asList(Level.OFF, Level.SEVERE, Level.WARNING, Level.INFO,
1✔
82
                Level.FINE, Level.FINER, Level.FINEST, Level.ALL);
83
        int i = standardLevels.indexOf(baseLogLevel);
1✔
84
        for (int num : new int[]{100, 50, 10, 1}) {
1✔
85
            if (i >= standardLevels.size()) {
1✔
86
                break;
×
87
            }
88

89
            Level level = standardLevels.get(i);
1✔
90
            if (level == null) {
1✔
91
                break;
×
92
            }
93
            levelCountMap.put(level, num);
1✔
94
            if (num == 1) {
1✔
95
                break;
1✔
96
            }
97
            i++;
1✔
98
        }
99

100
        // Assuming the printProgress configuration setting cannot be changed on the fly.
101
        if (!baseLogLevel.equals(Level.OFF) && isPrintProgress) {
1✔
102
            spawnLogThread();
×
103
        }
104
    }
1✔
105

106
    private void spawnLogThread() {
107
        // spawn a logger thread.
108
        run = true;
×
109
        loggerThread = new Thread(this::logLoop,
×
110
                "progress-thread-" + suffix.replace(" ", "_"));
×
111
        loggerThread.start();
×
112
    }
×
113

114
    /**
115
     * Increment counter. The actual logging will be done eventually.
116
     */
117
    public void increment() {
118
        this.currentCount.incrementAndGet();
1✔
119

120
        if (loggerThread != null) {
1✔
121
            // nag the thread.
122
            synchronized (sync) {
×
123
                sync.notifyAll();
×
124
            }
×
125
        }
126
    }
1✔
127

128
    private void logLoop() {
129
        long cachedCount = 0;
×
130
        Map<Level, Long> lastLoggedChunk = new HashMap<>();
×
131

132
        while (true) {
133
            long longCurrentCount = this.currentCount.get();
×
134
            Level currentLevel = Level.FINEST;
×
135

136
            // Do not log if there was no progress.
137
            if (cachedCount < longCurrentCount) {
×
138
                currentLevel = getLevel(lastLoggedChunk, longCurrentCount, currentLevel);
×
139
                logIt(lastLoggedChunk, longCurrentCount, currentLevel);
×
140
            }
141

142
            if (!run) {
×
143
                return;
×
144
            }
145

146
            cachedCount = longCurrentCount;
×
147

148
            // wait for event
149
            try {
150
                synchronized (sync) {
×
151
                    if (!run) {
×
152
                        // Loop once more to do the final logging.
153
                        continue;
×
154
                    }
155
                    sync.wait();
×
156
                }
×
157
            } catch (InterruptedException e) {
×
158
                logger.log(Level.WARNING, "logger thread interrupted");
×
159
            }
×
160
        }
×
161
    }
162

163
    @VisibleForTesting
164
    Level getLevel(Map<Level, Long> lastLoggedChunk, long currentCount, Level currentLevel) {
165
        // The intention is to log the initial and final count at the base log level.
166
        if (currentCount <= 1 || (totalCount != null && currentCount == totalCount)) {
×
167
            currentLevel = baseLogLevel;
×
168
        } else {
169
            // Set the log level based on the "buckets".
170
            for (var levelCountItem : levelCountMap.entrySet()) {
×
171
                if (lastLoggedChunk.getOrDefault(levelCountItem.getKey(), -1L) <
×
172
                        currentCount / levelCountItem.getValue()) {
×
173
                    currentLevel = levelCountItem.getKey();
×
174
                    break;
×
175
                }
176
            }
×
177
        }
178
        return currentLevel;
×
179
    }
180

181
    private void logIt(Map<Level, Long> lastLoggedChunk, long currentCount, Level currentLevel) {
182
        if (logger.isLoggable(currentLevel)) {
×
183
            lastLoggedChunk.put(currentLevel, currentCount / levelCountMap.get(currentLevel));
×
184
            StringBuilder stringBuilder = new StringBuilder();
×
185
            stringBuilder.append("Progress: ");
×
186
            stringBuilder.append(currentCount);
×
187
            stringBuilder.append(" ");
×
188
            if (totalCount != null) {
×
189
                stringBuilder.append("(");
×
190
                stringBuilder.append(String.format("%.2f", currentCount * 100.0f / totalCount));
×
191
                stringBuilder.append("%) ");
×
192
            }
193
            stringBuilder.append(suffix);
×
194
            logger.log(currentLevel, stringBuilder.toString());
×
195
        }
196
    }
×
197

198
    @Override
199
    public void close() {
200
        if (loggerThread == null) {
1✔
201
            return;
1✔
202
        }
203

204
        try {
205
            run = false;
×
206
            synchronized (sync) {
×
207
                sync.notifyAll();
×
208
            }
×
209
            loggerThread.join();
×
210
        } catch (InterruptedException e) {
×
211
            logger.log(Level.WARNING, "logger thread interrupted");
×
212
        }
×
213
    }
×
214
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc