Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add HardwareResourcesDescription class #47175

Merged
merged 2 commits into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions DataFormats/Provenance/interface/HardwareResourcesDescription.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#ifndef DataFormats_Provenance_interface_HardwareResourcesDescription_h
#define DataFormats_Provenance_interface_HardwareResourcesDescription_h

#include <iosfwd>
#include <string>
#include <string_view>
#include <vector>

namespace edm {
struct HardwareResourcesDescription {
HardwareResourcesDescription() = default;
explicit HardwareResourcesDescription(std::string_view serialized);

std::string serialize() const;

bool operator==(HardwareResourcesDescription const& other) const;

std::string microarchitecture;
std::vector<std::string> cpuModels;
std::vector<std::string> selectedAccelerators;
std::vector<std::string> gpuModels;
};

std::ostream& operator<<(std::ostream& os, HardwareResourcesDescription const& rd);
} // namespace edm

#endif
57 changes: 57 additions & 0 deletions DataFormats/Provenance/src/HardwareResourcesDescription.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#include "DataFormats/Provenance/interface/HardwareResourcesDescription.h"
#include "FWCore/Utilities/interface/EDMException.h"
#include "FWCore/Utilities/interface/compactStringSerializer.h"

#include <iterator>
#include <ostream>

namespace edm {
HardwareResourcesDescription::HardwareResourcesDescription(std::string_view serialized) {
// allowing empty input is mostly for backwards compatibility
if (not serialized.empty()) {
auto ret = edm::compactString::deserialize(serialized,
microarchitecture,
std::back_inserter(cpuModels),
std::back_inserter(selectedAccelerators),
std::back_inserter(gpuModels));
// not comparing against serialized.size() to allow serialized
// to have more content (for kind of forward compatibility)
if (ret == 0) {
throw Exception(errors::EventCorruption) << "Failed to deserialize HardwareResourcesDescription string format";
}
}
}

std::string HardwareResourcesDescription::serialize() const {
return edm::compactString::serialize(microarchitecture, cpuModels, selectedAccelerators, gpuModels);
}

bool HardwareResourcesDescription::operator==(HardwareResourcesDescription const& other) const {
return microarchitecture == other.microarchitecture and std::ranges::equal(cpuModels, other.cpuModels) and
std::ranges::equal(selectedAccelerators, other.selectedAccelerators) and
std::ranges::equal(gpuModels, other.gpuModels);
}

std::ostream& operator<<(std::ostream& os, HardwareResourcesDescription const& rd) {
auto printContainer = [&os](std::string_view header, std::vector<std::string> const& cont) {
os << header << ": " << cont.front();
for (auto it = cont.begin() + 1; it != cont.end(); ++it) {
os << ", " << *it;
}
};

os << "uarch: " << rd.microarchitecture << "\n";
if (not rd.cpuModels.empty()) {
printContainer("CPU models", rd.cpuModels);
os << "\n";
}
if (not rd.selectedAccelerators.empty()) {
printContainer("Selected accelerators", rd.selectedAccelerators);
os << "\n";
}
if (not rd.gpuModels.empty()) {
printContainer("GPU models", rd.gpuModels);
}
return os;
}
} // namespace edm
2 changes: 1 addition & 1 deletion DataFormats/Provenance/test/BuildFile.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<use name="DataFormats/Provenance"/>
</bin>

<bin name="testDataFormatsProvenanceCatch2" file="ElementID_t.cpp,Parentage_t.cpp,StoredProcessBlockHelper_t.cpp,CompactHash_t.cpp">
<bin name="testDataFormatsProvenanceCatch2" file="ElementID_t.cpp,Parentage_t.cpp,StoredProcessBlockHelper_t.cpp,CompactHash_t.cpp,HardwareResourcesDescription_t.cpp">
<use name="DataFormats/Provenance"/>
<use name="catch2"/>
</bin>
Expand Down
80 changes: 80 additions & 0 deletions DataFormats/Provenance/test/HardwareResourcesDescription_t.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#include "catch.hpp"

#include "DataFormats/Provenance/interface/HardwareResourcesDescription.h"
#include "FWCore/Utilities/interface/EDMException.h"

