]> git.kaiwu.me - haproxy.git/commitdiff
MINOR: haload: import source code and documentation master
authorFrederic Lecaille <flecaille@haproxy.com>
Thu, 19 Mar 2026 17:27:48 +0000 (18:27 +0100)
committerFrederic Lecaille <flecaille@haproxy.com>
Wed, 1 Jul 2026 13:22:14 +0000 (15:22 +0200)
This patch imports the implementation of haload, a lightweight,
multi-threaded traffic generator designed to benchmark HTTP infrastructures
under heavy loads. Built onto HAProxy's highly scalable
architecture, it natively supports HTTP/1, HTTP/2, and HTTP/3 (QUIC).

It uses the previously exposed initialization functions, the no-listener mode,
the lightweight hbuf API, and the specialized hldstream object types to
dynamically derive and generate its configuration in memory from basic
command-line inputs. By leveraging HAProxy's internal HTX
(Internal HTTP Native Representation) format, haload abstractly manipulates
HTTP elements independently of the wire protocol. This
abstraction allows it to generate unified requests and process responses
seamlessly across HTTP/1.1, HTTP/2, or HTTP/3 without duplicating the payload
handling logic for each version.

 - Makefile:
   Introduce the 'haload' compilation target and define HALOAD_OBJS.

 - src/haload.c, include/haproxy/haload.h:
   Add user and stream task scheduling handlers, HTX-driven traffic orchestration
   mechanisms, and terminal benchmarking statistical summary rendering.

 - src/haload_init.c:
   Implement program arguments parsing, fileless HAProxy memory configuration
   generation, and target URL allocations.

 - src/stconn.c:
   Wire up sc_attach_mux() to properly allocate the specific tasklet
   context when dealing with a haload stream.

 - doc/haload.txt:
   Add detailed documentation covering compilation, flags, and usage examples.

Makefile
doc/haload.txt [new file with mode: 0644]
include/haproxy/haload.h [new file with mode: 0644]
include/haproxy/hldstream-t.h
src/haload.c [new file with mode: 0644]
src/haload_init.c [new file with mode: 0644]
src/stconn.c

index 9b7e04b669c72352f3d4d0f3149d904875ac82b8..fd33984aeeb307679934069786ec71c4d237656f 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -902,6 +902,7 @@ endif # TARGET
 
 OBJS =
 HATERM_OBJS =
 
 OBJS =
 HATERM_OBJS =
+HALOAD_OBJS =
 
 ifneq ($(EXTRA_OBJS),)
   OBJS += $(EXTRA_OBJS)
 
 ifneq ($(EXTRA_OBJS),)
   OBJS += $(EXTRA_OBJS)
@@ -957,6 +958,8 @@ endif
 
 HATERM_OBJS += $(OBJS) src/haterm_init.o src/hbuf.o
 
 
 HATERM_OBJS += $(OBJS) src/haterm_init.o src/hbuf.o
 
+HALOAD_OBJS += $(OBJS) src/haload_init.o src/haload.o src/hbuf.o
+
 # Used only for forced dependency checking. May be cleared during development.
 INCLUDES = $(wildcard include/*/*.h)
 DEP = $(INCLUDES) .build_opts
 # Used only for forced dependency checking. May be cleared during development.
 INCLUDES = $(wildcard include/*/*.h)
 DEP = $(INCLUDES) .build_opts
@@ -1007,6 +1010,9 @@ haproxy: $(OPTIONS_OBJS) $(OBJS)
 haterm: $(OPTIONS_OBJS) $(HATERM_OBJS)
        $(cmd_LD) $(ARCH_FLAGS) $(LDFLAGS) -o $@ $^ $(LDOPTS)
 
 haterm: $(OPTIONS_OBJS) $(HATERM_OBJS)
        $(cmd_LD) $(ARCH_FLAGS) $(LDFLAGS) -o $@ $^ $(LDOPTS)
 
+haload: $(OPTIONS_OBJS) $(HALOAD_OBJS)
+       $(cmd_LD) $(ARCH_FLAGS) $(LDFLAGS) -o $@ $^ $(LDOPTS)
+
 objsize: haproxy
        $(Q)objdump -t $^|grep ' g '|grep -F '.text'|awk '{print $$5 FS $$6}'|sort
 
 objsize: haproxy
        $(Q)objdump -t $^|grep ' g '|grep -F '.text'|awk '{print $$5 FS $$6}'|sort
 
