Skip to content
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

New %codes for computing message forwarding delays #260

Open
wants to merge 45 commits into
base: bag64a
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
334d87a
Incomplete and messy feature documentation draft
rousskov Jun 28, 2024
f19be20
fixup: Simplified and generalized
rousskov Jun 28, 2024
03361d8
Exclude various errors to KISS
rousskov Jul 5, 2024
98ccecb
fixup: Clarified adaptation timing w.r.t. byte events
rousskov Jul 5, 2024
b34e422
Switched from _byte to _read terms to be able to add _write events
rousskov Jul 5, 2024
e843568
Reduce "first request" vs "first read" ambiguity
rousskov Jul 5, 2024
44b7842
Document all 8 cases: send/receive first/last request/response byte
rousskov Jul 5, 2024
fa1b0ad
Added %request_first_read_time and %request_last_read_time codes
eduard-bagdasaryan Jul 15, 2024
330ef13
Introduced a new MessageTimer class
eduard-bagdasaryan Jul 18, 2024
c410a1c
Simplified by removing ConnStateData::requestTimer
eduard-bagdasaryan Jul 20, 2024
8a8da4b
Added %request_first_write_time and %request_last_write_time codes
eduard-bagdasaryan Jul 22, 2024
380f33a
Polished
eduard-bagdasaryan Jul 22, 2024
bfb0102
Removed an unused type
eduard-bagdasaryan Jul 22, 2024
a4f5a99
Reworked 8a8da4b, covering some missed FTP cases
eduard-bagdasaryan Jul 23, 2024
e4fad62
Cleanup
eduard-bagdasaryan Jul 23, 2024
38038ad
Added %response_first_read_time and %response_last_read_time codes
eduard-bagdasaryan Jul 24, 2024
109de07
Update requestReadTimer when uploading FTP data
eduard-bagdasaryan Jul 25, 2024
507ab43
Update responseReadTimer on EOF
eduard-bagdasaryan Jul 25, 2024
38daef3
Update requestReadTimer on EOF
eduard-bagdasaryan Jul 27, 2024
57fda97
Update timers on EOF in tunnel.cc
eduard-bagdasaryan Jul 27, 2024
a9c51e7
Added %response_first_write_time and %response_last_write_time codes
eduard-bagdasaryan Jul 27, 2024
3b23fd3
Added a missing WrittenToClient() in FTP
eduard-bagdasaryan Jul 28, 2024
0a19226
Added a TODO
eduard-bagdasaryan Jul 28, 2024
626598c
Addressed a TODO about duplication
eduard-bagdasaryan Aug 9, 2024
8865b6f
Update requestReadTimer as soon as possible
eduard-bagdasaryan Aug 11, 2024
bf09e96
Adjusted new format codes to be compatible with %tS
eduard-bagdasaryan Aug 12, 2024
7b4134b
Undo out-of-scope statCounter improvements
rousskov Aug 13, 2024
c0dd69b
fixup: Simplified and fixed ClientHttpRequest(nullptr) crashes
rousskov Aug 13, 2024
7ab3b89
WIP: A failed attempt to time end-of-request discovery
rousskov Aug 13, 2024
ed0be1a
Revert "WIP: A failed attempt to time end-of-request discovery"
rousskov Aug 14, 2024
88c458c
Unify *ReadTimer.update() calls
rousskov Aug 14, 2024
3fb4e85
fixup: Simplified a bit
rousskov Aug 14, 2024
ff53998
Unified *WriteTimer.update() calls
rousskov Aug 14, 2024
235e422
fixup: Treat FTP data connection establishment as I/O
rousskov Aug 14, 2024
692aaf8
fixup: Polished EOF-without-transactions event description
rousskov Aug 14, 2024
12d9909
fixup: I/O event timings should not depend on Store state
rousskov Aug 14, 2024
55e55d7
Go back to 'first request byte' timestamp
rousskov Aug 14, 2024
9d3b7e5
fixup: Do not assume FwdState::al is always available
rousskov Aug 14, 2024
6e9189f
fixup: Time knowledge is optional, but we know what Time is
rousskov Aug 15, 2024
d8af1f5
fixup: Fixed timer name mixed up by my branch commit 88c458cd
rousskov Aug 15, 2024
9dd2725
Renamed and redocumented new %codes to better match code
rousskov Aug 15, 2024
ba8d04b
Remove timer updates from Http::Tunneler
rousskov Aug 15, 2024
b70587b
Update timers on control messages
eduard-bagdasaryan Aug 22, 2024
cfff89f
Update responseWriteTimer only if the control message was sent
eduard-bagdasaryan Aug 26, 2024
3317bda
Polished
eduard-bagdasaryan Aug 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions src/AccessLogEntry.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,45 @@
#include "ssl/support.h"
#endif

