mirror of
https://github.com/dromara/hertzbeat.git
synced 2026-09-17 09:40:58 +00:00
[feat]: migrate blocking executors to virtual threads (#4062)
This commit is contained in:
@@ -21,6 +21,7 @@ import com.usthe.sureness.subject.SubjectSum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.ai.config.McpContextHolder;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.MonitorDto;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.ParamDefineInfo;
|
||||
import org.apache.hertzbeat.manager.service.MonitorService;
|
||||
import org.apache.hertzbeat.manager.service.AppService;
|
||||
import org.apache.hertzbeat.ai.utils.UtilityClass;
|
||||
@@ -32,7 +33,6 @@ import org.springframework.ai.tool.annotation.ToolParam;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.apache.hertzbeat.common.entity.manager.Monitor;
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -280,7 +280,9 @@ public class MonitorToolsImpl implements MonitorTools {
|
||||
|
||||
// Validate that all required parameters for this monitor type are provided
|
||||
try {
|
||||
MonitorDto monitorDto = MonitorDto.builder().monitor(monitor).params(paramList).build();
|
||||
MonitorDto monitorDto = new MonitorDto();
|
||||
monitorDto.setMonitor(monitor);
|
||||
monitorDto.setParams(paramList);
|
||||
monitorService.validate(monitorDto, false);
|
||||
} catch (IllegalArgumentException argumentException) {
|
||||
if (argumentException.getMessage().contains("required")) {
|
||||
@@ -456,7 +458,7 @@ public class MonitorToolsImpl implements MonitorTools {
|
||||
}
|
||||
|
||||
// Get parameter definitions from app service
|
||||
List<ParamDefine> paramDefines = appService.getAppParamDefines(app.toLowerCase().trim());
|
||||
List<ParamDefineInfo> paramDefines = appService.getAppParamDefines(app.toLowerCase().trim());
|
||||
|
||||
if (paramDefines == null || paramDefines.isEmpty()) {
|
||||
return String.format("No parameter definitions found for monitor type '%s'. "
|
||||
@@ -468,7 +470,7 @@ public class MonitorToolsImpl implements MonitorTools {
|
||||
response.append(String.format("Parameter Definitions for Monitor Type '%s' (Total: %d):\n\n",
|
||||
app, paramDefines.size()));
|
||||
|
||||
for (ParamDefine paramDefine : paramDefines) {
|
||||
for (ParamDefineInfo paramDefine : paramDefines) {
|
||||
response.append("• Field: ").append(paramDefine.getField()).append("\n");
|
||||
|
||||
// Add display name if available
|
||||
|
||||
@@ -40,6 +40,11 @@
|
||||
<artifactId>hertzbeat-common-core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.hertzbeat</groupId>
|
||||
<artifactId>hertzbeat-common-spring</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!-- plugin -->
|
||||
<dependency>
|
||||
<groupId>org.apache.hertzbeat</groupId>
|
||||
|
||||
+103
-17
@@ -18,12 +18,20 @@
|
||||
package org.apache.hertzbeat.alert;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.common.concurrent.ManagedExecutor;
|
||||
import org.apache.hertzbeat.common.concurrent.ManagedExecutors;
|
||||
import org.apache.hertzbeat.common.config.VirtualThreadProperties;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
@@ -31,16 +39,25 @@ import org.springframework.stereotype.Component;
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class AlerterWorkerPool {
|
||||
public class AlerterWorkerPool implements DisposableBean {
|
||||
|
||||
private ThreadPoolExecutor workerExecutor;
|
||||
private ThreadPoolExecutor notifyExecutor;
|
||||
private ThreadPoolExecutor logWorkerExecutor;
|
||||
private ManagedExecutor notifyExecutor;
|
||||
private ManagedExecutor logWorkerExecutor;
|
||||
private Map<Byte, Semaphore> notifyChannelPermits;
|
||||
private int notifyMaxConcurrentPerChannel;
|
||||
|
||||
public AlerterWorkerPool() {
|
||||
this(VirtualThreadProperties.defaults());
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public AlerterWorkerPool(VirtualThreadProperties virtualThreadProperties) {
|
||||
VirtualThreadProperties properties =
|
||||
virtualThreadProperties == null ? VirtualThreadProperties.defaults() : virtualThreadProperties;
|
||||
initWorkExecutor();
|
||||
initNotifyExecutor();
|
||||
initLogWorkerExecutor();
|
||||
initNotifyExecutor(properties);
|
||||
initLogWorkerExecutor(properties);
|
||||
}
|
||||
|
||||
private void initWorkExecutor() {
|
||||
@@ -61,16 +78,32 @@ public class AlerterWorkerPool {
|
||||
new ThreadPoolExecutor.AbortPolicy());
|
||||
}
|
||||
|
||||
private void initNotifyExecutor() {
|
||||
private void initNotifyExecutor(VirtualThreadProperties properties) {
|
||||
Thread.UncaughtExceptionHandler handler = (thread, throwable) -> {
|
||||
log.error("Alerter notifyExecutor has uncaughtException.");
|
||||
log.error(throwable.getMessage(), throwable);
|
||||
};
|
||||
if (properties.enabled()) {
|
||||
VirtualThreadProperties.AlerterProperties alerterProperties = properties.alerter();
|
||||
VirtualThreadProperties.PoolProperties notifyProperties = alerterProperties.notifyPool();
|
||||
notifyMaxConcurrentPerChannel = Math.max(1, alerterProperties.notifyMaxConcurrentPerChannel());
|
||||
notifyChannelPermits = new ConcurrentHashMap<>(8);
|
||||
notifyExecutor = ManagedExecutors.newVirtualExecutor("notify-worker", "notify-worker-",
|
||||
notifyProperties.mode(), notifyProperties.maxConcurrentJobs(), handler);
|
||||
return;
|
||||
}
|
||||
notifyMaxConcurrentPerChannel = 0;
|
||||
notifyChannelPermits = null;
|
||||
notifyExecutor = ManagedExecutors.wrap("notify-worker", createLegacyNotifyExecutor(handler));
|
||||
}
|
||||
|
||||
private ThreadPoolExecutor createLegacyNotifyExecutor(Thread.UncaughtExceptionHandler handler) {
|
||||
ThreadFactory threadFactory = new ThreadFactoryBuilder()
|
||||
.setUncaughtExceptionHandler((thread, throwable) -> {
|
||||
log.error("Alerter notifyExecutor has uncaughtException.");
|
||||
log.error(throwable.getMessage(), throwable);
|
||||
})
|
||||
.setUncaughtExceptionHandler(handler)
|
||||
.setDaemon(true)
|
||||
.setNameFormat("notify-worker-%d")
|
||||
.build();
|
||||
notifyExecutor = new ThreadPoolExecutor(6,
|
||||
return new ThreadPoolExecutor(6,
|
||||
6,
|
||||
10,
|
||||
TimeUnit.SECONDS,
|
||||
@@ -79,16 +112,27 @@ public class AlerterWorkerPool {
|
||||
new ThreadPoolExecutor.AbortPolicy());
|
||||
}
|
||||
|
||||
private void initLogWorkerExecutor() {
|
||||
private void initLogWorkerExecutor(VirtualThreadProperties properties) {
|
||||
Thread.UncaughtExceptionHandler handler = (thread, throwable) -> {
|
||||
log.error("Alerter logWorkerExecutor has uncaughtException.");
|
||||
log.error(throwable.getMessage(), throwable);
|
||||
};
|
||||
if (properties.enabled()) {
|
||||
VirtualThreadProperties.QueueProperties logWorkerProperties = properties.alerter().logWorker();
|
||||
logWorkerExecutor = ManagedExecutors.newQueuedVirtualExecutor("alerter-log-worker", "log-worker-",
|
||||
logWorkerProperties.maxConcurrentJobs(), logWorkerProperties.queueCapacity(), handler);
|
||||
return;
|
||||
}
|
||||
logWorkerExecutor = ManagedExecutors.wrap("alerter-log-worker", createLegacyLogWorkerExecutor(handler));
|
||||
}
|
||||
|
||||
private ThreadPoolExecutor createLegacyLogWorkerExecutor(Thread.UncaughtExceptionHandler handler) {
|
||||
ThreadFactory threadFactory = new ThreadFactoryBuilder()
|
||||
.setUncaughtExceptionHandler((thread, throwable) -> {
|
||||
log.error("Alerter logWorkerExecutor has uncaughtException.");
|
||||
log.error(throwable.getMessage(), throwable);
|
||||
})
|
||||
.setUncaughtExceptionHandler(handler)
|
||||
.setDaemon(true)
|
||||
.setNameFormat("log-worker-%d")
|
||||
.build();
|
||||
logWorkerExecutor = new ThreadPoolExecutor(10, 10, 10, TimeUnit.SECONDS,
|
||||
return new ThreadPoolExecutor(10, 10, 10, TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(1000),
|
||||
threadFactory,
|
||||
new ThreadPoolExecutor.AbortPolicy());
|
||||
@@ -113,6 +157,41 @@ public class AlerterWorkerPool {
|
||||
notifyExecutor.execute(runnable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the given runnable task using the notify executor with per-channel concurrency control.
|
||||
*
|
||||
* @param channelType notification channel type
|
||||
* @param runnable the task to be executed
|
||||
* @throws RejectedExecutionException if the task cannot be accepted for execution
|
||||
*/
|
||||
public void executeNotify(byte channelType, Runnable runnable) throws RejectedExecutionException {
|
||||
if (notifyChannelPermits == null) {
|
||||
notifyExecutor.execute(runnable);
|
||||
return;
|
||||
}
|
||||
Semaphore semaphore = notifyChannelPermits.computeIfAbsent(channelType,
|
||||
key -> new Semaphore(notifyMaxConcurrentPerChannel));
|
||||
if (!semaphore.tryAcquire()) {
|
||||
throw new RejectedExecutionException(
|
||||
"notify-worker rejected task because channel concurrency limit was reached for type " + channelType);
|
||||
}
|
||||
boolean submitted = false;
|
||||
try {
|
||||
notifyExecutor.execute(() -> {
|
||||
try {
|
||||
runnable.run();
|
||||
} finally {
|
||||
semaphore.release();
|
||||
}
|
||||
});
|
||||
submitted = true;
|
||||
} finally {
|
||||
if (!submitted) {
|
||||
semaphore.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the given runnable task using the logWorkerExecutor.
|
||||
*
|
||||
@@ -122,4 +201,11 @@ public class AlerterWorkerPool {
|
||||
public void executeLogJob(Runnable runnable) throws RejectedExecutionException {
|
||||
logWorkerExecutor.execute(runnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
workerExecutor.shutdownNow();
|
||||
notifyExecutor.close();
|
||||
logWorkerExecutor.close();
|
||||
}
|
||||
}
|
||||
|
||||
+148
-15
@@ -25,12 +25,19 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.dao.AlertDefineDao;
|
||||
import org.apache.hertzbeat.common.config.VirtualThreadProperties;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
|
||||
@@ -40,18 +47,33 @@ import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class PeriodicAlertRuleScheduler implements CommandLineRunner {
|
||||
public class PeriodicAlertRuleScheduler implements CommandLineRunner, DisposableBean {
|
||||
|
||||
private final MetricsPeriodicAlertCalculator metricsCalculator;
|
||||
private final LogPeriodicAlertCalculator logCalculator;
|
||||
private final AlertDefineDao alertDefineDao;
|
||||
private final ScheduledExecutorService scheduledExecutor;
|
||||
private final Map<Long, ScheduledFuture<?>> scheduledFutures;
|
||||
private final ExecutorService periodicExecutor;
|
||||
private final Semaphore periodicPermits;
|
||||
private final boolean virtualThreadsEnabled;
|
||||
private final Map<Long, ScheduledTaskState> scheduledTasks;
|
||||
|
||||
public PeriodicAlertRuleScheduler(MetricsPeriodicAlertCalculator metricsCalculator, LogPeriodicAlertCalculator logCalculator, AlertDefineDao alertDefineDao) {
|
||||
this(metricsCalculator, logCalculator, alertDefineDao, VirtualThreadProperties.defaults());
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public PeriodicAlertRuleScheduler(MetricsPeriodicAlertCalculator metricsCalculator,
|
||||
LogPeriodicAlertCalculator logCalculator,
|
||||
AlertDefineDao alertDefineDao,
|
||||
VirtualThreadProperties virtualThreadProperties) {
|
||||
this.metricsCalculator = metricsCalculator;
|
||||
this.logCalculator = logCalculator;
|
||||
this.alertDefineDao = alertDefineDao;
|
||||
Thread.UncaughtExceptionHandler handler = (thread, throwable) -> {
|
||||
log.error("Scheduled periodic alert threshold has uncaughtException.");
|
||||
log.error(throwable.getMessage(), throwable);
|
||||
};
|
||||
ThreadFactory threadFactory = new ThreadFactoryBuilder()
|
||||
.setUncaughtExceptionHandler((thread, throwable) -> {
|
||||
log.error("Scheduled periodic alert threshold has uncaughtException.");
|
||||
@@ -61,17 +83,27 @@ public class PeriodicAlertRuleScheduler implements CommandLineRunner {
|
||||
.setNameFormat("periodic-alert-threshold-worker-%d")
|
||||
.build();
|
||||
this.scheduledExecutor = Executors.newScheduledThreadPool(10, threadFactory);
|
||||
this.scheduledFutures = new ConcurrentHashMap<>();
|
||||
VirtualThreadProperties properties =
|
||||
virtualThreadProperties == null ? VirtualThreadProperties.defaults() : virtualThreadProperties;
|
||||
this.virtualThreadsEnabled = properties.enabled();
|
||||
int maxConcurrentPeriodicTasks = Math.max(1, properties.alerter().periodicMaxConcurrentJobs());
|
||||
this.periodicExecutor = virtualThreadsEnabled
|
||||
? Executors.newThreadPerTaskExecutor(Thread.ofVirtual()
|
||||
.name("periodic-alert-task-", 0)
|
||||
.uncaughtExceptionHandler(handler)
|
||||
.factory())
|
||||
: null;
|
||||
this.periodicPermits = virtualThreadsEnabled ? new Semaphore(maxConcurrentPeriodicTasks) : null;
|
||||
this.scheduledTasks = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
public void cancelSchedule(Long ruleId) {
|
||||
if (ruleId == null) {
|
||||
return;
|
||||
}
|
||||
ScheduledFuture<?> future = scheduledFutures.get(ruleId);
|
||||
if (future != null) {
|
||||
future.cancel(true);
|
||||
scheduledFutures.remove(ruleId);
|
||||
ScheduledTaskState state = scheduledTasks.remove(ruleId);
|
||||
if (state != null) {
|
||||
state.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,14 +115,12 @@ public class PeriodicAlertRuleScheduler implements CommandLineRunner {
|
||||
cancelSchedule(rule.getId());
|
||||
if (rule.getType().equals(METRIC_ALERT_THRESHOLD_TYPE_PERIODIC)
|
||||
|| rule.getType().equals(LOG_ALERT_THRESHOLD_TYPE_PERIODIC)) {
|
||||
ScheduledFuture<?> future = scheduledExecutor.scheduleAtFixedRate(() -> {
|
||||
if (rule.getType().equals(METRIC_ALERT_THRESHOLD_TYPE_PERIODIC)) {
|
||||
metricsCalculator.calculate(rule);
|
||||
} else if (rule.getType().equals(LOG_ALERT_THRESHOLD_TYPE_PERIODIC)) {
|
||||
logCalculator.calculate(rule);
|
||||
}
|
||||
}, 0, rule.getPeriod(), java.util.concurrent.TimeUnit.SECONDS);
|
||||
scheduledFutures.put(rule.getId(), future);
|
||||
ScheduledTaskState state = new ScheduledTaskState(rule);
|
||||
ScheduledFuture<?> future = scheduledExecutor.scheduleAtFixedRate(
|
||||
virtualThreadsEnabled ? state::trigger : () -> executeRule(rule),
|
||||
0, rule.getPeriod(), TimeUnit.SECONDS);
|
||||
state.setScheduledFuture(future);
|
||||
scheduledTasks.put(rule.getId(), state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,4 +136,107 @@ public class PeriodicAlertRuleScheduler implements CommandLineRunner {
|
||||
updateSchedule(rule);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
scheduledTasks.values().forEach(ScheduledTaskState::cancel);
|
||||
scheduledTasks.clear();
|
||||
scheduledExecutor.shutdownNow();
|
||||
if (periodicExecutor != null) {
|
||||
periodicExecutor.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
private void executeRule(AlertDefine rule) {
|
||||
if (rule.getType().equals(METRIC_ALERT_THRESHOLD_TYPE_PERIODIC)) {
|
||||
metricsCalculator.calculate(rule);
|
||||
} else if (rule.getType().equals(LOG_ALERT_THRESHOLD_TYPE_PERIODIC)) {
|
||||
logCalculator.calculate(rule);
|
||||
}
|
||||
}
|
||||
|
||||
private final class ScheduledTaskState {
|
||||
|
||||
private final AlertDefine rule;
|
||||
private ScheduledFuture<?> scheduledFuture;
|
||||
private Future<?> runningFuture;
|
||||
private boolean running;
|
||||
private boolean pending;
|
||||
private boolean cancelled;
|
||||
|
||||
private ScheduledTaskState(AlertDefine rule) {
|
||||
this.rule = rule;
|
||||
}
|
||||
|
||||
private synchronized void setScheduledFuture(ScheduledFuture<?> scheduledFuture) {
|
||||
this.scheduledFuture = scheduledFuture;
|
||||
}
|
||||
|
||||
private synchronized void trigger() {
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
if (running) {
|
||||
pending = true;
|
||||
return;
|
||||
}
|
||||
running = true;
|
||||
submitLocked();
|
||||
}
|
||||
|
||||
private synchronized void cancel() {
|
||||
cancelled = true;
|
||||
pending = false;
|
||||
ScheduledFuture<?> periodicFuture = scheduledFuture;
|
||||
Future<?> currentFuture = runningFuture;
|
||||
if (periodicFuture != null) {
|
||||
periodicFuture.cancel(true);
|
||||
}
|
||||
if (currentFuture != null) {
|
||||
currentFuture.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void submitLocked() {
|
||||
try {
|
||||
runningFuture = periodicExecutor.submit(() -> {
|
||||
boolean permitAcquired = false;
|
||||
try {
|
||||
periodicPermits.acquire();
|
||||
permitAcquired = true;
|
||||
if (!Thread.currentThread().isInterrupted()) {
|
||||
executeRule(rule);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
} catch (Exception e) {
|
||||
log.error("Periodic alert rule {} execution error: {}", rule.getName(), e.getMessage(), e);
|
||||
} finally {
|
||||
if (permitAcquired) {
|
||||
periodicPermits.release();
|
||||
}
|
||||
onComplete();
|
||||
}
|
||||
});
|
||||
} catch (RuntimeException e) {
|
||||
running = false;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void onComplete() {
|
||||
runningFuture = null;
|
||||
if (cancelled) {
|
||||
running = false;
|
||||
pending = false;
|
||||
return;
|
||||
}
|
||||
if (!pending) {
|
||||
running = false;
|
||||
return;
|
||||
}
|
||||
pending = false;
|
||||
submitLocked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+41
-23
@@ -20,6 +20,11 @@ package org.apache.hertzbeat.alert.calculate.realtime.window;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
|
||||
import org.apache.hertzbeat.common.concurrent.ManagedExecutor;
|
||||
import org.apache.hertzbeat.common.concurrent.ManagedExecutors;
|
||||
import org.apache.hertzbeat.common.config.VirtualThreadProperties;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import org.apache.hertzbeat.alert.util.AlertTemplateUtil;
|
||||
@@ -32,10 +37,6 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Alarm Evaluator - Final alarm logic trigger
|
||||
@@ -47,36 +48,48 @@ import java.util.concurrent.TimeUnit;
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class AlarmEvaluator {
|
||||
public class AlarmEvaluator implements DisposableBean {
|
||||
|
||||
private static final String WINDOW_START_TIME = "window_start_time";
|
||||
private static final String WINDOW_END_TIME = "window_end_time";
|
||||
private static final String MATCHING_LOGS_COUNT = "matching_logs_count";
|
||||
|
||||
private final AlarmCommonReduce alarmCommonReduce;
|
||||
private ThreadPoolExecutor workerExecutor;
|
||||
private final ManagedExecutor workerExecutor;
|
||||
|
||||
public AlarmEvaluator(AlarmCommonReduce alarmCommonReduce) {
|
||||
this.alarmCommonReduce = alarmCommonReduce;
|
||||
initAlarmEvaluator();
|
||||
this(alarmCommonReduce, VirtualThreadProperties.defaults());
|
||||
}
|
||||
|
||||
public void initAlarmEvaluator() {
|
||||
ThreadFactory threadFactory = new ThreadFactoryBuilder()
|
||||
.setUncaughtExceptionHandler((thread, throwable) -> {
|
||||
log.error("alerter-reduce-worker has uncaughtException.");
|
||||
log.error(throwable.getMessage(), throwable);
|
||||
})
|
||||
.setDaemon(true)
|
||||
.setNameFormat("alerter-reduce-worker-%d")
|
||||
.build();
|
||||
workerExecutor = new ThreadPoolExecutor(2,
|
||||
@Autowired
|
||||
public AlarmEvaluator(AlarmCommonReduce alarmCommonReduce, VirtualThreadProperties virtualThreadProperties) {
|
||||
this.alarmCommonReduce = alarmCommonReduce;
|
||||
VirtualThreadProperties properties =
|
||||
virtualThreadProperties == null ? VirtualThreadProperties.defaults() : virtualThreadProperties;
|
||||
this.workerExecutor = initAlarmEvaluator(properties);
|
||||
}
|
||||
|
||||
public ManagedExecutor initAlarmEvaluator(VirtualThreadProperties properties) {
|
||||
Thread.UncaughtExceptionHandler handler = (thread, throwable) -> {
|
||||
log.error("alerter-reduce-worker has uncaughtException.");
|
||||
log.error(throwable.getMessage(), throwable);
|
||||
};
|
||||
if (properties.enabled()) {
|
||||
VirtualThreadProperties.QueueProperties queueProperties = properties.alerter().windowEvaluator();
|
||||
return ManagedExecutors.newQueuedVirtualExecutor("alerter-window-evaluator", "alerter-window-evaluator-",
|
||||
queueProperties.maxConcurrentJobs(), queueProperties.queueCapacity(), handler);
|
||||
}
|
||||
return ManagedExecutors.wrap("alerter-window-evaluator", new java.util.concurrent.ThreadPoolExecutor(2,
|
||||
10,
|
||||
10,
|
||||
TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(),
|
||||
threadFactory,
|
||||
new ThreadPoolExecutor.AbortPolicy());
|
||||
java.util.concurrent.TimeUnit.SECONDS,
|
||||
new java.util.concurrent.LinkedBlockingQueue<>(),
|
||||
new ThreadFactoryBuilder()
|
||||
.setUncaughtExceptionHandler(handler)
|
||||
.setDaemon(true)
|
||||
.setNameFormat("alerter-reduce-worker-%d")
|
||||
.build(),
|
||||
new java.util.concurrent.ThreadPoolExecutor.AbortPolicy()));
|
||||
}
|
||||
|
||||
public void sendAndProcessWindowData(WindowAggregator.WindowData windowData) {
|
||||
@@ -314,4 +327,9 @@ public class AlarmEvaluator {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
workerExecutor.close();
|
||||
}
|
||||
}
|
||||
|
||||
+23
-9
@@ -21,6 +21,7 @@ import com.google.common.collect.Maps;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.AlerterWorkerPool;
|
||||
import org.apache.hertzbeat.alert.config.AlertSseManager;
|
||||
@@ -121,14 +122,27 @@ public class AlertNoticeDispatch {
|
||||
}
|
||||
|
||||
private void sendNotify(GroupAlert alert) {
|
||||
matchNoticeRulesByAlert(alert).ifPresent(noticeRules -> noticeRules.forEach(rule -> workerPool.executeNotify(() -> rule.getReceiverId()
|
||||
.forEach(receiverId -> {
|
||||
try {
|
||||
sendNoticeMsg(getOneReceiverById(receiverId),
|
||||
getOneTemplateById(rule.getTemplateId()), alert);
|
||||
} catch (AlertNoticeException e) {
|
||||
log.warn("DispatchTask sendNoticeMsg error, message: {}", e.getMessage());
|
||||
}
|
||||
}))));
|
||||
matchNoticeRulesByAlert(alert).ifPresent(noticeRules -> noticeRules.forEach(rule -> {
|
||||
NoticeTemplate noticeTemplate = getOneTemplateById(rule.getTemplateId());
|
||||
rule.getReceiverId().forEach(receiverId -> {
|
||||
NoticeReceiver receiver = getOneReceiverById(receiverId);
|
||||
if (receiver == null || receiver.getType() == null) {
|
||||
log.warn("DispatchTask skip invalid receiver, receiverId: {}, alertId: {}", receiverId, alert.getId());
|
||||
return;
|
||||
}
|
||||
try {
|
||||
workerPool.executeNotify(receiver.getType(), () -> {
|
||||
try {
|
||||
sendNoticeMsg(receiver, noticeTemplate, alert);
|
||||
} catch (AlertNoticeException e) {
|
||||
log.warn("DispatchTask sendNoticeMsg error, message: {}", e.getMessage());
|
||||
}
|
||||
});
|
||||
} catch (RejectedExecutionException e) {
|
||||
log.warn("DispatchTask rejected notify task, receiverId: {}, type: {}, message: {}",
|
||||
receiverId, receiver.getType(), e.getMessage());
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
+41
-28
@@ -18,16 +18,16 @@
|
||||
package org.apache.hertzbeat.alert.reduce;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.common.concurrent.ManagedExecutor;
|
||||
import org.apache.hertzbeat.common.concurrent.ManagedExecutors;
|
||||
import org.apache.hertzbeat.common.config.VirtualThreadProperties;
|
||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
@@ -35,33 +35,45 @@ import org.springframework.stereotype.Service;
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class AlarmCommonReduce {
|
||||
public class AlarmCommonReduce implements DisposableBean {
|
||||
|
||||
private final AlarmGroupReduce alarmGroupReduce;
|
||||
|
||||
private ThreadPoolExecutor workerExecutor;
|
||||
|
||||
private final ManagedExecutor workerExecutor;
|
||||
|
||||
public AlarmCommonReduce(AlarmGroupReduce alarmGroupReduce) {
|
||||
initWorkExecutor();
|
||||
this.alarmGroupReduce = alarmGroupReduce;
|
||||
this(alarmGroupReduce, VirtualThreadProperties.defaults());
|
||||
}
|
||||
|
||||
private void initWorkExecutor() {
|
||||
ThreadFactory threadFactory = new ThreadFactoryBuilder()
|
||||
.setUncaughtExceptionHandler((thread, throwable) -> {
|
||||
log.error("alerter-reduce-worker has uncaughtException.");
|
||||
log.error(throwable.getMessage(), throwable);
|
||||
})
|
||||
.setDaemon(true)
|
||||
.setNameFormat("alerter-reduce-worker-%d")
|
||||
.build();
|
||||
workerExecutor = new ThreadPoolExecutor(2,
|
||||
@Autowired
|
||||
public AlarmCommonReduce(AlarmGroupReduce alarmGroupReduce, VirtualThreadProperties virtualThreadProperties) {
|
||||
this.alarmGroupReduce = alarmGroupReduce;
|
||||
VirtualThreadProperties properties =
|
||||
virtualThreadProperties == null ? VirtualThreadProperties.defaults() : virtualThreadProperties;
|
||||
this.workerExecutor = initWorkExecutor(properties);
|
||||
}
|
||||
|
||||
private ManagedExecutor initWorkExecutor(VirtualThreadProperties properties) {
|
||||
Thread.UncaughtExceptionHandler handler = (thread, throwable) -> {
|
||||
log.error("alerter-reduce-worker has uncaughtException.");
|
||||
log.error(throwable.getMessage(), throwable);
|
||||
};
|
||||
if (properties.enabled()) {
|
||||
VirtualThreadProperties.QueueProperties queueProperties = properties.alerter().reduce();
|
||||
return ManagedExecutors.newQueuedVirtualExecutor("alerter-reduce-worker", "alerter-reduce-worker-",
|
||||
queueProperties.maxConcurrentJobs(), queueProperties.queueCapacity(), handler);
|
||||
}
|
||||
return ManagedExecutors.wrap("alerter-reduce-worker", new java.util.concurrent.ThreadPoolExecutor(2,
|
||||
2,
|
||||
10,
|
||||
TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(),
|
||||
threadFactory,
|
||||
new ThreadPoolExecutor.AbortPolicy());
|
||||
java.util.concurrent.TimeUnit.SECONDS,
|
||||
new java.util.concurrent.LinkedBlockingQueue<>(),
|
||||
new ThreadFactoryBuilder()
|
||||
.setUncaughtExceptionHandler(handler)
|
||||
.setDaemon(true)
|
||||
.setNameFormat("alerter-reduce-worker-%d")
|
||||
.build(),
|
||||
new java.util.concurrent.ThreadPoolExecutor.AbortPolicy()));
|
||||
}
|
||||
|
||||
|
||||
@@ -103,10 +115,6 @@ public class AlarmCommonReduce {
|
||||
* Fingerprint is based on labels excluding timestamp related fields
|
||||
*/
|
||||
private String generateAlertFingerprint(Map<String, String> labels) {
|
||||
// Remove timestamp related fields
|
||||
labels.remove("timestamp");
|
||||
labels.remove("start_at");
|
||||
labels.remove("active_at");
|
||||
return labels.entrySet().stream()
|
||||
.filter(e -> !"timestamp".equals(e.getKey())
|
||||
&& !"starts_at".equals(e.getKey()) && !"actives_at".equals(e.getKey())
|
||||
@@ -116,4 +124,9 @@ public class AlarmCommonReduce {
|
||||
.map(e -> e.getKey() + ":" + e.getValue())
|
||||
.collect(Collectors.joining(","));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
workerExecutor.close();
|
||||
}
|
||||
}
|
||||
|
||||
+140
-17
@@ -26,17 +26,22 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.dao.AlertGroupConvergeDao;
|
||||
import org.apache.hertzbeat.common.config.VirtualThreadProperties;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertGroupConverge;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
@@ -45,7 +50,7 @@ import org.springframework.stereotype.Component;
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class AlarmGroupReduce {
|
||||
public class AlarmGroupReduce implements DisposableBean {
|
||||
|
||||
/**
|
||||
* Default initial group wait time 30s
|
||||
@@ -88,16 +93,60 @@ public class AlarmGroupReduce {
|
||||
*/
|
||||
private final Map<String, GroupAlertCache> groupCacheMap;
|
||||
|
||||
private final ScheduledExecutorService scheduledExecutor;
|
||||
|
||||
private final ExecutorService workerExecutor;
|
||||
|
||||
private final ScheduledDispatchTask checkTask;
|
||||
|
||||
public AlarmGroupReduce(AlarmInhibitReduce alarmInhibitReduce, AlertGroupConvergeDao alertGroupConvergeDao) {
|
||||
this(alarmInhibitReduce, alertGroupConvergeDao, VirtualThreadProperties.defaults(), true);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public AlarmGroupReduce(AlarmInhibitReduce alarmInhibitReduce, AlertGroupConvergeDao alertGroupConvergeDao,
|
||||
VirtualThreadProperties virtualThreadProperties) {
|
||||
this(alarmInhibitReduce, alertGroupConvergeDao, virtualThreadProperties, true);
|
||||
}
|
||||
|
||||
AlarmGroupReduce(AlarmInhibitReduce alarmInhibitReduce, AlertGroupConvergeDao alertGroupConvergeDao,
|
||||
VirtualThreadProperties virtualThreadProperties, boolean autoStart) {
|
||||
this.alarmInhibitReduce = alarmInhibitReduce;
|
||||
this.groupDefines = new ConcurrentHashMap<>(8);
|
||||
this.groupCacheMap = new ConcurrentHashMap<>(8);
|
||||
VirtualThreadProperties properties =
|
||||
virtualThreadProperties == null ? VirtualThreadProperties.defaults() : virtualThreadProperties;
|
||||
this.scheduledExecutor = createScheduler();
|
||||
this.workerExecutor = createVirtualExecutor(properties);
|
||||
this.checkTask = new ScheduledDispatchTask(workerExecutor, this::runCheckAndSendGroups);
|
||||
List<AlertGroupConverge> groupConverges = alertGroupConvergeDao.findAlertGroupConvergesByEnableIsTrue();
|
||||
refreshGroupDefines(groupConverges);
|
||||
startCheckAndSendGroups();
|
||||
if (autoStart) {
|
||||
startCheckAndSendGroups();
|
||||
}
|
||||
}
|
||||
|
||||
private void startCheckAndSendGroups() {
|
||||
scheduledExecutor.scheduleAtFixedRate(this::dispatchCheckAndSendGroups, 10000, CHECK_INTERVAL,
|
||||
TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
void dispatchCheckAndSendGroups() {
|
||||
checkTask.dispatch();
|
||||
}
|
||||
|
||||
void beforeCheckAndSendGroupsRun() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
scheduledExecutor.shutdownNow();
|
||||
if (workerExecutor != null) {
|
||||
workerExecutor.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
private ScheduledExecutorService createScheduler() {
|
||||
ThreadFactory threadFactory = new ThreadFactoryBuilder()
|
||||
.setUncaughtExceptionHandler((thread, throwable) -> {
|
||||
log.error("Check alarm groups calculate has uncaughtException.");
|
||||
@@ -106,21 +155,36 @@ public class AlarmGroupReduce {
|
||||
.setDaemon(true)
|
||||
.setNameFormat("alarm-group-calculate-%d")
|
||||
.build();
|
||||
ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor(threadFactory);
|
||||
scheduledExecutor.scheduleAtFixedRate(() -> {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
groupCacheMap.forEach((groupKey, cache) -> {
|
||||
if (shouldSendGroup(cache, now)) {
|
||||
sendGroupAlert(cache);
|
||||
cache.setLastSendTime(now);
|
||||
cache.getAlertFingerprints().clear();
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
log.error("Check alarm groups calculate has exception.: {}", e.getMessage(), e);
|
||||
}
|
||||
}, 10000, CHECK_INTERVAL, java.util.concurrent.TimeUnit.MILLISECONDS);
|
||||
return Executors.newSingleThreadScheduledExecutor(threadFactory);
|
||||
}
|
||||
|
||||
private ExecutorService createVirtualExecutor(VirtualThreadProperties properties) {
|
||||
if (!properties.enabled()) {
|
||||
return null;
|
||||
}
|
||||
return Executors.newThreadPerTaskExecutor(Thread.ofVirtual()
|
||||
.name("alarm-group-calculate-vt-", 0)
|
||||
.uncaughtExceptionHandler((thread, throwable) -> {
|
||||
log.error("Check alarm groups calculate worker has uncaughtException.");
|
||||
log.error(throwable.getMessage(), throwable);
|
||||
})
|
||||
.factory());
|
||||
}
|
||||
|
||||
private void runCheckAndSendGroups() {
|
||||
beforeCheckAndSendGroupsRun();
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
groupCacheMap.forEach((groupKey, cache) -> {
|
||||
if (shouldSendGroup(cache, now)) {
|
||||
sendGroupAlert(cache);
|
||||
cache.setLastSendTime(now);
|
||||
cache.getAlertFingerprints().clear();
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
log.error("Check alarm groups calculate has exception.: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -339,4 +403,63 @@ public class AlarmGroupReduce {
|
||||
private long lastSendTime;
|
||||
private long lastRepeatTime;
|
||||
}
|
||||
|
||||
private static final class ScheduledDispatchTask {
|
||||
|
||||
private final ExecutorService executor;
|
||||
|
||||
private final Runnable task;
|
||||
|
||||
private boolean running;
|
||||
|
||||
private int pendingRuns;
|
||||
|
||||
private ScheduledDispatchTask(ExecutorService executor, Runnable task) {
|
||||
this.executor = executor;
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
private void dispatch() {
|
||||
boolean shouldSchedule;
|
||||
synchronized (this) {
|
||||
pendingRuns++;
|
||||
shouldSchedule = !running;
|
||||
if (shouldSchedule) {
|
||||
running = true;
|
||||
}
|
||||
}
|
||||
if (shouldSchedule) {
|
||||
scheduleRun();
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleRun() {
|
||||
if (executor != null) {
|
||||
executor.execute(this::runOnce);
|
||||
} else {
|
||||
runOnce();
|
||||
}
|
||||
}
|
||||
|
||||
private void runOnce() {
|
||||
try {
|
||||
task.run();
|
||||
} finally {
|
||||
scheduleNextIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleNextIfNeeded() {
|
||||
boolean shouldSchedule;
|
||||
synchronized (this) {
|
||||
pendingRuns = Math.max(0, pendingRuns - 1);
|
||||
shouldSchedule = pendingRuns > 0;
|
||||
if (!shouldSchedule) {
|
||||
running = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
scheduleRun();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+144
-22
@@ -18,25 +18,28 @@
|
||||
package org.apache.hertzbeat.alert.reduce;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.alert.AlerterProperties;
|
||||
import org.apache.hertzbeat.alert.dao.AlertInhibitDao;
|
||||
import org.apache.hertzbeat.common.config.VirtualThreadProperties;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertInhibit;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.Collections;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
@@ -46,7 +49,7 @@ import lombok.AllArgsConstructor;
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class AlarmInhibitReduce {
|
||||
public class AlarmInhibitReduce implements DisposableBean {
|
||||
|
||||
/**
|
||||
* Interval for checking and cleaning up expired source alerts
|
||||
@@ -73,22 +76,69 @@ public class AlarmInhibitReduce {
|
||||
/**
|
||||
* Default TTL for source alerts (4 hours)
|
||||
*/
|
||||
private static long SOURCE_ALERT_TTL = 4 * 60 * 60 * 1000L;
|
||||
private final long sourceAlertTtl;
|
||||
|
||||
private final ScheduledExecutorService cleanupScheduler;
|
||||
|
||||
private final ExecutorService cleanupExecutor;
|
||||
|
||||
private final ScheduledDispatchTask cleanupTask;
|
||||
|
||||
public AlarmInhibitReduce(AlarmSilenceReduce alarmSilenceReduce, AlertInhibitDao alertInhibitDao
|
||||
, AlerterProperties alerterProperties) {
|
||||
this(alarmSilenceReduce, alertInhibitDao, alerterProperties, VirtualThreadProperties.defaults(), true);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public AlarmInhibitReduce(AlarmSilenceReduce alarmSilenceReduce, AlertInhibitDao alertInhibitDao,
|
||||
AlerterProperties alerterProperties, VirtualThreadProperties virtualThreadProperties) {
|
||||
this(alarmSilenceReduce, alertInhibitDao, alerterProperties, virtualThreadProperties, true);
|
||||
}
|
||||
|
||||
AlarmInhibitReduce(AlarmSilenceReduce alarmSilenceReduce, AlertInhibitDao alertInhibitDao,
|
||||
AlerterProperties alerterProperties, VirtualThreadProperties virtualThreadProperties,
|
||||
boolean autoStart) {
|
||||
this.alarmSilenceReduce = alarmSilenceReduce;
|
||||
VirtualThreadProperties properties =
|
||||
virtualThreadProperties == null ? VirtualThreadProperties.defaults() : virtualThreadProperties;
|
||||
if (alerterProperties.getInhibit() != null && alerterProperties.getInhibit().getTtl() > 0) {
|
||||
SOURCE_ALERT_TTL = alerterProperties.getInhibit().getTtl();
|
||||
this.sourceAlertTtl = alerterProperties.getInhibit().getTtl();
|
||||
} else {
|
||||
this.sourceAlertTtl = 4 * 60 * 60 * 1000L;
|
||||
}
|
||||
inhibitRules = new ConcurrentHashMap<>(8);
|
||||
sourceAlertCache = new ConcurrentHashMap<>(8);
|
||||
this.cleanupScheduler = createCleanupScheduler();
|
||||
this.cleanupExecutor = createCleanupExecutor(properties);
|
||||
this.cleanupTask = new ScheduledDispatchTask(cleanupExecutor, this::runCleanupCache);
|
||||
List<AlertInhibit> inhibits = alertInhibitDao.findAlertInhibitsByEnableIsTrue();
|
||||
refreshInhibitRules(inhibits);
|
||||
startScheduledCleanupCache();
|
||||
if (autoStart) {
|
||||
startScheduledCleanupCache();
|
||||
}
|
||||
}
|
||||
|
||||
private void startScheduledCleanupCache() {
|
||||
cleanupScheduler.scheduleAtFixedRate(this::dispatchCleanupCache, CHECK_INTERVAL, CHECK_INTERVAL,
|
||||
TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
void dispatchCleanupCache() {
|
||||
cleanupTask.dispatch();
|
||||
}
|
||||
|
||||
void beforeCleanupCacheRun() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
cleanupScheduler.shutdownNow();
|
||||
if (cleanupExecutor != null) {
|
||||
cleanupExecutor.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
private ScheduledExecutorService createCleanupScheduler() {
|
||||
ThreadFactory threadFactory = new ThreadFactoryBuilder()
|
||||
.setUncaughtExceptionHandler((thread, throwable) -> {
|
||||
log.error("Scheduled clean up inhibit cache has uncaughtException.");
|
||||
@@ -97,17 +147,30 @@ public class AlarmInhibitReduce {
|
||||
.setDaemon(true)
|
||||
.setNameFormat("inhibit-clean-up-%d")
|
||||
.build();
|
||||
ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor(threadFactory);
|
||||
// Scheduled cleanup of all expired source alerts
|
||||
scheduledExecutor.scheduleAtFixedRate(() -> {
|
||||
try {
|
||||
sourceAlertCache.values().forEach(this::cleanupExpiredEntries);
|
||||
// Remove empty rule caches
|
||||
sourceAlertCache.entrySet().removeIf(entry -> entry.getValue().isEmpty());
|
||||
} catch (Exception e) {
|
||||
log.error("Error during scheduled cleanup", e);
|
||||
}
|
||||
}, CHECK_INTERVAL, CHECK_INTERVAL, TimeUnit.MILLISECONDS);
|
||||
return Executors.newSingleThreadScheduledExecutor(threadFactory);
|
||||
}
|
||||
|
||||
private ExecutorService createCleanupExecutor(VirtualThreadProperties properties) {
|
||||
if (!properties.enabled()) {
|
||||
return null;
|
||||
}
|
||||
return Executors.newThreadPerTaskExecutor(Thread.ofVirtual()
|
||||
.name("inhibit-clean-up-vt-", 0)
|
||||
.uncaughtExceptionHandler((thread, throwable) -> {
|
||||
log.error("Scheduled clean up inhibit cache worker has uncaughtException.");
|
||||
log.error(throwable.getMessage(), throwable);
|
||||
})
|
||||
.factory());
|
||||
}
|
||||
|
||||
private void runCleanupCache() {
|
||||
beforeCleanupCacheRun();
|
||||
try {
|
||||
sourceAlertCache.values().forEach(this::cleanupExpiredEntries);
|
||||
sourceAlertCache.entrySet().removeIf(entry -> entry.getValue().isEmpty());
|
||||
} catch (Exception e) {
|
||||
log.error("Error during scheduled cleanup", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -264,7 +327,7 @@ public class AlarmInhibitReduce {
|
||||
SourceAlertEntry entry = new SourceAlertEntry(
|
||||
alert,
|
||||
System.currentTimeMillis(),
|
||||
System.currentTimeMillis() + SOURCE_ALERT_TTL
|
||||
System.currentTimeMillis() + sourceAlertTtl
|
||||
);
|
||||
ruleCache.put(alert.getFingerprint(), entry);
|
||||
cleanupExpiredEntries(ruleCache);
|
||||
@@ -315,4 +378,63 @@ public class AlarmInhibitReduce {
|
||||
private final long createTime;
|
||||
private final long expiryTime;
|
||||
}
|
||||
|
||||
private static final class ScheduledDispatchTask {
|
||||
|
||||
private final ExecutorService executor;
|
||||
|
||||
private final Runnable task;
|
||||
|
||||
private boolean running;
|
||||
|
||||
private int pendingRuns;
|
||||
|
||||
private ScheduledDispatchTask(ExecutorService executor, Runnable task) {
|
||||
this.executor = executor;
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
private void dispatch() {
|
||||
boolean shouldSchedule;
|
||||
synchronized (this) {
|
||||
pendingRuns++;
|
||||
shouldSchedule = !running;
|
||||
if (shouldSchedule) {
|
||||
running = true;
|
||||
}
|
||||
}
|
||||
if (shouldSchedule) {
|
||||
scheduleRun();
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleRun() {
|
||||
if (executor != null) {
|
||||
executor.execute(this::runOnce);
|
||||
} else {
|
||||
runOnce();
|
||||
}
|
||||
}
|
||||
|
||||
private void runOnce() {
|
||||
try {
|
||||
task.run();
|
||||
} finally {
|
||||
scheduleNextIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleNextIfNeeded() {
|
||||
boolean shouldSchedule;
|
||||
synchronized (this) {
|
||||
pendingRuns = Math.max(0, pendingRuns - 1);
|
||||
shouldSchedule = pendingRuns > 0;
|
||||
if (!shouldSchedule) {
|
||||
running = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
scheduleRun();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+163
-36
@@ -18,9 +18,18 @@
|
||||
package org.apache.hertzbeat.alert;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.apache.hertzbeat.common.concurrent.AdmissionMode;
|
||||
import org.apache.hertzbeat.common.config.VirtualThreadProperties;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
@@ -28,60 +37,178 @@ import org.junit.jupiter.api.Test;
|
||||
*/
|
||||
class AlerterWorkerPoolTest {
|
||||
|
||||
private static final int NUMBER_OF_THREADS = 10;
|
||||
private AlerterWorkerPool pool;
|
||||
private AtomicInteger counter;
|
||||
private CountDownLatch latch;
|
||||
private static final int NUMBER_OF_TASKS = 10;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
pool = new AlerterWorkerPool();
|
||||
counter = new AtomicInteger();
|
||||
latch = new CountDownLatch(NUMBER_OF_THREADS);
|
||||
private AlerterWorkerPool pool;
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
if (pool != null) {
|
||||
pool.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void executeJob() throws InterruptedException {
|
||||
for (int i = 0; i < NUMBER_OF_THREADS; i++) {
|
||||
pool = new AlerterWorkerPool();
|
||||
AtomicInteger counter = new AtomicInteger();
|
||||
CountDownLatch latch = new CountDownLatch(NUMBER_OF_TASKS);
|
||||
|
||||
for (int i = 0; i < NUMBER_OF_TASKS; i++) {
|
||||
pool.executeJob(() -> {
|
||||
counter.incrementAndGet();
|
||||
latch.countDown();
|
||||
});
|
||||
}
|
||||
latch.await();
|
||||
|
||||
assertEquals(NUMBER_OF_THREADS, counter.get());
|
||||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
assertEquals(NUMBER_OF_TASKS, counter.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void executeNotify() throws InterruptedException {
|
||||
counter = new AtomicInteger();
|
||||
latch = new CountDownLatch(NUMBER_OF_THREADS);
|
||||
|
||||
for (int i = 0; i < NUMBER_OF_THREADS; i++) {
|
||||
pool.executeNotify(() -> {
|
||||
counter.incrementAndGet();
|
||||
latch.countDown();
|
||||
});
|
||||
}
|
||||
latch.await();
|
||||
void executeNotifyRunsOnVirtualThread() throws Exception {
|
||||
pool = new AlerterWorkerPool();
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicBoolean virtualThread = new AtomicBoolean(false);
|
||||
|
||||
assertEquals(NUMBER_OF_THREADS, counter.get());
|
||||
pool.executeNotify((byte) 1, () -> {
|
||||
virtualThread.set(Thread.currentThread().isVirtual());
|
||||
latch.countDown();
|
||||
});
|
||||
|
||||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
assertTrue(virtualThread.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void executeNotifyRejectsWhenGlobalConcurrencyLimitReached() throws Exception {
|
||||
VirtualThreadProperties properties = new VirtualThreadProperties(
|
||||
true,
|
||||
VirtualThreadProperties.PoolProperties.collectorDefaults(),
|
||||
VirtualThreadProperties.PoolProperties.commonDefaults(),
|
||||
VirtualThreadProperties.PoolProperties.managerDefaults(),
|
||||
new VirtualThreadProperties.AlerterProperties(
|
||||
new VirtualThreadProperties.PoolProperties(AdmissionMode.LIMIT_AND_REJECT, 1),
|
||||
10,
|
||||
VirtualThreadProperties.QueueProperties.logWorkerDefaults(),
|
||||
VirtualThreadProperties.QueueProperties.reduceDefaults(),
|
||||
VirtualThreadProperties.QueueProperties.windowEvaluatorDefaults(),
|
||||
8),
|
||||
VirtualThreadProperties.PoolProperties.warehouseDefaults(),
|
||||
VirtualThreadProperties.AsyncProperties.defaults());
|
||||
pool = new AlerterWorkerPool(properties);
|
||||
|
||||
CountDownLatch started = new CountDownLatch(1);
|
||||
CountDownLatch release = new CountDownLatch(1);
|
||||
pool.executeNotify((byte) 1, () -> {
|
||||
started.countDown();
|
||||
try {
|
||||
release.await(5, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
});
|
||||
assertTrue(started.await(5, TimeUnit.SECONDS));
|
||||
|
||||
try {
|
||||
assertThrows(RejectedExecutionException.class, () -> pool.executeNotify((byte) 2, () -> {
|
||||
}));
|
||||
} finally {
|
||||
release.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void executeNotifyRejectsWhenChannelLimitReached() throws Exception {
|
||||
VirtualThreadProperties properties = new VirtualThreadProperties(
|
||||
true,
|
||||
VirtualThreadProperties.PoolProperties.collectorDefaults(),
|
||||
VirtualThreadProperties.PoolProperties.commonDefaults(),
|
||||
VirtualThreadProperties.PoolProperties.managerDefaults(),
|
||||
new VirtualThreadProperties.AlerterProperties(
|
||||
new VirtualThreadProperties.PoolProperties(AdmissionMode.LIMIT_AND_REJECT, 8),
|
||||
10,
|
||||
VirtualThreadProperties.QueueProperties.logWorkerDefaults(),
|
||||
VirtualThreadProperties.QueueProperties.reduceDefaults(),
|
||||
VirtualThreadProperties.QueueProperties.windowEvaluatorDefaults(),
|
||||
1),
|
||||
VirtualThreadProperties.PoolProperties.warehouseDefaults(),
|
||||
VirtualThreadProperties.AsyncProperties.defaults());
|
||||
pool = new AlerterWorkerPool(properties);
|
||||
|
||||
CountDownLatch started = new CountDownLatch(1);
|
||||
CountDownLatch release = new CountDownLatch(1);
|
||||
pool.executeNotify((byte) 1, () -> {
|
||||
started.countDown();
|
||||
try {
|
||||
release.await(5, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
});
|
||||
assertTrue(started.await(5, TimeUnit.SECONDS));
|
||||
|
||||
try {
|
||||
assertThrows(RejectedExecutionException.class, () -> pool.executeNotify((byte) 1, () -> {
|
||||
}));
|
||||
} finally {
|
||||
release.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void executeLogJob() throws InterruptedException {
|
||||
counter = new AtomicInteger();
|
||||
latch = new CountDownLatch(NUMBER_OF_THREADS);
|
||||
|
||||
for (int i = 0; i < NUMBER_OF_THREADS; i++) {
|
||||
pool.executeLogJob(() -> {
|
||||
counter.incrementAndGet();
|
||||
latch.countDown();
|
||||
});
|
||||
}
|
||||
latch.await();
|
||||
pool = new AlerterWorkerPool();
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicBoolean virtualThread = new AtomicBoolean(false);
|
||||
|
||||
assertEquals(NUMBER_OF_THREADS, counter.get());
|
||||
pool.executeLogJob(() -> {
|
||||
virtualThread.set(Thread.currentThread().isVirtual());
|
||||
latch.countDown();
|
||||
});
|
||||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
assertTrue(virtualThread.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void executeLogJobRejectsWhenQueueCapacityReached() throws InterruptedException {
|
||||
VirtualThreadProperties properties = new VirtualThreadProperties(
|
||||
true,
|
||||
VirtualThreadProperties.PoolProperties.collectorDefaults(),
|
||||
VirtualThreadProperties.PoolProperties.commonDefaults(),
|
||||
VirtualThreadProperties.PoolProperties.managerDefaults(),
|
||||
new VirtualThreadProperties.AlerterProperties(
|
||||
VirtualThreadProperties.PoolProperties.alerterNotifyDefaults(),
|
||||
10,
|
||||
new VirtualThreadProperties.QueueProperties(1, 1),
|
||||
VirtualThreadProperties.QueueProperties.reduceDefaults(),
|
||||
VirtualThreadProperties.QueueProperties.windowEvaluatorDefaults(),
|
||||
4),
|
||||
VirtualThreadProperties.PoolProperties.warehouseDefaults(),
|
||||
VirtualThreadProperties.AsyncProperties.defaults());
|
||||
pool = new AlerterWorkerPool(properties);
|
||||
|
||||
CountDownLatch firstStarted = new CountDownLatch(1);
|
||||
CountDownLatch releaseFirst = new CountDownLatch(1);
|
||||
CountDownLatch secondStarted = new CountDownLatch(1);
|
||||
pool.executeLogJob(() -> {
|
||||
firstStarted.countDown();
|
||||
try {
|
||||
releaseFirst.await(5, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
});
|
||||
assertTrue(firstStarted.await(5, TimeUnit.SECONDS));
|
||||
|
||||
pool.executeLogJob(secondStarted::countDown);
|
||||
assertFalse(secondStarted.await(200, TimeUnit.MILLISECONDS));
|
||||
try {
|
||||
assertThrows(RejectedExecutionException.class, () -> pool.executeLogJob(() -> {
|
||||
}));
|
||||
} finally {
|
||||
releaseFirst.countDown();
|
||||
}
|
||||
assertTrue(secondStarted.await(5, TimeUnit.SECONDS));
|
||||
}
|
||||
}
|
||||
|
||||
+213
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.alert.calculate.periodic;
|
||||
|
||||
import static org.apache.hertzbeat.common.constants.CommonConstants.METRIC_ALERT_THRESHOLD_TYPE_PERIODIC;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.apache.hertzbeat.alert.dao.AlertDefineDao;
|
||||
import org.apache.hertzbeat.common.config.VirtualThreadProperties;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
/**
|
||||
* Tests for {@link PeriodicAlertRuleScheduler}.
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PeriodicAlertRuleSchedulerTest {
|
||||
|
||||
@Mock
|
||||
private MetricsPeriodicAlertCalculator metricsCalculator;
|
||||
|
||||
@Mock
|
||||
private LogPeriodicAlertCalculator logCalculator;
|
||||
|
||||
@Mock
|
||||
private AlertDefineDao alertDefineDao;
|
||||
|
||||
private PeriodicAlertRuleScheduler scheduler;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
scheduler = new PeriodicAlertRuleScheduler(metricsCalculator, logCalculator, alertDefineDao,
|
||||
VirtualThreadProperties.defaults());
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
if (scheduler != null) {
|
||||
scheduler.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateScheduleRunsPeriodicCalculationOnVirtualThread() throws InterruptedException {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicBoolean virtualThread = new AtomicBoolean(false);
|
||||
doAnswer(invocation -> {
|
||||
virtualThread.set(Thread.currentThread().isVirtual());
|
||||
latch.countDown();
|
||||
return null;
|
||||
}).when(metricsCalculator).calculate(any(AlertDefine.class));
|
||||
|
||||
AlertDefine rule = metricRule(1L);
|
||||
scheduler.updateSchedule(rule);
|
||||
|
||||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
assertTrue(virtualThread.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateScheduleKeepsSingleInFlightExecutionPerRule() throws InterruptedException {
|
||||
CountDownLatch firstStarted = new CountDownLatch(1);
|
||||
CountDownLatch releaseFirst = new CountDownLatch(1);
|
||||
CountDownLatch secondStarted = new CountDownLatch(1);
|
||||
AtomicInteger concurrent = new AtomicInteger();
|
||||
AtomicInteger maxConcurrent = new AtomicInteger();
|
||||
AtomicInteger invocations = new AtomicInteger();
|
||||
doAnswer(invocation -> {
|
||||
int active = concurrent.incrementAndGet();
|
||||
maxConcurrent.updateAndGet(current -> Math.max(current, active));
|
||||
int count = invocations.incrementAndGet();
|
||||
try {
|
||||
if (count == 1) {
|
||||
firstStarted.countDown();
|
||||
releaseFirst.await(5, TimeUnit.SECONDS);
|
||||
} else if (count == 2) {
|
||||
secondStarted.countDown();
|
||||
}
|
||||
} finally {
|
||||
concurrent.decrementAndGet();
|
||||
}
|
||||
return null;
|
||||
}).when(metricsCalculator).calculate(any(AlertDefine.class));
|
||||
|
||||
scheduler.updateSchedule(metricRule(2L));
|
||||
|
||||
assertTrue(firstStarted.await(5, TimeUnit.SECONDS));
|
||||
Thread.sleep(1200L);
|
||||
assertEquals(1, maxConcurrent.get());
|
||||
|
||||
releaseFirst.countDown();
|
||||
assertTrue(secondStarted.await(5, TimeUnit.SECONDS));
|
||||
assertEquals(1, maxConcurrent.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void cancelScheduleInterruptsRunningVirtualTask() throws InterruptedException {
|
||||
CountDownLatch started = new CountDownLatch(1);
|
||||
CountDownLatch interrupted = new CountDownLatch(1);
|
||||
doAnswer(invocation -> {
|
||||
started.countDown();
|
||||
try {
|
||||
Thread.sleep(5000L);
|
||||
} catch (InterruptedException e) {
|
||||
interrupted.countDown();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
return null;
|
||||
}).when(metricsCalculator).calculate(any(AlertDefine.class));
|
||||
|
||||
AlertDefine rule = metricRule(3L);
|
||||
scheduler.updateSchedule(rule);
|
||||
|
||||
assertTrue(started.await(5, TimeUnit.SECONDS));
|
||||
scheduler.cancelSchedule(rule.getId());
|
||||
assertTrue(interrupted.await(5, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateScheduleHonorsConfiguredGlobalPeriodicConcurrencyLimit() throws InterruptedException {
|
||||
scheduler.destroy();
|
||||
scheduler = new PeriodicAlertRuleScheduler(metricsCalculator, logCalculator, alertDefineDao,
|
||||
periodicProperties(1));
|
||||
|
||||
CountDownLatch firstStarted = new CountDownLatch(1);
|
||||
CountDownLatch releaseFirst = new CountDownLatch(1);
|
||||
CountDownLatch secondStarted = new CountDownLatch(1);
|
||||
AtomicInteger concurrent = new AtomicInteger();
|
||||
AtomicInteger maxConcurrent = new AtomicInteger();
|
||||
doAnswer(invocation -> {
|
||||
int active = concurrent.incrementAndGet();
|
||||
maxConcurrent.updateAndGet(current -> Math.max(current, active));
|
||||
AlertDefine rule = invocation.getArgument(0);
|
||||
try {
|
||||
if (rule.getId().equals(4L)) {
|
||||
firstStarted.countDown();
|
||||
releaseFirst.await(5, TimeUnit.SECONDS);
|
||||
} else if (rule.getId().equals(5L)) {
|
||||
secondStarted.countDown();
|
||||
}
|
||||
} finally {
|
||||
concurrent.decrementAndGet();
|
||||
}
|
||||
return null;
|
||||
}).when(metricsCalculator).calculate(any(AlertDefine.class));
|
||||
|
||||
scheduler.updateSchedule(metricRule(4L));
|
||||
assertTrue(firstStarted.await(5, TimeUnit.SECONDS));
|
||||
|
||||
scheduler.updateSchedule(metricRule(5L));
|
||||
Thread.sleep(200L);
|
||||
assertEquals(1, maxConcurrent.get());
|
||||
assertEquals(1L, secondStarted.getCount());
|
||||
|
||||
releaseFirst.countDown();
|
||||
assertTrue(secondStarted.await(5, TimeUnit.SECONDS));
|
||||
assertEquals(1, maxConcurrent.get());
|
||||
}
|
||||
|
||||
private AlertDefine metricRule(Long id) {
|
||||
return AlertDefine.builder()
|
||||
.id(id)
|
||||
.name("periodic-rule-" + id)
|
||||
.type(METRIC_ALERT_THRESHOLD_TYPE_PERIODIC)
|
||||
.period(1)
|
||||
.enable(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
private VirtualThreadProperties periodicProperties(int maxConcurrentJobs) {
|
||||
return new VirtualThreadProperties(
|
||||
true,
|
||||
VirtualThreadProperties.PoolProperties.collectorDefaults(),
|
||||
VirtualThreadProperties.PoolProperties.commonDefaults(),
|
||||
VirtualThreadProperties.PoolProperties.managerDefaults(),
|
||||
new VirtualThreadProperties.AlerterProperties(
|
||||
VirtualThreadProperties.PoolProperties.alerterNotifyDefaults(),
|
||||
maxConcurrentJobs,
|
||||
VirtualThreadProperties.QueueProperties.logWorkerDefaults(),
|
||||
VirtualThreadProperties.QueueProperties.reduceDefaults(),
|
||||
VirtualThreadProperties.QueueProperties.windowEvaluatorDefaults(),
|
||||
4),
|
||||
VirtualThreadProperties.PoolProperties.warehouseDefaults(),
|
||||
VirtualThreadProperties.AsyncProperties.defaults());
|
||||
}
|
||||
}
|
||||
+115
@@ -19,9 +19,11 @@ package org.apache.hertzbeat.alert.calculate.realtime.window;
|
||||
|
||||
import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.config.VirtualThreadProperties;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertDefine;
|
||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||
import org.apache.hertzbeat.common.entity.log.LogEntry;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -32,14 +34,20 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertAll;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyList;
|
||||
import static org.mockito.ArgumentMatchers.anyMap;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
@@ -98,6 +106,13 @@ class AlarmEvaluatorTest {
|
||||
windowData.addMatchingLog(matchingEvent);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
if (alarmEvaluator != null) {
|
||||
alarmEvaluator.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessWindowDataWithIndividualMode() throws InterruptedException {
|
||||
// Given - alert define with individual mode
|
||||
@@ -343,4 +358,104 @@ class AlarmEvaluatorTest {
|
||||
assertEquals(2, alerts.size());
|
||||
assertEquals(2, alerts.get(0).getTriggerTimes()); // Each alert should have trigger times = total count
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSendAndProcessWindowDataRunsOnVirtualThread() throws Exception {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicBoolean virtualThread = new AtomicBoolean(false);
|
||||
doAnswer(invocation -> {
|
||||
virtualThread.set(Thread.currentThread().isVirtual());
|
||||
latch.countDown();
|
||||
return null;
|
||||
}).when(alarmCommonReduce).reduceAndSendAlarm(any(SingleAlert.class));
|
||||
|
||||
alertDefine.setLabels(Map.of(CommonConstants.ALERT_MODE_LABEL, CommonConstants.ALERT_MODE_INDIVIDUAL));
|
||||
alarmEvaluator.sendAndProcessWindowData(windowData);
|
||||
|
||||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
assertTrue(virtualThread.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSendAndProcessWindowDataQueuesWhenConcurrencyLimitReached() throws Exception {
|
||||
VirtualThreadProperties properties = new VirtualThreadProperties(
|
||||
true,
|
||||
VirtualThreadProperties.PoolProperties.collectorDefaults(),
|
||||
VirtualThreadProperties.PoolProperties.commonDefaults(),
|
||||
VirtualThreadProperties.PoolProperties.managerDefaults(),
|
||||
new VirtualThreadProperties.AlerterProperties(
|
||||
VirtualThreadProperties.PoolProperties.alerterNotifyDefaults(),
|
||||
10,
|
||||
VirtualThreadProperties.QueueProperties.logWorkerDefaults(),
|
||||
VirtualThreadProperties.QueueProperties.reduceDefaults(),
|
||||
new VirtualThreadProperties.QueueProperties(1, 0),
|
||||
4),
|
||||
VirtualThreadProperties.PoolProperties.warehouseDefaults(),
|
||||
VirtualThreadProperties.AsyncProperties.defaults());
|
||||
alarmEvaluator.destroy();
|
||||
alarmEvaluator = new AlarmEvaluator(alarmCommonReduce, properties);
|
||||
|
||||
CountDownLatch firstStarted = new CountDownLatch(1);
|
||||
CountDownLatch releaseFirst = new CountDownLatch(1);
|
||||
CountDownLatch secondStarted = new CountDownLatch(1);
|
||||
AtomicInteger invocationOrder = new AtomicInteger();
|
||||
doAnswer(invocation -> {
|
||||
int order = invocationOrder.incrementAndGet();
|
||||
if (order == 1) {
|
||||
firstStarted.countDown();
|
||||
try {
|
||||
releaseFirst.await(5, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
} else if (order == 2) {
|
||||
secondStarted.countDown();
|
||||
}
|
||||
return null;
|
||||
}).when(alarmCommonReduce).reduceAndSendAlarm(any(SingleAlert.class));
|
||||
|
||||
WindowAggregator.WindowData firstWindow = cloneWindowDataWithBody("first");
|
||||
WindowAggregator.WindowData secondWindow = cloneWindowDataWithBody("second");
|
||||
firstWindow.getAlertDefine().setLabels(Map.of(CommonConstants.ALERT_MODE_LABEL, CommonConstants.ALERT_MODE_INDIVIDUAL));
|
||||
secondWindow.getAlertDefine().setLabels(Map.of(CommonConstants.ALERT_MODE_LABEL, CommonConstants.ALERT_MODE_INDIVIDUAL));
|
||||
|
||||
alarmEvaluator.sendAndProcessWindowData(firstWindow);
|
||||
assertTrue(firstStarted.await(5, TimeUnit.SECONDS));
|
||||
|
||||
alarmEvaluator.sendAndProcessWindowData(secondWindow);
|
||||
assertFalse(secondStarted.await(200, TimeUnit.MILLISECONDS));
|
||||
|
||||
releaseFirst.countDown();
|
||||
assertTrue(secondStarted.await(5, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
private WindowAggregator.WindowData cloneWindowDataWithBody(String body) {
|
||||
LogEntry logEntry = LogEntry.builder()
|
||||
.timeUnixNano(System.currentTimeMillis() * 1_000_000L)
|
||||
.severityText("ERROR")
|
||||
.body(body)
|
||||
.build();
|
||||
AlertDefine define = AlertDefine.builder()
|
||||
.id(alertDefine.getId())
|
||||
.name(alertDefine.getName())
|
||||
.type(alertDefine.getType())
|
||||
.expr(alertDefine.getExpr())
|
||||
.times(alertDefine.getTimes())
|
||||
.template(alertDefine.getTemplate())
|
||||
.labels(alertDefine.getLabels())
|
||||
.annotations(alertDefine.getAnnotations())
|
||||
.enable(alertDefine.isEnable())
|
||||
.build();
|
||||
MatchingLogEvent event = MatchingLogEvent.builder()
|
||||
.logEntry(logEntry)
|
||||
.alertDefine(define)
|
||||
.eventTimestamp(System.currentTimeMillis())
|
||||
.workerTimestamp(System.currentTimeMillis())
|
||||
.build();
|
||||
WindowAggregator.WindowData clonedWindowData = new WindowAggregator.WindowData(
|
||||
new WindowAggregator.WindowKey(define.getId(), System.currentTimeMillis() - 60000, System.currentTimeMillis()),
|
||||
define);
|
||||
clonedWindowData.addMatchingLog(event);
|
||||
return clonedWindowData;
|
||||
}
|
||||
}
|
||||
|
||||
+31
@@ -19,7 +19,10 @@ package org.apache.hertzbeat.alert.notice;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyByte;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -149,4 +152,32 @@ class AlertNoticeDispatchTest {
|
||||
|
||||
assertFalse(alertNoticeDispatch.sendNoticeMsg(receiver, null, alert));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDispatchAlarmUsesTypedNotifyExecution() {
|
||||
NoticeTemplate template = new NoticeTemplate();
|
||||
template.setId(1L);
|
||||
template.setName("default-template");
|
||||
|
||||
when(alertStoreHandler.store(alert)).thenReturn(alert);
|
||||
when(noticeConfigService.getReceiverFilterRule(alert)).thenReturn(Collections.singletonList(
|
||||
org.apache.hertzbeat.common.entity.alerter.NoticeRule.builder()
|
||||
.receiverId(Collections.singletonList(1L))
|
||||
.templateId(1L)
|
||||
.build()));
|
||||
when(noticeConfigService.getReceiverById(1L)).thenReturn(receiver);
|
||||
when(noticeConfigService.getOneTemplateById(1L)).thenReturn(template);
|
||||
doNothing().when(alertNotifyHandler).send(eq(receiver), eq(template), eq(alert));
|
||||
doAnswer(invocation -> {
|
||||
Runnable task = invocation.getArgument(1);
|
||||
task.run();
|
||||
return null;
|
||||
}).when(workerPool).executeNotify(anyByte(), any(Runnable.class));
|
||||
|
||||
alertNoticeDispatch.dispatchAlarm(alert);
|
||||
|
||||
verify(workerPool).executeNotify(eq((byte) 1), any(Runnable.class));
|
||||
verify(alertNotifyHandler).send(eq(receiver), eq(template), eq(alert));
|
||||
verify(emitterManager).broadcast(any(String.class));
|
||||
}
|
||||
}
|
||||
|
||||
+87
-2
@@ -17,7 +17,20 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.reduce;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||
import org.apache.hertzbeat.common.config.VirtualThreadProperties;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -40,14 +53,86 @@ class AlarmCommonReduceTest {
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
|
||||
testAlert = SingleAlert.builder().build();
|
||||
testAlert = SingleAlert.builder().labels(new HashMap<>(Map.of("alertname", "test"))).build();
|
||||
alarmCommonReduce = new AlarmCommonReduce(alarmGroupReduce);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
if (alarmCommonReduce != null) {
|
||||
alarmCommonReduce.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testReduceAndSendAlarm() {
|
||||
alarmCommonReduce.reduceAndSendAlarm(testAlert);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testReduceAndSendAlarmRunsOnVirtualThread() throws Exception {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicBoolean virtualThread = new AtomicBoolean(false);
|
||||
doAnswer(invocation -> {
|
||||
virtualThread.set(Thread.currentThread().isVirtual());
|
||||
latch.countDown();
|
||||
return null;
|
||||
}).when(alarmGroupReduce).processGroupAlert(any(SingleAlert.class));
|
||||
|
||||
alarmCommonReduce.reduceAndSendAlarm(testAlert);
|
||||
|
||||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
assertTrue(virtualThread.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testReduceAndSendAlarmQueuesWhenConcurrencyLimitReached() throws Exception {
|
||||
VirtualThreadProperties properties = new VirtualThreadProperties(
|
||||
true,
|
||||
VirtualThreadProperties.PoolProperties.collectorDefaults(),
|
||||
VirtualThreadProperties.PoolProperties.commonDefaults(),
|
||||
VirtualThreadProperties.PoolProperties.managerDefaults(),
|
||||
new VirtualThreadProperties.AlerterProperties(
|
||||
VirtualThreadProperties.PoolProperties.alerterNotifyDefaults(),
|
||||
10,
|
||||
VirtualThreadProperties.QueueProperties.logWorkerDefaults(),
|
||||
new VirtualThreadProperties.QueueProperties(1, 0),
|
||||
VirtualThreadProperties.QueueProperties.windowEvaluatorDefaults(),
|
||||
4),
|
||||
VirtualThreadProperties.PoolProperties.warehouseDefaults(),
|
||||
VirtualThreadProperties.AsyncProperties.defaults());
|
||||
alarmCommonReduce.destroy();
|
||||
alarmCommonReduce = new AlarmCommonReduce(alarmGroupReduce, properties);
|
||||
|
||||
CountDownLatch firstStarted = new CountDownLatch(1);
|
||||
CountDownLatch releaseFirst = new CountDownLatch(1);
|
||||
CountDownLatch secondStarted = new CountDownLatch(1);
|
||||
AtomicInteger invocationOrder = new AtomicInteger();
|
||||
doAnswer(invocation -> {
|
||||
int order = invocationOrder.incrementAndGet();
|
||||
if (order == 1) {
|
||||
firstStarted.countDown();
|
||||
try {
|
||||
releaseFirst.await(5, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
} else if (order == 2) {
|
||||
secondStarted.countDown();
|
||||
}
|
||||
return null;
|
||||
}).when(alarmGroupReduce).processGroupAlert(any(SingleAlert.class));
|
||||
|
||||
alarmCommonReduce.reduceAndSendAlarm(SingleAlert.builder()
|
||||
.labels(new HashMap<>(Map.of("name", "first"))).build());
|
||||
assertTrue(firstStarted.await(5, TimeUnit.SECONDS));
|
||||
|
||||
alarmCommonReduce.reduceAndSendAlarm(SingleAlert.builder()
|
||||
.labels(new HashMap<>(Map.of("name", "second"))).build());
|
||||
assertFalse(secondStarted.await(200, TimeUnit.MILLISECONDS));
|
||||
|
||||
releaseFirst.countDown();
|
||||
assertTrue(secondStarted.await(5, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+117
-1
@@ -38,6 +38,9 @@
|
||||
|
||||
package org.apache.hertzbeat.alert.reduce;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.Mockito.never;
|
||||
@@ -47,9 +50,15 @@ import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.apache.hertzbeat.alert.dao.AlertGroupConvergeDao;
|
||||
import org.apache.hertzbeat.common.config.VirtualThreadProperties;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertGroupConverge;
|
||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mock;
|
||||
@@ -73,7 +82,15 @@ class AlarmGroupReduceTest {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
when(alertGroupConvergeDao.findAlertGroupConvergesByEnableIsTrue())
|
||||
.thenReturn(Collections.emptyList());
|
||||
alarmGroupReduce = new AlarmGroupReduce(alarmInhibitReduce, alertGroupConvergeDao);
|
||||
alarmGroupReduce = new AlarmGroupReduce(alarmInhibitReduce, alertGroupConvergeDao,
|
||||
new VirtualThreadProperties(), false);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
if (alarmGroupReduce != null) {
|
||||
alarmGroupReduce.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -112,6 +129,42 @@ class AlarmGroupReduceTest {
|
||||
verify(alarmInhibitReduce, never()).inhibitAlarm(any()); // Should not send immediately due to group wait
|
||||
}
|
||||
|
||||
@Test
|
||||
void dispatchCheckAndSendGroupsRunsOnVirtualThread() throws Exception {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicBoolean virtualThread = new AtomicBoolean(false);
|
||||
alarmGroupReduce.destroy();
|
||||
alarmGroupReduce = new TestAlarmGroupReduce(alarmInhibitReduce, alertGroupConvergeDao,
|
||||
new VirtualThreadProperties(), latch, virtualThread, null, null, null, null, null);
|
||||
|
||||
alarmGroupReduce.dispatchCheckAndSendGroups();
|
||||
|
||||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
assertTrue(virtualThread.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void dispatchCheckAndSendGroupsDoesNotRunConcurrently() throws Exception {
|
||||
CountDownLatch firstStarted = new CountDownLatch(1);
|
||||
CountDownLatch releaseFirst = new CountDownLatch(1);
|
||||
CountDownLatch secondStarted = new CountDownLatch(1);
|
||||
AtomicInteger maxConcurrent = new AtomicInteger();
|
||||
alarmGroupReduce.destroy();
|
||||
alarmGroupReduce = new TestAlarmGroupReduce(alarmInhibitReduce, alertGroupConvergeDao,
|
||||
new VirtualThreadProperties(), null, null, firstStarted, releaseFirst, secondStarted,
|
||||
maxConcurrent, new AtomicInteger());
|
||||
|
||||
alarmGroupReduce.dispatchCheckAndSendGroups();
|
||||
assertTrue(firstStarted.await(5, TimeUnit.SECONDS));
|
||||
|
||||
alarmGroupReduce.dispatchCheckAndSendGroups();
|
||||
assertFalse(secondStarted.await(200, TimeUnit.MILLISECONDS));
|
||||
|
||||
releaseFirst.countDown();
|
||||
assertTrue(secondStarted.await(5, TimeUnit.SECONDS));
|
||||
assertEquals(1, maxConcurrent.get());
|
||||
}
|
||||
|
||||
private Map<String, String> createLabels(String... keyValues) {
|
||||
Map<String, String> labels = new HashMap<>();
|
||||
for (int i = 0; i < keyValues.length; i += 2) {
|
||||
@@ -119,4 +172,67 @@ class AlarmGroupReduceTest {
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
|
||||
private static final class TestAlarmGroupReduce extends AlarmGroupReduce {
|
||||
|
||||
private final CountDownLatch virtualThreadLatch;
|
||||
|
||||
private final AtomicBoolean virtualThread;
|
||||
|
||||
private final CountDownLatch firstStarted;
|
||||
|
||||
private final CountDownLatch releaseFirst;
|
||||
|
||||
private final CountDownLatch secondStarted;
|
||||
|
||||
private final AtomicInteger maxConcurrent;
|
||||
|
||||
private final AtomicInteger concurrent;
|
||||
|
||||
private final AtomicInteger invocations;
|
||||
|
||||
private TestAlarmGroupReduce(AlarmInhibitReduce alarmInhibitReduce, AlertGroupConvergeDao alertGroupConvergeDao,
|
||||
VirtualThreadProperties properties, CountDownLatch virtualThreadLatch,
|
||||
AtomicBoolean virtualThread, CountDownLatch firstStarted,
|
||||
CountDownLatch releaseFirst, CountDownLatch secondStarted,
|
||||
AtomicInteger maxConcurrent, AtomicInteger invocations) {
|
||||
super(alarmInhibitReduce, alertGroupConvergeDao, properties, false);
|
||||
this.virtualThreadLatch = virtualThreadLatch;
|
||||
this.virtualThread = virtualThread;
|
||||
this.firstStarted = firstStarted;
|
||||
this.releaseFirst = releaseFirst;
|
||||
this.secondStarted = secondStarted;
|
||||
this.maxConcurrent = maxConcurrent;
|
||||
this.invocations = invocations;
|
||||
this.concurrent = maxConcurrent == null ? null : new AtomicInteger();
|
||||
}
|
||||
|
||||
@Override
|
||||
void beforeCheckAndSendGroupsRun() {
|
||||
if (virtualThread != null) {
|
||||
virtualThread.set(Thread.currentThread().isVirtual());
|
||||
}
|
||||
if (virtualThreadLatch != null) {
|
||||
virtualThreadLatch.countDown();
|
||||
}
|
||||
if (maxConcurrent == null || invocations == null) {
|
||||
return;
|
||||
}
|
||||
int running = concurrent.incrementAndGet();
|
||||
maxConcurrent.accumulateAndGet(running, Math::max);
|
||||
int currentInvocation = invocations.incrementAndGet();
|
||||
try {
|
||||
if (currentInvocation == 1 && firstStarted != null && releaseFirst != null) {
|
||||
firstStarted.countDown();
|
||||
releaseFirst.await(5, TimeUnit.SECONDS);
|
||||
} else if (currentInvocation == 2 && secondStarted != null) {
|
||||
secondStarted.countDown();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
} finally {
|
||||
concurrent.decrementAndGet();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+120
-4
@@ -39,6 +39,7 @@
|
||||
package org.apache.hertzbeat.alert.reduce;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
@@ -49,14 +50,19 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import org.apache.hertzbeat.alert.AlerterProperties;
|
||||
import org.apache.hertzbeat.alert.dao.AlertInhibitDao;
|
||||
import org.apache.hertzbeat.common.config.VirtualThreadProperties;
|
||||
import org.apache.hertzbeat.common.entity.alerter.AlertInhibit;
|
||||
import org.apache.hertzbeat.common.entity.alerter.GroupAlert;
|
||||
import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mock;
|
||||
@@ -89,7 +95,15 @@ class AlarmInhibitReduceTest {
|
||||
inhibitProperties.setTtl(60000);
|
||||
when(alerterProperties.getInhibit()).thenReturn(inhibitProperties);
|
||||
|
||||
alarmInhibitReduce = new AlarmInhibitReduce(alarmSilenceReduce, alertInhibitDao, alerterProperties);
|
||||
alarmInhibitReduce = new AlarmInhibitReduce(alarmSilenceReduce, alertInhibitDao, alerterProperties,
|
||||
new VirtualThreadProperties(), false);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
if (alarmInhibitReduce != null) {
|
||||
alarmInhibitReduce.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -290,7 +304,9 @@ class AlarmInhibitReduceTest {
|
||||
AlerterProperties.InhibitProperties inhibitProperties = new AlerterProperties.InhibitProperties();
|
||||
inhibitProperties.setTtl(100);
|
||||
when(alerterProperties.getInhibit()).thenReturn(inhibitProperties);
|
||||
alarmInhibitReduce = new AlarmInhibitReduce(alarmSilenceReduce, alertInhibitDao, alerterProperties);
|
||||
alarmInhibitReduce.destroy();
|
||||
alarmInhibitReduce = new AlarmInhibitReduce(alarmSilenceReduce, alertInhibitDao, alerterProperties,
|
||||
new VirtualThreadProperties(), false);
|
||||
|
||||
AlertInhibit rule = AlertInhibit.builder()
|
||||
.id(1L)
|
||||
@@ -324,6 +340,42 @@ class AlarmInhibitReduceTest {
|
||||
verify(alarmSilenceReduce).silenceAlarm(targetGroupAlert);
|
||||
}
|
||||
|
||||
@Test
|
||||
void dispatchCleanupCacheRunsOnVirtualThread() throws Exception {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicBoolean virtualThread = new AtomicBoolean(false);
|
||||
alarmInhibitReduce.destroy();
|
||||
alarmInhibitReduce = new TestAlarmInhibitReduce(alarmSilenceReduce, alertInhibitDao, alerterProperties,
|
||||
new VirtualThreadProperties(), latch, virtualThread, null, null, null, null, null);
|
||||
|
||||
alarmInhibitReduce.dispatchCleanupCache();
|
||||
|
||||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
assertTrue(virtualThread.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void dispatchCleanupCacheDoesNotRunConcurrently() throws Exception {
|
||||
CountDownLatch firstStarted = new CountDownLatch(1);
|
||||
CountDownLatch releaseFirst = new CountDownLatch(1);
|
||||
CountDownLatch secondStarted = new CountDownLatch(1);
|
||||
AtomicInteger maxConcurrent = new AtomicInteger();
|
||||
alarmInhibitReduce.destroy();
|
||||
alarmInhibitReduce = new TestAlarmInhibitReduce(alarmSilenceReduce, alertInhibitDao, alerterProperties,
|
||||
new VirtualThreadProperties(), null, null, firstStarted, releaseFirst, secondStarted,
|
||||
maxConcurrent, new AtomicInteger());
|
||||
|
||||
alarmInhibitReduce.dispatchCleanupCache();
|
||||
assertTrue(firstStarted.await(5, TimeUnit.SECONDS));
|
||||
|
||||
alarmInhibitReduce.dispatchCleanupCache();
|
||||
assertFalse(secondStarted.await(200, TimeUnit.MILLISECONDS));
|
||||
|
||||
releaseFirst.countDown();
|
||||
assertTrue(secondStarted.await(5, TimeUnit.SECONDS));
|
||||
assertEquals(1, maxConcurrent.get());
|
||||
}
|
||||
|
||||
private GroupAlert createGroupAlert(String status, Map<String, String> labels, List<SingleAlert> alerts) {
|
||||
return GroupAlert.builder()
|
||||
.status(status)
|
||||
@@ -347,4 +399,68 @@ class AlarmInhibitReduceTest {
|
||||
.labels(labels)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class TestAlarmInhibitReduce extends AlarmInhibitReduce {
|
||||
|
||||
private final CountDownLatch virtualThreadLatch;
|
||||
|
||||
private final AtomicBoolean virtualThread;
|
||||
|
||||
private final CountDownLatch firstStarted;
|
||||
|
||||
private final CountDownLatch releaseFirst;
|
||||
|
||||
private final CountDownLatch secondStarted;
|
||||
|
||||
private final AtomicInteger maxConcurrent;
|
||||
|
||||
private final AtomicInteger concurrent;
|
||||
|
||||
private final AtomicInteger invocations;
|
||||
|
||||
private TestAlarmInhibitReduce(AlarmSilenceReduce alarmSilenceReduce, AlertInhibitDao alertInhibitDao,
|
||||
AlerterProperties alerterProperties, VirtualThreadProperties properties,
|
||||
CountDownLatch virtualThreadLatch, AtomicBoolean virtualThread,
|
||||
CountDownLatch firstStarted, CountDownLatch releaseFirst,
|
||||
CountDownLatch secondStarted, AtomicInteger maxConcurrent,
|
||||
AtomicInteger invocations) {
|
||||
super(alarmSilenceReduce, alertInhibitDao, alerterProperties, properties, false);
|
||||
this.virtualThreadLatch = virtualThreadLatch;
|
||||
this.virtualThread = virtualThread;
|
||||
this.firstStarted = firstStarted;
|
||||
this.releaseFirst = releaseFirst;
|
||||
this.secondStarted = secondStarted;
|
||||
this.maxConcurrent = maxConcurrent;
|
||||
this.invocations = invocations;
|
||||
this.concurrent = maxConcurrent == null ? null : new AtomicInteger();
|
||||
}
|
||||
|
||||
@Override
|
||||
void beforeCleanupCacheRun() {
|
||||
if (virtualThread != null) {
|
||||
virtualThread.set(Thread.currentThread().isVirtual());
|
||||
}
|
||||
if (virtualThreadLatch != null) {
|
||||
virtualThreadLatch.countDown();
|
||||
}
|
||||
if (maxConcurrent == null || invocations == null) {
|
||||
return;
|
||||
}
|
||||
int running = concurrent.incrementAndGet();
|
||||
maxConcurrent.accumulateAndGet(running, Math::max);
|
||||
int currentInvocation = invocations.incrementAndGet();
|
||||
try {
|
||||
if (currentInvocation == 1 && firstStarted != null && releaseFirst != null) {
|
||||
firstStarted.countDown();
|
||||
releaseFirst.await(5, TimeUnit.SECONDS);
|
||||
} else if (currentInvocation == 2 && secondStarted != null) {
|
||||
secondStarted.countDown();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
} finally {
|
||||
concurrent.decrementAndGet();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+53
-26
@@ -17,12 +17,15 @@
|
||||
|
||||
package org.apache.hertzbeat.collector.dispatch;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import org.apache.hertzbeat.common.concurrent.AdmissionMode;
|
||||
import org.apache.hertzbeat.common.config.VirtualThreadProperties;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
@@ -32,33 +35,57 @@ class WorkerPoolTest {
|
||||
|
||||
private WorkerPool workerPool;
|
||||
|
||||
private Runnable mockTask;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
@AfterEach
|
||||
void tearDown() throws Exception {
|
||||
if (workerPool != null) {
|
||||
workerPool.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecuteJobRunsOnVirtualThread() throws Exception {
|
||||
workerPool = new WorkerPool();
|
||||
mockTask = mock(Runnable.class);
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicBoolean virtualThread = new AtomicBoolean(false);
|
||||
|
||||
workerPool.executeJob(() -> {
|
||||
virtualThread.set(Thread.currentThread().isVirtual());
|
||||
latch.countDown();
|
||||
});
|
||||
|
||||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
assertTrue(virtualThread.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecuteJob() {
|
||||
void testExecuteJobRejectsWhenConcurrencyLimitReached() throws Exception {
|
||||
VirtualThreadProperties properties = new VirtualThreadProperties(
|
||||
true,
|
||||
new VirtualThreadProperties.PoolProperties(AdmissionMode.LIMIT_AND_REJECT, 1),
|
||||
VirtualThreadProperties.PoolProperties.commonDefaults(),
|
||||
VirtualThreadProperties.PoolProperties.managerDefaults(),
|
||||
VirtualThreadProperties.AlerterProperties.defaults(),
|
||||
VirtualThreadProperties.PoolProperties.warehouseDefaults(),
|
||||
VirtualThreadProperties.AsyncProperties.defaults());
|
||||
workerPool = new WorkerPool(properties);
|
||||
|
||||
assertDoesNotThrow(() -> workerPool.executeJob(mockTask));
|
||||
CountDownLatch started = new CountDownLatch(1);
|
||||
CountDownLatch release = new CountDownLatch(1);
|
||||
workerPool.executeJob(() -> {
|
||||
started.countDown();
|
||||
try {
|
||||
release.await(5, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
});
|
||||
assertTrue(started.await(5, TimeUnit.SECONDS));
|
||||
|
||||
try {
|
||||
assertThrows(RejectedExecutionException.class, () -> workerPool.executeJob(() -> {
|
||||
}));
|
||||
} finally {
|
||||
release.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecuteJobThrowsException() {
|
||||
|
||||
workerPool = mock(WorkerPool.class);
|
||||
doThrow(new RejectedExecutionException()).when(workerPool).executeJob(mockTask);
|
||||
|
||||
assertThrows(RejectedExecutionException.class, () -> workerPool.executeJob(mockTask));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDestroy() {
|
||||
assertDoesNotThrow(() -> workerPool.destroy());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+75
-2
@@ -18,19 +18,27 @@
|
||||
package org.apache.hertzbeat.collector.dispatch.entrance;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import io.netty.channel.Channel;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.apache.hertzbeat.collector.dispatch.CollectorInfoProperties;
|
||||
import org.apache.hertzbeat.collector.dispatch.DispatchProperties;
|
||||
import org.apache.hertzbeat.collector.dispatch.entrance.internal.CollectJobService;
|
||||
import org.apache.hertzbeat.collector.timer.TimerDispatch;
|
||||
import org.apache.hertzbeat.common.concurrent.BackgroundTaskExecutor;
|
||||
import org.apache.hertzbeat.common.config.VirtualThreadProperties;
|
||||
import org.apache.hertzbeat.common.entity.message.ClusterMsg;
|
||||
import org.apache.hertzbeat.common.support.CommonThreadPool;
|
||||
import org.apache.hertzbeat.remoting.RemotingClient;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -62,7 +70,7 @@ class CollectServerTest {
|
||||
private DispatchProperties.EntranceProperties.NettyProperties nettyProperties;
|
||||
|
||||
@Mock
|
||||
private CommonThreadPool threadPool;
|
||||
private BackgroundTaskExecutor threadPool;
|
||||
|
||||
@Mock
|
||||
private CollectorInfoProperties infoProperties;
|
||||
@@ -142,4 +150,69 @@ class CollectServerTest {
|
||||
assertNotNull(scheduledExecutor);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDispatchHeartbeatRunsOnVirtualThread() throws Exception {
|
||||
VirtualThreadProperties properties = new VirtualThreadProperties();
|
||||
collectServer = new CollectServer(collectJobService, timerDispatch, properties(), threadPool, infoProperties, properties);
|
||||
|
||||
RemotingClient remotingClient = mock(RemotingClient.class);
|
||||
ReflectionTestUtils.setField(collectServer, "remotingClient", remotingClient);
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicBoolean virtualThread = new AtomicBoolean(false);
|
||||
org.mockito.Mockito.doAnswer(invocation -> {
|
||||
virtualThread.set(Thread.currentThread().isVirtual());
|
||||
latch.countDown();
|
||||
return null;
|
||||
}).when(remotingClient).sendMsg(any(ClusterMsg.Message.class));
|
||||
|
||||
collectServer.dispatchHeartbeat("collector1");
|
||||
|
||||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
assertTrue(virtualThread.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDispatchHeartbeatDoesNotRunConcurrently() throws Exception {
|
||||
VirtualThreadProperties properties = new VirtualThreadProperties();
|
||||
collectServer = new CollectServer(collectJobService, timerDispatch, properties(), threadPool, infoProperties, properties);
|
||||
|
||||
RemotingClient remotingClient = mock(RemotingClient.class);
|
||||
ReflectionTestUtils.setField(collectServer, "remotingClient", remotingClient);
|
||||
|
||||
CountDownLatch firstStarted = new CountDownLatch(1);
|
||||
CountDownLatch releaseFirst = new CountDownLatch(1);
|
||||
CountDownLatch secondStarted = new CountDownLatch(1);
|
||||
AtomicInteger concurrent = new AtomicInteger();
|
||||
AtomicInteger maxConcurrent = new AtomicInteger();
|
||||
AtomicInteger invocations = new AtomicInteger();
|
||||
org.mockito.Mockito.doAnswer(invocation -> {
|
||||
int running = concurrent.incrementAndGet();
|
||||
maxConcurrent.accumulateAndGet(running, Math::max);
|
||||
int currentInvocation = invocations.incrementAndGet();
|
||||
if (currentInvocation == 1) {
|
||||
firstStarted.countDown();
|
||||
releaseFirst.await(5, TimeUnit.SECONDS);
|
||||
} else if (currentInvocation == 2) {
|
||||
secondStarted.countDown();
|
||||
}
|
||||
concurrent.decrementAndGet();
|
||||
return null;
|
||||
}).when(remotingClient).sendMsg(any(ClusterMsg.Message.class));
|
||||
|
||||
collectServer.dispatchHeartbeat("collector1");
|
||||
assertTrue(firstStarted.await(5, TimeUnit.SECONDS));
|
||||
|
||||
collectServer.dispatchHeartbeat("collector1");
|
||||
assertFalse(secondStarted.await(200, TimeUnit.MILLISECONDS));
|
||||
|
||||
releaseFirst.countDown();
|
||||
assertTrue(secondStarted.await(5, TimeUnit.SECONDS));
|
||||
assertEquals(1, maxConcurrent.get());
|
||||
}
|
||||
|
||||
private DispatchProperties properties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-2
@@ -114,7 +114,7 @@ public class CommonDispatcher implements MetricsTaskDispatch, CollectDataDispatc
|
||||
public void start() {
|
||||
try {
|
||||
// Pull the collection task from the task queue and put it into the thread pool for execution
|
||||
workerPool.executeJob(() -> {
|
||||
workerPool.executeLongRunning(() -> {
|
||||
Thread.currentThread().setName("metrics-task-dispatcher");
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
MetricsCollect metricsCollect = null;
|
||||
@@ -379,4 +379,4 @@ public class CommonDispatcher implements MetricsTaskDispatch, CollectDataDispatc
|
||||
private Metrics metrics;
|
||||
private Timeout timeout;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,3 +75,12 @@ push:
|
||||
common:
|
||||
queue:
|
||||
type: netty
|
||||
|
||||
hertzbeat:
|
||||
# Optional virtual-thread overrides. Remove this whole block to use built-in defaults.
|
||||
vthreads:
|
||||
enabled: true
|
||||
common:
|
||||
mode: UNBOUNDED_VT
|
||||
collector:
|
||||
mode: LIMIT_AND_REJECT
|
||||
|
||||
+107
-11
@@ -20,14 +20,15 @@ package org.apache.hertzbeat.collector.collect.common.cache;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Singleton LRU global resource cache for client-server connections
|
||||
@@ -56,10 +57,20 @@ public class GlobalConnectionCache {
|
||||
*/
|
||||
private final ConcurrentLinkedHashMap<Object, AbstractConnection<?>> cacheMap;
|
||||
|
||||
private final ScheduledExecutorService scheduledExecutor;
|
||||
|
||||
private final ExecutorService cleanupExecutor;
|
||||
|
||||
private final ScheduledDispatchTask cleanupTask;
|
||||
|
||||
/**
|
||||
* Private constructor to prevent instantiation
|
||||
*/
|
||||
private GlobalConnectionCache() {
|
||||
this(true);
|
||||
}
|
||||
|
||||
GlobalConnectionCache(boolean autoStart) {
|
||||
cacheMap = new ConcurrentLinkedHashMap.Builder<Object, AbstractConnection<?>>()
|
||||
.maximumWeightedCapacity(Integer.MAX_VALUE)
|
||||
.listener((key, value) -> {
|
||||
@@ -72,7 +83,13 @@ public class GlobalConnectionCache {
|
||||
log.info("GlobalConnectionCache discarded key: {}, value: {}.", key, value);
|
||||
})
|
||||
.build();
|
||||
initCacheMonitor();
|
||||
this.scheduledExecutor = createCacheMonitorScheduler();
|
||||
this.cleanupExecutor = createCleanupExecutor();
|
||||
this.cleanupTask = new ScheduledDispatchTask(cleanupExecutor, this::runCleanTimeoutOrUnHealthyCache);
|
||||
if (autoStart) {
|
||||
initCacheMonitor();
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(this::destroy));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,18 +112,42 @@ public class GlobalConnectionCache {
|
||||
* Initialize the cache monitor for cleaning up expired connections
|
||||
*/
|
||||
private void initCacheMonitor() {
|
||||
ThreadFactory threadFactory = new ThreadFactoryBuilder()
|
||||
.setNameFormat("connection-cache-timeout-detector-%d")
|
||||
.setDaemon(true)
|
||||
.build();
|
||||
ScheduledThreadPoolExecutor scheduledExecutor = new ScheduledThreadPoolExecutor(1, threadFactory);
|
||||
scheduledExecutor.scheduleWithFixedDelay(this::cleanTimeoutOrUnHealthyCache, 2, 100, TimeUnit.SECONDS);
|
||||
scheduledExecutor.scheduleWithFixedDelay(this::dispatchCleanupCache, 2, 100, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean and remove timeout or unhealthy cache entries
|
||||
*/
|
||||
private void cleanTimeoutOrUnHealthyCache() {
|
||||
void dispatchCleanupCache() {
|
||||
cleanupTask.dispatch();
|
||||
}
|
||||
|
||||
void beforeCleanTimeoutOrUnHealthyCacheRun() {
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
scheduledExecutor.shutdownNow();
|
||||
cleanupExecutor.shutdownNow();
|
||||
}
|
||||
|
||||
private ScheduledExecutorService createCacheMonitorScheduler() {
|
||||
ThreadFactory threadFactory = new ThreadFactoryBuilder()
|
||||
.setNameFormat("connection-cache-timeout-detector-%d")
|
||||
.setDaemon(true)
|
||||
.build();
|
||||
return Executors.newSingleThreadScheduledExecutor(threadFactory);
|
||||
}
|
||||
|
||||
private ExecutorService createCleanupExecutor() {
|
||||
return Executors.newThreadPerTaskExecutor(Thread.ofVirtual()
|
||||
.name("connection-cache-cleaner-vt-", 0)
|
||||
.uncaughtExceptionHandler((thread, throwable) ->
|
||||
log.error("Connection cache cleanup has uncaughtException.", throwable))
|
||||
.factory());
|
||||
}
|
||||
|
||||
private void runCleanTimeoutOrUnHealthyCache() {
|
||||
beforeCleanTimeoutOrUnHealthyCacheRun();
|
||||
try {
|
||||
cacheMap.forEach((key, value) -> {
|
||||
Long[] cacheTime = timeoutMap.get(key);
|
||||
@@ -204,4 +245,59 @@ public class GlobalConnectionCache {
|
||||
log.error("Connection close error for key {}: {}", key, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ScheduledDispatchTask {
|
||||
|
||||
private final ExecutorService executor;
|
||||
|
||||
private final Runnable task;
|
||||
|
||||
private boolean running;
|
||||
|
||||
private int pendingRuns;
|
||||
|
||||
private ScheduledDispatchTask(ExecutorService executor, Runnable task) {
|
||||
this.executor = executor;
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
private void dispatch() {
|
||||
boolean shouldSchedule;
|
||||
synchronized (this) {
|
||||
pendingRuns++;
|
||||
shouldSchedule = !running;
|
||||
if (shouldSchedule) {
|
||||
running = true;
|
||||
}
|
||||
}
|
||||
if (shouldSchedule) {
|
||||
scheduleRun();
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleRun() {
|
||||
executor.execute(this::runOnce);
|
||||
}
|
||||
|
||||
private void runOnce() {
|
||||
try {
|
||||
task.run();
|
||||
} finally {
|
||||
scheduleNextIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleNextIfNeeded() {
|
||||
boolean shouldSchedule;
|
||||
synchronized (this) {
|
||||
pendingRuns = Math.max(0, pendingRuns - 1);
|
||||
shouldSchedule = pendingRuns > 0;
|
||||
if (!shouldSchedule) {
|
||||
running = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
scheduleRun();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+120
-11
@@ -22,6 +22,7 @@ import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateExpiredException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
@@ -52,6 +53,14 @@ public class CommonHttpClient {
|
||||
|
||||
private static PoolingHttpClientConnectionManager connectionManager;
|
||||
|
||||
private static ScheduledExecutorService scheduledExecutor;
|
||||
|
||||
private static ExecutorService cleanupExecutor;
|
||||
|
||||
private static ScheduledDispatchTask cleanupTask;
|
||||
|
||||
private static volatile Runnable beforeCleanupHook;
|
||||
|
||||
/**
|
||||
* all max total connection
|
||||
*/
|
||||
@@ -137,15 +146,9 @@ public class CommonHttpClient {
|
||||
// clean up available but idle connections
|
||||
.evictIdleConnections(100, TimeUnit.SECONDS)
|
||||
.build();
|
||||
ThreadFactory threadFactory = new ThreadFactoryBuilder()
|
||||
.setNameFormat("http-connection-pool-cleaner-%d")
|
||||
.setDaemon(true)
|
||||
.build();
|
||||
ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(1, threadFactory);
|
||||
scheduledExecutor.scheduleWithFixedDelay(() -> {
|
||||
connectionManager.closeExpiredConnections();
|
||||
connectionManager.closeIdleConnections(40, TimeUnit.SECONDS);
|
||||
}, 40L, 40L, TimeUnit.SECONDS);
|
||||
initializeCleanupExecutors();
|
||||
scheduledExecutor.scheduleWithFixedDelay(CommonHttpClient::dispatchConnectionPoolCleanup,
|
||||
40L, 40L, TimeUnit.SECONDS);
|
||||
|
||||
// shutdown hook
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(CommonHttpClient::close));
|
||||
@@ -156,13 +159,119 @@ public class CommonHttpClient {
|
||||
public static CloseableHttpClient getHttpClient() {
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
public static PoolingHttpClientConnectionManager getConnectionManager() {
|
||||
return connectionManager;
|
||||
}
|
||||
|
||||
static void dispatchConnectionPoolCleanup() {
|
||||
if (cleanupTask != null) {
|
||||
cleanupTask.dispatch();
|
||||
}
|
||||
}
|
||||
|
||||
static void setConnectionManagerForTest(PoolingHttpClientConnectionManager manager) {
|
||||
connectionManager = manager;
|
||||
}
|
||||
|
||||
static void setBeforeCleanupHookForTest(Runnable hook) {
|
||||
beforeCleanupHook = hook;
|
||||
}
|
||||
|
||||
public static void close() {
|
||||
try {
|
||||
httpClient.close();
|
||||
if (httpClient != null) {
|
||||
httpClient.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("close http client error", e);
|
||||
}
|
||||
if (scheduledExecutor != null) {
|
||||
scheduledExecutor.shutdownNow();
|
||||
}
|
||||
if (cleanupExecutor != null) {
|
||||
cleanupExecutor.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
private static void initializeCleanupExecutors() {
|
||||
ThreadFactory threadFactory = new ThreadFactoryBuilder()
|
||||
.setNameFormat("http-connection-pool-cleaner-%d")
|
||||
.setDaemon(true)
|
||||
.build();
|
||||
scheduledExecutor = Executors.newSingleThreadScheduledExecutor(threadFactory);
|
||||
cleanupExecutor = Executors.newThreadPerTaskExecutor(Thread.ofVirtual()
|
||||
.name("http-connection-pool-cleaner-vt-", 0)
|
||||
.uncaughtExceptionHandler((thread, throwable) ->
|
||||
log.error("HTTP connection pool cleanup has uncaughtException.", throwable))
|
||||
.factory());
|
||||
cleanupTask = new ScheduledDispatchTask(cleanupExecutor, CommonHttpClient::runConnectionPoolCleanup);
|
||||
}
|
||||
|
||||
private static void runConnectionPoolCleanup() {
|
||||
Runnable hook = beforeCleanupHook;
|
||||
if (hook != null) {
|
||||
hook.run();
|
||||
}
|
||||
if (connectionManager == null) {
|
||||
return;
|
||||
}
|
||||
connectionManager.closeExpiredConnections();
|
||||
connectionManager.closeIdleConnections(40, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private static final class ScheduledDispatchTask {
|
||||
|
||||
private final ExecutorService executor;
|
||||
|
||||
private final Runnable task;
|
||||
|
||||
private boolean running;
|
||||
|
||||
private int pendingRuns;
|
||||
|
||||
private ScheduledDispatchTask(ExecutorService executor, Runnable task) {
|
||||
this.executor = executor;
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
private void dispatch() {
|
||||
boolean shouldSchedule;
|
||||
synchronized (this) {
|
||||
pendingRuns++;
|
||||
shouldSchedule = !running;
|
||||
if (shouldSchedule) {
|
||||
running = true;
|
||||
}
|
||||
}
|
||||
if (shouldSchedule) {
|
||||
scheduleRun();
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleRun() {
|
||||
executor.execute(this::runOnce);
|
||||
}
|
||||
|
||||
private void runOnce() {
|
||||
try {
|
||||
task.run();
|
||||
} finally {
|
||||
scheduleNextIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleNextIfNeeded() {
|
||||
boolean shouldSchedule;
|
||||
synchronized (this) {
|
||||
pendingRuns = Math.max(0, pendingRuns - 1);
|
||||
shouldSchedule = pendingRuns > 0;
|
||||
if (!shouldSchedule) {
|
||||
running = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
scheduleRun();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+59
-15
@@ -18,15 +18,19 @@
|
||||
package org.apache.hertzbeat.collector.dispatch;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.common.concurrent.ManagedExecutor;
|
||||
import org.apache.hertzbeat.common.concurrent.ManagedExecutors;
|
||||
import org.apache.hertzbeat.common.config.VirtualThreadProperties;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Collection task worker thread pool
|
||||
@@ -35,25 +39,55 @@ import java.util.concurrent.TimeUnit;
|
||||
@Slf4j
|
||||
public class WorkerPool implements DisposableBean {
|
||||
|
||||
private ThreadPoolExecutor workerExecutor;
|
||||
private final ManagedExecutor workerExecutor;
|
||||
|
||||
private final ManagedExecutor longRunningExecutor;
|
||||
|
||||
public WorkerPool() {
|
||||
initWorkExecutor();
|
||||
this(VirtualThreadProperties.defaults());
|
||||
}
|
||||
|
||||
private void initWorkExecutor() {
|
||||
// thread factory
|
||||
ThreadFactory threadFactory = new ThreadFactoryBuilder()
|
||||
.setUncaughtExceptionHandler((thread, throwable) -> {
|
||||
log.error("[Important] WorkerPool workerExecutor has uncaughtException.", throwable);
|
||||
@Autowired
|
||||
public WorkerPool(VirtualThreadProperties virtualThreadProperties) {
|
||||
VirtualThreadProperties properties =
|
||||
virtualThreadProperties == null ? VirtualThreadProperties.defaults() : virtualThreadProperties;
|
||||
this.workerExecutor = createWorkerExecutor(properties);
|
||||
this.longRunningExecutor = createLongRunningExecutor(properties, workerExecutor);
|
||||
}
|
||||
|
||||
private ManagedExecutor createWorkerExecutor(VirtualThreadProperties properties) {
|
||||
Thread.UncaughtExceptionHandler handler = (thread, throwable) -> {
|
||||
log.error("[Important] WorkerPool workerExecutor has uncaughtException.", throwable);
|
||||
log.error("Thread Name {} : {}", thread.getName(), throwable.getMessage(), throwable);
|
||||
};
|
||||
if (properties.enabled()) {
|
||||
VirtualThreadProperties.PoolProperties poolProperties = properties.collector();
|
||||
return ManagedExecutors.newVirtualExecutor("collector-worker", "collect-worker-",
|
||||
poolProperties.mode(), poolProperties.maxConcurrentJobs(), handler);
|
||||
}
|
||||
return ManagedExecutors.wrap("collector-worker", createLegacyExecutor(handler));
|
||||
}
|
||||
|
||||
private ManagedExecutor createLongRunningExecutor(VirtualThreadProperties properties, ManagedExecutor fallback) {
|
||||
if (!properties.enabled()) {
|
||||
return fallback;
|
||||
}
|
||||
return ManagedExecutors.newPlatformExecutor("collector-long-running", "collect-long-running-",
|
||||
(thread, throwable) -> {
|
||||
log.error("[Important] WorkerPool longRunningExecutor has uncaughtException.", throwable);
|
||||
log.error("Thread Name {} : {}", thread.getName(), throwable.getMessage(), throwable);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
private ExecutorService createLegacyExecutor(Thread.UncaughtExceptionHandler handler) {
|
||||
ThreadFactory threadFactory = new ThreadFactoryBuilder()
|
||||
.setUncaughtExceptionHandler(handler)
|
||||
.setDaemon(true)
|
||||
.setNameFormat("collect-worker-%d")
|
||||
.build();
|
||||
int coreSize = Math.max(2, Runtime.getRuntime().availableProcessors());
|
||||
int maxSize = Runtime.getRuntime().availableProcessors() * 16;
|
||||
workerExecutor = new ThreadPoolExecutor(coreSize,
|
||||
return new ThreadPoolExecutor(coreSize,
|
||||
maxSize,
|
||||
10,
|
||||
TimeUnit.SECONDS,
|
||||
@@ -72,10 +106,20 @@ public class WorkerPool implements DisposableBean {
|
||||
workerExecutor.execute(runnable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the long-lived dispatcher job outside of the collector admission limit.
|
||||
*
|
||||
* @param runnable dispatcher task
|
||||
*/
|
||||
public void executeLongRunning(Runnable runnable) {
|
||||
longRunningExecutor.execute(runnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
if (workerExecutor != null) {
|
||||
workerExecutor.shutdownNow();
|
||||
workerExecutor.close();
|
||||
if (longRunningExecutor != workerExecutor) {
|
||||
longRunningExecutor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+146
-20
@@ -32,19 +32,24 @@ import org.apache.hertzbeat.collector.dispatch.entrance.processor.GoOfflineProce
|
||||
import org.apache.hertzbeat.collector.dispatch.entrance.processor.GoOnlineProcessor;
|
||||
import org.apache.hertzbeat.collector.dispatch.entrance.processor.HeartbeatProcessor;
|
||||
import org.apache.hertzbeat.collector.timer.TimerDispatch;
|
||||
import org.apache.hertzbeat.common.concurrent.BackgroundTaskExecutor;
|
||||
import org.apache.hertzbeat.common.config.VirtualThreadProperties;
|
||||
import org.apache.hertzbeat.common.entity.dto.CollectorInfo;
|
||||
import org.apache.hertzbeat.common.entity.message.ClusterMsg;
|
||||
import org.apache.hertzbeat.common.support.CommonThreadPool;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.apache.hertzbeat.remoting.RemotingClient;
|
||||
import org.apache.hertzbeat.remoting.event.NettyEventListener;
|
||||
import org.apache.hertzbeat.remoting.netty.NettyClientConfig;
|
||||
import org.apache.hertzbeat.remoting.netty.NettyRemotingClient;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
@@ -70,11 +75,42 @@ public class CollectServer implements CommandLineRunner {
|
||||
|
||||
private ScheduledExecutorService scheduledExecutor;
|
||||
|
||||
private final ExecutorService heartbeatExecutor;
|
||||
|
||||
private final Object heartbeatLock = new Object();
|
||||
|
||||
private boolean heartbeatRunning;
|
||||
|
||||
private boolean heartbeatPending;
|
||||
|
||||
private final Runnable closeApplicationAction;
|
||||
|
||||
public CollectServer(final CollectJobService collectJobService,
|
||||
final TimerDispatch timerDispatch,
|
||||
final DispatchProperties properties,
|
||||
final CommonThreadPool threadPool,
|
||||
final BackgroundTaskExecutor threadPool,
|
||||
final CollectorInfoProperties infoProperties) {
|
||||
this(collectJobService, timerDispatch, properties, threadPool, infoProperties, null,
|
||||
VirtualThreadProperties.defaults());
|
||||
}
|
||||
|
||||
public CollectServer(final CollectJobService collectJobService,
|
||||
final TimerDispatch timerDispatch,
|
||||
final DispatchProperties properties,
|
||||
final BackgroundTaskExecutor threadPool,
|
||||
final CollectorInfoProperties infoProperties,
|
||||
final VirtualThreadProperties virtualThreadProperties) {
|
||||
this(collectJobService, timerDispatch, properties, threadPool, infoProperties, null, virtualThreadProperties);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public CollectServer(final CollectJobService collectJobService,
|
||||
final TimerDispatch timerDispatch,
|
||||
final DispatchProperties properties,
|
||||
final BackgroundTaskExecutor threadPool,
|
||||
final CollectorInfoProperties infoProperties,
|
||||
final ConfigurableApplicationContext applicationContext,
|
||||
final VirtualThreadProperties virtualThreadProperties) {
|
||||
if (properties == null || properties.getEntrance() == null || properties.getEntrance().getNetty() == null) {
|
||||
log.error("init error, please config dispatch entrance netty props in application.yml");
|
||||
throw new IllegalArgumentException("please config dispatch entrance netty props");
|
||||
@@ -87,10 +123,12 @@ public class CollectServer implements CommandLineRunner {
|
||||
this.timerDispatch = timerDispatch;
|
||||
this.collectJobService.setCollectServer(this);
|
||||
this.infoProperties = infoProperties;
|
||||
this.heartbeatExecutor = createHeartbeatExecutor(virtualThreadProperties);
|
||||
this.closeApplicationAction = createCloseApplicationAction(applicationContext);
|
||||
this.init(properties, threadPool);
|
||||
}
|
||||
|
||||
private void init(final DispatchProperties properties, final CommonThreadPool threadPool) {
|
||||
private void init(final DispatchProperties properties, final BackgroundTaskExecutor threadPool) {
|
||||
NettyClientConfig nettyClientConfig = new NettyClientConfig();
|
||||
DispatchProperties.EntranceProperties.NettyProperties nettyProperties = properties.getEntrance().getNetty();
|
||||
nettyClientConfig.setServerHost(nettyProperties.getManagerHost());
|
||||
@@ -101,13 +139,19 @@ public class CollectServer implements CommandLineRunner {
|
||||
this.remotingClient.registerProcessor(ClusterMsg.MessageType.ISSUE_CYCLIC_TASK, new CollectCyclicDataProcessor(this));
|
||||
this.remotingClient.registerProcessor(ClusterMsg.MessageType.DELETE_CYCLIC_TASK, new DeleteCyclicTaskProcessor(this));
|
||||
this.remotingClient.registerProcessor(ClusterMsg.MessageType.ISSUE_ONE_TIME_TASK, new CollectOneTimeDataProcessor(this));
|
||||
this.remotingClient.registerProcessor(ClusterMsg.MessageType.GO_OFFLINE, new GoOfflineProcessor());
|
||||
this.remotingClient.registerProcessor(ClusterMsg.MessageType.GO_ONLINE, new GoOnlineProcessor());
|
||||
this.remotingClient.registerProcessor(ClusterMsg.MessageType.GO_CLOSE, new GoCloseProcessor(this));
|
||||
this.remotingClient.registerProcessor(ClusterMsg.MessageType.GO_OFFLINE, new GoOfflineProcessor(timerDispatch));
|
||||
this.remotingClient.registerProcessor(ClusterMsg.MessageType.GO_ONLINE, new GoOnlineProcessor(timerDispatch));
|
||||
this.remotingClient.registerProcessor(ClusterMsg.MessageType.GO_CLOSE,
|
||||
new GoCloseProcessor(this, timerDispatch, closeApplicationAction));
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
this.scheduledExecutor.shutdownNow();
|
||||
if (this.scheduledExecutor != null) {
|
||||
this.scheduledExecutor.shutdownNow();
|
||||
}
|
||||
if (this.heartbeatExecutor != null) {
|
||||
this.heartbeatExecutor.shutdownNow();
|
||||
}
|
||||
|
||||
this.remotingClient.shutdown();
|
||||
}
|
||||
@@ -120,6 +164,21 @@ public class CollectServer implements CommandLineRunner {
|
||||
this.remotingClient.sendMsg(message);
|
||||
}
|
||||
|
||||
void dispatchHeartbeat(String identity) {
|
||||
if (heartbeatExecutor == null) {
|
||||
sendHeartbeat(identity);
|
||||
return;
|
||||
}
|
||||
synchronized (heartbeatLock) {
|
||||
if (heartbeatRunning) {
|
||||
heartbeatPending = true;
|
||||
return;
|
||||
}
|
||||
heartbeatRunning = true;
|
||||
}
|
||||
submitHeartbeat(identity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(String... args) throws Exception {
|
||||
this.remotingClient.start();
|
||||
@@ -161,19 +220,8 @@ public class CollectServer implements CommandLineRunner {
|
||||
.build();
|
||||
scheduledExecutor = Executors.newSingleThreadScheduledExecutor(threadFactory);
|
||||
// schedule send heartbeat message
|
||||
scheduledExecutor.scheduleAtFixedRate(() -> {
|
||||
try {
|
||||
ClusterMsg.Message heartbeat = ClusterMsg.Message.newBuilder()
|
||||
.setIdentity(identity)
|
||||
.setDirection(ClusterMsg.Direction.REQUEST)
|
||||
.setType(ClusterMsg.MessageType.HEARTBEAT)
|
||||
.build();
|
||||
CollectServer.this.sendMsg(heartbeat);
|
||||
log.info("collector send cluster server heartbeat, time: {}.", System.currentTimeMillis());
|
||||
} catch (Exception e) {
|
||||
log.error("schedule send heartbeat to server error.{}", e.getMessage());
|
||||
}
|
||||
}, 5, 5, TimeUnit.SECONDS);
|
||||
scheduledExecutor.scheduleAtFixedRate(() -> CollectServer.this.dispatchHeartbeat(identity),
|
||||
5, 5, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,4 +230,82 @@ public class CollectServer implements CommandLineRunner {
|
||||
log.info("handle idle event triggered. collector is going offline.");
|
||||
}
|
||||
}
|
||||
|
||||
private ExecutorService createHeartbeatExecutor(VirtualThreadProperties virtualThreadProperties) {
|
||||
VirtualThreadProperties properties =
|
||||
virtualThreadProperties == null ? VirtualThreadProperties.defaults() : virtualThreadProperties;
|
||||
if (!properties.enabled()) {
|
||||
return null;
|
||||
}
|
||||
return Executors.newThreadPerTaskExecutor(Thread.ofVirtual()
|
||||
.name("heartbeat-worker-vt-", 0)
|
||||
.uncaughtExceptionHandler((thread, throwable) -> {
|
||||
log.error("HeartBeat worker has uncaughtException.");
|
||||
log.error(throwable.getMessage(), throwable);
|
||||
})
|
||||
.factory());
|
||||
}
|
||||
|
||||
private void submitHeartbeat(String identity) {
|
||||
boolean submitted = false;
|
||||
try {
|
||||
heartbeatExecutor.execute(() -> {
|
||||
try {
|
||||
sendHeartbeat(identity);
|
||||
} finally {
|
||||
onHeartbeatComplete(identity);
|
||||
}
|
||||
});
|
||||
submitted = true;
|
||||
} finally {
|
||||
if (!submitted) {
|
||||
synchronized (heartbeatLock) {
|
||||
heartbeatRunning = false;
|
||||
heartbeatPending = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onHeartbeatComplete(String identity) {
|
||||
boolean shouldRunAgain;
|
||||
synchronized (heartbeatLock) {
|
||||
if (heartbeatPending) {
|
||||
heartbeatPending = false;
|
||||
shouldRunAgain = true;
|
||||
} else {
|
||||
heartbeatRunning = false;
|
||||
shouldRunAgain = false;
|
||||
}
|
||||
}
|
||||
if (shouldRunAgain) {
|
||||
submitHeartbeat(identity);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendHeartbeat(String identity) {
|
||||
try {
|
||||
ClusterMsg.Message heartbeat = ClusterMsg.Message.newBuilder()
|
||||
.setIdentity(identity)
|
||||
.setDirection(ClusterMsg.Direction.REQUEST)
|
||||
.setType(ClusterMsg.MessageType.HEARTBEAT)
|
||||
.build();
|
||||
CollectServer.this.sendMsg(heartbeat);
|
||||
log.info("collector send cluster server heartbeat, time: {}.", System.currentTimeMillis());
|
||||
} catch (Exception e) {
|
||||
log.error("schedule send heartbeat to server error.{}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private Runnable createCloseApplicationAction(ConfigurableApplicationContext applicationContext) {
|
||||
if (applicationContext == null) {
|
||||
return () -> {};
|
||||
}
|
||||
return () -> {
|
||||
SpringApplication.exit(applicationContext, () -> 0);
|
||||
if (applicationContext.isActive()) {
|
||||
applicationContext.close();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+8
-9
@@ -23,9 +23,7 @@ import org.apache.hertzbeat.collector.dispatch.entrance.CollectServer;
|
||||
import org.apache.hertzbeat.collector.timer.TimerDispatch;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.message.ClusterMsg;
|
||||
import org.apache.hertzbeat.common.support.SpringContextHolder;
|
||||
import org.apache.hertzbeat.remoting.netty.NettyRemotingProcessor;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
|
||||
/**
|
||||
* handle collector close message
|
||||
@@ -34,24 +32,25 @@ import org.springframework.boot.SpringApplication;
|
||||
@Slf4j
|
||||
public class GoCloseProcessor implements NettyRemotingProcessor {
|
||||
private final CollectServer collectServer;
|
||||
private TimerDispatch timerDispatch;
|
||||
private final TimerDispatch timerDispatch;
|
||||
private final Runnable closeApplicationAction;
|
||||
|
||||
public GoCloseProcessor(final CollectServer collectServer) {
|
||||
public GoCloseProcessor(final CollectServer collectServer,
|
||||
final TimerDispatch timerDispatch,
|
||||
final Runnable closeApplicationAction) {
|
||||
this.collectServer = collectServer;
|
||||
this.timerDispatch = timerDispatch;
|
||||
this.closeApplicationAction = closeApplicationAction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClusterMsg.Message handle(ChannelHandlerContext ctx, ClusterMsg.Message message) {
|
||||
if (this.timerDispatch == null) {
|
||||
this.timerDispatch = SpringContextHolder.getBean(TimerDispatch.class);
|
||||
}
|
||||
if (message.getMsg().toStringUtf8().contains(CommonConstants.COLLECTOR_AUTH_FAILED)) {
|
||||
log.error("[Auth Failed]receive client auth failed message and go close. {}", message.getMsg());
|
||||
}
|
||||
this.timerDispatch.goOffline();
|
||||
this.collectServer.shutdown();
|
||||
SpringApplication.exit(SpringContextHolder.getApplicationContext(), () -> 0);
|
||||
SpringContextHolder.shutdown();
|
||||
closeApplicationAction.run();
|
||||
log.info("receive offline message and close success");
|
||||
return null;
|
||||
}
|
||||
|
||||
+6
-6
@@ -23,7 +23,6 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.collector.timer.TimerDispatch;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.message.ClusterMsg;
|
||||
import org.apache.hertzbeat.common.support.SpringContextHolder;
|
||||
import org.apache.hertzbeat.remoting.netty.NettyRemotingProcessor;
|
||||
|
||||
/**
|
||||
@@ -32,14 +31,15 @@ import org.apache.hertzbeat.remoting.netty.NettyRemotingProcessor;
|
||||
*/
|
||||
@Slf4j
|
||||
public class GoOfflineProcessor implements NettyRemotingProcessor {
|
||||
|
||||
private TimerDispatch timerDispatch;
|
||||
|
||||
private final TimerDispatch timerDispatch;
|
||||
|
||||
public GoOfflineProcessor(TimerDispatch timerDispatch) {
|
||||
this.timerDispatch = timerDispatch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClusterMsg.Message handle(ChannelHandlerContext ctx, ClusterMsg.Message message) {
|
||||
if (this.timerDispatch == null) {
|
||||
this.timerDispatch = SpringContextHolder.getBean(TimerDispatch.class);
|
||||
}
|
||||
timerDispatch.goOffline();
|
||||
log.info("receive offline message and handle success");
|
||||
if (message.getMsg().toStringUtf8().contains(CommonConstants.COLLECTOR_AUTH_FAILED)) {
|
||||
|
||||
+5
-5
@@ -24,7 +24,6 @@ import org.apache.hertzbeat.collector.timer.TimerDispatch;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.dto.ServerInfo;
|
||||
import org.apache.hertzbeat.common.entity.message.ClusterMsg;
|
||||
import org.apache.hertzbeat.common.support.SpringContextHolder;
|
||||
import org.apache.hertzbeat.common.util.AesUtil;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.apache.hertzbeat.remoting.netty.NettyRemotingProcessor;
|
||||
@@ -36,13 +35,14 @@ import org.apache.hertzbeat.remoting.netty.NettyRemotingProcessor;
|
||||
@Slf4j
|
||||
public class GoOnlineProcessor implements NettyRemotingProcessor {
|
||||
|
||||
private TimerDispatch timerDispatch;
|
||||
private final TimerDispatch timerDispatch;
|
||||
|
||||
public GoOnlineProcessor(TimerDispatch timerDispatch) {
|
||||
this.timerDispatch = timerDispatch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClusterMsg.Message handle(ChannelHandlerContext ctx, ClusterMsg.Message message) {
|
||||
if (this.timerDispatch == null) {
|
||||
this.timerDispatch = SpringContextHolder.getBean(TimerDispatch.class);
|
||||
}
|
||||
if (message.getMsg().isEmpty()) {
|
||||
log.warn("The message that server response to collector is empty, please upgrade server");
|
||||
} else {
|
||||
|
||||
+28
-1
@@ -25,9 +25,11 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.collector.constants.ScheduleTypeEnum;
|
||||
import org.apache.hertzbeat.collector.dispatch.MetricsTaskDispatch;
|
||||
import org.apache.hertzbeat.collector.dispatch.entrance.internal.CollectResponseEventListener;
|
||||
import org.apache.hertzbeat.common.entity.job.Job;
|
||||
import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
@@ -35,6 +37,8 @@ import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.common.timer.HashedWheelTimer;
|
||||
import org.apache.hertzbeat.common.timer.Timeout;
|
||||
import org.apache.hertzbeat.common.timer.Timer;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.scheduling.support.CronExpression;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -69,7 +73,19 @@ public class TimerDispatcher implements TimerDispatch, DisposableBean {
|
||||
*/
|
||||
private final AtomicBoolean started;
|
||||
|
||||
private final Supplier<MetricsTaskDispatch> metricsTaskDispatchSupplier;
|
||||
|
||||
public TimerDispatcher() {
|
||||
this(() -> timeout -> {
|
||||
});
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public TimerDispatcher(ObjectProvider<MetricsTaskDispatch> metricsTaskDispatchProvider) {
|
||||
this(resolveMetricsTaskDispatchSupplier(metricsTaskDispatchProvider));
|
||||
}
|
||||
|
||||
private TimerDispatcher(Supplier<MetricsTaskDispatch> metricsTaskDispatchSupplier) {
|
||||
this.wheelTimer = new HashedWheelTimer(r -> {
|
||||
Thread ret = new Thread(r, "wheelTimer");
|
||||
ret.setDaemon(true);
|
||||
@@ -79,6 +95,16 @@ public class TimerDispatcher implements TimerDispatch, DisposableBean {
|
||||
this.currentTempTaskMap = new ConcurrentHashMap<>(8);
|
||||
this.eventListeners = new ConcurrentHashMap<>(8);
|
||||
this.started = new AtomicBoolean(true);
|
||||
this.metricsTaskDispatchSupplier = metricsTaskDispatchSupplier;
|
||||
}
|
||||
|
||||
private static Supplier<MetricsTaskDispatch> resolveMetricsTaskDispatchSupplier(
|
||||
ObjectProvider<MetricsTaskDispatch> metricsTaskDispatchProvider) {
|
||||
if (metricsTaskDispatchProvider == null) {
|
||||
return () -> timeout -> {
|
||||
};
|
||||
}
|
||||
return metricsTaskDispatchProvider::getObject;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -87,7 +113,8 @@ public class TimerDispatcher implements TimerDispatch, DisposableBean {
|
||||
log.warn("Collector is offline, can not dispatch collect jobs.");
|
||||
return;
|
||||
}
|
||||
WheelTimerTask timerJob = new WheelTimerTask(addJob);
|
||||
// Delay dispatcher lookup to avoid a startup cycle with CommonDispatcher.
|
||||
WheelTimerTask timerJob = new WheelTimerTask(addJob, metricsTaskDispatchSupplier);
|
||||
if (addJob.isCyclic()) {
|
||||
Long nextExecutionTime = getNextExecutionInterval(addJob);
|
||||
Timeout timeout = wheelTimer.newTimeout(timerJob, nextExecutionTime, TimeUnit.SECONDS);
|
||||
|
||||
+9
-5
@@ -27,7 +27,6 @@ import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.job.Configmap;
|
||||
import org.apache.hertzbeat.common.entity.job.Job;
|
||||
import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
import org.apache.hertzbeat.common.support.SpringContextHolder;
|
||||
import org.apache.hertzbeat.common.timer.Timeout;
|
||||
import org.apache.hertzbeat.common.timer.TimerTask;
|
||||
import org.apache.hertzbeat.common.util.AesUtil;
|
||||
@@ -35,6 +34,7 @@ import org.apache.hertzbeat.common.util.AesUtil;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -44,11 +44,15 @@ import java.util.stream.Collectors;
|
||||
public class WheelTimerTask implements TimerTask {
|
||||
|
||||
private final Job job;
|
||||
private final MetricsTaskDispatch metricsTaskDispatch;
|
||||
private final Supplier<MetricsTaskDispatch> metricsTaskDispatchSupplier;
|
||||
private static final Gson GSON = new Gson();
|
||||
|
||||
public WheelTimerTask(Job job) {
|
||||
this.metricsTaskDispatch = SpringContextHolder.getBean(MetricsTaskDispatch.class);
|
||||
public WheelTimerTask(Job job, MetricsTaskDispatch metricsTaskDispatch) {
|
||||
this(job, () -> metricsTaskDispatch);
|
||||
}
|
||||
|
||||
public WheelTimerTask(Job job, Supplier<MetricsTaskDispatch> metricsTaskDispatchSupplier) {
|
||||
this.metricsTaskDispatchSupplier = metricsTaskDispatchSupplier;
|
||||
this.job = job;
|
||||
// The initialization job will monitor the actual parameter value and replace the collection field
|
||||
initJobMetrics(job);
|
||||
@@ -93,7 +97,7 @@ public class WheelTimerTask implements TimerTask {
|
||||
@Override
|
||||
public void run(Timeout timeout) throws Exception {
|
||||
job.setDispatchTime(System.currentTimeMillis());
|
||||
metricsTaskDispatch.dispatchMetricsTask(timeout);
|
||||
metricsTaskDispatchSupplier.get().dispatchMetricsTask(timeout);
|
||||
}
|
||||
|
||||
public Job getJob() {
|
||||
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.collector.collect.common.cache;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Tests for {@link GlobalConnectionCache}.
|
||||
*/
|
||||
class GlobalConnectionCacheTest {
|
||||
|
||||
private TestGlobalConnectionCache globalConnectionCache;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
globalConnectionCache = new TestGlobalConnectionCache();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
if (globalConnectionCache != null) {
|
||||
globalConnectionCache.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void dispatchCleanupCacheRunsOnVirtualThread() throws Exception {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicBoolean virtualThread = new AtomicBoolean(false);
|
||||
globalConnectionCache.setVirtualThreadHook(latch, virtualThread);
|
||||
|
||||
globalConnectionCache.dispatchCleanupCache();
|
||||
|
||||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
assertTrue(virtualThread.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void dispatchCleanupCacheDoesNotRunConcurrently() throws Exception {
|
||||
CountDownLatch firstStarted = new CountDownLatch(1);
|
||||
CountDownLatch releaseFirst = new CountDownLatch(1);
|
||||
CountDownLatch secondStarted = new CountDownLatch(1);
|
||||
AtomicInteger maxConcurrent = new AtomicInteger();
|
||||
globalConnectionCache.setConcurrencyHook(firstStarted, releaseFirst, secondStarted, maxConcurrent);
|
||||
|
||||
globalConnectionCache.dispatchCleanupCache();
|
||||
assertTrue(firstStarted.await(5, TimeUnit.SECONDS));
|
||||
|
||||
globalConnectionCache.dispatchCleanupCache();
|
||||
assertFalse(secondStarted.await(200, TimeUnit.MILLISECONDS));
|
||||
|
||||
releaseFirst.countDown();
|
||||
assertTrue(secondStarted.await(5, TimeUnit.SECONDS));
|
||||
assertEquals(1, maxConcurrent.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void dispatchCleanupCacheClosesExpiredConnections() throws Exception {
|
||||
TestConnection connection = new TestConnection();
|
||||
globalConnectionCache.addCache("expired", connection, -1L);
|
||||
|
||||
globalConnectionCache.dispatchCleanupCache();
|
||||
|
||||
assertTrue(connection.closed.await(5, TimeUnit.SECONDS));
|
||||
assertEquals(1, connection.closeCount.get());
|
||||
assertTrue(globalConnectionCache.getCache("expired", false).isEmpty());
|
||||
}
|
||||
|
||||
private static final class TestGlobalConnectionCache extends GlobalConnectionCache {
|
||||
|
||||
private CountDownLatch virtualThreadLatch;
|
||||
|
||||
private AtomicBoolean virtualThread;
|
||||
|
||||
private CountDownLatch firstStarted;
|
||||
|
||||
private CountDownLatch releaseFirst;
|
||||
|
||||
private CountDownLatch secondStarted;
|
||||
|
||||
private AtomicInteger maxConcurrent;
|
||||
|
||||
private final AtomicInteger concurrent = new AtomicInteger();
|
||||
|
||||
private final AtomicInteger invocations = new AtomicInteger();
|
||||
|
||||
private TestGlobalConnectionCache() {
|
||||
super(false);
|
||||
}
|
||||
|
||||
private void setVirtualThreadHook(CountDownLatch latch, AtomicBoolean flag) {
|
||||
this.virtualThreadLatch = latch;
|
||||
this.virtualThread = flag;
|
||||
}
|
||||
|
||||
private void setConcurrencyHook(CountDownLatch firstStarted, CountDownLatch releaseFirst,
|
||||
CountDownLatch secondStarted, AtomicInteger maxConcurrent) {
|
||||
this.firstStarted = firstStarted;
|
||||
this.releaseFirst = releaseFirst;
|
||||
this.secondStarted = secondStarted;
|
||||
this.maxConcurrent = maxConcurrent;
|
||||
}
|
||||
|
||||
@Override
|
||||
void beforeCleanTimeoutOrUnHealthyCacheRun() {
|
||||
if (virtualThread != null) {
|
||||
virtualThread.set(Thread.currentThread().isVirtual());
|
||||
}
|
||||
if (virtualThreadLatch != null) {
|
||||
virtualThreadLatch.countDown();
|
||||
}
|
||||
if (maxConcurrent == null) {
|
||||
return;
|
||||
}
|
||||
int active = concurrent.incrementAndGet();
|
||||
maxConcurrent.accumulateAndGet(active, Math::max);
|
||||
int currentInvocation = invocations.incrementAndGet();
|
||||
try {
|
||||
if (currentInvocation == 1) {
|
||||
firstStarted.countDown();
|
||||
releaseFirst.await(5, TimeUnit.SECONDS);
|
||||
} else if (currentInvocation == 2) {
|
||||
secondStarted.countDown();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
} finally {
|
||||
concurrent.decrementAndGet();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class TestConnection extends AbstractConnection<Object> {
|
||||
|
||||
private final CountDownLatch closed = new CountDownLatch(1);
|
||||
|
||||
private final AtomicInteger closeCount = new AtomicInteger();
|
||||
|
||||
@Override
|
||||
public Object getConnection() {
|
||||
return new Object();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeConnection() {
|
||||
closeCount.incrementAndGet();
|
||||
closed.countDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.collector.collect.common.http;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Tests for CommonHttpClient cleanup dispatch.
|
||||
*/
|
||||
class CommonHttpClientVirtualThreadTest {
|
||||
|
||||
private final PoolingHttpClientConnectionManager originalConnectionManager = CommonHttpClient.getConnectionManager();
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
CommonHttpClient.setBeforeCleanupHookForTest(null);
|
||||
CommonHttpClient.setConnectionManagerForTest(originalConnectionManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
void dispatchConnectionPoolCleanupRunsOnVirtualThread() throws Exception {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicBoolean virtualThread = new AtomicBoolean(false);
|
||||
PoolingHttpClientConnectionManager manager = mock(PoolingHttpClientConnectionManager.class);
|
||||
CommonHttpClient.setConnectionManagerForTest(manager);
|
||||
CommonHttpClient.setBeforeCleanupHookForTest(() -> {
|
||||
virtualThread.set(Thread.currentThread().isVirtual());
|
||||
latch.countDown();
|
||||
});
|
||||
|
||||
CommonHttpClient.dispatchConnectionPoolCleanup();
|
||||
|
||||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
assertTrue(virtualThread.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void dispatchConnectionPoolCleanupDoesNotRunConcurrently() throws Exception {
|
||||
CountDownLatch firstStarted = new CountDownLatch(1);
|
||||
CountDownLatch releaseFirst = new CountDownLatch(1);
|
||||
CountDownLatch secondStarted = new CountDownLatch(1);
|
||||
AtomicInteger concurrent = new AtomicInteger();
|
||||
AtomicInteger maxConcurrent = new AtomicInteger();
|
||||
AtomicInteger invocations = new AtomicInteger();
|
||||
PoolingHttpClientConnectionManager manager = mock(PoolingHttpClientConnectionManager.class);
|
||||
CommonHttpClient.setConnectionManagerForTest(manager);
|
||||
CommonHttpClient.setBeforeCleanupHookForTest(() -> {
|
||||
int active = concurrent.incrementAndGet();
|
||||
maxConcurrent.accumulateAndGet(active, Math::max);
|
||||
int currentInvocation = invocations.incrementAndGet();
|
||||
try {
|
||||
if (currentInvocation == 1) {
|
||||
firstStarted.countDown();
|
||||
releaseFirst.await(5, TimeUnit.SECONDS);
|
||||
} else if (currentInvocation == 2) {
|
||||
secondStarted.countDown();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
} finally {
|
||||
concurrent.decrementAndGet();
|
||||
}
|
||||
});
|
||||
|
||||
CommonHttpClient.dispatchConnectionPoolCleanup();
|
||||
assertTrue(firstStarted.await(5, TimeUnit.SECONDS));
|
||||
|
||||
CommonHttpClient.dispatchConnectionPoolCleanup();
|
||||
assertFalse(secondStarted.await(200, TimeUnit.MILLISECONDS));
|
||||
|
||||
releaseFirst.countDown();
|
||||
assertTrue(secondStarted.await(5, TimeUnit.SECONDS));
|
||||
assertEquals(1, maxConcurrent.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void dispatchConnectionPoolCleanupClosesExpiredAndIdleConnections() throws Exception {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
PoolingHttpClientConnectionManager manager = mock(PoolingHttpClientConnectionManager.class);
|
||||
doAnswer(invocation -> {
|
||||
latch.countDown();
|
||||
return null;
|
||||
}).when(manager).closeExpiredConnections();
|
||||
CommonHttpClient.setConnectionManagerForTest(manager);
|
||||
|
||||
CommonHttpClient.dispatchConnectionPoolCleanup();
|
||||
|
||||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
verify(manager, times(1)).closeExpiredConnections();
|
||||
verify(manager, times(1)).closeIdleConnections(40, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
+17
-26
@@ -20,19 +20,15 @@ package org.apache.hertzbeat.collector.dispatch.entrance.processor;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.protobuf.ByteString;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import org.apache.hertzbeat.collector.timer.TimerDispatch;
|
||||
import org.apache.hertzbeat.collector.timer.TimerDispatcher;
|
||||
import org.apache.hertzbeat.common.entity.job.Job;
|
||||
import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
import org.apache.hertzbeat.common.entity.message.ClusterMsg;
|
||||
import org.apache.hertzbeat.common.support.SpringContextHolder;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
@@ -51,32 +47,27 @@ class GoOnlineProcessorTest {
|
||||
@Mock
|
||||
private ChannelHandlerContext channelHandlerContext;
|
||||
|
||||
private MockedStatic<SpringContextHolder> springContextHolderMockedStatic;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
goOnlineProcessor = new GoOnlineProcessor();
|
||||
timerDispatcher = new TimerDispatcher();
|
||||
springContextHolderMockedStatic = Mockito.mockStatic(SpringContextHolder.class);
|
||||
springContextHolderMockedStatic.when(() -> SpringContextHolder.getBean(TimerDispatch.class)).thenReturn(timerDispatcher);
|
||||
goOnlineProcessor = new GoOnlineProcessor(timerDispatcher);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() throws Exception {
|
||||
springContextHolderMockedStatic.close();
|
||||
timerDispatcher.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
void verifyTaskMapPreservation() throws Exception {
|
||||
Job job = Job.builder()
|
||||
.app("test")
|
||||
.id(12345L)
|
||||
.metrics(Lists.newArrayList(Metrics.builder().interval(100L).build()))
|
||||
.configmap(Lists.newArrayList())
|
||||
.isCyclic(true)
|
||||
.build();
|
||||
.app("test")
|
||||
.id(12345L)
|
||||
.metrics(Lists.newArrayList(Metrics.builder().interval(100L).build()))
|
||||
.configmap(Lists.newArrayList())
|
||||
.isCyclic(true)
|
||||
.build();
|
||||
timerDispatcher.addJob(job, null);
|
||||
|
||||
Field cyclicTaskMapField = TimerDispatcher.class.getDeclaredField("currentCyclicTaskMap");
|
||||
@@ -85,20 +76,20 @@ class GoOnlineProcessorTest {
|
||||
assertEquals(1, currentCyclicTaskMap.size(), "Task map should have 1 job initially");
|
||||
|
||||
ClusterMsg.Message responseMsg = ClusterMsg.Message.newBuilder()
|
||||
.setType(ClusterMsg.MessageType.GO_ONLINE)
|
||||
.setDirection(ClusterMsg.Direction.RESPONSE)
|
||||
.setMsg(ByteString.copyFromUtf8(JsonUtil.toJson(job)))
|
||||
.setIdentity("test-identity")
|
||||
.build();
|
||||
.setType(ClusterMsg.MessageType.GO_ONLINE)
|
||||
.setDirection(ClusterMsg.Direction.RESPONSE)
|
||||
.setMsg(ByteString.copyFromUtf8(JsonUtil.toJson(job)))
|
||||
.setIdentity("test-identity")
|
||||
.build();
|
||||
goOnlineProcessor.handle(channelHandlerContext, responseMsg);
|
||||
assertEquals(1, currentCyclicTaskMap.size(), "Task map should still have 1 job after receiving RESPONSE");
|
||||
|
||||
ClusterMsg.Message requestMsg = ClusterMsg.Message.newBuilder()
|
||||
.setType(ClusterMsg.MessageType.GO_ONLINE)
|
||||
.setDirection(ClusterMsg.Direction.REQUEST)
|
||||
.setMsg(ByteString.copyFromUtf8(JsonUtil.toJson(job)))
|
||||
.setIdentity("test-identity")
|
||||
.build();
|
||||
.setType(ClusterMsg.MessageType.GO_ONLINE)
|
||||
.setDirection(ClusterMsg.Direction.REQUEST)
|
||||
.setMsg(ByteString.copyFromUtf8(JsonUtil.toJson(job)))
|
||||
.setIdentity("test-identity")
|
||||
.build();
|
||||
goOnlineProcessor.handle(channelHandlerContext, requestMsg);
|
||||
assertEquals(0, currentCyclicTaskMap.size(), "Task map should be empty after receiving REQUEST");
|
||||
}
|
||||
|
||||
+14
-5
@@ -46,7 +46,6 @@ import org.apache.kafka.clients.admin.TopicDescription;
|
||||
import org.apache.kafka.clients.consumer.OffsetAndMetadata;
|
||||
import org.apache.kafka.common.TopicPartition;
|
||||
import org.apache.kafka.common.TopicPartitionInfo;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -223,14 +222,14 @@ public class KafkaCollectImpl extends AbstractCollect {
|
||||
|
||||
@Override
|
||||
public void preCheck(Metrics metrics) throws IllegalArgumentException {
|
||||
Assert.isTrue(metrics != null, "Metrics cannot be null");
|
||||
require(metrics != null, "Metrics cannot be null");
|
||||
KafkaProtocol kafkaProtocol = metrics.getKclient();
|
||||
|
||||
// Ensure that metrics and kafkaProtocol are not null
|
||||
Assert.isTrue(metrics != null && kafkaProtocol != null, "Kafka collect must have kafkaProtocol params");
|
||||
require(kafkaProtocol != null, "Kafka collect must have kafkaProtocol params");
|
||||
// Ensure that host and port are not empty
|
||||
Assert.hasText(kafkaProtocol.getHost(), "Kafka Protocol host is required.");
|
||||
Assert.hasText(kafkaProtocol.getPort(), "Kafka Protocol port is required.");
|
||||
requireHasText(kafkaProtocol.getHost(), "Kafka Protocol host is required.");
|
||||
requireHasText(kafkaProtocol.getPort(), "Kafka Protocol port is required.");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -384,4 +383,14 @@ public class KafkaCollectImpl extends AbstractCollect {
|
||||
public String supportProtocol() {
|
||||
return DispatchConstants.PROTOCOL_KAFKA;
|
||||
}
|
||||
|
||||
private static void require(boolean expression, String message) {
|
||||
if (!expression) {
|
||||
throw new IllegalArgumentException(message);
|
||||
}
|
||||
}
|
||||
|
||||
private static void requireHasText(String value, String message) {
|
||||
require(value != null && !value.trim().isEmpty(), message);
|
||||
}
|
||||
}
|
||||
|
||||
+14
-5
@@ -43,7 +43,6 @@ import org.apache.hertzbeat.common.entity.job.protocol.MongodbProtocol;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.common.util.CommonUtil;
|
||||
import org.bson.Document;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Mongodb single collect
|
||||
@@ -87,11 +86,11 @@ public class MongodbSingleCollectImpl extends AbstractCollect {
|
||||
*/
|
||||
@Override
|
||||
public void preCheck(Metrics metrics) throws IllegalArgumentException{
|
||||
Assert.isTrue(metrics != null && metrics.getMongodb() != null, "Mongodb collect must has mongodb params");
|
||||
require(metrics != null && metrics.getMongodb() != null, "Mongodb collect must has mongodb params");
|
||||
MongodbProtocol mongodbProtocol = metrics.getMongodb();
|
||||
Assert.hasText(mongodbProtocol.getCommand(), "Mongodb Protocol command is required.");
|
||||
Assert.hasText(mongodbProtocol.getHost(), "Mongodb Protocol host is required.");
|
||||
Assert.hasText(mongodbProtocol.getPort(), "Mongodb Protocol port is required.");
|
||||
requireHasText(mongodbProtocol.getCommand(), "Mongodb Protocol command is required.");
|
||||
requireHasText(mongodbProtocol.getHost(), "Mongodb Protocol host is required.");
|
||||
requireHasText(mongodbProtocol.getPort(), "Mongodb Protocol port is required.");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -226,4 +225,14 @@ public class MongodbSingleCollectImpl extends AbstractCollect {
|
||||
connectionCommonCache.addCache(identifier, mongodbConnect, 3600 * 1000L);
|
||||
return mongoClient;
|
||||
}
|
||||
|
||||
private static void require(boolean expression, String message) {
|
||||
if (!expression) {
|
||||
throw new IllegalArgumentException(message);
|
||||
}
|
||||
}
|
||||
|
||||
private static void requireHasText(String value, String message) {
|
||||
require(value != null && !value.trim().isEmpty(), message);
|
||||
}
|
||||
}
|
||||
|
||||
+14
-11
@@ -23,6 +23,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Stream;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.hertzbeat.collector.collect.AbstractCollect;
|
||||
@@ -33,8 +34,6 @@ import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.NgqlProtocol;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep.MetricsData.Builder;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StopWatch;
|
||||
|
||||
/**
|
||||
* connect nebulaGraph and collect metrics use NGQL
|
||||
@@ -53,18 +52,17 @@ public class NgqlCollectImpl extends AbstractCollect {
|
||||
@Override
|
||||
public void preCheck(Metrics metrics) throws IllegalArgumentException {
|
||||
NgqlProtocol ngql = metrics.getNgql();
|
||||
Assert.hasText(ngql.getHost(), "NGQL protocol host is required");
|
||||
Assert.hasText(ngql.getPort(), "Port protocol host is required");
|
||||
Assert.hasText(ngql.getParseType(), "NGQL protocol parseType is required");
|
||||
Assert.hasText(ngql.getUsername(), "NGQL protocol username is required");
|
||||
Assert.hasText(ngql.getPassword(), "NGQL protocol password is required");
|
||||
requireHasText(ngql.getHost(), "NGQL protocol host is required");
|
||||
requireHasText(ngql.getPort(), "Port protocol host is required");
|
||||
requireHasText(ngql.getParseType(), "NGQL protocol parseType is required");
|
||||
requireHasText(ngql.getUsername(), "NGQL protocol username is required");
|
||||
requireHasText(ngql.getPassword(), "NGQL protocol password is required");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void collect(Builder builder, Metrics metrics) {
|
||||
NgqlProtocol ngql = metrics.getNgql();
|
||||
StopWatch stopWatch = new StopWatch();
|
||||
stopWatch.start();
|
||||
long startTimeNanos = System.nanoTime();
|
||||
NebulaTemplate nebulaTemplate = new NebulaTemplate();
|
||||
try {
|
||||
boolean initSuccess = nebulaTemplate.initSession(ngql);
|
||||
@@ -79,8 +77,7 @@ public class NgqlCollectImpl extends AbstractCollect {
|
||||
return;
|
||||
}
|
||||
|
||||
stopWatch.stop();
|
||||
long responseTime = stopWatch.getTotalTimeMillis();
|
||||
long responseTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTimeNanos);
|
||||
try {
|
||||
switch (ngql.getParseType()) {
|
||||
case PARSE_TYPE_FILTER_COUNT -> filterCount(nebulaTemplate, ngql, metrics.getAliasFields(), builder, responseTime);
|
||||
@@ -247,4 +244,10 @@ public class NgqlCollectImpl extends AbstractCollect {
|
||||
result.put("running_jobs", String.valueOf(jobs.stream().filter(job -> Objects.equals(job.get("Status"), STATUS_RUNNING)).count()));
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void requireHasText(String value, String message) {
|
||||
if (value == null || value.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,12 +28,6 @@
|
||||
<artifactId>hertzbeat-collector-rocketmq</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.hertzbeat</groupId>
|
||||
@@ -47,4 +41,4 @@
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
</project>
|
||||
|
||||
+38
-26
@@ -19,7 +19,6 @@ package org.apache.hertzbeat.collector.collect.rocketmq;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
@@ -28,10 +27,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -40,6 +35,8 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.hertzbeat.collector.collect.AbstractCollect;
|
||||
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
|
||||
import org.apache.hertzbeat.collector.util.JsonPathParser;
|
||||
import org.apache.hertzbeat.common.concurrent.ManagedExecutor;
|
||||
import org.apache.hertzbeat.common.concurrent.ManagedExecutors;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.RocketmqProtocol;
|
||||
@@ -55,23 +52,21 @@ import org.apache.rocketmq.common.protocol.body.KVTable;
|
||||
import org.apache.rocketmq.common.protocol.body.SubscriptionGroupWrapper;
|
||||
import org.apache.rocketmq.common.protocol.body.TopicList;
|
||||
import org.apache.rocketmq.common.protocol.route.BrokerData;
|
||||
import org.apache.rocketmq.common.utils.ThreadUtils;
|
||||
import org.apache.rocketmq.remoting.RPCHook;
|
||||
import org.apache.rocketmq.tools.admin.DefaultMQAdminExt;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* rocketmq collect
|
||||
*/
|
||||
@Slf4j
|
||||
public class RocketmqSingleCollectImpl extends AbstractCollect implements DisposableBean {
|
||||
public class RocketmqSingleCollectImpl extends AbstractCollect {
|
||||
|
||||
private static final int WAIT_TIMEOUT = 10;
|
||||
static final int QUEUE_CAPACITY = 5000;
|
||||
|
||||
private static final Set<String> SYSTEM_GROUP_SET = new HashSet<>();
|
||||
|
||||
private final ExecutorService executorService;
|
||||
private final ManagedExecutor executorService;
|
||||
|
||||
static {
|
||||
// system consumer group
|
||||
@@ -86,24 +81,27 @@ public class RocketmqSingleCollectImpl extends AbstractCollect implements Dispos
|
||||
}
|
||||
|
||||
public RocketmqSingleCollectImpl() {
|
||||
this(createExecutor());
|
||||
}
|
||||
|
||||
RocketmqSingleCollectImpl(ManagedExecutor executorService) {
|
||||
this.executorService = executorService;
|
||||
}
|
||||
|
||||
private static ManagedExecutor createExecutor() {
|
||||
Runtime runtime = Runtime.getRuntime();
|
||||
int corePoolSize = Math.max(8, runtime.availableProcessors());
|
||||
int maximumPoolSize = Math.max(16, runtime.availableProcessors());
|
||||
ThreadFactory threadFactory = new ThreadFactoryBuilder()
|
||||
.setUncaughtExceptionHandler((thread, throwable) -> {
|
||||
log.error("RocketMQCollectGroup has uncaughtException.");
|
||||
log.error(throwable.getMessage(), throwable);
|
||||
})
|
||||
.setDaemon(true)
|
||||
.setNameFormat("rocketMQ-collector-%d")
|
||||
.build();
|
||||
this.executorService = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, 60L, TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(5000), threadFactory, new ThreadPoolExecutor.DiscardOldestPolicy());
|
||||
Thread.UncaughtExceptionHandler handler = (thread, throwable) -> {
|
||||
log.error("RocketMQCollectGroup has uncaughtException.");
|
||||
log.error(throwable.getMessage(), throwable);
|
||||
};
|
||||
return ManagedExecutors.newDiscardOldestVirtualExecutor("rocketmq-collector", "rocketmq-collector-",
|
||||
corePoolSize, maximumPoolSize, QUEUE_CAPACITY, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
ThreadUtils.shutdownGracefully(this.executorService, 10L, TimeUnit.SECONDS);
|
||||
this.executorService.close();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,10 +110,10 @@ public class RocketmqSingleCollectImpl extends AbstractCollect implements Dispos
|
||||
*/
|
||||
@Override
|
||||
public void preCheck(Metrics metrics) throws IllegalArgumentException {
|
||||
Assert.isTrue(metrics != null && metrics.getRocketmq() != null, "Rocketmq collect must has rocketmq params");
|
||||
require(metrics != null && metrics.getRocketmq() != null, "Rocketmq collect must has rocketmq params");
|
||||
RocketmqProtocol rocketmq = metrics.getRocketmq();
|
||||
Assert.hasText(rocketmq.getNamesrvHost(), "Rocketmq Protocol namesrvHost is required.");
|
||||
Assert.hasText(rocketmq.getNamesrvPort(), "Rocketmq Protocol namesrvPort is required.");
|
||||
requireHasText(rocketmq.getNamesrvHost(), "Rocketmq Protocol namesrvHost is required.");
|
||||
requireHasText(rocketmq.getNamesrvPort(), "Rocketmq Protocol namesrvPort is required.");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -270,7 +268,7 @@ public class RocketmqSingleCollectImpl extends AbstractCollect implements Dispos
|
||||
if (SYSTEM_GROUP_SET.contains(consumerGroup)) {
|
||||
continue;
|
||||
}
|
||||
executorService.submit(() -> {
|
||||
executeConsumerTask(() -> {
|
||||
RocketmqCollectData.ConsumerInfo consumerInfo = new RocketmqCollectData.ConsumerInfo();
|
||||
consumerInfoList.add(consumerInfo);
|
||||
consumerInfo.setConsumerGroup(consumerGroup);
|
||||
@@ -369,4 +367,18 @@ public class RocketmqSingleCollectImpl extends AbstractCollect implements Dispos
|
||||
builder.addValueRow(valueRowBuilder.build());
|
||||
}
|
||||
}
|
||||
|
||||
void executeConsumerTask(Runnable runnable) {
|
||||
executorService.execute(runnable);
|
||||
}
|
||||
|
||||
private static void require(boolean expression, String message) {
|
||||
if (!expression) {
|
||||
throw new IllegalArgumentException(message);
|
||||
}
|
||||
}
|
||||
|
||||
private static void requireHasText(String value, String message) {
|
||||
require(value != null && !value.trim().isEmpty(), message);
|
||||
}
|
||||
}
|
||||
|
||||
+62
@@ -19,12 +19,20 @@ package org.apache.hertzbeat.collector.collect.rocketmq;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import org.apache.hertzbeat.collector.dispatch.DispatchConstants;
|
||||
import org.apache.hertzbeat.common.concurrent.ManagedExecutor;
|
||||
import org.apache.hertzbeat.common.concurrent.ManagedExecutors;
|
||||
import org.apache.hertzbeat.common.entity.job.Metrics;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.RocketmqProtocol;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -39,6 +47,13 @@ public class RocketmqSingleCollectTest {
|
||||
collect = new RocketmqSingleCollectImpl();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
if (collect != null) {
|
||||
collect.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void preCheck() {
|
||||
// metrics is null
|
||||
@@ -97,4 +112,51 @@ public class RocketmqSingleCollectTest {
|
||||
void supportProtocol() {
|
||||
assertEquals(DispatchConstants.PROTOCOL_ROCKETMQ, collect.supportProtocol());
|
||||
}
|
||||
|
||||
@Test
|
||||
void executeConsumerTaskRunsOnVirtualThread() throws InterruptedException {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicBoolean virtualThread = new AtomicBoolean(false);
|
||||
|
||||
collect.executeConsumerTask(() -> {
|
||||
virtualThread.set(Thread.currentThread().isVirtual());
|
||||
latch.countDown();
|
||||
});
|
||||
|
||||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
assertTrue(virtualThread.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void executeConsumerTaskDiscardsOldestWhenQueueIsFull() throws InterruptedException {
|
||||
ManagedExecutor executor = ManagedExecutors.newDiscardOldestVirtualExecutor("rocketmq-test",
|
||||
"rocketmq-test-", 1, 1, 1, (thread, throwable) -> {
|
||||
});
|
||||
RocketmqSingleCollectImpl testCollect = new RocketmqSingleCollectImpl(executor);
|
||||
CountDownLatch firstStarted = new CountDownLatch(1);
|
||||
CountDownLatch releaseFirst = new CountDownLatch(1);
|
||||
CountDownLatch thirdStarted = new CountDownLatch(1);
|
||||
AtomicBoolean secondExecuted = new AtomicBoolean(false);
|
||||
try {
|
||||
testCollect.executeConsumerTask(() -> {
|
||||
firstStarted.countDown();
|
||||
try {
|
||||
releaseFirst.await(5, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
});
|
||||
assertTrue(firstStarted.await(5, TimeUnit.SECONDS));
|
||||
|
||||
testCollect.executeConsumerTask(() -> secondExecuted.set(true));
|
||||
testCollect.executeConsumerTask(thirdStarted::countDown);
|
||||
|
||||
releaseFirst.countDown();
|
||||
assertTrue(thirdStarted.await(5, TimeUnit.SECONDS));
|
||||
assertFalse(secondExecuted.get());
|
||||
} finally {
|
||||
releaseFirst.countDown();
|
||||
testCollect.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
<artifactId>hertzbeat-common-core</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<description>Framework-agnostic shared runtime models, utilities, and protocol support.</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- Tool dependencies -->
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.common.concurrent;
|
||||
|
||||
/**
|
||||
* Task admission mode for managed executors.
|
||||
*/
|
||||
public enum AdmissionMode {
|
||||
|
||||
/**
|
||||
* Start each task on its own virtual thread without an executor-level concurrency cap.
|
||||
*/
|
||||
UNBOUNDED_VT,
|
||||
|
||||
/**
|
||||
* Reject immediately when the configured concurrency cap has been reached.
|
||||
*/
|
||||
LIMIT_AND_REJECT,
|
||||
|
||||
/**
|
||||
* Block the submitter until a permit is available.
|
||||
*/
|
||||
LIMIT_AND_BLOCK
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.common.concurrent;
|
||||
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
|
||||
/**
|
||||
* Generic background task executor abstraction for runtime components.
|
||||
*/
|
||||
public interface BackgroundTaskExecutor {
|
||||
|
||||
/**
|
||||
* Execute a short-lived task.
|
||||
*
|
||||
* @param runnable task
|
||||
* @throws RejectedExecutionException when execution is rejected
|
||||
*/
|
||||
void execute(Runnable runnable) throws RejectedExecutionException;
|
||||
|
||||
/**
|
||||
* Execute a long-lived background task.
|
||||
*
|
||||
* @param runnable task
|
||||
*/
|
||||
void executeLongRunning(Runnable runnable);
|
||||
|
||||
/**
|
||||
* Release executor resources.
|
||||
*
|
||||
* @throws Exception close exception
|
||||
*/
|
||||
void destroy() throws Exception;
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.common.concurrent;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* Executor with a stable logical name and close semantics.
|
||||
*/
|
||||
public interface ManagedExecutor extends Executor, AutoCloseable {
|
||||
|
||||
/**
|
||||
* Logical executor name for logging and metrics tags.
|
||||
*
|
||||
* @return executor name
|
||||
*/
|
||||
String name();
|
||||
|
||||
@Override
|
||||
void close();
|
||||
}
|
||||
+428
@@ -0,0 +1,428 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.common.concurrent;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.BlockingDeque;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Factory methods for managed executors.
|
||||
*/
|
||||
public final class ManagedExecutors {
|
||||
|
||||
private ManagedExecutors() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap an existing executor service.
|
||||
*
|
||||
* @param name executor name
|
||||
* @param executorService delegate executor service
|
||||
* @return managed executor wrapper
|
||||
*/
|
||||
public static ManagedExecutor wrap(String name, ExecutorService executorService) {
|
||||
return new DefaultManagedExecutor(name, executorService, null, AdmissionMode.UNBOUNDED_VT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a per-task virtual-thread executor with optional admission control.
|
||||
*
|
||||
* @param name executor name
|
||||
* @param threadNamePrefix thread name prefix
|
||||
* @param mode admission mode
|
||||
* @param maxConcurrentTasks max concurrent tasks for limited modes
|
||||
* @param handler uncaught exception handler
|
||||
* @return managed executor
|
||||
*/
|
||||
public static ManagedExecutor newVirtualExecutor(String name, String threadNamePrefix, AdmissionMode mode,
|
||||
int maxConcurrentTasks, Thread.UncaughtExceptionHandler handler) {
|
||||
ThreadFactory threadFactory = Thread.ofVirtual()
|
||||
.name(threadNamePrefix, 0)
|
||||
.uncaughtExceptionHandler(handler)
|
||||
.factory();
|
||||
ExecutorService executorService = Executors.newThreadPerTaskExecutor(threadFactory);
|
||||
return new DefaultManagedExecutor(name, executorService, semaphore(mode, maxConcurrentTasks), mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a platform-thread-per-task executor for a small number of long-running tasks.
|
||||
*
|
||||
* @param name executor name
|
||||
* @param threadNamePrefix thread name prefix
|
||||
* @param handler uncaught exception handler
|
||||
* @return managed executor
|
||||
*/
|
||||
public static ManagedExecutor newPlatformExecutor(String name, String threadNamePrefix,
|
||||
Thread.UncaughtExceptionHandler handler) {
|
||||
ThreadFactory threadFactory = Thread.ofPlatform()
|
||||
.daemon(true)
|
||||
.name(threadNamePrefix, 0)
|
||||
.uncaughtExceptionHandler(handler)
|
||||
.factory();
|
||||
ExecutorService executorService = Executors.newThreadPerTaskExecutor(threadFactory);
|
||||
return new DefaultManagedExecutor(name, executorService, null, AdmissionMode.UNBOUNDED_VT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a queued executor that preserves queue semantics while executing tasks on virtual threads.
|
||||
*
|
||||
* @param name executor name
|
||||
* @param threadNamePrefix virtual-thread name prefix
|
||||
* @param maxConcurrentTasks max concurrent tasks
|
||||
* @param queueCapacity queue capacity, {@code <= 0} means unbounded
|
||||
* @param handler uncaught exception handler
|
||||
* @return managed executor
|
||||
*/
|
||||
public static ManagedExecutor newQueuedVirtualExecutor(String name, String threadNamePrefix, int maxConcurrentTasks,
|
||||
int queueCapacity, Thread.UncaughtExceptionHandler handler) {
|
||||
if (maxConcurrentTasks <= 0) {
|
||||
throw new IllegalArgumentException("maxConcurrentTasks must be greater than zero for queued executors");
|
||||
}
|
||||
return new QueuedVirtualManagedExecutor(name, threadNamePrefix, maxConcurrentTasks, queueCapacity, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a virtual-thread executor that preserves {@link java.util.concurrent.ThreadPoolExecutor}
|
||||
* core/max/queue semantics with discard-oldest overflow handling.
|
||||
*
|
||||
* @param name executor name
|
||||
* @param threadNamePrefix virtual-thread name prefix
|
||||
* @param coreConcurrentTasks core concurrent tasks
|
||||
* @param maxConcurrentTasks max concurrent tasks
|
||||
* @param queueCapacity queue capacity
|
||||
* @param handler uncaught exception handler
|
||||
* @return managed executor
|
||||
*/
|
||||
public static ManagedExecutor newDiscardOldestVirtualExecutor(String name, String threadNamePrefix,
|
||||
int coreConcurrentTasks, int maxConcurrentTasks,
|
||||
int queueCapacity,
|
||||
Thread.UncaughtExceptionHandler handler) {
|
||||
if (coreConcurrentTasks <= 0) {
|
||||
throw new IllegalArgumentException("coreConcurrentTasks must be greater than zero");
|
||||
}
|
||||
if (maxConcurrentTasks < coreConcurrentTasks) {
|
||||
throw new IllegalArgumentException("maxConcurrentTasks must be greater than or equal to coreConcurrentTasks");
|
||||
}
|
||||
if (queueCapacity <= 0) {
|
||||
throw new IllegalArgumentException("queueCapacity must be greater than zero");
|
||||
}
|
||||
return new DiscardOldestVirtualManagedExecutor(name, threadNamePrefix, coreConcurrentTasks,
|
||||
maxConcurrentTasks, queueCapacity, handler);
|
||||
}
|
||||
|
||||
private static Semaphore semaphore(AdmissionMode mode, int maxConcurrentTasks) {
|
||||
if (mode == AdmissionMode.UNBOUNDED_VT) {
|
||||
return null;
|
||||
}
|
||||
if (maxConcurrentTasks <= 0) {
|
||||
throw new IllegalArgumentException("maxConcurrentTasks must be greater than zero for limited executors");
|
||||
}
|
||||
return new Semaphore(maxConcurrentTasks);
|
||||
}
|
||||
|
||||
private static final class DefaultManagedExecutor implements ManagedExecutor {
|
||||
|
||||
private final String name;
|
||||
private final ExecutorService delegate;
|
||||
private final Semaphore permits;
|
||||
private final AdmissionMode admissionMode;
|
||||
|
||||
private DefaultManagedExecutor(String name, ExecutorService delegate, Semaphore permits,
|
||||
AdmissionMode admissionMode) {
|
||||
this.name = Objects.requireNonNull(name, "name");
|
||||
this.delegate = Objects.requireNonNull(delegate, "delegate");
|
||||
this.permits = permits;
|
||||
this.admissionMode = Objects.requireNonNull(admissionMode, "admissionMode");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Runnable command) {
|
||||
Objects.requireNonNull(command, "command");
|
||||
acquirePermit();
|
||||
boolean submitted = false;
|
||||
try {
|
||||
delegate.execute(() -> {
|
||||
try {
|
||||
command.run();
|
||||
} finally {
|
||||
releasePermit();
|
||||
}
|
||||
});
|
||||
submitted = true;
|
||||
} finally {
|
||||
if (!submitted) {
|
||||
releasePermit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
delegate.shutdownNow();
|
||||
}
|
||||
|
||||
private void acquirePermit() {
|
||||
if (permits == null) {
|
||||
return;
|
||||
}
|
||||
switch (admissionMode) {
|
||||
case LIMIT_AND_REJECT:
|
||||
if (!permits.tryAcquire()) {
|
||||
throw new RejectedExecutionException(name + " rejected task because concurrency limit was reached");
|
||||
}
|
||||
break;
|
||||
case LIMIT_AND_BLOCK:
|
||||
try {
|
||||
permits.acquire();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RejectedExecutionException(name + " interrupted while waiting for an execution permit", e);
|
||||
}
|
||||
break;
|
||||
case UNBOUNDED_VT:
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unsupported admission mode: " + admissionMode);
|
||||
}
|
||||
}
|
||||
|
||||
private void releasePermit() {
|
||||
if (permits != null) {
|
||||
permits.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class QueuedVirtualManagedExecutor implements ManagedExecutor {
|
||||
|
||||
private final String name;
|
||||
private final ExecutorService delegate;
|
||||
private final ExecutorService dispatcher;
|
||||
private final BlockingDeque<Runnable> queue;
|
||||
private final Semaphore permits;
|
||||
private final Semaphore permitSignals;
|
||||
private final AtomicBoolean closed;
|
||||
|
||||
private QueuedVirtualManagedExecutor(String name, String threadNamePrefix, int maxConcurrentTasks,
|
||||
int queueCapacity, Thread.UncaughtExceptionHandler handler) {
|
||||
this.name = Objects.requireNonNull(name, "name");
|
||||
ThreadFactory virtualFactory = Thread.ofVirtual()
|
||||
.name(threadNamePrefix, 0)
|
||||
.uncaughtExceptionHandler(handler)
|
||||
.factory();
|
||||
this.delegate = Executors.newThreadPerTaskExecutor(virtualFactory);
|
||||
this.queue = queueCapacity > 0 ? new LinkedBlockingDeque<>(queueCapacity) : new LinkedBlockingDeque<>();
|
||||
this.permits = new Semaphore(maxConcurrentTasks);
|
||||
this.permitSignals = new Semaphore(0);
|
||||
this.closed = new AtomicBoolean(false);
|
||||
ThreadFactory dispatcherFactory = Thread.ofPlatform()
|
||||
.daemon(true)
|
||||
.name(threadNamePrefix + "dispatcher-", 0)
|
||||
.uncaughtExceptionHandler(handler)
|
||||
.factory();
|
||||
this.dispatcher = Executors.newSingleThreadExecutor(dispatcherFactory);
|
||||
this.dispatcher.execute(this::dispatchLoop);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Runnable command) {
|
||||
Objects.requireNonNull(command, "command");
|
||||
if (closed.get()) {
|
||||
throw new RejectedExecutionException(name + " rejected task because executor is closed");
|
||||
}
|
||||
if (!queue.offerLast(command)) {
|
||||
throw new RejectedExecutionException(name + " rejected task because queue capacity was reached");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (!closed.compareAndSet(false, true)) {
|
||||
return;
|
||||
}
|
||||
dispatcher.shutdownNow();
|
||||
delegate.shutdownNow();
|
||||
queue.clear();
|
||||
}
|
||||
|
||||
private void dispatchLoop() {
|
||||
try {
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
Runnable command = queue.takeFirst();
|
||||
if (!permits.tryAcquire()) {
|
||||
queue.putFirst(command);
|
||||
permitSignals.acquire();
|
||||
continue;
|
||||
}
|
||||
submit(command);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
private void submit(Runnable command) {
|
||||
boolean submitted = false;
|
||||
try {
|
||||
delegate.execute(() -> {
|
||||
try {
|
||||
command.run();
|
||||
} finally {
|
||||
permits.release();
|
||||
permitSignals.release();
|
||||
}
|
||||
});
|
||||
submitted = true;
|
||||
} finally {
|
||||
if (!submitted) {
|
||||
permits.release();
|
||||
permitSignals.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class DiscardOldestVirtualManagedExecutor implements ManagedExecutor {
|
||||
|
||||
private final String name;
|
||||
private final ExecutorService delegate;
|
||||
private final int coreConcurrentTasks;
|
||||
private final int maxConcurrentTasks;
|
||||
private final ArrayDeque<Runnable> queue;
|
||||
private final int queueCapacity;
|
||||
private final Object lock;
|
||||
private boolean closed;
|
||||
private int runningTasks;
|
||||
|
||||
private DiscardOldestVirtualManagedExecutor(String name, String threadNamePrefix, int coreConcurrentTasks,
|
||||
int maxConcurrentTasks, int queueCapacity,
|
||||
Thread.UncaughtExceptionHandler handler) {
|
||||
this.name = Objects.requireNonNull(name, "name");
|
||||
ThreadFactory virtualFactory = Thread.ofVirtual()
|
||||
.name(threadNamePrefix, 0)
|
||||
.uncaughtExceptionHandler(handler)
|
||||
.factory();
|
||||
this.delegate = Executors.newThreadPerTaskExecutor(virtualFactory);
|
||||
this.coreConcurrentTasks = coreConcurrentTasks;
|
||||
this.maxConcurrentTasks = maxConcurrentTasks;
|
||||
this.queueCapacity = queueCapacity;
|
||||
this.queue = new ArrayDeque<>(queueCapacity);
|
||||
this.lock = new Object();
|
||||
this.closed = false;
|
||||
this.runningTasks = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Runnable command) {
|
||||
Objects.requireNonNull(command, "command");
|
||||
Runnable taskToStart = null;
|
||||
synchronized (lock) {
|
||||
if (closed) {
|
||||
throw new RejectedExecutionException(name + " rejected task because executor is closed");
|
||||
}
|
||||
if (runningTasks < coreConcurrentTasks) {
|
||||
runningTasks++;
|
||||
taskToStart = command;
|
||||
} else if (queue.size() < queueCapacity) {
|
||||
queue.offerLast(command);
|
||||
return;
|
||||
} else if (runningTasks < maxConcurrentTasks) {
|
||||
runningTasks++;
|
||||
taskToStart = command;
|
||||
} else {
|
||||
queue.pollFirst();
|
||||
queue.offerLast(command);
|
||||
return;
|
||||
}
|
||||
}
|
||||
submit(taskToStart);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
synchronized (lock) {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
closed = true;
|
||||
queue.clear();
|
||||
}
|
||||
delegate.shutdownNow();
|
||||
}
|
||||
|
||||
private void submit(Runnable command) {
|
||||
boolean submitted = false;
|
||||
try {
|
||||
delegate.execute(() -> {
|
||||
try {
|
||||
command.run();
|
||||
} finally {
|
||||
onTaskComplete();
|
||||
}
|
||||
});
|
||||
submitted = true;
|
||||
} finally {
|
||||
if (!submitted) {
|
||||
synchronized (lock) {
|
||||
runningTasks--;
|
||||
}
|
||||
throw new RejectedExecutionException(name + " rejected task because delegate submission failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onTaskComplete() {
|
||||
Runnable nextTask = null;
|
||||
synchronized (lock) {
|
||||
if (closed) {
|
||||
runningTasks--;
|
||||
return;
|
||||
}
|
||||
nextTask = queue.pollFirst();
|
||||
if (nextTask == null) {
|
||||
runningTasks--;
|
||||
return;
|
||||
}
|
||||
}
|
||||
submit(nextTask);
|
||||
}
|
||||
}
|
||||
}
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.common.config;
|
||||
|
||||
import org.apache.hertzbeat.common.concurrent.AdmissionMode;
|
||||
|
||||
/**
|
||||
* Framework-agnostic virtual-thread runtime configuration.
|
||||
*/
|
||||
public record VirtualThreadProperties(
|
||||
boolean enabled,
|
||||
PoolProperties collector,
|
||||
PoolProperties common,
|
||||
PoolProperties manager,
|
||||
AlerterProperties alerter,
|
||||
PoolProperties warehouse,
|
||||
AsyncProperties async) {
|
||||
|
||||
private static final int DEFAULT_COLLECTOR_MAX_CONCURRENT_JOBS = 512;
|
||||
private static final int DEFAULT_MANAGER_MAX_CONCURRENT_JOBS = 10;
|
||||
private static final int DEFAULT_NOTIFY_MAX_CONCURRENT_JOBS = 64;
|
||||
private static final int DEFAULT_PERIODIC_MAX_CONCURRENT_JOBS = 10;
|
||||
private static final int DEFAULT_NOTIFY_MAX_CONCURRENT_PER_CHANNEL = 4;
|
||||
|
||||
public VirtualThreadProperties {
|
||||
collector = normalizePool(collector, PoolProperties.collectorDefaults());
|
||||
common = common == null ? PoolProperties.commonDefaults() : common;
|
||||
manager = normalizePool(manager, PoolProperties.managerDefaults());
|
||||
alerter = alerter == null ? AlerterProperties.defaults() : alerter;
|
||||
warehouse = warehouse == null ? PoolProperties.warehouseDefaults() : warehouse;
|
||||
async = async == null ? AsyncProperties.defaults() : async;
|
||||
}
|
||||
|
||||
public VirtualThreadProperties() {
|
||||
this(true, PoolProperties.collectorDefaults(), PoolProperties.commonDefaults(),
|
||||
PoolProperties.managerDefaults(), AlerterProperties.defaults(),
|
||||
PoolProperties.warehouseDefaults(), AsyncProperties.defaults());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a detached properties instance with runtime defaults.
|
||||
*
|
||||
* @return defaults instance
|
||||
*/
|
||||
public static VirtualThreadProperties defaults() {
|
||||
return new VirtualThreadProperties();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pool-level configuration.
|
||||
*/
|
||||
public record PoolProperties(
|
||||
AdmissionMode mode,
|
||||
int maxConcurrentJobs) {
|
||||
|
||||
public PoolProperties {
|
||||
mode = mode == null ? AdmissionMode.UNBOUNDED_VT : mode;
|
||||
}
|
||||
|
||||
public PoolProperties() {
|
||||
this(AdmissionMode.UNBOUNDED_VT, 0);
|
||||
}
|
||||
|
||||
public static PoolProperties collectorDefaults() {
|
||||
return new PoolProperties(AdmissionMode.LIMIT_AND_REJECT, defaultCollectorConcurrency());
|
||||
}
|
||||
|
||||
public static PoolProperties warehouseDefaults() {
|
||||
return new PoolProperties();
|
||||
}
|
||||
|
||||
public static PoolProperties commonDefaults() {
|
||||
return new PoolProperties();
|
||||
}
|
||||
|
||||
public static PoolProperties managerDefaults() {
|
||||
return new PoolProperties(AdmissionMode.LIMIT_AND_REJECT, DEFAULT_MANAGER_MAX_CONCURRENT_JOBS);
|
||||
}
|
||||
|
||||
public static PoolProperties alerterNotifyDefaults() {
|
||||
return new PoolProperties(AdmissionMode.LIMIT_AND_REJECT, DEFAULT_NOTIFY_MAX_CONCURRENT_JOBS);
|
||||
}
|
||||
|
||||
private static int defaultCollectorConcurrency() {
|
||||
return DEFAULT_COLLECTOR_MAX_CONCURRENT_JOBS;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alerter-specific executor configuration.
|
||||
*/
|
||||
public record AlerterProperties(
|
||||
PoolProperties notifyPool,
|
||||
int periodicMaxConcurrentJobs,
|
||||
QueueProperties logWorker,
|
||||
QueueProperties reduce,
|
||||
QueueProperties windowEvaluator,
|
||||
int notifyMaxConcurrentPerChannel) {
|
||||
|
||||
public AlerterProperties {
|
||||
notifyPool = normalizePool(notifyPool, PoolProperties.alerterNotifyDefaults());
|
||||
periodicMaxConcurrentJobs = periodicMaxConcurrentJobs <= 0
|
||||
? DEFAULT_PERIODIC_MAX_CONCURRENT_JOBS : periodicMaxConcurrentJobs;
|
||||
logWorker = normalizeQueue(logWorker, QueueProperties.logWorkerDefaults());
|
||||
reduce = normalizeQueue(reduce, QueueProperties.reduceDefaults());
|
||||
windowEvaluator = normalizeQueue(windowEvaluator, QueueProperties.windowEvaluatorDefaults());
|
||||
notifyMaxConcurrentPerChannel = notifyMaxConcurrentPerChannel <= 0
|
||||
? DEFAULT_NOTIFY_MAX_CONCURRENT_PER_CHANNEL : notifyMaxConcurrentPerChannel;
|
||||
}
|
||||
|
||||
public AlerterProperties() {
|
||||
this(PoolProperties.alerterNotifyDefaults(), DEFAULT_PERIODIC_MAX_CONCURRENT_JOBS,
|
||||
QueueProperties.logWorkerDefaults(), QueueProperties.reduceDefaults(),
|
||||
QueueProperties.windowEvaluatorDefaults(), DEFAULT_NOTIFY_MAX_CONCURRENT_PER_CHANNEL);
|
||||
}
|
||||
|
||||
public static AlerterProperties defaults() {
|
||||
return new AlerterProperties();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue-preserving executor configuration.
|
||||
*/
|
||||
public record QueueProperties(
|
||||
int maxConcurrentJobs,
|
||||
int queueCapacity) {
|
||||
|
||||
public QueueProperties() {
|
||||
this(0, 0);
|
||||
}
|
||||
|
||||
public static QueueProperties reduceDefaults() {
|
||||
return new QueueProperties(2, 0);
|
||||
}
|
||||
|
||||
public static QueueProperties logWorkerDefaults() {
|
||||
return new QueueProperties(10, 1000);
|
||||
}
|
||||
|
||||
public static QueueProperties windowEvaluatorDefaults() {
|
||||
return new QueueProperties(2, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Async executor configuration.
|
||||
*/
|
||||
public record AsyncProperties(
|
||||
boolean enabled,
|
||||
int concurrencyLimit,
|
||||
boolean rejectWhenLimitReached,
|
||||
long taskTerminationTimeout) {
|
||||
|
||||
public AsyncProperties() {
|
||||
this(true, 256, true, 5000L);
|
||||
}
|
||||
|
||||
public static AsyncProperties defaults() {
|
||||
return new AsyncProperties();
|
||||
}
|
||||
}
|
||||
|
||||
private static PoolProperties normalizePool(PoolProperties configured, PoolProperties defaults) {
|
||||
if (configured == null) {
|
||||
return defaults;
|
||||
}
|
||||
if (configured.mode() != AdmissionMode.UNBOUNDED_VT && configured.maxConcurrentJobs() <= 0) {
|
||||
return new PoolProperties(configured.mode(), defaults.maxConcurrentJobs());
|
||||
}
|
||||
return configured;
|
||||
}
|
||||
|
||||
private static QueueProperties normalizeQueue(QueueProperties configured, QueueProperties defaults) {
|
||||
if (configured == null) {
|
||||
return defaults;
|
||||
}
|
||||
int maxConcurrentJobs = configured.maxConcurrentJobs() <= 0
|
||||
? defaults.maxConcurrentJobs() : configured.maxConcurrentJobs();
|
||||
int queueCapacity = configured.queueCapacity();
|
||||
if (queueCapacity <= 0 && defaults.queueCapacity() > 0) {
|
||||
queueCapacity = defaults.queueCapacity();
|
||||
}
|
||||
return new QueueProperties(maxConcurrentJobs, queueCapacity);
|
||||
}
|
||||
}
|
||||
+1
-5
@@ -21,17 +21,13 @@ import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* SMS configuration
|
||||
* Framework-agnostic SMS runtime configuration.
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "alerter.sms")
|
||||
public class SmsConfig {
|
||||
|
||||
/**
|
||||
+29
-34
@@ -17,7 +17,6 @@
|
||||
|
||||
package org.apache.hertzbeat.common.entity.job;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
@@ -35,13 +34,11 @@ import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import org.apache.hertzbeat.common.entity.message.CollectRep;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* Collect task details
|
||||
* Collect task details.
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@@ -51,15 +48,15 @@ import org.springframework.util.CollectionUtils;
|
||||
public class Job {
|
||||
|
||||
/**
|
||||
* Task Job id
|
||||
* Task Job id.
|
||||
*/
|
||||
private long id;
|
||||
/**
|
||||
* Tenant id
|
||||
* Tenant id.
|
||||
*/
|
||||
private long tenantId = 0;
|
||||
/**
|
||||
* Monitoring Task ID
|
||||
* Monitoring Task ID.
|
||||
*/
|
||||
private long monitorId;
|
||||
/**
|
||||
@@ -68,11 +65,11 @@ public class Job {
|
||||
*/
|
||||
private Map<String, String> metadata;
|
||||
/**
|
||||
* bind labels
|
||||
* bind labels.
|
||||
*/
|
||||
private Map<String, String> labels;
|
||||
/**
|
||||
* bind annotations
|
||||
* bind annotations.
|
||||
*/
|
||||
private Map<String, String> annotations;
|
||||
/**
|
||||
@@ -86,12 +83,12 @@ public class Job {
|
||||
*/
|
||||
private String category;
|
||||
/**
|
||||
* Type of monitoring eg: linux | mysql | jvm
|
||||
* Type of monitoring eg: linux | mysql | jvm.
|
||||
*/
|
||||
private String app;
|
||||
/**
|
||||
* The internationalized name of the monitoring type
|
||||
* PING CONNECT
|
||||
* PING CONNECT.
|
||||
*/
|
||||
private Map<String, String> name;
|
||||
/**
|
||||
@@ -101,55 +98,54 @@ public class Job {
|
||||
*/
|
||||
private Map<String, String> help;
|
||||
/**
|
||||
* The monitor help link
|
||||
* The monitor help link.
|
||||
*/
|
||||
private Map<String, String> helpLink;
|
||||
/**
|
||||
* Task dispatch start timestamp
|
||||
* Task dispatch start timestamp.
|
||||
*/
|
||||
private long timestamp;
|
||||
/**
|
||||
* Default task collection time interval (unit: second) eg: 30,60,600
|
||||
* Default task collection time interval (unit: second) eg: 30,60,600.
|
||||
*/
|
||||
private long defaultInterval = 600L;
|
||||
/**
|
||||
* Refresh time list for one cycle of the job
|
||||
* Refresh time list for one cycle of the job.
|
||||
*/
|
||||
private ConcurrentLinkedDeque<Long> intervals;
|
||||
/**
|
||||
* Whether it is a recurring periodic task true is yes, false is no
|
||||
* Whether it is a recurring periodic task true is yes, false is no.
|
||||
*/
|
||||
private boolean isCyclic = false;
|
||||
/**
|
||||
* monitor input need params
|
||||
* monitor input need params.
|
||||
*/
|
||||
private List<ParamDefine> params;
|
||||
private List<RuntimeParamDefine> params;
|
||||
/**
|
||||
* Metrics configuration eg: cpu memory
|
||||
* eg: cpu memory
|
||||
* Metrics configuration eg: cpu memory.
|
||||
*/
|
||||
private List<Metrics> metrics;
|
||||
/**
|
||||
* Monitoring configuration parameter properties and values eg: username password timeout host
|
||||
* Monitoring configuration parameter properties and values eg: username password timeout host.
|
||||
*/
|
||||
private List<Configmap> configmap;
|
||||
/**
|
||||
* Whether it is a service discovery job, true is yes, false is no
|
||||
* Whether it is a service discovery job, true is yes, false is no.
|
||||
*/
|
||||
private boolean isSd = false;
|
||||
|
||||
/**
|
||||
* Whether to use the Prometheus proxy
|
||||
* Whether to use the Prometheus proxy.
|
||||
*/
|
||||
private boolean prometheusProxyMode = false;
|
||||
|
||||
/**
|
||||
* Scheduling type: interval or cron
|
||||
* Scheduling type: interval or cron.
|
||||
*/
|
||||
private String scheduleType = "interval";
|
||||
|
||||
/**
|
||||
* Cron expression for scheduling, used when scheduleType is "cron"
|
||||
* Cron expression for scheduling, used when scheduleType is "cron".
|
||||
*/
|
||||
private String cronExpression = null;
|
||||
|
||||
@@ -160,7 +156,7 @@ public class Job {
|
||||
private Map<String, Configmap> envConfigmaps;
|
||||
|
||||
/**
|
||||
* collector use - timestamp when the task was scheduled by the time wheel
|
||||
* collector use - timestamp when the task was scheduled by the time wheel.
|
||||
*/
|
||||
@JsonIgnore
|
||||
private transient long dispatchTime;
|
||||
@@ -179,13 +175,13 @@ public class Job {
|
||||
private transient LinkedList<Set<Metrics>> priorMetrics;
|
||||
|
||||
/**
|
||||
* collector use - Temporarily store one-time task metrics response data
|
||||
* collector use - Temporarily store one-time task metrics response data.
|
||||
*/
|
||||
@JsonIgnore
|
||||
private transient List<CollectRep.MetricsData> responseDataTemp;
|
||||
|
||||
/**
|
||||
* collector use - construct to initialize metrics execution view
|
||||
* collector use - construct to initialize metrics execution view.
|
||||
*/
|
||||
public synchronized void constructPriorMetrics() {
|
||||
long now = System.currentTimeMillis();
|
||||
@@ -232,7 +228,7 @@ public class Job {
|
||||
}
|
||||
|
||||
/**
|
||||
* collector use - to get the next set of priority metric group tasks
|
||||
* collector use - to get the next set of priority metric group tasks.
|
||||
*
|
||||
* @param metrics Current Metrics
|
||||
* @param first Is it the first time to get
|
||||
@@ -314,7 +310,7 @@ public class Job {
|
||||
}
|
||||
|
||||
/**
|
||||
* The greatest common divisor
|
||||
* The greatest common divisor.
|
||||
*/
|
||||
public static long gcd(long a, long b) {
|
||||
while (b != 0) {
|
||||
@@ -326,7 +322,7 @@ public class Job {
|
||||
}
|
||||
|
||||
/**
|
||||
* The least common multiple
|
||||
* The least common multiple.
|
||||
*/
|
||||
public static long lcm(List<Long> array) {
|
||||
if (array != null && !array.isEmpty()) {
|
||||
@@ -340,9 +336,8 @@ public class Job {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param metricsIntervals A unique list composed of intervals for all metrics
|
||||
* Generate a list of refresh intervals for metric collection
|
||||
* Generate a list of refresh intervals for metric collection.
|
||||
*/
|
||||
public synchronized void generateMetricsIntervals(List<Long> metricsIntervals) {
|
||||
// 1. To find the least common multiple (LCM) of all metric refresh intervals
|
||||
@@ -368,7 +363,7 @@ public class Job {
|
||||
}
|
||||
|
||||
public synchronized long getInterval() {
|
||||
if (!CollectionUtils.isEmpty(this.intervals)) {
|
||||
if (this.intervals != null && !this.intervals.isEmpty()) {
|
||||
Long interval = this.intervals.removeFirst();
|
||||
if (interval != null) {
|
||||
this.intervals.addLast(interval);
|
||||
+41
-19
@@ -17,38 +17,60 @@
|
||||
|
||||
package org.apache.hertzbeat.common.entity.job;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Monitoring configuration parameter properties and values
|
||||
* During the process, you need to replace the content with the identifier ^_^key^_^
|
||||
* in the protocol configuration parameter with the real value in the configuration parameter
|
||||
* Framework-agnostic parameter definition used by runtime templates and jobs.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
public class Configmap implements Serializable {
|
||||
public class RuntimeParamDefine {
|
||||
|
||||
private String app;
|
||||
|
||||
private Map<String, String> name;
|
||||
|
||||
private String field;
|
||||
|
||||
private String type;
|
||||
|
||||
private boolean required = false;
|
||||
|
||||
private String defaultValue;
|
||||
|
||||
private String placeholder;
|
||||
|
||||
private String range;
|
||||
|
||||
private Short limit;
|
||||
|
||||
private List<Option> options;
|
||||
|
||||
private String keyAlias;
|
||||
|
||||
private String valueAlias;
|
||||
|
||||
private boolean hide = false;
|
||||
|
||||
private Map<String, List<Object>> depend;
|
||||
|
||||
/**
|
||||
* Parameter key, replace the content with the identifier ^^_key_^^ in the protocol
|
||||
* configuration parameter with the real value in the configuration parameter
|
||||
* Runtime option definition.
|
||||
*/
|
||||
private String key;
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static final class Option {
|
||||
|
||||
/**
|
||||
* parameter value
|
||||
*/
|
||||
private Object value;
|
||||
private String label;
|
||||
|
||||
/**
|
||||
* Parameter type
|
||||
* 0: number 1: string 2: encrypted string 3: json string mapped by map
|
||||
* number,string,secret
|
||||
*/
|
||||
private byte type = 1;
|
||||
private String value;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -81,7 +81,7 @@ public class SshTunnel implements CommonRequestProtocol, Protocol {
|
||||
@Override
|
||||
public boolean isInvalid() {
|
||||
|
||||
// todo: add
|
||||
return true;
|
||||
// todo add
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-6
@@ -17,23 +17,20 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
// Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
// source: cluster_msg.proto
|
||||
|
||||
package org.apache.hertzbeat.common.entity.plugin;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import org.apache.hertzbeat.common.entity.job.RuntimeParamDefine;
|
||||
|
||||
/**
|
||||
* The configuration file of the plugin, including parameters and other information
|
||||
* The configuration file of the plugin, including parameters and other information.
|
||||
*/
|
||||
@Data
|
||||
public class PluginConfig {
|
||||
|
||||
private List<ParamDefine> params;
|
||||
private List<RuntimeParamDefine> params;
|
||||
|
||||
public PluginConfig() {
|
||||
this.params = new ArrayList<>();
|
||||
+1
-1
@@ -25,4 +25,4 @@ public class AlertExpressionException extends RuntimeException {
|
||||
public AlertExpressionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -36,4 +36,4 @@ public class CommonDataQueueUnknownException extends RuntimeException {
|
||||
public CommonDataQueueUnknownException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -26,4 +26,4 @@ public class ExpressionVisitorException extends RuntimeException {
|
||||
public ExpressionVisitorException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.common.concurrent;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Tests for {@link ManagedExecutors}.
|
||||
*/
|
||||
class ManagedExecutorsTest {
|
||||
|
||||
@Test
|
||||
void shouldRunTaskOnVirtualThread() throws Exception {
|
||||
ManagedExecutor executor = ManagedExecutors.newVirtualExecutor("test", "test-vt-",
|
||||
AdmissionMode.UNBOUNDED_VT, 0, (thread, throwable) -> {
|
||||
});
|
||||
try {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicBoolean virtualThread = new AtomicBoolean(false);
|
||||
|
||||
executor.execute(() -> {
|
||||
virtualThread.set(Thread.currentThread().isVirtual());
|
||||
latch.countDown();
|
||||
});
|
||||
|
||||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
assertTrue(virtualThread.get());
|
||||
} finally {
|
||||
executor.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectTaskWhenAdmissionLimitReached() throws Exception {
|
||||
ManagedExecutor executor = ManagedExecutors.newVirtualExecutor("limited", "limited-vt-",
|
||||
AdmissionMode.LIMIT_AND_REJECT, 1, (thread, throwable) -> {
|
||||
});
|
||||
CountDownLatch started = new CountDownLatch(1);
|
||||
CountDownLatch release = new CountDownLatch(1);
|
||||
try {
|
||||
executor.execute(() -> {
|
||||
started.countDown();
|
||||
try {
|
||||
release.await(5, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
});
|
||||
assertTrue(started.await(5, TimeUnit.SECONDS));
|
||||
assertThrows(RejectedExecutionException.class, () -> executor.execute(() -> {
|
||||
}));
|
||||
} finally {
|
||||
release.countDown();
|
||||
executor.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldQueueTasksWhileKeepingVirtualThreadExecution() throws Exception {
|
||||
ManagedExecutor executor = ManagedExecutors.newQueuedVirtualExecutor("queued", "queued-vt-",
|
||||
1, 0, (thread, throwable) -> {
|
||||
});
|
||||
CountDownLatch firstStarted = new CountDownLatch(1);
|
||||
CountDownLatch releaseFirst = new CountDownLatch(1);
|
||||
CountDownLatch secondStarted = new CountDownLatch(1);
|
||||
AtomicBoolean firstVirtual = new AtomicBoolean(false);
|
||||
AtomicBoolean secondVirtual = new AtomicBoolean(false);
|
||||
try {
|
||||
executor.execute(() -> {
|
||||
firstVirtual.set(Thread.currentThread().isVirtual());
|
||||
firstStarted.countDown();
|
||||
try {
|
||||
releaseFirst.await(5, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
});
|
||||
assertTrue(firstStarted.await(5, TimeUnit.SECONDS));
|
||||
|
||||
executor.execute(() -> {
|
||||
secondVirtual.set(Thread.currentThread().isVirtual());
|
||||
secondStarted.countDown();
|
||||
});
|
||||
|
||||
assertFalse(secondStarted.await(200, TimeUnit.MILLISECONDS));
|
||||
releaseFirst.countDown();
|
||||
assertTrue(secondStarted.await(5, TimeUnit.SECONDS));
|
||||
assertTrue(firstVirtual.get());
|
||||
assertTrue(secondVirtual.get());
|
||||
} finally {
|
||||
releaseFirst.countDown();
|
||||
executor.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectTaskWhenQueuedExecutorCapacityReached() throws Exception {
|
||||
ManagedExecutor executor = ManagedExecutors.newQueuedVirtualExecutor("queued", "queued-vt-",
|
||||
1, 1, (thread, throwable) -> {
|
||||
});
|
||||
CountDownLatch firstStarted = new CountDownLatch(1);
|
||||
CountDownLatch releaseFirst = new CountDownLatch(1);
|
||||
CountDownLatch secondStarted = new CountDownLatch(1);
|
||||
try {
|
||||
executor.execute(() -> {
|
||||
firstStarted.countDown();
|
||||
try {
|
||||
releaseFirst.await(5, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
});
|
||||
assertTrue(firstStarted.await(5, TimeUnit.SECONDS));
|
||||
|
||||
executor.execute(secondStarted::countDown);
|
||||
assertFalse(secondStarted.await(200, TimeUnit.MILLISECONDS));
|
||||
assertThrows(RejectedExecutionException.class, () -> executor.execute(() -> {
|
||||
}));
|
||||
} finally {
|
||||
releaseFirst.countDown();
|
||||
executor.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDiscardOldestTaskWhenDiscardOldestExecutorQueueIsFull() throws Exception {
|
||||
ManagedExecutor executor = ManagedExecutors.newDiscardOldestVirtualExecutor("discard-oldest",
|
||||
"discard-oldest-vt-", 1, 1, 1, (thread, throwable) -> {
|
||||
});
|
||||
CountDownLatch firstStarted = new CountDownLatch(1);
|
||||
CountDownLatch releaseFirst = new CountDownLatch(1);
|
||||
CountDownLatch thirdStarted = new CountDownLatch(1);
|
||||
AtomicBoolean secondExecuted = new AtomicBoolean(false);
|
||||
AtomicBoolean thirdVirtual = new AtomicBoolean(false);
|
||||
try {
|
||||
executor.execute(() -> {
|
||||
firstStarted.countDown();
|
||||
try {
|
||||
releaseFirst.await(5, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
});
|
||||
assertTrue(firstStarted.await(5, TimeUnit.SECONDS));
|
||||
|
||||
executor.execute(() -> secondExecuted.set(true));
|
||||
executor.execute(() -> {
|
||||
thirdVirtual.set(Thread.currentThread().isVirtual());
|
||||
thirdStarted.countDown();
|
||||
});
|
||||
|
||||
releaseFirst.countDown();
|
||||
assertTrue(thirdStarted.await(5, TimeUnit.SECONDS));
|
||||
assertFalse(secondExecuted.get());
|
||||
assertTrue(thirdVirtual.get());
|
||||
} finally {
|
||||
releaseFirst.countDown();
|
||||
executor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
<artifactId>hertzbeat-common-spring</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<description>Spring Boot, configuration, validation, and JPA integration built on common-core.</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- hertzbeat-common-core dependency -->
|
||||
|
||||
+7
-1
@@ -21,6 +21,7 @@ import org.apache.hertzbeat.common.constants.ConfigConstants;
|
||||
import org.apache.hertzbeat.common.constants.SignConstants;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
|
||||
/**
|
||||
@@ -31,6 +32,11 @@ import org.springframework.context.annotation.ComponentScan;
|
||||
@ComponentScan(basePackages = ConfigConstants.PkgConstant.PKG
|
||||
+ SignConstants.DOT
|
||||
+ ConfigConstants.FunctionModuleConstants.COMMON)
|
||||
@EnableConfigurationProperties(CommonProperties.class)
|
||||
@EnableConfigurationProperties({CommonProperties.class, VirtualThreadPropertiesBinding.class, SmsConfigBinding.class})
|
||||
public class CommonConfig {
|
||||
|
||||
@Bean
|
||||
public VirtualThreadProperties virtualThreadProperties(VirtualThreadPropertiesBinding binding) {
|
||||
return binding.toRuntimeProperties();
|
||||
}
|
||||
}
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.common.config;
|
||||
|
||||
import org.apache.hertzbeat.common.entity.dto.sms.SmsConfig;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Spring Boot binding adapter for {@link SmsConfig}.
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "alerter.sms")
|
||||
public class SmsConfigBinding extends SmsConfig {
|
||||
}
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.common.config;
|
||||
|
||||
import org.apache.hertzbeat.common.concurrent.AdmissionMode;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.bind.ConstructorBinding;
|
||||
import org.springframework.boot.context.properties.bind.DefaultValue;
|
||||
import org.springframework.boot.context.properties.bind.Name;
|
||||
|
||||
/**
|
||||
* Spring Boot binding adapter for {@link VirtualThreadProperties}.
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "hertzbeat.vthreads")
|
||||
public record VirtualThreadPropertiesBinding(
|
||||
@DefaultValue("true") boolean enabled,
|
||||
PoolProperties collector,
|
||||
PoolProperties common,
|
||||
PoolProperties manager,
|
||||
AlerterProperties alerter,
|
||||
PoolProperties warehouse,
|
||||
AsyncProperties async) {
|
||||
|
||||
@ConstructorBinding
|
||||
public VirtualThreadPropertiesBinding {
|
||||
VirtualThreadProperties runtimeProperties = new VirtualThreadProperties(enabled,
|
||||
toRuntimePool(collector),
|
||||
toRuntimePool(common),
|
||||
toRuntimePool(manager),
|
||||
toRuntimeAlerter(alerter),
|
||||
toRuntimePool(warehouse),
|
||||
toRuntimeAsync(async));
|
||||
enabled = runtimeProperties.enabled();
|
||||
collector = PoolProperties.fromRuntime(runtimeProperties.collector());
|
||||
common = PoolProperties.fromRuntime(runtimeProperties.common());
|
||||
manager = PoolProperties.fromRuntime(runtimeProperties.manager());
|
||||
alerter = AlerterProperties.fromRuntime(runtimeProperties.alerter());
|
||||
warehouse = PoolProperties.fromRuntime(runtimeProperties.warehouse());
|
||||
async = AsyncProperties.fromRuntime(runtimeProperties.async());
|
||||
}
|
||||
|
||||
public VirtualThreadProperties toRuntimeProperties() {
|
||||
return new VirtualThreadProperties(enabled,
|
||||
toRuntimePool(collector),
|
||||
toRuntimePool(common),
|
||||
toRuntimePool(manager),
|
||||
toRuntimeAlerter(alerter),
|
||||
toRuntimePool(warehouse),
|
||||
toRuntimeAsync(async));
|
||||
}
|
||||
|
||||
/**
|
||||
* Pool-level binding model.
|
||||
*/
|
||||
public record PoolProperties(
|
||||
@DefaultValue("UNBOUNDED_VT") AdmissionMode mode,
|
||||
@DefaultValue("0") int maxConcurrentJobs) {
|
||||
|
||||
@ConstructorBinding
|
||||
public PoolProperties {
|
||||
mode = mode == null ? AdmissionMode.UNBOUNDED_VT : mode;
|
||||
}
|
||||
|
||||
static PoolProperties fromRuntime(VirtualThreadProperties.PoolProperties runtimeProperties) {
|
||||
return runtimeProperties == null ? null
|
||||
: new PoolProperties(runtimeProperties.mode(), runtimeProperties.maxConcurrentJobs());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alerter-specific binding model.
|
||||
*/
|
||||
public record AlerterProperties(
|
||||
@Name("notify") PoolProperties notifyPool,
|
||||
@DefaultValue("10") int periodicMaxConcurrentJobs,
|
||||
QueueProperties logWorker,
|
||||
QueueProperties reduce,
|
||||
QueueProperties windowEvaluator,
|
||||
@DefaultValue("4") int notifyMaxConcurrentPerChannel) {
|
||||
|
||||
@ConstructorBinding
|
||||
public AlerterProperties {
|
||||
}
|
||||
|
||||
static AlerterProperties fromRuntime(VirtualThreadProperties.AlerterProperties runtimeProperties) {
|
||||
return runtimeProperties == null ? null
|
||||
: new AlerterProperties(
|
||||
PoolProperties.fromRuntime(runtimeProperties.notifyPool()),
|
||||
runtimeProperties.periodicMaxConcurrentJobs(),
|
||||
QueueProperties.fromRuntime(runtimeProperties.logWorker()),
|
||||
QueueProperties.fromRuntime(runtimeProperties.reduce()),
|
||||
QueueProperties.fromRuntime(runtimeProperties.windowEvaluator()),
|
||||
runtimeProperties.notifyMaxConcurrentPerChannel());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue-preserving binding model.
|
||||
*/
|
||||
public record QueueProperties(
|
||||
@DefaultValue("0") int maxConcurrentJobs,
|
||||
@DefaultValue("0") int queueCapacity) {
|
||||
|
||||
@ConstructorBinding
|
||||
public QueueProperties {
|
||||
}
|
||||
|
||||
static QueueProperties fromRuntime(VirtualThreadProperties.QueueProperties runtimeProperties) {
|
||||
return runtimeProperties == null ? null
|
||||
: new QueueProperties(runtimeProperties.maxConcurrentJobs(), runtimeProperties.queueCapacity());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Async executor binding model.
|
||||
*/
|
||||
public record AsyncProperties(
|
||||
@DefaultValue("true") boolean enabled,
|
||||
@DefaultValue("256") int concurrencyLimit,
|
||||
@DefaultValue("true") boolean rejectWhenLimitReached,
|
||||
@DefaultValue("5000") long taskTerminationTimeout) {
|
||||
|
||||
@ConstructorBinding
|
||||
public AsyncProperties {
|
||||
}
|
||||
|
||||
static AsyncProperties fromRuntime(VirtualThreadProperties.AsyncProperties runtimeProperties) {
|
||||
return runtimeProperties == null ? null
|
||||
: new AsyncProperties(runtimeProperties.enabled(), runtimeProperties.concurrencyLimit(),
|
||||
runtimeProperties.rejectWhenLimitReached(), runtimeProperties.taskTerminationTimeout());
|
||||
}
|
||||
}
|
||||
|
||||
private static VirtualThreadProperties.PoolProperties toRuntimePool(PoolProperties poolProperties) {
|
||||
return poolProperties == null ? null
|
||||
: new VirtualThreadProperties.PoolProperties(poolProperties.mode(), poolProperties.maxConcurrentJobs());
|
||||
}
|
||||
|
||||
private static VirtualThreadProperties.AlerterProperties toRuntimeAlerter(AlerterProperties alerterProperties) {
|
||||
return alerterProperties == null ? null
|
||||
: new VirtualThreadProperties.AlerterProperties(
|
||||
toRuntimePool(alerterProperties.notifyPool()),
|
||||
alerterProperties.periodicMaxConcurrentJobs(),
|
||||
toRuntimeQueue(alerterProperties.logWorker()),
|
||||
toRuntimeQueue(alerterProperties.reduce()),
|
||||
toRuntimeQueue(alerterProperties.windowEvaluator()),
|
||||
alerterProperties.notifyMaxConcurrentPerChannel());
|
||||
}
|
||||
|
||||
private static VirtualThreadProperties.QueueProperties toRuntimeQueue(QueueProperties queueProperties) {
|
||||
return queueProperties == null ? null
|
||||
: new VirtualThreadProperties.QueueProperties(queueProperties.maxConcurrentJobs(),
|
||||
queueProperties.queueCapacity());
|
||||
}
|
||||
|
||||
private static VirtualThreadProperties.AsyncProperties toRuntimeAsync(AsyncProperties asyncProperties) {
|
||||
return asyncProperties == null ? null
|
||||
: new VirtualThreadProperties.AsyncProperties(asyncProperties.enabled(),
|
||||
asyncProperties.concurrencyLimit(), asyncProperties.rejectWhenLimitReached(),
|
||||
asyncProperties.taskTerminationTimeout());
|
||||
}
|
||||
}
|
||||
-87
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.common.entity.job;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.CommonRequestProtocol;
|
||||
import org.apache.hertzbeat.common.entity.job.protocol.Protocol;
|
||||
|
||||
/**
|
||||
* ssh tunnel
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class SshTunnel implements CommonRequestProtocol, Protocol {
|
||||
|
||||
/**
|
||||
* enable ssh tunnel
|
||||
*/
|
||||
private String enable = "false";
|
||||
|
||||
/**
|
||||
* IP ADDRESS OR DOMAIN NAME OF THE PEER HOST
|
||||
*/
|
||||
private String host;
|
||||
|
||||
/**
|
||||
* Peer host port
|
||||
*/
|
||||
private String port = "22";
|
||||
|
||||
/**
|
||||
* TIME OUT PERIOD
|
||||
*/
|
||||
private String timeout = "6000";
|
||||
|
||||
/**
|
||||
* UserName
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* Password (optional)
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* Private key (optional)
|
||||
*/
|
||||
private String privateKey;
|
||||
|
||||
/**
|
||||
* private key passphrase (optional)
|
||||
*/
|
||||
private String privateKeyPassphrase;
|
||||
|
||||
/**
|
||||
* share connection session
|
||||
*/
|
||||
private String shareConnection = "true";
|
||||
|
||||
@Override
|
||||
public boolean isInvalid() {
|
||||
|
||||
// todo add
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+58
-11
@@ -18,13 +18,19 @@
|
||||
package org.apache.hertzbeat.common.support;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.common.concurrent.BackgroundTaskExecutor;
|
||||
import org.apache.hertzbeat.common.concurrent.ManagedExecutor;
|
||||
import org.apache.hertzbeat.common.concurrent.ManagedExecutors;
|
||||
import org.apache.hertzbeat.common.config.VirtualThreadProperties;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
@@ -32,24 +38,55 @@ import org.springframework.stereotype.Component;
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class CommonThreadPool implements DisposableBean {
|
||||
public class CommonThreadPool implements BackgroundTaskExecutor, DisposableBean {
|
||||
|
||||
private ThreadPoolExecutor workerExecutor;
|
||||
private final ManagedExecutor workerExecutor;
|
||||
|
||||
private final ManagedExecutor longRunningExecutor;
|
||||
|
||||
public CommonThreadPool() {
|
||||
initWorkExecutor();
|
||||
this(VirtualThreadProperties.defaults());
|
||||
}
|
||||
|
||||
private void initWorkExecutor() {
|
||||
ThreadFactory threadFactory = new ThreadFactoryBuilder()
|
||||
.setUncaughtExceptionHandler((thread, throwable) -> {
|
||||
log.error("common executor has uncaughtException.");
|
||||
@Autowired
|
||||
public CommonThreadPool(VirtualThreadProperties virtualThreadProperties) {
|
||||
VirtualThreadProperties properties =
|
||||
virtualThreadProperties == null ? VirtualThreadProperties.defaults() : virtualThreadProperties;
|
||||
this.workerExecutor = createWorkerExecutor(properties);
|
||||
this.longRunningExecutor = createLongRunningExecutor(properties, workerExecutor);
|
||||
}
|
||||
|
||||
private ManagedExecutor createWorkerExecutor(VirtualThreadProperties properties) {
|
||||
Thread.UncaughtExceptionHandler handler = (thread, throwable) -> {
|
||||
log.error("common executor has uncaughtException.");
|
||||
log.error(throwable.getMessage(), throwable);
|
||||
};
|
||||
if (properties.enabled()) {
|
||||
VirtualThreadProperties.PoolProperties poolProperties = properties.common();
|
||||
return ManagedExecutors.newVirtualExecutor("common-worker", "common-worker-",
|
||||
poolProperties.mode(), poolProperties.maxConcurrentJobs(), handler);
|
||||
}
|
||||
return ManagedExecutors.wrap("common-worker", createLegacyExecutor(handler));
|
||||
}
|
||||
|
||||
private ManagedExecutor createLongRunningExecutor(VirtualThreadProperties properties, ManagedExecutor fallback) {
|
||||
if (!properties.enabled()) {
|
||||
return fallback;
|
||||
}
|
||||
return ManagedExecutors.newPlatformExecutor("common-long-running", "common-long-running-",
|
||||
(thread, throwable) -> {
|
||||
log.error("common longRunningExecutor has uncaughtException.");
|
||||
log.error(throwable.getMessage(), throwable);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
private ExecutorService createLegacyExecutor(Thread.UncaughtExceptionHandler handler) {
|
||||
ThreadFactory threadFactory = new ThreadFactoryBuilder()
|
||||
.setUncaughtExceptionHandler(handler)
|
||||
.setDaemon(true)
|
||||
.setNameFormat("common-worker-%d")
|
||||
.build();
|
||||
workerExecutor = new ThreadPoolExecutor(1,
|
||||
return new ThreadPoolExecutor(1,
|
||||
Integer.MAX_VALUE,
|
||||
10,
|
||||
TimeUnit.SECONDS,
|
||||
@@ -67,10 +104,20 @@ public class CommonThreadPool implements DisposableBean {
|
||||
workerExecutor.execute(runnable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a long-lived task outside of the short-task execution lane.
|
||||
*
|
||||
* @param runnable task
|
||||
*/
|
||||
public void executeLongRunning(Runnable runnable) {
|
||||
longRunningExecutor.execute(runnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
if (workerExecutor != null) {
|
||||
workerExecutor.shutdownNow();
|
||||
workerExecutor.close();
|
||||
if (longRunningExecutor != workerExecutor) {
|
||||
longRunningExecutor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-169
@@ -1,169 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.common.util;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.regex.Pattern;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.common.constants.NetworkConstants;
|
||||
import org.apache.hertzbeat.common.constants.SignConstants;
|
||||
import org.apache.http.conn.util.InetAddressUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* ipv4 ipv6 domain util.
|
||||
*/
|
||||
@Slf4j
|
||||
public final class IpDomainUtil {
|
||||
|
||||
private static final Pattern DOMAIN_PATTERN =
|
||||
Pattern.compile("^[-\\w]+(\\.[-\\w]+)*$");
|
||||
|
||||
private static final String LOCALHOST = "localhost";
|
||||
|
||||
/**
|
||||
* HTTP header schema.
|
||||
*/
|
||||
private static final Pattern DOMAIN_SCHEMA = Pattern.compile("^([hH][tT]{2}[pP]://|[hH][tT]{2}[pP][sS]://){1}[^\\s]*");
|
||||
|
||||
private IpDomainUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* whether it is ip or domain.
|
||||
* @param ipDomain ip domain string
|
||||
* @return true-yes false-no
|
||||
*/
|
||||
public static boolean validateIpDomain(String ipDomain) {
|
||||
if (ipDomain == null || !StringUtils.hasText(ipDomain)) {
|
||||
return false;
|
||||
}
|
||||
ipDomain = ipDomain.trim();
|
||||
if (LOCALHOST.equalsIgnoreCase(ipDomain)) {
|
||||
return true;
|
||||
}
|
||||
if (InetAddressUtils.isIPv4Address(ipDomain)) {
|
||||
return true;
|
||||
}
|
||||
if (InetAddressUtils.isIPv6Address(ipDomain)) {
|
||||
return true;
|
||||
}
|
||||
return DOMAIN_PATTERN.matcher(ipDomain).matches();
|
||||
}
|
||||
|
||||
/**
|
||||
* if domain or ip has http / https schema.
|
||||
* @param domainIp host
|
||||
* @return true or false
|
||||
*/
|
||||
public static boolean isHasSchema(String domainIp) {
|
||||
if (domainIp == null || !StringUtils.hasText(domainIp)) {
|
||||
return false;
|
||||
}
|
||||
return DOMAIN_SCHEMA.matcher(domainIp).matches();
|
||||
}
|
||||
|
||||
/**
|
||||
* if instance has the port with mark
|
||||
* @param instance instance ip:port
|
||||
* @return true if has
|
||||
*/
|
||||
public static boolean isHasPortWithMark(String instance) {
|
||||
if (instance == null || !StringUtils.hasText(instance)) {
|
||||
return false;
|
||||
}
|
||||
String[] parts = instance.split(SignConstants.DOUBLE_MARK);
|
||||
if (parts.length >= 2) {
|
||||
String port = parts[parts.length - 1];
|
||||
return CommonUtil.isNumeric(port);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* get localhost IP.
|
||||
* @return ip
|
||||
*/
|
||||
public static String getLocalhostIp() {
|
||||
try {
|
||||
Enumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces();
|
||||
InetAddress ip;
|
||||
while (allNetInterfaces.hasMoreElements()) {
|
||||
NetworkInterface netInterface = allNetInterfaces.nextElement();
|
||||
if (!netInterface.isLoopback() && !netInterface.isVirtual() && netInterface.isUp()) {
|
||||
Enumeration<InetAddress> addresses = netInterface.getInetAddresses();
|
||||
while (addresses.hasMoreElements()) {
|
||||
ip = addresses.nextElement();
|
||||
if (ip instanceof Inet4Address) {
|
||||
return ip.getHostAddress();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn(e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* check IP address type.
|
||||
* @param ipDomain ip domain
|
||||
* @return IP address type
|
||||
*/
|
||||
public static String checkIpAddressType(String ipDomain){
|
||||
if (StringUtils.hasText(ipDomain) && InetAddressUtils.isIPv6Address(ipDomain)) {
|
||||
return NetworkConstants.IPV6;
|
||||
}
|
||||
return NetworkConstants.IPV4;
|
||||
}
|
||||
|
||||
/**
|
||||
* get current local host name.
|
||||
* @return hostname
|
||||
*/
|
||||
public static String getCurrentHostName() {
|
||||
try {
|
||||
InetAddress inetAddress = InetAddress.getLocalHost();
|
||||
return inetAddress.getHostName();
|
||||
} catch (UnknownHostException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check port is valid.
|
||||
* @return true if valid
|
||||
*/
|
||||
public static boolean validPort(String portStr) {
|
||||
if (portStr == null || portStr.trim().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
int port = Integer.parseInt(portStr);
|
||||
return port >= 0 && port <= 65535;
|
||||
} catch (NumberFormatException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.common.config;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.apache.hertzbeat.common.entity.dto.sms.SmsConfig;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
|
||||
class SmsConfigBindingTest {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(BindingConfig.class);
|
||||
|
||||
@Test
|
||||
void bindsRuntimeSmsConfigWithoutSpringAnnotationsOnModel() {
|
||||
contextRunner.withPropertyValues(
|
||||
"alerter.sms.enable=true",
|
||||
"alerter.sms.type=smslocal",
|
||||
"alerter.sms.smslocal.api-key=test-key")
|
||||
.run(context -> {
|
||||
SmsConfig smsConfig = context.getBean(SmsConfig.class);
|
||||
assertTrue(smsConfig.isEnable());
|
||||
assertEquals("smslocal", smsConfig.getType());
|
||||
assertEquals("test-key", smsConfig.getSmslocal().getApiKey());
|
||||
});
|
||||
}
|
||||
|
||||
@EnableConfigurationProperties(SmsConfigBinding.class)
|
||||
static class BindingConfig {
|
||||
}
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.common.config;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.apache.hertzbeat.common.concurrent.AdmissionMode;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
class VirtualThreadPropertiesTest {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(BindingConfig.class);
|
||||
|
||||
@Test
|
||||
void defaultsRemainSafeWithoutExternalConfiguration() {
|
||||
VirtualThreadProperties properties = VirtualThreadProperties.defaults();
|
||||
|
||||
assertTrue(properties.enabled());
|
||||
|
||||
assertEquals(AdmissionMode.LIMIT_AND_REJECT, properties.collector().mode());
|
||||
assertEquals(512, properties.collector().maxConcurrentJobs());
|
||||
|
||||
assertEquals(AdmissionMode.UNBOUNDED_VT, properties.common().mode());
|
||||
assertEquals(AdmissionMode.LIMIT_AND_REJECT, properties.manager().mode());
|
||||
assertEquals(10, properties.manager().maxConcurrentJobs());
|
||||
|
||||
assertEquals(AdmissionMode.LIMIT_AND_REJECT, properties.alerter().notifyPool().mode());
|
||||
assertEquals(64, properties.alerter().notifyPool().maxConcurrentJobs());
|
||||
assertEquals(10, properties.alerter().periodicMaxConcurrentJobs());
|
||||
assertEquals(10, properties.alerter().logWorker().maxConcurrentJobs());
|
||||
assertEquals(1000, properties.alerter().logWorker().queueCapacity());
|
||||
assertEquals(2, properties.alerter().reduce().maxConcurrentJobs());
|
||||
assertEquals(0, properties.alerter().reduce().queueCapacity());
|
||||
assertEquals(2, properties.alerter().windowEvaluator().maxConcurrentJobs());
|
||||
assertEquals(0, properties.alerter().windowEvaluator().queueCapacity());
|
||||
assertEquals(4, properties.alerter().notifyMaxConcurrentPerChannel());
|
||||
|
||||
assertEquals(AdmissionMode.UNBOUNDED_VT, properties.warehouse().mode());
|
||||
|
||||
assertTrue(properties.async().enabled());
|
||||
assertEquals(256, properties.async().concurrencyLimit());
|
||||
assertTrue(properties.async().rejectWhenLimitReached());
|
||||
assertEquals(5000L, properties.async().taskTerminationTimeout());
|
||||
}
|
||||
|
||||
@Test
|
||||
void collectorModeOnlyBindingRetainsDefaultConcurrency() {
|
||||
contextRunner.withPropertyValues("hertzbeat.vthreads.collector.mode=LIMIT_AND_REJECT")
|
||||
.run(context -> {
|
||||
VirtualThreadProperties properties = context.getBean(VirtualThreadProperties.class);
|
||||
assertEquals(AdmissionMode.LIMIT_AND_REJECT, properties.collector().mode());
|
||||
assertEquals(512, properties.collector().maxConcurrentJobs());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void collectorModeOverrideUsesConfiguredMode() {
|
||||
contextRunner.withPropertyValues("hertzbeat.vthreads.collector.mode=LIMIT_AND_BLOCK")
|
||||
.run(context -> {
|
||||
VirtualThreadProperties properties = context.getBean(VirtualThreadProperties.class);
|
||||
assertEquals(AdmissionMode.LIMIT_AND_BLOCK, properties.collector().mode());
|
||||
assertEquals(512, properties.collector().maxConcurrentJobs());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void notifyModeOnlyBindingRetainsDefaultConcurrency() {
|
||||
contextRunner.withPropertyValues("hertzbeat.vthreads.alerter.notify.mode=LIMIT_AND_BLOCK")
|
||||
.run(context -> {
|
||||
VirtualThreadProperties properties = context.getBean(VirtualThreadProperties.class);
|
||||
assertEquals(AdmissionMode.LIMIT_AND_BLOCK, properties.alerter().notifyPool().mode());
|
||||
assertEquals(64, properties.alerter().notifyPool().maxConcurrentJobs());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void logWorkerQueueOnlyBindingRetainsDefaultConcurrency() {
|
||||
contextRunner.withPropertyValues("hertzbeat.vthreads.alerter.log-worker.queue-capacity=32")
|
||||
.run(context -> {
|
||||
VirtualThreadProperties properties = context.getBean(VirtualThreadProperties.class);
|
||||
assertEquals(10, properties.alerter().logWorker().maxConcurrentJobs());
|
||||
assertEquals(32, properties.alerter().logWorker().queueCapacity());
|
||||
});
|
||||
}
|
||||
|
||||
@EnableConfigurationProperties(VirtualThreadPropertiesBinding.class)
|
||||
static class BindingConfig {
|
||||
|
||||
@Bean
|
||||
VirtualThreadProperties virtualThreadProperties(VirtualThreadPropertiesBinding binding) {
|
||||
return binding.toRuntimeProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
+63
-63
@@ -17,92 +17,92 @@
|
||||
|
||||
package org.apache.hertzbeat.common.support;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import org.apache.hertzbeat.common.concurrent.AdmissionMode;
|
||||
import org.apache.hertzbeat.common.config.VirtualThreadProperties;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* test for {@link CommonThreadPool}
|
||||
* Test for {@link CommonThreadPool}.
|
||||
*/
|
||||
|
||||
class CommonThreadPoolTest {
|
||||
|
||||
private CommonThreadPool commonThreadPool;
|
||||
|
||||
private ThreadPoolExecutor executorMock;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() throws Exception {
|
||||
@AfterEach
|
||||
void tearDown() throws Exception {
|
||||
if (commonThreadPool != null) {
|
||||
commonThreadPool.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecuteRunsOnVirtualThread() throws Exception {
|
||||
commonThreadPool = new CommonThreadPool();
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicBoolean virtualThread = new AtomicBoolean(false);
|
||||
|
||||
Field workerExecutorField = CommonThreadPool.class.getDeclaredField("workerExecutor");
|
||||
workerExecutorField.setAccessible(true);
|
||||
executorMock = mock(ThreadPoolExecutor.class);
|
||||
workerExecutorField.set(commonThreadPool, executorMock);
|
||||
commonThreadPool.execute(() -> {
|
||||
virtualThread.set(Thread.currentThread().isVirtual());
|
||||
latch.countDown();
|
||||
});
|
||||
|
||||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
assertTrue(virtualThread.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteTask() {
|
||||
void testExecuteLongRunningRunsOnPlatformThread() throws Exception {
|
||||
commonThreadPool = new CommonThreadPool();
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicBoolean virtualThread = new AtomicBoolean(true);
|
||||
|
||||
Runnable task = mock(Runnable.class);
|
||||
commonThreadPool.execute(task);
|
||||
verify(executorMock).execute(task);
|
||||
commonThreadPool.executeLongRunning(() -> {
|
||||
virtualThread.set(Thread.currentThread().isVirtual());
|
||||
latch.countDown();
|
||||
});
|
||||
|
||||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
assertFalse(virtualThread.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteTaskThrowsEx() {
|
||||
void testExecuteRejectsWhenConcurrencyLimitReached() throws Exception {
|
||||
VirtualThreadProperties properties = new VirtualThreadProperties(
|
||||
true,
|
||||
VirtualThreadProperties.PoolProperties.collectorDefaults(),
|
||||
new VirtualThreadProperties.PoolProperties(AdmissionMode.LIMIT_AND_REJECT, 1),
|
||||
VirtualThreadProperties.PoolProperties.managerDefaults(),
|
||||
VirtualThreadProperties.AlerterProperties.defaults(),
|
||||
VirtualThreadProperties.PoolProperties.warehouseDefaults(),
|
||||
VirtualThreadProperties.AsyncProperties.defaults());
|
||||
commonThreadPool = new CommonThreadPool(properties);
|
||||
|
||||
Runnable task = mock(Runnable.class);
|
||||
doThrow(RejectedExecutionException.class).when(executorMock).execute(task);
|
||||
CountDownLatch started = new CountDownLatch(1);
|
||||
CountDownLatch release = new CountDownLatch(1);
|
||||
commonThreadPool.execute(() -> {
|
||||
started.countDown();
|
||||
try {
|
||||
release.await(5, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
});
|
||||
assertTrue(started.await(5, TimeUnit.SECONDS));
|
||||
|
||||
assertThrows(
|
||||
RejectedExecutionException.class,
|
||||
() -> commonThreadPool.execute(task)
|
||||
);
|
||||
try {
|
||||
assertThrows(RejectedExecutionException.class, () -> commonThreadPool.execute(() -> {
|
||||
}));
|
||||
} finally {
|
||||
release.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDestroy() throws Exception {
|
||||
|
||||
commonThreadPool.destroy();
|
||||
verify(executorMock).shutdownNow();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDestroyWithNull() throws Exception {
|
||||
|
||||
Field workerExecutorField = CommonThreadPool.class.getDeclaredField("workerExecutor");
|
||||
workerExecutorField.setAccessible(true);
|
||||
workerExecutorField.set(commonThreadPool, null);
|
||||
|
||||
commonThreadPool.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitialization() throws Exception {
|
||||
CommonThreadPool pool = new CommonThreadPool();
|
||||
|
||||
Field workerExecutorField = CommonThreadPool.class.getDeclaredField("workerExecutor");
|
||||
workerExecutorField.setAccessible(true);
|
||||
ThreadPoolExecutor workerExecutor = (ThreadPoolExecutor) workerExecutorField.get(pool);
|
||||
|
||||
assertNotNull(workerExecutor);
|
||||
assertEquals(1, workerExecutor.getCorePoolSize());
|
||||
assertEquals(Integer.MAX_VALUE, workerExecutor.getMaximumPoolSize());
|
||||
assertEquals(10, workerExecutor.getKeepAliveTime(TimeUnit.SECONDS));
|
||||
assertTrue(workerExecutor.getQueue() instanceof SynchronousQueue);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -61,11 +61,11 @@ public class LogSseManager {
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
});
|
||||
private final ExecutorService senderPool = Executors.newCachedThreadPool(r -> {
|
||||
Thread t = new Thread(r, "sse-sender");
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
});
|
||||
private final ExecutorService senderPool = Executors.newThreadPerTaskExecutor(Thread.ofVirtual()
|
||||
.name("sse-sender-", 0)
|
||||
.uncaughtExceptionHandler((thread, throwable) ->
|
||||
log.error("SSE sender has uncaughtException.", throwable))
|
||||
.factory());
|
||||
private final AtomicLong queueSize = new AtomicLong(0);
|
||||
|
||||
public LogSseManager() {
|
||||
|
||||
@@ -26,7 +26,9 @@ import org.junit.jupiter.api.Test;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
@@ -35,6 +37,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
@@ -88,6 +91,24 @@ class LogSseManagerTest {
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSendBatchOnVirtualThread() throws IOException, InterruptedException {
|
||||
SseEmitter mockEmitter = mock(SseEmitter.class);
|
||||
AtomicBoolean virtualThread = new AtomicBoolean(false);
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
doAnswer(invocation -> {
|
||||
virtualThread.set(Thread.currentThread().isVirtual());
|
||||
latch.countDown();
|
||||
return null;
|
||||
}).when(mockEmitter).send(any(SseEmitter.SseEventBuilder.class));
|
||||
subscribeClient(CLIENT_ID, null, mockEmitter);
|
||||
|
||||
logSseManager.broadcast(createLogEntry("INFO", "virtual-thread-send"));
|
||||
|
||||
assertTrue(latch.await(1, TimeUnit.SECONDS));
|
||||
assertTrue(virtualThread.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotBroadcastLogWhenFilterDoesNotMatch() throws IOException, InterruptedException {
|
||||
// Given: A client with a filter for "ERROR" logs
|
||||
@@ -172,6 +193,16 @@ class LogSseManagerTest {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDropLogsWhenQueueSizeLimitReached() {
|
||||
for (int i = 0; i < 10_001; i++) {
|
||||
logSseManager.broadcast(createLogEntry("INFO", "log-" + i));
|
||||
}
|
||||
|
||||
assertEquals(10_000, logSseManager.getQueueSize());
|
||||
assertEquals(10_000, logSseManager.getLogQueue().size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to create a subscriber and inject a mock emitter for testing
|
||||
*/
|
||||
@@ -190,4 +221,4 @@ class LogSseManagerTest {
|
||||
.body(body)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -78,7 +78,7 @@ public class ServiceDiscoveryWorker implements InitializingBean {
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
workerPool.executeJob(new SdUpdateTask());
|
||||
workerPool.executeLongRunning(new SdUpdateTask());
|
||||
}
|
||||
|
||||
private class SdUpdateTask implements Runnable {
|
||||
|
||||
+289
-147
@@ -30,11 +30,13 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hertzbeat.common.config.VirtualThreadProperties;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.manager.Monitor;
|
||||
import org.apache.hertzbeat.common.entity.manager.StatusPageComponent;
|
||||
@@ -45,6 +47,8 @@ import org.apache.hertzbeat.manager.dao.MonitorDao;
|
||||
import org.apache.hertzbeat.manager.dao.StatusPageComponentDao;
|
||||
import org.apache.hertzbeat.manager.dao.StatusPageHistoryDao;
|
||||
import org.apache.hertzbeat.manager.dao.StatusPageOrgDao;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -53,7 +57,7 @@ import org.springframework.stereotype.Component;
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class CalculateStatus {
|
||||
public class CalculateStatus implements DisposableBean {
|
||||
|
||||
private static final int DEFAULT_CALCULATE_INTERVAL_TIME = 300;
|
||||
|
||||
@@ -67,108 +71,60 @@ public class CalculateStatus {
|
||||
|
||||
private final int intervals;
|
||||
|
||||
private final ScheduledExecutorService calculateScheduler;
|
||||
|
||||
private final ScheduledExecutorService combineHistoryScheduler;
|
||||
|
||||
private final ExecutorService calculateExecutor;
|
||||
|
||||
private final ExecutorService combineHistoryExecutor;
|
||||
|
||||
private final ScheduledDispatchTask calculateTask;
|
||||
|
||||
private final ScheduledDispatchTask combineHistoryTask;
|
||||
|
||||
public CalculateStatus(StatusPageOrgDao statusPageOrgDao, StatusPageComponentDao statusPageComponentDao,
|
||||
StatusProperties statusProperties, StatusPageHistoryDao statusPageHistoryDao,
|
||||
MonitorDao monitorDao) {
|
||||
this(statusPageOrgDao, statusPageComponentDao, statusProperties, statusPageHistoryDao, monitorDao,
|
||||
VirtualThreadProperties.defaults(), true);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public CalculateStatus(StatusPageOrgDao statusPageOrgDao, StatusPageComponentDao statusPageComponentDao,
|
||||
StatusProperties statusProperties, StatusPageHistoryDao statusPageHistoryDao,
|
||||
MonitorDao monitorDao, VirtualThreadProperties virtualThreadProperties) {
|
||||
this(statusPageOrgDao, statusPageComponentDao, statusProperties, statusPageHistoryDao, monitorDao,
|
||||
virtualThreadProperties, true);
|
||||
}
|
||||
|
||||
CalculateStatus(StatusPageOrgDao statusPageOrgDao, StatusPageComponentDao statusPageComponentDao,
|
||||
StatusProperties statusProperties, StatusPageHistoryDao statusPageHistoryDao,
|
||||
MonitorDao monitorDao, VirtualThreadProperties virtualThreadProperties, boolean autoStart) {
|
||||
this.statusPageOrgDao = statusPageOrgDao;
|
||||
this.monitorDao = monitorDao;
|
||||
this.statusPageComponentDao = statusPageComponentDao;
|
||||
this.statusPageHistoryDao = statusPageHistoryDao;
|
||||
intervals = statusProperties.getCalculate() == null ? DEFAULT_CALCULATE_INTERVAL_TIME : statusProperties.getCalculate().getInterval();
|
||||
startCalculate();
|
||||
startCombineHistory();
|
||||
this.calculateScheduler = createScheduler("status-page-calculate-%d", "Status calculate has uncaughtException.");
|
||||
this.combineHistoryScheduler = createScheduler("status-page-history-%d", "History combine has uncaughtException.");
|
||||
this.calculateExecutor = createVirtualExecutor(virtualThreadProperties, "status-page-calculate-vt-",
|
||||
"Status calculate worker has uncaughtException.");
|
||||
this.combineHistoryExecutor = createVirtualExecutor(virtualThreadProperties, "status-page-history-vt-",
|
||||
"History combine worker has uncaughtException.");
|
||||
this.calculateTask = new ScheduledDispatchTask(calculateExecutor, this::runCalculate);
|
||||
this.combineHistoryTask = new ScheduledDispatchTask(combineHistoryExecutor, this::runCombineHistory);
|
||||
if (autoStart) {
|
||||
startCalculate();
|
||||
startCombineHistory();
|
||||
}
|
||||
}
|
||||
|
||||
private void startCalculate() {
|
||||
ThreadFactory threadFactory = new ThreadFactoryBuilder()
|
||||
.setUncaughtExceptionHandler((thread, throwable) -> {
|
||||
log.error("Status calculate has uncaughtException.");
|
||||
log.error(throwable.getMessage(), throwable);
|
||||
})
|
||||
.setDaemon(true)
|
||||
.setNameFormat("status-page-calculate-%d")
|
||||
.build();
|
||||
ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor(threadFactory);
|
||||
scheduledExecutor.scheduleAtFixedRate(() -> {
|
||||
log.info("start to calculate status page state");
|
||||
try {
|
||||
// calculate component state from tag bind monitors status
|
||||
List<StatusPageOrg> statusPageOrgList = statusPageOrgDao.findAll();
|
||||
for (StatusPageOrg statusPageOrg : statusPageOrgList) {
|
||||
long orgId = statusPageOrg.getId();
|
||||
List<StatusPageComponent> pageComponentList = statusPageComponentDao.findByOrgId(orgId);
|
||||
Set<Byte> stateSet = new HashSet<>(8);
|
||||
for (StatusPageComponent component : pageComponentList) {
|
||||
byte state;
|
||||
if (component.getMethod() == CommonConstants.STATUS_PAGE_CALCULATE_METHOD_MANUAL) {
|
||||
state = component.getConfigState();
|
||||
} else {
|
||||
Map<String, String> labels = component.getLabels();
|
||||
if (labels == null || labels.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
Specification<Monitor> specification = (root, query, criteriaBuilder) -> {
|
||||
List<Predicate> predicates = new ArrayList<>();
|
||||
// create every label condition
|
||||
labels.forEach((key, value) -> {
|
||||
String pattern = String.format("%%\"%s\":\"%s\"%%", key, value);
|
||||
predicates.add(criteriaBuilder.like(root.get("labels"), pattern));
|
||||
});
|
||||
|
||||
// use or connect them
|
||||
return criteriaBuilder.or(predicates.toArray(new Predicate[0]));
|
||||
};
|
||||
List<Monitor> monitorList = monitorDao.findAll(specification);
|
||||
state = CommonConstants.STATUS_PAGE_COMPONENT_STATE_UNKNOWN;
|
||||
for (Monitor monitor : monitorList) {
|
||||
if (monitor.getStatus() == CommonConstants.MONITOR_DOWN_CODE) {
|
||||
state = CommonConstants.STATUS_PAGE_COMPONENT_STATE_ABNORMAL;
|
||||
break;
|
||||
} else if (monitor.getStatus() == CommonConstants.MONITOR_UP_CODE) {
|
||||
state = CommonConstants.STATUS_PAGE_COMPONENT_STATE_NORMAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
stateSet.add(state);
|
||||
component.setState(state);
|
||||
statusPageComponentDao.save(component);
|
||||
// insert component state history
|
||||
StatusPageHistory statusPageHistory = StatusPageHistory.builder()
|
||||
.componentId(component.getId())
|
||||
.state(state)
|
||||
.timestamp(System.currentTimeMillis())
|
||||
.build();
|
||||
statusPageHistoryDao.save(statusPageHistory);
|
||||
}
|
||||
stateSet.remove(CommonConstants.STATUS_PAGE_COMPONENT_STATE_UNKNOWN);
|
||||
if (stateSet.remove(CommonConstants.STATUS_PAGE_COMPONENT_STATE_ABNORMAL)) {
|
||||
if (stateSet.contains(CommonConstants.STATUS_PAGE_COMPONENT_STATE_NORMAL)) {
|
||||
statusPageOrg.setState(CommonConstants.STATUS_PAGE_ORG_STATE_SOME_ABNORMAL);
|
||||
} else {
|
||||
statusPageOrg.setState(CommonConstants.STATUS_PAGE_ORG_STATE_ALL_ABNORMAL);
|
||||
}
|
||||
} else {
|
||||
statusPageOrg.setState(CommonConstants.STATUS_PAGE_ORG_STATE_ALL_NORMAL);
|
||||
}
|
||||
statusPageOrg.setGmtUpdate(LocalDateTime.now());
|
||||
statusPageOrgDao.save(statusPageOrg);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("status page calculate component state error: {}", e.getMessage(), e);
|
||||
}
|
||||
}, 5, intervals, TimeUnit.SECONDS);
|
||||
calculateScheduler.scheduleAtFixedRate(this::dispatchCalculate, 5, intervals, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private void startCombineHistory() {
|
||||
ThreadFactory threadFactory = new ThreadFactoryBuilder()
|
||||
.setUncaughtExceptionHandler((thread, throwable) -> {
|
||||
log.error("History combine has uncaughtException.");
|
||||
log.error(throwable.getMessage(), throwable);
|
||||
})
|
||||
.setDaemon(true)
|
||||
.setNameFormat("status-page-calculate-%d")
|
||||
.build();
|
||||
ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor(threadFactory);
|
||||
// combine history every day at 1:00 AM
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
LocalDateTime nextRun = now.withHour(1).withMinute(0).withSecond(0);
|
||||
@@ -176,64 +132,8 @@ public class CalculateStatus {
|
||||
nextRun = nextRun.plusDays(1);
|
||||
}
|
||||
long delay = Duration.between(now, nextRun).toMillis();
|
||||
scheduledExecutor.scheduleAtFixedRate(() -> {
|
||||
try {
|
||||
// combine pre day status history to one record
|
||||
LocalDateTime nowTime = LocalDateTime.now();
|
||||
ZoneOffset zoneOffset = ZoneId.systemDefault().getRules().getOffset(Instant.now());
|
||||
LocalDateTime midnight = nowTime.withHour(0).withMinute(0).withSecond(0).withNano(0);
|
||||
LocalDateTime preNight = midnight.minusDays(1);
|
||||
long midnightTimestamp = midnight.toInstant(zoneOffset).toEpochMilli();
|
||||
long preNightTimestamp = preNight.toInstant(zoneOffset).toEpochMilli();
|
||||
List<StatusPageHistory> statusPageHistoryList = statusPageHistoryDao
|
||||
.findStatusPageHistoriesByTimestampBetween(preNightTimestamp, midnightTimestamp);
|
||||
Map<Long, StatusPageHistory> statusPageHistoryMap = new HashMap<>(8);
|
||||
for (StatusPageHistory statusPageHistory : statusPageHistoryList) {
|
||||
statusPageHistory.setNormal(0);
|
||||
statusPageHistory.setAbnormal(0);
|
||||
statusPageHistory.setUnknowing(0);
|
||||
if (statusPageHistoryMap.containsKey(statusPageHistory.getComponentId())) {
|
||||
StatusPageHistory history = statusPageHistoryMap.get(statusPageHistory.getComponentId());
|
||||
if (statusPageHistory.getState() == CommonConstants.STATUS_PAGE_COMPONENT_STATE_ABNORMAL) {
|
||||
history.setAbnormal(history.getAbnormal() + intervals);
|
||||
} else if (statusPageHistory.getState() == CommonConstants.STATUS_PAGE_COMPONENT_STATE_UNKNOWN) {
|
||||
history.setUnknowing(history.getUnknowing() + intervals);
|
||||
} else {
|
||||
history.setNormal(history.getNormal() + intervals);
|
||||
}
|
||||
statusPageHistoryMap.put(statusPageHistory.getComponentId(), history);
|
||||
} else {
|
||||
if (statusPageHistory.getState() == CommonConstants.STATUS_PAGE_COMPONENT_STATE_ABNORMAL) {
|
||||
statusPageHistory.setAbnormal(intervals);
|
||||
} else if (statusPageHistory.getState() == CommonConstants.STATUS_PAGE_COMPONENT_STATE_UNKNOWN) {
|
||||
statusPageHistory.setUnknowing(intervals);
|
||||
} else {
|
||||
statusPageHistory.setNormal(intervals);
|
||||
}
|
||||
statusPageHistoryMap.put(statusPageHistory.getComponentId(), statusPageHistory);
|
||||
}
|
||||
}
|
||||
statusPageHistoryDao.deleteAll(statusPageHistoryList);
|
||||
for (StatusPageHistory history : statusPageHistoryMap.values()) {
|
||||
double total = history.getNormal() + history.getAbnormal() + history.getUnknowing();
|
||||
double uptime = 0;
|
||||
if (total > 0) {
|
||||
uptime = (double) history.getNormal() / total;
|
||||
}
|
||||
history.setUptime(uptime);
|
||||
if (history.getAbnormal() > 0) {
|
||||
history.setState(CommonConstants.STATUS_PAGE_COMPONENT_STATE_ABNORMAL);
|
||||
} else if (history.getNormal() > 0) {
|
||||
history.setState(CommonConstants.STATUS_PAGE_COMPONENT_STATE_NORMAL);
|
||||
} else {
|
||||
history.setState(CommonConstants.STATUS_PAGE_COMPONENT_STATE_UNKNOWN);
|
||||
}
|
||||
statusPageHistoryDao.save(history);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("status page combine history error: {}", e.getMessage(), e);
|
||||
}
|
||||
}, delay, TimeUnit.DAYS.toMillis(1), TimeUnit.MILLISECONDS);
|
||||
combineHistoryScheduler.scheduleAtFixedRate(this::dispatchCombineHistory, delay,
|
||||
TimeUnit.DAYS.toMillis(1), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -243,4 +143,246 @@ public class CalculateStatus {
|
||||
public int getCalculateStatusIntervals() {
|
||||
return intervals;
|
||||
}
|
||||
|
||||
void dispatchCalculate() {
|
||||
calculateTask.dispatch();
|
||||
}
|
||||
|
||||
void dispatchCombineHistory() {
|
||||
combineHistoryTask.dispatch();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
calculateScheduler.shutdownNow();
|
||||
combineHistoryScheduler.shutdownNow();
|
||||
if (calculateExecutor != null) {
|
||||
calculateExecutor.shutdownNow();
|
||||
}
|
||||
if (combineHistoryExecutor != null) {
|
||||
combineHistoryExecutor.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
private void runCalculate() {
|
||||
log.info("start to calculate status page state");
|
||||
try {
|
||||
// calculate component state from tag bind monitors status
|
||||
List<StatusPageOrg> statusPageOrgList = statusPageOrgDao.findAll();
|
||||
for (StatusPageOrg statusPageOrg : statusPageOrgList) {
|
||||
long orgId = statusPageOrg.getId();
|
||||
List<StatusPageComponent> pageComponentList = statusPageComponentDao.findByOrgId(orgId);
|
||||
Set<Byte> stateSet = new HashSet<>(8);
|
||||
for (StatusPageComponent component : pageComponentList) {
|
||||
byte state;
|
||||
if (component.getMethod() == CommonConstants.STATUS_PAGE_CALCULATE_METHOD_MANUAL) {
|
||||
state = component.getConfigState();
|
||||
} else {
|
||||
Map<String, String> labels = component.getLabels();
|
||||
if (labels == null || labels.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
Specification<Monitor> specification = (root, query, criteriaBuilder) -> {
|
||||
List<Predicate> predicates = new ArrayList<>();
|
||||
// create every label condition
|
||||
labels.forEach((key, value) -> {
|
||||
String pattern = String.format("%%\"%s\":\"%s\"%%", key, value);
|
||||
predicates.add(criteriaBuilder.like(root.get("labels"), pattern));
|
||||
});
|
||||
|
||||
// use or connect them
|
||||
return criteriaBuilder.or(predicates.toArray(new Predicate[0]));
|
||||
};
|
||||
List<Monitor> monitorList = monitorDao.findAll(specification);
|
||||
state = CommonConstants.STATUS_PAGE_COMPONENT_STATE_UNKNOWN;
|
||||
for (Monitor monitor : monitorList) {
|
||||
if (monitor.getStatus() == CommonConstants.MONITOR_DOWN_CODE) {
|
||||
state = CommonConstants.STATUS_PAGE_COMPONENT_STATE_ABNORMAL;
|
||||
break;
|
||||
} else if (monitor.getStatus() == CommonConstants.MONITOR_UP_CODE) {
|
||||
state = CommonConstants.STATUS_PAGE_COMPONENT_STATE_NORMAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
stateSet.add(state);
|
||||
component.setState(state);
|
||||
statusPageComponentDao.save(component);
|
||||
// insert component state history
|
||||
StatusPageHistory statusPageHistory = StatusPageHistory.builder()
|
||||
.componentId(component.getId())
|
||||
.state(state)
|
||||
.timestamp(System.currentTimeMillis())
|
||||
.build();
|
||||
statusPageHistoryDao.save(statusPageHistory);
|
||||
}
|
||||
stateSet.remove(CommonConstants.STATUS_PAGE_COMPONENT_STATE_UNKNOWN);
|
||||
if (stateSet.remove(CommonConstants.STATUS_PAGE_COMPONENT_STATE_ABNORMAL)) {
|
||||
if (stateSet.contains(CommonConstants.STATUS_PAGE_COMPONENT_STATE_NORMAL)) {
|
||||
statusPageOrg.setState(CommonConstants.STATUS_PAGE_ORG_STATE_SOME_ABNORMAL);
|
||||
} else {
|
||||
statusPageOrg.setState(CommonConstants.STATUS_PAGE_ORG_STATE_ALL_ABNORMAL);
|
||||
}
|
||||
} else {
|
||||
statusPageOrg.setState(CommonConstants.STATUS_PAGE_ORG_STATE_ALL_NORMAL);
|
||||
}
|
||||
statusPageOrg.setGmtUpdate(LocalDateTime.now());
|
||||
statusPageOrgDao.save(statusPageOrg);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("status page calculate component state error: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void runCombineHistory() {
|
||||
try {
|
||||
// combine pre day status history to one record
|
||||
LocalDateTime nowTime = LocalDateTime.now();
|
||||
ZoneOffset zoneOffset = ZoneId.systemDefault().getRules().getOffset(Instant.now());
|
||||
LocalDateTime midnight = nowTime.withHour(0).withMinute(0).withSecond(0).withNano(0);
|
||||
LocalDateTime preNight = midnight.minusDays(1);
|
||||
long midnightTimestamp = midnight.toInstant(zoneOffset).toEpochMilli();
|
||||
long preNightTimestamp = preNight.toInstant(zoneOffset).toEpochMilli();
|
||||
List<StatusPageHistory> statusPageHistoryList = statusPageHistoryDao
|
||||
.findStatusPageHistoriesByTimestampBetween(preNightTimestamp, midnightTimestamp);
|
||||
Map<Long, StatusPageHistory> statusPageHistoryMap = new HashMap<>(8);
|
||||
for (StatusPageHistory statusPageHistory : statusPageHistoryList) {
|
||||
statusPageHistory.setNormal(0);
|
||||
statusPageHistory.setAbnormal(0);
|
||||
statusPageHistory.setUnknowing(0);
|
||||
if (statusPageHistoryMap.containsKey(statusPageHistory.getComponentId())) {
|
||||
StatusPageHistory history = statusPageHistoryMap.get(statusPageHistory.getComponentId());
|
||||
if (statusPageHistory.getState() == CommonConstants.STATUS_PAGE_COMPONENT_STATE_ABNORMAL) {
|
||||
history.setAbnormal(history.getAbnormal() + intervals);
|
||||
} else if (statusPageHistory.getState() == CommonConstants.STATUS_PAGE_COMPONENT_STATE_UNKNOWN) {
|
||||
history.setUnknowing(history.getUnknowing() + intervals);
|
||||
} else {
|
||||
history.setNormal(history.getNormal() + intervals);
|
||||
}
|
||||
statusPageHistoryMap.put(statusPageHistory.getComponentId(), history);
|
||||
} else {
|
||||
if (statusPageHistory.getState() == CommonConstants.STATUS_PAGE_COMPONENT_STATE_ABNORMAL) {
|
||||
statusPageHistory.setAbnormal(intervals);
|
||||
} else if (statusPageHistory.getState() == CommonConstants.STATUS_PAGE_COMPONENT_STATE_UNKNOWN) {
|
||||
statusPageHistory.setUnknowing(intervals);
|
||||
} else {
|
||||
statusPageHistory.setNormal(intervals);
|
||||
}
|
||||
statusPageHistoryMap.put(statusPageHistory.getComponentId(), statusPageHistory);
|
||||
}
|
||||
}
|
||||
statusPageHistoryDao.deleteAll(statusPageHistoryList);
|
||||
for (StatusPageHistory history : statusPageHistoryMap.values()) {
|
||||
double total = history.getNormal() + history.getAbnormal() + history.getUnknowing();
|
||||
double uptime = 0;
|
||||
if (total > 0) {
|
||||
uptime = (double) history.getNormal() / total;
|
||||
}
|
||||
history.setUptime(uptime);
|
||||
if (history.getAbnormal() > 0) {
|
||||
history.setState(CommonConstants.STATUS_PAGE_COMPONENT_STATE_ABNORMAL);
|
||||
} else if (history.getNormal() > 0) {
|
||||
history.setState(CommonConstants.STATUS_PAGE_COMPONENT_STATE_NORMAL);
|
||||
} else {
|
||||
history.setState(CommonConstants.STATUS_PAGE_COMPONENT_STATE_UNKNOWN);
|
||||
}
|
||||
statusPageHistoryDao.save(history);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("status page combine history error: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private ScheduledExecutorService createScheduler(String threadNameFormat, String errorMessage) {
|
||||
ThreadFactory threadFactory = new ThreadFactoryBuilder()
|
||||
.setUncaughtExceptionHandler((thread, throwable) -> {
|
||||
log.error(errorMessage);
|
||||
log.error(throwable.getMessage(), throwable);
|
||||
})
|
||||
.setDaemon(true)
|
||||
.setNameFormat(threadNameFormat)
|
||||
.build();
|
||||
return Executors.newSingleThreadScheduledExecutor(threadFactory);
|
||||
}
|
||||
|
||||
private ExecutorService createVirtualExecutor(VirtualThreadProperties virtualThreadProperties, String threadPrefix,
|
||||
String errorMessage) {
|
||||
VirtualThreadProperties properties =
|
||||
virtualThreadProperties == null ? VirtualThreadProperties.defaults() : virtualThreadProperties;
|
||||
if (!properties.enabled()) {
|
||||
return null;
|
||||
}
|
||||
return Executors.newThreadPerTaskExecutor(Thread.ofVirtual()
|
||||
.name(threadPrefix, 0)
|
||||
.uncaughtExceptionHandler((thread, throwable) -> {
|
||||
log.error(errorMessage);
|
||||
log.error(throwable.getMessage(), throwable);
|
||||
})
|
||||
.factory());
|
||||
}
|
||||
|
||||
private static final class ScheduledDispatchTask {
|
||||
|
||||
private final ExecutorService executorService;
|
||||
private final Runnable task;
|
||||
private final Object lock = new Object();
|
||||
private boolean running;
|
||||
private int pendingRuns;
|
||||
|
||||
private ScheduledDispatchTask(ExecutorService executorService, Runnable task) {
|
||||
this.executorService = executorService;
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
private void dispatch() {
|
||||
if (executorService == null) {
|
||||
task.run();
|
||||
return;
|
||||
}
|
||||
synchronized (lock) {
|
||||
if (running) {
|
||||
pendingRuns++;
|
||||
return;
|
||||
}
|
||||
running = true;
|
||||
}
|
||||
submit();
|
||||
}
|
||||
|
||||
private void submit() {
|
||||
boolean submitted = false;
|
||||
try {
|
||||
executorService.execute(() -> {
|
||||
try {
|
||||
task.run();
|
||||
} finally {
|
||||
onComplete();
|
||||
}
|
||||
});
|
||||
submitted = true;
|
||||
} finally {
|
||||
if (!submitted) {
|
||||
synchronized (lock) {
|
||||
running = false;
|
||||
pendingRuns = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onComplete() {
|
||||
boolean shouldRunAgain;
|
||||
synchronized (lock) {
|
||||
if (pendingRuns > 0) {
|
||||
pendingRuns--;
|
||||
shouldRunAgain = true;
|
||||
} else {
|
||||
running = false;
|
||||
shouldRunAgain = false;
|
||||
}
|
||||
}
|
||||
if (shouldRunAgain) {
|
||||
submit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -17,8 +17,8 @@
|
||||
|
||||
package org.apache.hertzbeat.manager.component.validator;
|
||||
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.MonitorParam;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.ParamDefineInfo;
|
||||
|
||||
/**
|
||||
* Parameter validator interface
|
||||
@@ -40,5 +40,5 @@ public interface ParamValidator {
|
||||
* @param param parameter actual value
|
||||
* @throws IllegalArgumentException if validation fails
|
||||
*/
|
||||
void validate(ParamDefine paramDefine, Param param) throws IllegalArgumentException;
|
||||
void validate(ParamDefineInfo paramDefine, MonitorParam param) throws IllegalArgumentException;
|
||||
}
|
||||
|
||||
+3
-3
@@ -17,8 +17,8 @@
|
||||
|
||||
package org.apache.hertzbeat.manager.component.validator;
|
||||
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.MonitorParam;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.ParamDefineInfo;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
@@ -35,7 +35,7 @@ public class ParamValidatorManager {
|
||||
this.validators = validators;
|
||||
}
|
||||
|
||||
public void validate(ParamDefine paramDefine, Param param) {
|
||||
public void validate(ParamDefineInfo paramDefine, MonitorParam param) {
|
||||
for (ParamValidator validator : validators) {
|
||||
if (validator.support(paramDefine.getType())) {
|
||||
validator.validate(paramDefine, param);
|
||||
|
||||
+3
-3
@@ -17,9 +17,9 @@
|
||||
|
||||
package org.apache.hertzbeat.manager.component.validator.impl;
|
||||
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import org.apache.hertzbeat.manager.component.validator.ParamValidator;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.MonitorParam;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.ParamDefineInfo;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
@@ -33,7 +33,7 @@ public class ArrayParamValidator implements ParamValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(ParamDefine paramDefine, Param param) {
|
||||
public void validate(ParamDefineInfo paramDefine, MonitorParam param) {
|
||||
String[] arrays = param.getParamValue().split(",");
|
||||
if (arrays.length == 0) {
|
||||
throw new IllegalArgumentException("Param field " + paramDefine.getField() + " value "
|
||||
|
||||
+3
-3
@@ -17,9 +17,9 @@
|
||||
|
||||
package org.apache.hertzbeat.manager.component.validator.impl;
|
||||
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import org.apache.hertzbeat.manager.component.validator.ParamValidator;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.MonitorParam;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.ParamDefineInfo;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
@@ -33,7 +33,7 @@ public class BooleanParamValidator implements ParamValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(ParamDefine paramDefine, Param param) {
|
||||
public void validate(ParamDefineInfo paramDefine, MonitorParam param) {
|
||||
String booleanValue = param.getParamValue();
|
||||
if (!"true".equalsIgnoreCase(booleanValue) && !"false".equalsIgnoreCase(booleanValue)) {
|
||||
throw new IllegalArgumentException("Params field " + paramDefine.getField() + " value "
|
||||
|
||||
+3
-3
@@ -17,9 +17,9 @@
|
||||
|
||||
package org.apache.hertzbeat.manager.component.validator.impl;
|
||||
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import org.apache.hertzbeat.manager.component.validator.ParamValidator;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.MonitorParam;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.ParamDefineInfo;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
@@ -40,7 +40,7 @@ public class HostParamValidatorAdapter implements ParamValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(ParamDefine paramDefine, Param param) {
|
||||
public void validate(ParamDefineInfo paramDefine, MonitorParam param) {
|
||||
if (!hostValidator.isValid(param.getParamValue(), null)) {
|
||||
throw new IllegalArgumentException("Params field " + paramDefine.getField() + " value "
|
||||
+ param.getParamValue() + " is invalid host value.");
|
||||
|
||||
+3
-3
@@ -18,10 +18,10 @@
|
||||
package org.apache.hertzbeat.manager.component.validator.impl;
|
||||
|
||||
import tools.jackson.core.type.TypeReference;
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import org.apache.hertzbeat.common.util.JsonUtil;
|
||||
import org.apache.hertzbeat.manager.component.validator.ParamValidator;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.MonitorParam;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.ParamDefineInfo;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
@@ -35,7 +35,7 @@ public class JsonParamValidator implements ParamValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(ParamDefine paramDefine, Param param) {
|
||||
public void validate(ParamDefineInfo paramDefine, MonitorParam param) {
|
||||
if (JsonUtil.fromJson(param.getParamValue(), new TypeReference<>() {
|
||||
}) == null) {
|
||||
throw new IllegalArgumentException("Params field " + paramDefine.getField() + " value "
|
||||
|
||||
+3
-3
@@ -18,10 +18,10 @@
|
||||
package org.apache.hertzbeat.manager.component.validator.impl;
|
||||
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import org.apache.hertzbeat.common.util.IntervalExpressionUtil;
|
||||
import org.apache.hertzbeat.manager.component.validator.ParamValidator;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.MonitorParam;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.ParamDefineInfo;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
@@ -35,7 +35,7 @@ public class NumberParamValidator implements ParamValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(ParamDefine paramDefine, Param param) {
|
||||
public void validate(ParamDefineInfo paramDefine, MonitorParam param) {
|
||||
Double doubleValue = org.apache.hertzbeat.common.util.CommonUtil.parseStrDouble(param.getParamValue());
|
||||
if (doubleValue == null) {
|
||||
throw new IllegalArgumentException("Params field " + paramDefine.getField() + " type "
|
||||
|
||||
+5
-5
@@ -17,9 +17,9 @@
|
||||
|
||||
package org.apache.hertzbeat.manager.component.validator.impl;
|
||||
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import org.apache.hertzbeat.manager.component.validator.ParamValidator;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.MonitorParam;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.ParamDefineInfo;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
@@ -35,11 +35,11 @@ public class OptionParamValidator implements ParamValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(ParamDefine paramDefine, Param param) {
|
||||
List<ParamDefine.Option> options = paramDefine.getOptions();
|
||||
public void validate(ParamDefineInfo paramDefine, MonitorParam param) {
|
||||
List<ParamDefineInfo.OptionInfo> options = paramDefine.getOptions();
|
||||
boolean invalid = true;
|
||||
if (options != null) {
|
||||
for (ParamDefine.Option option : options) {
|
||||
for (ParamDefineInfo.OptionInfo option : options) {
|
||||
if (param.getParamValue().equalsIgnoreCase(option.getValue())) {
|
||||
invalid = false;
|
||||
break;
|
||||
|
||||
+3
-3
@@ -18,10 +18,10 @@
|
||||
package org.apache.hertzbeat.manager.component.validator.impl;
|
||||
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import org.apache.hertzbeat.common.util.AesUtil;
|
||||
import org.apache.hertzbeat.manager.component.validator.ParamValidator;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.MonitorParam;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.ParamDefineInfo;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
@@ -35,7 +35,7 @@ public class PasswordParamValidator implements ParamValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(ParamDefine paramDefine, Param param) {
|
||||
public void validate(ParamDefineInfo paramDefine, MonitorParam param) {
|
||||
String passwordValue = param.getParamValue();
|
||||
if (!AesUtil.isCiphertext(passwordValue)) {
|
||||
passwordValue = AesUtil.aesEncode(passwordValue);
|
||||
|
||||
+3
-3
@@ -17,9 +17,9 @@
|
||||
|
||||
package org.apache.hertzbeat.manager.component.validator.impl;
|
||||
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import org.apache.hertzbeat.manager.component.validator.ParamValidator;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.MonitorParam;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.ParamDefineInfo;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
@@ -33,7 +33,7 @@ public class TextParamValidator implements ParamValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(ParamDefine paramDefine, Param param) {
|
||||
public void validate(ParamDefineInfo paramDefine, MonitorParam param) {
|
||||
Short limit = paramDefine.getLimit();
|
||||
if (limit != null && param.getParamValue().length() > limit) {
|
||||
throw new IllegalArgumentException("Params field " + paramDefine.getField() + " type "
|
||||
|
||||
+2
-2
@@ -27,10 +27,10 @@ import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import org.apache.hertzbeat.common.entity.dto.Message;
|
||||
import org.apache.hertzbeat.common.entity.job.Job;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
import org.apache.hertzbeat.common.util.ResponseUtil;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.Hierarchy;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.MonitorDefineDto;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.ParamDefineInfo;
|
||||
import org.apache.hertzbeat.manager.service.AppService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -64,7 +64,7 @@ public class AppController {
|
||||
@GetMapping(path = "/{app}/params")
|
||||
@Operation(summary = "The structure of the input parameters required to specify the monitoring type according to the app query",
|
||||
description = "The structure of the input parameters required to specify the monitoring type according to the app query")
|
||||
public ResponseEntity<Message<List<ParamDefine>>> queryAppParamDefines(
|
||||
public ResponseEntity<Message<List<ParamDefineInfo>>> queryAppParamDefines(
|
||||
@Parameter(description = "en: Monitoring type name", example = "api") @PathVariable("app") final String app) {
|
||||
return ResponseUtil.handle(() -> appService.getAppParamDefines(app.toLowerCase()));
|
||||
}
|
||||
|
||||
+1
-1
@@ -23,9 +23,9 @@ import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.apache.hertzbeat.common.entity.dto.CollectorSummary;
|
||||
import org.apache.hertzbeat.common.entity.dto.Message;
|
||||
import org.apache.hertzbeat.common.util.ResponseUtil;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.CollectorSummary;
|
||||
import org.apache.hertzbeat.manager.service.CollectorService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
|
||||
+10
-4
@@ -22,10 +22,12 @@ import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import org.apache.hertzbeat.common.entity.dto.Message;
|
||||
import org.apache.hertzbeat.common.entity.manager.Monitor;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.MonitorInfo;
|
||||
import org.apache.hertzbeat.manager.service.MonitorService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
@@ -53,7 +55,7 @@ public class MonitorsController {
|
||||
@GetMapping
|
||||
@Operation(summary = "Obtain a list of monitoring information based on query filter items",
|
||||
description = "Obtain a list of monitoring information based on query filter items")
|
||||
public ResponseEntity<Message<Page<Monitor>>> getMonitors(
|
||||
public ResponseEntity<Message<Page<MonitorInfo>>> getMonitors(
|
||||
@Parameter(description = "Monitor ID", example = "6565463543") @RequestParam(required = false) final List<Long> ids,
|
||||
@Parameter(description = "Monitor Type", example = "linux") @RequestParam(required = false) final String app,
|
||||
@Parameter(description = "Monitor Status 0:no monitor,1:usable,2:disabled,9:all status", example = "1") @RequestParam(required = false) final Byte status,
|
||||
@@ -64,15 +66,19 @@ public class MonitorsController {
|
||||
@Parameter(description = "List current page", example = "0") @RequestParam(defaultValue = "0") int pageIndex,
|
||||
@Parameter(description = "Number of list pagination ", example = "8") @RequestParam(defaultValue = "8") int pageSize) {
|
||||
Page<Monitor> monitorPage = monitorService.getMonitors(ids, app, search, status, sort, order, pageIndex, pageSize, labels);
|
||||
return ResponseEntity.ok(Message.success(monitorPage));
|
||||
Page<MonitorInfo> responsePage = monitorPage == null ? Page.empty() : monitorPage.map(MonitorInfo::fromEntity);
|
||||
return ResponseEntity.ok(Message.success(responsePage));
|
||||
}
|
||||
|
||||
@GetMapping(path = "/{app}")
|
||||
@Operation(summary = "Filter all acquired monitoring information lists of the specified monitoring type according to the query",
|
||||
description = "Filter all acquired monitoring information lists of the specified monitoring type according to the query")
|
||||
public ResponseEntity<Message<List<Monitor>>> getAppMonitors(
|
||||
public ResponseEntity<Message<List<MonitorInfo>>> getAppMonitors(
|
||||
@Parameter(description = "en: Monitoring type", example = "linux") @PathVariable(required = false) final String app) {
|
||||
return ResponseEntity.ok(Message.success(monitorService.getAppMonitors(app)));
|
||||
List<Monitor> monitors = monitorService.getAppMonitors(app);
|
||||
List<MonitorInfo> response = monitors == null ? Collections.emptyList()
|
||||
: monitors.stream().map(MonitorInfo::fromEntity).toList();
|
||||
return ResponseEntity.ok(Message.success(response));
|
||||
}
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -25,8 +25,8 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.hertzbeat.common.entity.dto.Message;
|
||||
import org.apache.hertzbeat.common.entity.dto.PluginUpload;
|
||||
import org.apache.hertzbeat.common.entity.manager.PluginMetadata;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.PluginUpload;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.PluginParam;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.PluginParametersVO;
|
||||
import org.apache.hertzbeat.manager.service.PluginService;
|
||||
|
||||
+19
-22
@@ -28,9 +28,9 @@ import java.util.List;
|
||||
import jakarta.validation.Valid;
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.dto.Message;
|
||||
import org.apache.hertzbeat.common.entity.manager.StatusPageComponent;
|
||||
import org.apache.hertzbeat.common.entity.manager.StatusPageIncident;
|
||||
import org.apache.hertzbeat.common.entity.manager.StatusPageOrg;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.StatusPageComponentInfo;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.StatusPageIncidentInfo;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.StatusPageOrgInfo;
|
||||
import org.apache.hertzbeat.manager.service.StatusPageService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -57,8 +57,8 @@ public class StatusPageController {
|
||||
|
||||
@GetMapping("/org")
|
||||
@Operation(summary = "Query Status Page Organization")
|
||||
public ResponseEntity<Message<StatusPageOrg>> queryStatusPageOrg() {
|
||||
StatusPageOrg statusPageOrg = statusPageService.queryStatusPageOrg();
|
||||
public ResponseEntity<Message<StatusPageOrgInfo>> queryStatusPageOrg() {
|
||||
StatusPageOrgInfo statusPageOrg = statusPageService.queryStatusPageOrg();
|
||||
if (statusPageOrg == null) {
|
||||
return ResponseEntity.ok(Message.fail(CommonConstants.FAIL_CODE, "Status Page Organization Not Found"));
|
||||
}
|
||||
@@ -67,28 +67,27 @@ public class StatusPageController {
|
||||
|
||||
@PostMapping("/org")
|
||||
@Operation(summary = "Save and Update Query Status Page Organization")
|
||||
public ResponseEntity<Message<StatusPageOrg>> saveStatusPageOrg(@Valid @RequestBody StatusPageOrg statusPageOrg) {
|
||||
StatusPageOrg org = statusPageService.saveStatusPageOrg(statusPageOrg);
|
||||
public ResponseEntity<Message<StatusPageOrgInfo>> saveStatusPageOrg(@Valid @RequestBody StatusPageOrgInfo statusPageOrg) {
|
||||
StatusPageOrgInfo org = statusPageService.saveStatusPageOrg(statusPageOrg);
|
||||
return ResponseEntity.ok(Message.success(org));
|
||||
}
|
||||
|
||||
@GetMapping("/component")
|
||||
@Operation(summary = "Query Status Page Components")
|
||||
public ResponseEntity<Message<List<StatusPageComponent>>> queryStatusPageComponent() {
|
||||
List<StatusPageComponent> statusPageComponents = statusPageService.queryStatusPageComponents();
|
||||
return ResponseEntity.ok(Message.success(statusPageComponents));
|
||||
public ResponseEntity<Message<List<StatusPageComponentInfo>>> queryStatusPageComponent() {
|
||||
return ResponseEntity.ok(Message.success(statusPageService.queryStatusPageComponents()));
|
||||
}
|
||||
|
||||
@PostMapping("/component")
|
||||
@Operation(summary = "Save Status Page Component")
|
||||
public ResponseEntity<Message<Void>> newStatusPageComponent(@Valid @RequestBody StatusPageComponent statusPageComponent) {
|
||||
public ResponseEntity<Message<Void>> newStatusPageComponent(@Valid @RequestBody StatusPageComponentInfo statusPageComponent) {
|
||||
statusPageService.newStatusPageComponent(statusPageComponent);
|
||||
return ResponseEntity.ok(Message.success("Add success"));
|
||||
}
|
||||
|
||||
@PutMapping("/component")
|
||||
@Operation(summary = "Update Status Page Component")
|
||||
public ResponseEntity<Message<Void>> updateStatusPageComponent(@Valid @RequestBody StatusPageComponent statusPageComponent) {
|
||||
public ResponseEntity<Message<Void>> updateStatusPageComponent(@Valid @RequestBody StatusPageComponentInfo statusPageComponent) {
|
||||
statusPageService.updateStatusPageComponent(statusPageComponent);
|
||||
return ResponseEntity.ok(Message.success("Update success"));
|
||||
}
|
||||
@@ -102,21 +101,20 @@ public class StatusPageController {
|
||||
|
||||
@GetMapping("/component/{id}")
|
||||
@Operation(summary = "Query Status Page Component")
|
||||
public ResponseEntity<Message<StatusPageComponent>> queryStatusPageComponent(@PathVariable("id") final long id) {
|
||||
StatusPageComponent statusPageComponent = statusPageService.queryStatusPageComponent(id);
|
||||
return ResponseEntity.ok(Message.success(statusPageComponent));
|
||||
public ResponseEntity<Message<StatusPageComponentInfo>> queryStatusPageComponent(@PathVariable("id") final long id) {
|
||||
return ResponseEntity.ok(Message.success(statusPageService.queryStatusPageComponent(id)));
|
||||
}
|
||||
|
||||
@PostMapping("/incident")
|
||||
@Operation(summary = "Save Status Page Incident")
|
||||
public ResponseEntity<Message<Void>> newStatusPageIncident(@Valid @RequestBody StatusPageIncident incident) {
|
||||
public ResponseEntity<Message<Void>> newStatusPageIncident(@Valid @RequestBody StatusPageIncidentInfo incident) {
|
||||
statusPageService.newStatusPageIncident(incident);
|
||||
return ResponseEntity.ok(Message.success("Add success"));
|
||||
}
|
||||
|
||||
@PutMapping("/incident")
|
||||
@Operation(summary = "Update Status Page Incident")
|
||||
public ResponseEntity<Message<Void>> updateStatusPageIncident(@Valid @RequestBody StatusPageIncident incident) {
|
||||
public ResponseEntity<Message<Void>> updateStatusPageIncident(@Valid @RequestBody StatusPageIncidentInfo incident) {
|
||||
statusPageService.updateStatusPageIncident(incident);
|
||||
return ResponseEntity.ok(Message.success("Update success"));
|
||||
}
|
||||
@@ -130,20 +128,19 @@ public class StatusPageController {
|
||||
|
||||
@GetMapping("/incident/{id}")
|
||||
@Operation(summary = "Get Status Page Incident")
|
||||
public ResponseEntity<Message<StatusPageIncident>> queryStatusPageIncident(@PathVariable("id") final long id) {
|
||||
StatusPageIncident incident = statusPageService.queryStatusPageIncident(id);
|
||||
return ResponseEntity.ok(Message.success(incident));
|
||||
public ResponseEntity<Message<StatusPageIncidentInfo>> queryStatusPageIncident(@PathVariable("id") final long id) {
|
||||
return ResponseEntity.ok(Message.success(statusPageService.queryStatusPageIncident(id)));
|
||||
}
|
||||
|
||||
@GetMapping("/incident")
|
||||
@Operation(summary = "Query Status Page Incidents")
|
||||
public ResponseEntity<Message<Page<StatusPageIncident>>> queryStatusPageIncident(
|
||||
public ResponseEntity<Message<Page<StatusPageIncidentInfo>>> queryStatusPageIncident(
|
||||
@Parameter(description = "Search-Target", example = "x") @RequestParam(required = false) String search,
|
||||
@Parameter(description = "Start Time", example = "1756384301907") @RequestParam(required = false) Long startTime,
|
||||
@Parameter(description = "End Time", example = "1756384301907") @RequestParam(required = false) Long endTime,
|
||||
@Parameter(description = "List current page", example = "0") @RequestParam(defaultValue = "0") int pageIndex,
|
||||
@Parameter(description = "Number of list pages", example = "8") @RequestParam(defaultValue = "8") int pageSize) {
|
||||
Page<StatusPageIncident> incidents = statusPageService.queryStatusPageIncidents(search, startTime, endTime, pageIndex, pageSize);
|
||||
Page<StatusPageIncidentInfo> incidents = statusPageService.queryStatusPageIncidents(search, startTime, endTime, pageIndex, pageSize);
|
||||
return ResponseEntity.ok(Message.success(incidents));
|
||||
}
|
||||
}
|
||||
|
||||
+6
-6
@@ -27,9 +27,9 @@ import java.util.List;
|
||||
|
||||
import org.apache.hertzbeat.common.constants.CommonConstants;
|
||||
import org.apache.hertzbeat.common.entity.dto.Message;
|
||||
import org.apache.hertzbeat.common.entity.manager.StatusPageIncident;
|
||||
import org.apache.hertzbeat.common.entity.manager.StatusPageOrg;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.ComponentStatus;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.StatusPageIncidentInfo;
|
||||
import org.apache.hertzbeat.manager.pojo.dto.StatusPageOrgInfo;
|
||||
import org.apache.hertzbeat.manager.service.StatusPageService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -52,8 +52,8 @@ public class StatusPagePublicController {
|
||||
|
||||
@GetMapping("/org")
|
||||
@Operation(summary = "Query Status Page Organization")
|
||||
public ResponseEntity<Message<StatusPageOrg>> queryStatusPageOrg() {
|
||||
StatusPageOrg statusPageOrg = statusPageService.queryStatusPageOrg();
|
||||
public ResponseEntity<Message<StatusPageOrgInfo>> queryStatusPageOrg() {
|
||||
StatusPageOrgInfo statusPageOrg = statusPageService.queryStatusPageOrg();
|
||||
if (statusPageOrg == null) {
|
||||
return ResponseEntity.ok(Message.fail(CommonConstants.FAIL_CODE, "Status Page Organization Not Found"));
|
||||
}
|
||||
@@ -76,13 +76,13 @@ public class StatusPagePublicController {
|
||||
|
||||
@GetMapping("/incident")
|
||||
@Operation(summary = "Query Status Page Incidents")
|
||||
public ResponseEntity<Message<Page<StatusPageIncident>>> queryStatusPageIncident(
|
||||
public ResponseEntity<Message<Page<StatusPageIncidentInfo>>> queryStatusPageIncident(
|
||||
@Parameter(description = "Search-Target", example = "x") @RequestParam(required = false) String search,
|
||||
@Parameter(description = "Start Time", example = "1756384301907") @RequestParam(required = false) Long startTime,
|
||||
@Parameter(description = "End Time", example = "1756384301907") @RequestParam(required = false) Long endTime,
|
||||
@Parameter(description = "List current page", example = "0") @RequestParam(defaultValue = "0") int pageIndex,
|
||||
@Parameter(description = "Number of list pages", example = "10") @RequestParam(defaultValue = "10") int pageSize) {
|
||||
Page<StatusPageIncident> incidents = statusPageService.queryStatusPageIncidents(search, startTime, endTime, pageIndex, pageSize);
|
||||
Page<StatusPageIncidentInfo> incidents = statusPageService.queryStatusPageIncidents(search, startTime, endTime, pageIndex, pageSize);
|
||||
return ResponseEntity.ok(Message.success(incidents));
|
||||
}
|
||||
}
|
||||
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.pojo.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.apache.hertzbeat.common.entity.manager.Collector;
|
||||
|
||||
/**
|
||||
* Collector info view.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Schema(description = "collector info")
|
||||
public class CollectorInfo {
|
||||
|
||||
@Schema(title = "primary id", example = "2")
|
||||
private Long id;
|
||||
|
||||
@Schema(title = "collector identity name", description = "collector identity name")
|
||||
private String name;
|
||||
|
||||
@Schema(title = "collector ip", description = "collector remote ip")
|
||||
private String ip;
|
||||
|
||||
@Schema(title = "collector version", description = "collector version")
|
||||
private String version;
|
||||
|
||||
@Schema(title = "collector status: 0-online 1-offline")
|
||||
private byte status;
|
||||
|
||||
@Schema(title = "collector mode: public or private")
|
||||
private String mode;
|
||||
|
||||
@Schema(title = "The creator of this record", example = "tom")
|
||||
private String creator;
|
||||
|
||||
@Schema(title = "This record was last modified by")
|
||||
private String modifier;
|
||||
|
||||
@Schema(title = "This record creation time (millisecond timestamp)")
|
||||
private LocalDateTime gmtCreate;
|
||||
|
||||
@Schema(title = "Record the latest modification time (timestamp in milliseconds)")
|
||||
private LocalDateTime gmtUpdate;
|
||||
|
||||
public static CollectorInfo fromEntity(Collector collector) {
|
||||
return CollectorInfo.builder()
|
||||
.id(collector.getId())
|
||||
.name(collector.getName())
|
||||
.ip(collector.getIp())
|
||||
.version(collector.getVersion())
|
||||
.status(collector.getStatus())
|
||||
.mode(collector.getMode())
|
||||
.creator(collector.getCreator())
|
||||
.modifier(collector.getModifier())
|
||||
.gmtCreate(collector.getGmtCreate())
|
||||
.gmtUpdate(collector.getGmtUpdate())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
+6
-7
@@ -15,17 +15,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.common.entity.dto;
|
||||
package org.apache.hertzbeat.manager.pojo.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.apache.hertzbeat.common.entity.manager.Collector;
|
||||
|
||||
/**
|
||||
* collector summary
|
||||
* Collector summary view.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@@ -33,13 +32,13 @@ import org.apache.hertzbeat.common.entity.manager.Collector;
|
||||
@NoArgsConstructor
|
||||
@Schema(description = "collector summary")
|
||||
public class CollectorSummary {
|
||||
|
||||
|
||||
@Schema(description = "the collector info")
|
||||
private Collector collector;
|
||||
|
||||
private CollectorInfo collector;
|
||||
|
||||
@Schema(description = "the number of monitors pinned in this collector")
|
||||
private int pinMonitorNum;
|
||||
|
||||
|
||||
@Schema(description = "the number of monitors dispatched in this collector")
|
||||
private int dispatchMonitorNum;
|
||||
}
|
||||
+52
-11
@@ -17,26 +17,67 @@
|
||||
|
||||
package org.apache.hertzbeat.manager.pojo.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.util.List;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.apache.hertzbeat.common.entity.manager.StatusPageComponent;
|
||||
import org.apache.hertzbeat.common.entity.manager.StatusPageHistory;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* status page's component status dto
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Schema(description = "Status Page's Component Status")
|
||||
public class ComponentStatus {
|
||||
|
||||
public class ComponentStatus {
|
||||
|
||||
private StatusPageComponentInfo componentInfo;
|
||||
|
||||
private List<StatusPageHistoryInfo> historyItems;
|
||||
|
||||
@Schema(description = "Component Info")
|
||||
private StatusPageComponent info;
|
||||
|
||||
@JsonProperty("info")
|
||||
public StatusPageComponentInfo getComponentInfo() {
|
||||
return componentInfo;
|
||||
}
|
||||
|
||||
@JsonProperty("info")
|
||||
public void setComponentInfo(StatusPageComponentInfo componentInfo) {
|
||||
this.componentInfo = componentInfo;
|
||||
}
|
||||
|
||||
@Schema(description = "Component History")
|
||||
private List<StatusPageHistory> history;
|
||||
@JsonProperty("history")
|
||||
public List<StatusPageHistoryInfo> getHistoryItems() {
|
||||
return historyItems;
|
||||
}
|
||||
|
||||
@JsonProperty("history")
|
||||
public void setHistoryItems(List<StatusPageHistoryInfo> historyItems) {
|
||||
this.historyItems = historyItems;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public StatusPageComponent getInfo() {
|
||||
return componentInfo == null ? null : componentInfo.toEntity();
|
||||
}
|
||||
|
||||
public void setInfo(StatusPageComponent info) {
|
||||
this.componentInfo = StatusPageComponentInfo.fromEntity(info);
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public List<StatusPageHistory> getHistory() {
|
||||
return historyItems == null ? null : historyItems.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(StatusPageHistoryInfo::toEntity)
|
||||
.toList();
|
||||
}
|
||||
|
||||
public void setHistory(List<StatusPageHistory> history) {
|
||||
this.historyItems = history == null ? null : history.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(StatusPageHistoryInfo::fromEntity)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
+78
-15
@@ -19,45 +19,108 @@ package org.apache.hertzbeat.manager.pojo.dto;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY;
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_WRITE;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.apache.hertzbeat.common.entity.grafana.GrafanaDashboard;
|
||||
import org.apache.hertzbeat.common.entity.manager.Monitor;
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Monitoring Information External Interaction Entities
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "Monitoring information entities")
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class MonitorDto {
|
||||
|
||||
@Schema(description = "monitor content", accessMode = READ_WRITE)
|
||||
@NotNull
|
||||
@Valid
|
||||
private Monitor monitor;
|
||||
private MonitorInfo monitorInfo;
|
||||
|
||||
@Schema(description = "monitor params", accessMode = READ_WRITE)
|
||||
@NotEmpty
|
||||
@Valid
|
||||
private List<Param> params;
|
||||
private List<MonitorParam> paramInfos;
|
||||
|
||||
@Schema(description = "Monitor Metrics", accessMode = READ_ONLY)
|
||||
private List<MetricsInfo> metrics;
|
||||
|
||||
@Schema(description = "pinned collector, default null if system dispatch", accessMode = READ_WRITE)
|
||||
private String collector;
|
||||
|
||||
@Schema(description = "grafana dashboard")
|
||||
private GrafanaDashboard grafanaDashboard;
|
||||
|
||||
@Schema(description = "monitor content", accessMode = READ_WRITE)
|
||||
@JsonProperty("monitor")
|
||||
public MonitorInfo getMonitorInfo() {
|
||||
return monitorInfo;
|
||||
}
|
||||
|
||||
@JsonProperty("monitor")
|
||||
public void setMonitorInfo(MonitorInfo monitorInfo) {
|
||||
this.monitorInfo = monitorInfo;
|
||||
}
|
||||
|
||||
@Schema(description = "monitor params", accessMode = READ_WRITE)
|
||||
@JsonProperty("params")
|
||||
public List<MonitorParam> getParamInfos() {
|
||||
return paramInfos;
|
||||
}
|
||||
|
||||
@JsonProperty("params")
|
||||
public void setParamInfos(List<MonitorParam> paramInfos) {
|
||||
this.paramInfos = paramInfos;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public Monitor getMonitor() {
|
||||
return monitorInfo == null ? null : monitorInfo.toEntity();
|
||||
}
|
||||
|
||||
public void setMonitor(Monitor monitor) {
|
||||
this.monitorInfo = MonitorInfo.fromEntity(monitor);
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public List<Param> getParams() {
|
||||
return paramInfos == null ? null : paramInfos.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(MonitorParam::toEntity)
|
||||
.toList();
|
||||
}
|
||||
|
||||
public void setParams(List<Param> params) {
|
||||
this.paramInfos = params == null ? null : params.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(MonitorParam::fromEntity)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Schema(description = "Monitor Metrics", accessMode = READ_ONLY)
|
||||
public List<MetricsInfo> getMetrics() {
|
||||
return metrics;
|
||||
}
|
||||
|
||||
public void setMetrics(List<MetricsInfo> metrics) {
|
||||
this.metrics = metrics;
|
||||
}
|
||||
|
||||
@Schema(description = "pinned collector, default null if system dispatch", accessMode = READ_WRITE)
|
||||
public String getCollector() {
|
||||
return collector;
|
||||
}
|
||||
|
||||
public void setCollector(String collector) {
|
||||
this.collector = collector;
|
||||
}
|
||||
|
||||
@Schema(description = "grafana dashboard")
|
||||
public GrafanaDashboard getGrafanaDashboard() {
|
||||
return grafanaDashboard;
|
||||
}
|
||||
|
||||
public void setGrafanaDashboard(GrafanaDashboard grafanaDashboard) {
|
||||
this.grafanaDashboard = grafanaDashboard;
|
||||
}
|
||||
}
|
||||
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.pojo.dto;
|
||||
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.apache.hertzbeat.common.entity.manager.Monitor;
|
||||
import org.apache.hertzbeat.common.support.valid.HostValid;
|
||||
|
||||
/**
|
||||
* Manager-side monitor DTO detached from JPA annotations.
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class MonitorInfo {
|
||||
|
||||
private Long id;
|
||||
|
||||
private Long jobId;
|
||||
|
||||
@Size(max = 100)
|
||||
private String name;
|
||||
|
||||
@Size(max = 100)
|
||||
private String app;
|
||||
|
||||
@Size(max = 100)
|
||||
private String scrape;
|
||||
|
||||
@Size(max = 100)
|
||||
@HostValid
|
||||
private String instance;
|
||||
|
||||
@Min(10)
|
||||
private Integer intervals;
|
||||
|
||||
@Size(max = 20)
|
||||
private String scheduleType;
|
||||
|
||||
@Size(max = 100)
|
||||
private String cronExpression;
|
||||
|
||||
@Min(0)
|
||||
@Max(4)
|
||||
private byte status;
|
||||
|
||||
private byte type;
|
||||
|
||||
private Map<String, String> labels;
|
||||
|
||||
private Map<String, String> annotations;
|
||||
|
||||
@Size(max = 255)
|
||||
private String description;
|
||||
|
||||
private String creator;
|
||||
|
||||
private String modifier;
|
||||
|
||||
private LocalDateTime gmtCreate;
|
||||
|
||||
private LocalDateTime gmtUpdate;
|
||||
|
||||
public static MonitorInfo fromEntity(Monitor monitor) {
|
||||
if (monitor == null) {
|
||||
return null;
|
||||
}
|
||||
MonitorInfo info = new MonitorInfo();
|
||||
info.setId(monitor.getId());
|
||||
info.setJobId(monitor.getJobId());
|
||||
info.setName(monitor.getName());
|
||||
info.setApp(monitor.getApp());
|
||||
info.setScrape(monitor.getScrape());
|
||||
info.setInstance(monitor.getInstance());
|
||||
info.setIntervals(monitor.getIntervals());
|
||||
info.setScheduleType(monitor.getScheduleType());
|
||||
info.setCronExpression(monitor.getCronExpression());
|
||||
info.setStatus(monitor.getStatus());
|
||||
info.setType(monitor.getType());
|
||||
info.setLabels(monitor.getLabels());
|
||||
info.setAnnotations(monitor.getAnnotations());
|
||||
info.setDescription(monitor.getDescription());
|
||||
info.setCreator(monitor.getCreator());
|
||||
info.setModifier(monitor.getModifier());
|
||||
info.setGmtCreate(monitor.getGmtCreate());
|
||||
info.setGmtUpdate(monitor.getGmtUpdate());
|
||||
return info;
|
||||
}
|
||||
|
||||
public Monitor toEntity() {
|
||||
Monitor monitor = new Monitor();
|
||||
monitor.setId(id);
|
||||
monitor.setJobId(jobId);
|
||||
monitor.setName(name);
|
||||
monitor.setApp(app);
|
||||
monitor.setScrape(scrape);
|
||||
monitor.setInstance(instance);
|
||||
monitor.setIntervals(intervals);
|
||||
monitor.setScheduleType(scheduleType);
|
||||
monitor.setCronExpression(cronExpression);
|
||||
monitor.setStatus(status);
|
||||
monitor.setType(type);
|
||||
monitor.setLabels(labels);
|
||||
monitor.setAnnotations(annotations);
|
||||
monitor.setDescription(description);
|
||||
monitor.setCreator(creator);
|
||||
monitor.setModifier(modifier);
|
||||
monitor.setGmtCreate(gmtCreate);
|
||||
monitor.setGmtUpdate(gmtUpdate);
|
||||
return monitor;
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.pojo.dto;
|
||||
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.apache.hertzbeat.common.entity.manager.Param;
|
||||
|
||||
/**
|
||||
* Manager-side monitor param DTO detached from JPA annotations.
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class MonitorParam {
|
||||
|
||||
private Long id;
|
||||
|
||||
private Long monitorId;
|
||||
|
||||
@Size(max = 100)
|
||||
@NotBlank(message = "field can not null")
|
||||
private String field;
|
||||
|
||||
@Size(max = 8126)
|
||||
private String paramValue;
|
||||
|
||||
@Min(0)
|
||||
private byte type;
|
||||
|
||||
private LocalDateTime gmtCreate;
|
||||
|
||||
private LocalDateTime gmtUpdate;
|
||||
|
||||
public static MonitorParam fromEntity(Param param) {
|
||||
if (param == null) {
|
||||
return null;
|
||||
}
|
||||
MonitorParam monitorParam = new MonitorParam();
|
||||
monitorParam.setId(param.getId());
|
||||
monitorParam.setMonitorId(param.getMonitorId());
|
||||
monitorParam.setField(param.getField());
|
||||
monitorParam.setParamValue(param.getParamValue());
|
||||
monitorParam.setType(param.getType());
|
||||
monitorParam.setGmtCreate(param.getGmtCreate());
|
||||
monitorParam.setGmtUpdate(param.getGmtUpdate());
|
||||
return monitorParam;
|
||||
}
|
||||
|
||||
public Param toEntity() {
|
||||
Param param = new Param();
|
||||
param.setId(id);
|
||||
param.setMonitorId(monitorId);
|
||||
param.setField(field);
|
||||
param.setParamValue(paramValue);
|
||||
param.setType(type);
|
||||
param.setGmtCreate(gmtCreate);
|
||||
param.setGmtUpdate(gmtUpdate);
|
||||
return param;
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -21,7 +21,6 @@ import java.util.List;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
|
||||
/**
|
||||
* Parameters define transfer entities
|
||||
@@ -33,5 +32,5 @@ public class ParamDefineDto {
|
||||
|
||||
private String app;
|
||||
|
||||
private List<ParamDefine> param;
|
||||
private List<ParamDefineInfo> param;
|
||||
}
|
||||
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hertzbeat.manager.pojo.dto;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.apache.hertzbeat.common.entity.job.RuntimeParamDefine;
|
||||
import org.apache.hertzbeat.common.entity.manager.ParamDefine;
|
||||
|
||||
/**
|
||||
* Manager-side parameter definition DTO detached from JPA annotations.
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ParamDefineInfo {
|
||||
|
||||
private Long id;
|
||||
|
||||
private String app;
|
||||
|
||||
private Map<String, String> name;
|
||||
|
||||
private String field;
|
||||
|
||||
private String type;
|
||||
|
||||
private boolean required;
|
||||
|
||||
private String defaultValue;
|
||||
|
||||
private String placeholder;
|
||||
|
||||
private String range;
|
||||
|
||||
private Short limit;
|
||||
|
||||
private List<OptionInfo> options;
|
||||
|
||||
private String keyAlias;
|
||||
|
||||
private String valueAlias;
|
||||
|
||||
private boolean hide;
|
||||
|
||||
private String creator;
|
||||
|
||||
private String modifier;
|
||||
|
||||
private LocalDateTime gmtCreate;
|
||||
|
||||
private LocalDateTime gmtUpdate;
|
||||
|
||||
private Map<String, List<Object>> depend;
|
||||
|
||||
public static ParamDefineInfo fromEntity(ParamDefine paramDefine) {
|
||||
if (paramDefine == null) {
|
||||
return null;
|
||||
}
|
||||
ParamDefineInfo info = new ParamDefineInfo();
|
||||
info.setId(paramDefine.getId());
|
||||
info.setApp(paramDefine.getApp());
|
||||
info.setName(paramDefine.getName());
|
||||
info.setField(paramDefine.getField());
|
||||
info.setType(paramDefine.getType());
|
||||
info.setRequired(paramDefine.isRequired());
|
||||
info.setDefaultValue(paramDefine.getDefaultValue());
|
||||
info.setPlaceholder(paramDefine.getPlaceholder());
|
||||
info.setRange(paramDefine.getRange());
|
||||
info.setLimit(paramDefine.getLimit());
|
||||
info.setOptions(paramDefine.getOptions() == null ? null
|
||||
: paramDefine.getOptions().stream().map(OptionInfo::fromEntity).toList());
|
||||
info.setKeyAlias(paramDefine.getKeyAlias());
|
||||
info.setValueAlias(paramDefine.getValueAlias());
|
||||
info.setHide(paramDefine.isHide());
|
||||
info.setCreator(paramDefine.getCreator());
|
||||
info.setModifier(paramDefine.getModifier());
|
||||
info.setGmtCreate(paramDefine.getGmtCreate());
|
||||
info.setGmtUpdate(paramDefine.getGmtUpdate());
|
||||
info.setDepend(paramDefine.getDepend());
|
||||
return info;
|
||||
}
|
||||
|
||||
public static ParamDefineInfo fromRuntime(RuntimeParamDefine runtimeParamDefine) {
|
||||
if (runtimeParamDefine == null) {
|
||||
return null;
|
||||
}
|
||||
ParamDefineInfo info = new ParamDefineInfo();
|
||||
info.setApp(runtimeParamDefine.getApp());
|
||||
info.setName(runtimeParamDefine.getName());
|
||||
info.setField(runtimeParamDefine.getField());
|
||||
info.setType(runtimeParamDefine.getType());
|
||||
info.setRequired(runtimeParamDefine.isRequired());
|
||||
info.setDefaultValue(runtimeParamDefine.getDefaultValue());
|
||||
info.setPlaceholder(runtimeParamDefine.getPlaceholder());
|
||||
info.setRange(runtimeParamDefine.getRange());
|
||||
info.setLimit(runtimeParamDefine.getLimit());
|
||||
info.setOptions(runtimeParamDefine.getOptions() == null ? null
|
||||
: runtimeParamDefine.getOptions().stream().map(OptionInfo::fromRuntime).toList());
|
||||
info.setKeyAlias(runtimeParamDefine.getKeyAlias());
|
||||
info.setValueAlias(runtimeParamDefine.getValueAlias());
|
||||
info.setHide(runtimeParamDefine.isHide());
|
||||
info.setDepend(runtimeParamDefine.getDepend());
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* DTO version of parameter options.
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class OptionInfo {
|
||||
private String label;
|
||||
private String value;
|
||||
|
||||
public static OptionInfo fromEntity(ParamDefine.Option option) {
|
||||
return option == null ? null : new OptionInfo(option.getLabel(), option.getValue());
|
||||
}
|
||||
|
||||
public static OptionInfo fromRuntime(RuntimeParamDefine.Option option) {
|
||||
return option == null ? null : new OptionInfo(option.getLabel(), option.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user