@ -19,17 +19,16 @@ import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures ;
import com.google.common.util.concurrent.Futures ;
import com.google.common.util.concurrent.ListenableFuture ;
import com.google.common.util.concurrent.ListenableFuture ;
import com.google.common.util.concurrent.MoreExecutors ;
import com.google.common.util.concurrent.MoreExecutors ;
import lombok.Getter ;
import lombok.extern.slf4j.Slf4j ;
import lombok.extern.slf4j.Slf4j ;
import org.checkerframework.checker.nullness.qual.Nullable ;
import org.checkerframework.checker.nullness.qual.Nullable ;
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.context.event.EventListener ;
import org.springframework.scheduling.annotation.Scheduled ;
import org.springframework.stereotype.Service ;
import org.springframework.stereotype.Service ;
import org.thingsboard.common.util.ThingsBoardThreadFactory ;
import org.thingsboard.common.util.ThingsBoardThreadFactory ;
import org.thingsboard.server.common.data.EntityView ;
import org.thingsboard.server.common.data.id.CustomerId ;
import org.thingsboard.server.common.data.id.CustomerId ;
import org.thingsboard.server.common.data.id.EntityViewId ;
import org.thingsboard.server.common.data.id.TenantId ;
import org.thingsboard.server.common.data.id.TenantId ;
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery ;
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery ;
import org.thingsboard.server.common.data.kv.ReadTsKvQuery ;
import org.thingsboard.server.common.data.kv.ReadTsKvQuery ;
@ -40,19 +39,12 @@ import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.common.data.query.EntityKey ;
import org.thingsboard.server.common.data.query.EntityKey ;
import org.thingsboard.server.common.data.query.EntityKeyType ;
import org.thingsboard.server.common.data.query.EntityKeyType ;
import org.thingsboard.server.common.data.query.TsValue ;
import org.thingsboard.server.common.data.query.TsValue ;
import org.thingsboard.server.common.msg.queue.ServiceType ;
import org.thingsboard.server.common.msg.queue.TbCallback ;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo ;
import org.thingsboard.server.dao.entity.EntityService ;
import org.thingsboard.server.dao.entity.EntityService ;
import org.thingsboard.server.dao.entityview.EntityViewService ;
import org.thingsboard.server.dao.entityview.EntityViewService ;
import org.thingsboard.server.dao.timeseries.TimeseriesService ;
import org.thingsboard.server.dao.timeseries.TimeseriesService ;
import org.thingsboard.server.queue.discovery.ClusterTopologyChangeEvent ;
import org.thingsboard.server.queue.discovery.PartitionChangeEvent ;
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.util.TbCoreComponent ;
import org.thingsboard.server.queue.util.TbCoreComponent ;
import org.thingsboard.server.service.queue.TbClusterService ;
import org.thingsboard.server.service.executors.DbCallbackExecutorService ;
import org.thingsboard.server.service.security.permission.Operation ;
import org.thingsboard.server.service.telemetry.DefaultTelemetryWebSocketService ;
import org.thingsboard.server.service.telemetry.DefaultTelemetryWebSocketService ;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketService ;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketService ;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef ;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef ;
@ -60,15 +52,18 @@ import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUnsubscribeCmd ;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUnsubscribeCmd ;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate ;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate ;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityHistoryCmd ;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityHistoryCmd ;
import org.thingsboard.server.service.telemetry.cmd.v2.GetTsCmd ;
import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd ;
import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd ;
import org.thingsboard.server.service.telemetry.cmd.v2.TimeSeriesCmd ;
import org.thingsboard.server.service.telemetry.cmd.v2.TimeSeriesCmd ;
import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode ;
import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode ;
import org.thingsboard.server.service.telemetry.sub.SubscriptionUpdate ;
import javax.annotation.PostConstruct ;
import javax.annotation.PostConstruct ;
import javax.annotation.PreDestroy ;
import javax.annotation.PreDestroy ;
import java.util.ArrayList ;
import java.util.ArrayList ;
import java.util.Arrays ;
import java.util.Collection ;
import java.util.Collection ;
import java.util.Collections ;
import java.util.Comparator ;
import java.util.HashMap ;
import java.util.HashMap ;
import java.util.LinkedHashMap ;
import java.util.LinkedHashMap ;
import java.util.LinkedHashSet ;
import java.util.LinkedHashSet ;
@ -79,6 +74,12 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException ;
import java.util.concurrent.ExecutionException ;
import java.util.concurrent.ExecutorService ;
import java.util.concurrent.ExecutorService ;
import java.util.concurrent.Executors ;
import java.util.concurrent.Executors ;
import java.util.concurrent.ScheduledExecutorService ;
import java.util.concurrent.ScheduledFuture ;
import java.util.concurrent.ThreadFactory ;
import java.util.concurrent.TimeUnit ;
import java.util.concurrent.atomic.AtomicInteger ;
import java.util.concurrent.atomic.AtomicLong ;
import java.util.stream.Collectors ;
import java.util.stream.Collectors ;
@Slf4j
@Slf4j
@ -87,28 +88,14 @@ import java.util.stream.Collectors;
public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubscriptionService {
public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubscriptionService {
private static final int DEFAULT_LIMIT = 100 ;
private static final int DEFAULT_LIMIT = 100 ;
private final Set < TopicPartitionInfo > currentPartitions = ConcurrentHashMap . newKeySet ( ) ;
private final Map < String , Map < Integer , TbEntityDataSubCtx > > subscriptionsBySessionId = new ConcurrentHashMap < > ( ) ;
private final Map < String , Map < Integer , TbEntityDataSubCtx > > subscriptionsBySessionId = new ConcurrentHashMap < > ( ) ;
@Autowired
@Autowired
private TelemetryWebSocketService wsService ;
private TelemetryWebSocketService wsService ;
@Autowired
private EntityViewService entityViewService ;
@Autowired
@Autowired
private EntityService entityService ;
private EntityService entityService ;
@Autowired
private PartitionService partitionService ;
@Autowired
private TbClusterService clusterService ;
@Autowired
@Lazy
private SubscriptionManagerService subscriptionManagerService ;
@Autowired
@Autowired
@Lazy
@Lazy
private TbLocalSubscriptionService localSubscriptionService ;
private TbLocalSubscriptionService localSubscriptionService ;
@ -119,18 +106,38 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
@Autowired
@Autowired
private TbServiceInfoProvider serviceInfoProvider ;
private TbServiceInfoProvider serviceInfoProvider ;
@Autowired
@Getter
private DbCallbackExecutorService dbCallbackExecutor ;
private ScheduledExecutorService scheduler ;
@Value ( "${database.ts.type}" )
@Value ( "${database.ts.type}" )
private String databaseTsType ;
private String databaseTsType ;
@Value ( "${server.ws.dynamic_page_link_refresh_interval:6}" )
private long dynamicPageLinkRefreshInterval ;
@Value ( "${server.ws.dynamic_page_link_refresh_pool_size:1}" )
private int dynamicPageLinkRefreshPoolSize ;
private ExecutorService wsCallBackExecutor ;
private ExecutorService wsCallBackExecutor ;
private boolean tsInSqlDB ;
private boolean tsInSqlDB ;
private String serviceId ;
private String serviceId ;
private AtomicInteger regularQueryInvocationCnt = new AtomicInteger ( ) ;
private AtomicInteger dynamicQueryInvocationCnt = new AtomicInteger ( ) ;
private AtomicLong regularQueryTimeSpent = new AtomicLong ( ) ;
private AtomicLong dynamicQueryTimeSpent = new AtomicLong ( ) ;
@PostConstruct
@PostConstruct
public void initExecutor ( ) {
public void initExecutor ( ) {
serviceId = serviceInfoProvider . getServiceId ( ) ;
serviceId = serviceInfoProvider . getServiceId ( ) ;
wsCallBackExecutor = Executors . newSingleThreadExecutor ( ThingsBoardThreadFactory . forName ( "ws-entity-sub-callback" ) ) ;
wsCallBackExecutor = Executors . newSingleThreadExecutor ( ThingsBoardThreadFactory . forName ( "ws-entity-sub-callback" ) ) ;
tsInSqlDB = databaseTsType . equalsIgnoreCase ( "sql" ) | | databaseTsType . equalsIgnoreCase ( "timescale" ) ;
tsInSqlDB = databaseTsType . equalsIgnoreCase ( "sql" ) | | databaseTsType . equalsIgnoreCase ( "timescale" ) ;
ThreadFactory tbThreadFactory = ThingsBoardThreadFactory . forName ( "ws-entity-sub-scheduler" ) ;
if ( dynamicPageLinkRefreshPoolSize = = 1 ) {
scheduler = Executors . newSingleThreadScheduledExecutor ( tbThreadFactory ) ;
} else {
scheduler = Executors . newScheduledThreadPool ( dynamicPageLinkRefreshPoolSize , tbThreadFactory ) ;
}
}
}
@PreDestroy
@PreDestroy
@ -140,45 +147,19 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
}
}
}
}
@Override
@EventListener ( PartitionChangeEvent . class )
public void onApplicationEvent ( PartitionChangeEvent partitionChangeEvent ) {
if ( ServiceType . TB_CORE . equals ( partitionChangeEvent . getServiceType ( ) ) ) {
currentPartitions . clear ( ) ;
currentPartitions . addAll ( partitionChangeEvent . getPartitions ( ) ) ;
}
}
@Override
@EventListener ( ClusterTopologyChangeEvent . class )
public void onApplicationEvent ( ClusterTopologyChangeEvent event ) {
if ( event . getServiceQueueKeys ( ) . stream ( ) . anyMatch ( key - > ServiceType . TB_CORE . equals ( key . getServiceType ( ) ) ) ) {
/ *
* If the cluster topology has changed , we need to push all current subscriptions to SubscriptionManagerService again .
* Otherwise , the SubscriptionManagerService may "forget" those subscriptions in case of restart .
* Although this is resource consuming operation , it is cheaper than sending ping / pong commands periodically
* It is also cheaper then caching the subscriptions by entity id and then lookup of those caches every time we have new telemetry in SubscriptionManagerService .
* Even if we cache locally the list of active subscriptions by entity id , it is still time consuming operation to get them from cache
* Since number of subscriptions is usually much less then number of devices that are pushing data .
// */
// subscriptionsBySessionId.values().forEach(map -> map.values()
// .forEach(sub -> pushSubscriptionToManagerService(sub, false)));
}
}
@Override
@Override
public void handleCmd ( TelemetryWebSocketSessionRef session , EntityDataCmd cmd ) {
public void handleCmd ( TelemetryWebSocketSessionRef session , EntityDataCmd cmd ) {
TbEntityDataSubCtx ctx = getSubCtx ( session . getSessionId ( ) , cmd . getCmdId ( ) ) ;
TbEntityDataSubCtx ctx = getSubCtx ( session . getSessionId ( ) , cmd . getCmdId ( ) ) ;
if ( ctx ! = null ) {
if ( ctx ! = null ) {
log . debug ( "[{}][{}] Updating existing subscriptions using: {}" , session . getSessionId ( ) , cmd . getCmdId ( ) , cmd ) ;
log . debug ( "[{}][{}] Updating existing subscriptions using: {}" , session . getSessionId ( ) , cmd . getCmdId ( ) , cmd ) ;
if ( cmd . getLatestCmd ( ) ! = null | | cmd . getTsCmd ( ) ! = null | | cmd . getHistoryCmd ( ) ! = null ) {
if ( cmd . getLatestCmd ( ) ! = null | | cmd . getTsCmd ( ) ! = null | | cmd . getHistoryCmd ( ) ! = null ) {
Collection < Integer > oldSubIds = ctx . clearSubscriptions ( ) ;
clearSubs ( ctx ) ;
oldSubIds . forEach ( subId - > localSubscriptionService . cancelSubscription ( serviceId , subId ) ) ;
}
}
} else {
} else {
log . debug ( "[{}][{}] Creating new subscription using: {}" , session . getSessionId ( ) , cmd . getCmdId ( ) , cmd ) ;
log . debug ( "[{}][{}] Creating new subscription using: {}" , session . getSessionId ( ) , cmd . getCmdId ( ) , cmd ) ;
ctx = createSubCtx ( session , cmd ) ;
ctx = createSubCtx ( session , cmd ) ;
}
}
ctx . setCurrentCmd ( cmd ) ;
if ( cmd . getQuery ( ) ! = null ) {
if ( cmd . getQuery ( ) ! = null ) {
if ( ctx . getQuery ( ) = = null ) {
if ( ctx . getQuery ( ) = = null ) {
log . debug ( "[{}][{}] Initializing data using query: {}" , session . getSessionId ( ) , cmd . getCmdId ( ) , cmd . getQuery ( ) ) ;
log . debug ( "[{}][{}] Initializing data using query: {}" , session . getSessionId ( ) , cmd . getCmdId ( ) , cmd . getQuery ( ) ) ;
@ -197,13 +178,26 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
}
}
} ) ;
} ) ;
}
}
long start = System . currentTimeMillis ( ) ;
PageData < EntityData > data = entityService . findEntityDataByQuery ( tenantId , customerId , ctx . getQuery ( ) ) ;
PageData < EntityData > data = entityService . findEntityDataByQuery ( tenantId , customerId , ctx . getQuery ( ) ) ;
long end = System . currentTimeMillis ( ) ;
regularQueryInvocationCnt . incrementAndGet ( ) ;
regularQueryTimeSpent . addAndGet ( end - start ) ;
if ( log . isTraceEnabled ( ) ) {
if ( log . isTraceEnabled ( ) ) {
data . getData ( ) . forEach ( ed - > {
data . getData ( ) . forEach ( ed - > {
log . trace ( "[{}][{}] EntityData: {}" , session . getSessionId ( ) , cmd . getCmdId ( ) , ed ) ;
log . trace ( "[{}][{}] EntityData: {}" , session . getSessionId ( ) , cmd . getCmdId ( ) , ed ) ;
} ) ;
} ) ;
}
}
ctx . setData ( data ) ;
ctx . setData ( data ) ;
ctx . cancelRefreshTask ( ) ;
if ( ctx . getQuery ( ) . getPageLink ( ) . isDynamic ( ) ) {
TbEntityDataSubCtx finalCtx = ctx ;
ScheduledFuture < ? > task = scheduler . scheduleWithFixedDelay (
( ) - > refreshDynamicQuery ( tenantId , customerId , finalCtx ) ,
dynamicPageLinkRefreshInterval , dynamicPageLinkRefreshInterval , TimeUnit . SECONDS ) ;
finalCtx . setRefreshTask ( task ) ;
}
}
}
ListenableFuture < TbEntityDataSubCtx > historyFuture ;
ListenableFuture < TbEntityDataSubCtx > historyFuture ;
if ( cmd . getHistoryCmd ( ) ! = null ) {
if ( cmd . getHistoryCmd ( ) ! = null ) {
@ -233,6 +227,35 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
} , wsCallBackExecutor ) ;
} , wsCallBackExecutor ) ;
}
}
private void refreshDynamicQuery ( TenantId tenantId , CustomerId customerId , TbEntityDataSubCtx finalCtx ) {
try {
long start = System . currentTimeMillis ( ) ;
Collection < Integer > oldSubIds = finalCtx . update ( entityService . findEntityDataByQuery ( tenantId , customerId , finalCtx . getQuery ( ) ) ) ;
long end = System . currentTimeMillis ( ) ;
dynamicQueryInvocationCnt . incrementAndGet ( ) ;
dynamicQueryTimeSpent . addAndGet ( end - start ) ;
oldSubIds . forEach ( subId - > localSubscriptionService . cancelSubscription ( serviceId , subId ) ) ;
} catch ( Exception e ) {
log . warn ( "[{}][{}] Failed to refresh query" , finalCtx . getSessionId ( ) , finalCtx . getCmdId ( ) , e ) ;
}
}
@Scheduled ( fixedDelayString = "${server.ws.dynamic_page_link_stats:10000}" )
public void printStats ( ) {
int regularQueryInvocationCntValue = regularQueryInvocationCnt . getAndSet ( 0 ) ;
long regularQueryInvocationTimeValue = regularQueryTimeSpent . getAndSet ( 0 ) ;
int dynamicQueryInvocationCntValue = dynamicQueryInvocationCnt . getAndSet ( 0 ) ;
long dynamicQueryInvocationTimeValue = dynamicQueryTimeSpent . getAndSet ( 0 ) ;
long dynamicQueryCnt = subscriptionsBySessionId . values ( ) . stream ( ) . map ( Map : : values ) . count ( ) ;
log . info ( "Stats: regularQueryInvocationCnt = [{}], regularQueryInvocationTime = [{}], dynamicQueryCnt = [{}] dynamicQueryInvocationCnt = [{}], dynamicQueryInvocationTime = [{}]" ,
regularQueryInvocationCntValue , regularQueryInvocationTimeValue , dynamicQueryCnt , dynamicQueryInvocationCntValue , dynamicQueryInvocationTimeValue ) ;
}
private void clearSubs ( TbEntityDataSubCtx ctx ) {
Collection < Integer > oldSubIds = ctx . clearSubscriptions ( ) ;
oldSubIds . forEach ( subId - > localSubscriptionService . cancelSubscription ( serviceId , subId ) ) ;
}
private TbEntityDataSubCtx createSubCtx ( TelemetryWebSocketSessionRef sessionRef , EntityDataCmd cmd ) {
private TbEntityDataSubCtx createSubCtx ( TelemetryWebSocketSessionRef sessionRef , EntityDataCmd cmd ) {
Map < Integer , TbEntityDataSubCtx > sessionSubs = subscriptionsBySessionId . computeIfAbsent ( sessionRef . getSessionId ( ) , k - > new HashMap < > ( ) ) ;
Map < Integer , TbEntityDataSubCtx > sessionSubs = subscriptionsBySessionId . computeIfAbsent ( sessionRef . getSessionId ( ) , k - > new HashMap < > ( ) ) ;
TbEntityDataSubCtx ctx = new TbEntityDataSubCtx ( serviceId , wsService , sessionRef , cmd . getCmdId ( ) ) ;
TbEntityDataSubCtx ctx = new TbEntityDataSubCtx ( serviceId , wsService , sessionRef , cmd . getCmdId ( ) ) ;
@ -250,49 +273,86 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
}
}
}
}
private void handleTimeSeriesCmd ( TbEntityDataSubCtx ctx , TimeSeriesCmd cmd ) {
private ListenableFuture < TbEntityDataSubCtx > handleTimeSeriesCmd ( TbEntityDataSubCtx ctx , TimeSeriesCmd cmd ) {
List < String > keys = cmd . getKeys ( ) ;
log . debug ( "[{}][{}] Fetching time-series data for last {} ms for keys: ({})" , ctx . getSessionId ( ) , ctx . getCmdId ( ) , cmd . getTimeWindow ( ) , cmd . getKeys ( ) ) ;
log . debug ( "[{}][{}] Fetching time-series data for last {} ms for keys: ({})" , ctx . getSessionId ( ) , ctx . getCmdId ( ) , cmd . getTimeWindow ( ) , cmd . getKeys ( ) ) ;
long startTs = cmd . getStartTs ( ) ;
return handleGetTsCmd ( ctx , cmd , true ) ;
long endTs = cmd . getStartTs ( ) + cmd . getTimeWindow ( ) ;
}
Map < EntityData , ListenableFuture < Map < String , List < TsValue > > > > tsFutures = new HashMap < > ( ) ;
for ( EntityData entityData : ctx . getData ( ) . getData ( ) ) {
private ListenableFuture < TbEntityDataSubCtx > handleHistoryCmd ( TbEntityDataSubCtx ctx , EntityHistoryCmd cmd ) {
List < ReadTsKvQuery > queries = keys . stream ( ) . map ( key - > new BaseReadTsKvQuery ( key , startTs , endTs , cmd . getInterval ( ) ,
log . debug ( "[{}][{}] Fetching history data for start {} and end {} ms for keys: ({})" , ctx . getSessionId ( ) , ctx . getCmdId ( ) , cmd . getStartTs ( ) , cmd . getEndTs ( ) , cmd . getKeys ( ) ) ;
getLimit ( cmd . getLimit ( ) ) , DefaultTelemetryWebSocketService . getAggregation ( cmd . getAgg ( ) ) ) ) . collect ( Collectors . toList ( ) ) ;
return handleGetTsCmd ( ctx , cmd , false ) ;
ListenableFuture < List < TsKvEntry > > tsDataFutures = tsService . findAll ( ctx . getTenantId ( ) , entityData . getEntityId ( ) , queries ) ;
}
tsFutures . put ( entityData , Futures . transform ( tsDataFutures , this : : toTsValues , MoreExecutors . directExecutor ( ) ) ) ;
private ListenableFuture < TbEntityDataSubCtx > handleGetTsCmd ( TbEntityDataSubCtx ctx , GetTsCmd cmd , boolean subscribe ) {
List < String > keys = cmd . getKeys ( ) ;
List < ReadTsKvQuery > finalTsKvQueryList ;
List < ReadTsKvQuery > tsKvQueryList = cmd . getKeys ( ) . stream ( ) . map ( key - > new BaseReadTsKvQuery (
key , cmd . getStartTs ( ) , cmd . getEndTs ( ) , cmd . getInterval ( ) , getLimit ( cmd . getLimit ( ) ) , cmd . getAgg ( )
) ) . collect ( Collectors . toList ( ) ) ;
if ( cmd . isFetchLatestPreviousPoint ( ) ) {
finalTsKvQueryList = new ArrayList < > ( tsKvQueryList ) ;
tsKvQueryList . addAll ( cmd . getKeys ( ) . stream ( ) . map ( key - > new BaseReadTsKvQuery (
key , cmd . getStartTs ( ) - TimeUnit . DAYS . toMillis ( 365 ) , cmd . getStartTs ( ) , cmd . getInterval ( ) , 1 , cmd . getAgg ( )
) ) . collect ( Collectors . toList ( ) ) ) ;
} else {
finalTsKvQueryList = tsKvQueryList ;
}
}
Futures . addCallback ( Futures . allAsList ( tsFutures . values ( ) ) , new FutureCallback < List < Map < String , List < TsValue > > > > ( ) {
Map < EntityData , ListenableFuture < List < TsKvEntry > > > fetchResultMap = new HashMap < > ( ) ;
@Override
ctx . getData ( ) . getData ( ) . forEach ( entityData - > fetchResultMap . put ( entityData ,
public void onSuccess ( @Nullable List < Map < String , List < TsValue > > > result ) {
tsService . findAll ( ctx . getTenantId ( ) , entityData . getEntityId ( ) , finalTsKvQueryList ) ) ) ;
tsFutures . forEach ( ( key , value ) - > {
return Futures . transform ( Futures . allAsList ( fetchResultMap . values ( ) ) , f - > {
try {
fetchResultMap . forEach ( ( entityData , future ) - > {
value . get ( ) . forEach ( ( k , v ) - > key . getTimeseries ( ) . put ( k , v . toArray ( new TsValue [ v . size ( ) ] ) ) ) ;
Map < String , List < TsValue > > keyData = new LinkedHashMap < > ( ) ;
} catch ( InterruptedException | ExecutionException e ) {
cmd . getKeys ( ) . forEach ( key - > keyData . put ( key , new ArrayList < > ( ) ) ) ;
log . warn ( "[{}][{}] Failed to lookup time-series data: {}:{}" , ctx . getSessionId ( ) , ctx . getCmdId ( ) , key . getEntityId ( ) , keys , e ) ;
try {
List < TsKvEntry > entityTsData = future . get ( ) ;
if ( entityTsData ! = null ) {
entityTsData . forEach ( entry - > keyData . get ( entry . getKey ( ) ) . add ( new TsValue ( entry . getTs ( ) , entry . getValueAsString ( ) ) ) ) ;
}
}
} ) ;
keyData . forEach ( ( k , v ) - > entityData . getTimeseries ( ) . put ( k , v . toArray ( new TsValue [ v . size ( ) ] ) ) ) ;
EntityDataUpdate update ;
if ( cmd . isFetchLatestPreviousPoint ( ) ) {
if ( ! ctx . isInitialDataSent ( ) ) {
entityData . getTimeseries ( ) . values ( ) . forEach ( dataArray - > {
update = new EntityDataUpdate ( ctx . getCmdId ( ) , ctx . getData ( ) , null ) ;
Arrays . sort ( dataArray , ( o1 , o2 ) - > Long . compare ( o2 . getTs ( ) , o1 . getTs ( ) ) ) ;
ctx . setInitialDataSent ( true ) ;
} ) ;
} else {
}
update = new EntityDataUpdate ( ctx . getCmdId ( ) , null , ctx . getData ( ) . getData ( ) ) ;
} catch ( InterruptedException | ExecutionException e ) {
log . warn ( "[{}][{}][{}] Failed to fetch historical data" , ctx . getSessionId ( ) , ctx . getCmdId ( ) , entityData . getEntityId ( ) , e ) ;
wsService . sendWsMsg ( ctx . getSessionId ( ) ,
new EntityDataUpdate ( ctx . getCmdId ( ) , SubscriptionErrorCode . INTERNAL_ERROR . getCode ( ) , "Failed to fetch historical data!" ) ) ;
}
}
wsService . sendWsMsg ( ctx . getSessionId ( ) , update ) ;
} ) ;
createSubscriptions ( ctx , keys . stream ( ) . map ( key - > new EntityKey ( EntityKeyType . TIME_SERIES , key ) ) . collect ( Collectors . toList ( ) ) , false ) ;
EntityDataUpdate update ;
if ( ! ctx . isInitialDataSent ( ) ) {
update = new EntityDataUpdate ( ctx . getCmdId ( ) , ctx . getData ( ) , null ) ;
ctx . setInitialDataSent ( true ) ;
} else {
update = new EntityDataUpdate ( ctx . getCmdId ( ) , null , ctx . getData ( ) . getData ( ) ) ;
}
}
wsService . sendWsMsg ( ctx . getSessionId ( ) , update ) ;
@Override
if ( subscribe ) {
public void onFailure ( Throwable t ) {
createSubscriptions ( ctx , keys . stream ( ) . map ( key - > new EntityKey ( EntityKeyType . TIME_SERIES , key ) ) . collect ( Collectors . toList ( ) ) , false ) ;
log . warn ( "[{}][{}] Failed to process websocket command: {}:{}" , ctx . getSessionId ( ) , ctx . getCmdId ( ) , ctx . getQuery ( ) , cmd , t ) ;
wsService . sendWsMsg ( ctx . getSessionId ( ) ,
new EntityDataUpdate ( ctx . getCmdId ( ) , SubscriptionErrorCode . INTERNAL_ERROR . getCode ( ) , "Failed to process websocket command!" ) ) ;
}
}
ctx . getData ( ) . getData ( ) . forEach ( ed - > ed . getTimeseries ( ) . clear ( ) ) ;
return ctx ;
} , wsCallBackExecutor ) ;
} , wsCallBackExecutor ) ;
}
}
private List < ReadTsKvQuery > getReadTsKvQueries ( GetTsCmd cmd ) {
List < ReadTsKvQuery > finalTsKvQueryList ;
List < ReadTsKvQuery > queries = cmd . getKeys ( ) . stream ( ) . map ( key - > new BaseReadTsKvQuery ( key , cmd . getStartTs ( ) , cmd . getEndTs ( ) , cmd . getInterval ( ) ,
getLimit ( cmd . getLimit ( ) ) , cmd . getAgg ( ) ) ) . collect ( Collectors . toList ( ) ) ;
if ( cmd . isFetchLatestPreviousPoint ( ) ) {
finalTsKvQueryList = new ArrayList < > ( queries ) ;
finalTsKvQueryList . addAll ( cmd . getKeys ( ) . stream ( ) . map ( key - > new BaseReadTsKvQuery (
key , cmd . getStartTs ( ) - TimeUnit . DAYS . toMillis ( 365 ) , cmd . getStartTs ( ) , cmd . getInterval ( ) , 1 , cmd . getAgg ( )
) ) . collect ( Collectors . toList ( ) ) ) ;
} else {
finalTsKvQueryList = queries ;
}
return finalTsKvQueryList ;
}
private void handleLatestCmd ( TbEntityDataSubCtx ctx , LatestValueCmd latestCmd ) {
private void handleLatestCmd ( TbEntityDataSubCtx ctx , LatestValueCmd latestCmd ) {
log . trace ( "[{}][{}] Going to process latest command: {}" , ctx . getSessionId ( ) , ctx . getCmdId ( ) , latestCmd ) ;
log . trace ( "[{}][{}] Going to process latest command: {}" , ctx . getSessionId ( ) , ctx . getCmdId ( ) , latestCmd ) ;
//Fetch the latest values for telemetry keys (in case they are not copied from NoSQL to SQL DB in hybrid mode.
//Fetch the latest values for telemetry keys (in case they are not copied from NoSQL to SQL DB in hybrid mode.
@ -377,150 +437,24 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
return results ;
return results ;
}
}
private ListenableFuture < TbEntityDataSubCtx > handleHistoryCmd ( TbEntityDataSubCtx ctx , EntityHistoryCmd historyCmd ) {
List < ReadTsKvQuery > tsKvQueryList = historyCmd . getKeys ( ) . stream ( ) . map ( key - > new BaseReadTsKvQuery (
key , historyCmd . getStartTs ( ) , historyCmd . getEndTs ( ) , historyCmd . getInterval ( ) , getLimit ( historyCmd . getLimit ( ) ) , historyCmd . getAgg ( )
) ) . collect ( Collectors . toList ( ) ) ;
Map < EntityData , ListenableFuture < List < TsKvEntry > > > fetchResultMap = new HashMap < > ( ) ;
ctx . getData ( ) . getData ( ) . forEach ( entityData - > fetchResultMap . put ( entityData ,
tsService . findAll ( ctx . getTenantId ( ) , entityData . getEntityId ( ) , tsKvQueryList ) ) ) ;
return Futures . transform ( Futures . allAsList ( fetchResultMap . values ( ) ) , f - > {
fetchResultMap . forEach ( ( entityData , future ) - > {
Map < String , List < TsValue > > keyData = new LinkedHashMap < > ( ) ;
historyCmd . getKeys ( ) . forEach ( key - > keyData . put ( key , new ArrayList < > ( ) ) ) ;
try {
List < TsKvEntry > entityTsData = future . get ( ) ;
if ( entityTsData ! = null ) {
entityTsData . forEach ( entry - > keyData . get ( entry . getKey ( ) ) . add ( new TsValue ( entry . getTs ( ) , entry . getValueAsString ( ) ) ) ) ;
}
keyData . forEach ( ( k , v ) - > entityData . getTimeseries ( ) . put ( k , v . toArray ( new TsValue [ v . size ( ) ] ) ) ) ;
} catch ( InterruptedException | ExecutionException e ) {
log . warn ( "[{}][{}][{}] Failed to fetch historical data" , ctx . getSessionId ( ) , ctx . getCmdId ( ) , entityData . getEntityId ( ) , e ) ;
wsService . sendWsMsg ( ctx . getSessionId ( ) ,
new EntityDataUpdate ( ctx . getCmdId ( ) , SubscriptionErrorCode . INTERNAL_ERROR . getCode ( ) , "Failed to fetch historical data!" ) ) ;
}
} ) ;
EntityDataUpdate update ;
if ( ! ctx . isInitialDataSent ( ) ) {
update = new EntityDataUpdate ( ctx . getCmdId ( ) , ctx . getData ( ) , null ) ;
ctx . setInitialDataSent ( true ) ;
} else {
update = new EntityDataUpdate ( ctx . getCmdId ( ) , null , ctx . getData ( ) . getData ( ) ) ;
}
wsService . sendWsMsg ( ctx . getSessionId ( ) , update ) ;
return ctx ;
} , wsCallBackExecutor ) ;
}
@Override
@Override
public void cancelSubscription ( String sessionId , EntityDataUnsubscribeCmd cmd ) {
public void cancelSubscription ( String sessionId , EntityDataUnsubscribeCmd cmd ) {
TbEntityDataSubCtx ctx = getSubCtx ( sessionId , cmd . getCmdId ( ) ) ;
cleanupAndCancel ( getSubCtx ( sessionId , cmd . getCmdId ( ) ) ) ;
}
}
// //TODO 3.1: replace null callbacks with callbacks from websocket service.
private void cleanupAndCancel ( TbEntityDataSubCtx ctx ) {
// @Override
if ( ctx ! = null ) {
// public void addSubscription(TbSubscription subscription) {
ctx . cancelRefreshTask ( ) ;
// EntityId entityId = subscription.getEntityId();
clearSubs ( ctx ) ;
// // Telemetry subscription on Entity Views are handled differently, because we need to allow only certain keys and time ranges;
}
// if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW) && TbSubscriptionType.TIMESERIES.equals(subscription.getType())) {
// subscription = resolveEntityViewSubscription((TbTimeseriesSubscription) subscription);
// }
// pushSubscriptionToManagerService(subscription, true);
// registerSubscription(subscription);
// }
// private void pushSubscriptionToManagerService(TbSubscription subscription, boolean pushToLocalService) {
// TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, subscription.getTenantId(), subscription.getEntityId());
// if (currentPartitions.contains(tpi)) {
// // Subscription is managed on the same server;
// if (pushToLocalService) {
// subscriptionManagerService.addSubscription(subscription, TbCallback.EMPTY);
// }
// } else {
// // Push to the queue;
// TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toNewSubscriptionProto(subscription);
// clusterService.pushMsgToCore(tpi, subscription.getEntityId().getId(), toCoreMsg, null);
// }
// }
@Override
public void onSubscriptionUpdate ( String sessionId , SubscriptionUpdate update , TbCallback callback ) {
// TbSubscription subscription = subscriptionsBySessionId
// .getOrDefault(sessionId, Collections.emptyMap()).get(update.getSubscriptionId());
// if (subscription != null) {
// switch (subscription.getType()) {
// case TIMESERIES:
// TbTimeseriesSubscription tsSub = (TbTimeseriesSubscription) subscription;
// update.getLatestValues().forEach((key, value) -> tsSub.getKeyStates().put(key, value));
// break;
// case ATTRIBUTES:
// TbAttributeSubscription attrSub = (TbAttributeSubscription) subscription;
// update.getLatestValues().forEach((key, value) -> attrSub.getKeyStates().put(key, value));
// break;
// }
// wsService.sendWsMsg(sessionId, update);
// }
// callback.onSuccess();
}
}
// @Override
// public void cancelSubscription(String sessionId, int subscriptionId) {
// log.debug("[{}][{}] Going to remove subscription.", sessionId, subscriptionId);
// Map<Integer, TbSubscription> sessionSubscriptions = subscriptionsBySessionId.get(sessionId);
// if (sessionSubscriptions != null) {
// TbSubscription subscription = sessionSubscriptions.remove(subscriptionId);
// if (subscription != null) {
// if (sessionSubscriptions.isEmpty()) {
// subscriptionsBySessionId.remove(sessionId);
// }
// TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, subscription.getTenantId(), subscription.getEntityId());
// if (currentPartitions.contains(tpi)) {
// // Subscription is managed on the same server;
// subscriptionManagerService.cancelSubscription(sessionId, subscriptionId, TbCallback.EMPTY);
// } else {
// // Push to the queue;
// TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toCloseSubscriptionProto(subscription);
// clusterService.pushMsgToCore(tpi, subscription.getEntityId().getId(), toCoreMsg, null);
// }
// } else {
// log.debug("[{}][{}] Subscription not found!", sessionId, subscriptionId);
// }
// } else {
// log.debug("[{}] No session subscriptions found!", sessionId);
// }
// }
@Override
@Override
public void cancelAllSessionSubscriptions ( String sessionId ) {
public void cancelAllSessionSubscriptions ( String sessionId ) {
// Map<Integer, TbSubscription> subscriptions = subscriptionsBySessionId.get(sessionId);
Map < Integer , TbEntityDataSubCtx > sessionSubs = subscriptionsBySessionId . remove ( sessionId ) ;
// if (subscriptions != null) {
if ( sessionSubs ! = null ) {
// Set<Integer> toRemove = new HashSet<>(subscriptions.keySet());
sessionSubs . values ( ) . forEach ( this : : cleanupAndCancel ) ;
// toRemove.forEach(id -> cancelSubscription(sessionId, id));
// }
}
private TbSubscription resolveEntityViewSubscription ( TbTimeseriesSubscription subscription ) {
EntityView entityView = entityViewService . findEntityViewById ( TenantId . SYS_TENANT_ID , new EntityViewId ( subscription . getEntityId ( ) . getId ( ) ) ) ;
Map < String , Long > keyStates ;
if ( subscription . isAllKeys ( ) ) {
keyStates = entityView . getKeys ( ) . getTimeseries ( ) . stream ( ) . collect ( Collectors . toMap ( k - > k , k - > 0L ) ) ;
} else {
keyStates = subscription . getKeyStates ( ) . entrySet ( )
. stream ( ) . filter ( entry - > entityView . getKeys ( ) . getTimeseries ( ) . contains ( entry . getKey ( ) ) )
. collect ( Collectors . toMap ( Map . Entry : : getKey , Map . Entry : : getValue ) ) ;
}
}
return TbTimeseriesSubscription . builder ( )
. serviceId ( subscription . getServiceId ( ) )
. sessionId ( subscription . getSessionId ( ) )
. subscriptionId ( subscription . getSubscriptionId ( ) )
. tenantId ( subscription . getTenantId ( ) )
. entityId ( entityView . getEntityId ( ) )
. startTime ( entityView . getStartTimeMs ( ) )
. endTime ( entityView . getEndTimeMs ( ) )
. allKeys ( false )
. keyStates ( keyStates ) . build ( ) ;
}
}
private int getLimit ( int limit ) {
private int getLimit ( int limit ) {