+/*
+ * run_compression_benchmarks.c
+ *
+ * Program to measure compression ratio and performance of wimlib and WIMGAPI.
+ */
+
+#define _WIN32_WINNT 0x0602
+#include "wimlib.h"
+#include "wimgapi_wrapper.h"
+
+#define ARRAY_LEN(A) (sizeof(A) / sizeof((A)[0]))
+
+#define VOLUME L"E:\\"
+#define INFILE L"in.wim"
+#define IN_IMAGE 1
+#define OUTFILE L"out.wim"
+#define TMPDIR L"."
+#define OUTDIR L"t"
+
+static void
+fatal_wimlib_error(const wchar_t *msg, int err)
+{
+ fwprintf(stderr, L"Error %ls: wimlib error code %d: %ls\n", msg,
+ err, wimlib_get_error_string(err));
+ exit(1);
+}
+
+static wchar_t *
+get_win32_error_string(DWORD err)
+{
+ static wchar_t buf[1024];
+ buf[0] = L'\0';
+ FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, 0,
+ buf, ARRAY_LEN(buf), NULL);
+ return buf;
+}
+
+static void
+fatal_win32_error(const wchar_t *msg)
+{
+ DWORD err = GetLastError();
+ fwprintf(stderr, L"Error %ls: Win32 error code %u: %ls\n", msg,
+ err, get_win32_error_string(err));
+ exit(1);
+}
+
+static uint64_t start_time_ms;
+
+static void
+sync_volume(void)
+{
+ wchar_t path[16];
+ HANDLE h;
+
+ wsprintf(path, L"\\\\.\\%lc:", VOLUME[0]);
+
+ h = CreateFile(path, GENERIC_WRITE, FILE_SHARE_VALID_FLAGS,
+ NULL, OPEN_EXISTING, 0, NULL);
+
+ if (!FlushFileBuffers(h))
+ fatal_win32_error(L"Unable to sync volume " VOLUME);
+
+ CloseHandle(h);
+}
+
+static void
+prefetch_input_wim(void)
+{
+ WIMStruct *wim;
+ int ret;
+
+ ret = wimlib_open_wim(INFILE, 0, &wim);
+ if (ret)
+ fatal_wimlib_error(L"opening input WIM for prefetch", ret);
+
+ ret = wimlib_verify_wim(wim, 0);
+ if (ret)
+ fatal_wimlib_error(L"prefetching input WIM", ret);
+
+ wimlib_free(wim);
+}
+
+static void
+begin_test(void)
+{
+ sync_volume();
+ prefetch_input_wim();
+ start_time_ms = GetTickCount64();
+}
+
+static void
+verify_output_wim_with_wimlib(void)
+{
+ WIMStruct *wim;
+ int ret;
+
+ ret = wimlib_open_wim(OUTFILE, 0, &wim);
+ if (ret) {
+ fatal_wimlib_error(L"opening output WIM for verification with "
+ "wimlib", ret);
+ }
+
+ ret = wimlib_verify_wim(wim, 0);
+ if (ret)
+ fatal_wimlib_error(L"verifying output WIM with wimlib", ret);
+
+ wimlib_free(wim);
+}
+
+static void
+verify_output_wim_with_wimgapi(void)
+{
+ HANDLE hWim;
+ HANDLE hImage;
+
+ hWim = WIMCreateFile(OUTFILE, WIM_GENERIC_READ, WIM_OPEN_EXISTING,
+ WIM_FLAG_SOLID, 0, NULL);
+ if (!hWim) {
+ fatal_win32_error(L"opening output WIM for verification with "
+ "WIMGAPI");
+ }
+ WIMSetTemporaryPath(hWim, TMPDIR);
+
+ hImage = WIMLoadImage(hWim, 1);
+ if (!hImage) {
+ fatal_win32_error(L"loading WIM image for verification with "
+ "WIMGAPI");
+ }
+
+ CreateDirectory(OUTDIR, NULL);
+
+ if (!WIMApplyImage(hImage, OUTDIR, WIM_FLAG_VERIFY))
+ fatal_win32_error(L"verifying output WIM with WIMGAPI");
+
+ WIMCloseHandle(hImage);
+ WIMCloseHandle(hWim);
+}
+
+
+static void
+verify_output_wim(void)
+{
+ verify_output_wim_with_wimlib();
+ verify_output_wim_with_wimgapi();
+}
+
+static void
+end_test(const char *description)
+{
+ uint64_t end_time_ms = GetTickCount64();
+ HANDLE h = CreateFile(OUTFILE, 0, FILE_SHARE_VALID_FLAGS, NULL,
+ OPEN_EXISTING, 0, NULL);
+ LARGE_INTEGER compressed_size = {.QuadPart = -1};
+ GetFileSizeEx(h, &compressed_size);
+ CloseHandle(h);
+ printf("%s: %"PRIu64" in %.1fs\n", description,
+ compressed_size.QuadPart,
+ (double)(end_time_ms - start_time_ms) / 1000);
+
+ verify_output_wim();
+}
+
+static const struct wimlib_test_spec {
+ const char *description;
+ enum wimlib_compression_type ctype;
+ int level;
+ uint32_t chunk_size;
+ bool solid;
+} wimlib_test_specs[] = {
+ {
+ .description = "wimlib, LZMS (solid)",
+ .ctype = WIMLIB_COMPRESSION_TYPE_LZMS,
+ .solid = true,
+ },
+ {
+ .description = "wimlib, LZMS (non-solid)",
+ .ctype = WIMLIB_COMPRESSION_TYPE_LZMS,
+ },
+ {
+ .description = "wimlib, LZX (slow)",
+ .ctype = WIMLIB_COMPRESSION_TYPE_LZX,
+ .level = 100,
+ },
+ {
+ .description = "wimlib, LZX (normal)",
+ .ctype = WIMLIB_COMPRESSION_TYPE_LZX,
+ },
+ {
+ .description = "wimlib, LZX (quick)",
+ .ctype = WIMLIB_COMPRESSION_TYPE_LZX,
+ .level = 20,
+ },
+ {
+ .description = "wimlib, XPRESS (slow)",
+ .ctype = WIMLIB_COMPRESSION_TYPE_XPRESS,
+ .level = 80,
+ },
+ {
+ .description = "wimlib, XPRESS",
+ .ctype = WIMLIB_COMPRESSION_TYPE_XPRESS,
+ },
+ {
+ .description = "wimlib, \"WIMBoot\" (slow)",
+ .ctype = WIMLIB_COMPRESSION_TYPE_XPRESS,
+ .level = 80,
+ .chunk_size = 4096,
+ },
+ {
+ .description = "wimlib, \"WIMBoot\"",
+ .ctype = WIMLIB_COMPRESSION_TYPE_XPRESS,
+ .chunk_size = 4096,
+ },
+ {
+ .description = "wimlib, None",
+ .ctype = WIMLIB_COMPRESSION_TYPE_NONE,
+ },
+};
+
+static const struct wimgapi_test_spec {
+ const char *description;
+ DWORD compressionType;
+ bool solid;
+ bool wimboot;
+} wimgapi_test_specs[] = {
+ {
+ .description = "WIMGAPI, LZMS (solid)",
+ .compressionType = WIM_COMPRESS_LZMS,
+ .solid = true,
+ },
+ {
+ .description = "WIMGAPI, LZX",
+ .compressionType = WIM_COMPRESS_LZX,
+ },
+ {
+ .description = "WIMGAPI, XPRESS",
+ .compressionType = WIM_COMPRESS_XPRESS,
+ },
+ {
+ .description = "WIMGAPI, \"WIMBoot\"",
+ .compressionType = WIM_COMPRESS_XPRESS,
+ .wimboot = true,
+ },
+ {
+ .description = "WIMGAPI, None",
+ .compressionType = WIM_COMPRESS_NONE,
+ },
+};
+
+static void
+run_wimlib_test(const struct wimlib_test_spec *testspec)
+{
+ WIMStruct *in, *out;
+ int ret;
+
+ ret = wimlib_set_default_compression_level(-1, 0);
+ if (ret)
+ fatal_wimlib_error(L"resetting wimlib compression levels", ret);
+
+ begin_test();
+
+ ret = wimlib_open_wim(INFILE, 0, &in);
+ if (ret)
+ fatal_wimlib_error(L"opening input WIM with wimlib", ret);
+
+ if (testspec->level) {
+ ret = wimlib_set_default_compression_level(testspec->ctype,
+ testspec->level);
+ if (ret) {
+ fatal_wimlib_error(L"setting wimlib compression level",
+ ret);
+ }
+ }
+
+ ret = wimlib_create_new_wim(testspec->ctype, &out);
+ if (ret)
+ fatal_wimlib_error(L"creating output WIMStruct", ret);
+
+ if (testspec->solid) {
+ ret = wimlib_set_output_pack_compression_type(out,
+ testspec->ctype);
+ if (ret) {
+ fatal_wimlib_error(L"setting wimlib solid compression "
+ "type", ret);
+ }
+ }
+
+ if (testspec->chunk_size) {
+ if (testspec->solid) {
+ ret = wimlib_set_output_pack_chunk_size(out,
+ testspec->chunk_size);
+ } else {
+ ret = wimlib_set_output_chunk_size(out,
+ testspec->chunk_size);
+ }
+ if (ret) {
+ fatal_wimlib_error(L"setting wimlib output chunk size",
+ ret);
+ }
+ }
+
+ ret = wimlib_export_image(in, IN_IMAGE, out, NULL, NULL, 0);
+ if (ret)
+ fatal_wimlib_error(L"exporting image with wimlib", ret);
+
+ ret = wimlib_write(out, OUTFILE, WIMLIB_ALL_IMAGES,
+ (testspec->solid ? WIMLIB_WRITE_FLAG_SOLID : 0), 0);
+ if (ret)
+ fatal_wimlib_error(L"writing output WIM with wimlib", ret);
+
+ wimlib_free(in);
+ wimlib_free(out);
+
+ end_test(testspec->description);
+}
+
+static void
+run_wimgapi_test(const struct wimgapi_test_spec *testspec)
+{
+ HANDLE hInWim, hOutWim;
+ HANDLE hInImage;
+ DWORD flags = 0;
+
+ begin_test();
+
+ hInWim = WIMCreateFile(INFILE, WIM_GENERIC_READ, WIM_OPEN_EXISTING,
+ 0, 0, NULL);
+
+ if (!hInWim)
+ fatal_win32_error(L"opening input WIM with WIMGAPI");
+
+ WIMSetTemporaryPath(hInWim, TMPDIR);
+
+ hInImage = WIMLoadImage(hInWim, IN_IMAGE);
+ if (!hInImage)
+ fatal_win32_error(L"loading input image with WIMGAPI");
+
+ if (testspec->solid)
+ flags |= WIM_FLAG_SOLID;
+ if (testspec->wimboot)
+ flags |= WIM_FLAG_WIM_BOOT;
+ hOutWim = WIMCreateFile(OUTFILE, WIM_GENERIC_WRITE, WIM_CREATE_ALWAYS,
+ flags, testspec->compressionType, NULL);
+ if (!hOutWim)
+ fatal_win32_error(L"opening output WIM with WIMGAPI");
+
+ WIMSetTemporaryPath(hOutWim, TMPDIR);
+
+ if (!WIMExportImage(hInImage, hOutWim, 0))
+ fatal_win32_error(L"exporting image with WIMGAPI");
+
+ WIMCloseHandle(hOutWim);
+ WIMCloseHandle(hInImage);
+ WIMCloseHandle(hInWim);
+
+ end_test(testspec->description);
+}
+
+int
+wmain(int argc, wchar_t *argv[])
+{
+ if (!SetCurrentDirectory(VOLUME))
+ fatal_win32_error(L"changing directory to " VOLUME);
+
+ for (size_t i = 0; i < ARRAY_LEN(wimlib_test_specs); i++)
+ run_wimlib_test(&wimlib_test_specs[i]);
+
+ for (size_t i = 0; i < ARRAY_LEN(wimgapi_test_specs); i++)
+ run_wimgapi_test(&wimgapi_test_specs[i]);
+
+ return 0;
+}