LCOV - code coverage report
Current view: top level - server/entity_manager - simple_entity.cpp (source / functions) Coverage Total Hit
Test: coverage.info Lines: 100.0 % 233 233
Test Date: 2026-08-12 11:05:07 Functions: 100.0 % 21 21

            Line data    Source code
       1              : /**
       2              :  * Copyright (c) 2025 Huawei Technologies Co., Ltd.
       3              :  * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
       4              :  * CANN Open Software License Agreement Version 2.0 (the "License").
       5              :  * Please refer to the License for details. You may not use this file except in compliance with the License.
       6              :  * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
       7              :  * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
       8              :  * See LICENSE in the root of the software repository for the full text of the License.
       9              :  */
      10              : 
      11              : #include "simple_entity.h"
      12              : #include "bqs_util.h"
      13              : #include "dgw_client.h"
      14              : #include "profile_manager.h"
      15              : namespace dgw {
      16              : 
      17              : namespace {
      18              : constexpr uint32_t FLAG_DEVICEID_OFFSET = 32U;
      19              : constexpr uint32_t MBUF_ALLOC_ALIGN = 64U;
      20              : } // namespace
      21              : 
      22          342 : SimpleEntity::SimpleEntity(const EntityMaterial& material, const uint32_t resIndex) : Entity(material, resIndex) {}
      23              : 
      24           71 : FsmStatus SimpleEntity::Dequeue()
      25              : {
      26           71 :     const auto dequeAllow = AllowDeque();
      27           71 :     if (dequeAllow != FsmStatus::FSM_SUCCESS) {
      28           20 :         return dequeAllow;
      29              :     }
      30              : 
      31           51 :     bqs::ProfileManager::GetInstance(resIndex_).AddDequeueNum();
      32           51 :     FsmStatus dequeRet = DoDequeue();
      33           51 :     if (dequeRet == FsmStatus::FSM_KEEP_STATE) {
      34           10 :         return dequeRet;
      35              :     }
      36           41 :     if (dequeRet == FsmStatus::FSM_SRC_EMPTY) {
      37           13 :         statInfo_.dequeueEmptyTimes++;
      38           13 :         DGW_LOG_DEBUG("QueueId[%u] on device[%u] has been dequeued to empty.", GetQueueId(), deviceId_);
      39           13 :         return FsmStatus::FSM_FAILED;
      40              :     }
      41           28 :     if (dequeRet != FsmStatus::FSM_SUCCESS) {
      42           13 :         statInfo_.dequeueFailTimes++;
      43           13 :         bqs::StatisticManager::GetInstance().DataScheduleFailedStat();
      44           13 :         return dequeRet;
      45              :     }
      46              : 
      47           15 :     DGW_LOG_INFO("Success to dequeue mbuf for entity[%s] in peek state.", entityDesc_.c_str());
      48           15 :     bqs::StatisticManager::GetInstance().DataDequeueStat();
      49           15 :     statInfo_.dequeueSuccTimes++;
      50              :     // PostDoDeque
      51           15 :     PostDeque();
      52              : 
      53              :     // save wait_scheduled mbuf to entity
      54           15 :     const auto refreshRet = RefreshWithData();
      55           15 :     if (refreshRet != FsmStatus::FSM_SUCCESS) {
      56            1 :         (void)halMbufFree(mbuf_);
      57            1 :         mbuf_ = nullptr;
      58            1 :         return refreshRet;
      59              :     }
      60           14 :     AddScheduleCount();
      61           14 :     return FsmStatus::FSM_SUCCESS;
      62              : }
      63              : 
      64           41 : FsmStatus SimpleEntity::DoDequeue() { return DoDequeueMbuf(PtrToPtr<Mbuf*, void*>(&mbuf_)); }
      65              : 
      66           48 : FsmStatus SimpleEntity::DoDequeueMbuf(void** mbufPtr) const
      67              : {
      68           48 :     const uint32_t queueId = GetQueueId();
      69           48 :     const auto ret = halQueueDeQueue(deviceId_, queueId, mbufPtr);
      70           48 :     if (ret == DRV_ERROR_QUEUE_EMPTY) {
      71           17 :         DGW_LOG_DEBUG("QueueId[%u] on device[%u] has been dequeued to empty.", queueId, deviceId_);
      72           17 :         return FsmStatus::FSM_SRC_EMPTY;
      73              :     }
      74           31 :     if ((ret != DRV_ERROR_NONE) || (*mbufPtr == nullptr)) {
      75           15 :         DGW_LOG_ERROR(
      76              :             "DeQueue from queueId[%u] on device[%u] failed, ret:[%d]", queueId, deviceId_, static_cast<int32_t>(ret));
      77           15 :         if (ret == DRV_ERROR_NOT_EXIST) {
      78            2 :             return FsmStatus::FSM_ERROR_PENDING;
      79              :         }
      80           13 :         return FsmStatus::FSM_FAILED;
      81              :     }
      82           16 :     return FsmStatus::FSM_SUCCESS;
      83              : }
      84              : 
      85           14 : void SimpleEntity::PostDeque() { return; }
      86              : 
      87           16 : FsmStatus SimpleEntity::RefreshWithData()
      88              : {
      89           16 :     if (!needTransId_) {
      90           13 :         return FsmStatus::FSM_SUCCESS;
      91              :     }
      92              : 
      93              :     // src entity is group entity or dst entities has group entity
      94              :     // get transId
      95            3 :     void* headBuf = nullptr;
      96            3 :     uint32_t headBufSize = 0U;
      97            3 :     const auto drvRet = halMbufGetPrivInfo(mbuf_, &headBuf, &headBufSize);
      98            3 :     if (drvRet != static_cast<int32_t>(DRV_ERROR_NONE)) {
      99            1 :         DGW_LOG_ERROR("halMbufGetPrivInfo from mbuf in entity[%s] failed.", entityDesc_.c_str());
     100            1 :         return FsmStatus::FSM_FAILED;
     101              :     }
     102            2 :     if (headBufSize < MBUF_HEAD_MAX_SIZE) {
     103            1 :         DGW_LOG_ERROR("mbuf head size:%u in entity[%s] is invalid.", headBufSize, entityDesc_.c_str());
     104            1 :         return FsmStatus::FSM_FAILED;
     105              :     }
     106              : 
     107            1 :     const uint32_t offset = headBufSize - static_cast<uint32_t>(sizeof(bqs::IdentifyInfo));
     108            1 :     bqs::IdentifyInfo* const info = PtrToPtr<void, bqs::IdentifyInfo>(ValueToPtr(PtrToValue(headBuf) + offset));
     109            1 :     transId_ = info->transId;
     110            1 :     routeLabel_ = info->routeLabel;
     111            1 :     return FsmStatus::FSM_SUCCESS;
     112              : }
     113              : 
     114            2 : FsmStatus SimpleEntity::ResetSrcState() { return ChangeState(FsmState::FSM_IDLE_STATE); }
     115              : 
     116           23 : void SimpleEntity::SelectDstEntities(
     117              :     const uint64_t key, std::vector<Entity*>& toPushDstEntities, std::vector<Entity*>& reprocessDstEntities,
     118              :     std::vector<Entity*>& abnormalDstEntities)
     119              : {
     120              :     (void)key;
     121              :     (void)reprocessDstEntities;
     122              :     (void)abnormalDstEntities;
     123           23 :     toPushDstEntities.emplace_back(this);
     124           23 : }
     125              : 
     126           31 : FsmStatus SimpleEntity::SendData(const DataObjPtr dataObj)
     127              : {
     128           31 :     Mbuf* const mbufToPush = PrepareMbufToPush(dataObj);
     129           31 :     if (mbufToPush == nullptr) {
     130            7 :         return FsmStatus::FSM_FAILED;
     131              :     }
     132           24 :     const auto sendRet = DoSendData(mbufToPush);
     133           24 :     if (sendRet == FsmStatus::FSM_SUCCESS) {
     134           10 :         statInfo_.enqueueSuccTimes++;
     135           14 :     } else if (sendRet != FsmStatus::FSM_KEEP_STATE) {
     136            4 :         Mbuf* const mbuf = const_cast<Mbuf*>(dataObj->GetMbuf());
     137            4 :         if (mbufToPush != mbuf) {
     138            1 :             (void)halMbufFree(mbufToPush);
     139              :         }
     140              :     }
     141           24 :     return sendRet;
     142              : }
     143              : 
     144           21 : Mbuf* SimpleEntity::PrepareMbufToPush(DataObjPtr dataObj) const
     145              : {
     146           21 :     Entity* const sendEntity = dataObj->GetSendEntity();
     147           21 :     Mbuf* const mbuf = const_cast<Mbuf*>(dataObj->GetMbuf());
     148              : 
     149           21 :     if ((sendEntity->GetMbufQueueType() != bqs::CLIENT_Q) && (GetDeviceId() != sendEntity->GetMbufDeviceId())) {
     150            2 :         return SdmaCopy(mbuf);
     151              :     }
     152           19 :     if (dataObj->GetRecvEntitySize() > 1U) {
     153            4 :         Mbuf* copyMbuf = nullptr;
     154            4 :         auto& profileInstance = bqs::ProfileManager::GetInstance(resIndex_);
     155            4 :         const uint64_t copyBegin = profileInstance.GetCpuTick();
     156            4 :         const int32_t drvRet = halMbufCopyRef(mbuf, &copyMbuf);
     157            4 :         profileInstance.AddCopyTotalCost(profileInstance.GetCpuTick() - copyBegin);
     158            4 :         if ((drvRet != static_cast<int32_t>(DRV_ERROR_NONE)) || (copyMbuf == nullptr)) {
     159            2 :             BQS_LOG_RUN_WARN(
     160              :                 "MbufCopy failed when from queue[%u] to queue[%u] in device[%u], error=[%d].", sendEntity->GetId(),
     161              :                 GetId(), GetDeviceId(), drvRet);
     162            2 :             bqs::StatisticManager::GetInstance().DataScheduleFailedStat();
     163              :         }
     164            4 :         return copyMbuf;
     165              :     }
     166              : 
     167           15 :     dataObj->MaintainMbuf();
     168           15 :     return mbuf;
     169              : }
     170              : 
     171            8 : FsmStatus SimpleEntity::DoSendData(Mbuf* const mbuf)
     172              : {
     173            8 :     const auto ret = halQueueEnQueue(deviceId_, id_, mbuf);
     174            8 :     DGW_LOG_INFO(
     175              :         "%s halQueueEnQueue queue id:[%u] device id:[%u] result:[%d].", entityDesc_.c_str(), id_, deviceId_,
     176              :         static_cast<int32_t>(ret));
     177            8 :     if (ret == DRV_ERROR_NONE) {
     178            5 :         bqs::StatisticManager::GetInstance().DataQueueEnqueueSuccStat();
     179            5 :         return FsmStatus::FSM_SUCCESS;
     180              :     }
     181              : 
     182            3 :     if (ret == DRV_ERROR_QUEUE_FULL) {
     183            1 :         bqs::StatisticManager::GetInstance().DataQueueEnqueueFullStat();
     184            1 :         DGW_LOG_INFO("[%s] halQueueEnQueue queue id:[%u] FULL!!!!.", GetTypeDesc().c_str(), id_);
     185            1 :         return FsmStatus::FSM_DEST_FULL;
     186              :     }
     187              : 
     188            2 :     bqs::StatisticManager::GetInstance().DataQueueEnqueueFailStat();
     189            2 :     DGW_LOG_ERROR("[%s] halQueueEnQueue queue id:[%u] FAILED!!!!.", GetTypeDesc().c_str(), id_);
     190            2 :     if (ret == DRV_ERROR_NOT_EXIST) {
     191            1 :         return FsmStatus::FSM_ERROR_PENDING;
     192              :     }
     193            1 :     return FsmStatus::FSM_FAILED;
     194              : }
     195              : 
     196           11 : Mbuf* SimpleEntity::SdmaCopy(Mbuf* const mbuf) const
     197              : {
     198              :     // 调用sdma拷贝
     199           11 :     uint64_t desBufLen = 0;
     200           11 :     auto retHal = halMbufGetDataLen(mbuf, &desBufLen);
     201           11 :     if (retHal != static_cast<int32_t>(DRV_ERROR_NONE)) {
     202            1 :         BQS_LOG_ERROR("halMbufGetDataLen error ret=[%d]", retHal);
     203            1 :         return nullptr;
     204              :     }
     205           10 :     void* srcDataBuf = nullptr;
     206           10 :     auto drvRet = halMbufGetBuffAddr(mbuf, &srcDataBuf);
     207           10 :     if ((drvRet != static_cast<int32_t>(DRV_ERROR_NONE)) || (srcDataBuf == nullptr)) {
     208            1 :         DGW_LOG_ERROR("Fail to get buff addr for mbuf, ret=[%d]", drvRet);
     209            1 :         return nullptr;
     210              :     }
     211            9 :     void* srcHeadBuf = nullptr;
     212            9 :     uint32_t srcHeadBufSize = 0U;
     213            9 :     drvRet = halMbufGetPrivInfo(mbuf, &srcHeadBuf, &srcHeadBufSize);
     214            9 :     if (drvRet != static_cast<int32_t>(DRV_ERROR_NONE)) {
     215            1 :         DGW_LOG_ERROR("halMbufGetPrivInfo fail, ret=[%d]", drvRet);
     216            1 :         return nullptr;
     217              :     }
     218              : 
     219            8 :     Mbuf* mbufPtr = AllocateMbuf(desBufLen);
     220            8 :     if (mbufPtr == nullptr) {
     221            2 :         BQS_LOG_ERROR("Allocate Mbuf fail.");
     222            2 :         return nullptr;
     223              :     }
     224              : 
     225            6 :     if (SdmaCopyData(srcDataBuf, desBufLen, mbufPtr) != FsmStatus::FSM_SUCCESS) {
     226            1 :         BQS_LOG_ERROR("Sdma copy data fail.");
     227            1 :         (void)halMbufFree(mbufPtr);
     228            1 :         return nullptr;
     229              :     }
     230              : 
     231            5 :     if (SdmaCopyHead(srcHeadBuf, srcHeadBufSize, mbufPtr) != FsmStatus::FSM_SUCCESS) {
     232            2 :         BQS_LOG_ERROR("Sdma copy head fail.");
     233            2 :         (void)halMbufFree(mbufPtr);
     234            2 :         return nullptr;
     235              :     }
     236              : 
     237            3 :     BQS_LOG_INFO("Success to sdma copy.");
     238            3 :     return mbufPtr;
     239              : }
     240              : 
     241            8 : Mbuf* SimpleEntity::AllocateMbuf(const uint64_t desBufLen) const
     242              : {
     243            8 :     uint64_t flag = (static_cast<uint64_t>(deviceId_) << FLAG_DEVICEID_OFFSET) | static_cast<uint64_t>(BUFF_SP_NORMAL);
     244            8 :     int32_t memGroupId = 0;
     245            8 :     Mbuf* mbufPtr = nullptr;
     246              :     // alignSize use 64
     247            8 :     auto drvRet = halMbufAllocEx(desBufLen, MBUF_ALLOC_ALIGN, flag, memGroupId, &mbufPtr);
     248            8 :     if ((drvRet != static_cast<int32_t>(DRV_ERROR_NONE)) || mbufPtr == nullptr) {
     249            1 :         BQS_LOG_ERROR(
     250              :             "halMbufAllocEx failed, drvRet=%d, dataSize=%lu, flag=%lu, groupId=%d.", drvRet, desBufLen, flag,
     251              :             memGroupId);
     252            1 :         return nullptr;
     253              :     }
     254            7 :     drvRet = halMbufSetDataLen(mbufPtr, desBufLen);
     255            7 :     if (drvRet != static_cast<int32_t>(DRV_ERROR_NONE)) {
     256            1 :         BQS_LOG_ERROR("halMbufSetDataLen failed, ret=%d.", drvRet);
     257            1 :         (void)halMbufFree(mbufPtr);
     258            1 :         return nullptr;
     259              :     }
     260            6 :     return mbufPtr;
     261              : }
     262              : 
     263            7 : FsmStatus SimpleEntity::SdmaCopyData(void* const srcDataBuf, const uint64_t desBufLen, Mbuf* const mbufPtr) const
     264              : {
     265            7 :     void* dstDataBuf = nullptr;
     266            7 :     auto drvRet = halMbufGetBuffAddr(mbufPtr, &dstDataBuf);
     267            7 :     if ((drvRet != static_cast<int32_t>(DRV_ERROR_NONE)) || (dstDataBuf == nullptr)) {
     268            1 :         DGW_LOG_ERROR("Fail to get buff addr for mbuf, ret=[%d].", drvRet);
     269            1 :         return FsmStatus::FSM_FAILED;
     270              :     }
     271            6 :     drvRet = halSdmaCopy(PtrToValue(dstDataBuf), desBufLen, PtrToValue(srcDataBuf), desBufLen);
     272            6 :     if (drvRet != static_cast<int32_t>(DRV_ERROR_NONE)) {
     273            1 :         DGW_LOG_ERROR("halSdmaCopy error ret:%d.", drvRet);
     274            1 :         return FsmStatus::FSM_FAILED;
     275              :     }
     276            5 :     return FsmStatus::FSM_SUCCESS;
     277              : }
     278              : 
     279            6 : FsmStatus SimpleEntity::SdmaCopyHead(void* const srcHeadBuf, const uint32_t srcHeadBufSize, Mbuf* const mbufPtr) const
     280              : {
     281            6 :     void* dstHeadBuf = nullptr;
     282            6 :     uint32_t dstHeadBufSize = 0U;
     283            6 :     auto drvRet = halMbufGetPrivInfo(mbufPtr, &dstHeadBuf, &dstHeadBufSize);
     284            6 :     if (drvRet != static_cast<int32_t>(DRV_ERROR_NONE)) {
     285            1 :         DGW_LOG_ERROR("halMbufGetPrivInfo fail, ret=[%d]", drvRet);
     286            1 :         return FsmStatus::FSM_FAILED;
     287              :     }
     288            5 :     if ((srcHeadBuf == nullptr) || (dstHeadBuf == nullptr) || (dstHeadBufSize < srcHeadBufSize)) {
     289            1 :         DGW_LOG_ERROR("dstHeadBufSize[%u] vs srcHeadBufSize[%u]", dstHeadBufSize, srcHeadBufSize);
     290            1 :         return FsmStatus::FSM_FAILED;
     291              :     }
     292            4 :     drvRet = halSdmaCopy(PtrToValue(dstHeadBuf), dstHeadBufSize, PtrToValue(srcHeadBuf), srcHeadBufSize);
     293            4 :     if (drvRet != static_cast<int32_t>(DRV_ERROR_NONE)) {
     294            1 :         DGW_LOG_ERROR("halSdmaCopy error ret:%d", drvRet);
     295            1 :         return FsmStatus::FSM_FAILED;
     296              :     }
     297            3 :     return FsmStatus::FSM_SUCCESS;
     298              : }
     299              : 
     300           30 : bqs::SubscribeManager* SimpleEntity::GetSubscriber() const
     301              : {
     302              :     bqs::SubscribeManager* const subscribeManager =
     303           30 :         bqs::Subscribers::GetInstance().GetSubscribeManager(resIndex_, deviceId_);
     304           30 :     if (subscribeManager == nullptr) {
     305           11 :         DGW_LOG_ERROR(
     306              :             "Failed to find subscribeManager for resIndex: %u, device: %u, queueType: %d", resIndex_, deviceId_,
     307              :             static_cast<int32_t>(queueType_));
     308              :     }
     309           30 :     return subscribeManager;
     310              : }
     311              : 
     312            5 : FsmStatus SimpleEntity::PauseSubscribe(const Entity& fullEntity)
     313              : {
     314            5 :     if (subscribeStatus_ == SubscribeStatus::SUBSCRIBE_PAUSE) {
     315            1 :         DGW_LOG_WARN("No need to pause subscribe for entity[%s].", entityDesc_.c_str());
     316            1 :         return FsmStatus::FSM_SUCCESS;
     317              :     }
     318            4 :     DGW_LOG_INFO(
     319              :         "[FSM] Pause subscribe src entity[%s] because dst entity[id:%u, type:%s] full.", entityDesc_.c_str(),
     320              :         fullEntity.GetId(), fullEntity.GetTypeDesc().c_str());
     321            4 :     subscribeStatus_ = SubscribeStatus::SUBSCRIBE_PAUSE;
     322              : 
     323            4 :     const auto subscriber = GetSubscriber();
     324            4 :     if (subscriber == nullptr) {
     325            1 :         return FsmStatus::FSM_FAILED;
     326              :     }
     327            3 :     subscriber->PauseSubscribe(GetQueueId(), fullEntity.GetId(), false);
     328            3 :     return FsmStatus::FSM_SUCCESS;
     329              : }
     330              : 
     331            5 : FsmStatus SimpleEntity::ResumeSubscribe(const Entity& notFullEntity)
     332              : {
     333            5 :     if (subscribeStatus_ == SubscribeStatus::SUBSCRIBE_RESUME) {
     334            1 :         DGW_LOG_WARN("No need to resume subscribe for entity[%s].", entityDesc_.c_str());
     335            1 :         return FsmStatus::FSM_SUCCESS;
     336              :     }
     337            4 :     DGW_LOG_INFO(
     338              :         "[FSM] Resume subscribe src entity[%s] because dst entity[id:%u, type:%s] not full.", entityDesc_.c_str(),
     339              :         notFullEntity.GetId(), notFullEntity.GetTypeDesc().c_str());
     340            4 :     subscribeStatus_ = SubscribeStatus::SUBSCRIBE_RESUME;
     341            4 :     const auto subscriber = GetSubscriber();
     342            4 :     if (subscriber == nullptr) {
     343            3 :         return FsmStatus::FSM_FAILED;
     344              :     }
     345            1 :     subscriber->ResumeSubscribe(GetQueueId(), notFullEntity.GetId());
     346            1 :     return FsmStatus::FSM_SUCCESS;
     347              : }
     348              : 
     349            8 : FsmStatus SimpleEntity::ClearQueue()
     350              : {
     351            8 :     DGW_LOG_INFO("Entity[%s] clear queue", entityDesc_.c_str());
     352              :     do {
     353            9 :         void* mbuf = nullptr;
     354            9 :         const auto dequeRet = DoDequeueMbuf(&mbuf);
     355            9 :         if (dequeRet == FsmStatus::FSM_SRC_EMPTY) {
     356            5 :             break;
     357              :         }
     358              : 
     359            4 :         if ((dequeRet != FsmStatus::FSM_SUCCESS) || (mbuf == nullptr)) {
     360            3 :             DGW_LOG_ERROR(
     361              :                 "DeQueue from queueId[%u] in device[%u] failed, ret:[%d]", GetQueueId(), deviceId_,
     362              :                 static_cast<int32_t>(dequeRet));
     363            3 :             return FsmStatus::FSM_FAILED;
     364              :         }
     365            1 :         (void)halMbufFree(PtrToPtr<void, Mbuf>(mbuf));
     366            1 :     } while (true);
     367              : 
     368            7 :     for (auto sendDataObj : sendDataObjs_) {
     369            4 :         for (auto recvEntity : sendDataObj->GetRecvEntities()) {
     370            2 :             auto& recvObjs = recvEntity->GetRecvDataObjs();
     371            5 :             for (auto iter = recvObjs.begin(); iter != recvObjs.end();) {
     372            3 :                 if (Equal((*iter)->GetSendEntity())) {
     373            2 :                     auto tempIter = iter;
     374            2 :                     iter++;
     375            2 :                     recvObjs.erase(tempIter);
     376              :                 } else {
     377            1 :                     iter++;
     378              :                 }
     379              :             }
     380              :         }
     381            2 :     }
     382            5 :     sendDataObjs_.clear();
     383            5 :     DGW_LOG_INFO("Entity[%s] clear queue finish", entityDesc_.c_str());
     384            5 :     return FsmStatus::FSM_SUCCESS;
     385              : }
     386              : 
     387           28 : bool SimpleEntity::IsDataPeeked() const { return (curState_ == FsmState::FSM_PEEK_STATE); }
     388              : 
     389          232 : FsmStatus SimpleEntity::Uninit()
     390              : {
     391          232 :     DGW_LOG_RUN_INFO(
     392              :         "[%s] has dequeued[%lu], enqueued[%lu].", entityDesc_.c_str(), statInfo_.dequeueSuccTimes,
     393              :         statInfo_.enqueueSuccTimes);
     394          232 :     return FsmStatus::FSM_SUCCESS;
     395              : }
     396              : 
     397              : } // namespace dgw
        

Generated by: LCOV version 2.0-1