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