This is an automated email from the ASF dual-hosted git repository. bcall pushed a commit to branch 9.2.x in repository https://gitbox.apache.org/repos/asf/trafficserver.git
The following commit(s) were added to refs/heads/9.2.x by this push: new 75328aa8f4 header_freq: Fix msg lock issues (#10988) 75328aa8f4 is described below commit 75328aa8f47a744c0bb3c4d43795736fa0b11ba4 Author: Chris McFarlen <ch...@mcfarlen.us> AuthorDate: Mon Jan 29 16:39:40 2024 -0600 header_freq: Fix msg lock issues (#10988) * header_freq: Fix msg lock issues * fix docs Co-authored-by: Chris McFarlen <cmcfar...@apple.com> (cherry picked from commit 78ccc61c9c803c39b3a874588fe2b3b2d52e9a0d) --- doc/admin-guide/plugins/header_freq.en.rst | 20 ++- plugins/experimental/header_freq/header_freq.cc | 186 +++++++++++++++++------- 2 files changed, 144 insertions(+), 62 deletions(-) diff --git a/doc/admin-guide/plugins/header_freq.en.rst b/doc/admin-guide/plugins/header_freq.en.rst index a5214b5ef2..398b7357cf 100644 --- a/doc/admin-guide/plugins/header_freq.en.rst +++ b/doc/admin-guide/plugins/header_freq.en.rst @@ -20,21 +20,29 @@ Header Frequency Plugin specific language governing permissions and limitations under the License. -The Header Frequency plugin keeps track of the number of times headers -have been seen in transactions. Two separate counteres are kept for -the origin and the client. This information is accessible via :: +The Header Frequency plugin keeps track of the number of times headers have been +seen in transactions. Two separate counteres are kept for the origin and the +client. This information is accessible via the ``log`` plugin message. By +default the data is sent to traffic.out but it can alternatively be appended to +an arbitrary file. The following logs the stats to ``traffic.out``:: traffic_ctl plugin msg header_freq log +The following appends the stats to ``/tmp/log.txt``. Note that this file must be +writeable by the traffic_server process's user:: + + traffic_ctl plugin msg header_freq log:/tmp/log.txt + + Installation ------------ -This plugin is only built if the configure option :: +Since Header Frequency plugin is an expiremental plugin, traffic_server must be configured +to build experimental plugins in order to use it:: --enable-experimental-plugins -is given at build time. -Add the following line to :file:`plugin.config`:: +Once built, add the following line to :file:`plugin.config` and restart traffic_server to use it:: header_freq.so diff --git a/plugins/experimental/header_freq/header_freq.cc b/plugins/experimental/header_freq/header_freq.cc index e498b3769f..3422d097f0 100644 --- a/plugins/experimental/header_freq/header_freq.cc +++ b/plugins/experimental/header_freq/header_freq.cc @@ -22,12 +22,19 @@ limitations under the License. */ -#include <iostream> -#include <map> -#include <fstream> +#include <algorithm> +#include <atomic> #include <cstdlib> #include <cstring> +#include <fstream> +#include <iostream> +#include <mutex> +#include <shared_mutex> #include <string> +#include <unordered_map> +#include <vector> + +#include "ts/apidefs.h" #include <ts/ts.h> namespace @@ -43,31 +50,47 @@ const char DEBUG_TAG_INIT[] = "header_freq.init"; // debug messages in continuation callbacks const char DEBUG_TAG_HOOK[] = "header_freq.hook"; -// maps from header name to # of times encountered -std::map<std::string, unsigned int> client_freq; -std::map<std::string, unsigned int> origin_freq; +// A map from header name to the number of times the header was encountered. +using CountMap_t = std::unordered_map<std::string, std::atomic<unsigned int>>; +CountMap_t client_freq; +CountMap_t origin_freq; +std::shared_mutex map_mutex; + +// A vector for when we want to sort the map. +using CountVector_t = std::vector<std::pair<std::string, unsigned int>>; // for traffic_ctl, name is a convenient identifier const char *ctl_tag = PLUGIN_NAME; const char CONTROL_MSG_LOG[] = "log"; // log all data const size_t CONTROL_MSG_LOG_LEN = sizeof(CONTROL_MSG_LOG) - 1; +void +Log_Sorted_Map(CountMap_t const &map, std::ostream &ss) +{ + CountVector_t sorted_vector; + { + std::shared_lock<std::shared_mutex> lock(map_mutex); + sorted_vector = CountVector_t(map.begin(), map.end()); + } + std::sort(sorted_vector.begin(), sorted_vector.end(), [](const auto &a, const auto &b) -> bool { return a.second > b.second; }); + + for (auto const &[header_name, count] : sorted_vector) { + ss << header_name << ": " << count << std::endl; + } +} + void Log_Data(std::ostream &ss) { ss << std::endl << std::string(100, '+') << std::endl; ss << "CLIENT HEADERS" << std::endl; - for (auto &elem : client_freq) { - ss << elem.first << ": " << elem.second << std::endl; - } + Log_Sorted_Map(client_freq, ss); ss << std::endl; ss << "ORIGIN HEADERS" << std::endl; - for (auto &elem : origin_freq) { - ss << elem.first << ": " << elem.second << std::endl; - } + Log_Sorted_Map(origin_freq, ss); ss << std::string(100, '+') << std::endl; } @@ -80,6 +103,11 @@ int CB_Command_Log(TSCont contp, TSEvent event, void *edata) { std::string *command = static_cast<std::string *>(TSContDataGet(contp)); + if (nullptr == command) { + TSError("[%s] Could not get the message argument from the log handler.", PLUGIN_NAME); + return TS_ERROR; + } + std::string::size_type colon_idx; if (std::string::npos != (colon_idx = command->find(':'))) { @@ -94,12 +122,13 @@ CB_Command_Log(TSCont contp, TSEvent event, void *edata) if (out.is_open()) { Log_Data(out); } else { - TSError("[%s] Failed to open file '%s' for logging", PLUGIN_NAME, path.c_str()); + TSError("[%s] Failed to open file '%s' for logging: %s", PLUGIN_NAME, path.c_str(), strerror(errno)); } } else { TSError("[%s] Invalid (zero length) file name for logging", PLUGIN_NAME); } } else { + // No filename provided, log to stdout (traffic.out). Log_Data(std::cout); } @@ -114,7 +143,7 @@ CB_Command_Log(TSCont contp, TSEvent event, void *edata) * against existing entries is case-insensitive. */ static void -count_all_headers(TSMBuffer &bufp, TSMLoc &hdr_loc, std::map<std::string, unsigned int> &map) +count_all_headers(TSMBuffer &bufp, TSMLoc &hdr_loc, CountMap_t &map) { TSMLoc hdr, next_hdr; hdr = TSMimeHdrFieldGet(bufp, hdr_loc, 0); @@ -132,7 +161,20 @@ count_all_headers(TSMBuffer &bufp, TSMLoc &hdr_loc, std::map<std::string, unsign c = tolower(c); } - ++map[str]; + { // For lock scoping. + std::shared_lock<std::shared_mutex> reader_lock{map_mutex}; + if (map.find(str) == map.end()) { + // Upgrade the lock to be exclusive. + reader_lock.unlock(); + std::unique_lock<std::shared_mutex> ulock{map_mutex}; + // There's a potential race condition here such that another thread may + // have inserted the key while we were upgrading the lock. Regardless, + // incrementing the value here always does the right thing. + ++map[str]; + } else { + ++map[str]; + } + } next_hdr = TSMimeHdrFieldNext(bufp, hdr_loc, hdr); TSHandleMLocRelease(bufp, hdr_loc, hdr); @@ -142,47 +184,73 @@ count_all_headers(TSMBuffer &bufp, TSMLoc &hdr_loc, std::map<std::string, unsign TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc); } -/** - * Continuation callback. Invoked to count headers on READ_REQUEST_HDR and - * SEND_RESPONSE_HDR hooks and to log through traffic_ctl's LIFECYCLE_MSG. +/** Handle common logic between the request and response headers. + * @param[in] txnp The transaction pointer for this HTTP message. + * @param[in] event The event that triggered this callback. + * @param[out] freq_map The map to update with the header counts. + * @return TS_SUCCESS if the event was handled successfully, TS_ERROR otherwise. */ int -handle_hook(TSCont contp, TSEvent event, void *edata) +handle_header_event(TSHttpTxn txnp, TSEvent event, CountMap_t &freq_map) { - TSHttpTxn txnp; TSMBuffer bufp; TSMLoc hdr_loc; - int ret_val = 0; + TSReturnCode ret; + + char const *message_type = nullptr; + if (event == TS_EVENT_HTTP_READ_REQUEST_HDR) { + message_type = "request"; + ret = TSHttpTxnClientReqGet(txnp, &bufp, &hdr_loc); + } else { // TS_EVENT_HTTP_SEND_RESPONSE_HDR + message_type = "response"; + ret = TSHttpTxnClientRespGet(txnp, &bufp, &hdr_loc); + } + + if (ret != TS_SUCCESS) { + TSError("[%s] could not get %s headers", PLUGIN_NAME, message_type); + TSHttpTxnReenable(txnp, TS_EVENT_HTTP_ERROR); + return TS_ERROR; + } + + count_all_headers(bufp, hdr_loc, freq_map); + TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); + return TS_SUCCESS; +} + +/** Continuation callback. Invoked to count headers on READ_REQUEST_HDR and + * SEND_RESPONSE_HDR hooks. + */ +int +header_handle_hook(TSCont contp, TSEvent event, void *edata) +{ + TSHttpTxn txnp = reinterpret_cast<TSHttpTxn>(edata); + int ret_val = TS_SUCCESS; switch (event) { case TS_EVENT_HTTP_READ_REQUEST_HDR: // count client headers - { TSDebug(DEBUG_TAG_HOOK, "event TS_EVENT_HTTP_READ_REQUEST_HDR"); - txnp = reinterpret_cast<TSHttpTxn>(edata); - // get the client request so we can loop through the headers - if (TSHttpTxnClientReqGet(txnp, &bufp, &hdr_loc) != TS_SUCCESS) { - TSError("[%s] could not get request headers", PLUGIN_NAME); - TSHttpTxnReenable(txnp, TS_EVENT_HTTP_ERROR); - ret_val = -1; - break; - } - count_all_headers(bufp, hdr_loc, client_freq); - TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); - } break; + ret_val = handle_header_event(txnp, event, client_freq); + break; case TS_EVENT_HTTP_SEND_RESPONSE_HDR: // count origin headers - { TSDebug(DEBUG_TAG_HOOK, "event TS_EVENT_HTTP_SEND_RESPONSE_HDR"); - // get the response so we can loop through the headers - txnp = reinterpret_cast<TSHttpTxn>(edata); - if (TSHttpTxnClientRespGet(txnp, &bufp, &hdr_loc) != TS_SUCCESS) { - TSError("[%s] could not get response headers", PLUGIN_NAME); - TSHttpTxnReenable(txnp, TS_EVENT_HTTP_ERROR); - ret_val = -2; - break; - } - count_all_headers(bufp, hdr_loc, origin_freq); - TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); - } break; + ret_val = handle_header_event(txnp, event, origin_freq); + break; + default: + TSError("[%s] unexpected event in header handler: %d", PLUGIN_NAME, event); + break; + } + + return ret_val; +} + +/** + * Continuation callback. Invoked to handler the LIFE_CYCLE_MSG event to log + * header stats. + */ +int +msg_handle_hook(TSCont contp, TSEvent event, void *edata) +{ + switch (event) { case TS_EVENT_LIFECYCLE_MSG: // Handle external command { TSPluginMsg *msgp = static_cast<TSPluginMsg *>(edata); @@ -203,15 +271,15 @@ handle_hook(TSCont contp, TSEvent event, void *edata) } } } break; - // do nothing in any of the other states default: + TSError("[%s] unexpected event in message handler: %d", PLUGIN_NAME, event); break; } - return ret_val; + return TS_SUCCESS; } -} // namespace +} // anonymous namespace /// Registration entry point for plugin. void @@ -225,15 +293,21 @@ TSPluginInit(int argc, const char *argv[]) TSError("[%s](%s) Plugin registration failed. \n", PLUGIN_NAME, __FUNCTION__); } - TSCont contp = TSContCreate(handle_hook, TSMutexCreate()); - if (contp == nullptr) { + TSCont header_contp = TSContCreate(header_handle_hook, nullptr); + if (header_contp == nullptr) { // Continuation initialization failed. Unrecoverable, report and exit. - TSError("[%s](%s) could not create continuation", PLUGIN_NAME, __FUNCTION__); + TSError("[%s](%s) could not create the header handler continuation", PLUGIN_NAME, __FUNCTION__); + abort(); + } + // Continuation initialization succeeded + TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK, header_contp); + TSHttpHookAdd(TS_HTTP_SEND_RESPONSE_HDR_HOOK, header_contp); + + TSCont msg_contp = TSContCreate(msg_handle_hook, nullptr); + if (msg_contp == nullptr) { + // Continuation initialization failed. Unrecoverable, report and exit. + TSError("[%s](%s) could not create the message handler continuation", PLUGIN_NAME, __FUNCTION__); abort(); - } else { - // Continuation initialization succeeded - TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK, contp); - TSHttpHookAdd(TS_HTTP_SEND_RESPONSE_HDR_HOOK, contp); - TSLifecycleHookAdd(TS_LIFECYCLE_MSG_HOOK, contp); } + TSLifecycleHookAdd(TS_LIFECYCLE_MSG_HOOK, msg_contp); }