From 8778a0accae67fac16e615342441ede2a48439b3 Mon Sep 17 00:00:00 2001 From: Bellekallu Rajkiran Date: Thu, 20 Jul 2023 18:51:40 +0000 Subject: [PATCH] feature(sysman): Optimize Sysfs reads Maintain cache of file names and file descriptor to avoid invoking open and close system calls on every read call. Related-To: LOCI-4556 Signed-off-by: Bellekallu Rajkiran --- .../sysman/source/linux/sysman_fs_access.cpp | 190 +++++++----------- .../sysman/source/linux/sysman_fs_access.h | 23 +++ .../unit_tests/sources/linux/test_sysman.cpp | 81 ++++++++ .../tools/source/sysman/linux/fs_access.cpp | 45 ++++- .../tools/source/sysman/linux/fs_access.h | 19 ++ .../sources/sysman/linux/test_sysman.cpp | 81 ++++++++ 6 files changed, 321 insertions(+), 118 deletions(-) diff --git a/level_zero/sysman/source/linux/sysman_fs_access.cpp b/level_zero/sysman/source/linux/sysman_fs_access.cpp index 2855703d87..fb8c7c6f0c 100644 --- a/level_zero/sysman/source/linux/sysman_fs_access.cpp +++ b/level_zero/sysman/source/linux/sysman_fs_access.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include namespace L0 { @@ -32,81 +33,94 @@ static ze_result_t getResult(int err) { } } +void FdCache::eraseLeastUsedEntryFromCache() { + auto it = fdMap.begin(); + uint32_t fdCountRef = it->second.second; + std::map>::iterator leastUsedIterator = it; + while (++it != fdMap.end()) { + if (it->second.second < fdCountRef) { + fdCountRef = it->second.second; + leastUsedIterator = it; + } + } + NEO::SysCalls::close(leastUsedIterator->second.first); + fdMap.erase(leastUsedIterator); +} + +int FdCache::getFd(std::string file) { + int fd = -1; + if (fdMap.find(file) == fdMap.end()) { + fd = NEO::SysCalls::open(file.c_str(), O_RDONLY); + if (fd < 0) { + return -1; + } + if (fdMap.size() == maxSize) { + eraseLeastUsedEntryFromCache(); + } + fdMap[file] = std::make_pair(fd, 1); + } else { + auto &fdPair = fdMap[file]; + fdPair.second++; + } + return fdMap[file].first; +} + +FdCache::~FdCache() { + for (auto it = fdMap.begin(); it != fdMap.end(); ++it) { + NEO::SysCalls::close(it->second.second); + } + fdMap.clear(); +} + +template +ze_result_t FsAccess::readValue(const std::string file, T &val) { + + std::string readVal(64, '\0'); + int fd = pFdCache->getFd(file); + if (fd < 0) { + return getResult(errno); + } + + ssize_t bytesRead = NEO::SysCalls::pread(fd, readVal.data(), readVal.size(), 0); + if (bytesRead < 0) { + return getResult(errno); + } + + std::istringstream stream(readVal); + stream >> val; + if (stream.fail()) { + return ZE_RESULT_ERROR_UNKNOWN; + } + + return ZE_RESULT_SUCCESS; +} // Generic Filesystem Access FsAccess::FsAccess() { + pFdCache = std::make_unique(); } +FsAccess::FsAccess(const FsAccess &fsAccess) : pFdCache(std::unique_ptr(new FdCache())) {} + FsAccess *FsAccess::create() { return new FsAccess(); } ze_result_t FsAccess::read(const std::string file, uint64_t &val) { - // Read a single line from text file without trailing newline - std::ifstream fs; - - fs.open(file.c_str()); - if (fs.fail()) { - return getResult(errno); - } - fs >> val; - if (fs.fail()) { - fs.close(); - return getResult(errno); - } - fs.close(); - return ZE_RESULT_SUCCESS; + return readValue(file, val); } ze_result_t FsAccess::read(const std::string file, double &val) { - // Read a single line from text file without trailing newline - std::ifstream fs; - - fs.open(file.c_str()); - if (fs.fail()) { - return getResult(errno); - } - fs >> val; - if (fs.fail()) { - fs.close(); - return getResult(errno); - } - fs.close(); - return ZE_RESULT_SUCCESS; + return readValue(file, val); } ze_result_t FsAccess::read(const std::string file, int32_t &val) { - // Read a single line from text file without trailing newline - std::ifstream fs; - - fs.open(file.c_str()); - if (fs.fail()) { - return getResult(errno); - } - fs >> val; - if (fs.fail()) { - fs.close(); - return getResult(errno); - } - fs.close(); - return ZE_RESULT_SUCCESS; + return readValue(file, val); } ze_result_t FsAccess::read(const std::string file, uint32_t &val) { - // Read a single line from text file without trailing newline - std::ifstream fs; - - fs.open(file.c_str()); - if (fs.fail()) { - return getResult(errno); - } - fs >> val; - if (fs.fail()) { - fs.close(); - return getResult(errno); - } - fs.close(); - return ZE_RESULT_SUCCESS; + return readValue(file, val); } + ze_result_t FsAccess::read(const std::string file, std::string &val) { // Read a single line from text file without trailing newline std::ifstream fs; @@ -427,75 +441,19 @@ ze_result_t SysfsAccess::read(const std::string file, std::string &val) { } ze_result_t SysfsAccess::read(const std::string file, int32_t &val) { - std::string str; - ze_result_t result; - - result = FsAccess::read(fullPath(file), str); - if (ZE_RESULT_SUCCESS != result) { - return result; - } - - std::istringstream stream(str); - stream >> val; - - if (stream.fail()) { - return ZE_RESULT_ERROR_UNKNOWN; - } - return ZE_RESULT_SUCCESS; + return FsAccess::read(fullPath(file), val); } ze_result_t SysfsAccess::read(const std::string file, uint32_t &val) { - std::string str; - ze_result_t result; - - result = FsAccess::read(fullPath(file), str); - if (ZE_RESULT_SUCCESS != result) { - return result; - } - - std::istringstream stream(str); - stream >> val; - - if (stream.fail()) { - return ZE_RESULT_ERROR_UNKNOWN; - } - return ZE_RESULT_SUCCESS; + return FsAccess::read(fullPath(file), val); } ze_result_t SysfsAccess::read(const std::string file, double &val) { - std::string str; - ze_result_t result; - - result = FsAccess::read(fullPath(file), str); - if (ZE_RESULT_SUCCESS != result) { - return result; - } - - std::istringstream stream(str); - stream >> val; - - if (stream.fail()) { - return ZE_RESULT_ERROR_UNKNOWN; - } - return ZE_RESULT_SUCCESS; + return FsAccess::read(fullPath(file), val); } ze_result_t SysfsAccess::read(const std::string file, uint64_t &val) { - std::string str; - ze_result_t result; - - result = FsAccess::read(fullPath(file), str); - if (ZE_RESULT_SUCCESS != result) { - return result; - } - - std::istringstream stream(str); - stream >> val; - - if (stream.fail()) { - return ZE_RESULT_ERROR_UNKNOWN; - } - return ZE_RESULT_SUCCESS; + return FsAccess::read(fullPath(file), val); } ze_result_t SysfsAccess::read(const std::string file, std::vector &val) { diff --git a/level_zero/sysman/source/linux/sysman_fs_access.h b/level_zero/sysman/source/linux/sysman_fs_access.h index 595731fb0f..cf301abbda 100644 --- a/level_zero/sysman/source/linux/sysman_fs_access.h +++ b/level_zero/sysman/source/linux/sysman_fs_access.h @@ -15,6 +15,8 @@ #include #include #include +#include +#include #include #include #include @@ -26,9 +28,25 @@ namespace L0 { namespace Sysman { +class FdCache { + public: + FdCache() = default; + ~FdCache(); + + static const int maxSize = 10; + int getFd(std::string file); + + protected: + std::map> fdMap = {}; + + private: + void eraseLeastUsedEntryFromCache(); +}; + class FsAccess { public: static FsAccess *create(); + FsAccess(const FsAccess &fsAccess); virtual ~FsAccess() = default; virtual ze_result_t canRead(const std::string file); @@ -57,6 +75,11 @@ class FsAccess { FsAccess(); decltype(&NEO::SysCalls::access) accessSyscall = NEO::SysCalls::access; decltype(&stat) statSyscall = stat; + + private: + template + ze_result_t readValue(const std::string file, T &val); + std::unique_ptr pFdCache = nullptr; }; class ProcfsAccess : private FsAccess { diff --git a/level_zero/sysman/test/unit_tests/sources/linux/test_sysman.cpp b/level_zero/sysman/test/unit_tests/sources/linux/test_sysman.cpp index 7aac2c79cf..61bd00215f 100644 --- a/level_zero/sysman/test/unit_tests/sources/linux/test_sysman.cpp +++ b/level_zero/sysman/test/unit_tests/sources/linux/test_sysman.cpp @@ -160,6 +160,87 @@ TEST_F(SysmanDeviceFixture, GivenCreateProcfsAccessHandleWhenCallinggetProcfsAcc EXPECT_EQ(&pLinuxSysmanImp->getProcfsAccess(), pLinuxSysmanImp->pProcfsAccess); } +TEST_F(SysmanDeviceFixture, GivenSysfsAccessClassAndIntegerWhenCallingReadOnMultipleFilesThenSuccessIsReturned) { + + VariableBackup mockOpen(&NEO::SysCalls::sysCallsOpen, [](const char *pathname, int flags) -> int { + return 1; + }); + + VariableBackup mockPread(&NEO::SysCalls::sysCallsPread, [](int fd, void *buf, size_t count, off_t offset) -> ssize_t { + std::string value = "123"; + memcpy(buf, value.data(), value.size()); + return value.size(); + }); + + PublicSysfsAccess *tempSysfsAccess = new PublicSysfsAccess(); + std::string fileName = {}; + int iVal32; + for (auto i = 0; i < L0::Sysman::FdCache::maxSize + 2; i++) { + fileName = "mockfile" + std::to_string(i) + ".txt"; + EXPECT_EQ(ZE_RESULT_SUCCESS, tempSysfsAccess->read(fileName, iVal32)); + } + delete tempSysfsAccess; +} + +TEST(FdCacheTest, GivenValidFdCacheWhenCallingGetFdOnSameFileThenVerifyCacheIsUpdatedProperly) { + + class MockFdCache : public FdCache { + public: + using FdCache::fdMap; + }; + + VariableBackup mockOpen(&NEO::SysCalls::sysCallsOpen, [](const char *pathname, int flags) -> int { + return 1; + }); + + std::unique_ptr pFdCache = std::make_unique(); + std::string fileName = {}; + for (auto i = 0; i < L0::Sysman::FdCache::maxSize; i++) { + fileName = "mockfile" + std::to_string(i) + ".txt"; + EXPECT_LE(0, pFdCache->getFd(fileName)); + } + + fileName = "mockfile0.txt"; + for (auto i = 0; i < 3; i++) { + EXPECT_LE(0, pFdCache->getFd(fileName)); + } + + for (auto i = 1; i < L0::Sysman::FdCache::maxSize - 1; i++) { + fileName = "mockfile" + std::to_string(i) + ".txt"; + EXPECT_LE(0, pFdCache->getFd(fileName)); + } + + // Get Fd after the cache is full. + EXPECT_LE(0, pFdCache->getFd("dummy.txt")); + + // Verify Cache have the elemets that are accessed more number of times + EXPECT_NE(pFdCache->fdMap.end(), pFdCache->fdMap.find("mockfile0.txt")); + + // Verify cache doesn't have an element that is accessed less number of times. + EXPECT_EQ(pFdCache->fdMap.end(), pFdCache->fdMap.find("mockfile9.txt")); +} + +TEST_F(SysmanDeviceFixture, GivenSysfsAccessClassAndOpenSysCallFailsWhenCallingReadThenFailureIsReturned) { + + VariableBackup mockOpen(&NEO::SysCalls::sysCallsOpen, [](const char *pathname, int flags) -> int { + return -1; + }); + + VariableBackup mockPread(&NEO::SysCalls::sysCallsPread, [](int fd, void *buf, size_t count, off_t offset) -> ssize_t { + std::string value = "123"; + memcpy(buf, value.data(), value.size()); + return value.size(); + }); + + PublicSysfsAccess *tempSysfsAccess = new PublicSysfsAccess(); + const std::string fileName = "mockFile.txt"; + int iVal32; + + errno = ENOENT; + EXPECT_EQ(ZE_RESULT_ERROR_NOT_AVAILABLE, tempSysfsAccess->read(fileName, iVal32)); + delete tempSysfsAccess; +} + TEST_F(SysmanDeviceFixture, GivenValidPidWhenCallingProcfsAccessIsAliveThenSuccessIsReturned) { VariableBackup allowFakeDevicePathBackup(&SysCalls::allowFakeDevicePath, true); auto procfsAccess = pLinuxSysmanImp->getProcfsAccess(); diff --git a/level_zero/tools/source/sysman/linux/fs_access.cpp b/level_zero/tools/source/sysman/linux/fs_access.cpp index df951a3b0f..84f4c1481c 100644 --- a/level_zero/tools/source/sysman/linux/fs_access.cpp +++ b/level_zero/tools/source/sysman/linux/fs_access.cpp @@ -31,17 +31,55 @@ static ze_result_t getResult(int err) { } } +void FdCache::eraseLeastUsedEntryFromCache() { + auto it = fdMap.begin(); + uint32_t fdCountRef = it->second.second; + std::map>::iterator leastUsedIterator = it; + while (++it != fdMap.end()) { + if (it->second.second < fdCountRef) { + fdCountRef = it->second.second; + leastUsedIterator = it; + } + } + NEO::SysCalls::close(leastUsedIterator->second.first); + fdMap.erase(leastUsedIterator); +} + +int FdCache::getFd(std::string file) { + int fd = -1; + if (fdMap.find(file) == fdMap.end()) { + fd = NEO::SysCalls::open(file.c_str(), O_RDONLY); + if (fd < 0) { + return -1; + } + if (fdMap.size() == maxSize) { + eraseLeastUsedEntryFromCache(); + } + fdMap[file] = std::make_pair(fd, 1); + } else { + auto &fdPair = fdMap[file]; + fdPair.second++; + } + return fdMap[file].first; +} + +FdCache::~FdCache() { + for (auto it = fdMap.begin(); it != fdMap.end(); ++it) { + NEO::SysCalls::close(it->second.second); + } + fdMap.clear(); +} + template ze_result_t FsAccess::readValue(const std::string file, T &val) { std::string readVal(64, '\0'); - int fd = NEO::SysCalls::open(file.c_str(), O_RDONLY); + int fd = pFdCache->getFd(file); if (fd < 0) { return getResult(errno); } ssize_t bytesRead = NEO::SysCalls::pread(fd, readVal.data(), readVal.size(), 0); - NEO::SysCalls::close(fd); if (bytesRead < 0) { return getResult(errno); } @@ -57,8 +95,11 @@ ze_result_t FsAccess::readValue(const std::string file, T &val) { // Generic Filesystem Access FsAccess::FsAccess() { + pFdCache = std::make_unique(); } +FsAccess::FsAccess(const FsAccess &fsAccess) : pFdCache(std::unique_ptr(new FdCache())) {} + FsAccess *FsAccess::create() { return new FsAccess(); } diff --git a/level_zero/tools/source/sysman/linux/fs_access.h b/level_zero/tools/source/sysman/linux/fs_access.h index 6293301040..72f6b2238e 100644 --- a/level_zero/tools/source/sysman/linux/fs_access.h +++ b/level_zero/tools/source/sysman/linux/fs_access.h @@ -16,6 +16,8 @@ #include #include #include +#include +#include #include #include #include @@ -26,9 +28,25 @@ namespace L0 { +class FdCache { + public: + FdCache() = default; + ~FdCache(); + + static const int maxSize = 10; + int getFd(std::string file); + + protected: + std::map> fdMap = {}; + + private: + void eraseLeastUsedEntryFromCache(); +}; + class FsAccess { public: static FsAccess *create(); + FsAccess(const FsAccess &fsAccess); virtual ~FsAccess() = default; virtual ze_result_t canRead(const std::string file); @@ -61,6 +79,7 @@ class FsAccess { private: template ze_result_t readValue(const std::string file, T &val); + std::unique_ptr pFdCache = nullptr; }; class ProcfsAccess : private FsAccess { diff --git a/level_zero/tools/test/unit_tests/sources/sysman/linux/test_sysman.cpp b/level_zero/tools/test/unit_tests/sources/sysman/linux/test_sysman.cpp index 908f3077a1..50b48fe786 100644 --- a/level_zero/tools/test/unit_tests/sources/sysman/linux/test_sysman.cpp +++ b/level_zero/tools/test/unit_tests/sources/sysman/linux/test_sysman.cpp @@ -413,6 +413,87 @@ TEST_F(SysmanDeviceFixture, GivenSysfsAccessClassAndIntegerWhenCallingReadThenSu delete tempSysfsAccess; } +TEST_F(SysmanDeviceFixture, GivenSysfsAccessClassAndOpenSysCallFailsWhenCallingReadThenFailureIsReturned) { + + VariableBackup mockOpen(&NEO::SysCalls::sysCallsOpen, [](const char *pathname, int flags) -> int { + return -1; + }); + + VariableBackup mockPread(&NEO::SysCalls::sysCallsPread, [](int fd, void *buf, size_t count, off_t offset) -> ssize_t { + std::string value = "123"; + memcpy(buf, value.data(), value.size()); + return value.size(); + }); + + PublicSysfsAccess *tempSysfsAccess = new PublicSysfsAccess(); + const std::string fileName = "mockFile.txt"; + int iVal32; + + errno = ENOENT; + EXPECT_EQ(ZE_RESULT_ERROR_NOT_AVAILABLE, tempSysfsAccess->read(fileName, iVal32)); + delete tempSysfsAccess; +} + +TEST_F(SysmanDeviceFixture, GivenSysfsAccessClassAndIntegerWhenCallingReadOnMultipleFilesThenSuccessIsReturned) { + + VariableBackup mockOpen(&NEO::SysCalls::sysCallsOpen, [](const char *pathname, int flags) -> int { + return 1; + }); + + VariableBackup mockPread(&NEO::SysCalls::sysCallsPread, [](int fd, void *buf, size_t count, off_t offset) -> ssize_t { + std::string value = "123"; + memcpy(buf, value.data(), value.size()); + return value.size(); + }); + + PublicSysfsAccess *tempSysfsAccess = new PublicSysfsAccess(); + std::string fileName = {}; + int iVal32; + for (auto i = 0; i < L0::FdCache::maxSize + 2; i++) { + fileName = "mockfile" + std::to_string(i) + ".txt"; + EXPECT_EQ(ZE_RESULT_SUCCESS, tempSysfsAccess->read(fileName, iVal32)); + } + delete tempSysfsAccess; +} + +TEST(FdCacheTest, GivenValidFdCacheWhenCallingGetFdOnSameFileThenVerifyCacheIsUpdatedProperly) { + + class MockFdCache : public FdCache { + public: + using FdCache::fdMap; + }; + + VariableBackup mockOpen(&NEO::SysCalls::sysCallsOpen, [](const char *pathname, int flags) -> int { + return 1; + }); + + std::unique_ptr pFdCache = std::make_unique(); + std::string fileName = {}; + for (auto i = 0; i < L0::FdCache::maxSize; i++) { + fileName = "mockfile" + std::to_string(i) + ".txt"; + EXPECT_LE(0, pFdCache->getFd(fileName)); + } + + fileName = "mockfile0.txt"; + for (auto i = 0; i < 3; i++) { + EXPECT_LE(0, pFdCache->getFd(fileName)); + } + + for (auto i = 1; i < L0::FdCache::maxSize - 1; i++) { + fileName = "mockfile" + std::to_string(i) + ".txt"; + EXPECT_LE(0, pFdCache->getFd(fileName)); + } + + // Get Fd after the cache is full. + EXPECT_LE(0, pFdCache->getFd("dummy.txt")); + + // Verify Cache have the elemets that are accessed more number of times + EXPECT_NE(pFdCache->fdMap.end(), pFdCache->fdMap.find("mockfile0.txt")); + + // Verify cache doesn't have an element that is accessed less number of times. + EXPECT_EQ(pFdCache->fdMap.end(), pFdCache->fdMap.find("mockfile9.txt")); +} + TEST_F(SysmanDeviceFixture, GivenSysfsAccessClassAndUnsignedIntegerWhenCallingReadThenSuccessIsReturned) { VariableBackup mockOpen(&NEO::SysCalls::sysCallsOpen, [](const char *pathname, int flags) -> int {