B接口性能增强模式

在实际的项目中,虽然大多数场景下,FsuOS的默认配置可以达到比较满意的效果,但是有时候我们也遇到性能尴尬的情况,核心原因主要还是在嵌入式系统上PHP的执行效率不高,在通常的配置下,设备数量和信号都不算多的情况, 整体性能还可以接受。但在以下情况下,性能恶化较快,表现为内存快速耗尽,CPU特别繁忙导致响应特别慢:

  1. 目标FSU的配置较低,CPU较差,目前主要为303MINI和EISU,EISU由于现场情况确实简单,目前表现尚可,平均响应时间在2-5秒。303MINI遇到对方SC丝毫不考虑我方设备的硬件资源情况,非要开几个并发频繁请求,导致我方卡顿,内存耗尽OOM杀进程。
  2. 目标FSU配置了较多的设备,目前观测到GFSU上配置了37个spm33电表,虽然一个电表的B接口信号就几十条,37个电表堆在一起也得2000条信号,发送一次B接口数据,得20多秒,并且这20多秒基本独占CPU。
  3. 目标FSU配置了几个设备,但是设备挂了大量的信号,目前观测到303PRO-T上配置了8组240节电池,每组电池有240个电压,内阻,温度,大概就是750个信号,乘以8,大概每包6000个信号,发送一次得40多秒。实测优化后,不到2秒。

我们注意到有不少友商是直接采用C语言进行设备协议驱动的研发和信号的映射,我们评估过此方案,C/CPP语言可以保证程序的执行效率,基本上没有其他语言可以达到同等性能,但是C语言开发的问题也很明显,可使用C/CPP语言进行研发的程序员较少, 研发坑比较多,细节问题较多,开发效率低速度慢,难以快速适配新问题,因此FsuOS采用的是混合结构,cpp编写的驱动负责采集和告警,保持最大的实时性。PHP做用户数据展示和B接口转换,和SC对接。

我们选择PHP也不是随便选的,考虑到目标FSU设备中位配置为CPU arm9 500Mhz, 内存128MB, 硬盘256MB; 低配置为CPU arm9 200MHz, 内存64MB, 硬盘64MB, 常用的web研发语言.net/c#,java,python,ruby,nodejs个个都是内存杀手,这点配置都不够他们启动的,而PHP的执行程序仅仅5MB就可以将php运行起来,其他小语言lua,tcl本身缺乏web的生态,做一些业务逻辑还可以,做web麻烦更多。

问题还是要解决,FsuOS提供了B接口性能增强模式(boost模式),目前支持电信和联通B接口,对电信和联通的GET_DATA,GET_AIDATA,GET_SUINFO等心跳,请求频繁的动作,由SMDDevice直接处理,不经过PHP,其他普通的B接口命令,继续由php响应。

SMDDevice 在 2024-08-26 以后的版本都集成了B接口性能增强模式(boost模式)

激活boost模式的准备工作: 调整设备协议驱动,由于GET_DATA由SMDDevice响应,SMDevice就要知道怎么将设备实时数据转换为B接口数据。 方法为在DeviceIoControl函数,对ioControlCode加上801分支,此分支返回json格式的解析后的数据,同php的helper文件返回的一致。 大家可以参考下面代码,我们对中间重点进行解读:

  • std::lock_guardstd::mutex lLock(jsonStrMutex); 801这个命令要加锁,因为B接口请求可能是并发的,不加锁有可能会同时进入下面lastTime != lastJsonTime_的情况,造成对同一数据同时转json,浪费CPU时间。
  • lastTime != lastJsonTime_ 这个只有数据发生变化后,才会做重新的解析。
  • if(outBufferSize >= jsonStr.size()) 当接收区的空间足够时,才能memcpy,返回0,表示成功。否则通过bytesReturned返回需要的接收区大小,返回-1,告诉调用者准备好再来。原理上,调用者要准备bytesReturned+1的char buffer。
    头文件添加变量
 boost::posix_time::ptime lastJsonTime_;
    std::mutex jsonStrMutex;
    std::string jsonStr;
