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 | 131 | "esize", |
| 132 | 132 | "eval", |
| 133 | 133 | "extlibdir", |
| 134 | + "fclose", | |
| 134 | 135 | "fdict", |
| 135 | 136 | "ffield", |
| 136 | 137 | "fghij", |
| ... | ... | @@ -268,6 +269,7 @@ |
| 268 | 269 | "maxdepth", |
| 269 | 270 | "maxobjectid", |
| 270 | 271 | "mdash", |
| 272 | + "memstream", | |
| 271 | 273 | "mindepth", |
| 272 | 274 | "mkdir", |
| 273 | 275 | "mkinstalldirs", | ... | ... |
include/qpdf/QUtil.hh
| ... | ... | @@ -525,7 +525,17 @@ namespace QUtil |
| 525 | 525 | wchar_t const* const argv[], |
| 526 | 526 | std::function<int(int, char const* const[])> realmain); |
| 527 | 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 | 540 | inline bool |
| 531 | 541 | QUtil::is_hex_digit(char ch) | ... | ... |
libqpdf/CMakeLists.txt
| ... | ... | @@ -375,6 +375,29 @@ int main(int argc, char* argv[]) { |
| 375 | 375 | endif() |
| 376 | 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 | 401 | qpdf_check_ll_fmt("%lld" fmt_lld) |
| 379 | 402 | qpdf_check_ll_fmt("%I64d" fmt_i64d) |
| 380 | 403 | qpdf_check_ll_fmt("%I64lld" fmt_i64lld) | ... | ... |
libqpdf/QUtil.cc
| ... | ... | @@ -37,6 +37,9 @@ |
| 37 | 37 | # include <sys/stat.h> |
| 38 | 38 | # include <unistd.h> |
| 39 | 39 | #endif |
| 40 | +#ifdef HAVE_MALLOC_INFO | |
| 41 | +# include <malloc.h> | |
| 42 | +#endif | |
| 40 | 43 | |
| 41 | 44 | // First element is 24 |
| 42 | 45 | static unsigned short pdf_doc_low_to_unicode[] = { |
| ... | ... | @@ -1968,3 +1971,73 @@ QUtil::call_main_from_wmain( |
| 1968 | 1971 | } |
| 1969 | 1972 | |
| 1970 | 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
libtests/qtest/qutil/qutil.out
libtests/qutil.cc
| ... | ... | @@ -703,6 +703,18 @@ is_long_long_test() |
| 703 | 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 | 718 | int |
| 707 | 719 | main(int argc, char* argv[]) |
| 708 | 720 | { |
| ... | ... | @@ -739,6 +751,8 @@ main(int argc, char* argv[]) |
| 739 | 751 | timestamp_test(); |
| 740 | 752 | std::cout << "---- is_long_long" << std::endl; |
| 741 | 753 | is_long_long_test(); |
| 754 | + std::cout << "---- memory usage" << std::endl; | |
| 755 | + memory_usage_test(); | |
| 742 | 756 | } catch (std::exception& e) { |
| 743 | 757 | std::cout << "unexpected exception: " << e.what() << std::endl; |
| 744 | 758 | } | ... | ... |