]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: sample: accept_date / request_date return %Ts / %tr timestamp values
authorWilliam Lallemand <wlallemand@haproxy.org>
Thu, 13 Jul 2023 14:18:47 +0000 (16:18 +0200)
committerWilliam Lallemand <wlallemand@haproxy.org>
Mon, 24 Jul 2023 15:12:29 +0000 (17:12 +0200)
Implement %[accept_date] which returns the same as %Ts log-format tag.
Implement %[request_date] which is a timestamp for %tr.

accept_date and request_date take an faculative unit argument which can
be 's', 'ms' or 'us'.

The goal is to be able to convert these 2 timestamps to HAProxy date
format like its done with %T, %tr, %trg etc

doc/configuration.txt
src/tcp_sample.c

index b1947b24d51aa64ea154f6562b04c4b4e9273099..e125f59c9950c1f996d5bda1cc1ca5fd6c580134 100644 (file)
@@ -19760,6 +19760,24 @@ An optional table may be specified with the "sc*" form, in which case the
 currently tracked key will be looked up into this alternate table instead of
 the table currently being tracked.
 
+accept_date([<unit>]) : integer
+  This is the exact date when the connection was received by HAProxy
+  (which might be very slightly different from the date observed on the
+  network if there was some queuing in the system's backlog). This is usually
+  the same date which may appear in any upstream firewall's log. When used in
+  HTTP mode, the accept_date field will be reset to the first moment the
+  connection is ready to receive a new request (end of previous response for
+  HTTP/1, immediately after previous request for HTTP/2).
+
+  Returns a value in number of seconds since epoch.
+
+  <unit> is facultative, and can be set to "s" for seconds (default behavior),
+  "ms" for  milliseconds or "us" for microseconds.
+  If unit is set, return value is an integer reflecting either seconds,
+  milliseconds or microseconds since epoch.
+  It is useful when a time resolution of less than a second is needed.
+
+
 bc_dst : ip
   This is the destination ip address of the connection on the server side,
   which is the server address HAProxy connected to. It is of type IP and works
@@ -21885,6 +21903,19 @@ hdr([<name>[,<occ>]]) : string
   Note that contrary to the hdr() sample fetch method, the hdr_* ACL keywords
   unambiguously apply to the request headers.
 
+request_date([<unit>]) : integer
+  This is the exact date when the first byte of the HTTP request was received
+  by HAProxy (log-format tag %tr). This is computed from accept_date +
+  handshake time (%Th) + idle time (%Ti).
+
+  Returns a value in number of seconds since epoch.
+
+  <unit> is facultative, and can be set to "s" for seconds (default behavior),
+  "ms" for  milliseconds or "us" for microseconds.
+  If unit is set, return value is an integer reflecting either seconds,
+  milliseconds or microseconds since epoch.
+  It is useful when a time resolution of less than a second is needed.
+
 req.fhdr(<name>[,<occ>]) : string
   This returns the full value of the last occurrence of header <name> in an
   HTTP request. It differs from req.hdr() in that any commas present in the
index 8096e987c4909600e2493e5abf000f0bb7459343..9fbf920a258ba375ebd4fdcf0f925f263cf67025 100644 (file)
@@ -497,12 +497,91 @@ smp_fetch_fc_reordering(const struct arg *args, struct sample *smp, const char *
 #endif
 #endif // TCP_INFO
 
+/* Validates the data unit argument passed to "accept_date" fetch. Argument 0 support an
+ * optional string representing the unit of the result: "s" for seconds, "ms" for
+ * milliseconds and "us" for microseconds.
+ * Returns 0 on error and non-zero if OK.
+ */
+int smp_check_accept_date_unit(struct arg *args, char **err)
+{
+       if (args[0].type == ARGT_STR) {
+               long long int unit;
+
+               if (strcmp(args[0].data.str.area, "s") == 0) {
+                       unit = TIME_UNIT_S;
+               }
+               else if (strcmp(args[0].data.str.area, "ms") == 0) {
+                       unit = TIME_UNIT_MS;
+               }
+               else if (strcmp(args[0].data.str.area, "us") == 0) {
+                       unit = TIME_UNIT_US;
+               }
+               else {
+                       memprintf(err, "expects 's', 'ms' or 'us', got '%s'",
+                                 args[0].data.str.area);
+                       return 0;
+               }
+
+               chunk_destroy(&args[0].data.str);
+               args[0].type = ARGT_SINT;
+               args[0].data.sint = unit;
+       }
+       else if (args[0].type != ARGT_STOP) {
+               memprintf(err, "Unexpected arg type");
+               return 0;
+       }
+
+       return 1;
+}
+
+/* retrieve the accept or request date in epoch time, converts it to milliseconds
+ * or microseconds if asked to in optional args[1] unit param */
+static int
+smp_fetch_accept_date(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+       struct strm_logs *logs;
+       struct timeval tv;
+
+       if (!smp->strm)
+               return 0;
+
+       logs = &smp->strm->logs;
+
+       if (kw[0] == 'r') {  /* request_date */
+               tv_ms_add(&tv, &logs->accept_date, logs->t_idle >= 0 ? logs->t_idle + logs->t_handshake : 0);
+       } else {             /* accept_date */
+               tv.tv_sec = logs->accept_date.tv_sec;
+               tv.tv_usec = logs->accept_date.tv_usec;
+       }
+
+       smp->data.u.sint = tv.tv_sec;
+
+       /* report in milliseconds */
+       if (args[0].type == ARGT_SINT && args[0].data.sint == TIME_UNIT_MS) {
+               smp->data.u.sint *= 1000;
+               smp->data.u.sint += tv.tv_usec / 1000;
+       }
+       /* report in microseconds */
+       else if (args[0].type == ARGT_SINT && args[0].data.sint == TIME_UNIT_US) {
+               smp->data.u.sint *= 1000000;
+               smp->data.u.sint += tv.tv_usec;
+       }
+
+       smp->data.type = SMP_T_SINT;
+       smp->flags |= SMP_F_VOL_TEST | SMP_F_MAY_CHANGE;
+       return 1;
+}
+
 /* Note: must not be declared <const> as its list will be overwritten.
  * Note: fetches that may return multiple types should be declared using the
  * appropriate pseudo-type. If not available it must be declared as the lowest
  * common denominator, the type that can be casted into all other ones.
  */
 static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, {
+       /* timestamps */
+       { "accept_date", smp_fetch_accept_date,  ARG1(0,STR), smp_check_accept_date_unit, SMP_T_SINT, SMP_USE_L4CLI },
+       { "request_date", smp_fetch_accept_date,  ARG1(0,STR), smp_check_accept_date_unit, SMP_T_SINT, SMP_USE_HRQHP },
+
        { "bc_dst",      smp_fetch_dst,   0, NULL, SMP_T_ADDR, SMP_USE_L4SRV },
        { "bc_dst_port", smp_fetch_dport, 0, NULL, SMP_T_SINT, SMP_USE_L4SRV },
        { "bc_src",      smp_fetch_src,   0, NULL, SMP_T_ADDR, SMP_USE_L4SRV },