| /** @file |
| |
| A brief file description |
| |
| @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. |
| */ |
| |
| /***************************************************************************** |
| * |
| * Congestion.cc - Content and User Access Control |
| * |
| * |
| ****************************************************************************/ |
| #include "libts.h" |
| #include "I_Net.h" |
| #include "CongestionDB.h" |
| #include "Congestion.h" |
| #include "ControlMatcher.h" |
| |
| RecRawStatBlock *congest_rsb; |
| |
| InkRand CongestionRand(123); |
| |
| static const char *congestPrefix = "[CongestionControl]"; |
| |
| static const matcher_tags congest_dest_tags = { |
| "dest_host", |
| "dest_domain", |
| "dest_ip", |
| NULL, |
| "host_regex", |
| true |
| }; |
| |
| #define CONGESTION_CONTROL_CONFIG_TIMEOUT 120 |
| /* default congestion control values */ |
| |
| char *DEFAULT_error_page = NULL; |
| int DEFAULT_max_connection_failures = 5; |
| int DEFAULT_fail_window = 120; |
| int DEFAULT_proxy_retry_interval = 10; |
| int DEFAULT_client_wait_interval = 300; |
| int DEFAULT_wait_interval_alpha = 30; |
| int DEFAULT_live_os_conn_timeout = 60; |
| int DEFAULT_live_os_conn_retries = 2; |
| int DEFAULT_dead_os_conn_timeout = 15; |
| int DEFAULT_dead_os_conn_retries = 1; |
| int DEFAULT_max_connection = -1; |
| char *DEFAULT_congestion_scheme_str = NULL; |
| int DEFAULT_congestion_scheme = PER_IP; |
| |
| /* congestion control limits */ |
| #define CONG_RULE_MAX_max_connection_failures \ |
| (1<<(sizeof(cong_hist_t) * 8)) |
| |
| #define CONG_RULE_ULIMITED_max_connection_failures -1 |
| #define CONG_RULE_ULIMITED_mac_connection -1 |
| |
| static Ptr<ProxyMutex> reconfig_mutex; |
| CongestionMatcherTable *CongestionMatcher = NULL; |
| int congestionControlEnabled = 0; |
| int congestionControlLocalTime = 0; |
| |
| CongestionControlRecord::CongestionControlRecord(const CongestionControlRecord & rec) |
| { |
| prefix = xstrdup(rec.prefix); |
| prefix_len = rec.prefix_len; |
| port = rec.port; |
| congestion_scheme = rec.congestion_scheme; |
| error_page = xstrdup(rec.error_page); |
| max_connection_failures = rec.max_connection_failures; |
| fail_window = rec.fail_window; |
| proxy_retry_interval = rec.proxy_retry_interval; |
| client_wait_interval = rec.client_wait_interval; |
| wait_interval_alpha = rec.wait_interval_alpha; |
| live_os_conn_timeout = rec.live_os_conn_timeout; |
| live_os_conn_retries = rec.live_os_conn_retries; |
| dead_os_conn_timeout = rec.dead_os_conn_timeout; |
| dead_os_conn_retries = rec.dead_os_conn_retries; |
| max_connection = rec.max_connection; |
| pRecord = NULL; |
| ref_count = 1; |
| line_num = rec.line_num; |
| rank = 0; |
| } |
| |
| void |
| CongestionControlRecord::setdefault() |
| { |
| cleanup(); |
| congestion_scheme = DEFAULT_congestion_scheme; |
| port = 0; |
| prefix_len = 0; |
| rank = 0; |
| max_connection_failures = DEFAULT_max_connection_failures; |
| fail_window = DEFAULT_fail_window; |
| proxy_retry_interval = DEFAULT_proxy_retry_interval; |
| client_wait_interval = DEFAULT_client_wait_interval; |
| wait_interval_alpha = DEFAULT_wait_interval_alpha; |
| live_os_conn_timeout = DEFAULT_live_os_conn_timeout; |
| live_os_conn_retries = DEFAULT_live_os_conn_retries; |
| dead_os_conn_timeout = DEFAULT_dead_os_conn_timeout; |
| dead_os_conn_retries = DEFAULT_dead_os_conn_retries; |
| max_connection = DEFAULT_max_connection; |
| } |
| |
| char * |
| CongestionControlRecord::validate() |
| { |
| char *error_buf = NULL; |
| int error_len = 1024; |
| |
| #define IsGt0(var)\ |
| if ( var < 1 ) { \ |
| error_buf = (char*) xmalloc(error_len); \ |
| snprintf(error_buf, error_len, "line %d: invalid %s = %d, %s must > 0", \ |
| line_num, #var, var, #var); \ |
| cleanup(); \ |
| return error_buf; \ |
| } |
| |
| if (error_page == NULL) |
| error_page = xstrdup(DEFAULT_error_page); |
| if (max_connection_failures >= CONG_RULE_MAX_max_connection_failures || |
| (max_connection_failures <= 0 && max_connection_failures != CONG_RULE_ULIMITED_max_connection_failures) |
| ) { |
| error_buf = (char *) xmalloc(error_len); |
| snprintf(error_buf, error_len, "line %d: invalid %s = %d not in [1, %d) range", |
| line_num, "max_connection_failures", max_connection_failures, CONG_RULE_MAX_max_connection_failures); |
| cleanup(); |
| return error_buf; |
| } |
| |
| IsGt0(fail_window); |
| IsGt0(proxy_retry_interval); |
| IsGt0(client_wait_interval); |
| IsGt0(wait_interval_alpha); |
| IsGt0(live_os_conn_timeout); |
| IsGt0(live_os_conn_retries); |
| IsGt0(dead_os_conn_timeout); |
| IsGt0(dead_os_conn_retries); |
| // max_connection_failures <= 0 no failure num control |
| // max_connection == -1 no max_connection control |
| // max_connection_failures <= 0 && max_connection == -1 no congestion control for the rule |
| // max_connection == 0, no connection allow to the origin server for the rule |
| #undef IsGt0 |
| return error_buf; |
| } |
| |
| char * |
| CongestionControlRecord::Init(matcher_line * line_info) |
| { |
| char *errBuf; |
| const int errBufLen = 1024; |
| const char *tmp; |
| char *label; |
| char *val; |
| line_num = line_info->line_num; |
| |
| /* initialize the rule to defaults */ |
| setdefault(); |
| |
| for (int i = 0; i < MATCHER_MAX_TOKENS; i++) { |
| label = line_info->line[0][i]; |
| val = line_info->line[1][i]; |
| |
| if (label == NULL) { |
| continue; |
| } |
| if (strcasecmp(label, "max_connection_failures") == 0) { |
| max_connection_failures = atoi(val); |
| } else if (strcasecmp(label, "fail_window") == 0) { |
| fail_window = atoi(val); |
| } else if (strcasecmp(label, "proxy_retry_interval") == 0) { |
| proxy_retry_interval = atoi(val); |
| } else if (strcasecmp(label, "client_wait_interval") == 0) { |
| client_wait_interval = atoi(val); |
| } else if (strcasecmp(label, "wait_interval_alpha") == 0) { |
| wait_interval_alpha = atoi(val); |
| } else if (strcasecmp(label, "live_os_conn_timeout") == 0) { |
| live_os_conn_timeout = atoi(val); |
| } else if (strcasecmp(label, "live_os_conn_retries") == 0) { |
| live_os_conn_retries = atoi(val); |
| } else if (strcasecmp(label, "dead_os_conn_timeout") == 0) { |
| dead_os_conn_timeout = atoi(val); |
| } else if (strcasecmp(label, "dead_os_conn_retries") == 0) { |
| dead_os_conn_retries = atoi(val); |
| } else if (strcasecmp(label, "max_connection") == 0) { |
| max_connection = atoi(val); |
| } else if (strcasecmp(label, "congestion_scheme") == 0) { |
| if (!strcasecmp(val, "per_ip")) { |
| congestion_scheme = PER_IP; |
| } else if (!strcasecmp(val, "per_host")) { |
| congestion_scheme = PER_HOST; |
| } else { |
| congestion_scheme = PER_IP; |
| } |
| } else if (strcasecmp(label, "error_page") == 0) { |
| error_page = xstrdup(val); |
| } else if (strcasecmp(label, "prefix") == 0) { |
| prefix = xstrdup(val); |
| prefix_len = strlen(prefix); |
| rank += 1; |
| // prefix will be used in the ControlBase |
| continue; |
| } else if (strcasecmp(label, "port") == 0) { |
| port = atoi(val); |
| rank += 2; |
| // port will be used in the ControlBase; |
| continue; |
| } else |
| continue; |
| // Consume the label/value pair we used |
| line_info->line[0][i] = NULL; |
| line_info->num_el--; |
| } |
| if (line_info->num_el > 0) { |
| tmp = ProcessModifiers(line_info); |
| |
| if (tmp != NULL) { |
| errBuf = (char *) xmalloc(errBufLen * sizeof(char)); |
| snprintf(errBuf, errBufLen, "%s %s at line %d in congestion.config", congestPrefix, tmp, line_num); |
| return errBuf; |
| } |
| |
| } |
| |
| char *err_msg = validate(); |
| if (err_msg == NULL) { |
| pRecord = new CongestionControlRecord(*this); |
| } |
| return err_msg; |
| } |
| |
| void |
| CongestionControlRecord::UpdateMatch(CongestionControlRule * pRule, RD * rdata) |
| { |
| /* |
| * Select the first matching rule specified in congestion.config |
| * rank Matches |
| * 3 dest && prefix && port |
| * 2 dest && port |
| * 1 dest && prefix |
| * 0 dest |
| */ |
| if (pRule->record == 0 || |
| pRule->record->rank < rank || (pRule->record->line_num > line_num && pRule->record->rank == rank)) { |
| if (rank > 0) { |
| if (rdata->data_type() == RequestData::RD_CONGEST_ENTRY) { |
| // Enforce the same port and prefix |
| if (port != 0 && port != ((CongestionEntry *) rdata)->pRecord->port) |
| return; |
| if (prefix != NULL && ((CongestionEntry *) rdata)->pRecord->prefix == NULL) |
| return; |
| if (prefix != NULL && strncmp(prefix, ((CongestionEntry *) rdata)->pRecord->prefix, prefix_len)) |
| return; |
| } else if (!this->CheckModifiers((HttpRequestData *) rdata)) { |
| return; |
| } |
| } |
| pRule->record = this; |
| Debug("congestion_config", "Matched with record 0x%x at line %d", this, line_num); |
| } |
| } |
| |
| void |
| CongestionControlRecord::Print() |
| { |
| #define PrintNUM(var) \ |
| Debug("congestion_config", "%30s = %d", #var, var); |
| #define PrintSTR(var) \ |
| Debug("congestion_config", "%30s = %s", #var, (var == NULL? "NULL" : var)); |
| |
| PrintNUM(line_num); |
| PrintSTR(prefix); |
| PrintNUM(congestion_scheme); |
| PrintSTR(error_page); |
| PrintNUM(max_connection_failures); |
| PrintNUM(fail_window); |
| PrintNUM(proxy_retry_interval); |
| PrintNUM(client_wait_interval); |
| PrintNUM(wait_interval_alpha); |
| PrintNUM(live_os_conn_timeout); |
| PrintNUM(live_os_conn_retries); |
| PrintNUM(dead_os_conn_timeout); |
| PrintNUM(dead_os_conn_retries); |
| PrintNUM(max_connection); |
| #undef PrintNUM |
| #undef PrintSTR |
| } |
| |
| struct CongestionControl_UpdateContinuation: public Continuation |
| { |
| int congestion_update_handler(int etype, void *data) |
| { |
| NOWARN_UNUSED(etype); |
| NOWARN_UNUSED(data); |
| reloadCongestionControl(); |
| delete this; |
| return EVENT_DONE; |
| } |
| CongestionControl_UpdateContinuation(ProxyMutex * m):Continuation(m) |
| { |
| SET_HANDLER(&CongestionControl_UpdateContinuation::congestion_update_handler); |
| } |
| }; |
| |
| extern void initCongestionDB(); |
| |
| // place holder for congestion control enable config |
| static int |
| CongestionControlEnabledChanged(const char *name, RecDataT data_type, RecData data, void *cookie) |
| { |
| NOWARN_UNUSED(name); |
| NOWARN_UNUSED(data_type); |
| NOWARN_UNUSED(data); |
| NOWARN_UNUSED(cookie); |
| if (congestionControlEnabled == 1 || congestionControlEnabled == 2) { |
| revalidateCongestionDB(); |
| } |
| return 0; |
| } |
| |
| static int |
| CongestionControlFile_CB(const char *name, RecDataT data_type, RecData data, void *cookie) |
| { |
| NOWARN_UNUSED(name); |
| NOWARN_UNUSED(data_type); |
| NOWARN_UNUSED(data); |
| NOWARN_UNUSED(cookie); |
| eventProcessor.schedule_imm(new CongestionControl_UpdateContinuation(reconfig_mutex), ET_NET); |
| return 0; |
| } |
| |
| static int |
| CongestionControlDefaultSchemeChanged(const char *name, RecDataT data_type, RecData data, void *cookie) |
| { |
| NOWARN_UNUSED(name); |
| NOWARN_UNUSED(data_type); |
| NOWARN_UNUSED(data); |
| NOWARN_UNUSED(cookie); |
| if (strcasecmp(DEFAULT_congestion_scheme_str, "per_host") == 0) { |
| DEFAULT_congestion_scheme = PER_HOST; |
| } else { |
| DEFAULT_congestion_scheme = PER_IP; |
| } |
| return 0; |
| } |
| |
| //----------------------------------------------- |
| // hack for link the RegressionTest into the |
| // TS binary |
| //----------------------------------------------- |
| extern void init_CongestionRegressionTest(); |
| |
| #define CC_EstablishStaticConfigInteger(v, n) REC_EstablishStaticConfigInt32(v, n) |
| #define CC_EstablishStaticConfigStringAlloc(v, n) REC_EstablishStaticConfigStringAlloc(v, n) |
| |
| void |
| initCongestionControl() |
| { |
| // TODO: This is very, very strange, we run the regression tests even on a normal startup?? |
| #if TS_HAS_TESTS |
| init_CongestionRegressionTest(); |
| #endif |
| ink_assert(CongestionMatcher == NULL); |
| // register the stats variables |
| register_congest_stats(); |
| // you must grab this mutex before reconfig the congestion control matcher table |
| reconfig_mutex = new_ProxyMutex(); |
| |
| // register config variables |
| CC_EstablishStaticConfigInteger(congestionControlEnabled, "proxy.config.http.congestion_control.enabled"); |
| CC_EstablishStaticConfigInteger(DEFAULT_max_connection_failures, "proxy.config.http.congestion_control.default.max_connection_failures"); |
| CC_EstablishStaticConfigInteger(DEFAULT_fail_window, "proxy.config.http.congestion_control.default.fail_window"); |
| CC_EstablishStaticConfigInteger(DEFAULT_proxy_retry_interval, "proxy.config.http.congestion_control.default.proxy_retry_interval"); |
| CC_EstablishStaticConfigInteger(DEFAULT_client_wait_interval, "proxy.config.http.congestion_control.default.client_wait_interval"); |
| CC_EstablishStaticConfigInteger(DEFAULT_wait_interval_alpha, "proxy.config.http.congestion_control.default.wait_interval_alpha"); |
| CC_EstablishStaticConfigInteger(DEFAULT_live_os_conn_timeout, "proxy.config.http.congestion_control.default.live_os_conn_timeout"); |
| CC_EstablishStaticConfigInteger(DEFAULT_live_os_conn_retries, "proxy.config.http.congestion_control.default.live_os_conn_retries"); |
| CC_EstablishStaticConfigInteger(DEFAULT_dead_os_conn_timeout, "proxy.config.http.congestion_control.default.dead_os_conn_timeout"); |
| CC_EstablishStaticConfigInteger(DEFAULT_dead_os_conn_retries, "proxy.config.http.congestion_control.default.dead_os_conn_retries"); |
| CC_EstablishStaticConfigInteger(DEFAULT_max_connection, "proxy.config.http.congestion_control.default.max_connection"); |
| CC_EstablishStaticConfigStringAlloc(DEFAULT_congestion_scheme_str, "proxy.config.http.congestion_control.default.congestion_scheme"); |
| CC_EstablishStaticConfigStringAlloc(DEFAULT_error_page, "proxy.config.http.congestion_control.default.error_page"); |
| CC_EstablishStaticConfigInteger(congestionControlLocalTime, "proxy.config.http.congestion_control.localtime"); |
| { |
| RecData recdata; |
| recdata.rec_int = 0; |
| CongestionControlDefaultSchemeChanged(NULL, RECD_NULL, recdata, NULL); |
| } |
| |
| CongestionMatcher = NEW(new CongestionMatcherTable("proxy.config.http.congestion_control.filename", congestPrefix, &congest_dest_tags)); |
| #ifdef DEBUG_CONGESTION_MACTHER |
| CongestionMatcher->Print(); |
| #endif |
| |
| if (congestionControlEnabled) { |
| Debug("congestion_config", "congestion control enabled"); |
| initCongestionDB(); |
| } else { |
| Debug("congestion_config", "congestion control disabled"); |
| } |
| RecRegisterConfigUpdateCb("proxy.config.http.congestion_control.default.congestion_scheme", &CongestionControlDefaultSchemeChanged, NULL); |
| RecRegisterConfigUpdateCb("proxy.config.http.congestion_control.enabled", &CongestionControlEnabledChanged, NULL); |
| RecRegisterConfigUpdateCb("proxy.config.http.congestion_control.filename", &CongestionControlFile_CB, NULL); |
| } |
| |
| void |
| reloadCongestionControl() |
| { |
| CongestionMatcherTable *newTable; |
| Debug("congestion_config", "congestion control config changed, reloading"); |
| ink_hrtime t = CONGESTION_CONTROL_CONFIG_TIMEOUT; |
| newTable = NEW(new CongestionMatcherTable("proxy.config.http.congestion_control.filename", congestPrefix, &congest_dest_tags)); |
| #ifdef DEBUG_CONGESTION_MACTHER |
| newTable->Print(); |
| #endif |
| new_Deleter(CongestionMatcher, t); |
| ink_atomic_swap_ptr(&CongestionMatcher, newTable); |
| if (congestionControlEnabled) { |
| revalidateCongestionDB(); |
| } |
| } |
| |
| CongestionControlRecord * |
| CongestionControlled(RD * rdata) |
| { |
| if (congestionControlEnabled) { |
| CongestionControlRule result; |
| CongestionMatcher->Match(rdata, &result); |
| if (result.record) { |
| return result.record->pRecord; |
| } |
| } else { |
| return NULL; |
| } |
| return NULL; |
| } |
| |
| uint64_t |
| make_key(char *hostname, unsigned long ip, CongestionControlRecord * record) |
| { |
| int host_len = 0; |
| if (hostname) { |
| host_len = strlen(hostname); |
| } |
| return make_key(hostname, host_len, ip, record); |
| } |
| |
| uint64_t |
| make_key(char *hostname, int len, unsigned long ip, CongestionControlRecord * record) |
| { |
| INK_MD5 md5; |
| #ifdef USE_MMH |
| MMH_CTX ctx; |
| ink_code_incr_MMH_init(&ctx); |
| if (record->congestion_scheme == PER_HOST && len > 0) |
| ink_code_incr_MMH_update(&ctx, hostname, len); |
| else |
| ink_code_incr_MMH_update(&ctx, (char *) &ip, sizeof(ip)); |
| if (record->port != 0) { |
| unsigned short p = port; |
| p = htons(p); |
| ink_code_incr_MMH_update(&ctx, (char *) &p, 2); |
| } |
| if (record->prefix != NULL) { |
| ink_code_incr_MMH_update(&ctx, record->prefix, record->prefix_len); |
| } |
| ink_code_incr_MMH_final((char *) &md5, &ctx); |
| #else |
| INK_DIGEST_CTX ctx; |
| ink_code_incr_md5_init(&ctx); |
| if (record->congestion_scheme == PER_HOST && len > 0) |
| ink_code_incr_md5_update(&ctx, hostname, len); |
| else |
| ink_code_incr_md5_update(&ctx, (char *) &ip, sizeof(ip)); |
| if (record->port != 0) { |
| unsigned short p = record->port; |
| p = htons(p); |
| ink_code_incr_md5_update(&ctx, (char *) &p, 2); |
| } |
| if (record->prefix != NULL) { |
| ink_code_incr_md5_update(&ctx, record->prefix, record->prefix_len); |
| } |
| ink_code_incr_md5_final((char *) &md5, &ctx); |
| #endif |
| return md5.fold(); |
| } |
| |
| uint64_t |
| make_key(char *hostname, int len, unsigned long ip, char *prefix, int prelen, short port) |
| { |
| /* if the hostname != NULL, use hostname, else, use ip */ |
| INK_MD5 md5; |
| #ifdef USE_MMH |
| MMH_CTX ctx; |
| ink_code_incr_MMH_init(&ctx); |
| if (hostname && len > 0) |
| ink_code_incr_MMH_update(&ctx, hostname, len); |
| else |
| ink_code_incr_MMH_update(&ctx, (char *) &ip, sizeof(ip)); |
| if (port != 0) { |
| unsigned short p = port; |
| p = htons(p); |
| ink_code_incr_MMH_update(&ctx, (char *) &p, 2); |
| } |
| if (prefix != NULL) { |
| ink_code_incr_MMH_update(&ctx, prefix, prelen); |
| } |
| ink_code_incr_MMH_final((char *) &md5, &ctx); |
| #else |
| INK_DIGEST_CTX ctx; |
| ink_code_incr_md5_init(&ctx); |
| if (hostname && len > 0) |
| ink_code_incr_md5_update(&ctx, hostname, len); |
| else |
| ink_code_incr_md5_update(&ctx, (char *) &ip, sizeof(ip)); |
| if (port != 0) { |
| unsigned short p = port; |
| p = htons(p); |
| ink_code_incr_md5_update(&ctx, (char *) &p, 2); |
| } |
| if (prefix != NULL) { |
| ink_code_incr_md5_update(&ctx, prefix, prelen); |
| } |
| ink_code_incr_md5_final((char *) &md5, &ctx); |
| #endif |
| return md5.fold(); |
| } |
| |
| //---------------------------------------------------------- |
| // FailHistory Implementation |
| //---------------------------------------------------------- |
| void |
| FailHistory::init(int window) |
| { |
| bin_len = (window + CONG_HIST_ENTRIES) / CONG_HIST_ENTRIES; |
| if (bin_len <= 0) |
| bin_len = 1; |
| length = bin_len * CONG_HIST_ENTRIES; |
| for (int i = 0; i < CONG_HIST_ENTRIES; i++) { |
| bins[i] = 0; |
| } |
| last_event = 0; |
| cur_index = 0; |
| events = 0; |
| start = 0; |
| } |
| |
| void |
| FailHistory::init_event(long t, int n) |
| { |
| last_event = t; |
| cur_index = 0; |
| events = n; |
| bins[0] = n; |
| for (int i = 1; i < CONG_HIST_ENTRIES; i++) { |
| bins[i] = 0; |
| } |
| start = (last_event + bin_len) - last_event % bin_len - length; |
| } |
| |
| int |
| FailHistory::regist_event(long t, int n) |
| { |
| if (t < start) |
| return events; |
| if (t > last_event + length) { |
| init_event(t, n); |
| return events; |
| } |
| if (t < start + length) { |
| bins[((t - start) / bin_len + 1 + cur_index) % CONG_HIST_ENTRIES] += n; |
| } else { |
| do { |
| start += bin_len; |
| cur_index++; |
| if (cur_index == CONG_HIST_ENTRIES) |
| cur_index = 0; |
| events -= bins[cur_index]; |
| bins[cur_index] = 0; |
| } while (start + length < t); |
| bins[cur_index] = n; |
| } |
| events += n; |
| if (last_event < t) |
| last_event = t; |
| return events; |
| } |
| |
| //---------------------------------------------------------- |
| // CongestionEntry Implementation |
| //---------------------------------------------------------- |
| CongestionEntry::CongestionEntry(const char *hostname, ip_addr_t ip, CongestionControlRecord * rule, uint64_t key) |
| :m_key(key), |
| m_ip(ip), |
| m_last_congested(0), |
| m_congested(0), |
| m_stat_congested_conn_failures(0), |
| m_M_congested(0), m_last_M_congested(0), m_num_connections(0), m_stat_congested_max_conn(0), m_ref_count(1) |
| { |
| m_hostname = xstrdup(hostname); |
| rule->get(); |
| pRecord = rule; |
| clearFailHistory(); |
| m_hist_lock = new_ProxyMutex(); |
| } |
| |
| void |
| CongestionEntry::init(CongestionControlRecord * rule) |
| { |
| if (pRecord) |
| pRecord->put(); |
| rule->get(); |
| pRecord = rule; |
| clearFailHistory(); |
| |
| // TODO: This used to signal via SNMP |
| if ((pRecord->max_connection > m_num_connections) |
| && ink_atomic_swap(&m_M_congested, 0)) { |
| // action not congested? |
| } |
| } |
| |
| bool |
| CongestionEntry::validate() |
| { |
| CongestionControlRecord *p = CongestionControlled(this); |
| if (p == NULL) { |
| return false; |
| } |
| |
| uint64_t key = make_key(m_hostname, |
| m_ip, |
| p); |
| if (key != m_key) { |
| return false; |
| } |
| applyNewRule(p); |
| return true; |
| } |
| |
| void |
| CongestionEntry::applyNewRule(CongestionControlRecord * rule) |
| { |
| if (pRecord->fail_window != rule->fail_window) { |
| init(rule); |
| return; |
| } |
| int mcf = pRecord->max_connection_failures; |
| pRecord->put(); |
| rule->get(); |
| pRecord = rule; |
| // TODO: This used to signal via SNMP |
| if (((pRecord->max_connection < 0) |
| || (pRecord->max_connection > m_num_connections)) |
| && ink_atomic_swap(&m_M_congested, 0)) { |
| // action not congested ? |
| } |
| // TODO: This used to signal via SNMP |
| if (pRecord->max_connection_failures < 0) { |
| if (ink_atomic_swap(&m_congested, 0)) { |
| // action not congested ? |
| } |
| return; |
| } |
| // TODO: This used to signal via SNMP |
| if (mcf < pRecord->max_connection_failures) { |
| if (ink_atomic_swap(&m_congested, 0)) { |
| // action not congested? |
| } |
| } else if (mcf > pRecord->max_connection_failures && m_history.events >= pRecord->max_connection_failures) { |
| if (!ink_atomic_swap(&m_congested, 1)) { |
| // action congested? |
| } |
| } |
| } |
| |
| int |
| CongestionEntry::sprint(char *buf, int buflen, int format) |
| { |
| struct in_addr addr; |
| char str_time[100] = " "; |
| int len = 0; |
| ink_hrtime timestamp = 0; |
| char state; |
| if (pRecord->max_connection >= 0 && m_num_connections >= pRecord->max_connection) { |
| timestamp = ink_hrtime_to_sec(ink_get_hrtime()); |
| state = 'M'; |
| } else { |
| timestamp = m_last_congested; |
| state = (m_congested ? 'F' : ' '); |
| } |
| len += snprintf(buf + len, buflen - len, "%" PRId64 "|%d|%s|%s", |
| timestamp, |
| pRecord->line_num, |
| (m_hostname ? m_hostname : " "), (m_ip ? (addr.s_addr = htonl(m_ip), inet_ntoa(addr)) : " ")); |
| |
| len += snprintf(buf + len, buflen - len, "|%s|%s|%c", |
| (pRecord->congestion_scheme == PER_IP ? "per_ip" : "per_host"), |
| (pRecord->prefix ? pRecord->prefix : " "), state); |
| |
| len += snprintf(buf + len, buflen - len, "|%d|%d", m_stat_congested_conn_failures, m_stat_congested_max_conn); |
| |
| if (format > 0) { |
| if (m_congested) { |
| struct tm time; |
| time_t seconds = m_last_congested; |
| if (congestionControlLocalTime) { |
| ink_localtime_r(&seconds, &time); |
| } else { |
| ink_gmtime_r(&seconds, &time); |
| } |
| snprintf(str_time, sizeof(str_time), "%04d/%02d/%02d %02d:%02d:%02d", |
| time.tm_year + 1900, time.tm_mon + 1, time.tm_mday, time.tm_hour, time.tm_min, time.tm_sec); |
| } |
| len += snprintf(buf + len, buflen - len, "|%s", str_time); |
| |
| if (format > 1) { |
| len += snprintf(buf + len, buflen - len, "|%" PRIu64 "", m_key); |
| |
| if (format > 2) { |
| len += snprintf(buf + len, buflen - len, "|%ld", m_history.last_event); |
| |
| if (format > 3) { |
| len += snprintf(buf + len, buflen - len, "|%d|%d|%d", m_history.events, m_ref_count, m_num_connections); |
| } |
| } |
| } |
| } |
| len += snprintf(buf + len, buflen - len, "\n"); |
| return len; |
| } |
| |
| //------------------------------------------------------------- |
| // When a connection failure happened, try to get the lock |
| // first and change register the event, if we can not get |
| // the lock, discard the event |
| //------------------------------------------------------------- |
| void |
| CongestionEntry::failed_at(ink_hrtime t) |
| { |
| if (pRecord->max_connection_failures == -1) |
| return; |
| // long time = ink_hrtime_to_sec(t); |
| long time = t; |
| Debug("congestion_control", "failed_at: %d", time); |
| MUTEX_TRY_LOCK(lock, m_hist_lock, this_ethread()); |
| if (lock) { |
| m_history.regist_event(time); |
| if (!m_congested) { |
| int32_t new_congested = compCongested(); |
| // TODO: This used to signal via SNMP |
| if (new_congested && !ink_atomic_swap(&m_congested, 1)) { |
| m_last_congested = m_history.last_event; |
| // action congested ? |
| } |
| } |
| } else { |
| Debug("congestion_control", "failure info lost due to lock contention(Entry: 0x%x, Time: %d)", (void *) this, time); |
| } |
| } |
| |
| void |
| CongestionEntry::go_alive() |
| { |
| // TODO: This used to signal via SNMP |
| if (ink_atomic_swap(&m_congested, 0)) { |
| // Action not congested ? |
| } |
| } |
| |
| #define SERVER_CONGESTED_SIG REC_SIGNAL_HTTP_CONGESTED_SERVER |
| #define SERVER_ALLEVIATED_SIG REC_SIGNAL_HTTP_ALLEVIATED_SERVER |
| #define CC_SignalWarning(sig, msg) \ |
| REC_SignalWarning(sig, msg) |