blob: 2ee83ee698645ef125618a153f82bb5f0c9ffe4e [file] [log] [blame]
/** @file
@section license License
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include "tscore/ink_defs.h"
#include "tsutil/ts_bw_format.h"
#include "ts/ts.h"
#include <string>
#include <string_view>
#include <unordered_map>
#define PLUGIN_NAME "remap_stats"
#define DEBUG_TAG PLUGIN_NAME
#define MAX_STAT_LENGTH (1 << 8)
enum UriType {
REMAP,
PRISTINE,
};
struct config_t {
TSMutex stat_creation_mutex{nullptr};
UriType uri_type{PRISTINE};
TSStatPersistence persist_type{TS_STAT_NON_PERSISTENT};
int txn_slot{-1};
};
namespace
{
DbgCtl dbg_ctl{DEBUG_TAG};
void
stat_add(const char *name, TSMgmtInt amount, TSStatPersistence persist_type, TSMutex create_mutex)
{
static thread_local std::unordered_map<std::string, int> hash;
int stat_id = -1;
if (unlikely(hash.find(name) == hash.cend())) {
// This is an unlikely path because we most likely have the stat cached
// so this mutex won't be much overhead and it fixes a race condition
// in the RecCore. Hopefully this can be removed in the future.
TSMutexLock(create_mutex);
if (TS_ERROR == TSStatFindName(name, &stat_id)) {
stat_id = TSStatCreate(name, TS_RECORDDATATYPE_INT, persist_type, TS_STAT_SYNC_SUM);
if (stat_id == TS_ERROR) {
Dbg(dbg_ctl, "Error creating stat_name: %s", name);
} else {
Dbg(dbg_ctl, "Created stat_name: %s stat_id: %d", name, stat_id);
}
}
TSMutexUnlock(create_mutex);
if (stat_id >= 0) {
hash.emplace(name, stat_id);
Dbg(dbg_ctl, "Cached stat_name: %s stat_id: %d", name, stat_id);
}
} else {
stat_id = hash.at(name);
}
if (likely(stat_id >= 0)) {
TSStatIntIncrement(stat_id, amount);
} else {
Dbg(dbg_ctl, "stat error! stat_name: %s stat_id: %d", name, stat_id);
}
}
std::string
get_hostname(TSHttpTxn txnp, UriType uriType)
{
std::string hostname;
switch (uriType) {
case PRISTINE: {
TSMBuffer hbuf;
TSMLoc hloc;
if (TS_SUCCESS == TSHttpTxnPristineUrlGet(txnp, &hbuf, &hloc)) {
int tlen = 0;
char const *const thost = TSUrlHostGet(hbuf, hloc, &tlen);
if (nullptr != thost && 0 < tlen) {
hostname.assign(thost, tlen);
}
TSHandleMLocRelease(hbuf, TS_NULL_MLOC, hloc);
}
} break;
case REMAP: {
TSMBuffer hbuf;
TSMLoc hloc;
if (TS_SUCCESS == TSHttpTxnClientReqGet(txnp, &hbuf, &hloc)) {
TSMLoc url_loc;
if (TS_SUCCESS == TSHttpHdrUrlGet(hbuf, hloc, &url_loc)) {
int tlen = 0;
char const *const thost = TSUrlHostGet(hbuf, url_loc, &tlen);
if (nullptr != thost && 0 < tlen) {
hostname.assign(thost, tlen);
}
TSHandleMLocRelease(hbuf, hloc, url_loc);
}
TSHandleMLocRelease(hbuf, TS_NULL_MLOC, hloc);
}
} break;
default:
break;
}
return hostname;
}
int
handle_post_remap(TSCont cont, TSEvent event ATS_UNUSED, void *edata)
{
TSHttpTxn txn = static_cast<TSHttpTxn>(edata);
config_t *const config = static_cast<config_t *>(TSContDataGet(cont));
void *const txnd = reinterpret_cast<void *>(0x01);
TSUserArgSet(txn, config->txn_slot, txnd);
TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE);
Dbg(dbg_ctl, "Post Remap Handler Finished");
return 0;
}
void
create_stat_name(swoc::FixedBufferWriter &stat_name, std::string_view const h, std::string_view const b)
{
stat_name.clear().restrict(1);
stat_name.print("plugin.{}.{}.{}", PLUGIN_NAME, h, b);
stat_name.restore(1).write('\0');
}
int
handle_txn_close(TSCont cont, TSEvent event ATS_UNUSED, void *edata)
{
TSHttpTxn const txn = static_cast<TSHttpTxn>(edata);
config_t const *const config = static_cast<config_t *>(TSContDataGet(cont));
void const *const txnd = TSUserArgGet(txn, config->txn_slot);
static std::string_view const unknown = "unknown";
// check remap successful
if (nullptr != txnd) {
std::string const hostname = get_hostname(txn, config->uri_type);
std::string_view hostsv;
if (!hostname.empty()) {
hostsv = hostname;
} else {
hostsv = unknown;
}
uint64_t in_bytes = TSHttpTxnClientReqHdrBytesGet(txn);
in_bytes += TSHttpTxnClientReqBodyBytesGet(txn);
swoc::LocalBufferWriter<MAX_STAT_LENGTH> stat_name;
create_stat_name(stat_name, hostsv, "in_bytes");
stat_add(stat_name.data(), static_cast<TSMgmtInt>(in_bytes), config->persist_type, config->stat_creation_mutex);
uint64_t out_bytes = TSHttpTxnClientRespHdrBytesGet(txn);
out_bytes += TSHttpTxnClientRespBodyBytesGet(txn);
create_stat_name(stat_name, hostsv, "out_bytes");
stat_add(stat_name.data(), static_cast<TSMgmtInt>(out_bytes), config->persist_type, config->stat_creation_mutex);
TSMBuffer buf = nullptr;
TSMLoc hdr_loc = nullptr;
if (TSHttpTxnClientRespGet(txn, &buf, &hdr_loc) == TS_SUCCESS) {
int const status_code = static_cast<int>(TSHttpHdrStatusGet(buf, hdr_loc));
TSHandleMLocRelease(buf, TS_NULL_MLOC, hdr_loc);
if (status_code < 200) {
create_stat_name(stat_name, hostsv, "status_other");
} else if (status_code <= 299) {
create_stat_name(stat_name, hostsv, "status_2xx");
} else if (status_code <= 399) {
create_stat_name(stat_name, hostsv, "status_3xx");
} else if (status_code <= 499) {
create_stat_name(stat_name, hostsv, "status_4xx");
} else if (status_code <= 599) {
create_stat_name(stat_name, hostsv, "status_5xx");
} else {
create_stat_name(stat_name, hostsv, "status_other");
}
stat_add(stat_name.data(), 1, config->persist_type, config->stat_creation_mutex);
} else {
create_stat_name(stat_name, hostsv, "status_unknown");
stat_add(stat_name.data(), 1, config->persist_type, config->stat_creation_mutex);
}
} else {
Dbg(dbg_ctl, "skipping unsuccessfully remapped transaction");
}
TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE);
Dbg(dbg_ctl, "Handler Finished");
return 0;
}
} // namespace
void
TSPluginInit(int argc, const char *argv[])
{
TSPluginRegistrationInfo info;
info.plugin_name = PLUGIN_NAME;
info.vendor_name = "Apache Software Foundation";
info.support_email = "dev@trafficserver.apache.org";
if (TSPluginRegister(&info) != TS_SUCCESS) {
TSError("[remap_stats] Plugin registration failed");
return;
} else {
Dbg(dbg_ctl, "Plugin registration succeeded");
}
auto config = new config_t;
config->stat_creation_mutex = TSMutexCreate();
config->uri_type = PRISTINE;
config->persist_type = TS_STAT_NON_PERSISTENT;
if (argc > 1) {
// Argument parser
for (int ii = 0; ii < argc; ++ii) {
std::string_view const arg(argv[ii]);
if (arg == "-P" || arg == "--post-remap-host") {
config->uri_type = REMAP;
Dbg(dbg_ctl, "Using post remap hostname");
} else if (arg == "-p" || arg == "--persistent") {
config->persist_type = TS_STAT_PERSISTENT;
Dbg(dbg_ctl, "Using persistent stats");
}
}
}
TSUserArgIndexReserve(TS_USER_ARGS_TXN, PLUGIN_NAME, "txn data", &(config->txn_slot));
// this is to mark the transaction as successfully remapped
TSCont const post_remap_cont = TSContCreate(handle_post_remap, nullptr);
TSContDataSet(post_remap_cont, static_cast<void *>(config));
TSHttpHookAdd(TS_HTTP_POST_REMAP_HOOK, post_remap_cont);
// collects stats for successful remaps
TSCont const global_cont = TSContCreate(handle_txn_close, nullptr);
TSContDataSet(global_cont, static_cast<void *>(config));
TSHttpHookAdd(TS_HTTP_TXN_CLOSE_HOOK, global_cont);
Dbg(dbg_ctl, "Init complete");
}