implemented telemetry metadata instead of separate topic for metrics
This commit is contained in:
		
							parent
							
								
									e65fc9d55e
								
							
						
					
					
						commit
						137b1732bb
					
				@ -1021,7 +1021,7 @@ transport:
 | 
			
		||||
    disconnect_timeout: "${MQTT_DISCONNECT_TIMEOUT:1000}"
 | 
			
		||||
    msg_queue_size_per_device_limit: "${MQTT_MSG_QUEUE_SIZE_PER_DEVICE_LIMIT:100}" # messages await in the queue before the device connected state. This limit works on the low level before TenantProfileLimits mechanism
 | 
			
		||||
    # Interval of periodic report of the gateway metrics
 | 
			
		||||
    gateway_metrics_report_interval_sec: "${MQTT_GATEWAY_METRICS_REPORT_INTERVAL_SEC:3600}"
 | 
			
		||||
    gateway_metrics_report_interval_sec: "${MQTT_GATEWAY_METRICS_REPORT_INTERVAL_SEC:60}"
 | 
			
		||||
    netty:
 | 
			
		||||
      # Netty leak detector level
 | 
			
		||||
      leak_detector_level: "${NETTY_LEAK_DETECTOR_LVL:DISABLED}"
 | 
			
		||||
 | 
			
		||||
@ -18,7 +18,6 @@ package org.thingsboard.server.transport.mqtt.mqttv3.telemetry.timeseries;
 | 
			
		||||
import com.fasterxml.jackson.core.type.TypeReference;
 | 
			
		||||
import io.netty.handler.codec.mqtt.MqttQoS;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.awaitility.Awaitility;
 | 
			
		||||
import org.junit.Before;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
import org.mockito.ArgumentCaptor;
 | 
			
		||||
@ -55,7 +54,6 @@ import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVIC
 | 
			
		||||
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_TELEMETRY_SHORT_TOPIC;
 | 
			
		||||
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_TELEMETRY_TOPIC;
 | 
			
		||||
import static org.thingsboard.server.common.data.device.profile.MqttTopics.GATEWAY_CONNECT_TOPIC;
 | 
			
		||||
import static org.thingsboard.server.common.data.device.profile.MqttTopics.GATEWAY_METRICS_TOPIC;
 | 
			
		||||
import static org.thingsboard.server.common.data.device.profile.MqttTopics.GATEWAY_TELEMETRY_TOPIC;
 | 
			
		||||
import static org.thingsboard.server.transport.mqtt.gateway.GatewayMetricsService.GATEWAY_METRICS;
 | 
			
		||||
 | 
			
		||||
@ -138,13 +136,10 @@ public abstract class AbstractMqttTimeseriesIntegrationTest extends AbstractMqtt
 | 
			
		||||
                .build();
 | 
			
		||||
        processBeforeTest(configProperties);
 | 
			
		||||
 | 
			
		||||
        MqttTestClient client = new MqttTestClient();
 | 
			
		||||
        client.connectAndWait(gatewayAccessToken);
 | 
			
		||||
 | 
			
		||||
        Map<String, List<Long>> gwLatencies = new HashMap<>();
 | 
			
		||||
        Map<String, List<Long>> transportLatencies = new HashMap<>();
 | 
			
		||||
 | 
			
		||||
        publishLatency(client, gwLatencies, transportLatencies, 5);
 | 
			
		||||
        publishLatency(gwLatencies, transportLatencies, 5);
 | 
			
		||||
 | 
			
		||||
        gatewayMetricsService.reportMetrics();
 | 
			
		||||
 | 
			
		||||
