apache / plc4x

PLC4X The Industrial IoT adapter

Home Page:https://plc4x.apache.org/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[Bug]: plc4j connection cost too time than milo

anganing opened this issue · comments

What happened?

hello,
I want to replace milo with plc4j.

the plc4j is very slow than milo:

milo:539ms, plc4j:22s.

The gap is very large.

but, if I use the same connection(no close it, use it again), it cost 83 ms.

what can I do for cost less time?

use plc4j code:

private List<StationStateDto> selectStationStateByPlc4x(List<String> stationCodeList, String deviceCode) throws Exception {
        // 根据设备编码查询设备信息
        DcDeviceInfo dcDeviceInfo = new LambdaQueryChainWrapper<>(dcDeviceInfoMapper)
                .eq(DcDeviceInfo::getDeviceCode, deviceCode)
                .one();
        // 查询S7头表信息
        DcDeviceS7Header dcDeviceS7Header = new LambdaQueryChainWrapper<>(dcDeviceS7HeaderMapper)
                .eq(DcDeviceS7Header::getDeviceId, dcDeviceInfo.getDeviceId())
                .one();
        // 查询S7明细表信息
        List<DcDeviceS7Detail> dcDeviceS7Details = new LambdaQueryChainWrapper<>(dcDeviceS7DetailMapper)
                .eq(DcDeviceS7Detail::getS7HeaderId, dcDeviceS7Header.getS7HeaderId())
                .list();
        // 将dcDeviceS7Details转换为Map便于查找
        Map<String, DcDeviceS7Detail> dcDeviceS7DetailMap = dcDeviceS7Details.stream()
                .filter(dcDeviceS7Detail -> dcDeviceS7Detail.getMatchingCode() != null && !dcDeviceS7Detail.getMatchingCode().isEmpty())
                .collect(Collectors.toMap(DcDeviceS7Detail::getMatchingCode, dcDeviceS7Detail -> dcDeviceS7Detail));

        // 获取OPC_UA连接
        log.info("连接设备:{}", dcDeviceInfo.getDeviceName());
        PlcConnection plcConnection = new PlcDriverManager().getConnection(OpcUaUtils.getPlc4xConnectionString(dcDeviceS7Header.getEndPointUrl()));
        boolean canRead = plcConnection.getMetadata().canRead();
        if (!canRead) {
            log.error("该plc4x连接不支持读取数据");
            throw new BizException("该连接不支持读取数据");
        }

        List<StationStateDto> stationStateDtoList = new ArrayList<>();
        PlcReadRequest.Builder builder = plcConnection.readRequestBuilder();

        for (String stationCode : stationCodeList) {
            DcDeviceS7Detail dcDeviceS7Detail = dcDeviceS7DetailMap.get(stationCode);
            String fieldQuery = "ns="+dcDeviceS7Detail.getNamespaceIndex() + ";s=" + dcDeviceS7Detail.getIdentifier() + ";" + dcDeviceS7Detail.getDataType();
            builder.addItem(stationCode, fieldQuery);
        }

        PlcReadRequest readRequest = builder.build();
        PlcReadResponse response = readRequest.execute().get();
        for (String fieldName : response.getFieldNames()) {
            if (response.getResponseCode(fieldName) == PlcResponseCode.OK) {
                StationStateDto stationStateDto = new StationStateDto();
                stationStateDto.setStationCode(fieldName);
                stationStateDto.setState((Short) response.getObject(fieldName));
                stationStateDtoList.add(stationStateDto);
            } else {
                log.error("Error[" + fieldName + "]: " + response.getResponseCode(fieldName).name());
            }
        }

        // 关闭连接
        plcConnection.close();

        log.info("工位占用状态集合:{}", JSONUtil.toJsonPrettyStr(stationStateDtoList));
        return stationStateDtoList;
    }

use milo code:

