Browse Source

Merge remote-tracking branch 'upstream/rc' into fix-autocommit-cf

pull/13148/head
Andrii Landiak 1 year ago
parent
commit
ccb8ae6378
  1. 14
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  2. 2
      application/src/main/java/org/thingsboard/server/actors/app/AppActor.java
  3. 8
      application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerActor.java
  4. 135
      application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerMessageProcessor.java
  5. 5
      application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
  6. 2
      application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldCache.java
  7. 11
      application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldInitService.java
  8. 95
      application/src/main/java/org/thingsboard/server/service/cf/cache/DefaultCalculatedFieldEntityProfileCache.java
  9. 50
      application/src/main/java/org/thingsboard/server/service/cf/cache/TenantEntityProfileCache.java
  10. 2
      application/src/main/java/org/thingsboard/server/service/cf/ctx/state/KafkaCalculatedFieldStateService.java
  11. 2
      application/src/main/java/org/thingsboard/server/service/cf/ctx/state/RocksDBCalculatedFieldStateService.java
  12. 1
      application/src/main/java/org/thingsboard/server/service/edge/rpc/KafkaEdgeGrpcSession.java
  13. 2
      application/src/main/java/org/thingsboard/server/service/housekeeper/stats/HousekeeperStatsService.java
  14. 10
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCalculatedFieldConsumerService.java
  15. 4
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
  16. 2
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbEdgeConsumerService.java
  17. 7
      application/src/main/java/org/thingsboard/server/service/queue/ruleengine/TbRuleEngineQueueConsumerManager.java
  18. 13
      application/src/main/java/org/thingsboard/server/service/ttl/KafkaEdgeTopicsCleanUpService.java
  19. 6
      application/src/main/resources/thingsboard.yml
  20. 23
      application/src/test/java/org/thingsboard/server/service/entitiy/EntityServiceTest.java
  21. 22
      common/cluster-api/src/main/java/org/thingsboard/server/queue/TbEdgeQueueAdmin.java
  22. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetService.java
  23. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/cf/CalculatedFieldService.java
  24. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java
  25. 2
      common/edqs/src/main/java/org/thingsboard/server/edqs/data/BaseEntityData.java
  26. 6
      common/edqs/src/main/java/org/thingsboard/server/edqs/data/dp/CompressedJsonDataPoint.java
  27. 10
      common/edqs/src/main/java/org/thingsboard/server/edqs/data/dp/CompressedStringDataPoint.java
  28. 2
      common/edqs/src/main/java/org/thingsboard/server/edqs/processor/EdqsProcessor.java
  29. 5
      common/edqs/src/main/java/org/thingsboard/server/edqs/query/processor/AbstractQueryProcessor.java
  30. 9
      common/edqs/src/main/java/org/thingsboard/server/edqs/repo/DefaultEdqsRepository.java
  31. 2
      common/edqs/src/main/java/org/thingsboard/server/edqs/repo/TenantRepo.java
  32. 5
      common/edqs/src/main/java/org/thingsboard/server/edqs/state/KafkaEdqsStateService.java
  33. 89
      common/edqs/src/main/java/org/thingsboard/server/edqs/stats/DefaultEdqsStatsService.java
  34. 39
      common/edqs/src/main/java/org/thingsboard/server/edqs/util/EdqsConverter.java
  35. 2
      common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java
  36. 33
      common/message/src/main/java/org/thingsboard/server/common/msg/cf/CalculatedFieldCacheInitMsg.java
  37. 26
      common/message/src/main/java/org/thingsboard/server/common/msg/cf/CalculatedFieldInitProfileEntityMsg.java
  38. 6
      common/queue/src/main/java/org/thingsboard/server/queue/common/consumer/MainQueueConsumerManager.java
  39. 2
      common/queue/src/main/java/org/thingsboard/server/queue/common/consumer/PartitionedQueueConsumerManager.java
  40. 2
      common/queue/src/main/java/org/thingsboard/server/queue/edqs/EdqsConfig.java
  41. 36
      common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaAdmin.java
  42. 3
      common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryMonolithQueueFactory.java
  43. 30
      common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java
  44. 11
      common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java
  45. 19
      common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java
  46. 9
      common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineQueueFactory.java
  47. 2
      common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueCalculatedFieldSettings.java
  48. 5
      common/queue/src/main/java/org/thingsboard/server/queue/util/AfterStartUp.java
  49. 15
      common/stats/src/main/java/org/thingsboard/server/common/stats/DefaultStatsFactory.java
  50. 16
      common/stats/src/main/java/org/thingsboard/server/common/stats/DummyEdqsStatsService.java
  51. 12
      common/stats/src/main/java/org/thingsboard/server/common/stats/EdqsStatsService.java
  52. 8
      common/stats/src/main/java/org/thingsboard/server/common/stats/StatsFactory.java
  53. 6
      common/util/src/main/java/org/thingsboard/common/util/TbBytePool.java
  54. 6
      common/util/src/main/java/org/thingsboard/common/util/TbStringPool.java
  55. 2
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java
  56. 8
      dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
  57. 16
      dao/src/main/java/org/thingsboard/server/dao/cf/BaseCalculatedFieldService.java
  58. 2
      dao/src/main/java/org/thingsboard/server/dao/cf/CalculatedFieldDao.java
  59. 4
      dao/src/main/java/org/thingsboard/server/dao/cf/CalculatedFieldLinkDao.java
  60. 4
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java
  61. 8
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
  62. 19
      dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java
  63. 10
      dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java
  64. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/cf/CalculatedFieldLinkRepository.java
  65. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/cf/CalculatedFieldRepository.java
  66. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/cf/JpaCalculatedFieldDao.java
  67. 11
      dao/src/main/java/org/thingsboard/server/dao/sql/cf/JpaCalculatedFieldLinkDao.java
  68. 17
      dao/src/main/java/org/thingsboard/server/dao/sql/device/DefaultNativeAssetRepository.java
  69. 11
      dao/src/main/java/org/thingsboard/server/dao/sql/device/DefaultNativeDeviceRepository.java
  70. 16
      dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java
  71. 4
      dao/src/main/java/org/thingsboard/server/dao/sql/device/NativeProfileEntityRepository.java
  72. 4
      edqs/src/main/resources/edqs.yml
  73. 4
      ui-ngx/src/app/modules/common/modules-map.ts
  74. 1
      ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.scss
  75. 2
      ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts
  76. 149
      ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.ts
  77. 4
      ui-ngx/src/app/shared/components/image/upload-image-dialog.component.html
  78. 63
      ui-ngx/src/app/shared/components/image/upload-image-dialog.component.ts
  79. 4
      ui-ngx/src/assets/locale/locale.constant-en_US.json

14
application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java

