File indexing completed on 2024-04-28 05:41:19

0001 /*
0002     SPDX-FileCopyrightText: 2018 Milian Wolff <mail@milianw.de>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-or-later
0005 */
0006 
0007 #ifndef LINEWRITER_H
0008 #define LINEWRITER_H
0009 
0010 #include <algorithm>
0011 #include <iterator>
0012 #include <memory>
0013 #include <type_traits>
0014 
0015 #include <cassert>
0016 #include <climits>
0017 #include <cmath>
0018 #include <cstring>
0019 #include <string>
0020 
0021 #include <errno.h>
0022 #include <unistd.h>
0023 
0024 /**
0025  * Custom buffered I/O writer for high performance and signal safety
0026  * See e.g.: https://bugs.kde.org/show_bug.cgi?id=393387
0027  */
0028 class LineWriter
0029 {
0030 public:
0031     enum
0032     {
0033         BUFFER_CAPACITY = PIPE_BUF
0034     };
0035 
0036     LineWriter(int fd)
0037         : fd(fd)
0038         , buffer(new char[BUFFER_CAPACITY])
0039     {
0040         memset(buffer.get(), 0, BUFFER_CAPACITY);
0041     }
0042 
0043     ~LineWriter()
0044     {
0045         close();
0046     }
0047 
0048     /**
0049      * write an arbitrarily formatted string to the buffer
0050      */
0051     template <typename... T>
0052     bool write(const char* fmt, T... args)
0053     {
0054         enum
0055         {
0056             FirstTry,
0057             SecondTry
0058         };
0059         for (auto i : {FirstTry, SecondTry}) {
0060             const auto available = availableSpace();
0061             int ret = snprintf(out(), available, fmt, args...);
0062 
0063             if (ret < 0) {
0064                 // snprintf failure
0065                 return false;
0066             } else if (static_cast<unsigned>(ret) < available) {
0067                 // success
0068                 bufferSize += ret;
0069                 return true;
0070             }
0071 
0072             // message didn't fit into available space
0073             if (i == SecondTry || static_cast<unsigned>(ret) > BUFFER_CAPACITY) {
0074                 // message doesn't fit into BUFFER_CAPACITY - this should never happen
0075                 assert(false && "message doesn't fit into buffer");
0076                 errno = EFBIG;
0077                 return false;
0078             }
0079 
0080             if (!flush()) {
0081                 // write failed to flush
0082                 return false;
0083             } // else try again after flush
0084         }
0085 
0086         // the loop above should have returned already
0087         __builtin_unreachable();
0088         return false;
0089     }
0090 
0091     /**
0092      * write an arbitrary string literal to the buffer
0093      */
0094     bool write(const char* line)
0095     {
0096         // TODO: could be optimized to use strncpy or similar, but this is rarely called
0097         return write("%s", line);
0098     }
0099 
0100     /**
0101      * write an arbitrary string literal to the buffer
0102      */
0103     bool write(const std::string& line)
0104     {
0105         const auto length = line.length();
0106 
0107         {
0108             // first write the size of the string
0109             constexpr const int maxHexCharsForSize = 16 + 1; // 2^64 == 16^16 + 1 space
0110             if (availableSpace() < maxHexCharsForSize && !flush())
0111                 return false;
0112 
0113             const auto start = out();
0114             auto end = writeHexNumber(start, length);
0115             *end = ' ';
0116             ++end;
0117             bufferSize += (end - start);
0118         }
0119 
0120         if (availableSpace() < length) {
0121             if (!flush()) {
0122                 return false;
0123             }
0124             if (availableSpace() < length) {
0125                 int ret = 0;
0126                 do {
0127                     ret = ::write(fd, line.data(), length);
0128                 } while (ret < 0 && errno == EINTR);
0129                 return ret >= 0;
0130             }
0131         }
0132         memcpy(out(), line.data(), length);
0133         bufferSize += length;
0134         return true;
0135     }
0136 
0137     /**
0138      * write one of the common heaptrack output lines to the buffer
0139      *
0140      * @arg type char that identifies the type of the line
0141      * @arg args are all printed as hex numbers without leading 0x prefix
0142      *
0143      * e.g.: i 561072a1cf63 1 1c 18 70
0144      */
0145     template <typename... T>
0146     bool writeHexLine(const char type, T... args)
0147     {
0148         constexpr const int numArgs = sizeof...(T);
0149         constexpr const int maxHexCharsPerArg = 16; // 2^64 == 16^16
0150         constexpr const int maxCharsForArgs = numArgs * maxHexCharsPerArg;
0151         constexpr const int spaceCharsForArgs = numArgs;
0152         constexpr const int otherChars = 2; // type char and newline at end
0153         constexpr const int totalMaxChars = otherChars + maxCharsForArgs + spaceCharsForArgs + otherChars;
0154         static_assert(totalMaxChars < BUFFER_CAPACITY, "cannot write line larger than buffer capacity");
0155 
0156         if (totalMaxChars > availableSpace() && !flush()) {
0157             return false;
0158         }
0159 
0160         auto* buffer = out();
0161         const auto* start = buffer;
0162 
0163         *buffer = type;
0164         ++buffer;
0165 
0166         *buffer = ' ';
0167         ++buffer;
0168 
0169         buffer = writeHexNumbers(buffer, args...);
0170 
0171         *buffer = '\n';
0172         ++buffer;
0173 
0174         bufferSize += buffer - start;
0175 
0176         return true;
0177     }
0178 
0179     inline static unsigned clz(unsigned V)
0180     {
0181         return __builtin_clz(V);
0182     }
0183 
0184     inline static unsigned clz(long unsigned V)
0185     {
0186         return __builtin_clzl(V);
0187     }
0188 
0189     inline static unsigned clz(long long unsigned V)
0190     {
0191         return __builtin_clzll(V);
0192     }
0193 
0194     template <typename V>
0195     static char* writeHexNumber(char* buffer, V value)
0196     {
0197         static_assert(std::is_unsigned<V>::value, "can only convert unsigned numbers to hex");
0198 
0199         constexpr const unsigned numBits = sizeof(value) * 8;
0200         static_assert(numBits <= 64, "only up to 64bit of input are supported");
0201 
0202         // clz is undefined for 0, so handle that manually
0203         const unsigned zeroBits = value ? clz(value) : numBits;
0204         assert(zeroBits <= numBits);
0205 
0206         const unsigned usedBits = numBits - zeroBits;
0207         // 2^(usedBits) = 16^(requiredBufSize) = 2^(4 * requiredBufSize)
0208         // usedBits = 4 * requiredBufSize
0209         // requiredBufSize = usedBits / 4
0210         // but we need to round up, so actually use (usedBits + 3) / 4
0211         // and for 0 input, we still need one char, so use at least 1
0212         const unsigned requiredBufSize = std::max(1u, (usedBits + 3) / 4);
0213         assert(requiredBufSize <= 16);
0214 
0215         constexpr const char hexChars[16] = {'0', '1', '2', '3', '4', '5', '6', '7',
0216                                              '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
0217 
0218         char* out = buffer + requiredBufSize - 1;
0219         while (value >= 16) {
0220             const auto rest = value % 16;
0221             value /= 16;
0222 
0223             *out = hexChars[rest];
0224             --out;
0225         }
0226         *out = hexChars[value];
0227         assert(out == buffer);
0228 
0229         return buffer + requiredBufSize;
0230     }
0231 
0232     template <typename V>
0233     static char* writeHexNumbers(char* buffer, V value)
0234     {
0235         return writeHexNumber(buffer, value);
0236     }
0237 
0238     template <typename V, typename... T>
0239     static char* writeHexNumbers(char* buffer, V value, T... args)
0240     {
0241         buffer = writeHexNumber(buffer, value);
0242         *buffer = ' ';
0243         ++buffer;
0244         return writeHexNumbers(buffer, args...);
0245     }
0246 
0247     bool flush()
0248     {
0249         if (!canWrite()) {
0250             return false;
0251         } else if (!bufferSize) {
0252             return true;
0253         }
0254 
0255         int ret = 0;
0256         do {
0257             ret = ::write(fd, buffer.get(), bufferSize);
0258         } while (ret < 0 && errno == EINTR);
0259 
0260         if (ret < 0) {
0261             return false;
0262         }
0263 
0264         bufferSize = 0;
0265 
0266         return true;
0267     }
0268 
0269     bool canWrite() const
0270     {
0271         return fd != -1;
0272     }
0273 
0274     void close()
0275     {
0276         if (fd != -1) {
0277             ::close(fd);
0278             fd = -1;
0279         }
0280     }
0281 
0282 private:
0283     size_t availableSpace() const
0284     {
0285         return BUFFER_CAPACITY - bufferSize;
0286     }
0287 
0288     char* out()
0289     {
0290         return buffer.get() + bufferSize;
0291     }
0292 
0293     int fd = -1;
0294     unsigned bufferSize = 0;
0295     std::unique_ptr<char[]> buffer;
0296 };
0297 
0298 #endif