ctresalloc.cxx 12 KB

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