From f9d695577f256ac685d49945c17367cd5823cb4e Mon Sep 17 00:00:00 2001 From: Arkadiusz Szczepkowicz Date: Fri, 22 Dec 2023 17:07:55 +0100 Subject: [PATCH 01/10] #2229: Add attributes field for rank and object --- scripts/JSON_data_files_validator.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/JSON_data_files_validator.py b/scripts/JSON_data_files_validator.py index bacf268dde..273e66a08f 100644 --- a/scripts/JSON_data_files_validator.py +++ b/scripts/JSON_data_files_validator.py @@ -64,6 +64,7 @@ def _get_valid_schema(self) -> Schema: 'range': [[int]], }, }, + Optional('attributes'): dict }, 'phases': [ { @@ -77,7 +78,8 @@ def _get_valid_schema(self) -> Schema: Optional('index'): [int], 'type': str, 'migratable': bool, - Optional('objgroup_id'): int + Optional('objgroup_id'): int, + Optional('attributes'): dict }, 'node': int, 'resource': str, From e730ffd52176582c6a3104a9a88599494c7637b9 Mon Sep 17 00:00:00 2001 From: Arkadiusz Szczepkowicz Date: Fri, 5 Jan 2024 16:11:01 +0100 Subject: [PATCH 02/10] #2229: Add reading and writing of user attributes in LBDataHolder --- .../vrt/collection/balance/lb_data_holder.cc | 32 +++++++++++++++---- .../vrt/collection/balance/lb_data_holder.h | 11 +++++-- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/vt/vrt/collection/balance/lb_data_holder.cc b/src/vt/vrt/collection/balance/lb_data_holder.cc index 9ab01a380c..5483b05ba1 100644 --- a/src/vt/vrt/collection/balance/lb_data_holder.cc +++ b/src/vt/vrt/collection/balance/lb_data_holder.cc @@ -48,7 +48,7 @@ namespace vt { namespace vrt { namespace collection { namespace balance { -void LBDataHolder::outputEntity(nlohmann::json& j, ElementIDStruct const& id) const { +void LBDataHolder::outputEntity(PhaseType phase, nlohmann::json& j, ElementIDStruct const& id) const { j["type"] = "object"; j["id"] = id.id; j["home"] = id.getHomeNode(); @@ -66,6 +66,11 @@ void LBDataHolder::outputEntity(nlohmann::json& j, ElementIDStruct const& id) co } else { // bare handler } + if (node_user_attributes_.find(phase) != node_user_attributes_.end()) { + if (node_user_attributes_.at(phase).find(id) != node_user_attributes_.at(phase).end()) { + j["attributes"] = *(node_user_attributes_.at(phase).at(id)); + } + } } std::unique_ptr LBDataHolder::metadataToJson() const { @@ -117,6 +122,10 @@ std::unique_ptr LBDataHolder::metadataToJson() const { } } + if (rank_attributes_) { + j["attributes"] = *rank_attributes_; + } + // Save metadata j["skipped"]["list"] = skipped_list; j["skipped"]["range"] = skipped_ranges; @@ -148,7 +157,7 @@ std::unique_ptr LBDataHolder::toJson(PhaseType phase) const { } } } - outputEntity(j["tasks"][i]["entity"], id); + outputEntity(phase, j["tasks"][i]["entity"], id); auto const& subphase_times = elm.second.subphase_loads; std::size_t const subphases = subphase_times.size(); @@ -179,8 +188,8 @@ std::unique_ptr LBDataHolder::toJson(PhaseType phase) const { } else { j["communications"][i]["type"] = "Broadcast"; } - outputEntity(j["communications"][i]["from"], key.fromObj()); - outputEntity(j["communications"][i]["to"], key.toObj()); + outputEntity(phase, j["communications"][i]["from"], key.fromObj()); + outputEntity(phase, j["communications"][i]["to"], key.toObj()); break; } case elm::CommCategory::NodeToCollection: @@ -193,7 +202,7 @@ std::unique_ptr LBDataHolder::toJson(PhaseType phase) const { j["communications"][i]["from"]["type"] = "node"; j["communications"][i]["from"]["id"] = key.fromNode(); - outputEntity(j["communications"][i]["to"], key.toObj()); + outputEntity(phase, j["communications"][i]["to"], key.toObj()); break; } case elm::CommCategory::CollectionToNode: @@ -206,7 +215,7 @@ std::unique_ptr LBDataHolder::toJson(PhaseType phase) const { j["communications"][i]["to"]["type"] = "node"; j["communications"][i]["to"]["id"] = key.toNode(); - outputEntity(j["communications"][i]["from"], key.fromObj()); + outputEntity(phase, j["communications"][i]["from"], key.fromObj()); break; } case elm::CommCategory::LocalInvoke: @@ -274,6 +283,12 @@ LBDataHolder::LBDataHolder(nlohmann::json const& j) } } + if (task["entity"].find("attributes") != task["entity"].end()) { + auto attrs = task["entity"]["attributes"]; + node_user_attributes_[id][elm] = std::make_shared(); + *(node_user_attributes_[id][elm]) = attrs; + } + if (task.find("subphases") != task.end()) { auto subphases = task["subphases"]; if (subphases.is_array()) { @@ -441,6 +456,11 @@ void LBDataHolder::readMetadata(nlohmann::json const& j) { } } } + // load rank user atrributes + if (metadata.find("attributes") != metadata.end()) { + rank_attributes_ = std::make_shared(); + *(rank_attributes_) = metadata["attributes"]; + } } } diff --git a/src/vt/vrt/collection/balance/lb_data_holder.h b/src/vt/vrt/collection/balance/lb_data_holder.h index 3a69999d98..7173cf651c 100644 --- a/src/vt/vrt/collection/balance/lb_data_holder.h +++ b/src/vt/vrt/collection/balance/lb_data_holder.h @@ -85,6 +85,8 @@ struct LBDataHolder { s | skipped_phases_; s | identical_phases_; s | user_defined_lb_info_; + s | node_user_attributes_; + s | rank_attributes_; } /** @@ -97,7 +99,7 @@ struct LBDataHolder { std::unique_ptr toJson(PhaseType phase) const; /** - * \brief Output a LB phase's metdadata to JSON + * \brief Output a LB phase's metadata to JSON * * \return the json data structure */ @@ -112,10 +114,11 @@ struct LBDataHolder { /** * \brief Output an entity to json * + * \param[in] phase the phase * \param[in] j the json * \param[in] elm_id the element to output */ - void outputEntity(nlohmann::json& j, ElementIDStruct const& elm_id) const; + void outputEntity(PhaseType phase, nlohmann::json& j, ElementIDStruct const& elm_id) const; /** * \brief Read the LB phase's metadata @@ -125,6 +128,7 @@ struct LBDataHolder { void readMetadata(nlohmann::json const& j); public: + std::shared_ptr rank_attributes_; /// Node timings for each local object std::unordered_map node_data_; /// Node communication graph for each local object @@ -135,6 +139,9 @@ struct LBDataHolder { std::unordered_map >> user_defined_json_; + std::unordered_map + >> node_user_attributes_; std::unordered_map> user_per_phase_json_; /// User-defined data from each phase for LB From 41ac48e3297264f3c9b0e55d82530ac1a9d29591 Mon Sep 17 00:00:00 2001 From: Arkadiusz Szczepkowicz Date: Fri, 5 Jan 2024 16:11:28 +0100 Subject: [PATCH 03/10] #2229: Add tests for reading and writing user attributes in LBDataHolder --- tests/unit/collection/test_lb_data_holder.cc | 75 ++++++++++++++++++-- 1 file changed, 70 insertions(+), 5 deletions(-) diff --git a/tests/unit/collection/test_lb_data_holder.cc b/tests/unit/collection/test_lb_data_holder.cc index a5f9cb2c88..bd5def8609 100644 --- a/tests/unit/collection/test_lb_data_holder.cc +++ b/tests/unit/collection/test_lb_data_holder.cc @@ -58,7 +58,7 @@ namespace vt { namespace tests { namespace unit { namespace lb { static constexpr int const num_phases = 10; -struct TestOptionalPhasesMetadata : TestParallelHarnessParam { }; +struct TestLBDataHolder : TestParallelHarnessParam { }; void addPhasesDataToJson(nlohmann::json& json, PhaseType amountOfPhasesToAdd, std::vector toSkip) { using ElementIDStruct = vt::vrt::collection::balance::ElementIDStruct; @@ -90,7 +90,7 @@ void addPhasesDataToJson(nlohmann::json& json, PhaseType amountOfPhasesToAdd, st json["phases"] = phases; } -TEST_F(TestOptionalPhasesMetadata, test_no_metadata) { +TEST_F(TestLBDataHolder, test_no_metadata) { using LBDataHolder = vt::vrt::collection::balance::LBDataHolder; nlohmann::json json; @@ -117,7 +117,7 @@ TEST_F(TestOptionalPhasesMetadata, test_no_metadata) { EXPECT_EQ((*outJsonPtr)["identical_to_previous"]["range"], expectedIdenticalRanges); } -TEST_F(TestOptionalPhasesMetadata, test_no_lb_phases_metadata) { +TEST_F(TestLBDataHolder, test_no_lb_phases_metadata) { using LBDataHolder = vt::vrt::collection::balance::LBDataHolder; nlohmann::json json; @@ -146,7 +146,7 @@ TEST_F(TestOptionalPhasesMetadata, test_no_lb_phases_metadata) { EXPECT_EQ((*outJsonPtr)["identical_to_previous"]["range"], expectedIdenticalRanges); } -TEST_F(TestOptionalPhasesMetadata, test_lb_phases_metadata_empty) { +TEST_F(TestLBDataHolder, test_lb_phases_metadata_empty) { using LBDataHolder = vt::vrt::collection::balance::LBDataHolder; nlohmann::json metadata, phasesMetadata, json; @@ -182,7 +182,7 @@ TEST_F(TestOptionalPhasesMetadata, test_lb_phases_metadata_empty) { EXPECT_EQ((*outJsonPtr)["identical_to_previous"]["range"], expectedIdenticalRanges); } -TEST_F(TestOptionalPhasesMetadata, test_lb_phases_metadata_filled) { +TEST_F(TestLBDataHolder, test_lb_phases_metadata_filled) { using LBDataHolder = vt::vrt::collection::balance::LBDataHolder; nlohmann::json metadata, phasesMetadata, json; @@ -218,6 +218,71 @@ TEST_F(TestOptionalPhasesMetadata, test_lb_phases_metadata_filled) { EXPECT_EQ((*outJsonPtr)["identical_to_previous"]["range"], expectedIdenticalRanges); } +TEST_F(TestLBDataHolder, test_lb_rank_attributes) { + using LBDataHolder = vt::vrt::collection::balance::LBDataHolder; + + nlohmann::json json = R"( + { + "type": "LBDatafile", + "metadata" : { + "attributes": { + "some_val": 123 + } + }, + "phases" : [] + } + )"_json; + + LBDataHolder testObj(json); + EXPECT_TRUE(nullptr != testObj.rank_attributes_); + EXPECT_EQ(123, (*testObj.rank_attributes_)["some_val"]); + + auto outJsonPtr = testObj.metadataToJson(); + ASSERT_TRUE(outJsonPtr != nullptr); + EXPECT_EQ(123, (*outJsonPtr)["attributes"]["some_val"]); +} + +TEST_F(TestLBDataHolder, test_lb_entity_attributes) { + using LBDataHolder = vt::vrt::collection::balance::LBDataHolder; + + nlohmann::json json = R"( + { + "type": "LBDatafile", + "phases": [ + { + "id": 0, + "tasks": [ + { + "entity": { + "home": 0, + "id": 524291, + "type": "object", + "migratable": true, + "attributes": { + "some_val": 123 + } + }, + "node": 0, + "resource": "cpu", + "time": 3.0 + } + ] + } + ] + } + )"_json; + auto id = vt::vrt::collection::balance::ElementIDStruct{524291, 0}; + + LBDataHolder testObj(json); + EXPECT_TRUE(testObj.node_user_attributes_.find(0) != testObj.node_user_attributes_.end()); + EXPECT_TRUE(testObj.node_user_attributes_[0].find(id) != testObj.node_user_attributes_[0].end()); + EXPECT_EQ(123, (*testObj.node_user_attributes_[0][id])["some_val"]); + + auto outJsonPtr = testObj.toJson(0); + ASSERT_TRUE(outJsonPtr != nullptr); + EXPECT_EQ(123, (*outJsonPtr)["tasks"][0]["entity"]["attributes"]["some_val"]); +} + }}}} // end namespace vt::tests::unit::lb #endif /*vt_check_enabled(lblite)*/ From 6c024a5030147e15707218e8f4815d8a1e278828 Mon Sep 17 00:00:00 2001 From: Arkadiusz Szczepkowicz Date: Tue, 13 Feb 2024 12:50:04 +0100 Subject: [PATCH 04/10] #2229: Fix attributes output to metadata and erase old phases from node_user_attributes --- src/vt/vrt/collection/balance/lb_data_holder.cc | 4 ---- src/vt/vrt/collection/balance/node_lb_data.cc | 8 ++++++++ src/vt/vrt/collection/balance/node_lb_data.h | 7 +++++++ tests/unit/collection/test_lb_data_holder.cc | 4 ---- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/vt/vrt/collection/balance/lb_data_holder.cc b/src/vt/vrt/collection/balance/lb_data_holder.cc index 5483b05ba1..934332fed4 100644 --- a/src/vt/vrt/collection/balance/lb_data_holder.cc +++ b/src/vt/vrt/collection/balance/lb_data_holder.cc @@ -122,10 +122,6 @@ std::unique_ptr LBDataHolder::metadataToJson() const { } } - if (rank_attributes_) { - j["attributes"] = *rank_attributes_; - } - // Save metadata j["skipped"]["list"] = skipped_list; j["skipped"]["range"] = skipped_ranges; diff --git a/src/vt/vrt/collection/balance/node_lb_data.cc b/src/vt/vrt/collection/balance/node_lb_data.cc index 18828426cb..1b37997bdc 100644 --- a/src/vt/vrt/collection/balance/node_lb_data.cc +++ b/src/vt/vrt/collection/balance/node_lb_data.cc @@ -110,6 +110,10 @@ std::unordered_map> con return &lb_data_->node_subphase_comm_; } +std::shared_ptr const NodeLBData::getNodeAttributes() const { + return lb_data_->rank_attributes_; +} + CommMapType* NodeLBData::getNodeComm(PhaseType phase) { auto iter = lb_data_->node_comm_.find(phase); return (iter != lb_data_->node_comm_.end()) ? &iter->second : nullptr; @@ -127,6 +131,7 @@ void NodeLBData::startIterCleanup(PhaseType phase, unsigned int look_back) { lb_data_->node_comm_.erase(phase - look_back); lb_data_->node_subphase_comm_.erase(phase - look_back); lb_data_->user_defined_lb_info_.erase(phase - look_back); + lb_data_->node_user_attributes_.erase(phase - look_back); } // Clear migrate lambdas and proxy lookup since LB is complete @@ -215,6 +220,9 @@ void NodeLBData::createLBDataFile() { if(phasesMetadata) { metadata["phases"] = *phasesMetadata; } + if(lb_data_->rank_attributes_) { + metadata["attributes"] = *lb_data_->rank_attributes_; + } lb_data_writer_ = std::make_unique( "phases", metadata, file_name, compress ); diff --git a/src/vt/vrt/collection/balance/node_lb_data.h b/src/vt/vrt/collection/balance/node_lb_data.h index 3060f944c8..0f86c81168 100644 --- a/src/vt/vrt/collection/balance/node_lb_data.h +++ b/src/vt/vrt/collection/balance/node_lb_data.h @@ -204,6 +204,13 @@ struct NodeLBData : runtime::component::Component { */ std::unordered_map> const* getNodeSubphaseComm() const; + /** + * \internal \brief Get stored node attributes + * + * \return an observer shared pointer to the node attributes + */ + std::shared_ptr const getNodeAttributes() const; + /** * \internal \brief Test if this node has an object to migrate * diff --git a/tests/unit/collection/test_lb_data_holder.cc b/tests/unit/collection/test_lb_data_holder.cc index bd5def8609..2daadbaf9d 100644 --- a/tests/unit/collection/test_lb_data_holder.cc +++ b/tests/unit/collection/test_lb_data_holder.cc @@ -236,10 +236,6 @@ TEST_F(TestLBDataHolder, test_lb_rank_attributes) { LBDataHolder testObj(json); EXPECT_TRUE(nullptr != testObj.rank_attributes_); EXPECT_EQ(123, (*testObj.rank_attributes_)["some_val"]); - - auto outJsonPtr = testObj.metadataToJson(); - ASSERT_TRUE(outJsonPtr != nullptr); - EXPECT_EQ(123, (*outJsonPtr)["attributes"]["some_val"]); } TEST_F(TestLBDataHolder, test_lb_entity_attributes) { From 28876d9ef7570cb56272b11b38b6cfd24f1c1d85 Mon Sep 17 00:00:00 2001 From: Arkadiusz Szczepkowicz Date: Tue, 13 Feb 2024 14:58:26 +0100 Subject: [PATCH 05/10] #2229: Change attributes to use std::variant --- scripts/JSON_data_files_validator.py | 6 +-- .../vrt/collection/balance/lb_data_holder.cc | 49 ++++++++++++------- .../vrt/collection/balance/lb_data_holder.h | 9 ++-- src/vt/vrt/collection/balance/node_lb_data.cc | 5 ++ src/vt/vrt/collection/balance/node_lb_data.h | 7 +++ tests/unit/collection/test_lb_data_holder.cc | 21 +++++--- 6 files changed, 65 insertions(+), 32 deletions(-) diff --git a/scripts/JSON_data_files_validator.py b/scripts/JSON_data_files_validator.py index 273e66a08f..d3e3a30ef1 100644 --- a/scripts/JSON_data_files_validator.py +++ b/scripts/JSON_data_files_validator.py @@ -78,8 +78,7 @@ def _get_valid_schema(self) -> Schema: Optional('index'): [int], 'type': str, 'migratable': bool, - Optional('objgroup_id'): int, - Optional('attributes'): dict + Optional('objgroup_id'): int }, 'node': int, 'resource': str, @@ -90,7 +89,8 @@ def _get_valid_schema(self) -> Schema: } ], 'time': float, - Optional('user_defined'): dict + Optional('user_defined'): dict, + Optional('attributes'): dict }, ], Optional('communications'): [ diff --git a/src/vt/vrt/collection/balance/lb_data_holder.cc b/src/vt/vrt/collection/balance/lb_data_holder.cc index 934332fed4..30356200ce 100644 --- a/src/vt/vrt/collection/balance/lb_data_holder.cc +++ b/src/vt/vrt/collection/balance/lb_data_holder.cc @@ -48,7 +48,7 @@ namespace vt { namespace vrt { namespace collection { namespace balance { -void LBDataHolder::outputEntity(PhaseType phase, nlohmann::json& j, ElementIDStruct const& id) const { +void LBDataHolder::outputEntity(nlohmann::json& j, ElementIDStruct const& id) const { j["type"] = "object"; j["id"] = id.id; j["home"] = id.getHomeNode(); @@ -66,11 +66,6 @@ void LBDataHolder::outputEntity(PhaseType phase, nlohmann::json& j, ElementIDStr } else { // bare handler } - if (node_user_attributes_.find(phase) != node_user_attributes_.end()) { - if (node_user_attributes_.at(phase).find(id) != node_user_attributes_.at(phase).end()) { - j["attributes"] = *(node_user_attributes_.at(phase).at(id)); - } - } } std::unique_ptr LBDataHolder::metadataToJson() const { @@ -153,7 +148,21 @@ std::unique_ptr LBDataHolder::toJson(PhaseType phase) const { } } } - outputEntity(phase, j["tasks"][i]["entity"], id); + outputEntity(j["tasks"][i]["entity"], id); + + if (node_user_attributes_.find(phase) != node_user_attributes_.end()) { + if (node_user_attributes_.at(phase).find(id) != node_user_attributes_.at(phase).end()) { + for (auto const& [key, value] : node_user_attributes_.at(phase).at(id)) { + if (std::holds_alternative(value)) { + j["tasks"][i]["attributes"][key] = std::get(value); + } else if (std::holds_alternative(value)) { + j["tasks"][i]["attributes"][key] = std::get(value); + } else if (std::holds_alternative(value)) { + j["tasks"][i]["attributes"][key] = std::get(value); + } + } + } + } auto const& subphase_times = elm.second.subphase_loads; std::size_t const subphases = subphase_times.size(); @@ -184,8 +193,8 @@ std::unique_ptr LBDataHolder::toJson(PhaseType phase) const { } else { j["communications"][i]["type"] = "Broadcast"; } - outputEntity(phase, j["communications"][i]["from"], key.fromObj()); - outputEntity(phase, j["communications"][i]["to"], key.toObj()); + outputEntity(j["communications"][i]["from"], key.fromObj()); + outputEntity(j["communications"][i]["to"], key.toObj()); break; } case elm::CommCategory::NodeToCollection: @@ -198,7 +207,7 @@ std::unique_ptr LBDataHolder::toJson(PhaseType phase) const { j["communications"][i]["from"]["type"] = "node"; j["communications"][i]["from"]["id"] = key.fromNode(); - outputEntity(phase, j["communications"][i]["to"], key.toObj()); + outputEntity(j["communications"][i]["to"], key.toObj()); break; } case elm::CommCategory::CollectionToNode: @@ -211,7 +220,7 @@ std::unique_ptr LBDataHolder::toJson(PhaseType phase) const { j["communications"][i]["to"]["type"] = "node"; j["communications"][i]["to"]["id"] = key.toNode(); - outputEntity(phase, j["communications"][i]["from"], key.fromObj()); + outputEntity(j["communications"][i]["from"], key.fromObj()); break; } case elm::CommCategory::LocalInvoke: @@ -279,12 +288,6 @@ LBDataHolder::LBDataHolder(nlohmann::json const& j) } } - if (task["entity"].find("attributes") != task["entity"].end()) { - auto attrs = task["entity"]["attributes"]; - node_user_attributes_[id][elm] = std::make_shared(); - *(node_user_attributes_[id][elm]) = attrs; - } - if (task.find("subphases") != task.end()) { auto subphases = task["subphases"]; if (subphases.is_array()) { @@ -314,6 +317,18 @@ LBDataHolder::LBDataHolder(nlohmann::json const& j) } } } + + if (task.find("attributes") != task.end()) { + for (auto const& [key, value] : task["attributes"].items()) { + if (value.is_number_integer()) { + node_user_attributes_[id][elm][key] = value.get(); + } else if (value.is_number_float()) { + node_user_attributes_[id][elm][key] = value.get(); + } else if (value.is_string()) { + node_user_attributes_[id][elm][key] = value.get(); + } + } + } } } } diff --git a/src/vt/vrt/collection/balance/lb_data_holder.h b/src/vt/vrt/collection/balance/lb_data_holder.h index 7173cf651c..7b75bc9477 100644 --- a/src/vt/vrt/collection/balance/lb_data_holder.h +++ b/src/vt/vrt/collection/balance/lb_data_holder.h @@ -114,11 +114,10 @@ struct LBDataHolder { /** * \brief Output an entity to json * - * \param[in] phase the phase * \param[in] j the json * \param[in] elm_id the element to output */ - void outputEntity(PhaseType phase, nlohmann::json& j, ElementIDStruct const& elm_id) const; + void outputEntity(nlohmann::json& j, ElementIDStruct const& elm_id) const; /** * \brief Read the LB phase's metadata @@ -128,6 +127,7 @@ struct LBDataHolder { void readMetadata(nlohmann::json const& j); public: + /// Node attributes for the current rank std::shared_ptr rank_attributes_; /// Node timings for each local object std::unordered_map node_data_; @@ -139,13 +139,12 @@ struct LBDataHolder { std::unordered_map >> user_defined_json_; - std::unordered_map - >> node_user_attributes_; std::unordered_map> user_per_phase_json_; /// User-defined data from each phase for LB std::unordered_map user_defined_lb_info_; + /// User-defined attributes from each phase + std::unordered_map node_user_attributes_; /// Node indices for each ID along with the proxy ID std::unordered_map>> node_idx_; /// Map from id to objgroup proxy diff --git a/src/vt/vrt/collection/balance/node_lb_data.cc b/src/vt/vrt/collection/balance/node_lb_data.cc index 1b37997bdc..b4b900b606 100644 --- a/src/vt/vrt/collection/balance/node_lb_data.cc +++ b/src/vt/vrt/collection/balance/node_lb_data.cc @@ -102,6 +102,11 @@ NodeLBData::getUserData() const { return &lb_data_->user_defined_lb_info_; } +std::unordered_map const* +NodeLBData::getUserAttributes() const { + return &lb_data_->node_user_attributes_; +} + std::unordered_map const* NodeLBData::getNodeComm() const { return &lb_data_->node_comm_; } diff --git a/src/vt/vrt/collection/balance/node_lb_data.h b/src/vt/vrt/collection/balance/node_lb_data.h index 0f86c81168..fd8321c336 100644 --- a/src/vt/vrt/collection/balance/node_lb_data.h +++ b/src/vt/vrt/collection/balance/node_lb_data.h @@ -188,6 +188,13 @@ struct NodeLBData : runtime::component::Component { */ std::unordered_map const* getUserData() const; + /** + * \internal \brief Get the user-defined attributes + * + * \return an observer pointer to the user-defined attributes + */ + std::unordered_map const* getUserAttributes() const; + /** * \internal \brief Get stored object comm data for a specific phase * diff --git a/tests/unit/collection/test_lb_data_holder.cc b/tests/unit/collection/test_lb_data_holder.cc index 2daadbaf9d..a680e10e4b 100644 --- a/tests/unit/collection/test_lb_data_holder.cc +++ b/tests/unit/collection/test_lb_data_holder.cc @@ -253,14 +253,16 @@ TEST_F(TestLBDataHolder, test_lb_entity_attributes) { "home": 0, "id": 524291, "type": "object", - "migratable": true, - "attributes": { - "some_val": 123 - } + "migratable": true }, "node": 0, "resource": "cpu", - "time": 3.0 + "time": 3.0, + "attributes": { + "intSample": 123, + "doubleSample": 1.99, + "stringSample": "abc" + } } ] } @@ -272,11 +274,16 @@ TEST_F(TestLBDataHolder, test_lb_entity_attributes) { LBDataHolder testObj(json); EXPECT_TRUE(testObj.node_user_attributes_.find(0) != testObj.node_user_attributes_.end()); EXPECT_TRUE(testObj.node_user_attributes_[0].find(id) != testObj.node_user_attributes_[0].end()); - EXPECT_EQ(123, (*testObj.node_user_attributes_[0][id])["some_val"]); + auto attributes = testObj.node_user_attributes_[0][id]; + EXPECT_EQ(123, std::get(attributes["intSample"])); + EXPECT_EQ(1.99, std::get(attributes["doubleSample"])); + EXPECT_EQ("abc", std::get(attributes["stringSample"])); auto outJsonPtr = testObj.toJson(0); ASSERT_TRUE(outJsonPtr != nullptr); - EXPECT_EQ(123, (*outJsonPtr)["tasks"][0]["entity"]["attributes"]["some_val"]); + EXPECT_EQ(123, (*outJsonPtr)["tasks"][0]["attributes"]["intSample"]); + EXPECT_EQ(1.99, (*outJsonPtr)["tasks"][0]["attributes"]["doubleSample"]); + EXPECT_EQ("abc", (*outJsonPtr)["tasks"][0]["attributes"]["stringSample"]); } }}}} // end namespace vt::tests::unit::lb From fb4022c70dbd6d55e4a2198de118cb3c3fcbe01a Mon Sep 17 00:00:00 2001 From: Arkadiusz Szczepkowicz Date: Tue, 13 Feb 2024 15:50:41 +0100 Subject: [PATCH 06/10] #2229: Include attributes field in json file --- src/vt/vrt/collection/balance/node_lb_data.cc | 5 ++++ .../vrt/collection/types/storage/storable.h | 17 +++++++++++- .../collection/types/storage/storable.impl.h | 6 ++--- .../vrt/collection/types/storage/store_elm.h | 27 ++++++++++++++----- tests/unit/collection/test_lb.extended.cc | 4 +-- 5 files changed, 47 insertions(+), 12 deletions(-) diff --git a/src/vt/vrt/collection/balance/node_lb_data.cc b/src/vt/vrt/collection/balance/node_lb_data.cc index b4b900b606..4057495560 100644 --- a/src/vt/vrt/collection/balance/node_lb_data.cc +++ b/src/vt/vrt/collection/balance/node_lb_data.cc @@ -365,6 +365,11 @@ void NodeLBData::addNodeLBData( lb_data_->user_defined_lb_info_[phase][id][key] = val->toVariant(); } ); + storable->collectAttributes( + [&](std::string const& key, auto val) { + lb_data_->node_user_attributes_[phase][id][key] = val->toVariant(); + } + ); } in->updatePhase(1); diff --git a/src/vt/vrt/collection/types/storage/storable.h b/src/vt/vrt/collection/types/storage/storable.h index 444828f3b8..c23fcc03a3 100644 --- a/src/vt/vrt/collection/types/storage/storable.h +++ b/src/vt/vrt/collection/types/storage/storable.h @@ -94,13 +94,14 @@ struct Storable { * \param[in] u the value * \param[in] dump_to_json whether to include in json file * \param[in] provide_to_lb whether to provide the data to the load balancers + * \param[in] dump_to_attributes whether to dump to attributes in JSON output * * \note If \c provide_to_lb is enabled, the data must be convertible to an * integer, double, or string */ template void valInsert( - std::string const& str, U&& u, bool dump_to_json, bool provide_to_lb + std::string const& str, U&& u, bool dump_to_json, bool provide_to_lb, bool dump_to_attributes ); /** @@ -160,6 +161,20 @@ struct Storable { } } + /** + * \brief Traverse the map and calls Callable on each attribute + * + * \param[in] c the callable + */ + template + void collectAttributes(Callable&& c) { + for (auto const& [key, value] : map_) { + if (value->isAttribute()) { + c(key, value.get()); + } + } + } + private: /// Map of type-erased, stored values std::unordered_map> map_; diff --git a/src/vt/vrt/collection/types/storage/storable.impl.h b/src/vt/vrt/collection/types/storage/storable.impl.h index 1ecf9b4734..dd61fd5e29 100644 --- a/src/vt/vrt/collection/types/storage/storable.impl.h +++ b/src/vt/vrt/collection/types/storage/storable.impl.h @@ -60,7 +60,7 @@ void Storable::valInsert(std::string const& str, U&& u) { std::forward_as_tuple(str), std::forward_as_tuple( std::make_unique::type>>( - std::forward(u), false, false + std::forward(u), false, false, false ) ) ); @@ -68,14 +68,14 @@ void Storable::valInsert(std::string const& str, U&& u) { template void Storable::valInsert( - std::string const& str, U&& u, bool dump_to_json, bool provide_to_lb + std::string const& str, U&& u, bool dump_to_json, bool provide_to_lb, bool dump_to_attributes ) { map_.emplace( std::piecewise_construct, std::forward_as_tuple(str), std::forward_as_tuple( std::make_unique::type>>( - std::forward(u), dump_to_json, provide_to_lb + std::forward(u), dump_to_json, provide_to_lb, dump_to_attributes ) ) ); diff --git a/src/vt/vrt/collection/types/storage/store_elm.h b/src/vt/vrt/collection/types/storage/store_elm.h index 0f9524bdb4..1937179243 100644 --- a/src/vt/vrt/collection/types/storage/store_elm.h +++ b/src/vt/vrt/collection/types/storage/store_elm.h @@ -74,10 +74,12 @@ struct StoreElmBase { * * \param[in] dump_to_json whether to dump to JSON output * \param[in] provide_to_lb whether to provide to the LB + * \param[in] dump_to_attributes whether to dump to attributes in JSON output */ - StoreElmBase(bool dump_to_json, bool provide_to_lb) + StoreElmBase(bool dump_to_json, bool provide_to_lb, bool dump_to_attributes) : dump_to_json_(dump_to_json), - provide_to_lb_(provide_to_lb) + provide_to_lb_(provide_to_lb), + dump_to_attributes_(dump_to_attributes) {} virtual ~StoreElmBase() {} @@ -121,6 +123,7 @@ struct StoreElmBase { void serialize(SerializerT& s) { s | dump_to_json_; s | provide_to_lb_; + s | dump_to_attributes_; } /** @@ -139,6 +142,15 @@ struct StoreElmBase { return provide_to_lb_; } + /** + * \brief Whether the value should be dumped to the json attributes field + * + * \return whether it is an attribute + */ + bool isAttribute() const { + return dump_to_attributes_; + } + /** * \brief Generate the json because it is jsonable * @@ -177,6 +189,7 @@ struct StoreElmBase { protected: bool dump_to_json_ = false; bool provide_to_lb_ = false; + bool dump_to_attributes_ = false; }; /** @@ -209,10 +222,11 @@ struct StoreElm< * \param[in] u the value * \param[in] dump_to_json whether to dump to json * \param[in] provide_to_lb whether to provide to LB + * \param[in] dump_to_attributes whether to dump to attributes in JSON output */ template - explicit StoreElm(U&& u, bool dump_to_json, bool provide_to_lb) - : StoreElmBase(dump_to_json, provide_to_lb), + explicit StoreElm(U&& u, bool dump_to_json, bool provide_to_lb, bool dump_to_attributes) + : StoreElmBase(dump_to_json, provide_to_lb, dump_to_attributes), elm_(std::forward(u)) { } @@ -328,10 +342,11 @@ struct StoreElm< * \param[in] u the value * \param[in] dump_to_json whether to dump to json * \param[in] provide_to_lb whether to provide to LB + * \param[in] dump_to_attributes whether to dump to attributes in JSON output */ template - explicit StoreElm(U&& u, bool dump_to_json, bool provide_to_lb) - : StoreElmBase(dump_to_json, provide_to_lb), + explicit StoreElm(U&& u, bool dump_to_json, bool provide_to_lb, bool dump_to_attributes) + : StoreElmBase(dump_to_json, provide_to_lb, dump_to_attributes), wrapper_(detail::ByteWrapper{std::forward(u)}) { } diff --git a/tests/unit/collection/test_lb.extended.cc b/tests/unit/collection/test_lb.extended.cc index d9d0dd199c..702891715d 100644 --- a/tests/unit/collection/test_lb.extended.cc +++ b/tests/unit/collection/test_lb.extended.cc @@ -745,8 +745,8 @@ TEST_P(TestDumpUserdefinedData, test_dump_userdefined_json) { EXPECT_NE(elm_ptr, nullptr); if (elm_ptr != nullptr) { auto elm_id = elm_ptr->getElmID(); - elm_ptr->valInsert("hello", std::string("world"), should_dump, false); - elm_ptr->valInsert("elephant", 123456789, should_dump, false); + elm_ptr->valInsert("hello", std::string("world"), should_dump, false, false); + elm_ptr->valInsert("elephant", 123456789, should_dump, false, false); lbdh.user_defined_json_[phase][elm_id] = std::make_shared( elm_ptr->toJson() ); From 020c0c99b1081195c4ca77b8164ff6146f94360b Mon Sep 17 00:00:00 2001 From: Arkadiusz Szczepkowicz Date: Tue, 13 Feb 2024 16:19:58 +0100 Subject: [PATCH 07/10] #2229: Add unit test for dumping attributes field in json data file --- tests/unit/collection/test_lb.extended.cc | 62 +++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/tests/unit/collection/test_lb.extended.cc b/tests/unit/collection/test_lb.extended.cc index 702891715d..3a1919060b 100644 --- a/tests/unit/collection/test_lb.extended.cc +++ b/tests/unit/collection/test_lb.extended.cc @@ -777,6 +777,68 @@ INSTANTIATE_TEST_SUITE_P( DumpUserdefinedDataExplode, TestDumpUserdefinedData, booleans ); +struct TestDumpAttributesFieldData : TestParallelHarnessParam { }; + +TEST_P(TestDumpAttributesFieldData, test_dump_attributes_json) { + bool is_attribute = GetParam(); + + auto this_node = vt::theContext()->getNode(); + auto num_nodes = vt::theContext()->getNumNodes(); + + vt::vrt::collection::CollectionProxy proxy; + auto const range = vt::Index1D(num_nodes * 1); + + // Construct a collection + runInEpochCollective([&] { + proxy = vt::theCollection()->constructCollective( + range, "test_dump_attributes_json" + ); + }); + + vt::vrt::collection::balance::LBDataHolder lbdh; + PhaseType phase = 0; + + { + lbdh.node_data_[phase]; + lbdh.node_comm_[phase]; + + vt::Index1D idx(this_node * 1); + auto elm_ptr = proxy(idx).tryGetLocalPtr(); + EXPECT_NE(elm_ptr, nullptr); + if (elm_ptr != nullptr) { + auto elm_id = elm_ptr->getElmID(); + elm_ptr->valInsert("intSample", 123, false, false, is_attribute); + elm_ptr->valInsert("doubleSample", 123.456, false, false, is_attribute); + elm_ptr->valInsert("stringSample", std::string("abc"), false, false, is_attribute); + + elm_ptr->collectAttributes( + [&](std::string const& key, auto val) { + lbdh.node_user_attributes_[phase][elm_id][key] = val->toVariant(); + } + ); + lbdh.node_data_[phase][elm_id].whole_phase_load = 1.0; + } + } + + auto json_str = getJsonStringForPhase(phase, lbdh); + fmt::print("{}\n", json_str); + if (is_attribute) { + EXPECT_NE(json_str.find("attributes"), std::string::npos); + EXPECT_NE(json_str.find("intSample"), std::string::npos); + EXPECT_NE(json_str.find("doubleSample"), std::string::npos); + EXPECT_NE(json_str.find("stringSample"), std::string::npos); + } else { + EXPECT_EQ(json_str.find("attributes"), std::string::npos); + EXPECT_EQ(json_str.find("intSample"), std::string::npos); + EXPECT_EQ(json_str.find("doubleSample"), std::string::npos); + EXPECT_EQ(json_str.find("stringSample"), std::string::npos); + } +} + +INSTANTIATE_TEST_SUITE_P( + DumpAttributesFieldDataExplode, TestDumpAttributesFieldData, booleans +); + struct SerializationTestCol : vt::Collection { template void serialize(SerializerT &s) { vt::Collection::serialize(s); From 13f970e638c7742e752c64e1298b4319e59a1674 Mon Sep 17 00:00:00 2001 From: Arkadiusz Szczepkowicz Date: Fri, 16 Feb 2024 13:44:21 +0100 Subject: [PATCH 08/10] #2229: Change rank_attributes to std::variant --- .../vrt/collection/balance/lb_data_holder.cc | 27 +++++++++++++++++-- .../vrt/collection/balance/lb_data_holder.h | 9 ++++++- src/vt/vrt/collection/balance/node_lb_data.cc | 9 ++++--- src/vt/vrt/collection/balance/node_lb_data.h | 4 +-- tests/unit/collection/test_lb_data_holder.cc | 15 ++++++++--- 5 files changed, 52 insertions(+), 12 deletions(-) diff --git a/src/vt/vrt/collection/balance/lb_data_holder.cc b/src/vt/vrt/collection/balance/lb_data_holder.cc index 30356200ce..147b725f83 100644 --- a/src/vt/vrt/collection/balance/lb_data_holder.cc +++ b/src/vt/vrt/collection/balance/lb_data_holder.cc @@ -125,6 +125,22 @@ std::unique_ptr LBDataHolder::metadataToJson() const { return std::make_unique(std::move(j)); } +std::unique_ptr LBDataHolder::rankAttributesToJson() const { + nlohmann::json j; + + for (auto const& [key, value] : rank_attributes_) { + if (std::holds_alternative(value)) { + j["attributes"][key] = std::get(value); + } else if (std::holds_alternative(value)) { + j["attributes"][key] = std::get(value); + } else if (std::holds_alternative(value)) { + j["attributes"][key] = std::get(value); + } + } + + return std::make_unique(std::move(j)); +} + std::unique_ptr LBDataHolder::toJson(PhaseType phase) const { using json = nlohmann::json; @@ -469,8 +485,15 @@ void LBDataHolder::readMetadata(nlohmann::json const& j) { } // load rank user atrributes if (metadata.find("attributes") != metadata.end()) { - rank_attributes_ = std::make_shared(); - *(rank_attributes_) = metadata["attributes"]; + for (auto const& [key, value] : metadata["attributes"].items()) { + if (value.is_number_integer()) { + rank_attributes_[key] = value.get(); + } else if (value.is_number_float()) { + rank_attributes_[key] = value.get(); + } else if (value.is_string()) { + rank_attributes_[key] = value.get(); + } + } } } } diff --git a/src/vt/vrt/collection/balance/lb_data_holder.h b/src/vt/vrt/collection/balance/lb_data_holder.h index 7b75bc9477..caeb4e4cea 100644 --- a/src/vt/vrt/collection/balance/lb_data_holder.h +++ b/src/vt/vrt/collection/balance/lb_data_holder.h @@ -105,6 +105,13 @@ struct LBDataHolder { */ std::unique_ptr metadataToJson() const; + /** + * \brief Output a LB rank attributes metadata to JSON + * + * \return the json data structure + */ + std::unique_ptr rankAttributesToJson() const; + /** * \brief Clear all LB data */ @@ -128,7 +135,7 @@ struct LBDataHolder { public: /// Node attributes for the current rank - std::shared_ptr rank_attributes_; + ElmUserDataType rank_attributes_; /// Node timings for each local object std::unordered_map node_data_; /// Node communication graph for each local object diff --git a/src/vt/vrt/collection/balance/node_lb_data.cc b/src/vt/vrt/collection/balance/node_lb_data.cc index 4057495560..b1e04c0caf 100644 --- a/src/vt/vrt/collection/balance/node_lb_data.cc +++ b/src/vt/vrt/collection/balance/node_lb_data.cc @@ -115,8 +115,8 @@ std::unordered_map> con return &lb_data_->node_subphase_comm_; } -std::shared_ptr const NodeLBData::getNodeAttributes() const { - return lb_data_->rank_attributes_; +ElmUserDataType const* NodeLBData::getNodeAttributes() const { + return &lb_data_->rank_attributes_; } CommMapType* NodeLBData::getNodeComm(PhaseType phase) { @@ -225,8 +225,9 @@ void NodeLBData::createLBDataFile() { if(phasesMetadata) { metadata["phases"] = *phasesMetadata; } - if(lb_data_->rank_attributes_) { - metadata["attributes"] = *lb_data_->rank_attributes_; + auto attributesMetadata = lb_data_->rankAttributesToJson(); + if(attributesMetadata) { + metadata["attributes"] = *attributesMetadata; } lb_data_writer_ = std::make_unique( "phases", metadata, file_name, compress diff --git a/src/vt/vrt/collection/balance/node_lb_data.h b/src/vt/vrt/collection/balance/node_lb_data.h index fd8321c336..652330928d 100644 --- a/src/vt/vrt/collection/balance/node_lb_data.h +++ b/src/vt/vrt/collection/balance/node_lb_data.h @@ -214,9 +214,9 @@ struct NodeLBData : runtime::component::Component { /** * \internal \brief Get stored node attributes * - * \return an observer shared pointer to the node attributes + * \return an observer pointer to the node attributes */ - std::shared_ptr const getNodeAttributes() const; + ElmUserDataType const* getNodeAttributes() const; /** * \internal \brief Test if this node has an object to migrate diff --git a/tests/unit/collection/test_lb_data_holder.cc b/tests/unit/collection/test_lb_data_holder.cc index a680e10e4b..af95937b92 100644 --- a/tests/unit/collection/test_lb_data_holder.cc +++ b/tests/unit/collection/test_lb_data_holder.cc @@ -226,7 +226,9 @@ TEST_F(TestLBDataHolder, test_lb_rank_attributes) { "type": "LBDatafile", "metadata" : { "attributes": { - "some_val": 123 + "intSample": 123, + "doubleSample": 1.99, + "stringSample": "abc" } }, "phases" : [] @@ -234,8 +236,15 @@ TEST_F(TestLBDataHolder, test_lb_rank_attributes) { )"_json; LBDataHolder testObj(json); - EXPECT_TRUE(nullptr != testObj.rank_attributes_); - EXPECT_EQ(123, (*testObj.rank_attributes_)["some_val"]); + EXPECT_EQ(123, std::get(testObj.rank_attributes_["intSample"])); + EXPECT_EQ(1.99, std::get(testObj.rank_attributes_["doubleSample"])); + EXPECT_EQ("abc", std::get(testObj.rank_attributes_["stringSample"])); + + auto outAttributesJsonPtr = testObj.rankAttributesToJson(); + ASSERT_TRUE(outAttributesJsonPtr != nullptr); + EXPECT_EQ(123, (*outAttributesJsonPtr)["attributes"]["intSample"]); + EXPECT_EQ(1.99, (*outAttributesJsonPtr)["attributes"]["doubleSample"]); + EXPECT_EQ("abc", (*outAttributesJsonPtr)["attributes"]["stringSample"]); } TEST_F(TestLBDataHolder, test_lb_entity_attributes) { From 01039e37d0b6be553138bd92c27057494ad83f07 Mon Sep 17 00:00:00 2001 From: Arkadiusz Szczepkowicz Date: Fri, 16 Feb 2024 14:01:29 +0100 Subject: [PATCH 09/10] #2229: Rename get method for phase attributes --- src/vt/vrt/collection/balance/lb_data_holder.cc | 2 +- src/vt/vrt/collection/balance/node_lb_data.cc | 2 +- src/vt/vrt/collection/balance/node_lb_data.h | 2 +- src/vt/vrt/collection/types/storage/storable.impl.h | 2 +- tests/unit/collection/test_lb.extended.cc | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vt/vrt/collection/balance/lb_data_holder.cc b/src/vt/vrt/collection/balance/lb_data_holder.cc index 147b725f83..bf81b33168 100644 --- a/src/vt/vrt/collection/balance/lb_data_holder.cc +++ b/src/vt/vrt/collection/balance/lb_data_holder.cc @@ -176,7 +176,7 @@ std::unique_ptr LBDataHolder::toJson(PhaseType phase) const { } else if (std::holds_alternative(value)) { j["tasks"][i]["attributes"][key] = std::get(value); } - } + } } } diff --git a/src/vt/vrt/collection/balance/node_lb_data.cc b/src/vt/vrt/collection/balance/node_lb_data.cc index b1e04c0caf..6d65cad1b2 100644 --- a/src/vt/vrt/collection/balance/node_lb_data.cc +++ b/src/vt/vrt/collection/balance/node_lb_data.cc @@ -103,7 +103,7 @@ NodeLBData::getUserData() const { } std::unordered_map const* -NodeLBData::getUserAttributes() const { +NodeLBData::getPhaseAttributes() const { return &lb_data_->node_user_attributes_; } diff --git a/src/vt/vrt/collection/balance/node_lb_data.h b/src/vt/vrt/collection/balance/node_lb_data.h index 652330928d..686ac258b3 100644 --- a/src/vt/vrt/collection/balance/node_lb_data.h +++ b/src/vt/vrt/collection/balance/node_lb_data.h @@ -193,7 +193,7 @@ struct NodeLBData : runtime::component::Component { * * \return an observer pointer to the user-defined attributes */ - std::unordered_map const* getUserAttributes() const; + std::unordered_map const* getPhaseAttributes() const; /** * \internal \brief Get stored object comm data for a specific phase diff --git a/src/vt/vrt/collection/types/storage/storable.impl.h b/src/vt/vrt/collection/types/storage/storable.impl.h index dd61fd5e29..b03b04378c 100644 --- a/src/vt/vrt/collection/types/storage/storable.impl.h +++ b/src/vt/vrt/collection/types/storage/storable.impl.h @@ -68,7 +68,7 @@ void Storable::valInsert(std::string const& str, U&& u) { template void Storable::valInsert( - std::string const& str, U&& u, bool dump_to_json, bool provide_to_lb, bool dump_to_attributes + std::string const& str, U&& u, bool dump_to_json, bool provide_to_lb, bool dump_to_attributes ) { map_.emplace( std::piecewise_construct, diff --git a/tests/unit/collection/test_lb.extended.cc b/tests/unit/collection/test_lb.extended.cc index 3a1919060b..a865ef5113 100644 --- a/tests/unit/collection/test_lb.extended.cc +++ b/tests/unit/collection/test_lb.extended.cc @@ -810,7 +810,7 @@ TEST_P(TestDumpAttributesFieldData, test_dump_attributes_json) { elm_ptr->valInsert("intSample", 123, false, false, is_attribute); elm_ptr->valInsert("doubleSample", 123.456, false, false, is_attribute); elm_ptr->valInsert("stringSample", std::string("abc"), false, false, is_attribute); - + elm_ptr->collectAttributes( [&](std::string const& key, auto val) { lbdh.node_user_attributes_[phase][elm_id][key] = val->toVariant(); From 81d91e096619dd9acd85163154bf7039ed46d85f Mon Sep 17 00:00:00 2001 From: Jacob Domagala Date: Thu, 22 Feb 2024 23:51:20 +0100 Subject: [PATCH 10/10] #2229: Return nullptr for metadata's attribute field when we don't have any --- src/vt/vrt/collection/balance/lb_data_holder.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vt/vrt/collection/balance/lb_data_holder.cc b/src/vt/vrt/collection/balance/lb_data_holder.cc index bf81b33168..43b1e61802 100644 --- a/src/vt/vrt/collection/balance/lb_data_holder.cc +++ b/src/vt/vrt/collection/balance/lb_data_holder.cc @@ -126,6 +126,10 @@ std::unique_ptr LBDataHolder::metadataToJson() const { } std::unique_ptr LBDataHolder::rankAttributesToJson() const { + if (rank_attributes_.empty()) { + return nullptr; + } + nlohmann::json j; for (auto const& [key, value] : rank_attributes_) {