-
Notifications
You must be signed in to change notification settings - Fork 500
[EXPORTER] Optimize OTLP HTTP compression #3178
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ce73ba7
e6244f2
361e602
ed7d92b
60d253f
67ead79
c6abb4b
5b610f7
f9acefe
7e62e99
e2d6ed1
eca2f54
61e9a2b
34bd0bd
fadff90
2332906
135c6a0
d418b2d
a90f88f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,14 +3,17 @@ | |
|
||
#include <curl/curl.h> | ||
#include <curl/curlver.h> | ||
#include <algorithm> | ||
#include <array> | ||
#include <atomic> | ||
#include <chrono> | ||
#include <cstddef> | ||
#include <cstdint> | ||
#include <cstring> | ||
#include <list> | ||
#include <mutex> | ||
#include <string> | ||
#include <thread> | ||
#include <type_traits> | ||
#include <unordered_map> | ||
#include <unordered_set> | ||
#include <utility> | ||
|
@@ -57,11 +60,85 @@ nostd::shared_ptr<HttpCurlGlobalInitializer> HttpCurlGlobalInitializer::GetInsta | |
return shared_initializer; | ||
} | ||
|
||
#ifdef ENABLE_OTLP_COMPRESSION_PREVIEW | ||
// Original source: | ||
// https://stackoverflow.com/questions/12398377/is-it-possible-to-have-zlib-read-from-and-write-to-the-same-memory-buffer/12412863#12412863 | ||
int deflateInPlace(z_stream *strm, unsigned char *buf, uint32_t len, uint32_t *max_len) | ||
{ | ||
// must be large enough to hold zlib or gzip header (if any) and one more byte -- 11 works for the | ||
// worst case here, but if gzip encoding is used and a deflateSetHeader() call is inserted in this | ||
// code after the deflateReset(), then the 11 needs to be increased to accommodate the resulting | ||
// gzip header size plus one | ||
std::array<unsigned char, 11> temp{}; | ||
|
||
// kick start the process with a temporary output buffer -- this allows deflate to consume a large | ||
// chunk of input data in order to make room for output data there | ||
strm->next_in = buf; | ||
strm->avail_in = len; | ||
if (*max_len < len) | ||
{ | ||
*max_len = len; | ||
} | ||
strm->next_out = temp.data(); | ||
strm->avail_out = (std::min)(static_cast<decltype(z_stream::avail_out)>(temp.size()), *max_len); | ||
auto ret = deflate(strm, Z_FINISH); | ||
if (ret == Z_STREAM_ERROR) | ||
{ | ||
return ret; | ||
} | ||
|
||
// if we can, copy the temporary output data to the consumed portion of the input buffer, and then | ||
// continue to write up to the start of the consumed input for as long as possible | ||
auto have = strm->next_out - temp.data(); // number of bytes in temp[] | ||
if (have <= static_cast<decltype(have)>(strm->avail_in ? len - strm->avail_in : *max_len)) | ||
{ | ||
std::memcpy(buf, temp.data(), have); | ||
strm->next_out = buf + have; | ||
have = 0; | ||
while (ret == Z_OK) | ||
{ | ||
strm->avail_out = | ||
strm->avail_in ? strm->next_in - strm->next_out : (buf + *max_len) - strm->next_out; | ||
ret = deflate(strm, Z_FINISH); | ||
} | ||
if (ret != Z_BUF_ERROR || strm->avail_in == 0) | ||
{ | ||
*max_len = strm->next_out - buf; | ||
return ret == Z_STREAM_END ? Z_OK : ret; | ||
} | ||
} | ||
|
||
// the output caught up with the input due to insufficiently compressible data -- copy the | ||
// remaining input data into an allocated buffer and complete the compression from there to the | ||
// now empty input buffer (this will only occur for long incompressible streams, more than ~20 MB | ||
// for the default deflate memLevel of 8, or when *max_len is too small and less than the length | ||
// of the header plus one byte) | ||
auto hold = static_cast<std::remove_const_t<decltype(z_stream::next_in)>>( | ||
strm->zalloc(strm->opaque, strm->avail_in, 1)); // allocated buffer to hold input data | ||
if (hold == Z_NULL) | ||
{ | ||
return Z_MEM_ERROR; | ||
} | ||
std::memcpy(hold, strm->next_in, strm->avail_in); | ||
strm->next_in = hold; | ||
if (have) | ||
{ | ||
std::memcpy(buf, temp.data(), have); | ||
strm->next_out = buf + have; | ||
} | ||
strm->avail_out = (buf + *max_len) - strm->next_out; | ||
ret = deflate(strm, Z_FINISH); | ||
strm->zfree(strm->opaque, hold); | ||
*max_len = strm->next_out - buf; | ||
return ret == Z_OK ? Z_BUF_ERROR : (ret == Z_STREAM_END ? Z_OK : ret); | ||
} | ||
#endif // ENABLE_OTLP_COMPRESSION_PREVIEW | ||
|
||
void Session::SendRequest( | ||
std::shared_ptr<opentelemetry::ext::http::client::EventHandler> callback) noexcept | ||
{ | ||
is_session_active_.store(true, std::memory_order_release); | ||
std::string url = host_ + std::string(http_request_->uri_); | ||
const auto &url = host_ + http_request_->uri_; | ||
auto callback_ptr = callback.get(); | ||
bool reuse_connection = false; | ||
|
||
|
@@ -76,44 +153,47 @@ void Session::SendRequest( | |
if (http_request_->compression_ == opentelemetry::ext::http::client::Compression::kGzip) | ||
{ | ||
#ifdef ENABLE_OTLP_COMPRESSION_PREVIEW | ||
http_request_->AddHeader("Content-Encoding", "gzip"); | ||
|
||
opentelemetry::ext::http::client::Body compressed_body(http_request_->body_.size()); | ||
z_stream zs; | ||
zs.zalloc = Z_NULL; | ||
zs.zfree = Z_NULL; | ||
zs.opaque = Z_NULL; | ||
zs.avail_in = static_cast<uInt>(http_request_->body_.size()); | ||
zs.next_in = http_request_->body_.data(); | ||
zs.avail_out = static_cast<uInt>(compressed_body.size()); | ||
zs.next_out = compressed_body.data(); | ||
z_stream zs{}; | ||
zs.zalloc = Z_NULL; | ||
zs.zfree = Z_NULL; | ||
zs.opaque = Z_NULL; | ||
|
||
// ZLIB: Have to maually specify 16 bits for the Gzip headers | ||
const int window_bits = 15 + 16; | ||
static constexpr int kWindowBits = MAX_WBITS + 16; | ||
static constexpr int kMemLevel = MAX_MEM_LEVEL; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here, MAX_MEM_LEVEL appears to be 9 and, from the official documentation
The default level is not exposed, unfortunately (header zutil.h not available for including), but the code to drive the default is: #if MAX_MEM_LEVEL >= 8
# define DEF_MEM_LEVEL 8
#else
# define DEF_MEM_LEVEL MAX_MEM_LEVEL
#endif
/* default memLevel */ My intuition was to not hardcode the "default" value 8 and use the max level setting provided by the macro but I can revert this back to a hardcoded value of 8 as in the current compression code. |
||
|
||
int stream = | ||
deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, window_bits, 8, Z_DEFAULT_STRATEGY); | ||
auto stream = deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, kWindowBits, kMemLevel, | ||
Z_DEFAULT_STRATEGY); | ||
|
||
if (stream == Z_OK) | ||
{ | ||
deflate(&zs, Z_FINISH); | ||
deflateEnd(&zs); | ||
compressed_body.resize(zs.total_out); | ||
http_request_->SetBody(compressed_body); | ||
auto size = static_cast<uInt>(http_request_->body_.size()); | ||
auto max_size = size; | ||
stream = deflateInPlace(&zs, http_request_->body_.data(), size, &max_size); | ||
|
||
if (stream == Z_OK) | ||
{ | ||
http_request_->AddHeader("Content-Encoding", "gzip"); | ||
http_request_->body_.resize(max_size); | ||
} | ||
} | ||
else | ||
|
||
if (stream != Z_OK) | ||
{ | ||
if (callback) | ||
{ | ||
callback->OnEvent(opentelemetry::ext::http::client::SessionState::CreateFailed, ""); | ||
callback->OnEvent(opentelemetry::ext::http::client::SessionState::CreateFailed, | ||
zs.msg ? zs.msg : ""); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is just a precaution; the documentation states that The list of possible messages to be reported: z_const char * const z_errmsg[10] = {
(z_const char *)"need dictionary", /* Z_NEED_DICT 2 */
(z_const char *)"stream end", /* Z_STREAM_END 1 */
(z_const char *)"", /* Z_OK 0 */
(z_const char *)"file error", /* Z_ERRNO (-1) */
(z_const char *)"stream error", /* Z_STREAM_ERROR (-2) */
(z_const char *)"data error", /* Z_DATA_ERROR (-3) */
(z_const char *)"insufficient memory", /* Z_MEM_ERROR (-4) */
(z_const char *)"buffer error", /* Z_BUF_ERROR (-5) */
(z_const char *)"incompatible version",/* Z_VERSION_ERROR (-6) */
(z_const char *)""
}; |
||
} | ||
is_session_active_.store(false, std::memory_order_release); | ||
} | ||
|
||
deflateEnd(&zs); | ||
#else | ||
OTEL_INTERNAL_LOG_ERROR( | ||
"[HTTP Client Curl] Set WITH_OTLP_HTTP_COMPRESSION=ON to use gzip compression with the " | ||
"OTLP HTTP Exporter"); | ||
#endif | ||
#endif // ENABLE_OTLP_COMPRESSION_PREVIEW | ||
} | ||
|
||
curl_operation_.reset(new HttpOperation( | ||
|
@@ -226,7 +306,7 @@ HttpClient::~HttpClient() | |
std::shared_ptr<opentelemetry::ext::http::client::Session> HttpClient::CreateSession( | ||
nostd::string_view url) noexcept | ||
{ | ||
auto parsedUrl = common::UrlParser(std::string(url)); | ||
const auto parsedUrl = common::UrlParser(std::string(url)); | ||
if (!parsedUrl.success_) | ||
{ | ||
return std::make_shared<Session>(*this); | ||
|
Uh oh!
There was an error while loading. Please reload this page.