From 41b3623296c2203acced1a9363c3ef7abd08a158 Mon Sep 17 00:00:00 2001 From: Winlin Date: Wed, 21 Aug 2024 15:39:01 +0800 Subject: [PATCH] API: Support new HTTP API for VALGRIND. v6.0.149 (#4150) New features for valgrind: 1. ST: Support /api/v1/valgrind for leaking check. 2. ST: Support /api/v1/valgrind?check=full|added|changed|new|quick To use Valgrind to detect memory leaks in SRS, even though Valgrind hooks are supported in ST, there are still many false positives. A more reasonable approach is to have Valgrind report incremental memory leaks. This way, global and static variables can be avoided, and detection can be achieved without exiting the program. Follow these steps: 1. Compile SRS with Valgrind support: `./configure --valgrind=on && make` 2. Start SRS with memory leak detection enabled: `valgrind --leak-check=full ./objs/srs -c conf/console.conf` 3. Trigger memory detection by using curl to access the API and generate calibration data. There will still be many false positives, but these can be ignored: `curl http://127.0.0.1:1985/api/v1/valgrind?check=added` 4. Perform load testing or test the suspected leaking functionality, such as RTMP streaming: `ffmpeg -re -i doc/source.flv -c copy -f flv rtmp://127.0.0.1/live/livestream` 5. Stop streaming and wait for SRS to clean up the Source memory, approximately 30 seconds. 6. Perform incremental memory leak detection. The reported leaks will be very accurate at this point: `curl http://127.0.0.1:1985/api/v1/valgrind?check=added` > Note: To avoid interference from the HTTP request itself on Valgrind, SRS uses a separate coroutine to perform periodic checks. Therefore, after accessing the API, you may need to wait a few seconds for the detection to be triggered. --------- Co-authored-by: Jacob Su --- trunk/auto/auto_headers.sh | 5 ++ trunk/doc/CHANGELOG.md | 1 + trunk/src/app/srs_app_gb28181.hpp | 10 +-- trunk/src/app/srs_app_http_api.cpp | 100 ++++++++++++++++++++++++++ trunk/src/app/srs_app_http_api.hpp | 17 +++++ trunk/src/app/srs_app_http_conn.hpp | 2 +- trunk/src/app/srs_app_recv_thread.hpp | 2 +- trunk/src/app/srs_app_rtmp_conn.hpp | 2 +- trunk/src/app/srs_app_server.cpp | 10 ++- trunk/src/app/srs_app_st.hpp | 2 +- trunk/src/core/srs_core_version6.hpp | 2 +- 11 files changed, 142 insertions(+), 11 deletions(-) diff --git a/trunk/auto/auto_headers.sh b/trunk/auto/auto_headers.sh index 1998a93745..82d77186d5 100755 --- a/trunk/auto/auto_headers.sh +++ b/trunk/auto/auto_headers.sh @@ -171,6 +171,11 @@ if [ $SRS_SANITIZER_LOG == YES ]; then else srs_undefine_macro "SRS_SANITIZER_LOG" $SRS_AUTO_HEADERS_H fi +if [ $SRS_VALGRIND == YES ]; then + srs_define_macro "SRS_VALGRIND" $SRS_AUTO_HEADERS_H +else + srs_undefine_macro "SRS_VALGRIND" $SRS_AUTO_HEADERS_H +fi ##################################################################################### # for embeded. diff --git a/trunk/doc/CHANGELOG.md b/trunk/doc/CHANGELOG.md index 2aedae0161..5d6b42374e 100644 --- a/trunk/doc/CHANGELOG.md +++ b/trunk/doc/CHANGELOG.md @@ -7,6 +7,7 @@ The changelog for SRS. ## SRS 6.0 Changelog +* v6.0, 2024-08-21, Merge [#4150](https://github.com/ossrs/srs/pull/4150): API: Support new HTTP API for VALGRIND. v6.0.149 (#4150) * v6.0, 2024-08-15, Merge [#4144](https://github.com/ossrs/srs/pull/4144): HTTP-FLV: Crash when multiple viewers. v6.0.148 (#4144) * v6.0, 2024-08-15, Merge [#4142](https://github.com/ossrs/srs/pull/4142): Config: Add more utest for env config. v6.0.147 (#4142) * v6.0, 2024-08-14, Merge [#4141](https://github.com/ossrs/srs/pull/4141): Live: Crash for invalid live stream state when unmount HTTP. v6.0.146 (#4141) diff --git a/trunk/src/app/srs_app_gb28181.hpp b/trunk/src/app/srs_app_gb28181.hpp index be62509481..3ac91a3da0 100644 --- a/trunk/src/app/srs_app_gb28181.hpp +++ b/trunk/src/app/srs_app_gb28181.hpp @@ -188,7 +188,7 @@ class SrsGbSession : public ISrsResource, public ISrsCoroutineHandler, public IS void on_media_transport(SrsSharedResource media); // Get the candidate for SDP generation, the public IP address for device to connect to. std::string pip(); -// Interface ISrsOneCycleThreadHandler +// Interface ISrsCoroutineHandler public: virtual srs_error_t cycle(); private: @@ -305,7 +305,7 @@ class SrsGbSipTcpConn : public ISrsResource, public ISrsCoroutineHandler, public public: virtual const SrsContextId& get_id(); virtual std::string desc(); -// Interface ISrsOneCycleThreadHandler +// Interface ISrsCoroutineHandler public: virtual srs_error_t cycle(); private: @@ -333,7 +333,7 @@ class SrsGbSipTcpReceiver : public ISrsStartable, public ISrsCoroutineHandler // Interface ISrsStartable public: virtual srs_error_t start(); -// Interface ISrsOneCycleThreadHandler +// Interface ISrsCoroutineHandler public: virtual srs_error_t cycle(); private: @@ -362,7 +362,7 @@ class SrsGbSipTcpSender : public ISrsStartable, public ISrsCoroutineHandler // Interface ISrsStartable public: virtual srs_error_t start(); -// Interface ISrsOneCycleThreadHandler +// Interface ISrsCoroutineHandler public: virtual srs_error_t cycle(); private: @@ -422,7 +422,7 @@ class SrsGbMediaTcpConn : public ISrsResource, public ISrsCoroutineHandler, publ public: virtual const SrsContextId& get_id(); virtual std::string desc(); -// Interface ISrsOneCycleThreadHandler +// Interface ISrsCoroutineHandler public: virtual srs_error_t cycle(); private: diff --git a/trunk/src/app/srs_app_http_api.cpp b/trunk/src/app/srs_app_http_api.cpp index c8bc710103..b1bd339a43 100644 --- a/trunk/src/app/srs_app_http_api.cpp +++ b/trunk/src/app/srs_app_http_api.cpp @@ -31,6 +31,11 @@ using namespace std; #include #include +#ifdef SRS_VALGRIND +#include +#include +#endif + #if defined(__linux__) || defined(SRS_OSX) #include #endif @@ -267,6 +272,7 @@ srs_error_t SrsGoApiV1::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r urls->set("clusters", SrsJsonAny::str("origin cluster server API")); urls->set("perf", SrsJsonAny::str("System performance stat")); urls->set("tcmalloc", SrsJsonAny::str("tcmalloc api with params ?page=summary|api")); + urls->set("valgrind", SrsJsonAny::str("valgrind api with params ?check=full|added|changed|new|quick")); SrsJsonObject* tests = SrsJsonAny::object(); obj->set("tests", tests); @@ -1090,6 +1096,100 @@ srs_error_t SrsGoApiTcmalloc::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMess } #endif +#ifdef SRS_VALGRIND + +SrsGoApiValgrind::SrsGoApiValgrind() +{ + trd_ = NULL; +} + +SrsGoApiValgrind::~SrsGoApiValgrind() +{ + srs_freep(trd_); +} + +srs_error_t SrsGoApiValgrind::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r) +{ + srs_error_t err = srs_success; + + if (!trd_) { + trd_ = new SrsSTCoroutine("valgrind", this, _srs_context->get_id()); + if ((err = trd_->start()) != srs_success) { + return srs_error_wrap(err, "start"); + } + } + + string check = r->query_get("check"); + srs_trace("query check=%s", check.c_str()); + + // Must be full|added|changed|new|quick, set to full for other values. + if (check != "full" && check != "added" && check != "changed" && check != "new" && check != "quick") { + srs_warn("force set check=%s to full", check.c_str()); + check = "full"; + } + + // By default, response the json style response. + SrsUniquePtr obj(SrsJsonAny::object()); + + obj->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); + + SrsJsonObject* res = SrsJsonAny::object(); + res->set("check", SrsJsonAny::str(check.c_str())); + res->set("help", SrsJsonAny::str("?check=full|added|changed|new|quick")); + res->set("see", SrsJsonAny::str("https://valgrind.org/docs/manual/mc-manual.html")); + obj->set("data", res); + + // Does a memory check later. + if (check == "full") { + res->set("call", SrsJsonAny::str("VALGRIND_DO_LEAK_CHECK")); + } else if (check == "quick") { + res->set("call", SrsJsonAny::str("VALGRIND_DO_QUICK_LEAK_CHECK")); + } else if (check == "added") { + res->set("call", SrsJsonAny::str("VALGRIND_DO_ADDED_LEAK_CHECK")); + } else if (check == "changed") { + res->set("call", SrsJsonAny::str("VALGRIND_DO_CHANGED_LEAK_CHECK")); + } else if (check == "new") { + res->set("call", SrsJsonAny::str("VALGRIND_DO_NEW_LEAK_CHECK")); + } + task_ = check; + + return srs_api_response(w, r, obj->dumps()); +} + +srs_error_t SrsGoApiValgrind::cycle() +{ + srs_error_t err = srs_success; + + while (true) { + if ((err = trd_->pull()) != srs_success) { + return srs_error_wrap(err, "pull"); + } + + std::string check = task_; + task_ = ""; + + if (!check.empty()) { + srs_trace("do memory check=%s", check.c_str()); + } + + if (check == "full") { + VALGRIND_DO_LEAK_CHECK; + } else if (check == "quick") { + VALGRIND_DO_QUICK_LEAK_CHECK; + } else if (check == "added") { + VALGRIND_DO_ADDED_LEAK_CHECK; + } else if (check == "changed") { + VALGRIND_DO_CHANGED_LEAK_CHECK; + } else if (check == "new") { + VALGRIND_DO_NEW_LEAK_CHECK; + } + + srs_usleep(3 * SRS_UTIME_SECONDS); + } + + return err; +} +#endif SrsGoApiMetrics::SrsGoApiMetrics() { diff --git a/trunk/src/app/srs_app_http_api.hpp b/trunk/src/app/srs_app_http_api.hpp index 8bcb30e503..8789d42e99 100644 --- a/trunk/src/app/srs_app_http_api.hpp +++ b/trunk/src/app/srs_app_http_api.hpp @@ -216,6 +216,23 @@ class SrsGoApiTcmalloc : public ISrsHttpHandler }; #endif +#ifdef SRS_VALGRIND +class SrsGoApiValgrind : public ISrsHttpHandler, public ISrsCoroutineHandler +{ +private: + SrsCoroutine* trd_; + std::string task_; +public: + SrsGoApiValgrind(); + virtual ~SrsGoApiValgrind(); +public: + virtual srs_error_t serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r); +// Interface ISrsCoroutineHandler +public: + virtual srs_error_t cycle(); +}; +#endif + class SrsGoApiMetrics : public ISrsHttpHandler { private: diff --git a/trunk/src/app/srs_app_http_conn.hpp b/trunk/src/app/srs_app_http_conn.hpp index 57f984a498..9d1cc10557 100644 --- a/trunk/src/app/srs_app_http_conn.hpp +++ b/trunk/src/app/srs_app_http_conn.hpp @@ -94,7 +94,7 @@ class SrsHttpConn : public ISrsConnection, public ISrsStartable, public ISrsCoro // Interface ISrsStartable public: virtual srs_error_t start(); -// Interface ISrsOneCycleThreadHandler +// Interface ISrsCoroutineHandler public: virtual srs_error_t cycle(); private: diff --git a/trunk/src/app/srs_app_recv_thread.hpp b/trunk/src/app/srs_app_recv_thread.hpp index 5fa011db57..7d29a4b39b 100644 --- a/trunk/src/app/srs_app_recv_thread.hpp +++ b/trunk/src/app/srs_app_recv_thread.hpp @@ -204,7 +204,7 @@ class SrsHttpRecvThread : public ISrsCoroutineHandler virtual srs_error_t start(); public: virtual srs_error_t pull(); -// Interface ISrsOneCycleThreadHandler +// Interface ISrsCoroutineHandler public: virtual srs_error_t cycle(); }; diff --git a/trunk/src/app/srs_app_rtmp_conn.hpp b/trunk/src/app/srs_app_rtmp_conn.hpp index 8c86627fec..9a772ffb74 100644 --- a/trunk/src/app/srs_app_rtmp_conn.hpp +++ b/trunk/src/app/srs_app_rtmp_conn.hpp @@ -181,7 +181,7 @@ class SrsRtmpConn : public ISrsConnection, public ISrsStartable, public ISrsRelo // when client cycle thread stop, invoke the on_thread_stop(), which will use server // To remove the client by server->remove(this). virtual srs_error_t start(); -// Interface ISrsOneCycleThreadHandler +// Interface ISrsCoroutineHandler public: // The thread cycle function, // when serve connection completed, terminate the loop which will terminate the thread, diff --git a/trunk/src/app/srs_app_server.cpp b/trunk/src/app/srs_app_server.cpp index aff62f02e1..16bc99e8c4 100644 --- a/trunk/src/app/srs_app_server.cpp +++ b/trunk/src/app/srs_app_server.cpp @@ -773,9 +773,17 @@ srs_error_t SrsServer::http_handle() // The test api for get tcmalloc stats. // @see Memory Introspection in https://gperftools.github.io/gperftools/tcmalloc.html if ((err = http_api_mux->handle("/api/v1/tcmalloc", new SrsGoApiTcmalloc())) != srs_success) { - return srs_error_wrap(err, "handle tests errors"); + return srs_error_wrap(err, "handle tcmalloc errors"); } #endif + +#ifdef SRS_VALGRIND + // The test api for valgrind. See VALGRIND_DO_LEAK_CHECK in https://valgrind.org/docs/manual/mc-manual.html + if ((err = http_api_mux->handle("/api/v1/valgrind", new SrsGoApiValgrind())) != srs_success) { + return srs_error_wrap(err, "handle valgrind errors"); + } +#endif + // metrics by prometheus if ((err = http_api_mux->handle("/metrics", new SrsGoApiMetrics())) != srs_success) { return srs_error_wrap(err, "handle tests errors"); diff --git a/trunk/src/app/srs_app_st.hpp b/trunk/src/app/srs_app_st.hpp index d7315b20cd..11f2c2195e 100644 --- a/trunk/src/app/srs_app_st.hpp +++ b/trunk/src/app/srs_app_st.hpp @@ -291,7 +291,7 @@ class SrsExecutorCoroutine : public ISrsResource, public ISrsStartable, public I public: virtual const SrsContextId& cid(); virtual void set_cid(const SrsContextId& cid); -// Interface ISrsOneCycleThreadHandler +// Interface ISrsCoroutineHandler public: virtual srs_error_t cycle(); // Interface ISrsResource diff --git a/trunk/src/core/srs_core_version6.hpp b/trunk/src/core/srs_core_version6.hpp index 33a00d8c94..9e3f4b4a10 100644 --- a/trunk/src/core/srs_core_version6.hpp +++ b/trunk/src/core/srs_core_version6.hpp @@ -9,6 +9,6 @@ #define VERSION_MAJOR 6 #define VERSION_MINOR 0 -#define VERSION_REVISION 148 +#define VERSION_REVISION 149 #endif