diff --git a/monitoring/pom.xml b/monitoring/pom.xml
new file mode 100644
index 0000000000..429df521b6
--- /dev/null
+++ b/monitoring/pom.xml
@@ -0,0 +1,160 @@
+
+
+
+ 4.0.0
+
+ org.thingsboard
+ 3.5.0-SNAPSHOT
+ thingsboard
+
+
+ monitoring
+ ThingsBoard Monitoring Service
+ jar
+
+
+ UTF-8
+ ${basedir}/..
+ java
+ false
+ process-resources
+ package
+ tb-monitoring
+ false
+ ${project.build.directory}/windows
+ ThingsBoard Monitoring Service
+ org.thingsboard.monitoring.ThingsboardMonitoringApplication
+
+
+
+
+ org.thingsboard.common
+ data
+
+
+ org.thingsboard.common
+ util
+
+
+ org.thingsboard
+ rest-client
+ compile
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+ org.eclipse.californium
+ californium-core
+
+
+ org.eclipse.californium
+ scandium
+
+
+ org.eclipse.paho
+ org.eclipse.paho.client.mqttv3
+
+
+ org.apache.httpcomponents
+ httpclient
+
+
+ org.java-websocket
+ Java-WebSocket
+ compile
+
+
+ com.google.guava
+ guava
+
+
+ org.apache.commons
+ commons-lang3
+
+
+ org.slf4j
+ slf4j-api
+
+
+ org.slf4j
+ log4j-over-slf4j
+
+
+ ch.qos.logback
+ logback-core
+
+
+ ch.qos.logback
+ logback-classic
+
+
+
+
+ ${pkg.name}-${project.version}
+
+
+ ${project.basedir}/src/main/resources
+
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ org.thingsboard
+ gradle-maven-plugin
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+
+
+ org.apache.maven.plugins
+ maven-install-plugin
+
+
+
+
+
+ jenkins
+ Jenkins Repository
+ https://repo.jenkins-ci.org/releases
+
+ false
+
+
+
+
diff --git a/monitoring/src/main/conf/logback.xml b/monitoring/src/main/conf/logback.xml
new file mode 100644
index 0000000000..58e49c7639
--- /dev/null
+++ b/monitoring/src/main/conf/logback.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+ ${pkg.logFolder}/${pkg.name}.log
+
+ ${pkg.logFolder}/${pkg.name}.%d{yyyy-MM-dd}.%i.log
+ 100MB
+ 30
+ 3GB
+
+
+ %d{ISO8601} [%thread] %-5level %logger{36} - %msg%n
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/monitoring/src/main/conf/tb-monitoring.conf b/monitoring/src/main/conf/tb-monitoring.conf
new file mode 100644
index 0000000000..d1d729bd20
--- /dev/null
+++ b/monitoring/src/main/conf/tb-monitoring.conf
@@ -0,0 +1,22 @@
+#
+# Copyright © 2016-2023 The Thingsboard Authors
+#
+# Licensed 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.
+#
+
+export JAVA_OPTS="$JAVA_OPTS -Xlog:gc*,heap*,age*,safepoint=debug:file=/var/log/tb-monitoring/gc.log:time,uptime,level,tags:filecount=10,filesize=10M"
+export JAVA_OPTS="$JAVA_OPTS -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError"
+export JAVA_OPTS="$JAVA_OPTS -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark"
+export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=500 -XX:+UseStringDeduplication -XX:+ParallelRefProcEnabled -XX:MaxTenuringThreshold=10"
+export LOG_FILENAME=tb-monitoring.out
+export LOADER_PATH=/usr/share/tb-monitoring/conf
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/ThingsboardMonitoringApplication.java b/monitoring/src/main/java/org/thingsboard/monitoring/ThingsboardMonitoringApplication.java
new file mode 100644
index 0000000000..3fa43a8175
--- /dev/null
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/ThingsboardMonitoringApplication.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright © 2016-2023 The Thingsboard Authors
+ *
+ * Licensed 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.thingsboard.monitoring;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.thingsboard.common.util.ThingsBoardThreadFactory;
+
+import java.util.Map;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+
+@SpringBootApplication
+@EnableScheduling
+@Slf4j
+public class ThingsboardMonitoringApplication {
+
+ public static void main(String[] args) {
+ new SpringApplicationBuilder(ThingsboardMonitoringApplication.class)
+ .properties(Map.of("spring.config.name", "tb-monitoring"))
+ .run(args);
+ }
+
+}
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/client/TbClient.java b/monitoring/src/main/java/org/thingsboard/monitoring/client/TbClient.java
new file mode 100644
index 0000000000..9dcc58cf2f
--- /dev/null
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/client/TbClient.java
@@ -0,0 +1,49 @@
+/**
+ * Copyright © 2016-2023 The Thingsboard Authors
+ *
+ * Licensed 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.thingsboard.monitoring.client;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+import org.thingsboard.rest.client.RestClient;
+
+import java.time.Duration;
+
+@Component
+@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
+public class TbClient extends RestClient {
+
+ @Value("${monitoring.rest.username}")
+ private String username;
+ @Value("${monitoring.rest.password}")
+ private String password;
+
+ public TbClient(@Value("${monitoring.rest.base_url}") String baseUrl,
+ @Value("${monitoring.rest.request_timeout_ms}") int requestTimeoutMs) {
+ super(new RestTemplateBuilder()
+ .setConnectTimeout(Duration.ofMillis(requestTimeoutMs))
+ .setReadTimeout(Duration.ofMillis(requestTimeoutMs))
+ .build(), baseUrl);
+ }
+
+ public String logIn() {
+ login(username, password);
+ return getToken();
+ }
+
+}
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/client/WsClient.java b/monitoring/src/main/java/org/thingsboard/monitoring/client/WsClient.java
new file mode 100644
index 0000000000..20e7a900ff
--- /dev/null
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/client/WsClient.java
@@ -0,0 +1,194 @@
+/**
+ * Copyright © 2016-2023 The Thingsboard Authors
+ *
+ * Licensed 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.thingsboard.monitoring.client;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.RandomUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.java_websocket.client.WebSocketClient;
+import org.java_websocket.handshake.ServerHandshake;
+import org.thingsboard.common.util.JacksonUtil;
+import org.thingsboard.monitoring.data.cmd.CmdsWrapper;
+import org.thingsboard.monitoring.data.cmd.EntityDataCmd;
+import org.thingsboard.monitoring.data.cmd.EntityDataUpdate;
+import org.thingsboard.monitoring.data.cmd.LatestValueCmd;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.query.EntityDataPageLink;
+import org.thingsboard.server.common.data.query.EntityDataQuery;
+import org.thingsboard.server.common.data.query.EntityKey;
+import org.thingsboard.server.common.data.query.EntityKeyType;
+import org.thingsboard.server.common.data.query.EntityListFilter;
+
+import javax.net.ssl.SSLParameters;
+import java.net.URI;
+import java.nio.channels.NotYetConnectedException;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.stream.Collectors;
+
+@Slf4j
+public class WsClient extends WebSocketClient implements AutoCloseable {
+
+ public volatile JsonNode lastMsg;
+ private CountDownLatch reply;
+ private CountDownLatch update;
+
+ private final Lock updateLock = new ReentrantLock();
+
+ private long requestTimeoutMs;
+
+ public WsClient(URI serverUri, long requestTimeoutMs) {
+ super(serverUri);
+ this.requestTimeoutMs = requestTimeoutMs;
+ }
+
+ @Override
+ public void onOpen(ServerHandshake serverHandshake) {
+
+ }
+
+ @Override
+ public void onMessage(String s) {
+ if (s == null) {
+ return;
+ }
+ updateLock.lock();
+ try {
+ lastMsg = JacksonUtil.toJsonNode(s);
+ log.trace("Received new msg: {}", lastMsg.toPrettyString());
+ if (update != null) {
+ update.countDown();
+ }
+ if (reply != null) {
+ reply.countDown();
+ }
+ } finally {
+ updateLock.unlock();
+ }
+ }
+
+ @Override
+ public void onClose(int i, String s, boolean b) {
+ log.debug("WebSocket client is closed");
+ }
+
+ @Override
+ public void onError(Exception e) {
+ log.error("WebSocket client error:", e);
+ }
+
+ public void registerWaitForUpdate() {
+ updateLock.lock();
+ try {
+ lastMsg = null;
+ update = new CountDownLatch(1);
+ } finally {
+ updateLock.unlock();
+ }
+ log.trace("Registered wait for update");
+ }
+
+ @Override
+ public void send(String text) throws NotYetConnectedException {
+ updateLock.lock();
+ try {
+ reply = new CountDownLatch(1);
+ } finally {
+ updateLock.unlock();
+ }
+ super.send(text);
+ }
+
+ public WsClient subscribeForTelemetry(List devices, String key) {
+ EntityDataCmd cmd = new EntityDataCmd();
+ cmd.setCmdId(RandomUtils.nextInt(0, 1000));
+
+ EntityListFilter devicesFilter = new EntityListFilter();
+ devicesFilter.setEntityType(EntityType.DEVICE);
+ devicesFilter.setEntityList(devices.stream().map(UUID::toString).collect(Collectors.toList()));
+ EntityDataPageLink pageLink = new EntityDataPageLink(100,0, null, null);
+ EntityDataQuery devicesQuery = new EntityDataQuery(devicesFilter, pageLink, Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
+ cmd.setQuery(devicesQuery);
+
+ LatestValueCmd latestCmd = new LatestValueCmd();
+ latestCmd.setKeys(List.of(new EntityKey(EntityKeyType.TIME_SERIES, key)));
+ cmd.setLatestCmd(latestCmd);
+
+ CmdsWrapper wrapper = new CmdsWrapper();
+ wrapper.setEntityDataCmds(List.of(cmd));
+ send(JacksonUtil.toString(wrapper));
+ return this;
+ }
+
+ public JsonNode waitForUpdate(long ms) {
+ log.trace("update latch count: {}", update.getCount());
+ try {
+ if (update.await(ms, TimeUnit.MILLISECONDS)) {
+ log.trace("Waited for update");
+ return getLastMsg();
+ }
+ } catch (InterruptedException e) {
+ log.debug("Failed to await reply", e);
+ }
+ log.trace("No update arrived within {} ms", ms);
+ return null;
+ }
+
+ public JsonNode waitForReply() {
+ try {
+ if (reply.await(requestTimeoutMs, TimeUnit.MILLISECONDS)) {
+ log.trace("Waited for reply");
+ return getLastMsg();
+ }
+ } catch (InterruptedException e) {
+ log.debug("Failed to await reply", e);
+ }
+ log.trace("No reply arrived within {} ms", requestTimeoutMs);
+ throw new IllegalStateException("No WS reply arrived within " + requestTimeoutMs + " ms");
+ }
+
+ private JsonNode getLastMsg() {
+ if (lastMsg != null) {
+ JsonNode errorMsg = lastMsg.get("errorMsg");
+ if (errorMsg != null && !errorMsg.isNull() && StringUtils.isNotEmpty(errorMsg.asText())) {
+ throw new RuntimeException("WS error from server: " + errorMsg.asText());
+ } else {
+ return lastMsg;
+ }
+ } else {
+ return null;
+ }
+ }
+
+ public Object getTelemetryUpdate(UUID deviceId, String key) {
+ JsonNode lastMsg = getLastMsg();
+ if (lastMsg == null || lastMsg.isNull()) return null;
+ EntityDataUpdate update = JacksonUtil.treeToValue(lastMsg, EntityDataUpdate.class);
+ return update.getLatest(deviceId, key);
+ }
+
+ @Override
+ protected void onSetSSLParameters(SSLParameters sslParameters) {
+ sslParameters.setEndpointIdentificationAlgorithm(null);
+ }
+
+}
\ No newline at end of file
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/client/WsClientFactory.java b/monitoring/src/main/java/org/thingsboard/monitoring/client/WsClientFactory.java
new file mode 100644
index 0000000000..1bab803dc9
--- /dev/null
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/client/WsClientFactory.java
@@ -0,0 +1,59 @@
+/**
+ * Copyright © 2016-2023 The Thingsboard Authors
+ *
+ * Licensed 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.thingsboard.monitoring.client;
+
+import lombok.RequiredArgsConstructor;
+import org.apache.http.ssl.SSLContextBuilder;
+import org.apache.http.ssl.SSLContexts;
+import org.apache.http.ssl.TrustStrategy;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.thingsboard.monitoring.data.Latencies;
+import org.thingsboard.monitoring.service.MonitoringReporter;
+import org.thingsboard.monitoring.util.TbStopWatch;
+
+import java.net.URI;
+import java.util.concurrent.TimeUnit;
+
+@Component
+@RequiredArgsConstructor
+public class WsClientFactory {
+
+ private final MonitoringReporter monitoringReporter;
+ private final TbStopWatch stopWatch;
+ @Value("${monitoring.ws.base_url}")
+ private String baseUrl;
+ @Value("${monitoring.ws.request_timeout_ms}")
+ private int requestTimeoutMs;
+
+ public WsClient createClient(String accessToken) throws Exception {
+ URI uri = new URI(baseUrl + "/api/ws/plugins/telemetry?token=" + accessToken);
+ stopWatch.start();
+ WsClient wsClient = new WsClient(uri, requestTimeoutMs);
+ if (baseUrl.startsWith("wss")) {
+ SSLContextBuilder builder = SSLContexts.custom();
+ builder.loadTrustMaterial(null, (TrustStrategy) (chain, authType) -> true);
+ wsClient.setSocketFactory(builder.build().getSocketFactory());
+ }
+ boolean connected = wsClient.connectBlocking(requestTimeoutMs, TimeUnit.MILLISECONDS);
+ if (!connected) {
+ throw new IllegalStateException("Failed to establish WS session");
+ }
+ monitoringReporter.reportLatency(Latencies.WS_CONNECT, stopWatch.getTime());
+ return wsClient;
+ }
+
+}
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/config/DeviceConfig.java b/monitoring/src/main/java/org/thingsboard/monitoring/config/DeviceConfig.java
new file mode 100644
index 0000000000..548ad6d08b
--- /dev/null
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/config/DeviceConfig.java
@@ -0,0 +1,34 @@
+/**
+ * Copyright © 2016-2023 The Thingsboard Authors
+ *
+ * Licensed 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.thingsboard.monitoring.config;
+
+import lombok.Data;
+import org.apache.commons.lang3.StringUtils;
+import org.thingsboard.server.common.data.security.DeviceCredentials;
+
+import java.util.UUID;
+
+@Data
+public class DeviceConfig {
+
+ private UUID id;
+ private DeviceCredentials credentials;
+
+ public void setId(String id) {
+ this.id = StringUtils.isNotEmpty(id) ? UUID.fromString(id) : null;
+ }
+
+}
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/config/MonitoringTargetConfig.java b/monitoring/src/main/java/org/thingsboard/monitoring/config/MonitoringTargetConfig.java
new file mode 100644
index 0000000000..5f1ab49e91
--- /dev/null
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/config/MonitoringTargetConfig.java
@@ -0,0 +1,26 @@
+/**
+ * Copyright © 2016-2023 The Thingsboard Authors
+ *
+ * Licensed 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.thingsboard.monitoring.config;
+
+import lombok.Data;
+
+@Data
+public class MonitoringTargetConfig {
+
+ private String baseUrl;
+ private DeviceConfig device;
+
+}
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/config/TransportType.java b/monitoring/src/main/java/org/thingsboard/monitoring/config/TransportType.java
new file mode 100644
index 0000000000..ed0a4ea331
--- /dev/null
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/config/TransportType.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright © 2016-2023 The Thingsboard Authors
+ *
+ * Licensed 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.thingsboard.monitoring.config;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.thingsboard.monitoring.transport.TransportHealthChecker;
+import org.thingsboard.monitoring.transport.impl.CoapTransportHealthChecker;
+import org.thingsboard.monitoring.transport.impl.HttpTransportHealthChecker;
+import org.thingsboard.monitoring.transport.impl.MqttTransportHealthChecker;
+
+@AllArgsConstructor
+@Getter
+public enum TransportType {
+
+ MQTT(MqttTransportHealthChecker.class),
+ COAP(CoapTransportHealthChecker.class),
+ HTTP(HttpTransportHealthChecker.class);
+
+ private final Class extends TransportHealthChecker>> serviceClass;
+
+}
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/config/service/CoapTransportMonitoringConfig.java b/monitoring/src/main/java/org/thingsboard/monitoring/config/service/CoapTransportMonitoringConfig.java
new file mode 100644
index 0000000000..905858b4eb
--- /dev/null
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/config/service/CoapTransportMonitoringConfig.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright © 2016-2023 The Thingsboard Authors
+ *
+ * Licensed 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.thingsboard.monitoring.config.service;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+import org.thingsboard.monitoring.config.TransportType;
+
+@Component
+@ConditionalOnProperty(name = "monitoring.transports.coap.enabled", havingValue = "true")
+@ConfigurationProperties(prefix = "monitoring.transports.coap")
+public class CoapTransportMonitoringConfig extends TransportMonitoringConfig {
+
+ @Override
+ public TransportType getTransportType() {
+ return TransportType.COAP;
+ }
+
+}
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/config/service/HttpTransportMonitoringConfig.java b/monitoring/src/main/java/org/thingsboard/monitoring/config/service/HttpTransportMonitoringConfig.java
new file mode 100644
index 0000000000..3a3e8f612c
--- /dev/null
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/config/service/HttpTransportMonitoringConfig.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright © 2016-2023 The Thingsboard Authors
+ *
+ * Licensed 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.thingsboard.monitoring.config.service;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+import org.thingsboard.monitoring.config.TransportType;
+
+@Component
+@ConditionalOnProperty(name = "monitoring.transports.http.enabled", havingValue = "true")
+@ConfigurationProperties(prefix = "monitoring.transports.http")
+public class HttpTransportMonitoringConfig extends TransportMonitoringConfig {
+
+ @Override
+ public TransportType getTransportType() {
+ return TransportType.HTTP;
+ }
+
+}
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/config/service/MqttTransportMonitoringConfig.java b/monitoring/src/main/java/org/thingsboard/monitoring/config/service/MqttTransportMonitoringConfig.java
new file mode 100644
index 0000000000..7fef5b95dc
--- /dev/null
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/config/service/MqttTransportMonitoringConfig.java
@@ -0,0 +1,39 @@
+/**
+ * Copyright © 2016-2023 The Thingsboard Authors
+ *
+ * Licensed 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.thingsboard.monitoring.config.service;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+import org.thingsboard.monitoring.config.TransportType;
+
+@Component
+@ConditionalOnProperty(name = "monitoring.transports.mqtt.enabled", havingValue = "true")
+@ConfigurationProperties(prefix = "monitoring.transports.mqtt")
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class MqttTransportMonitoringConfig extends TransportMonitoringConfig {
+
+ private Integer qos;
+
+ @Override
+ public TransportType getTransportType() {
+ return TransportType.MQTT;
+ }
+
+}
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/config/service/TransportMonitoringConfig.java b/monitoring/src/main/java/org/thingsboard/monitoring/config/service/TransportMonitoringConfig.java
new file mode 100644
index 0000000000..0712d1d919
--- /dev/null
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/config/service/TransportMonitoringConfig.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright © 2016-2023 The Thingsboard Authors
+ *
+ * Licensed 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.thingsboard.monitoring.config.service;
+
+import lombok.Data;
+import org.thingsboard.monitoring.config.MonitoringTargetConfig;
+import org.thingsboard.monitoring.config.TransportType;
+
+import java.util.List;
+
+@Data
+public abstract class TransportMonitoringConfig {
+
+ private int requestTimeoutMs;
+
+ private List targets;
+
+ public abstract TransportType getTransportType();
+
+}
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/data/Latencies.java b/monitoring/src/main/java/org/thingsboard/monitoring/data/Latencies.java
new file mode 100644
index 0000000000..8141c34586
--- /dev/null
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/data/Latencies.java
@@ -0,0 +1,30 @@
+/**
+ * Copyright © 2016-2023 The Thingsboard Authors
+ *
+ * Licensed 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.thingsboard.monitoring.data;
+
+import org.thingsboard.monitoring.config.TransportType;
+
+public class Latencies {
+
+ public static final String WS_UPDATE = "wsUpdate";
+ public static final String WS_CONNECT = "wsConnect";
+ public static final String LOG_IN = "logIn";
+
+ public static String transportRequest(TransportType transportType) {
+ return String.format("%sTransportRequest", transportType.name().toLowerCase());
+ }
+
+}
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/data/Latency.java b/monitoring/src/main/java/org/thingsboard/monitoring/data/Latency.java
new file mode 100644
index 0000000000..c64291e30f
--- /dev/null
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/data/Latency.java
@@ -0,0 +1,67 @@
+/**
+ * Copyright © 2016-2023 The Thingsboard Authors
+ *
+ * Licensed 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.thingsboard.monitoring.data;
+
+import com.google.common.util.concurrent.AtomicDouble;
+import lombok.RequiredArgsConstructor;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+@RequiredArgsConstructor
+public class Latency {
+
+ private final String key;
+ private final AtomicDouble latencySum = new AtomicDouble();
+ private final AtomicInteger counter = new AtomicInteger();
+
+ public synchronized void report(double latencyInMs) {
+ latencySum.addAndGet(latencyInMs);
+ counter.incrementAndGet();
+ }
+
+ public synchronized double getAvg() {
+ return latencySum.get() / counter.get();
+ }
+
+ public boolean isNotEmpty() {
+ return counter.get() > 0;
+ }
+
+ public synchronized void reset() {
+ latencySum.set(0.0);
+ counter.set(0);
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public synchronized Latency snapshot() {
+ Latency snapshot = new Latency(key);
+ snapshot.latencySum.set(latencySum.get());
+ snapshot.counter.set(counter.get());
+ return snapshot;
+ }
+
+ @Override
+ public String toString() {
+ return "Latency{" +
+ "key='" + key + '\'' +
+ ", avgLatency=" + getAvg() +
+ '}';
+ }
+
+}
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/data/MonitoredServiceKey.java b/monitoring/src/main/java/org/thingsboard/monitoring/data/MonitoredServiceKey.java
new file mode 100644
index 0000000000..825162952f
--- /dev/null
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/data/MonitoredServiceKey.java
@@ -0,0 +1,22 @@
+/**
+ * Copyright © 2016-2023 The Thingsboard Authors
+ *
+ * Licensed 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.thingsboard.monitoring.data;
+
+public class MonitoredServiceKey {
+
+ public static final String GENERAL = "Monitoring";
+
+}
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/data/TransportFailureException.java b/monitoring/src/main/java/org/thingsboard/monitoring/data/TransportFailureException.java
new file mode 100644
index 0000000000..8157c4af5f
--- /dev/null
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/data/TransportFailureException.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright © 2016-2023 The Thingsboard Authors
+ *
+ * Licensed 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.thingsboard.monitoring.data;
+
+public class TransportFailureException extends RuntimeException {
+
+ public TransportFailureException(Throwable cause) {
+ super(cause.getMessage(), cause);
+ }
+
+ public TransportFailureException(String message) {
+ super(message);
+ }
+
+}
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/data/TransportInfo.java b/monitoring/src/main/java/org/thingsboard/monitoring/data/TransportInfo.java
new file mode 100644
index 0000000000..d7619ea32d
--- /dev/null
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/data/TransportInfo.java
@@ -0,0 +1,32 @@
+/**
+ * Copyright © 2016-2023 The Thingsboard Authors
+ *
+ * Licensed 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.thingsboard.monitoring.data;
+
+import lombok.Data;
+import org.thingsboard.monitoring.config.TransportType;
+
+@Data
+public class TransportInfo {
+
+ private final TransportType transportType;
+ private final String url;
+
+ @Override
+ public String toString() {
+ return String.format("%s (%s)", transportType, url);
+ }
+
+}
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/data/cmd/CmdsWrapper.java b/monitoring/src/main/java/org/thingsboard/monitoring/data/cmd/CmdsWrapper.java
new file mode 100644
index 0000000000..34227150ea
--- /dev/null
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/data/cmd/CmdsWrapper.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright © 2016-2023 The Thingsboard Authors
+ *
+ * Licensed 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.thingsboard.monitoring.data.cmd;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class CmdsWrapper {
+
+ private List entityDataCmds;
+
+}
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/data/cmd/EntityDataCmd.java b/monitoring/src/main/java/org/thingsboard/monitoring/data/cmd/EntityDataCmd.java
new file mode 100644
index 0000000000..ccd6fa8f7c
--- /dev/null
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/data/cmd/EntityDataCmd.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright © 2016-2023 The Thingsboard Authors
+ *
+ * Licensed 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.thingsboard.monitoring.data.cmd;
+
+import lombok.Data;
+import org.thingsboard.server.common.data.query.EntityDataQuery;
+
+@Data
+public class EntityDataCmd {
+
+ private int cmdId;
+ private EntityDataQuery query;
+ private LatestValueCmd latestCmd;
+
+}
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/data/cmd/EntityDataUpdate.java b/monitoring/src/main/java/org/thingsboard/monitoring/data/cmd/EntityDataUpdate.java
new file mode 100644
index 0000000000..5f3a466217
--- /dev/null
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/data/cmd/EntityDataUpdate.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright © 2016-2023 The Thingsboard Authors
+ *
+ * Licensed 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.thingsboard.monitoring.data.cmd;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import lombok.Data;
+import org.thingsboard.server.common.data.query.EntityData;
+import org.thingsboard.server.common.data.query.EntityKeyType;
+import org.thingsboard.server.common.data.query.TsValue;
+
+import java.util.List;
+import java.util.UUID;
+
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class EntityDataUpdate {
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ private List update;
+
+ public String getLatest(UUID entityId, String key) {
+ if (update == null) return null;
+
+ return update.stream()
+ .filter(entityData -> entityData.getEntityId().getId().equals(entityId)).findFirst()
+ .map(EntityData::getLatest).map(latest -> latest.get(EntityKeyType.TIME_SERIES))
+ .map(latest -> latest.get(key)).map(TsValue::getValue)
+ .orElse(null);
+ }
+
+}
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/data/cmd/LatestValueCmd.java b/monitoring/src/main/java/org/thingsboard/monitoring/data/cmd/LatestValueCmd.java
new file mode 100644
index 0000000000..5c2cffcbb6
--- /dev/null
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/data/cmd/LatestValueCmd.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright © 2016-2023 The Thingsboard Authors
+ *
+ * Licensed 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.thingsboard.monitoring.data.cmd;
+
+import lombok.Data;
+import org.thingsboard.server.common.data.query.EntityKey;
+
+import java.util.List;
+
+@Data
+public class LatestValueCmd {
+
+ private List keys;
+
+}
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/data/notification/HighLatencyNotification.java b/monitoring/src/main/java/org/thingsboard/monitoring/data/notification/HighLatencyNotification.java
new file mode 100644
index 0000000000..939cb7440f
--- /dev/null
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/data/notification/HighLatencyNotification.java
@@ -0,0 +1,42 @@
+/**
+ * Copyright © 2016-2023 The Thingsboard Authors
+ *
+ * Licensed 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.thingsboard.monitoring.data.notification;
+
+import org.thingsboard.monitoring.data.Latency;
+
+import java.util.Collection;
+
+public class HighLatencyNotification implements Notification {
+
+ private final Collection latencies;
+ private final int thresholdMs;
+
+ public HighLatencyNotification(Collection latencies, int thresholdMs) {
+ this.latencies = latencies;
+ this.thresholdMs = thresholdMs;
+ }
+
+ @Override
+ public String getText() {
+ StringBuilder text = new StringBuilder();
+ text.append("Some of the latencies are higher than ").append(thresholdMs).append(" ms:\n");
+ latencies.forEach(latency -> {
+ text.append(String.format("[%s] %,.2f ms\n", latency.getKey(), latency.getAvg()));
+ });
+ return text.toString();
+ }
+
+}
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/data/notification/Notification.java b/monitoring/src/main/java/org/thingsboard/monitoring/data/notification/Notification.java
new file mode 100644
index 0000000000..21e5994d84
--- /dev/null
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/data/notification/Notification.java
@@ -0,0 +1,22 @@
+/**
+ * Copyright © 2016-2023 The Thingsboard Authors
+ *
+ * Licensed 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.thingsboard.monitoring.data.notification;
+
+public interface Notification {
+
+ String getText();
+
+}
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/data/notification/ServiceFailureNotification.java b/monitoring/src/main/java/org/thingsboard/monitoring/data/notification/ServiceFailureNotification.java
new file mode 100644
index 0000000000..4b78d96a63
--- /dev/null
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/data/notification/ServiceFailureNotification.java
@@ -0,0 +1,49 @@
+/**
+ * Copyright © 2016-2023 The Thingsboard Authors
+ *
+ * Licensed 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.thingsboard.monitoring.data.notification;
+
+import lombok.Getter;
+import org.apache.commons.lang3.exception.ExceptionUtils;
+
+@Getter
+public class ServiceFailureNotification implements Notification {
+
+ private final Object serviceKey;
+ private final Throwable error;
+ private final int failuresCount;
+
+ public ServiceFailureNotification(Object serviceKey, Throwable error, int failuresCount) {
+ this.serviceKey = serviceKey;
+ this.error = error;
+ this.failuresCount = failuresCount;
+ }
+
+ @Override
+ public String getText() {
+ String errorMsg = error.getMessage();
+ if (errorMsg == null || errorMsg.equals("null")) {
+ Throwable cause = ExceptionUtils.getRootCause(error);
+ if (cause != null) {
+ errorMsg = cause.getMessage();
+ }
+ }
+ if (errorMsg == null) {
+ errorMsg = error.getClass().getSimpleName();
+ }
+ return String.format("[%s] Failure: %s (number of subsequent failures: %s)", serviceKey, errorMsg, failuresCount);
+ }
+
+}
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/data/notification/ServiceRecoveryNotification.java b/monitoring/src/main/java/org/thingsboard/monitoring/data/notification/ServiceRecoveryNotification.java
new file mode 100644
index 0000000000..44eaf093cc
--- /dev/null
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/data/notification/ServiceRecoveryNotification.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright © 2016-2023 The Thingsboard Authors
+ *
+ * Licensed 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.thingsboard.monitoring.data.notification;
+
+public class ServiceRecoveryNotification implements Notification {
+
+ private final Object serviceKey;
+
+ public ServiceRecoveryNotification(Object serviceKey) {
+ this.serviceKey = serviceKey;
+ }
+
+ @Override
+ public String getText() {
+ return String.format("[%s] is OK", serviceKey);
+ }
+
+}
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/notification/NotificationService.java b/monitoring/src/main/java/org/thingsboard/monitoring/notification/NotificationService.java
new file mode 100644
index 0000000000..f882105048
--- /dev/null
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/notification/NotificationService.java
@@ -0,0 +1,53 @@
+/**
+ * Copyright © 2016-2023 The Thingsboard Authors
+ *
+ * Licensed 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.thingsboard.monitoring.notification;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.thingsboard.monitoring.data.notification.Notification;
+import org.thingsboard.monitoring.notification.channels.NotificationChannel;
+
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.function.Consumer;
+
+@Component
+@RequiredArgsConstructor
+@Slf4j
+public class NotificationService {
+
+ private final List notificationChannels;
+ private final ExecutorService notificationExecutor = Executors.newSingleThreadExecutor();
+
+ public void sendNotification(Notification notification) {
+ forEachNotificationChannel(notificationChannel -> notificationChannel.sendNotification(notification));
+ }
+
+ private void forEachNotificationChannel(Consumer function) {
+ notificationChannels.forEach(notificationChannel -> {
+ notificationExecutor.submit(() -> {
+ try {
+ function.accept(notificationChannel);
+ } catch (Exception e) {
+ log.error("Failed to send notification to {}", notificationChannel.getClass().getSimpleName(), e);
+ }
+ });
+ });
+ }
+
+}
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/notification/channels/NotificationChannel.java b/monitoring/src/main/java/org/thingsboard/monitoring/notification/channels/NotificationChannel.java
new file mode 100644
index 0000000000..6c1c23cc28
--- /dev/null
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/notification/channels/NotificationChannel.java
@@ -0,0 +1,24 @@
+/**
+ * Copyright © 2016-2023 The Thingsboard Authors
+ *
+ * Licensed 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.thingsboard.monitoring.notification.channels;
+
+import org.thingsboard.monitoring.data.notification.Notification;
+
+public interface NotificationChannel {
+
+ void sendNotification(Notification notification);
+
+}
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/notification/channels/impl/SlackNotificationChannel.java b/monitoring/src/main/java/org/thingsboard/monitoring/notification/channels/impl/SlackNotificationChannel.java
new file mode 100644
index 0000000000..092ea0b8c6
--- /dev/null
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/notification/channels/impl/SlackNotificationChannel.java
@@ -0,0 +1,58 @@
+/**
+ * Copyright © 2016-2023 The Thingsboard Authors
+ *
+ * Licensed 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.thingsboard.monitoring.notification.channels.impl;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestTemplate;
+import org.thingsboard.monitoring.data.notification.Notification;
+import org.thingsboard.monitoring.notification.channels.NotificationChannel;
+
+import javax.annotation.PostConstruct;
+import java.time.Duration;
+import java.util.Map;
+
+@Component
+@ConditionalOnProperty(value = "monitoring.notification_channels.slack.enabled", havingValue = "true")
+@Slf4j
+public class SlackNotificationChannel implements NotificationChannel {
+
+ @Value("${monitoring.notification_channels.slack.webhook_url}")
+ private String webhookUrl;
+
+ private RestTemplate restTemplate;
+
+ @PostConstruct
+ private void init() {
+ restTemplate = new RestTemplateBuilder()
+ .setConnectTimeout(Duration.ofSeconds(5))
+ .setReadTimeout(Duration.ofSeconds(2))
+ .build();
+ }
+
+ @Override
+ public void sendNotification(Notification notification) {
+ sendNotification(notification.getText());
+ }
+
+ private void sendNotification(String message) {
+ restTemplate.postForObject(webhookUrl, Map.of("text", message), String.class);
+ }
+
+}
diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/service/MonitoringReporter.java b/monitoring/src/main/java/org/thingsboard/monitoring/service/MonitoringReporter.java
new file mode 100644
index 0000000000..4649e31ac4
--- /dev/null
+++ b/monitoring/src/main/java/org/thingsboard/monitoring/service/MonitoringReporter.java
@@ -0,0 +1,144 @@
+/**
+ * Copyright © 2016-2023 The Thingsboard Authors
+ *
+ * Licensed 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.thingsboard.monitoring.service;
+
+import com.fasterxml.jackson.databind.node.DoubleNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.thingsboard.common.util.JacksonUtil;
+import org.thingsboard.monitoring.client.TbClient;
+import org.thingsboard.monitoring.data.Latency;
+import org.thingsboard.monitoring.data.MonitoredServiceKey;
+import org.thingsboard.monitoring.data.notification.HighLatencyNotification;
+import org.thingsboard.monitoring.data.notification.ServiceFailureNotification;
+import org.thingsboard.monitoring.data.notification.ServiceRecoveryNotification;
+import org.thingsboard.monitoring.notification.NotificationService;
+import org.thingsboard.server.common.data.asset.Asset;
+import org.thingsboard.server.common.data.id.AssetId;
+
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+
+@Component
+@RequiredArgsConstructor
+@Slf4j
+public class MonitoringReporter {
+
+ private final NotificationService notificationService;
+
+ private final Map latencies = new ConcurrentHashMap<>();
+ private final Map