@ -30,9 +30,9 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.DeviceStateManager;
import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.rule.engine.api.NotificationCenter; import org.thingsboard.rule.engine.api.NotificationCenter;
import org.thingsboard.rule.engine.api.DeviceStateManager;
import org.thingsboard.rule.engine.api.SmsService; import org.thingsboard.rule.engine.api.SmsService;
import org.thingsboard.rule.engine.api.notification.SlackService; import org.thingsboard.rule.engine.api.notification.SlackService;
import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory;
@ -106,11 +106,11 @@ import org.thingsboard.server.dao.widget.WidgetsBundleService;
import org.thingsboard.server.queue.discovery.DiscoveryService; import org.thingsboard.server.queue.discovery.DiscoveryService;
import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.settings.TbQueueCalculatedFieldSettings;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.cf.CalculatedFieldProcessingService; import org.thingsboard.server.service.cf.CalculatedFieldProcessingService;
import org.thingsboard.server.service.cf.CalculatedFieldQueueService; import org.thingsboard.server.service.cf.CalculatedFieldQueueService;
import org.thingsboard.server.service.cf.CalculatedFieldStateService; import org.thingsboard.server.service.cf.CalculatedFieldStateService;
import org.thingsboard.server.service.cf.cache.CalculatedFieldEntityProfileCache;
import org.thingsboard.server.service.cf.ctx.state.ArgumentEntry; import org.thingsboard.server.service.cf.ctx.state.ArgumentEntry;
import org.thingsboard.server.service.component.ComponentDiscoveryService; import org.thingsboard.server.service.component.ComponentDiscoveryService;
import org.thingsboard.server.service.edge.rpc.EdgeRpcService; import org.thingsboard.server.service.edge.rpc.EdgeRpcService;
@ -460,6 +460,11 @@ public class ActorSystemContext {
@Getter @Getter
private DebugModeRateLimitsConfig debugModeRateLimitsConfig; private DebugModeRateLimitsConfig debugModeRateLimitsConfig;
@Lazy
@Autowired(required = false)
@Getter
private TbQueueCalculatedFieldSettings calculatedFieldSettings;
/** /**
* The following Service will be null if we operate in tb-core mode * The following Service will be null if we operate in tb-core mode
*/ */
@ -546,11 +551,6 @@ public class ActorSystemContext {
@Getter @Getter
private CalculatedFieldQueueService calculatedFieldQueueService; private CalculatedFieldQueueService calculatedFieldQueueService;
@Lazy
@Autowired(required = false)
@Getter
private CalculatedFieldEntityProfileCache calculatedFieldEntityProfileCache;
@Value("${actors.session.max_concurrent_sessions_per_device:1}") @Value("${actors.session.max_concurrent_sessions_per_device:1}")
@Getter @Getter
private int maxConcurrentSessionsPerDevice; private int maxConcurrentSessionsPerDevice;

2
application/src/main/java/org/thingsboard/server/actors/app/AppActor.java

@ -113,6 +113,8 @@ public class AppActor extends ContextAwareActor {
case SESSION_TIMEOUT_MSG: case SESSION_TIMEOUT_MSG:
ctx.broadcastToChildrenByType(msg, EntityType.TENANT); ctx.broadcastToChildrenByType(msg, EntityType.TENANT);
break; break;
case CF_CACHE_INIT_MSG:
case CF_INIT_PROFILE_ENTITY_MSG:
case CF_INIT_MSG: case CF_INIT_MSG:
case CF_LINK_INIT_MSG: case CF_LINK_INIT_MSG:
case CF_STATE_RESTORE_MSG: case CF_STATE_RESTORE_MSG:

8
application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerActor.java

@ -22,8 +22,10 @@ import org.thingsboard.server.actors.TbActorException;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.TbActorStopReason; import org.thingsboard.server.common.msg.TbActorStopReason;
import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg; import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
import org.thingsboard.server.common.msg.cf.CalculatedFieldCacheInitMsg;
import org.thingsboard.server.common.msg.cf.CalculatedFieldEntityLifecycleMsg; import org.thingsboard.server.common.msg.cf.CalculatedFieldEntityLifecycleMsg;
import org.thingsboard.server.common.msg.cf.CalculatedFieldInitMsg; import org.thingsboard.server.common.msg.cf.CalculatedFieldInitMsg;
import org.thingsboard.server.common.msg.cf.CalculatedFieldInitProfileEntityMsg;
import org.thingsboard.server.common.msg.cf.CalculatedFieldLinkInitMsg; import org.thingsboard.server.common.msg.cf.CalculatedFieldLinkInitMsg;
import org.thingsboard.server.common.msg.cf.CalculatedFieldPartitionChangeMsg; import org.thingsboard.server.common.msg.cf.CalculatedFieldPartitionChangeMsg;
@ -65,6 +67,12 @@ public class CalculatedFieldManagerActor extends AbstractCalculatedFieldActor {
case CF_PARTITIONS_CHANGE_MSG: case CF_PARTITIONS_CHANGE_MSG:
processor.onPartitionChange((CalculatedFieldPartitionChangeMsg) msg); processor.onPartitionChange((CalculatedFieldPartitionChangeMsg) msg);
break; break;
case CF_CACHE_INIT_MSG:
processor.onCacheInitMsg((CalculatedFieldCacheInitMsg) msg);
break;
case CF_INIT_PROFILE_ENTITY_MSG:
processor.onProfileEntityMsg((CalculatedFieldInitProfileEntityMsg) msg);
break;
case CF_INIT_MSG: case CF_INIT_MSG:
processor.onFieldInitMsg((CalculatedFieldInitMsg) msg); processor.onFieldInitMsg((CalculatedFieldInitMsg) msg);
break; break;

135
application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerMessageProcessor.java

@ -24,6 +24,7 @@ import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor; import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ProfileEntityIdInfo;
import org.thingsboard.server.common.data.cf.CalculatedField; import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.cf.CalculatedFieldLink; import org.thingsboard.server.common.data.cf.CalculatedFieldLink;
import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.AssetId;
@ -31,17 +32,23 @@ import org.thingsboard.server.common.data.id.CalculatedFieldId;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.msg.cf.CalculatedFieldCacheInitMsg;
import org.thingsboard.server.common.msg.cf.CalculatedFieldEntityLifecycleMsg; import org.thingsboard.server.common.msg.cf.CalculatedFieldEntityLifecycleMsg;
import org.thingsboard.server.common.msg.cf.CalculatedFieldInitMsg; import org.thingsboard.server.common.msg.cf.CalculatedFieldInitMsg;
import org.thingsboard.server.common.msg.cf.CalculatedFieldInitProfileEntityMsg;
import org.thingsboard.server.common.msg.cf.CalculatedFieldLinkInitMsg; import org.thingsboard.server.common.msg.cf.CalculatedFieldLinkInitMsg;
import org.thingsboard.server.common.msg.cf.CalculatedFieldPartitionChangeMsg; import org.thingsboard.server.common.msg.cf.CalculatedFieldPartitionChangeMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.cf.CalculatedFieldService; import org.thingsboard.server.dao.cf.CalculatedFieldService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.queue.settings.TbQueueCalculatedFieldSettings;
import org.thingsboard.server.service.cf.CalculatedFieldProcessingService; import org.thingsboard.server.service.cf.CalculatedFieldProcessingService;
import org.thingsboard.server.service.cf.CalculatedFieldStateService; import org.thingsboard.server.service.cf.CalculatedFieldStateService;
import org.thingsboard.server.service.cf.cache.CalculatedFieldEntityProfileCache; import org.thingsboard.server.service.cf.cache.TenantEntityProfileCache;
import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId; import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId;
import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldCtx; import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldCtx;
import org.thingsboard.server.service.profile.TbAssetProfileCache; import org.thingsboard.server.service.profile.TbAssetProfileCache;
@ -68,22 +75,28 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
private final CalculatedFieldProcessingService cfExecService; private final CalculatedFieldProcessingService cfExecService;
private final CalculatedFieldStateService cfStateService; private final CalculatedFieldStateService cfStateService;
private final CalculatedFieldEntityProfileCache cfEntityCache;
private final CalculatedFieldService cfDaoService; private final CalculatedFieldService cfDaoService;
private final DeviceService deviceService;
private final AssetService assetService;
private final TbAssetProfileCache assetProfileCache; private final TbAssetProfileCache assetProfileCache;
private final TbDeviceProfileCache deviceProfileCache; private final TbDeviceProfileCache deviceProfileCache;
private final TenantEntityProfileCache entityProfileCache;
private final TbQueueCalculatedFieldSettings cfSettings;
protected final TenantId tenantId; protected final TenantId tenantId;
protected TbActorCtx ctx; protected TbActorCtx ctx;
CalculatedFieldManagerMessageProcessor(ActorSystemContext systemContext, TenantId tenantId) { CalculatedFieldManagerMessageProcessor(ActorSystemContext systemContext, TenantId tenantId) {
super(systemContext); super(systemContext);
this.cfEntityCache = systemContext.getCalculatedFieldEntityProfileCache();
this.cfExecService = systemContext.getCalculatedFieldProcessingService(); this.cfExecService = systemContext.getCalculatedFieldProcessingService();
this.cfStateService = systemContext.getCalculatedFieldStateService(); this.cfStateService = systemContext.getCalculatedFieldStateService();
this.cfDaoService = systemContext.getCalculatedFieldService(); this.cfDaoService = systemContext.getCalculatedFieldService();
this.deviceService = systemContext.getDeviceService();
this.assetService = systemContext.getAssetService();
this.assetProfileCache = systemContext.getAssetProfileCache(); this.assetProfileCache = systemContext.getAssetProfileCache();
this.deviceProfileCache = systemContext.getDeviceProfileCache(); this.deviceProfileCache = systemContext.getDeviceProfileCache();
this.entityProfileCache = new TenantEntityProfileCache();
this.cfSettings = systemContext.getCalculatedFieldSettings();
this.tenantId = tenantId; this.tenantId = tenantId;
} }
@ -100,6 +113,19 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
ctx.stop(ctx.getSelf()); ctx.stop(ctx.getSelf());
} }
public void onCacheInitMsg(CalculatedFieldCacheInitMsg msg) {
log.debug("[{}] Processing CF actor init message.", msg.getTenantId().getId());
initEntityProfileCache();
initCalculatedFields();
msg.getCallback().onSuccess();
}
public void onProfileEntityMsg(CalculatedFieldInitProfileEntityMsg msg) {
log.debug("[{}] Processing profile entity message.", msg.getTenantId().getId());
entityProfileCache.add(msg.getProfileEntityId(), msg.getEntityId());
msg.getCallback().onSuccess();
}
public void onFieldInitMsg(CalculatedFieldInitMsg msg) throws CalculatedFieldException { public void onFieldInitMsg(CalculatedFieldInitMsg msg) throws CalculatedFieldException {
log.debug("[{}] Processing CF init message.", msg.getCf().getId()); log.debug("[{}] Processing CF init message.", msg.getCf().getId());
var cf = msg.getCf(); var cf = msg.getCf();
@ -180,16 +206,35 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
} }
break; break;
} }
case DEVICE_PROFILE:
case ASSET_PROFILE: {
switch (event) {
case DELETED:
onProfileDeleted(msg.getData(), msg.getCallback());
break;
default:
msg.getCallback().onSuccess();
break;
}
break;
}
default: { default: {
msg.getCallback().onSuccess(); msg.getCallback().onSuccess();
} }
} }
} }
private void onProfileDeleted(ComponentLifecycleMsg msg, TbCallback callback) {
entityProfileCache.removeProfileId(msg.getEntityId());
callback.onSuccess();
}
private void onEntityCreated(ComponentLifecycleMsg msg, TbCallback callback) { private void onEntityCreated(ComponentLifecycleMsg msg, TbCallback callback) {
EntityId entityId = msg.getEntityId(); EntityId entityId = msg.getEntityId();
EntityId profileId = getProfileId(tenantId, entityId); EntityId profileId = getProfileId(tenantId, entityId);
cfEntityCache.add(tenantId, profileId, entityId); if (profileId != null) {
entityProfileCache.add(profileId, entityId);
}
if (!isMyPartition(entityId, callback)) { if (!isMyPartition(entityId, callback)) {
return; return;
} }
@ -207,7 +252,7 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
private void onEntityUpdated(ComponentLifecycleMsg msg, TbCallback callback) { private void onEntityUpdated(ComponentLifecycleMsg msg, TbCallback callback) {
if (msg.getOldProfileId() != null && !msg.getOldProfileId().equals(msg.getProfileId())) { if (msg.getOldProfileId() != null && !msg.getOldProfileId().equals(msg.getProfileId())) {
cfEntityCache.update(tenantId, msg.getOldProfileId(), msg.getProfileId(), msg.getEntityId()); entityProfileCache.update(msg.getOldProfileId(), msg.getProfileId(), msg.getEntityId());
if (!isMyPartition(msg.getEntityId(), callback)) { if (!isMyPartition(msg.getEntityId(), callback)) {
return; return;
} }
@ -226,7 +271,7 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
} }
private void onEntityDeleted(ComponentLifecycleMsg msg, TbCallback callback) { private void onEntityDeleted(ComponentLifecycleMsg msg, TbCallback callback) {
cfEntityCache.evict(tenantId, msg.getEntityId()); entityProfileCache.removeEntityId(msg.getEntityId());
if (isMyPartition(msg.getEntityId(), callback)) { if (isMyPartition(msg.getEntityId(), callback)) {
log.debug("Pushing entity lifecycle msg to specific actor [{}]", msg.getEntityId()); log.debug("Pushing entity lifecycle msg to specific actor [{}]", msg.getEntityId());
getOrCreateActor(msg.getEntityId()).tell(new CalculatedFieldEntityDeleteMsg(tenantId, msg.getEntityId(), callback)); getOrCreateActor(msg.getEntityId()).tell(new CalculatedFieldEntityDeleteMsg(tenantId, msg.getEntityId(), callback));
@ -322,11 +367,15 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
EntityId entityId = cfCtx.getEntityId(); EntityId entityId = cfCtx.getEntityId();
EntityType entityType = cfCtx.getEntityId().getEntityType(); EntityType entityType = cfCtx.getEntityId().getEntityType();
if (isProfileEntity(entityType)) { if (isProfileEntity(entityType)) {
var entityIds = cfEntityCache.getMyEntityIdsByProfileId(tenantId, entityId); var entityIds = entityProfileCache.getEntityIdsByProfileId(entityId);
if (!entityIds.isEmpty()) { if (!entityIds.isEmpty()) {
//TODO: no need to do this if we cache all created actors and know which one belong to us; //TODO: no need to do this if we cache all created actors and know which one belong to us;
var multiCallback = new MultipleTbCallback(entityIds.size(), callback); var multiCallback = new MultipleTbCallback(entityIds.size(), callback);
entityIds.forEach(id -> deleteCfForEntity(id, cfId, multiCallback)); entityIds.forEach(id -> {
if (isMyPartition(id, multiCallback)) {
deleteCfForEntity(id, cfId, multiCallback);
}
});
} else { } else {
callback.onSuccess(); callback.onSuccess();
} }
@ -366,10 +415,11 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
EntityId sourceEntityId = msg.getEntityId(); EntityId sourceEntityId = msg.getEntityId();
log.debug("Received linked telemetry msg from entity [{}]", sourceEntityId); log.debug("Received linked telemetry msg from entity [{}]", sourceEntityId);
var proto = msg.getProto(); var proto = msg.getProto();
var callback = msg.getCallback();
var linksList = proto.getLinksList(); var linksList = proto.getLinksList();
if (linksList.isEmpty()) { if (linksList.isEmpty()) {
log.debug("[{}] No CF links to process new telemetry.", msg.getTenantId()); log.debug("[{}] No CF links to process new telemetry.", msg.getTenantId());
msg.getCallback().onSuccess(); callback.onSuccess();
} }
for (var linkProto : linksList) { for (var linkProto : linksList) {
var link = fromProto(linkProto); var link = fromProto(linkProto);
@ -378,21 +428,23 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
var cf = calculatedFields.get(link.cfId()); var cf = calculatedFields.get(link.cfId());
if (EntityType.DEVICE_PROFILE.equals(targetEntityType) || EntityType.ASSET_PROFILE.equals(targetEntityType)) { if (EntityType.DEVICE_PROFILE.equals(targetEntityType) || EntityType.ASSET_PROFILE.equals(targetEntityType)) {
// iterate over all entities that belong to profile and push the message for corresponding CF // iterate over all entities that belong to profile and push the message for corresponding CF
var entityIds = cfEntityCache.getMyEntityIdsByProfileId(tenantId, targetEntityId); var entityIds = entityProfileCache.getEntityIdsByProfileId(targetEntityId);
if (!entityIds.isEmpty()) { if (!entityIds.isEmpty()) {
MultipleTbCallback callback = new MultipleTbCallback(entityIds.size(), msg.getCallback()); MultipleTbCallback multipleCallback = new MultipleTbCallback(entityIds.size(), callback);
var newMsg = new EntityCalculatedFieldLinkedTelemetryMsg(tenantId, sourceEntityId, proto.getMsg(), cf, callback); var newMsg = new EntityCalculatedFieldLinkedTelemetryMsg(tenantId, sourceEntityId, proto.getMsg(), cf, multipleCallback);
entityIds.forEach(entityId -> { entityIds.forEach(entityId -> {
log.debug("Pushing linked telemetry msg to specific actor [{}]", entityId); if (isMyPartition(entityId, multipleCallback)) {
getOrCreateActor(entityId).tell(newMsg); log.debug("Pushing linked telemetry msg to specific actor [{}]", entityId);
getOrCreateActor(entityId).tell(newMsg);
}
}); });
} else { } else {
msg.getCallback().onSuccess(); callback.onSuccess();
} }
} else { } else {
if (isMyPartition(targetEntityId, msg.getCallback())) { if (isMyPartition(targetEntityId, callback)) {
log.debug("Pushing linked telemetry msg to specific actor [{}]", targetEntityId); log.debug("Pushing linked telemetry msg to specific actor [{}]", targetEntityId);
var newMsg = new EntityCalculatedFieldLinkedTelemetryMsg(tenantId, sourceEntityId, proto.getMsg(), cf, msg.getCallback()); var newMsg = new EntityCalculatedFieldLinkedTelemetryMsg(tenantId, sourceEntityId, proto.getMsg(), cf, callback);
getOrCreateActor(targetEntityId).tell(newMsg); getOrCreateActor(targetEntityId).tell(newMsg);
} }
} }
@ -438,10 +490,14 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
EntityId entityId = cfCtx.getEntityId(); EntityId entityId = cfCtx.getEntityId();
EntityType entityType = cfCtx.getEntityId().getEntityType(); EntityType entityType = cfCtx.getEntityId().getEntityType();
if (isProfileEntity(entityType)) { if (isProfileEntity(entityType)) {
var entityIds = cfEntityCache.getMyEntityIdsByProfileId(tenantId, entityId); var entityIds = entityProfileCache.getEntityIdsByProfileId(entityId);
if (!entityIds.isEmpty()) { if (!entityIds.isEmpty()) {
var multiCallback = new MultipleTbCallback(entityIds.size(), callback); var multiCallback = new MultipleTbCallback(entityIds.size(), callback);
entityIds.forEach(id -> initCfForEntity(id, cfCtx, forceStateReinit, multiCallback)); entityIds.forEach(id -> {
if (isMyPartition(id, multiCallback)) {
initCfForEntity(id, cfCtx, forceStateReinit, multiCallback);
}
});
} else { } else {
callback.onSuccess(); callback.onSuccess();
} }
@ -504,4 +560,45 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
public void onPartitionChange(CalculatedFieldPartitionChangeMsg msg) { public void onPartitionChange(CalculatedFieldPartitionChangeMsg msg) {
ctx.broadcastToChildren(msg, true); ctx.broadcastToChildren(msg, true);
} }
public void initCalculatedFields() {
PageDataIterable<CalculatedField> cfs = new PageDataIterable<>(pageLink -> cfDaoService.findCalculatedFieldsByTenantId(tenantId, pageLink), cfSettings.getInitTenantFetchPackSize());
cfs.forEach(cf -> {
log.trace("Processing calculated field record: {}", cf);
try {
onFieldInitMsg(new CalculatedFieldInitMsg(cf.getTenantId(), cf));
} catch (CalculatedFieldException e) {
log.error("Failed to process calculated field record: {}", cf, e);
}
});
calculatedFields.values().forEach(cf -> {
entityIdCalculatedFields.computeIfAbsent(cf.getEntityId(), id -> new CopyOnWriteArrayList<>()).add(cf);
});
PageDataIterable<CalculatedFieldLink> cfls = new PageDataIterable<>(pageLink -> cfDaoService.findAllCalculatedFieldLinksByTenantId(tenantId, pageLink), cfSettings.getInitTenantFetchPackSize());
cfls.forEach(link -> {
onLinkInitMsg(new CalculatedFieldLinkInitMsg(link.getTenantId(), link));
});
}
private void initEntityProfileCache() {
PageDataIterable<ProfileEntityIdInfo> deviceIdInfos = new PageDataIterable<>(pageLink -> deviceService.findProfileEntityIdInfosByTenantId(tenantId, pageLink), cfSettings.getInitTenantFetchPackSize());
for (ProfileEntityIdInfo idInfo : deviceIdInfos) {
log.trace("Processing device record: {}", idInfo);
try {
entityProfileCache.add(idInfo.getProfileId(), idInfo.getEntityId());
} catch (Exception e) {
log.error("Failed to process device record: {}", idInfo, e);
}
}
PageDataIterable<ProfileEntityIdInfo> assetIdInfos = new PageDataIterable<>(pageLink -> assetService.findProfileEntityIdInfosByTenantId(tenantId, pageLink), cfSettings.getInitTenantFetchPackSize());
for (ProfileEntityIdInfo idInfo : assetIdInfos) {
log.trace("Processing asset record: {}", idInfo);
try {
entityProfileCache.add(idInfo.getProfileId(), idInfo.getEntityId());
} catch (Exception e) {
log.error("Failed to process asset record: {}", idInfo, e);
}
}
}
} }

5
application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java

@ -50,6 +50,7 @@ import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg; import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
import org.thingsboard.server.common.msg.aware.DeviceAwareMsg; import org.thingsboard.server.common.msg.aware.DeviceAwareMsg;
import org.thingsboard.server.common.msg.aware.RuleChainAwareMsg; import org.thingsboard.server.common.msg.aware.RuleChainAwareMsg;
import org.thingsboard.server.common.msg.cf.CalculatedFieldCacheInitMsg;
import org.thingsboard.server.common.msg.cf.CalculatedFieldEntityLifecycleMsg; import org.thingsboard.server.common.msg.cf.CalculatedFieldEntityLifecycleMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.PartitionChangeMsg; import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
@ -176,6 +177,8 @@ public class TenantActor extends RuleChainManagerActor {
case RULE_CHAIN_TO_RULE_CHAIN_MSG: case RULE_CHAIN_TO_RULE_CHAIN_MSG:
onRuleChainMsg((RuleChainAwareMsg) msg); onRuleChainMsg((RuleChainAwareMsg) msg);
break; break;
case CF_CACHE_INIT_MSG:
case CF_INIT_PROFILE_ENTITY_MSG:
case CF_INIT_MSG: case CF_INIT_MSG:
case CF_LINK_INIT_MSG: case CF_LINK_INIT_MSG:
case CF_STATE_RESTORE_MSG: case CF_STATE_RESTORE_MSG:
@ -199,6 +202,7 @@ public class TenantActor extends RuleChainManagerActor {
} else { } else {
log.debug("[{}] CF Actor is not initialized. ToCalculatedFieldSystemMsg: [{}]", tenantId, msg); log.debug("[{}] CF Actor is not initialized. ToCalculatedFieldSystemMsg: [{}]", tenantId, msg);
} }
msg.getCallback().onSuccess();
return; return;
} }
if (priority) { if (priority) {
@ -274,6 +278,7 @@ public class TenantActor extends RuleChainManagerActor {
() -> DefaultActorService.CF_MANAGER_DISPATCHER_NAME, () -> DefaultActorService.CF_MANAGER_DISPATCHER_NAME,
() -> new CalculatedFieldManagerActorCreator(systemContext, tenantId), () -> new CalculatedFieldManagerActorCreator(systemContext, tenantId),
() -> true); () -> true);
cfActor.tellWithHighPriority(new CalculatedFieldCacheInitMsg(tenantId));
} catch (Exception e) { } catch (Exception e) {
log.info("[{}] Failed to init CF Actor.", tenantId, e); log.info("[{}] Failed to init CF Actor.", tenantId, e);
} }

2
application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldCache.java

@ -64,7 +64,7 @@ public class DefaultCalculatedFieldCache implements CalculatedFieldCache {
private final ConcurrentMap<EntityId, List<CalculatedFieldLink>> entityIdCalculatedFieldLinks = new ConcurrentHashMap<>(); private final ConcurrentMap<EntityId, List<CalculatedFieldLink>> entityIdCalculatedFieldLinks = new ConcurrentHashMap<>();
private final ConcurrentMap<CalculatedFieldId, CalculatedFieldCtx> calculatedFieldsCtx = new ConcurrentHashMap<>(); private final ConcurrentMap<CalculatedFieldId, CalculatedFieldCtx> calculatedFieldsCtx = new ConcurrentHashMap<>();
@Value("${calculatedField.initFetchPackSize:50000}") @Value("${queue.calculated_fields.init_fetch_pack_size:50000}")
@Getter @Getter
private int initFetchPackSize; private int initFetchPackSize;

11
application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldInitService.java

@ -20,13 +20,14 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.ProfileEntityIdInfo; import org.thingsboard.server.common.data.ProfileEntityIdInfo;
import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.msg.cf.CalculatedFieldInitProfileEntityMsg;
import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.queue.util.AfterStartUp; import org.thingsboard.server.queue.util.AfterStartUp;
import org.thingsboard.server.queue.util.TbRuleEngineComponent; import org.thingsboard.server.queue.util.TbRuleEngineComponent;
import org.thingsboard.server.service.cf.cache.CalculatedFieldEntityProfileCache;
@Slf4j @Slf4j
@Service @Service
@ -34,11 +35,11 @@ import org.thingsboard.server.service.cf.cache.CalculatedFieldEntityProfileCache
@RequiredArgsConstructor @RequiredArgsConstructor
public class DefaultCalculatedFieldInitService implements CalculatedFieldInitService { public class DefaultCalculatedFieldInitService implements CalculatedFieldInitService {
private final CalculatedFieldEntityProfileCache entityProfileCache;
private final AssetService assetService; private final AssetService assetService;
private final DeviceService deviceService; private final DeviceService deviceService;
private final ActorSystemContext actorSystemContext;
@Value("${calculated_fields.init_fetch_pack_size:50000}") @Value("${queue.calculated_fields.init_fetch_pack_size:50000}")
@Getter @Getter
private int initFetchPackSize; private int initFetchPackSize;
@ -48,7 +49,7 @@ public class DefaultCalculatedFieldInitService implements CalculatedFieldInitSer
for (ProfileEntityIdInfo idInfo : deviceIdInfos) { for (ProfileEntityIdInfo idInfo : deviceIdInfos) {
log.trace("Processing device record: {}", idInfo); log.trace("Processing device record: {}", idInfo);
try { try {
entityProfileCache.add(idInfo.getTenantId(), idInfo.getProfileId(), idInfo.getEntityId()); actorSystemContext.tell(new CalculatedFieldInitProfileEntityMsg(idInfo.getTenantId(), idInfo.getProfileId(), idInfo.getEntityId()));
} catch (Exception e) { } catch (Exception e) {
log.error("Failed to process device record: {}", idInfo, e); log.error("Failed to process device record: {}", idInfo, e);
} }
@ -57,7 +58,7 @@ public class DefaultCalculatedFieldInitService implements CalculatedFieldInitSer
for (ProfileEntityIdInfo idInfo : assetIdInfos) { for (ProfileEntityIdInfo idInfo : assetIdInfos) {
log.trace("Processing asset record: {}", idInfo); log.trace("Processing asset record: {}", idInfo);
try { try {
entityProfileCache.add(idInfo.getTenantId(), idInfo.getProfileId(), idInfo.getEntityId()); actorSystemContext.tell(new CalculatedFieldInitProfileEntityMsg(idInfo.getTenantId(), idInfo.getProfileId(), idInfo.getEntityId()));
} catch (Exception e) { } catch (Exception e) {
log.error("Failed to process asset record: {}", idInfo, e); log.error("Failed to process asset record: {}", idInfo, e);
} }

95
application/src/main/java/org/thingsboard/server/service/cf/cache/DefaultCalculatedFieldEntityProfileCache.java

@ -1,95 +0,0 @@
/**
* 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.server.service.cf.cache;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.QueueKey;
import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.util.TbRuleEngineComponent;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;
@TbRuleEngineComponent
@Service
@Slf4j
@RequiredArgsConstructor
//TODO ashvayka: remove and use TenantEntityProfileCache in each CalculatedFieldManagerMessageProcessor;
public class DefaultCalculatedFieldEntityProfileCache extends TbApplicationEventListener<PartitionChangeEvent> implements CalculatedFieldEntityProfileCache {
private static final Integer UNKNOWN = 0;
private final ConcurrentMap<TenantId, TenantEntityProfileCache> tenantCache = new ConcurrentHashMap<>();
private final PartitionService partitionService;
private volatile List<Integer> myPartitions = Collections.emptyList();
@Override
protected void onTbApplicationEvent(PartitionChangeEvent event) {
myPartitions = event.getCfPartitions().stream()
.filter(TopicPartitionInfo::isMyPartition)
.map(tpi -> tpi.getPartition().orElse(UNKNOWN)).collect(Collectors.toList());
//Naive approach that need to be improved.
tenantCache.values().forEach(cache -> cache.setMyPartitions(myPartitions));
}
@Override
public void add(TenantId tenantId, EntityId profileId, EntityId entityId) {
var tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, DataConstants.CF_QUEUE_NAME, tenantId, entityId);
var partition = tpi.getPartition().orElse(UNKNOWN);
tenantCache.computeIfAbsent(tenantId, id -> new TenantEntityProfileCache())
.add(profileId, entityId, partition, tpi.isMyPartition());
}
@Override
public void update(TenantId tenantId, EntityId oldProfileId, EntityId newProfileId, EntityId entityId) {
var tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, DataConstants.CF_QUEUE_NAME, tenantId, entityId);
var partition = tpi.getPartition().orElse(UNKNOWN);
var cache = tenantCache.computeIfAbsent(tenantId, id -> new TenantEntityProfileCache());
//TODO: make this method atomic;
cache.remove(oldProfileId, entityId);
cache.add(newProfileId, entityId, partition, tpi.isMyPartition());
}
@Override
public void evict(TenantId tenantId, EntityId entityId) {
var cache = tenantCache.computeIfAbsent(tenantId, id -> new TenantEntityProfileCache());
cache.removeEntityId(entityId);
}
@Override
public Collection<EntityId> getMyEntityIdsByProfileId(TenantId tenantId, EntityId profileId) {
return tenantCache.computeIfAbsent(tenantId, id -> new TenantEntityProfileCache()).getMyEntityIdsByProfileId(profileId);
}
@Override
public int getEntityIdPartition(TenantId tenantId, EntityId entityId) {
var tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, DataConstants.CF_QUEUE_NAME, tenantId, entityId);
return tpi.getPartition().orElse(UNKNOWN);
}
}

50
application/src/main/java/org/thingsboard/server/service/cf/cache/TenantEntityProfileCache.java

@ -32,31 +32,13 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
public class TenantEntityProfileCache { public class TenantEntityProfileCache {
private final ReadWriteLock lock = new ReentrantReadWriteLock(); private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Map<Integer, Map<EntityId, Set<EntityId>>> allEntities = new HashMap<>(); private final Map<EntityId, Set<EntityId>> allEntities = new HashMap<>();
private final Map<EntityId, Set<EntityId>> myEntities = new HashMap<>();
public void setMyPartitions(List<Integer> myPartitions) {
lock.writeLock().lock();
try {
myEntities.clear();
myPartitions.forEach(partitionId -> {
var map = allEntities.get(partitionId);
if (map != null) {
map.forEach((profileId, entityIds) -> myEntities.computeIfAbsent(profileId, k -> new HashSet<>()).addAll(entityIds));
}
});
} finally {
lock.writeLock().unlock();
}
}
public void removeProfileId(EntityId profileId) { public void removeProfileId(EntityId profileId) {
lock.writeLock().lock(); lock.writeLock().lock();
try { try {
// Remove from allEntities // Remove from allEntities
allEntities.values().forEach(map -> map.remove(profileId)); allEntities.remove(profileId);
// Remove from myEntities
myEntities.remove(profileId);
} finally { } finally {
lock.writeLock().unlock(); lock.writeLock().unlock();
} }
@ -66,9 +48,7 @@ public class TenantEntityProfileCache {
lock.writeLock().lock(); lock.writeLock().lock();
try { try {
// Remove from allEntities // Remove from allEntities
allEntities.values().forEach(map -> map.values().forEach(set -> set.remove(entityId))); allEntities.values().forEach(set -> set.remove(entityId));
// Remove from myEntities
myEntities.values().forEach(set -> set.remove(entityId));
} finally { } finally {
lock.writeLock().unlock(); lock.writeLock().unlock();
} }
@ -78,33 +58,33 @@ public class TenantEntityProfileCache {
lock.writeLock().lock(); lock.writeLock().lock();
try { try {
// Remove from allEntities // Remove from allEntities
allEntities.values().forEach(map -> removeSafely(map, profileId, entityId)); removeSafely(allEntities, profileId, entityId);
// Remove from myEntities
removeSafely(myEntities, profileId, entityId);
} finally { } finally {
lock.writeLock().unlock(); lock.writeLock().unlock();
} }
} }
public void add(EntityId profileId, EntityId entityId, Integer partition, boolean mine) { public void add(EntityId profileId, EntityId entityId) {
lock.writeLock().lock(); lock.writeLock().lock();
try { try {
if(EntityType.DEVICE.equals(profileId.getEntityType())){ if (EntityType.DEVICE.equals(profileId.getEntityType()) || EntityType.ASSET.equals(profileId.getEntityType())) {
throw new RuntimeException("WTF?"); throw new RuntimeException("Entity type '" + profileId.getEntityType() + "' is not a profileId.");
}
if (mine) {
myEntities.computeIfAbsent(profileId, k -> new HashSet<>()).add(entityId);
} }
allEntities.computeIfAbsent(partition, k -> new HashMap<>()).computeIfAbsent(profileId, p -> new HashSet<>()).add(entityId); allEntities.computeIfAbsent(profileId, k -> new HashSet<>()).add(entityId);
} finally { } finally {
lock.writeLock().unlock(); lock.writeLock().unlock();
} }
} }
public Collection<EntityId> getMyEntityIdsByProfileId(EntityId profileId) { public void update(EntityId oldProfileId, EntityId newProfileId, EntityId entityId) {
remove(oldProfileId, entityId);
add(newProfileId, entityId);
}
public Collection<EntityId> getEntityIdsByProfileId(EntityId profileId) {
lock.readLock().lock(); lock.readLock().lock();
try { try {
var entities = myEntities.getOrDefault(profileId, Collections.emptySet()); var entities = allEntities.getOrDefault(profileId, Collections.emptySet());
List<EntityId> result = new ArrayList<>(entities.size()); List<EntityId> result = new ArrayList<>(entities.size());
result.addAll(entities); result.addAll(entities);
return result; return result;

2
application/src/main/java/org/thingsboard/server/service/cf/ctx/state/KafkaCalculatedFieldStateService.java

@ -91,7 +91,7 @@ public class KafkaCalculatedFieldStateService extends AbstractCalculatedFieldSta
} }
} }
}) })
.consumerCreator((config, partitionId) -> queueFactory.createCalculatedFieldStateConsumer()) .consumerCreator((queueConfig, tpi) -> queueFactory.createCalculatedFieldStateConsumer())
.queueAdmin(queueFactory.getCalculatedFieldQueueAdmin()) .queueAdmin(queueFactory.getCalculatedFieldQueueAdmin())
.consumerExecutor(eventConsumer.getConsumerExecutor()) .consumerExecutor(eventConsumer.getConsumerExecutor())
.scheduler(eventConsumer.getScheduler()) .scheduler(eventConsumer.getScheduler())

2
application/src/main/java/org/thingsboard/server/service/cf/ctx/state/RocksDBCalculatedFieldStateService.java

@ -22,11 +22,11 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.queue.common.state.DefaultQueueStateService;
import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldStateProto; import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldStateProto;
import org.thingsboard.server.gen.transport.TransportProtos.ToCalculatedFieldMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCalculatedFieldMsg;
import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.common.consumer.PartitionedQueueConsumerManager; import org.thingsboard.server.queue.common.consumer.PartitionedQueueConsumerManager;
import org.thingsboard.server.queue.common.state.DefaultQueueStateService;
import org.thingsboard.server.queue.discovery.QueueKey; import org.thingsboard.server.queue.discovery.QueueKey;
import org.thingsboard.server.service.cf.AbstractCalculatedFieldStateService; import org.thingsboard.server.service.cf.AbstractCalculatedFieldStateService;
import org.thingsboard.server.service.cf.CfRocksDb; import org.thingsboard.server.service.cf.CfRocksDb;

1
application/src/main/java/org/thingsboard/server/service/edge/rpc/KafkaEdgeGrpcSession.java

@ -141,6 +141,7 @@ public class KafkaEdgeGrpcSession extends EdgeGrpcSession {
String topic = topicService.buildEdgeEventNotificationsTopicPartitionInfo(tenantId, edge.getId()).getTopic(); String topic = topicService.buildEdgeEventNotificationsTopicPartitionInfo(tenantId, edge.getId()).getTopic();
TbKafkaAdmin kafkaAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getEdgeEventConfigs()); TbKafkaAdmin kafkaAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getEdgeEventConfigs());
kafkaAdmin.deleteTopic(topic); kafkaAdmin.deleteTopic(topic);
kafkaAdmin.deleteConsumerGroup(topic);
} }
} }

2
application/src/main/java/org/thingsboard/server/service/housekeeper/stats/HousekeeperStatsService.java

@ -106,7 +106,7 @@ public class HousekeeperStatsService {
this.failedProcessingCounter = register("failedProcessing", statsFactory); this.failedProcessingCounter = register("failedProcessing", statsFactory);
this.reprocessedCounter = register("reprocessed", statsFactory); this.reprocessedCounter = register("reprocessed", statsFactory);
this.failedReprocessingCounter = register("failedReprocessing", statsFactory); this.failedReprocessingCounter = register("failedReprocessing", statsFactory);
this.processingTimer = statsFactory.createTimer(StatsType.HOUSEKEEPER, "processingTime", "taskType", taskType.name()); this.processingTimer = statsFactory.createStatsTimer(StatsType.HOUSEKEEPER.getName(), "processingTime", "taskType", taskType.name());
} }
private StatsCounter register(String statsName, StatsFactory statsFactory) { private StatsCounter register(String statsName, StatsFactory statsFactory) {

10
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCalculatedFieldConsumerService.java

@ -99,12 +99,12 @@ public class DefaultTbCalculatedFieldConsumerService extends AbstractPartitionBa
@Override @Override
protected void onStartUp() { protected void onStartUp() {
var queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, DataConstants.CF_QUEUE_NAME); var queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, DataConstants.CF_QUEUE_NAME);
PartitionedQueueConsumerManager<TbProtoQueueMsg<ToCalculatedFieldMsg>> eventConsumer = PartitionedQueueConsumerManager.<TbProtoQueueMsg<ToCalculatedFieldMsg>>create() var eventConsumer = PartitionedQueueConsumerManager.<TbProtoQueueMsg<ToCalculatedFieldMsg>>create()
.queueKey(queueKey) .queueKey(queueKey)
.topic(partitionService.getTopic(queueKey)) .topic(partitionService.getTopic(queueKey))
.pollInterval(pollInterval) .pollInterval(pollInterval)
.msgPackProcessor(this::processMsgs) .msgPackProcessor(this::processMsgs)
.consumerCreator((config, partitionId) -> queueFactory.createToCalculatedFieldMsgConsumer()) .consumerCreator((queueConfig, tpi) -> queueFactory.createToCalculatedFieldMsgConsumer(tpi))
.queueAdmin(queueFactory.getCalculatedFieldQueueAdmin()) .queueAdmin(queueFactory.getCalculatedFieldQueueAdmin())
.consumerExecutor(consumersExecutor) .consumerExecutor(consumersExecutor)
.scheduler(scheduler) .scheduler(scheduler)
@ -127,7 +127,7 @@ public class DefaultTbCalculatedFieldConsumerService extends AbstractPartitionBa
protected void onPartitionChangeEvent(PartitionChangeEvent event) { protected void onPartitionChangeEvent(PartitionChangeEvent event) {
try { try {
event.getNewPartitions().forEach((queueKey, partitions) -> { event.getNewPartitions().forEach((queueKey, partitions) -> {
if (queueKey.getQueueName().equals(DataConstants.CF_QUEUE_NAME)) { if (DataConstants.CF_QUEUE_NAME.equals(queueKey.getQueueName())) {
stateService.restore(queueKey, partitions); stateService.restore(queueKey, partitions);
} }
}); });
@ -175,9 +175,7 @@ public class DefaultTbCalculatedFieldConsumerService extends AbstractPartitionBa
packSubmitFuture.cancel(true); packSubmitFuture.cancel(true);
log.info("Timeout to process message: {}", pendingMsgHolder.getMsg()); log.info("Timeout to process message: {}", pendingMsgHolder.getMsg());
} }
if (log.isDebugEnabled()) { ctx.getAckMap().forEach((id, msg) -> log.warn("[{}] Timeout to process message: {}", id, msg.getValue()));
ctx.getAckMap().forEach((id, msg) -> log.debug("[{}] Timeout to process message: {}", id, msg.getValue()));
}
ctx.getFailedMap().forEach((id, msg) -> log.warn("[{}] Failed to process message: {}", id, msg.getValue())); ctx.getFailedMap().forEach((id, msg) -> log.warn("[{}] Failed to process message: {}", id, msg.getValue()));
} }
consumer.commit(); consumer.commit();

4
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java

@ -20,6 +20,7 @@ import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy; import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
@ -116,6 +117,7 @@ import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Service @Service
@Slf4j
@TbCoreComponent @TbCoreComponent
public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCoreNotificationMsg> implements TbCoreConsumerService { public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCoreNotificationMsg> implements TbCoreConsumerService {
@ -204,7 +206,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
.queueKey(new QueueKey(ServiceType.TB_CORE)) .queueKey(new QueueKey(ServiceType.TB_CORE))
.config(QueueConfig.of(consumerPerPartition, pollInterval)) .config(QueueConfig.of(consumerPerPartition, pollInterval))
.msgPackProcessor(this::processMsgs) .msgPackProcessor(this::processMsgs)
.consumerCreator((config, partitionId) -> queueFactory.createToCoreMsgConsumer()) .consumerCreator((config, tpi) -> queueFactory.createToCoreMsgConsumer())
.consumerExecutor(consumersExecutor) .consumerExecutor(consumersExecutor)
.scheduler(scheduler) .scheduler(scheduler)
.taskExecutor(mgmtExecutor) .taskExecutor(mgmtExecutor)

2
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbEdgeConsumerService.java

@ -102,7 +102,7 @@ public class DefaultTbEdgeConsumerService extends AbstractConsumerService<ToEdge
.queueKey(new QueueKey(ServiceType.TB_CORE).withQueueName(DataConstants.EDGE_QUEUE_NAME)) .queueKey(new QueueKey(ServiceType.TB_CORE).withQueueName(DataConstants.EDGE_QUEUE_NAME))
.config(QueueConfig.of(consumerPerPartition, pollInterval)) .config(QueueConfig.of(consumerPerPartition, pollInterval))
.msgPackProcessor(this::processMsgs) .msgPackProcessor(this::processMsgs)
.consumerCreator((config, partitionId) -> queueFactory.createEdgeMsgConsumer()) .consumerCreator((config, tpi) -> queueFactory.createEdgeMsgConsumer())
.consumerExecutor(consumersExecutor) .consumerExecutor(consumersExecutor)
.scheduler(scheduler) .scheduler(scheduler)
.taskExecutor(mgmtExecutor) .taskExecutor(mgmtExecutor)

7
application/src/main/java/org/thingsboard/server/service/queue/ruleengine/TbRuleEngineQueueConsumerManager.java

@ -72,7 +72,12 @@ public class TbRuleEngineQueueConsumerManager extends MainQueueConsumerManager<T
ExecutorService consumerExecutor, ExecutorService consumerExecutor,
ScheduledExecutorService scheduler, ScheduledExecutorService scheduler,
ExecutorService taskExecutor) { ExecutorService taskExecutor) {
super(queueKey, null, null, ctx.getQueueFactory()::createToRuleEngineMsgConsumer, consumerExecutor, scheduler, taskExecutor, null); super(queueKey, null, null,
(queueConfig, tpi) -> {
Integer partitionId = tpi != null ? tpi.getPartition().orElse(-1) : null;
return ctx.getQueueFactory().createToRuleEngineMsgConsumer(queueConfig, partitionId);
},
consumerExecutor, scheduler, taskExecutor, null);
this.ctx = ctx; this.ctx = ctx;
this.stats = new TbRuleEngineConsumerStats(queueKey, ctx.getStatsFactory()); this.stats = new TbRuleEngineConsumerStats(queueKey, ctx.getStatsFactory());
} }

13
application/src/main/java/org/thingsboard/server/service/ttl/KafkaEdgeTopicsCleanUpService.java

@ -113,7 +113,7 @@ public class KafkaEdgeTopicsCleanUpService extends AbstractCleanUpService {
.ifPresentOrElse(lastConnectTime -> { .ifPresentOrElse(lastConnectTime -> {
String topic = topicService.buildEdgeEventNotificationsTopicPartitionInfo(tenantId, edgeId).getTopic(); String topic = topicService.buildEdgeEventNotificationsTopicPartitionInfo(tenantId, edgeId).getTopic();
if (kafkaAdmin.isTopicEmpty(topic)) { if (kafkaAdmin.isTopicEmpty(topic)) {
kafkaAdmin.deleteTopic(topic); deleteTopicAndConsumerGroup(topic);
log.info("[{}] Removed outdated topic {} for edge {} older than {}", log.info("[{}] Removed outdated topic {} for edge {} older than {}",
tenantId, topic, edgeId, Date.from(Instant.ofEpochMilli(currentTimeMillis - ttlMillis))); tenantId, topic, edgeId, Date.from(Instant.ofEpochMilli(currentTimeMillis - ttlMillis)));
} }
@ -121,7 +121,7 @@ public class KafkaEdgeTopicsCleanUpService extends AbstractCleanUpService {
Edge edge = edgeService.findEdgeById(tenantId, edgeId); Edge edge = edgeService.findEdgeById(tenantId, edgeId);
if (edge == null) { if (edge == null) {
String topic = topicService.buildEdgeEventNotificationsTopicPartitionInfo(tenantId, edgeId).getTopic(); String topic = topicService.buildEdgeEventNotificationsTopicPartitionInfo(tenantId, edgeId).getTopic();
kafkaAdmin.deleteTopic(topic); deleteTopicAndConsumerGroup(topic);
log.info("[{}] Removed topic {} for deleted edge {}", tenantId, topic, edgeId); log.info("[{}] Removed topic {} for deleted edge {}", tenantId, topic, edgeId);
} }
}); });
@ -132,12 +132,17 @@ public class KafkaEdgeTopicsCleanUpService extends AbstractCleanUpService {
} else { } else {
for (EdgeId edgeId : edgeIds) { for (EdgeId edgeId : edgeIds) {
String topic = topicService.buildEdgeEventNotificationsTopicPartitionInfo(tenantId, edgeId).getTopic(); String topic = topicService.buildEdgeEventNotificationsTopicPartitionInfo(tenantId, edgeId).getTopic();
kafkaAdmin.deleteTopic(topic); deleteTopicAndConsumerGroup(topic);
} }
log.info("[{}] Removed topics for not existing tenant and edges {}", tenantId, edgeIds); log.info("[{}] Removed topics for not existing tenant and edges {}", tenantId, edgeIds);
} }
} }
private void deleteTopicAndConsumerGroup(String topic) {
kafkaAdmin.deleteTopic(topic);
kafkaAdmin.deleteConsumerGroup(topic);
}
private boolean isTopicExpired(long lastConnectTime, long ttlMillis, long currentTimeMillis) { private boolean isTopicExpired(long lastConnectTime, long ttlMillis, long currentTimeMillis) {
return lastConnectTime + ttlMillis < currentTimeMillis; return lastConnectTime + ttlMillis < currentTimeMillis;
} }
@ -146,7 +151,7 @@ public class KafkaEdgeTopicsCleanUpService extends AbstractCleanUpService {
Map<TenantId, List<EdgeId>> tenantEdgeMap = new HashMap<>(); Map<TenantId, List<EdgeId>> tenantEdgeMap = new HashMap<>();
for (String topic : topics) { for (String topic : topics) {
try { try {
String remaining = topic.substring(prefix.length()); String remaining = topic.substring(prefix.length() + 1);
String[] parts = remaining.split("\\."); String[] parts = remaining.split("\\.");
TenantId tenantId = TenantId.fromUUID(UUID.fromString(parts[0])); TenantId tenantId = TenantId.fromUUID(UUID.fromString(parts[0]));
EdgeId edgeId = new EdgeId(UUID.fromString(parts[1])); EdgeId edgeId = new EdgeId(UUID.fromString(parts[1]));

6
application/src/main/resources/thingsboard.yml

@ -1778,7 +1778,7 @@ queue:
# EDQS responses topic # EDQS responses topic
responses_topic: "${TB_EDQS_RESPONSES_TOPIC:edqs.responses}" responses_topic: "${TB_EDQS_RESPONSES_TOPIC:edqs.responses}"
# Poll interval for EDQS topics # Poll interval for EDQS topics
poll_interval: "${TB_EDQS_POLL_INTERVAL_MS:125}" poll_interval: "${TB_EDQS_POLL_INTERVAL_MS:25}"
# Maximum amount of pending requests to EDQS # Maximum amount of pending requests to EDQS
max_pending_requests: "${TB_EDQS_MAX_PENDING_REQUESTS:10000}" max_pending_requests: "${TB_EDQS_MAX_PENDING_REQUESTS:10000}"
# Maximum timeout for requests to EDQS # Maximum timeout for requests to EDQS
@ -1853,6 +1853,10 @@ queue:
pool_size: "${TB_QUEUE_CF_POOL_SIZE:8}" pool_size: "${TB_QUEUE_CF_POOL_SIZE:8}"
# RocksDB path for storing CF states # RocksDB path for storing CF states
rocks_db_path: "${TB_QUEUE_CF_ROCKS_DB_PATH:${user.home}/.rocksdb/cf_states}" rocks_db_path: "${TB_QUEUE_CF_ROCKS_DB_PATH:${user.home}/.rocksdb/cf_states}"
# The fetch size specifies how many rows will be fetched from the database per request for initial fetching
init_fetch_pack_size: "${TB_QUEUE_CF_FETCH_PACK_SIZE:50000}"
# The fetch size specifies how many rows will be fetched from the database per request for per-tenant fetching
init_tenant_fetch_pack_size: "${TB_QUEUE_CF_TENANT_FETCH_PACK_SIZE:1000}"
transport: transport:
# For high-priority notifications that require minimum latency and processing time # For high-priority notifications that require minimum latency and processing time
notifications_topic: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_TOPIC:tb_transport.notifications}" notifications_topic: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_TOPIC:tb_transport.notifications}"

23
application/src/test/java/org/thingsboard/server/service/entitiy/EntityServiceTest.java

@ -1116,6 +1116,29 @@ public class EntityServiceTest extends AbstractControllerTest {
assertThat(deviceName).isEqualTo(devices.get(0).getName()); assertThat(deviceName).isEqualTo(devices.get(0).getName());
} }
@Test
public void testFindCustomerBySingleEntityFilter() {
SingleEntityFilter singleEntityFilter = new SingleEntityFilter();
singleEntityFilter.setSingleEntity(customerId);
List<EntityKey> entityFields = List.of(
new EntityKey(EntityKeyType.ENTITY_FIELD, "name")
);
EntityDataPageLink pageLink = new EntityDataPageLink(1000, 0, null, null);
EntityDataQuery query = new EntityDataQuery(singleEntityFilter, pageLink, entityFields, null, null);
//find by tenant
PageData<EntityData> result = findByQueryAndCheck(query, 1);
String customerName = result.getData().get(0).getLatest().get(EntityKeyType.ENTITY_FIELD).get("name").getValue();
assertThat(customerName).isEqualTo(TEST_CUSTOMER_NAME);
// find by customer user
PageData<EntityData> customerResults = findByQueryAndCheck(customerId, query, 1);
customerName = customerResults.getData().get(0).getLatest().get(EntityKeyType.ENTITY_FIELD).get("name").getValue();
assertThat(customerName).isEqualTo(TEST_CUSTOMER_NAME);
}
@Test @Test
public void testFindEntitiesByRelationEntityTypeFilter() { public void testFindEntitiesByRelationEntityTypeFilter() {
Customer customer = new Customer(); Customer customer = new Customer();

22
common/cluster-api/src/main/java/org/thingsboard/server/queue/TbEdgeQueueAdmin.java

@ -0,0 +1,22 @@
/**
* 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.server.queue;
public interface TbEdgeQueueAdmin extends TbQueueAdmin {
void syncEdgeNotificationsOffsets(String fatGroupId, String newGroupId);
void deleteConsumerGroup(String consumerGroupId);
}

2
common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetService.java

@ -66,6 +66,8 @@ public interface AssetService extends EntityDaoService {
PageData<ProfileEntityIdInfo> findProfileEntityIdInfos(PageLink pageLink); PageData<ProfileEntityIdInfo> findProfileEntityIdInfos(PageLink pageLink);
PageData<ProfileEntityIdInfo> findProfileEntityIdInfosByTenantId(TenantId tenantId, PageLink pageLink);
PageData<AssetId> findAssetIdsByTenantIdAndAssetProfileId(TenantId tenantId, AssetProfileId assetProfileId, PageLink pageLink); PageData<AssetId> findAssetIdsByTenantIdAndAssetProfileId(TenantId tenantId, AssetProfileId assetProfileId, PageLink pageLink);
ListenableFuture<List<Asset>> findAssetsByTenantIdAndIdsAsync(TenantId tenantId, List<AssetId> assetIds); ListenableFuture<List<Asset>> findAssetsByTenantIdAndIdsAsync(TenantId tenantId, List<AssetId> assetIds);

4
common/dao-api/src/main/java/org/thingsboard/server/dao/cf/CalculatedFieldService.java

@ -39,6 +39,8 @@ public interface CalculatedFieldService extends EntityDaoService {
PageData<CalculatedField> findAllCalculatedFields(PageLink pageLink); PageData<CalculatedField> findAllCalculatedFields(PageLink pageLink);
PageData<CalculatedField> findCalculatedFieldsByTenantId(TenantId tenantId, PageLink pageLink);
PageData<CalculatedField> findAllCalculatedFieldsByEntityId(TenantId tenantId, EntityId entityId, PageLink pageLink); PageData<CalculatedField> findAllCalculatedFieldsByEntityId(TenantId tenantId, EntityId entityId, PageLink pageLink);
void deleteCalculatedField(TenantId tenantId, CalculatedFieldId calculatedFieldId); void deleteCalculatedField(TenantId tenantId, CalculatedFieldId calculatedFieldId);
@ -53,6 +55,8 @@ public interface CalculatedFieldService extends EntityDaoService {
List<CalculatedFieldLink> findAllCalculatedFieldLinksByEntityId(TenantId tenantId, EntityId entityId); List<CalculatedFieldLink> findAllCalculatedFieldLinksByEntityId(TenantId tenantId, EntityId entityId);
PageData<CalculatedFieldLink> findAllCalculatedFieldLinksByTenantId(TenantId tenantId, PageLink pageLink);
PageData<CalculatedFieldLink> findAllCalculatedFieldLinks(PageLink pageLink); PageData<CalculatedFieldLink> findAllCalculatedFieldLinks(PageLink pageLink);
boolean referencedInAnyCalculatedField(TenantId tenantId, EntityId referencedEntityId); boolean referencedInAnyCalculatedField(TenantId tenantId, EntityId referencedEntityId);

2
common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java

@ -76,6 +76,8 @@ public interface DeviceService extends EntityDaoService {
PageData<ProfileEntityIdInfo> findProfileEntityIdInfos(PageLink pageLink); PageData<ProfileEntityIdInfo> findProfileEntityIdInfos(PageLink pageLink);
PageData<ProfileEntityIdInfo> findProfileEntityIdInfosByTenantId(TenantId tenantId, PageLink pageLink);
PageData<Device> findDevicesByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink); PageData<Device> findDevicesByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink);
PageData<DeviceId> findDeviceIdsByTenantIdAndDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId, PageLink pageLink); PageData<DeviceId> findDeviceIdsByTenantIdAndDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId, PageLink pageLink);

2
common/edqs/src/main/java/org/thingsboard/server/edqs/data/BaseEntityData.java

@ -125,7 +125,7 @@ public abstract class BaseEntityData<T extends EntityFields> implements EntityDa
return switch (key) { return switch (key) {
case "createdTime" -> new LongDataPoint(System.currentTimeMillis(), fields.getCreatedTime()); case "createdTime" -> new LongDataPoint(System.currentTimeMillis(), fields.getCreatedTime());
case "edgeTemplate" -> new BoolDataPoint(System.currentTimeMillis(), fields.isEdgeTemplate()); case "edgeTemplate" -> new BoolDataPoint(System.currentTimeMillis(), fields.isEdgeTemplate());
case "parentId" -> new StringDataPoint(System.currentTimeMillis(), getRelatedParentId(ctx)); case "parentId" -> new StringDataPoint(System.currentTimeMillis(), getRelatedParentId(ctx), false);
default -> new StringDataPoint(System.currentTimeMillis(), getField(key), false); default -> new StringDataPoint(System.currentTimeMillis(), getField(key), false);
}; };
} }

6
common/edqs/src/main/java/org/thingsboard/server/edqs/data/dp/CompressedJsonDataPoint.java

@ -17,10 +17,12 @@ package org.thingsboard.server.edqs.data.dp;
import org.thingsboard.server.common.data.kv.DataType; import org.thingsboard.server.common.data.kv.DataType;
import java.util.function.Function;
public class CompressedJsonDataPoint extends CompressedStringDataPoint { public class CompressedJsonDataPoint extends CompressedStringDataPoint {
public CompressedJsonDataPoint(long ts, byte[] compressedValue) { public CompressedJsonDataPoint(long ts, byte[] compressedValue, Function<byte[], String> uncompressor) {
super(ts, compressedValue); super(ts, compressedValue, uncompressor);
} }
@Override @Override

10
common/edqs/src/main/java/org/thingsboard/server/edqs/data/dp/CompressedStringDataPoint.java

@ -19,17 +19,21 @@ import lombok.Getter;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import org.thingsboard.common.util.TbBytePool; import org.thingsboard.common.util.TbBytePool;
import org.thingsboard.server.common.data.kv.DataType; import org.thingsboard.server.common.data.kv.DataType;
import org.xerial.snappy.Snappy;
import java.util.function.Function;
public class CompressedStringDataPoint extends AbstractDataPoint { public class CompressedStringDataPoint extends AbstractDataPoint {
@Getter @Getter
private final byte[] compressedValue; private final byte[] compressedValue;
protected final Function<byte[], String> uncompressor;
@SneakyThrows @SneakyThrows
public CompressedStringDataPoint(long ts, byte[] compressedValue) { public CompressedStringDataPoint(long ts, byte[] compressedValue, Function<byte[], String> uncompressor) {
super(ts); super(ts);
this.compressedValue = TbBytePool.intern(compressedValue); this.compressedValue = TbBytePool.intern(compressedValue);
this.uncompressor = uncompressor;
} }
@Override @Override
@ -40,7 +44,7 @@ public class CompressedStringDataPoint extends AbstractDataPoint {
@SneakyThrows @SneakyThrows
@Override @Override
public String getStr() { public String getStr() {
return Snappy.uncompressString(compressedValue); return uncompressor.apply(compressedValue);
} }
@Override @Override

2
common/edqs/src/main/java/org/thingsboard/server/edqs/processor/EdqsProcessor.java

@ -139,7 +139,7 @@ public class EdqsProcessor implements TbQueueHandler<TbProtoQueueMsg<ToEdqsMsg>,
} }
consumer.commit(); consumer.commit();
}) })
.consumerCreator((config, partitionId) -> queueFactory.createEdqsEventsConsumer()) .consumerCreator((config, tpi) -> queueFactory.createEdqsEventsConsumer())
.queueAdmin(queueFactory.getEdqsQueueAdmin()) .queueAdmin(queueFactory.getEdqsQueueAdmin())
.consumerExecutor(consumersExecutor) .consumerExecutor(consumersExecutor)
.taskExecutor(taskExecutor) .taskExecutor(taskExecutor)

5
common/edqs/src/main/java/org/thingsboard/server/edqs/query/processor/AbstractQueryProcessor.java

@ -64,8 +64,9 @@ public abstract class AbstractQueryProcessor<T extends EntityFilter> implements
} }
protected static boolean checkCustomerId(UUID customerId, EntityData<?> ed) { protected static boolean checkCustomerId(UUID customerId, EntityData<?> ed) {
return customerId.equals(ed.getCustomerId()) || (ed.getEntityType() == EntityType.DASHBOARD && return customerId.equals(ed.getCustomerId())
ed.getFields().getAssignedCustomerIds().contains(customerId)); || (ed.getEntityType() == EntityType.DASHBOARD && ed.getFields().getAssignedCustomerIds().contains(customerId))
|| (ed.getEntityType() == EntityType.CUSTOMER && customerId.equals(ed.getId()));
} }
protected boolean matches(EntityData<?> ed) { protected boolean matches(EntityData<?> ed) {

9
common/edqs/src/main/java/org/thingsboard/server/edqs/repo/DefaultEdqsRepository.java

@ -16,6 +16,7 @@
package org.thingsboard.server.edqs.repo; package org.thingsboard.server.edqs.repo;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.ObjectType; import org.thingsboard.server.common.data.ObjectType;
@ -40,6 +41,7 @@ import java.util.function.Predicate;
@Slf4j @Slf4j
public class DefaultEdqsRepository implements EdqsRepository { public class DefaultEdqsRepository implements EdqsRepository {
@Getter
private final static ConcurrentMap<TenantId, TenantRepo> repos = new ConcurrentHashMap<>(); private final static ConcurrentMap<TenantId, TenantRepo> repos = new ConcurrentHashMap<>();
private final EdqsStatsService statsService; private final EdqsStatsService statsService;
@ -52,6 +54,7 @@ public class DefaultEdqsRepository implements EdqsRepository {
if (event.getEventType() == EdqsEventType.DELETED && event.getObjectType() == ObjectType.TENANT) { if (event.getEventType() == EdqsEventType.DELETED && event.getObjectType() == ObjectType.TENANT) {
log.info("Tenant {} deleted", event.getTenantId()); log.info("Tenant {} deleted", event.getTenantId());
repos.remove(event.getTenantId()); repos.remove(event.getTenantId());
statsService.reportRemoved(ObjectType.TENANT);
} else { } else {
get(event.getTenantId()).processEvent(event); get(event.getTenantId()).processEvent(event);
} }
@ -61,8 +64,7 @@ public class DefaultEdqsRepository implements EdqsRepository {
public long countEntitiesByQuery(TenantId tenantId, CustomerId customerId, EntityCountQuery query, boolean ignorePermissionCheck) { public long countEntitiesByQuery(TenantId tenantId, CustomerId customerId, EntityCountQuery query, boolean ignorePermissionCheck) {
long startNs = System.nanoTime(); long startNs = System.nanoTime();
long result = get(tenantId).countEntitiesByQuery(customerId, query, ignorePermissionCheck); long result = get(tenantId).countEntitiesByQuery(customerId, query, ignorePermissionCheck);
double timingMs = (double) (System.nanoTime() - startNs) / 1000_000; statsService.reportEdqsCountQuery(tenantId, query, System.nanoTime() - startNs);
log.info("countEntitiesByQuery done in {} ms", timingMs);
return result; return result;
} }
@ -71,8 +73,7 @@ public class DefaultEdqsRepository implements EdqsRepository {
EntityDataQuery query, boolean ignorePermissionCheck) { EntityDataQuery query, boolean ignorePermissionCheck) {
long startNs = System.nanoTime(); long startNs = System.nanoTime();
var result = get(tenantId).findEntityDataByQuery(customerId, query, ignorePermissionCheck); var result = get(tenantId).findEntityDataByQuery(customerId, query, ignorePermissionCheck);
double timingMs = (double) (System.nanoTime() - startNs) / 1000_000; statsService.reportEdqsDataQuery(tenantId, query, System.nanoTime() - startNs);
log.info("findEntityDataByQuery done in {} ms", timingMs);
return result; return result;
} }

2
common/edqs/src/main/java/org/thingsboard/server/edqs/repo/TenantRepo.java

@ -332,7 +332,6 @@ public class TenantRepo {
public PageData<QueryResult> findEntityDataByQuery(CustomerId customerId, EntityDataQuery oldQuery, boolean ignorePermissionCheck) { public PageData<QueryResult> findEntityDataByQuery(CustomerId customerId, EntityDataQuery oldQuery, boolean ignorePermissionCheck) {
EdqsDataQuery query = RepositoryUtils.toNewQuery(oldQuery); EdqsDataQuery query = RepositoryUtils.toNewQuery(oldQuery);
log.info("[{}][{}] findEntityDataByQuery: {}", tenantId, customerId, query);
QueryContext ctx = buildContext(customerId, query.getEntityFilter(), ignorePermissionCheck); QueryContext ctx = buildContext(customerId, query.getEntityFilter(), ignorePermissionCheck);
EntityQueryProcessor queryProcessor = EntityQueryProcessorFactory.create(this, ctx, query); EntityQueryProcessor queryProcessor = EntityQueryProcessorFactory.create(this, ctx, query);
return sortAndConvert(query, queryProcessor.processQuery(), ctx); return sortAndConvert(query, queryProcessor.processQuery(), ctx);
@ -340,7 +339,6 @@ public class TenantRepo {
public long countEntitiesByQuery(CustomerId customerId, EntityCountQuery oldQuery, boolean ignorePermissionCheck) { public long countEntitiesByQuery(CustomerId customerId, EntityCountQuery oldQuery, boolean ignorePermissionCheck) {
EdqsQuery query = RepositoryUtils.toNewQuery(oldQuery); EdqsQuery query = RepositoryUtils.toNewQuery(oldQuery);
log.info("[{}][{}] countEntitiesByQuery: {}", tenantId, customerId, query);
QueryContext ctx = buildContext(customerId, query.getEntityFilter(), ignorePermissionCheck); QueryContext ctx = buildContext(customerId, query.getEntityFilter(), ignorePermissionCheck);
EntityQueryProcessor queryProcessor = EntityQueryProcessorFactory.create(this, ctx, query); EntityQueryProcessor queryProcessor = EntityQueryProcessorFactory.create(this, ctx, query);
return queryProcessor.count(); return queryProcessor.count();

5
common/edqs/src/main/java/org/thingsboard/server/edqs/state/KafkaEdqsStateService.java

@ -59,7 +59,8 @@ public class KafkaEdqsStateService implements EdqsStateService {
private final EdqsConfig config; private final EdqsConfig config;
private final EdqsPartitionService partitionService; private final EdqsPartitionService partitionService;
private final KafkaEdqsQueueFactory queueFactory; private final KafkaEdqsQueueFactory queueFactory;
@Autowired @Lazy @Autowired
@Lazy
private EdqsProcessor edqsProcessor; private EdqsProcessor edqsProcessor;
private PartitionedQueueConsumerManager<TbProtoQueueMsg<ToEdqsMsg>> stateConsumer; private PartitionedQueueConsumerManager<TbProtoQueueMsg<ToEdqsMsg>> stateConsumer;
@ -93,7 +94,7 @@ public class KafkaEdqsStateService implements EdqsStateService {
} }
consumer.commit(); consumer.commit();
}) })
.consumerCreator((config, partitionId) -> queueFactory.createEdqsStateConsumer()) .consumerCreator((config, tpi) -> queueFactory.createEdqsStateConsumer())
.queueAdmin(queueAdmin) .queueAdmin(queueAdmin)
.consumerExecutor(eventConsumer.getConsumerExecutor()) .consumerExecutor(eventConsumer.getConsumerExecutor())
.taskExecutor(eventConsumer.getTaskExecutor()) .taskExecutor(eventConsumer.getTaskExecutor())

89
common/edqs/src/main/java/org/thingsboard/server/edqs/stats/DefaultEdqsStatsService.java

@ -15,78 +15,117 @@
*/ */
package org.thingsboard.server.edqs.stats; package org.thingsboard.server.edqs.stats;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.TbBytePool;
import org.thingsboard.common.util.TbStringPool;
import org.thingsboard.server.common.data.ObjectType; import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.query.EntityCountQuery; import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityDataQuery; import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.common.stats.EdqsStatsService; import org.thingsboard.server.common.stats.EdqsStatsService;
import org.thingsboard.server.common.stats.StatsCounter;
import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.common.stats.StatsTimer; import org.thingsboard.server.common.stats.StatsTimer;
import org.thingsboard.server.common.stats.StatsType; import org.thingsboard.server.edqs.repo.DefaultEdqsRepository;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@Service @Service
@Slf4j @Slf4j
@RequiredArgsConstructor
@ConditionalOnExpression("'${queue.edqs.api.supported:true}' == 'true' && '${queue.edqs.stats.enabled:true}' == 'true'") @ConditionalOnExpression("'${queue.edqs.api.supported:true}' == 'true' && '${queue.edqs.stats.enabled:true}' == 'true'")
public class DefaultEdqsStatsService implements EdqsStatsService { public class DefaultEdqsStatsService implements EdqsStatsService {
private final StatsFactory statsFactory; private final StatsFactory statsFactory;
@Value("${queue.edqs.stats.slow_query_threshold:3000}") @Value("${queue.edqs.stats.slow_query_threshold}")
private int slowQueryThreshold; private int slowQueryThreshold;
private final ConcurrentHashMap<ObjectType, AtomicInteger> objectCounters = new ConcurrentHashMap<>(); private final ConcurrentMap<ObjectType, AtomicInteger> objectCounters = new ConcurrentHashMap<>();
private final StatsTimer dataQueryTimer; private final ConcurrentMap<String, StatsTimer> timers = new ConcurrentHashMap<>();
private final StatsTimer countQueryTimer; private final ConcurrentMap<String, StatsCounter> counters = new ConcurrentHashMap<>();
private DefaultEdqsStatsService(StatsFactory statsFactory) { @PostConstruct
this.statsFactory = statsFactory; private void init() {
dataQueryTimer = statsFactory.createTimer(StatsType.EDQS, "entityDataQueryTimer"); statsFactory.createGauge("edqsMapGauges", "stringPoolSize", TbStringPool.getPool(), Map::size);
countQueryTimer = statsFactory.createTimer(StatsType.EDQS, "entityCountQueryTimer"); statsFactory.createGauge("edqsMapGauges", "bytePoolSize", TbBytePool.getPool(), Map::size);
statsFactory.createGauge("edqsMapGauges", "tenantReposSize", DefaultEdqsRepository.getRepos(), Map::size);
} }
@Override @Override
public void reportAdded(ObjectType objectType) { public void reportAdded(ObjectType objectType) {
getObjectCounter(objectType).incrementAndGet(); getObjectGauge(objectType).incrementAndGet();
} }
@Override @Override
public void reportRemoved(ObjectType objectType) { public void reportRemoved(ObjectType objectType) {
getObjectCounter(objectType).decrementAndGet(); getObjectGauge(objectType).decrementAndGet();
} }
@Override @Override
public void reportDataQuery(TenantId tenantId, EntityDataQuery query, long timingNanos) { public void reportEntityDataQuery(TenantId tenantId, EntityDataQuery query, long timingNanos) {
double timingMs = timingNanos / 1000_000.0; checkTiming(tenantId, query, timingNanos);
if (timingMs < slowQueryThreshold) { getTimer("entityDataQueryTimer").record(timingNanos, TimeUnit.NANOSECONDS);
log.debug("[{}] Executed data query in {} ms: {}", tenantId, timingMs, query); }
} else {
log.warn("[{}] Executed slow data query in {} ms: {}", tenantId, timingMs, query); @Override
} public void reportEntityCountQuery(TenantId tenantId, EntityCountQuery query, long timingNanos) {
dataQueryTimer.record(timingNanos, TimeUnit.NANOSECONDS); checkTiming(tenantId, query, timingNanos);
getTimer("entityCountQueryTimer").record(timingNanos, TimeUnit.NANOSECONDS);
}
@Override
public void reportEdqsDataQuery(TenantId tenantId, EntityDataQuery query, long timingNanos) {
checkTiming(tenantId, query, timingNanos);
getTimer("edqsDataQueryTimer").record(timingNanos, TimeUnit.NANOSECONDS);
}
@Override
public void reportEdqsCountQuery(TenantId tenantId, EntityCountQuery query, long timingNanos) {
checkTiming(tenantId, query, timingNanos);
getTimer("edqsCountQueryTimer").record(timingNanos, TimeUnit.NANOSECONDS);
}
@Override
public void reportStringCompressed() {
getCounter("stringsCompressed").increment();
} }
@Override @Override
public void reportCountQuery(TenantId tenantId, EntityCountQuery query, long timingNanos) { public void reportStringUncompressed() {
getCounter("stringsUncompressed").increment();
}
private void checkTiming(TenantId tenantId, EntityCountQuery query, long timingNanos) {
double timingMs = timingNanos / 1000_000.0; double timingMs = timingNanos / 1000_000.0;
String queryType = query instanceof EntityDataQuery ? "data" : "count";
if (timingMs < slowQueryThreshold) { if (timingMs < slowQueryThreshold) {
log.debug("[{}] Executed count query in {} ms: {}", tenantId, timingMs, query); log.debug("[{}] Executed " + queryType + " query in {} ms: {}", tenantId, timingMs, query);
} else { } else {
log.warn("[{}] Executed slow count query in {} ms: {}", tenantId, timingMs, query); log.warn("[{}] Executed slow " + queryType + " query in {} ms: {}", tenantId, timingMs, query);
} }
countQueryTimer.record(timingNanos, TimeUnit.NANOSECONDS);
} }
private AtomicInteger getObjectCounter(ObjectType objectType) { private StatsTimer getTimer(String name) {
return timers.computeIfAbsent(name, __ -> statsFactory.createStatsTimer("edqsTimers", name));
}
private StatsCounter getCounter(String name) {
return counters.computeIfAbsent(name, __ -> statsFactory.createStatsCounter("edqsCounters", name));
}
private AtomicInteger getObjectGauge(ObjectType objectType) {
return objectCounters.computeIfAbsent(objectType, type -> return objectCounters.computeIfAbsent(objectType, type ->
statsFactory.createGauge("edqsObjectsCount", new AtomicInteger(), "objectType", type.name())); statsFactory.createGauge("edqsGauges", "objectsCount", new AtomicInteger(), "objectType", type.name()));
} }
} }

39
common/edqs/src/main/java/org/thingsboard/server/edqs/util/EdqsConverter.java

@ -43,6 +43,7 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.stats.EdqsStatsService;
import org.thingsboard.server.common.util.ProtoUtils; import org.thingsboard.server.common.util.ProtoUtils;
import org.thingsboard.server.edqs.data.dp.BoolDataPoint; import org.thingsboard.server.edqs.data.dp.BoolDataPoint;
import org.thingsboard.server.edqs.data.dp.CompressedJsonDataPoint; import org.thingsboard.server.edqs.data.dp.CompressedJsonDataPoint;
@ -56,14 +57,18 @@ import org.thingsboard.server.gen.transport.TransportProtos.DataPointProto;
import org.xerial.snappy.Snappy; import org.xerial.snappy.Snappy;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
@Service @Service
@RequiredArgsConstructor
@Slf4j @Slf4j
public class EdqsConverter { public class EdqsConverter {
private final EdqsStatsService edqsStatsService;
@Value("${queue.edqs.string_compression_length_threshold:512}") @Value("${queue.edqs.string_compression_length_threshold:512}")
private int stringCompressionLengthThreshold; private int stringCompressionLengthThreshold;
@ -171,26 +176,44 @@ public class EdqsConverter {
} else if (proto.hasDoubleV()) { } else if (proto.hasDoubleV()) {
return new DoubleDataPoint(ts, proto.getDoubleV()); return new DoubleDataPoint(ts, proto.getDoubleV());
} else if (proto.hasStringV()) { } else if (proto.hasStringV()) {
return new StringDataPoint(ts, proto.getStringV()); String stringV = proto.getStringV();
if (stringV.length() < stringCompressionLengthThreshold) {
return new StringDataPoint(ts, stringV);
} else {
return new CompressedStringDataPoint(ts, compress(stringV), this::uncompress);
}
} else if (proto.hasCompressedStringV()) { } else if (proto.hasCompressedStringV()) {
return new CompressedStringDataPoint(ts, proto.getCompressedStringV().toByteArray()); return new CompressedStringDataPoint(ts, proto.getCompressedStringV().toByteArray(), this::uncompress);
} else if (proto.hasJsonV()) { } else if (proto.hasJsonV()) {
return new JsonDataPoint(ts, proto.getJsonV()); String jsonV = proto.getJsonV();
if (jsonV.length() < stringCompressionLengthThreshold) {
return new JsonDataPoint(ts, jsonV);
} else {
return new CompressedJsonDataPoint(ts, compress(jsonV), this::uncompress);
}
} else if (proto.hasCompressedJsonV()) { } else if (proto.hasCompressedJsonV()) {
return new CompressedJsonDataPoint(ts, proto.getCompressedJsonV().toByteArray()); return new CompressedJsonDataPoint(ts, proto.getCompressedJsonV().toByteArray(), this::uncompress);
} else { } else {
throw new IllegalArgumentException("Unsupported data point proto: " + proto); throw new IllegalArgumentException("Unsupported data point proto: " + proto);
} }
} }
@SneakyThrows @SneakyThrows
private static byte[] compress(String value) { private byte[] compress(String value) {
byte[] compressed = Snappy.compress(value); byte[] compressed = Snappy.compress(value, StandardCharsets.UTF_8);
// TODO: limit the size log.debug("Compressed {} chars to {} bytes", value.length(), compressed.length);
log.debug("Compressed {} bytes to {} bytes", value.length(), compressed.length); edqsStatsService.reportStringCompressed();
return compressed; return compressed;
} }
@SneakyThrows
private String uncompress(byte[] compressed) {
String value = Snappy.uncompressString(compressed, StandardCharsets.UTF_8);
log.debug("Uncompressed {} bytes to {} chars", compressed.length, value.length());
edqsStatsService.reportStringUncompressed();
return value;
}
public static Entity toEntity(EntityType entityType, Object entity) { public static Entity toEntity(EntityType entityType, Object entity) {
Entity edqsEntity = new Entity(); Entity edqsEntity = new Entity();
edqsEntity.setType(entityType); edqsEntity.setType(entityType);

2
common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java

@ -136,6 +136,8 @@ public enum MsgType {
EDGE_SYNC_RESPONSE_FROM_EDGE_SESSION_MSG, EDGE_SYNC_RESPONSE_FROM_EDGE_SESSION_MSG,
CF_CACHE_INIT_MSG, // Sent to init caches for CF actor;
CF_INIT_PROFILE_ENTITY_MSG, // Sent to init profile entities cache;
CF_INIT_MSG, // Sent to init particular calculated field; CF_INIT_MSG, // Sent to init particular calculated field;
CF_LINK_INIT_MSG, // Sent to init particular calculated field; CF_LINK_INIT_MSG, // Sent to init particular calculated field;
CF_STATE_RESTORE_MSG, // Sent to restore particular calculated field entity state; CF_STATE_RESTORE_MSG, // Sent to restore particular calculated field entity state;

33
common/message/src/main/java/org/thingsboard/server/common/msg/cf/CalculatedFieldCacheInitMsg.java

@ -0,0 +1,33 @@
/**
* 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.server.common.msg.cf;
import lombok.Data;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
@Data
public class CalculatedFieldCacheInitMsg implements ToCalculatedFieldSystemMsg {
private final TenantId tenantId;
@Override
public MsgType getMsgType() {
return MsgType.CF_CACHE_INIT_MSG;
}
}

26
application/src/main/java/org/thingsboard/server/service/cf/cache/CalculatedFieldEntityProfileCache.java → common/message/src/main/java/org/thingsboard/server/common/msg/cf/CalculatedFieldInitProfileEntityMsg.java

@ -13,24 +13,24 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.service.cf.cache; package org.thingsboard.server.common.msg.cf;
import org.springframework.context.ApplicationListener; import lombok.Data;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
import java.util.Collection; @Data
public class CalculatedFieldInitProfileEntityMsg implements ToCalculatedFieldSystemMsg {
public interface CalculatedFieldEntityProfileCache extends ApplicationListener<PartitionChangeEvent> { private final TenantId tenantId;
private final EntityId profileEntityId;
private final EntityId entityId;
void add(TenantId tenantId, EntityId profileId, EntityId entityId); @Override
public MsgType getMsgType() {
return MsgType.CF_INIT_PROFILE_ENTITY_MSG;
}
void update(TenantId tenantId, EntityId oldProfileId, EntityId newProfileId, EntityId entityId);
void evict(TenantId tenantId, EntityId entityId);
Collection<EntityId> getMyEntityIdsByProfileId(TenantId tenantId, EntityId profileId);
int getEntityIdPartition(TenantId tenantId, EntityId entityId);
} }

6
common/queue/src/main/java/org/thingsboard/server/queue/common/consumer/MainQueueConsumerManager.java

@ -54,7 +54,7 @@ public class MainQueueConsumerManager<M extends TbQueueMsg, C extends QueueConfi
@Getter @Getter
protected C config; protected C config;
protected final MsgPackProcessor<M, C> msgPackProcessor; protected final MsgPackProcessor<M, C> msgPackProcessor;
protected final BiFunction<C, Integer, TbQueueConsumer<M>> consumerCreator; protected final BiFunction<C, TopicPartitionInfo, TbQueueConsumer<M>> consumerCreator;
@Getter @Getter
protected final ExecutorService consumerExecutor; protected final ExecutorService consumerExecutor;
@Getter @Getter
@ -74,7 +74,7 @@ public class MainQueueConsumerManager<M extends TbQueueMsg, C extends QueueConfi
@Builder @Builder
public MainQueueConsumerManager(QueueKey queueKey, C config, public MainQueueConsumerManager(QueueKey queueKey, C config,
MsgPackProcessor<M, C> msgPackProcessor, MsgPackProcessor<M, C> msgPackProcessor,
BiFunction<C, Integer, TbQueueConsumer<M>> consumerCreator, BiFunction<C, TopicPartitionInfo, TbQueueConsumer<M>> consumerCreator,
ExecutorService consumerExecutor, ExecutorService consumerExecutor,
ScheduledExecutorService scheduler, ScheduledExecutorService scheduler,
ExecutorService taskExecutor, ExecutorService taskExecutor,
@ -313,7 +313,7 @@ public class MainQueueConsumerManager<M extends TbQueueMsg, C extends QueueConfi
Runnable callback = onStop != null ? () -> onStop.accept(tpi) : null; Runnable callback = onStop != null ? () -> onStop.accept(tpi) : null;
TbQueueConsumerTask<M> consumer = new TbQueueConsumerTask<>(key, () -> { TbQueueConsumerTask<M> consumer = new TbQueueConsumerTask<>(key, () -> {
TbQueueConsumer<M> queueConsumer = consumerCreator.apply(config, partitionId); TbQueueConsumer<M> queueConsumer = consumerCreator.apply(config, tpi);
if (startOffsetProvider != null && queueConsumer instanceof TbKafkaConsumerTemplate<M> kafkaConsumer) { if (startOffsetProvider != null && queueConsumer instanceof TbKafkaConsumerTemplate<M> kafkaConsumer) {
kafkaConsumer.setStartOffsetProvider(startOffsetProvider); kafkaConsumer.setStartOffsetProvider(startOffsetProvider);
} }

2
common/queue/src/main/java/org/thingsboard/server/queue/common/consumer/PartitionedQueueConsumerManager.java

@ -45,7 +45,7 @@ public class PartitionedQueueConsumerManager<M extends TbQueueMsg> extends MainQ
@Builder(builderMethodName = "create") // not to conflict with super.builder() @Builder(builderMethodName = "create") // not to conflict with super.builder()
public PartitionedQueueConsumerManager(QueueKey queueKey, String topic, long pollInterval, MsgPackProcessor<M, QueueConfig> msgPackProcessor, public PartitionedQueueConsumerManager(QueueKey queueKey, String topic, long pollInterval, MsgPackProcessor<M, QueueConfig> msgPackProcessor,
BiFunction<QueueConfig, Integer, TbQueueConsumer<M>> consumerCreator, TbQueueAdmin queueAdmin, BiFunction<QueueConfig, TopicPartitionInfo, TbQueueConsumer<M>> consumerCreator, TbQueueAdmin queueAdmin,
ExecutorService consumerExecutor, ScheduledExecutorService scheduler, ExecutorService consumerExecutor, ScheduledExecutorService scheduler,
ExecutorService taskExecutor, Consumer<Throwable> uncaughtErrorHandler) { ExecutorService taskExecutor, Consumer<Throwable> uncaughtErrorHandler) {
super(queueKey, QueueConfig.of(true, pollInterval), msgPackProcessor, consumerCreator, consumerExecutor, scheduler, taskExecutor, uncaughtErrorHandler); super(queueKey, QueueConfig.of(true, pollInterval), msgPackProcessor, consumerCreator, consumerExecutor, scheduler, taskExecutor, uncaughtErrorHandler);

2
common/queue/src/main/java/org/thingsboard/server/queue/edqs/EdqsConfig.java

@ -38,7 +38,7 @@ public class EdqsConfig {
private String requestsTopic; private String requestsTopic;
@Value("${queue.edqs.responses_topic:edqs.responses}") @Value("${queue.edqs.responses_topic:edqs.responses}")
private String responsesTopic; private String responsesTopic;
@Value("${queue.edqs.poll_interval:125}") @Value("${queue.edqs.poll_interval:25}")
private long pollInterval; private long pollInterval;
@Value("${queue.edqs.max_pending_requests:10000}") @Value("${queue.edqs.max_pending_requests:10000}")
private int maxPendingRequests; private int maxPendingRequests;

36
common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaAdmin.java

@ -26,6 +26,7 @@ import org.apache.kafka.clients.admin.TopicDescription;
import org.apache.kafka.clients.consumer.OffsetAndMetadata; import org.apache.kafka.clients.consumer.OffsetAndMetadata;
import org.apache.kafka.common.TopicPartition; import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.errors.TopicExistsException; import org.apache.kafka.common.errors.TopicExistsException;
import org.thingsboard.server.queue.TbEdgeQueueAdmin;
import org.thingsboard.server.queue.TbQueueAdmin; import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.util.PropertyUtils; import org.thingsboard.server.queue.util.PropertyUtils;
@ -43,7 +44,7 @@ import java.util.stream.Collectors;
* Created by ashvayka on 24.09.18. * Created by ashvayka on 24.09.18.
*/ */
@Slf4j @Slf4j
public class TbKafkaAdmin implements TbQueueAdmin { public class TbKafkaAdmin implements TbQueueAdmin, TbEdgeQueueAdmin {
private final TbKafkaSettings settings; private final TbKafkaSettings settings;
private final Map<String, String> topicConfigs; private final Map<String, String> topicConfigs;
@ -149,17 +150,38 @@ public class TbKafkaAdmin implements TbQueueAdmin {
* */ * */
public void syncOffsets(String fatGroupId, String newGroupId, Integer partitionId) { public void syncOffsets(String fatGroupId, String newGroupId, Integer partitionId) {
try { try {
syncOffsetsUnsafe(fatGroupId, newGroupId, partitionId); log.info("syncOffsets [{}][{}][{}]", fatGroupId, newGroupId, partitionId);
if (partitionId == null) {
return;
}
syncOffsetsUnsafe(fatGroupId, newGroupId, "." + partitionId);
} catch (Exception e) { } catch (Exception e) {
log.warn("Failed to syncOffsets from {} to {} partitionId {}", fatGroupId, newGroupId, partitionId, e); log.warn("Failed to syncOffsets from {} to {} partitionId {}", fatGroupId, newGroupId, partitionId, e);
} }
} }
void syncOffsetsUnsafe(String fatGroupId, String newGroupId, Integer partitionId) throws ExecutionException, InterruptedException, TimeoutException { /**
log.info("syncOffsets [{}][{}][{}]", fatGroupId, newGroupId, partitionId); * Sync edge notifications offsets from a fat group to a single group per edge
if (partitionId == null) { * */
return; public void syncEdgeNotificationsOffsets(String fatGroupId, String newGroupId) {
try {
log.info("syncEdgeNotificationsOffsets [{}][{}]", fatGroupId, newGroupId);
syncOffsetsUnsafe(fatGroupId, newGroupId, newGroupId);
} catch (Exception e) {
log.warn("Failed to syncEdgeNotificationsOffsets from {} to {}", fatGroupId, newGroupId, e);
}
}
@Override
public void deleteConsumerGroup(String consumerGroupId) {
try {
settings.getAdminClient().deleteConsumerGroups(Collections.singletonList(consumerGroupId));
} catch (Exception e) {
log.warn("Failed to delete consumer group {}", consumerGroupId, e);
} }
}
void syncOffsetsUnsafe(String fatGroupId, String newGroupId, String topicSuffix) throws ExecutionException, InterruptedException, TimeoutException {
Map<TopicPartition, OffsetAndMetadata> oldOffsets = getConsumerGroupOffsets(fatGroupId); Map<TopicPartition, OffsetAndMetadata> oldOffsets = getConsumerGroupOffsets(fatGroupId);
if (oldOffsets.isEmpty()) { if (oldOffsets.isEmpty()) {
return; return;
@ -167,7 +189,7 @@ public class TbKafkaAdmin implements TbQueueAdmin {
for (var consumerOffset : oldOffsets.entrySet()) { for (var consumerOffset : oldOffsets.entrySet()) {
var tp = consumerOffset.getKey(); var tp = consumerOffset.getKey();
if (!tp.topic().endsWith("." + partitionId)) { if (!tp.topic().endsWith(topicSuffix)) {
continue; continue;
} }
var om = consumerOffset.getValue(); var om = consumerOffset.getValue();

3
common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryMonolithQueueFactory.java

@ -22,6 +22,7 @@ import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.gen.js.JsInvokeProtos; import org.thingsboard.server.gen.js.JsInvokeProtos;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldStateProto; import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldStateProto;
@ -133,7 +134,7 @@ public class InMemoryMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE
} }
@Override @Override
public TbQueueConsumer<TbProtoQueueMsg<TransportProtos.ToCalculatedFieldMsg>> createToCalculatedFieldMsgConsumer() { public TbQueueConsumer<TbProtoQueueMsg<TransportProtos.ToCalculatedFieldMsg>> createToCalculatedFieldMsgConsumer(TopicPartitionInfo tpi) {
return new InMemoryTbQueueConsumer<>(storage, topicService.buildTopicName(calculatedFieldSettings.getEventTopic())); return new InMemoryTbQueueConsumer<>(storage, topicService.buildTopicName(calculatedFieldSettings.getEventTopic()));
} }

30
common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java

@ -20,10 +20,12 @@ import jakarta.annotation.PreDestroy;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.gen.js.JsInvokeProtos; import org.thingsboard.server.gen.js.JsInvokeProtos;
import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldStateProto; import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldStateProto;
import org.thingsboard.server.gen.transport.TransportProtos.FromEdqsMsg; import org.thingsboard.server.gen.transport.TransportProtos.FromEdqsMsg;
@ -44,6 +46,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceM
import org.thingsboard.server.gen.transport.TransportProtos.ToVersionControlServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToVersionControlServiceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg;
import org.thingsboard.server.queue.TbEdgeQueueAdmin;
import org.thingsboard.server.queue.TbQueueAdmin; import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.TbQueueProducer;
@ -101,7 +104,7 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi
private final TbQueueAdmin vcAdmin; private final TbQueueAdmin vcAdmin;
private final TbQueueAdmin housekeeperAdmin; private final TbQueueAdmin housekeeperAdmin;
private final TbQueueAdmin housekeeperReprocessingAdmin; private final TbQueueAdmin housekeeperReprocessingAdmin;
private final TbQueueAdmin edgeAdmin; private final TbEdgeQueueAdmin edgeAdmin;
private final TbQueueAdmin edgeEventAdmin; private final TbQueueAdmin edgeEventAdmin;
private final TbQueueAdmin cfAdmin; private final TbQueueAdmin cfAdmin;
private final TbQueueAdmin cfStateAdmin; private final TbQueueAdmin cfStateAdmin;
@ -493,9 +496,13 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi
public TbQueueConsumer<TbProtoQueueMsg<ToEdgeEventNotificationMsg>> createEdgeEventMsgConsumer(TenantId tenantId, EdgeId edgeId) { public TbQueueConsumer<TbProtoQueueMsg<ToEdgeEventNotificationMsg>> createEdgeEventMsgConsumer(TenantId tenantId, EdgeId edgeId) {
TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder<TbProtoQueueMsg<ToEdgeEventNotificationMsg>> consumerBuilder = TbKafkaConsumerTemplate.builder(); TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder<TbProtoQueueMsg<ToEdgeEventNotificationMsg>> consumerBuilder = TbKafkaConsumerTemplate.builder();
consumerBuilder.settings(kafkaSettings); consumerBuilder.settings(kafkaSettings);
consumerBuilder.topic(topicService.buildEdgeEventNotificationsTopicPartitionInfo(tenantId, edgeId).getTopic()); String topic = topicService.buildEdgeEventNotificationsTopicPartitionInfo(tenantId, edgeId).getTopic();
edgeAdmin.syncEdgeNotificationsOffsets(topicService.buildTopicName("monolith-edge-event-consumer"), topic);
consumerBuilder.topic(topic);
consumerBuilder.clientId("monolith-to-edge-event-consumer-" + serviceInfoProvider.getServiceId() + "-" + edgeConsumerCount.incrementAndGet()); consumerBuilder.clientId("monolith-to-edge-event-consumer-" + serviceInfoProvider.getServiceId() + "-" + edgeConsumerCount.incrementAndGet());
consumerBuilder.groupId(topicService.buildTopicName("monolith-edge-event-consumer")); consumerBuilder.groupId(topic);
consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToEdgeEventNotificationMsg.parseFrom(msg.getData()), msg.getHeaders())); consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToEdgeEventNotificationMsg.parseFrom(msg.getData()), msg.getHeaders()));
consumerBuilder.admin(edgeEventAdmin); consumerBuilder.admin(edgeEventAdmin);
consumerBuilder.statsService(consumerStatsService); consumerBuilder.statsService(consumerStatsService);
@ -513,15 +520,24 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi
} }
@Override @Override
public TbQueueConsumer<TbProtoQueueMsg<ToCalculatedFieldMsg>> createToCalculatedFieldMsgConsumer() { public TbQueueConsumer<TbProtoQueueMsg<ToCalculatedFieldMsg>> createToCalculatedFieldMsgConsumer(TopicPartitionInfo tpi) {
String queueName = DataConstants.CF_QUEUE_NAME;
if (tpi == null) {
throw new IllegalArgumentException("TopicPartitionInfo is required.");
}
TenantId tenantId = tpi.getTenantId().orElse(TenantId.SYS_TENANT_ID);
Integer partitionId = tpi.getPartition().orElseThrow(() -> new IllegalArgumentException("PartitionId is required."));
String groupId = topicService.buildConsumerGroupId("cf-", tenantId, queueName, partitionId);
TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder<TbProtoQueueMsg<ToCalculatedFieldMsg>> consumerBuilder = TbKafkaConsumerTemplate.builder(); TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder<TbProtoQueueMsg<ToCalculatedFieldMsg>> consumerBuilder = TbKafkaConsumerTemplate.builder();
consumerBuilder.settings(kafkaSettings); consumerBuilder.settings(kafkaSettings);
consumerBuilder.topic(topicService.buildTopicName(calculatedFieldSettings.getEventTopic())); consumerBuilder.topic(topicService.buildTopicName(calculatedFieldSettings.getEventTopic()));
consumerBuilder.clientId("monolith-calculated-field-consumer-" + serviceInfoProvider.getServiceId() + "-" + consumerCount.incrementAndGet()); consumerBuilder.clientId("cf-" + queueName + "-consumer-" + serviceInfoProvider.getServiceId() + "-" + consumerCount.incrementAndGet());
consumerBuilder.groupId(topicService.buildTopicName("monolith-calculated-field-consumer")); consumerBuilder.groupId(groupId);
consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToCalculatedFieldMsg.parseFrom(msg.getData()), msg.getHeaders())); consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToCalculatedFieldMsg.parseFrom(msg.getData()), msg.getHeaders()));
consumerBuilder.admin(cfAdmin); consumerBuilder.admin(cfAdmin);
consumerBuilder.statsService(consumerStatsService); consumerBuilder.statsService(consumerStatsService);
return consumerBuilder.build(); return consumerBuilder.build();
} }
@ -571,7 +587,7 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi
.readFromBeginning(true) .readFromBeginning(true)
.stopWhenRead(true) .stopWhenRead(true)
.clientId("monolith-calculated-field-state-consumer-" + serviceInfoProvider.getServiceId() + "-" + consumerCount.incrementAndGet()) .clientId("monolith-calculated-field-state-consumer-" + serviceInfoProvider.getServiceId() + "-" + consumerCount.incrementAndGet())
.groupId(topicService.buildTopicName("monolith-calculated-field-state-consumer")) .groupId(null) // not using consumer group management
.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), msg.getData() != null ? CalculatedFieldStateProto.parseFrom(msg.getData()) : null, msg.getHeaders())) .decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), msg.getData() != null ? CalculatedFieldStateProto.parseFrom(msg.getData()) : null, msg.getHeaders()))
.admin(cfStateAdmin) .admin(cfStateAdmin)
.statsService(consumerStatsService) .statsService(consumerStatsService)

11
common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java

@ -42,6 +42,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceM
import org.thingsboard.server.gen.transport.TransportProtos.ToVersionControlServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToVersionControlServiceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg;
import org.thingsboard.server.queue.TbEdgeQueueAdmin;
import org.thingsboard.server.queue.TbQueueAdmin; import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.TbQueueProducer;
@ -99,7 +100,7 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory {
private final TbQueueAdmin vcAdmin; private final TbQueueAdmin vcAdmin;
private final TbQueueAdmin housekeeperAdmin; private final TbQueueAdmin housekeeperAdmin;
private final TbQueueAdmin housekeeperReprocessingAdmin; private final TbQueueAdmin housekeeperReprocessingAdmin;
private final TbQueueAdmin edgeAdmin; private final TbEdgeQueueAdmin edgeAdmin;
private final TbQueueAdmin edgeEventAdmin; private final TbQueueAdmin edgeEventAdmin;
private final TbQueueAdmin cfAdmin; private final TbQueueAdmin cfAdmin;
private final TbQueueAdmin edqsEventsAdmin; private final TbQueueAdmin edqsEventsAdmin;
@ -439,9 +440,13 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory {
public TbQueueConsumer<TbProtoQueueMsg<ToEdgeEventNotificationMsg>> createEdgeEventMsgConsumer(TenantId tenantId, EdgeId edgeId) { public TbQueueConsumer<TbProtoQueueMsg<ToEdgeEventNotificationMsg>> createEdgeEventMsgConsumer(TenantId tenantId, EdgeId edgeId) {
TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder<TbProtoQueueMsg<ToEdgeEventNotificationMsg>> consumerBuilder = TbKafkaConsumerTemplate.builder(); TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder<TbProtoQueueMsg<ToEdgeEventNotificationMsg>> consumerBuilder = TbKafkaConsumerTemplate.builder();
consumerBuilder.settings(kafkaSettings); consumerBuilder.settings(kafkaSettings);
consumerBuilder.topic(topicService.buildEdgeEventNotificationsTopicPartitionInfo(tenantId, edgeId).getTopic()); String topic = topicService.buildEdgeEventNotificationsTopicPartitionInfo(tenantId, edgeId).getTopic();
edgeAdmin.syncEdgeNotificationsOffsets(topicService.buildTopicName("tb-core-edge-event-consumer"), topic);
consumerBuilder.topic(topic);
consumerBuilder.clientId("tb-core-edge-event-consumer-" + serviceInfoProvider.getServiceId() + "-" + edgeConsumerCount.incrementAndGet()); consumerBuilder.clientId("tb-core-edge-event-consumer-" + serviceInfoProvider.getServiceId() + "-" + edgeConsumerCount.incrementAndGet());
consumerBuilder.groupId(topicService.buildTopicName("tb-core-edge-event-consumer")); consumerBuilder.groupId(topic);
consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToEdgeEventNotificationMsg.parseFrom(msg.getData()), msg.getHeaders())); consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToEdgeEventNotificationMsg.parseFrom(msg.getData()), msg.getHeaders()));
consumerBuilder.admin(edgeEventAdmin); consumerBuilder.admin(edgeEventAdmin);
consumerBuilder.statsService(consumerStatsService); consumerBuilder.statsService(consumerStatsService);

19
common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java

@ -20,8 +20,11 @@ import jakarta.annotation.PreDestroy;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.gen.js.JsInvokeProtos; import org.thingsboard.server.gen.js.JsInvokeProtos;
import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldStateProto; import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldStateProto;
import org.thingsboard.server.gen.transport.TransportProtos.FromEdqsMsg; import org.thingsboard.server.gen.transport.TransportProtos.FromEdqsMsg;
@ -313,12 +316,20 @@ public class KafkaTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory {
} }
@Override @Override
public TbQueueConsumer<TbProtoQueueMsg<ToCalculatedFieldMsg>> createToCalculatedFieldMsgConsumer() { public TbQueueConsumer<TbProtoQueueMsg<ToCalculatedFieldMsg>> createToCalculatedFieldMsgConsumer(TopicPartitionInfo tpi) {
String queueName = DataConstants.CF_QUEUE_NAME;
if (tpi == null) {
throw new IllegalArgumentException("TopicPartitionInfo is required.");
}
TenantId tenantId = tpi.getTenantId().orElse(TenantId.SYS_TENANT_ID);
Integer partitionId = tpi.getPartition().orElseThrow(() -> new IllegalArgumentException("PartitionId is required."));
String groupId = topicService.buildConsumerGroupId("cf-", tenantId, queueName, partitionId);
TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder<TbProtoQueueMsg<ToCalculatedFieldMsg>> consumerBuilder = TbKafkaConsumerTemplate.builder(); TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder<TbProtoQueueMsg<ToCalculatedFieldMsg>> consumerBuilder = TbKafkaConsumerTemplate.builder();
consumerBuilder.settings(kafkaSettings); consumerBuilder.settings(kafkaSettings);
consumerBuilder.topic(topicService.buildTopicName(calculatedFieldSettings.getEventTopic())); consumerBuilder.topic(topicService.buildTopicName(calculatedFieldSettings.getEventTopic()));
consumerBuilder.clientId("tb-rule-engine-calculated-field-consumer-" + serviceInfoProvider.getServiceId() + "-" + consumerCount.incrementAndGet()); consumerBuilder.clientId("cf-" + queueName + "-consumer-" + serviceInfoProvider.getServiceId() + "-" + consumerCount.incrementAndGet());
consumerBuilder.groupId(topicService.buildTopicName("tb-rule-engine-calculated-field-consumer")); consumerBuilder.groupId(groupId);
consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToCalculatedFieldMsg.parseFrom(msg.getData()), msg.getHeaders())); consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToCalculatedFieldMsg.parseFrom(msg.getData()), msg.getHeaders()));
consumerBuilder.admin(cfAdmin); consumerBuilder.admin(cfAdmin);
consumerBuilder.statsService(consumerStatsService); consumerBuilder.statsService(consumerStatsService);
@ -371,7 +382,7 @@ public class KafkaTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory {
.readFromBeginning(true) .readFromBeginning(true)
.stopWhenRead(true) .stopWhenRead(true)
.clientId("tb-rule-engine-calculated-field-state-consumer-" + serviceInfoProvider.getServiceId() + "-" + consumerCount.incrementAndGet()) .clientId("tb-rule-engine-calculated-field-state-consumer-" + serviceInfoProvider.getServiceId() + "-" + consumerCount.incrementAndGet())
.groupId(topicService.buildTopicName("tb-rule-engine-calculated-field-state-consumer")) .groupId(null) // not using consumer group management
.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), msg.getData() != null ? CalculatedFieldStateProto.parseFrom(msg.getData()) : null, msg.getHeaders())) .decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), msg.getData() != null ? CalculatedFieldStateProto.parseFrom(msg.getData()) : null, msg.getHeaders()))
.admin(cfStateAdmin) .admin(cfStateAdmin)
.statsService(consumerStatsService) .statsService(consumerStatsService)

9
common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineQueueFactory.java

@ -16,6 +16,7 @@
package org.thingsboard.server.queue.provider; package org.thingsboard.server.queue.provider;
import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.gen.js.JsInvokeProtos; import org.thingsboard.server.gen.js.JsInvokeProtos;
import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldStateProto; import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldStateProto;
import org.thingsboard.server.gen.transport.TransportProtos.ToCalculatedFieldMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCalculatedFieldMsg;
@ -95,8 +96,8 @@ public interface TbRuleEngineQueueFactory extends TbUsageStatsClientQueueFactory
/** /**
* Used to consume messages by TB Rule Engine Service * Used to consume messages by TB Rule Engine Service
* *
* @return
* @param configuration * @param configuration
* @return
*/ */
TbQueueConsumer<TbProtoQueueMsg<ToRuleEngineMsg>> createToRuleEngineMsgConsumer(Queue configuration); TbQueueConsumer<TbProtoQueueMsg<ToRuleEngineMsg>> createToRuleEngineMsgConsumer(Queue configuration);
@ -104,9 +105,9 @@ public interface TbRuleEngineQueueFactory extends TbUsageStatsClientQueueFactory
* Used to consume messages by TB Rule Engine Service * Used to consume messages by TB Rule Engine Service
* Intended usage for consumer per partition strategy * Intended usage for consumer per partition strategy
* *
* @return TbQueueConsumer
* @param configuration * @param configuration
* @param partitionId as a suffix for consumer name * @param partitionId as a suffix for consumer name
* @return TbQueueConsumer
*/ */
default TbQueueConsumer<TbProtoQueueMsg<ToRuleEngineMsg>> createToRuleEngineMsgConsumer(Queue configuration, Integer partitionId) { default TbQueueConsumer<TbProtoQueueMsg<ToRuleEngineMsg>> createToRuleEngineMsgConsumer(Queue configuration, Integer partitionId) {
return createToRuleEngineMsgConsumer(configuration); return createToRuleEngineMsgConsumer(configuration);
@ -121,7 +122,7 @@ public interface TbRuleEngineQueueFactory extends TbUsageStatsClientQueueFactory
TbQueueRequestTemplate<TbProtoJsQueueMsg<JsInvokeProtos.RemoteJsRequest>, TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>> createRemoteJsRequestTemplate(); TbQueueRequestTemplate<TbProtoJsQueueMsg<JsInvokeProtos.RemoteJsRequest>, TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>> createRemoteJsRequestTemplate();
TbQueueConsumer<TbProtoQueueMsg<ToCalculatedFieldMsg>> createToCalculatedFieldMsgConsumer(); TbQueueConsumer<TbProtoQueueMsg<ToCalculatedFieldMsg>> createToCalculatedFieldMsgConsumer(TopicPartitionInfo tpi);
TbQueueAdmin getCalculatedFieldQueueAdmin(); TbQueueAdmin getCalculatedFieldQueueAdmin();

2
common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueCalculatedFieldSettings.java

@ -31,5 +31,7 @@ public class TbQueueCalculatedFieldSettings {
@Value("${queue.calculated_fields.state_topic}") @Value("${queue.calculated_fields.state_topic}")
private String stateTopic; private String stateTopic;
@Value("${queue.calculated_fields.init_tenant_fetch_pack_size:1000}")
private int initTenantFetchPackSize;
} }

5
common/queue/src/main/java/org/thingsboard/server/queue/util/AfterStartUp.java

@ -36,10 +36,11 @@ public @interface AfterStartUp {
int STARTUP_SERVICE = 8; int STARTUP_SERVICE = 8;
int ACTOR_SYSTEM = 9; int ACTOR_SYSTEM = 9;
int REGULAR_SERVICE = 10;
int CF_READ_PROFILE_ENTITIES_SERVICE = 10; int CF_READ_PROFILE_ENTITIES_SERVICE = 10;
int CF_READ_CF_SERVICE = 11; int CF_READ_CF_SERVICE = 10;
int REGULAR_SERVICE = 11;
int BEFORE_TRANSPORT_SERVICE = Integer.MAX_VALUE - 1001; int BEFORE_TRANSPORT_SERVICE = Integer.MAX_VALUE - 1001;
int TRANSPORT_SERVICE = Integer.MAX_VALUE - 1000; int TRANSPORT_SERVICE = Integer.MAX_VALUE - 1000;

15
common/stats/src/main/java/org/thingsboard/server/common/stats/DefaultStatsFactory.java

@ -27,6 +27,7 @@ import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.ToDoubleFunction;
@Service @Service
public class DefaultStatsFactory implements StatsFactory { public class DefaultStatsFactory implements StatsFactory {
@ -86,6 +87,16 @@ public class DefaultStatsFactory implements StatsFactory {
return meterRegistry.gauge(key, Tags.of(tags), number); return meterRegistry.gauge(key, Tags.of(tags), number);
} }
@Override
public <T extends Number> T createGauge(String type, String name, T number, String... tags) {
return createGauge(type, number, getTags(name, tags));
}
@Override
public <S> void createGauge(String type, String name, S stateObject, ToDoubleFunction<S> numberProvider, String... tags) {
meterRegistry.gauge(type, Tags.of(getTags(name, tags)), stateObject, numberProvider);
}
@Override @Override
public MessagesStats createMessagesStats(String key) { public MessagesStats createMessagesStats(String key) {
StatsCounter totalCounter = createStatsCounter(key, TOTAL_MSGS); StatsCounter totalCounter = createStatsCounter(key, TOTAL_MSGS);
@ -106,8 +117,8 @@ public class DefaultStatsFactory implements StatsFactory {
} }
@Override @Override
public StatsTimer createTimer(StatsType type, String name, String... tags) { public StatsTimer createStatsTimer(String type, String name, String... tags) {
return new StatsTimer(name, Timer.builder(type.getName()) return new StatsTimer(name, Timer.builder(type)
.tags(getTags(name, tags)) .tags(getTags(name, tags))
.register(meterRegistry)); .register(meterRegistry));
} }

16
common/stats/src/main/java/org/thingsboard/server/common/stats/DummyEdqsStatsService.java

@ -33,9 +33,21 @@ public class DummyEdqsStatsService implements EdqsStatsService {
public void reportRemoved(ObjectType objectType) {} public void reportRemoved(ObjectType objectType) {}
@Override @Override
public void reportDataQuery(TenantId tenantId, EntityDataQuery query, long timingNanos) {} public void reportEntityDataQuery(TenantId tenantId, EntityDataQuery query, long timingNanos) {}
@Override @Override
public void reportCountQuery(TenantId tenantId, EntityCountQuery query, long timingNanos) {} public void reportEntityCountQuery(TenantId tenantId, EntityCountQuery query, long timingNanos) {}
@Override
public void reportEdqsDataQuery(TenantId tenantId, EntityDataQuery query, long timingNanos) {}
@Override
public void reportEdqsCountQuery(TenantId tenantId, EntityCountQuery query, long timingNanos) {}
@Override
public void reportStringCompressed() {}
@Override
public void reportStringUncompressed() {}
} }

12
common/stats/src/main/java/org/thingsboard/server/common/stats/EdqsStatsService.java

@ -26,8 +26,16 @@ public interface EdqsStatsService {
void reportRemoved(ObjectType objectType); void reportRemoved(ObjectType objectType);
void reportDataQuery(TenantId tenantId, EntityDataQuery query, long timingNanos); void reportEntityDataQuery(TenantId tenantId, EntityDataQuery query, long timingNanos);
void reportCountQuery(TenantId tenantId, EntityCountQuery query, long timingNanos); void reportEntityCountQuery(TenantId tenantId, EntityCountQuery query, long timingNanos);
void reportEdqsDataQuery(TenantId tenantId, EntityDataQuery query, long timingNanos);
void reportEdqsCountQuery(TenantId tenantId, EntityCountQuery query, long timingNanos);
void reportStringCompressed();
void reportStringUncompressed();
} }

8
common/stats/src/main/java/org/thingsboard/server/common/stats/StatsFactory.java

@ -17,6 +17,8 @@ package org.thingsboard.server.common.stats;
import io.micrometer.core.instrument.Timer; import io.micrometer.core.instrument.Timer;
import java.util.function.ToDoubleFunction;
public interface StatsFactory { public interface StatsFactory {
StatsCounter createStatsCounter(String key, String statsName, String... otherTags); StatsCounter createStatsCounter(String key, String statsName, String... otherTags);
@ -25,10 +27,14 @@ public interface StatsFactory {
<T extends Number> T createGauge(String key, T number, String... tags); <T extends Number> T createGauge(String key, T number, String... tags);
<T extends Number> T createGauge(String type, String name, T number, String... tags);
<S> void createGauge(String type, String name, S stateObject, ToDoubleFunction<S> numberProvider, String... tags);
MessagesStats createMessagesStats(String key); MessagesStats createMessagesStats(String key);
Timer createTimer(String key, String... tags); Timer createTimer(String key, String... tags);
StatsTimer createTimer(StatsType type, String name, String... tags); StatsTimer createStatsTimer(String type, String name, String... tags);
} }

6
common/util/src/main/java/org/thingsboard/common/util/TbBytePool.java

@ -16,12 +16,14 @@
package org.thingsboard.common.util; package org.thingsboard.common.util;
import com.google.common.hash.Hashing; import com.google.common.hash.Hashing;
import lombok.Getter;
import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.ConcurrentReferenceHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
public class TbBytePool { public class TbBytePool {
@Getter
private static final ConcurrentMap<String, byte[]> pool = new ConcurrentReferenceHashMap<>(); private static final ConcurrentMap<String, byte[]> pool = new ConcurrentReferenceHashMap<>();
public static byte[] intern(byte[] data) { public static byte[] intern(byte[] data) {
@ -32,8 +34,4 @@ public class TbBytePool {
return pool.computeIfAbsent(checksum, c -> data); return pool.computeIfAbsent(checksum, c -> data);
} }
public static int size(){
return pool.size();
}
} }

6
common/util/src/main/java/org/thingsboard/common/util/TbStringPool.java

@ -15,12 +15,14 @@
*/ */
package org.thingsboard.common.util; package org.thingsboard.common.util;
import lombok.Getter;
import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.ConcurrentReferenceHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
public class TbStringPool { public class TbStringPool {
@Getter
private static final ConcurrentMap<String, String> pool = new ConcurrentReferenceHashMap<>(); private static final ConcurrentMap<String, String> pool = new ConcurrentReferenceHashMap<>();
public static String intern(String data) { public static String intern(String data) {
@ -30,8 +32,4 @@ public class TbStringPool {
return pool.computeIfAbsent(data, str -> str); return pool.computeIfAbsent(data, str -> str);
} }
public static int size(){
return pool.size();
}
} }

2
dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java

@ -240,4 +240,6 @@ public interface AssetDao extends Dao<Asset>, TenantEntityDao<Asset>, Exportable
PageData<ProfileEntityIdInfo> findProfileEntityIdInfos(PageLink pageLink); PageData<ProfileEntityIdInfo> findProfileEntityIdInfos(PageLink pageLink);
PageData<ProfileEntityIdInfo> findProfileEntityIdInfosByTenantId(UUID tenantId, PageLink pageLink);
} }

8
dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java

@ -293,6 +293,14 @@ public class BaseAssetService extends AbstractCachedEntityService<AssetCacheKey,
return assetDao.findProfileEntityIdInfos(pageLink); return assetDao.findProfileEntityIdInfos(pageLink);
} }
@Override
public PageData<ProfileEntityIdInfo> findProfileEntityIdInfosByTenantId(TenantId tenantId, PageLink pageLink) {
log.trace("Executing findProfileEntityIdInfosByTenantId, tenantId[{}], pageLink [{}]", tenantId, pageLink);
validateId(tenantId, id -> INCORRECT_TENANT_ID + id);
validatePageLink(pageLink);
return assetDao.findProfileEntityIdInfosByTenantId(tenantId.getId(), pageLink);
}
@Override @Override
public PageData<AssetId> findAssetIdsByTenantIdAndAssetProfileId(TenantId tenantId, AssetProfileId assetProfileId, PageLink pageLink) { public PageData<AssetId> findAssetIdsByTenantIdAndAssetProfileId(TenantId tenantId, AssetProfileId assetProfileId, PageLink pageLink) {
log.trace("Executing findAssetIdsByTenantIdAndAssetProfileId, tenantId [{}], assetProfileId [{}]", tenantId, assetProfileId); log.trace("Executing findAssetIdsByTenantIdAndAssetProfileId, tenantId [{}], assetProfileId [{}]", tenantId, assetProfileId);

16
dao/src/main/java/org/thingsboard/server/dao/cf/BaseCalculatedFieldService.java

@ -104,6 +104,14 @@ public class BaseCalculatedFieldService extends AbstractEntityService implements
return calculatedFieldDao.findAll(pageLink); return calculatedFieldDao.findAll(pageLink);
} }
@Override
public PageData<CalculatedField> findCalculatedFieldsByTenantId(TenantId tenantId, PageLink pageLink) {
log.trace("Executing findAllByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink);
validateId(tenantId, id -> INCORRECT_TENANT_ID + id);
validatePageLink(pageLink);
return calculatedFieldDao.findAllByTenantId(tenantId, pageLink);
}
@Override @Override
public PageData<CalculatedField> findAllCalculatedFieldsByEntityId(TenantId tenantId, EntityId entityId, PageLink pageLink) { public PageData<CalculatedField> findAllCalculatedFieldsByEntityId(TenantId tenantId, EntityId entityId, PageLink pageLink) {
log.trace("Executing findAllByEntityId, entityId [{}], pageLink [{}]", entityId, pageLink); log.trace("Executing findAllByEntityId, entityId [{}], pageLink [{}]", entityId, pageLink);
@ -174,6 +182,14 @@ public class BaseCalculatedFieldService extends AbstractEntityService implements
return calculatedFieldLinkDao.findCalculatedFieldLinksByEntityId(tenantId, entityId); return calculatedFieldLinkDao.findCalculatedFieldLinksByEntityId(tenantId, entityId);
} }
@Override
public PageData<CalculatedFieldLink> findAllCalculatedFieldLinksByTenantId(TenantId tenantId, PageLink pageLink) {
log.trace("Executing findAllCalculatedFieldLinksByTenantId, tenantId[{}] pageLink [{}]", tenantId, pageLink);
validateId(tenantId, id -> INCORRECT_TENANT_ID + id);
validatePageLink(pageLink);
return calculatedFieldLinkDao.findAllByTenantId(tenantId, pageLink);
}
@Override @Override
public PageData<CalculatedFieldLink> findAllCalculatedFieldLinks(PageLink pageLink) { public PageData<CalculatedFieldLink> findAllCalculatedFieldLinks(PageLink pageLink) {
log.trace("Executing findAllCalculatedFieldLinks, pageLink [{}]", pageLink); log.trace("Executing findAllCalculatedFieldLinks, pageLink [{}]", pageLink);

2
dao/src/main/java/org/thingsboard/server/dao/cf/CalculatedFieldDao.java

@ -37,6 +37,8 @@ public interface CalculatedFieldDao extends Dao<CalculatedField> {
PageData<CalculatedField> findAll(PageLink pageLink); PageData<CalculatedField> findAll(PageLink pageLink);
PageData<CalculatedField> findAllByTenantId(TenantId tenantId, PageLink pageLink);
PageData<CalculatedField> findAllByEntityId(TenantId tenantId, EntityId entityId, PageLink pageLink); PageData<CalculatedField> findAllByEntityId(TenantId tenantId, EntityId entityId, PageLink pageLink);
List<CalculatedField> removeAllByEntityId(TenantId tenantId, EntityId entityId); List<CalculatedField> removeAllByEntityId(TenantId tenantId, EntityId entityId);

4
dao/src/main/java/org/thingsboard/server/dao/cf/CalculatedFieldLinkDao.java

@ -31,8 +31,12 @@ public interface CalculatedFieldLinkDao extends Dao<CalculatedFieldLink> {
List<CalculatedFieldLink> findCalculatedFieldLinksByEntityId(TenantId tenantId, EntityId entityId); List<CalculatedFieldLink> findCalculatedFieldLinksByEntityId(TenantId tenantId, EntityId entityId);
List<CalculatedFieldLink> findCalculatedFieldLinksByTenantId(TenantId tenantId);
List<CalculatedFieldLink> findAll(); List<CalculatedFieldLink> findAll();
PageData<CalculatedFieldLink> findAll(PageLink pageLink); PageData<CalculatedFieldLink> findAll(PageLink pageLink);
PageData<CalculatedFieldLink> findAllByTenantId(TenantId tenantId, PageLink pageLink);
} }

4
dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java

@ -23,9 +23,7 @@ import org.thingsboard.server.common.data.DeviceInfoFilter;
import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.ProfileEntityIdInfo; import org.thingsboard.server.common.data.ProfileEntityIdInfo;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.data.ota.OtaPackageType;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
@ -233,6 +231,8 @@ public interface DeviceDao extends Dao<Device>, TenantEntityDao<Device>, Exporta
PageData<ProfileEntityIdInfo> findProfileEntityIdInfos(PageLink pageLink); PageData<ProfileEntityIdInfo> findProfileEntityIdInfos(PageLink pageLink);
PageData<ProfileEntityIdInfo> findProfileEntityIdInfosByTenantId(UUID tenantId, PageLink pageLink);
PageData<DeviceInfo> findDeviceInfosByFilter(DeviceInfoFilter filter, PageLink pageLink); PageData<DeviceInfo> findDeviceInfosByFilter(DeviceInfoFilter filter, PageLink pageLink);
} }

8
dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java

@ -394,6 +394,14 @@ public class DeviceServiceImpl extends CachedVersionedEntityService<DeviceCacheK
return deviceDao.findProfileEntityIdInfos(pageLink); return deviceDao.findProfileEntityIdInfos(pageLink);
} }
@Override
public PageData<ProfileEntityIdInfo> findProfileEntityIdInfosByTenantId(TenantId tenantId, PageLink pageLink) {
log.trace("Executing findProfileEntityIdInfosByTenantId, tenantId[{}], pageLink [{}]", tenantId, pageLink);
validateId(tenantId, id -> INCORRECT_TENANT_ID + id);
validatePageLink(pageLink);
return deviceDao.findProfileEntityIdInfosByTenantId(tenantId.getId(), pageLink);
}
@Override @Override
public PageData<Device> findDevicesByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink) { public PageData<Device> findDevicesByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink) {
log.trace("Executing findDevicesByTenantIdAndType, tenantId [{}], type [{}], pageLink [{}]", tenantId, type, pageLink); log.trace("Executing findDevicesByTenantIdAndType, tenantId [{}], type [{}], pageLink [{}]", tenantId, type, pageLink);

19
dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java

@ -20,7 +20,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.thingsboard.common.util.TbStopWatch;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.HasCustomerId; import org.thingsboard.server.common.data.HasCustomerId;
import org.thingsboard.server.common.data.HasEmail; import org.thingsboard.server.common.data.HasEmail;
@ -43,6 +42,7 @@ import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.common.data.query.EntityFilterType; import org.thingsboard.server.common.data.query.EntityFilterType;
import org.thingsboard.server.common.data.query.EntityKey; import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityListFilter; import org.thingsboard.server.common.data.query.EntityListFilter;
import org.thingsboard.server.common.data.query.EntityNameFilter;
import org.thingsboard.server.common.data.query.EntityTypeFilter; import org.thingsboard.server.common.data.query.EntityTypeFilter;
import org.thingsboard.server.common.data.query.KeyFilter; import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.RelationsQueryFilter; import org.thingsboard.server.common.data.query.RelationsQueryFilter;
@ -61,6 +61,7 @@ import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.id.EntityId.NULL_UUID; import static org.thingsboard.server.common.data.id.EntityId.NULL_UUID;
import static org.thingsboard.server.common.data.query.EntityFilterType.ENTITY_NAME;
import static org.thingsboard.server.common.data.query.EntityFilterType.ENTITY_TYPE; import static org.thingsboard.server.common.data.query.EntityFilterType.ENTITY_TYPE;
import static org.thingsboard.server.dao.service.Validator.validateEntityDataPageLink; import static org.thingsboard.server.dao.service.Validator.validateEntityDataPageLink;
import static org.thingsboard.server.dao.service.Validator.validateId; import static org.thingsboard.server.dao.service.Validator.validateId;
@ -101,7 +102,7 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe
validateId(customerId, id -> INCORRECT_CUSTOMER_ID + id); validateId(customerId, id -> INCORRECT_CUSTOMER_ID + id);
validateEntityCountQuery(query); validateEntityCountQuery(query);
TbStopWatch stopWatch = TbStopWatch.create(); long startNs = System.nanoTime();
Long result; Long result;
if (edqsApiService.isEnabled() && validForEdqs(query) && !tenantId.isSysTenantId()) { if (edqsApiService.isEnabled() && validForEdqs(query) && !tenantId.isSysTenantId()) {
EdqsRequest request = EdqsRequest.builder() EdqsRequest request = EdqsRequest.builder()
@ -112,7 +113,7 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe
} else { } else {
result = entityQueryDao.countEntitiesByQuery(tenantId, customerId, query); result = entityQueryDao.countEntitiesByQuery(tenantId, customerId, query);
} }
edqsStatsService.reportCountQuery(tenantId, query, stopWatch.stopAndGetTotalTimeNanos()); edqsStatsService.reportEntityCountQuery(tenantId, query, System.nanoTime() - startNs);
return result; return result;
} }
@ -123,7 +124,7 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe
validateId(customerId, id -> INCORRECT_CUSTOMER_ID + id); validateId(customerId, id -> INCORRECT_CUSTOMER_ID + id);
validateEntityDataQuery(query); validateEntityDataQuery(query);
TbStopWatch stopWatch = TbStopWatch.create(); long startNs = System.nanoTime();
PageData<EntityData> result; PageData<EntityData> result;
if (edqsApiService.isEnabled() && validForEdqs(query)) { if (edqsApiService.isEnabled() && validForEdqs(query)) {
EdqsRequest request = EdqsRequest.builder() EdqsRequest request = EdqsRequest.builder()
@ -146,7 +147,7 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe
} }
} }
} }
edqsStatsService.reportDataQuery(tenantId, query, stopWatch.stopAndGetTotalTimeNanos()); edqsStatsService.reportEntityDataQuery(tenantId, query, System.nanoTime() - startNs);
return result; return result;
} }
@ -250,6 +251,8 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe
validateRelationQuery((RelationsQueryFilter) query.getEntityFilter()); validateRelationQuery((RelationsQueryFilter) query.getEntityFilter());
} else if (query.getEntityFilter().getType().equals(ENTITY_TYPE)) { } else if (query.getEntityFilter().getType().equals(ENTITY_TYPE)) {
validateEntityTypeQuery((EntityTypeFilter) query.getEntityFilter()); validateEntityTypeQuery((EntityTypeFilter) query.getEntityFilter());
} else if (query.getEntityFilter().getType().equals(ENTITY_NAME)) {
validateEntityNameQuery((EntityNameFilter) query.getEntityFilter());
} }
} }
@ -264,6 +267,12 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe
} }
} }
private static void validateEntityNameQuery(EntityNameFilter filter) {
if (filter.getEntityType() == null) {
throw new IncorrectParameterException("Entity type is required");
}
}
private static void validateRelationQuery(RelationsQueryFilter queryFilter) { private static void validateRelationQuery(RelationsQueryFilter queryFilter) {
if (queryFilter.isMultiRoot() && queryFilter.getMultiRootEntitiesType() == null) { if (queryFilter.isMultiRoot() && queryFilter.getMultiRootEntitiesType() == null) {
throw new IncorrectParameterException("Multi-root relation query filter should contain 'multiRootEntitiesType'"); throw new IncorrectParameterException("Multi-root relation query filter should contain 'multiRootEntitiesType'");

10
dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java

@ -39,14 +39,12 @@ import org.thingsboard.server.dao.model.sql.AssetEntity;
import org.thingsboard.server.dao.model.sql.AssetInfoEntity; import org.thingsboard.server.dao.model.sql.AssetInfoEntity;
import org.thingsboard.server.dao.sql.JpaAbstractDao; import org.thingsboard.server.dao.sql.JpaAbstractDao;
import org.thingsboard.server.dao.sql.device.NativeAssetRepository; import org.thingsboard.server.dao.sql.device.NativeAssetRepository;
import org.thingsboard.server.dao.sql.device.NativeDeviceRepository;
import org.thingsboard.server.dao.util.SqlDao; import org.thingsboard.server.dao.util.SqlDao;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors;
import static org.thingsboard.server.dao.DaoUtil.convertTenantEntityInfosToDto; import static org.thingsboard.server.dao.DaoUtil.convertTenantEntityInfosToDto;
@ -262,10 +260,16 @@ public class JpaAssetDao extends JpaAbstractDao<AssetEntity, Asset> implements A
@Override @Override
public PageData<ProfileEntityIdInfo> findProfileEntityIdInfos(PageLink pageLink) { public PageData<ProfileEntityIdInfo> findProfileEntityIdInfos(PageLink pageLink) {
log.debug("Find profile device id infos by pageLink [{}]", pageLink); log.debug("Find profile asset id infos by pageLink [{}]", pageLink);
return nativeAssetRepository.findProfileEntityIdInfos(DaoUtil.toPageable(pageLink)); return nativeAssetRepository.findProfileEntityIdInfos(DaoUtil.toPageable(pageLink));
} }
@Override
public PageData<ProfileEntityIdInfo> findProfileEntityIdInfosByTenantId(UUID tenantId, PageLink pageLink) {
log.debug("Find profile asset id infos by pageLink [{}]", pageLink);
return nativeAssetRepository.findProfileEntityIdInfosByTenantId(tenantId, DaoUtil.toPageable(pageLink));
}
@Override @Override
public Long countByTenantId(TenantId tenantId) { public Long countByTenantId(TenantId tenantId) {
return assetRepository.countByTenantId(tenantId.getId()); return assetRepository.countByTenantId(tenantId.getId());

6
dao/src/main/java/org/thingsboard/server/dao/sql/cf/CalculatedFieldLinkRepository.java

@ -15,6 +15,8 @@
*/ */
package org.thingsboard.server.dao.sql.cf; package org.thingsboard.server.dao.sql.cf;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.thingsboard.server.dao.model.sql.CalculatedFieldLinkEntity; import org.thingsboard.server.dao.model.sql.CalculatedFieldLinkEntity;
@ -27,4 +29,8 @@ public interface CalculatedFieldLinkRepository extends JpaRepository<CalculatedF
List<CalculatedFieldLinkEntity> findAllByTenantIdAndEntityId(UUID tenantId, UUID entityId); List<CalculatedFieldLinkEntity> findAllByTenantIdAndEntityId(UUID tenantId, UUID entityId);
List<CalculatedFieldLinkEntity> findAllByTenantId(UUID tenantId);
Page<CalculatedFieldLinkEntity> findAllByTenantId(UUID tenantId, Pageable pageable);
} }

2
dao/src/main/java/org/thingsboard/server/dao/sql/cf/CalculatedFieldRepository.java

@ -32,6 +32,8 @@ public interface CalculatedFieldRepository extends JpaRepository<CalculatedField
List<CalculatedFieldEntity> findAllByTenantIdAndEntityId(UUID tenantId, UUID entityId); List<CalculatedFieldEntity> findAllByTenantIdAndEntityId(UUID tenantId, UUID entityId);
Page<CalculatedFieldEntity> findAllByTenantId(UUID tenantId, Pageable pageable);
Page<CalculatedFieldEntity> findAllByTenantIdAndEntityId(UUID tenantId, UUID entityId, Pageable pageable); Page<CalculatedFieldEntity> findAllByTenantIdAndEntityId(UUID tenantId, UUID entityId, Pageable pageable);
List<CalculatedFieldEntity> findAllByTenantId(UUID tenantId); List<CalculatedFieldEntity> findAllByTenantId(UUID tenantId);

6
dao/src/main/java/org/thingsboard/server/dao/sql/cf/JpaCalculatedFieldDao.java

@ -71,6 +71,12 @@ public class JpaCalculatedFieldDao extends JpaAbstractDao<CalculatedFieldEntity,
return nativeCalculatedFieldRepository.findCalculatedFields(DaoUtil.toPageable(pageLink)); return nativeCalculatedFieldRepository.findCalculatedFields(DaoUtil.toPageable(pageLink));
} }
@Override
public PageData<CalculatedField> findAllByTenantId(TenantId tenantId, PageLink pageLink) {
log.debug("Try to find calculated fields by tenantId[{}] and pageLink [{}]", tenantId, pageLink);
return DaoUtil.toPageData(calculatedFieldRepository.findAllByTenantId(tenantId.getId(), DaoUtil.toPageable(pageLink)));
}
@Override @Override
public PageData<CalculatedField> findAllByEntityId(TenantId tenantId, EntityId entityId, PageLink pageLink) { public PageData<CalculatedField> findAllByEntityId(TenantId tenantId, EntityId entityId, PageLink pageLink) {
log.debug("Try to find calculated fields by entityId[{}] and pageLink [{}]", entityId, pageLink); log.debug("Try to find calculated fields by entityId[{}] and pageLink [{}]", entityId, pageLink);

11
dao/src/main/java/org/thingsboard/server/dao/sql/cf/JpaCalculatedFieldLinkDao.java

@ -54,6 +54,11 @@ public class JpaCalculatedFieldLinkDao extends JpaAbstractDao<CalculatedFieldLin
return DaoUtil.convertDataList(calculatedFieldLinkRepository.findAllByTenantIdAndEntityId(tenantId.getId(), entityId.getId())); return DaoUtil.convertDataList(calculatedFieldLinkRepository.findAllByTenantIdAndEntityId(tenantId.getId(), entityId.getId()));
} }
@Override
public List<CalculatedFieldLink> findCalculatedFieldLinksByTenantId(TenantId tenantId) {
return DaoUtil.convertDataList(calculatedFieldLinkRepository.findAllByTenantId(tenantId.getId()));
}
@Override @Override
public List<CalculatedFieldLink> findAll() { public List<CalculatedFieldLink> findAll() {
return DaoUtil.convertDataList(calculatedFieldLinkRepository.findAll()); return DaoUtil.convertDataList(calculatedFieldLinkRepository.findAll());
@ -65,6 +70,12 @@ public class JpaCalculatedFieldLinkDao extends JpaAbstractDao<CalculatedFieldLin
return nativeCalculatedFieldRepository.findCalculatedFieldLinks(DaoUtil.toPageable(pageLink)); return nativeCalculatedFieldRepository.findCalculatedFieldLinks(DaoUtil.toPageable(pageLink));
} }
@Override
public PageData<CalculatedFieldLink> findAllByTenantId(TenantId tenantId, PageLink pageLink) {
log.debug("Try to find calculated field links by tenantId [{}], pageLink [{}]", tenantId, pageLink);
return DaoUtil.toPageData(calculatedFieldLinkRepository.findAllByTenantId(tenantId.getId(), DaoUtil.toPageable(pageLink)));
}
@Override @Override
protected Class<CalculatedFieldLinkEntity> getEntityClass() { protected Class<CalculatedFieldLinkEntity> getEntityClass() {
return CalculatedFieldLinkEntity.class; return CalculatedFieldLinkEntity.class;

17
dao/src/main/java/org/thingsboard/server/dao/sql/device/DefaultNativeAssetRepository.java

@ -20,12 +20,9 @@ import org.springframework.data.domain.Pageable;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import org.springframework.transaction.support.TransactionTemplate; import org.springframework.transaction.support.TransactionTemplate;
import org.thingsboard.server.common.data.DeviceIdInfo;
import org.thingsboard.server.common.data.ProfileEntityIdInfo; import org.thingsboard.server.common.data.ProfileEntityIdInfo;
import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.AssetProfileId; import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
@ -43,8 +40,8 @@ public class DefaultNativeAssetRepository extends AbstractNativeRepository imple
@Override @Override
public PageData<ProfileEntityIdInfo> findProfileEntityIdInfos(Pageable pageable) { public PageData<ProfileEntityIdInfo> findProfileEntityIdInfos(Pageable pageable) {
String PROFILE_DEVICE_ID_INFO_QUERY = "SELECT tenant_id as tenantId, asset_profile_id as profileId, id as id FROM asset ORDER BY created_time ASC LIMIT %s OFFSET %s"; String PROFILE_ASSET_ID_INFO_QUERY = "SELECT tenant_id as tenantId, asset_profile_id as profileId, id as id FROM asset ORDER BY created_time ASC LIMIT %s OFFSET %s";
return find(COUNT_QUERY, PROFILE_DEVICE_ID_INFO_QUERY, pageable, row -> { return find(COUNT_QUERY, PROFILE_ASSET_ID_INFO_QUERY, pageable, row -> {
AssetId id = new AssetId((UUID) row.get("id")); AssetId id = new AssetId((UUID) row.get("id"));
AssetProfileId profileId = new AssetProfileId((UUID) row.get("profileId")); AssetProfileId profileId = new AssetProfileId((UUID) row.get("profileId"));
var tenantIdObj = row.get("tenantId"); var tenantIdObj = row.get("tenantId");
@ -52,4 +49,14 @@ public class DefaultNativeAssetRepository extends AbstractNativeRepository imple
}); });
} }
@Override
public PageData<ProfileEntityIdInfo> findProfileEntityIdInfosByTenantId(UUID tenantId, Pageable pageable) {
String PROFILE_ASSET_ID_INFO_QUERY = String.format("SELECT tenant_id as tenantId, asset_profile_id as profileId, id as id FROM asset WHERE tenant_id = '%s' ORDER BY created_time ASC LIMIT %%s OFFSET %%s", tenantId);
return find(COUNT_QUERY, PROFILE_ASSET_ID_INFO_QUERY, pageable, row -> {
AssetId id = new AssetId((UUID) row.get("id"));
AssetProfileId profileId = new AssetProfileId((UUID) row.get("profileId"));
var tenantIdObj = row.get("tenantId");
return ProfileEntityIdInfo.create(tenantIdObj != null ? (UUID) tenantIdObj : TenantId.SYS_TENANT_ID.getId(), profileId, id);
});
}
} }

11
dao/src/main/java/org/thingsboard/server/dao/sql/device/DefaultNativeDeviceRepository.java

@ -61,4 +61,15 @@ public class DefaultNativeDeviceRepository extends AbstractNativeRepository impl
}); });
} }
@Override
public PageData<ProfileEntityIdInfo> findProfileEntityIdInfosByTenantId(UUID tenantId, Pageable pageable) {
String PROFILE_DEVICE_ID_INFO_QUERY = String.format("SELECT tenant_id as tenantId, device_profile_id as profileId, id as id FROM device WHERE tenant_id = '%s' ORDER BY created_time ASC LIMIT %%s OFFSET %%s", tenantId);
return find(COUNT_QUERY, PROFILE_DEVICE_ID_INFO_QUERY, pageable, row -> {
DeviceId id = new DeviceId((UUID) row.get("id"));
DeviceProfileId profileId = new DeviceProfileId((UUID) row.get("profileId"));
var tenantIdObj = row.get("tenantId");
return ProfileEntityIdInfo.create(tenantIdObj != null ? (UUID) tenantIdObj : TenantId.SYS_TENANT_ID.getId(), profileId, id);
});
}
} }