#include <optional>

/* forward decls */
class HttpReply;
class HttpRequest;
class CustomLog;

// TODO: move
/// Accumulates timings of message IO events.
class MessageTimer
{
public:
using Clock = std::chrono::system_clock;
using Time = Clock::time_point;

MessageTimer() = default;
explicit MessageTimer(const Time &s) : first(s), last(s) {}

/// sets firstTime (if uninitialized yet) and lastTime (always) to the current time
void update() {
last = Clock::now();
if (!first)
first = last;
}

/// removes firstTime and lastTime values
void reset() { *this = MessageTimer(); }

/// the time of the first IO for the message
auto firstTime() const { return first; }

/// the time of the last IO for the message
auto lastTime() const { return last; }

private:
std::optional<Time> first;
std::optional<Time> last;
};

class AccessLogEntry: public CodeContext
{

Expand Down Expand Up @@ -164,6 +198,11 @@ class AccessLogEntry: public CodeContext
Security::CertPointer sslClientCert; ///< cert received from the client
#endif
AnyP::PortCfgPointer port;

MessageTimer requestReadTimer; ///< first/last IO when receiving request from the client
MessageTimer responseWriteTimer; ///< first/last IO when writing response to the client
MessageTimer requestWriteTimer; ///< first/last IO when writing request to the peer
MessageTimer responseReadTimer; ///< first/last IO when reading response from the peer
} cache;

/** \brief This subclass holds log info for various headers in raw format
Expand Down
3 changes: 3 additions & 0 deletions src/FwdState.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1063,6 +1063,9 @@ FwdState::successfullyConnectedToPeer(const Comm::ConnectionPointer &conn)

NoteOutgoingConnectionSuccess(serverConnection()->getPeer());

if (al)
al->cache.requestWriteTimer.reset();

dispatch();
}

Expand Down
4 changes: 2 additions & 2 deletions src/HttpControlMsg.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
#include "HttpControlMsg.h"

void
HttpControlMsgSink::doneWithControlMsg()
HttpControlMsgSink::doneWithControlMsg(const bool)
{
if (cbControlMsgSent) {
ScheduleCallHere(cbControlMsgSent);
Expand All @@ -28,7 +28,7 @@ HttpControlMsgSink::wroteControlMsg(const CommIoCbParams &params)
return;

if (params.flag == Comm::OK) {
doneWithControlMsg();
doneWithControlMsg(true);
return;
}

Expand Down
3 changes: 2 additions & 1 deletion src/HttpControlMsg.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ class HttpControlMsgSink: public virtual AsyncJob
/// called to send the 1xx message and notify the Source
virtual void sendControlMsg(HttpControlMsg msg) = 0;

virtual void doneWithControlMsg();
/// \param wasSent whether the message was written to the client
virtual void doneWithControlMsg(bool wasSent);

/// callback to handle Comm::Write completion
void wroteControlMsg(const CommIoCbParams &);
Expand Down
3 changes: 0 additions & 3 deletions src/auth/basic/NIS/nis_support.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@
#ifndef SQUID_SRC_AUTH_BASIC_NIS_NIS_SUPPORT_H
#define SQUID_SRC_AUTH_BASIC_NIS_NIS_SUPPORT_H

#ifndef SQUID_SRC_AUTH_BASIC_NIS_NIS_SUPPORT_H
#define SQUID_SRC_AUTH_BASIC_NIS_NIS_SUPPORT_H

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is out of scope - it fixes a build error.

extern char * get_nis_password(char *user, char *nisdomain, char *nismap);

#endif /* SQUID_SRC_AUTH_BASIC_NIS_NIS_SUPPORT_H */
Expand Down
87 changes: 87 additions & 0 deletions src/cf.data.pre
Original file line number Diff line number Diff line change
Expand Up @@ -4868,6 +4868,93 @@ DOC_START
values may significantly understate or exaggerate actual times.
Do not use this measurement unless you know it works in your case.

