ctresalloc.cxx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  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(char const* argv0)
  37. {
  38. std::cout << "Usage: " << argv0 << " (write|verify) <args...>" << std::endl;
  39. return 1;
  40. }
  41. static int usageWrite(char const* 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(char const* 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. char const* 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 = cmStrCat("CTEST_RESOURCE_GROUP_", i);
  116. char const* actualResourcesCStr = cmSystemTools::GetEnv(prefix);
  117. if (!actualResourcesCStr) {
  118. std::cout << prefix << " should be defined" << std::endl;
  119. return 1;
  120. }
  121. auto actualResourcesVec =
  122. cmSystemTools::SplitString(actualResourcesCStr, ',');
  123. std::set<std::string> actualResources;
  124. for (auto const& r : actualResourcesVec) {
  125. if (!r.empty()) {
  126. actualResources.insert(r);
  127. }
  128. }
  129. if (actualResources != expectedResources) {
  130. std::cout << prefix << " did not list expected resources"
  131. << std::endl;
  132. return 1;
  133. }
  134. // Verify that we got what we asked for and write it to the log
  135. prefix += '_';
  136. std::map<std::string,
  137. std::vector<cmCTestMultiProcessHandler::ResourceAllocation>>
  138. resEntry;
  139. for (auto const& type : actualResources) {
  140. auto it = resourceGroup.begin();
  141. std::string varName = prefix;
  142. varName += cmSystemTools::UpperCase(type);
  143. char const* varVal = cmSystemTools::GetEnv(varName);
  144. if (!varVal) {
  145. std::cout << varName << " should be defined" << std::endl;
  146. return 1;
  147. }
  148. auto received = cmSystemTools::SplitString(varVal, ';');
  149. for (auto const& r : received) {
  150. while (it->ResourceType != type || it->UnitsNeeded == 0) {
  151. ++it;
  152. if (it == resourceGroup.end()) {
  153. std::cout << varName << " did not list expected resources"
  154. << std::endl;
  155. return 1;
  156. }
  157. }
  158. auto split = cmSystemTools::SplitString(r, ',');
  159. if (split.size() != 2) {
  160. std::cout << varName << " was ill-formed" << std::endl;
  161. return 1;
  162. }
  163. if (!cmHasLiteralPrefix(split[0], "id:")) {
  164. std::cout << varName << " was ill-formed" << std::endl;
  165. return 1;
  166. }
  167. auto id = split[0].substr(3);
  168. if (!cmHasLiteralPrefix(split[1], "slots:")) {
  169. std::cout << varName << " was ill-formed" << std::endl;
  170. return 1;
  171. }
  172. auto slots = split[1].substr(6);
  173. unsigned int amount = std::atoi(slots.c_str());
  174. if (amount != static_cast<unsigned int>(it->SlotsNeeded)) {
  175. std::cout << varName << " did not list expected resources"
  176. << std::endl;
  177. return 1;
  178. }
  179. --it->UnitsNeeded;
  180. fout << "alloc " << type << " " << id << " " << amount
  181. << std::endl;
  182. resEntry[type].push_back({ id, amount });
  183. }
  184. bool ended = false;
  185. while (it->ResourceType != type || it->UnitsNeeded == 0) {
  186. ++it;
  187. if (it == resourceGroup.end()) {
  188. ended = true;
  189. break;
  190. }
  191. }
  192. if (!ended) {
  193. std::cout << varName << " did not list expected resources"
  194. << std::endl;
  195. return 1;
  196. }
  197. }
  198. resources.push_back(resEntry);
  199. ++i;
  200. } catch (...) {
  201. std::cout << "Unknown error while processing resources" << std::endl;
  202. return 1;
  203. }
  204. }
  205. auto unlockResult = lock.Release();
  206. if (!unlockResult.IsOk()) {
  207. std::cout << "Could not unlock file" << std::endl;
  208. return 1;
  209. }
  210. } else {
  211. if (cmSystemTools::GetEnv("CTEST_RESOURCE_GROUP_COUNT")) {
  212. std::cout << "CTEST_RESOURCE_GROUP_COUNT should not be defined"
  213. << std::endl;
  214. return 1;
  215. }
  216. }
  217. std::this_thread::sleep_for(std::chrono::seconds(sleepTime));
  218. if (argc == 6) {
  219. if (!cmSystemTools::Touch(logFile + ".lock", true)) {
  220. std::cout << "Could not create lock file" << std::endl;
  221. return 1;
  222. }
  223. cmFileLock lock;
  224. auto lockResult =
  225. lock.Lock(logFile + ".lock", static_cast<unsigned long>(-1));
  226. if (!lockResult.IsOk()) {
  227. std::cout << "Could not lock file" << std::endl;
  228. return 1;
  229. }
  230. cmsys::ofstream fout(logFile.c_str(), std::ios::app);
  231. for (auto const& group : resources) {
  232. for (auto const& it : group) {
  233. for (auto const& it2 : it.second) {
  234. fout << "dealloc " << it.first << " " << it2.Id << " " << it2.Slots
  235. << std::endl;
  236. }
  237. }
  238. }
  239. fout << "end " << testName << std::endl;
  240. auto unlockResult = lock.Release();
  241. if (!unlockResult.IsOk()) {
  242. std::cout << "Could not unlock file" << std::endl;
  243. return 1;
  244. }
  245. }
  246. return 0;
  247. }
  248. static int doVerify(int argc, char const* const* argv)
  249. {
  250. if (argc < 4 || argc > 5) {
  251. return usageVerify(argv[0]);
  252. }
  253. std::string logFile = argv[2];
  254. std::string resFile = argv[3];
  255. std::string testNames;
  256. if (argc == 5) {
  257. testNames = argv[4];
  258. }
  259. cmList testNameList{ testNames };
  260. std::set<std::string> testNameSet(testNameList.begin(), testNameList.end());
  261. cmCTestResourceSpec spec;
  262. if (spec.ReadFromJSONFile(resFile) != true) {
  263. std::cout << "Could not read resource spec " << resFile << std::endl;
  264. return 1;
  265. }
  266. cmCTestResourceAllocator allocator;
  267. allocator.InitializeFromResourceSpec(spec);
  268. cmsys::ifstream fin(logFile.c_str(), std::ios::in);
  269. if (!fin) {
  270. std::cout << "Could not open log file " << logFile << std::endl;
  271. return 1;
  272. }
  273. std::string command;
  274. std::string resourceName;
  275. std::string resourceId;
  276. std::string testName;
  277. unsigned int amount;
  278. std::set<std::string> inProgressTests;
  279. std::set<std::string> completedTests;
  280. try {
  281. while (fin >> command) {
  282. if (command == "begin") {
  283. if (!(fin >> testName)) {
  284. std::cout << "Could not read begin line" << std::endl;
  285. return 1;
  286. }
  287. if (!testNameSet.count(testName) || inProgressTests.count(testName) ||
  288. completedTests.count(testName)) {
  289. std::cout << "Could not begin test" << std::endl;
  290. return 1;
  291. }
  292. inProgressTests.insert(testName);
  293. } else if (command == "alloc") {
  294. if (!(fin >> resourceName) || !(fin >> resourceId) ||
  295. !(fin >> amount)) {
  296. std::cout << "Could not read alloc line" << std::endl;
  297. return 1;
  298. }
  299. if (!allocator.AllocateResource(resourceName, resourceId, amount)) {
  300. std::cout << "Could not allocate resources" << std::endl;
  301. return 1;
  302. }
  303. } else if (command == "dealloc") {
  304. if (!(fin >> resourceName) || !(fin >> resourceId) ||
  305. !(fin >> amount)) {
  306. std::cout << "Could not read dealloc line" << std::endl;
  307. return 1;
  308. }
  309. if (!allocator.DeallocateResource(resourceName, resourceId, amount)) {
  310. std::cout << "Could not deallocate resources" << std::endl;
  311. return 1;
  312. }
  313. } else if (command == "end") {
  314. if (!(fin >> testName)) {
  315. std::cout << "Could not read end line" << std::endl;
  316. return 1;
  317. }
  318. if (!inProgressTests.erase(testName)) {
  319. std::cout << "Could not end test" << std::endl;
  320. return 1;
  321. }
  322. if (!completedTests.insert(testName).second) {
  323. std::cout << "Could not end test" << std::endl;
  324. return 1;
  325. }
  326. }
  327. }
  328. } catch (...) {
  329. std::cout << "Unknown error while reading log file" << std::endl;
  330. return 1;
  331. }
  332. auto const& avail = allocator.GetResources();
  333. for (auto const& it : avail) {
  334. for (auto const& it2 : it.second) {
  335. if (it2.second.Locked != 0) {
  336. std::cout << "Resource was not unlocked" << std::endl;
  337. return 1;
  338. }
  339. }
  340. }
  341. if (completedTests != testNameSet) {
  342. std::cout << "Tests were not ended" << std::endl;
  343. return 1;
  344. }
  345. return 0;
  346. }
  347. int main(int argc, char const* const* argv)
  348. {
  349. cmsys::Encoding::CommandLineArguments args =
  350. cmsys::Encoding::CommandLineArguments::Main(argc, argv);
  351. argc = args.argc();
  352. argv = args.argv();
  353. if (argc < 2) {
  354. return usage(argv[0]);
  355. }
  356. std::string argv1 = argv[1];
  357. if (argv1 == "write") {
  358. return doWrite(argc, argv);
  359. }
  360. if (argv1 == "verify") {
  361. return doVerify(argc, argv);
  362. }
  363. return usage(argv[0]);
  364. }