16
dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java

@ -179,11 +179,11 @@ public class JpaDeviceDao extends JpaAbstractDao<DeviceEntity, Device> implement
@Override @Override
public PageData<DeviceId> findDeviceIdsByTenantIdAndDeviceProfileId(UUID tenantId, UUID deviceProfileId, PageLink pageLink) { public PageData<DeviceId> findDeviceIdsByTenantIdAndDeviceProfileId(UUID tenantId, UUID deviceProfileId, PageLink pageLink) {
return DaoUtil.pageToPageData( return DaoUtil.pageToPageData(
deviceRepository.findIdsByTenantIdAndDeviceProfileId( deviceRepository.findIdsByTenantIdAndDeviceProfileId(
tenantId, tenantId,
deviceProfileId, deviceProfileId,
pageLink.getTextSearch(), pageLink.getTextSearch(),
DaoUtil.toPageable(pageLink))) DaoUtil.toPageable(pageLink)))
.mapData(DeviceId::new); .mapData(DeviceId::new);
} }
@ -281,6 +281,12 @@ public class JpaDeviceDao extends JpaAbstractDao<DeviceEntity, Device> implement
return nativeDeviceRepository.findProfileEntityIdInfos(DaoUtil.toPageable(pageLink)); return nativeDeviceRepository.findProfileEntityIdInfos(DaoUtil.toPageable(pageLink));
} }
@Override
public PageData<ProfileEntityIdInfo> findProfileEntityIdInfosByTenantId(UUID tenantId, PageLink pageLink) {
log.debug("Find profile device id infos by tenantId[{}], pageLink [{}]", tenantId, pageLink);
return nativeDeviceRepository.findProfileEntityIdInfosByTenantId(tenantId, DaoUtil.toPageable(pageLink));
}
@Override @Override
public Device findByTenantIdAndExternalId(UUID tenantId, UUID externalId) { public Device findByTenantIdAndExternalId(UUID tenantId, UUID externalId) {
return DaoUtil.getData(deviceRepository.findByTenantIdAndExternalId(tenantId, externalId)); return DaoUtil.getData(deviceRepository.findByTenantIdAndExternalId(tenantId, externalId));

4
dao/src/main/java/org/thingsboard/server/dao/sql/device/NativeProfileEntityRepository.java

@ -19,8 +19,12 @@ import org.springframework.data.domain.Pageable;
import org.thingsboard.server.common.data.ProfileEntityIdInfo; import org.thingsboard.server.common.data.ProfileEntityIdInfo;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import java.util.UUID;
public interface NativeProfileEntityRepository { public interface NativeProfileEntityRepository {
PageData<ProfileEntityIdInfo> findProfileEntityIdInfos(Pageable pageable); PageData<ProfileEntityIdInfo> findProfileEntityIdInfos(Pageable pageable);
PageData<ProfileEntityIdInfo> findProfileEntityIdInfosByTenantId(UUID tenantId, Pageable pageable);
} }

4
edqs/src/main/resources/edqs.yml

@ -66,7 +66,7 @@ queue:
# EDQS responses topic # EDQS responses topic
responses_topic: "${TB_EDQS_RESPONSES_TOPIC:edqs.responses}" responses_topic: "${TB_EDQS_RESPONSES_TOPIC:edqs.responses}"
# Poll interval for EDQS topics # Poll interval for EDQS topics
poll_interval: "${TB_EDQS_POLL_INTERVAL_MS:125}" poll_interval: "${TB_EDQS_POLL_INTERVAL_MS:25}"
# Maximum amount of pending requests to EDQS # Maximum amount of pending requests to EDQS
max_pending_requests: "${TB_EDQS_MAX_PENDING_REQUESTS:10000}" max_pending_requests: "${TB_EDQS_MAX_PENDING_REQUESTS:10000}"
# Maximum timeout for requests to EDQS # Maximum timeout for requests to EDQS
@ -76,6 +76,8 @@ queue:
stats: stats:
# Enable/disable statistics for EDQS # Enable/disable statistics for EDQS
enabled: "${TB_EDQS_STATS_ENABLED:true}" enabled: "${TB_EDQS_STATS_ENABLED:true}"
# Threshold for slow queries to log, in milliseconds
slow_query_threshold: "${TB_EDQS_SLOW_QUERY_THRESHOLD_MS:200}"
kafka: kafka:
# Kafka Bootstrap nodes in "host:port" format # Kafka Bootstrap nodes in "host:port" format

4
ui-ngx/src/app/modules/common/modules-map.ts

@ -76,8 +76,6 @@ import * as TranslateCore from '@ngx-translate/core';
import * as MatDateTimePicker from '@mat-datetimepicker/core'; import * as MatDateTimePicker from '@mat-datetimepicker/core';
import _moment from 'moment'; import _moment from 'moment';
import * as tslib from 'tslib'; import * as tslib from 'tslib';
import * as Echarts from 'echarts';
import * as EchartsCore from 'echarts/core';
import * as TbCore from '@core/public-api'; import * as TbCore from '@core/public-api';
import * as TbShared from '@shared/public-api'; import * as TbShared from '@shared/public-api';
@ -407,8 +405,6 @@ class ModulesMap implements IModulesMap {
'@mat-datetimepicker/core': MatDateTimePicker, '@mat-datetimepicker/core': MatDateTimePicker,
moment: _moment, moment: _moment,
tslib, tslib,
'echarts': Echarts,
'echarts/core': EchartsCore,
'@core/public-api': TbCore, '@core/public-api': TbCore,
'@shared/public-api': TbShared, '@shared/public-api': TbShared,

1
ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.scss

@ -33,6 +33,7 @@
.table-container { .table-container {
overflow: auto; overflow: auto;
z-index: 1;
} }
} }
} }

