# Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: henrik@henriknordstrom.net-20080711204343-\ # jhqo27vfnrb0080u # target_branch: file:///data/bzr/squid3/trunk/ # testament_sha1: c3b574b953b0e22d77a8a35b435b908e1b1ac646 # timestamp: 2008-07-11 22:44:44 +0200 # base_revision_id: henrik@henriknordstrom.net-20080711202453-\ # 0lhqbyg4izx30e4b # # Begin patch === modified file 'src/Makefile.am' --- src/Makefile.am 2008-07-04 12:09:33 +0000 +++ src/Makefile.am 2008-07-11 20:43:43 +0000 @@ -593,6 +593,8 @@ PeerDigest.h \ peer_digest.cc \ peer_select.cc \ + peer_sourcehash.cc \ + peer_userhash.cc \ PeerSelectState.h \ PingData.h \ protos.h \ @@ -890,6 +892,8 @@ pconn.cc \ peer_digest.cc \ peer_select.cc \ + peer_sourcehash.cc \ + peer_userhash.cc \ protos.h \ redirect.cc \ referer.cc \ @@ -1435,6 +1439,8 @@ pconn.cc \ peer_digest.cc \ peer_select.cc \ + peer_sourcehash.cc \ + peer_userhash.cc \ redirect.cc \ referer.cc \ refresh.cc \ @@ -1606,6 +1612,8 @@ pconn.cc \ peer_digest.cc \ peer_select.cc \ + peer_sourcehash.cc \ + peer_userhash.cc \ redirect.cc \ referer.cc \ refresh.cc \ @@ -1762,6 +1770,8 @@ pconn.cc \ peer_digest.cc \ peer_select.cc \ + peer_sourcehash.cc \ + peer_userhash.cc \ redirect.cc \ referer.cc \ refresh.cc \ @@ -1907,6 +1917,8 @@ Parsing.cc \ peer_digest.cc \ peer_select.cc \ + peer_sourcehash.cc \ + peer_userhash.cc \ pconn.cc \ redirect.cc \ referer.cc \ @@ -2068,6 +2080,8 @@ pconn.cc \ peer_digest.cc \ peer_select.cc \ + peer_sourcehash.cc \ + peer_userhash.cc \ redirect.cc \ referer.cc \ refresh.cc \ @@ -2430,6 +2444,8 @@ pconn.cc \ peer_digest.cc \ peer_select.cc \ + peer_sourcehash.cc \ + peer_userhash.cc \ redirect.cc \ referer.cc \ refresh.cc \ === modified file 'src/cache_cf.cc' --- src/cache_cf.cc 2008-07-11 19:32:10 +0000 +++ src/cache_cf.cc 2008-07-11 20:20:31 +0000 @@ -1740,6 +1740,18 @@ p->options.carp = 1; + } else if (!strcasecmp(token, "userhash")) { + if (p->type != PEER_PARENT) + fatalf("parse_peer: non-parent userhash peer %s/%d\n", p->host, p->http_port); + + p->options.userhash = 1; + + } else if (!strcasecmp(token, "sourcehash")) { + if (p->type != PEER_PARENT) + fatalf("parse_peer: non-parent sourcehash peer %s/%d\n", p->host, p->http_port); + + p->options.sourcehash = 1; + #if DELAY_POOLS } else if (!strcasecmp(token, "no-delay")) { === modified file 'src/cf.data.pre' --- src/cf.data.pre 2008-06-30 16:27:12 +0000 +++ src/cf.data.pre 2008-07-11 20:20:31 +0000 @@ -1571,6 +1571,8 @@ round-robin weighted-round-robin carp + userhash + sourcehash multicast-responder closest-only no-digest @@ -1645,6 +1647,12 @@ distributed among the parents based on the CARP load balancing hash function based on their weight. + use 'userhash' to load-balance amongst a set of parents + based on the client proxy_auth or ident username. + + use 'sourcehash' to load-balance amongst a set of parents + based on the client source ip. + 'multicast-responder' indicates the named peer is a member of a multicast group. ICP queries will not be sent directly to the peer, but ICP replies === modified file 'src/enums.h' --- src/enums.h 2008-07-11 19:32:10 +0000 +++ src/enums.h 2008-07-11 20:43:43 +0000 @@ -175,6 +175,8 @@ #endif CARP, ANY_OLD_PARENT, + USERHASH_PARENT, + SOURCEHASH_PARENT, HIER_MAX } hier_code; === modified file 'src/main.cc' --- src/main.cc 2008-07-11 20:14:45 +0000 +++ src/main.cc 2008-07-11 20:20:31 +0000 @@ -628,6 +628,8 @@ peerSelectInit(); carpInit(); + peerUserHashInit(); + peerSourceHashInit(); } void @@ -969,6 +971,8 @@ asnRegisterWithCacheManager(manager); authenticateRegisterWithCacheManager(&Config.authConfiguration, manager); carpRegisterWithCacheManager(manager); + peerUserHashRegisterWithCacheManager(manager); + peerSourceHashRegisterWithCacheManager(manager); cbdataRegisterWithCacheManager(manager); /* These use separate calls so that the comm loops can eventually * coexist. === modified file 'src/neighbors.cc' --- src/neighbors.cc 2008-06-12 12:49:29 +0000 +++ src/neighbors.cc 2008-07-11 20:20:31 +0000 @@ -1538,6 +1538,15 @@ if (p->options.roundrobin) storeAppendPrintf(sentry, " round-robin"); + if (p->options.carp) + storeAppendPrintf(sentry, " carp"); + + if (p->options.userhash) + storeAppendPrintf(sentry, " userhash"); + + if (p->options.userhash) + storeAppendPrintf(sentry, " sourcehash"); + if (p->options.weighted_roundrobin) storeAppendPrintf(sentry, " weighted-round-robin"); === modified file 'src/peer_select.cc' --- src/peer_select.cc 2008-07-11 19:32:10 +0000 +++ src/peer_select.cc 2008-07-11 20:43:43 +0000 @@ -65,6 +65,8 @@ #endif "CARP", "ANY_PARENT", + "USERHASH", + "SOURCEHASH", "INVALID CODE" }; @@ -503,6 +505,10 @@ if ((p = getDefaultParent(request))) { code = DEFAULT_PARENT; + } else if ((p = peerUserHashSelectParent(request))) { + code = USERHASH_PARENT; + } else if ((p = peerSourceHashSelectParent(request))) { + code = SOURCEHASH_PARENT; } else if ((p = carpSelectParent(request))) { code = CARP; } else if ((p = getRoundRobinParent(request))) { === added file 'src/peer_sourcehash.cc' --- src/peer_sourcehash.cc 1970-01-01 00:00:00 +0000 +++ src/peer_sourcehash.cc 2008-07-11 20:43:43 +0000 @@ -0,0 +1,229 @@ + +/* + * $Id: carp.cc,v 1.27 2008/01/14 12:13:49 hno Exp $ + * + * DEBUG: section 39 Peer source hash based selection + * AUTHOR: Henrik Nordstrom + * BASED ON: carp.cc + * + * SQUID Web Proxy Cache http://www.squid-cache.org/ + * ---------------------------------------------------------- + * + * Squid is the result of efforts by numerous individuals from + * the Internet community; see the CONTRIBUTORS file for full + * details. Many organizations have provided support for Squid's + * development; see the SPONSORS file for full details. Squid is + * Copyrighted (C) 2001 by the Regents of the University of + * California; see the COPYRIGHT file for full details. Squid + * incorporates software developed and/or copyrighted by other + * sources; see the CREDITS file for full details. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + * + */ + +#include "squid.h" +#include "CacheManager.h" +#include "Store.h" +#include "HttpRequest.h" + +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +static int n_sourcehash_peers = 0; +static peer **sourcehash_peers = NULL; +static OBJH peerSourceHashCachemgr; + +static int +peerSortWeight(const void *a, const void *b) +{ + const peer *const *p1 = (const peer *const *)a; + const peer *const *p2 = (const peer *const *)b; + return (*p1)->weight - (*p2)->weight; +} + +void +peerSourceHashInit(void) +{ + int W = 0; + int K; + int k; + double P_last, X_last, Xn; + peer *p; + peer **P; + char *t; + /* Clean up */ + + for (k = 0; k < n_sourcehash_peers; k++) { + cbdataReferenceDone(sourcehash_peers[k]); + } + + safe_free(sourcehash_peers); + n_sourcehash_peers = 0; + /* find out which peers we have */ + + for (p = Config.peers; p; p = p->next) { + if (!p->options.sourcehash) + continue; + + assert(p->type == PEER_PARENT); + + if (p->weight == 0) + continue; + + n_sourcehash_peers++; + + W += p->weight; + } + + if (n_sourcehash_peers == 0) + return; + + sourcehash_peers = (peer **)xcalloc(n_sourcehash_peers, sizeof(*sourcehash_peers)); + + /* Build a list of the found peers and calculate hashes and load factors */ + for (P = sourcehash_peers, p = Config.peers; p; p = p->next) { + if (!p->options.sourcehash) + continue; + + if (p->weight == 0) + continue; + + /* calculate this peers hash */ + p->sourcehash.hash = 0; + + for (t = p->name; *t != 0; t++) + p->sourcehash.hash += ROTATE_LEFT(p->sourcehash.hash, 19) + (unsigned int) *t; + + p->sourcehash.hash += p->sourcehash.hash * 0x62531965; + + p->sourcehash.hash = ROTATE_LEFT(p->sourcehash.hash, 21); + + /* and load factor */ + p->sourcehash.load_factor = ((double) p->weight) / (double) W; + + if (floor(p->sourcehash.load_factor * 1000.0) == 0.0) + p->sourcehash.load_factor = 0.0; + + /* add it to our list of peers */ + *P++ = cbdataReference(p); + } + + /* Sort our list on weight */ + qsort(sourcehash_peers, n_sourcehash_peers, sizeof(*sourcehash_peers), peerSortWeight); + + /* Calculate the load factor multipliers X_k + * + * X_1 = pow ((K*p_1), (1/K)) + * X_k = ([K-k+1] * [P_k - P_{k-1}])/(X_1 * X_2 * ... * X_{k-1}) + * X_k += pow ((X_{k-1}, {K-k+1}) + * X_k = pow (X_k, {1/(K-k+1)}) + * simplified to have X_1 part of the loop + */ + K = n_sourcehash_peers; + + P_last = 0.0; /* Empty P_0 */ + + Xn = 1.0; /* Empty starting point of X_1 * X_2 * ... * X_{x-1} */ + + X_last = 0.0; /* Empty X_0, nullifies the first pow statement */ + + for (k = 1; k <= K; k++) { + double Kk1 = (double) (K - k + 1); + p = sourcehash_peers[k - 1]; + p->sourcehash.load_multiplier = (Kk1 * (p->sourcehash.load_factor - P_last)) / Xn; + p->sourcehash.load_multiplier += pow(X_last, Kk1); + p->sourcehash.load_multiplier = pow(p->sourcehash.load_multiplier, 1.0 / Kk1); + Xn *= p->sourcehash.load_multiplier; + X_last = p->sourcehash.load_multiplier; + P_last = p->sourcehash.load_factor; + } +} + +void +peerSourceHashRegisterWithCacheManager(CacheManager & manager) +{ + manager.registerAction("sourcehash", "peer sourcehash information", peerSourceHashCachemgr, 0, 1); +} + +peer * +peerSourceHashSelectParent(HttpRequest * request) +{ + int k; + const char *c; + peer *p = NULL; + peer *tp; + unsigned int user_hash = 0; + unsigned int combined_hash; + double score; + double high_score = 0; + const char *key = NULL; + char ntoabuf[MAX_IPSTRLEN]; + + if (n_sourcehash_peers == 0) + return NULL; + + key = request->client_addr.NtoA(ntoabuf, sizeof(ntoabuf)); + + /* calculate hash key */ + debugs(39, 2, "peerSourceHashSelectParent: Calculating hash for " << key); + + for (c = key; *c != 0; c++) + user_hash += ROTATE_LEFT(user_hash, 19) + *c; + + /* select peer */ + for (k = 0; k < n_sourcehash_peers; k++) { + tp = sourcehash_peers[k]; + combined_hash = (user_hash ^ tp->sourcehash.hash); + combined_hash += combined_hash * 0x62531965; + combined_hash = ROTATE_LEFT(combined_hash, 21); + score = combined_hash * tp->sourcehash.load_multiplier; + debugs(39, 3, "peerSourceHashSelectParent: " << tp->name << " combined_hash " << combined_hash << + " score " << std::setprecision(0) << score); + + if ((score > high_score) && peerHTTPOkay(tp, request)) { + p = tp; + high_score = score; + } + } + + if (p) + debugs(39, 2, "peerSourceHashSelectParent: selected " << p->name); + + return p; +} + +static void +peerSourceHashCachemgr(StoreEntry * sentry) +{ + peer *p; + int sumfetches = 0; + storeAppendPrintf(sentry, "%24s %10s %10s %10s %10s\n", + "Hostname", + "Hash", + "Multiplier", + "Factor", + "Actual"); + + for (p = Config.peers; p; p = p->next) + sumfetches += p->stats.fetches; + + for (p = Config.peers; p; p = p->next) { + storeAppendPrintf(sentry, "%24s %10x %10f %10f %10f\n", + p->name, p->sourcehash.hash, + p->sourcehash.load_multiplier, + p->sourcehash.load_factor, + sumfetches ? (double) p->stats.fetches / sumfetches : -1.0); + } +} === added file 'src/peer_userhash.cc' --- src/peer_userhash.cc 1970-01-01 00:00:00 +0000 +++ src/peer_userhash.cc 2008-07-11 20:43:43 +0000 @@ -0,0 +1,233 @@ + +/* + * $Id: carp.cc,v 1.27 2008/01/14 12:13:49 hno Exp $ + * + * DEBUG: section 39 Peer user hash based selection + * AUTHOR: Henrik Nordstrom + * BASED ON: carp.cc + * + * SQUID Web Proxy Cache http://www.squid-cache.org/ + * ---------------------------------------------------------- + * + * Squid is the result of efforts by numerous individuals from + * the Internet community; see the CONTRIBUTORS file for full + * details. Many organizations have provided support for Squid's + * development; see the SPONSORS file for full details. Squid is + * Copyrighted (C) 2001 by the Regents of the University of + * California; see the COPYRIGHT file for full details. Squid + * incorporates software developed and/or copyrighted by other + * sources; see the CREDITS file for full details. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + * + */ + +#include "squid.h" +#include "CacheManager.h" +#include "Store.h" +#include "HttpRequest.h" +#include "AuthUserRequest.h" + +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +static int n_userhash_peers = 0; +static peer **userhash_peers = NULL; +static OBJH peerUserHashCachemgr; + +static int +peerSortWeight(const void *a, const void *b) +{ + const peer *const *p1 = (const peer *const *)a; + const peer *const *p2 = (const peer *const *)b; + return (*p1)->weight - (*p2)->weight; +} + +void +peerUserHashInit(void) +{ + int W = 0; + int K; + int k; + double P_last, X_last, Xn; + peer *p; + peer **P; + char *t; + /* Clean up */ + + for (k = 0; k < n_userhash_peers; k++) { + cbdataReferenceDone(userhash_peers[k]); + } + + safe_free(userhash_peers); + n_userhash_peers = 0; + /* find out which peers we have */ + + for (p = Config.peers; p; p = p->next) { + if (!p->options.userhash) + continue; + + assert(p->type == PEER_PARENT); + + if (p->weight == 0) + continue; + + n_userhash_peers++; + + W += p->weight; + } + + if (n_userhash_peers == 0) + return; + + userhash_peers = (peer **)xcalloc(n_userhash_peers, sizeof(*userhash_peers)); + + /* Build a list of the found peers and calculate hashes and load factors */ + for (P = userhash_peers, p = Config.peers; p; p = p->next) { + if (!p->options.userhash) + continue; + + if (p->weight == 0) + continue; + + /* calculate this peers hash */ + p->userhash.hash = 0; + + for (t = p->name; *t != 0; t++) + p->userhash.hash += ROTATE_LEFT(p->userhash.hash, 19) + (unsigned int) *t; + + p->userhash.hash += p->userhash.hash * 0x62531965; + + p->userhash.hash = ROTATE_LEFT(p->userhash.hash, 21); + + /* and load factor */ + p->userhash.load_factor = ((double) p->weight) / (double) W; + + if (floor(p->userhash.load_factor * 1000.0) == 0.0) + p->userhash.load_factor = 0.0; + + /* add it to our list of peers */ + *P++ = cbdataReference(p); + } + + /* Sort our list on weight */ + qsort(userhash_peers, n_userhash_peers, sizeof(*userhash_peers), peerSortWeight); + + /* Calculate the load factor multipliers X_k + * + * X_1 = pow ((K*p_1), (1/K)) + * X_k = ([K-k+1] * [P_k - P_{k-1}])/(X_1 * X_2 * ... * X_{k-1}) + * X_k += pow ((X_{k-1}, {K-k+1}) + * X_k = pow (X_k, {1/(K-k+1)}) + * simplified to have X_1 part of the loop + */ + K = n_userhash_peers; + + P_last = 0.0; /* Empty P_0 */ + + Xn = 1.0; /* Empty starting point of X_1 * X_2 * ... * X_{x-1} */ + + X_last = 0.0; /* Empty X_0, nullifies the first pow statement */ + + for (k = 1; k <= K; k++) { + double Kk1 = (double) (K - k + 1); + p = userhash_peers[k - 1]; + p->userhash.load_multiplier = (Kk1 * (p->userhash.load_factor - P_last)) / Xn; + p->userhash.load_multiplier += pow(X_last, Kk1); + p->userhash.load_multiplier = pow(p->userhash.load_multiplier, 1.0 / Kk1); + Xn *= p->userhash.load_multiplier; + X_last = p->userhash.load_multiplier; + P_last = p->userhash.load_factor; + } +} + +void +peerUserHashRegisterWithCacheManager(CacheManager & manager) +{ + manager.registerAction("userhash", "peer userhash information", peerUserHashCachemgr, 0, 1); +} + +peer * +peerUserHashSelectParent(HttpRequest * request) +{ + int k; + const char *c; + peer *p = NULL; + peer *tp; + unsigned int user_hash = 0; + unsigned int combined_hash; + double score; + double high_score = 0; + const char *key = NULL; + + if (n_userhash_peers == 0) + return NULL; + + if (request->auth_user_request) + key = request->auth_user_request->username(); + + if (!key) + return NULL; + + /* calculate hash key */ + debugs(39, 2, "peerUserHashSelectParent: Calculating hash for " << key); + + for (c = key; *c != 0; c++) + user_hash += ROTATE_LEFT(user_hash, 19) + *c; + + /* select peer */ + for (k = 0; k < n_userhash_peers; k++) { + tp = userhash_peers[k]; + combined_hash = (user_hash ^ tp->userhash.hash); + combined_hash += combined_hash * 0x62531965; + combined_hash = ROTATE_LEFT(combined_hash, 21); + score = combined_hash * tp->userhash.load_multiplier; + debugs(39, 3, "peerUserHashSelectParent: " << tp->name << " combined_hash " << combined_hash << + " score " << std::setprecision(0) << score); + + if ((score > high_score) && peerHTTPOkay(tp, request)) { + p = tp; + high_score = score; + } + } + + if (p) + debugs(39, 2, "peerUserHashSelectParent: selected " << p->name); + + return p; +} + +static void +peerUserHashCachemgr(StoreEntry * sentry) +{ + peer *p; + int sumfetches = 0; + storeAppendPrintf(sentry, "%24s %10s %10s %10s %10s\n", + "Hostname", + "Hash", + "Multiplier", + "Factor", + "Actual"); + + for (p = Config.peers; p; p = p->next) + sumfetches += p->stats.fetches; + + for (p = Config.peers; p; p = p->next) { + storeAppendPrintf(sentry, "%24s %10x %10f %10f %10f\n", + p->name, p->userhash.hash, + p->userhash.load_multiplier, + p->userhash.load_factor, + sumfetches ? (double) p->stats.fetches / sumfetches : -1.0); + } +} === modified file 'src/protos.h' --- src/protos.h 2008-07-11 19:32:10 +0000 +++ src/protos.h 2008-07-11 20:26:11 +0000 @@ -729,9 +729,16 @@ SQUIDCEXTERN int internalHostnameIs(const char *); SQUIDCEXTERN void carpInit(void); -extern void carpRegisterWithCacheManager(CacheManager & manager); +SQUIDCEXTERN void carpRegisterWithCacheManager(CacheManager & manager); SQUIDCEXTERN peer *carpSelectParent(HttpRequest *); +SQUIDCEXTERN void peerUserHashInit(void); +SQUIDCEXTERN void peerUserHashRegisterWithCacheManager(CacheManager & manager); +SQUIDCEXTERN peer * peerUserHashSelectParent(HttpRequest * request); + +SQUIDCEXTERN void peerSourceHashInit(void); +SQUIDCEXTERN void peerSourceHashRegisterWithCacheManager(CacheManager & manager); +SQUIDCEXTERN peer * peerSourceHashSelectParent(HttpRequest * request); #if USE_LEAKFINDER SQUIDCEXTERN void leakInit(void); === modified file 'src/structs.h' --- src/structs.h 2008-07-11 19:32:10 +0000 +++ src/structs.h 2008-07-11 20:20:31 +0000 @@ -930,6 +930,8 @@ #endif unsigned int allow_miss:1; unsigned int carp:1; + unsigned int userhash:1; + unsigned int sourcehash:1; unsigned int originserver:1; } options; @@ -972,6 +974,20 @@ double load_factor; /* normalized weight value */ } carp; + struct + { + unsigned int hash; + double load_multiplier; + double load_factor; /* normalized weight value */ + } userhash; + + struct + { + unsigned int hash; + double load_multiplier; + double load_factor; /* normalized weight value */ + } sourcehash; + char *login; /* Proxy authorization */ time_t connect_timeout; int max_conn; # Begin bundle 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 YcMfMafna0086P/i7kinChIM9s5xYA==