21 changed files with 348 additions and 289 deletions
@ -0,0 +1,44 @@ |
|||
/** |
|||
* Copyright © 2016-2022 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.monitoring.data.cmd; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.query.EntityData; |
|||
import org.thingsboard.server.common.data.query.EntityKeyType; |
|||
import org.thingsboard.server.common.data.query.TsValue; |
|||
|
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
|
|||
@Data |
|||
@JsonIgnoreProperties(ignoreUnknown = true) |
|||
public class EntityDataUpdate { |
|||
|
|||
@JsonIgnoreProperties(ignoreUnknown = true) |
|||
private List<EntityData> update; |
|||
|
|||
public String getLatest(UUID entityId, String key) { |
|||
if (update == null) return null; |
|||
|
|||
return update.stream() |
|||
.filter(entityData -> entityData.getEntityId().getId().equals(entityId)).findFirst() |
|||
.map(EntityData::getLatest).map(latest -> latest.get(EntityKeyType.TIME_SERIES)) |
|||
.map(latest -> latest.get(key)).map(TsValue::getValue) |
|||
.orElse(null); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
/** |
|||
* Copyright © 2016-2022 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.monitoring.data.cmd; |
|||
|
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.query.EntityKey; |
|||
|
|||
import java.util.List; |
|||
|
|||
@Data |
|||
public class LatestValueCmd { |
|||
|
|||
private List<EntityKey> keys; |
|||
|
|||
} |
|||
@ -1,52 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2022 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.monitoring.data.cmd; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
|||
import lombok.Data; |
|||
import org.springframework.data.util.Pair; |
|||
|
|||
import java.util.Comparator; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.Objects; |
|||
|
|||
@Data |
|||
@JsonIgnoreProperties(ignoreUnknown = true) |
|||
public class TimeseriesUpdate { |
|||
|
|||
private final int subscriptionId; |
|||
private final int errorCode; |
|||
private final String errorMsg; |
|||
private Map<String, List<List<Object>>> data; |
|||
|
|||
public Object getLatest(String key) { |
|||
if (!data.containsKey(key)) return null; |
|||
return data.get(key).stream() |
|||
.map(tsAndValue -> { |
|||
if (tsAndValue == null || tsAndValue.size() != 2) { |
|||
return null; |
|||
} |
|||
long ts = Long.parseLong(tsAndValue.get(0).toString()); |
|||
Object value = tsAndValue.get(1); |
|||
return Pair.of(ts, value); |
|||
}) |
|||
.filter(Objects::nonNull) |
|||
.max(Comparator.comparing(Pair::getFirst)) |
|||
.map(Pair::getSecond).orElse(null); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,143 @@ |
|||
/** |
|||
* Copyright © 2016-2022 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.monitoring.transport; |
|||
|
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.boot.context.event.ApplicationReadyEvent; |
|||
import org.springframework.context.ApplicationContext; |
|||
import org.springframework.context.event.EventListener; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.common.util.ThingsBoardThreadFactory; |
|||
import org.thingsboard.monitoring.client.TbClient; |
|||
import org.thingsboard.monitoring.client.WsClient; |
|||
import org.thingsboard.monitoring.client.WsClientFactory; |
|||
import org.thingsboard.monitoring.config.DeviceConfig; |
|||
import org.thingsboard.monitoring.config.MonitoringTargetConfig; |
|||
import org.thingsboard.monitoring.config.service.TransportMonitoringConfig; |
|||
import org.thingsboard.monitoring.data.Latencies; |
|||
import org.thingsboard.monitoring.data.MonitoredServiceKey; |
|||
import org.thingsboard.monitoring.service.MonitoringReporter; |
|||
import org.thingsboard.monitoring.util.TbStopWatch; |
|||
import org.thingsboard.server.common.data.Device; |
|||
import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration; |
|||
import org.thingsboard.server.common.data.device.data.DefaultDeviceTransportConfiguration; |
|||
import org.thingsboard.server.common.data.device.data.DeviceData; |
|||
import org.thingsboard.server.common.data.id.DeviceId; |
|||
import org.thingsboard.server.common.data.security.DeviceCredentials; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
import java.util.LinkedList; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.Executors; |
|||
import java.util.concurrent.ScheduledExecutorService; |
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
@Service |
|||
@RequiredArgsConstructor |
|||
@Slf4j |
|||
public final class TransportMonitoringService { |
|||
|
|||
private final List<TransportMonitoringConfig> configs; |
|||
private final List<TransportHealthChecker<?>> transportHealthCheckers = new LinkedList<>(); |
|||
private final List<UUID> devices = new LinkedList<>(); |
|||
|
|||
private final TbClient tbClient; |
|||
private final WsClientFactory wsClientFactory; |
|||
private final TbStopWatch stopWatch; |
|||
private final MonitoringReporter reporter; |
|||
private final ApplicationContext applicationContext; |
|||
private ScheduledExecutorService scheduler; |
|||
@Value("${monitoring.transports.monitoring_rate_ms}") |
|||
private int monitoringRateMs; |
|||
|
|||
@PostConstruct |
|||
private void init() { |
|||
configs.forEach(config -> { |
|||
config.getTargets().stream() |
|||
.filter(target -> StringUtils.isNotBlank(target.getBaseUrl())) |
|||
.peek(target -> checkMonitoringTarget(config, target, tbClient)) |
|||
.forEach(target -> { |
|||
TransportHealthChecker<?> transportHealthChecker = applicationContext.getBean(config.getTransportType().getServiceClass(), config, target); |
|||
transportHealthCheckers.add(transportHealthChecker); |
|||
devices.add(target.getDevice().getId()); |
|||
}); |
|||
}); |
|||
scheduler = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("monitoring-executor")); |
|||
} |
|||
|
|||
@EventListener(ApplicationReadyEvent.class) |
|||
public void startMonitoring() { |
|||
scheduleCheck(0); |
|||
} |
|||
|
|||
private void scheduleCheck(int delay) { |
|||
log.debug("Scheduling next check for {} ms", delay); |
|||
scheduler.schedule(() -> { |
|||
try { |
|||
stopWatch.start(); |
|||
String accessToken = tbClient.logIn(); |
|||
reporter.reportLatency(Latencies.LOG_IN, stopWatch.getTime()); |
|||
|
|||
try (WsClient wsClient = wsClientFactory.createClient(accessToken)) { |
|||
wsClient.subscribeForTelemetry(devices, TransportHealthChecker.TEST_TELEMETRY_KEY).waitForReply(); |
|||
|
|||
for (TransportHealthChecker<?> transportHealthChecker : transportHealthCheckers) { |
|||
transportHealthChecker.check(wsClient); |
|||
} |
|||
} |
|||
reporter.reportLatencies(tbClient); |
|||
} catch (Exception e) { |
|||
reporter.serviceFailure(MonitoredServiceKey.GENERAL, e); |
|||
} |
|||
scheduleCheck(monitoringRateMs); |
|||
}, delay, TimeUnit.MILLISECONDS); |
|||
} |
|||
|
|||
private void checkMonitoringTarget(TransportMonitoringConfig config, MonitoringTargetConfig target, TbClient tbClient) { |
|||
DeviceConfig deviceConfig = target.getDevice(); |
|||
tbClient.logIn(); |
|||
|
|||
DeviceId deviceId; |
|||
if (deviceConfig == null || deviceConfig.getId() == null) { |
|||
String deviceName = String.format("[%s] Monitoring device (%s)", config.getTransportType(), target.getBaseUrl()); |
|||
Device device = tbClient.getTenantDevice(deviceName) |
|||
.orElseGet(() -> { |
|||
log.info("Creating new device '{}'", deviceName); |
|||
Device monitoringDevice = new Device(); |
|||
monitoringDevice.setName(deviceName); |
|||
monitoringDevice.setType("default"); |
|||
DeviceData deviceData = new DeviceData(); |
|||
deviceData.setConfiguration(new DefaultDeviceConfiguration()); |
|||
deviceData.setTransportConfiguration(new DefaultDeviceTransportConfiguration()); |
|||
return tbClient.saveDevice(monitoringDevice); |
|||
}); |
|||
deviceId = device.getId(); |
|||
target.getDevice().setId(deviceId.toString()); |
|||
} else { |
|||
deviceId = new DeviceId(deviceConfig.getId()); |
|||
} |
|||
|
|||
log.debug("Loading credentials for device {}", deviceId); |
|||
DeviceCredentials credentials = tbClient.getDeviceCredentialsByDeviceId(deviceId) |
|||
.orElseThrow(() -> new IllegalArgumentException("No credentials found for device " + deviceId)); |
|||
target.getDevice().setCredentials(credentials); |
|||
} |
|||
|
|||
} |
|||
Loading…
Reference in new issue