The following x_time %codes produce approximate absolute time of event
x in <full seconds since epoch>.<fractional seconds> format. Squid
should remember these events when they happen, but various temporary
implementation deficiencies may result in information loss for
unsuccessful transactions. Using logged times for erroneous
transaction analysis requires understanding of these Squid-version
specific limitations and is not recommended.

For CONNECT tunnels, all bytes (and a successful connection closure
indication) received from the client and forwarded to the server are
considered to be a part of the CONNECT request, while all bytes (and a
successful connection closure indication) received from the server and
forwarded to the client are considered to be a part of the CONNECT
response.

A failed socket read(2) or write(2) call is normally not reflected in
these %codes. Such I/O failures usually result in transaction errors.

If Squid received (or sent) the entire message in one network I/O (or
equivalent), then the corresponding x_first_byte_time value will be
the same as x_last_byte_time value.

received_request_first_byte_time: Squid starts parsing an incoming
client HTTP or FTP request. For requests generated by Squid (e.g.,
to download missing intermediate certificates or to fake a CONNECT
request during SslBump operations), this time is the time of that
request generation.

In most cases, Squid receives the entire request header in one
network read(2), but it is possible that the header will not be
fully received (and parsed) until a later time. Thus, this %code
time does not always correspond to the time when Squid starts
evaluating (beyond basic parsing) and forwarding a request.

In most cases, a client does not send the next request on a
client-to-Squid connection until Squid responds to the previous
one, but it is possible that Squid reads bytes belonging to
multiple requests in one network read(2). Squid may also read
PROXY protocol or TLS messages before reading HTTP or FTP request
bytes. In such and similar cases, this %code value does not match
the last read(2) time because Squid has to spend time on handling
other read bytes first.

Currently, this time may not match %tS because %tS timestamps is
created when the entire request header has been parsed.

Client-to-Squid TCP connection acceptance and client-Squid TLS
handshake (if any) happen earlier. REQMOD adaptation of read bytes
(if any) happens later.

received_request_last_byte_time: The last time Squid received HTTP or
FTP bytes (or a successful connection closure indication)
associated with the current request.

sent_request_first_byte_time: Squid successfully sent the first HTTP
or FTP request byte (at least) to an origin server or cache_peer.

If Squid retries or re-forwards a failed request, then this %code
reflects the _last_ such attempt.

Any Squid-to-peer TCP connection establishment, CONNECT tunnel
establishment, and TLS handshake happen earlier. RESPMOD
adaptation happens later.

sent_request_last_byte_time: The last time Squid successfully sent
HTTP or FTP bytes associated with the current request. See
%sent_request_first_byte_time %code for more caveats.

received_response_first_byte_time: Squid successfully received the
first HTTP or FTP response byte from an origin server or
cache_peer.

If Squid retries or re-forwards a failed request, then this %code
reflects the response in the _last_ such attempt.

received_response_last_byte_time: The last time Squid received HTTP or
FTP bytes (or a successful connection closure indication)
associated with the current response. See
%received_response_first_byte_time %code for more caveats.

sent_response_first_byte_time: Squid successfully sent the first HTTP
or FTP response byte (at least) to the client.

sent_response_last_byte_time: The last time Squid successfully sent
HTTP or FTP bytes associated with the current response to the
client. See %sent_response_first_byte_time %code for more caveats.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR implementation differs from the proposed definitions (for request_first_read/request_last_read):

