|
|
@ -24,12 +24,14 @@ import com.google.common.util.concurrent.ListenableFuture; |
|
|
import com.google.common.util.concurrent.ListeningExecutorService; |
|
|
import com.google.common.util.concurrent.ListeningExecutorService; |
|
|
import com.google.common.util.concurrent.MoreExecutors; |
|
|
import com.google.common.util.concurrent.MoreExecutors; |
|
|
import lombok.Getter; |
|
|
import lombok.Getter; |
|
|
|
|
|
import lombok.RequiredArgsConstructor; |
|
|
import lombok.Setter; |
|
|
import lombok.Setter; |
|
|
import lombok.extern.slf4j.Slf4j; |
|
|
import lombok.extern.slf4j.Slf4j; |
|
|
import org.apache.commons.lang3.tuple.Pair; |
|
|
import org.apache.commons.lang3.tuple.Pair; |
|
|
import org.springframework.beans.factory.annotation.Autowired; |
|
|
import org.springframework.beans.factory.annotation.Autowired; |
|
|
import org.springframework.beans.factory.annotation.Value; |
|
|
import org.springframework.beans.factory.annotation.Value; |
|
|
import org.springframework.context.annotation.Lazy; |
|
|
import org.springframework.context.annotation.Lazy; |
|
|
|
|
|
import org.springframework.scheduling.annotation.Scheduled; |
|
|
import org.springframework.stereotype.Service; |
|
|
import org.springframework.stereotype.Service; |
|
|
import org.thingsboard.common.util.JacksonUtil; |
|
|
import org.thingsboard.common.util.JacksonUtil; |
|
|
import org.thingsboard.common.util.ThingsBoardExecutors; |
|
|
import org.thingsboard.common.util.ThingsBoardExecutors; |
|
|
@ -113,6 +115,7 @@ import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; |
|
|
@Service |
|
|
@Service |
|
|
@TbCoreComponent |
|
|
@TbCoreComponent |
|
|
@Slf4j |
|
|
@Slf4j |
|
|
|
|
|
@RequiredArgsConstructor |
|
|
public class DefaultDeviceStateService extends AbstractPartitionBasedService<DeviceId> implements DeviceStateService { |
|
|
public class DefaultDeviceStateService extends AbstractPartitionBasedService<DeviceId> implements DeviceStateService { |
|
|
|
|
|
|
|
|
public static final String ACTIVITY_STATE = "active"; |
|
|
public static final String ACTIVITY_STATE = "active"; |
|
|
@ -147,13 +150,11 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev |
|
|
new EntityKey(EntityKeyType.ENTITY_FIELD, "label"), |
|
|
new EntityKey(EntityKeyType.ENTITY_FIELD, "label"), |
|
|
new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime")); |
|
|
new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime")); |
|
|
|
|
|
|
|
|
private final TenantService tenantService; |
|
|
|
|
|
private final DeviceService deviceService; |
|
|
private final DeviceService deviceService; |
|
|
private final AttributesService attributesService; |
|
|
private final AttributesService attributesService; |
|
|
private final TimeseriesService tsService; |
|
|
private final TimeseriesService tsService; |
|
|
private final TbClusterService clusterService; |
|
|
private final TbClusterService clusterService; |
|
|
private final PartitionService partitionService; |
|
|
private final PartitionService partitionService; |
|
|
private final TbServiceInfoProvider serviceInfoProvider; |
|
|
|
|
|
private final EntityQueryRepository entityQueryRepository; |
|
|
private final EntityQueryRepository entityQueryRepository; |
|
|
private final DbTypeInfoComponent dbTypeInfoComponent; |
|
|
private final DbTypeInfoComponent dbTypeInfoComponent; |
|
|
private final TbApiUsageReportClient apiUsageReportClient; |
|
|
private final TbApiUsageReportClient apiUsageReportClient; |
|
|
@ -174,6 +175,10 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev |
|
|
@Getter |
|
|
@Getter |
|
|
private int defaultStateCheckIntervalInSec; |
|
|
private int defaultStateCheckIntervalInSec; |
|
|
|
|
|
|
|
|
|
|
|
@Value("${usage.stats.devices.report_interval:60}") |
|
|
|
|
|
@Getter |
|
|
|
|
|
private int defaultActivityStatsIntervalInSec; |
|
|
|
|
|
|
|
|
@Value("${state.persistToTelemetry:false}") |
|
|
@Value("${state.persistToTelemetry:false}") |
|
|
@Getter |
|
|
@Getter |
|
|
@Setter |
|
|
@Setter |
|
|
@ -187,31 +192,13 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev |
|
|
|
|
|
|
|
|
final ConcurrentMap<DeviceId, DeviceStateData> deviceStates = new ConcurrentHashMap<>(); |
|
|
final ConcurrentMap<DeviceId, DeviceStateData> deviceStates = new ConcurrentHashMap<>(); |
|
|
|
|
|
|
|
|
public DefaultDeviceStateService(TenantService tenantService, DeviceService deviceService, |
|
|
|
|
|
AttributesService attributesService, TimeseriesService tsService, |
|
|
|
|
|
TbClusterService clusterService, PartitionService partitionService, |
|
|
|
|
|
TbServiceInfoProvider serviceInfoProvider, |
|
|
|
|
|
EntityQueryRepository entityQueryRepository, |
|
|
|
|
|
DbTypeInfoComponent dbTypeInfoComponent, |
|
|
|
|
|
TbApiUsageReportClient apiUsageReportClient) { |
|
|
|
|
|
this.tenantService = tenantService; |
|
|
|
|
|
this.deviceService = deviceService; |
|
|
|
|
|
this.attributesService = attributesService; |
|
|
|
|
|
this.tsService = tsService; |
|
|
|
|
|
this.clusterService = clusterService; |
|
|
|
|
|
this.partitionService = partitionService; |
|
|
|
|
|
this.serviceInfoProvider = serviceInfoProvider; |
|
|
|
|
|
this.entityQueryRepository = entityQueryRepository; |
|
|
|
|
|
this.dbTypeInfoComponent = dbTypeInfoComponent; |
|
|
|
|
|
this.apiUsageReportClient = apiUsageReportClient; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@PostConstruct |
|
|
@PostConstruct |
|
|
public void init() { |
|
|
public void init() { |
|
|
super.init(); |
|
|
super.init(); |
|
|
deviceStateExecutor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool( |
|
|
deviceStateExecutor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool( |
|
|
Math.max(4, Runtime.getRuntime().availableProcessors()), "device-state")); |
|
|
Math.max(4, Runtime.getRuntime().availableProcessors()), "device-state")); |
|
|
scheduledExecutor.scheduleAtFixedRate(this::checkStates, new Random().nextInt(defaultStateCheckIntervalInSec), defaultStateCheckIntervalInSec, TimeUnit.SECONDS); |
|
|
scheduledExecutor.scheduleWithFixedDelay(this::checkStates, new Random().nextInt(defaultStateCheckIntervalInSec), defaultStateCheckIntervalInSec, TimeUnit.SECONDS); |
|
|
|
|
|
scheduledExecutor.scheduleWithFixedDelay(this::reportActivityStats, defaultActivityStatsIntervalInSec, defaultActivityStatsIntervalInSec, TimeUnit.SECONDS); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@PreDestroy |
|
|
@PreDestroy |
|
|
@ -439,7 +426,6 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev |
|
|
void checkStates() { |
|
|
void checkStates() { |
|
|
try { |
|
|
try { |
|
|
final long ts = System.currentTimeMillis(); |
|
|
final long ts = System.currentTimeMillis(); |
|
|
Map<TenantId, Pair<AtomicInteger, AtomicInteger>> devicesActivity = new HashMap<>(); |
|
|
|
|
|
partitionedEntities.forEach((tpi, deviceIds) -> { |
|
|
partitionedEntities.forEach((tpi, deviceIds) -> { |
|
|
log.debug("Calculating state updates. tpi {} for {} devices", tpi.getFullTopicName(), deviceIds.size()); |
|
|
log.debug("Calculating state updates. tpi {} for {} devices", tpi.getFullTopicName(), deviceIds.size()); |
|
|
Set<DeviceId> idsFromRemovedTenant = new HashSet<>(); |
|
|
Set<DeviceId> idsFromRemovedTenant = new HashSet<>(); |
|
|
@ -456,22 +442,32 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev |
|
|
} catch (Exception e) { |
|
|
} catch (Exception e) { |
|
|
if (e instanceof TenantNotFoundException) { |
|
|
if (e instanceof TenantNotFoundException) { |
|
|
idsFromRemovedTenant.add(deviceId); |
|
|
idsFromRemovedTenant.add(deviceId); |
|
|
continue; |
|
|
|
|
|
} else { |
|
|
} else { |
|
|
log.warn("[{}] Failed to update inactivity state [{}]", deviceId, e.getMessage()); |
|
|
log.warn("[{}] Failed to update inactivity state [{}]", deviceId, e.getMessage()); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
Pair<AtomicInteger, AtomicInteger> tenantDevicesActivity = devicesActivity.computeIfAbsent(stateData.getTenantId(), |
|
|
|
|
|
tenantId -> Pair.of(new AtomicInteger(), new AtomicInteger())); |
|
|
|
|
|
if (stateData.getState().isActive()) { |
|
|
|
|
|
tenantDevicesActivity.getLeft().incrementAndGet(); |
|
|
|
|
|
} else { |
|
|
|
|
|
tenantDevicesActivity.getRight().incrementAndGet(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
deviceIds.removeAll(idsFromRemovedTenant); |
|
|
deviceIds.removeAll(idsFromRemovedTenant); |
|
|
}); |
|
|
}); |
|
|
devicesActivity.forEach((tenantId, tenantDevicesActivity) -> { |
|
|
} catch (Throwable t) { |
|
|
|
|
|
log.warn("Failed to check devices states", t); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void reportActivityStats() { |
|
|
|
|
|
try{ |
|
|
|
|
|
Map<TenantId, Pair<AtomicInteger, AtomicInteger>> stats = new HashMap<>(); |
|
|
|
|
|
for (DeviceStateData stateData : deviceStates.values()) { |
|
|
|
|
|
Pair<AtomicInteger, AtomicInteger> tenantDevicesActivity = stats.computeIfAbsent(stateData.getTenantId(), |
|
|
|
|
|
tenantId -> Pair.of(new AtomicInteger(), new AtomicInteger())); |
|
|
|
|
|
if (stateData.getState().isActive()) { |
|
|
|
|
|
tenantDevicesActivity.getLeft().incrementAndGet(); |
|
|
|
|
|
} else { |
|
|
|
|
|
tenantDevicesActivity.getRight().incrementAndGet(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
stats.forEach((tenantId, tenantDevicesActivity) -> { |
|
|
int active = tenantDevicesActivity.getLeft().get(); |
|
|
int active = tenantDevicesActivity.getLeft().get(); |
|
|
int inactive = tenantDevicesActivity.getRight().get(); |
|
|
int inactive = tenantDevicesActivity.getRight().get(); |
|
|
apiUsageReportClient.report(tenantId, null, ApiUsageRecordKey.ACTIVE_DEVICES, active); |
|
|
apiUsageReportClient.report(tenantId, null, ApiUsageRecordKey.ACTIVE_DEVICES, active); |
|
|
@ -481,7 +477,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev |
|
|
} |
|
|
} |
|
|
}); |
|
|
}); |
|
|
} catch (Throwable t) { |
|
|
} catch (Throwable t) { |
|
|
log.warn("Failed to check devices states", t); |
|
|
log.warn("Failed to report activity states", t); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|