int H3GTABatteryBalancer160::DeviceIoControl(int ioControlCode, const void* inBuffer, int inBufferSize, void* outBuffer, int outBufferSize, int& bytesReturned)
{
	switch(ioControlCode) {
        case 801:
            {
                //get fsuos json data
                std::lock_guard<std::mutex> lLock(jsonStrMutex);
                if(lastTime != lastJsonTime_)
                {
                    lastJsonTime_ = lastTime;
                    Json::Value retJson;
                    for(int i=0;i<240;i++)
                    {
                        std::string vName = "电池" + boost::lexical_cast<std::string>(i+1) + "电压";
                        std::stringstream vSS;
                        vSS<<std::setprecision(3)<<((float)cData.battery_voltage[i])/1000;
                        retJson[vName] = vSS.str();
                        
                        std::string rName = "电池" + boost::lexical_cast<std::string>(i+1) + "内阻";
                        std::stringstream rSS;
                        rSS<<std::setprecision(3)<<((float)cData.battery_resist[i])/1000;
                        retJson[rName] = rSS.str();
                        
                        std::string tName = "电池" + boost::lexical_cast<std::string>(i+1) + "温度";
                        std::stringstream tSS;
                        int sign = ((cData.battery_temperature[i] >> 15) & 0x1) ? -1 : 1;
                        tSS<<std::setprecision(2)<<sign*((float)(cData.battery_temperature[i]&0x7FFF))/100;
                        retJson[tName] = tSS.str();                
                    }
                    std::stringstream gSS;
                    gSS<<std::setprecision(3)<<((float)cData.group_voltage)/1000;
                    retJson["整组电压"] = gSS.str();
                    
                    std::stringstream iSS;
                    iSS<<std::setprecision(3)<<((float)cData.current)/1000;
                    retJson["充放电电流"] = iSS.str();
                    
                    std::stringstream t1SS;
                    t1SS<<std::setprecision(1)<<((float)cData.temperature1)/10;
                    retJson["环境温度1"] = t1SS.str();
                    
                    std::stringstream t2SS;
                    t2SS<<std::setprecision(1)<<((float)cData.temperature2)/10;
                    retJson["环境温度2"] = t2SS.str();
                    
                    std::stringstream uSS;
                    uSS<<(int)cData.update_time.year<<"-"<<(int)cData.update_time.month<<"-"<<(int)cData.update_time.day<<" "<<(int)cData.update_time.hour<<":"<<(int)cData.update_time.minute<<":"<<(int)cData.update_time.second;
                    retJson["更新时间"] = uSS.str();
                    
                    Json::FastWriter writer;
                    jsonStr = writer.write(retJson);
                }
                if(outBufferSize >= jsonStr.size())
                {
                    memcpy(outBuffer, jsonStr.data(), jsonStr.size());
                    bytesReturned = jsonStr.size();
                    return 0;
                }else{
                    bytesReturned = jsonStr.size();
                    return -1;
                }
            return 0;
            }

启用boost模式后,本FSU上所有的协议驱动,都需要实现801命令,才能保证boost模式正常工作。

激活步骤:

  1. 更新/opt/SMDDevice或者/FsuOS下的SMDDevice
  2. 修改/opt/SMDDevice/或者/FsuOS下的SMDDevice.conf
b_mode=2                                       工作在电信B接口模式
b_mode_port=8080                       B接口监听端口8080
b_mode_worker=5                        B 接口支持的并发为5个
  1. 修改/etc/lighttpd/或者/FsuOS/web/lighttpd/lighttpd.conf 搜索8080端口,屏蔽,参考黄色部分
##                        
## Document root
##                                                                   
server.document-root = server_root                                     
#$SERVER["socket"] == ":8080" {                  
#        server.document-root = server_root 
#} 

4 .在/opt/SMDDevice或者/FsuOS下做符号链接,SMDDevice启动时候可以读取到B接口映射配置文件
ln -s /opt/www/application/helpers/ini .
5. 重启lighttpd 6. 重启SMDDevice