request_first_read

  1. valid requests: Just after request parsing starts (parseOneRequest())
  2. invalid requests (created by abortRequestParsing(): Same as (1)
  3. fake requests: When the fake request is created (buildFakeRequest())

request_last_read

  1. valid requests: request_first_read (for parsed complete requests) or just after it reads the last request data block from the client socket (for parsed incomplete requests)
  2. invalid requests (created by abortRequestParsing(): same as request_first_read
  3. fake requests: When the fake request is created (buildFakeRequest())

request_first_write
Just after the first request data block is written to the peer socket (CommIoCbParams/IOCB callbacks)

request_last_write
Just after the last request data block is written to the peer socket (CommIoCbParams/IOCB callbacks)

response_first_read
Just after the first response data block is read from the peer socket (CommIoCbParams/IOCB callbacks)

response_last_read
Just after the last response data block is read from the peer socket (CommIoCbParams/IOCB callbacks)

response_first_write
Just after the first request data block is written to the client socket (CommIoCbParams/IOCB callbacks)

response_last_write
Just after the last request data block is written to the client socket (CommIoCbParams/IOCB callbacks)


We could simplify request_first_read/request_last_read calculation, ignoring parsing time and measuring them from ClientHttpRequest constructor. However, this approach would minimize request_first_read/request_last_read difference in some cases, concealing this useful information from the admin.

Access Control related format codes:

et Tag returned by external acl
Expand Down
20 changes: 17 additions & 3 deletions src/client_side.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1026,6 +1026,8 @@ ConnStateData::afterClientWrite(size_t size)
if (pipeline.empty())
return;

currentWriter().al->cache.responseWriteTimer.update();

auto ctx = pipeline.front();
if (size) {
statCounter.client_http.kbytes_out += size;
Expand Down Expand Up @@ -1936,6 +1938,13 @@ ConnStateData::clientParseRequests()
assert(!preservingClientData_);
}

// The "first byte" time of a (possibly pipelined) request is defined as
// the time we _start_ parsing its header. If requestFirstByteTime is
// set, then do nothing as we continue to parse a request header we
// started to parse earlier. Otherwise, remember the time we started.
if (!requestFirstByteTime)
requestFirstByteTime = MessageTimer::Clock::now();

if (Http::StreamPointer context = parseOneRequest()) {
debugs(33, 5, clientConnection << ": done parsing a request");
extendLifetime();
Expand Down Expand Up @@ -3723,7 +3732,7 @@ ConnStateData::sendControlMsg(HttpControlMsg msg)

if (!writeControlMsgAndCall(rep.getRaw(), call)) {
// but still inform the caller (so it may resume its operation)
doneWithControlMsg();
doneWithControlMsg(false);
}
return;
}
Expand All @@ -3733,9 +3742,11 @@ ConnStateData::sendControlMsg(HttpControlMsg msg)
}

void
ConnStateData::doneWithControlMsg()
ConnStateData::doneWithControlMsg(const bool wasSent)
{
HttpControlMsgSink::doneWithControlMsg();
if (wasSent && HttpControlMsgSink::cbControlMsgSent)
currentWriter().al->cache.responseWriteTimer.update();
HttpControlMsgSink::doneWithControlMsg(wasSent);

if (Http::StreamPointer deferredRequest = pipeline.front()) {
debugs(33, 3, clientConnection << ": calling PushDeferredIfNeeded after control msg wrote");
Expand Down Expand Up @@ -3900,6 +3911,9 @@ ConnStateData::clientPinnedConnectionRead(const CommIoCbParams &io)
if (io.flag == Comm::ERR_CLOSING)
return; // close handler will clean up

// AccessLogEntry::CacheDetails::responseReadTimer is not updated here
// because idle connections do not have an associated transaction.

Must(pinning.serverConnection == io.conn);

#if USE_OPENSSL
Expand Down
7 changes: 6 additions & 1 deletion src/client_side.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#ifndef SQUID_SRC_CLIENT_SIDE_H
#define SQUID_SRC_CLIENT_SIDE_H

#include "AccessLogEntry.h"
#include "acl/ChecklistFiller.h"
#include "base/RunnersRegistry.h"
#include "clientStreamForward.h"
Expand Down Expand Up @@ -95,7 +96,7 @@ class ConnStateData:

/* HttpControlMsgSink API */
void sendControlMsg(HttpControlMsg) override;
void doneWithControlMsg() override;
void doneWithControlMsg(bool) override;

/// Traffic parsing
bool clientParseRequests();
Expand Down Expand Up @@ -384,6 +385,10 @@ class ConnStateData:
/// managers logging of the being-accepted TLS connection secrets
Security::KeyLogger keyLogger;

/// The first time parseOneRequest() was called for the current request.
/// This member gets cleared when request headers are successfully parsed.
std::optional<MessageTimer::Time> requestFirstByteTime;

protected:
void startDechunkingRequest();
void finishDechunkingRequest(bool withSuccess);
Expand Down
24 changes: 24 additions & 0 deletions src/client_side_request.cc
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,25 @@ ClientRequestContext::ClientRequestContext(ClientHttpRequest *anHttp) :

CBDATA_CLASS_INIT(ClientHttpRequest);

/// computes effective ClientHttpRequest start time, purging ConnStateData cache
static auto
ExtractRequestStartTime(ConnStateData * const conn)
{
if (conn) {
// TODO: Create ClientHttpRequest earlier, when we _start_ parsing, to
// avoid temporary storing info in ConnStateData (among other problems).
if (auto &requestFirstByteTime = conn->requestFirstByteTime) {
const auto result = *requestFirstByteTime;
requestFirstByteTime.reset();
return result;
}
// else this is a "fake" request not triggered by header parsing
}
// else this is an "internal" request not associated with ConnStateData

return MessageTimer::Clock::now();
}

ClientHttpRequest::ClientHttpRequest(ConnStateData * aConn) :
#if USE_ADAPTATION
AsyncJob("ClientHttpRequest"),
Expand All @@ -133,7 +152,12 @@ ClientHttpRequest::ClientHttpRequest(ConnStateData * aConn) :
conn_(cbdataReference(aConn))
{
CodeContext::Reset(al);

// TODO: Adjust transaction start (%tS) definition and code to include
// header parsing, removing this field and using cache.requestReadTimer.
al->cache.start_time = current_time;
al->cache.requestReadTimer = MessageTimer(ExtractRequestStartTime(aConn));

if (aConn) {
al->tcpClient = aConn->clientConnection;
al->cache.port = aConn->port;
Expand Down
3 changes: 3 additions & 0 deletions src/clients/Client.cc
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,9 @@ Client::sentRequestBody(const CommIoCbParams &io)
return;
}

if (fwd->al)
fwd->al->cache.requestWriteTimer.update();

if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
abortOnData("store entry aborted while sending request body");
return;
Expand Down
9 changes: 9 additions & 0 deletions src/clients/FtpClient.cc
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,9 @@ Ftp::Client::readControlReply(const CommIoCbParams &io)
if (io.flag == Comm::ERR_CLOSING)
return;

if (io.flag == Comm::OK && fwd->al)
fwd->al->cache.responseReadTimer.update();

if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
if (abortOnData("entry aborted during control reply read"))
return;
Expand Down Expand Up @@ -872,6 +875,9 @@ Ftp::Client::writeCommandCallback(const CommIoCbParams &io)
/* failed closes ctrl.conn and frees ftpState */
return;
}

if (fwd->al)
fwd->al->cache.requestWriteTimer.update();
}

/// handler called by Comm when FTP control channel is closed unexpectedly
Expand Down Expand Up @@ -973,6 +979,9 @@ Ftp::Client::dataRead(const CommIoCbParams &io)

assert(io.fd == data.conn->fd);

if (io.flag == Comm::OK && fwd->al)
fwd->al->cache.responseReadTimer.update();

if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
abortOnData("entry aborted during dataRead");
return;
Expand Down
8 changes: 8 additions & 0 deletions src/format/ByteCode.h
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,14 @@ typedef enum {
LFT_TIME_LOCALTIME,
LFT_TIME_GMT,
LFT_TIME_START, // the time the master transaction started
LFT_RECEIVED_REQUEST_FIRST_BYTE_TIME,
LFT_RECEIVED_REQUEST_LAST_BYTE_TIME,
LFT_SENT_REQUEST_FIRST_BYTE_TIME,
LFT_SENT_REQUEST_LAST_BYTE_TIME,
LFT_RECEIVED_RESPONSE_FIRST_BYTE_TIME,
LFT_RECEIVED_RESPONSE_LAST_BYTE_TIME,
LFT_SENT_RESPONSE_FIRST_BYTE_TIME,
LFT_SENT_RESPONSE_LAST_BYTE_TIME,

/* processing time details */
LFT_TIME_TO_HANDLE_REQUEST,
Expand Down
Loading