public List<StationStateDto> selectStationState(List<String> stationCodeList, String deviceCode) throws Exception {
        // 根据设备编码查询设备信息
        DcDeviceInfo dcDeviceInfo = new LambdaQueryChainWrapper<>(dcDeviceInfoMapper)
                .eq(DcDeviceInfo::getDeviceCode, deviceCode)
                .one();
        // 查询S7头表信息
        DcDeviceS7Header dcDeviceS7Header = new LambdaQueryChainWrapper<>(dcDeviceS7HeaderMapper)
                .eq(DcDeviceS7Header::getDeviceId, dcDeviceInfo.getDeviceId())
                .one();
        // 查询S7明细表信息
        List<DcDeviceS7Detail> dcDeviceS7Details = new LambdaQueryChainWrapper<>(dcDeviceS7DetailMapper)
                .eq(DcDeviceS7Detail::getS7HeaderId, dcDeviceS7Header.getS7HeaderId())
                .list();
        // 获取OPC_UA连接
        log.info("连接设备:{}", dcDeviceInfo.getDeviceName());
        OpcUaClient opcUaClient = OpcUaUtils.createClient(dcDeviceS7Header.getEndPointUrl(), dcDeviceS7Header.getApplicationName(), dcDeviceS7Header.getApplicationUri());

        // 开启连接
        opcUaClient.connect().get();

        List<StationStateDto> stationStateDtoList = new ArrayList<>();

        for (String stationCode : stationCodeList) {
            OpcUaReadSubscribeVo opcUaReadSubscribeVo = new OpcUaReadSubscribeVo();
            opcUaReadSubscribeVo.setNamespaceIndex(dcDeviceS7Details.stream().filter(p -> Objects.equals(p.getMatchingCode(), stationCode)).collect(Collectors.toList()).get(0).getNamespaceIndex());
            opcUaReadSubscribeVo.setIdentifier(dcDeviceS7Details.stream().filter(p -> Objects.equals(p.getMatchingCode(), stationCode)).collect(Collectors.toList()).get(0).getIdentifier());

            OpcNodeId opcNodeId = OpcUaUtils.readNode(opcUaClient, opcUaReadSubscribeVo);

            StationStateDto stationStateDto = new StationStateDto();
            stationStateDto.setStationCode(stationCode);
            stationStateDto.setState((Short) opcNodeId.getValue());


            stationStateDtoList.add(stationStateDto);
        }

        // 关闭连接
        opcUaClient.disconnect().get();

        log.info("工位占用状态集合:{}", JSONUtil.toJsonPrettyStr(stationStateDtoList));

        return stationStateDtoList;

//        return selectStationStateByPlc4x(stationCodeList, deviceCode);
    }

Version

v0.10.0

Programming Languages

  • plc4j
  • plc4go
  • plc4c
  • plc4net

Protocols

  • AB-Ethernet
  • ADS /AMS
  • BACnet/IP
  • CANopen
  • DeltaV
  • DF1
  • EtherNet/IP
  • Firmata
  • KNXnet/IP
  • Modbus
  • OPC-UA
  • S7

Would be really interesting in what's causing our driver to be that slow ... usually the PLC4X drivers perform quite well in comparisons.