TEST_CASE("HardwareResourcesDescription", "[HardwareResourcesDescription]") {
SECTION("Construction from empty string") {
CHECK(edm::HardwareResourcesDescription("") == edm::HardwareResourcesDescription());
}

SECTION("Default construction") {
edm::HardwareResourcesDescription resources;
CHECK(edm::HardwareResourcesDescription(resources.serialize()) == resources);
}

SECTION("Microarchitecture") {
edm::HardwareResourcesDescription resources;
resources.microarchitecture = "x86-64-v3";
CHECK(edm::HardwareResourcesDescription(resources.serialize()) == resources);
}

SECTION("CPU models") {
edm::HardwareResourcesDescription resources;
resources.cpuModels = {"Intel something", "AMD something else"};
CHECK(edm::HardwareResourcesDescription(resources.serialize()) == resources);
}

SECTION("accelerators") {
edm::HardwareResourcesDescription resources;
resources.selectedAccelerators = {"cpu", "gpu"};
CHECK(edm::HardwareResourcesDescription(resources.serialize()) == resources);
}

SECTION("GPU models") {
edm::HardwareResourcesDescription resources;
resources.gpuModels = {"NVIDIA something", "NVIDIA something else"};
CHECK(edm::HardwareResourcesDescription(resources.serialize()) == resources);
}

SECTION("All fields") {
edm::HardwareResourcesDescription resources;
resources.microarchitecture = "x86-64-v3";
resources.cpuModels = {"Intel something", "AMD something else"};
resources.selectedAccelerators = {"cpu", "gpu"};
resources.gpuModels = {"NVIDIA something", "NVIDIA something else"};
CHECK(edm::HardwareResourcesDescription(resources.serialize()) == resources);
}

SECTION("Serialization has additional things (forward compatibility)") {
edm::HardwareResourcesDescription resources, resources2;
resources.microarchitecture = "x86-64-v3";
resources.cpuModels = {"Intel something", "AMD something else"};
resources.selectedAccelerators = {"cpu", "gpu"};
resources.gpuModels = {"NVIDIA something", "NVIDIA something else"};
resources2.microarchitecture = "this";
resources2.cpuModels = {"is"};
resources2.selectedAccelerators = {"something"};
resources2.gpuModels = {"else"};
auto const serial = resources.serialize() + resources2.serialize();
CHECK(edm::HardwareResourcesDescription(serial) == resources);
}

SECTION("Error cases") {
SECTION("Invalid serialized string") {
CHECK_THROWS_AS(edm::HardwareResourcesDescription("foo"), edm::Exception);

edm::HardwareResourcesDescription resources;
resources.microarchitecture = "x86-64-v3";
auto serialized = resources.serialize();
SECTION("Last container does not have the delimiter") {
serialized.back() = ',';
CHECK_THROWS_AS(edm::HardwareResourcesDescription(serialized), edm::Exception);
}
SECTION("Too few containers") {
serialized.pop_back();
CHECK_THROWS_AS(edm::HardwareResourcesDescription(serialized), edm::Exception);
}
}
}
}
140 changes: 140 additions & 0 deletions FWCore/Utilities/interface/compactStringSerializer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
#ifndef FWCore_Utilities_interface_compactStringSerializer_h
#define FWCore_Utilities_interface_compactStringSerializer_h

#include <cassert>
#include <iterator>
#include <numeric>
#include <ranges>
#include <string>
#include <string_view>