@ -174,38 +169,37 @@ public abstract class AbstractMqttTimeseriesIntegrationTest extends AbstractMqtt
 | 
			
		||||
            assertNotNull(connectorLatencyResult);
 | 
			
		||||
            checkConnectorLatencyResult(connectorLatencyResult, avgGwLatency, minGwLatency, maxGwLatency, avgTransportLatency, minTransportLatency, maxTransportLatency);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        client.disconnect();
 | 
			
		||||
 | 
			
		||||
        Awaitility.await()
 | 
			
		||||
                .atMost(5, TimeUnit.SECONDS)
 | 
			
		||||
                .untilAsserted(() -> verify(gatewayMetricsService).onDeviceDisconnect(savedGateway.getId()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void publishLatency(MqttTestClient client, Map<String, List<Long>> gwLatencies,  Map<String, List<Long>> transportLatencies, int n) throws Exception {
 | 
			
		||||
    private void publishLatency(Map<String, List<Long>> gwLatencies, Map<String, List<Long>> transportLatencies, int n) throws Exception {
 | 
			
		||||
        Random random = new Random();
 | 
			
		||||
        for (int i = 0; i < n; i++) {
 | 
			
		||||
            Map<String, GatewayMetricsData> data = new HashMap<>();
 | 
			
		||||
            long publishedTs = System.currentTimeMillis() - 10;
 | 
			
		||||
            long gatewayLatencyA = random.nextLong(100, 500);
 | 
			
		||||
            data.put("connectorA", new GatewayMetricsData(publishedTs - gatewayLatencyA, publishedTs));
 | 
			
		||||
            var firstData = new GatewayMetricsData("connectorA", publishedTs - gatewayLatencyA, publishedTs);
 | 
			
		||||
            gwLatencies.computeIfAbsent("connectorA", key -> new ArrayList<>()).add(gatewayLatencyA);
 | 
			
		||||
            boolean sendB = i % 2 == 0;
 | 
			
		||||
            if (sendB) {
 | 
			
		||||
            long gatewayLatencyB = random.nextLong(120, 450);
 | 
			
		||||
                data.put("connectorB", new GatewayMetricsData(publishedTs - gatewayLatencyB, publishedTs));
 | 
			
		||||
            var secondData = new GatewayMetricsData("connectorB", publishedTs - gatewayLatencyB, publishedTs);
 | 
			
		||||
            gwLatencies.computeIfAbsent("connectorB", key -> new ArrayList<>()).add(gatewayLatencyB);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            client.publishAndWait(GATEWAY_METRICS_TOPIC, JacksonUtil.writeValueAsBytes(data));
 | 
			
		||||
            ArgumentCaptor<Long> transportReceiveTsCaptor = ArgumentCaptor.forClass(Long.class);
 | 
			
		||||
            verify(gatewayMetricsService).process(any(), eq(savedGateway.getId()), eq(data), transportReceiveTsCaptor.capture());
 | 
			
		||||
            Long transportReceiveTs = transportReceiveTsCaptor.getValue();
 | 
			
		||||
            Long transportLatency = transportReceiveTs - publishedTs;
 | 
			
		||||
            transportLatencies.computeIfAbsent("connectorA", key -> new ArrayList<>()).add(transportLatency);
 | 
			
		||||
            if (sendB) {
 | 
			
		||||
                transportLatencies.computeIfAbsent("connectorB", key -> new ArrayList<>()).add(transportLatency);
 | 
			
		||||
            }
 | 
			
		||||
            List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
 | 
			
		||||
            String deviceName1 = "Device A";
 | 
			
		||||
            String deviceName2 = "Device B";
 | 
			
		||||
            String firstMetadata = JacksonUtil.writeValueAsString(firstData);
 | 
			
		||||
            String secondMetadata = JacksonUtil.writeValueAsString(secondData);
 | 
			
		||||
            String payload = getGatewayTelemetryJsonPayloadWithMetadata(deviceName1, deviceName2, "10000", "20000", firstMetadata, secondMetadata);
 | 
			
		||||
            processGatewayTelemetryTest(GATEWAY_TELEMETRY_TOPIC, expectedKeys, payload.getBytes(), deviceName1, deviceName2);
 | 
			
		||||
 | 
			
		||||
            ArgumentCaptor<Long> transportReceiveTsCaptorA = ArgumentCaptor.forClass(Long.class);
 | 
			
		||||
            ArgumentCaptor<Long> transportReceiveTsCaptorB = ArgumentCaptor.forClass(Long.class);
 | 
			
		||||
            verify(gatewayMetricsService).process(any(), eq(savedGateway.getId()), eq(List.of(firstData)), transportReceiveTsCaptorA.capture());
 | 
			
		||||
            verify(gatewayMetricsService).process(any(), eq(savedGateway.getId()), eq(List.of(secondData)), transportReceiveTsCaptorB.capture());
 | 
			
		||||
            Long transportReceiveTsA = transportReceiveTsCaptorA.getValue();
 | 
			
		||||
            Long transportReceiveTsB = transportReceiveTsCaptorB.getValue();
 | 
			
		||||
            Long transportLatencyA = transportReceiveTsA - publishedTs;
 | 
			
		||||
            Long transportLatencyB = transportReceiveTsB - publishedTs;
 | 
			
		||||
            transportLatencies.computeIfAbsent("connectorA", key -> new ArrayList<>()).add(transportLatencyA);
 | 
			
		||||
            transportLatencies.computeIfAbsent("connectorB", key -> new ArrayList<>()).add(transportLatencyB);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -250,7 +244,8 @@ public abstract class AbstractMqttTimeseriesIntegrationTest extends AbstractMqtt
 | 
			
		||||
        long end = System.currentTimeMillis() + 5000;
 | 
			
		||||
        Map<String, List<Map<String, Object>>> values = null;
 | 
			
		||||
        while (start <= end) {
 | 
			
		||||
            values = doGetAsyncTyped(getTelemetryValuesUrl, new TypeReference<>() {});
 | 
			
		||||
            values = doGetAsyncTyped(getTelemetryValuesUrl, new TypeReference<>() {
 | 
			
		||||
            });
 | 
			
		||||
            boolean valid = values.size() == expectedKeys.size();
 | 
			
		||||
            if (valid) {
 | 
			
		||||
                for (String key : expectedKeys) {
 | 
			
		||||
@ -289,7 +284,7 @@ public abstract class AbstractMqttTimeseriesIntegrationTest extends AbstractMqtt
 | 
			
		||||
        MqttTestClient client = new MqttTestClient();
 | 
			
		||||
        client.connectAndWait(gatewayAccessToken);
 | 
			
		||||
        client.publishAndWait(topic, payload);
 | 
			
		||||
        client.disconnect();
 | 
			
		||||
        client.disconnectAndWait();
 | 
			
		||||
 | 
			
		||||
        Device firstDevice = doExecuteWithRetriesAndInterval(() -> doGet("/api/tenant/devices?deviceName=" + firstDeviceName, Device.class),
 | 
			
		||||
                20,
 | 
			
		||||
@ -346,6 +341,16 @@ public abstract class AbstractMqttTimeseriesIntegrationTest extends AbstractMqtt
 | 
			
		||||
        return "{\"" + deviceA + "\": " + payload + ",  \"" + deviceB + "\": " + payload + "}";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected String getGatewayTelemetryJsonPayloadWithMetadata(String deviceA, String deviceB, String firstTsValue, String secondTsValue, String firstMetadata, String secondMetadata) {
 | 
			
		||||
        String payloadA = "[{\"ts\": " + firstTsValue + ", \"values\": " + PAYLOAD_VALUES_STR + ",  \"metadata\":" + firstMetadata + "}, " +
 | 
			
		||||
                "{\"ts\": " + secondTsValue + ", \"values\": " + PAYLOAD_VALUES_STR + "}]";
 | 
			
		||||
 | 
			
		||||
        String payloadB = "[{\"ts\": " + firstTsValue + ", \"values\": " + PAYLOAD_VALUES_STR + ",  \"metadata\":" + secondMetadata + "}, " +
 | 
			
		||||
                "{\"ts\": " + secondTsValue + ", \"values\": " + PAYLOAD_VALUES_STR + "}]";
 | 
			
		||||
 | 
			
		||||
        return "{\"" + deviceA + "\": " + payloadA + ",  \"" + deviceB + "\": " + payloadB + "}";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private String getTelemetryValuesUrl(DeviceId deviceId, Set<String> actualKeySet) {
 | 
			
		||||
        return "/api/plugins/telemetry/DEVICE/" + deviceId + "/values/timeseries?startTs=0&endTs=25000&keys=" + String.join(",", actualKeySet);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -75,7 +75,6 @@ public class MqttTopics {
 | 
			
		||||
    public static final String GATEWAY_RPC_TOPIC = BASE_GATEWAY_API_TOPIC + RPC;
 | 
			
		||||
    public static final String GATEWAY_ATTRIBUTES_REQUEST_TOPIC = BASE_GATEWAY_API_TOPIC + ATTRIBUTES_REQUEST;
 | 
			
		||||
    public static final String GATEWAY_ATTRIBUTES_RESPONSE_TOPIC = BASE_GATEWAY_API_TOPIC + ATTRIBUTES_RESPONSE;
 | 
			
		||||
    public static final String GATEWAY_METRICS_TOPIC = BASE_GATEWAY_API_TOPIC + "/metrics";
 | 
			
		||||
    // v2 topics
 | 
			
		||||
    public static final String BASE_DEVICE_API_TOPIC_V2 = "v2";
 | 
			
		||||
    public static final String REQUEST_ID_PATTERN = "(?<requestId>\\d+)";
 | 
			
		||||
 | 
			
		||||
@ -74,22 +74,34 @@ public class JsonConverter {
 | 
			
		||||
    private static int maxStringValueLength = 0;
 | 
			
		||||
 | 
			
		||||
    public static PostTelemetryMsg convertToTelemetryProto(JsonElement jsonElement, long ts) throws JsonSyntaxException {
 | 
			
		||||
        return convertToTelemetryProto(jsonElement, ts, null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static PostTelemetryMsg convertToTelemetryProto(JsonElement jsonElement, long ts, List<JsonElement> metadataResult) throws JsonSyntaxException {
 | 
			
		||||
        PostTelemetryMsg.Builder builder = PostTelemetryMsg.newBuilder();
 | 
			
		||||
        convertToTelemetry(jsonElement, ts, null, builder);
 | 
			
		||||
        convertToTelemetry(jsonElement, ts, null, builder, metadataResult);
 | 
			
		||||
        return builder.build();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static PostTelemetryMsg convertToTelemetryProto(JsonElement jsonElement, List<JsonElement> metadataResult) throws JsonSyntaxException {
 | 
			
		||||
        return convertToTelemetryProto(jsonElement, System.currentTimeMillis(), metadataResult);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static PostTelemetryMsg convertToTelemetryProto(JsonElement jsonElement) throws JsonSyntaxException {
 | 
			
		||||
        return convertToTelemetryProto(jsonElement, System.currentTimeMillis());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void convertToTelemetry(JsonElement jsonElement, long systemTs, Map<Long, List<KvEntry>> result, PostTelemetryMsg.Builder builder) {
 | 
			
		||||
    private static void convertToTelemetry(JsonElement jsonElement, long systemTs, Map<Long, List<KvEntry>> result, PostTelemetryMsg.Builder builder, List<JsonElement> metadataResult) {
 | 
			
		||||
        if (jsonElement.isJsonObject()) {
 | 
			
		||||
            parseObject(systemTs, result, builder, jsonElement.getAsJsonObject());
 | 
			
		||||
        } else if (jsonElement.isJsonArray()) {
 | 
			
		||||
            jsonElement.getAsJsonArray().forEach(je -> {
 | 
			
		||||
                if (je.isJsonObject()) {
 | 
			
		||||
                    parseObject(systemTs, result, builder, je.getAsJsonObject());
 | 
			
		||||
                    JsonObject jo = je.getAsJsonObject();
 | 
			
		||||
                    if (metadataResult != null && jo.has("metadata")) {
 | 
			
		||||
                        metadataResult.add(jo.get("metadata"));
 | 
			
		||||
                    }
 | 
			
		||||
                    parseObject(systemTs, result, builder, jo);
 | 
			
		||||
                } else {
 | 
			
		||||
                    throw new JsonSyntaxException(CAN_T_PARSE_VALUE + je);
 | 
			
		||||
                }
 | 
			
		||||
@ -550,7 +562,7 @@ public class JsonConverter {
 | 
			
		||||
    public static Map<Long, List<KvEntry>> convertToTelemetry(JsonElement jsonElement, long systemTs, boolean sorted) throws
 | 
			
		||||
            JsonSyntaxException {
 | 
			
		||||
        Map<Long, List<KvEntry>> result = sorted ? new TreeMap<>() : new HashMap<>();
 | 
			
		||||
        convertToTelemetry(jsonElement, systemTs, result, null);
 | 
			
		||||
        convertToTelemetry(jsonElement, systemTs, result, null, null);
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -423,9 +423,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 | 
			
		||||
                case MqttTopics.GATEWAY_DISCONNECT_TOPIC:
 | 
			
		||||
                    gatewaySessionHandler.onDeviceDisconnect(mqttMsg);
 | 
			
		||||
                    break;
 | 
			
		||||
                case MqttTopics.GATEWAY_METRICS_TOPIC:
 | 
			
		||||
                    gatewaySessionHandler.onGatewayMetrics(mqttMsg);
 | 
			
		||||
                    break;
 | 
			
		||||
                default:
 | 
			
		||||
                    ack(ctx, msgId, MqttReasonCodes.PubAck.TOPIC_NAME_INVALID);
 | 
			
		||||
            }
 | 
			
		||||
@ -1199,7 +1196,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 | 
			
		||||
            transportService.process(deviceSessionCtx.getSessionInfo(), SESSION_EVENT_MSG_CLOSED, null);
 | 
			
		||||
            transportService.deregisterSession(deviceSessionCtx.getSessionInfo());
 | 
			
		||||
            if (gatewaySessionHandler != null) {
 | 
			
		||||
                gatewaySessionHandler.onGatewayDisconnect();
 | 
			
		||||
                gatewaySessionHandler.onDevicesDisconnect();
 | 
			
		||||
            }
 | 
			
		||||
            if (sparkplugSessionHandler != null) {
 | 
			
		||||
                // add Msg Telemetry node: key STATE type: String value: OFFLINE ts: sparkplugBProto.getTimestamp()
 | 
			
		||||
 | 
			
		||||
@ -30,6 +30,7 @@ import org.thingsboard.server.transport.mqtt.TbMqttTransportComponent;
 | 
			
		||||
import org.thingsboard.server.transport.mqtt.gateway.metrics.GatewayMetricsData;
 | 
			
		||||
import org.thingsboard.server.transport.mqtt.gateway.metrics.GatewayMetricsState;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.concurrent.ConcurrentHashMap;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
@ -41,7 +42,7 @@ public class GatewayMetricsService {
 | 
			
		||||
 | 
			
		||||
    public static final String GATEWAY_METRICS = "gatewayMetrics";
 | 
			
		||||
 | 
			
		||||
    @Value("${transport.mqtt.gateway_metrics_report_interval_sec:3600}")
 | 
			
		||||
    @Value("${transport.mqtt.gateway_metrics_report_interval_sec:60}")
 | 
			
		||||
    private int metricsReportIntervalSec;
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
@ -57,8 +58,8 @@ public class GatewayMetricsService {
 | 
			
		||||
        scheduler.scheduleAtFixedRate(this::reportMetrics, metricsReportIntervalSec, metricsReportIntervalSec, TimeUnit.SECONDS);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void process(TransportProtos.SessionInfoProto sessionInfo, DeviceId gatewayId, Map<String, GatewayMetricsData> data, long ts) {
 | 
			
		||||
        states.computeIfAbsent(gatewayId, k -> new GatewayMetricsState(sessionInfo)).update(ts, data);
 | 
			
		||||
    public void process(TransportProtos.SessionInfoProto sessionInfo, DeviceId gatewayId, List<GatewayMetricsData> data, long serverReceiveTs) {
 | 
			
		||||
        states.computeIfAbsent(gatewayId, k -> new GatewayMetricsState(sessionInfo)).update(data, serverReceiveTs);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, DeviceId gatewayId) {
 | 
			
		||||
@ -72,26 +73,18 @@ public class GatewayMetricsService {
 | 
			
		||||
        states.remove(deviceId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void onDeviceDisconnect(DeviceId deviceId) {
 | 
			
		||||
        GatewayMetricsState state = states.remove(deviceId);
 | 
			
		||||
        if (state != null) {
 | 
			
		||||
            reportMetrics(state, System.currentTimeMillis());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void reportMetrics() {
 | 
			
		||||
        if (states.isEmpty()) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        Map<DeviceId, GatewayMetricsState> oldStates = states;
 | 
			
		||||
        Map<DeviceId, GatewayMetricsState> statesToReport = states;
 | 
			
		||||
        states = new ConcurrentHashMap<>();
 | 
			
		||||
 | 
			
		||||
        long ts = System.currentTimeMillis();
 | 
			
		||||
 | 
			
		||||
        oldStates.forEach((gatewayId, state) -> {
 | 
			
		||||
        statesToReport.forEach((gatewayId, state) -> {
 | 
			
		||||
            reportMetrics(state, ts);
 | 
			
		||||
        });
 | 
			
		||||
        oldStates.clear();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void reportMetrics(GatewayMetricsState state, long ts) {
 | 
			
		||||
 | 
			
		||||
@ -15,5 +15,5 @@
 | 
			
		||||
 */
 | 
			
		||||
package org.thingsboard.server.transport.mqtt.gateway.metrics;
 | 
			
		||||
 | 
			
		||||
public record GatewayMetricsData(long receivedTs, long publishedTs) {
 | 
			
		||||
public record GatewayMetricsData(String connector, long receivedTs, long publishedTs) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -19,6 +19,7 @@ import lombok.Getter;
 | 
			
		||||
import org.thingsboard.server.gen.transport.TransportProtos;
 | 
			
		||||
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.concurrent.atomic.AtomicInteger;
 | 
			
		||||
import java.util.concurrent.atomic.AtomicLong;
 | 
			
		||||
@ -43,11 +44,11 @@ public class GatewayMetricsState {
 | 
			
		||||
        this.sessionInfo = sessionInfo;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void update(long ts, Map<String, GatewayMetricsData> metricsData) {
 | 
			
		||||
    public void update(List<GatewayMetricsData> metricsData, long serverReceiveTs) {
 | 
			
		||||
        updateLock.lock();
 | 
			
		||||
        try {
 | 
			
		||||
            metricsData.forEach((connectorName, data) -> {
 | 
			
		||||
                connectors.computeIfAbsent(connectorName, k -> new ConnectorMetricsState()).update(ts, data);
 | 
			
		||||
            metricsData.forEach(data -> {
 | 
			
		||||
                connectors.computeIfAbsent(data.connector(), k -> new ConnectorMetricsState()).update(data, serverReceiveTs);
 | 
			
		||||
            });
 | 
			
		||||
        } finally {
 | 
			
		||||
            updateLock.unlock();
 | 
			
		||||
@ -86,7 +87,7 @@ public class GatewayMetricsState {
 | 
			
		||||
            this.transportLatencySum = new AtomicLong(0);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void update(long serverReceiveTs, GatewayMetricsData metricsData) {
 | 
			
		||||
        private void update(GatewayMetricsData metricsData, long serverReceiveTs) {
 | 
			
		||||
            long gwLatency = metricsData.publishedTs() - metricsData.receivedTs();
 | 
			
		||||
            long transportLatency = serverReceiveTs - metricsData.publishedTs();
 | 
			
		||||
            count.incrementAndGet();
 | 
			
		||||
 | 
			
		||||
@ -62,6 +62,8 @@ import org.thingsboard.server.transport.mqtt.MqttTransportHandler;
 | 
			
		||||
import org.thingsboard.server.transport.mqtt.adaptors.JsonMqttAdaptor;
 | 
			
		||||
import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
 | 
			
		||||
import org.thingsboard.server.transport.mqtt.adaptors.ProtoMqttAdaptor;
 | 
			
		||||
import org.thingsboard.server.transport.mqtt.gateway.GatewayMetricsService;
 | 
			
		||||
import org.thingsboard.server.transport.mqtt.gateway.metrics.GatewayMetricsData;
 | 
			
		||||
import org.thingsboard.server.transport.mqtt.util.sparkplug.SparkplugConnectionState;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
@ -114,6 +116,7 @@ public abstract class AbstractGatewaySessionHandler<T extends AbstractGatewayDev
 | 
			
		||||
    protected final ConcurrentMap<MqttTopicMatcher, Integer> mqttQoSMap;
 | 
			
		||||
    protected final ChannelHandlerContext channel;
 | 
			
		||||
    protected final DeviceSessionCtx deviceSessionCtx;
 | 
			
		||||
    protected final GatewayMetricsService gatewayMetricsService;
 | 
			
		||||
 | 
			
		||||
    @Getter
 | 
			
		||||
    @Setter
 | 
			
		||||
@ -131,6 +134,7 @@ public abstract class AbstractGatewaySessionHandler<T extends AbstractGatewayDev
 | 
			
		||||
        this.mqttQoSMap = deviceSessionCtx.getMqttQoSMap();
 | 
			
		||||
        this.channel = deviceSessionCtx.getChannel();
 | 
			
		||||
        this.overwriteDevicesActivity = overwriteDevicesActivity;
 | 
			
		||||
        this.gatewayMetricsService = deviceSessionCtx.getContext().getGatewayMetricsService();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ConcurrentReferenceHashMap<String, Lock> createWeakMap() {
 | 
			
		||||
@ -380,7 +384,9 @@ public abstract class AbstractGatewaySessionHandler<T extends AbstractGatewayDev
 | 
			
		||||
 | 
			
		||||
    private void processPostTelemetryMsg(T deviceCtx, JsonElement msg, String deviceName, int msgId) {
 | 
			
		||||
        try {
 | 
			
		||||
            TransportProtos.PostTelemetryMsg postTelemetryMsg = JsonConverter.convertToTelemetryProto(msg.getAsJsonArray());
 | 
			
		||||
            List<JsonElement> metadata = new ArrayList<>();
 | 
			
		||||
            TransportProtos.PostTelemetryMsg postTelemetryMsg = JsonConverter.convertToTelemetryProto(msg.getAsJsonArray(), metadata);
 | 
			
		||||
            processTelemetryMetadataMsg(metadata);
 | 
			
		||||
            transportService.process(deviceCtx.getSessionInfo(), postTelemetryMsg, getPubAckCallback(channel, deviceName, msgId, postTelemetryMsg));
 | 
			
		||||
        } catch (Throwable e) {
 | 
			
		||||
            log.warn("[{}][{}][{}] Failed to convert telemetry: [{}]", gateway.getTenantId(), gateway.getDeviceId(), deviceName, msg, e);
 | 
			
		||||
@ -388,6 +394,20 @@ public abstract class AbstractGatewaySessionHandler<T extends AbstractGatewayDev
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void processTelemetryMetadataMsg(List<JsonElement> metadata) {
 | 
			
		||||
        var serverReceiveTs = System.currentTimeMillis();
 | 
			
		||||
        var metricsData = metadata.stream()
 | 
			
		||||
                .filter(JsonElement::isJsonObject)
 | 
			
		||||
                .map(je -> {
 | 
			
		||||
                    var jo = je.getAsJsonObject();
 | 
			
		||||
                    var connector = jo.get("connector").getAsString();
 | 
			
		||||
                    var receivedTs = jo.get("receivedTs").getAsLong();
 | 
			
		||||
                    var publishedTs = jo.get("publishedTs").getAsLong();
 | 
			
		||||
                    return new GatewayMetricsData(connector, receivedTs, publishedTs);
 | 
			
		||||
                }).toList();
 | 
			
		||||
       gatewayMetricsService.process(deviceSessionCtx.getSessionInfo(), gateway.getDeviceId(), metricsData, serverReceiveTs);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected void onDeviceTelemetryProto(int msgId, ByteBuf payload) throws AdaptorException {
 | 
			
		||||
        try {
 | 
			
		||||
            TransportApiProtos.GatewayTelemetryMsg telemetryMsgProto = TransportApiProtos.GatewayTelemetryMsg.parseFrom(getBytes(payload));
 | 
			
		||||
 | 
			
		||||
@ -15,36 +15,27 @@
 | 
			
		||||
 */
 | 
			
		||||
package org.thingsboard.server.transport.mqtt.session;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.core.type.TypeReference;
 | 
			
		||||
import io.netty.buffer.ByteBuf;
 | 
			
		||||
import io.netty.handler.codec.mqtt.MqttPublishMessage;
 | 
			
		||||
import io.netty.handler.codec.mqtt.MqttReasonCodes;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.thingsboard.common.util.JacksonUtil;
 | 
			
		||||
import org.thingsboard.server.common.adaptor.AdaptorException;
 | 
			
		||||
import org.thingsboard.server.common.data.Device;
 | 
			
		||||
import org.thingsboard.server.common.data.DeviceProfile;
 | 
			
		||||
import org.thingsboard.server.common.data.id.DeviceId;
 | 
			
		||||
import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse;
 | 
			
		||||
import org.thingsboard.server.gen.transport.TransportProtos;
 | 
			
		||||
import org.thingsboard.server.transport.mqtt.gateway.GatewayMetricsService;
 | 
			
		||||
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
 | 
			
		||||
import static com.amazonaws.util.StringUtils.UTF8;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Created by nickAS21 on 26.12.22
 | 
			
		||||
 */
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class GatewaySessionHandler extends AbstractGatewaySessionHandler<GatewayDeviceSessionContext> {
 | 
			
		||||
 | 
			
		||||
    private final GatewayMetricsService gatewayMetricsService;
 | 
			
		||||
 | 
			
		||||
    public GatewaySessionHandler(DeviceSessionCtx deviceSessionCtx, UUID sessionId, boolean overwriteDevicesActivity) {
 | 
			
		||||
        super(deviceSessionCtx, sessionId, overwriteDevicesActivity);
 | 
			
		||||
        this.gatewayMetricsService = deviceSessionCtx.getContext().getGatewayMetricsService();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void onDeviceConnect(MqttPublishMessage mqttMsg) throws AdaptorException {
 | 
			
		||||
@ -70,11 +61,6 @@ public class GatewaySessionHandler extends AbstractGatewaySessionHandler<Gateway
 | 
			
		||||
        return new GatewayDeviceSessionContext(this, msg.getDeviceInfo(), msg.getDeviceProfile(), mqttQoSMap, transportService);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void onGatewayDisconnect() {
 | 
			
		||||
        this.onDevicesDisconnect();
 | 
			
		||||
        gatewayMetricsService.onDeviceDisconnect(gateway.getDeviceId());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void onGatewayUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional<DeviceProfile> deviceProfileOpt) {
 | 
			
		||||
        this.onDeviceUpdate(sessionInfo, device, deviceProfileOpt);
 | 
			
		||||
        gatewayMetricsService.onDeviceUpdate(sessionInfo, gateway.getDeviceId());
 | 
			
		||||
@ -84,21 +70,4 @@ public class GatewaySessionHandler extends AbstractGatewaySessionHandler<Gateway
 | 
			
		||||
        gatewayMetricsService.onDeviceDelete(deviceId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void onGatewayMetrics(MqttPublishMessage mqttMsg) throws AdaptorException {
 | 
			
		||||
        int msgId = getMsgId(mqttMsg);
 | 
			
		||||
        ByteBuf payloadData = mqttMsg.payload();
 | 
			
		||||
        String payload = payloadData.toString(UTF8);
 | 
			
		||||
        if (payload == null) {
 | 
			
		||||
            log.debug("[{}][{}][{}] Payload is empty!", gateway.getTenantId(), gateway.getDeviceId(), sessionId);
 | 
			
		||||
            throw new AdaptorException(new IllegalArgumentException("Payload is empty!"));
 | 
			
		||||
        }
 | 
			
		||||
        long ts = System.currentTimeMillis();
 | 
			
		||||
        try {
 | 
			
		||||
            gatewayMetricsService.process(deviceSessionCtx.getSessionInfo(), gateway.getDeviceId(), JacksonUtil.fromString(payload, new TypeReference<>() {}), ts);
 | 
			
		||||
            ack(msgId, MqttReasonCodes.PubAck.SUCCESS);
 | 
			
		||||
        } catch (Throwable t) {
 | 
			
		||||
            ackOrClose(msgId);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -147,7 +147,7 @@ transport:
 | 
			
		||||
    disconnect_timeout: "${MQTT_DISCONNECT_TIMEOUT:1000}"
 | 
			
		||||
    msg_queue_size_per_device_limit: "${MQTT_MSG_QUEUE_SIZE_PER_DEVICE_LIMIT:100}" # messages await in the queue before device connected state. This limit works on low level before TenantProfileLimits mechanism
 | 
			
		||||
    # Interval of periodic report of the gateway metrics
 | 
			
		||||
    gateway_metrics_report_interval_sec: "${MQTT_GATEWAY_METRICS_REPORT_INTERVAL_SEC:3600}"
 | 
			
		||||
    gateway_metrics_report_interval_sec: "${MQTT_GATEWAY_METRICS_REPORT_INTERVAL_SEC:60}"
 | 
			
		||||
    netty:
 | 
			
		||||
      # Netty leak detector level
 | 
			
		||||
      leak_detector_level: "${NETTY_LEAK_DETECTOR_LVL:DISABLED}"
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user