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 <[email protected]>
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 <[email protected]>
(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);
}