Perhaps it would be worth profiling your use-case with JProfiler (I know that we've got a free license for doing PLC4X stuff by PMC members) ... maybe Ben has some time to have a look at it?

But reading the part of "reusing" the connection ... do I understand it correctly: If the connection is already established and you are re-using the connection, then PLC4X takes 83ms and Milo is at 539ms? ... if that was the case, it would really make me happy. And in that case I would strongly suggest to have a look at our connection-cache (We refactored the connection-pool since 0.10.0 quite a bit and it's now called connection cache).

It creates a connection, just the same way the normal Driver manager does it, but it doesn't close the connection ... the next time the same connection-string is used, it returns the existing connection. Perhaps this approach would not help speed up the first connection, but it would definitely help with all following ones.

But we still should look into why it's taking so long to connect ... could you please share more details on your setup?

Yes, but milo 539ms(contain connection time), plc4j83ms(not contain connection time).

I had try your connection-cache. but I dont know how to reconnect opc-ua when the connection is disconnection or wrong with somethings from your doc.

so, can you give me a more detail example about connction-cache?

Well the connection-cache (in the 0.11.0-SNAPSHOT version) should automatically reconnect ... however this will then again take the 22s, which we definitely need to optimize.

Can you please send us a packet capture? and as @chrisdutz mentioned your setup (Device, Connection String, etc.. ) Hopefully that will give a bit more of a hint at what's going on.

Well the connection-cache (in the 0.11.0-SNAPSHOT version) should automatically reconnect ... however this will then again take the 22s, which we definitely need to optimize.

I put it to Spring container.
image
then, autowrite it:
image
image

when I first to read requset:
image

when I second request it is ok!!!(no restart springboot app)

Can you please send us a packet capture? and as @chrisdutz mentioned your setup (Device, Connection String, etc.. ) Hopefully that will give a bit more of a hint at what's going on.

I can only provide limited information:
image

ConnectionString: opcua:tcp://192.168.0.2:4840

I dont know how to get a packet capture.

In fact, when using Prosys OPC UA Simulation Server as a simulation device, accessing it using plc4j is also relatively slow. You can use it to reproduce this scene. Download address: http://www.prosysopc.cn/products/opc-ua-simulation-server/

Can you please send us a packet capture? and as @chrisdutz mentioned your setup (Device, Connection String, etc.. ) Hopefully that will give a bit more of a hint at what's going on.

I can only provide limited information: image

ConnectionString: opcua:tcp://192.168.0.2:4840

I dont know how to get a packet capture.

In fact, when using Prosys OPC UA Simulation Server as a simulation device, accessing it using plc4j is also relatively slow. You can use it to reproduce this scene. Download address: http://www.prosysopc.cn/products/opc-ua-simulation-server/

Hi, you can try to capture packets with Wireshark.

image

This is what I'd expect from the driver, this is using the hello world write example from 0.10.0
There seems to be something odd going on with your setup.

But send through a packet capture, a list of the addresses you're querying and I might be able to figure out whats going on.

Can you please send us a packet capture? and as @chrisdutz mentioned your setup (Device, Connection String, etc.. ) Hopefully that will give a bit more of a hint at what's going on.

I can only provide limited information: image
ConnectionString: opcua:tcp://192.168.0.2:4840
I dont know how to get a packet capture.
In fact, when using Prosys OPC UA Simulation Server as a simulation device, accessing it using plc4j is also relatively slow. You can use it to reproduce this scene. Download address: http://www.prosysopc.cn/products/opc-ua-simulation-server/

Hi, you can try to capture packets with Wireshark.

oh, thank you. I get it.

image

This is what I'd expect from the driver, this is using the hello world write example from 0.10.0 There seems to be something odd going on with your setup.

But send through a packet capture, a list of the addresses you're querying and I might be able to figure out whats going on.

hello,

I do it.

here is the file of Wireshark:

plc4j-opc-ua-slow.zip

Thanks for that. It looks like the Activate Session Request from PLC4X to the OPCUA server gets sent out 10 seconds after receiving the previous response from the OPCUA server.

Looking at the code to see what could delay it within the Activate Session Request logic, there is a few calls to look up the hostname.

image

As the request does eventually get sent out, I suspect that one of these calls is taking too long too complete. Can you please try adding the OPCUA server hostname in the Hosts file on the client.

Something similar to this.
https://stackoverflow.com/questions/47031687/getcanonicalhostname-is-very-slow

That sort of sounds like a timeout ... right?

Yeah, that's what I'm suspecting. Hopefully @anganing can confirm that it was their problem, but we should probably look at faster options anyway.

Yeah, that's what I'm suspecting. Hopefully @anganing can confirm that it was their problem, but we should probably look at faster options anyway.

Sorry for the confusion caused to you.

I jsut use "local"--Improved a lot, now it only takes about 2 seconds
image

Don't be sorry, you brought up an issue, and put in effort to help solve it. Thank you.

Yeah that would cause the hostname lookup to respond pretty quickly. I'll try and think if we need to do all those hostname lookup calls or if we can get away with only a couple.