<commit_before>/** @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 "ink_config.h"
#include "ink_defs.h"

#include "ts/ts.h"
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
#include <getopt.h>
#include <search.h>

#define PLUGIN_NAME "remap_stats"
#define DEBUG_TAG PLUGIN_NAME

typedef struct
{
    bool post_remap_host;
    int txn_slot;
    TSStatPersistence persist_type;
    TSMutex stat_creation_mutex;
} config_t;

typedef struct
{
    char *hostname;
    bool remap_success;
} txn_data_t;

static void
stat_add(char *name, TSMgmtInt amount, TSStatPersistence persist_type, TSMutex create_mutex)
{
    int stat_id = -1, *statp;
    ENTRY search, *result = NULL;
    static __thread struct hsearch_data stat_cache;
    static __thread bool hash_init = false;

    if (unlikely(!hash_init))
    {
        hcreate_r(TS_MAX_API_STATS << 1, &stat_cache);
        hash_init = true;
        TSDebug(DEBUG_TAG, "stat cache hash init");
    }

    search.key = name;
    hsearch_r(search, FIND, &result, &stat_cache);

    if (unlikely(result == NULL))
    {
        // 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((const char *) name, &stat_id))
        {
            stat_id = TSStatCreate((const char *) name, TS_RECORDDATATYPE_INT, persist_type, TS_STAT_SYNC_SUM);
            if (stat_id == TS_ERROR)
                TSDebug(DEBUG_TAG, "Error creating stat_name: %s", name);
            else
                TSDebug(DEBUG_TAG, "Created stat_name: %s stat_id: %d", name, stat_id);
        }
        TSMutexUnlock(create_mutex);

        search.key = TSstrdup(name);
        statp = TSmalloc(sizeof(int));
        *statp = stat_id;
        search.data = (void *) statp;
        hsearch_r(search, ENTER, &result, &stat_cache);
        TSDebug(DEBUG_TAG, "Cached stat_name: %s stat_id: %d", name, stat_id);
    }
    else
        stat_id = *((int *) result->data);

    if (likely(stat_id >= 0))
        TSStatIntIncrement(stat_id, amount);
    else
        TSDebug(DEBUG_TAG, "stat error! stat_name: %s stat_id: %d", name, stat_id);
}

static char *
get_effective_host(TSHttpTxn txn)
{
    char *effective_url, *tmp;
    const char *host;
    int len;
    TSMBuffer buf;
    TSMLoc url_loc;

    effective_url = TSHttpTxnEffectiveUrlStringGet(txn, &len);
    buf = TSMBufferCreate();
    TSUrlCreate(buf, &url_loc);
    tmp = effective_url;
    TSUrlParse(buf, url_loc, (const char **) (&tmp), (const char *) (effective_url + len));
    TSfree(effective_url);
    host = TSUrlHostGet(buf, url_loc, &len);
    tmp = TSstrndup(host, len);
    TSHandleMLocRelease(buf, TS_NULL_MLOC, url_loc);
    TSMBufferDestroy(buf);
    return tmp;
}

static char *
create_stat_name(char *hostname, char *basename)
{
    char *stat_name;
    size_t stat_len;

    stat_len = strlen(hostname) + strlen(basename) + strlen(PLUGIN_NAME) + 3 + strlen("plugin.");
    stat_name = TSmalloc(stat_len * sizeof(char));
    snprintf(stat_name, stat_len, "plugin.%s.%s.%s", PLUGIN_NAME, hostname, basename);
    return stat_name;
}

static int
handle_read_req_hdr(TSCont cont, TSEvent event, void *edata)
{
    TSHttpTxn txn = (TSHttpTxn) edata;
    config_t *config;
    txn_data_t *txnd;

    config = (config_t *) TSContDataGet(cont);
    txnd = TSmalloc(sizeof(txn_data_t));
    txnd->remap_success = false;
    txnd->hostname = get_effective_host(txn);
    TSHttpTxnArgSet(txn, config->txn_slot, (void *) txnd);

    TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE);
    TSDebug(DEBUG_TAG, "Read Req Handler Finished");
    return 0;
}

static int
handle_post_remap(TSCont cont, TSEvent event, void *edata)
{
    TSHttpTxn txn = (TSHttpTxn) edata;
    config_t *config;
    txn_data_t *txnd;

    config = (config_t *) TSContDataGet(cont);

    if (config->post_remap_host)
    {
        txnd = TSmalloc(sizeof(txn_data_t));
        txnd->remap_success = true;
        txnd->hostname = NULL;
        TSHttpTxnArgSet(txn, config->txn_slot, (void *) txnd);
    }
    else
    {
        txnd = (txn_data_t *) TSHttpTxnArgGet(txn, config->txn_slot);
        txnd->remap_success = true;
    }

    TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE);
    TSDebug(DEBUG_TAG, "Post Remap Handler Finished");
    return 0;
}

static int
handle_txn_close(TSCont cont, TSEvent event, void *edata)
{
    TSHttpTxn txn = (TSHttpTxn) edata;
    config_t *config;
    txn_data_t *txnd;
    TSHttpStatus status_code = 0;
    TSMBuffer buf;
    TSMLoc hdr_loc;
    uint64_t out_bytes, in_bytes;
    char *remap, *stat_name;

    config = (config_t *) TSContDataGet(cont);<commit_msg>TS-2291: Fix compiler warning about unused variable.<commit_after>/** @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 "ink_config.h"
#include "ink_defs.h"

#include "ts/ts.h"
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
#include <getopt.h>
#include <search.h>

#define PLUGIN_NAME "remap_stats"
#define DEBUG_TAG PLUGIN_NAME

typedef struct
{
    bool post_remap_host;
    int txn_slot;
    TSStatPersistence persist_type;
    TSMutex stat_creation_mutex;
} config_t;

typedef struct
{
    char *hostname;
    bool remap_success;
} txn_data_t;

static void
stat_add(char *name, TSMgmtInt amount, TSStatPersistence persist_type, TSMutex create_mutex)
{
    int stat_id = -1, *statp;
    ENTRY search, *result = NULL;
    static __thread struct hsearch_data stat_cache;
    static __thread bool hash_init = false;

    if (unlikely(!hash_init))
    {
        hcreate_r(TS_MAX_API_STATS << 1, &stat_cache);
        hash_init = true;
        TSDebug(DEBUG_TAG, "stat cache hash init");
    }

    search.key = name;
    hsearch_r(search, FIND, &result, &stat_cache);

    if (unlikely(result == NULL))
    {
        // 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((const char *) name, &stat_id))
        {
            stat_id = TSStatCreate((const char *) name, TS_RECORDDATATYPE_INT, persist_type, TS_STAT_SYNC_SUM);
            if (stat_id == TS_ERROR)
                TSDebug(DEBUG_TAG, "Error creating stat_name: %s", name);
            else
                TSDebug(DEBUG_TAG, "Created stat_name: %s stat_id: %d", name, stat_id);
        }
        TSMutexUnlock(create_mutex);

        search.key = TSstrdup(name);
        statp = TSmalloc(sizeof(int));
        *statp = stat_id;
        search.data = (void *) statp;
        hsearch_r(search, ENTER, &result, &stat_cache);
        TSDebug(DEBUG_TAG, "Cached stat_name: %s stat_id: %d", name, stat_id);
    }
    else
        stat_id = *((int *) result->data);

    if (likely(stat_id >= 0))
        TSStatIntIncrement(stat_id, amount);
    else
        TSDebug(DEBUG_TAG, "stat error! stat_name: %s stat_id: %d", name, stat_id);
}

static char *
get_effective_host(TSHttpTxn txn)
{
    char *effective_url, *tmp;
    const char *host;
    int len;
    TSMBuffer buf;
    TSMLoc url_loc;

    effective_url = TSHttpTxnEffectiveUrlStringGet(txn, &len);
    buf = TSMBufferCreate();
    TSUrlCreate(buf, &url_loc);
    tmp = effective_url;
    TSUrlParse(buf, url_loc, (const char **) (&tmp), (const char *) (effective_url + len));
    TSfree(effective_url);
    host = TSUrlHostGet(buf, url_loc, &len);
    tmp = TSstrndup(host, len);
    TSHandleMLocRelease(buf, TS_NULL_MLOC, url_loc);
    TSMBufferDestroy(buf);
    return tmp;
}

static char *
create_stat_name(char *hostname, char *basename)
{
    char *stat_name;
    size_t stat_len;

    stat_len = strlen(hostname) + strlen(basename) + strlen(PLUGIN_NAME) + 3 + strlen("plugin.");
    stat_name = TSmalloc(stat_len * sizeof(char));
    snprintf(stat_name, stat_len, "plugin.%s.%s.%s", PLUGIN_NAME, hostname, basename);
    return stat_name;
}

static int
handle_read_req_hdr(TSCont cont, TSEvent event ATS_UNUSED, void *edata)
{
    TSHttpTxn txn = (TSHttpTxn) edata;
    config_t *config;
    txn_data_t *txnd;

    config = (config_t *) TSContDataGet(cont);
    txnd = TSmalloc(sizeof(txn_data_t));
    txnd->remap_success = false;
    txnd->hostname = get_effective_host(txn);
    TSHttpTxnArgSet(txn, config->txn_slot, (void *) txnd);

    TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE);
    TSDebug(DEBUG_TAG, "Read Req Handler Finished");
    return 0;
}

static int
handle_post_remap(TSCont cont, TSEvent event ATS_UNUSED, void *edata)
{
    TSHttpTxn txn = (TSHttpTxn) edata;
    config_t *config;
    txn_data_t *txnd;

    config = (config_t *) TSContDataGet(cont);

    if (config->post_remap_host)
    {
        txnd = TSmalloc(sizeof(txn_data_t));
        txnd->remap_success = true;
        txnd->hostname = NULL;
        TSHttpTxnArgSet(txn, config->txn_slot, (void *) txnd);
    }
    else
    {
        txnd = (txn_data_t *) TSHttpTxnArgGet(txn, config->txn_slot);
        txnd->remap_success = true;
    }

    TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE);
    TSDebug(DEBUG_TAG, "Post Remap Handler Finished");
    return 0;
}

static int
handle_txn_close(TSCont cont, TSEvent event ATS_UNUSED, void *edata)
{
    TSHttpTxn txn = (TSHttpTxn) edata;
    config_t *config;
    txn_data_t *txnd;
    TSHttpStatus status_code = 0;
    TSMBuffer buf;
    TSMLoc hdr_loc;
    uint64_t out_bytes, in_bytes;
    char *remap, *stat_name;

    config = (config_t *) TSContDataGet(cont);
{'fraction_non_alphanumeric': 0.10356188780053428, 'fraction_numerical': 0.0026714158504007124, 'mean_word_length': 3.3362934362934364, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 35, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3425784', 'n_tokens_mistral': 4015, 'n_tokens_neox': 3919, 'n_words': 1024}