compile-keymap.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. /*
  2. * Copyright © 2024 Pierre Le Marre <dev@wismill.eu>
  3. * SPDX-License-Identifier: MIT
  4. */
  5. #include "config.h"
  6. #include <fcntl.h>
  7. #include <limits.h>
  8. #include <time.h>
  9. #include <getopt.h>
  10. #include "test.h"
  11. #include "xkbcommon/xkbcommon.h"
  12. #include "utils.h"
  13. #include "keymap-formats.h"
  14. #include "bench.h"
  15. #define DEFAULT_ITERATIONS 3000
  16. #define DEFAULT_STDEV 0.05
  17. static void
  18. usage(FILE *fp, char **argv)
  19. {
  20. fprintf(fp, "Usage: %s [OPTIONS]\n"
  21. "\n"
  22. "Benchmark compilation of the given RMLVO\n"
  23. "\n"
  24. "Options:\n"
  25. " --help\n"
  26. " Print this help and exit\n"
  27. " --iter\n"
  28. " Exact number of iterations to run\n"
  29. " --stdev\n"
  30. " Minimal relative standard deviation (percentage) to reach.\n"
  31. " (default: %f)\n"
  32. "Note: --iter and --stdev are mutually exclusive.\n"
  33. "\n"
  34. "XKB-specific options:\n"
  35. " --input-format <format>\n"
  36. " The keymap format to use for parsing (default: '%s')\n"
  37. #ifdef KEYMAP_DUMP
  38. " --output-format <format>\n"
  39. " The keymap format to use for serializing (default: same as input)\n"
  40. #endif
  41. " --pretty\n"
  42. " Enable pretty-printing in keymap serialization\n"
  43. " --keep-unused\n"
  44. " Keep unused bits in keymap serialization\n"
  45. " --explicit-values\n"
  46. " Force serializing explicit values\n"
  47. " --keymap\n"
  48. " Load the corresponding XKB file, ignore RMLVO options.\n"
  49. " --rules <rules>\n"
  50. " The XKB ruleset (default: '%s')\n"
  51. " --model <model>\n"
  52. " The XKB model (default: '%s')\n"
  53. " --layout <layout>\n"
  54. " The XKB layout (default: '%s')\n"
  55. " --variant <variant>\n"
  56. " The XKB layout variant (default: '%s')\n"
  57. " --options <options>\n"
  58. " The XKB options (default: '%s')\n"
  59. "\n",
  60. argv[0], DEFAULT_STDEV * 100,
  61. xkb_keymap_get_format_label(DEFAULT_INPUT_KEYMAP_FORMAT),
  62. DEFAULT_XKB_RULES, DEFAULT_XKB_MODEL, DEFAULT_XKB_LAYOUT,
  63. DEFAULT_XKB_VARIANT ? DEFAULT_XKB_VARIANT : "<none>",
  64. DEFAULT_XKB_OPTIONS ? DEFAULT_XKB_OPTIONS : "<none>");
  65. }
  66. static struct xkb_keymap *
  67. load_keymap(struct xkb_context *ctx, const char *keymap_path,
  68. const struct xkb_rule_names * rmlvo, enum xkb_keymap_format format,
  69. enum xkb_keymap_compile_flags flags)
  70. {
  71. if (keymap_path) {
  72. FILE *file = fopen(keymap_path, "r");
  73. if (!file) {
  74. fprintf(stderr, "ERROR: cannot open file: %s\n", keymap_path);
  75. return NULL;
  76. }
  77. struct xkb_keymap *keymap = xkb_keymap_new_from_file(
  78. ctx, file, format, XKB_KEYMAP_COMPILE_NO_FLAGS
  79. );
  80. fclose(file);
  81. return keymap;
  82. } else {
  83. return xkb_keymap_new_from_names2(ctx, rmlvo, format,
  84. XKB_KEYMAP_COMPILE_NO_FLAGS);
  85. }
  86. }
  87. int
  88. main(int argc, char **argv)
  89. {
  90. struct xkb_context *context;
  91. struct bench bench;
  92. struct bench_time elapsed;
  93. struct estimate est;
  94. enum xkb_keymap_format keymap_input_format = DEFAULT_INPUT_KEYMAP_FORMAT;
  95. #ifdef KEYMAP_DUMP
  96. static_assert(DEFAULT_OUTPUT_KEYMAP_FORMAT == XKB_KEYMAP_USE_ORIGINAL_FORMAT,
  97. "Out of sync usage()");
  98. enum xkb_keymap_format keymap_output_format = DEFAULT_OUTPUT_KEYMAP_FORMAT;
  99. #endif
  100. enum xkb_keymap_serialize_flags serialize_flags = XKB_KEYMAP_SERIALIZE_NO_FLAGS;
  101. bool explicit_iterations = false;
  102. int ret = 0;
  103. char *keymap_path = NULL;
  104. struct xkb_rule_names rmlvo = {
  105. .rules = DEFAULT_XKB_RULES,
  106. .model = DEFAULT_XKB_MODEL,
  107. /* layout and variant are tied together, so we either get user-supplied for
  108. * both or default for both, see below */
  109. .layout = NULL,
  110. .variant = NULL,
  111. .options = DEFAULT_XKB_OPTIONS,
  112. };
  113. unsigned int max_iterations = DEFAULT_ITERATIONS;
  114. double stdev = DEFAULT_STDEV;
  115. enum options {
  116. OPT_KEYMAP_INPUT_FORMAT,
  117. OPT_KEYMAP_OUTPUT_FORMAT,
  118. OPT_KEYMAP_PRETTY,
  119. OPT_KEYMAP_KEEP_UNUSED,
  120. OPT_KEYMAP_EXPLICIT_VALUES,
  121. OPT_KEYMAP,
  122. OPT_RULES,
  123. OPT_MODEL,
  124. OPT_LAYOUT,
  125. OPT_VARIANT,
  126. OPT_OPTION,
  127. OPT_ITERATIONS,
  128. OPT_STDEV,
  129. };
  130. static struct option opts[] = {
  131. {"help", no_argument, 0, 'h'},
  132. {"input-format", required_argument, 0, OPT_KEYMAP_INPUT_FORMAT},
  133. #ifdef KEYMAP_DUMP
  134. {"output-format", required_argument, 0, OPT_KEYMAP_OUTPUT_FORMAT},
  135. #endif
  136. {"pretty", no_argument, 0, OPT_KEYMAP_PRETTY},
  137. {"keep-unused", no_argument, 0, OPT_KEYMAP_KEEP_UNUSED},
  138. {"explicit-values", no_argument, 0, OPT_KEYMAP_EXPLICIT_VALUES},
  139. {"keymap", required_argument, 0, OPT_KEYMAP},
  140. {"rules", required_argument, 0, OPT_RULES},
  141. {"model", required_argument, 0, OPT_MODEL},
  142. {"layout", required_argument, 0, OPT_LAYOUT},
  143. {"variant", required_argument, 0, OPT_VARIANT},
  144. {"options", required_argument, 0, OPT_OPTION},
  145. {"iter", required_argument, 0, OPT_ITERATIONS},
  146. {"stdev", required_argument, 0, OPT_STDEV},
  147. {0, 0, 0, 0},
  148. };
  149. while (1) {
  150. int c;
  151. int option_index = 0;
  152. c = getopt_long(argc, argv, "h", opts, &option_index);
  153. if (c == -1)
  154. break;
  155. switch (c) {
  156. case 'h':
  157. usage(stdout, argv);
  158. exit(EXIT_SUCCESS);
  159. case OPT_KEYMAP_INPUT_FORMAT:
  160. keymap_input_format = xkb_keymap_parse_format(optarg);
  161. if (!keymap_input_format) {
  162. fprintf(stderr, "ERROR: invalid --input-format: \"%s\"\n", optarg);
  163. usage(stderr, argv);
  164. exit(EXIT_INVALID_USAGE);
  165. }
  166. break;
  167. #ifdef KEYMAP_DUMP
  168. case OPT_KEYMAP_OUTPUT_FORMAT:
  169. keymap_output_format = xkb_keymap_parse_format(optarg);
  170. if (!keymap_output_format) {
  171. fprintf(stderr, "ERROR: invalid --output-format: \"%s\"\n", optarg);
  172. usage(stderr, argv);
  173. exit(EXIT_INVALID_USAGE);
  174. }
  175. break;
  176. #endif
  177. case OPT_KEYMAP_PRETTY:
  178. serialize_flags |= XKB_KEYMAP_SERIALIZE_PRETTY;
  179. break;
  180. case OPT_KEYMAP_KEEP_UNUSED:
  181. serialize_flags |= XKB_KEYMAP_SERIALIZE_KEEP_UNUSED;
  182. break;
  183. case OPT_KEYMAP_EXPLICIT_VALUES:
  184. serialize_flags |= TEST_KEYMAP_SERIALIZE_EXPLICIT;
  185. break;
  186. case OPT_KEYMAP:
  187. keymap_path = optarg;
  188. break;
  189. case OPT_RULES:
  190. rmlvo.rules = optarg;
  191. break;
  192. case OPT_MODEL:
  193. rmlvo.model = optarg;
  194. break;
  195. case OPT_LAYOUT:
  196. rmlvo.layout = optarg;
  197. break;
  198. case OPT_VARIANT:
  199. rmlvo.variant = optarg;
  200. break;
  201. case OPT_OPTION:
  202. rmlvo.options = optarg;
  203. break;
  204. case OPT_ITERATIONS:
  205. if (max_iterations == 0) {
  206. usage(stderr, argv);
  207. exit(EXIT_INVALID_USAGE);
  208. }
  209. {
  210. errno = 0;
  211. char *endp = optarg;
  212. const unsigned long raw = strtoul(optarg, &endp, 10);
  213. if (errno || optarg == endp || *endp != '\0' ||
  214. !raw || raw > UINT_MAX) {
  215. fprintf(stderr,
  216. "ERROR: invalid 'iter' parameter; "
  217. "using default: %u\n", DEFAULT_ITERATIONS);
  218. max_iterations = DEFAULT_ITERATIONS;
  219. } else {
  220. max_iterations = (unsigned int)raw;
  221. }
  222. }
  223. explicit_iterations = true;
  224. break;
  225. case OPT_STDEV:
  226. if (explicit_iterations) {
  227. usage(stderr, argv);
  228. exit(EXIT_INVALID_USAGE);
  229. }
  230. {
  231. errno = 0;
  232. char *endp = optarg;
  233. stdev = strtod(optarg, &endp) / 100;
  234. if (errno || optarg == endp || *endp != '\0' || stdev <= 0){
  235. fprintf(stderr,
  236. "ERROR: invalid 'stdev' parameter; "
  237. "using default: %.3f\n", DEFAULT_STDEV);
  238. stdev = DEFAULT_STDEV;
  239. }
  240. }
  241. max_iterations = 0;
  242. break;
  243. default:
  244. usage(stderr, argv);
  245. exit(EXIT_INVALID_USAGE);
  246. }
  247. }
  248. /* Now fill in the layout */
  249. if (!rmlvo.layout || !*rmlvo.layout) {
  250. if (rmlvo.variant && *rmlvo.variant) {
  251. fprintf(stderr, "Error: a variant requires a layout\n");
  252. return EXIT_INVALID_USAGE;
  253. }
  254. rmlvo.layout = DEFAULT_XKB_LAYOUT;
  255. rmlvo.variant = DEFAULT_XKB_VARIANT;
  256. }
  257. context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
  258. if (!context)
  259. exit(1);
  260. struct xkb_keymap *keymap = load_keymap(context, keymap_path, &rmlvo,
  261. keymap_input_format,
  262. XKB_KEYMAP_COMPILE_NO_FLAGS);
  263. if (!keymap) {
  264. fprintf(stderr, "ERROR: Cannot compile keymap.\n");
  265. ret = EXIT_FAILURE;
  266. goto keymap_error;
  267. }
  268. #ifndef KEYMAP_DUMP
  269. /* Cache the keymap input to mitigate I/O latency */
  270. char *keymap_str = NULL;
  271. size_t keymap_str_length = 0;
  272. FILE *keymap_file = NULL;
  273. if (keymap_path) {
  274. /* Load keymap file into memory */
  275. keymap_file = fopen(keymap_path, "r");
  276. if (!keymap_file) {
  277. fprintf(stderr, "ERROR: cannot open file: %s\n", keymap_path);
  278. ret = EXIT_FAILURE;
  279. goto keymap_error;
  280. }
  281. if (!map_file(keymap_file, &keymap_str, &keymap_str_length)) {
  282. fclose(keymap_file);
  283. ret = EXIT_FAILURE;
  284. goto keymap_error;
  285. }
  286. } else {
  287. /*
  288. * Serialize from RMLVO
  289. *
  290. * This has the caveat that the benchmarked input is different from the
  291. * original KcCGST files.
  292. */
  293. keymap_str = xkb_keymap_get_as_string2(
  294. keymap, XKB_KEYMAP_USE_ORIGINAL_FORMAT, serialize_flags
  295. );
  296. if (!keymap_str) {
  297. fprintf(stderr, "ERROR: cannot serialize keymap\n");
  298. ret = EXIT_FAILURE;
  299. goto keymap_error;
  300. }
  301. keymap_str_length = strlen(keymap_str);
  302. }
  303. xkb_keymap_unref(keymap);
  304. #endif
  305. /* Suspend stdout and stderr outputs */
  306. fflush(stdout);
  307. int stdout_old = dup(STDOUT_FILENO);
  308. int stdout_new = open("/dev/null", O_WRONLY);
  309. if (stdout_old == -1 || stdout_new == -1 ||
  310. dup2(stdout_new, STDOUT_FILENO) == -1) {
  311. perror("Stdout error");
  312. exit(EXIT_FAILURE);
  313. }
  314. close(stdout_new);
  315. fflush(stderr);
  316. int stderr_old = dup(STDERR_FILENO);
  317. int stderr_new = open("/dev/null", O_WRONLY);
  318. if (stderr_old == -1 || stderr_new == -1 ||
  319. dup2(stderr_new, STDERR_FILENO) == -1) {
  320. perror("Stderr error");
  321. exit(EXIT_FAILURE);
  322. }
  323. close(stderr_new);
  324. if (explicit_iterations) {
  325. stdev = 0;
  326. bench_start2(&bench);
  327. for (unsigned int i = 0; i < max_iterations; i++) {
  328. #ifdef KEYMAP_DUMP
  329. char *s = xkb_keymap_get_as_string2(keymap, keymap_output_format,
  330. serialize_flags);
  331. assert(s);
  332. free(s);
  333. #else
  334. keymap = xkb_keymap_new_from_buffer(
  335. context, keymap_str, keymap_str_length,
  336. keymap_input_format, XKB_KEYMAP_COMPILE_NO_FLAGS
  337. );
  338. assert(keymap);
  339. xkb_keymap_unref(keymap);
  340. #endif
  341. }
  342. bench_stop2(&bench);
  343. bench_elapsed(&bench, &elapsed);
  344. est.elapsed = (bench_time_elapsed_nanoseconds(&elapsed)) / max_iterations;
  345. est.stdev = 0;
  346. } else {
  347. bench_start2(&bench);
  348. #ifdef KEYMAP_DUMP
  349. BENCH(stdev, max_iterations, elapsed, est,
  350. char *s = xkb_keymap_get_as_string2(keymap, keymap_output_format,
  351. serialize_flags);
  352. assert(s);
  353. free(s);
  354. );
  355. #else
  356. BENCH(stdev, max_iterations, elapsed, est,
  357. keymap = xkb_keymap_new_from_buffer(
  358. context, keymap_str, keymap_str_length,
  359. keymap_input_format, XKB_KEYMAP_COMPILE_NO_FLAGS
  360. );
  361. assert(keymap);
  362. xkb_keymap_unref(keymap);
  363. );
  364. #endif
  365. bench_stop2(&bench);
  366. }
  367. /* Restore stdout and stderr outputs */
  368. fflush(stdout);
  369. dup2(stdout_old, STDOUT_FILENO);
  370. close(stdout_old);
  371. fflush(stderr);
  372. dup2(stderr_old, STDERR_FILENO);
  373. close(stderr_old);
  374. #ifdef KEYMAP_DUMP
  375. xkb_keymap_unref(keymap);
  376. #else
  377. if (keymap_str && keymap_file) {
  378. unmap_file(keymap_str, keymap_str_length);
  379. fclose(keymap_file);
  380. } else {
  381. free(keymap_str);
  382. }
  383. #endif
  384. struct bench_time total_elapsed;
  385. bench_elapsed(&bench, &total_elapsed);
  386. if (explicit_iterations) {
  387. fprintf(stderr,
  388. "mean: %lld µs; compiled %u keymaps in %ld.%06lds\n",
  389. est.elapsed / 1000, max_iterations,
  390. total_elapsed.seconds, total_elapsed.nanoseconds / 1000);
  391. } else {
  392. fprintf(stderr,
  393. "mean: %lld µs; stdev: %Lf%% (target: %f%%); "
  394. "last run: compiled %u keymaps in %ld.%06lds; "
  395. "total time: %ld.%06lds\n", est.elapsed / 1000,
  396. (long double) est.stdev * 100.0 / (long double) est.elapsed,
  397. stdev * 100,
  398. max_iterations, elapsed.seconds, elapsed.nanoseconds / 1000,
  399. total_elapsed.seconds, total_elapsed.nanoseconds / 1000);
  400. }
  401. keymap_error:
  402. xkb_context_unref(context);
  403. return ret;
  404. }