LCOV - code coverage report
Current view: top level - src/extension - discovery_viewer.cc (source / functions) Hit Total Coverage
Test: vlink Lines: 451 510 88.4 %
Date: 2026-07-26 14:05:51 Functions: 26 28 92.9 %
Branches: 483 802 60.2 %

           Branch data     Line data    Source code
       1                 :            : /*
       2                 :            :  * Copyright (C) 2026 by Thun Lu. All rights reserved.
       3                 :            :  * Author: Thun Lu <thun.lu@zohomail.cn>
       4                 :            :  * Repo:   https://github.com/thun-res/vlink
       5                 :            :  *  _    __   __      _           __
       6                 :            :  * | |  / /  / /     (_) ____    / /__
       7                 :            :  * | | / /  / /     / / / __ \  / //_/
       8                 :            :  * | |/ /  / /___  / / / / / / / ,<
       9                 :            :  * |___/  /_____/ /_/ /_/ /_/ /_/|_|
      10                 :            :  *
      11                 :            :  * Licensed under the Apache License, Version 2.0 (the "License");
      12                 :            :  * you may not use this file except in compliance with the License.
      13                 :            :  * You may obtain a copy of the License at
      14                 :            :  *
      15                 :            :  *     http://www.apache.org/licenses/LICENSE-2.0
      16                 :            :  *
      17                 :            :  * Unless required by applicable law or agreed to in writing, software
      18                 :            :  * distributed under the License is distributed on an "AS IS" BASIS,
      19                 :            :  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      20                 :            :  * See the License for the specific language governing permissions and
      21                 :            :  * limitations under the License.
      22                 :            :  */
      23                 :            : 
      24                 :            : #include "./extension/discovery_viewer.h"
      25                 :            : 
      26                 :            : #include <algorithm>
      27                 :            : #include <charconv>
      28                 :            : #include <cstring>
      29                 :            : #include <ctime>
      30                 :            : #include <exception>
      31                 :            : #include <map>
      32                 :            : #include <memory>
      33                 :            : #include <shared_mutex>
      34                 :            : #include <string>
      35                 :            : #include <thread>
      36                 :            : #include <unordered_map>
      37                 :            : #include <unordered_set>
      38                 :            : #include <utility>
      39                 :            : #include <vector>
      40                 :            : 
      41                 :            : #include "./base/bytes.h"
      42                 :            : #include "./base/elapsed_timer.h"
      43                 :            : #include "./base/helpers.h"
      44                 :            : #include "./base/logger.h"
      45                 :            : #include "./base/utils.h"
      46                 :            : #include "./impl/url.h"
      47                 :            : 
      48                 :            : #if __has_include(<unistd.h>)
      49                 :            : #include <unistd.h>
      50                 :            : #endif
      51                 :            : 
      52                 :            : #ifdef _WIN32
      53                 :            : #include <Winsock2.h>
      54                 :            : #include <ws2tcpip.h>
      55                 :            : #else
      56                 :            : #ifdef __QNX__
      57                 :            : #include <sys/time.h>
      58                 :            : #endif
      59                 :            : #include <arpa/inet.h>
      60                 :            : #include <netinet/in.h>
      61                 :            : #include <sys/socket.h>
      62                 :            : #endif
      63                 :            : 
      64                 :            : #define VLINK_DISCOVERY_MULTICAST 1
      65                 :            : #define VLINK_DISCOVERY_OFFLINE 0
      66                 :            : 
      67                 :            : namespace vlink {
      68                 :            : 
      69                 :            : #ifdef _WIN32
      70                 :            : using SocketHandle = SOCKET;
      71                 :            : static constexpr SocketHandle kInvalidSocket = INVALID_SOCKET;
      72                 :            : #else
      73                 :            : using SocketHandle = int;
      74                 :            : static constexpr SocketHandle kInvalidSocket = -1;
      75                 :            : #endif
      76                 :            : 
      77                 :            : [[maybe_unused]] static constexpr int kCollectInterval = 500;
      78                 :            : [[maybe_unused]] static constexpr size_t kMaxTaskSize = 50000U;
      79                 :            : [[maybe_unused]] static constexpr uint32_t kMaxElapsedTime = 1000;
      80                 :            : [[maybe_unused]] static constexpr int kBroadcastBindPort = 51694;
      81                 :            : [[maybe_unused]] static constexpr size_t kBufferSize = 1024 * 1024U;
      82                 :            : 
      83                 :            : #if VLINK_DISCOVERY_MULTICAST
      84                 :            : [[maybe_unused]] static constexpr const char* kBroadcastAddress = "239.255.0.100";
      85                 :            : #else
      86                 :            : [[maybe_unused]] static constexpr const char* kBroadcastAddress = "255.255.255.255";
      87                 :            : #endif
      88                 :            : 
      89                 :         29 : [[maybe_unused]] static std::string node_count_to_string(size_t node_count) {
      90         [ +  + ]:         29 :   if (node_count > 9) {
      91         [ +  - ]:          1 :     return std::string("~");
      92                 :            :   }
      93                 :            : 
      94                 :         28 :   return std::to_string(node_count);
      95                 :            : }
      96                 :            : 
      97                 :            : // DiscoveryViewer::Impl
      98                 :            : struct DiscoveryViewer::Impl final {
      99                 :            :   struct Comparator final {
     100                 :       1342 :     bool operator()(const DiscoveryViewer::Info& lhs, const DiscoveryViewer::Info& rhs) const {
     101         [ +  + ]:       1342 :       if (lhs.sort_index < rhs.sort_index) {
     102                 :        101 :         return true;
     103         [ +  + ]:       1241 :       } else if (lhs.sort_index > rhs.sort_index) {
     104                 :         41 :         return false;
     105                 :            :       }
     106                 :            : 
     107         [ +  + ]:       1200 :       if (lhs.url < rhs.url) {
     108                 :        631 :         return true;
     109         [ +  + ]:        569 :       } else if (lhs.url > rhs.url) {
     110                 :        269 :         return false;
     111                 :            :       }
     112                 :            : 
     113         [ -  + ]:        300 :       if (lhs.type < rhs.type) {
     114                 :          0 :         return true;
     115         [ -  + ]:        300 :       } else if (lhs.type > rhs.type) {
     116                 :          0 :         return false;
     117                 :            :       }
     118                 :            : 
     119         [ -  + ]:        300 :       if (lhs.schema_type < rhs.schema_type) {
     120                 :          0 :         return true;
     121         [ -  + ]:        300 :       } else if (lhs.schema_type > rhs.schema_type) {
     122                 :          0 :         return false;
     123                 :            :       }
     124                 :            : 
     125         [ -  + ]:        300 :       if (lhs.ser_type < rhs.ser_type) {
     126                 :            :         return true;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     127         [ -  + ]:        300 :       } else if (lhs.ser_type > rhs.ser_type) {
     128                 :            :         return false;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     129                 :            :       }
     130                 :            : 
     131                 :        300 :       return lhs.process_list < rhs.process_list;
     132                 :            :     }
     133                 :            :   };
     134                 :            : 
     135                 :            :   DiscoveryViewer::FilterType filter_type{DiscoveryViewer::kFilterNone};
     136                 :            :   std::string native_hostname;
     137                 :            : 
     138                 :            :   std::map<DiscoveryViewer::Info, ElapsedTimer, Comparator> info_map;
     139                 :            :   std::map<std::pair<int, uint32_t>, std::string> process_map;
     140                 :            :   std::unordered_map<std::string, std::string> ser_map;
     141                 :            :   std::unordered_map<std::string, SchemaType> schema_type_map;
     142                 :            :   std::vector<DiscoveryViewer::Info> info_list;
     143                 :            : 
     144                 :            :   std::recursive_mutex mtx;
     145                 :            :   std::shared_mutex ser_mtx;
     146                 :            :   bool info_dirty{false};
     147                 :            : 
     148                 :            :   DiscoveryViewer::Callback callback;
     149                 :            :   std::thread thread;
     150                 :            :   std::vector<uint8_t> buffer;
     151                 :            :   Timer timer;
     152                 :            : 
     153                 :            :   SocketHandle sock{kInvalidSocket};
     154                 :            :   sockaddr_in address;
     155                 :            :   bool enable_native_discovery{false};
     156                 :            : #ifdef _WIN32
     157                 :            :   bool winsock_initialized{false};
     158                 :            : #endif
     159                 :            : };
     160                 :            : 
     161                 :            : // DiscoveryViewer::Process
     162                 :        612 : bool DiscoveryViewer::Process::operator<(const DiscoveryViewer::Process& target) const noexcept {
     163         [ +  + ]:        612 :   if (type < target.type) {
     164                 :          1 :     return true;
     165         [ -  + ]:        611 :   } else if (type > target.type) {
     166                 :          0 :     return false;
     167         [ +  + ]:        611 :   } else if (host < target.host) {
     168                 :          1 :     return true;
     169         [ +  + ]:        610 :   } else if (host > target.host) {
     170                 :          1 :     return false;
     171         [ +  + ]:        609 :   } else if (ip < target.ip) {
     172                 :          1 :     return true;
     173         [ -  + ]:        608 :   } else if (ip > target.ip) {
     174                 :          0 :     return false;
     175         [ +  + ]:        608 :   } else if (name < target.name) {
     176                 :          1 :     return true;
     177         [ -  + ]:        607 :   } else if (name > target.name) {
     178                 :          0 :     return false;
     179                 :            :   } else {
     180                 :        607 :     return pid < target.pid;
     181                 :            :   }
     182                 :            : }
     183                 :            : 
     184                 :            : // DiscoveryViewer::Info
     185                 :          9 : bool DiscoveryViewer::Info::operator<(const DiscoveryViewer::Info& target) const noexcept {
     186         [ +  + ]:          9 :   if (type < target.type) {
     187                 :          1 :     return true;
     188         [ -  + ]:          8 :   } else if (type > target.type) {
     189                 :          0 :     return false;
     190         [ +  + ]:          8 :   } else if (sort_index < target.sort_index) {
     191                 :          1 :     return true;
     192         [ +  + ]:          7 :   } else if (sort_index > target.sort_index) {
     193                 :          1 :     return false;
     194         [ +  + ]:          6 :   } else if (url < target.url) {
     195                 :          1 :     return true;
     196         [ +  + ]:          5 :   } else if (url > target.url) {
     197                 :          1 :     return false;
     198         [ +  + ]:          4 :   } else if (schema_type < target.schema_type) {
     199                 :          1 :     return true;
     200         [ -  + ]:          3 :   } else if (schema_type > target.schema_type) {
     201                 :          0 :     return false;
     202         [ +  + ]:          3 :   } else if (ser_type < target.ser_type) {
     203                 :          1 :     return true;
     204         [ -  + ]:          2 :   } else if (ser_type > target.ser_type) {
     205                 :          0 :     return false;
     206                 :            :   } else {
     207                 :          2 :     return process_list < target.process_list;
     208                 :            :   }
     209                 :            : }
     210                 :            : 
     211                 :            : // DiscoveryViewer
     212                 :        182 : ImplType DiscoveryViewer::convert_type(std::string_view str) {
     213         [ +  + ]:        182 :   if (str == "Ser") {
     214                 :          1 :     return kServer;
     215         [ +  + ]:        181 :   } else if (str == "Cli") {
     216                 :         25 :     return kClient;
     217         [ +  + ]:        156 :   } else if (str == "Pub") {
     218                 :         36 :     return kPublisher;
     219         [ +  + ]:        120 :   } else if (str == "Sub") {
     220                 :         92 :     return kSubscriber;
     221         [ +  + ]:         28 :   } else if (str == "Set") {
     222                 :          1 :     return kSetter;
     223         [ +  + ]:         27 :   } else if (str == "Get") {
     224                 :         23 :     return kGetter;
     225                 :            :   } else {
     226                 :          4 :     return kUnknownImplType;
     227                 :            :   }
     228                 :            : }
     229                 :            : 
     230                 :         20 : std::string DiscoveryViewer::convert_type_to_view(uint32_t type) {
     231   [ +  +  +  +  :         20 :   switch (type) {
          +  +  +  +  +  
                +  +  + ]
     232                 :          1 :     case kPublisher | kSubscriber:
     233         [ +  - ]:          1 :       return "Pub|Sub";
     234                 :          1 :     case kSetter | kGetter:
     235         [ +  - ]:          1 :       return "Set|Get";
     236                 :          1 :     case kServer | kClient:
     237         [ +  - ]:          1 :       return "Ser|Cli";
     238                 :          1 :     case kPublisher:
     239         [ +  - ]:          1 :       return "Pub|---";
     240                 :          1 :     case kSubscriber:
     241         [ +  - ]:          1 :       return "---|Sub";
     242                 :          1 :     case kSetter:
     243         [ +  - ]:          1 :       return "Set|---";
     244                 :          1 :     case kGetter:
     245         [ +  - ]:          1 :       return "---|Get";
     246                 :          1 :     case kServer:
     247         [ +  - ]:          1 :       return "Ser|---";
     248                 :          1 :     case kClient:
     249         [ +  - ]:          1 :       return "---|Cli";
     250                 :          1 :     case kPublisher | kGetter:
     251         [ +  - ]:          1 :       return "Pub|Get";
     252                 :          1 :     case kSetter | kSubscriber:
     253         [ +  - ]:          1 :       return "Set|Sub";
     254                 :          9 :     default:
     255         [ +  + ]:          9 :       if (type == (kPublisher | kSetter)) {
     256         [ +  - ]:          1 :         return "Pub|---";
     257         [ +  + ]:          8 :       } else if (type == (kPublisher | kSetter | kSubscriber)) {
     258         [ +  - ]:          1 :         return "Pub|Sub";
     259         [ +  + ]:          7 :       } else if (type == (kPublisher | kSetter | kGetter)) {
     260         [ +  - ]:          1 :         return "Pub|Get";
     261         [ +  + ]:          6 :       } else if (type == (kPublisher | kSetter | kSubscriber | kGetter)) {
     262         [ +  - ]:          1 :         return "Pub|Sub";
     263         [ +  + ]:          5 :       } else if (type == (kSubscriber | kGetter)) {
     264         [ +  - ]:          1 :         return "---|Sub";
     265         [ +  + ]:          4 :       } else if (type == (kSubscriber | kGetter | kPublisher)) {
     266         [ +  - ]:          1 :         return "Pub|Sub";
     267         [ +  + ]:          3 :       } else if (type == (kSubscriber | kGetter | kSetter)) {
     268         [ +  - ]:          1 :         return "Set|Sub";
     269                 :            :       } else {
     270         [ +  - ]:          2 :         return "???|???";
     271                 :            :       }
     272                 :            :   }
     273                 :            : }
     274                 :            : 
     275                 :         21 : std::string DiscoveryViewer::convert_type_to_view(uint32_t type, const std::vector<Process>& process_list) {
     276                 :         21 :   int left_cnt = 0;
     277                 :         21 :   int right_cnt = 0;
     278                 :            : 
     279   [ +  +  +  +  :         21 :   switch (type) {
          +  +  +  +  +  
                +  +  + ]
     280                 :          1 :     case kPublisher | kSubscriber:
     281         [ +  + ]:          3 :       for (const auto& process : process_list) {
     282         [ +  + ]:          2 :         if (process.type == kPublisher) {
     283                 :          1 :           ++left_cnt;
     284         [ +  - ]:          1 :         } else if (process.type == kSubscriber) {
     285                 :          1 :           ++right_cnt;
     286                 :            :         }
     287                 :            :       }
     288                 :            : 
     289   [ +  -  +  -  :          2 :       return "Pub*" + node_count_to_string(left_cnt) + "|Sub*" + node_count_to_string(right_cnt);
             +  -  +  - ]
     290                 :          1 :     case kSetter | kGetter:
     291         [ +  + ]:          4 :       for (const auto& process : process_list) {
     292         [ +  + ]:          3 :         if (process.type == kSetter) {
     293                 :          1 :           ++left_cnt;
     294         [ +  - ]:          2 :         } else if (process.type == kGetter) {
     295                 :          2 :           ++right_cnt;
     296                 :            :         }
     297                 :            :       }
     298                 :            : 
     299   [ +  -  +  -  :          2 :       return "Set*" + node_count_to_string(left_cnt) + "|Get*" + node_count_to_string(right_cnt);
             +  -  +  - ]
     300                 :          1 :     case kServer | kClient:
     301         [ +  + ]:          4 :       for (const auto& process : process_list) {
     302         [ +  + ]:          3 :         if (process.type == kServer) {
     303                 :          2 :           ++left_cnt;
     304         [ +  - ]:          1 :         } else if (process.type == kClient) {
     305                 :          1 :           ++right_cnt;
     306                 :            :         }
     307                 :            :       }
     308                 :            : 
     309   [ +  -  +  -  :          2 :       return "Ser*" + node_count_to_string(left_cnt) + "|Cli*" + node_count_to_string(right_cnt);
             +  -  +  - ]
     310                 :          2 :     case kPublisher:
     311         [ +  + ]:         23 :       for (const auto& process : process_list) {
     312         [ +  + ]:         21 :         if (process.type == kPublisher) {
     313                 :         16 :           ++left_cnt;
     314                 :            :         }
     315                 :            :       }
     316                 :            : 
     317   [ +  -  +  - ]:          4 :       return "Pub*" + node_count_to_string(left_cnt) + "|-----";
     318                 :          1 :     case kSubscriber:
     319         [ +  + ]:          7 :       for (const auto& process : process_list) {
     320         [ +  + ]:          6 :         if (process.type == kSubscriber) {
     321                 :          1 :           ++right_cnt;
     322                 :            :         }
     323                 :            :       }
     324                 :            : 
     325         [ +  - ]:          2 :       return "-----|Sub*" + node_count_to_string(right_cnt);
     326                 :          1 :     case kSetter:
     327         [ +  + ]:          7 :       for (const auto& process : process_list) {
     328         [ +  + ]:          6 :         if (process.type == kSetter) {
     329                 :          1 :           ++left_cnt;
     330                 :            :         }
     331                 :            :       }
     332                 :            : 
     333   [ +  -  +  - ]:          2 :       return "Set*" + node_count_to_string(left_cnt) + "|-----";
     334                 :          1 :     case kGetter:
     335         [ +  + ]:          7 :       for (const auto& process : process_list) {
     336         [ +  + ]:          6 :         if (process.type == kGetter) {
     337                 :          1 :           ++right_cnt;
     338                 :            :         }
     339                 :            :       }
     340                 :            : 
     341         [ +  - ]:          2 :       return "-----|Get*" + node_count_to_string(right_cnt);
     342                 :          1 :     case kServer:
     343         [ +  + ]:          7 :       for (const auto& process : process_list) {
     344         [ +  + ]:          6 :         if (process.type == kServer) {
     345                 :          1 :           ++left_cnt;
     346                 :            :         }
     347                 :            :       }
     348                 :            : 
     349   [ +  -  +  - ]:          2 :       return "Ser*" + node_count_to_string(left_cnt) + "|-----";
     350                 :          1 :     case kClient:
     351         [ +  + ]:          7 :       for (const auto& process : process_list) {
     352         [ +  + ]:          6 :         if (process.type == kClient) {
     353                 :          1 :           ++right_cnt;
     354                 :            :         }
     355                 :            :       }
     356                 :            : 
     357         [ +  - ]:          2 :       return "-----|Cli*" + node_count_to_string(right_cnt);
     358                 :            : 
     359                 :          1 :     case kPublisher | kGetter:
     360         [ +  + ]:          7 :       for (const auto& process : process_list) {
     361         [ +  + ]:          6 :         if (process.type == kPublisher) {
     362                 :          1 :           ++left_cnt;
     363         [ +  + ]:          5 :         } else if (process.type == kGetter) {
     364                 :          1 :           ++right_cnt;
     365                 :            :         }
     366                 :            :       }
     367                 :            : 
     368   [ +  -  +  -  :          2 :       return "Pub*" + node_count_to_string(left_cnt) + "|Get*" + node_count_to_string(right_cnt);
             +  -  +  - ]
     369                 :            : 
     370                 :          1 :     case kSetter | kSubscriber:
     371         [ +  + ]:          7 :       for (const auto& process : process_list) {
     372         [ +  + ]:          6 :         if (process.type == kSetter) {
     373                 :          1 :           ++left_cnt;
     374         [ +  + ]:          5 :         } else if (process.type == kSubscriber) {
     375                 :          1 :           ++right_cnt;
     376                 :            :         }
     377                 :            :       }
     378                 :            : 
     379   [ +  -  +  -  :          2 :       return "Set*" + node_count_to_string(left_cnt) + "|Sub*" + node_count_to_string(right_cnt);
             +  -  +  - ]
     380                 :          9 :     default:
     381         [ +  + ]:          9 :       if (type == (kPublisher | kSetter)) {
     382         [ +  + ]:          7 :         for (const auto& process : process_list) {
     383   [ +  +  +  + ]:          6 :           if (process.type == kPublisher || process.type == kSetter) {
     384                 :          2 :             ++left_cnt;
     385                 :            :           }
     386                 :            :         }
     387                 :            : 
     388   [ +  -  +  - ]:          2 :         return "Pub*" + node_count_to_string(left_cnt) + "|-----";
     389         [ +  + ]:          8 :       } else if (type == (kPublisher | kSetter | kSubscriber)) {
     390         [ +  + ]:          7 :         for (const auto& process : process_list) {
     391   [ +  +  +  + ]:          6 :           if (process.type == kPublisher || process.type == kSetter) {
     392                 :          2 :             ++left_cnt;
     393         [ +  + ]:          4 :           } else if (process.type == kSubscriber) {
     394                 :          1 :             ++right_cnt;
     395                 :            :           }
     396                 :            :         }
     397                 :            : 
     398   [ +  -  +  -  :          2 :         return "Pub*" + node_count_to_string(left_cnt) + "|Sub*" + node_count_to_string(right_cnt);
             +  -  +  - ]
     399         [ +  + ]:          7 :       } else if (type == (kPublisher | kSetter | kGetter)) {
     400         [ +  + ]:          7 :         for (const auto& process : process_list) {
     401   [ +  +  +  + ]:          6 :           if (process.type == kPublisher || process.type == kSetter) {
     402                 :          2 :             ++left_cnt;
     403         [ +  + ]:          4 :           } else if (process.type == kGetter) {
     404                 :          1 :             ++right_cnt;
     405                 :            :           }
     406                 :            :         }
     407                 :            : 
     408   [ +  -  +  -  :          2 :         return "Pub*" + node_count_to_string(left_cnt) + "|Get*" + node_count_to_string(right_cnt);
             +  -  +  - ]
     409         [ +  + ]:          6 :       } else if (type == (kPublisher | kSetter | kSubscriber | kGetter)) {
     410         [ +  + ]:          7 :         for (const auto& process : process_list) {
     411   [ +  +  +  + ]:          6 :           if (process.type == kPublisher || process.type == kSetter) {
     412                 :          2 :             ++left_cnt;
     413   [ +  +  +  + ]:          4 :           } else if (process.type == kSubscriber || process.type == kGetter) {
     414                 :          2 :             ++right_cnt;
     415                 :            :           }
     416                 :            :         }
     417                 :            : 
     418   [ +  -  +  -  :          2 :         return "Pub*" + node_count_to_string(left_cnt) + "|Sub*" + node_count_to_string(right_cnt);
             +  -  +  - ]
     419         [ +  + ]:          5 :       } else if (type == (kSubscriber | kGetter)) {
     420         [ +  + ]:          7 :         for (const auto& process : process_list) {
     421   [ +  +  +  + ]:          6 :           if (process.type == kSubscriber || process.type == kGetter) {
     422                 :          2 :             ++right_cnt;
     423                 :            :           }
     424                 :            :         }
     425                 :            : 
     426         [ +  - ]:          2 :         return "-----|Sub*" + node_count_to_string(right_cnt);
     427         [ +  + ]:          4 :       } else if (type == (kSubscriber | kGetter | kPublisher)) {
     428         [ +  + ]:          7 :         for (const auto& process : process_list) {
     429         [ +  + ]:          6 :           if (process.type == kPublisher) {
     430                 :          1 :             ++left_cnt;
     431   [ +  +  +  + ]:          5 :           } else if (process.type == kSubscriber || process.type == kGetter) {
     432                 :          2 :             ++right_cnt;
     433                 :            :           }
     434                 :            :         }
     435                 :            : 
     436   [ +  -  +  -  :          2 :         return "Pub*" + node_count_to_string(left_cnt) + "|Sub*" + node_count_to_string(right_cnt);
             +  -  +  - ]
     437         [ +  + ]:          3 :       } else if (type == (kSubscriber | kGetter | kSetter)) {
     438         [ +  + ]:          7 :         for (const auto& process : process_list) {
     439         [ +  + ]:          6 :           if (process.type == kSetter) {
     440                 :          1 :             ++left_cnt;
     441   [ +  +  +  + ]:          5 :           } else if (process.type == kSubscriber || process.type == kGetter) {
     442                 :          2 :             ++right_cnt;
     443                 :            :           }
     444                 :            :         }
     445                 :            : 
     446   [ +  -  +  -  :          2 :         return "Set*" + node_count_to_string(left_cnt) + "|Sub*" + node_count_to_string(right_cnt);
             +  -  +  - ]
     447                 :            :       } else {
     448         [ +  - ]:          2 :         return "?????|?????";
     449                 :            :       }
     450                 :            :   }
     451                 :            : }
     452                 :            : 
     453         [ +  - ]:          2 : std::string DiscoveryViewer::get_listen_address() { return kBroadcastAddress; }
     454                 :            : 
     455         [ +  - ]:         32 : DiscoveryViewer::DiscoveryViewer(FilterType type) : impl_(std::make_unique<Impl>()) {
     456   [ +  -  +  - ]:         32 :   set_name("DiscoveryViewer");
     457                 :            : 
     458   [ +  -  +  - ]:         64 :   const std::string native_discovery = Utils::get_env("VLINK_DISCOVER_NATIVE");
     459                 :            : 
     460         [ -  + ]:         32 :   if (native_discovery == "1") {
     461                 :          0 :     impl_->enable_native_discovery = true;
     462                 :            :   }
     463                 :            : 
     464         [ +  - ]:         32 :   impl_->ser_map.reserve(128);
     465         [ +  - ]:         32 :   impl_->schema_type_map.reserve(128);
     466                 :            : 
     467                 :         32 :   impl_->filter_type = type;
     468                 :         32 :   impl_->native_hostname = Utils::get_host_name();
     469                 :            : 
     470                 :            : #ifdef _WIN32
     471                 :            :   ::WSADATA wsa_data;
     472                 :            : 
     473                 :            :   if VUNLIKELY (::WSAStartup(MAKEWORD(2, 2), &wsa_data) != 0) {
     474                 :            :     VLOG_F("DiscoveryViewer: Failed to initialize winsock.");
     475                 :            :     return;
     476                 :            :   }
     477                 :            : 
     478                 :            :   impl_->winsock_initialized = true;
     479                 :            : #endif
     480                 :            : 
     481                 :            :   // timer
     482         [ +  - ]:         32 :   impl_->timer.set_interval(kCollectInterval);
     483         [ +  - ]:         32 :   impl_->timer.set_loop_count(Timer::kInfinite);
     484         [ +  - ]:         32 :   impl_->timer.attach(this);
     485                 :            : 
     486   [ +  -  +  - ]:        202 :   impl_->timer.start([this]() { process_timeout(); });
     487                 :            : 
     488                 :            :   // socket init
     489                 :         32 :   impl_->sock = ::socket(AF_INET, SOCK_DGRAM, 0);
     490                 :            : 
     491         [ -  + ]:         32 :   if VUNLIKELY (impl_->sock == kInvalidSocket) {
     492                 :            :     VLOG_F("DiscoveryViewer: Failed to create socket.");  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     493                 :            :     return;                                               // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     494                 :            :   }
     495                 :            : 
     496                 :            : #ifdef _WIN32
     497                 :            :   DWORD timeout = 100U;
     498                 :            : 
     499                 :            :   if VUNLIKELY (::setsockopt(impl_->sock, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<const char*>(&timeout),
     500                 :            :                              sizeof(timeout)) < 0) {
     501                 :            :     VLOG_F("DiscoveryViewer: Failed to set receive timeout.");
     502                 :            :     return;
     503                 :            :   }
     504                 :            : #else
     505                 :            :   timeval timeout;
     506                 :         32 :   std::memset(&timeout, 0, sizeof(timeout));
     507                 :         32 :   timeout.tv_sec = 0U;
     508                 :         32 :   timeout.tv_usec = 1000 * 100U;
     509                 :            : 
     510         [ -  + ]:         32 :   if VUNLIKELY (::setsockopt(impl_->sock, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<const char*>(&timeout),
     511                 :            :                              sizeof(timeout)) < 0) {
     512                 :            :     VLOG_F("DiscoveryViewer: Failed to set receive timeout.");  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     513                 :            :     return;                                                     // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     514                 :            :   }
     515                 :            : #endif
     516                 :            : 
     517                 :         32 :   int enable_reuse_addr = 1;
     518                 :            : 
     519         [ -  + ]:         32 :   if VUNLIKELY (::setsockopt(impl_->sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<const char*>(&enable_reuse_addr),
     520                 :            :                              sizeof(enable_reuse_addr)) < 0) {
     521                 :            :     VLOG_F("DiscoveryViewer: Failed to set reuse address option.");  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     522                 :            :     return;                                                          // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     523                 :            :   }
     524                 :            : 
     525                 :            : #ifdef SO_REUSEPORT
     526                 :         32 :   int enable_reuse_port = 1;
     527                 :            : 
     528         [ -  + ]:         32 :   if VUNLIKELY (::setsockopt(impl_->sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast<const char*>(&enable_reuse_port),
     529                 :            :                              sizeof(enable_reuse_port)) < 0) {
     530                 :            :     VLOG_F("DiscoveryViewer: Failed to set reuse port option.");  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     531                 :            :     return;                                                       // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     532                 :            :   }
     533                 :            : #endif
     534                 :            : 
     535                 :         32 :   std::memset(&impl_->address, 0, sizeof(impl_->address));
     536                 :         32 :   impl_->address.sin_family = AF_INET;
     537                 :         32 :   impl_->address.sin_port = htons(kBroadcastBindPort);
     538                 :         32 :   impl_->address.sin_addr.s_addr = htonl(INADDR_ANY);
     539                 :            : 
     540         [ -  + ]:         32 :   if VUNLIKELY (::bind(impl_->sock, reinterpret_cast<sockaddr*>(&impl_->address), sizeof(impl_->address)) < 0) {
     541                 :            :     VLOG_F("DiscoveryViewer: Failed to bind socket.");  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     542                 :            :     return;                                             // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     543                 :            :   }
     544                 :            : 
     545                 :            : #if VLINK_DISCOVERY_MULTICAST
     546                 :            :   ip_mreq mreq;
     547                 :         32 :   std::memset(&mreq, 0, sizeof(mreq));
     548                 :            : 
     549         [ -  + ]:         32 :   if (impl_->enable_native_discovery) {
     550                 :          0 :     mreq.imr_multiaddr.s_addr = inet_addr(kBroadcastAddress);
     551                 :          0 :     mreq.imr_interface.s_addr = inet_addr("127.0.0.1");
     552                 :            : 
     553         [ #  # ]:          0 :     if VUNLIKELY (::setsockopt(impl_->sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, reinterpret_cast<const char*>(&mreq),
     554                 :            :                                sizeof(mreq)) < 0) {
     555                 :            :       VLOG_F("DiscoveryViewer: Failed to send multicast to 127.0.0.1.");  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     556                 :            :       return;                                                             // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     557                 :            :     }
     558                 :            :   } else {
     559                 :         32 :     mreq.imr_multiaddr.s_addr = inet_addr(kBroadcastAddress);
     560                 :         32 :     mreq.imr_interface.s_addr = htonl(INADDR_ANY);
     561                 :            : 
     562         [ -  + ]:         32 :     if VUNLIKELY (::setsockopt(impl_->sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, reinterpret_cast<const char*>(&mreq),
     563                 :            :                                sizeof(mreq)) < 0) {
     564                 :            : #ifdef __QNX__
     565                 :            :       CLOG_F(
     566                 :            :           "DiscoveryViewer: Failed to set multicast, please add address [%s] to target device. "
     567                 :            :           "\nExamples(QNX): route add -net %s -interface eth0.",
     568                 :            :           kBroadcastAddress, kBroadcastAddress);
     569                 :            : #elif defined(__APPLE__)
     570                 :            :       CLOG_F(
     571                 :            :           "DiscoveryViewer: Failed to set multicast, please add address [%s] to target device. "
     572                 :            :           "\nExamples(MACOS): route add -net %s -interface eth0.",
     573                 :            :           kBroadcastAddress, kBroadcastAddress);
     574                 :            : #else
     575                 :            :       CLOG_F(  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     576                 :            :           "DiscoveryViewer: Failed to set multicast, please add address [%s] to target device. "
     577                 :            :           "\nExamples(Linux): route add %s eth0.",
     578                 :            :           kBroadcastAddress, kBroadcastAddress);
     579                 :            : #endif
     580                 :            :       return;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     581                 :            :     }
     582                 :            :   }
     583                 :            : 
     584                 :            : #else
     585                 :            :   int enable_broadcast = 1;
     586                 :            : 
     587                 :            :   if VUNLIKELY (::setsockopt(impl_->sock, SOL_SOCKET, SO_BROADCAST, reinterpret_cast<const char*>(&enable_broadcast),
     588                 :            :                              sizeof(enable_broadcast)) < 0) {
     589                 :            :     VLOG_F("DiscoveryViewer: Failed to enable broadcast.");
     590                 :            :     return;
     591                 :            :   }
     592                 :            : #endif
     593                 :            : 
     594         [ +  - ]:         32 :   impl_->buffer.resize(kBufferSize);
     595                 :         32 :   std::memset(impl_->buffer.data(), 0, impl_->buffer.size());
     596                 :            : 
     597                 :          3 :   static auto global_ip_set = []() {
     598                 :          3 :     auto list = Utils::get_all_ipv4_address();
     599         [ +  - ]:          6 :     return std::unordered_set<std::string>(list.begin(), list.end());
     600   [ +  +  +  -  :         35 :   }();
             +  -  -  - ]
     601                 :            : 
     602                 :            :   // socket listen
     603         [ +  - ]:         64 :   impl_->thread = std::thread([this]() {
     604                 :            :     sockaddr_in target_address;
     605                 :         32 :     socklen_t target_address_len = sizeof(target_address);
     606                 :         32 :     std::memset(&target_address, 0, target_address_len);
     607                 :            : 
     608                 :         32 :     char target_ip[INET_ADDRSTRLEN] = {0};
     609                 :            : 
     610                 :            :     for (;;) {
     611         [ +  - ]:        931 :       int size = ::recvfrom(impl_->sock, reinterpret_cast<char*>(impl_->buffer.data()), impl_->buffer.size(), 0,
     612                 :        931 :                             reinterpret_cast<sockaddr*>(&target_address), &target_address_len);
     613                 :            : 
     614   [ +  -  +  + ]:        931 :       if VUNLIKELY (is_ready_to_quit()) {
     615                 :         32 :         break;
     616                 :            :       }
     617                 :            : 
     618   [ +  +  -  +  :        899 :       if VUNLIKELY (size <= 0 || static_cast<size_t>(size) > impl_->buffer.size()) {
                   +  + ]
     619                 :        737 :         continue;
     620                 :            :       }
     621                 :            : 
     622         [ -  + ]:        162 :       if VUNLIKELY (::inet_ntop(AF_INET, &target_address.sin_addr, target_ip, INET_ADDRSTRLEN) == nullptr) {
     623                 :            :         continue;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     624                 :            :       }
     625                 :            : 
     626         [ +  + ]:        162 :       if (impl_->filter_type == kFilterNative) {
     627   [ +  -  +  -  :        140 :         if (global_ip_set.count(target_ip) == 0) {
                   -  + ]
     628                 :            :           continue;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     629                 :            :         }
     630                 :            :       }
     631                 :            : 
     632                 :        162 :       std::string message = Bytes::shallow_copy(impl_->buffer.data(), size).to_string();
     633                 :            : 
     634   [ +  -  -  + ]:        162 :       if VUNLIKELY (!is_running()) {
     635                 :            :         Utils::yield_cpu();  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     636                 :            :         continue;            // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     637                 :            :       }
     638                 :            : 
     639   [ +  -  +  -  :        162 :       post_task([this, message, target_ip_str = std::string(target_ip)]() {
             +  -  +  - ]
     640                 :            : #if VLINK_DISCOVERY_OFFLINE
     641                 :            :         if VUNLIKELY (Helpers::has_startwith(message, "offline")) {
     642                 :            :           auto offline_list_view = Helpers::split_view(message, '\n');
     643                 :            : 
     644                 :            :           if VUNLIKELY (offline_list_view.size() < 2) {
     645                 :            :             return;
     646                 :            :           }
     647                 :            : 
     648                 :            :           if VUNLIKELY (offline_list_view.at(0) != "offline") {
     649                 :            :             return;
     650                 :            :           }
     651                 :            : 
     652                 :            :           auto process_view = offline_list_view.at(1);
     653                 :            : 
     654                 :            :           auto process_list_view = Helpers::split_view(process_view, ':');
     655                 :            : 
     656                 :            :           if VUNLIKELY (process_list_view.size() < 3) {
     657                 :            :             return;
     658                 :            :           }
     659                 :            : 
     660                 :            :           uint32_t process_pid = 0;
     661                 :            : 
     662                 :            :           const std::string hostname = Helpers::unescape_field(process_list_view.at(0));
     663                 :            : 
     664                 :            :           auto process_pid_view = process_list_view.at(1);
     665                 :            : 
     666                 :            :           auto [ptr, error] =
     667                 :            :               std::from_chars(process_pid_view.data(), process_pid_view.data() + process_pid_view.size(), process_pid);
     668                 :            : 
     669                 :            :           if VUNLIKELY (error != std::errc{}) {
     670                 :            :             process_pid = 0;
     671                 :            :           }
     672                 :            : 
     673                 :            :           const std::string process_name = Helpers::unescape_field(process_list_view.at(2));
     674                 :            : 
     675                 :            :           process_offline(hostname, process_pid, process_name);
     676                 :            : 
     677                 :            :           return;
     678                 :            :         }
     679                 :            : #endif
     680                 :            : 
     681                 :        162 :         auto message_list_view = Helpers::split_view(message, '\n');
     682                 :            : 
     683         [ -  + ]:        162 :         if VUNLIKELY (message_list_view.empty()) {
     684                 :            :           return;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     685                 :            :         }
     686                 :            : 
     687                 :            :         {
     688         [ +  - ]:        162 :           std::lock_guard lock(impl_->mtx);
     689                 :            : 
     690         [ +  + ]:        334 :           for (auto str : message_list_view) {
     691                 :        172 :             auto list = Helpers::split_view(str, ' ');
     692                 :            : 
     693         [ -  + ]:        172 :             if VUNLIKELY (list.size() < 5) {
     694                 :          0 :               continue;
     695                 :            :             }
     696                 :            : 
     697   [ +  -  +  - ]:        172 :             ImplType type = convert_type(list.at(0));
     698         [ +  - ]:        172 :             auto url_view = list.at(1);
     699         [ +  - ]:        172 :             auto ser_type_view = list.at(2);
     700         [ +  - ]:        172 :             auto schema_type_view = list.at(3);
     701         [ +  - ]:        172 :             auto process_view = list.at(4);
     702                 :            : 
     703                 :        172 :             uint32_t schema_type_num = 0;
     704                 :        172 :             SchemaType schema_type = SchemaType::kUnknown;
     705                 :        172 :             uint32_t process_pid = 0;
     706                 :        172 :             double profiler = -1;
     707                 :            : 
     708                 :            :             {
     709         [ +  - ]:        172 :               auto [ptr, error] = std::from_chars(schema_type_view.data(),
     710                 :        172 :                                                   schema_type_view.data() + schema_type_view.size(), schema_type_num);
     711                 :            : 
     712         [ -  + ]:        172 :               if VUNLIKELY (error != std::errc{}) {
     713                 :          0 :                 schema_type_num = 0;
     714                 :            :               }
     715                 :            : 
     716         [ +  - ]:        172 :               if (error == std::errc{}) {
     717                 :        172 :                 auto parsed_schema_type = static_cast<SchemaType>(schema_type_num);
     718                 :            : 
     719         [ +  - ]:        172 :                 if (SchemaData::is_valid_type(parsed_schema_type)) {
     720                 :        172 :                   schema_type = parsed_schema_type;
     721                 :            :                 }
     722                 :            :               }
     723                 :            :             }
     724                 :            : 
     725                 :        172 :             auto process_list_view = Helpers::split_view(process_view, ':');
     726                 :            : 
     727         [ -  + ]:        172 :             if (process_list_view.size() < 3) {
     728                 :            :               continue;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     729                 :            :             }
     730                 :            : 
     731                 :        172 :             std::string url = Helpers::unescape_field(url_view);
     732                 :        172 :             std::string ser_type = Helpers::unescape_field(ser_type_view);
     733         [ +  - ]:        172 :             std::string hostname = Helpers::unescape_field(process_list_view.at(0));
     734                 :            : 
     735         [ +  + ]:        172 :             if (impl_->filter_type == kFilterAvailable) {
     736         [ -  + ]:         24 :               if (hostname != impl_->native_hostname) {
     737   [ #  #  #  # ]:          0 :                 if (Url::is_local_type(url)) {
     738                 :          0 :                   continue;
     739                 :            :                 }
     740                 :            :               }
     741         [ +  - ]:        148 :             } else if (impl_->filter_type == kFilterNative) {
     742         [ -  + ]:        148 :               if (hostname != impl_->native_hostname) {
     743                 :          0 :                 continue;
     744                 :            :               }
     745                 :            :             }
     746                 :            : 
     747         [ +  - ]:        172 :             auto process_pid_view = process_list_view.at(1);
     748                 :            : 
     749                 :            :             {
     750         [ +  - ]:        172 :               auto [ptr, error] = std::from_chars(process_pid_view.data(),
     751                 :        172 :                                                   process_pid_view.data() + process_pid_view.size(), process_pid);
     752                 :            : 
     753         [ -  + ]:        172 :               if VUNLIKELY (error != std::errc{}) {
     754                 :          0 :                 process_pid = 0;
     755                 :            :               }
     756                 :            :             }
     757                 :            : 
     758         [ +  - ]:        172 :             std::string process_name = Helpers::unescape_field(process_list_view.at(2));
     759                 :            : 
     760         [ -  + ]:        172 :             if (process_list_view.size() >= 4) {
     761         [ #  # ]:          0 :               auto profiler_view = process_list_view.at(3);
     762                 :            : 
     763                 :            :               try {
     764   [ #  #  #  # ]:          0 :                 profiler = std::stod(std::string(profiler_view));
     765         [ -  - ]:          0 :               } catch (std::exception&) {
     766                 :          0 :                 profiler = -1;
     767                 :          0 :               }
     768                 :            :             }
     769                 :            : 
     770   [ +  -  -  +  :        172 :             if VUNLIKELY (type == kUnknownImplType || url.empty()) {
                   -  + ]
     771                 :          0 :               continue;
     772                 :            :             }
     773                 :            : 
     774         [ +  - ]:        172 :             int sort_index = Url::get_sort_index(url);
     775                 :            : 
     776         [ -  + ]:        172 :             if VUNLIKELY (sort_index < 0) {
     777                 :            :               continue;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     778                 :            :             }
     779                 :            : 
     780         [ +  + ]:        172 :             if (ser_type == "{}") {
     781                 :         52 :               ser_type.clear();
     782                 :            :             }
     783                 :            : 
     784                 :        172 :             Info info{sort_index,  type,
     785                 :            :                       url,         ser_type,
     786   [ +  -  +  -  :        344 :                       schema_type, {Process{type, hostname, process_pid, process_name, target_ip_str, profiler}}};
          +  -  +  -  +  
          -  +  -  +  +  
                   -  - ]
     787                 :            : 
     788         [ +  - ]:        172 :             auto [iter, inserted] = impl_->info_map.try_emplace(std::move(info), ElapsedTimer{});
     789                 :        172 :             iter->second.restart();
     790                 :            : 
     791         [ +  + ]:        172 :             if (!inserted) {
     792                 :         33 :               auto& existing_info = const_cast<Info&>(iter->first);
     793                 :         33 :               existing_info.process_list[0].profiler = profiler;
     794                 :            :             }
     795                 :            : 
     796                 :        172 :             impl_->info_dirty = true;
     797                 :            : 
     798         [ +  - ]:        172 :             if (!url.empty()) {
     799                 :        172 :               std::string merged_ser_type;
     800                 :        172 :               SchemaType merged_schema_type = SchemaType::kUnknown;
     801                 :        172 :               bool has_ser_conflict = false;
     802                 :        172 :               bool has_schema_conflict = false;
     803                 :            : 
     804         [ +  + ]:       1094 :               for (const auto& [active_info, active_timer] : impl_->info_map) {
     805                 :            :                 (void)active_timer;
     806                 :            : 
     807         [ +  + ]:        922 :                 if (active_info.url != url) {
     808                 :        750 :                   continue;
     809                 :            :                 }
     810                 :            : 
     811         [ +  + ]:        172 :                 if (!active_info.ser_type.empty()) {
     812   [ -  +  -  -  :        120 :                   if (merged_ser_type.empty() || merged_ser_type == "Bytes") {
                   +  - ]
     813         [ +  - ]:        120 :                     merged_ser_type = active_info.ser_type;
     814   [ #  #  #  #  :          0 :                   } else if (active_info.ser_type != "Bytes" && active_info.ser_type != merged_ser_type) {
                   #  # ]
     815                 :          0 :                     has_ser_conflict = true;
     816                 :            :                   }
     817                 :            :                 }
     818                 :            : 
     819   [ +  -  -  + ]:        172 :                 if (has_schema_conflict || active_info.schema_type == SchemaType::kUnknown) {
     820                 :          0 :                   continue;
     821                 :            :                 }
     822                 :            : 
     823         [ +  - ]:        172 :                 if (merged_schema_type == SchemaType::kUnknown) {
     824                 :        172 :                   merged_schema_type = active_info.schema_type;
     825         [ #  # ]:          0 :                 } else if (merged_schema_type != active_info.schema_type) {
     826                 :          0 :                   merged_schema_type = SchemaType::kUnknown;
     827                 :          0 :                   has_schema_conflict = true;
     828                 :            :                 }
     829                 :            :               }
     830                 :            : 
     831         [ -  + ]:        172 :               if VUNLIKELY (has_ser_conflict) {
     832   [ #  #  #  # ]:          0 :                 CLOG_W(
     833                 :            :                     "DiscoveryViewer: Different ser: url = %s, current_ser = %s, new_ser = %s, process_name = %s, "
     834                 :            :                     "process_pid = %u.",
     835                 :            :                     url.c_str(), merged_ser_type.c_str(), ser_type.c_str(), process_name.c_str(), process_pid);
     836                 :          0 :                 merged_ser_type.clear();
     837                 :            :               }
     838                 :            : 
     839         [ +  - ]:        172 :               std::lock_guard ser_lock(impl_->ser_mtx);
     840                 :            : 
     841         [ +  + ]:        172 :               if (!merged_ser_type.empty()) {
     842   [ +  -  +  - ]:        120 :                 impl_->ser_map[url] = merged_ser_type;
     843                 :            :               } else {
     844         [ +  - ]:         52 :                 impl_->ser_map.erase(url);
     845                 :            :               }
     846                 :            : 
     847         [ +  - ]:        172 :               impl_->schema_type_map[url] = merged_schema_type;
     848                 :        172 :             }
     849   [ +  -  +  -  :        172 :           }
          +  -  +  -  +  
                -  +  - ]
     850                 :        162 :         }
     851                 :            : 
     852         [ +  - ]:        162 :         report_list();
     853         [ +  - ]:        162 :       });
     854         [ +  - ]:       1061 :     }
     855                 :         64 :   });
     856         [ +  - ]:         32 : }
     857                 :            : 
     858                 :         56 : DiscoveryViewer::~DiscoveryViewer() {
     859                 :         32 :   quit(true);
     860                 :            : 
     861         [ +  - ]:         32 :   if VLIKELY (impl_->sock != kInvalidSocket) {
     862                 :            : #ifdef _WIN32
     863                 :            :     ::shutdown(impl_->sock, SD_BOTH);
     864                 :            : #else
     865                 :         32 :     ::shutdown(impl_->sock, SHUT_RDWR);
     866                 :            : #endif
     867                 :            :   }
     868                 :            : 
     869                 :         32 :   wait_for_quit();
     870                 :            : 
     871         [ +  - ]:         32 :   if VLIKELY (impl_->thread.joinable()) {
     872                 :         32 :     impl_->thread.join();
     873                 :            :   }
     874                 :            : 
     875         [ +  - ]:         32 :   if VLIKELY (impl_->sock != kInvalidSocket) {
     876                 :            : #ifdef _WIN32
     877                 :            :     ::closesocket(impl_->sock);
     878                 :            : #else
     879                 :         32 :     ::close(impl_->sock);
     880                 :            : #endif
     881                 :         32 :     impl_->sock = kInvalidSocket;
     882                 :            :   }
     883                 :            : 
     884                 :            : #ifdef _WIN32
     885                 :            :   if VLIKELY (impl_->winsock_initialized) {
     886                 :            :     ::WSACleanup();
     887                 :            :     impl_->winsock_initialized = false;
     888                 :            :   }
     889                 :            : #endif
     890                 :            : 
     891                 :         32 :   impl_->buffer.clear();
     892                 :         56 : }
     893                 :            : 
     894                 :         25 : void DiscoveryViewer::register_callback(Callback&& callback) {
     895         [ +  - ]:         25 :   std::lock_guard lock(impl_->mtx);
     896                 :         25 :   impl_->callback = std::move(callback);
     897                 :         25 : }
     898                 :            : 
     899                 :          1 : std::vector<DiscoveryViewer::Info> DiscoveryViewer::get_info_list() {
     900         [ +  - ]:          1 :   std::lock_guard lock(impl_->mtx);
     901         [ +  - ]:          2 :   return impl_->info_list;
     902                 :          1 : }
     903                 :            : 
     904                 :          1 : std::string DiscoveryViewer::get_ser_type(const std::string& url) const {
     905         [ +  - ]:          1 :   std::shared_lock lock(impl_->ser_mtx);
     906         [ +  - ]:          1 :   auto iter = impl_->ser_map.find(url);
     907                 :            : 
     908         [ -  + ]:          1 :   if VLIKELY (iter != impl_->ser_map.end()) {
     909         [ #  # ]:          0 :     return iter->second;
     910                 :            :   }
     911                 :            : 
     912                 :          1 :   return {};
     913                 :          1 : }
     914                 :            : 
     915                 :          1 : SchemaType DiscoveryViewer::get_schema_type(const std::string& url) const {
     916         [ +  - ]:          1 :   std::shared_lock lock(impl_->ser_mtx);
     917         [ +  - ]:          1 :   auto iter = impl_->schema_type_map.find(url);
     918                 :            : 
     919         [ -  + ]:          1 :   if VLIKELY (iter != impl_->schema_type_map.end()) {
     920                 :          0 :     return iter->second;
     921                 :            :   }
     922                 :            : 
     923                 :          1 :   return SchemaType::kUnknown;
     924                 :          1 : }
     925                 :            : 
     926                 :        496 : size_t DiscoveryViewer::get_max_task_count() const { return kMaxTaskSize; }
     927                 :            : 
     928                 :       1488 : uint32_t DiscoveryViewer::get_max_elapsed_time() const { return kMaxElapsedTime; }
     929                 :            : 
     930                 :         21 : void DiscoveryViewer::on_begin() { MessageLoop::on_begin(); }
     931                 :            : 
     932                 :         21 : void DiscoveryViewer::on_end() { MessageLoop::on_end(); }
     933                 :            : 
     934                 :        170 : void DiscoveryViewer::process_timeout() {
     935                 :        170 :   std::vector<DiscoveryViewer::Info> erase_list;
     936                 :            : 
     937                 :            :   {
     938         [ +  - ]:        170 :     std::lock_guard lock(impl_->mtx);
     939                 :            : 
     940         [ +  + ]:        790 :     for (const auto& [info, elapsed] : impl_->info_map) {
     941   [ +  +  -  +  :        620 :       if (elapsed.get() > kCollectInterval * 4 || !elapsed.is_active()) {
                   +  + ]
     942         [ +  - ]:        117 :         erase_list.emplace_back(info);
     943                 :            :       }
     944                 :            :     }
     945                 :            : 
     946         [ +  + ]:        170 :     if (!erase_list.empty()) {
     947         [ +  + ]:        192 :       for (const auto& info : erase_list) {
     948         [ +  - ]:        117 :         impl_->info_map.erase(info);
     949                 :            :       }
     950                 :            : 
     951                 :         75 :       impl_->info_dirty = true;
     952                 :            :     }
     953                 :        170 :   }
     954                 :            : 
     955         [ +  - ]:        170 :   report_list();
     956                 :        170 : }
     957                 :            : 
     958                 :            : // LCOV_EXCL_START GCOVR_EXCL_START
     959                 :            : void DiscoveryViewer::process_offline(std::string_view hostname, uint32_t pid, std::string_view process_name) {
     960                 :            :   std::vector<std::pair<DiscoveryViewer::Info, DiscoveryViewer::Info>> updated_list;
     961                 :            : 
     962                 :            :   {
     963                 :            :     std::lock_guard lock(impl_->mtx);
     964                 :            : 
     965                 :            :     for (const auto& [info, timer] : impl_->info_map) {
     966                 :            :       auto it = std::find_if(info.process_list.begin(), info.process_list.end(),
     967                 :            :                              [pid, process_name, hostname](const auto& process) {
     968                 :            :                                return process.pid == pid && process.name == process_name && process.host == hostname;
     969                 :            :                              });
     970                 :            : 
     971                 :            :       if (it != info.process_list.end()) {
     972                 :            :         DiscoveryViewer::Info new_info = info;
     973                 :            :         auto offset = std::distance(info.process_list.begin(), it);
     974                 :            :         auto it_copy = new_info.process_list.begin() + offset;
     975                 :            : 
     976                 :            :         new_info.process_list.erase(it_copy);
     977                 :            :         updated_list.emplace_back(info, std::move(new_info));
     978                 :            :       }
     979                 :            :     }
     980                 :            : 
     981                 :            :     if (updated_list.empty()) {
     982                 :            :       return;
     983                 :            :     }
     984                 :            : 
     985                 :            :     for (const auto& [old_info, new_info] : updated_list) {
     986                 :            :       impl_->info_map.erase(old_info);
     987                 :            : 
     988                 :            :       if (!new_info.process_list.empty()) {
     989                 :            :         auto [it, inserted] = impl_->info_map.try_emplace(new_info, ElapsedTimer{});
     990                 :            :         it->second.restart();
     991                 :            :       }
     992                 :            :     }
     993                 :            : 
     994                 :            :     impl_->info_dirty = true;
     995                 :            :   }
     996                 :            : 
     997                 :            :   report_list();
     998                 :            : }
     999                 :            : // LCOV_EXCL_STOP GCOVR_EXCL_STOP
    1000                 :            : 
    1001                 :        237 : void DiscoveryViewer::sort_url() {
    1002                 :        237 :   impl_->info_list.clear();
    1003                 :        237 :   std::unordered_map<std::string, std::string> next_ser_map;
    1004                 :        237 :   std::unordered_map<std::string, SchemaType> next_schema_type_map;
    1005                 :        237 :   std::unordered_set<std::string> ser_conflict_urls;
    1006                 :        237 :   std::unordered_set<std::string> schema_conflict_urls;
    1007         [ +  - ]:        237 :   next_ser_map.reserve(impl_->info_map.size());
    1008         [ +  - ]:        237 :   next_schema_type_map.reserve(impl_->info_map.size());
    1009         [ +  - ]:        237 :   ser_conflict_urls.reserve(impl_->info_map.size());
    1010         [ +  - ]:        237 :   schema_conflict_urls.reserve(impl_->info_map.size());
    1011                 :            : 
    1012         [ +  + ]:       1430 :   for (const auto& [info, timer] : impl_->info_map) {
    1013         [ +  + ]:       1193 :     if VLIKELY (!impl_->info_list.empty()) {
    1014                 :        959 :       auto& merged = impl_->info_list.back();
    1015                 :            : 
    1016         [ -  + ]:        959 :       if (merged.url == info.url) {
    1017                 :          0 :         merged.type |= info.type;
    1018         [ #  # ]:          0 :         merged.process_list.insert(merged.process_list.end(), info.process_list.begin(), info.process_list.end());
    1019                 :            : 
    1020   [ #  #  #  #  :          0 :         if (!info.ser_type.empty() && ser_conflict_urls.count(info.url) == 0) {
             #  #  #  # ]
    1021   [ #  #  #  #  :          0 :           if (merged.ser_type.empty() || (merged.ser_type == "Bytes" && info.ser_type != "Bytes")) {
             #  #  #  # ]
    1022         [ #  # ]:          0 :             merged.ser_type = info.ser_type;
    1023   [ #  #  #  #  :          0 :           } else if (merged.ser_type != "Bytes" && info.ser_type != merged.ser_type) {
                   #  # ]
    1024                 :          0 :             merged.ser_type.clear();
    1025         [ #  # ]:          0 :             ser_conflict_urls.emplace(info.url);
    1026                 :            :           }
    1027                 :            :         }
    1028                 :            : 
    1029   [ #  #  #  #  :          0 :         if (info.schema_type != SchemaType::kUnknown && schema_conflict_urls.count(info.url) == 0) {
             #  #  #  # ]
    1030         [ #  # ]:          0 :           if (merged.schema_type == SchemaType::kUnknown) {
    1031                 :          0 :             merged.schema_type = info.schema_type;
    1032         [ #  # ]:          0 :           } else if (merged.schema_type != info.schema_type) {
    1033                 :          0 :             merged.schema_type = SchemaType::kUnknown;
    1034         [ #  # ]:          0 :             schema_conflict_urls.emplace(info.url);
    1035                 :            :           }
    1036                 :            :         }
    1037                 :            : 
    1038                 :          0 :         continue;
    1039                 :            :       }
    1040                 :            :     }
    1041                 :            : 
    1042         [ +  - ]:       1193 :     impl_->info_list.emplace_back(info);
    1043                 :            :   }
    1044                 :            : 
    1045         [ +  + ]:       1430 :   for (auto& info : impl_->info_list) {
    1046         [ -  + ]:       1193 :     if (info.process_list.size() > 1) {
    1047         [ #  # ]:          0 :       std::sort(info.process_list.begin(), info.process_list.end());
    1048   [ #  #  #  # ]:          0 :       info.process_list.erase(std::unique(info.process_list.begin(), info.process_list.end(),
    1049                 :          0 :                                           [](const auto& lhs, const auto& rhs) {
    1050   [ #  #  #  #  :          0 :                                             return lhs.type == rhs.type && lhs.host == rhs.host && lhs.pid == rhs.pid &&
                   #  # ]
    1051   [ #  #  #  # ]:          0 :                                                    lhs.name == rhs.name && lhs.ip == rhs.ip;
    1052                 :            :                                           }),
    1053                 :          0 :                               info.process_list.end());
    1054                 :            :     }
    1055                 :            : 
    1056         [ +  - ]:       1193 :     auto& ser_type = next_ser_map[info.url];
    1057         [ +  - ]:       1193 :     auto& schema_type = next_schema_type_map[info.url];
    1058                 :            : 
    1059   [ +  -  -  + ]:       1193 :     if (ser_conflict_urls.count(info.url) != 0) {
    1060                 :          0 :       ser_type.clear();
    1061   [ -  +  -  -  :       1193 :     } else if ((ser_type.empty() || ser_type == "Bytes") && !info.ser_type.empty()) {
             +  +  +  + ]
    1062         [ +  - ]:        890 :       ser_type = info.ser_type;
    1063                 :            :     }
    1064                 :            : 
    1065   [ +  -  -  + ]:       1193 :     if (schema_conflict_urls.count(info.url) != 0) {
    1066                 :          0 :       schema_type = SchemaType::kUnknown;
    1067         [ +  - ]:       1193 :     } else if (info.schema_type != SchemaType::kUnknown) {
    1068         [ +  - ]:       1193 :       if (schema_type == SchemaType::kUnknown) {
    1069                 :       1193 :         schema_type = info.schema_type;
    1070                 :            :       } else if (schema_type != info.schema_type) {  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1071                 :            :         schema_type = SchemaType::kUnknown;          // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1072                 :            :         schema_conflict_urls.emplace(info.url);      // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1073                 :            :       }
    1074                 :            :     }
    1075                 :            :   }
    1076                 :            : 
    1077                 :            :   {
    1078         [ +  - ]:        237 :     std::unique_lock lock(impl_->ser_mtx);
    1079                 :        237 :     impl_->ser_map.swap(next_ser_map);
    1080                 :        237 :     impl_->schema_type_map.swap(next_schema_type_map);
    1081                 :        237 :   }
    1082                 :        237 : }
    1083                 :            : 
    1084                 :        332 : void DiscoveryViewer::report_list() {
    1085                 :        332 :   Callback callback;
    1086                 :        332 :   std::vector<Info> info_list;
    1087                 :            : 
    1088                 :            :   {
    1089         [ +  - ]:        332 :     std::lock_guard lock(impl_->mtx);
    1090                 :            : 
    1091         [ +  + ]:        332 :     if (!impl_->info_dirty) {
    1092                 :         95 :       return;
    1093                 :            :     }
    1094                 :            : 
    1095         [ +  - ]:        237 :     sort_url();
    1096                 :            : 
    1097                 :        237 :     impl_->info_dirty = false;
    1098         [ +  - ]:        237 :     callback = impl_->callback;
    1099         [ +  - ]:        237 :     info_list = impl_->info_list;
    1100         [ +  + ]:        332 :   }
    1101                 :            : 
    1102         [ +  + ]:        237 :   if (callback) {
    1103         [ +  - ]:         22 :     callback(info_list);
    1104                 :            :   }
    1105   [ +  +  +  + ]:        427 : }
    1106                 :            : 
    1107                 :            : }  // namespace vlink

Generated by: LCOV version 1.14