namespace edm::compactString {
namespace detail {
constexpr std::string_view kDelimiters = "\x1d\x1e";
constexpr char kContainerDelimiter = kDelimiters[0]; // "group separator" in ASCII
constexpr char kElementDelimiter = kDelimiters[1]; // "record separator" in ASCII

void throwIfContainsDelimiters(std::string const& str);
} // namespace detail

/**
* Following three functions serialize a sequence of strings and containers of strings
*
* Each top-level string or container of strings is separated with kContainerDelimeter
* In case of container of strings, each element is separated with kElementDelimeter
* The serialized string will end with kContainerDelimeter and a null character
*
* The functions throw an exception if the serialized strings
* contain any of the delimeter characters. The underlying string
* operations may also throw exceptions.
*/
inline std::string serialize(std::string arg) noexcept(false) {
detail::throwIfContainsDelimiters(arg);
arg += detail::kContainerDelimiter;
return arg;
}

template <typename R>
requires std::ranges::input_range<R> and std::is_same_v<std::ranges::range_value_t<R>, std::string>
std::string serialize(R const& arg) noexcept(false) {
std::string ret;

if (not std::ranges::empty(arg)) {
for (std::string const& elem : arg) {
ret.reserve(ret.size() + elem.size() + 1);
detail::throwIfContainsDelimiters(elem);
ret += elem;
ret += detail::kElementDelimiter;
}
}

ret += detail::kContainerDelimiter;
return ret;
}

template <typename T, typename... Args>
requires(sizeof...(Args) >= 1)
std::string serialize(T&& arg0, Args&&... args) noexcept(false) {
return serialize(std::forward<T>(arg0)) + serialize(std::forward<Args>(args)...);
}

/**
* Following three functions deserialize a string 'input' into a
* sequence of strings and containers of strings
*
* The 'input' string is assumed to be serialized with the
* serialize() functions above.
*
* The output arguments following the 'input' define the schema of
* the deserialization.
* - std::string& for strings
* - output iterator for containers of strings (e.g. std::back_inserter(vector))
*
* Upon success, the return value is the position in `input` for the
* next possible element (i.e. the position after the
* kContainerDelimiter), that is also the number of characters
* consumed by the deserializatiom..
*
* Upon failure, returns 0 to denote the beginning of `input`. The
* output arguments may have been modified.
*
* The functions do not explicitly throw exceptions, but underlying
* operations may throw exceptions.
*/
inline std::string_view::size_type deserialize(std::string_view input, std::string& arg) {
auto const pos = input.find_first_of(detail::kDelimiters);
if (pos == std::string_view::npos or input[pos] != detail::kContainerDelimiter) {
return 0;
}
arg = input.substr(0, pos);
return pos + 1; // skip delimiter
}

template <std::output_iterator<std::string> I>
inline std::string_view::size_type deserialize(std::string_view input, I arg) {
auto pos = input.find_first_of(detail::kDelimiters);
// invalid input
if (pos == std::string_view::npos) {
return 0;
}
// no elements
if (input[pos] == detail::kContainerDelimiter) {
// invalid input for empty container
if (pos != 0) {
return 0;
}
// skip delimiter
return pos + 1;
}

std::string_view::size_type prev = 0;
while (pos != std::string_view::npos and input[pos] == detail::kElementDelimiter) {
*arg = std::string(input.substr(prev, pos - prev));
++arg;
prev = pos + 1; //skip delimiter
pos = input.find_first_of(detail::kDelimiters, prev);
}

// every container must end with kContainerDelimiter
// reaching npos is an error
if (pos == std::string_view::npos) {
return 0;
}
assert(input[pos] == detail::kContainerDelimiter);

return pos + 1; // skip delimiter
}

template <typename T, typename... Args>
requires(sizeof...(Args) >= 1)
std::string_view::size_type deserialize(std::string_view input, T&& arg0, Args&&... args) {
auto pos = deserialize(input, std::forward<T>(arg0));
if (pos != 0) {
auto const ret = deserialize(input.substr(pos), std::forward<Args>(args)...);
pos = (ret == 0) ? 0 : pos + ret;
}
return pos;
}
} // namespace edm::compactString

#endif
19 changes: 19 additions & 0 deletions FWCore/Utilities/src/compactStringSerializer.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#include "FWCore/Utilities/interface/compactStringSerializer.h"
#include "FWCore/Utilities/interface/Exception.h"

namespace edm::compactString::detail {
void throwIfContainsDelimiters(std::string const& str) {
auto pos = str.find_first_of(kDelimiters);
if (pos != std::string::npos) {
cms::Exception ex("compactString");
ex << "Serialized string '" << str << "' contains ";
if (str[pos] == kContainerDelimiter) {
ex << "container";
} else {
ex << "element";
}
ex << " delimiter at position " << pos;
throw ex;
}
}
} // namespace edm::compactString::detail
Loading