Commit 0a54247652e49ce384dcf0d8df078201aa106089
1 parent
0adfd74f
Add QUtil::get_max_memory_usage for testing
Showing
7 changed files
with
127 additions
and
1 deletions
cSpell.json
| @@ -131,6 +131,7 @@ | @@ -131,6 +131,7 @@ | ||
| 131 | "esize", | 131 | "esize", |
| 132 | "eval", | 132 | "eval", |
| 133 | "extlibdir", | 133 | "extlibdir", |
| 134 | + "fclose", | ||
| 134 | "fdict", | 135 | "fdict", |
| 135 | "ffield", | 136 | "ffield", |
| 136 | "fghij", | 137 | "fghij", |
| @@ -268,6 +269,7 @@ | @@ -268,6 +269,7 @@ | ||
| 268 | "maxdepth", | 269 | "maxdepth", |
| 269 | "maxobjectid", | 270 | "maxobjectid", |
| 270 | "mdash", | 271 | "mdash", |
| 272 | + "memstream", | ||
| 271 | "mindepth", | 273 | "mindepth", |
| 272 | "mkdir", | 274 | "mkdir", |
| 273 | "mkinstalldirs", | 275 | "mkinstalldirs", |
include/qpdf/QUtil.hh
| @@ -525,7 +525,17 @@ namespace QUtil | @@ -525,7 +525,17 @@ namespace QUtil | ||
| 525 | wchar_t const* const argv[], | 525 | wchar_t const* const argv[], |
| 526 | std::function<int(int, char const* const[])> realmain); | 526 | std::function<int(int, char const* const[])> realmain); |
| 527 | #endif // QPDF_NO_WCHAR_T | 527 | #endif // QPDF_NO_WCHAR_T |
| 528 | -}; // namespace QUtil | 528 | + |
| 529 | + // Try to return the maximum amount of memory allocated by the | ||
| 530 | + // current process and its threads. Return 0 if unable to | ||
| 531 | + // determine. This is Linux-specific and not implemented to be | ||
| 532 | + // completely reliable. It is used during development for | ||
| 533 | + // performance testing to detect changes that may significantly | ||
| 534 | + // change memory usage. It is not recommended for use for other | ||
| 535 | + // purposes. | ||
| 536 | + QPDF_DLL | ||
| 537 | + size_t get_max_memory_usage(); | ||
| 538 | +}; // namespace QUtil | ||
| 529 | 539 | ||
| 530 | inline bool | 540 | inline bool |
| 531 | QUtil::is_hex_digit(char ch) | 541 | QUtil::is_hex_digit(char ch) |
libqpdf/CMakeLists.txt
| @@ -375,6 +375,29 @@ int main(int argc, char* argv[]) { | @@ -375,6 +375,29 @@ int main(int argc, char* argv[]) { | ||
| 375 | endif() | 375 | endif() |
| 376 | endfunction() | 376 | endfunction() |
| 377 | 377 | ||
| 378 | +check_c_source_compiles( | ||
| 379 | +"#include <malloc.h> | ||
| 380 | +#include <stdio.h> | ||
| 381 | +int main(int argc, char* argv[]) { | ||
| 382 | + malloc_info(0, stdout); | ||
| 383 | + return 0; | ||
| 384 | +}" | ||
| 385 | + HAVE_MALLOC_INFO) | ||
| 386 | + | ||
| 387 | +check_c_source_compiles( | ||
| 388 | +"#include <stdio.h> | ||
| 389 | +#include <stdlib.h> | ||
| 390 | +int main(int argc, char* argv[]) { | ||
| 391 | + char* buf; | ||
| 392 | + size_t size; | ||
| 393 | + FILE* f; | ||
| 394 | + f = open_memstream(&buf, &size); | ||
| 395 | + fclose(f); | ||
| 396 | + free(buf); | ||
| 397 | + return 0; | ||
| 398 | +}" | ||
| 399 | + HAVE_OPEN_MEMSTREAM) | ||
| 400 | + | ||
| 378 | qpdf_check_ll_fmt("%lld" fmt_lld) | 401 | qpdf_check_ll_fmt("%lld" fmt_lld) |
| 379 | qpdf_check_ll_fmt("%I64d" fmt_i64d) | 402 | qpdf_check_ll_fmt("%I64d" fmt_i64d) |
| 380 | qpdf_check_ll_fmt("%I64lld" fmt_i64lld) | 403 | qpdf_check_ll_fmt("%I64lld" fmt_i64lld) |
libqpdf/QUtil.cc
| @@ -37,6 +37,9 @@ | @@ -37,6 +37,9 @@ | ||
| 37 | # include <sys/stat.h> | 37 | # include <sys/stat.h> |
| 38 | # include <unistd.h> | 38 | # include <unistd.h> |
| 39 | #endif | 39 | #endif |
| 40 | +#ifdef HAVE_MALLOC_INFO | ||
| 41 | +# include <malloc.h> | ||
| 42 | +#endif | ||
| 40 | 43 | ||
| 41 | // First element is 24 | 44 | // First element is 24 |
| 42 | static unsigned short pdf_doc_low_to_unicode[] = { | 45 | static unsigned short pdf_doc_low_to_unicode[] = { |
| @@ -1968,3 +1971,73 @@ QUtil::call_main_from_wmain( | @@ -1968,3 +1971,73 @@ QUtil::call_main_from_wmain( | ||
| 1968 | } | 1971 | } |
| 1969 | 1972 | ||
| 1970 | #endif // QPDF_NO_WCHAR_T | 1973 | #endif // QPDF_NO_WCHAR_T |
| 1974 | + | ||
| 1975 | +size_t | ||
| 1976 | +QUtil::get_max_memory_usage() | ||
| 1977 | +{ | ||
| 1978 | +#if defined(HAVE_MALLOC_INFO) && defined(HAVE_OPEN_MEMSTREAM) | ||
| 1979 | + static std::regex tag_re("<(/?\\w+)([^>]*?)>"); | ||
| 1980 | + static std::regex attr_re("(\\w+)=\"(.*?)\""); | ||
| 1981 | + | ||
| 1982 | + char* buf; | ||
| 1983 | + size_t size; | ||
| 1984 | + FILE* f = open_memstream(&buf, &size); | ||
| 1985 | + if (f == nullptr) { | ||
| 1986 | + return 0; | ||
| 1987 | + } | ||
| 1988 | + malloc_info(0, f); | ||
| 1989 | + fclose(f); | ||
| 1990 | + if (QUtil::get_env("QPDF_DEBUG_MEM_USAGE")) { | ||
| 1991 | + fprintf(stderr, "%s", buf); | ||
| 1992 | + } | ||
| 1993 | + | ||
| 1994 | + // Warning: this code uses regular expression to extract data from | ||
| 1995 | + // an XML string. This is generally a bad idea, but we're going to | ||
| 1996 | + // do it anyway because QUtil.hh warns against using this function | ||
| 1997 | + // for other than development/testing, and if this function fails | ||
| 1998 | + // to generate reasonable output during performance testing, it | ||
| 1999 | + // will be noticed. | ||
| 2000 | + | ||
| 2001 | + // This is my best guess at how to interpret malloc_info. Anyway | ||
| 2002 | + // it seems to provide useful information for detecting code | ||
| 2003 | + // changes that drastically change memory usage. | ||
| 2004 | + size_t result = 0; | ||
| 2005 | + try { | ||
| 2006 | + std::cregex_iterator m_begin(buf, buf + size, tag_re); | ||
| 2007 | + std::cregex_iterator cr_end; | ||
| 2008 | + std::sregex_iterator sr_end; | ||
| 2009 | + | ||
| 2010 | + int in_heap = 0; | ||
| 2011 | + for (auto m = m_begin; m != cr_end; ++m) { | ||
| 2012 | + std::string tag(m->str(1)); | ||
| 2013 | + if (tag == "heap") { | ||
| 2014 | + ++in_heap; | ||
| 2015 | + } else if (tag == "/heap") { | ||
| 2016 | + --in_heap; | ||
| 2017 | + } else if (in_heap == 0) { | ||
| 2018 | + std::string rest = m->str(2); | ||
| 2019 | + std::map<std::string, std::string> attrs; | ||
| 2020 | + std::sregex_iterator a_begin(rest.begin(), rest.end(), attr_re); | ||
| 2021 | + for (auto m2 = a_begin; m2 != sr_end; ++m2) { | ||
| 2022 | + attrs[m2->str(1)] = m2->str(2); | ||
| 2023 | + } | ||
| 2024 | + if (tag == "total") { | ||
| 2025 | + if (attrs.count("size") > 0) { | ||
| 2026 | + result += QIntC::to_size( | ||
| 2027 | + QUtil::string_to_ull(attrs["size"].c_str())); | ||
| 2028 | + } | ||
| 2029 | + } else if (tag == "system" && attrs["type"] == "max") { | ||
| 2030 | + result += QIntC::to_size( | ||
| 2031 | + QUtil::string_to_ull(attrs["size"].c_str())); | ||
| 2032 | + } | ||
| 2033 | + } | ||
| 2034 | + } | ||
| 2035 | + } catch (...) { | ||
| 2036 | + // ignore -- just return 0 | ||
| 2037 | + } | ||
| 2038 | + free(buf); | ||
| 2039 | + return result; | ||
| 2040 | +#else | ||
| 2041 | + return 0; | ||
| 2042 | +#endif | ||
| 2043 | +} |
libqpdf/qpdf/qpdf-config.h.in
| @@ -21,6 +21,8 @@ | @@ -21,6 +21,8 @@ | ||
| 21 | #cmakedefine HAVE_LOCALTIME_R 1 | 21 | #cmakedefine HAVE_LOCALTIME_R 1 |
| 22 | #cmakedefine HAVE_RANDOM 1 | 22 | #cmakedefine HAVE_RANDOM 1 |
| 23 | #cmakedefine HAVE_TM_GMTOFF 1 | 23 | #cmakedefine HAVE_TM_GMTOFF 1 |
| 24 | +#cmakedefine HAVE_MALLOC_INFO 1 | ||
| 25 | +#cmakedefine HAVE_OPEN_MEMSTREAM 1 | ||
| 24 | 26 | ||
| 25 | /* printf format for long long */ | 27 | /* printf format for long long */ |
| 26 | #cmakedefine LL_FMT "${LL_FMT}" | 28 | #cmakedefine LL_FMT "${LL_FMT}" |
libtests/qtest/qutil/qutil.out
libtests/qutil.cc
| @@ -703,6 +703,18 @@ is_long_long_test() | @@ -703,6 +703,18 @@ is_long_long_test() | ||
| 703 | std::cout << "done" << std::endl; | 703 | std::cout << "done" << std::endl; |
| 704 | } | 704 | } |
| 705 | 705 | ||
| 706 | +void | ||
| 707 | +memory_usage_test() | ||
| 708 | +{ | ||
| 709 | + auto u1 = QUtil::get_max_memory_usage(); | ||
| 710 | + if (u1 > 0) { | ||
| 711 | + auto x = QUtil::make_shared_array<int>(10 << 20); | ||
| 712 | + auto u2 = QUtil::get_max_memory_usage(); | ||
| 713 | + assert(u2 > u1); | ||
| 714 | + } | ||
| 715 | + std::cout << "memory usage okay" << std::endl; | ||
| 716 | +} | ||
| 717 | + | ||
| 706 | int | 718 | int |
| 707 | main(int argc, char* argv[]) | 719 | main(int argc, char* argv[]) |
| 708 | { | 720 | { |
| @@ -739,6 +751,8 @@ main(int argc, char* argv[]) | @@ -739,6 +751,8 @@ main(int argc, char* argv[]) | ||
| 739 | timestamp_test(); | 751 | timestamp_test(); |
| 740 | std::cout << "---- is_long_long" << std::endl; | 752 | std::cout << "---- is_long_long" << std::endl; |
| 741 | is_long_long_test(); | 753 | is_long_long_test(); |
| 754 | + std::cout << "---- memory usage" << std::endl; | ||
| 755 | + memory_usage_test(); | ||
| 742 | } catch (std::exception& e) { | 756 | } catch (std::exception& e) { |
| 743 | std::cout << "unexpected exception: " << e.what() << std::endl; | 757 | std::cout << "unexpected exception: " << e.what() << std::endl; |
| 744 | } | 758 | } |