Improving the ACLK performance - initial changes (#8399)
Add an inspection point for VerneMQ in the local dev env. Remove the bottleneck in sending websocket messages, at the expense of increased CPU-load. Fixed the message encoding. Added support for stress testing - it is still enabled in the main loop so will fire stress-testing payloads when the ACLK is established. Next patch will integrate the socket polling properly to reduce the CPU overhead and remove the stress testing payloads.
Andrew Moss committed
Mar 14, 2020 at 07:35 UTC
87a0559ec3f885ff64764ab1d48bb1cb52ce0ebe
9 files changed
+192
-31
.codacy.yml
+1
@@ -13,3 +13,4 @@ exclude_paths:
13
- web/gui/src/**
14
- web/gui/main.js
15
- tests/**
16
+ - aclk/tests/**
aclk/aclk_lws_wss_client.c
+72
-10
@@ -10,14 +10,57 @@ struct aclk_lws_wss_perconnect_data {
10
int todo;
11
};
12
13
-struct lws_wss_packet_buffer {
14
- unsigned char *data;
15
- size_t data_size;
16
- struct lws_wss_packet_buffer *next;
17
-};
18
-
13
static struct aclk_lws_wss_engine_instance *engine_instance = NULL;
14
15
+void lws_wss_check_queues(size_t *write_len, size_t *write_len_bytes, size_t *read_len)
16
+{
17
+ if (write_len != NULL && write_len_bytes != NULL)
18
+ {
19
+ *write_len = 0;
20
+ *write_len_bytes = 0;
21
+ if (engine_instance != NULL)
22
+ {
23
+ aclk_lws_mutex_lock(&engine_instance->write_buf_mutex);
24
+
25
+ struct lws_wss_packet_buffer *write_b;
26
+ size_t w,wb;
27
+ for(w=0, wb=0, write_b = engine_instance->write_buffer_head; write_b != NULL; write_b = write_b->next)
28
+ {
29
+ w++;
30
+ wb += write_b->data_size;
31
+ }
32
+ *write_len = w;
33
+ *write_len_bytes = wb;
34
+ aclk_lws_mutex_unlock(&engine_instance->write_buf_mutex);
35
+ }
36
+ }
37
+ else if (write_len != NULL)
38
+ {
39
+ *write_len = 0;
40
+ if (engine_instance != NULL)
41
+ {
42
+ aclk_lws_mutex_lock(&engine_instance->write_buf_mutex);
43
+
44
+ struct lws_wss_packet_buffer *write_b;
45
+ size_t w;
46
+ for(w=0, write_b = engine_instance->write_buffer_head; write_b != NULL; write_b = write_b->next)
47
+ w++;
48
+ *write_len = w;
49
+ aclk_lws_mutex_unlock(&engine_instance->write_buf_mutex);
50
+ }
51
+ }
52
+ if (read_len != NULL)
53
+ {
54
+ *read_len = 0;
55
+ if (engine_instance != NULL)
56
+ {
57
+ aclk_lws_mutex_lock(&engine_instance->read_buf_mutex);
58
+ *read_len = lws_ring_get_count_waiting_elements(engine_instance->read_ringbuffer, NULL);
59
+ aclk_lws_mutex_unlock(&engine_instance->read_buf_mutex);
60
+ }
61
+ }
62
+}
63
+
64
static inline struct lws_wss_packet_buffer *lws_wss_packet_buffer_new(void *data, size_t size)
65
{
66
struct lws_wss_packet_buffer *new = callocz(1, sizeof(struct lws_wss_packet_buffer));
@@ -25,6 +68,7 @@ static inline struct lws_wss_packet_buffer *lws_wss_packet_buffer_new(void *data
68
new->data = mallocz(LWS_PRE + size);
69
memcpy(new->data + LWS_PRE, data, size);
70
new->data_size = size;
71
+ new->written = 0;
72
}
73
return new;
74
}
@@ -355,7 +399,7 @@ static int aclk_lws_wss_callback(struct lws *wsi, enum lws_callback_reasons reas
399
{
400
UNUSED(user);
401
struct lws_wss_packet_buffer *data;
358
- int retval = 0;
402
+ int retval = 0, rc;
403
404
// Callback servicing is forced when we are closed from above.
405
if (engine_instance->upstream_reconnect_request) {
@@ -372,12 +416,24 @@ static int aclk_lws_wss_callback(struct lws *wsi, enum lws_callback_reasons reas
416
switch (reason) {
417
case LWS_CALLBACK_CLIENT_WRITEABLE:
418
aclk_lws_mutex_lock(&engine_instance->write_buf_mutex);
375
- data = lws_wss_packet_buffer_pop(&engine_instance->write_buffer_head);
419
+ data = engine_instance->write_buffer_head;
420
if (likely(data)) {
377
- lws_write(wsi, data->data + LWS_PRE, data->data_size, LWS_WRITE_BINARY);
378
- lws_wss_packet_buffer_free(data);
421
+ size_t bytes_left = data->data_size - data->written;
422
+ if ( bytes_left > 65536 )
423
+ bytes_left = 65536;
424
+ rc = lws_write(wsi, data->data + LWS_PRE + data->written, bytes_left, LWS_WRITE_BINARY);
425
+ error("lws_write(req=%u,written=%u) %zu of %zu",bytes_left, rc, data->written,data->data_size,rc);
426
+ data->written += bytes_left;
427
+ if (data->written == data->data_size)
428
+ {
429
+ lws_wss_packet_buffer_pop(&engine_instance->write_buffer_head);
430
+ lws_wss_packet_buffer_free(data);
431
+ }
432
if (engine_instance->write_buffer_head)
433
+ {
434
+ error("Req write");
435
lws_callback_on_writable(engine_instance->lws_wsi);
436
+ }
437
}
438
aclk_lws_mutex_unlock(&engine_instance->write_buf_mutex);
439
return retval;
@@ -487,7 +543,13 @@ abort:
543
void aclk_lws_wss_service_loop()
544
{
545
if (engine_instance)
546
+ {
547
+ if (engine_instance->lws_wsi) {
548
+ lws_cancel_service(engine_instance->lws_context);
549
+ lws_callback_on_writable(engine_instance->lws_wsi);
550
+ }
551
lws_service(engine_instance->lws_context, 0);
552
+ }
553
}
554
555
// in case the MQTT connection disconnect while lws transport is still operational
aclk/aclk_lws_wss_client.h
+6
-1
@@ -31,7 +31,11 @@ struct aclk_lws_wss_engine_callbacks {
31
void (*connection_closed)();
32
};
33
34
-struct lws_wss_packet_buffer;
34
+struct lws_wss_packet_buffer {
35
+ unsigned char *data;
36
+ size_t data_size, written;
37
+ struct lws_wss_packet_buffer *next;
38
+};
39
40
struct aclk_lws_wss_engine_instance {
41
//target host/port for connection
@@ -73,6 +77,7 @@ void aclk_lws_wss_mqtt_layer_disconect_notif();
77
void aclk_lws_connection_established();
78
void aclk_lws_connection_data_received();
79
void aclk_lws_connection_closed();
80
+void lws_wss_check_queues(size_t *write_len, size_t *write_len_bytes, size_t *read_len);
81
82
83
#endif
aclk/agent_cloud_link.c
+35
-17
@@ -1281,7 +1281,7 @@ void aclk_get_challenge(char *aclk_hostname, char *aclk_port)
1281
return;
1282
}
1283
if (challenge.result == NULL ) {
1284
- error("Could not retrieve challenge from auth response");
1284
+ error("Could not retrieve challenge from auth response: %s", payload);
1285
return;
1286
}
1287
@@ -1362,6 +1362,7 @@ static void aclk_try_to_connect(char *hostname, char *port, int port_num)
1362
*
1363
* @return It always returns NULL
1364
*/
1365
+void lws_wss_check_queues(size_t *write_len, size_t *write_len_bytes, size_t *read_len);
1366
void *aclk_main(void *ptr)
1367
{
1368
struct netdata_static_thread *query_thread;
@@ -1410,9 +1411,10 @@ void *aclk_main(void *ptr)
1411
1412
while (!netdata_exit) {
1413
static int first_init = 0;
1413
-
1414
- info("loop state first_init_%d connected=%d connecting=%d", first_init, aclk_connected, aclk_connecting);
1415
- sleep_usec(USEC_PER_MS * 500);
1414
+ size_t write_q, write_q_bytes, read_q;
1415
+ lws_wss_check_queues(&write_q, &write_q_bytes, &read_q);
1416
+ info("loop state first_init_%d connected=%d connecting=%d wq=%zu (%zu-bytes) rq=%zu",
1417
+ first_init, aclk_connected, aclk_connecting, write_q, read_q);
1418
if (unlikely(!aclk_connected)) {
1419
if (unlikely(!first_init)) {
1420
aclk_try_to_connect(aclk_hostname, aclk_port, port_num);
@@ -1439,7 +1441,10 @@ void *aclk_main(void *ptr)
1441
}
1442
1443
_link_event_loop();
1442
- sleep_usec(USEC_PER_MS * 100);
1444
+ //sleep_usec(USEC_PER_MS * 50);
1445
+ static int stress_counter = 0;
1446
+ if (stress_counter++ % 100 == 0 && write_q==0)
1447
+ aclk_send_stress_test(2000000);
1448
1449
// TODO: Move to on-connect
1450
if (unlikely(!aclk_subscribed)) {
@@ -1498,7 +1503,7 @@ int aclk_send_message(char *sub_topic, char *message, char *msg_id)
1503
}
1504
1505
ACLK_LOCK;
1501
- rc = _link_send_message(final_topic, message, &mid);
1506
+ rc = _link_send_message(final_topic, (unsigned char *)message, &mid);
1507
// TODO: link the msg_id with the mid so we can trace it
1508
ACLK_UNLOCK;
1509
@@ -1603,8 +1608,6 @@ inline void aclk_create_header(BUFFER *dest, char *type, char *msg_id)
1608
debug(D_ACLK, "Sending v%d msgid [%s] type [%s] time [%ld]", ACLK_VERSION, msg_id, type, time_created);
1609
}
1610
1606
-//#define EYE_FRIENDLY
1607
-
1611
/*
1612
* Take a buffer, encode it and rewrite it
1613
*
@@ -1612,10 +1615,6 @@ inline void aclk_create_header(BUFFER *dest, char *type, char *msg_id)
1615
1616
BUFFER *aclk_encode_response(BUFFER *contents)
1617
{
1615
-#ifdef EYE_FRIENDLY
1616
-
1617
- return contents;
1618
-#else
1618
char *tmp_buffer = mallocz(contents->len * 2);
1619
char *src, *dst;
1620
@@ -1652,7 +1651,6 @@ BUFFER *aclk_encode_response(BUFFER *contents)
1651
1652
freez(tmp_buffer);
1653
return contents;
1655
-#endif
1654
}
1655
1656
/*
@@ -1684,7 +1682,7 @@ void aclk_send_alarm_metadata()
1682
debug(D_ACLK, "Metadata %s with alarms_active has %zu bytes", msg_id, local_buffer->len);
1683
1684
buffer_sprintf(local_buffer, "\n}\n}");
1687
- aclk_send_message(ACLK_ALARMS_TOPIC, aclk_encode_response(local_buffer)->buffer, msg_id);
1685
+ aclk_send_message(ACLK_ALARMS_TOPIC, local_buffer->buffer, msg_id);
1686
debug(D_ACLK, "Metadata %s encoded has %zu bytes", msg_id, local_buffer->len);
1687
1688
freez(msg_id);
@@ -1711,7 +1709,7 @@ int aclk_send_info_metadata()
1709
buffer_sprintf(local_buffer, "\n}\n}");
1710
debug(D_ACLK, "Metadata %s with chart has %zu bytes", msg_id, local_buffer->len);
1711
1714
- aclk_send_message(ACLK_METADATA_TOPIC, aclk_encode_response(local_buffer)->buffer, msg_id);
1712
+ aclk_send_message(ACLK_METADATA_TOPIC, local_buffer->buffer, msg_id);
1713
debug(D_ACLK, "Metadata %s encoded has %zu bytes", msg_id, local_buffer->len);
1714
freez(msg_id);
1715
@@ -1719,10 +1717,30 @@ int aclk_send_info_metadata()
1717
return 0;
1718
}
1719
1720
+void aclk_send_stress_test(size_t size)
1721
+{
1722
+ char *buffer = mallocz(size);
1723
+ if (buffer != NULL)
1724
+ {
1725
+ for(size_t i=0; i<size; i++)
1726
+ buffer[i] = 'x';
1727
+ buffer[size-1] = 0;
1728
+ time_t time_created = now_realtime_sec();
1729
+ sprintf(buffer,"{\"type\":\"stress\", \"timestamp\":%ld,\"payload\":", time_created);
1730
+ buffer[strlen(buffer)] = '"';
1731
+ buffer[size-2] = '}';
1732
+ buffer[size-3] = '"';
1733
+ aclk_send_message(ACLK_METADATA_TOPIC, buffer, NULL);
1734
+ error("Sending stress of size %zu at time %ld", size, time_created);
1735
+ }
1736
+ free(buffer);
1737
+}
1738
+
1739
// Send info metadata message to the cloud if the link is established
1740
// or on request
1741
int aclk_send_metadata()
1742
{
1743
+
1744
aclk_send_info_metadata();
1745
aclk_send_alarm_metadata();
1746
@@ -1774,7 +1792,7 @@ int aclk_send_single_chart(char *hostname, char *chart)
1792
rrdset2json(st, local_buffer, NULL, NULL, 1);
1793
buffer_sprintf(local_buffer, "\t\n}");
1794
1777
- aclk_send_message(ACLK_CHART_TOPIC, aclk_encode_response(local_buffer)->buffer, msg_id);
1795
+ aclk_send_message(ACLK_CHART_TOPIC, local_buffer->buffer, msg_id);
1796
1797
freez(msg_id);
1798
buffer_free(local_buffer);
@@ -1833,7 +1851,7 @@ int aclk_update_alarm(RRDHOST *host, ALARM_ENTRY *ae)
1851
netdata_rwlock_unlock(&host->health_log.alarm_log_rwlock);
1852
1853
buffer_sprintf(local_buffer, "\n}");
1836
- aclk_queue_query(ACLK_ALARMS_TOPIC, NULL, msg_id, aclk_encode_response(local_buffer)->buffer, 0, 1, ACLK_CMD_ALARM);
1854
+ aclk_queue_query(ACLK_ALARMS_TOPIC, NULL, msg_id, local_buffer->buffer, 0, 1, ACLK_CMD_ALARM);
1855
1856
freez(msg_id);
1857
buffer_free(local_buffer);
aclk/mqtt.c
+2
-2
@@ -275,7 +275,7 @@ int _link_subscribe(char *topic, int qos)
275
*
276
*/
277
278
-int _link_send_message(char *topic, char *message, int *mid)
278
+int _link_send_message(char *topic, unsigned char *message, int *mid)
279
{
280
int rc;
281
@@ -285,7 +285,7 @@ int _link_send_message(char *topic, char *message, int *mid)
285
return rc;
286
287
int msg_len = strlen(message);
288
-
288
+ error("Sending MQTT len=%d starts %02x %02x %02x", msg_len, message[0], message[1], message[2]);
289
rc = mosquitto_publish(mosq, mid, topic, msg_len, message, ACLK_QOS, 0);
290
291
// TODO: Add better handling -- error will flood the logfile here
aclk/mqtt.h
+1
-1
@@ -14,7 +14,7 @@ int mqtt_attempt_connection(char *aclk_hostname, int aclk_port, char *username,
14
//int _link_lib_init();
15
int _mqtt_lib_init();
16
int _link_subscribe(char *topic, int qos);
17
-int _link_send_message(char *topic, char *message, int *mid);
17
+int _link_send_message(char *topic, unsigned char *message, int *mid);
18
const char *_link_strerror(int rc);
19
20
int aclk_handle_cloud_request(char *);
aclk/tests/launch-paho.sh
new
+4
@@ -0,0 +1,4 @@
1
+#!/usr/bin/env bash
2
+
3
+docker build -f paho.Dockerfile . --build-arg "HOST_HOSTNAME=$(ping -c1 "$(hostname).local" | head -n1 | grep -o '[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*')" -t paho-client
4
+docker run -it paho-client
aclk/tests/paho-inspection.py
new
+57
@@ -0,0 +1,57 @@
1
+import ssl
2
+import paho.mqtt.client as mqtt
3
+import json
4
+import time
5
+import sys
6
+
7
+def on_connect(mqttc, obj, flags, rc):
8
+ if rc==0:
9
+ print("Successful connection", flush=True)
10
+ else :
11
+ print(f"Connection error rc={rc}", flush=True)
12
+ mqttc.subscribe("/agent/#",0)
13
+
14
+def on_disconnect(mqttc, obj, flags, rc):
15
+ print("disconnected rc: "+str(rc), flush=True)
16
+
17
+def on_message(mqttc, obj, msg):
18
+ print(f"{msg.topic} {len(msg.payload)}-bytes qos={msg.qos}", flush=True)
19
+ try:
20
+ print(f"Trying decode of {msg.payload[:60]}",flush=True)
21
+ api_msg = json.loads(msg.payload)
22
+ except Exception as e:
23
+ print(e,flush=True)
24
+ return
25
+ ts = api_msg["timestamp"]
26
+ mtype = api_msg["type"]
27
+ print(f"Message {mtype} time={ts} size {len(api_msg)}", flush=True)
28
+ now = time.time()
29
+ print(f"Current {now} -> Delay {now-ts}", flush=True)
30
+
31
+def on_publish(mqttc, obj, mid):
32
+ print("mid: "+str(mid), flush=True)
33
+
34
+def on_subscribe(mqttc, obj, mid, granted_qos):
35
+ print("Subscribed: "+str(mid)+" "+str(granted_qos), flush=True)
36
+
37
+def on_log(mqttc, obj, level, string):
38
+ print(string)
39
+
40
+print(f"Starting paho-inspection on {sys.argv[1]}", flush=True)
41
+mqttc = mqtt.Client(transport='websockets',client_id="paho")
42
+#mqttc.tls_set(certfile="server.crt", keyfile="server.key", cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLS, ciphers=None)
43
+#mqttc.tls_set(ca_certs="server.crt", cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLS, ciphers=None)
44
+mqttc.tls_set(cert_reqs=ssl.CERT_NONE, tls_version=ssl.PROTOCOL_TLS, ciphers=None)
45
+mqttc.tls_insecure_set(True)
46
+mqttc.on_message = on_message
47
+mqttc.on_connect = on_connect
48
+mqttc.on_disconnect = on_disconnect
49
+mqttc.on_publish = on_publish
50
+mqttc.on_subscribe = on_subscribe
51
+mqttc.username_pw_set("paho","paho")
52
+mqttc.connect(sys.argv[1], 8443, 60)
53
+
54
+#mqttc.publish("/agent/mine","Test1")
55
+#mqttc.subscribe("$SYS/#", 0)
56
+print("Connected succesfully, monitoring /agent/#", flush=True)
57
+mqttc.loop_forever()
aclk/tests/paho.Dockerfile
new
+14
@@ -0,0 +1,14 @@
1
+FROM archlinux/base:latest
2
+
3
+RUN pacman -Syyu --noconfirm
4
+RUN pacman --noconfirm --needed -S python-pip
5
+
6
+RUN pip install paho-mqtt
7
+
8
+RUN mkdir -p /opt/paho
9
+COPY paho-inspection.py /opt/paho/
10
+
11
+WORKDIR /opt/paho
12
+ARG HOST_HOSTNAME
13
+RUN echo $HOST_HOSTNAME >host
14
+CMD ["/bin/bash", "-c", "/usr/sbin/python paho-inspection.py $(cat host)"]