diff options
-rw-r--r-- | cSpell.json | 2 | ||||
-rw-r--r-- | include/qpdf/QUtil.hh | 12 | ||||
-rw-r--r-- | libqpdf/CMakeLists.txt | 23 | ||||
-rw-r--r-- | libqpdf/QUtil.cc | 73 | ||||
-rw-r--r-- | libqpdf/qpdf/qpdf-config.h.in | 2 | ||||
-rw-r--r-- | libtests/qtest/qutil/qutil.out | 2 | ||||
-rw-r--r-- | libtests/qutil.cc | 14 |
7 files changed, 127 insertions, 1 deletions
diff --git a/cSpell.json b/cSpell.json index 6251d984..88f7e22d 100644 --- a/cSpell.json +++ b/cSpell.json @@ -131,6 +131,7 @@ "esize", "eval", "extlibdir", + "fclose", "fdict", "ffield", "fghij", @@ -268,6 +269,7 @@ "maxdepth", "maxobjectid", "mdash", + "memstream", "mindepth", "mkdir", "mkinstalldirs", diff --git a/include/qpdf/QUtil.hh b/include/qpdf/QUtil.hh index 41b89da4..96f4f7ed 100644 --- a/include/qpdf/QUtil.hh +++ b/include/qpdf/QUtil.hh @@ -525,7 +525,17 @@ namespace QUtil wchar_t const* const argv[], std::function<int(int, char const* const[])> realmain); #endif // QPDF_NO_WCHAR_T -}; // namespace QUtil + + // Try to return the maximum amount of memory allocated by the + // current process and its threads. Return 0 if unable to + // determine. This is Linux-specific and not implemented to be + // completely reliable. It is used during development for + // performance testing to detect changes that may significantly + // change memory usage. It is not recommended for use for other + // purposes. + QPDF_DLL + size_t get_max_memory_usage(); +}; // namespace QUtil inline bool QUtil::is_hex_digit(char ch) diff --git a/libqpdf/CMakeLists.txt b/libqpdf/CMakeLists.txt index cf807f6d..106292a3 100644 --- a/libqpdf/CMakeLists.txt +++ b/libqpdf/CMakeLists.txt @@ -375,6 +375,29 @@ int main(int argc, char* argv[]) { endif() endfunction() +check_c_source_compiles( +"#include <malloc.h> +#include <stdio.h> +int main(int argc, char* argv[]) { + malloc_info(0, stdout); + return 0; +}" + HAVE_MALLOC_INFO) + +check_c_source_compiles( +"#include <stdio.h> +#include <stdlib.h> +int main(int argc, char* argv[]) { + char* buf; + size_t size; + FILE* f; + f = open_memstream(&buf, &size); + fclose(f); + free(buf); + return 0; +}" + HAVE_OPEN_MEMSTREAM) + qpdf_check_ll_fmt("%lld" fmt_lld) qpdf_check_ll_fmt("%I64d" fmt_i64d) qpdf_check_ll_fmt("%I64lld" fmt_i64lld) diff --git a/libqpdf/QUtil.cc b/libqpdf/QUtil.cc index d565ece0..98a8f318 100644 --- a/libqpdf/QUtil.cc +++ b/libqpdf/QUtil.cc @@ -37,6 +37,9 @@ # include <sys/stat.h> # include <unistd.h> #endif +#ifdef HAVE_MALLOC_INFO +# include <malloc.h> +#endif // First element is 24 static unsigned short pdf_doc_low_to_unicode[] = { @@ -1968,3 +1971,73 @@ QUtil::call_main_from_wmain( } #endif // QPDF_NO_WCHAR_T + +size_t +QUtil::get_max_memory_usage() +{ +#if defined(HAVE_MALLOC_INFO) && defined(HAVE_OPEN_MEMSTREAM) + static std::regex tag_re("<(/?\\w+)([^>]*?)>"); + static std::regex attr_re("(\\w+)=\"(.*?)\""); + + char* buf; + size_t size; + FILE* f = open_memstream(&buf, &size); + if (f == nullptr) { + return 0; + } + malloc_info(0, f); + fclose(f); + if (QUtil::get_env("QPDF_DEBUG_MEM_USAGE")) { + fprintf(stderr, "%s", buf); + } + + // Warning: this code uses regular expression to extract data from + // an XML string. This is generally a bad idea, but we're going to + // do it anyway because QUtil.hh warns against using this function + // for other than development/testing, and if this function fails + // to generate reasonable output during performance testing, it + // will be noticed. + + // This is my best guess at how to interpret malloc_info. Anyway + // it seems to provide useful information for detecting code + // changes that drastically change memory usage. + size_t result = 0; + try { + std::cregex_iterator m_begin(buf, buf + size, tag_re); + std::cregex_iterator cr_end; + std::sregex_iterator sr_end; + + int in_heap = 0; + for (auto m = m_begin; m != cr_end; ++m) { + std::string tag(m->str(1)); + if (tag == "heap") { + ++in_heap; + } else if (tag == "/heap") { + --in_heap; + } else if (in_heap == 0) { + std::string rest = m->str(2); + std::map<std::string, std::string> attrs; + std::sregex_iterator a_begin(rest.begin(), rest.end(), attr_re); + for (auto m2 = a_begin; m2 != sr_end; ++m2) { + attrs[m2->str(1)] = m2->str(2); + } + if (tag == "total") { + if (attrs.count("size") > 0) { + result += QIntC::to_size( + QUtil::string_to_ull(attrs["size"].c_str())); + } + } else if (tag == "system" && attrs["type"] == "max") { + result += QIntC::to_size( + QUtil::string_to_ull(attrs["size"].c_str())); + } + } + } + } catch (...) { + // ignore -- just return 0 + } + free(buf); + return result; +#else + return 0; +#endif +} diff --git a/libqpdf/qpdf/qpdf-config.h.in b/libqpdf/qpdf/qpdf-config.h.in index 8a22b875..500f55cc 100644 --- a/libqpdf/qpdf/qpdf-config.h.in +++ b/libqpdf/qpdf/qpdf-config.h.in @@ -21,6 +21,8 @@ #cmakedefine HAVE_LOCALTIME_R 1 #cmakedefine HAVE_RANDOM 1 #cmakedefine HAVE_TM_GMTOFF 1 +#cmakedefine HAVE_MALLOC_INFO 1 +#cmakedefine HAVE_OPEN_MEMSTREAM 1 /* printf format for long long */ #cmakedefine LL_FMT "${LL_FMT}" diff --git a/libtests/qtest/qutil/qutil.out b/libtests/qtest/qutil/qutil.out index 48d22fb9..8d3e6d8e 100644 --- a/libtests/qtest/qutil/qutil.out +++ b/libtests/qtest/qutil/qutil.out @@ -134,3 +134,5 @@ D:20210209191925Z 2021-02-09T19:19:25Z ---- is_long_long done +---- memory usage +memory usage okay diff --git a/libtests/qutil.cc b/libtests/qutil.cc index 995a7599..82c2dd1a 100644 --- a/libtests/qutil.cc +++ b/libtests/qutil.cc @@ -703,6 +703,18 @@ is_long_long_test() std::cout << "done" << std::endl; } +void +memory_usage_test() +{ + auto u1 = QUtil::get_max_memory_usage(); + if (u1 > 0) { + auto x = QUtil::make_shared_array<int>(10 << 20); + auto u2 = QUtil::get_max_memory_usage(); + assert(u2 > u1); + } + std::cout << "memory usage okay" << std::endl; +} + int main(int argc, char* argv[]) { @@ -739,6 +751,8 @@ main(int argc, char* argv[]) timestamp_test(); std::cout << "---- is_long_long" << std::endl; is_long_long_test(); + std::cout << "---- memory usage" << std::endl; + memory_usage_test(); } catch (std::exception& e) { std::cout << "unexpected exception: " << e.what() << std::endl; } |