Commit 0a54247652e49ce384dcf0d8df078201aa106089

Authored by Jay Berkenbilt
1 parent 0adfd74f

Add QUtil::get_max_memory_usage for testing

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
@@ -134,3 +134,5 @@ D:20210209191925Z @@ -134,3 +134,5 @@ D:20210209191925Z
134 2021-02-09T19:19:25Z 134 2021-02-09T19:19:25Z
135 ---- is_long_long 135 ---- is_long_long
136 done 136 done
  137 +---- memory usage
  138 +memory usage okay
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 }