2
ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts

@ -271,7 +271,7 @@ export const updateScadaSymbolMetadataInContent = (svgContent: string, metadata:
const svgDoc = new DOMParser().parseFromString(svgContent, 'image/svg+xml'); const svgDoc = new DOMParser().parseFromString(svgContent, 'image/svg+xml');
const parsererror = svgDoc.getElementsByTagName('parsererror'); const parsererror = svgDoc.getElementsByTagName('parsererror');
if (parsererror?.length) { if (parsererror?.length) {
return parsererror[0].outerHTML; throw Error(parsererror[0].textContent)
} }
updateScadaSymbolMetadataInDom(svgDoc, metadata); updateScadaSymbolMetadataInDom(svgDoc, metadata);
return svgDoc.documentElement.outerHTML; return svgDoc.documentElement.outerHTML;

149
ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.ts

@ -79,6 +79,7 @@ import {
SaveWidgetTypeAsDialogResult SaveWidgetTypeAsDialogResult
} from '@home/pages/widget/save-widget-type-as-dialog.component'; } from '@home/pages/widget/save-widget-type-as-dialog.component';
import { WidgetService } from '@core/http/widget.service'; import { WidgetService } from '@core/http/widget.service';
import { ActionNotificationShow } from '@core/notification/notification.actions';
@Component({ @Component({
selector: 'tb-scada-symbol', selector: 'tb-scada-symbol',
@ -213,32 +214,36 @@ export class ScadaSymbolComponent extends PageComponent
this.editObjectCallbacks.tagsUpdated(tags); this.editObjectCallbacks.tagsUpdated(tags);
} }
const metadata: ScadaSymbolMetadata = this.scadaSymbolFormGroup.get('metadata').value; const metadata: ScadaSymbolMetadata = this.scadaSymbolFormGroup.get('metadata').value;
const scadaSymbolContent = this.prepareScadaSymbolContent(metadata); try {
const file = createFileFromContent(scadaSymbolContent, this.symbolData.imageResource.fileName, const scadaSymbolContent = this.prepareScadaSymbolContent(metadata);
this.symbolData.imageResource.descriptor.mediaType); const file = createFileFromContent(scadaSymbolContent, this.symbolData.imageResource.fileName,
const type = imageResourceType(this.symbolData.imageResource); this.symbolData.imageResource.descriptor.mediaType);
let imageInfoObservable = const type = imageResourceType(this.symbolData.imageResource);
this.imageService.updateImage(type, this.symbolData.imageResource.resourceKey, file); let imageInfoObservable =
if (metadata.title !== this.symbolData.imageResource.title) { this.imageService.updateImage(type, this.symbolData.imageResource.resourceKey, file);
imageInfoObservable = imageInfoObservable.pipe( if (metadata.title !== this.symbolData.imageResource.title) {
switchMap(imageInfo => { imageInfoObservable = imageInfoObservable.pipe(
imageInfo.title = metadata.title; switchMap(imageInfo => {
return this.imageService.updateImageInfo(imageInfo); imageInfo.title = metadata.title;
}) return this.imageService.updateImageInfo(imageInfo);
); })
} );
imageInfoObservable.pipe( }
switchMap(imageInfo => this.imageService.getImageString( imageInfoObservable.pipe(
switchMap(imageInfo => this.imageService.getImageString(
`${IMAGES_URL_PREFIX}/${type}/${encodeURIComponent(imageInfo.resourceKey)}`).pipe( `${IMAGES_URL_PREFIX}/${type}/${encodeURIComponent(imageInfo.resourceKey)}`).pipe(
map(content => ({ map(content => ({
imageResource: imageInfo, imageResource: imageInfo,
scadaSymbolContent: content scadaSymbolContent: content
})) }))
)) ))
).subscribe(data => { ).subscribe(data => {
this.init(data); this.init(data);
this.updateBreadcrumbs.emit(); this.updateBreadcrumbs.emit();
}); });
} catch (e) {
this.store.dispatch(new ActionNotificationShow({ message: e.message, type: 'error' }));
}
} }
} }
@ -248,43 +253,47 @@ export class ScadaSymbolComponent extends PageComponent
enterPreviewMode() { enterPreviewMode() {
this.previewMetadata = this.scadaSymbolFormGroup.get('metadata').value; this.previewMetadata = this.scadaSymbolFormGroup.get('metadata').value;
this.symbolData.scadaSymbolContent = this.prepareScadaSymbolContent(this.previewMetadata); try {
this.previewScadaSymbolObjectSettings = { this.symbolData.scadaSymbolContent = this.prepareScadaSymbolContent(this.previewMetadata);
behavior: {}, this.previewScadaSymbolObjectSettings = {
properties: {} behavior: {},
}; properties: {}
this.scadaPreviewFormGroup.patchValue({ };
scadaSymbolObjectSettings: this.previewScadaSymbolObjectSettings this.scadaPreviewFormGroup.patchValue({
}, {emitEvent: false}); scadaSymbolObjectSettings: this.previewScadaSymbolObjectSettings
this.scadaPreviewFormGroup.markAsPristine(); }, {emitEvent: false});
const settings: ScadaSymbolWidgetSettings = {...scadaSymbolWidgetDefaultSettings, this.scadaPreviewFormGroup.markAsPristine();
...{ const settings: ScadaSymbolWidgetSettings = {...scadaSymbolWidgetDefaultSettings,
...{
simulated: true, simulated: true,
scadaSymbolUrl: null, scadaSymbolUrl: null,
scadaSymbolContent: this.symbolData.scadaSymbolContent, scadaSymbolContent: this.symbolData.scadaSymbolContent,
scadaSymbolObjectSettings: this.previewScadaSymbolObjectSettings, scadaSymbolObjectSettings: this.previewScadaSymbolObjectSettings,
padding: '0', padding: '0',
background: colorBackground('rgba(0,0,0,0)') background: colorBackground('rgba(0,0,0,0)')
} }
}; };
this.previewWidget = { this.previewWidget = {
typeFullFqn: 'system.scada_symbol', typeFullFqn: 'system.scada_symbol',
type: widgetType.rpc, type: widgetType.rpc,
sizeX: this.previewMetadata.widgetSizeX || 3, sizeX: this.previewMetadata.widgetSizeX || 3,
sizeY: this.previewMetadata.widgetSizeY || 3, sizeY: this.previewMetadata.widgetSizeY || 3,
row: 0, row: 0,
col: 0, col: 0,
config: { config: {
settings, settings,
showTitle: false, showTitle: false,
dropShadow: false, dropShadow: false,
padding: '0', padding: '0',
margin: '0', margin: '0',
backgroundColor: 'rgba(0,0,0,0)' backgroundColor: 'rgba(0,0,0,0)'
} }
}; };
this.previewWidgets = [this.previewWidget]; this.previewWidgets = [this.previewWidget];
this.previewMode = true; this.previewMode = true;
} catch (e) {
this.store.dispatch(new ActionNotificationShow({ message: e.message, type: 'error' }));
}
} }
exitPreviewMode() { exitPreviewMode() {
@ -374,19 +383,23 @@ export class ScadaSymbolComponent extends PageComponent
metadata = parseScadaSymbolMetadataFromContent(this.origSymbolData.scadaSymbolContent); metadata = parseScadaSymbolMetadataFromContent(this.origSymbolData.scadaSymbolContent);
} }
const linkElement = document.createElement('a'); const linkElement = document.createElement('a');
const scadaSymbolContent = this.prepareScadaSymbolContent(metadata); try {
const blob = new Blob([scadaSymbolContent], { type: this.symbolData.imageResource.descriptor.mediaType }); const scadaSymbolContent = this.prepareScadaSymbolContent(metadata);
const url = URL.createObjectURL(blob); const blob = new Blob([scadaSymbolContent], { type: this.symbolData.imageResource.descriptor.mediaType });
linkElement.setAttribute('href', url); const url = URL.createObjectURL(blob);
linkElement.setAttribute('download', this.symbolData.imageResource.fileName); linkElement.setAttribute('href', url);
const clickEvent = new MouseEvent('click', linkElement.setAttribute('download', this.symbolData.imageResource.fileName);
{ const clickEvent = new MouseEvent('click',
view: window, {
bubbles: true, view: window,
cancelable: false bubbles: true,
} cancelable: false
); }
linkElement.dispatchEvent(clickEvent); );
linkElement.dispatchEvent(clickEvent);
} catch (e) {
this.store.dispatch(new ActionNotificationShow({ message: e.message, type: 'error' }));
}
} }
createWidget() { createWidget() {

4
ui-ngx/src/app/shared/components/image/upload-image-dialog.component.html

@ -28,8 +28,8 @@
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar> </mat-progress-bar>
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<div mat-dialog-content> <div mat-dialog-content class="relative">
<fieldset [disabled]="isLoading$ | async"> <fieldset [disabled]="isLoading$ | async" tb-toast toastTarget="uploadRoot" >
<tb-image-input resultAsFile <tb-image-input resultAsFile
processImageApiLink processImageApiLink
label="{{(isScada ? 'scada.symbol-preview' : 'image.image-preview') | translate}}" label="{{(isScada ? 'scada.symbol-preview' : 'image.image-preview') | translate}}"

63
ui-ngx/src/app/shared/components/image/upload-image-dialog.component.ts

@ -41,6 +41,7 @@ import {
updateScadaSymbolMetadataInContent updateScadaSymbolMetadataInContent
} from '@home/components/widget/lib/scada/scada-symbol.models'; } from '@home/components/widget/lib/scada/scada-symbol.models';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { ActionNotificationShow } from '@core/notification/notification.actions';
export interface UploadImageDialogData { export interface UploadImageDialogData {
imageSubType: ResourceSubType; imageSubType: ResourceSubType;
@ -135,38 +136,48 @@ export class UploadImageDialogComponent extends
upload(): void { upload(): void {
this.submitted = true; this.submitted = true;
let file: File = this.uploadImageFormGroup.get('file').value; let file: File = this.uploadImageFormGroup.get('file').value;
if (this.uploadImage) { try {
const title: string = this.uploadImageFormGroup.get('title').value; if (this.uploadImage) {
if (this.isScada) { const title: string = this.uploadImageFormGroup.get('title').value;
if (!this.scadaSymbolMetadata) { if (this.isScada) {
this.scadaSymbolMetadata = emptyMetadata(); if (!this.scadaSymbolMetadata) {
} this.scadaSymbolMetadata = emptyMetadata();
if (this.scadaSymbolMetadata.title !== title) { }
this.scadaSymbolMetadata.title = title; if (this.scadaSymbolMetadata.title !== title) {
this.scadaSymbolMetadata.title = title;
}
const newContent = updateScadaSymbolMetadataInContent(this.scadaSymbolContent, this.scadaSymbolMetadata);
file = updateFileContent(file, newContent);
} }
const newContent = updateScadaSymbolMetadataInContent(this.scadaSymbolContent, this.scadaSymbolMetadata);
file = updateFileContent(file, newContent);
}
forkJoin([
this.imageService.uploadImage(file, title, this.data.imageSubType),
blobToBase64(file)
]).subscribe(([imageInfo, base64]) => {
this.dialogRef.close({image: Object.assign(imageInfo, {base64})});
});
} else {
if (this.isScada) {
blobToText(file).subscribe(scadaSymbolContent => {
this.dialogRef.close({scadaSymbolContent});
});
} else {
const image = this.data.image;
forkJoin([ forkJoin([
this.imageService.updateImage(imageResourceType(image), image.resourceKey, file), this.imageService.uploadImage(file, title, this.data.imageSubType),
blobToBase64(file) blobToBase64(file)
]).subscribe(([imageInfo, base64]) => { ]).subscribe(([imageInfo, base64]) => {
this.dialogRef.close({image:Object.assign(imageInfo, {base64})}); this.dialogRef.close({image: Object.assign(imageInfo, {base64})});
}); });
} else {
if (this.isScada) {
blobToText(file).subscribe(scadaSymbolContent => {
this.dialogRef.close({scadaSymbolContent});
});
} else {
const image = this.data.image;
forkJoin([
this.imageService.updateImage(imageResourceType(image), image.resourceKey, file),
blobToBase64(file)
]).subscribe(([imageInfo, base64]) => {
this.dialogRef.close({image:Object.assign(imageInfo, {base64})});
});
}
} }
} catch (e) {
this.store.dispatch(new ActionNotificationShow({
message: e.message,
type: 'error',
verticalPosition: 'top',
horizontalPosition: 'right',
target: 'uploadRoot'
}));
} }
} }
} }

4
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -5258,7 +5258,7 @@
"advanced-mode": "Advanced", "advanced-mode": "Advanced",
"save-time-series": { "save-time-series": {
"processing-settings": "Processing settings", "processing-settings": "Processing settings",
"processing-settings-hint": "Define how incoming messages are processed. In Basic mode, select a preconfigured processing strategy or enable only WebSocket updates. Advanced mode allows you to select individual processing strategies for each action.", "processing-settings-hint": "Define how incoming messages are processed. Basic processing settings allow you to select preconfigured strategies, while Advanced settings allow you to select individual processing strategies for each action.",
"advanced-settings-hint": "Be cautious when configuring processing strategies. Certain combinations can lead to unexpected behavior.", "advanced-settings-hint": "Be cautious when configuring processing strategies. Certain combinations can lead to unexpected behavior.",
"strategy": "Strategy", "strategy": "Strategy",
"deduplication-interval": "Deduplication interval", "deduplication-interval": "Deduplication interval",
@ -5277,7 +5277,7 @@
}, },
"save-attribute": { "save-attribute": {
"processing-settings": "Processing settings", "processing-settings": "Processing settings",
"processing-settings-hint": "Define how incoming messages are processed. In Basic mode, select a preconfigured processing strategy or enable only WebSocket updates. Advanced mode allows you to select individual processing strategies for each action.", "processing-settings-hint": "Define how incoming messages are processed. Basic processing settings allow you to select preconfigured strategies, while Advanced settings allow you to select individual processing strategies for each action.",
"advanced-settings-hint": "Be cautious when configuring processing strategies. Certain combinations can lead to unexpected behavior.", "advanced-settings-hint": "Be cautious when configuring processing strategies. Certain combinations can lead to unexpected behavior.",
"strategy": "Strategy", "strategy": "Strategy",
"deduplication-interval": "Deduplication interval", "deduplication-interval": "Deduplication interval",

Loading…
Cancel
Save