diff --git a/doc/haload.txt b/doc/haload.txt
new file mode 100644 (file)
index 0000000..c82e6d9
--- /dev/null
@@ -0,0 +1,146 @@
+------
+                                  HALoad
+                                  ------
+                            HAProxy's dummy HTTP
+                           client for benchmarks
+
+1. Background
+-------------
+
+HALoad is a lightweight, multi-threaded traffic generator designed to benchmark
+HTTP infrastructures under heavy loads. Built directly onto HAProxy's highly
+scalable core architecture, it shares its parent engine's efficient handling of
+connections. This framework allows the tool to generate high-volume traffic
+across all standard application layers, including HTTP/1, HTTP/2, and HTTP/3
+(QUIC), over either cleartext or secured TLS connections.
+
+The primary design goal is to provide a modernized alternative to legacy tools
+like h1load, extending benchmarking capabilities to newer protocols. Notably,
+HALoad introduces the concept of users (-u), a feature completely absent
+from h1load. Here, a "user" strictly represents an independent, concurrent
+HTTP client task. Under this architecture, each simulated client will
+instantiate as many backend server connections as there are target URLs
+specified on the command line.
+
+HALoad does not require any configuration file. Instead, the configuration is
+dynamically derived from basic command line inputs. This ensures immediate
+usability for test operators while retaining the ability to test complex,
+multi-protocol setups.
+
+
+2. Compilation
+--------------
+
+The compilation process mirrors standard HAProxy builds, specifying "haload"
+as the compilation target via the command line:
+
+  $ make -j $(nproc) TARGET=linux-glibc haload
+
+To enable encrypted communication layers (TLS/SSL):
+
+  $ make TARGET=linux-glibc USE_OPENSSL=1 haload
+
+For advanced HTTP/3 over QUIC load testing:
+
+  $ make -j $(nproc) TARGET=linux-glibc USE_OPENSSL=1 USE_QUIC=1 haload
+
+Because HALoad shares the code of the main HAProxy binary, it natively
+inherits all standard HAProxy compiler flags, optimizations, and build
+targets.
+
+
+3. Execution
+------------
+
+HALoad displays its usage when run without argument or wrong arguments:
+
+    Usage : haload [opts] [URL]
+    where <opts> may be any combination of:
+            -d <time>        test duration in seconds (0)
+            -e               stop upon first connection error
+            -h(0|1|2|2c|3)   use h0 (hq-interop for QUIC), h1, h2, h2c or h3
+                             (QUIC/TCP) protocols (*)
+            -(0|1|2|2c|3)    same as above (*)
+            -l               enable long output format; double for raw values
+            -m <streams>     maximum concurrent streams (1)
+            -n <reqs>        maximum total requests (-1)
+            -r <reqs>        number of requests per connection (-1)
+            -s <time>        soft start: time in sec to reach 100% load
+            -t <threads>     number of threads
+            -u <users>       number of users (1)
+            -w <time>        I/O timeout in milliseconds (10000)
+            -C               dump the configuration and exit
+            -H "foo:bar"     add this header name and value
+            -I               use HEAD instead of GET
+            -v               shows version
+            --defaults <str> add a string to default section
+            --global <str>   add a string to global section
+            --server <opts>  set server <opt> options as defined for "server"
+                             haproxy keyword
+            --show-status-codes show HTTP status codes distribution
+            --traces         enable the traces for all the HTTP protocols
+    SSL options:
+            --tls-ciphers <ciphers>      for TLS1.2 and below (*)
+            --tls-ciphersuites <ciphers> for TLS1.3 and above (*)
+            --tls-curves <curves> (*)
+    URL format:
+            (http://|https://|quic://)<addr>:<port>/<path>
+    Note: Options marked with an asterisk (*) are positional and MUST be placed
+          BEFORE the URLs they are intended to affect.
+
+
+At startup, HALoad dynamically generates an HAProxy configuration in memory.
+Options like --global and --defaults allow raw configuration lines to be added
+directly to the "global" and "defaults" sections, while --server appends
+options to the backend server. These parameters support standard escaped
+notation (e.g., '\n' or '\t') to insert multi-line statements in a single
+argument en ligne de commande.
+
+
+Examples:
+
+    # 30-second test using 4 threads simulating 10 distinct users
+    $ ./haload -t 4 -u 10 -d 30 http://127.0.0.1:8888/
+
+    # HTTP/3 test limiting concurrent streams to 3 per connection (-m3)
+    $ ./haload -m3 quic://127.0.0.1:8889/
+
+    # Dump the generated memory configuration and exit (-C)
+    $ ./haload https://127.0.0.1:4443 -C
+    global
+            tune.memory.hot-size 3145728
+            ssl-server-verify none
+            tune.h2.be.max-concurrent-streams 1
+            tune.quic.be.stream.max-concurrent 1
+
+    # Dump the configuration and exit (-C) to check -m3 effect
+    $ ./haload https://127.0.0.1:4443 -m3 -C
+    global
+            tune.memory.hot-size 3145728
+            ssl-server-verify none
+            tune.h2.be.max-concurrent-streams 3
+            tune.quic.be.stream.max-concurrent 3
+
+    # Add a custom directive into defaults, dump the configuration and exit (-C)
+    $ ./haload https://127.0.0.1:4443 --defaults "timeout connect 5s" -C
+    global
+            tune.memory.hot-size 3145728
+            ssl-server-verify none
+            tune.h2.be.max-concurrent-streams 1
+            tune.quic.be.stream.max-concurrent 1
+    defaults
+            timeout connect 5s
+
+
+4. Limitations
+--------------
+
+When using HTTP/3 (QUIC or QMUX) protocols with the maximum concurrent streams
+option (-m) enabled, HAProxy's connection layer currently does not enforce or
+respect the strict limit on the number of open connections usually dictated by
+the concurrent users option (-u).
+
+For example, when targeting a single URL, specifying "-u 10 -m 5" might lead
+to opening more than 10 concurrent connections to the target server under
+heavy load, instead of strictly capping the total connections to the requested
+number of users.
diff --git a/include/haproxy/haload.h b/include/haproxy/haload.h
new file mode 100644 (file)
index 0000000..c75e4e0
--- /dev/null
@@ -0,0 +1,70 @@
+#ifndef _HAPROXY_HALOAD_H
+#define _HAPROXY_HALOAD_H
+
+#include <import/ist.h>
+#include <haproxy/list-t.h>
+#include <haproxy/proxy-t.h>
+#include <haproxy/server-t.h>
+#include <haproxy/task-t.h>
+
+struct hld_path {
+       char *path;
+       struct hld_path *next;
+};
+
+struct hld_url_cfg {
+       int ssl;
+       int is_quic;
+       int h2c;
+       char *addr;
+       char *raw_addr; // used only to set the host header value
+       char *srv_opts;
+       char *tls_opts;
+       char *alpn;
+       struct server *srv;
+       struct hld_path *paths;
+       struct hld_path *cur_path;
+       struct hld_url_cfg *next;
+};
+
+struct hld_url {
+       int mreqs;
+       int flags;
+       uint64_t tot_req;
+       uint64_t tot_rconn_done;
+       uint64_t tot_rconn_sent;
+       struct hld_url_cfg *cfg;
+       struct hld_url *next;
+};
+
+/* haload header */
+struct hld_hdr {
+       struct ist name;
+       struct ist value;
+       struct list list;
+};
+
+extern const char *arg_host;
+extern const char *arg_conn_hdr;
+extern const char *arg_uri;
+extern const char *arg_path; // TO REMOVE
+extern struct list hld_hdrs;
+extern struct hld_url_cfg *hld_url_cfgs;
+
+extern struct proxy hld_proxy;
+extern int arg_accu;
+extern int arg_dura;
+extern int arg_fast;
+extern int arg_head;
+extern int arg_hscd;
+extern int arg_long;
+extern int arg_mreqs;
+extern int arg_reqs;
+extern int arg_rcon;
+extern int arg_slow;
+extern int arg_serr;
+extern int arg_usr;
+extern int arg_thrd;
+extern int arg_wait;
+
+#endif /* _HAPROXY_HALOAD_H */
index 6c445b0df5c21019dc6f6a317a0db382e67b9dc3..5f743aa561ba33837b92e54462e3ada650e266c1 100644 (file)
@@ -12,6 +12,7 @@
 struct hldstream {
        enum obj_type obj_type;
        struct connection *conn;
 struct hldstream {
        enum obj_type obj_type;
        struct connection *conn;
+       unsigned int expire;
        int64_t hash;
        struct hld_usr *usr;
        struct hld_url *url;
        int64_t hash;
        struct hld_usr *usr;
        struct hld_url *url;
@@ -23,6 +24,7 @@ struct hldstream {
        int flags;
        int state;
        unsigned long long to_send; /* number of body data bytes to send */
        int flags;
        int state;
        unsigned long long to_send; /* number of body data bytes to send */
+       struct timeval req_date;
        struct list list;
 };
 
        struct list list;
 };
 
diff --git a/src/haload.c b/src/haload.c
new file mode 100644 (file)
index 0000000..470f9a3
--- /dev/null
@@ -0,0 +1,1834 @@
+#include <openssl/ssl.h>
+
+#include <haproxy/api.h>
+#include <haproxy/dynbuf.h>
+#include <haproxy/errors.h>
+#include <haproxy/http.h>
+#include <haproxy/http_htx.h>
+#include <haproxy/htx.h>
+#include <haproxy/haload.h>
+#include <haproxy/proxy.h>
+#include <haproxy/task.h>
+#include <haproxy/trace.h>
+#include <haproxy/protocol.h>
+#include <haproxy/quic_tp.h>
+#include <haproxy/server.h>
+#include <haproxy/session.h>
+#include <haproxy/stats.h>
+#include <haproxy/stconn.h>
+#include <haproxy/stream.h>
+
+/* haload stream state flags */
+#define HLD_STRM_ST_IN_ALLOC     0x0001
+#define HLD_STRM_ST_OUT_ALLOC    0x0002
+#define HLD_STRM_ST_CONN_ERR     0x0004
+#define HLD_STRM_ST_HDRS_SENT    0x0008
+#define HLD_STRM_ST_REQ_TO_BUILD 0x0010
+#define HLD_STRM_ST_MUST_RECV    0x0020
+#define HLD_STRM_ST_GOT_RESP_SL  0x0040
+
+static inline struct hld_usr *hld_new_usr(int nreqs, int tid);
+static void hld_dealloc_thrs_info(void);
+
+struct hld_mtask {
+       struct task *t;
+       unsigned int show_time;
+} mtask;
+
+struct hld_freq_ctr {
+       uint32_t curr_sec; /* start date of current period (seconds from now.tv_sec) */
+       uint32_t curr_ctr; /* cumulated value for current period */
+       uint32_t prev_ctr; /* value for last period */
+
+};
+
+struct hld_thr_info {
+       struct timeval now;          // current time
+       struct hld_freq_ctr req_rate;    // thread's measured request rate
+       struct hld_freq_ctr conn_rate;   // thread's measured connection rate
+       uint32_t cur_req;            // number of active requests
+       uint32_t curconn;            // number of active connections
+       uint32_t curusrs;            // number of active users
+       uint32_t maxusrs;            // max number of users
+       uint64_t tot_conn;           // total conns attempted on this thread
+       uint64_t tot_done;           // total requests finished (successes+failures)
+       uint64_t tot_rcvd;           // total bytes received on this thread
+       uint64_t tot_perr;           // total protocol errors on this thread
+       uint64_t tot_fbs;            // total number of ttfb samples
+       uint64_t tot_ttfb;           // total time-to-first-byte (us)
+       uint64_t tot_lbs;            // total number of ttlb samples
+       uint64_t tot_ttlb;           // total time-to-last-byte (us)
+       uint64_t *ttfb_pct;          // counts per ttfb value for percentile
+       uint64_t *ttlb_pct;          // counts per ttlb value for percentile
+       uint64_t tot_sc[5];          // total status codes on this thread: 1xx,2xx,3xx,4xx,5xx
+       __attribute__((aligned(64))) union { } __pad;
+};
+
+/* User flags */
+#define HLD_USR_FL_STOP  0x00000001 // this user must stop sending requests
+
+struct hld_usr {
+       struct task *task;
+       struct session *sess;
+       struct list strms;
+       struct hld_url *urls;
+       struct hld_url *cur_url;
+       int nreqs;
+       int flags;
+};
+
+struct hld_thr_info *thrs_info;
+
+struct list hld_hdrs = LIST_HEAD_INIT(hld_hdrs);
+struct proxy hld_proxy;
+
+const char *arg_host;
+const char *arg_conn_hdr;
+const char *arg_uri;
+const char *arg_path;
+
+int arg_accu;          // more accurate req/time measurements in keep-alive
+int arg_dura;          // test duration in sec if non-nul
+int arg_fast;          // merge send with connect's ACK
+int arg_head;          // use HEAD
+int arg_hscd;          // HTTP status code distribution
+int arg_long;          // long output format; 2=raw values
+int arg_mreqs = 1;     // max concurrent streams by connection
+int arg_rcon = -1;     // max requests per conn
+int arg_reqs = -1;     // max total requests
+int arg_serr;          // stop on first error
+int arg_slow;          // slow start: delay in milliseconds
+int arg_thrd = -1;     // number of threads
+int arg_usr = 1;       // number of users
+int arg_wait = 10000;  // I/O time out (ms)
+
+int all_usr_stop_asap; // all users must stop as soon as possible
+int usr_tid;
+int usr_cnt;           // user counter incremented by <mtask> main task
+int running_usrs;      // user counter decremented each time a user is released
+
+char *hld_args[MAX_LINE_ARGS + 1];
+
+volatile unsigned long global_req; // global (started) req counter to sync user tasks.
+
+/************ time manipulation functions ***************/
+
+struct timeval hld_start_date, hld_stop_date, hld_now;
+volatile uint32_t throttle = 0;  // pass to mul32hi() if not null.
+
+/* timeval is not set */
+#define TV_UNSET ((struct timeval){ .tv_sec = 0, .tv_usec = ~0 })
+
+/* make a timeval from <sec>, <usec> */
+static inline struct timeval tv_set(time_t sec, suseconds_t usec)
+{
+       struct timeval ret = { .tv_sec = sec, .tv_usec = usec };
+       return ret;
+}
+
+/* used to unset a timeout */
+static inline struct timeval tv_unset(void)
+{
+       return tv_set(0, ~0);
+}
+
+/* returns the interval in microseconds, which must be set */
+static inline uint64_t tv_us(const struct timeval tv)
+{
+       return tv.tv_sec * (uint64_t)1000000 + tv.tv_usec;
+}
+
+/* returns the delay between <past> and <now> or zero if <past> is after <now> */
+static inline struct timeval tv_diff(const struct timeval *past, const struct timeval *now)
+{
+       struct timeval ret = { .tv_sec = 0, .tv_usec = 0 };
+
+       if (tv_isbefore(past, now)) {
+               ret.tv_sec  = now->tv_sec  - past->tv_sec;
+               ret.tv_usec = now->tv_usec - past->tv_usec;
+
+               if ((signed)ret.tv_usec < 0) { // overflow
+                       ret.tv_usec += 1000000;
+                       ret.tv_sec  -= 1;
+               }
+       }
+       return ret;
+}
+
+/* read a freq counter over a 1-second period and return the event rate/s */
+uint32_t hdl_read_freq_ctr(struct hld_freq_ctr *ctr, const struct timeval now)
+{
+       uint32_t curr, past;
+       uint32_t age;
+
+       age = now.tv_sec - ctr->curr_sec;
+       if (age > 1)
+               return 0;
+
+       curr = 0;
+       past = ctr->curr_ctr;
+       if (!age) {
+               curr = past;
+               past = ctr->prev_ctr;
+       }
+
+       if (past <= 1 && !curr)
+               return past; /* very low rate, avoid flapping */
+
+       return curr + mul32hi(past, (unsigned)(999999 - now.tv_usec) * 4294U);
+}
+
+/* returns the number of remaining events that can occur on this freq counter
+ * while respecting <freq> and taking into account that <pend> events are
+ * already known to be pending. Returns 0 if limit was reached.
+ */
+uint32_t hld_freq_ctr_remain(struct hld_freq_ctr *ctr, uint32_t freq,
+                             uint32_t pend, const struct timeval now)
+{
+       uint32_t curr, past;
+       uint32_t age;
+
+       curr = 0;
+       age = now.tv_sec - ctr->curr_sec;
+
+       if (age <= 1) {
+               past = ctr->curr_ctr;
+               if (!age) {
+                       curr = past;
+                       past = ctr->prev_ctr;
+               }
+               curr += mul32hi(past, (unsigned)(999999 - now.tv_usec) * 4294U);
+       }
+       curr += pend;
+
+       if (curr >= freq)
+               return 0;
+       return freq - curr;
+}
+
+/* return the expected wait time in ms before the next event may occur,
+ * respecting frequency <freq>, and assuming there may already be some pending
+ * events. It returns zero if we can proceed immediately, otherwise the wait
+ * time, which will be rounded down 1ms for better accuracy, with a minimum
+ * of one ms.
+ */
+uint32_t hld_next_event_delay(struct hld_freq_ctr *ctr, uint32_t freq,
+                              uint32_t pend, const struct timeval now)
+{
+       uint32_t curr, past;
+       uint32_t wait, age;
+
+       past = 0;
+       curr = 0;
+       age = now.tv_sec - ctr->curr_sec;
+
+       if (age <= 1) {
+               past = ctr->curr_ctr;
+               if (!age) {
+                       curr = past;
+                       past = ctr->prev_ctr;
+               }
+               curr += mul32hi(past, (unsigned)(999999 - now.tv_usec) * 4294U);
+       }
+       curr += pend;
+
+       if (curr < freq)
+               return 0;
+
+       /* too many events already, let's count how long to wait before they're
+        * processed.
+        */
+       curr = curr - freq; // number of events left after current period
+
+       /* each events takes 1/freq second or 1000/freq ms */
+
+       wait = curr * 1000 / freq;
+       if (!wait)
+               wait = 1;
+       return wait;
+}
+
+/* Rotate a frequency counter when current period is over. Must not be called
+ * during a valid period. It is important that it correctly initializes a null
+ * area.
+ */
+static inline void hld_rotate_freq_ctr(struct hld_freq_ctr *ctr,
+                                       const struct timeval now)
+{
+       ctr->prev_ctr = ctr->curr_ctr;
+       if (now.tv_sec - ctr->curr_sec != 1) {
+               /* we missed more than one second */
+               ctr->prev_ctr = 0;
+       }
+       ctr->curr_sec = now.tv_sec;
+       ctr->curr_ctr = 0; /* leave it at the end to help gcc optimize it away */
+}
+
+/* Update a frequency counter by <inc> incremental units. It is automatically
+ * rotated if the period is over. It is important that it correctly initializes
+ * a null area.
+ */
+__attribute__((unused))
+static inline void hdl_update_freq_ctr(struct hld_freq_ctr *ctr, uint32_t inc,
+                                       const struct timeval now)
+{
+       if (ctr->curr_sec == now.tv_sec) {
+               ctr->curr_ctr += inc;
+               return;
+       }
+       hld_rotate_freq_ctr(ctr, now);
+       ctr->curr_ctr = inc;
+}
+
+#define TRACE_SOURCE &trace_haload
+struct trace_source trace_haload;
+static void hld_trace(enum trace_level level, uint64_t mask, const struct trace_source *src,
+                         const struct ist where, const struct ist func,
+                         const void *a1, const void *a2, const void *a3, const void *a4);
+static inline void hldstream_free(struct hldstream **hs);
+
+static const struct name_desc hld_trace_logon_args[4] = {
+       /* arg1 */ { /* already used by the haload stream */ },
+       /* arg2 */ {
+               .name = "hld",
+               .desc = "haload",
+       },
+       /* arg3 */ { },
+       /* arg4 */ { }
+};
+
+static const struct trace_event hld_trace_events[] = {
+#define HLD_EV_MAIN_TASK  (1ULL << 0)
+       { .mask = HLD_EV_MAIN_TASK, .name = "mtask",      .desc = "haload main task" },
+#define HLD_EV_USR_TASK  (1ULL << 0)
+       { .mask = HLD_EV_USR_TASK,  .name = "usr_task",   .desc = "haload user task" },
+#define HLD_STRM_EV_TX     (1ULL << 1)
+       { .mask = HLD_STRM_EV_TX,     .name = "tx",        .desc = "haload stream sending" },
+#define HLD_STRM_EV_TX_BLK (1ULL << 2)
+       { .mask = HLD_STRM_EV_TX_BLK, .name = "tx_blk",    .desc = "haload stream sending blocked" },
+#define HLD_STRM_EV_RX     (1ULL << 3)
+       { .mask = HLD_STRM_EV_RX,     .name = "rx",        .desc = "haload stream receiving" },
+#define HLD_STRM_EV_RX_BLK (1ULL << 4)
+       { .mask = HLD_STRM_EV_RX_BLK, .name = "rx_blk",    .desc = "haload stream receiving blocked" },
+#define HLD_STRM_EV_TASK   (1ULL << 5)
+       { .mask = HLD_STRM_EV_TASK,   .name = "strm_task", .desc = "haload stream task" },
+#define HLD_STRM_EV_IO_CB  (1ULL << 6)
+       { .mask = HLD_STRM_EV_IO_CB,  .name = "io_cb",     .desc = "stconn i/o callback call" },
+};
+
+static const struct name_desc hld_trace_decoding[] = {
+#define HALOAD_VERB_CLEAN 1
+       { .name = "clean", .desc = "only user-friendly stuff, generally suitable for level \"user\"" },
+};
+
+struct trace_source trace_haload = {
+       .name = IST("haload"),
+       .desc = "haload benchmark tool",
+       /* TRACE()'s first argument is always a haload stream */
+       .arg_def = TRC_ARG1_HLDSTRM,
+       .default_cb = hld_trace,
+       .known_events = hld_trace_events,
+       .lockon_args = hld_trace_logon_args,
+       .decoding = hld_trace_decoding,
+       .report_events = ~0, /* report everything by default */
+};
+
+INITCALL1(STG_REGISTER, trace_register_source, TRACE_SOURCE);
+
+static void hld_trace(enum trace_level level, uint64_t mask, const struct trace_source *src,
+                         const struct ist where, const struct ist func,
+                         const void *a1, const void *a2, const void *a3, const void *a4)
+{
+       const struct hldstream *hs = a1;
+
+       if (!hs || src->verbosity < HALOAD_VERB_CLEAN)
+               return;
+
+       chunk_appendf(&trace_buf, " hs@%p conn@%p se@%p to_send=%u tot_req=%lu",
+                     hs, __sc_conn(hs->sc), __sc_endp(hs->sc),
+                     htxbuf(&hs->bo)->data, hs->url->tot_req);
+       if (hs->sc) {
+               struct connection *conn = sc_conn(hs->sc);
+               chunk_appendf(&trace_buf, " - conn=%p(0x%08x)", conn, conn ? conn->flags : 0);
+               chunk_appendf(&trace_buf, " sc=%p(0x%08x)", hs->sc, hs->sc->flags);
+       }
+}
+
+/* Tries to grab a buffer and to re-enables processing on haload stream <target>.
+ * The flags are used to figure what buffer was requested. It returns 1 if the
+ * allocation succeeds, in which case the haload stream is woken up, or 0 if it's
+ * impossible to wake up and we prefer to be woken up later.
+ */
+int hldstream_buf_available(void *target)
+{
+       struct hldstream *hs = target;
+
+       if ((hs->flags & HLD_STRM_ST_IN_ALLOC) && b_alloc(&hs->bi, DB_CHANNEL)) {
+               hs->flags &= ~HLD_STRM_ST_IN_ALLOC;
+               TRACE_STATE("unblocking stream, input buffer allocated",
+                           HLD_STRM_EV_RX|HLD_STRM_EV_RX_BLK, hs);
+               task_wakeup(hs->task, TASK_WOKEN_IO);
+               return 1;
+       }
+
+       if ((hs->flags & HLD_STRM_ST_OUT_ALLOC) && b_alloc(&hs->bo, DB_CHANNEL)) {
+               hs->flags &= ~HLD_STRM_ST_OUT_ALLOC;
+               TRACE_STATE("unblocking stream, ouput buffer allocated",
+                           HLD_STRM_EV_TX|HLD_STRM_EV_TX_BLK, hs);
+               task_wakeup(hs->task, TASK_WOKEN_IO);
+               return 1;
+       }
+
+       return 0;
+}
+
+/* Allocate a buffer. If it fails, it adds the stream in buffer wait queue */
+struct buffer *hldstream_get_buf(struct hldstream *hs, struct buffer *bptr)
+{
+       struct buffer *buf = NULL;
+
+       if (likely(!LIST_INLIST(&hs->buf_wait.list)) &&
+           unlikely((buf = b_alloc(bptr, DB_CHANNEL)) == NULL)) {
+               b_queue(DB_CHANNEL, &hs->buf_wait, hs, hldstream_buf_available);
+       }
+
+       return buf;
+}
+
+/* Allocate the output buffer attached to <hs> haload stream and returns
+ * it if succeded, NULL if not.
+ */
+static inline struct buffer *hldstream_get_obuf(struct hldstream *hs)
+{
+       return hldstream_get_buf(hs, &hs->bo);
+}
+
+/* Allocate the input buffer attached to <hs> haload stream and returns
+ * it if succeded, NULL if not.
+ */
+static inline struct buffer *hldstream_get_ibuf(struct hldstream *hs)
+{
+       return hldstream_get_buf(hs, &hs->bi);
+}
+
+/* Release a buffer, if any, and try to wake up entities waiting in the buffer
+ * wait queue.
+ */
+void hldstream_release_buf(struct hldstream *hs, struct buffer *bptr)
+{
+       if (bptr->size) {
+               b_free(bptr);
+               offer_buffers(hs->buf_wait.target, 1);
+       }
+}
+
+/* Release the input buffer attached to <hs> haload stream */
+static inline void hldstream_release_ibuf(struct hldstream *hs)
+{
+       hldstream_release_buf(hs, &hs->bi);
+}
+
+/* Release the output buffer attached to <hs> haload stream */
+static inline void hldstream_release_obuf(struct hldstream *hs)
+{
+       hldstream_release_buf(hs, &hs->bo);
+}
+
+/* Free <*hs> haload stream and reset its address to NULL */
+static inline void hldstream_free(struct hldstream **hs)
+{
+       struct hldstream *h = *hs;
+
+       TRACE_PRINTF(TRACE_LEVEL_PROTO, HLD_STRM_EV_TASK, hs, 0, 0, 0,
+                    "freeing %p stream", h);
+       se_shutdown(h->sc->sedesc, SE_SHW_SILENT);
+       hldstream_release_ibuf(h);
+       hldstream_release_obuf(h);
+       task_destroy(h->task);
+       sc_destroy(h->sc);
+       ha_free(hs);
+       TRACE_LEAVE(HLD_STRM_EV_TASK);
+}
+
+
+/* Creates a new stream connector from a haload connection. There is no endpoint
+ * here, thus it will be created by sc_new(). So the SE_FL_DETACHED flag is set.
+ * It returns NULL on error. On success, the new stream connector is returned.
+ */
+static inline struct stconn *sc_new_from_hldstream(struct hldstream *hs, unsigned int flags)
+{
+       struct stconn *sc;
+
+       sc = sc_new(NULL);
+       if (unlikely(!sc))
+               return NULL;
+
+       sc->flags |= flags;
+       sc_ep_set(sc, SE_FL_DETACHED);
+       sc->app = &hs->obj_type;
+       return sc;
+}
+
+/* reports a locally allocated string to represent a human-readable positive
+ * number on 4 characters (3 digits and a unit, which may be "." for ones) :
+ *   XXXu
+ *   XXuX
+ *   XuXX
+ */
+static const char *human_number(double x)
+{
+       static char str[5];
+       char unit = '.';
+
+       if (x < 0)
+               x = -x;
+
+       do {
+               if (x == 0.0 || x >= 1.0) break;
+               x *= 1000.0; unit = 'm';
+               if (x >= 1.0) break;
+               x *= 1000.0; unit = 'u';
+               if (x >= 1.0) break;
+               x *= 1000.0; unit = 'n';
+               if (x >= 1.0) break;
+               x *= 1000.0; unit = 'p';
+               if (x >= 1.0) break;
+               x *= 1000.0; unit = 'f';
+       } while (0);
+
+       do {
+               if (x < 1000.0) break;
+               x /= 1000.0; unit = 'k';
+               if (x < 1000.0) break;
+               x /= 1000.0; unit = 'M';
+               if (x < 1000.0) break;
+               x /= 1000.0; unit = 'G';
+               if (x < 1000.0) break;
+               x /= 1000.0; unit = 'T';
+               if (x < 1000.0) break;
+               x /= 1000.0; unit = 'P';
+               if (x < 1000.0) break;
+               x /= 1000.0; unit = 'E';
+       } while (0);
+
+       if (x < 10.0)
+               snprintf(str, sizeof(str), "%d%c%02d", (int)x, unit, (int)((x - (int)x)*100));
+       else if (x < 100.0)
+               snprintf(str, sizeof(str), "%d%c%d",   (int)x, unit, (int)((x - (int)x)*10));
+       else
+               snprintf(str, sizeof(str), "%d%c",     (int)x, unit);
+       return str;
+}
+
+/* Builds a string from the time interval <us> (in microsecond), made of a 5
+ * digit value followed by a unit among 'n', 'u', 'm', 's' for "nanoseconds",
+ * "microseconds", "milliseconds", "seconds" respectively. Large values will
+ * stick to the seconds unit and will enlarge the output, though this is not
+ * expected to be a common case. This way the output can be converted back
+ * into integer values without too much hassle (e.g. for graphs). The string
+ * is locally allocated so this must not be used by multiple threads. Negative
+ * values are reported as "  -  ".
+ */
+static const char *short_delay_str(double us)
+{
+       static char str[20];
+       char unit;
+
+       if (us <= 0.0) {
+               return "   -  ";
+       }
+       else if (us < 1.0) {
+               us *= 1000.0;
+               unit = 'n';
+       }
+       else if (us < 1000.0) {
+               unit = 'u';
+       }
+       else if (us < 1000000.0) {
+               us /= 1000.0;
+               unit = 'm';
+       }
+       else {
+               us /= 1000000.0;
+               unit = 's';
+       }
+
+       if (us < 10.0)
+               snprintf(str, sizeof(str), "%1.3f%c", us, unit);
+       else if (us < 100.0)
+               snprintf(str, sizeof(str), "%2.2f%c", us, unit);
+       else if (us < 1000.0)
+               snprintf(str, sizeof(str), "%3.1f%c", us, unit);
+       else
+               snprintf(str, sizeof(str), "%5f%c", us, unit);
+       return str;
+}
+
+/* reports current date (now) and aggragated stats */
+void hld_summary(void)
+{
+       int th;
+       uint64_t cur_conn, tot_conn, tot_req, tot_err, tot_rcvd, bytes;
+       uint64_t tot_ttfb, tot_ttlb, tot_fbs, tot_lbs, tot_sc[5];
+       static uint64_t prev_totc, prev_totr, prev_totb;
+       static uint64_t prev_ttfb, prev_ttlb, prev_fbs, prev_lbs, prev_sc[5];
+       static struct timeval prev_date = TV_UNSET;
+       double interval;
+
+       cur_conn = tot_conn = tot_req = tot_err = tot_rcvd = 0;
+       tot_ttfb = tot_ttlb = tot_fbs = tot_lbs = 0;
+       tot_sc[0] = tot_sc[1] = tot_sc[2] = tot_sc[3] = tot_sc[4] = 0;
+
+       for (th = 0; th < global.nbthread; th++) {
+               cur_conn += HA_ATOMIC_LOAD(&thrs_info[th].curconn);
+               tot_conn += HA_ATOMIC_LOAD(&thrs_info[th].tot_conn);
+               tot_req  += HA_ATOMIC_LOAD(&thrs_info[th].tot_done);
+               tot_err  += HA_ATOMIC_LOAD(&thrs_info[th].tot_perr);
+               tot_rcvd += HA_ATOMIC_LOAD(&thrs_info[th].tot_rcvd);
+               tot_ttfb += HA_ATOMIC_LOAD(&thrs_info[th].tot_ttfb);
+               tot_ttlb += HA_ATOMIC_LOAD(&thrs_info[th].tot_ttlb);
+               tot_fbs  += HA_ATOMIC_LOAD(&thrs_info[th].tot_fbs);
+               tot_lbs  += HA_ATOMIC_LOAD(&thrs_info[th].tot_lbs);
+               tot_sc[0]+= HA_ATOMIC_LOAD(&thrs_info[th].tot_sc[0]);
+               tot_sc[1]+= HA_ATOMIC_LOAD(&thrs_info[th].tot_sc[1]);
+               tot_sc[2]+= HA_ATOMIC_LOAD(&thrs_info[th].tot_sc[2]);
+               tot_sc[3]+= HA_ATOMIC_LOAD(&thrs_info[th].tot_sc[3]);
+               tot_sc[4]+= HA_ATOMIC_LOAD(&thrs_info[th].tot_sc[4]);
+       }
+
+       if (tv_isset(&prev_date))
+               interval = tv_ms_remain(&prev_date, &hld_now) / 1000.0;
+       else
+               interval = 1.0;
+
+       printf("%10lu %5lu %8llu %8llu %14llu %6lu ",
+              arg_long ? (unsigned long)hld_now.tv_sec :
+              (unsigned long)(hld_now.tv_sec - hld_start_date.tv_sec),
+              (unsigned long)cur_conn,
+              (unsigned long long)tot_conn,
+              (unsigned long long)tot_req,
+              (unsigned long long)tot_rcvd,
+              (unsigned long)tot_err);
+
+       bytes = tot_rcvd - prev_totb;
+
+       if (arg_long >= 2)
+               printf("%3u ", throttle ? mul32hi(100, throttle) : 100);
+
+       if (arg_long >= 2)
+               printf("%.1f ", (tot_conn - prev_totc) / interval);
+       else
+               printf("%s ", human_number((tot_conn - prev_totc) / interval));
+
+       if (arg_long >= 2)
+               printf("%.1f ", (tot_req  - prev_totr) / interval);
+       else
+               printf("%s ", human_number((tot_req  - prev_totr) / interval));
+
+       if (arg_long >= 2)
+               printf("%.1f ", bytes / interval);
+       else if (arg_long)
+               printf("%s ", human_number(bytes / interval));
+
+       if (arg_long >= 2)
+               printf("%.1f ", bytes * 8 / interval);
+       else
+               printf("%s ", human_number(bytes * 8 / interval));
+
+       if (arg_long >= 2) {
+               if (tot_fbs - prev_fbs)
+                       printf("%.1f ", (tot_ttfb - prev_ttfb) / (double)(tot_fbs - prev_fbs));
+               else
+                       printf("- ");
+       }
+       else
+               printf("%s ", tot_fbs == prev_fbs ? "   -  " :
+                      short_delay_str((tot_ttfb - prev_ttfb) / (double)(tot_fbs - prev_fbs)));
+
+       if (arg_long >= 2) {
+               if (tot_lbs - prev_lbs)
+                       printf("%.1f ", (tot_ttlb - prev_ttlb) / (double)(tot_lbs - prev_lbs));
+               else
+                       printf("- ");
+       }
+       else if (arg_long)
+               printf("%s ", tot_lbs == prev_lbs ? "   -  " :
+                      short_delay_str((tot_ttlb - prev_ttlb) / (double)(tot_lbs - prev_lbs)));
+
+       /* status codes distribution */
+       if (arg_hscd)
+               printf("%3llu %3llu %3llu %3llu %3llu ",
+                      (unsigned long long)(tot_sc[0] - prev_sc[0]),
+                      (unsigned long long)(tot_sc[1] - prev_sc[1]),
+                      (unsigned long long)(tot_sc[2] - prev_sc[2]),
+                      (unsigned long long)(tot_sc[3] - prev_sc[3]),
+                      (unsigned long long)(tot_sc[4] - prev_sc[4]));
+
+       putchar('\n');
+
+       prev_totc = tot_conn;
+       prev_totr = tot_req;
+       prev_totb = tot_rcvd;
+       prev_fbs  = tot_fbs;
+       prev_lbs  = tot_lbs;
+       prev_ttfb = tot_ttfb;
+       prev_ttlb = tot_ttlb;
+       prev_sc[0]= tot_sc[0];
+       prev_sc[1]= tot_sc[1];
+       prev_sc[2]= tot_sc[2];
+       prev_sc[3]= tot_sc[3];
+       prev_sc[4]= tot_sc[4];
+       prev_date = hld_now;
+}
+
+/* Same throttle handling as for h1load */
+void update_throttle()
+{
+       int duration;
+       uint32_t ratio = 0;
+       uint32_t step, steps = 10, pos, base;
+
+       if (!arg_slow)
+               goto end;
+
+       duration = tv_ms_remain(&hld_start_date, &hld_now);
+       if (duration >= arg_slow)
+               goto end;
+
+       /* The ramp-up duration is cut into <steps> steps.
+        * Each step shows a ramp-up during the first quarter of its
+        * duration, and a stabilisation period during the last 3/4.
+        * For instance, with 4 steps, we have this:
+        *
+        *      ramp up
+        * |<-------------->|
+        * |             __________
+        * |         ___/:  :
+        * |     ___/   ::  :
+        * | ___/       ::  :
+        * |/           ::  :
+        * +------------++------------>
+        *
+        * Thus we have to determine the current step and the position within
+        * this step. In order to simplify this, we'll pretend there are 4
+        * times more steps and that only steps 0 mod 4 ramp up the load.
+        * The throttle is stable along the last 3 quarters of a step, at the
+        * base value of the next step.
+        */
+
+       step = (steps * 4) * duration / arg_slow;
+       if (step & 3) {
+               ratio = (uint64_t)0xffffffffU * (step / 4 + 1) / steps + 1;
+               goto end;
+       }
+
+       /* position in ms within the current step */
+       pos = duration - step * arg_slow / (steps * 4);
+
+       /* get a ratio out of it. We divide 4* the position by the step width
+        * (arg_slow/steps), and multiply this by 1/steps to get the relative
+        * height vs 100%. steps cancel each other.
+        */
+       pos = (uint64_t)0xffffffffU * pos * 4 / arg_slow;
+       base = (uint64_t)0xffffffffU * (step / 4) / steps;
+       ratio = base + pos;
+
+       //printf("base=%#x (%u)  pos=%#x (%u) tot=%#x (%u)\n",
+       //       base, mul32hi(100,base),
+       //       pos, mul32hi(100,pos),
+       //       ratio, mul32hi(100,ratio));
+
+       if (ratio < 1)
+               ratio = 1;
+ end:
+       throttle = ratio;
+}
+
+/* main task */
+static struct task *mtask_cb(struct task *t, void *context, unsigned int state)
+{
+       TRACE_ENTER(HLD_EV_MAIN_TASK);
+
+       gettimeofday(&hld_now, NULL);
+
+       update_throttle();
+       if (tick_is_expired(mtask.show_time, now_ms)) {
+               hld_summary();
+               if (!HA_ATOMIC_LOAD(&running_usrs)) {
+                       task_destroy(t);
+                       t = NULL;
+                       hld_dealloc_thrs_info();
+                       /* The process will exit after this call */
+                       soft_stop();
+                       goto leave;
+               }
+
+               if (arg_dura && !tv_isbefore(&hld_now, &hld_stop_date))
+                       HA_ATOMIC_STORE(&all_usr_stop_asap, 1);
+
+               mtask.show_time = tick_add(now_ms, MS_TO_TICKS(1000));
+       }
+
+       /* users initializations */
+       if (usr_cnt < arg_usr) {
+               if (throttle) {
+                       int i, nb_usr;
+
+                       for (i = 0; i < global.nbthread; i++) {
+                               nb_usr = mul32hi(thrs_info[i].maxusrs, throttle);
+                               nb_usr = nb_usr ? nb_usr : 1;
+
+                               while (thrs_info[i].curusrs < nb_usr) {
+                                       struct hld_usr *hu;
+                                       int req = arg_reqs == -1 ? -1 : (arg_reqs + usr_cnt) / arg_usr;
+
+                                       hu = hld_new_usr(req, i);
+                                       if (!hu) {
+                                               ha_alert("could not allocate a new haload user\n");
+                                               break;
+                                       }
+
+                                       HA_ATOMIC_INC(&running_usrs);
+                                       usr_cnt++;
+                               }
+                       }
+
+                       t->expire = tick_add(now_ms, MS_TO_TICKS(20));
+               }
+               else {
+                       int i, nb_usr;
+
+                       nb_usr = MIN(arg_usr, arg_usr - usr_cnt);
+                       nb_usr = MIN(80, nb_usr);
+                       for (i = 0; i < nb_usr; i++, usr_cnt++) {
+                               struct hld_usr *hu;
+                               int req = arg_reqs == -1 ? -1 : (arg_reqs + usr_cnt) / arg_usr;
+
+                               hu = hld_new_usr(req, usr_tid++ % global.nbthread);
+                               if (!hu) {
+                                       ha_alert("could not allocate a new haload user\n");
+                                       break;
+                               }
+                       }
+
+                       HA_ATOMIC_ADD(&running_usrs, nb_usr);
+                       task_wakeup(t, TASK_WOKEN_IO);
+               }
+
+       }
+       else
+               t->expire = tick_add(now_ms, MS_TO_TICKS(1000));
+
+leave:
+       TRACE_LEAVE(HLD_EV_MAIN_TASK);
+       return t;
+}
+
+static int hldstream_build_http_req(struct hldstream *hs, struct ist path, int eom)
+{
+       int ret = 0;
+       struct buffer *buf;
+       struct htx *htx;
+       struct htx_sl *sl;
+       struct ist meth_ist;
+       struct hld_hdr *hdr;
+       unsigned int flags = HTX_SL_F_VER_11 | HTX_SL_F_XFER_LEN |
+               (!hs->to_send ? HTX_SL_F_BODYLESS : 0);
+
+       TRACE_ENTER(HLD_STRM_EV_TX, hs);
+       buf = hldstream_get_obuf(hs);
+       if (!buf) {
+               TRACE_STATE("waiting for ouput buffer", HLD_STRM_EV_TX|HLD_STRM_EV_TX_BLK, hs);
+               hs->flags |= HLD_STRM_ST_OUT_ALLOC;
+               goto leave;
+       }
+
+       htx = htx_from_buf(buf);
+       meth_ist = !arg_head ? ist("GET") : ist("HEAD");
+       sl = htx_add_stline(htx, HTX_BLK_REQ_SL, flags, meth_ist, path, ist("HTTP/1.1"));
+       if (!sl)
+               goto err;
+
+       sl->info.req.meth = !arg_head ? HTTP_METH_GET : HTTP_METH_HEAD;
+       list_for_each_entry(hdr, &hld_hdrs, list)
+               if (!htx_add_header(htx, hdr->name, hdr->value)) {
+                       TRACE_ERROR("could not add a header", HLD_STRM_EV_TX, hs);
+                       goto err;
+               }
+
+       if (!arg_host &&
+           !http_add_header(htx, ist("host"), ist(hs->url->cfg->raw_addr), 1)) {
+               TRACE_ERROR("could not add host header", HLD_STRM_EV_TX, hs);
+               goto err;
+       }
+
+       if (arg_conn_hdr && !http_add_header(htx, ist("Connection"), ist("close"), 0)) {
+               TRACE_ERROR("could not add connection header", HLD_STRM_EV_TX, hs);
+               goto err;
+       }
+
+       if (!htx_add_endof(htx, HTX_BLK_EOH))
+               goto err;
+
+       if (eom)
+               htx->flags |= HTX_FL_EOM;
+       htx_to_buf(htx, &hs->bo);
+ leave:
+       ret = 1;
+       TRACE_LEAVE(HLD_STRM_EV_TX, hs);
+       return ret;
+ err:
+       hs->flags |= HLD_STRM_ST_CONN_ERR;
+       TRACE_DEVEL("leaving on error", HLD_STRM_EV_TX, hs);
+       goto leave;
+}
+
+/* Send HTX data prepared for <hs> haload stream from <conn> connection */
+static int hldstream_htx_buf_snd(struct connection *conn, struct hldstream *hs)
+{
+       struct stconn *sc = hs->sc;
+       int ret = 0;
+       int nret;
+
+       TRACE_ENTER(HLD_STRM_EV_TX, hs);
+
+       if (!htxbuf(&hs->bo)->data) {
+               /* This is possible after having drained the body, so after
+                * having sent the response here when req_after_res=1.
+                */
+               ret = 1;
+               goto out;
+       }
+
+       nret = CALL_MUX_WITH_RET(conn->mux, snd_buf(hs->sc, &hs->bo, (htxbuf(&hs->bo))->data, 0));
+       if (nret <= 0) {
+               if (hs->flags & HLD_STRM_ST_CONN_ERR ||
+                   conn->flags & CO_FL_ERROR || sc_ep_test(sc, SE_FL_ERROR)) {
+                       TRACE_DEVEL("connection error during send", HLD_STRM_EV_TX, hs);
+                       goto out;
+               }
+       }
+
+       hs->req_date = date;
+       /* The HTX data are not fully sent if the last HTX data
+        * were not fully transfered or if there are remaining data
+        * to send (->to_send > 0).
+        */
+       if (!htx_is_empty(htxbuf(&hs->bo))) {
+               TRACE_DEVEL("data not fully sent, wait", HLD_STRM_EV_TX, hs);
+               conn->mux->subscribe(sc, SUB_RETRY_SEND, &sc->wait_event);
+       }
+       else if (hs->to_send) {
+               TRACE_STATE("waking up task", HLD_STRM_EV_TX, hs);
+               task_wakeup(hs->task, TASK_WOKEN_IO);
+       }
+
+       ret = 1;
+ out:
+       if (htx_is_empty(htxbuf(&hs->bo)) || ret == 0) {
+               TRACE_DEVEL("releasing underlying buffer", HLD_STRM_EV_TX, hs);
+               hldstream_release_obuf(hs);
+       }
+
+       TRACE_LEAVE(HLD_STRM_EV_TX, hs);
+       return ret;
+}
+
+/* Handle HTX data to be received by <h> haload stream. Also set
+ * <*fin> to 1 if the end of stream is reached.
+ */
+static void hldstream_htx_buf_rcv(struct connection *conn,
+                                  struct hldstream *hs, int *fin)
+{
+       struct buffer *buf;
+       size_t max, read = 0, cur_read = 0;
+       int is_empty = 0;
+       struct htx_sl *sl = NULL;
+       uint64_t ttfb, ttlb;     // time-to-first-byte, time-to-last-byte (in us)
+
+       TRACE_ENTER(HLD_STRM_EV_RX, hs);
+
+       *fin = 0;
+       if (hs->sc->wait_event.events & SUB_RETRY_RECV) {
+               TRACE_DEVEL("subscribed for RECV, waiting for data", HLD_STRM_EV_RX, hs);
+               goto leave;
+       }
+
+       if (hs->flags & HLD_STRM_ST_IN_ALLOC) {
+               TRACE_STATE("waiting for input buffer", HLD_STRM_EV_RX, hs);
+               goto leave;
+       }
+
+       buf = hldstream_get_ibuf(hs);
+       if (!buf) {
+               TRACE_STATE("waiting for input buffer", HLD_STRM_EV_RX, hs);
+               hs->flags |= HLD_STRM_ST_IN_ALLOC;
+               goto leave;
+       }
+
+       while (sc_ep_test(hs->sc, SE_FL_RCV_MORE) ||
+              (!(conn->flags & CO_FL_ERROR) &&
+               !sc_ep_test(hs->sc, SE_FL_ERROR | SE_FL_EOS))) {
+               htx_reset(htxbuf(&hs->bi));
+               max = (IS_HTX_SC(hs->sc) ?
+                      htx_free_space(htxbuf(&hs->bi)) : b_room(&hs->bi));
+               sc_ep_clr(hs->sc, SE_FL_WANT_ROOM);
+               read = CALL_MUX_WITH_RET(conn->mux, rcv_buf(hs->sc, &hs->bi, max, 0));
+               if (!(hs->flags & HLD_STRM_ST_GOT_RESP_SL) && read && !sl) {
+                       int status;
+                       sl = http_get_stline(htx_from_buf(&hs->bi));
+                       if (!sl) {
+                               TRACE_ERROR("start line not found", HLD_STRM_EV_RX, hs);
+                               hs->flags |= HLD_STRM_ST_CONN_ERR;
+                               goto leave;
+                       }
+
+                       status = sl->info.res.status;
+                       hs->flags |= HLD_STRM_ST_GOT_RESP_SL;
+                       TRACE_PRINTF(TRACE_LEVEL_PROTO, HLD_STRM_EV_RX, hs, 0, 0, 0,
+                                    "HTTP status: %d cur_read=%d",
+                                    status, (int)cur_read);
+                       thrs_info[tid].tot_sc[status * 41 / 4096 - 1]++;
+                       if (hs->url->tot_req > 1 || !arg_accu) {
+                               ttfb = tv_us(tv_diff(&hs->req_date, &date));
+                               thrs_info[tid].tot_fbs++;
+                               thrs_info[tid].tot_ttfb += ttfb;
+                       }
+               }
+
+               cur_read += read;
+               if (!htx_expect_more(htxbuf(&hs->bi)) || sc_ep_test(hs->sc, SE_FL_EOS)) {
+                   *fin = 1;
+                       thrs_info[tid].tot_done++;
+                       if (hs->url->tot_req > 1 || !arg_accu) {
+                               ttlb = tv_us(tv_diff(&hs->req_date, &date));
+                               thrs_info[tid].tot_lbs++;
+                               thrs_info[tid].tot_ttlb += ttlb;
+                       }
+                   break;
+               }
+
+               if (!read)
+                       break;
+       }
+
+       is_empty = (IS_HTX_SC(hs->sc) ?
+                   htx_is_empty(htxbuf(&hs->bi)) : !b_data(&hs->bi));
+       if (is_empty &&
+           ((conn->flags & CO_FL_ERROR) || sc_ep_test(hs->sc, SE_FL_ERROR))) {
+               /* Report network errors only if we got no other data. Otherwise
+                * we'll let the upper layers decide whether the response is OK
+                * or not. It is very common that an RST sent by the server is
+                * reported as an error just after the last data chunk.
+                */
+               TRACE_ERROR("connection error during recv", HLD_STRM_EV_RX, hs);
+               hs->flags |= HLD_STRM_ST_CONN_ERR;
+       }
+       else if (!*fin && !sc_ep_test(hs->sc, SE_FL_ERROR | SE_FL_EOS)) {
+               TRACE_DEVEL("subscribing for read data", HLD_STRM_EV_RX, hs);
+               conn->mux->subscribe(hs->sc, SUB_RETRY_RECV, &hs->sc->wait_event);
+       }
+
+       thrs_info[tid].tot_rcvd += cur_read;
+ leave:
+       if (!is_empty)
+               hldstream_release_ibuf(hs);
+       TRACE_PRINTF(TRACE_LEVEL_PROTO, HLD_STRM_EV_RX, hs, 0, 0, 0,
+                    "data received (%llu) read=%d *fin=%d",
+                    (unsigned long long)cur_read, (int)read, *fin);
+       TRACE_LEAVE(HLD_STRM_EV_RX, hs);
+}
+
+static void hld_conn_destroy(struct connection *conn)
+{
+       TRACE_ENTER(HLD_STRM_EV_TASK);
+       BUG_ON(!thrs_info[tid].curconn);
+       thrs_info[tid].curconn--;
+       TRACE_LEAVE(HLD_STRM_EV_TASK);
+}
+
+/* Try to reuse a connection from server <srv>, session <sess>, and
+ * stream connector <sc>.
+ * Always set the connection's <hash> to be reused, and return it
+ * at the <conn> address if found.
+ * Returns 1 if successful (no error, even if no connection was
+ * available to reuse), or 0 otherwise.
+ */
+static int hld_be_reuse_conn(struct connection **conn, int64_t *hash,
+                             struct stconn *sc, struct session *sess,
+                             struct server *srv)
+{
+       int ret;
+       struct sockaddr_storage dst;
+
+       /* Reset to ensure <conn> is always initialized */
+       *conn = NULL;
+       dst = srv->addr;
+       set_host_port(&dst, srv->svc_port);
+       *hash = be_calculate_conn_hash(srv, NULL, sess, NULL, &dst, IST_NULL);
+       ret = be_reuse_connection(*hash, sess, &hld_proxy, srv, sc, &srv->obj_type, 0);
+       if (ret == SF_ERR_INTERNAL) {
+               TRACE_ERROR("error during connection reuse", HLD_STRM_EV_TASK);
+               ret = 0;
+               goto leave;
+       }
+
+       if (ret == SF_ERR_NONE) {
+               TRACE_STATE("performed connection reuse", HLD_STRM_EV_TASK);
+               *conn = __sc_conn(sc);
+               conn_set_owner(*conn, sess, hld_conn_destroy);
+       }
+
+       ret = 1;
+ leave:
+       return ret;
+}
+
+/* haload stream task handler */
+struct task *hld_strm_task(struct task *t, void *context, unsigned int state)
+{
+       struct hldstream *hs = context, *first_hs;
+       struct hld_usr *usr = hs->usr;
+       struct hld_url *url = hs->url;
+       struct connection *conn = sc_conn(hs->sc);
+       struct session *sess = usr->sess;
+       struct server *srv = url->cfg->srv;
+       int fin = 0;
+
+       TRACE_ENTER(HLD_STRM_EV_TASK, hs);
+
+       if (sc_ep_test(hs->sc, SE_FL_ERROR) || (conn && (conn->flags & CO_FL_ERROR))) {
+               TRACE_ERROR("connection error", HLD_STRM_EV_IO_CB, hs);
+               hs->flags |= HLD_STRM_ST_CONN_ERR;
+               goto err;
+       }
+
+       if (tick_is_expired(t->expire, now_ms)) {
+               TRACE_STATE("expired task", HLD_STRM_EV_TASK, hs);
+               t = NULL;
+               goto err;
+       }
+
+       if (conn && conn->mux && conn->flags & CO_FL_WAIT_XPRT) {
+               TRACE_STATE("waiting for xprt, subscribing to send", HLD_STRM_EV_TASK, hs);
+               if (conn->mux->subscribe(hs->sc, SUB_RETRY_SEND, &hs->sc->wait_event) < 0) {
+                       TRACE_ERROR("send subscribing error", HLD_STRM_EV_TASK, hs);
+                       goto out;
+               }
+       }
+
+       if (!hs->conn) {
+               struct protocol *proto;
+               int flags = arg_fast ? (CONNECT_HAS_DATA|CONNECT_DELACK_ALWAYS) : 0;
+               int status;
+
+               BUG_ON(conn);
+
+               hldstream_release_ibuf(hs);
+               hldstream_release_obuf(hs);
+
+               conn = conn_new(&srv->obj_type);
+               if (!conn) {
+                       TRACE_ERROR("stconn allocation error", HLD_STRM_EV_TASK, hs);
+                       goto err;
+               }
+
+               conn->hash_node.key = hs->hash;
+               // VOIR la CB ici :
+               conn_set_owner(conn, sess, hld_conn_destroy);
+               BUG_ON(hs->sc->sedesc->sc != hs->sc);
+               if (sc_attach_mux(hs->sc, hs->sc->sedesc, conn) < 0) {
+                       TRACE_ERROR("mux attach error", HLD_STRM_EV_TASK, hs);
+                       goto err;
+               }
+
+               if (!sockaddr_alloc(&conn->dst, NULL, 0)) {
+                       TRACE_ERROR("sockaddr allocation error", HLD_STRM_EV_TASK, hs);
+                       goto err;
+               }
+
+               *conn->dst = srv->addr;
+               proto = protocol_lookup(conn->dst->ss_family,
+                                                               srv->addr_type.proto_type, srv->alt_proto);
+               set_host_port(conn->dst, srv->svc_port);
+
+               if (conn_prepare(conn, proto, srv->xprt) < 0) {
+                       TRACE_ERROR("xprt allocation error", HLD_STRM_EV_TASK, hs);
+                       goto err;
+               }
+
+               /* Note that in case of connect() failure, the callback
+                * set by conn_set_owner() is called. Its role is to decrement
+                * the <currconn> counter. So, it must incremented here.
+                */
+               thrs_info[tid].curconn++;
+               thrs_info[tid].tot_conn++;
+
+               BUG_ON(!proto || !proto->connect);
+               status = proto->connect(conn, flags);
+               if (status != SF_ERR_NONE) {
+                       TRACE_ERROR("proto connect error", HLD_STRM_EV_TASK, hs);
+                       goto err;
+               }
+
+               conn_set_private(conn);
+               session_add_conn(sess, conn);
+               conn->ctx = hs->sc;
+
+               if (conn_xprt_start(conn) < 0) {
+                       TRACE_ERROR("could not start xprt", HLD_STRM_EV_TASK, hs);
+                       goto err;
+               }
+
+               if (!conn_is_ssl(conn) || !srv->ssl_ctx.alpn_str) {
+                       const struct mux_ops *mux_ops;
+
+                       if (srv->mux_proto)
+                               mux_ops = srv->mux_proto->mux;
+                       else
+                               mux_ops = conn_get_best_mux(conn, IST_NULL, IST_NULL, PROTO_SIDE_BE, PROTO_MODE_HTTP);
+                       if (!mux_ops || conn_install_mux(conn, mux_ops, hs->sc, &hld_proxy, sess) < 0) {
+                               TRACE_ERROR("mux installation failed", HLD_STRM_EV_TASK, hs);
+                               goto err;
+                       }
+               }
+
+               hs->conn = conn;
+               if (conn->flags & CO_FL_WAIT_XPRT) {
+                       TRACE_STATE("waiting for xprt", HLD_STRM_EV_TASK, hs);
+                       if (conn->mux) {
+                               TRACE_STATE("subscribing to send", HLD_STRM_EV_TASK, hs);
+                               conn->mux->subscribe(hs->sc, SUB_RETRY_SEND, &hs->sc->wait_event);
+                       }
+               }
+
+               goto out;
+       }
+
+       if (hs->flags & HLD_STRM_ST_REQ_TO_BUILD) {
+               if (!hldstream_build_http_req(hs, ist(hs->path), 1))
+                       goto out;
+
+               hs->flags &= ~HLD_STRM_ST_REQ_TO_BUILD;
+       }
+
+       if (!hldstream_htx_buf_snd(conn, hs))
+               goto out;
+
+       hldstream_htx_buf_rcv(conn, hs, &fin);
+
+ out:
+       if (hs->flags & HLD_STRM_ST_CONN_ERR) {
+               TRACE_ERROR("haload stream error", HLD_STRM_EV_TASK, hs);
+               goto err;
+       }
+
+       if (fin) {
+               TRACE_STATE("end of stream", HLD_STRM_EV_TASK, hs);
+               goto done;
+       }
+
+       /* Update this stream expiration */
+       hs->expire = tick_add(now_ms, MS_TO_TICKS(arg_wait));
+       /* Requeue it at the end of the usr streams list */
+       LIST_DELETE(&hs->list);
+       LIST_APPEND(&usr->strms, &hs->list);
+       /* Update the user task expiration from the first stream which
+        * is also the stream with the oldest expiration time.
+        */
+       first_hs = LIST_ELEM(usr->strms.n, struct hldstream *, list);
+       hs->usr->task->expire = first_hs->expire;
+       task_queue(hs->usr->task);
+ leave:
+       TRACE_LEAVE(HLD_STRM_EV_TASK, hs);
+       return t;
+ done:
+       url->tot_rconn_done++;
+       BUG_ON(arg_rcon > 0 && url->tot_rconn_done > arg_rcon);
+       url->mreqs++;
+       if (arg_rcon > 0 && url->tot_rconn_done == arg_rcon) {
+               /* All the streams attached to this connection will be release */
+               TRACE_STATE("releasing connection", HLD_EV_USR_TASK, hs);
+               sc_ep_set(hs->sc, SE_FL_KILL_CONN);
+               /* Reset these counters here. Cannot be done elsewhere */
+               url->tot_rconn_done = 0;
+               url->tot_rconn_sent = 0;
+               url->mreqs = arg_mreqs;
+       }
+
+       /* Note that the user task will release all the expired streams
+        * attached to it.
+        */
+       task_wakeup(usr->task, TASK_WOKEN_IO);
+
+       LIST_DELETE(&hs->list);
+       hldstream_free(&hs);
+       t = NULL;
+
+       goto leave;
+ err:
+       TRACE_DEVEL("leaving on error", HLD_STRM_EV_TASK, hs);
+       thrs_info[tid].tot_perr++;
+       url->mreqs++;
+       if (arg_rcon > 0) {
+               BUG_ON(!url->tot_rconn_sent);
+               url->tot_rconn_sent--;
+       }
+       BUG_ON(arg_rcon > 0 && url->tot_rconn_done > arg_rcon);
+       /* Note that the user task will release all the expired streams
+        * attached to it.
+        */
+       task_wakeup(usr->task, TASK_WOKEN_IO);
+       LIST_DELETE(&hs->list);
+       hldstream_free(&hs);
+       t = NULL;
+       if (arg_serr) {
+               usr->flags |= HLD_USR_FL_STOP;
+               HA_ATOMIC_STORE(&all_usr_stop_asap, 1);
+       }
+
+       goto leave;
+}
+
+/* Allocate a new haload stream.
+ * Return 1 if succeeded, 0 if not.
+ */
+static struct hldstream *hld_new_strm(struct hld_usr *usr,
+                                      struct hld_url *url,
+                                      struct hld_path *path)
+{
+       struct hldstream *hs;
+       struct stconn *sc;
+       struct task *t;
+       int64_t hash;
+       struct connection *conn;
+
+       TRACE_ENTER(HLD_STRM_EV_TASK);
+       hs = malloc(sizeof(*hs));
+       sc = sc_new_from_hldstream(hs, SC_FL_NONE);
+       t = task_new_here();
+       if (unlikely(!hs || !sc || !t)) {
+               TRACE_ERROR("could not allocate a new stconn", HLD_STRM_EV_TASK);
+               goto err;
+       }
+
+       /* Mandatory to make sc_attach_mux() identify this stream type */
+       hs->obj_type = OBJ_TYPE_HALOAD;
+       if (!hld_be_reuse_conn(&conn, &hash, sc, usr->sess, url->cfg->srv)) {
+               TRACE_ERROR("internal error during a connection reuse attempt",
+                           HLD_STRM_EV_TASK);
+               goto err;
+       }
+
+       t->context = hs;
+       t->process = hld_strm_task;
+       t->expire = TICK_ETERNITY;
+
+       hs->conn = conn;
+       hs->expire = tick_add(now_ms, MS_TO_TICKS(arg_wait));
+       hs->hash = hash;
+       hs->usr = usr;
+       hs->url = url;
+       hs->path = path->path;
+       hs->sc = sc;
+       hs->bi = hs->bo = BUF_NULL;
+       LIST_INIT(&hs->buf_wait.list);
+       hs->task = t;
+       hs->flags = conn ? HLD_STRM_ST_REQ_TO_BUILD : HLD_STRM_ST_REQ_TO_BUILD;
+       hs->state = 0;
+       hs->to_send = 0;
+       hs->req_date = tv_unset();
+       LIST_APPEND(&usr->strms, &hs->list);
+       task_wakeup(t, TASK_WOKEN_INIT);
+
+       TRACE_LEAVE(HLD_STRM_EV_TASK, hs);
+       return hs;
+ err:
+       TRACE_DEVEL("leaving on error", HLD_STRM_EV_TASK, hs);
+       task_destroy(t);
+       sc_destroy(sc);
+       free(hs);
+       return NULL;
+}
+
+static inline struct hld_url *hld_next_url(struct hld_url *list,
+                                           struct hld_url *cur)
+{
+       return cur->next ? cur->next : list;
+}
+
+static inline struct hld_path *hld_next_path(struct hld_path *list,
+                                             struct hld_path *cur)
+{
+       return cur->next ? cur->next : list;
+}
+
+/* Release the memory allocated for <*usr> user.
+ * Also free the session attached to it.
+ */
+static inline void hld_usr_release(struct hld_usr **usr)
+{
+       struct hld_url *url;
+
+       url = (*usr)->urls;
+       while (url) {
+               struct hld_url *url_next = url->next;
+               ha_free(&url);
+               url = url_next;
+       }
+
+       task_destroy((*usr)->task);
+       session_free((*usr)->sess);
+       ha_free(usr);
+}
+
+static struct task *hld_usr_task(struct task *t, void *context, unsigned int state)
+{
+       struct hld_usr *usr = context;
+       struct hld_url *url, *urls = usr->cur_url, *first_url = urls;
+       struct hldstream *hs, *hsbak;
+       int nreqs;
+       int remain = -1;
+
+       TRACE_ENTER(HLD_EV_USR_TASK);
+
+       if (tick_is_expired(t->expire, now_ms))
+               t->expire = TICK_ETERNITY;
+
+       list_for_each_entry_safe(hs, hsbak, &usr->strms, list) {
+               if (!tick_is_expired(hs->expire, now_ms))
+                       break;
+
+               TRACE_STATE("expired task", HLD_EV_USR_TASK, hs);
+               thrs_info[tid].tot_done++;
+               hs->url->mreqs++;
+               usr->nreqs = usr->nreqs == -1 ? -1 : usr->nreqs + 1;
+               LIST_DELETE(&hs->list);
+               hldstream_free(&hs);
+       }
+
+       if ((usr->flags & HLD_USR_FL_STOP) || HA_ATOMIC_LOAD(&all_usr_stop_asap)) {
+               usr->flags |= HLD_USR_FL_STOP;
+               goto skip_new_strms;
+       }
+
+       for (url = urls; url; url = hld_next_url(urls, url)) {
+               struct hld_path *path, *paths = url->cfg->cur_path;
+
+               nreqs = usr->nreqs >= 0 ? MIN(usr->nreqs, url->mreqs) : url->mreqs;
+               BUG_ON(arg_rcon > 0 && url->tot_rconn_sent > arg_rcon);
+               nreqs = arg_rcon > 0 ? MIN(arg_rcon - url->tot_rconn_sent, nreqs) : nreqs;
+
+               for (path = paths; path && nreqs; path = hld_next_path(url->cfg->paths, path)) {
+                       struct hldstream *hs;
+
+                       if ((hs = hld_new_strm(usr, url, path)) == NULL) {
+                               TRACE_ERROR("could start a new stream task", HLD_EV_USR_TASK);
+                               goto out;
+                       }
+
+                       url->cfg->cur_path = hld_next_path(url->cfg->paths, path);
+                       BUG_ON(!url->mreqs || !usr->nreqs || !nreqs);
+
+                       if (arg_rcon > 0)
+                               url->tot_rconn_sent++;
+                       url->mreqs--;
+                       nreqs--;
+                       usr->nreqs = usr->nreqs == -1 ? -1 : usr->nreqs - 1;
+
+                       if (hs->conn) {
+                               url->tot_req++;
+                               remain = hs->conn->mux->avail_streams(hs->conn);
+                               TRACE_PRINTF(TRACE_LEVEL_PROTO, HLD_STRM_EV_TASK, hs, 0, 0, 0,
+                                            "remain %d avail. strms", remain);
+                               if (!remain)
+                                       break;
+                       }
+                       else {
+                               /* Connecting */
+                               url->tot_req = 1;
+                               remain = 0;
+                               break;
+                       }
+
+                       if (!usr->nreqs)
+                               break;
+               }
+
+               if (!usr->nreqs || hld_next_url(urls, url) == first_url)
+                       break;
+       }
+
+ skip_new_strms:
+       /* From here, some new streams may have been instantiated or
+        * release upon expiration. This is where this user task
+        * expiration must be updated.
+        */
+       if (!LIST_ISEMPTY(&usr->strms)) {
+               struct hldstream *first_hs =
+                       LIST_ELEM(usr->strms.n, struct hldstream *, list);
+               BUG_ON(tick_is_expired(first_hs->expire, now_ms));
+               usr->task->expire = first_hs->expire;
+       }
+
+       if (((usr->flags & HLD_USR_FL_STOP) || !usr->nreqs) && LIST_ISEMPTY(&usr->strms)) {
+               HA_ATOMIC_DEC(&running_usrs);
+               hld_usr_release(&usr);
+               t = NULL;
+               goto out;
+       }
+
+ out:
+       TRACE_LEAVE(HLD_EV_USR_TASK);
+       return t;
+}
+
+/* Instantiate a haload user and wake up its underlying task */
+static inline struct hld_usr *hld_new_usr(int nreqs, int tid)
+{
+       struct hld_usr *usr;
+       struct hld_url_cfg *cfg;
+       struct hld_url *url, *urls = NULL, *next_url;
+       struct task *t;
+       struct session *sess;
+
+       BUG_ON(!nreqs);
+       usr = malloc(sizeof(*usr));
+       t = task_new_on(tid);
+       sess = session_new(&hld_proxy, NULL, NULL);
+       if (!usr || !t || !sess) {
+               ha_alert("could not allocate a new user\n");
+               goto err;
+       }
+
+       t->process = hld_usr_task;
+       t->context = usr;
+       t->expire = TICK_ETERNITY;
+
+       usr->task = t;
+       usr->sess = sess;
+       usr->flags = 0;
+       usr->urls = NULL;
+       usr->nreqs = nreqs;
+       LIST_INIT(&usr->strms);
+
+       for (cfg = hld_url_cfgs; cfg; cfg = cfg->next) {
+               struct hld_url *url;
+
+               url = malloc(sizeof(*url));
+               if (!url)
+                       goto err;
+
+               url->tot_req = 0;
+               url->tot_rconn_done = 0;
+               url->tot_rconn_sent = 0;
+               url->mreqs = arg_mreqs;
+               url->flags = 0;
+               url->cfg = cfg;
+               url->next = usr->urls;
+               usr->urls = url;
+       }
+
+       HA_ATOMIC_INC(&thrs_info[tid].curusrs);
+       /* inverse the URLs order */
+       url = usr->urls;
+       while (url) {
+               next_url = url->next;
+               url->next = urls;
+               urls = url;
+               url = next_url;
+       }
+       usr->urls = urls;
+
+       usr->cur_url = usr->urls;
+       task_wakeup(t, TASK_WOKEN_INIT);
+       return usr;
+
+ err:
+       url = usr->urls;
+       while (url) {
+               next_url = url->next;
+               free(url);
+               url = next_url;
+       }
+       task_destroy(t);
+       free(usr);
+       return NULL;
+}
+
+/* Parse <opt> options for <s> server */
+static int hld_srv_parse_opts(char *opts, struct server *s)
+{
+       int ret = 0;
+       size_t outlen = 256;
+       int cur_arg = 0;
+       char *outline;
+       uint32_t err;
+       int err_code;
+       int arg = sizeof(hld_args) / sizeof(*hld_args);
+       const char *errptr = NULL;
+
+       outline = malloc(256);
+       if (!outline)
+               return 0;
+
+       err = parse_line(opts, outline, &outlen, hld_args, &arg,
+                                        PARSE_OPT_ENV    | PARSE_OPT_DQUOTE  |
+                                        PARSE_OPT_SQUOTE | PARSE_OPT_BKSLASH |
+                                        PARSE_OPT_SHARP  | PARSE_OPT_WORD_EXPAND, &errptr);
+       if (err) {
+               ha_alert("ssl opts parsing error\n");
+               goto err;
+       }
+
+       while (*hld_args[cur_arg]) {
+               err_code = _srv_parse_kw(s, hld_args, &cur_arg, &hld_proxy, 0);
+               if (err_code)
+                       goto err;
+       }
+
+       ret = 1;
+ leave:
+       free(outline);
+       return ret;
+ err:
+       goto leave;
+}
+
+static int hld_cfg_finalize(void)
+{
+       int ret = 0;
+       struct hld_url_cfg *cfg;
+
+       for (cfg = hld_url_cfgs; cfg; cfg = cfg->next) {
+               struct server *srv;
+               struct sockaddr_storage *sk;
+               int alt_proto, port;
+               char *errmsg = NULL;
+
+               /* Same as _srv_parse_init() from here */
+               srv = new_server(&hld_proxy);
+               if (!srv) {
+                       ha_alert("could not allocate a new server\n");
+                       goto leave;
+               }
+
+               sk = str2sa_range(cfg->addr, &port, NULL, NULL, NULL, NULL,
+                                                 &srv->addr_type, &errmsg, NULL, NULL, &alt_proto,
+                                                 PA_O_PORT_OK | PA_O_STREAM | PA_O_DGRAM | PA_O_XPRT);
+               if (!sk) {
+                       ha_alert("%s\n", errmsg);
+                       ha_free(&errmsg);
+                       goto leave;
+               }
+
+               srv->id = strdup("haload");
+               srv->addr = *sk;
+               srv->svc_port = port;
+               srv->alt_proto = alt_proto;
+               srv->use_ssl = cfg->ssl;
+               srv->xprt = srv_is_quic(srv) ? xprt_get(XPRT_QUIC) :
+                       srv->use_ssl ? xprt_get(XPRT_SSL) : xprt_get(XPRT_RAW);
+
+#ifdef USE_QUIC
+               if (srv_is_quic(srv))
+                       quic_transport_params_init(&srv->quic_params, 0);
+#endif
+
+               /* XXX Must this be done? XXX */
+               //srv_set_addr_desc(srv, 0);
+               srv_settings_init(srv);
+
+               if (cfg->srv_opts && !hld_srv_parse_opts(cfg->srv_opts, srv))
+                       goto leave;
+
+               if (cfg->tls_opts && !hld_srv_parse_opts(cfg->tls_opts, srv))
+                       goto leave;
+
+               /* Same as _srv_parse_finalize() from here */
+               if (srv_is_quic(srv)) {
+                       if (!srv->use_ssl)
+                               srv->use_ssl = 1;
+
+                       if (!srv->ssl_ctx.alpn_str) {
+                               srv->ssl_ctx.alpn_str = strdup("\002h3");
+                               if (!srv->ssl_ctx.alpn_str) {
+                                       ha_alert("could not allocate a default alpn.\n");
+                                       goto leave;
+                               }
+
+                               srv->ssl_ctx.alpn_len = strlen(srv->ssl_ctx.alpn_str);
+                       }
+               }
+
+               if (!srv->mux_proto) {
+                       if (srv_is_quic(srv))
+                               srv->mux_proto = get_mux_proto(ist("quic"));
+                       else if (cfg->h2c)
+                               srv->mux_proto = get_mux_proto(ist("h2"));
+               }
+
+               if (srv->mux_proto) {
+                       int proto_mode = conn_pr_mode_to_proto_mode(hld_proxy.mode);
+                       const struct mux_proto_list *mux_ent;
+
+                       mux_ent = conn_get_best_mux_entry(srv->mux_proto->mux_proto, IST_NULL,
+                                                         PROTO_SIDE_BE,
+                                                         srv_is_quic(srv), proto_mode);
+
+                       if (!mux_ent || !isteq(mux_ent->mux_proto, srv->mux_proto->mux_proto)) {
+                               ha_alert("MUX protocol is not usable for server.\n");
+                               goto leave;
+                       }
+                       else {
+                               if ((mux_ent->mux->flags & MX_FL_FRAMED) && !srv_is_quic(srv)) {
+                                       ha_alert("MUX protocol is incompatible with stream"
+                                                " transport used by server.\n");
+                                       goto leave;
+                               }
+                               else if (!(mux_ent->mux->flags & MX_FL_FRAMED) && srv_is_quic(srv)) {
+                                       ha_alert("MUX protocol is incompatible with framed"
+                                                " transport used by server.\n");
+                                       goto leave;
+                               }
+                       }
+               }
+
+               /* ensure minconn/maxconn consistency */
+               srv_minmax_conn_apply(srv);
+
+               if (srv->use_ssl) {
+                       if (xprt_get(XPRT_SSL) && xprt_get(XPRT_SSL)->prepare_srv) {
+                               if (xprt_get(XPRT_SSL)->prepare_srv(srv))
+                                       goto leave;
+                       }
+                       /* XXX TO CHECK XXX: in fact XPRT_SSL and XPRT_QUIC have the same
+                        * ->prepare_srv() callback.
+                        */
+                       else if (xprt_get(XPRT_QUIC) && xprt_get(XPRT_QUIC)->prepare_srv) {
+                               if (xprt_get(XPRT_QUIC)->prepare_srv(srv))
+                                       goto leave;
+                       }
+               }
+
+               if (srv_preinit(srv))
+                       goto leave;
+#if 0
+               /* XXX Must this be done? XXX */
+               if (!srv_alloc_lb(srv, &hld_proxy)) {
+                       ha_alert("Failed to initialize load-balancing data.\n");
+                       goto leave;
+               }
+#endif
+
+               if (!stats_allocate_proxy_counters_internal(&srv->extra_counters,
+                                                           COUNTERS_SV, STATS_PX_CAP_SRV,
+                                                           &srv->per_tgrp->extra_counters_storage,
+                                                           &srv->per_tgrp[1].extra_counters_storage -
+                                                           &srv->per_tgrp[0].extra_counters_storage)) {
+                       ha_alert("failed to allocate extra counters for server.\n");
+                       goto leave;
+               }
+
+               if (srv_postinit(srv))
+                       goto leave;
+
+               /* Attach the server to the URL */
+               cfg->srv = srv;
+       }
+
+       ret = 1;
+leave:
+       return ret;
+}
+
+/* this is in order to cleanly stop on Ctrl-C */
+void sigint_handler(int sig)
+{
+       /* make sure a second Ctrl-C really stops */
+       HA_ATOMIC_STORE(&all_usr_stop_asap, 1);
+       signal(SIGINT, SIG_DFL);
+}
+
+/* Deallocate the thread information structs */
+static void hld_dealloc_thrs_info(void)
+{
+       free(thrs_info);
+       thrs_info = NULL;
+}
+
+/* Allocate all thread information structs */
+static int hld_alloc_thrs_info(void)
+{
+       int i;
+
+       thrs_info = calloc(global.nbthread, sizeof(*thrs_info));
+       if (!thrs_info) {
+               ha_alert("failed to alloct threads information array.\n");
+               return 0;
+       }
+
+       for (i = 0; i < global.nbthread; i++)
+               thrs_info[i].maxusrs = (arg_usr + i) / global.nbthread;
+
+       return 1;
+}
+
+static int hld_init(void)
+{
+       int ret = ERR_ALERT | ERR_FATAL;
+       char *errmsg = NULL;
+
+       if (arg_slow)
+               throttle = 1;
+
+       if (!hld_cfg_finalize())
+               goto leave;
+
+       /* Consider the case where <arg_reqs> < <arg_usr> */
+       if (arg_reqs != -1 && arg_reqs < arg_usr)
+               arg_usr = arg_reqs;
+
+       if (!hld_alloc_thrs_info())
+               goto leave;
+
+       mtask.t = task_new_here();
+       if (mtask.t == NULL) {
+               ha_alert("could start main task\n");
+               goto leave;
+       }
+
+       if (arg_long >= 2)
+               printf("#_____time conns tot_conn  tot_req      tot_bytes"
+                      "    err thr cps rps Bps bps ttfb(us) ttlb(us)");
+       else if (arg_long)
+               printf("#     time conns tot_conn  tot_req      tot_bytes"
+                      "    err  cps  rps  Bps  bps   ttfb   ttlb");
+       else
+               printf("#     time conns tot_conn  tot_req      tot_bytes"
+                      "    err  cps  rps  bps   ttfb");
+       if (arg_hscd)
+               printf(" 1xx 2xx 3xx 4xx 5xx");
+       putchar('\n');
+
+       mtask.t->process = mtask_cb;
+       mtask.t->state |= TASK_RT;
+       mtask.t->expire = TICK_ETERNITY;
+       mtask.show_time = tick_add(now_ms, MS_TO_TICKS(1000));
+
+       task_wakeup(mtask.t, TASK_WOKEN_INIT);
+
+       gettimeofday(&hld_start_date, NULL);
+       if (arg_dura)
+               tv_ms_add(&hld_stop_date, &hld_start_date, arg_dura * 1000);
+       else
+               hld_stop_date = tv_unset();
+
+       signal(SIGINT, sigint_handler);
+
+       ret = ERR_NONE;
+ leave:
+       ha_free(&errmsg);
+       return ret;
+}
+REGISTER_POST_CHECK(hld_init);
diff --git a/src/haload_init.c b/src/haload_init.c
new file mode 100644 (file)
index 0000000..f842fe0
--- /dev/null
@@ -0,0 +1,737 @@
+#include <errno.h>
+
+#include <haproxy/connection.h>
+#include <haproxy/errors.h>
+#include <haproxy/global.h>
+#include <haproxy/hbuf.h>
+#include <haproxy/haload.h>
+#include <haproxy/proxy.h>
+#include <haproxy/version.h>
+#include <haproxy/server.h>
+
+static int hld_debug;
+struct hld_url_cfg *hld_url_cfgs;
+char *srv_opts, *tls_ciphers, *tls_ciphersuites, *tls_curves, *alpn;
+int h2c;
+
+static void  hld_usage(char *name, int argc)
+{
+       fprintf(stderr,
+               "Usage : %s [opts] [URL]\n"
+               "where <opts> may be any combination of:\n"
+               "        -d <time>        test duration in seconds (0)\n"
+               "        -e               stop upon first connection error\n"
+               "        -h(0|1|2|2c|3)   use h0 (hq-interop for QUIC), h1, h2, h2c or h3 (QUIC/TCP) protocols (*)\n"
+               "        -(0|1|2|2c|3)    same as above (*)\n"
+               "        -l               enable long output format; double for raw values\n"
+               "        -m <streams>     maximum concurrent streams (1)\n"
+               "        -n <reqs>        maximum total requests (-1)\n"
+               "        -r <reqs>        number of requests per connection (-1)\n"
+               "        -s <time>        soft start: time in sec to reach 100%% load\n"
+               "        -t <threads>     number of threads\n"
+               "        -u <users>       number of users (1)\n"
+               "        -w <time>        I/O timeout in milliseconds (10000)\n"
+               "        -A               ignore 1st req for resp time measurements\n"
+               "        -C               dump the configuration and exit\n"
+               "        -F               merge send() with connect's ACK\n"
+               "        -H \"foo:bar\"   add this header name and value\n"
+               "        -I               use HEAD instead of GET\n"
+               "        -v               shows version\n"
+               "        --defaults <str> add a string to default section\n"
+               "        --global <str>   add a string to global section\n"
+               "        --server <opts>  set server <opt> options as defined for \"server\" haproxy keyword\n"
+               "        --show-status-codes show HTTP status codes distribution\n"
+               "        --traces         enable the traces for all the HTTP protocols\n"
+               "SSL options:\n"
+               "        --tls-ciphers <ciphers>       for TLS1.2 and below (*)\n"
+               "        --tls-ciphersuites <ciphers>  for TLS1.3 and above (*)\n"
+               "        --tls-curves <curves> (*)\n"
+               "URL format:\n"
+               "        (http|https|quic)://<addr>:<port>/<path>\n"
+               "Note: Options marked with an asterisk (*) are positional and MUST be placed\n"
+               "      BEFORE the URLs they are intended to affect.\n",
+               name);
+       exit(1);
+}
+
+static const char *hld_cfg_traces_str =
+       "traces\n"
+#if defined(USE_OPENSSL)
+               "\ttrace ssl sink stderr level developer start now verbosity minimal\n"
+#endif
+#if defined(USE_QUIC)
+               "\ttrace quic sink stderr level developer start now\n"
+               "\ttrace h3 sink stderr level developer start now verbosity minimal\n"
+               "\ttrace qmux sink stderr level developer start now verbosity minimal\n"
+#endif
+               "\ttrace haload sink stderr level developer start now verbosity clean\n"
+               "\ttrace h1 sink stderr level developer start now verbosity minimal\n"
+               "\ttrace h2 sink stderr level developer start now verbosity minimal\n";
+
+/* Allocate <hdr_str> header with "<name>:<value>" form and
+ * returs it.
+ * Return the hld_hdr struct header if succeeded, NULL if not
+ */
+static struct hld_hdr *hld_parse_hdr(char *hdr_str)
+{
+       struct hld_hdr *hdr= NULL;
+       char *value = strchr(hdr_str, ':');
+
+       if (value) {
+               *value++ = '\0';
+               if (!*value)
+                       value = NULL;
+       }
+
+       if (strcasecmp(hdr_str, "host") == 0)
+               arg_host = value;
+       else if (strcasecmp(hdr_str, "connection") == 0)
+               arg_conn_hdr = value;
+
+       hdr = malloc(sizeof(*hdr));
+       if (hdr) {
+               hdr->name = ist(hdr_str);
+               hdr->value = ist(value);
+       }
+
+       return hdr;
+}
+
+/* Add option made of <kw> keyword and <value> value to <buf> buffer */
+static int hld_add_opt_to_buf(struct hbuf *buf,
+                              const char *kw, const char *value)
+{
+       if (hbuf_is_null(buf)) {
+               if (hbuf_alloc(buf) == NULL) {
+                       ha_alert("failed to allocate a buffer.\n");
+                       return 0;
+               }
+       }
+       else
+               hbuf_appendf(buf, " ");
+
+       hbuf_appendf(buf, "%s", kw);
+       hbuf_appendf(buf, " ");
+       hbuf_appendf(buf, "%s", value);
+       return 1;
+}
+
+/* Free <u> URL config */
+static inline void hld_free_url_cfg(struct hld_url_cfg *u)
+{
+       if (!u)
+               return;
+
+       free(u->addr);
+       free(u->srv_opts);
+       free(u->tls_opts);
+       free(u);
+}
+
+/* Free all the allocated URL configs */
+static inline void hld_free_url_cfgs(void)
+{
+       struct hld_url_cfg *purl;
+
+       purl = hld_url_cfgs;
+
+       while (purl) {
+               struct hld_url_cfg *purl_next;
+               struct hld_path *path;
+
+               path = purl->paths;
+               while (path) {
+                       struct hld_path *path_next;
+
+                       path_next = path->next;
+                       free(path->path);
+                       free(path);
+                       path = path_next;
+               }
+
+               purl_next = purl->next;
+
+               hld_free_url_cfg(purl);
+               purl = purl_next;
+       }
+}
+
+/* Return 1 if <path> already exists in <ur> URL conf */
+int hld_url_cfg_path_exist(struct hld_url_cfg *u, const char *path)
+{
+       struct hld_path *p = u->paths;
+
+       while (p) {
+               if (!strcmp(p->path, path))
+                       return 1;
+
+               p = p->next;
+       }
+
+       return 0;
+}
+
+/* Allocate a URL from <url> command line argument without
+ * duplicating it, and append it to <hld_url_cfgs> list of URL.
+ * A URL is identified by its peer address and if it uses
+ * SSL or not. When a URL with the same peer address already
+ * exists, this function only add a new path to the list
+ * of paths attaached to the URL.
+ * Return the URL if succeeded, NULL if not.
+ */
+static struct hld_url_cfg *hld_alloc_url(char *url)
+{
+       int ssl = 0, is_quic = 0;
+       char *addr = NULL, *raw_addr = NULL, *path = NULL;
+       struct hld_url_cfg *hld_url_cfg = NULL;
+       struct hld_url_cfg *purl;
+       struct hld_path *p = NULL;
+       struct hbuf opts_buf = HBUF_NULL;
+       char quic_addr[128];
+
+       if (strncmp(url, "http://", 7) == 0)
+               addr = url + 7;
+       else if (strncmp(url, "https://", 8) == 0) {
+#if !defined(USE_OPENSSL)
+               ha_warning("SSL support not compiled in. Rebuild with USE_OPENSSL=1.\n");
+               goto err;
+#else
+               ssl = 1;
+               addr = url + 8;
+#endif
+       }
+       else if (strncmp(url, "quic://",  7) == 0) {
+#if defined(USE_QUIC)
+               ssl = 1;
+               addr = url + 7;
+               is_quic = 1;
+#else
+               ha_warning("QUIC support not compiled in. Rebuild with USE_QUIC=1.\n");
+               goto err;
+#endif
+       }
+       else
+               addr = url;
+
+       path = strchr(addr, '/');
+       if (path) {
+               char *new_path = strdup(path);
+               *path = '\0';
+               path = new_path;
+       }
+       else
+               path = strdup("/");
+
+       if (!path)
+               goto err;
+
+       for (purl = hld_url_cfgs; purl; purl = purl->next) {
+               if (purl->is_quic == is_quic && purl->ssl == ssl &&
+                   strcmp(purl->raw_addr, addr) == 0 && strcmp(purl->alpn, alpn) == 0) {
+                       if (hld_url_cfg_path_exist(purl, path)) {
+                               free(path);
+                               ha_warning("'%s' URL already exists. Skipped...\n", url);
+                               return purl;
+                       }
+
+                       /* Already existing URL with the same address. */
+                       hld_url_cfg = purl;
+
+                       p = calloc(1, sizeof(*p));
+                       if (!p)
+                               goto err;
+
+                       /* Append a new path to this URL */
+                       p->path = path;
+                       p->next = hld_url_cfg->paths;
+                       hld_url_cfg->paths = p;
+
+                       return hld_url_cfg;
+               }
+       }
+
+       if (!is_quic) {
+               addr = strdup(addr);
+       }
+       else {
+               snprintf(quic_addr, sizeof(quic_addr), "quic+%s", addr);
+               addr = strdup(quic_addr);
+       }
+
+       if (!addr)
+               goto err;
+
+       raw_addr = strchr(addr, '+');
+       if (!raw_addr)
+               raw_addr = strchr(addr, '@');
+
+       raw_addr = raw_addr ? raw_addr + 1: addr;
+
+       hld_url_cfg = calloc(1, sizeof(*hld_url_cfg));
+       p = malloc(sizeof(*p));
+       if (!hld_url_cfg || !p)
+               goto err;
+
+       p->path = path;
+       p->next = NULL;
+
+       hld_url_cfg->ssl = ssl;
+       hld_url_cfg->is_quic = is_quic;
+       hld_url_cfg->h2c = h2c;
+       hld_url_cfg->addr = addr;
+       hld_url_cfg->raw_addr = raw_addr;
+       if (alpn) {
+               hld_url_cfg->alpn = strdup(alpn);
+               if (!hld_url_cfg->alpn) {
+                       ha_warning("Could not allocate alpn.\n");
+                       goto err;
+               }
+       }
+
+       if (srv_opts) {
+               hld_url_cfg->srv_opts = strdup(srv_opts);
+               if (!hld_url_cfg->srv_opts)
+                       goto err;
+       }
+
+       if (tls_ciphers &&
+           !hld_add_opt_to_buf(&opts_buf, "ciphers", tls_ciphers))
+               goto err;
+
+       if (tls_ciphersuites &&
+           !hld_add_opt_to_buf(&opts_buf, "ciphersuites", tls_ciphersuites))
+               goto err;
+
+       if (tls_curves &&
+           !hld_add_opt_to_buf(&opts_buf, "curves", tls_curves))
+               goto err;
+
+       if (alpn && !h2c &&
+           !hld_add_opt_to_buf(&opts_buf, "alpn", alpn))
+               goto err;
+
+       if (!hbuf_is_null(&opts_buf))
+               hld_url_cfg->tls_opts = strdup(opts_buf.area);
+
+       free_hbuf(&opts_buf);
+       hld_url_cfg->srv = NULL;
+       hld_url_cfg->paths = p;
+       /* Append this new URL to the list */
+       hld_url_cfg->next = hld_url_cfgs;
+       hld_url_cfgs = hld_url_cfg;
+
+       return hld_url_cfg;
+ err:
+       hld_free_url_cfgs();
+       hld_free_url_cfg(hld_url_cfg);
+       free(p);
+       free(path);
+       return NULL;
+}
+
+/* Parse <opt> argument from <*arvg> command line array of argument as
+ * an integer positive value into <*val> and update <*argv> and <*argc>.
+ * Display the halod program usage if failed and exit(1).
+ */
+static void hld_parse_long(int *val, char *opt, int *argc, char ***argv)
+{
+       char *endptr;
+
+       if (!*opt) {
+               ++*argv; --*argc;
+               if (*argc <= 0 || ***argv == '-')
+                       hld_usage(progname, *argc);
+
+               opt = **argv;
+       }
+
+       *val = strtol(opt, &endptr, 0);
+       if (endptr == opt || *val < 0)
+               hld_usage(progname, *argc);
+}
+
+/* Inverse the order of the allocated URL configs */
+static inline void hld_url_cfgs_inv(void)
+{
+       struct hld_url_cfg *urls = NULL, *url = hld_url_cfgs, *next_url;
+
+       /* inverse the URLs order */
+       while (url) {
+               struct hld_path *paths = NULL, *path = url->paths, *next_path;
+
+               /* inverse the paths order */
+               while (path) {
+                       next_path = path->next;
+                       path->next = paths;
+                       paths = path;
+                       path = next_path;
+               }
+               url->paths = url->cur_path = paths;
+
+               next_url = url->next;
+               url->next = urls;
+               urls = url;
+               url = next_url;
+       }
+
+       hld_url_cfgs = urls;
+}
+
+
+void haproxy_init_args(int argc, char **argv)
+{
+       int err = 1, dump = 0;
+       struct hbuf buf = HBUF_NULL;  // cfgfile
+       struct hbuf gbuf = HBUF_NULL; // "global" section
+       struct hbuf tbuf = HBUF_NULL; // "traces" section
+       struct hbuf dbuf = HBUF_NULL; // "default" section
+
+       if (argc <= 1)
+               hld_usage(progname, argc);
+
+       if (hbuf_alloc(&gbuf) == NULL) {
+               ha_alert("failed to allocate a buffer.\n");
+               goto leave;
+       }
+
+       /* use 3MB of local cache per thread mainly for QUIC.
+        * Also trust the server certificates
+        */
+       hbuf_appendf(&gbuf, "global\n");
+       hbuf_appendf(&gbuf, "\ttune.memory.hot-size 3145728\n");
+       hbuf_appendf(&gbuf, "\tssl-server-verify none\n");
+
+       if (hbuf_alloc(&buf) == NULL) {
+               ha_alert("failed to allocate a buffer\n");
+               exit(1);
+       }
+
+       fileless_mode = 1;
+       no_listener_mode = 1;
+       /* skip program name and start */
+       argc--; argv++;
+
+       while (argc > 0) {
+               if (**argv == '-') {
+                       char *opt = *argv + 1;
+
+                       if (*opt == '-') {
+                               /* long option */
+                               opt++;
+                               if (strcmp(opt, "defaults") == 0) {
+                                       argv++; argc--;
+                                       if (argc <= 0 || **argv == '-')
+                                               hld_usage(progname, argc);
+
+                                       if (hbuf_is_null(&dbuf)) {
+                                               if (hbuf_alloc(&dbuf) == NULL) {
+                                                       ha_alert("failed to allocate a buffer.\n");
+                                                       goto leave;
+                                               }
+
+                                               hbuf_appendf(&dbuf, "defaults\n");
+                                       }
+
+                                       hbuf_str_append(&dbuf, *argv);
+                               }
+                               else if (strcmp(opt, "global") == 0) {
+                                       argv++; argc--;
+                                       if (argc <= 0 || **argv == '-')
+                                               hld_usage(progname, argc);
+
+                                       hbuf_str_append(&gbuf, *argv);
+                               }
+                               else if (strcmp(opt, "server") == 0) {
+                                       argv++, argc--;
+                                       if ((argc <= 0 || **argv == '-'))
+                                               hld_usage(progname, argc);
+
+                                       opt = *argv;
+                                       free(srv_opts);
+                                       srv_opts = strdup(opt);
+                               }
+                               else if (strcmp(opt, "show-status-codes") == 0) {
+                                       arg_hscd = 1;
+                               }
+                               else if (strcmp(opt, "tls-ciphers") == 0) {
+                                       argv++, argc--;
+                                       if ((argc <= 0 || **argv == '-'))
+                                               hld_usage(progname, argc);
+                                       opt = *argv;
+                                       free(tls_ciphers);
+                                       tls_ciphers = strdup(opt);
+                               }
+                               else if (strcmp(opt, "tls-ciphersuites") == 0) {
+                                       argv++, argc--;
+                                       if ((argc <= 0 || **argv == '-'))
+                                               hld_usage(progname, argc);
+                                       opt = *argv;
+                                       free(tls_ciphersuites);
+                                       tls_ciphersuites = strdup(opt);
+                               }
+                               else if (strcmp(opt, "tls-curves") == 0) {
+                                       argv++, argc--;
+                                       if ((argc <= 0 || **argv == '-'))
+                                               hld_usage(progname, argc);
+                                       opt = *argv;
+                                       free(tls_curves);
+                                       tls_curves = strdup(opt);
+                               }
+                               else if (strcmp(opt, "traces") == 0) {
+                                       hld_debug = 1;
+                               }
+                               else
+                                       hld_usage(progname, argc);
+                       }
+                       else if (strcmp(opt, "0") == 0 ||
+                                strcmp(opt, "h0") == 0) {
+                               alpn = "hq-interop";
+                               h2c = 0;
+                       }
+                       else if (strcmp(opt, "1") == 0 ||
+                                strcmp(opt, "h1") == 0) {
+                               alpn = "http/1.1";
+                               h2c = 0;
+                       }
+                       else if (strcmp(opt, "2") == 0 ||
+                                strcmp(opt, "h2") == 0) {
+                               alpn = "h2";
+                               h2c = 0;
+                       }
+                       else if (strcmp(opt, "2c") == 0 ||
+                                strcmp(opt, "h2c") == 0) {
+                               alpn = NULL;
+                               h2c = 1;
+                       }
+                       else if (strcmp(opt, "3") == 0 ||
+                                strcmp(opt, "h3") == 0) {
+#if defined(USE_QUIC)
+                               alpn = "h3";
+                               h2c = 0;
+#else
+                               ha_warning("QUIC support not compiled in. Rebuild with USE_QUIC=1.\n");
+                               goto leave;
+#endif
+                       }
+                       else if (*opt == 'd') {
+                               opt++;
+                               hld_parse_long(&arg_dura, opt, &argc, &argv);
+                       }
+                       else if (*opt == 'e') {
+                               /* empty option */
+                               if (*(opt + 1))
+                                       hld_usage(progname, argc);
+
+                               arg_serr = 1;
+                       }
+                       else if (*opt == 'l') {
+                               arg_long++;
+                               while (*++opt && *opt == 'l')
+                                       arg_long++;
+                       }
+                       else if (*opt == 'm') {
+                               opt++;
+                               hld_parse_long(&arg_mreqs, opt, &argc, &argv);
+                       }
+                       else if (*opt == 'n') {
+                               opt++;
+                               hld_parse_long(&arg_reqs, opt, &argc, &argv);
+                       }
+                       else if (*opt == 'r') {
+                               opt++;
+                               hld_parse_long(&arg_rcon, opt, &argc, &argv);
+                       }
+                       else if (*opt == 's') {
+                               opt++;
+                               hld_parse_long(&arg_slow, opt, &argc, &argv);
+                               arg_slow *= 1000;
+                       }
+                       else if (*opt == 't') {
+                               opt++;
+                               hld_parse_long(&arg_thrd, opt, &argc, &argv);
+                       }
+                       else if (*opt == 'u') {
+                               opt++;
+                               hld_parse_long(&arg_usr, opt, &argc, &argv);
+                       }
+                       else if (*opt == 'w') {
+                               opt++;
+                               hld_parse_long(&arg_wait, opt, &argc, &argv);
+                       }
+                       else if (*opt == 'A') {
+                               /* empty option */
+                               if (*(opt + 1))
+                                       hld_usage(progname, argc);
+
+                               arg_accu = 1;
+                       }
+                       else if (*opt == 'C') {
+                               /* empty option */
+                               if (*(opt + 1))
+                                       hld_usage(progname, argc);
+
+                               dump = 1;
+                       }
+                       else if (*opt == 'F') {
+                               /* empty option */
+                               if (*(opt + 1))
+                                       hld_usage(progname, argc);
+
+                               arg_fast = 1;
+                       }
+                       else if (*opt == 'H') {
+                               char *hdr_str;
+                               struct hld_hdr *hdr;
+
+                               opt++;
+                               if (!*opt) {
+                                       argv++; argc--;
+                                       if ((argc <= 0 || **argv == '-'))
+                                               hld_usage(progname, argc);
+
+                                       opt = *argv;
+                               }
+
+                               hdr_str = opt;
+                               hdr = hld_parse_hdr(hdr_str);
+                               if (!hdr) {
+                                       ha_alert("could not allocate a header\n");
+                                       goto leave;
+                               }
+
+                               LIST_APPEND(&hld_hdrs, &hdr->list);
+                       }
+                       else if (*opt == 'I') {
+                               /* empty option */
+                               if (*(opt + 1))
+                                       hld_usage(progname, argc);
+
+                               arg_head = 1;
+                       }
+                       else if (*opt == 'v') {
+                               /* empty option */
+                               if (*(opt + 1))
+                                       hld_usage(progname, argc);
+
+                               printf("haload version " HAPROXY_VERSION " released " HAPROXY_DATE "\n");
+                               exit(0);
+                       }
+                       else
+                               hld_usage(progname, argc);
+               }
+               else {
+                       struct hld_url_cfg *url;
+
+                       url = hld_alloc_url(*argv);
+                       if (!url) {
+                               ha_alert("could not parse a new URL\n");
+                               goto leave;
+                       }
+
+
+               }
+
+               argv++; argc--;
+       }
+
+       if (arg_rcon > 0 && arg_rcon < arg_mreqs) {
+               ha_warning("number of maximum concurrent streams is greater that number of requests by connection\n");
+               ha_warning("set both these parameters values to %d (number of requests by connection)\n", arg_rcon);
+               arg_mreqs = arg_rcon;
+       }
+
+       if (!hld_url_cfgs) {
+               ha_alert("no URL provided\n");
+               goto leave;
+       }
+
+       /* "global" section */
+       hbuf_appendf(&buf, "%.*s", (int)gbuf.data, gbuf.area);
+       if (arg_thrd != -1)
+               hbuf_appendf(&buf, "\tnbthread %d\n", arg_thrd);
+       if (arg_mreqs)
+               hbuf_appendf(&buf,
+                            "\ttune.h2.be.max-concurrent-streams %d\n", arg_mreqs);
+#ifdef USE_QUIC
+       if (arg_mreqs)
+               hbuf_appendf(&buf,
+                            "\ttune.quic.be.stream.max-concurrent %d\n", arg_mreqs);
+#endif
+       /* "traces" section */
+       if (hld_debug) {
+               hbuf_appendf(&buf, "%s", hld_cfg_traces_str);
+               if (!hbuf_is_null(&tbuf))
+                       hbuf_appendf(&buf, "%.*s\n", (int)tbuf.data, tbuf.area);
+       }
+       /* "default section */
+       if (!hbuf_is_null(&dbuf))
+               hbuf_appendf(&buf, "%.*s\n", (int)dbuf.data, dbuf.area);
+
+       fileless_cfg.filename = strdup("haterm cfgfile");
+       fileless_cfg.content = strdup(buf.area);
+       if (!fileless_cfg.filename || !fileless_cfg.content) {
+               ha_alert("cfgfile strdup() failed.\n");
+               goto leave;
+       }
+
+       fileless_cfg.size = buf.data;
+
+       /* Config dump */
+       if (dump) {
+               fprintf(stdout, "%.*s", (int)fileless_cfg.size, fileless_cfg.content);
+               exit(0);
+       }
+
+       /* Inverse the URLs and their paths */
+       hld_url_cfgs_inv();
+       err = 0;
+leave:
+       free_hbuf(&dbuf);
+       free_hbuf(&gbuf);
+       free_hbuf(&buf);
+       if (err)
+               exit(1);
+}
+
+/* Dummy argv copier function */
+char **copy_argv(int argc, char **argv)
+{
+       char **ret = calloc(1, sizeof(*ret));
+
+       if (ret)
+               *ret = strdup("");
+
+       return ret;
+}
+
+static int hld_pre_check(void)
+{
+       char *errmsg = NULL;
+
+       if (!setup_new_proxy(&hld_proxy, "<HALOAD-BE>",
+                            PR_CAP_FE | PR_CAP_BE | PR_CAP_INT, &errmsg)) {
+               ha_alert("could not setup internal proxy: %s\n", errmsg);
+               ha_free(&errmsg);
+               return ERR_FATAL;
+       }
+
+       hld_proxy.mode = PR_MODE_HTTP;
+       if (arg_fast)
+               hld_proxy.options2 = PR_O2_SMARTCON;
+       hld_proxy.next = proxies_list;
+    proxies_list = &hld_proxy;
+
+    hld_proxy.timeout.server = 60000;
+    hld_proxy.timeout.connect = 60000;
+
+       return ERR_NONE;
+}
+REGISTER_PRE_CHECK(hld_pre_check);
+
+static int hld_deinit(void)
+{
+       ha_free(&old_argv[0]);
+       ha_free(&old_argv);
+       return 1;
+}
+REGISTER_POST_DEINIT(hld_deinit);
index 8cc052d99193716edce2c80892d96e269561aeb6..6e30b38514f465fa6755f33481f9fb7556be3d9d 100644 (file)
 DECLARE_TYPED_POOL(pool_head_connstream, "stconn", struct stconn);
 DECLARE_TYPED_POOL(pool_head_sedesc, "sedesc", struct sedesc);
 
 DECLARE_TYPED_POOL(pool_head_connstream, "stconn", struct stconn);
 DECLARE_TYPED_POOL(pool_head_sedesc, "sedesc", struct sedesc);
 
+/* Only used by haload */
+extern __attribute__((weak))
+struct task *hld_strm_task(struct task *t, void *context, unsigned int state)
+{
+       return NULL;
+}
+
 static int sc_conn_recv(struct stconn *sc);
 static int sc_conn_send(struct stconn *sc);
 
 static int sc_conn_recv(struct stconn *sc);
 static int sc_conn_send(struct stconn *sc);
 
@@ -301,6 +308,17 @@ int sc_attach_mux(struct stconn *sc, void *sd, void *ctx)
                        sc->wait_event.events = 0;
                }
        }
                        sc->wait_event.events = 0;
                }
        }
+       else if (sc_hldstream(sc)) {
+               if (!sc->wait_event.tasklet) {
+                       sc->wait_event.tasklet = tasklet_new();
+                       if (!sc->wait_event.tasklet)
+                               return -1;
+                       sc->wait_event.tasklet->process = hld_strm_task;
+                       sc->wait_event.tasklet->expire = TICK_ETERNITY;
+                       sc->wait_event.tasklet->context = __sc_hldstream(sc);;
+                       sc->wait_event.events = 0;
+               }
+       }
 
        sedesc->se = sd;
        sedesc->conn = ctx;
 
        sedesc->se = sd;
        sedesc->conn = ctx;