You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by am...@apache.org on 2018/05/16 16:48:20 UTC
[trafficserver] branch master updated: BufferWriter: Formatting
tweaks, updated documentation.
This is an automated email from the ASF dual-hosted git repository.
amc pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
The following commit(s) were added to refs/heads/master by this push:
new 172888f BufferWriter: Formatting tweaks, updated documentation.
172888f is described below
commit 172888ff63b2199d5ca1269c37e2d9522d44600d
Author: Alan M. Carroll <am...@apache.org>
AuthorDate: Thu Apr 5 21:26:18 2018 -0500
BufferWriter: Formatting tweaks, updated documentation.
---
CMakeLists.txt | 1 +
doc/conf.py | 10 +-
doc/developer-guide/api/types/CoreTypes.en.rst | 8 +
.../internal-libraries/buffer-writer.en.rst | 847 +++++++++++++++++----
iocore/eventsystem/IOBuffer.cc | 74 ++
iocore/eventsystem/I_MIOBufferWriter.h | 72 +-
iocore/eventsystem/Makefile.am | 9 +-
.../eventsystem/unit-tests/test_MIOBufferWriter.cc | 110 +--
lib/ts/BufferWriter.h | 227 ++++--
lib/ts/BufferWriterFormat.cc | 124 ++-
lib/ts/BufferWriterForward.h | 61 +-
lib/ts/Makefile.am | 2 +
lib/ts/bwf_std_format.h | 36 +
lib/ts/ink_inet.cc | 250 ++++++
lib/ts/ink_inet.h | 13 +
lib/ts/unit-tests/test_BufferWriter.cc | 12 +-
lib/ts/unit-tests/test_BufferWriterFormat.cc | 58 +-
lib/ts/unit-tests/test_ink_inet.cc | 102 +++
proxy/http/HttpDebugNames.h | 41 +
proxy/http/HttpProxyServerMain.cc | 22 +
20 files changed, 1686 insertions(+), 393 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 523b643..f8cd5b4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1150,6 +1150,7 @@ add_library(libtsutil SHARED
lib/ts/BaseLogFile.h
lib/ts/Bitops.cc
lib/ts/Bitops.h
+ lib/ts/bwf_std_format.h
lib/ts/BufferWriter.h
lib/ts/BufferWriterForward.h
lib/ts/BufferWriterFormat.cc
diff --git a/doc/conf.py b/doc/conf.py
index 55e5f5c..322769d 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -169,7 +169,15 @@ pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
-nitpicky = 1
+nitpicky = True
+nitpick_ignore = [ ('cpp:typeOrConcept', 'std')
+ , ('cpp:typeOrConcept', 'std::shared_ptr')
+ , ('cpp:typeOrConcept', 'std::ostream')
+ , ('cpp:typeOrConcept', 'std::string')
+ , ('cpp:typeOrConcept', 'std::tuple')
+ , ('cpp:typeOrConcept', 'V') # template arguments which should be matched but aren't.
+ , ('cpp:typeOrConcept', 'Args')
+ ]
# Autolink issue references.
# See Customizing the Parser in the docutils.parsers.rst module.
diff --git a/doc/developer-guide/api/types/CoreTypes.en.rst b/doc/developer-guide/api/types/CoreTypes.en.rst
index 9badf57..613bc51 100644
--- a/doc/developer-guide/api/types/CoreTypes.en.rst
+++ b/doc/developer-guide/api/types/CoreTypes.en.rst
@@ -34,3 +34,11 @@ Description
These types are provided by the compiler ("built-in") or from a required operating system, POSIX, or package header.
.. cpp:type:: uint24_t
+
+.. cpp:class:: IpEndpoint
+
+ A wrapper for :code:`sockaddr` types.
+
+.. cpp:class:: IpAddr
+
+ Storage for an IP address.
diff --git a/doc/developer-guide/internal-libraries/buffer-writer.en.rst b/doc/developer-guide/internal-libraries/buffer-writer.en.rst
index 7e05547..aa76fa0 100644
--- a/doc/developer-guide/internal-libraries/buffer-writer.en.rst
+++ b/doc/developer-guide/internal-libraries/buffer-writer.en.rst
@@ -16,7 +16,6 @@
under the License.
.. include:: ../../common.defs
-
.. highlight:: cpp
.. default-domain:: cpp
@@ -30,21 +29,87 @@ Synopsis
.. code-block:: cpp
- #include <ts/BufferWriter.h>
+ #include <ts/BufferWriterForward.h> // Custom formatter support only.
+ #include <ts/BufferWriter.h> // General usage.
Description
+++++++++++
-:class:`BufferWriter` is designed to make writing text to a buffer fast, convenient, and safe. It is
-easier and less error-prone than using a combination of :code:`sprintf` and :code:`memcpy` as is
-done in many places in the code.. A :class:`BufferWriter` can have a size and will prevent writing
-past the end, while tracking the theoretical output to enable buffer resizing after the fact. This
-also lets a :class:`BufferWriter` instance write into the middle of a larger buffer, making nested
-output logic easy to build.
+:class:`BufferWriter` is intended to increase code reliability and reduce complexity in the common
+circumstance of generating formatted output strings in fixed buffers. Current usage is a mixture of
+:code:`snprintf` and :code:`memcpy` which provides a large scope for errors and verbose code to
+check for buffer overruns. The goal is to provide a wrapper over buffer size tracking to make such
+code simpler and less vulnerable to implementation error.
+
+:class:`BufferWriter` itself is an abstract class to describe the base interface to wrappers for
+various types of output buffers. As a common example, :class:`FixedBufferWriter` is a subclass
+designed to wrap a fixed size buffer. :class:`FixedBufferWriter` is constructed by passing it a
+buffer and a size, which it then tracks as data is written. Writing past the end of the buffer is
+clipped to prevent overruns.
+
+Consider current code that looks like this.
+
+.. code-block:: cpp
+
+ char buff[1024];
+ char * ptr = buff;
+ size_t len = sizeof(buff);
+ //...
+ if (len > 0) {
+ auto n = std::min(len, thing1_len);
+ memcpy(ptr, thing1, n);
+ len -= n;
+ }
+ if (len > 0) {
+ auto n = std::min(len, thing2_len);
+ memcpy(ptr, thing2, n);
+ len -= n;
+ }
+ if (len > 0) {
+ auto n = std::min(len, thing3_len);
+ memcpy(ptr, thing3, n);
+ len -= n;
+ }
+
+This is changed to
+
+.. code-block:: cpp
+
+ char buff[1024];
+ ts::FixedBufferWriter bw(buff, sizeof(buff));
+ //...
+ bw.write(thing1, thing1_len);
+ bw.write(thing2, thing2_len);
+ bw.write(thing3, thing3_len);
+
+The remaining length is updated every time and checked every time. A series of checks, calls to
+:code:`memcpy`, and size updates become a simple series of calls to :func:`BufferWriter::write`.
+
+For other types of interaction, :class:`FixedBufferWriter` provides access to the unused buffer via
+:func:`BufferWriter::auxBuffer` and :func:`BufferWriter::remaining`. This makes it possible to easily
+use :code:`snprintf`, given that :code:`snprint` returns the number of bytes written.
+:func:`BufferWriter::fill` is used to indicate how much of the unused buffer was used. Therefore
+something like (riffing off the previous example)::
+
+ if (len > 0) {
+ len -= snprintf(ptr, len, "format string", args...);
+ }
+
+becomes::
-The header files are divided in to two variants. ``BufferWriter.h`` provides the basic capabilities
-of buffer output control. ``BufferWriterFormat.h`` provides formatted output mechanisms, primarily
-the implementation and ancillary classes for :func:`BufferWriter::print`.
+ bw.fill(snprintf(bw.auxBuffer(), bw.remaining(),
+ "format string", args...));
+
+By hiding the length tracking and checking, the result is a simple linear sequence of output chunks,
+making the logic much eaier to follow.
+
+Usage
++++++
+
+The header files are divided in to two variants. :ts:git:`lib/ts/BufferWriter.h` provides the basic
+capabilities of buffer output control. :ts:git:`lib/ts/BufferWriterFormat.h` provides the basic
+:ref:`formatted output mechanisms <bw-formatting>`, primarily the implementation and ancillary
+classes for :class:`BWFSpec` which is used to build formatters.
:class:`BufferWriter` is an abstract base class, in the style of :code:`std::ostream`. There are
several subclasses for various use cases. When passing around this is the common type.
@@ -55,22 +120,22 @@ external to the function or already exists.
:class:`LocalBufferWriter` is a templated class whose template argument is the size of an internal
buffer. This is useful when the buffer is local to a function and the results will be transferred
-from the buffer to other storage after the output is assembled. Rather than having code like
-
-.. code-block:: cpp
+from the buffer to other storage after the output is assembled. Rather than having code like::
char buff[1024];
- ts::FixedBufferWriter w(buff, sizeof(buff));
-
-can be more compactly and robustly done as:
+ ts::FixedBufferWriter bw(buff, sizeof(buff));
-.. code-block:: cpp
+it can be written more compactly as::
- ts::LocalBufferWriter<1024> w;
+ ts::LocalBufferWriter<1024> bw;
-In many cases using :class:`LocalBufferWriter` this is the only place the size of the buffer needs
-to be specified, and therefore can simply be a constant without the overhead of defining a size to
-maintain consistency.
+In many cases, when using :class:`LocalBufferWriter` this is the only place the size of the buffer
+needs to be specified and therefore can simply be a constant without the overhead of defining a size
+to maintain consistency. The choice between :class:`LocalBufferWriter` and :class:`FixedBufferWriter`
+comes down to the owner of the buffer - the former has its own buffer while the latter operates on
+a buffer owned by some other object. Therefore if the buffer is declared locally, use
+:class:`LocalBufferWriter` and if the buffer is recevied from an external source (such as via a
+function parameter) use :class:`FixedBufferWriter`.
Writing
-------
@@ -86,25 +151,22 @@ There are also stream operators in the style of C++ stream I/O. The basic templa
template < typename T > ts::BufferWriter& operator << (ts::BufferWriter& w, T const& t);
-Several basic types are overloaded and it is easy to extend to additional types. For instance, to make :code:`ts::TextView` work with :class:`BufferWriter`, the code would be
-
-.. code-block:: cpp
-
- ts::BufferWriter & operator << (ts::BufferWriter & w, TextView const & sv) {
- w.write(sv.data(), sv.size());
- return w;
- }
+The stream operators are provided as a convenience, the primary mechanism for formatted output is
+via overloading the :func:`bwformat` function. Except for a limited set of cases the stream operators
+are implemented by calling :func:`bwformat` with the Buffer Writer, the argument, and a default
+format specification.
Reading
-------
-The data in the buffer can be extracted using :func:`BufferWriter::data`. This and
+Data in the buffer can be extracted using :func:`BufferWriter::data`. This and
:func:`BufferWriter::size` return a pointer to the start of the buffer and the amount of data
-written to the buffer. This is very similar to :func:`BufferWriter::view` which returns a
+written to the buffer. This is effectively the same as :func:`BufferWriter::view` which returns a
:class:`string_view` which covers the output data. Calling :func:`BufferWriter::error` will indicate
-if more data than space available was written. :func:`BufferWriter::extent` returns the amount of
-data written to the :class:`BufferWriter`. This can be used in a two pass style with a null / size 0
-buffer to determine the buffer size required for the full output.
+if more data than space available was written (i.e. the buffer would have been overrun).
+:func:`BufferWriter::extent` returns the amount of data written to the :class:`BufferWriter`. This
+can be used in a two pass style with a null / size 0 buffer to determine the buffer size required
+for the full output.
Advanced
--------
@@ -121,7 +183,7 @@ delimiter.
:func:`BufferWriter::remaining` returns the amount of buffer space not yet consumed.
:func:`BufferWriter::auxBuffer` returns a pointer to the first byte of the buffer not yet used. This
-is useful to do speculative output, or do bounded output in a manner similar to use
+is useful to do speculative output, or do bounded output in a manner similar to using
:func:`BufferWriter::clip` and :func:`BufferWriter::extend`. A new :class:`BufferWriter` instance
can be constructed with
@@ -129,6 +191,10 @@ can be constructed with
ts::FixedBufferWriter subw(w.auxBuffer(), w.remaining());
+or as a convenience ::
+
+ ts::FixedBuffer subw{w.auxBuffer()};
+
Output can be written to :arg:`subw`. If successful, then :code:`w.fill(subw.size())` will add that
output to the main buffer. Depending on the purpose, :code:`w.fill(subw.extent())` can be used -
this will track the attempted output if sizing is important. Note that space for any terminal
@@ -137,14 +203,12 @@ underrun as the argument is an unsigned type.
If there is an error then :arg:`subw` can be ignored and some suitable error output written to
:arg:`w` instead. A common use case is to verify there is sufficient space in the buffer and create
-a "not enough space" message if not. E.g.
-
-.. code-block:: cpp
+a "not enough space" message if not. E.g. ::
- ts::FixedBufferWriter subw(w.auxBuffer(), w.remaining());
+ ts::FixedBufferWriter subw{w.auxWriter()};
this->write_some_output(subw);
if (!subw.error()) w.fill(subw.size());
- else w << "Insufficient space"_sv;
+ else w.write("Insufficient space"_sv);
Examples
++++++++
@@ -179,16 +243,550 @@ becomes
// ...
- w << " [";
+ w.write(" ["_sv);
if (s->txn_conf->insert_request_via_string > 2) { // Highest verbosity
- w << incoming_via;
+ w.write(incoming_via);
} else {
- w << ts::string_view{incoming_via + VIA_CLIENT, VIA_SERVER - VIA_CLIENT};
+ w.write(ts::string_view{incoming_via + VIA_CLIENT, VIA_SERVER - VIA_CLIENT});
}
- w << ']';
+ w.write(']');
+
+There will be no overrun on the memory buffer in :arg:`w`, in strong contrast to the original code.
+This can be done better, as ::
+
+ if (w.remaining() >= 3) {
+ w.clip(1).write(" ["_sv);
+ if (s->txn_conf->insert_request_via_string > 2) { // Highest verbosity
+ w.write(incoming_via);
+ } else {
+ w.write(ts::string_view{incoming_via + VIA_CLIENT, VIA_SERVER - VIA_CLIENT});
+ }
+ w.extend(1).write(']');
+ }
+
+This has the result that the terminal bracket will always be present which is very much appreciated
+by code that parses the resulting log file.
+
+.. _bw-formatting:
+
+Formatted Output
+++++++++++++++++
+
+The base :class:`BufferWriter` was made to provide memory safety for formatted output. Support for
+formmatted output was made to provide *type* safety. The implementation deduces the types of the
+arguments to be formatted and handles them in a type specific and safe way.
+
+The formatting style is of the "prefix" or "printf" style - the format is specified first and then
+all the arguments. This contrasts to the "infix" or "streaming" style where formatting, literals,
+and argument are intermixed in the order of output. There are various arguments for both styles but
+conversations within the |TS| community indicated a clear preference for the prefix style. Therefore
+formatted out consists of a format string, containing *formats*, which are replaced during output
+with the values of arguments to the print function.
+
+The primary use case for formatting is formatted output to fixed buffers. This is by far the
+dominant style of output in |TS| and during the design phase I was told any performance loss must be
+minimal. While work has and will be done to extend :class:`BufferWriter` to operate on non-fixed
+buffers, such use is secondary to operating directly on memory.
+
+.. important::
+
+ The overriding design goal is to provide the type specific formatting and flexibility of C++
+ stream operators with the performance of :code:`snprintf` and :code:`memcpy`.
+
+This will preserve the general style of output in |TS| while still reaping the benefits of type safe
+formatting with little to no performance cost.
+
+Type safe formatting has two major benefits -
+
+* No mismatch between the format specifier and the argument. Although some modern compilers do
+ better at catching this at run time, there is still risk (especially with non-constant format
+ strings) and divergence between operating systems such that there is no `universally correct
+ choice <https://github.com/apache/trafficserver/pull/3476/files>`__. In addition the number of
+ arguments can be verified to be correct which is often useful.
+
+* Formatting can be customized per type or even per partial type (e.g. :code:`T*` for generic
+ :code:`T`). This enables embedding common formatting work in the format system once, rather than
+ duplicating it in many places (e.g. converting enum values to names). This makes it easier for
+ developers to make useful error messages. See :ref:`this example <bwf-http-debug-name-example>`
+ for more detail.
+
+As a result of these benefits there has been other work on similar projects, to replace
+:code:`printf` a better mechanism. Unfortunately most of these are rather project specific and don't
+suit the use case in |TS|. The two best options, `Boost.Format
+<https://www.boost.org/doc/libs/1_64_0/libs/format/>`__ and `fmt <https://github.com/fmtlib/fmt>`__,
+while good, are also not quite close enough to outweight the benefits of a version specifically
+tuned for |TS|. ``Boost.Format`` is not acceptable because of the Boost footprint. ``fmt`` has the
+problem of depending on C++ stream operators and therefore not having the required level of
+performance or memory characteristics. Its main benefit, of reusing stream operators, doesn't apply
+to |TS| because of the nigh non-existence of such operators. The possibility of using C++ stream
+operators was investigated but changing those to use pre-existing buffers not allocated internally
+was very difficult, judged worse than building a relatively simple implementation from scratch. The
+actual core implementation of formatted output for :class:`BufferWriter` is not very large - most of
+the overall work will be writing formatters, work which would need to be done in any case but in
+contrast to current practice, only done once.
+
+:class:`BufferWriter` supports formatting output in a style similar to Python formatting via
+:func:`BufferWriter::print`. Looking at the other versions of work in this area, almost all of them
+have gone with this style. Boost.Format also takes basically this same approach, just using
+different paired delimiters. |TS| contains increasing amounts of native Python code which means many
+|TS| developers will already be familiar (or should become familiar) with this style of formatting.
+While not *exactly* the same at the Python version, BWF (:class:`BufferWriter` Formatting) tries to
+be as similar as language and internal needs allow.
+
+As noted previously and in the Python and even :code:`printf` way, a format string consists of
+literal text in which formats are embedded. Each format marks a place where formatted data of
+an argument will be placed, along with argument specific formatting. The format is divided in to
+three parts, separated by colons.
+
+While this seems a bit complex, all of it is optional. If default output is acceptable, then BWF
+will work with just the format ``{}``. In a sense, ``{}`` serves the same function for output as
+:code:`auto` does for programming - the compiler knows the type, it should be able to do something
+reasonable without the programmer needing to be explicit.
+
+.. productionList:: Format
+ format: "{" [name] [":" [specifier] [":" extension]] "}"
+ name: index | ICHAR+
+ index: non-negative integer
+ extension: ICHAR*
+ ICHAR: a printable ASCII character except for '{', '}', ':'
+
+:token:`name`
+ The :token:`name` of the argument to use. This can be a non-negative integer in which case it is
+ the zero based index of the argument to the method call. E.g. ``{0}`` means the first argument
+ and ``{2}`` is the third argument after the format.
+
+ ``bw.print("{0} {1}", 'a', 'b')`` => ``a b``
+
+ ``bw.print("{1} {0}", 'a', 'b')`` => ``b a``
+
+ The :token:`name` can be omitted in which case it is treated as an index in parallel to the
+ position in the format string. Only the position in the format string matters, not what names
+ other format elements may have used.
+
+ ``bw.print("{0} {2} {}", 'a', 'b', 'c')`` => ``a c c``
+
+ ``bw.print("{0} {2} {2}", 'a', 'b', 'c')`` => ``a c c``
+
+ Note that an argument can be printed more than once if the name is used more than once.
+
+ ``bw.print("{0} {} {0}", 'a', 'b')`` => ``a b a``
+
+ ``bw.print("{0} {1} {0}", 'a', 'b')`` => ``a b a``
+
+ Alphanumeric names refer to values in a global table. These will be described in more detail
+ someday. Such names, however, do not count in terms of default argument indexing.
+
+:token:`specifier`
+ Basic formatting control.
+
+ .. productionList:: specifier
+ specifier: [[fill]align][sign]["#"]["0"][[min][.precision][,max][type]]
+ fill: fill-char | URI-char
+ URI-char: "%" hex-digit hex-digit
+ fill-char: printable character except "{", "}", ":", "%"
+ align: "<" | ">" | "=" | "^"
+ sign: "+" | "-" | " "
+ min: non-negative integer
+ precision: positive integer
+ max: non-negative integer
+ type: type: "g" | "s" | "S" | "x" | "X" | "d" | "o" | "b" | "B" | "p" | "P"
+ hex-digit: "0" .. "9" | "a" .. "f" | "A" .. "F"
+
+ The output is placed in a field that is at least :token:`min` wide and no more than :token:`max` wide. If
+ the output is less than :token:`min` then
+
+ * The :token:`fill` character is used for the extra space required. This can be an explicit
+ character or a URI encoded one (to allow otherwise reserved characters).
+
+ * The output is shifted according to the :token:`align`.
+
+ <
+ Align to the left, fill to the right.
+
+ >
+ Align to the right, fill to the left.
+
+ ^
+ Align in the middle, fill to left and right.
+
+ =
+ Numerically align, putting the fill between the sign character and the value.
+
+ The output is clipped by :token:`max` width characters and by the end of the buffer.
+ :token:`precision` is used by floating point values to specify the number of places of precision.
+
+ :token:`type` is used to indicate type specific formatting. For integers it indicates the output
+ radix and if ``#`` is present the radix is prefix is generated (one of ``0xb``, ``0``, ``0x``).
+ Format types of the same letter are equivalent, varying only in the character case used for
+ output. Most commonly 'x' prints values in lower cased hexadecimal (:code:`0x1337beef`) while 'X'
+ prints in upper case hexadecimal (:code:`0X1337BEEF`). Note there is no upper case decimal or
+ octal type because case is irrelevant for those.
+
+ = ===============
+ g generic, default.
+ b binary
+ B Binary
+ d decimal
+ o octal
+ x hexadecimal
+ X Hexadecimal
+ p pointer (hexadecimal address)
+ P Pointer (Hexadecimal address)
+ s string
+ S String (upper case)
+ = ===============
+
+ For several specializations the hexadecimal format is taken to indicate printing the value as if
+ it were a hexidecimal value, in effect providing a hex dump of the value. This is the case for
+ :class:`string_view` and therefore a hex dump of an object can be done by creating a
+ :class:`string_view` covering the data and then printing it with :code:`{:x}`.
+
+ The string type ('s' or 'S') is generally used to cause alphanumeric output for a value that would
+ normally use numeric output. For instance, a :code:`bool` is normally ``0`` or ``1``. Using the
+ type 's' yields ``true` or ``false``. The upper case form, 'S', applies only in these cases where the
+ formatter generates the text, it does not apply to normally text based values unless specifically noted.
+
+:token:`extension`
+ Text (excluding braces) that is passed to the type specific formatter function. This can be used
+ to provide extensions for specific argument types (e.g., IP addresses). The base logic ignores it
+ but passes it on to the formatting function which can then behave different based on the
+ extension.
+
+Usage Examples
+--------------
+
+Some examples, comparing :code:`snprintf` and :func:`BufferWriter::print`. ::
+
+ if (len > 0) {
+ auto n = snprintf(buff, len, "count %d", count);
+ len -= n;
+ buff += n;
+ }
+
+ bw.print("count {}", count);
+
+ // --
+
+ if (len > 0) {
+ auto n = snprintf(buff, len, "Size %" PRId64 " bytes", sizeof(thing));
+ len -= n;
+ buff += n;
+ }
+
+ bw.print("Size {} bytes", sizeof(thing));
+
+ // --
+
+ if (len > 0) {
+ auto n = snprintf(buff, len, "Number of items %ld", thing->count());
+ len -= n;
+ buff += n;
+ }
+
+ bw.print("Number of items {}", thing->count());
+
+Enumerations become easier. Note in this case argument indices are used in order to print both a
+name and a value for the enumeration. A key benefit here is the lack of need for a developer to know
+the specific free function or method needed to do the name lookup. In this case,
+:code:`HttpDebugNuames::get_server_state_name`. Rather than every developer having to memorize the
+assocation between the type and the name lookup function, or grub through the code hoping for an
+example, the compiler is told once and henceforth does the lookup. The internal implementation of
+this is :ref:`here <bwf-http-debug-name-example>` ::
+
+ if (len > 0) {
+ auto n = snprintf(buff, len, "Unexpected event %d in state %s[%d] for %.*s",
+ event,
+ HttpDebugNames::get_server_state_name(t_state.current.state),
+ t_state.current.state,
+ static_cast<int>(host_len), host);
+ buff += n;
+ len -= n;
+ }
+
+ bw.print("Unexpected event {0} in state {1}[{1:d}] for {2}",
+ event, t_state.current.state, string_view{host, host_len});
+
+Using :code:`std::string`, which illustrates the advantage of a formatter overloading knowing how to
+get the size from the object and not having to deal with restrictions on the numeric type (e.g.,
+that :code:`%.*s` requires an :code:`int`, not a :code:`size_t`). ::
+
+ if (len > 0) {
+ len -= snprintf(buff, len, "%.*s", static_cast<int>(s.size()), s.data);
+ }
+
+ bw.print("{}", s);
+
+IP addresses are much easier. There are two big advantages here. One is not having to know the
+conversion function name. The other is the lack of having to declare local variables and having to
+remember what the appropriate size is. Beyond there this code is more performant because the output
+is rendered directly in the output buffer, not rendered to a temporary and then copied over. This
+lack of local variables can be particularly nice in the context of a :code:`switch` statement where
+local variables for a :code:`case` mean having to add extra braces, or declare the temporaries at an
+outer scope. ::
+
+ char ip_buff1[INET6_ADDRPORTSTRLEN];
+ char ip_buff2[INET6_ADDRPORTSTRLEN];
+ ats_ip_nptop(ip_buff1, sizeof(ip_buff1), addr1);
+ ats_ip_nptop(ip_buff2, sizeof(ip_buff2), add2);
+ if (len > 0) {
+ snprintf(buff, len, "Connecting to %s from %s", ip_buff1, ip_buff2);
+ }
+
+ bw.print("Connecting to {} from {}", addr1, addr2);
+
+User Defined Formatting
++++++++++++++++++++++++
+
+To get the full benefit of type safe formatting it is necessary to provide type specific formatting
+functions which are called when a value of that type is formatted. This is how type specific
+knowledge such as the names of enumeration values are encoded in a single location. Additional type
+specific formatting can be provided via the :token:`extension` field. Without this, special formatting
+requires extra functions and additional work at the call site, rather than a single consolidated
+formatting function.
+
+To provide a formatter for a type :code:`V` the function :code:`bwformat` is overloaded. The signature
+would look like this::
+
+ BufferWriter& ts::bwformat(BufferWriter& w, BWFSpec const& spec, V const& v)
+
+:arg:`w` is the output and :arg:`spec` the parsed specifier, including the extension (if any). The
+calling framework will handle basic alignment as per :arg:`spec` therfore the overload does not need
+to unless the alignment requirements are more detailed (e.g. integer alignment operations) or
+performance is critical. In the latter case the formatter should make sure to use at least the
+minimum width in order to disable any additional alignment operation.
+
+It is important to note that a formatter can call another formatter. For example, the formatter for
+pointers looks like::
+
+ // Pointers that are not specialized.
+ inline BufferWriter &
+ bwformat(BufferWriter &w, BWFSpec const &spec, const void * ptr)
+ {
+ BWFSpec ptr_spec{spec};
+ ptr_spec._radix_lead_p = true;
+ if (ptr_spec._type == BWFSpec::DEFAULT_TYPE || ptr_spec._type == 'p') {
+ // if default or specifically 'p', switch to lower case hex.
+ ptr_spec._type = 'x';
+ } else if (ptr_spec._type == 'P') {
+ // Incoming 'P' means upper case hex.
+ ptr_spec._type = 'X';
+ }
+ return bw_fmt::Format_Integer(w, ptr_spec,
+ reinterpret_cast<intptr_t>(ptr), false);
+ }
+
+The code checks if the type ``p`` or ``P`` was used in order to select the appropriate case, then
+delegates the actual rendering to the integer formatter with a type of ``x`` or ``X`` as
+appropriate. In turn other formatters, if given the type ``p`` or ``P`` can cast the value to
+:code:`const void*` and call :code:`bwformat` on that to output the value as a pointer.
+
+To help reduce duplication, the output stream operator :code:`operator<<` is defined to call this
+function with a default constructed :code:`BWFSpec` instance so that absent a specific overload
+a BWF formatter will also provide a C++ stream output operator.
+
+Enum Example
+------------
+
+.. _bwf-http-debug-name-example:
+
+For a specific example of using BufferWriter formatting to make debug messages easier, consider the
+case of :code:`HttpDebugNames`. This is a class that serves as a namespace to provide various
+methods that convert state machine related data into descriptive strings. Currently this is
+undocumented (and even uncommented) and is therefore used infrequently, as that requires either
+blind cut and paste, or tracing through header files to understand the code. This can be greatly
+simplified by adding formatters to :ts:git:`proxy/http/HttpDebugNames.h` ::
+
+ inline ts::BufferWriter &
+ bwformat(ts::BufferWriter &w, ts::BWFSpec const &spec, HttpTransact::ServerState_t state)
+ {
+ if (spec.has_numeric_type()) {
+ // allow the user to force numeric output with '{:d}' or other numeric type.
+ return bwformat(w, spec, static_cast<uintmax_t>(state));
+ } else {
+ return bwformat(w, spec, HttpDebugNames::get_server_state_name(state));
+ }
+ }
+
+With this in place, any one wanting to print the name of the server state enumeration can do ::
-Note that in addition there will be no overrun on the memory buffer in :arg:`w`, in strong contrast
-to the original code.
+ bw.print("state {}", t_state.current_state);
+
+There is no need to remember names like :code:`HttpDebugNames` nor which method in it does the
+conversion. The developer making the :code:`HttpDebugNames` class or equivalent can take care of
+that in the same header file that provides the type.
+
+.. note::
+
+ In actual practice, due to this method being so obscure it's not actually used as far as I
+ can determine.
+
+Argument Forwarding
+-------------------
+
+It will frequently be useful for other libraries to allow local formatting (such as :code:`Errata`).
+For such cases the class methods will need to take variable arguments and then forward them on to
+the formatter. :class:`BufferWriter` provides the :func:`BufferWriter::printv` overload for this
+purpose. Instead of taking variable arguments, these overloads take a :code:`std::tuple` of
+arguments. Such as tuple is easily created with `std::forward_as_tuple
+<http://en.cppreference.com/w/cpp/utility/tuple/forward_as_tuple>`__. A standard implementation that
+uses the :code:`std::string` overload for :func:`bwprint` would look like ::
+
+ template < typename ... Args >
+ std::string message(string_view fmt, Args &&... args) {
+ std::string zret;
+ return ts::bwprint(zret, fmt, std::forward_as_tuple(args...));
+ }
+
+This gathers the argument (generally references to the arguments) in to a single tuple which is then
+passed by reference, to avoid restacking the arguments for every nested function call.
+
+Specialized Types
+-----------------
+
+These are types for which there exists a type specific BWF formatter.
+
+:class:`string_view`
+ Generally the contents of the view.
+
+ 'x' or 'X'
+ A hexadecimal dump of the contents of the view in lower ('x') or upper ('X') case.
+
+ 'p' or 'P'
+ The pointer and length value of the view in lower ('p') or upper ('P') case.
+
+ The :token:`precision` is interpreted specially for this type to mean "skip :token:`precision`
+ initial characters". When combined with :token:`max` this allows a mechanism for printing
+ substrings of the :class:`string_view`. For instance, to print the 10th through 20th characters
+ the format ``{:.10,20}`` would suffice. Given the method :code:`substr` for :class:`string_view`
+ is cheap, it's unclear how useful this is.
+
+:code:`sockaddr const*`
+ The IP address is printed. Fill is used to fill in address segments if provided, not to the
+ minimum width if specified. :class:`IpEndpoint` and :class:`IpAddr` are supported with the same
+ formatting. The formatting support in this case is extensive because of the commonality and
+ importance of IP address data.
+
+ Type overrides
+
+ 'p' or 'P'
+ The pointer address is printed as hexadecimal lower ('p') or upper ('P') case.
+
+ The extension can be used to control which parts of the address are printed. These can be in any order,
+ the output is always address, port, family. The default is the equivalent of "ap". In addition, the
+ character '=' ("numeric align") can be used to internally right justify the elements.
+
+ 'a'
+ The address.
+
+ 'p'
+ The port (host order).
+
+ 'f'
+ The IP address family.
+
+ '='
+ Internally justify the numeric values. This must be the first or second character. If it is the second
+ the first character is treated as the internal fill character. If omitted '0' (zero) is used.
+
+ E.g. ::
+
+ void func(sockaddr const* addr) {
+ bw.print("To {}", addr); // -> "To 172.19.3.105:4951"
+ bw.print("To {0::a} on port {0::p}", addr); // -> "To 172.19.3.105 on port 4951"
+ bw.print("To {::=}", addr); // -> "To 127.019.003.105:04951"
+ bw.print("Using address family {::f}", addr);
+ bw.print("{::a}",addr); // -> "172.19.3.105"
+ bw.print("{::=a}",addr); // -> "172.019.003.105"
+ bw.print("{::0=a}",addr); // -> "172.019.003.105"
+ bw.print("{:: =a}",addr); // -> "172. 19. 3.105"
+ bw.print("{:>20:a}",addr); // -> " 172.19.3.105"
+ bw.print("{:>20:=a}",addr); // -> " 172.019.003.105"
+ bw.print("{:>20: =a}",addr); // -> " 172. 19. 3.105"
+ }
+
+Global Names
+++++++++++++
+
+As a convenience, there are a few predefined global names that can be used to generate output. These
+do not take any arguments to :func:`BufferWriter::print`, the data needed for output is either
+process or thread global and is retrieved directly. They also are not counted for automatic indexing.
+
+now
+ The epoch time in seconds.
+
+tick
+ The high resolution clock tick.
+
+timestamp
+ UTC time in the format "Year Month Date Hour:Minute:Second", e.g. "2018 Apr 17 14:23:47".
+
+thread-id
+ The id of the current thread.
+
+thread-name
+ The name of the current thread.
+
+ts-thread
+ A pointer to the |TS| :class:`Thread` object for the current thread. This is useful for comparisons.
+
+ts-ethread
+ A pointer to the |TS| :class:`EThread` object for the current thread. This is useful for comparisons
+ or to indicate if the thread is an :class:`EThread` (if not, the value will be :code:`nullptr`).
+
+For example, to have the same output as the normal diagnostic messages with a timestamp and the current thread::
+
+ bw.print("{timestamp} {ts-thread} Counter is {}", counter);
+
+Note that even though no argument is provided the global names do not count as part of the argument
+indexing, therefore the preceeding example could be written as::
+
+ bw.print("{timestamp} {ts-thread} Counter is {0}", counter);
+
+Working with standard I/O
++++++++++++++++++++++++++
+
+:class:`BufferWriter` can be used with some of the basic I/O functionality of a C++ environment. At the lowest
+level the output stream operator can be used with a file descriptor or a :code:`std::ostream`. For these
+examples assume :code:`bw` is an instance of :class:`BufferWriter` with data in it.
+
+.. code-block:: cpp
+
+ int fd = open("some_file", O_RDWR);
+ bw >> fd; // Write to file.
+ bw >> std::cout; // write to standard out.
+
+For convenience a stream operator for :code:`std::stream` is provided to make the use more natural.
+
+.. code-block:: cpp
+
+ std::cout << bw;
+ std::cout << bw.view(); // identical effect as the previous line.
+
+Using a :class:`BufferWriter` with :code:`printf` is straight forward by use of the sized string
+format code.
+
+.. code-block:: cpp
+
+ ts::LocalBufferWriter<256> bw;
+ bw.print("Failed to connect to {}", addr1);
+ printf("%.*s\n", static_cast<int>(bw.size()), bw.data());
+
+Alternatively the output can be null terminated in the formatting to avoid having to pass the size. ::
+
+ ts::LocalBufferWriter<256> bw;
+ printf("%s\n", bw.print("Failed to connect to {}\0", addr1).data());
+
+When using C++ stream I/O, writing to a stream can be done without any local variables at all.
+
+.. code-block:: cpp
+
+ std::cout << ts::LocalBufferWriter<256>().print("Failed to connect to {}\n", addr1);
+
+This is handy for temporary debugging messages as it avoids having to clean up local variable
+declarations later, particularly when the types involved themselves require additional local
+declarations (such as in this example, an IP address which would normally require a local text
+buffer for conversion before printing). As noted previously this is particularly useful inside a
+:code:`case` where local variables are more annoying to set up.
Reference
+++++++++
@@ -214,7 +812,7 @@ Reference
Write the character :arg:`c` to the buffer. If there is no space in the buffer the character
is not written.
- .. function:: fill(size_t n)
+ .. function:: BufferWriter & fill(size_t n)
Increase the output size by :arg:`n` without changing the buffer contents. This is used in
conjuction with :func:`BufferWriter::auxBuffer` after writing output to the buffer returned by
@@ -273,6 +871,20 @@ Reference
Print the arguments according to the format. See `bw-formatting`_.
+ .. function:: template <typename ... Args> \
+ BufferWriter & printv(TextView fmt, std::tuple<Args...> && args)
+
+ Print the arguments in the tuple :arg:`args` according to the format. See `bw-formatting`_.
+
+ .. function:: std::ostream & operator >> (std::ostream & stream) const
+
+ Write the contents of the buffer to :arg:`stream` and return :arg:`stream`.
+
+ .. function:: ssize_t operator >> (int fd)
+
+ Write the contents of the buffer to file descriptor :arg:`fd` and return the number of bytes
+ write (the results of the call to file :code:`write()`).
+
.. class:: FixedBufferWriter : public BufferWriter
This is a class that implements :class:`BufferWriter` on a fixed buffer, passed in to the constructor.
@@ -282,10 +894,13 @@ Reference
Construct an instance that will write to :arg:`buffer` at most :arg:`length` bytes. If more
data is written, all data past the maximum size is discarded.
- .. function:: reduce(size_t n)
+ .. function:: FixedBufferWriter & reduce(size_t n)
+
+ Roll back the output to have :arg:`n` valid (used) bytes.
+
+ .. function:: FixedBufferWriter & reset()
- Roll back the output to :arg:`n` bytes. This is useful primarily for clearing the buffer by
- calling :code:`reduce(0)`.
+ Equivalent to :code:`reduce(0)`, provide for convenience.
.. function:: FixedBufferWriter auxWriter(size_t reserve = 0)
@@ -298,123 +913,39 @@ Reference
This is a convenience class which is a subclass of :class:`FixedBufferWriter`. It which creates a
buffer as a member rather than having an external buffer that is passed to the instance. The
- buffer is :arg:`N` bytes long.
+ buffer is :arg:`N` bytes long. This differs from its super class only in the constructor, which
+ is only a default constructor.
.. function:: LocalBufferWriter::LocalBufferWriter()
Construct an instance with a capacity of :arg:`N`.
-.. _bw-formatting:
+.. class:: BWFSpec
-Formatted Output
-++++++++++++++++
-
-:class:`BufferWriter` supports formatting output in a style similar to Python formatting via
-:func:`BufferWriter::print`. This takes a format string which then controls the use of subsquent
-arguments in generating out in the buffer. The basic format is divided in to three parts, separated by colons.
+ This holds a format specifier. It has the parsing logic for a specifier and if the constructor is
+ passed a :class:`string_view` of a specifier, that will parse it and loaded into the class
+ members. This is useful to specialized implementations of :func:`bwformat`.
-.. productionList:: BufferWriterFormat
- Format: "{" [name] [":" [specifier] [":" extension]] "}"
- name: index | name
- extension: <printable character except "{}">*
+.. function:: template<typename V> BufferWriter& bwformat(BufferWriter & w, BWFSpec const & spec, V const & v)
-:arg:`name`
- The name of the argument to use. This can be a number in which case it is the zero based index of the argument to the method call. E.g. ``{0}`` means the first argument and ``{2}`` is the third argument after the format.
-
- ``bw.print("{0} {1}", 'a', 'b')`` => ``a b``
-
- ``bw.print("{1} {0}", 'a', 'b')`` => ``b a``
+ A family of overloads that perform formatted output on a :class:`BufferWriter`. The set of types
+ supported can be extended by defining an overload of this function for the types.
- The name can be omitted in which case it is treated as an index in parallel to the position in
- the format string. Only the position in the format string matters, not what names those other
- format elements may have used.
-
- ``bw.print("{0} {2} {}", 'a', 'b', 'c')`` => ``a c c``
-
- ``bw.print("{0} {2} {2}", 'a', 'b', 'c')`` => ``a c c``
-
- Note that an argument can be printed more than once if the name is used more than once.
-
- ``bw.print("{0} {} {0}", 'a', 'b')`` => ``a b a``
-
- ``bw.print("{0} {1} {0}", 'a', 'b')`` => ``a b a``
-
- Alphanumeric names refer to values in a global table. These will be described in more detail someday.
-
-:arg:`specifier`
- Basic formatting control.
-
- .. productionList:: specifier
- specifier: [[fill]align][sign]["#"]["0"][[min][.precision][,max][type]]
- fill: <printable character except "{}%:"> | URI-char
- URI-char: "%" hex-digit hex-digit
- align: "<" | ">" | "=" | "^"
- sign: "+" | "-" | " "
- min: integer
- precision: integer
- max: integer
- type: "x" | "o" | "b"
-
- The output is placed in a field that is at least :token:`min` wide and no more than :token:`max` wide. If
- the output is less than :token:`min` then
-
- * The :token:`fill` character is used for the extra space required. This can be an explicit
- character or a URI encoded one (to allow otherwise reserved characters).
- * The output is shifted according to the :token:`align`.
-
- <
- Align to the left, fill to the right.
-
- >
- Align to the right, fill to the left.
-
- ^
- Align in the middle, fill to left and right.
-
- =
- Numerically align, putting the fill between the output and the sign character.
-
- The output is clipped by :token:`max` width characters or the end of the buffer. :token:`precision` is used by
- floating point values to specify the number of places of precision. The precense of the ``#`` character is used for
- integer values and causes a radix indicator to be used (one of ``0xb``, ``0``, ``0x``).
-
- :token:`type` is used to indicate type specific formatting. For integers it indicates the output
- radix. If ``#`` is present the radix is prefix is generated with case matching that of the type
- (e.g. type ``x`` causes ``0x`` and type ``X`` causes ``0X``).
-
- = ===============
- b binary
- o octal
- x hexadecimal
- = ===============
-
-
-:arg:`extension`
- Text (excluding braces) that is passed to the formatting function. This can be used to provide
- extensions for specific argument types (e.g., IP addresses). The base logic ignores it but passes
- it on to the formatting function for the corresponding argument type which can then behave
- different based on the extension.
-
-User Defined Formatting
-+++++++++++++++++++++++
-
-When an value needs to be formatted an overloaded function for type :code:`V` is called.
-
-.. code-block:: cpp
-
- BufferWriter& ts::bwformat(BufferWriter& w, BWFSpec const& spec, V const& v)
+.. function:: template < typename ... Args > \
+ std::string& bwprint(std::string & s, string_view format, Args &&... args)
-This can (and should be) overloaded for user defined types. This makes it easier and cheaper to
-build one overload on another by tweaking the :arg:`spec` as it passed through. The calling
-framework will handle basic alignment, the overload does not need to unless the alignment
-requirements are more detailed (e.g. integer alignment operations).
+ Generate formatted output in :arg:`s` based on the :arg:`format` and arguments :arg:`args`. The
+ string :arg:`s` is adjusted in size to be the exact length as required by the output. If the
+ string already had enough capacity it is not re-allocated, otherwise the resizing will cause
+ a re-allocation.
-The output stream operator :code:`operator<<` is defined to call this function with a default
-constructed :code:`BWFSpec` instance.
+.. function:: template < typename ... Args > \
+ std::string& bwprintv(std::string & s, string_view format, std::tuple<Args...> args)
-Futures
-+++++++
+ Generate formatted output in :arg:`s` based on the :arg:`format` and :arg:`args`, which must be a
+ tuple of the arguments to use for the format. The string :arg:`s` is adjusted in size to be the
+ exact length as required by the output. If the string already had enough capacity it is not
+ re-allocated, otherwise the resizing will cause a re-allocation.
-A planned future extension is a variant of :class:`BufferWriter` that operates on a
-:code:`MIOBuffer`. This would be very useful in many places that work with :code:`MIOBuffer`
-instances, most specifically in the body factory logic.
+ This overload is used primarily as a back end to another function which takes the arguments for
+ the formatting independently.
diff --git a/iocore/eventsystem/IOBuffer.cc b/iocore/eventsystem/IOBuffer.cc
index 2ef31ca..e92dc94 100644
--- a/iocore/eventsystem/IOBuffer.cc
+++ b/iocore/eventsystem/IOBuffer.cc
@@ -26,6 +26,7 @@
**************************************************************************/
#include "ts/ink_defs.h"
+#include "I_MIOBufferWriter.h"
#include "P_EventSystem.h"
//
@@ -277,3 +278,76 @@ IOBufferReader::memcpy(const void *ap, int64_t len, int64_t offset)
return p;
}
+
+//-- MIOBufferWriter
+MIOBufferWriter &
+MIOBufferWriter::write(const void *data_, size_t length)
+{
+ const char *data = static_cast<const char *>(data_);
+
+ while (length) {
+ IOBufferBlock *iobbPtr = _miob->first_write_block();
+
+ if (!iobbPtr) {
+ addBlock();
+
+ iobbPtr = _miob->first_write_block();
+
+ ink_assert(iobbPtr);
+ }
+
+ size_t writeSize = iobbPtr->write_avail();
+
+ if (length < writeSize) {
+ writeSize = length;
+ }
+
+ std::memcpy(iobbPtr->end(), data, writeSize);
+ iobbPtr->fill(writeSize);
+
+ data += writeSize;
+ length -= writeSize;
+
+ _numWritten += writeSize;
+ }
+
+ return *this;
+}
+
+std::ostream &
+MIOBufferWriter::operator>>(std::ostream &stream) const
+{
+ IOBufferReader *r = _miob->alloc_reader();
+ if (r) {
+ IOBufferBlock *b;
+ while (nullptr != (b = r->get_current_block())) {
+ auto n = b->read_avail();
+ stream.write(b->start(), n);
+ r->consume(n);
+ }
+ _miob->dealloc_reader(r);
+ }
+ return stream;
+}
+
+ssize_t
+MIOBufferWriter::operator>>(int fd) const
+{
+ ssize_t zret = 0;
+ IOBufferReader *reader = _miob->alloc_reader();
+ if (reader) {
+ IOBufferBlock *b;
+ while (nullptr != (b = reader->get_current_block())) {
+ auto n = b->read_avail();
+ auto r = ::write(fd, b->start(), n);
+ if (r <= 0) {
+ break;
+ } else {
+ reader->consume(r);
+ zret += r;
+ }
+ }
+ _miob->dealloc_reader(reader);
+ }
+ return zret;
+}
diff --git a/iocore/eventsystem/I_MIOBufferWriter.h b/iocore/eventsystem/I_MIOBufferWriter.h
index ce9ccb3..ac73f44 100644
--- a/iocore/eventsystem/I_MIOBufferWriter.h
+++ b/iocore/eventsystem/I_MIOBufferWriter.h
@@ -24,6 +24,7 @@
#pragma once
#include <cstring>
+#include <iosfwd>
#include <ts/ink_assert.h>
#include <ts/BufferWriter.h>
@@ -32,49 +33,23 @@
#include <I_IOBuffer.h>
#endif
+/** BufferWriter interface on top of IOBuffer blocks.
+
+ @internal This should be changed to IOBufferChain once I port that to open source.
+ */
class MIOBufferWriter : public ts::BufferWriter
{
-public:
- MIOBufferWriter(MIOBuffer &miob) : _miob(miob) {}
-
- MIOBufferWriter &
- write(const void *data_, size_t length) override
- {
- const char *data = static_cast<const char *>(data_);
-
- while (length) {
- IOBufferBlock *iobbPtr = _miob.first_write_block();
-
- if (!iobbPtr) {
- addBlock();
-
- iobbPtr = _miob.first_write_block();
-
- ink_assert(iobbPtr);
- }
+ using self_type = MIOBufferWriter; ///< Self reference type.
- size_t writeSize = iobbPtr->write_avail();
-
- if (length < writeSize) {
- writeSize = length;
- }
-
- std::memcpy(iobbPtr->end(), data, writeSize);
- iobbPtr->fill(writeSize);
-
- data += writeSize;
- length -= writeSize;
-
- _numWritten += writeSize;
- }
+public:
+ MIOBufferWriter(MIOBuffer *miob) : _miob(miob) {}
- return *this;
- }
+ self_type &write(const void *data_, size_t length) override;
- MIOBufferWriter &
+ self_type &
write(char c) override
{
- return write(&c, 1);
+ return this->write(&c, 1);
}
bool
@@ -86,7 +61,7 @@ public:
char *
auxBuffer() override
{
- IOBufferBlock *iobbPtr = _miob.first_write_block();
+ IOBufferBlock *iobbPtr = _miob->first_write_block();
if (!iobbPtr) {
return nullptr;
@@ -98,7 +73,7 @@ public:
size_t
auxBufferCapacity() const
{
- IOBufferBlock *iobbPtr = _miob.first_write_block();
+ IOBufferBlock *iobbPtr = _miob->first_write_block();
if (!iobbPtr) {
return 0;
@@ -110,11 +85,11 @@ public:
// Write the first n characters that have been placed in the auxiliary buffer. This call invalidates the auxiliary buffer.
// This function should not be called if no auxiliary buffer is available.
//
- MIOBufferWriter &
+ self_type &
fill(size_t n) override
{
if (n) {
- IOBufferBlock *iobbPtr = _miob.first_write_block();
+ IOBufferBlock *iobbPtr = _miob->first_write_block();
ink_assert(iobbPtr and (n <= size_t(iobbPtr->write_avail())));
@@ -142,11 +117,11 @@ public:
// Not useful in this derived class.
//
- BufferWriter &clip(size_t) override { return *this; }
+ self_type &clip(size_t) override { return *this; }
// Not useful in this derived class.
//
- BufferWriter &extend(size_t) override { return *this; }
+ self_type &extend(size_t) override { return *this; }
// This must not be called for this derived class.
//
@@ -157,8 +132,15 @@ public:
return nullptr;
}
+ /// Output the buffer contents to the @a stream.
+ /// @return The destination stream.
+ std::ostream &operator>>(std::ostream &stream) const override;
+ /// Output the buffer contents to the file for file descriptor @a fd.
+ /// @return The number of bytes written.
+ ssize_t operator>>(int fd) const override;
+
protected:
- MIOBuffer &_miob;
+ MIOBuffer *_miob;
private:
size_t _numWritten = 0;
@@ -166,8 +148,8 @@ private:
virtual void
addBlock()
{
- _miob.add_block();
+ _miob->add_block();
}
// INTERNAL - Overload removed, make sure it's not used.
- MIOBufferWriter &write(size_t n);
+ self_type &write(size_t n);
};
diff --git a/iocore/eventsystem/Makefile.am b/iocore/eventsystem/Makefile.am
index cc060aa..5e4d95e 100644
--- a/iocore/eventsystem/Makefile.am
+++ b/iocore/eventsystem/Makefile.am
@@ -119,11 +119,12 @@ test_Event_LDFLAGS = $(test_LD_FLAGS)
test_Buffer_LDADD = $(test_LD_ADD)
test_Event_LDADD = $(test_LD_ADD)
-test_MIOBufferWriter_CPPFLAGS = $(AM_CPPFLAGS)\
- -I$(abs_top_srcdir)/tests/include
-test_MIOBufferWriter_SOURCES = \
- unit-tests/test_MIOBufferWriter.cc
+test_MIOBufferWriter_SOURCES = unit-tests/test_MIOBufferWriter.cc
+
+test_MIOBufferWriter_CPPFLAGS = $(test_CPP_FLAGS) -I$(abs_top_srcdir)/tests/include
+test_MIOBufferWriter_LDFLAGS = $(test_LD_FLAGS)
+test_MIOBufferWriter_LDADD = $(test_LD_ADD)
include $(top_srcdir)/build/tidy.mk
diff --git a/iocore/eventsystem/unit-tests/test_MIOBufferWriter.cc b/iocore/eventsystem/unit-tests/test_MIOBufferWriter.cc
index cb5b901..7a15e0b 100644
--- a/iocore/eventsystem/unit-tests/test_MIOBufferWriter.cc
+++ b/iocore/eventsystem/unit-tests/test_MIOBufferWriter.cc
@@ -21,94 +21,39 @@
limitations under the License.
*/
-#define CATCH_CONFIG_MAIN
+#define CATCH_CONFIG_RUNNER
#include "catch.hpp"
#include <cstdint>
#include <cstdlib>
-struct IOBufferBlock {
- std::int64_t write_avail();
+#include "I_EventSystem.h"
+#include "ts/I_Layout.h"
+//#include "ts/ink_string.h"
- char *end();
-
- void fill(int64_t);
-};
-
-struct MIOBuffer {
- IOBufferBlock *first_write_block();
-
- void add_block();
-};
-
-#define UNIT_TEST_BUFFER_WRITER
+#include "diags.i"
#include "I_MIOBufferWriter.h"
-IOBufferBlock iobb[1];
-int iobbIdx{0};
-
-const int BlockSize = 11 * 11;
-char block[BlockSize];
-int blockUsed{0};
-
-std::int64_t
-IOBufferBlock::write_avail()
-{
- REQUIRE(this == (iobb + iobbIdx));
- return BlockSize - blockUsed;
-}
-
-char *
-IOBufferBlock::end()
-{
- REQUIRE(this == (iobb + iobbIdx));
- return block + blockUsed;
-}
-
-void
-IOBufferBlock::fill(int64_t len)
-{
- static std::uint8_t dataCheck;
-
- REQUIRE(this == (iobb + iobbIdx));
-
- while (len-- and (blockUsed < BlockSize)) {
- REQUIRE(block[blockUsed] == static_cast<char>(dataCheck));
-
- ++blockUsed;
-
- dataCheck += 7;
- }
-
- REQUIRE(len == -1);
-}
-
-MIOBuffer theMIOBuffer;
-
-IOBufferBlock *
-MIOBuffer::first_write_block()
+int
+main(int argc, char *argv[])
{
- REQUIRE(this == &theMIOBuffer);
-
- REQUIRE(blockUsed <= BlockSize);
-
- if (blockUsed == BlockSize) {
- return nullptr;
- }
+ // global setup...
+ Layout::create();
+ init_diags("", nullptr);
+ RecProcessInit(RECM_STAND_ALONE);
- return iobb + iobbIdx;
-}
+ ink_event_system_init(EVENT_SYSTEM_MODULE_VERSION);
+ eventProcessor.start(2);
-void
-MIOBuffer::add_block()
-{
- REQUIRE(this == &theMIOBuffer);
+ Thread *main_thread = new EThread;
+ main_thread->set_specific();
- REQUIRE(blockUsed == BlockSize);
+ std::cout << "Pre-Catch" << std::endl;
+ int result = Catch::Session().run(argc, argv);
- blockUsed = 0;
+ // global clean-up...
- ++iobbIdx;
+ exit(result);
}
std::string
@@ -154,7 +99,7 @@ writeOnce(MIOBufferWriter &bw, std::size_t len)
toggle = !toggle;
- REQUIRE(bw.auxBufferCapacity() <= BlockSize);
+ REQUIRE(bw.auxBufferCapacity() <= DEFAULT_BUFFER_NUMBER);
}
class InkAssertExcept
@@ -163,10 +108,10 @@ class InkAssertExcept
TEST_CASE("MIOBufferWriter", "[MIOBW]")
{
+ MIOBuffer *theMIOBuffer = new_MIOBuffer(default_large_iobuffer_size);
MIOBufferWriter bw(theMIOBuffer);
- REQUIRE(bw.auxBufferCapacity() == BlockSize);
-
+#if 0
writeOnce(bw, 0);
writeOnce(bw, 1);
writeOnce(bw, 1);
@@ -180,18 +125,15 @@ TEST_CASE("MIOBufferWriter", "[MIOBW]")
writeOnce(bw, 69);
writeOnce(bw, 666);
+ std::cout << "Pre Loop" << std::endl;
for (int i = 0; i < 3000; i += 13) {
writeOnce(bw, i);
}
+ std::cout << "Post Loop" << std::endl;
writeOnce(bw, 0);
writeOnce(bw, 1);
- REQUIRE(bw.extent() == ((iobbIdx * BlockSize) + blockUsed));
-}
-
-void
-_ink_assert(const char *a, const char *f, int l)
-{
- std::exit(1);
+ REQUIRE(bw.extent() == 3000);
+#endif
}
diff --git a/lib/ts/BufferWriter.h b/lib/ts/BufferWriter.h
index 0a298d1..678c628 100644
--- a/lib/ts/BufferWriter.h
+++ b/lib/ts/BufferWriter.h
@@ -28,6 +28,7 @@
#include <cstring>
#include <vector>
#include <string>
+#include <iosfwd>
#include <ts/ink_std_compat.h>
#include <ts/TextView.h>
@@ -184,11 +185,25 @@ public:
"{} {1} {}", and "{0} {1} {2}" are equivalent. Using an explicit index does not reset the
position of subsequent substiations, therefore "{} {0} {}" is equivalent to "{0} {0} {2}".
*/
- template <typename... Rest> BufferWriter &print(TextView fmt, Rest... rest);
-
- template <typename... Rest> BufferWriter &print(BWFormat const &fmt, Rest... rest);
+ template <typename... Rest> BufferWriter &print(TextView fmt, Rest &&... rest);
+ /** Print overload to take arguments as a tuple instead of explicitly.
+ This is useful for forwarding variable arguments from other functions / methods.
+ */
+ template <typename... Args> BufferWriter &printv(TextView fmt, std::tuple<Args...> const &args);
- // bwprint(*this, fmt, std::forward<Rest>(rest)...);
+ /// Print using a preparsed @a fmt.
+ template <typename... Args> BufferWriter &print(BWFormat const &fmt, Args &&... args);
+ /** Print overload to take arguments as a tuple instead of explicitly.
+ This is useful for forwarding variable arguments from other functions / methods.
+ */
+ template <typename... Args> BufferWriter &printv(BWFormat const &fmt, std::tuple<Args...> const &args);
+
+ /// Output the buffer contents to the @a stream.
+ /// @return The destination stream.
+ virtual std::ostream &operator>>(std::ostream &stream) const = 0;
+ /// Output the buffer contents to the file for file descriptor @a fd.
+ /// @return The number of bytes written.
+ virtual ssize_t operator>>(int fd) const = 0;
};
/** A @c BufferWrite concrete subclass to write to a fixed size buffer.
@@ -196,6 +211,7 @@ public:
class FixedBufferWriter : public BufferWriter
{
using super_type = BufferWriter;
+ using self_type = FixedBufferWriter;
public:
/** Construct a buffer writer on a fixed @a buffer of size @a capacity.
@@ -220,6 +236,8 @@ public:
/// Move assignment.
FixedBufferWriter &operator=(FixedBufferWriter &&) = default;
+ FixedBufferWriter(MemSpan &span) : _buf(span.begin()), _capacity(static_cast<size_t>(span.size())) {}
+
/// Write a single character @a c to the buffer.
FixedBufferWriter &
write(char c) override
@@ -325,13 +343,28 @@ public:
/// Reduce extent to @a n.
/// If @a n is less than the capacity the error condition, if any, is cleared.
- /// This can be used to clear the output by calling @c reduce(0)
- void
+ /// This can be used to clear the output by calling @c reduce(0). In contrast
+ /// to @c clip this reduces the data in the buffer, rather than the capacity.
+ self_type &
reduce(size_t n)
{
ink_assert(n <= _attempted);
_attempted = n;
+ return *this;
+ }
+
+ /// Clear the buffer, reset to empty (no data).
+ /// This is a convenience for reusing a buffer. For instance
+ /// @code
+ /// bw.reset().print("....."); // clear old data and print new data.
+ /// @endcode
+ /// This is equivalent to @c reduce(0) but clearer for that case.
+ self_type &
+ reset()
+ {
+ _attempted = 0;
+ return *this;
}
/// Provide a string_view of all successfully written characters.
@@ -356,6 +389,11 @@ public:
return {this->auxBuffer(), reserve < this->remaining() ? this->remaining() - reserve : 0};
}
+ /// Output the buffer contents to the @a stream.
+ std::ostream &operator>>(std::ostream &stream) const override;
+ /// Output the buffer contents to the file for file descriptor @a fd.
+ ssize_t operator>>(int fd) const override;
+
protected:
char *const _buf; ///< Output buffer.
size_t _capacity; ///< Size of output buffer.
@@ -453,10 +491,14 @@ protected:
// generate output on @a w
}
@endcode
+
+ The argument can be passed by value if that would be more efficient.
*/
namespace bw_fmt
{
+ /// Internal signature for template generated formatting.
+ /// @a args is a forwarded tuple of arguments to be processed.
template <typename TUPLE> using ArgFormatterSignature = BufferWriter &(*)(BufferWriter &w, BWFSpec const &, TUPLE const &args);
/// Internal error / reporting message generators
@@ -467,7 +509,7 @@ namespace bw_fmt
/// This selects the @a I th argument in the @a TUPLE arg pack and calls the formatter on it. This
/// (or the equivalent lambda) is needed because the array of formatters must have a homogenous
/// signature, not vary per argument. Effectively this indirection erases the type of the specific
- /// argument being formatter.
+ /// argument being formatted. Instances of this have the signature @c ArgFormatterSignature.
template <typename TUPLE, size_t I>
BufferWriter &
Arg_Formatter(BufferWriter &w, BWFSpec const &spec, TUPLE const &args)
@@ -477,7 +519,7 @@ namespace bw_fmt
/// This exists only to expand the index sequence into an array of formatters for the tuple type
/// @a TUPLE. Due to langauge limitations it cannot be done directly. The formatters can be
- /// access via standard array access in constrast to templated tuple access. The actual array is
+ /// accessed via standard array access in constrast to templated tuple access. The actual array is
/// static and therefore at run time the only operation is loading the address of the array.
template <typename TUPLE, size_t... N>
ArgFormatterSignature<TUPLE> *
@@ -488,6 +530,8 @@ namespace bw_fmt
}
/// Perform alignment adjustments / fill on @a w of the content in @a lw.
+ /// This is the normal mechanism, but a number of the builtin types handle this internally
+ /// for performance reasons.
void Do_Alignment(BWFSpec const &spec, BufferWriter &w, BufferWriter &lw);
/// Global named argument table.
@@ -504,7 +548,15 @@ namespace bw_fmt
} // namespace bw_fmt
-/** Compiled BufferWriter format
+using BWGlobalNameSignature = bw_fmt::GlobalSignature;
+/// Add a global @a name to BufferWriter formatting, output generated by @a formatter.
+/// @return @c true if the name was register, @c false if not (name already in use).
+bool bwf_register_global(string_view name, BWGlobalNameSignature formatter);
+
+/** Compiled BufferWriter format.
+
+ @note This is not as useful as hoped, the performance is not much better using this than parsing
+ on the fly (about 30% better, which is fine for tight loops but not for general use).
*/
class BWFormat
{
@@ -548,16 +600,24 @@ protected:
static void Format_Literal(BufferWriter &w, BWFSpec const &spec);
};
-template <typename... Rest>
+template <typename... Args>
+BufferWriter &
+BufferWriter::print(TextView fmt, Args &&... args)
+{
+ return this->printv(fmt, std::forward_as_tuple(args...));
+}
+
+template <typename... Args>
BufferWriter &
-BufferWriter::print(TextView fmt, Rest... rest)
+BufferWriter::printv(TextView fmt, std::tuple<Args...> const &args)
{
- static constexpr int N = sizeof...(Rest);
- auto args(std::forward_as_tuple(rest...));
- auto fa = bw_fmt::Get_Arg_Formatter_Array<decltype(args)>(std::index_sequence_for<Rest...>{});
- int arg_idx = 0;
+ static constexpr int N = sizeof...(Args); // used as loop limit
+ static const auto fa = bw_fmt::Get_Arg_Formatter_Array<decltype(args)>(std::index_sequence_for<Args...>{});
+ int arg_idx = 0; // the next argument index to be processed.
while (fmt.size()) {
+ // Next string piece of interest is an (optional) literal and then an (optinal) format specifier.
+ // There will always be a specifier except for the possible trailing literal.
string_view lit_v;
string_view spec_v;
bool spec_p = BWFormat::parse(fmt, lit_v, spec_v);
@@ -565,8 +625,9 @@ BufferWriter::print(TextView fmt, Rest... rest)
if (lit_v.size()) {
this->write(lit_v);
}
+
if (spec_p) {
- BWFSpec spec{spec_v};
+ BWFSpec spec{spec_v}; // parse the specifier.
size_t width = this->remaining();
if (spec._max < width) {
width = spec._max;
@@ -582,31 +643,36 @@ BufferWriter::print(TextView fmt, Rest... rest)
} else {
bw_fmt::Err_Bad_Arg_Index(lw, spec._idx, N);
}
+ ++arg_idx;
} else if (spec._name.size()) {
auto gf = bw_fmt::Global_Table_Find(spec._name);
if (gf) {
gf(lw, spec);
} else {
- static constexpr TextView msg{"{invalid name:"};
- lw.write(msg).write(spec._name).write('}');
+ lw.write("{~"_sv).write(spec._name).write("~}"_sv);
}
}
if (lw.extent()) {
bw_fmt::Do_Alignment(spec, *this, lw);
}
- ++arg_idx;
}
}
return *this;
}
-template <typename... Rest>
+template <typename... Args>
+BufferWriter &
+BufferWriter::print(BWFormat const &fmt, Args &&... args)
+{
+ return this->printv(fmt, std::forward_as_tuple(args...));
+}
+
+template <typename... Args>
BufferWriter &
-BufferWriter::print(BWFormat const &fmt, Rest... rest)
+BufferWriter::printv(BWFormat const &fmt, std::tuple<Args...> const &args)
{
- static constexpr int N = sizeof...(Rest);
- auto const args(std::forward_as_tuple(rest...));
- static const auto fa = bw_fmt::Get_Arg_Formatter_Array<decltype(args)>(std::index_sequence_for<Rest...>{});
+ static constexpr int N = sizeof...(Args);
+ static const auto fa = bw_fmt::Get_Arg_Formatter_Array<decltype(args)>(std::index_sequence_for<Args...>{});
for (BWFormat::Item const &item : fmt._items) {
size_t width = this->remaining();
@@ -620,8 +686,8 @@ BufferWriter::print(BWFormat const &fmt, Rest... rest)
auto idx = item._spec._idx;
if (0 <= idx && idx < N) {
fa[idx](lw, item._spec, args);
- } else if (item._spec._name.size() && (nullptr != (item._gf = bw_fmt::Global_Table_Find(item._spec._name)))) {
- item._gf(lw, item._spec);
+ } else if (item._spec._name.size()) {
+ lw.write("{~"_sv).write(item._spec._name).write("~}"_sv);
}
}
bw_fmt::Do_Alignment(item._spec, *this, lw);
@@ -629,15 +695,7 @@ BufferWriter::print(BWFormat const &fmt, Rest... rest)
return *this;
}
-// Generically a stream operator is a formatter with the default specification.
-template <typename V>
-BufferWriter &
-operator<<(BufferWriter &w, V &&v)
-{
- return bwformat(w, BWFSpec::DEFAULT, std::forward<V>(v));
-}
-
-// Pointers
+// Pointers that are not specialized.
inline BufferWriter &
bwformat(BufferWriter &w, BWFSpec const &spec, const void *ptr)
{
@@ -658,25 +716,6 @@ BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, MemSpan const &span
BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, string_view sv);
-inline BufferWriter &
-bwformat(BufferWriter &w, BWFSpec const &, char c)
-{
- return w.write(c);
-}
-
-inline BufferWriter &
-bwformat(BufferWriter &w, BWFSpec const &spec, bool f)
-{
- if ('s' == spec._type) {
- w.write(f ? "true"_sv : "false"_sv);
- } else if ('S' == spec._type) {
- w.write(f ? "TRUE"_sv : "FALSE"_sv);
- } else {
- bw_fmt::Format_Integer(w, spec, static_cast<uintmax_t>(f), false);
- }
- return w;
-}
-
template <size_t N>
BufferWriter &
bwformat(BufferWriter &w, BWFSpec const &spec, const char (&a)[N])
@@ -696,19 +735,21 @@ bwformat(BufferWriter &w, BWFSpec const &spec, const char *v)
}
inline BufferWriter &
-bwformat(BufferWriter &w, BWFSpec const &spec, TextView const &tv)
+bwformat(BufferWriter &w, BWFSpec const &spec, TextView tv)
{
return bwformat(w, spec, static_cast<string_view>(tv));
}
inline BufferWriter &
-bwformat(BufferWriter &w, BWFSpec const &spec, double const &d)
+bwformat(BufferWriter &w, BWFSpec const &spec, std::string const &s)
{
- return d < 0 ? bw_fmt::Format_Floating(w, spec, -d, true) : bw_fmt::Format_Floating(w, spec, d, false);
+ return bwformat(w, spec, string_view{s});
}
-inline BufferWriter &
-bwformat(BufferWriter &w, BWFSpec const &spec, float const &f)
+template <typename F>
+auto
+bwformat(BufferWriter &w, BWFSpec const &spec, F &&f) ->
+ typename std::enable_if<std::is_floating_point<typename std::remove_reference<F>::type>::value, BufferWriter &>::type
{
return f < 0 ? bw_fmt::Format_Floating(w, spec, -f, true) : bw_fmt::Format_Floating(w, spec, f, false);
}
@@ -727,36 +768,75 @@ bwformat(BufferWriter &w, BWFSpec const &spec, float const &f)
template <typename I>
auto
-bwformat(BufferWriter &w, BWFSpec const &spec, I const &i) ->
- typename std::enable_if<std::is_unsigned<I>::value, BufferWriter &>::type
+bwformat(BufferWriter &w, BWFSpec const &spec, I &&i) ->
+ typename std::enable_if<std::is_unsigned<typename std::remove_reference<I>::type>::value &&
+ std::is_integral<typename std::remove_reference<I>::type>::value,
+ BufferWriter &>::type
{
return bw_fmt::Format_Integer(w, spec, i, false);
}
template <typename I>
auto
-bwformat(BufferWriter &w, BWFSpec const &spec, I const &i) ->
- typename std::enable_if<std::is_signed<I>::value, BufferWriter &>::type
+bwformat(BufferWriter &w, BWFSpec const &spec, I &&i) ->
+ typename std::enable_if<std::is_signed<typename std::remove_reference<I>::type>::value &&
+ std::is_integral<typename std::remove_reference<I>::type>::value,
+ BufferWriter &>::type
{
return i < 0 ? bw_fmt::Format_Integer(w, spec, -i, true) : bw_fmt::Format_Integer(w, spec, i, false);
}
+inline BufferWriter &
+bwformat(BufferWriter &w, BWFSpec const &, char c)
+{
+ return w.write(c);
+}
+
+inline BufferWriter &
+bwformat(BufferWriter &w, BWFSpec const &spec, bool f)
+{
+ if ('s' == spec._type) {
+ w.write(f ? "true"_sv : "false"_sv);
+ } else if ('S' == spec._type) {
+ w.write(f ? "TRUE"_sv : "FALSE"_sv);
+ } else {
+ bw_fmt::Format_Integer(w, spec, static_cast<uintmax_t>(f), false);
+ }
+ return w;
+}
+
+// Generically a stream operator is a formatter with the default specification.
+template <typename V>
+BufferWriter &
+operator<<(BufferWriter &w, V &&v)
+{
+ return bwformat(w, BWFSpec::DEFAULT, std::forward<V>(v));
+}
+
// std::string support
/** Print to a @c std::string
Print to the string @a s. If there is overflow then resize the string sufficiently to hold the output
and print again. The effect is the string is resized only as needed to hold the output.
*/
-template <typename... Rest>
-void
-bwprint(std::string &s, ts::TextView fmt, Rest &&... rest)
+template <typename... Args>
+std::string &
+bwprintv(std::string &s, ts::TextView fmt, std::tuple<Args...> const &args)
{
- auto len = s.size();
- size_t n = ts::FixedBufferWriter(const_cast<char *>(s.data()), s.size()).print(fmt, std::forward<Rest>(rest)...).extent();
+ auto len = s.size(); // remember initial size
+ size_t n = ts::FixedBufferWriter(const_cast<char *>(s.data()), s.size()).printv(fmt, std::move(args)).extent();
s.resize(n); // always need to resize - if shorter, must clip pre-existing text.
if (n > len) { // dropped data, try again.
- ts::FixedBufferWriter(const_cast<char *>(s.data()), s.size()).print(fmt, std::forward<Rest>(rest)...);
+ ts::FixedBufferWriter(const_cast<char *>(s.data()), s.size()).printv(fmt, std::move(args));
}
+ return s;
+}
+
+template <typename... Args>
+std::string &
+bwprint(std::string &s, ts::TextView fmt, Args &&... args)
+{
+ return bwprintv(s, fmt, std::forward_as_tuple(args...));
}
// -- FixedBufferWriter --
@@ -768,3 +848,12 @@ inline FixedBufferWriter::FixedBufferWriter(char *buffer, size_t capacity) : _bu
inline FixedBufferWriter::FixedBufferWriter(std::nullptr_t) : _buf(nullptr), _capacity(0) {}
} // end namespace ts
+
+namespace std
+{
+inline ostream &
+operator<<(ostream &s, ts::BufferWriter const &w)
+{
+ return w >> s;
+}
+} // end namespace std
diff --git a/lib/ts/BufferWriterFormat.cc b/lib/ts/BufferWriterFormat.cc
index 463f98b..4bee3a1 100644
--- a/lib/ts/BufferWriterFormat.cc
+++ b/lib/ts/BufferWriterFormat.cc
@@ -22,11 +22,14 @@
*/
#include <ts/BufferWriter.h>
+#include <unistd.h>
+#include <sys/param.h>
#include <cctype>
#include <ctime>
#include <cmath>
#include <cmath>
#include <array>
+#include <chrono>
namespace
{
@@ -60,12 +63,40 @@ tv_to_positive_decimal(ts::TextView src, ts::TextView *out)
namespace ts
{
-const ts::BWFSpec ts::BWFSpec::DEFAULT;
+const BWFSpec BWFSpec::DEFAULT;
+
+const BWFSpec::Property BWFSpec::_prop;
+
+#pragma GCC diagnostic ignored "-Wchar-subscripts"
+BWFSpec::Property::Property()
+{
+ memset(_data, 0, sizeof(_data));
+ _data['b'] = TYPE_CHAR | NUMERIC_TYPE_CHAR;
+ _data['B'] = TYPE_CHAR | NUMERIC_TYPE_CHAR | UPPER_TYPE_CHAR;
+ _data['d'] = TYPE_CHAR | NUMERIC_TYPE_CHAR;
+ _data['g'] = TYPE_CHAR;
+ _data['o'] = TYPE_CHAR | NUMERIC_TYPE_CHAR;
+ _data['p'] = TYPE_CHAR;
+ _data['P'] = TYPE_CHAR | UPPER_TYPE_CHAR;
+ _data['s'] = TYPE_CHAR;
+ _data['S'] = TYPE_CHAR | UPPER_TYPE_CHAR;
+ _data['x'] = TYPE_CHAR | NUMERIC_TYPE_CHAR;
+ _data['X'] = TYPE_CHAR | NUMERIC_TYPE_CHAR | UPPER_TYPE_CHAR;
+
+ _data[' '] = SIGN_CHAR;
+ _data['-'] = SIGN_CHAR;
+ _data['+'] = SIGN_CHAR;
+
+ _data['<'] = static_cast<uint8_t>(BWFSpec::Align::LEFT);
+ _data['>'] = static_cast<uint8_t>(BWFSpec::Align::RIGHT);
+ _data['^'] = static_cast<uint8_t>(BWFSpec::Align::CENTER);
+ _data['='] = static_cast<uint8_t>(BWFSpec::Align::SIGN);
+}
/// Parse a format specification.
BWFSpec::BWFSpec(TextView fmt)
{
- TextView num;
+ TextView num; // temporary for number parsing.
intmax_t n;
_name = fmt.take_prefix_at(':');
@@ -623,46 +654,54 @@ BWFormat::BWFormat(ts::TextView fmt)
if (spec_p) {
bw_fmt::GlobalSignature gf = nullptr;
BWFSpec parsed_spec{spec_str};
- if (parsed_spec._name.size() == 0) {
+ if (parsed_spec._name.size() == 0) { // no name provided, use implicit index.
parsed_spec._idx = arg_idx;
}
- if (parsed_spec._idx < 0) {
+ if (parsed_spec._idx < 0) { // name wasn't missing or a valid index, assume global name.
gf = bw_fmt::Global_Table_Find(parsed_spec._name);
+ } else {
+ ++arg_idx; // bump this if not a global name.
}
_items.emplace_back(parsed_spec, gf);
- ++arg_idx;
}
}
}
BWFormat::~BWFormat() {}
+/// Parse out the next literal and/or format specifier from the format string.
+/// Pass the results back in @a literal and @a specifier as appropriate.
+/// Update @a fmt to strip the parsed text.
bool
BWFormat::parse(ts::TextView &fmt, string_view &literal, string_view &specifier)
{
TextView::size_type off;
+ // Check for brace delimiters.
off = fmt.find_if([](char c) { return '{' == c || '}' == c; });
if (off == TextView::npos) {
+ // not found, it's a literal, ship it.
literal = fmt;
fmt.remove_prefix(literal.size());
return false;
}
+ // Processing for braces that don't enclose specifiers.
if (fmt.size() > off + 1) {
char c1 = fmt[off];
char c2 = fmt[off + 1];
if (c1 == c2) {
+ // double braces count as literals, but must tweak to out only 1 brace.
literal = fmt.take_prefix_at(off + 1);
return false;
} else if ('}' == c1) {
- throw std::invalid_argument("Unopened }");
+ throw std::invalid_argument("BWFormat:: Unopened } in format string.");
} else {
literal = string_view{fmt.data(), off};
fmt.remove_prefix(off + 1);
}
} else {
- throw std::invalid_argument("Invalid trailing character");
+ throw std::invalid_argument("BWFormat: Invalid trailing character in format string.");
}
if (fmt.size()) {
@@ -670,7 +709,7 @@ BWFormat::parse(ts::TextView &fmt, string_view &literal, string_view &specifier)
// take_prefix_at failed to find the delimiter or found it as the first byte.
off = fmt.find('}');
if (off == TextView::npos) {
- throw std::invalid_argument("Unclosed {");
+ throw std::invalid_argument("BWFormat: Unclosed { in format string");
}
specifier = fmt.take_prefix_at(off);
return true;
@@ -696,19 +735,84 @@ bw_fmt::Global_Table_Find(string_view name)
return nullptr;
}
+std::ostream &
+FixedBufferWriter::operator>>(std::ostream &s) const
+{
+ return s << this->view();
+}
+
+ssize_t
+FixedBufferWriter::operator>>(int fd) const
+{
+ return ::write(fd, this->data(), this->size());
+}
+
+bool
+bwf_register_global(string_view name, BWGlobalNameSignature formatter)
+{
+ return ts::bw_fmt::BWF_GLOBAL_TABLE.emplace(name, formatter).second;
+}
+
} // namespace ts
namespace
{
void
-BWF_Now(ts::BufferWriter &w, ts::BWFSpec const &spec)
+BWF_Timestamp(ts::BufferWriter &w, ts::BWFSpec const &spec)
{
+ // Unfortunately need to write to a temporary buffer or the sizing isn't correct if @a w is clipped
+ // because @c strftime returns 0 if the buffer isn't large enough.
+ char buff[32];
std::time_t t = std::time(nullptr);
- w.fill(std::strftime(w.auxBuffer(), w.remaining(), "%Y%b%d:%H%M%S", std::localtime(&t)));
+ auto n = strftime(buff, sizeof(buff), "%Y %b %d %H:%M:%S", std::localtime(&t));
+ w.write(ts::string_view{buff, n});
+}
+
+void
+BWF_Now(ts::BufferWriter &w, ts::BWFSpec const &spec)
+{
+ bwformat(w, spec, std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()));
+}
+
+void
+BWF_Tick(ts::BufferWriter &w, ts::BWFSpec const &spec)
+{
+ bwformat(w, spec, std::chrono::high_resolution_clock::now().time_since_epoch().count());
+}
+
+void
+BWF_ThreadID(ts::BufferWriter &w, ts::BWFSpec const &spec)
+{
+ bwformat(w, spec, pthread_self());
+}
+
+void
+BWF_ThreadName(ts::BufferWriter &w, ts::BWFSpec const &spec)
+{
+#if defined(__FreeBSD_version)
+ bwformat(w, spec, "thread"_sv); // no thread names in FreeBSD.
+#else
+ char name[32]; // manual says at least 16, bump that up a bit.
+ pthread_getname_np(pthread_self(), name, sizeof(name));
+ bwformat(w, spec, ts::string_view{name});
+#endif
}
static bool BW_INITIALIZED __attribute__((unused)) = []() -> bool {
ts::bw_fmt::BWF_GLOBAL_TABLE.emplace("now", &BWF_Now);
+ ts::bw_fmt::BWF_GLOBAL_TABLE.emplace("tick", &BWF_Tick);
+ ts::bw_fmt::BWF_GLOBAL_TABLE.emplace("timestamp", &BWF_Timestamp);
+ ts::bw_fmt::BWF_GLOBAL_TABLE.emplace("thread-id", &BWF_ThreadID);
+ ts::bw_fmt::BWF_GLOBAL_TABLE.emplace("thread-name", &BWF_ThreadName);
return true;
}();
} // namespace
+
+namespace std
+{
+ostream &
+operator<<(ostream &s, ts::FixedBufferWriter &w)
+{
+ return s << w.view();
+}
+} // namespace std
diff --git a/lib/ts/BufferWriterForward.h b/lib/ts/BufferWriterForward.h
index 882e85a..0d50f77 100644
--- a/lib/ts/BufferWriterForward.h
+++ b/lib/ts/BufferWriterForward.h
@@ -53,8 +53,8 @@ struct BWFSpec {
NONE, ///< No alignment.
LEFT, ///< Left alignment '<'.
RIGHT, ///< Right alignment '>'.
- CENTER, ///< Center alignment '='.
- SIGN ///< Align plus/minus sign before numeric fill. '^'
+ CENTER, ///< Center alignment '^'.
+ SIGN ///< Align plus/minus sign before numeric fill. '='
} _align = Align::NONE; ///< Output field alignment.
char _type = DEFAULT_TYPE; ///< Type / radix indicator.
bool _radix_lead_p = false; ///< Print leading radix indication.
@@ -68,6 +68,19 @@ struct BWFSpec {
static const self_type DEFAULT;
+ /// Validate @a c is a specifier type indicator.
+ static bool is_type(char c);
+ /// Check if the type flag is numeric.
+ static bool is_numeric_type(char c);
+ /// Check if the type is an upper case variant.
+ static bool is_upper_case_type(char c);
+ /// Check if the type @a in @a this is numeric.
+ bool has_numeric_type() const;
+ /// Check if the type in @a this is an upper case variant.
+ bool has_upper_case_type() const;
+ /// Check if the type is a raw pointer.
+ bool has_pointer_type() const;
+
protected:
/// Validate character is alignment character and return the appropriate enum value.
Align align_of(char c);
@@ -75,26 +88,60 @@ protected:
/// Validate is sign indicator.
bool is_sign(char c);
- /// Validate @a c is a specifier type indicator.
- bool is_type(char c);
+ /// Handrolled initialization the character syntactic property data.
+ static const struct Property {
+ Property(); ///< Default constructor, creates initialized flag set.
+ /// Flag storage, indexed by character value.
+ uint8_t _data[0x100];
+ /// Flag mask values.
+ static constexpr uint8_t ALIGN_MASK = 0x0F; ///< Alignment type.
+ static constexpr uint8_t TYPE_CHAR = 0x10; ///< A valid type character.
+ static constexpr uint8_t UPPER_TYPE_CHAR = 0x20; ///< Upper case flag.
+ static constexpr uint8_t NUMERIC_TYPE_CHAR = 0x40; ///< Numeric output.
+ static constexpr uint8_t SIGN_CHAR = 0x80; ///< Is sign character.
+ } _prop;
};
inline BWFSpec::Align
BWFSpec::align_of(char c)
{
- return '<' == c ? Align::LEFT : '>' == c ? Align::RIGHT : '=' == c ? Align::CENTER : '^' == c ? Align::SIGN : Align::NONE;
+ return static_cast<Align>(_prop._data[static_cast<unsigned>(c)] & Property::ALIGN_MASK);
}
inline bool
BWFSpec::is_sign(char c)
{
- return '+' == c || '-' == c || ' ' == c;
+ return _prop._data[static_cast<unsigned>(c)] & Property::SIGN_CHAR;
}
inline bool
BWFSpec::is_type(char c)
{
- return 'x' == c || 'X' == c || 'o' == c || 'b' == c || 'B' == c || 'd' == c || 's' == c || 'S' == c;
+ return _prop._data[static_cast<unsigned>(c)] & Property::TYPE_CHAR;
+}
+
+inline bool
+BWFSpec::is_upper_case_type(char c)
+{
+ return _prop._data[static_cast<unsigned>(c)] & Property::UPPER_TYPE_CHAR;
+}
+
+inline bool
+BWFSpec::has_numeric_type() const
+{
+ return _prop._data[static_cast<unsigned>(_type)] & Property::NUMERIC_TYPE_CHAR;
+}
+
+inline bool
+BWFSpec::has_upper_case_type() const
+{
+ return _prop._data[static_cast<unsigned>(_type)] & Property::UPPER_TYPE_CHAR;
+}
+
+inline bool
+BWFSpec::has_pointer_type() const
+{
+ return _type == 'p' || _type == 'P';
}
class BWFormat;
diff --git a/lib/ts/Makefile.am b/lib/ts/Makefile.am
index 2cc746d..0ace85c 100644
--- a/lib/ts/Makefile.am
+++ b/lib/ts/Makefile.am
@@ -212,6 +212,8 @@ libtsutil_la_SOURCES = \
X509HostnameValidator.cc \
X509HostnameValidator.h
+BufferWriterFormat.o : AM_CPPFLAGS += -Wno-char-subscripts
+
#test_UNUSED_SOURCES = \
# load_http_hdr.cc \
# IntrusivePtrTest.cc \
diff --git a/lib/ts/bwf_std_format.h b/lib/ts/bwf_std_format.h
new file mode 100644
index 0000000..9ea50a5
--- /dev/null
+++ b/lib/ts/bwf_std_format.h
@@ -0,0 +1,36 @@
+/** @file
+
+ BufferWriter formatters for types in the std namespace.
+
+ @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.
+ */
+
+#pragma once
+
+#include <atomic>
+
+namespace std
+{
+template <typename T>
+ts::BufferWriter &
+bwformat(ts::BufferWriter &w, ts::BWFSpec const &spec, std::atomic<T> const &v)
+{
+ return ts::bwformat(w, spec, v.load());
+}
+} // end namespace std
diff --git a/lib/ts/ink_inet.cc b/lib/ts/ink_inet.cc
index 3f88a2f..daa496b 100644
--- a/lib/ts/ink_inet.cc
+++ b/lib/ts/ink_inet.cc
@@ -674,3 +674,253 @@ ats_tcp_somaxconn()
return value;
}
+
+namespace ts
+{
+BufferWriter &
+bwformat(BufferWriter &w, BWFSpec const &spec, in_addr_t addr)
+{
+ uint8_t *ptr = reinterpret_cast<uint8_t *>(&addr);
+ BWFSpec local_spec{spec}; // Format for address elements.
+ bool align_p = false;
+
+ if (spec._ext.size()) {
+ if (spec._ext.front() == '=') {
+ align_p = true;
+ local_spec._fill = '0';
+ } else if (spec._ext.size() > 1 && spec._ext[1] == '=') {
+ align_p = true;
+ local_spec._fill = spec._ext[0];
+ }
+ }
+
+ if (align_p) {
+ local_spec._min = 3;
+ local_spec._align = BWFSpec::Align::RIGHT;
+ } else {
+ local_spec._min = 0;
+ }
+
+ bwformat(w, local_spec, ptr[0]);
+ w.write('.');
+ bwformat(w, local_spec, ptr[1]);
+ w.write('.');
+ bwformat(w, local_spec, ptr[2]);
+ w.write('.');
+ bwformat(w, local_spec, ptr[3]);
+ return w;
+}
+
+BufferWriter &
+bwformat(BufferWriter &w, BWFSpec const &spec, in6_addr const &addr)
+{
+ using QUAD = uint16_t const;
+ BWFSpec local_spec{spec}; // Format for address elements.
+ uint8_t const *ptr = addr.s6_addr;
+ uint8_t const *limit = ptr + sizeof(addr.s6_addr);
+ QUAD *lower = nullptr; // the best zero range
+ QUAD *upper = nullptr;
+ bool align_p = false;
+
+ if (spec._ext.size()) {
+ if (spec._ext.front() == '=') {
+ align_p = true;
+ local_spec._fill = '0';
+ } else if (spec._ext.size() > 1 && spec._ext[1] == '=') {
+ align_p = true;
+ local_spec._fill = spec._ext[0];
+ }
+ }
+
+ if (align_p) {
+ local_spec._min = 4;
+ local_spec._align = BWFSpec::Align::RIGHT;
+ } else {
+ local_spec._min = 0;
+ // do 0 compression if there's no internal fill.
+ for (QUAD *spot = reinterpret_cast<QUAD *>(ptr), *last = reinterpret_cast<QUAD *>(limit), *current = nullptr; spot < last;
+ ++spot) {
+ if (0 == *spot) {
+ if (current) {
+ // If there's no best, or this is better, remember it.
+ if (!lower || (upper - lower < spot - current)) {
+ lower = current;
+ upper = spot;
+ }
+ } else {
+ current = spot;
+ }
+ } else {
+ current = nullptr;
+ }
+ }
+ }
+
+ if (!local_spec.has_numeric_type()) {
+ local_spec._type = 'x';
+ }
+
+ for (; ptr < limit; ptr += 2) {
+ if (reinterpret_cast<uint8_t const *>(lower) <= ptr && ptr <= reinterpret_cast<uint8_t const *>(upper)) {
+ if (ptr == addr.s6_addr) {
+ w.write(':'); // only if this is the first quad.
+ }
+ if (ptr == reinterpret_cast<uint8_t const *>(upper)) {
+ w.write(':');
+ }
+ } else {
+ uint16_t f = (ptr[0] << 8) + ptr[1];
+ bwformat(w, local_spec, f);
+ if (ptr != limit - 2) {
+ w.write(':');
+ }
+ }
+ }
+ return w;
+}
+
+BufferWriter &
+bwformat(BufferWriter &w, BWFSpec const &spec, IpAddr const &addr)
+{
+ BWFSpec local_spec{spec}; // Format for address elements and port.
+ bool addr_p{true};
+ bool family_p{false};
+
+ if (spec._ext.size()) {
+ if (spec._ext.front() == '=') {
+ local_spec._ext.remove_prefix(1);
+ } else if (spec._ext.size() > 1 && spec._ext[1] == '=') {
+ local_spec._ext.remove_prefix(2);
+ }
+ }
+ if (local_spec._ext.size()) {
+ addr_p = false;
+ for (char c : local_spec._ext) {
+ switch (c) {
+ case 'a':
+ case 'A':
+ addr_p = true;
+ break;
+ case 'f':
+ case 'F':
+ family_p = true;
+ break;
+ }
+ }
+ }
+
+ if (addr_p) {
+ if (addr.isIp4()) {
+ bwformat(w, spec, addr._addr._ip4);
+ } else if (addr.isIp6()) {
+ bwformat(w, spec, addr._addr._ip6);
+ } else {
+ w.print("*Not IP address [{}]*", addr.family());
+ }
+ }
+
+ if (family_p) {
+ local_spec._min = 0;
+ if (addr_p) {
+ w.write(' ');
+ }
+ if (spec.has_numeric_type()) {
+ bwformat(w, local_spec, static_cast<uintmax_t>(addr.family()));
+ } else {
+ bwformat(w, local_spec, addr.family());
+ }
+ }
+ return w;
+}
+
+BufferWriter &
+bwformat(BufferWriter &w, BWFSpec const &spec, sockaddr const *addr)
+{
+ BWFSpec local_spec{spec}; // Format for address elements and port.
+ bool port_p{true};
+ bool addr_p{true};
+ bool family_p{false};
+ bool local_numeric_fill_p{false};
+ char local_numeric_fill_char{'0'};
+
+ if (spec._type == 'p' || spec._type == 'P') {
+ bwformat(w, spec, static_cast<void const *>(addr));
+ return w;
+ }
+
+ if (spec._ext.size()) {
+ if (spec._ext.front() == '=') {
+ local_numeric_fill_p = true;
+ local_spec._ext.remove_prefix(1);
+ } else if (spec._ext.size() > 1 && spec._ext[1] == '=') {
+ local_numeric_fill_p = true;
+ local_numeric_fill_char = spec._ext.front();
+ local_spec._ext.remove_prefix(2);
+ }
+ }
+ if (local_spec._ext.size()) {
+ addr_p = port_p = false;
+ for (char c : local_spec._ext) {
+ switch (c) {
+ case 'a':
+ case 'A':
+ addr_p = true;
+ break;
+ case 'p':
+ case 'P':
+ port_p = true;
+ break;
+ case 'f':
+ case 'F':
+ family_p = true;
+ break;
+ }
+ }
+ }
+
+ if (addr_p) {
+ bool bracket_p = false;
+ switch (addr->sa_family) {
+ case AF_INET:
+ bwformat(w, spec, ats_ip4_addr_cast(addr));
+ break;
+ case AF_INET6:
+ if (port_p) {
+ w.write('[');
+ bracket_p = true; // take a note - put in the trailing bracket.
+ }
+ bwformat(w, spec, ats_ip6_addr_cast(addr));
+ break;
+ default:
+ w.print("*Not IP address [{}]*", addr->sa_family);
+ break;
+ }
+ if (bracket_p)
+ w.write(']');
+ if (port_p)
+ w.write(':');
+ }
+ if (port_p) {
+ if (local_numeric_fill_p) {
+ local_spec._min = 5;
+ local_spec._fill = local_numeric_fill_char;
+ local_spec._align = BWFSpec::Align::RIGHT;
+ } else {
+ local_spec._min = 0;
+ }
+ bwformat(w, local_spec, static_cast<uintmax_t>(ats_ip_port_host_order(addr)));
+ }
+ if (family_p) {
+ local_spec._min = 0;
+ if (addr_p || port_p)
+ w.write(' ');
+ if (spec.has_numeric_type()) {
+ bwformat(w, local_spec, static_cast<uintmax_t>(addr->sa_family));
+ } else {
+ bwformat(w, local_spec, ats_ip_family_name(addr->sa_family));
+ }
+ }
+ return w;
+}
+
+} // namespace ts
diff --git a/lib/ts/ink_inet.h b/lib/ts/ink_inet.h
index 98342fa..b048830 100644
--- a/lib/ts/ink_inet.h
+++ b/lib/ts/ink_inet.h
@@ -30,6 +30,7 @@
#include <ts/ink_memory.h>
#include <ts/ink_apidefs.h>
#include <ts/string_view.h>
+#include <ts/BufferWriterForward.h>
#if !TS_HAS_IN6_IS_ADDR_UNSPECIFIED
#if defined(IN6_IS_ADDR_UNSPECIFIED)
@@ -1545,3 +1546,15 @@ IpEndpoint::setToLoopback(int family)
}
return *this;
}
+
+// BufferWriter formatting support.
+namespace ts
+{
+BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, IpAddr const &addr);
+BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, sockaddr const *addr);
+inline BufferWriter &
+bwformat(BufferWriter &w, BWFSpec const &spec, IpEndpoint const &addr)
+{
+ return bwformat(w, spec, &addr.sa);
+}
+} // namespace ts
diff --git a/lib/ts/unit-tests/test_BufferWriter.cc b/lib/ts/unit-tests/test_BufferWriter.cc
index cfccfd2..4675060 100644
--- a/lib/ts/unit-tests/test_BufferWriter.cc
+++ b/lib/ts/unit-tests/test_BufferWriter.cc
@@ -83,6 +83,16 @@ TEST_CASE("BufferWriter::write(StringView)", "[BWWSV]")
}
X &clip(size_t) override { return *this; }
X &extend(size_t) override { return *this; }
+ std::ostream &
+ operator>>(std::ostream &stream) const override
+ {
+ return stream;
+ }
+ ssize_t
+ operator>>(int fd) const override
+ {
+ return 0;
+ }
};
X x;
@@ -276,7 +286,7 @@ TEST_CASE("Concrete Buffer Writers 2", "[BWC2]")
REQUIRE(bw20.extent() == 10);
REQUIRE(bw20.size() == 10);
- auto abw = bw20.auxWriter();
+ ts::FixedBufferWriter abw{bw20.auxWriter()};
REQUIRE(abw.remaining() == 10);
abw.write("abcdefghijklmnopqrstuvwxyz");
bw20.fill(abw.extent());
diff --git a/lib/ts/unit-tests/test_BufferWriterFormat.cc b/lib/ts/unit-tests/test_BufferWriterFormat.cc
index d6b73d5..db07736 100644
--- a/lib/ts/unit-tests/test_BufferWriterFormat.cc
+++ b/lib/ts/unit-tests/test_BufferWriterFormat.cc
@@ -25,6 +25,7 @@
#include <chrono>
#include <iostream>
#include <ts/BufferWriter.h>
+#include <ts/bwf_std_format.h>
#include <ts/MemSpan.h>
#include <ts/INK_MD5.h>
#include <ts/CryptoHash.h>
@@ -68,19 +69,19 @@ TEST_CASE("bwprint basics", "[bwprint]")
bw.print("right |{:.>10}|", "text");
REQUIRE(bw.view() == "right |......text|");
bw.reduce(0);
- bw.print("center |{:.=10}|", "text");
+ bw.print("center |{:.^10}|", "text");
REQUIRE(bw.view() == "center |...text...|");
bw.reduce(0);
- bw.print("center |{:.=11}|", "text");
+ bw.print("center |{:.^11}|", "text");
REQUIRE(bw.view() == "center |...text....|");
bw.reduce(0);
- bw.print("center |{:==10}|", "text");
- REQUIRE(bw.view() == "center |===text===|");
+ bw.print("center |{:^^10}|", "text");
+ REQUIRE(bw.view() == "center |^^^text^^^|");
bw.reduce(0);
- bw.print("center |{:%3A=10}|", "text");
+ bw.print("center |{:%3A^10}|", "text");
REQUIRE(bw.view() == "center |:::text:::|");
bw.reduce(0);
- bw.print("left >{0:<9}< right >{0:>9}< center >{0:=9}<", 956);
+ bw.print("left >{0:<9}< right >{0:>9}< center >{0:^9}<", 956);
REQUIRE(bw.view() == "left >956 < right > 956< center > 956 <");
bw.reduce(0);
@@ -121,14 +122,22 @@ TEST_CASE("bwprint basics", "[bwprint]")
bw.print("Arg {0} Arg {{{{}}}} {}", 9, 10);
REQUIRE(bw.view() == "Arg 9 Arg {{}} 10");
bw.reduce(0);
- bw.print("Time is {now}");
- // REQUIRE(bw.view() == "Time is");
+
+ bw.reset().print("{leif}");
+ REQUIRE(bw.view() == "{~leif~}"); // expected to be missing.
+
+ bw.reset().print("Thread: {thread-name} [{thread-id:#x}] - Tick: {tick} - Epoch: {now} - timestamp: {timestamp} {0}\n", 31267);
+ // std::cout << bw;
+ /*
+ std::cout << ts::LocalBufferWriter<256>().print(
+ "Thread: {thread-name} [{thread-id:#x}] - Tick: {tick} - Epoch: {now} - timestamp: {timestamp} {0}{}", 31267, '\n');
+ */
}
-TEST_CASE("BWFormat", "[bwprint][bwformat]")
+TEST_CASE("BWFormat numerics", "[bwprint][bwformat]")
{
ts::LocalBufferWriter<256> bw;
- ts::BWFormat fmt("left >{0:<9}< right >{0:>9}< center >{0:=9}<");
+ ts::BWFormat fmt("left >{0:<9}< right >{0:>9}< center >{0:^9}<");
ts::string_view text{"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"};
bw.reduce(0);
@@ -195,7 +204,7 @@ TEST_CASE("BWFormat", "[bwprint][bwformat]")
bw.print("|{:>16x}|", sv);
REQUIRE(bw.view() == "| 616263313233|");
bw.reduce(0);
- bw.print("|{:=16x}|", sv);
+ bw.print("|{:^16x}|", sv);
REQUIRE(bw.view() == "| 616263313233 |");
bw.reduce(0);
bw.print("|{:>16.2x}|", sv);
@@ -203,6 +212,8 @@ TEST_CASE("BWFormat", "[bwprint][bwformat]")
bw.reduce(0);
bw.print("|{:<0.2,5x}|", sv);
REQUIRE(bw.view() == "|63313|");
+ bw.reset().print("|{:<.2,5x}|", sv);
+ REQUIRE(bw.view() == "|63313|");
bw.reduce(0);
bw.print("|{}|", true);
@@ -220,15 +231,15 @@ TEST_CASE("BWFormat", "[bwprint][bwformat]")
bw.print("|{:>9s}|", false);
REQUIRE(bw.view() == "| false|");
bw.reduce(0);
- bw.print("|{:=10s}|", true);
+ bw.print("|{:^10s}|", true);
REQUIRE(bw.view() == "| true |");
// Test clipping a bit.
ts::LocalBufferWriter<20> bw20;
- bw20.print("0123456789abc|{:=10s}|", true);
+ bw20.print("0123456789abc|{:^10s}|", true);
REQUIRE(bw20.view() == "0123456789abc| tru");
bw20.reduce(0);
- bw20.print("012345|{:=10s}|6789abc", true);
+ bw20.print("012345|{:^10s}|6789abc", true);
REQUIRE(bw20.view() == "012345| true |67");
INK_MD5 md5;
@@ -239,6 +250,11 @@ TEST_CASE("BWFormat", "[bwprint][bwformat]")
bw.reduce(0);
bw.print("{}", md5);
REQUIRE(bw.view() == "e99a18c428cb38d5f260853678922e03");
+
+ bw.reset().print("Char '{}'", 'a');
+ REQUIRE(bw.view() == "Char 'a'");
+ bw.reset().print("Byte '{}'", uint8_t{'a'});
+ REQUIRE(bw.view() == "Byte '97'");
}
TEST_CASE("bwstring", "[bwprint][bwstring]")
@@ -256,6 +272,13 @@ TEST_CASE("bwstring", "[bwprint][bwstring]")
ts::bwprint(s, "{} .. |{:,20}|", 32767, text);
REQUIRE(s == "32767 .. |e99a18c428cb38d5f260|");
+
+ ts::LocalBufferWriter<128> bw;
+ char buff[128];
+ snprintf(buff, sizeof(buff), "|%s|", bw.print("Deep Silent Complete by {}\0", "Nightwish"_sv).data());
+ REQUIRE(ts::string_view(buff) == "|Deep Silent Complete by Nightwish|");
+ snprintf(buff, sizeof(buff), "|%s|", bw.reset().print("Deep Silent Complete by {}\0elided junk", "Nightwish"_sv).data());
+ REQUIRE(ts::string_view(buff) == "|Deep Silent Complete by Nightwish|");
}
TEST_CASE("BWFormat integral", "[bwprint][bwformat]")
@@ -315,6 +338,13 @@ TEST_CASE("BWFormat integral", "[bwprint][bwformat]")
REQUIRE(bw.view() == "1 2 2");
bwformat(bw, center, three_n);
REQUIRE(bw.view() == "1 2 2 -3 ");
+
+ std::atomic<int> ax{0};
+ bw.reset().print("ax == {}", ax);
+ REQUIRE(bw.view() == "ax == 0");
+ ++ax;
+ bw.reset().print("ax == {}", ax);
+ REQUIRE(bw.view() == "ax == 1");
}
TEST_CASE("BWFormat floating", "[bwprint][bwformat]")
diff --git a/lib/ts/unit-tests/test_ink_inet.cc b/lib/ts/unit-tests/test_ink_inet.cc
index e40ba47..ac25bb8 100644
--- a/lib/ts/unit-tests/test_ink_inet.cc
+++ b/lib/ts/unit-tests/test_ink_inet.cc
@@ -22,6 +22,7 @@
*/
#include <ts/TextView.h>
+#include <ts/BufferWriter.h>
#include <ts/ink_inet.h>
#include <catch.hpp>
#include <iostream>
@@ -144,3 +145,104 @@ TEST_CASE("ats_ip_pton", "[libts][inet][ink_inet]")
addr.load("c000::ffff:ffff:ffff:ffff:ffff:ffff");
REQUIRE(addr == upper);
}
+
+TEST_CASE("inet formatting", "[libts][ink_inet][bwformat]")
+{
+ IpEndpoint ep;
+ ts::string_view addr_1{"[ffee::24c3:3349:3cee:143]:8080"};
+ ts::string_view addr_2{"172.17.99.231:23995"};
+ ts::string_view addr_3{"[1337:ded:BEEF::]:53874"};
+ ts::string_view addr_4{"[1337::ded:BEEF]:53874"};
+ ts::string_view addr_5{"[1337:0:0:ded:BEEF:0:0:956]:53874"};
+ ts::string_view addr_6{"[1337:0:0:ded:BEEF:0:0:0]:53874"};
+ ts::string_view addr_7{"172.19.3.105:4951"};
+ ts::string_view addr_null{"[::]:53874"};
+ ts::LocalBufferWriter<1024> w;
+
+ REQUIRE(0 == ats_ip_pton(addr_1, &ep.sa));
+ w.print("{}", ep);
+ REQUIRE(w.view() == addr_1);
+ w.reset().print("{::p}", ep);
+ REQUIRE(w.view() == "8080");
+ w.reset().print("{::a}", ep);
+ REQUIRE(w.view() == addr_1.substr(1, 24)); // check the brackets are dropped.
+ w.reset().print("[{::a}]", ep);
+ REQUIRE(w.view() == addr_1.substr(0, 26)); // check the brackets are dropped.
+ w.reset().print("[{0::a}]:{0::p}", ep);
+ REQUIRE(w.view() == addr_1); // check the brackets are dropped.
+ w.reset().print("{::=a}", ep);
+ REQUIRE(w.view() == "ffee:0000:0000:0000:24c3:3349:3cee:0143");
+ w.reset().print("{:: =a}", ep);
+ REQUIRE(w.view() == "ffee: 0: 0: 0:24c3:3349:3cee: 143");
+ ep.setToLoopback(AF_INET6);
+ w.reset().print("{::a}", ep);
+ REQUIRE(w.view() == "::1");
+ REQUIRE(0 == ats_ip_pton(addr_3, &ep.sa));
+ w.reset().print("{::a}", ep);
+ REQUIRE(w.view() == "1337:ded:beef::");
+ REQUIRE(0 == ats_ip_pton(addr_4, &ep.sa));
+ w.reset().print("{::a}", ep);
+ REQUIRE(w.view() == "1337::ded:beef");
+
+ REQUIRE(0 == ats_ip_pton(addr_5, &ep.sa));
+ w.reset().print("{:X:a}", ep);
+ REQUIRE(w.view() == "1337::DED:BEEF:0:0:956");
+
+ REQUIRE(0 == ats_ip_pton(addr_6, &ep.sa));
+ w.reset().print("{::a}", ep);
+ REQUIRE(w.view() == "1337:0:0:ded:beef::");
+
+ REQUIRE(0 == ats_ip_pton(addr_null, &ep.sa));
+ w.reset().print("{::a}", ep);
+ REQUIRE(w.view() == "::");
+
+ REQUIRE(0 == ats_ip_pton(addr_2, &ep.sa));
+ w.reset().print("{::a}", ep);
+ REQUIRE(w.view() == addr_2.substr(0, 13));
+ w.reset().print("{0::a}", ep);
+ REQUIRE(w.view() == addr_2.substr(0, 13));
+ w.reset().print("{::ap}", ep);
+ REQUIRE(w.view() == addr_2);
+ w.reset().print("{::f}", ep);
+ REQUIRE(w.view() == IP_PROTO_TAG_IPV4);
+ w.reset().print("{::fpa}", ep);
+ REQUIRE(w.view() == "172.17.99.231:23995 ipv4");
+ w.reset().print("{0::a} .. {0::p}", ep);
+ REQUIRE(w.view() == "172.17.99.231 .. 23995");
+ w.reset().print("<+> {0::a} <+> {0::p}", ep);
+ REQUIRE(w.view() == "<+> 172.17.99.231 <+> 23995");
+ w.reset().print("<+> {0::a} <+> {0::p} <+>", ep);
+ REQUIRE(w.view() == "<+> 172.17.99.231 <+> 23995 <+>");
+ w.reset().print("{:: =a}", ep);
+ REQUIRE(w.view() == "172. 17. 99.231");
+ w.reset().print("{::=a}", ep);
+ REQUIRE(w.view() == "172.017.099.231");
+
+ // Documentation examples
+ REQUIRE(0 == ats_ip_pton(addr_7, &ep.sa));
+ w.reset().print("To {}", ep);
+ REQUIRE(w.view() == "To 172.19.3.105:4951");
+ w.reset().print("To {0::a} on port {0::p}", ep); // no need to pass the argument twice.
+ REQUIRE(w.view() == "To 172.19.3.105 on port 4951");
+ w.reset().print("To {::=}", ep);
+ REQUIRE(w.view() == "To 172.019.003.105:04951");
+ w.reset().print("{::a}", ep);
+ REQUIRE(w.view() == "172.19.3.105");
+ w.reset().print("{::=a}", ep);
+ REQUIRE(w.view() == "172.019.003.105");
+ w.reset().print("{::0=a}", ep);
+ REQUIRE(w.view() == "172.019.003.105");
+ w.reset().print("{:: =a}", ep);
+ REQUIRE(w.view() == "172. 19. 3.105");
+ w.reset().print("{:>20:a}", ep);
+ REQUIRE(w.view() == " 172.19.3.105");
+ w.reset().print("{:>20:=a}", ep);
+ REQUIRE(w.view() == " 172.019.003.105");
+ w.reset().print("{:>20: =a}", ep);
+ REQUIRE(w.view() == " 172. 19. 3.105");
+ w.reset().print("{:<20:a}", ep);
+ REQUIRE(w.view() == "172.19.3.105 ");
+
+ w.reset().print("{:p}", reinterpret_cast<sockaddr const *>(0x1337beef));
+ REQUIRE(w.view() == "0x1337beef");
+}
diff --git a/proxy/http/HttpDebugNames.h b/proxy/http/HttpDebugNames.h
index ed7d7aa..ae76737 100644
--- a/proxy/http/HttpDebugNames.h
+++ b/proxy/http/HttpDebugNames.h
@@ -24,6 +24,7 @@
#pragma once
#include "HttpTransact.h"
+#include <ts/BufferWriter.h>
class HttpDebugNames
{
@@ -35,3 +36,43 @@ public:
static const char *get_api_hook_name(TSHttpHookID t);
static const char *get_server_state_name(HttpTransact::ServerState_t state);
};
+
+inline ts::BufferWriter &
+bwformat(ts::BufferWriter &w, ts::BWFSpec const &spec, HttpTransact::ServerState_t state)
+{
+ if (spec.has_numeric_type()) {
+ return bwformat(w, spec, static_cast<uintmax_t>(state));
+ } else {
+ return bwformat(w, spec, HttpDebugNames::get_server_state_name(state));
+ }
+}
+
+inline ts::BufferWriter &
+bwformat(ts::BufferWriter &w, ts::BWFSpec const &spec, HttpTransact::CacheAction_t state)
+{
+ if (spec.has_numeric_type()) {
+ return bwformat(w, spec, static_cast<uintmax_t>(state));
+ } else {
+ return bwformat(w, spec, HttpDebugNames::get_cache_action_name(state));
+ }
+}
+
+inline ts::BufferWriter &
+bwformat(ts::BufferWriter &w, ts::BWFSpec const &spec, HttpTransact::StateMachineAction_t state)
+{
+ if (spec.has_numeric_type()) {
+ return bwformat(w, spec, static_cast<uintmax_t>(state));
+ } else {
+ return bwformat(w, spec, HttpDebugNames::get_action_name(state));
+ }
+}
+
+inline ts::BufferWriter &
+bwformat(ts::BufferWriter &w, ts::BWFSpec const &spec, TSHttpHookID id)
+{
+ if (spec.has_numeric_type()) {
+ return bwformat(w, spec, static_cast<uintmax_t>(id));
+ } else {
+ return bwformat(w, spec, HttpDebugNames::get_api_hook_name(id));
+ }
+}
diff --git a/proxy/http/HttpProxyServerMain.cc b/proxy/http/HttpProxyServerMain.cc
index 56ea50a..5e40a4d 100644
--- a/proxy/http/HttpProxyServerMain.cc
+++ b/proxy/http/HttpProxyServerMain.cc
@@ -58,6 +58,28 @@ bool et_net_threads_ready = false;
extern int num_of_net_threads;
extern int num_accept_threads;
+/// Global BufferWriter format name functions.
+namespace
+{
+void
+TS_bwf_thread(ts::BufferWriter &w, ts::BWFSpec const &spec)
+{
+ bwformat(w, spec, this_thread());
+}
+void
+TS_bwf_ethread(ts::BufferWriter &w, ts::BWFSpec const &spec)
+{
+ bwformat(w, spec, this_ethread());
+}
+} // namespace
+
+// File / process scope initializations
+static bool HTTP_SERVER_INITIALIZED __attribute__((unused)) = []() -> bool {
+ ts::bwf_register_global("ts-thread", &TS_bwf_thread);
+ ts::bwf_register_global("ts-ethread", &TS_bwf_ethread);
+ return true;
+}();
+
bool
ssl_register_protocol(const char *protocol, Continuation *contp)
{
--
To stop receiving notification emails like this one, please contact
amc@apache.org.