Commit 9328bbc0b7819341ec121e321c624fbd9ce51a64

Authored by Andrew Shvayka
1 parent 87d05f7c

Aggregation Implementation

... ... @@ -151,20 +151,9 @@ public final class PluginProcessingContext implements PluginContext {
151 151 }
152 152
153 153 @Override
154   - public List<TsKvEntry> loadTimeseries(DeviceId deviceId, TsKvQuery query) {
  154 + public void loadTimeseries(DeviceId deviceId, List<TsKvQuery> queries, PluginCallback<List<TsKvEntry>> callback) {
155 155 validate(deviceId);
156   - try {
157   - return pluginCtx.tsService.findAll(DataConstants.DEVICE, deviceId, query).get();
158   - } catch (Exception e) {
159   - log.error("TODO", e);
160   - throw new RuntimeException(e);
161   - }
162   - }
163   -
164   - @Override
165   - public void loadTimeseries(DeviceId deviceId, TsKvQuery query, PluginCallback<List<TsKvEntry>> callback) {
166   - validate(deviceId);
167   - ListenableFuture<List<TsKvEntry>> future = pluginCtx.tsService.findAll(DataConstants.DEVICE, deviceId, query);
  156 + ListenableFuture<List<TsKvEntry>> future = pluginCtx.tsService.findAll(DataConstants.DEVICE, deviceId, queries);
168 157 Futures.addCallback(future, getCallback(callback, v -> v), executor);
169 158 }
170 159
... ...
... ... @@ -96,7 +96,21 @@ public class BaseTimeseriesDao extends AbstractDao implements TimeseriesDao {
96 96 }
97 97
98 98 @Override
99   - public ListenableFuture<List<TsKvEntry>> findAllAsync(String entityType, UUID entityId, TsKvQuery query) {
  99 + public ListenableFuture<List<TsKvEntry>> findAllAsync(String entityType, UUID entityId, List<TsKvQuery> queries) {
  100 + List<ListenableFuture<List<TsKvEntry>>> futures = queries.stream().map(query -> findAllAsync(entityType, entityId, query)).collect(Collectors.toList());
  101 + return Futures.transform(Futures.allAsList(futures), new Function<List<List<TsKvEntry>>, List<TsKvEntry>>() {
  102 + @Nullable
  103 + @Override
  104 + public List<TsKvEntry> apply(@Nullable List<List<TsKvEntry>> results) {
  105 + List<TsKvEntry> result = new ArrayList<TsKvEntry>();
  106 + results.forEach(r -> result.addAll(r));
  107 + return result;
  108 + }
  109 + }, readResultsProcessingExecutor);
  110 + }
  111 +
  112 +
  113 + private ListenableFuture<List<TsKvEntry>> findAllAsync(String entityType, UUID entityId, TsKvQuery query) {
100 114 if (query.getAggregation() == Aggregation.NONE) {
101 115 return findAllAsyncWithLimit(entityType, entityId, query);
102 116 } else {
... ...
... ... @@ -18,6 +18,7 @@ package org.thingsboard.server.dao.timeseries;
18 18 import com.datastax.driver.core.ResultSet;
19 19 import com.datastax.driver.core.ResultSetFuture;
20 20 import com.datastax.driver.core.Row;
  21 +import com.google.common.base.Function;
21 22 import com.google.common.collect.Lists;
22 23 import com.google.common.util.concurrent.Futures;
23 24 import com.google.common.util.concurrent.ListenableFuture;
... ... @@ -32,6 +33,7 @@ import org.springframework.beans.factory.annotation.Value;
32 33 import org.springframework.stereotype.Service;
33 34 import org.thingsboard.server.dao.service.Validator;
34 35
  36 +import javax.annotation.Nullable;
35 37 import javax.annotation.PostConstruct;
36 38 import javax.annotation.PreDestroy;
37 39 import java.time.Instant;
... ... @@ -40,6 +42,7 @@ import java.time.ZoneOffset;
40 42 import java.util.*;
41 43 import java.util.concurrent.ExecutorService;
42 44 import java.util.concurrent.Executors;
  45 +import java.util.stream.Collectors;
43 46
44 47 import static org.apache.commons.lang3.StringUtils.isBlank;
45 48
... ... @@ -56,10 +59,10 @@ public class BaseTimeseriesService implements TimeseriesService {
56 59 private TimeseriesDao timeseriesDao;
57 60
58 61 @Override
59   - public ListenableFuture<List<TsKvEntry>> findAll(String entityType, UUIDBased entityId, TsKvQuery query) {
  62 + public ListenableFuture<List<TsKvEntry>> findAll(String entityType, UUIDBased entityId, List<TsKvQuery> queries) {
60 63 validate(entityType, entityId);
61   - validate(query);
62   - return timeseriesDao.findAllAsync(entityType, entityId.getId(), query);
  64 + queries.forEach(query -> validate(query));
  65 + return timeseriesDao.findAllAsync(entityType, entityId.getId(), queries);
63 66 }
64 67
65 68 @Override
... ... @@ -132,7 +135,7 @@ public class BaseTimeseriesService implements TimeseriesService {
132 135 throw new IncorrectParameterException("TsKvQuery can't be null");
133 136 } else if (isBlank(query.getKey())) {
134 137 throw new IncorrectParameterException("Incorrect TsKvQuery. Key can't be empty");
135   - } else if (query.getAggregation() == null){
  138 + } else if (query.getAggregation() == null) {
136 139 throw new IncorrectParameterException("Incorrect TsKvQuery. Aggregation can't be empty");
137 140 }
138 141 }
... ...
... ... @@ -33,9 +33,7 @@ public interface TimeseriesDao {
33 33
34 34 long toPartitionTs(long ts);
35 35
36   - ListenableFuture<List<TsKvEntry>> findAllAsync(String entityType, UUID entityId, TsKvQuery query);
37   -
38   -// List<TsKvEntry> find(String entityType, UUID entityId, TsKvQuery query, Optional<Long> minPartition, Optional<Long> maxPartition);
  36 + ListenableFuture<List<TsKvEntry>> findAllAsync(String entityType, UUID entityId, List<TsKvQuery> queries);
39 37
40 38 ResultSetFuture findLatest(String entityType, UUID entityId, String key);
41 39
... ...
... ... @@ -33,7 +33,7 @@ import java.util.Set;
33 33 */
34 34 public interface TimeseriesService {
35 35
36   - ListenableFuture<List<TsKvEntry>> findAll(String entityType, UUIDBased entityId, TsKvQuery query);
  36 + ListenableFuture<List<TsKvEntry>> findAll(String entityType, UUIDBased entityId, List<TsKvQuery> queries);
37 37
38 38 ListenableFuture<List<ResultSet>> findLatest(String entityType, UUIDBased entityId, Collection<String> keys);
39 39
... ...
... ... @@ -82,9 +82,7 @@ public interface PluginContext {
82 82
83 83 void saveTsData(DeviceId deviceId, List<TsKvEntry> entry, PluginCallback<Void> callback);
84 84
85   - List<TsKvEntry> loadTimeseries(DeviceId deviceId, TsKvQuery query);
86   -
87   - void loadTimeseries(DeviceId deviceId, TsKvQuery query, PluginCallback<List<TsKvEntry>> callback);
  85 + void loadTimeseries(DeviceId deviceId, List<TsKvQuery> queries, PluginCallback<List<TsKvEntry>> callback);
88 86
89 87 void loadLatestTimeseries(DeviceId deviceId, Collection<String> keys, PluginCallback<List<TsKvEntry>> callback);
90 88
... ...
... ... @@ -15,9 +15,16 @@
15 15 */
16 16 package org.thingsboard.server.extensions.core.plugin.telemetry.cmd;
17 17
  18 +import lombok.AllArgsConstructor;
  19 +import lombok.Data;
  20 +import lombok.NoArgsConstructor;
  21 +
18 22 /**
19 23 * @author Andrew Shvayka
20 24 */
  25 +@NoArgsConstructor
  26 +@AllArgsConstructor
  27 +@Data
21 28 public class GetHistoryCmd implements TelemetryPluginCmd {
22 29
23 30 private int cmdId;
... ... @@ -25,46 +32,7 @@ public class GetHistoryCmd implements TelemetryPluginCmd {
25 32 private String keys;
26 33 private long startTs;
27 34 private long endTs;
  35 + private int limit;
  36 + private String agg;
28 37
29   - @Override
30   - public int getCmdId() {
31   - return cmdId;
32   - }
33   -
34   - @Override
35   - public void setCmdId(int cmdId) {
36   - this.cmdId = cmdId;
37   - }
38   -
39   - public String getDeviceId() {
40   - return deviceId;
41   - }
42   -
43   - public void setDeviceId(String deviceId) {
44   - this.deviceId = deviceId;
45   - }
46   -
47   - public String getKeys() {
48   - return keys;
49   - }
50   -
51   - public void setKeys(String keys) {
52   - this.keys = keys;
53   - }
54   -
55   - public long getStartTs() {
56   - return startTs;
57   - }
58   -
59   - public void setStartTs(long startTs) {
60   - this.startTs = startTs;
61   - }
62   -
63   - public long getEndTs() {
64   - return endTs;
65   - }
66   -
67   - public void setEndTs(long endTs) {
68   - this.endTs = endTs;
69   - }
70 38 }
... ...
... ... @@ -16,11 +16,13 @@
16 16 package org.thingsboard.server.extensions.core.plugin.telemetry.cmd;
17 17
18 18 import lombok.AllArgsConstructor;
  19 +import lombok.Data;
19 20 import lombok.NoArgsConstructor;
20 21 import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionType;
21 22
22 23 @NoArgsConstructor
23 24 @AllArgsConstructor
  25 +@Data
24 26 public abstract class SubscriptionCmd implements TelemetryPluginCmd {
25 27
26 28 private int cmdId;
... ... @@ -31,46 +33,6 @@ public abstract class SubscriptionCmd implements TelemetryPluginCmd {
31 33
32 34 public abstract SubscriptionType getType();
33 35
34   - public int getCmdId() {
35   - return cmdId;
36   - }
37   -
38   - public void setCmdId(int cmdId) {
39   - this.cmdId = cmdId;
40   - }
41   -
42   - public String getDeviceId() {
43   - return deviceId;
44   - }
45   -
46   - public void setDeviceId(String deviceId) {
47   - this.deviceId = deviceId;
48   - }
49   -
50   - public String getKeys() {
51   - return keys;
52   - }
53   -
54   - public void setTags(String tags) {
55   - this.keys = tags;
56   - }
57   -
58   - public boolean isUnsubscribe() {
59   - return unsubscribe;
60   - }
61   -
62   - public void setUnsubscribe(boolean unsubscribe) {
63   - this.unsubscribe = unsubscribe;
64   - }
65   -
66   - public String getScope() {
67   - return scope;
68   - }
69   -
70   - public void setKeys(String keys) {
71   - this.keys = keys;
72   - }
73   -
74 36 @Override
75 37 public String toString() {
76 38 return "SubscriptionCmd [deviceId=" + deviceId + ", tags=" + keys + ", unsubscribe=" + unsubscribe + "]";
... ...
... ... @@ -15,6 +15,8 @@
15 15 */
16 16 package org.thingsboard.server.extensions.core.plugin.telemetry.cmd;
17 17
  18 +import lombok.AllArgsConstructor;
  19 +import lombok.Data;
18 20 import lombok.NoArgsConstructor;
19 21 import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionType;
20 22
... ... @@ -22,17 +24,13 @@ import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionT
22 24 * @author Andrew Shvayka
23 25 */
24 26 @NoArgsConstructor
  27 +@AllArgsConstructor
  28 +@Data
25 29 public class TimeseriesSubscriptionCmd extends SubscriptionCmd {
26 30
27 31 private long timeWindow;
28   -
29   - public long getTimeWindow() {
30   - return timeWindow;
31   - }
32   -
33   - public void setTimeWindow(long timeWindow) {
34   - this.timeWindow = timeWindow;
35   - }
  32 + private int limit;
  33 + private String agg;
36 34
37 35 @Override
38 36 public SubscriptionType getType() {
... ...
... ... @@ -158,40 +158,14 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler {
158 158 log.debug("[{}] fetching timeseries data for last {} ms for keys: ({}) for device : {}", sessionId, cmd.getTimeWindow(), cmd.getKeys(), cmd.getDeviceId());
159 159 long endTs = System.currentTimeMillis();
160 160 startTs = endTs - cmd.getTimeWindow();
161   - for (String key : keys) {
162   - TsKvQuery query = new BaseTsKvQuery(key, startTs, endTs);
163   - data.addAll(ctx.loadTimeseries(deviceId, query));
164   - }
165   - sendWsMsg(ctx, sessionRef, new SubscriptionUpdate(cmd.getCmdId(), data));
166 161
167   - Map<String, Long> subState = new HashMap<>(keys.size());
168   - keys.forEach(key -> subState.put(key, startTs));
169   - data.forEach(v -> subState.put(v.getKey(), v.getTs()));
170   - SubscriptionState sub = new SubscriptionState(sessionId, cmd.getCmdId(), deviceId, SubscriptionType.TIMESERIES, false, subState);
171   - subscriptionManager.addLocalWsSubscription(ctx, sessionId, deviceId, sub);
  162 + List<TsKvQuery> queries = keys.stream().map(key -> new BaseTsKvQuery(key, startTs, endTs, cmd.getLimit(), Aggregation.valueOf(cmd.getAgg()))).collect(Collectors.toList());
  163 + ctx.loadTimeseries(deviceId, queries, getSubscriptionCallback(sessionRef, cmd, sessionId, deviceId, startTs, keys));
172 164 } else {
173 165 List<String> keys = new ArrayList<>(getKeys(cmd).orElse(Collections.emptySet()));
174 166 startTs = System.currentTimeMillis();
175 167 log.debug("[{}] fetching latest timeseries data for keys: ({}) for device : {}", sessionId, cmd.getKeys(), cmd.getDeviceId());
176   - ctx.loadLatestTimeseries(deviceId, keys, new PluginCallback<List<TsKvEntry>>() {
177   - @Override
178   - public void onSuccess(PluginContext ctx, List<TsKvEntry> data) {
179   - sendWsMsg(ctx, sessionRef, new SubscriptionUpdate(cmd.getCmdId(), data));
180   -
181   - Map<String, Long> subState = new HashMap<>(keys.size());
182   - keys.forEach(key -> subState.put(key, startTs));
183   - data.forEach(v -> subState.put(v.getKey(), v.getTs()));
184   - SubscriptionState sub = new SubscriptionState(sessionId, cmd.getCmdId(), deviceId, SubscriptionType.TIMESERIES, false, subState);
185   - subscriptionManager.addLocalWsSubscription(ctx, sessionId, deviceId, sub);
186   - }
187   -
188   - @Override
189   - public void onFailure(PluginContext ctx, Exception e) {
190   - SubscriptionUpdate update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR,
191   - "Failed to fetch data!");
192   - sendWsMsg(ctx, sessionRef, update);
193   - }
194   - });
  168 + ctx.loadLatestTimeseries(deviceId, keys, getSubscriptionCallback(sessionRef, cmd, sessionId, deviceId, startTs, keys));
195 169 }
196 170 } else {
197 171 ctx.loadLatestTimeseries(deviceId, new PluginCallback<List<TsKvEntry>>() {
... ... @@ -216,6 +190,28 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler {
216 190 }
217 191 }
218 192
  193 + private PluginCallback<List<TsKvEntry>> getSubscriptionCallback(final PluginWebsocketSessionRef sessionRef, final TimeseriesSubscriptionCmd cmd, final String sessionId, final DeviceId deviceId, final long startTs, final List<String> keys) {
  194 + return new PluginCallback<List<TsKvEntry>>() {
  195 + @Override
  196 + public void onSuccess(PluginContext ctx, List<TsKvEntry> data) {
  197 + sendWsMsg(ctx, sessionRef, new SubscriptionUpdate(cmd.getCmdId(), data));
  198 +
  199 + Map<String, Long> subState = new HashMap<>(keys.size());
  200 + keys.forEach(key -> subState.put(key, startTs));
  201 + data.forEach(v -> subState.put(v.getKey(), v.getTs()));
  202 + SubscriptionState sub = new SubscriptionState(sessionId, cmd.getCmdId(), deviceId, SubscriptionType.TIMESERIES, false, subState);
  203 + subscriptionManager.addLocalWsSubscription(ctx, sessionId, deviceId, sub);
  204 + }
  205 +
  206 + @Override
  207 + public void onFailure(PluginContext ctx, Exception e) {
  208 + SubscriptionUpdate update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR,
  209 + "Failed to fetch data!");
  210 + sendWsMsg(ctx, sessionRef, update);
  211 + }
  212 + };
  213 + }
  214 +
219 215 private void handleWsHistoryCmd(PluginContext ctx, PluginWebsocketSessionRef sessionRef, GetHistoryCmd cmd) {
220 216 String sessionId = sessionRef.getSessionId();
221 217 WsSessionMetaData sessionMD = wsSessionsMap.get(sessionId);
... ... @@ -246,12 +242,19 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler {
246 242 return;
247 243 }
248 244 List<String> keys = new ArrayList<>(getKeys(cmd).orElse(Collections.emptySet()));
249   - List<TsKvEntry> data = new ArrayList<>();
250   - for (String key : keys) {
251   - TsKvQuery query = new BaseTsKvQuery(key, cmd.getStartTs(), cmd.getEndTs());
252   - data.addAll(ctx.loadTimeseries(deviceId, query));
253   - }
254   - sendWsMsg(ctx, sessionRef, new SubscriptionUpdate(cmd.getCmdId(), data));
  245 + List<TsKvQuery> queries = keys.stream().map(key -> new BaseTsKvQuery(key, cmd.getStartTs(), cmd.getEndTs(), cmd.getLimit(), Aggregation.valueOf(cmd.getAgg()))).collect(Collectors.toList());
  246 + ctx.loadTimeseries(deviceId, queries, new PluginCallback<List<TsKvEntry>>() {
  247 + @Override
  248 + public void onSuccess(PluginContext ctx, List<TsKvEntry> data) {
  249 + sendWsMsg(ctx, sessionRef, new SubscriptionUpdate(cmd.getCmdId(), data));
  250 + }
  251 +
  252 + @Override
  253 + public void onFailure(PluginContext ctx, Exception e) {
  254 + sendWsMsg(ctx, sessionRef, new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR,
  255 + "Failed to fetch data!"));
  256 + }
  257 + });
255 258 }
256 259
257 260 private boolean validateSessionMetadata(PluginContext ctx, PluginWebsocketSessionRef sessionRef, SubscriptionCmd cmd, String sessionId) {
... ...