ctresalloc.cxx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. #include <cassert>
  2. #include <chrono>
  3. #include <cstddef> // IWYU pragma: keep
  4. #include <cstdlib>
  5. #include <iostream>
  6. #include <map>
  7. #include <set>
  8. #include <string>
  9. #include <thread>
  10. #include <utility>
  11. #include <vector>
  12. #include "cmsys/Encoding.hxx"
  13. #include "cmsys/FStream.hxx"
  14. #include "cmCTestMultiProcessHandler.h"
  15. #include "cmCTestResourceAllocator.h"
  16. #include "cmCTestResourceSpec.h"
  17. #include "cmCTestTestHandler.h"
  18. #include "cmFileLock.h"
  19. #include "cmFileLockResult.h"
  20. #include "cmList.h"
  21. #include "cmStringAlgorithms.h"
  22. #include "cmSystemTools.h"
  23. /*
  24. * This helper program is used to verify that the CTest resource allocation
  25. * feature is working correctly. It consists of two stages:
  26. *
  27. * 1) write - This stage receives the RESOURCE_GROUPS property of the test and
  28. * compares it with the values passed in the CTEST_RESOURCE_GROUP_*
  29. * environment variables. If it received all of the resources it expected,
  30. * then it writes this information to a log file, which will be read in
  31. * the verify stage.
  32. * 2) verify - This stage compares the log file with the resource spec file to
  33. * make sure that no resources were over-subscribed, deallocated without
  34. * being allocated, or allocated without being deallocated.
  35. */
  36. static int usage(const char* argv0)
  37. {
  38. std::cout << "Usage: " << argv0 << " (write|verify) <args...>" << std::endl;
  39. return 1;
  40. }
  41. static int usageWrite(const char* argv0)
  42. {
  43. std::cout << "Usage: " << argv0
  44. << " write <log-file> <test-name> <sleep-time-secs>"
  45. " [<resource-groups-property>]"
  46. << std::endl;
  47. return 1;
  48. }
  49. static int usageVerify(const char* argv0)
  50. {
  51. std::cout << "Usage: " << argv0
  52. << " verify <log-file> <resource-spec-file> [<test-names>]"
  53. << std::endl;
  54. return 1;
  55. }
  56. static int doWrite(int argc, char const* const* argv)
  57. {
  58. if (argc < 5 || argc > 6) {
  59. return usageWrite(argv[0]);
  60. }
  61. std::string logFile = argv[2];
  62. std::string testName = argv[3];
  63. unsigned int sleepTime = std::atoi(argv[4]);
  64. std::vector<std::map<
  65. std::string, std::vector<cmCTestMultiProcessHandler::ResourceAllocation>>>
  66. resources;
  67. if (argc == 6) {
  68. // Parse RESOURCE_GROUPS property
  69. std::string resourceGroupsProperty = argv[5];
  70. std::vector<
  71. std::vector<cmCTestTestHandler::cmCTestTestResourceRequirement>>
  72. resourceGroups;
  73. bool result = cmCTestTestHandler::ParseResourceGroupsProperty(
  74. resourceGroupsProperty, resourceGroups);
  75. (void)result;
  76. assert(result);
  77. // Verify group count
  78. const char* resourceGroupCountEnv =
  79. cmSystemTools::GetEnv("CTEST_RESOURCE_GROUP_COUNT");
  80. if (!resourceGroupCountEnv) {
  81. std::cout << "CTEST_RESOURCE_GROUP_COUNT should be defined" << std::endl;
  82. return 1;
  83. }
  84. int resourceGroupCount = std::atoi(resourceGroupCountEnv);
  85. if (resourceGroups.size() !=
  86. static_cast<std::size_t>(resourceGroupCount)) {
  87. std::cout
  88. << "CTEST_RESOURCE_GROUP_COUNT does not match expected resource groups"
  89. << std::endl
  90. << "Expected: " << resourceGroups.size() << std::endl
  91. << "Actual: " << resourceGroupCount << std::endl;
  92. return 1;
  93. }
  94. if (!cmSystemTools::Touch(logFile + ".lock", true)) {
  95. std::cout << "Could not create lock file" << std::endl;
  96. return 1;
  97. }
  98. cmFileLock lock;
  99. auto lockResult =
  100. lock.Lock(logFile + ".lock", static_cast<unsigned long>(-1));
  101. if (!lockResult.IsOk()) {
  102. std::cout << "Could not lock file" << std::endl;
  103. return 1;
  104. }
  105. std::size_t i = 0;
  106. cmsys::ofstream fout(logFile.c_str(), std::ios::app);
  107. fout << "begin " << testName << std::endl;
  108. for (auto& resourceGroup : resourceGroups) {
  109. try {
  110. // Build and verify set of expected resources
  111. std::set<std::string> expectedResources;
  112. for (auto const& it : resourceGroup) {
  113. expectedResources.insert(it.ResourceType);
  114. }
  115. std::string prefix = "CTEST_RESOURCE_GROUP_";
  116. prefix += std::to_string(i);
  117. const char* actualResourcesCStr = cmSystemTools::GetEnv(prefix);
  118. if (!actualResourcesCStr) {
  119. std::cout << prefix << " should be defined" << std::endl;
  120. return 1;
  121. }
  122. auto actualResourcesVec =
  123. cmSystemTools::SplitString(actualResourcesCStr, ',');
  124. std::set<std::string> actualResources;
  125. for (auto const& r : actualResourcesVec) {
  126. if (!r.empty()) {
  127. actualResources.insert(r);
  128. }
  129. }
  130. if (actualResources != expectedResources) {
  131. std::cout << prefix << " did not list expected resources"
  132. << std::endl;
  133. return 1;
  134. }
  135. // Verify that we got what we asked for and write it to the log
  136. prefix += '_';
  137. std::map<std::string,
  138. std::vector<cmCTestMultiProcessHandler::ResourceAllocation>>
  139. resEntry;
  140. for (auto const& type : actualResources) {
  141. auto it = resourceGroup.begin();
  142. std::string varName = prefix;
  143. varName += cmSystemTools::UpperCase(type);
  144. const char* varVal = cmSystemTools::GetEnv(varName);
  145. if (!varVal) {
  146. std::cout << varName << " should be defined" << std::endl;
  147. return 1;
  148. }
  149. auto received = cmSystemTools::SplitString(varVal, ';');
  150. for (auto const& r : received) {
  151. while (it->ResourceType != type || it->UnitsNeeded == 0) {
  152. ++it;
  153. if (it == resourceGroup.end()) {
  154. std::cout << varName << " did not list expected resources"
  155. << std::endl;
  156. return 1;
  157. }
  158. }
  159. auto split = cmSystemTools::SplitString(r, ',');
  160. if (split.size() != 2) {
  161. std::cout << varName << " was ill-formed" << std::endl;
  162. return 1;
  163. }
  164. if (!cmHasLiteralPrefix(split[0], "id:")) {
  165. std::cout << varName << " was ill-formed" << std::endl;
  166. return 1;
  167. }
  168. auto id = split[0].substr(3);
  169. if (!cmHasLiteralPrefix(split[1], "slots:")) {
  170. std::cout << varName << " was ill-formed" << std::endl;
  171. return 1;
  172. }
  173. auto slots = split[1].substr(6);
  174. unsigned int amount = std::atoi(slots.c_str());
  175. if (amount != static_cast<unsigned int>(it->SlotsNeeded)) {
  176. std::cout << varName << " did not list expected resources"
  177. << std::endl;
  178. return 1;
  179. }
  180. --it->UnitsNeeded;
  181. fout << "alloc " << type << " " << id << " " << amount
  182. << std::endl;
  183. resEntry[type].push_back({ id, amount });
  184. }
  185. bool ended = false;
  186. while (it->ResourceType != type || it->UnitsNeeded == 0) {
  187. ++it;
  188. if (it == resourceGroup.end()) {
  189. ended = true;
  190. break;
  191. }
  192. }
  193. if (!ended) {
  194. std::cout << varName << " did not list expected resources"
  195. << std::endl;
  196. return 1;
  197. }
  198. }
  199. resources.push_back(resEntry);
  200. ++i;
  201. } catch (...) {
  202. std::cout << "Unknown error while processing resources" << std::endl;
  203. return 1;
  204. }
  205. }
  206. auto unlockResult = lock.Release();
  207. if (!unlockResult.IsOk()) {
  208. std::cout << "Could not unlock file" << std::endl;
  209. return 1;
  210. }
  211. } else {
  212. if (cmSystemTools::GetEnv("CTEST_RESOURCE_GROUP_COUNT")) {
  213. std::cout << "CTEST_RESOURCE_GROUP_COUNT should not be defined"
  214. << std::endl;
  215. return 1;
  216. }
  217. }
  218. std::this_thread::sleep_for(std::chrono::seconds(sleepTime));
  219. if (argc == 6) {
  220. if (!cmSystemTools::Touch(logFile + ".lock", true)) {
  221. std::cout << "Could not create lock file" << std::endl;
  222. return 1;
  223. }
  224. cmFileLock lock;
  225. auto lockResult =
  226. lock.Lock(logFile + ".lock", static_cast<unsigned long>(-1));
  227. if (!lockResult.IsOk()) {
  228. std::cout << "Could not lock file" << std::endl;
  229. return 1;
  230. }
  231. cmsys::ofstream fout(logFile.c_str(), std::ios::app);
  232. for (auto const& group : resources) {
  233. for (auto const& it : group) {
  234. for (auto const& it2 : it.second) {
  235. fout << "dealloc " << it.first << " " << it2.Id << " " << it2.Slots
  236. << std::endl;
  237. }
  238. }
  239. }
  240. fout << "end " << testName << std::endl;
  241. auto unlockResult = lock.Release();
  242. if (!unlockResult.IsOk()) {
  243. std::cout << "Could not unlock file" << std::endl;
  244. return 1;
  245. }
  246. }
  247. return 0;
  248. }
  249. static int doVerify(int argc, char const* const* argv)
  250. {
  251. if (argc < 4 || argc > 5) {
  252. return usageVerify(argv[0]);
  253. }
  254. std::string logFile = argv[2];
  255. std::string resFile = argv[3];
  256. std::string testNames;
  257. if (argc == 5) {
  258. testNames = argv[4];
  259. }
  260. cmList testNameList{ testNames };
  261. std::set<std::string> testNameSet(testNameList.begin(), testNameList.end());
  262. cmCTestResourceSpec spec;
  263. if (spec.ReadFromJSONFile(resFile) != true) {
  264. std::cout << "Could not read resource spec " << resFile << std::endl;
  265. return 1;
  266. }
  267. cmCTestResourceAllocator allocator;
  268. allocator.InitializeFromResourceSpec(spec);
  269. cmsys::ifstream fin(logFile.c_str(), std::ios::in);
  270. if (!fin) {
  271. std::cout << "Could not open log file " << logFile << std::endl;
  272. return 1;
  273. }
  274. std::string command;
  275. std::string resourceName;
  276. std::string resourceId;
  277. std::string testName;
  278. unsigned int amount;
  279. std::set<std::string> inProgressTests;
  280. std::set<std::string> completedTests;
  281. try {
  282. while (fin >> command) {
  283. if (command == "begin") {
  284. if (!(fin >> testName)) {
  285. std::cout << "Could not read begin line" << std::endl;
  286. return 1;
  287. }
  288. if (!testNameSet.count(testName) || inProgressTests.count(testName) ||
  289. completedTests.count(testName)) {
  290. std::cout << "Could not begin test" << std::endl;
  291. return 1;
  292. }
  293. inProgressTests.insert(testName);
  294. } else if (command == "alloc") {
  295. if (!(fin >> resourceName) || !(fin >> resourceId) ||
  296. !(fin >> amount)) {
  297. std::cout << "Could not read alloc line" << std::endl;
  298. return 1;
  299. }
  300. if (!allocator.AllocateResource(resourceName, resourceId, amount)) {
  301. std::cout << "Could not allocate resources" << std::endl;
  302. return 1;
  303. }
  304. } else if (command == "dealloc") {
  305. if (!(fin >> resourceName) || !(fin >> resourceId) ||
  306. !(fin >> amount)) {
  307. std::cout << "Could not read dealloc line" << std::endl;
  308. return 1;
  309. }
  310. if (!allocator.DeallocateResource(resourceName, resourceId, amount)) {
  311. std::cout << "Could not deallocate resources" << std::endl;
  312. return 1;
  313. }
  314. } else if (command == "end") {
  315. if (!(fin >> testName)) {
  316. std::cout << "Could not read end line" << std::endl;
  317. return 1;
  318. }
  319. if (!inProgressTests.erase(testName)) {
  320. std::cout << "Could not end test" << std::endl;
  321. return 1;
  322. }
  323. if (!completedTests.insert(testName).second) {
  324. std::cout << "Could not end test" << std::endl;
  325. return 1;
  326. }
  327. }
  328. }
  329. } catch (...) {
  330. std::cout << "Unknown error while reading log file" << std::endl;
  331. return 1;
  332. }
  333. auto const& avail = allocator.GetResources();
  334. for (auto const& it : avail) {
  335. for (auto const& it2 : it.second) {
  336. if (it2.second.Locked != 0) {
  337. std::cout << "Resource was not unlocked" << std::endl;
  338. return 1;
  339. }
  340. }
  341. }
  342. if (completedTests != testNameSet) {
  343. std::cout << "Tests were not ended" << std::endl;
  344. return 1;
  345. }
  346. return 0;
  347. }
  348. int main(int argc, char const* const* argv)
  349. {
  350. cmsys::Encoding::CommandLineArguments args =
  351. cmsys::Encoding::CommandLineArguments::Main(argc, argv);
  352. argc = args.argc();
  353. argv = args.argv();
  354. if (argc < 2) {
  355. return usage(argv[0]);
  356. }
  357. std::string argv1 = argv[1];
  358. if (argv1 == "write") {
  359. return doWrite(argc, argv);
  360. }
  361. if (argv1 == "verify") {
  362. return doVerify(argc, argv);
  363. }
  364. return usage(argv[0]);
  365. }