The attached patch add support for parameterized Cache Manager queries. 

Currently, one sends mgr queries to the "whole" Squid. Kids responses may get 
aggregated by Coordinator, and we, in general, want to aggregate all responses
that can be aggregated.

This patch allow us to aggregate stats for a subset of kids. For example, 
the following query aggregates stats for just the first and the third workers:

    mgr:info?workers=1,3

When query response information cannot be aggregated (or at least is not 
aggregated right now), then a parameterized query will result in several 
matching "byKid { ..." blocks.

This patch support the following scope variants:

    * raw interface with access to any kid process or groups of kids; similar 
      to ${process_number} macro we already support in squid.conf:    
          mgr:foo?processes=id,id,id...
    * higher-level interface to isolate workers by their numbers, starting 
      with 1 for the first worker:
	  mgr:foo?workers=num,num,num... 


Currently, all kids except Coordinator are workers, but that
will change in the future as we get more kinds of kids.Currently, one sends mgr queries to the "whole" Squid. Kids responses may get aggregated by Coordinator, and we, in general, want to aggregate all responses that can be aggregated.

=== modified file 'src/cache_manager.cc'
--- src/cache_manager.cc	2011-01-14 14:10:21 +0000
+++ src/cache_manager.cc	2011-01-24 17:25:19 +0000
@@ -31,40 +31,41 @@
  *  along with this program; if not, write to the Free Software
  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
  *
  */
 
 #include "config.h"
 #include "base/TextException.h"
 #include "CacheManager.h"
 #include "Debug.h"
 #include "errorpage.h"
 #include "fde.h"
 #include "HttpReply.h"
 #include "HttpRequest.h"
 #include "mgr/ActionCreator.h"
 #include "mgr/Action.h"
 #include "mgr/ActionProfile.h"
 #include "mgr/BasicActions.h"
 #include "mgr/Command.h"
 #include "mgr/Forwarder.h"
 #include "mgr/FunAction.h"
+#include "mgr/QueryParams.h"
 #include "protos.h" /* rotate_logs() */
 #include "SquidTime.h"
 #include "Store.h"
 #include "wordlist.h"
 #include <algorithm>
 
 
 /// \ingroup CacheManagerInternal
 #define MGR_PASSWD_SZ 128
 
 /// creates Action using supplied Action::Create method and command
 class ClassActionCreator: public Mgr::ActionCreator
 {
 public:
     typedef Mgr::Action::Pointer Handler(const Mgr::Command::Pointer &cmd);
 
 public:
     ClassActionCreator(Handler *aHandler): handler(aHandler) {}
 
     virtual Mgr::Action::Pointer create(const Mgr::Command::Pointer &cmd) const {
@@ -159,72 +160,88 @@
     cmd->profile = findAction(params.actionName.termedBuf());
     Must(cmd->profile != NULL);
     return cmd->profile->creator->create(cmd);
 }
 
 /**
  \ingroup CacheManagerInternal
  * define whether the URL is a cache-manager URL and parse the action
  * requested by the user. Checks via CacheManager::ActionProtection() that the
  * item is accessible by the user.
  \retval CacheManager::cachemgrStateData state object for the following handling
  \retval NULL if the action can't be found or can't be accessed by the user
  */
 Mgr::Command::Pointer
 CacheManager::ParseUrl(const char *url)
 {
     int t;
     LOCAL_ARRAY(char, host, MAX_URL);
     LOCAL_ARRAY(char, request, MAX_URL);
     LOCAL_ARRAY(char, password, MAX_URL);
-    t = sscanf(url, "cache_object://%[^/]/%[^@]@%s", host, request, password);
+    LOCAL_ARRAY(char, params, MAX_URL);
+    host[0] = 0;
+    request[0] = 0;
+    password[0] = 0;
+    params[0] = 0;
+    int pos = -1;
+    int len = strlen(url);
+    Must(len > 0);
+    t = sscanf(url, "cache_object://%[^/]/%[^@?]%n@%[^?]?%s", host, request, &pos, password, params);
+
+    if (pos >0 && url[pos] == '?') {
+        ++pos;
+        if (pos < len)
+            xstrncpy(params, url + pos, sizeof(params));
+    }
 
     if (t < 2)
         xstrncpy(request, "menu", MAX_URL);
 
 #ifdef _SQUID_OS2_
     if (t == 2 && request[0] == '\0') {
         /*
          * emx's sscanf insists of returning 2 because it sets request
          * to null
          */
         xstrncpy(request, "menu", MAX_URL);
     }
 #endif
 
     Mgr::ActionProfile::Pointer profile = findAction(request);
     if (!profile) {
         debugs(16, DBG_IMPORTANT, "CacheManager::ParseUrl: action '" << request << "' not found");
         return NULL;
     }
 
     const char *prot = ActionProtection(profile);
     if (!strcmp(prot, "disabled") || !strcmp(prot, "hidden")) {
         debugs(16, DBG_IMPORTANT, "CacheManager::ParseUrl: action '" << request << "' is " << prot);
         return NULL;
     }
 
     Mgr::Command::Pointer cmd = new Mgr::Command;
+    if (!Mgr::QueryParams::Parse(params, cmd->params.queryParams))
+        return NULL;
     cmd->profile = profile;
     cmd->params.httpUri = url;
     cmd->params.userName = String();
-    cmd->params.password = t == 3 ? String(password) : String();
+    cmd->params.password = password;
     cmd->params.actionName = request;
     return cmd;
 }
 
 /// \ingroup CacheManagerInternal
 /*
  \ingroup CacheManagerInternal
  * Decodes the headers needed to perform user authentication and fills
  * the details into the cachemgrStateData argument
  */
 void
 CacheManager::ParseHeaders(const HttpRequest * request, Mgr::ActionParams &params)
 {
     assert(request);
 
     params.httpMethod = request->method.id();
     params.httpFlags = request->flags;
 
 #if HAVE_AUTH_MODULE_BASIC
     // TODO: use the authentication system decode to retrieve these details properly.

=== modified file 'src/mgr/ActionParams.cc'
--- src/mgr/ActionParams.cc	2010-10-29 00:12:28 +0000
+++ src/mgr/ActionParams.cc	2011-01-24 13:39:06 +0000
@@ -10,33 +10,35 @@
 #include "ipc/TypedMsgHdr.h"
 #include "mgr/ActionParams.h"
 
 Mgr::ActionParams::ActionParams(): httpMethod(METHOD_NONE)
 {
 }
 
 Mgr::ActionParams::ActionParams(const Ipc::TypedMsgHdr &msg)
 {
     msg.getString(httpUri);
 
     const int m = msg.getInt();
     Must(METHOD_NONE <= m && m < METHOD_ENUM_END);
     httpMethod = static_cast<_method_t>(m);
 
     msg.getPod(httpFlags);
 
     msg.getString(actionName);
     msg.getString(userName);
     msg.getString(password);
+    queryParams.unpack(msg);
 }
 
 void
 Mgr::ActionParams::pack(Ipc::TypedMsgHdr &msg) const
 {
     msg.putString(httpUri);
     msg.putInt(httpMethod);
     msg.putPod(httpFlags);
 
     msg.putString(actionName);
     msg.putString(userName);
     msg.putString(password);
+    queryParams.pack(msg);
 }

=== modified file 'src/mgr/ActionParams.h'
--- src/mgr/ActionParams.h	2010-10-28 18:52:59 +0000
+++ src/mgr/ActionParams.h	2011-01-24 13:39:06 +0000
@@ -1,43 +1,44 @@
 /*
  * $Id$
  *
  * DEBUG: section 16    Cache Manager API
  *
  */
 
 #ifndef SQUID_MGR_ACTION_PARAMS_H
 #define SQUID_MGR_ACTION_PARAMS_H
 
 #include "HttpRequestMethod.h"
 #include "ipc/forward.h"
+#include "mgr/QueryParams.h"
 
 namespace Mgr
 {
 
 /// Cache Manager Action parameters extracted from the user request
 class ActionParams
 {
 public:
     ActionParams();
 
     explicit ActionParams(const Ipc::TypedMsgHdr &msg); ///< load from msg
     void pack(Ipc::TypedMsgHdr &msg) const; ///< store into msg
 
 public:
     /* details of the client HTTP request that caused the action */
     String httpUri; ///< HTTP request URI
     _method_t httpMethod; ///< HTTP request method
     request_flags httpFlags; ///< HTTP request flags
 
     /* action parameters extracted from the client HTTP request */
     String actionName; ///< action name (and credentials realm)
     String userName; ///< user login name; currently only used for logging
     String password; ///< user password; used for acceptance check and cleared
-
+    QueryParams queryParams;
 };
 
 } // namespace Mgr
 
 std::ostream &operator <<(std::ostream &os, const Mgr::ActionParams &params);
 
 #endif /* SQUID_MGR_ACTION_PARAMS_H */

=== modified file 'src/mgr/Inquirer.cc'
--- src/mgr/Inquirer.cc	2010-11-27 01:46:22 +0000
+++ src/mgr/Inquirer.cc	2011-01-27 14:49:37 +0000
@@ -1,111 +1,123 @@
 /*
  * $Id$
  *
  * DEBUG: section 16    Cache Manager API
  *
  */
 
 #include "config.h"
 #include "base/TextException.h"
 #include "comm/Write.h"
 #include "CommCalls.h"
 #include "HttpReply.h"
+#include "HttpRequest.h"
 #include "ipc/Coordinator.h"
 #include "mgr/ActionWriter.h"
 #include "mgr/Command.h"
+#include "mgr/IntParam.h"
 #include "mgr/Inquirer.h"
 #include "mgr/Request.h"
 #include "mgr/Response.h"
 #include "SquidTime.h"
+#include "errorpage.h"
 #include <memory>
 #include <algorithm>
 
 
 CBDATA_NAMESPACED_CLASS_INIT(Mgr, Inquirer);
 
 Mgr::Inquirer::RequestsMap Mgr::Inquirer::TheRequestsMap;
 unsigned int Mgr::Inquirer::LastRequestId = 0;
 
 /// compare Ipc::StrandCoord using kidId, for std::sort() below
 static bool
 LesserStrandByKidId(const Ipc::StrandCoord &c1, const Ipc::StrandCoord &c2)
 {
     return c1.kidId < c2.kidId;
 }
 
 Mgr::Inquirer::Inquirer(Action::Pointer anAction, int aFd,
                         const Request &aCause, const Ipc::StrandCoords &coords):
         AsyncJob("Mgr::Inquirer"),
         aggrAction(anAction),
         cause(aCause),
         fd(aFd),
-        strands(coords), pos(strands.begin()),
+        strands(applyQueryParams(coords, aCause.params.queryParams)), pos(strands.begin()),
         requestId(0), closer(NULL), timeout(aggrAction->atomic() ? 10 : 100)
 {
     debugs(16, 5, HERE << "FD " << aFd << " action: " << aggrAction);
 
-    // order by ascending kid IDs; useful for non-aggregatable stats
-    std::sort(strands.begin(), strands.end(), LesserStrandByKidId);
-
     closer = asyncCall(16, 5, "Mgr::Inquirer::noteCommClosed",
                        CommCbMemFunT<Inquirer, CommCloseCbParams>(this, &Inquirer::noteCommClosed));
     comm_add_close_handler(fd, closer);
 }
 
 Mgr::Inquirer::~Inquirer()
 {
     debugs(16, 5, HERE);
     close();
 }
 
 /// closes our copy of the client HTTP connection socket
 void
 Mgr::Inquirer::close()
 {
     if (fd >= 0) {
         removeCloseHandler();
         comm_close(fd);
         fd = -1;
     }
 }
 
 void
 Mgr::Inquirer::removeCloseHandler()
 {
     if (closer != NULL) {
         comm_remove_close_handler(fd, closer);
         closer = NULL;
     }
 }
 
 void
 Mgr::Inquirer::start()
 {
     debugs(16, 5, HERE);
     Must(fd >= 0);
     Must(aggrAction != NULL);
-
-    std::auto_ptr<HttpReply> reply(new HttpReply);
-    reply->setHeaders(HTTP_OK, NULL, "text/plain", -1, squid_curtime, squid_curtime);
-    reply->header.putStr(HDR_CONNECTION, "close"); // until we chunk response
-    std::auto_ptr<MemBuf> replyBuf(reply->pack());
+ 
+    std::auto_ptr<MemBuf> replyBuf;
+    if (strands.empty()) {
+        LOCAL_ARRAY(char, url, MAX_URL);
+        snprintf(url, MAX_URL, "%s", aggrAction->command().params.httpUri.termedBuf());
+        HttpRequest *req = HttpRequest::CreateFromUrl(url);
+        ErrorState *err = errorCon(ERR_INVALID_URL, HTTP_NOT_FOUND, req);
+        std::auto_ptr<HttpReply> reply(err->BuildHttpReply());
+        replyBuf.reset(reply->pack());
+        errorStateFree(err);
+    }
+    else {
+        std::auto_ptr<HttpReply> reply(new HttpReply);
+        reply->setHeaders(HTTP_OK, NULL, "text/plain", -1, squid_curtime, squid_curtime);
+        reply->header.putStr(HDR_CONNECTION, "close"); // until we chunk response
+        replyBuf.reset(reply->pack());
+    }
     writer = asyncCall(16, 5, "Mgr::Inquirer::noteWroteHeader",
                        CommCbMemFunT<Inquirer, CommIoCbParams>(this, &Inquirer::noteWroteHeader));
     Comm::Write(fd, replyBuf.get(), writer);
 }
 
 /// called when we wrote the response header
 void
 Mgr::Inquirer::noteWroteHeader(const CommIoCbParams& params)
 {
     debugs(16, 5, HERE);
     writer = NULL;
     Must(params.flag == COMM_OK);
     Must(params.fd == fd);
     Must(params.size != 0);
     // start inquiries at the initial pos
     inquire();
 }
 
 void
 Mgr::Inquirer::inquire()
@@ -149,41 +161,41 @@
 
 /// called when the HTTP client or some external force closed our socket
 void
 Mgr::Inquirer::noteCommClosed(const CommCloseCbParams& params)
 {
     debugs(16, 5, HERE);
     Must(fd < 0 || fd == params.fd);
     fd = -1;
     mustStop("commClosed");
 }
 
 void
 Mgr::Inquirer::swanSong()
 {
     debugs(16, 5, HERE);
     removeTimeoutEvent();
     if (requestId > 0) {
         DequeueRequest(requestId);
         requestId = 0;
     }
-    if (aggrAction->aggregatable()) {
+    if (!strands.empty() && aggrAction->aggregatable()) {
         removeCloseHandler();
         AsyncJob::Start(new ActionWriter(aggrAction, fd));
         fd = -1; // should not close fd because we passed it to ActionWriter
     }
     close();
 }
 
 bool
 Mgr::Inquirer::doneAll() const
 {
     return !writer && pos == strands.end();
 }
 
 /// returns and forgets the right Inquirer callback for strand request
 AsyncCall::Pointer
 Mgr::Inquirer::DequeueRequest(unsigned int requestId)
 {
     debugs(16, 3, HERE << " requestId " << requestId);
     Must(requestId != 0);
     AsyncCall::Pointer call;
@@ -234,20 +246,66 @@
 {
     debugs(16, 3, HERE);
     if (requestId != 0) {
         DequeueRequest(requestId);
         requestId = 0;
         Must(!done()); // or we should not be called
         ++pos; // advance after a failed inquiry
         inquire();
     }
 }
 
 const char*
 Mgr::Inquirer::status() const
 {
     static MemBuf buf;
     buf.reset();
     buf.Printf(" [FD %d, requestId %u]", fd, requestId);
     buf.terminate();
     return buf.content();
 }
+
+Ipc::StrandCoords
+Mgr::Inquirer::applyQueryParams(const Ipc::StrandCoords& aStrands, const QueryParams& aParams)
+{
+    Ipc::StrandCoords strands;
+
+    QueryParam::Pointer processesParam = cause.params.queryParams.get("processes");
+    QueryParam::Pointer workersParam = cause.params.queryParams.get("workers");
+
+    if (processesParam == NULL || workersParam == NULL) {
+        if (processesParam != NULL) {
+            IntParam* param = dynamic_cast<IntParam*>(processesParam.getRaw());
+            if (param != NULL && param->type == QueryParam::ptInt) {
+                const std::vector<int>& processes = param->value();
+                for (Ipc::StrandCoords::const_iterator iter = aStrands.begin();
+                     iter != aStrands.end(); ++iter)
+                {
+                    if (std::find(processes.begin(), processes.end(), iter->kidId) != processes.end())
+                        strands.push_back(*iter);
+                }
+            }
+        } else if (workersParam != NULL) {
+            IntParam* param = dynamic_cast<IntParam*>(workersParam.getRaw());
+            if (param != NULL && param->type == QueryParam::ptInt) {
+                const std::vector<int>& workers = param->value();
+                for (size_t i = 0; i < aStrands.size(); ++i)
+                {
+                    if (std::find(workers.begin(), workers.end(), i + 1) != workers.end())
+                        strands.push_back(aStrands[i]);
+                }
+            }
+        } else {
+            strands = aStrands;
+        }
+
+        // order by ascending kid IDs; useful for non-aggregatable stats
+        std::sort(strands.begin(), strands.end(), LesserStrandByKidId);
+    }
+
+    debugs(0, 0, HERE << "strands kid IDs = ");
+    for (Ipc::StrandCoords::const_iterator iter = strands.begin(); iter != strands.end(); ++iter) {
+        debugs(0, 0, HERE << iter->kidId);
+    }
+
+    return strands;
+}

=== modified file 'src/mgr/Inquirer.h'
--- src/mgr/Inquirer.h	2010-10-29 00:12:28 +0000
+++ src/mgr/Inquirer.h	2011-01-24 13:39:06 +0000
@@ -41,40 +41,42 @@
     virtual bool doneAll() const;
     virtual const char *status() const;
 
 private:
     typedef UnaryMemFunT<Inquirer, Response, const Response&> HandleAckDialer;
 
     void inquire();
     void noteWroteHeader(const CommIoCbParams& params);
     void noteCommClosed(const CommCloseCbParams& params);
 
     void handleRemoteAck(const Response& response);
 
     static AsyncCall::Pointer DequeueRequest(unsigned int requestId);
 
     static void RequestTimedOut(void* param);
     void requestTimedOut();
     void removeTimeoutEvent();
 
     void close();
     void removeCloseHandler();
+    Ipc::StrandCoords applyQueryParams(const Ipc::StrandCoords& aStrands,
+                                       const QueryParams& aParams);
 
 private:
     Action::Pointer aggrAction; //< action to aggregate
 
     Request cause; ///< cache manager request received from HTTP client
     int fd; ///< HTTP client socket descriptor
 
     Ipc::StrandCoords strands; ///< all strands we want to query, in order
     Ipc::StrandCoords::const_iterator pos; ///< strand we should query now
 
     unsigned int requestId; ///< ID of our outstanding request to strand
     AsyncCall::Pointer writer; ///< comm_write callback
     AsyncCall::Pointer closer; ///< comm_close handler
     const double timeout; ///< number of seconds to wait for strand response
 
     /// maps requestId to Inquirer::handleRemoteAck callback
     typedef std::map<unsigned int, AsyncCall::Pointer> RequestsMap;
     static RequestsMap TheRequestsMap; ///< pending strand requests
 
     static unsigned int LastRequestId; ///< last requestId used

=== added file 'src/mgr/IntParam.cc'
--- src/mgr/IntParam.cc	1970-01-01 00:00:00 +0000
+++ src/mgr/IntParam.cc	2011-01-24 13:44:39 +0000
@@ -0,0 +1,48 @@
+/*
+ * $Id$
+ *
+ * DEBUG: section 16    Cache Manager API
+ *
+ */
+
+#include "config.h"
+#include "base/TextException.h"
+#include "ipc/TypedMsgHdr.h"
+#include "mgr/IntParam.h"
+
+
+Mgr::IntParam::IntParam():
+    QueryParam(QueryParam::ptInt), array()
+{
+}
+
+Mgr::IntParam::IntParam(const std::vector<int>& anArray):
+    QueryParam(QueryParam::ptInt), array(anArray)
+{
+}
+
+void
+Mgr::IntParam::pack(Ipc::TypedMsgHdr& msg) const
+{
+    msg.putPod(type);
+    msg.putInt(array.size());
+    typedef std::vector<int>::const_iterator Iterator;
+    for (Iterator iter = array.begin(); iter != array.end(); ++iter)
+        msg.putInt(*iter);
+}
+
+void
+Mgr::IntParam::unpackValue(const Ipc::TypedMsgHdr& msg)
+{
+    array.clear();
+    int count = msg.getInt();
+    Must(count >= 0);
+    for ( ; count > 0; --count)
+        array.push_back(msg.getInt());
+}
+
+const std::vector<int>&
+Mgr::IntParam::value() const
+{
+    return array;
+}

=== added file 'src/mgr/IntParam.h'
--- src/mgr/IntParam.h	1970-01-01 00:00:00 +0000
+++ src/mgr/IntParam.h	2011-01-24 13:39:06 +0000
@@ -0,0 +1,35 @@
+/*
+ * $Id$
+ *
+ * DEBUG: section 16    Cache Manager API
+ *
+ */
+
+#ifndef SQUID_MGR_INT_PARAM_H
+#define SQUID_MGR_INT_PARAM_H
+
+#include "ipc/forward.h"
+#include "mgr/forward.h"
+#include "mgr/QueryParam.h"
+#include <vector>
+
+
+namespace Mgr
+{
+
+class IntParam: public QueryParam
+{
+public:
+    IntParam();
+    IntParam(const std::vector<int>& anArray);
+    virtual void pack(Ipc::TypedMsgHdr& msg) const;
+    virtual void unpackValue(const Ipc::TypedMsgHdr& msg);
+    const std::vector<int>& value() const;
+
+private:
+    std::vector<int> array;
+};
+
+} // namespace Mgr
+
+#endif /* SQUID_MGR_INT_PARAM_H */

=== modified file 'src/mgr/Makefile.am'
--- src/mgr/Makefile.am	2010-10-28 18:52:59 +0000
+++ src/mgr/Makefile.am	2011-01-24 13:39:06 +0000
@@ -27,21 +27,28 @@
 	FunAction.h \
 	InfoAction.cc \
 	InfoAction.h \
 	Inquirer.cc \
 	Inquirer.h \
 	IntervalAction.cc \
 	IntervalAction.h \
 	IoAction.cc \
 	IoAction.h \
 	Registration.cc \
 	Registration.h \
 	Request.cc \
 	Request.h \
 	Response.cc \
 	Response.h \
 	ServiceTimesAction.cc \
 	ServiceTimesAction.h \
 	StoreIoAction.cc \
 	StoreIoAction.h \
 	StoreToCommWriter.cc \
-	StoreToCommWriter.h
+	StoreToCommWriter.h \
+	QueryParam.h \
+	QueryParams.cc \
+	QueryParams.h \
+	IntParam.cc \
+	IntParam.h \
+	StringParam.cc \
+	StringParam.h

=== added file 'src/mgr/QueryParam.h'
--- src/mgr/QueryParam.h	1970-01-01 00:00:00 +0000
+++ src/mgr/QueryParam.h	2011-01-24 13:39:06 +0000
@@ -0,0 +1,40 @@
+/*
+ * $Id$
+ *
+ * DEBUG: section 16    Cache Manager API
+ *
+ */
+
+#ifndef SQUID_MGR_QUERY_PARAM_H
+#define SQUID_MGR_QUERY_PARAM_H
+
+#include "ipc/forward.h"
+#include "RefCount.h"
+
+
+namespace Mgr
+{
+
+class QueryParam: public RefCountable
+{
+public:
+    typedef enum {ptInt = 1, ptString} Type;
+    typedef RefCount<QueryParam> Pointer;
+
+public:
+    QueryParam(Type aType): type(aType) {}
+    virtual ~QueryParam() {}
+    virtual void pack(Ipc::TypedMsgHdr& msg) const = 0; ///< store parameter into msg
+    virtual void unpackValue(const Ipc::TypedMsgHdr& msg) = 0; ///< load parameter value from msg
+
+private:
+    QueryParam(const QueryParam&); // not implemented
+    QueryParam& operator= (const QueryParam&); // not implemented
+
+public:
+    Type type;
+};
+
+} // namespace Mgr
+
+#endif /* SQUID_MGR_QUERY_PARAM_H */

=== added file 'src/mgr/QueryParams.cc'
--- src/mgr/QueryParams.cc	1970-01-01 00:00:00 +0000
+++ src/mgr/QueryParams.cc	2011-01-24 13:44:14 +0000
@@ -0,0 +1,139 @@
+/*
+ * $Id$
+ *
+ * DEBUG: section 16    Cache Manager API
+ *
+ */
+
+#include "config.h"
+#include "base/TextException.h"
+#include "ipc/TypedMsgHdr.h"
+#include "mgr/IntParam.h"
+#include "mgr/StringParam.h"
+#include "mgr/QueryParams.h"
+#include <regex.h>
+
+
+Mgr::QueryParam::Pointer
+Mgr::QueryParams::get(const String& name)
+{
+    Must(name.size() != 0);
+    Params::iterator pos = find(name);
+    return (pos == params.end() ? NULL : pos->second);
+}
+
+void
+Mgr::QueryParams::pack(Ipc::TypedMsgHdr& msg) const
+{
+    msg.putInt(params.size());
+    for (Params::const_iterator iter = params.begin(); iter != params.end(); ++iter) {
+        Must(iter->first.size() != 0);
+        msg.putString(iter->first);
+        Must(iter->second != NULL);
+        iter->second->pack(msg);
+    }
+}
+
+void
+Mgr::QueryParams::unpack(const Ipc::TypedMsgHdr& msg)
+{
+    int count = msg.getInt();
+    Must(count >= 0);
+    params.clear();
+    for ( ; count > 0; --count) {
+        String name;
+        msg.getString(name);
+        Must(name.size() != 0);
+        QueryParam::Type type;
+        msg.getPod(type);
+        QueryParam::Pointer value = CreateParam(type);
+        value->unpackValue(msg);
+        params.push_back(Param(name, value));
+    }
+}
+
+Mgr::QueryParams::Params::iterator
+Mgr::QueryParams::find(const String& name)
+{
+    Must(name.size() != 0);
+    Params::iterator iter = params.begin();
+    for ( ; iter != params.end(); ++iter) {
+        if (name.caseCmp(iter->first) == 0)
+            break;
+    }
+    return iter;
+}
+
+bool
+Mgr::QueryParams::ParseParam(const String& paramStr, Param& param)
+{
+    bool parsed = false;
+    regmatch_t pmatch[3];
+    regex_t intExpr;
+    regcomp(&intExpr, "^([a-z][a-z0-9_]*)=([0-9]+((,[0-9]+))*)$", REG_EXTENDED | REG_ICASE);
+    regex_t stringExpr;
+    regcomp(&stringExpr, "^([a-z][a-z0-9_]*)=([^&= ]+)$", REG_EXTENDED | REG_ICASE);
+    if (regexec(&intExpr, paramStr.termedBuf(), 3, pmatch, 0) == 0) {
+        param.first = paramStr.substr(pmatch[1].rm_so, pmatch[1].rm_eo);
+        std::vector<int> array;
+        int n = pmatch[2].rm_so;
+        for (int i = n; i < pmatch[2].rm_eo; ++i) {
+            if (paramStr[i] == ',') {
+                array.push_back(atoi(paramStr.substr(n, i).termedBuf()));
+                n = i + 1;
+            }
+        }
+        if (n < pmatch[2].rm_eo)
+            array.push_back(atoi(paramStr.substr(n, pmatch[2].rm_eo).termedBuf()));
+        param.second = new IntParam(array);
+        parsed = true;
+    } else if (regexec(&stringExpr, paramStr.termedBuf(), 3, pmatch, 0) == 0) {
+        param.first = paramStr.substr(pmatch[1].rm_so, pmatch[1].rm_eo);
+        param.second = new StringParam(paramStr.substr(pmatch[2].rm_so, pmatch[2].rm_eo));
+        parsed = true;
+    }
+    regfree(&stringExpr);
+    regfree(&intExpr);
+    return parsed;
+}
+
+bool
+Mgr::QueryParams::Parse(const String& aParamsStr, QueryParams& aParams)
+{
+    if (aParamsStr.size() != 0) {
+        Param param;
+        size_t n = 0;
+        size_t len = aParamsStr.size();
+        for (size_t i = n; i < len; ++i) {
+            if (aParamsStr[i] == '&') {
+                if (!ParseParam(aParamsStr.substr(n, i), param))
+                    return false;
+                aParams.params.push_back(param);
+                n = i + 1;
+            }
+        }
+        if (n < len) {
+            if (!ParseParam(aParamsStr.substr(n, len), param))
+                return false;
+            aParams.params.push_back(param);
+        }
+    }
+    return true;
+}
+
+Mgr::QueryParam::Pointer
+Mgr::QueryParams::CreateParam(QueryParam::Type aType)
+{
+    switch(aType) {
+    case QueryParam::ptInt:
+        return new IntParam();
+
+    case QueryParam::ptString:
+        return new StringParam();
+
+    default:
+        throw TexcHere("unknown parameter type");
+        break;
+    }
+    return NULL;
+}

=== added file 'src/mgr/QueryParams.h'
--- src/mgr/QueryParams.h	1970-01-01 00:00:00 +0000
+++ src/mgr/QueryParams.h	2011-01-24 13:39:06 +0000
@@ -0,0 +1,49 @@
+/*
+ * $Id$
+ *
+ * DEBUG: section 16    Cache Manager API
+ *
+ */
+
+#ifndef SQUID_MGR_QUERY_PARAMS_H
+#define SQUID_MGR_QUERY_PARAMS_H
+
+#include "ipc/forward.h"
+#include "mgr/QueryParam.h"
+#include "SquidString.h"
+#include <vector>
+#include <utility>
+
+
+namespace Mgr
+{
+
+class QueryParams
+{
+public:
+    typedef std::pair<String, QueryParam::Pointer> Param;
+    typedef std::vector<Param> Params;
+
+public:
+    /// returns query parameter by name
+    QueryParam::Pointer get(const String& name);
+    void pack(Ipc::TypedMsgHdr& msg) const; ///< store params into msg
+    void unpack(const Ipc::TypedMsgHdr& msg); ///< load params from msg
+    /// parses the query string parameters
+    static bool Parse(const String& aParamsStr, QueryParams& aParams);
+
+private:
+    /// find query parameter by name
+    Params::iterator find(const String& name);
+    /// creates a parameter of the specified type
+    static QueryParam::Pointer CreateParam(QueryParam::Type aType);
+    /// parses string like "param=value"; returns true if success
+    static bool ParseParam(const String& paramStr, Param& param);
+
+private:
+    Params params;
+};
+
+} // namespace Mgr
+
+#endif /* SQUID_MGR_QUERY_PARAMS_H */

=== added file 'src/mgr/StringParam.cc'
--- src/mgr/StringParam.cc	1970-01-01 00:00:00 +0000
+++ src/mgr/StringParam.cc	2011-01-24 13:45:03 +0000
@@ -0,0 +1,40 @@
+/*
+ * $Id$
+ *
+ * DEBUG: section 16    Cache Manager API
+ *
+ */
+
+#include "config.h"
+#include "ipc/TypedMsgHdr.h"
+#include "mgr/StringParam.h"
+
+
+Mgr::StringParam::StringParam():
+    QueryParam(QueryParam::ptString), str()
+{
+}
+
+Mgr::StringParam::StringParam(const String& aString):
+    QueryParam(QueryParam::ptString), str(aString)
+{
+}
+
+void
+Mgr::StringParam::pack(Ipc::TypedMsgHdr& msg) const
+{
+    msg.putPod(type);
+    msg.putString(str);
+}
+
+void
+Mgr::StringParam::unpackValue(const Ipc::TypedMsgHdr& msg)
+{
+    msg.getString(str);
+}
+
+const String&
+Mgr::StringParam::value() const
+{
+    return str;
+}

=== added file 'src/mgr/StringParam.h'
--- src/mgr/StringParam.h	1970-01-01 00:00:00 +0000
+++ src/mgr/StringParam.h	2011-01-24 13:39:06 +0000
@@ -0,0 +1,35 @@
+/*
+ * $Id$
+ *
+ * DEBUG: section 16    Cache Manager API
+ *
+ */
+
+#ifndef SQUID_MGR_STRING_PARAM_H
+#define SQUID_MGR_STRING_PARAM_H
+
+#include "ipc/forward.h"
+#include "mgr/forward.h"
+#include "mgr/QueryParam.h"
+#include "SquidString.h"
+
+
+namespace Mgr
+{
+
+class StringParam: public QueryParam
+{
+public:
+    StringParam();
+    StringParam(const String& aString);
+    virtual void pack(Ipc::TypedMsgHdr& msg) const;
+    virtual void unpackValue(const Ipc::TypedMsgHdr& msg);
+    const String& value() const;
+
+private:
+    String str;
+};
+
+} // namespace Mgr
+
+#endif /* SQUID_MGR_STRING_PARAM_H */

=== modified file 'src/mgr/forward.h'
--- src/mgr/forward.h	2010-10-28 18:52:59 +0000
+++ src/mgr/forward.h	2011-01-24 13:39:06 +0000
@@ -3,31 +3,33 @@
  *
  * DEBUG: section 16    Cache Manager API
  *
  */
 
 #ifndef SQUID_MGR_FORWARD_H
 #define SQUID_MGR_FORWARD_H
 
 #include "RefCount.h"
 
 namespace Mgr
 {
 
 class Action;
 class ActionCreator;
 class ActionProfile;
 class ActionWriter;
 class Command;
 class Request;
 class Response;
+class QueryParam;
+class QueryParams;
 
 typedef RefCount<Action> ActionPointer;
 typedef RefCount<ActionProfile> ActionProfilePointer;
 typedef RefCount<ActionCreator> ActionCreatorPointer;
 typedef RefCount<Command> CommandPointer;
 
 typedef ActionPointer (ClassActionCreationHandler)(const CommandPointer &cmd);
 
 } // namespace Mgr
 
 #endif /* SQUID_MGR_FORWARD_H */

=== modified file 'tools/cachemgr.cc'
--- tools/cachemgr.cc	2010-11-01 05:44:28 +0000
+++ tools/cachemgr.cc	2011-01-26 15:48:34 +0000
@@ -115,40 +115,42 @@
 #endif
 #if HAVE_FNMATCH_H
 extern "C" {
 #include <fnmatch.h>
 }
 #endif
 
 
 #ifndef DEFAULT_CACHEMGR_CONFIG
 #define DEFAULT_CACHEMGR_CONFIG "/etc/squid/cachemgr.conf"
 #endif
 
 typedef struct {
     char *server;
     char *hostname;
     int port;
     char *action;
     char *user_name;
     char *passwd;
     char *pub_auth;
+    char *workers;
+    char *processes;
 } cachemgr_request;
 
 /*
  * Static variables and constants
  */
 static const time_t passwd_ttl = 60 * 60 * 3;	/* in sec */
 static const char *script_name = "/cgi-bin/cachemgr.cgi";
 static const char *progname = NULL;
 static time_t now;
 
 /*
  * Function prototypes
  */
 static const char *safe_str(const char *str);
 static const char *xstrtok(char **str, char del);
 static void print_trailer(void);
 static void auth_html(const char *host, int port, const char *user_name);
 static void error_html(const char *msg);
 static char *menu_url(cachemgr_request * req, const char *action);
 static int parse_status_line(const char *sline, const char **statusStr);
@@ -822,46 +824,48 @@
 #else
     if ((s = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
 #endif
         snprintf(buf, 1024, "socket: %s\n", xstrerror());
         error_html(buf);
         return 1;
     }
 
     if (connect(s, AI->ai_addr, AI->ai_addrlen) < 0) {
         snprintf(buf, 1024, "connect %s: %s\n",
                  S.ToURL(ipbuf,MAX_IPSTRLEN),
                  xstrerror());
         error_html(buf);
         S.FreeAddrInfo(AI);
         return 1;
     }
 
     S.FreeAddrInfo(AI);
 
     l = snprintf(buf, sizeof(buf),
-                 "GET cache_object://%s/%s HTTP/1.0\r\n"
+                 "GET cache_object://%s/%s%s%s HTTP/1.0\r\n"
                  "Accept: */*\r\n"
                  "%s"			/* Authentication info or nothing */
                  "\r\n",
                  req->hostname,
                  req->action,
+                 req->workers? "?workers=" : (req->processes ? "?processes=" : ""),
+                 req->workers? req->workers : (req->processes ? req->processes: ""),
                  make_auth_header(req));
     if (write(s, buf, l) < 0) {
         fprintf(stderr,"ERROR: (%d) writing request: '%s'\n", errno, buf);
     } else {
         debug("wrote request: '%s'\n", buf);
     }
     return read_reply(s, req);
 }
 
 int
 main(int argc, char *argv[])
 {
     char *s;
     cachemgr_request *req;
 
     now = time(NULL);
 #ifdef _SQUID_MSWIN_
 
     Win32SockInit();
     atexit(Win32SockCleanup);
@@ -992,53 +996,57 @@
         *q++ = '\0';
 
         rfc1738_unescape(t);
 
         rfc1738_unescape(q);
 
         if (0 == strcasecmp(t, "server") && strlen(q))
             req->server = xstrdup(q);
         else if (0 == strcasecmp(t, "host") && strlen(q))
             req->hostname = xstrdup(q);
         else if (0 == strcasecmp(t, "port") && strlen(q))
             req->port = atoi(q);
         else if (0 == strcasecmp(t, "user_name") && strlen(q))
             req->user_name = xstrdup(q);
         else if (0 == strcasecmp(t, "passwd") && strlen(q))
             req->passwd = xstrdup(q);
         else if (0 == strcasecmp(t, "auth") && strlen(q))
             req->pub_auth = xstrdup(q), decode_pub_auth(req);
         else if (0 == strcasecmp(t, "operation"))
             req->action = xstrdup(q);
+        else if(0 == strcasecmp(t, "workers") && strlen(q))
+            req->workers = xstrdup(q);
+         else if(0 == strcasecmp(t, "processes") && strlen(q))
+            req->processes = xstrdup(q);
     }
 
     if (req->server && !req->hostname) {
         char *p;
         req->hostname = strtok(req->server, ":");
 
         if ((p = strtok(NULL, ":")))
             req->port = atoi(p);
     }
 
     make_pub_auth(req);
-    debug("cmgr: got req: host: '%s' port: %d uname: '%s' passwd: '%s' auth: '%s' oper: '%s'\n",
-          safe_str(req->hostname), req->port, safe_str(req->user_name), safe_str(req->passwd), safe_str(req->pub_auth), safe_str(req->action));
+    debug("cmgr: got req: host: '%s' port: %d uname: '%s' passwd: '%s' auth: '%s' oper: '%s' workers: '%s' processes: '%s'\n",
+          safe_str(req->hostname), req->port, safe_str(req->user_name), safe_str(req->passwd), safe_str(req->pub_auth), safe_str(req->action), safe_str(req->workers), safe_str(req->processes));
     return req;
 }
 
 
 /* Routines to support authentication */
 
 /*
  * Encodes auth info into a "public" form.
  * Currently no powerful encryption is used.
  */
 static void
 make_pub_auth(cachemgr_request * req)
 {
     static char buf[1024];
     safe_free(req->pub_auth);
     debug("cmgr: encoding for pub...\n");
 
     if (!req->passwd || !strlen(req->passwd))
         return;
 


