From c1044a989e291134f9ae41155ec35fac8a62a99d Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Wed, 11 Feb 2026 14:10:20 +0200 Subject: [PATCH] Introduce urgent API usage keys for quicker usage state update --- .../src/main/resources/thingsboard.yml | 2 + .../server/service/apiusage/ApiUsageTest.java | 41 +++++++++++++++++-- .../server/common/data/ApiUsageRecordKey.java | 13 +++--- .../DefaultTbApiUsageReportClient.java | 20 +++++++-- .../src/main/resources/tb-vc-executor.yml | 2 + .../src/main/resources/tb-coap-transport.yml | 2 + .../src/main/resources/tb-http-transport.yml | 2 + .../src/main/resources/tb-lwm2m-transport.yml | 2 + .../src/main/resources/tb-mqtt-transport.yml | 2 + .../src/main/resources/tb-snmp-transport.yml | 2 + 10 files changed, 76 insertions(+), 12 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 84058729af..67ad5be16b 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -197,6 +197,8 @@ usage: enabled_per_customer: "${USAGE_STATS_REPORT_PER_CUSTOMER_ENABLED:false}" # Statistics reporting interval, set to send summarized data every 10 seconds by default interval: "${USAGE_STATS_REPORT_INTERVAL:60}" + # Reporting interval for urgent keys (e.g. SMS, Email) that require quicker usage state updates + urgent_interval: "${USAGE_STATS_REPORT_URGENT_INTERVAL:10}" # Amount of statistic messages in pack pack_size: "${USAGE_STATS_REPORT_PACK_SIZE:1024}" check: diff --git a/application/src/test/java/org/thingsboard/server/service/apiusage/ApiUsageTest.java b/application/src/test/java/org/thingsboard/server/service/apiusage/ApiUsageTest.java index da73600671..3dfdc39c5b 100644 --- a/application/src/test/java/org/thingsboard/server/service/apiusage/ApiUsageTest.java +++ b/application/src/test/java/org/thingsboard/server/service/apiusage/ApiUsageTest.java @@ -19,6 +19,8 @@ import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.TestPropertySource; +import org.thingsboard.server.common.data.ApiUsageRecordKey; +import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.SaveDeviceWithCredentialsRequest; @@ -30,6 +32,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; +import org.thingsboard.server.common.stats.TbApiUsageReportClient; import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.controller.TbUrlConstants; import org.thingsboard.server.dao.service.DaoSqlTest; @@ -46,6 +49,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @TestPropertySource(properties = { "usage.stats.report.enabled=true", "usage.stats.report.interval=2", + "usage.stats.report.urgent_interval=1", "usage.stats.gauge_report_interval=1", }) public class ApiUsageTest extends AbstractControllerTest { @@ -54,9 +58,12 @@ public class ApiUsageTest extends AbstractControllerTest { private User tenantAdmin; private static final int MAX_DP_ENABLE_VALUE = 12; + private static final int MAX_SMS_ENABLE_VALUE = 10; private static final double WARN_THRESHOLD_VALUE = 0.5; @Autowired private ApiUsageStateService apiUsageStateService; + @Autowired + private TbApiUsageReportClient apiUsageReportClient; @Before public void beforeTest() throws Exception { @@ -82,7 +89,7 @@ public class ApiUsageTest extends AbstractControllerTest { } @Test - public void testTelemetryApiCall() throws Exception { + public void testDbStorageApiUsage() throws Exception { Device device = createDevice(); assertNotNull(device); String telemetryPayload = "{\"temperature\":25, \"humidity\":60}"; @@ -94,7 +101,8 @@ public class ApiUsageTest extends AbstractControllerTest { doPostAsync(url, telemetryPayload, String.class, status().isOk()); } - await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> assertEquals(ApiUsageStateValue.WARNING, apiUsageStateService.findTenantApiUsageState(tenantId).getDbStorageState())); + await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> + assertEquals(ApiUsageStateValue.WARNING, getUsageState().getDbStorageState())); long VALUE_DISABLE = (long) (MAX_DP_ENABLE_VALUE - (MAX_DP_ENABLE_VALUE * WARN_THRESHOLD_VALUE)) / 2; @@ -104,10 +112,35 @@ public class ApiUsageTest extends AbstractControllerTest { await().atMost(TIMEOUT, TimeUnit.SECONDS) .untilAsserted(() -> { - assertEquals(ApiUsageStateValue.DISABLED, apiUsageStateService.findTenantApiUsageState(tenantId).getDbStorageState()); + assertEquals(ApiUsageStateValue.DISABLED, getUsageState().getDbStorageState()); }); } + @Test + public void testSmsApiUsageState() { + long smsWarnThreshold = (long) (MAX_SMS_ENABLE_VALUE * WARN_THRESHOLD_VALUE); + + for (int i = 0; i < smsWarnThreshold; i++) { + apiUsageReportClient.report(tenantId, null, ApiUsageRecordKey.SMS_EXEC_COUNT); + } + + await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> + assertEquals(ApiUsageStateValue.WARNING, getUsageState().getSmsExecState())); + + long smsDisableCount = MAX_SMS_ENABLE_VALUE - smsWarnThreshold; + + for (int i = 0; i < smsDisableCount; i++) { + apiUsageReportClient.report(tenantId, null, ApiUsageRecordKey.SMS_EXEC_COUNT); + } + + await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> + assertEquals(ApiUsageStateValue.DISABLED, getUsageState().getSmsExecState())); + } + + private ApiUsageState getUsageState() { + return apiUsageStateService.findTenantApiUsageState(tenantId); + } + private TenantProfile createTenantProfile() { TenantProfile tenantProfile = new TenantProfile(); tenantProfile.setName("Tenant Profile"); @@ -116,6 +149,8 @@ public class ApiUsageTest extends AbstractControllerTest { TenantProfileData tenantProfileData = new TenantProfileData(); DefaultTenantProfileConfiguration config = DefaultTenantProfileConfiguration.builder() .maxDPStorageDays(MAX_DP_ENABLE_VALUE) + .maxSms(MAX_SMS_ENABLE_VALUE) + .smsEnabled(true) .warnThreshold(WARN_THRESHOLD_VALUE) .build(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java index 5a679ce2cf..ceb657374b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java @@ -25,8 +25,8 @@ public enum ApiUsageRecordKey { RE_EXEC_COUNT(ApiFeature.RE, "ruleEngineExecutionCount", "ruleEngineExecutionLimit", "Rule Engine execution"), JS_EXEC_COUNT(ApiFeature.JS, "jsExecutionCount", "jsExecutionLimit", "JavaScript execution"), TBEL_EXEC_COUNT(ApiFeature.TBEL, "tbelExecutionCount", "tbelExecutionLimit", "Tbel execution"), - EMAIL_EXEC_COUNT(ApiFeature.EMAIL, "emailCount", "emailLimit", "email message"), - SMS_EXEC_COUNT(ApiFeature.SMS, "smsCount", "smsLimit", "SMS message"), + EMAIL_EXEC_COUNT(ApiFeature.EMAIL, "emailCount", "emailLimit", "email message", true, true), + SMS_EXEC_COUNT(ApiFeature.SMS, "smsCount", "smsLimit", "SMS message", true, true), CREATED_ALARMS_COUNT(ApiFeature.ALARM, "createdAlarmsCount", "createdAlarmsLimit", "alarm"), ACTIVE_DEVICES("activeDevicesCount"), INACTIVE_DEVICES("inactiveDevicesCount"); @@ -50,21 +50,24 @@ public enum ApiUsageRecordKey { private final String unitLabel; @Getter private final boolean counter; + @Getter + private final boolean urgent; // urgent keys are reported at a shorter interval for quicker usage state updates ApiUsageRecordKey(ApiFeature apiFeature, String apiCountKey, String apiLimitKey, String unitLabel) { - this(apiFeature, apiCountKey, apiLimitKey, unitLabel, true); + this(apiFeature, apiCountKey, apiLimitKey, unitLabel, true, false); } ApiUsageRecordKey(String apiCountKey) { - this(null, apiCountKey, null, null, false); + this(null, apiCountKey, null, null, false, false); } - ApiUsageRecordKey(ApiFeature apiFeature, String apiCountKey, String apiLimitKey, String unitLabel, boolean counter) { + ApiUsageRecordKey(ApiFeature apiFeature, String apiCountKey, String apiLimitKey, String unitLabel, boolean counter, boolean urgent) { this.apiFeature = apiFeature; this.apiCountKey = apiCountKey; this.apiLimitKey = apiLimitKey; this.unitLabel = unitLabel; this.counter = counter; + this.urgent = urgent; } public static ApiUsageRecordKey[] getKeys(ApiFeature feature) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageReportClient.java b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageReportClient.java index b6a785316b..50e131103b 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageReportClient.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageReportClient.java @@ -63,8 +63,10 @@ public class DefaultTbApiUsageReportClient implements TbApiUsageReportClient { private boolean enabled; @Value("${usage.stats.report.enabled_per_customer:false}") private boolean enabledPerCustomer; - @Value("${usage.stats.report.interval:10}") + @Value("${usage.stats.report.interval:60}") private int interval; + @Value("${usage.stats.report.urgent_interval:10}") + private int urgentInterval; @Value("${usage.stats.report.pack_size:1024}") private int packSize; @@ -83,20 +85,30 @@ public class DefaultTbApiUsageReportClient implements TbApiUsageReportClient { for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { stats.put(key, new ConcurrentHashMap<>()); } + Random random = new Random(); scheduler.scheduleWithFixedDelay(() -> { try { - reportStats(); + reportStats(false); } catch (Exception e) { log.warn("Failed to report statistics: ", e); } - }, new Random().nextInt(interval), interval, TimeUnit.SECONDS); + }, random.nextInt(interval), interval, TimeUnit.SECONDS); + scheduler.scheduleWithFixedDelay(() -> { + try { + reportStats(true); + } catch (Exception e) { + log.warn("Failed to report urgent statistics: ", e); + } + }, random.nextInt(urgentInterval), urgentInterval, TimeUnit.SECONDS); } } - private void reportStats() { + private void reportStats(boolean urgent) { ConcurrentMap report = new ConcurrentHashMap<>(); for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { + if (key.isUrgent() != urgent) continue; + ConcurrentMap statsForKey = stats.get(key); statsForKey.forEach((reportLevel, statsValue) -> { long value = statsValue.get(); diff --git a/msa/vc-executor/src/main/resources/tb-vc-executor.yml b/msa/vc-executor/src/main/resources/tb-vc-executor.yml index 8314985622..56e15d4b4d 100644 --- a/msa/vc-executor/src/main/resources/tb-vc-executor.yml +++ b/msa/vc-executor/src/main/resources/tb-vc-executor.yml @@ -215,6 +215,8 @@ usage: enabled_per_customer: "${USAGE_STATS_REPORT_PER_CUSTOMER_ENABLED:false}" # Interval of reporting the statistics. By default, the summarized statistics are sent every 10 seconds interval: "${USAGE_STATS_REPORT_INTERVAL:60}" + # Reporting interval for urgent keys (e.g. SMS, Email) that require quicker usage state updates + urgent_interval: "${USAGE_STATS_REPORT_URGENT_INTERVAL:10}" # Amount of statistic messages in pack pack_size: "${USAGE_STATS_REPORT_PACK_SIZE:1024}" diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml index 09138c2639..376075a0a4 100644 --- a/transport/coap/src/main/resources/tb-coap-transport.yml +++ b/transport/coap/src/main/resources/tb-coap-transport.yml @@ -421,6 +421,8 @@ usage: enabled_per_customer: "${USAGE_STATS_REPORT_PER_CUSTOMER_ENABLED:false}" # Interval of reporting the statistics. By default, the summarized statistics are sent every 10 seconds interval: "${USAGE_STATS_REPORT_INTERVAL:60}" + # Reporting interval for urgent keys (e.g. SMS, Email) that require quicker usage state updates + urgent_interval: "${USAGE_STATS_REPORT_URGENT_INTERVAL:10}" # Amount of statistic messages in pack pack_size: "${USAGE_STATS_REPORT_PACK_SIZE:1024}" diff --git a/transport/http/src/main/resources/tb-http-transport.yml b/transport/http/src/main/resources/tb-http-transport.yml index f869534088..1f6a251324 100644 --- a/transport/http/src/main/resources/tb-http-transport.yml +++ b/transport/http/src/main/resources/tb-http-transport.yml @@ -370,6 +370,8 @@ usage: enabled_per_customer: "${USAGE_STATS_REPORT_PER_CUSTOMER_ENABLED:false}" # Interval of reporting the statistics. By default, the summarized statistics are sent every 10 seconds interval: "${USAGE_STATS_REPORT_INTERVAL:60}" + # Reporting interval for urgent keys (e.g. SMS, Email) that require quicker usage state updates + urgent_interval: "${USAGE_STATS_REPORT_URGENT_INTERVAL:10}" # Amount of statistic messages in pack pack_size: "${USAGE_STATS_REPORT_PACK_SIZE:1024}" diff --git a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml index 08a4040fbe..b4d8d6c7eb 100644 --- a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml +++ b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml @@ -471,6 +471,8 @@ usage: enabled_per_customer: "${USAGE_STATS_REPORT_PER_CUSTOMER_ENABLED:false}" # Interval of reporting the statistics. By default, the summarized statistics are sent every 10 seconds interval: "${USAGE_STATS_REPORT_INTERVAL:60}" + # Reporting interval for urgent keys (e.g. SMS, Email) that require quicker usage state updates + urgent_interval: "${USAGE_STATS_REPORT_URGENT_INTERVAL:10}" # Amount of statistic messages in pack pack_size: "${USAGE_STATS_REPORT_PACK_SIZE:1024}" diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml index ccbd3901ce..0b96c57c13 100644 --- a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml +++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml @@ -404,6 +404,8 @@ usage: enabled_per_customer: "${USAGE_STATS_REPORT_PER_CUSTOMER_ENABLED:false}" # Interval of reporting the statistics. By default, the summarized statistics are sent every 10 seconds interval: "${USAGE_STATS_REPORT_INTERVAL:60}" + # Reporting interval for urgent keys (e.g. SMS, Email) that require quicker usage state updates + urgent_interval: "${USAGE_STATS_REPORT_URGENT_INTERVAL:10}" # Amount of statistic messages in pack pack_size: "${USAGE_STATS_REPORT_PACK_SIZE:1024}" diff --git a/transport/snmp/src/main/resources/tb-snmp-transport.yml b/transport/snmp/src/main/resources/tb-snmp-transport.yml index 656c01524d..71524ba47e 100644 --- a/transport/snmp/src/main/resources/tb-snmp-transport.yml +++ b/transport/snmp/src/main/resources/tb-snmp-transport.yml @@ -359,6 +359,8 @@ usage: enabled_per_customer: "${USAGE_STATS_REPORT_PER_CUSTOMER_ENABLED:false}" # Interval of reporting the statistics. By default, the summarized statistics are sent every 10 seconds interval: "${USAGE_STATS_REPORT_INTERVAL:60}" + # Reporting interval for urgent keys (e.g. SMS, Email) that require quicker usage state updates + urgent_interval: "${USAGE_STATS_REPORT_URGENT_INTERVAL:10}" # Amount of statistic messages in pack pack_size: "${USAGE_STATS_REPORT_PACK_SIZE:1024}"