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