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/qos_profile.h" 25 : : 26 : : #include <fstream> 27 : : #include <string> 28 : : #include <unordered_map> 29 : : 30 : : #include "./base/helpers.h" 31 : : #include "./base/logger.h" 32 : : #include "./base/utils.h" 33 : : 34 : : // json 35 : : #include <nlohmann/json.hpp> 36 : : 37 : : namespace vlink { 38 : : 39 : : // GlobalQosProfile 40 : : struct GlobalQosProfile final { 41 : 22 : GlobalQosProfile() { 42 [ + - ]: 22 : available_qos_map.emplace(QosProfile::kEvent.name, QosProfile::kEvent); 43 [ + - ]: 22 : available_qos_map.emplace(QosProfile::kMethod.name, QosProfile::kMethod); 44 [ + - ]: 22 : available_qos_map.emplace(QosProfile::kField.name, QosProfile::kField); 45 [ + - ]: 22 : available_qos_map.emplace(QosProfile::kSensor.name, QosProfile::kSensor); 46 [ + - ]: 22 : available_qos_map.emplace(QosProfile::kParameter.name, QosProfile::kParameter); 47 [ + - ]: 22 : available_qos_map.emplace(QosProfile::kService.name, QosProfile::kService); 48 [ + - ]: 22 : available_qos_map.emplace(QosProfile::kClock.name, QosProfile::kClock); 49 [ + - ]: 22 : available_qos_map.emplace(QosProfile::kStatic.name, QosProfile::kStatic); 50 [ + - ]: 22 : available_qos_map.emplace(QosProfile::kLight.name, QosProfile::kLight); 51 [ + - ]: 22 : available_qos_map.emplace(QosProfile::kPoor.name, QosProfile::kPoor); 52 [ + - ]: 22 : available_qos_map.emplace(QosProfile::kBetter.name, QosProfile::kBetter); 53 [ + - ]: 22 : available_qos_map.emplace(QosProfile::kBest.name, QosProfile::kBest); 54 [ + - ]: 22 : available_qos_map.emplace(QosProfile::kLarge.name, QosProfile::kLarge); 55 [ + - ]: 22 : available_qos_map.emplace(QosProfile::kAlarm.name, QosProfile::kAlarm); 56 [ + - ]: 22 : available_qos_map.emplace(QosProfile::kCommand.name, QosProfile::kCommand); 57 [ + - ]: 22 : available_qos_map.emplace(QosProfile::kLog.name, QosProfile::kLog); 58 : : 59 [ + - + - ]: 44 : const std::string qos_path = Utils::get_env("VLINK_QOS_CONFIG"); 60 : : 61 [ + - ]: 22 : if (qos_path.empty()) { 62 : 22 : return; 63 : : } 64 : : 65 : 0 : std::filesystem::path target_path; 66 : : 67 : : try { 68 : : #ifdef _WIN32 69 : : target_path = std::filesystem::path(Helpers::string_to_wstring(qos_path)); 70 : : #else 71 [ # # ]: 0 : target_path = std::filesystem::path(qos_path); 72 : : #endif 73 : : 74 [ # # # # ]: 0 : if VUNLIKELY (!std::filesystem::exists(target_path)) { 75 [ # # # # ]: 0 : VLOG_E("QosProfile: Qos config file does not exist."); 76 : 0 : return; 77 : : } 78 : : } catch (std::filesystem::filesystem_error&) { // LCOV_EXCL_LINE GCOVR_EXCL_LINE 79 : : VLOG_E("QosProfile: Qos config file does not exist."); // LCOV_EXCL_LINE GCOVR_EXCL_LINE 80 : : } // LCOV_EXCL_LINE GCOVR_EXCL_LINE 81 : : 82 : : try { 83 : 0 : nlohmann::json root_json; 84 : : 85 : : { 86 [ # # ]: 0 : std::ifstream file(target_path); 87 : : 88 [ # # ]: 0 : file >> root_json; 89 : : 90 [ # # ]: 0 : file.close(); 91 : 0 : } 92 : : 93 [ # # # # : 0 : if VUNLIKELY (root_json.empty() || !root_json.is_array()) { # # ] 94 [ # # # # ]: 0 : VLOG_E("QosProfile: Qos config file has wrong format."); 95 : 0 : return; 96 : : } 97 : : 98 [ # # # # : 0 : for (const auto& obj : root_json) { # # # # ] 99 [ # # ]: 0 : if VUNLIKELY (!obj.is_object()) { 100 [ # # # # ]: 0 : VLOG_E("QosProfile: Qos config file has wrong format."); 101 : 0 : return; 102 : : } 103 : : 104 : 0 : Qos qos; 105 : : 106 [ # # # # : 0 : if VUNLIKELY (!obj.contains("name") || !obj["name"].is_string()) { # # # # # # ] 107 [ # # # # ]: 0 : VLOG_E("QosProfile: Qos config file qos name missing or invalid."); 108 : 0 : return; 109 : : } 110 : : 111 [ # # # # ]: 0 : std::string qos_name = obj["name"]; 112 : : 113 [ # # ]: 0 : if VUNLIKELY (qos_name.empty()) { 114 [ # # # # ]: 0 : VLOG_E("QosProfile: Qos config file qos name is empty."); 115 : 0 : return; 116 : : } 117 : : 118 [ # # ]: 0 : if VUNLIKELY (qos_name.size() >= sizeof(qos.name)) { 119 [ # # # # ]: 0 : VLOG_E("QosProfile: Qos config file qos name length error."); 120 : 0 : return; 121 : : } 122 : : 123 : 0 : std::memcpy(qos.name, qos_name.data(), qos_name.size()); 124 : : 125 [ # # # # ]: 0 : if (obj.contains("reliability")) { 126 [ # # # # ]: 0 : auto tobj = obj["reliability"]; 127 : : 128 [ # # ]: 0 : if VUNLIKELY (!tobj.is_object()) { 129 [ # # # # ]: 0 : VLOG_E("QosProfile: Qos config file has wrong format."); 130 : 0 : return; 131 : : } 132 : : 133 [ # # # # ]: 0 : if (tobj.contains("kind")) { 134 [ # # # # ]: 0 : auto pkind = tobj["kind"]; 135 : : 136 [ # # # # : 0 : if (pkind == "BestEffort" || pkind == "kBestEffort") { # # ] 137 : 0 : qos.reliability.kind = Qos::Reliability::kBestEffort; 138 [ # # # # : 0 : } else if (pkind == "Reliable" || pkind == "kReliable") { # # ] 139 : 0 : qos.reliability.kind = Qos::Reliability::kReliable; 140 : : } else { 141 [ # # ]: 0 : qos.reliability.kind = pkind; 142 : : } 143 : 0 : } 144 : : 145 [ # # # # ]: 0 : if (tobj.contains("block_time")) { 146 [ # # # # ]: 0 : qos.reliability.block_time = tobj["block_time"]; 147 : : } 148 : : 149 [ # # # # ]: 0 : if (tobj.contains("heartbeat_time")) { 150 [ # # # # ]: 0 : qos.reliability.heartbeat_time = tobj["heartbeat_time"]; 151 : : } 152 [ # # ]: 0 : } 153 : : 154 [ # # # # ]: 0 : if (obj.contains("history")) { 155 [ # # # # ]: 0 : auto tobj = obj["history"]; 156 : : 157 [ # # ]: 0 : if VUNLIKELY (!tobj.is_object()) { 158 [ # # # # ]: 0 : VLOG_E("QosProfile: Qos config file has wrong format."); 159 : 0 : return; 160 : : } 161 : : 162 [ # # # # ]: 0 : if (tobj.contains("kind")) { 163 [ # # # # ]: 0 : auto pkind = tobj["kind"]; 164 : : 165 [ # # # # : 0 : if (pkind == "KeepLast" || pkind == "kKeepLast") { # # ] 166 : 0 : qos.history.kind = Qos::History::kKeepLast; 167 [ # # # # : 0 : } else if (pkind == "KeepAll" || pkind == "kKeepAll") { # # ] 168 : 0 : qos.history.kind = Qos::History::kKeepAll; 169 : : } else { 170 [ # # ]: 0 : qos.history.kind = pkind; 171 : : } 172 : 0 : } 173 : : 174 [ # # # # ]: 0 : if (tobj.contains("depth")) { 175 [ # # # # ]: 0 : qos.history.depth = tobj["depth"]; 176 : : } 177 [ # # ]: 0 : } 178 : : 179 [ # # # # ]: 0 : if (obj.contains("durability")) { 180 [ # # # # ]: 0 : auto tobj = obj["durability"]; 181 : : 182 [ # # ]: 0 : if VUNLIKELY (!tobj.is_object()) { 183 [ # # # # ]: 0 : VLOG_E("QosProfile: Qos config file has wrong format."); 184 : 0 : return; 185 : : } 186 : : 187 [ # # # # ]: 0 : if (tobj.contains("kind")) { 188 [ # # # # ]: 0 : auto pkind = tobj["kind"]; 189 : : 190 [ # # # # : 0 : if (pkind == "Volatile" || pkind == "kVolatile") { # # ] 191 : 0 : qos.durability.kind = Qos::Durability::kVolatile; 192 [ # # # # : 0 : } else if (pkind == "TransientLocal" || pkind == "kTransientLocal") { # # ] 193 : 0 : qos.durability.kind = Qos::Durability::kTransientLocal; 194 [ # # # # : 0 : } else if (pkind == "Transient" || pkind == "kTransient") { # # ] 195 : 0 : qos.durability.kind = Qos::Durability::kTransient; 196 [ # # # # : 0 : } else if (pkind == "Persistent" || pkind == "kPersistent") { # # ] 197 : 0 : qos.durability.kind = Qos::Durability::kPersistent; 198 : : } else { 199 [ # # ]: 0 : qos.durability.kind = pkind; 200 : : } 201 : 0 : } 202 [ # # ]: 0 : } 203 : : 204 [ # # # # ]: 0 : if (obj.contains("publish_mode")) { 205 [ # # # # ]: 0 : auto tobj = obj["publish_mode"]; 206 : : 207 [ # # ]: 0 : if VUNLIKELY (!tobj.is_object()) { 208 [ # # # # ]: 0 : VLOG_E("QosProfile: Qos config file has wrong format."); 209 : 0 : return; 210 : : } 211 : : 212 [ # # # # ]: 0 : if (tobj.contains("kind")) { 213 [ # # # # ]: 0 : auto pkind = tobj["kind"]; 214 : : 215 [ # # # # : 0 : if (pkind == "Sync" || pkind == "kSync") { # # ] 216 : 0 : qos.publish_mode.kind = Qos::PublishMode::kSync; 217 [ # # # # : 0 : } else if (pkind == "ASync" || pkind == "kASync") { # # ] 218 : 0 : qos.publish_mode.kind = Qos::PublishMode::kASync; 219 : : } else { 220 [ # # ]: 0 : qos.publish_mode.kind = pkind; 221 : : } 222 : 0 : } 223 [ # # ]: 0 : } 224 : : 225 [ # # # # ]: 0 : if (obj.contains("liveliness")) { 226 [ # # # # ]: 0 : auto tobj = obj["liveliness"]; 227 : : 228 [ # # ]: 0 : if VUNLIKELY (!tobj.is_object()) { 229 [ # # # # ]: 0 : VLOG_E("QosProfile: Qos config file has wrong format."); 230 : 0 : return; 231 : : } 232 : : 233 [ # # # # ]: 0 : if (tobj.contains("kind")) { 234 [ # # # # ]: 0 : auto pkind = tobj["kind"]; 235 : : 236 [ # # # # : 0 : if (pkind == "Automatic" || pkind == "kAutomatic") { # # ] 237 : 0 : qos.liveliness.kind = Qos::Liveliness::kAutomatic; 238 [ # # # # : 0 : } else if (pkind == "ManualParticipant" || pkind == "kManualParticipant") { # # ] 239 : 0 : qos.liveliness.kind = Qos::Liveliness::kManualParticipant; 240 [ # # # # : 0 : } else if (pkind == "ManualTopic" || pkind == "kManualTopic") { # # ] 241 : 0 : qos.liveliness.kind = Qos::Liveliness::kManualTopic; 242 : : } else { 243 [ # # ]: 0 : qos.liveliness.kind = pkind; 244 : : } 245 : 0 : } 246 : : 247 [ # # # # ]: 0 : if (tobj.contains("duration")) { 248 [ # # # # ]: 0 : qos.liveliness.duration = tobj["duration"]; 249 : : } 250 [ # # ]: 0 : } 251 : : 252 [ # # # # ]: 0 : if (obj.contains("destination_order")) { 253 [ # # # # ]: 0 : auto tobj = obj["destination_order"]; 254 : : 255 [ # # ]: 0 : if VUNLIKELY (!tobj.is_object()) { 256 [ # # # # ]: 0 : VLOG_E("QosProfile: Qos config file has wrong format."); 257 : 0 : return; 258 : : } 259 : : 260 [ # # # # ]: 0 : if (tobj.contains("kind")) { 261 [ # # # # ]: 0 : auto pkind = tobj["kind"]; 262 : : 263 [ # # # # : 0 : if (pkind == "ReceptionTimestamp" || pkind == "kReceptionTimestamp") { # # ] 264 : 0 : qos.destination_order.kind = Qos::DestinationOrder::kReceptionTimestamp; 265 [ # # # # : 0 : } else if (pkind == "SourceTimestamp" || pkind == "kSourceTimestamp") { # # ] 266 : 0 : qos.destination_order.kind = Qos::DestinationOrder::kSourceTimestamp; 267 : : } else { 268 [ # # ]: 0 : qos.destination_order.kind = pkind; 269 : : } 270 : 0 : } 271 [ # # ]: 0 : } 272 : : 273 [ # # # # ]: 0 : if (obj.contains("ownership")) { 274 [ # # # # ]: 0 : auto tobj = obj["ownership"]; 275 : : 276 [ # # ]: 0 : if VUNLIKELY (!tobj.is_object()) { 277 [ # # # # ]: 0 : VLOG_E("QosProfile: Qos config file has wrong format."); 278 : 0 : return; 279 : : } 280 : : 281 [ # # # # ]: 0 : if (tobj.contains("kind")) { 282 [ # # # # ]: 0 : auto pkind = tobj["kind"]; 283 : : 284 [ # # # # : 0 : if (pkind == "Shared" || pkind == "kShared") { # # ] 285 : 0 : qos.ownership.kind = Qos::Ownership::kShared; 286 [ # # # # : 0 : } else if (pkind == "Exclusive" || pkind == "kExclusive" || pkind == "ExClusive") { # # # # ] 287 : 0 : qos.ownership.kind = Qos::Ownership::kExclusive; 288 : : } else { 289 [ # # ]: 0 : qos.ownership.kind = pkind; 290 : : } 291 : 0 : } 292 [ # # ]: 0 : } 293 : : 294 [ # # # # ]: 0 : if (obj.contains("deadline")) { 295 [ # # # # ]: 0 : auto tobj = obj["deadline"]; 296 : : 297 [ # # ]: 0 : if VUNLIKELY (!tobj.is_object()) { 298 [ # # # # ]: 0 : VLOG_E("QosProfile: Qos config file has wrong format."); 299 : 0 : return; 300 : : } 301 : : 302 [ # # # # ]: 0 : if (tobj.contains("period")) { 303 [ # # # # ]: 0 : qos.deadline.period = tobj["period"]; 304 : : } 305 [ # # ]: 0 : } 306 : : 307 [ # # # # ]: 0 : if (obj.contains("lifespan")) { 308 [ # # # # ]: 0 : auto tobj = obj["lifespan"]; 309 : : 310 [ # # ]: 0 : if VUNLIKELY (!tobj.is_object()) { 311 [ # # # # ]: 0 : VLOG_E("QosProfile: Qos config file has wrong format."); 312 : 0 : return; 313 : : } 314 : : 315 [ # # # # ]: 0 : if (tobj.contains("duration")) { 316 [ # # # # ]: 0 : qos.lifespan.duration = tobj["duration"]; 317 : : } 318 [ # # ]: 0 : } 319 : : 320 [ # # # # ]: 0 : if (obj.contains("latency_budget")) { 321 [ # # # # ]: 0 : auto tobj = obj["latency_budget"]; 322 : : 323 [ # # ]: 0 : if VUNLIKELY (!tobj.is_object()) { 324 [ # # # # ]: 0 : VLOG_E("QosProfile: Qos config file has wrong format."); 325 : 0 : return; 326 : : } 327 : : 328 [ # # # # ]: 0 : if (tobj.contains("duration")) { 329 [ # # # # ]: 0 : qos.latency_budget.duration = tobj["duration"]; 330 : : } 331 [ # # ]: 0 : } 332 : : 333 [ # # # # ]: 0 : if (obj.contains("resource_limits")) { 334 [ # # # # ]: 0 : auto tobj = obj["resource_limits"]; 335 : : 336 [ # # ]: 0 : if VUNLIKELY (!tobj.is_object()) { 337 [ # # # # ]: 0 : VLOG_E("QosProfile: Qos config file has wrong format."); 338 : 0 : return; 339 : : } 340 : : 341 [ # # # # ]: 0 : if (tobj.contains("max_samples")) { 342 [ # # # # ]: 0 : qos.resource_limits.max_samples = tobj["max_samples"]; 343 : : } 344 : : 345 [ # # # # ]: 0 : if (tobj.contains("max_instances")) { 346 [ # # # # ]: 0 : qos.resource_limits.max_instances = tobj["max_instances"]; 347 : : } 348 : : 349 [ # # # # ]: 0 : if (tobj.contains("max_samples_per_instance")) { 350 [ # # # # ]: 0 : qos.resource_limits.max_samples_per_instance = tobj["max_samples_per_instance"]; 351 : : } 352 [ # # ]: 0 : } 353 : : 354 [ # # # # ]: 0 : if (obj.contains("additions")) { 355 [ # # # # ]: 0 : auto tobj = obj["additions"]; 356 : : 357 [ # # ]: 0 : if VUNLIKELY (!tobj.is_object()) { 358 [ # # # # ]: 0 : VLOG_E("QosProfile: Qos config file has wrong format."); 359 : 0 : return; 360 : : } 361 : : 362 [ # # # # ]: 0 : if (tobj.contains("priority")) { 363 [ # # # # ]: 0 : qos.additions.priority = static_cast<Qos::Additions::Priority>(tobj["priority"].get<uint8_t>()); 364 : : } 365 : : 366 [ # # # # ]: 0 : if (tobj.contains("is_express")) { 367 [ # # # # ]: 0 : qos.additions.is_express = tobj["is_express"]; 368 : : } 369 [ # # ]: 0 : } 370 : : 371 : 0 : qos.valid = true; 372 : : 373 [ # # # # ]: 0 : available_qos_map[qos.name] = qos; 374 [ # # ]: 0 : } 375 [ # # # # ]: 0 : } catch (nlohmann::json::exception& e) { 376 [ # # # # ]: 0 : VLOG_E("QosProfile: Qos config parse error: ", e.what(), "."); 377 : 0 : return; 378 : 0 : } 379 [ - - - + ]: 22 : } 380 : : 381 : 22 : ~GlobalQosProfile() = default; 382 : : 383 : : std::unordered_map<std::string, Qos> available_qos_map; 384 : : }; 385 : : 386 : : namespace QosProfile { 387 : : 388 : 27 : const std::unordered_map<std::string, Qos>& get_available_qos_map() noexcept { 389 [ + + + - ]: 27 : static GlobalQosProfile global_profile; 390 : : 391 : 27 : return global_profile.available_qos_map; 392 : : } 393 : : 394 : : } // namespace QosProfile 395 : : 396 : : } // namespace vlink