committed by
GitHub
22 changed files with 607 additions and 6 deletions
@ -0,0 +1,45 @@ |
|||
/** |
|||
* Copyright © 2016-2023 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.controller; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.security.access.prepost.PreAuthorize; |
|||
import org.springframework.web.bind.annotation.RequestMapping; |
|||
import org.springframework.web.bind.annotation.RequestMethod; |
|||
import org.springframework.web.bind.annotation.ResponseBody; |
|||
import org.springframework.web.bind.annotation.RestController; |
|||
import org.thingsboard.server.common.data.UsageInfo; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardException; |
|||
import org.thingsboard.server.dao.usage.UsageInfoService; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
|
|||
@RestController |
|||
@TbCoreComponent |
|||
@RequestMapping("/api") |
|||
@Slf4j |
|||
public class UsageInfoController extends BaseController { |
|||
|
|||
@Autowired |
|||
private UsageInfoService usageInfoService; |
|||
|
|||
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
|||
@RequestMapping(value = "/usage", method = RequestMethod.GET) |
|||
@ResponseBody |
|||
public UsageInfo getTenantUsageInfo() throws ThingsboardException { |
|||
return checkNotNull(usageInfoService.getUsageInfo(getCurrentUser().getTenantId())); |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
/** |
|||
* Copyright © 2016-2023 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.dao.entity; |
|||
|
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
|
|||
public interface EntityCountService { |
|||
|
|||
long countByTenantIdAndEntityType(TenantId tenantId, EntityType entityType); |
|||
|
|||
void publishCountEntityEvictEvent(TenantId tenantId, EntityType entityType); |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
/** |
|||
* Copyright © 2016-2023 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.dao.usage; |
|||
|
|||
import org.thingsboard.server.common.data.UsageInfo; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
|
|||
public interface UsageInfoService { |
|||
|
|||
UsageInfo getUsageInfo(TenantId tenantId); |
|||
|
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
/** |
|||
* Copyright © 2016-2023 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.common.data; |
|||
|
|||
import lombok.Data; |
|||
|
|||
@Data |
|||
public class UsageInfo { |
|||
private long devices; |
|||
private long maxDevices; |
|||
private long assets; |
|||
private long maxAssets; |
|||
private long customers; |
|||
private long maxCustomers; |
|||
private long users; |
|||
private long maxUsers; |
|||
private long dashboards; |
|||
private long maxDashboards; |
|||
|
|||
private long transportMessages; |
|||
private long maxTransportMessages; |
|||
private long jsExecutions; |
|||
private long maxJsExecutions; |
|||
private long emails; |
|||
private long maxEmails; |
|||
private long sms; |
|||
private long maxSms; |
|||
private long alarms; |
|||
private long maxAlarms; |
|||
} |
|||
@ -0,0 +1,49 @@ |
|||
/** |
|||
* Copyright © 2016-2023 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.dao.entity; |
|||
|
|||
import lombok.RequiredArgsConstructor; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.context.annotation.Lazy; |
|||
import org.springframework.stereotype.Service; |
|||
import org.springframework.transaction.event.TransactionalEventListener; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
|
|||
@Service |
|||
public class BaseEntityCountService extends AbstractCachedEntityService<EntityCountCacheKey, Long, EntityCountCacheEvictEvent> implements EntityCountService { |
|||
|
|||
@Lazy |
|||
@Autowired |
|||
private EntityServiceRegistry entityServiceRegistry; |
|||
|
|||
@Override |
|||
public long countByTenantIdAndEntityType(TenantId tenantId, EntityType entityType) { |
|||
return cache.getAndPutInTransaction(new EntityCountCacheKey(tenantId, entityType), |
|||
() -> entityServiceRegistry.getServiceByEntityType(entityType).countByTenantId(tenantId), false); |
|||
} |
|||
|
|||
@Override |
|||
public void publishCountEntityEvictEvent(TenantId tenantId, EntityType entityType) { |
|||
publishEvictEvent(new EntityCountCacheEvictEvent(tenantId, entityType)); |
|||
} |
|||
|
|||
@TransactionalEventListener(classes = EntityCountCacheEvictEvent.class) |
|||
@Override |
|||
public void handleEvictEvent(EntityCountCacheEvictEvent event) { |
|||
cache.evict(new EntityCountCacheKey(event.getTenantId(), event.getEntityType())); |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
/** |
|||
* Copyright © 2016-2023 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.dao.entity; |
|||
|
|||
import lombok.Data; |
|||
import lombok.RequiredArgsConstructor; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
|
|||
@Data |
|||
@RequiredArgsConstructor |
|||
class EntityCountCacheEvictEvent { |
|||
private final TenantId tenantId; |
|||
private final EntityType entityType; |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
/** |
|||
* Copyright © 2016-2023 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.dao.entity; |
|||
|
|||
import lombok.EqualsAndHashCode; |
|||
import lombok.Getter; |
|||
import lombok.RequiredArgsConstructor; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
|
|||
import java.io.Serializable; |
|||
|
|||
@Getter |
|||
@EqualsAndHashCode |
|||
@RequiredArgsConstructor |
|||
public class EntityCountCacheKey implements Serializable { |
|||
|
|||
private static final long serialVersionUID = -1992105662738434178L; |
|||
|
|||
private final TenantId tenantId; |
|||
private final EntityType entityType; |
|||
|
|||
@Override |
|||
public String toString() { |
|||
return tenantId + "_" + entityType; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
/** |
|||
* Copyright © 2016-2023 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.dao.entity.count; |
|||
|
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
|||
import org.springframework.cache.CacheManager; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.cache.CaffeineTbTransactionalCache; |
|||
import org.thingsboard.server.common.data.CacheConstants; |
|||
import org.thingsboard.server.dao.entity.EntityCountCacheKey; |
|||
|
|||
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true) |
|||
@Service("EntityCountCache") |
|||
public class EntityCountCaffeineCache extends CaffeineTbTransactionalCache<EntityCountCacheKey, Long> { |
|||
|
|||
public EntityCountCaffeineCache(CacheManager cacheManager) { |
|||
super(cacheManager, CacheConstants.ENTITY_COUNT_CACHE); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
/** |
|||
* Copyright © 2016-2023 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.dao.entity.count; |
|||
|
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
|||
import org.springframework.data.redis.connection.RedisConnectionFactory; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.cache.CacheSpecsMap; |
|||
import org.thingsboard.server.cache.RedisTbTransactionalCache; |
|||
import org.thingsboard.server.cache.TBRedisCacheConfiguration; |
|||
import org.thingsboard.server.cache.TbFSTRedisSerializer; |
|||
import org.thingsboard.server.common.data.CacheConstants; |
|||
import org.thingsboard.server.dao.entity.EntityCountCacheKey; |
|||
|
|||
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") |
|||
@Service("EntityCountCache") |
|||
public class EntityCountRedisCache extends RedisTbTransactionalCache<EntityCountCacheKey, Long> { |
|||
|
|||
public EntityCountRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { |
|||
super(CacheConstants.ENTITY_COUNT_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); |
|||
} |
|||
} |
|||
@ -0,0 +1,98 @@ |
|||
/** |
|||
* Copyright © 2016-2023 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.dao.usage; |
|||
|
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.context.annotation.Lazy; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.common.data.ApiUsageRecordKey; |
|||
import org.thingsboard.server.common.data.ApiUsageState; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.UsageInfo; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.kv.TsKvEntry; |
|||
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; |
|||
import org.thingsboard.server.dao.entity.EntityCountService; |
|||
import org.thingsboard.server.dao.tenant.TbTenantProfileCache; |
|||
import org.thingsboard.server.dao.timeseries.TimeseriesService; |
|||
import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; |
|||
|
|||
import java.util.Arrays; |
|||
import java.util.Collection; |
|||
import java.util.List; |
|||
import java.util.Optional; |
|||
import java.util.concurrent.ExecutionException; |
|||
|
|||
@Service |
|||
@Slf4j |
|||
@RequiredArgsConstructor |
|||
public class BasicUsageInfoService implements UsageInfoService { |
|||
|
|||
private final EntityCountService countService; |
|||
private final ApiUsageStateService apiUsageStateService; |
|||
private final TimeseriesService tsService; |
|||
@Lazy |
|||
private final TbTenantProfileCache tenantProfileCache; |
|||
|
|||
@Override |
|||
public UsageInfo getUsageInfo(TenantId tenantId) { |
|||
DefaultTenantProfileConfiguration profileConfiguration = |
|||
(DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); |
|||
UsageInfo usageInfo = new UsageInfo(); |
|||
usageInfo.setDevices(countService.countByTenantIdAndEntityType(tenantId, EntityType.DEVICE)); |
|||
usageInfo.setMaxDevices(profileConfiguration.getMaxDevices()); |
|||
usageInfo.setAssets(countService.countByTenantIdAndEntityType(tenantId, EntityType.ASSET)); |
|||
usageInfo.setMaxAssets(profileConfiguration.getMaxAssets()); |
|||
usageInfo.setCustomers(countService.countByTenantIdAndEntityType(tenantId, EntityType.CUSTOMER)); |
|||
usageInfo.setMaxCustomers(profileConfiguration.getMaxCustomers()); |
|||
usageInfo.setUsers(countService.countByTenantIdAndEntityType(tenantId, EntityType.USER)); |
|||
usageInfo.setMaxUsers(profileConfiguration.getMaxUsers()); |
|||
usageInfo.setDashboards(countService.countByTenantIdAndEntityType(tenantId, EntityType.DASHBOARD)); |
|||
usageInfo.setMaxDashboards(profileConfiguration.getMaxDashboards()); |
|||
|
|||
usageInfo.setMaxAlarms(profileConfiguration.getMaxCreatedAlarms()); |
|||
usageInfo.setMaxTransportMessages(profileConfiguration.getMaxTransportMessages()); |
|||
usageInfo.setMaxJsExecutions(profileConfiguration.getMaxJSExecutions()); |
|||
usageInfo.setMaxEmails(profileConfiguration.getMaxEmails()); |
|||
usageInfo.setMaxSms(profileConfiguration.getMaxSms()); |
|||
ApiUsageState apiUsageState = apiUsageStateService.findTenantApiUsageState(tenantId); |
|||
if (apiUsageState != null) { |
|||
Collection<String> keys = Arrays.asList( |
|||
ApiUsageRecordKey.TRANSPORT_MSG_COUNT.getApiCountKey(), |
|||
ApiUsageRecordKey.JS_EXEC_COUNT.getApiCountKey(), |
|||
ApiUsageRecordKey.EMAIL_EXEC_COUNT.getApiCountKey(), |
|||
ApiUsageRecordKey.SMS_EXEC_COUNT.getApiCountKey(), |
|||
ApiUsageRecordKey.CREATED_ALARMS_COUNT.getApiCountKey()); |
|||
try { |
|||
List<TsKvEntry> entries = tsService.findLatest(tenantId, apiUsageState.getId(), keys).get(); |
|||
usageInfo.setTransportMessages(getLongValueFromTsEntries(entries, ApiUsageRecordKey.TRANSPORT_MSG_COUNT.getApiCountKey())); |
|||
usageInfo.setJsExecutions(getLongValueFromTsEntries(entries, ApiUsageRecordKey.JS_EXEC_COUNT.getApiCountKey())); |
|||
usageInfo.setEmails(getLongValueFromTsEntries(entries, ApiUsageRecordKey.EMAIL_EXEC_COUNT.getApiCountKey())); |
|||
usageInfo.setSms(getLongValueFromTsEntries(entries, ApiUsageRecordKey.SMS_EXEC_COUNT.getApiCountKey())); |
|||
usageInfo.setAlarms(getLongValueFromTsEntries(entries, ApiUsageRecordKey.CREATED_ALARMS_COUNT.getApiCountKey())); |
|||
} catch (ExecutionException | InterruptedException e) { |
|||
throw new RuntimeException("Failed to fetch api usage values from timeseries!"); |
|||
} |
|||
} |
|||
return usageInfo; |
|||
} |
|||
|
|||
private long getLongValueFromTsEntries(List<TsKvEntry> entries, String key) { |
|||
Optional<TsKvEntry> entryOpt = entries.stream().filter(e -> e.getKey().equals(key)).findFirst(); |
|||
return entryOpt.map(entry -> entry.getLongValue().orElse(0L)).orElse(0L); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue