[squid-dev] [PATCH] PackableStream for cachemgr reports

Amos Jeffries squid3 at treenet.co.nz
Wed Aug 5 16:24:02 UTC 2015


Adds a PackableStream class which provides std::ostream semantics for
writing data to a cachemgr report.

FYI: This follows on from discussiosn back in 2011 regarding how to
restructure the cachemgr internal data storage for relay between workers
vs the report output formatting.

Current trunk 'improved' code uses FooActionData classes to store and
relay the report data internally, and classes implementing the Action
API to format the report for delivery.


For easy transition I have added an overload dump() method to the Action
class API. New FooAction child classes should override the
dump(ostream&) method instead of the StoreEntry one. Writing their data
to the stream.

Older actions overload the now-deprecated dump(StoreEntry*) method and
use some complicate nested function calls or share helper functions in
complex ways which make it hard to upgrade all at once. So for now the
dump(StoreEntry*) is made a stub that calls dump(ostream&) if not
overridden explicitly.

The CacheManager class still uses dump(StoreEntry*) to get the reply
payload from Action childs regardless of which dumper they implement.

The basic report types are converted as part of this to be the first
Actions using the stream output. The menu action in particular is
slightly polished to benefit from stream abilities.


Future steps along this path (in no particular order) are:
* convert old Action classes to new API
* refactor old C-code report generators to be Action classes
 - fixing display output syntax to minimal YAML as we go on both the
above. It mostly is already, but some reports have wrong syntax.
* update Actions to be hidden from the menu display
* update Actions to support menu name aliases
* update Action API to receive client desired format

Amos

-------------- next part --------------
=== modified file 'src/CacheManager.h'
--- src/CacheManager.h	2015-08-01 02:13:13 +0000
+++ src/CacheManager.h	2015-08-03 12:11:21 +0000
@@ -28,40 +28,40 @@
  */
 class CacheManager
 {
 public:
     typedef std::vector<Mgr::ActionProfilePointer> Menu;
 
     void registerProfile(char const * action, char const * desc,
                          OBJH * handler,
                          int pw_req_flag, int atomic);
     void registerProfile(char const * action, char const * desc,
                          Mgr::ClassActionCreationHandler *handler,
                          int pw_req_flag, int atomic);
     Mgr::ActionProfilePointer findAction(char const * action) const;
     Mgr::Action::Pointer createNamedAction(const char *actionName);
     Mgr::Action::Pointer createRequestedAction(const Mgr::ActionParams &);
     const Menu& menu() const { return menu_; }
 
     void Start(const Comm::ConnectionPointer &client, HttpRequest * request, StoreEntry * entry);
 
     static CacheManager* GetInstance();
-    const char *ActionProtection(const Mgr::ActionProfilePointer &profile);
+    const char *ActionProtection(const Mgr::ActionProfile &profile);
 
 protected:
     CacheManager() {} ///< use Instance() instead
 
     Mgr::CommandPointer ParseUrl(const char *url);
     void ParseHeaders(const HttpRequest * request, Mgr::ActionParams &params);
     int CheckPassword(const Mgr::Command &cmd);
     char *PasswdGet(Mgr::ActionPasswordList *, const char *);
 
     void registerProfile(const Mgr::ActionProfilePointer &profile);
 
     Menu menu_;
 
 private:
     static CacheManager* instance;
 };
 
 #endif /* SQUID_CACHEMANAGER_H */
 

=== modified file 'src/base/Makefile.am'
--- src/base/Makefile.am	2015-08-03 02:08:22 +0000
+++ src/base/Makefile.am	2015-08-04 15:09:03 +0000
@@ -11,28 +11,29 @@
 noinst_LTLIBRARIES = libbase.la
 
 libbase_la_SOURCES = \
 	AsyncCall.cc \
 	AsyncCall.h \
 	AsyncCbdataCalls.h \
 	AsyncJob.h \
 	AsyncJob.cc \
 	AsyncJobCalls.h \
 	AsyncCallQueue.cc \
 	AsyncCallQueue.h \
 	CbcPointer.h \
 	CbDataList.h \
 	CharacterSet.h \
 	CharacterSet.cc \
 	InstanceId.h \
 	Lock.h \
 	LookupTable.h \
 	LruMap.h \
 	Packable.h \
+	PackableStream.h \
 	RegexPattern.cc \
 	RegexPattern.h \
 	RunnersRegistry.cc \
 	RunnersRegistry.h \
 	Subscription.h \
 	TextException.cc \
 	TextException.h \
 	TidyPointer.h

=== added file 'src/base/PackableStream.h'
--- src/base/PackableStream.h	1970-01-01 00:00:00 +0000
+++ src/base/PackableStream.h	2015-08-03 12:06:45 +0000
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
+ *
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
+ */
+
+#ifndef SQUID_SRC_BASE_PACKABLESTREAM_H
+#define SQUID_SRC_BASE_PACKABLESTREAM_H
+
+#include "base/Packable.h"
+
+#include <iomanip>
+#include <ostream>
+
+/**
+ * A streambuf interface for writing to Packable objects.
+ * Typical use is via a PackableStream rather than direct manipulation.
+ */
+class PackableStreamBuf : public std::streambuf
+{
+public:
+    PackableStreamBuf(Packable *out) : outBuf(out) {}
+    ~PackableStreamBuf() {}
+
+protected:
+    /**
+     * flush the current buffer and the character that is overflowing
+     * to the Packable.
+     */
+    virtual int_type overflow(int_type aChar = traits_type::eof()) {
+        std::streamsize pending(pptr() - pbase());
+
+        if (pending && sync ())
+            return traits_type::eof();
+
+        if (aChar != traits_type::eof()) {
+            // NP: cast because GCC promotes int_type to 32-bit type
+            //     std::basic_streambuf<char>::int_type {aka int}
+            //     despite the definition with 8-bit type value.
+            char chars[1] = {char(aChar)};
+
+            if (aChar != traits_type::eof())
+                outBuf->append(chars, 1);
+        }
+
+        pbump (-pending);  // Reset pptr().
+        return aChar;
+    }
+
+    /* push the buffer to the store */
+    virtual int sync() {
+        std::streamsize pending(pptr() - pbase());
+        if (pending)
+            outBuf->append(pbase(), pending);
+        return 0;
+    }
+
+    /* write multiple characters to the store entry
+     * - this is an optimisation method.
+     */
+    virtual std::streamsize xsputn(const char * chars, std::streamsize number) {
+        if (number)
+            outBuf->append(chars, number);
+        return number;
+    }
+
+private:
+    Packable *outBuf;
+};
+
+class PackableStream : public std::ostream
+{
+public:
+    /* create a stream for writing text etc into theEntry */
+    // See http://www.codecomments.com/archive292-2005-2-396222.html
+    PackableStream(Packable *entry): std::ostream(0), theBuffer(entry) {
+        rdbuf(&theBuffer); // set the buffer to now-initialized theBuffer
+        clear(); //clear badbit set by calling init(0)
+    }
+
+private:
+    PackableStreamBuf theBuffer;
+};
+
+#endif /* SQUID_SRC_BASE_PACKABLESTREAM_H */
+

=== modified file 'src/cache_manager.cc'
--- src/cache_manager.cc	2015-01-13 07:25:36 +0000
+++ src/cache_manager.cc	2015-08-03 12:12:09 +0000
@@ -191,41 +191,41 @@
         /*
          * emx's sscanf insists of returning 2 because it sets request
          * to null
          */
         if (strncmp("cache_object://",url,15)==0)
             xstrncpy(request, "menu", MAX_URL);
         else
             xstrncpy(request, "index", MAX_URL);
     }
 #endif
 
     debugs(16, 3, HERE << "MGR request: t=" << t << ", host='" << host << "', request='" << request << "', pos=" << pos <<
            ", password='" << password << "', params='" << params << "'");
 
     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);
+    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 = password;
     cmd->params.actionName = request;
     return cmd;
 }
 
 /// \ingroup CacheManagerInternal
 /*
  \ingroup CacheManagerInternal
  * Decodes the headers needed to perform user authentication and fills
@@ -403,47 +403,46 @@
         return;
     }
 
     if (UsingSmp() && IamWorkerProcess()) {
         // is client the right connection to pass here?
         AsyncJob::Start(new Mgr::Forwarder(client, cmd->params, request, entry));
         return;
     }
 
     Mgr::Action::Pointer action = cmd->profile->creator->create(cmd);
     Must(action != NULL);
     action->run(entry, true);
 }
 
 /*
  \ingroup CacheManagerInternal
  * Renders the protection level text for an action.
  * Also doubles as a check for the protection level.
  */
 const char *
-CacheManager::ActionProtection(const Mgr::ActionProfile::Pointer &profile)
+CacheManager::ActionProtection(const Mgr::ActionProfile &profile)
 {
-    assert(profile != NULL);
-    const char *pwd = PasswdGet(Config.passwd_list, profile->name);
+    const char *pwd = PasswdGet(Config.passwd_list, profile.name);
 
     if (!pwd)
-        return profile->isPwReq ? "hidden" : "public";
+        return profile.isPwReq ? "hidden" : "public";
 
     if (!strcmp(pwd, "disable"))
         return "disabled";
 
     if (strcmp(pwd, "none") == 0)
         return "public";
 
     return "protected";
 }
 
 /*
  * \ingroup CacheManagerInternal
  * gets from the global Config the password the user would need to supply
  * for the action she queried
  */
 char *
 CacheManager::PasswdGet(Mgr::ActionPasswordList * a, const char *action)
 {
     wordlist *w;
 

=== modified file 'src/mgr/Action.cc'
--- src/mgr/Action.cc	2015-01-13 07:25:36 +0000
+++ src/mgr/Action.cc	2015-08-04 15:13:44 +0000
@@ -1,54 +1,63 @@
 /*
  * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 /* DEBUG: section 16    Cache Manager API */
 
 #include "squid.h"
+#include "base/PackableStream.h"
 #include "comm/Connection.h"
 #include "HttpReply.h"
 #include "ipc/Port.h"
 #include "mgr/Action.h"
 #include "mgr/ActionCreator.h"
 #include "mgr/ActionParams.h"
 #include "mgr/ActionProfile.h"
 #include "mgr/Command.h"
 #include "mgr/Request.h"
 #include "mgr/Response.h"
 #include "SquidTime.h"
 #include "Store.h"
 
 Mgr::Action::Action(const Command::Pointer &aCmd): cmd(aCmd)
 {
     Must(cmd != NULL);
     Must(cmd->profile != NULL);
 }
 
 Mgr::Action::~Action()
 {
 }
 
+void
+Mgr::Action::dump(StoreEntry *e)
+{
+    PackableStream p(e);
+    dump(p);
+    p.flush();
+}
+
 const Mgr::Command &
 Mgr::Action::command() const
 {
     Must(cmd != NULL);
     return *cmd;
 }
 
 bool
 Mgr::Action::atomic() const
 {
     return command().profile->isAtomic;
 }
 
 const char*
 Mgr::Action::name() const
 {
     return command().profile->name;
 }
 
 StoreEntry*

=== modified file 'src/mgr/Action.h'
--- src/mgr/Action.h	2015-01-13 07:25:36 +0000
+++ src/mgr/Action.h	2015-08-04 15:12:07 +0000
@@ -1,36 +1,38 @@
 /*
  * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 /* DEBUG: section 16    Cache Manager API */
 
 #ifndef SQUID_MGR_ACTION_H
 #define SQUID_MGR_ACTION_H
 
 #include "ipc/forward.h"
 #include "mgr/forward.h"
 
+#include <iosfwd>
+
 class StoreEntry;
 
 namespace Mgr
 {
 
 /// Base API for organizing the processing of a compiled cache manager command.
 /// Not a job because all methods are synchronous (but they may start jobs).
 class Action: public RefCountable
 {
 public:
     typedef RefCount<Action> Pointer;
 
 public:
     Action(const CommandPointer &aCmd);
     virtual ~Action();
 
     /* for local Cache Manager use */
 
     /// collect + fillEntry: collect local information and fill the store entry
     void run(StoreEntry *entry, bool writeHttpHeader);
@@ -63,34 +65,38 @@
     /// combined data should be written at the end of the coordinated response
     virtual bool aggregatable() const { return true; } // most kid classes are
 
     bool atomic() const; ///< dump() call writes everything before returning
     const char *name() const; ///< label as seen in the cache manager menu
     const Command &command() const; ///< the cause of this action
 
     StoreEntry *createStoreEntry() const; ///< creates store entry from params
 
     ///< Content-Type: header value for this report
     virtual const char *contentType() const {return "text/plain;charset=utf-8";}
 
 protected:
     /// calculate and keep local action-specific information
     virtual void collect() {}
 
     /** start writing action-specific info to Store entry;
      * may collect info during dump, especially if collect() did nothing
      * non-atomic() actions may continue writing asynchronously after returning
      */
-    virtual void dump(StoreEntry *) {}
+    virtual void dump(std::ostream &) {}
+
+    // magic wrapper.
+    // old code will override this instead of the new ostream dumper.
+    virtual void dump(StoreEntry *e);
 
 private:
     const CommandPointer cmd; ///< the command that caused this action
 
 private:
     Action(const Action &); // not implemented
     Action &operator= (const Action &); // not implemented
 };
 
 } // namespace Mgr
 
 #endif /* SQUID_MGR_ACTION_H */
 

=== modified file 'src/mgr/BasicActions.cc'
--- src/mgr/BasicActions.cc	2015-01-13 07:25:36 +0000
+++ src/mgr/BasicActions.cc	2015-08-03 12:09:35 +0000
@@ -14,146 +14,146 @@
 #include "mgr/ActionCreator.h"
 #include "mgr/ActionProfile.h"
 #include "mgr/BasicActions.h"
 #include "mgr/Registration.h"
 #include "protos.h"
 #include "SquidConfig.h"
 #include "Store.h"
 
 Mgr::IndexAction::Pointer
 Mgr::IndexAction::Create(const Command::Pointer &cmd)
 {
     return new IndexAction(cmd);
 }
 
 Mgr::IndexAction::IndexAction(const Command::Pointer &aCmd): Action(aCmd)
 {
     debugs(16, 5, HERE);
 }
 
 void
-Mgr::IndexAction::dump(StoreEntry *)
+Mgr::IndexAction::dump(std::ostream &)
 {
     debugs(16, 5, HERE);
 }
 
 Mgr::MenuAction::Pointer
 Mgr::MenuAction::Create(const Command::Pointer &cmd)
 {
     return new MenuAction(cmd);
 }
 
 Mgr::MenuAction::MenuAction(const Command::Pointer &aCmd): Action(aCmd)
 {
     debugs(16, 5, HERE);
 }
 
 void
-Mgr::MenuAction::dump(StoreEntry* entry)
+Mgr::MenuAction::dump(std::ostream &p)
 {
     debugs(16, 5, HERE);
-    Must(entry != NULL);
 
-    typedef CacheManager::Menu::const_iterator Iterator;
-    const CacheManager::Menu& menu = CacheManager::GetInstance()->menu();
-
-    for (Iterator a = menu.begin(); a != menu.end(); ++a) {
-        storeAppendPrintf(entry, " %-22s\t%-32s\t%s\n",
-                          (*a)->name, (*a)->desc,
-                          CacheManager::GetInstance()->ActionProtection(*a));
+    p.fill(' '); // space pad the menu fields for readability
+    p.setf(std::ios::left);
+    for (auto &a : CacheManager::GetInstance()->menu()) {
+        p << '\t' // menu is a table
+          << std::setw(22) << a->name << '\t'
+          << std::setw(32) << a->desc << '\t'
+          << std::setw(8) << CacheManager::GetInstance()->ActionProtection(*a)
+          << '\n';
     }
+    p.unsetf(std::ios::left);
 }
 
 Mgr::ShutdownAction::Pointer
 Mgr::ShutdownAction::Create(const Command::Pointer &cmd)
 {
     return new ShutdownAction(cmd);
 }
 
 Mgr::ShutdownAction::ShutdownAction(const Command::Pointer &aCmd): Action(aCmd)
 {
     debugs(16, 5, HERE);
 }
 
 void
-Mgr::ShutdownAction::dump(StoreEntry *)
+Mgr::ShutdownAction::dump(std::ostream &)
 {
     debugs(16, DBG_CRITICAL, "Shutdown by Cache Manager command.");
     shut_down(SIGTERM);
 }
 
 Mgr::ReconfigureAction::Pointer
 Mgr::ReconfigureAction::Create(const Command::Pointer &cmd)
 {
     return new ReconfigureAction(cmd);
 }
 
 Mgr::ReconfigureAction::ReconfigureAction(const Command::Pointer &aCmd):
     Action(aCmd)
 {
     debugs(16, 5, HERE);
 }
 
 void
-Mgr::ReconfigureAction::dump(StoreEntry* entry)
+Mgr::ReconfigureAction::dump(std::ostream &p)
 {
     debugs(16, DBG_IMPORTANT, "Reconfigure by Cache Manager command.");
-    storeAppendPrintf(entry, "Reconfiguring Squid Process ....");
+    p << "Reconfiguring Squid Process ....";
     reconfigure(SIGHUP);
 }
 
 Mgr::RotateAction::Pointer
 Mgr::RotateAction::Create(const Command::Pointer &cmd)
 {
     return new RotateAction(cmd);
 }
 
 Mgr::RotateAction::RotateAction(const Command::Pointer &aCmd): Action(aCmd)
 {
     debugs(16, 5, HERE);
 }
 
 void
-Mgr::RotateAction::dump(StoreEntry* entry)
+Mgr::RotateAction::dump(std::ostream &p)
 {
     debugs(16, DBG_IMPORTANT, "Rotate Logs by Cache Manager command.");
-    storeAppendPrintf(entry, "Rotating Squid Process Logs ....");
+    p << "Rotating Squid Process Logs ....";
 #if defined(_SQUID_LINUX_THREADS_)
     rotate_logs(SIGQUIT);
 #else
     rotate_logs(SIGUSR1);
 #endif
 }
 
 Mgr::OfflineToggleAction::Pointer
 Mgr::OfflineToggleAction::Create(const Command::Pointer &cmd)
 {
     return new OfflineToggleAction(cmd);
 }
 
 Mgr::OfflineToggleAction::OfflineToggleAction(const Command::Pointer &aCmd):
     Action(aCmd)
 {
     debugs(16, 5, HERE);
 }
 
 void
-Mgr::OfflineToggleAction::dump(StoreEntry* entry)
+Mgr::OfflineToggleAction::dump(std::ostream &p)
 {
     Config.onoff.offline = !Config.onoff.offline;
     debugs(16, DBG_IMPORTANT, "offline_mode now " << (Config.onoff.offline ? "ON" : "OFF") << " by Cache Manager request.");
 
-    storeAppendPrintf(entry, "offline_mode is now %s\n",
-                      Config.onoff.offline ? "ON" : "OFF");
+    p << "offline_mode is now " << (Config.onoff.offline ? "ON" : "OFF");
 }
 
 void
 Mgr::RegisterBasics()
 {
     RegisterAction("index", "Cache Manager Interface", &Mgr::IndexAction::Create, 0, 1);
     RegisterAction("menu", "Cache Manager Menu", &Mgr::MenuAction::Create, 0, 1);
     RegisterAction("offline_toggle", "Toggle offline_mode setting", &Mgr::OfflineToggleAction::Create, 1, 1);
     RegisterAction("shutdown", "Shut Down the Squid Process", &Mgr::ShutdownAction::Create, 1, 1);
     RegisterAction("reconfigure", "Reconfigure Squid", &Mgr::ReconfigureAction::Create, 1, 1);
     RegisterAction("rotate", "Rotate Squid Logs", &Mgr::RotateAction::Create, 1, 1);
 }
 

=== modified file 'src/mgr/BasicActions.h'
--- src/mgr/BasicActions.h	2015-01-13 07:25:36 +0000
+++ src/mgr/BasicActions.h	2015-08-03 12:04:57 +0000
@@ -10,93 +10,93 @@
 
 #ifndef SQUID_MGR_BASIC_ACTIONS_H
 #define SQUID_MGR_BASIC_ACTIONS_H
 
 #include "mgr/Action.h"
 
 /* a collection of simple, mostly stateless actions */
 
 namespace Mgr
 {
 
 /// A dummy action placeholder for the no-action requests
 /// a templated Cache Manager index ('home') page.
 /// Display output is produced directly by the receiving worker
 /// without invoking the co-ordinator or action Job.
 class IndexAction: public Action
 {
 public:
     static Pointer Create(const CommandPointer &cmd);
     /* Action API */
-    virtual void dump(StoreEntry *entry);
+    virtual void dump(std::ostream &);
 
 protected:
     IndexAction(const CommandPointer &cmd);
 };
 
 /// returns available Cache Manager actions and their access requirements
 class MenuAction: public Action
 {
 public:
     static Pointer Create(const CommandPointer &cmd);
     /* Action API */
-    virtual void dump(StoreEntry *entry);
+    virtual void dump(std::ostream &);
 
 protected:
     MenuAction(const CommandPointer &cmd);
 };
 
 /// shuts Squid down
 class ShutdownAction: public Action
 {
 public:
     static Pointer Create(const CommandPointer &cmd);
     /* Action API */
-    virtual void dump(StoreEntry *entry);
+    virtual void dump(std::ostream &);
 
 protected:
     ShutdownAction(const CommandPointer &cmd);
 };
 
 /// reconfigures Squid
 class ReconfigureAction: public Action
 {
 public:
     static Pointer Create(const CommandPointer &cmd);
     /* Action API */
-    virtual void dump(StoreEntry *entry);
+    virtual void dump(std::ostream &);
 
 protected:
     ReconfigureAction(const CommandPointer &cmd);
 };
 
 /// starts log rotation
 class RotateAction: public Action
 {
 public:
     static Pointer Create(const CommandPointer &cmd);
     /* Action API */
-    virtual void dump(StoreEntry *entry);
+    virtual void dump(std::ostream &);
 
 protected:
     RotateAction(const CommandPointer &cmd);
 };
 
 /// changes offline mode
 class OfflineToggleAction: public Action
 {
 public:
     static Pointer Create(const CommandPointer &cmd);
     /* Action API */
-    virtual void dump(StoreEntry *entry);
+    virtual void dump(std::ostream &);
 
 protected:
     OfflineToggleAction(const CommandPointer &cmd);
 };
 
 /// Registeres profiles for the actions above; \todo move elsewhere?
 void RegisterBasics();
 
 } // namespace Mgr
 
 #endif /* SQUID_MGR_BASIC_ACTIONS_H */
 



More information about the squid-dev mailing list