From 32212b9c5129d5b60a968310635fd98179118fee Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Mon, 12 May 2025 12:08:52 +0300 Subject: [PATCH] Monitoring: automatic rule chain update --- .../ThingsboardMonitoringApplication.java | 6 + .../monitoring/client/TbClient.java | 9 +- .../monitoring/service/BaseHealthChecker.java | 5 +- .../service/BaseMonitoringService.java | 10 +- .../service/MonitoringEntityService.java | 245 +++++++++++++ .../service/MonitoringReporter.java | 14 +- .../transport/TransportHealthChecker.java | 149 +------- .../monitoring/util/ResourceUtils.java | 16 +- .../{root_rule_chain.json => rule_chain.json} | 327 ++++++++---------- 9 files changed, 428 insertions(+), 353 deletions(-) create mode 100644 monitoring/src/main/java/org/thingsboard/monitoring/service/MonitoringEntityService.java rename monitoring/src/main/resources/{root_rule_chain.json => rule_chain.json} (76%) diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/ThingsboardMonitoringApplication.java b/monitoring/src/main/java/org/thingsboard/monitoring/ThingsboardMonitoringApplication.java index f5f9e73275..c8e8d7070b 100644 --- a/monitoring/src/main/java/org/thingsboard/monitoring/ThingsboardMonitoringApplication.java +++ b/monitoring/src/main/java/org/thingsboard/monitoring/ThingsboardMonitoringApplication.java @@ -25,6 +25,7 @@ import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.EnableScheduling; import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.monitoring.service.BaseMonitoringService; +import org.thingsboard.monitoring.service.MonitoringEntityService; import java.util.List; import java.util.Map; @@ -38,6 +39,8 @@ public class ThingsboardMonitoringApplication { @Autowired private List> monitoringServices; + @Autowired + private MonitoringEntityService entityService; @Value("${monitoring.monitoring_rate_ms}") private int monitoringRateMs; @@ -50,6 +53,9 @@ public class ThingsboardMonitoringApplication { @EventListener(ApplicationReadyEvent.class) public void startMonitoring() { + entityService.checkEntities(); + monitoringServices.forEach(BaseMonitoringService::init); + ScheduledExecutorService scheduler = ThingsBoardExecutors.newSingleThreadScheduledExecutor("monitoring-executor"); scheduler.scheduleWithFixedDelay(() -> { monitoringServices.forEach(monitoringService -> { diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/client/TbClient.java b/monitoring/src/main/java/org/thingsboard/monitoring/client/TbClient.java index e6a7e1b8af..0884cf7e5c 100644 --- a/monitoring/src/main/java/org/thingsboard/monitoring/client/TbClient.java +++ b/monitoring/src/main/java/org/thingsboard/monitoring/client/TbClient.java @@ -15,17 +15,15 @@ */ package org.thingsboard.monitoring.client; +import jakarta.annotation.PostConstruct; 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}") @@ -41,6 +39,11 @@ public class TbClient extends RestClient { .build(), baseUrl); } + @PostConstruct + private void init() { + logIn(); + } + public String logIn() { login(username, password); return getToken(); diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/service/BaseHealthChecker.java b/monitoring/src/main/java/org/thingsboard/monitoring/service/BaseHealthChecker.java index 1e9cdbe191..d19e723332 100644 --- a/monitoring/src/main/java/org/thingsboard/monitoring/service/BaseHealthChecker.java +++ b/monitoring/src/main/java/org/thingsboard/monitoring/service/BaseHealthChecker.java @@ -22,7 +22,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.thingsboard.monitoring.client.TbClient; import org.thingsboard.monitoring.client.WsClient; import org.thingsboard.monitoring.config.MonitoringConfig; import org.thingsboard.monitoring.config.MonitoringTarget; @@ -46,6 +45,8 @@ public abstract class BaseHealthChecker, T ext @Value("${monitoring.calculated_fields.enabled:true}") protected boolean checkCalculatedFields; - @PostConstruct - private void init() { + public void init() { if (configs == null || configs.isEmpty()) { return; } - tbClient.logIn(); + configs.forEach(config -> { config.getTargets().forEach(target -> { BaseHealthChecker healthChecker = initHealthChecker(target, config); @@ -108,7 +106,7 @@ public abstract class BaseMonitoringService, T ext private BaseHealthChecker initHealthChecker(T target, C config) { BaseHealthChecker healthChecker = (BaseHealthChecker) createHealthChecker(config, target); log.info("Initializing {} for {}", healthChecker.getClass().getSimpleName(), target.getBaseUrl()); - healthChecker.initialize(tbClient); + healthChecker.initialize(); devices.add(target.getDeviceId()); return healthChecker; } @@ -140,7 +138,7 @@ public abstract class BaseMonitoringService, T ext reporter.serviceIsOk(MonitoredServiceKey.EDQS); } - reporter.reportLatencies(tbClient); + reporter.reportLatencies(); log.debug("Finished {}", getName()); } catch (ServiceFailureException e) { reporter.serviceFailure(e.getServiceKey(), e); diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/service/MonitoringEntityService.java b/monitoring/src/main/java/org/thingsboard/monitoring/service/MonitoringEntityService.java new file mode 100644 index 0000000000..66dc9200e3 --- /dev/null +++ b/monitoring/src/main/java/org/thingsboard/monitoring/service/MonitoringEntityService.java @@ -0,0 +1,245 @@ +/** + * Copyright © 2016-2025 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.JsonNode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.RandomStringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.common.util.RegexUtils; +import org.thingsboard.monitoring.client.TbClient; +import org.thingsboard.monitoring.config.transport.DeviceConfig; +import org.thingsboard.monitoring.config.transport.TransportMonitoringConfig; +import org.thingsboard.monitoring.config.transport.TransportMonitoringTarget; +import org.thingsboard.monitoring.config.transport.TransportType; +import org.thingsboard.monitoring.util.ResourceUtils; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceProfileType; +import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.TbResource; +import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.cf.CalculatedField; +import org.thingsboard.server.common.data.cf.CalculatedFieldType; +import org.thingsboard.server.common.data.cf.configuration.Argument; +import org.thingsboard.server.common.data.cf.configuration.ArgumentType; +import org.thingsboard.server.common.data.cf.configuration.Output; +import org.thingsboard.server.common.data.cf.configuration.OutputType; +import org.thingsboard.server.common.data.cf.configuration.ReferencedEntityKey; +import org.thingsboard.server.common.data.cf.configuration.ScriptCalculatedFieldConfiguration; +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapClientCredentials; +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials; +import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecBootstrapClientCredential; +import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredential; +import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration; +import org.thingsboard.server.common.data.device.data.DefaultDeviceTransportConfiguration; +import org.thingsboard.server.common.data.device.data.DeviceData; +import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; +import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileData; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.kv.KvEntry; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleChainMetaData; +import org.thingsboard.server.common.data.rule.RuleChainType; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.data.security.DeviceCredentialsType; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.thingsboard.monitoring.service.BaseHealthChecker.TEST_CF_TELEMETRY_KEY; +import static org.thingsboard.monitoring.service.BaseHealthChecker.TEST_TELEMETRY_KEY; + +@Service +@Slf4j +@RequiredArgsConstructor +public class MonitoringEntityService { + + private final TbClient tbClient; + + @Value("${monitoring.calculated_fields.enabled:true}") + private boolean calculatedFieldsMonitoringEnabled; + + public void checkEntities() { + RuleChain ruleChain = tbClient.getRuleChains(RuleChainType.CORE, new PageLink(10)).getData().stream() + .filter(RuleChain::isRoot) + .findFirst().orElseThrow(); + RuleChainId ruleChainId = ruleChain.getId(); + + JsonNode ruleChainDescriptor = ResourceUtils.getResource("rule_chain.json"); + List attributeKeys = tbClient.getAttributeKeys(ruleChainId); + Map attributes = tbClient.getAttributeKvEntries(ruleChainId, attributeKeys).stream() + .collect(Collectors.toMap(KvEntry::getKey, KvEntry::getValueAsString)); + + int currentVersion = Integer.parseInt(attributes.getOrDefault("version", "0")); + int newVersion = ruleChainDescriptor.get("version").asInt(); + if (currentVersion == newVersion) { + log.info("Not updating rule chain, version is the same ({})", currentVersion); + return; + } else { + log.info("Updating rule chain '{}' from version {} to {}", ruleChain.getName(), currentVersion, newVersion); + } + + String metadataJson = RegexUtils.replace(ruleChainDescriptor.get("metadata").toString(), + "\\$\\{MONITORING:(.+?)}", matchResult -> { + String key = matchResult.group(1); + String value = attributes.get(key); + if (value == null) { + throw new IllegalArgumentException("No attribute found for key " + key); + } + log.info("Using {}: {}", key, value); + return value; + }); + RuleChainMetaData metaData = JacksonUtil.fromString(metadataJson, RuleChainMetaData.class); + metaData.setRuleChainId(ruleChainId); + tbClient.saveRuleChainMetaData(metaData); + } + + public Asset getOrCreateMonitoringAsset() { + String assetName = "[Monitoring] Latencies"; + return tbClient.findAsset(assetName).orElseGet(() -> { + Asset asset = new Asset(); + asset.setType("Monitoring"); + asset.setName(assetName); + asset = tbClient.saveAsset(asset); + log.info("Created monitoring asset {}", asset.getId()); + return asset; + }); + } + + public void checkEntities(TransportMonitoringConfig config, TransportMonitoringTarget target) { + Device device = getOrCreateDevice(config, target); + DeviceCredentials credentials = tbClient.getDeviceCredentialsByDeviceId(device.getId()) + .orElseThrow(() -> new IllegalArgumentException("No credentials found for device " + device.getId())); + + DeviceConfig deviceConfig = new DeviceConfig(); + deviceConfig.setId(device.getId().toString()); + deviceConfig.setName(device.getName()); + deviceConfig.setCredentials(credentials); + target.setDevice(deviceConfig); + } + + private Device getOrCreateDevice(TransportMonitoringConfig config, TransportMonitoringTarget target) { + TransportType transportType = config.getTransportType(); + String deviceName = String.format("%s %s (%s) - %s", target.getNamePrefix(), transportType.getName(), target.getQueue(), target.getBaseUrl()).trim(); + Device device = tbClient.getTenantDevice(deviceName).orElse(null); + if (device != null) { + if (calculatedFieldsMonitoringEnabled) { + CalculatedField calculatedField = tbClient.getCalculatedFieldsByEntityId(device.getId(), new PageLink(1, 0, TEST_CF_TELEMETRY_KEY)) + .getData().stream().findFirst().orElse(null); + if (calculatedField == null) { + createCalculatedField(device); + } + } + return device; + } + + log.info("Creating new device '{}'", deviceName); + device = new Device(); + device.setName(deviceName); + + DeviceCredentials credentials = new DeviceCredentials(); + credentials.setCredentialsId(RandomStringUtils.randomAlphabetic(20)); + DeviceData deviceData = new DeviceData(); + deviceData.setConfiguration(new DefaultDeviceConfiguration()); + + DeviceProfile deviceProfile = getOrCreateDeviceProfile(config, target); + device.setType(deviceProfile.getName()); + device.setDeviceProfileId(deviceProfile.getId()); + + if (transportType != TransportType.LWM2M) { + deviceData.setTransportConfiguration(new DefaultDeviceTransportConfiguration()); + credentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); + } else { + deviceData.setTransportConfiguration(new Lwm2mDeviceTransportConfiguration()); + credentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS); + LwM2MDeviceCredentials lwm2mCreds = new LwM2MDeviceCredentials(); + NoSecClientCredential client = new NoSecClientCredential(); + client.setEndpoint(credentials.getCredentialsId()); + lwm2mCreds.setClient(client); + LwM2MBootstrapClientCredentials bootstrap = new LwM2MBootstrapClientCredentials(); + bootstrap.setBootstrapServer(new NoSecBootstrapClientCredential()); + bootstrap.setLwm2mServer(new NoSecBootstrapClientCredential()); + lwm2mCreds.setBootstrap(bootstrap); + credentials.setCredentialsValue(JacksonUtil.toString(lwm2mCreds)); + } + + return tbClient.saveDeviceWithCredentials(device, credentials).get(); + } + + private DeviceProfile getOrCreateDeviceProfile(TransportMonitoringConfig config, TransportMonitoringTarget target) { + TransportType transportType = config.getTransportType(); + String profileName = String.format("%s %s (%s)", target.getNamePrefix(), transportType.getName(), target.getQueue()).trim(); + DeviceProfile deviceProfile = tbClient.getDeviceProfiles(new PageLink(1, 0, profileName)).getData() + .stream().findFirst().orElse(null); + if (deviceProfile != null) { + return deviceProfile; + } + + log.info("Creating new device profile '{}'", profileName); + if (transportType != TransportType.LWM2M) { + deviceProfile = new DeviceProfile(); + deviceProfile.setType(DeviceProfileType.DEFAULT); + deviceProfile.setTransportType(DeviceTransportType.DEFAULT); + DeviceProfileData profileData = new DeviceProfileData(); + profileData.setConfiguration(new DefaultDeviceProfileConfiguration()); + profileData.setTransportConfiguration(new DefaultDeviceProfileTransportConfiguration()); + deviceProfile.setProfileData(profileData); + } else { + tbClient.getResources(new PageLink(1, 0, "LwM2M Monitoring")).getData() + .stream().findFirst() + .orElseGet(() -> { + TbResource newResource = ResourceUtils.getResource("lwm2m/resource.json", TbResource.class); + log.info("Creating LwM2M resource"); + return tbClient.saveResource(newResource); + }); + deviceProfile = ResourceUtils.getResource("lwm2m/device_profile.json", DeviceProfile.class); + } + + deviceProfile.setName(profileName); + deviceProfile.setDefaultQueueName(target.getQueue()); + return tbClient.saveDeviceProfile(deviceProfile); + } + + private void createCalculatedField(Device device) { + log.info("Creating calculated field for device '{}'", device.getName()); + CalculatedField calculatedField = new CalculatedField(); + calculatedField.setName(TEST_CF_TELEMETRY_KEY); + calculatedField.setEntityId(device.getId()); + calculatedField.setType(CalculatedFieldType.SCRIPT); + ScriptCalculatedFieldConfiguration configuration = new ScriptCalculatedFieldConfiguration(); + Argument testDataArgument = new Argument(); + testDataArgument.setRefEntityKey(new ReferencedEntityKey(TEST_TELEMETRY_KEY, ArgumentType.TS_LATEST, null)); + configuration.setArguments(Map.of( + TEST_TELEMETRY_KEY, testDataArgument + )); + configuration.setExpression("return { \"" + TEST_CF_TELEMETRY_KEY + "\": " + TEST_TELEMETRY_KEY + " + \"-cf\" };"); + Output output = new Output(); + output.setType(OutputType.TIME_SERIES); + configuration.setOutput(output); + calculatedField.setConfiguration(configuration); + calculatedField.setDebugMode(true); + tbClient.saveCalculatedField(calculatedField); + } + +} diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/service/MonitoringReporter.java b/monitoring/src/main/java/org/thingsboard/monitoring/service/MonitoringReporter.java index 62ed0d74aa..99cb9feb3d 100644 --- a/monitoring/src/main/java/org/thingsboard/monitoring/service/MonitoringReporter.java +++ b/monitoring/src/main/java/org/thingsboard/monitoring/service/MonitoringReporter.java @@ -46,6 +46,8 @@ import java.util.stream.Collectors; public class MonitoringReporter { private final NotificationService notificationService; + private final TbClient tbClient; + private final MonitoringEntityService entityService; private final Map latencies = new ConcurrentHashMap<>(); private final Map failuresCounters = new ConcurrentHashMap<>(); @@ -62,7 +64,7 @@ public class MonitoringReporter { @Value("${monitoring.latency.reporting_asset_id}") private String reportingAssetId; - public void reportLatencies(TbClient tbClient) { + public void reportLatencies() { if (latencies.isEmpty()) { return; } @@ -81,15 +83,7 @@ public class MonitoringReporter { try { if (StringUtils.isBlank(reportingAssetId)) { - String assetName = "[Monitoring] Latencies"; - Asset monitoringAsset = tbClient.findAsset(assetName).orElseGet(() -> { - Asset asset = new Asset(); - asset.setType("Monitoring"); - asset.setName(assetName); - asset = tbClient.saveAsset(asset); - log.info("Created monitoring asset {}", asset.getId()); - return asset; - }); + Asset monitoringAsset = entityService.getOrCreateMonitoringAsset(); reportingAssetId = monitoringAsset.getId().toString(); } diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/service/transport/TransportHealthChecker.java b/monitoring/src/main/java/org/thingsboard/monitoring/service/transport/TransportHealthChecker.java index ea95740c68..946c3e6d3d 100644 --- a/monitoring/src/main/java/org/thingsboard/monitoring/service/transport/TransportHealthChecker.java +++ b/monitoring/src/main/java/org/thingsboard/monitoring/service/transport/TransportHealthChecker.java @@ -17,46 +17,13 @@ package org.thingsboard.monitoring.service.transport; import com.fasterxml.jackson.databind.node.TextNode; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.RandomStringUtils; import org.springframework.beans.factory.annotation.Value; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.monitoring.client.TbClient; -import org.thingsboard.monitoring.config.transport.DeviceConfig; import org.thingsboard.monitoring.config.transport.TransportInfo; import org.thingsboard.monitoring.config.transport.TransportMonitoringConfig; import org.thingsboard.monitoring.config.transport.TransportMonitoringTarget; import org.thingsboard.monitoring.config.transport.TransportType; import org.thingsboard.monitoring.service.BaseHealthChecker; -import org.thingsboard.monitoring.util.ResourceUtils; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.data.DeviceProfileType; -import org.thingsboard.server.common.data.DeviceTransportType; -import org.thingsboard.server.common.data.TbResource; -import org.thingsboard.server.common.data.cf.CalculatedField; -import org.thingsboard.server.common.data.cf.CalculatedFieldType; -import org.thingsboard.server.common.data.cf.configuration.Argument; -import org.thingsboard.server.common.data.cf.configuration.ArgumentType; -import org.thingsboard.server.common.data.cf.configuration.Output; -import org.thingsboard.server.common.data.cf.configuration.OutputType; -import org.thingsboard.server.common.data.cf.configuration.ReferencedEntityKey; -import org.thingsboard.server.common.data.cf.configuration.ScriptCalculatedFieldConfiguration; -import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapClientCredentials; -import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials; -import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecBootstrapClientCredential; -import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredential; -import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration; -import org.thingsboard.server.common.data.device.data.DefaultDeviceTransportConfiguration; -import org.thingsboard.server.common.data.device.data.DeviceData; -import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; -import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.DeviceProfileData; -import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.common.data.security.DeviceCredentials; -import org.thingsboard.server.common.data.security.DeviceCredentialsType; - -import java.util.Map; @Slf4j public abstract class TransportHealthChecker extends BaseHealthChecker { @@ -69,16 +36,8 @@ public abstract class TransportHealthChecker new IllegalArgumentException("No credentials found for device " + device.getId())); - - DeviceConfig deviceConfig = new DeviceConfig(); - deviceConfig.setId(device.getId().toString()); - deviceConfig.setName(device.getName()); - deviceConfig.setCredentials(credentials); - target.setDevice(deviceConfig); + protected void initialize() { + entityService.checkEntities(config, target); } @Override @@ -98,110 +57,6 @@ public abstract class TransportHealthChecker { - TbResource newResource = ResourceUtils.getResource("lwm2m/resource.json", TbResource.class); - log.info("Creating LwM2M resource"); - return tbClient.saveResource(newResource); - }); - deviceProfile = ResourceUtils.getResource("lwm2m/device_profile.json", DeviceProfile.class); - } - - deviceProfile.setName(profileName); - deviceProfile.setDefaultQueueName(target.getQueue()); - return tbClient.saveDeviceProfile(deviceProfile); - } - - private void createCalculatedField(TbClient tbClient, Device device) { - log.info("Creating calculated field for device '{}'", device.getName()); - CalculatedField calculatedField = new CalculatedField(); - calculatedField.setName(TEST_CF_TELEMETRY_KEY); - calculatedField.setEntityId(device.getId()); - calculatedField.setType(CalculatedFieldType.SCRIPT); - ScriptCalculatedFieldConfiguration configuration = new ScriptCalculatedFieldConfiguration(); - Argument testDataArgument = new Argument(); - testDataArgument.setRefEntityKey(new ReferencedEntityKey(TEST_TELEMETRY_KEY, ArgumentType.TS_LATEST, null)); - configuration.setArguments(Map.of( - TEST_TELEMETRY_KEY, testDataArgument - )); - configuration.setExpression("return { \"" + TEST_CF_TELEMETRY_KEY + "\": " + TEST_TELEMETRY_KEY + " + \"-cf\" };"); - Output output = new Output(); - output.setType(OutputType.TIME_SERIES); - configuration.setOutput(output); - calculatedField.setConfiguration(configuration); - calculatedField.setDebugMode(true); - tbClient.saveCalculatedField(calculatedField); - } - @Override protected boolean isCfMonitoringEnabled() { return calculatedFieldsMonitoringEnabled; diff --git a/monitoring/src/main/java/org/thingsboard/monitoring/util/ResourceUtils.java b/monitoring/src/main/java/org/thingsboard/monitoring/util/ResourceUtils.java index 3e25cfbd39..a92033f439 100644 --- a/monitoring/src/main/java/org/thingsboard/monitoring/util/ResourceUtils.java +++ b/monitoring/src/main/java/org/thingsboard/monitoring/util/ResourceUtils.java @@ -15,6 +15,7 @@ */ package org.thingsboard.monitoring.util; +import com.fasterxml.jackson.databind.JsonNode; import lombok.SneakyThrows; import org.thingsboard.common.util.JacksonUtil; @@ -24,14 +25,21 @@ public class ResourceUtils { @SneakyThrows public static T getResource(String path, Class type) { - InputStream resource = ResourceUtils.class.getClassLoader().getResourceAsStream(path); - if (resource == null) { - throw new IllegalArgumentException("Resource not found for path " + path); - } + InputStream resource = getResourceStream(path); return JacksonUtil.OBJECT_MAPPER.readValue(resource, type); } + @SneakyThrows + public static JsonNode getResource(String path) { + InputStream resource = getResourceStream(path); + return JacksonUtil.OBJECT_MAPPER.readTree(resource); + } + public static InputStream getResourceAsStream(String path) { + return getResourceStream(path); + } + + private static InputStream getResourceStream(String path) { InputStream resource = ResourceUtils.class.getClassLoader().getResourceAsStream(path); if (resource == null) { throw new IllegalArgumentException("Resource not found for path " + path); diff --git a/monitoring/src/main/resources/root_rule_chain.json b/monitoring/src/main/resources/rule_chain.json similarity index 76% rename from monitoring/src/main/resources/root_rule_chain.json rename to monitoring/src/main/resources/rule_chain.json index a1c12c8e9d..96d2388636 100644 --- a/monitoring/src/main/resources/root_rule_chain.json +++ b/monitoring/src/main/resources/rule_chain.json @@ -1,257 +1,232 @@ { + "version": 1, "ruleChain": { - "additionalInfo": null, "name": "Root Rule Chain", "type": "CORE", "firstRuleNodeId": null, "root": false, "debugMode": false, "configuration": null, - "externalId": null + "additionalInfo": null }, "metadata": { - "firstNodeIndex": 12, + "firstNodeIndex": 9, "nodes": [ { - "additionalInfo": { - "description": null, - "layoutX": 1202, - "layoutY": 221 - }, "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode", "name": "Save Timeseries", + "debugSettings": { + "failuresEnabled": true, + "allEnabled": false, + "allEnabledUntil": 1735310701003 + }, "singletonMode": false, + "queueName": null, "configurationVersion": 1, "configuration": { "defaultTTL": 0, - "useServerTs": false, "processingSettings": { "type": "ON_EVERY_MESSAGE" } }, - "externalId": null + "additionalInfo": { + "description": null, + "layoutX": 1202, + "layoutY": 221 + } }, { - "additionalInfo": { - "layoutX": 1000, - "layoutY": 167 - }, "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode", "name": "Save Attributes", + "debugSettings": null, "singletonMode": false, + "queueName": null, "configurationVersion": 3, "configuration": { + "scope": "CLIENT_SCOPE", + "notifyDevice": false, "processingSettings": { "type": "ON_EVERY_MESSAGE" }, - "scope": "CLIENT_SCOPE", - "notifyDevice": false, "sendAttributesUpdatedNotification": false, "updateAttributesOnlyOnValueChange": false }, - "externalId": null + "additionalInfo": { + "layoutX": 1000, + "layoutY": 167 + } }, { - "additionalInfo": { - "layoutX": 566, - "layoutY": 302 - }, "type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode", "name": "Message Type Switch", + "debugSettings": null, "singletonMode": false, + "queueName": null, "configurationVersion": 0, "configuration": { "version": 0 }, - "externalId": null + "additionalInfo": { + "layoutX": 566, + "layoutY": 302 + } }, { - "additionalInfo": { - "layoutX": 1000, - "layoutY": 381 - }, "type": "org.thingsboard.rule.engine.action.TbLogNode", "name": "Log RPC from Device", + "debugSettings": null, "singletonMode": false, + "queueName": null, "configurationVersion": 0, "configuration": { "scriptLang": "TBEL", "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);", "tbelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" }, - "externalId": null - }, - { "additionalInfo": { "layoutX": 1000, - "layoutY": 494 - }, + "layoutY": 381 + } + }, + { "type": "org.thingsboard.rule.engine.action.TbLogNode", "name": "Log Other", + "debugSettings": null, "singletonMode": false, + "queueName": null, "configurationVersion": 0, "configuration": { "scriptLang": "TBEL", "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);", "tbelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" }, - "externalId": null - }, - { "additionalInfo": { "layoutX": 1000, - "layoutY": 583 - }, + "layoutY": 494 + } + }, + { "type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode", "name": "RPC Call Request", + "debugSettings": null, "singletonMode": false, + "queueName": null, "configurationVersion": 0, "configuration": { "timeoutInSeconds": 60 }, - "externalId": null - }, - { - "additionalInfo": { - "layoutX": 255, - "layoutY": 301 - }, - "type": "org.thingsboard.rule.engine.filter.TbOriginatorTypeFilterNode", - "name": "Is Entity Group", - "singletonMode": false, - "configurationVersion": 0, - "configuration": { - "originatorTypes": [ - "ENTITY_GROUP" - ] - }, - "externalId": null - }, - { "additionalInfo": { - "layoutX": 319, - "layoutY": 109 - }, - "type": "org.thingsboard.rule.engine.filter.TbMsgTypeFilterNode", - "name": "Post attributes or RPC request", - "singletonMode": false, - "configurationVersion": 0, - "configuration": { - "messageTypes": [ - "POST_ATTRIBUTES_REQUEST", - "RPC_CALL_FROM_SERVER_TO_DEVICE" - ] - }, - "externalId": null + "layoutX": 1000, + "layoutY": 583 + } }, { - "additionalInfo": { - "layoutX": 627, - "layoutY": 108 + "type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode", + "name": "Device Profile Node", + "debugSettings": { + "failuresEnabled": true, + "allEnabled": false, + "allEnabledUntil": 1735310701003 }, - "type": "org.thingsboard.rule.engine.transform.TbDuplicateMsgToGroupNode", - "name": "Duplicate To Group Entities", "singletonMode": false, - "configurationVersion": 0, + "queueName": null, + "configurationVersion": 1, "configuration": { - "entityGroupId": null, - "entityGroupIsMessageOriginator": true + "persistAlarmRulesState": false, + "fetchAlarmRulesStateOnStart": false }, - "externalId": null - }, - { "additionalInfo": { "description": "Process incoming messages from devices with the alarm rules defined in the device profile. Dispatch all incoming messages with \"Success\" relation type.", "layoutX": 45, "layoutY": 359 - }, - "type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode", - "name": "Device Profile Node", - "singletonMode": false, - "configurationVersion": 0, - "configuration": { - "persistAlarmRulesState": false, - "fetchAlarmRulesStateOnStart": false - }, - "externalId": null + } }, { - "additionalInfo": { - "description": "", - "layoutX": 160, - "layoutY": 631 - }, "type": "org.thingsboard.rule.engine.filter.TbJsFilterNode", "name": "Test JS script", + "debugSettings": null, "singletonMode": false, + "queueName": null, "configurationVersion": 0, "configuration": { "scriptLang": "JS", - "jsScript": "var test = {\n a: 'a',\n b: 'b'\n};\nreturn test.a === 'a' && test.b === 'b';", + "jsScript": "var test = {\n a: 'a',\n b: 'b'\n};\n\nreturn test.a === 'a' && test.b === 'b';", "tbelScript": "return msg.temperature > 20;" }, - "externalId": null + "additionalInfo": { + "description": "dashboardId: ${MONITORING:dashboardId}", + "layoutX": 251, + "layoutY": 499 + } }, { - "additionalInfo": { - "description": "", - "layoutX": 427, - "layoutY": 541 - }, "type": "org.thingsboard.rule.engine.filter.TbJsFilterNode", "name": "Test TBEL script", + "debugSettings": null, "singletonMode": false, + "queueName": null, "configurationVersion": 0, "configuration": { "scriptLang": "TBEL", "jsScript": "return msg.temperature > 20;", "tbelScript": "var a = \"a\";\nvar b = \"b\";\nreturn a.equals(\"a\") && b.equals(\"b\");" }, - "externalId": null - }, - { "additionalInfo": { "description": "", - "layoutX": 40, - "layoutY": 252 - }, + "layoutX": 317, + "layoutY": 355 + } + }, + { "type": "org.thingsboard.rule.engine.transform.TbTransformMsgNode", "name": "Add arrival timestamp", + "debugSettings": { + "failuresEnabled": true, + "allEnabled": false, + "allEnabledUntil": 1744642101587 + }, "singletonMode": false, + "queueName": null, "configurationVersion": 0, "configuration": { "scriptLang": "TBEL", "jsScript": "return {msg: msg, metadata: metadata, msgType: msgType};", "tbelScript": "metadata.arrivalTs = Date.now();\nreturn {msg: msg, metadata: metadata, msgType: msgType};" }, - "externalId": null - }, - { "additionalInfo": { "description": "", - "layoutX": 1467, - "layoutY": 267 - }, + "layoutX": 40, + "layoutY": 252 + } + }, + { "type": "org.thingsboard.rule.engine.transform.TbTransformMsgNode", "name": "Calculate additional latencies", + "debugSettings": { + "failuresEnabled": true, + "allEnabled": false, + "allEnabledUntil": 1735310701003 + }, "singletonMode": false, + "queueName": null, "configurationVersion": 0, "configuration": { "scriptLang": "TBEL", "jsScript": "return {msg: msg, metadata: metadata, msgType: msgType};", "tbelScript": "var arrivalLatency = metadata.arrivalTs - metadata.ts;\nvar processingTime = Date.now() - metadata.arrivalTs;\nmsg = {\n arrivalLatency: arrivalLatency,\n processingTime: processingTime\n};\nreturn {msg: msg, metadata: metadata, msgType: msgType};" }, - "externalId": null - }, - { "additionalInfo": { "description": "", - "layoutX": 1438, - "layoutY": 403 - }, + "layoutX": 1467, + "layoutY": 267 + } + }, + { "type": "org.thingsboard.rule.engine.transform.TbChangeOriginatorNode", "name": "To latencies asset", + "debugSettings": null, "singletonMode": false, + "queueName": null, "configurationVersion": 0, "configuration": { "originatorSource": "ENTITY", @@ -269,64 +244,79 @@ "fetchLastLevelOnly": false } }, - "externalId": null + "additionalInfo": { + "description": "", + "layoutX": 1438, + "layoutY": 403 + } }, { - "additionalInfo": { - "description": null, - "layoutX": 1458, - "layoutY": 505 - }, "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode", "name": "Save Timeseries", + "debugSettings": { + "failuresEnabled": true, + "allEnabled": false, + "allEnabledUntil": 1735310701003 + }, "singletonMode": false, + "queueName": null, "configurationVersion": 1, "configuration": { "defaultTTL": 0, - "useServerTs": false, "processingSettings": { "type": "ON_EVERY_MESSAGE" } }, - "externalId": null + "additionalInfo": { + "description": null, + "layoutX": 1458, + "layoutY": 505 + } }, { - "additionalInfo": { - "description": "", - "layoutX": 928, - "layoutY": 266 - }, "type": "org.thingsboard.rule.engine.filter.TbCheckMessageNode", "name": "Has testData", + "debugSettings": null, "singletonMode": false, + "queueName": null, "configurationVersion": 0, "configuration": { "messageNames": [ - "testData" + "testData", + "testDataCf" ], "metadataNames": [], - "checkAllKeys": true + "checkAllKeys": false }, - "externalId": null + "additionalInfo": { + "description": "", + "layoutX": 928, + "layoutY": 266 + } }, { - "additionalInfo": { - "description": null, - "layoutX": 1203, - "layoutY": 327 - }, "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode", "name": "Save Timeseries with TTL", + "debugSettings": { + "failuresEnabled": true, + "allEnabled": false, + "allEnabledUntil": 1742305233839 + }, "singletonMode": false, + "queueName": null, "configurationVersion": 1, "configuration": { - "defaultTTL": 180, - "useServerTs": false, "processingSettings": { "type": "ON_EVERY_MESSAGE" - } + }, + "defaultTTL": 60, + "useServerTs": null }, - "externalId": null + "additionalInfo": { + "description": "", + "layoutX": 1203, + "layoutY": 327 + } } ], "connections": [ @@ -352,23 +342,13 @@ }, { "fromIndex": 2, - "toIndex": 16, + "toIndex": 13, "type": "Post telemetry" }, - { - "fromIndex": 6, - "toIndex": 2, - "type": "False" - }, { "fromIndex": 6, "toIndex": 7, - "type": "True" - }, - { - "fromIndex": 7, - "toIndex": 2, - "type": "False" + "type": "Success" }, { "fromIndex": 7, @@ -378,54 +358,39 @@ { "fromIndex": 8, "toIndex": 2, - "type": "Success" + "type": "True" }, { "fromIndex": 9, - "toIndex": 10, + "toIndex": 6, "type": "Success" }, { "fromIndex": 10, "toIndex": 11, - "type": "True" + "type": "Success" }, { "fromIndex": 11, - "toIndex": 6, - "type": "True" - }, - { - "fromIndex": 12, - "toIndex": 9, + "toIndex": 12, "type": "Success" }, { "fromIndex": 13, - "toIndex": 14, - "type": "Success" - }, - { - "fromIndex": 14, - "toIndex": 15, - "type": "Success" - }, - { - "fromIndex": 16, "toIndex": 0, "type": "False" }, { - "fromIndex": 16, - "toIndex": 17, + "fromIndex": 13, + "toIndex": 14, "type": "True" }, { - "fromIndex": 17, - "toIndex": 13, + "fromIndex": 14, + "toIndex": 10, "type": "Success" } ], "ruleChainConnections": null } -} +} \ No newline at end of file