From 45d7cb010e709d340da8a2a21e1efcea239a8499 Mon Sep 17 00:00:00 2001 From: erer1243 <1377477+erer1243@users.noreply.github.com> Date: Tue, 29 Oct 2024 20:23:24 -0400 Subject: [PATCH 1/6] Initial implementation of C api (#915) Implement a C interface to some of libswsscommon in support of sonic-dash-ha. Related: https://github.com/sonic-net/sonic-dash-ha/pull/6 https://github.com/sonic-net/sonic-swss-common/pull/921 Incoming follow up PR: https://github.com/erer1243/sonic-swss-common/pull/1 --------- Co-authored-by: erer1243 --- common/Makefile.am | 11 +- common/binaryserializer.h | 26 +- common/c-api/consumerstatetable.cpp | 34 +++ common/c-api/consumerstatetable.h | 28 +++ common/c-api/dbconnector.cpp | 84 +++++++ common/c-api/dbconnector.h | 101 ++++++++ common/c-api/producerstatetable.cpp | 53 ++++ common/c-api/producerstatetable.h | 44 ++++ common/c-api/subscriberstatetable.cpp | 52 ++++ common/c-api/subscriberstatetable.h | 43 ++++ common/c-api/util.cpp | 3 + common/c-api/util.h | 181 ++++++++++++++ common/c-api/zmqclient.cpp | 35 +++ common/c-api/zmqclient.h | 30 +++ common/c-api/zmqconsumerstatetable.cpp | 58 +++++ common/c-api/zmqconsumerstatetable.h | 48 ++++ common/c-api/zmqproducerstatetable.cpp | 29 +++ common/c-api/zmqproducerstatetable.h | 32 +++ common/c-api/zmqserver.cpp | 14 ++ common/c-api/zmqserver.h | 20 ++ common/dbconnector.cpp | 33 ++- common/dbconnector.h | 10 +- common/zmqserver.cpp | 5 +- debian/libswsscommon-dev.install | 1 + tests/Makefile.am | 1 + tests/c_api_ut.cpp | 325 +++++++++++++++++++++++++ tests/main.cpp | 4 + 27 files changed, 1282 insertions(+), 23 deletions(-) create mode 100644 common/c-api/consumerstatetable.cpp create mode 100644 common/c-api/consumerstatetable.h create mode 100644 common/c-api/dbconnector.cpp create mode 100644 common/c-api/dbconnector.h create mode 100644 common/c-api/producerstatetable.cpp create mode 100644 common/c-api/producerstatetable.h create mode 100644 common/c-api/subscriberstatetable.cpp create mode 100644 common/c-api/subscriberstatetable.h create mode 100644 common/c-api/util.cpp create mode 100644 common/c-api/util.h create mode 100644 common/c-api/zmqclient.cpp create mode 100644 common/c-api/zmqclient.h create mode 100644 common/c-api/zmqconsumerstatetable.cpp create mode 100644 common/c-api/zmqconsumerstatetable.h create mode 100644 common/c-api/zmqproducerstatetable.cpp create mode 100644 common/c-api/zmqproducerstatetable.h create mode 100644 common/c-api/zmqserver.cpp create mode 100644 common/c-api/zmqserver.h create mode 100644 tests/c_api_ut.cpp diff --git a/common/Makefile.am b/common/Makefile.am index 18cfd803..5d1de753 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -68,7 +68,16 @@ common_libswsscommon_la_SOURCES = \ common/zmqclient.cpp \ common/zmqserver.cpp \ common/asyncdbupdater.cpp \ - common/redis_table_waiter.cpp + common/redis_table_waiter.cpp \ + common/c-api/util.cpp \ + common/c-api/dbconnector.cpp \ + common/c-api/consumerstatetable.cpp \ + common/c-api/producerstatetable.cpp \ + common/c-api/subscriberstatetable.cpp \ + common/c-api/zmqclient.cpp \ + common/c-api/zmqserver.cpp \ + common/c-api/zmqconsumerstatetable.cpp \ + common/c-api/zmqproducerstatetable.cpp common_libswsscommon_la_CXXFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(LIBNL_CFLAGS) $(CODE_COVERAGE_CXXFLAGS) common_libswsscommon_la_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(LIBNL_CPPFLAGS) $(CODE_COVERAGE_CPPFLAGS) diff --git a/common/binaryserializer.h b/common/binaryserializer.h index 413ca501..6ae4dcd2 100644 --- a/common/binaryserializer.h +++ b/common/binaryserializer.h @@ -2,6 +2,8 @@ #define __BINARY_SERIALIZER__ #include "common/armhelper.h" +#include "common/rediscommand.h" +#include "common/table.h" #include @@ -11,6 +13,26 @@ namespace swss { class BinarySerializer { public: + static size_t serializedSize(const string &dbName, const string &tableName, + const vector &kcos) { + size_t n = 0; + n += dbName.size() + sizeof(size_t); + n += tableName.size() + sizeof(size_t); + + for (const KeyOpFieldsValuesTuple &kco : kcos) { + const vector &fvs = kfvFieldsValues(kco); + n += kfvKey(kco).size() + sizeof(size_t); + n += to_string(fvs.size()).size() + sizeof(size_t); + + for (const FieldValueTuple &fv : fvs) { + n += fvField(fv).size() + sizeof(size_t); + n += fvValue(fv).size() + sizeof(size_t); + } + } + + return n + sizeof(size_t); + } + static size_t serializeBuffer( const char* buffer, const size_t size, @@ -192,8 +214,8 @@ class BinarySerializer { { if ((size_t)(m_current_position - m_buffer + datalen + sizeof(size_t)) > m_buffer_size) { - SWSS_LOG_THROW("There are not enough buffer for binary serializer to serialize,\ - key count: %zu, data length %zu, buffer size: %zu", + SWSS_LOG_THROW("There are not enough buffer for binary serializer to serialize,\n" + " key count: %zu, data length %zu, buffer size: %zu", m_kvp_count, datalen, m_buffer_size); diff --git a/common/c-api/consumerstatetable.cpp b/common/c-api/consumerstatetable.cpp new file mode 100644 index 00000000..c01ed822 --- /dev/null +++ b/common/c-api/consumerstatetable.cpp @@ -0,0 +1,34 @@ +#include +#include +#include + +#include "../consumerstatetable.h" +#include "../dbconnector.h" +#include "../table.h" +#include "consumerstatetable.h" +#include "util.h" + +using namespace swss; +using namespace std; + +SWSSConsumerStateTable SWSSConsumerStateTable_new(SWSSDBConnector db, const char *tableName, + const int32_t *p_popBatchSize, + const int32_t *p_pri) { + int popBatchSize = p_popBatchSize ? numeric_cast(*p_popBatchSize) + : TableConsumable::DEFAULT_POP_BATCH_SIZE; + int pri = p_pri ? numeric_cast(*p_pri) : 0; + SWSSTry(return (SWSSConsumerStateTable) new ConsumerStateTable( + (DBConnector *)db, string(tableName), popBatchSize, pri)); +} + +void SWSSConsumerStateTable_free(SWSSConsumerStateTable tbl) { + SWSSTry(delete (ConsumerStateTable *)tbl); +} + +SWSSKeyOpFieldValuesArray SWSSConsumerStateTable_pops(SWSSConsumerStateTable tbl) { + SWSSTry({ + deque vkco; + ((ConsumerStateTable *)tbl)->pops(vkco); + return makeKeyOpFieldValuesArray(vkco); + }); +} diff --git a/common/c-api/consumerstatetable.h b/common/c-api/consumerstatetable.h new file mode 100644 index 00000000..bd2fdaaf --- /dev/null +++ b/common/c-api/consumerstatetable.h @@ -0,0 +1,28 @@ +#ifndef SWSS_COMMON_C_API_CONSUMERSTATETABLE_H +#define SWSS_COMMON_C_API_CONSUMERSTATETABLE_H + +#include "dbconnector.h" +#include "util.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef struct SWSSConsumerStateTableOpaque *SWSSConsumerStateTable; + +// Pass NULL for popBatchSize and/or pri to use the default values +SWSSConsumerStateTable SWSSConsumerStateTable_new(SWSSDBConnector db, const char *tableName, + const int32_t *popBatchSize, const int32_t *pri); + +void SWSSConsumerStateTable_free(SWSSConsumerStateTable tbl); + +// Result array and all of its members must be freed using free() +SWSSKeyOpFieldValuesArray SWSSConsumerStateTable_pops(SWSSConsumerStateTable tbl); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/common/c-api/dbconnector.cpp b/common/c-api/dbconnector.cpp new file mode 100644 index 00000000..bb32f42a --- /dev/null +++ b/common/c-api/dbconnector.cpp @@ -0,0 +1,84 @@ +#include +#include + +#include "../dbconnector.h" +#include "dbconnector.h" +#include "util.h" + +using namespace swss; +using namespace std; + +void SWSSSonicDBConfig_initialize(const char *path) { + SWSSTry(SonicDBConfig::initialize(path)); +} + +void SWSSSonicDBConfig_initializeGlobalConfig(const char *path) { + SWSSTry(SonicDBConfig::initializeGlobalConfig(path)); +} + +SWSSDBConnector SWSSDBConnector_new_tcp(int32_t dbId, const char *hostname, uint16_t port, + uint32_t timeout) { + SWSSTry(return (SWSSDBConnector) new DBConnector(dbId, string(hostname), port, timeout)); +} + +SWSSDBConnector SWSSDBConnector_new_unix(int32_t dbId, const char *sock_path, uint32_t timeout) { + SWSSTry(return (SWSSDBConnector) new DBConnector(dbId, string(sock_path), timeout)); +} + +SWSSDBConnector SWSSDBConnector_new_named(const char *dbName, uint32_t timeout_ms, uint8_t isTcpConn) { + SWSSTry(return (SWSSDBConnector) new DBConnector(string(dbName), timeout_ms, isTcpConn)); +} + +void SWSSDBConnector_free(SWSSDBConnector db) { + delete (DBConnector *)db; +} + +int8_t SWSSDBConnector_del(SWSSDBConnector db, const char *key) { + SWSSTry(return ((DBConnector *)db)->del(string(key)) ? 1 : 0); +} + +void SWSSDBConnector_set(SWSSDBConnector db, const char *key, const char *value) { + SWSSTry(((DBConnector *)db)->set(string(key), string(value))); +} + +char *SWSSDBConnector_get(SWSSDBConnector db, const char *key) { + SWSSTry({ + shared_ptr s = ((DBConnector *)db)->get(string(key)); + return s ? strdup(s->c_str()) : nullptr; + }); +} + +int8_t SWSSDBConnector_exists(SWSSDBConnector db, const char *key) { + SWSSTry(return ((DBConnector *)db)->exists(string(key)) ? 1 : 0); +} + +int8_t SWSSDBConnector_hdel(SWSSDBConnector db, const char *key, const char *field) { + SWSSTry(return ((DBConnector *)db)->hdel(string(key), string(field)) ? 1 : 0); +} + +void SWSSDBConnector_hset(SWSSDBConnector db, const char *key, const char *field, + const char *value) { + SWSSTry(((DBConnector *)db)->hset(string(key), string(field), string(value))); +} + +char *SWSSDBConnector_hget(SWSSDBConnector db, const char *key, const char *field) { + SWSSTry({ + shared_ptr s = ((DBConnector *)db)->hget(string(key), string(field)); + return s ? strdup(s->c_str()) : nullptr; + }); +} + +SWSSFieldValueArray SWSSDBConnector_hgetall(SWSSDBConnector db, const char *key) { + SWSSTry({ + auto map = ((DBConnector *)db)->hgetall(key); + return makeFieldValueArray(map); + }); +} + +int8_t SWSSDBConnector_hexists(SWSSDBConnector db, const char *key, const char *field) { + SWSSTry(return ((DBConnector *)db)->hexists(string(key), string(field)) ? 1 : 0); +} + +int8_t SWSSDBConnector_flushdb(SWSSDBConnector db) { + SWSSTry(return ((DBConnector *)db)->flushdb() ? 1 : 0); +} diff --git a/common/c-api/dbconnector.h b/common/c-api/dbconnector.h new file mode 100644 index 00000000..8e6c51e0 --- /dev/null +++ b/common/c-api/dbconnector.h @@ -0,0 +1,101 @@ +#ifndef SWSS_COMMON_C_API_DBCONNECTOR_H +#define SWSS_COMMON_C_API_DBCONNECTOR_H + +#include "util.h" +#ifdef __cplusplus +extern "C" { +#endif + +#include + +void SWSSSonicDBConfig_initialize(const char *path); + +void SWSSSonicDBConfig_initializeGlobalConfig(const char *path); + +typedef struct SWSSDBConnectorOpaque *SWSSDBConnector; + +// Pass 0 to timeout for infinity +SWSSDBConnector SWSSDBConnector_new_tcp(int32_t dbId, const char *hostname, uint16_t port, + uint32_t timeout_ms); + +// Pass 0 to timeout for infinity +SWSSDBConnector SWSSDBConnector_new_unix(int32_t dbId, const char *sock_path, uint32_t timeout_ms); + +// Pass 0 to timeout for infinity +SWSSDBConnector SWSSDBConnector_new_named(const char *dbName, uint32_t timeout_ms, uint8_t isTcpConn); + +void SWSSDBConnector_free(SWSSDBConnector db); + +// Returns 0 when key doesn't exist, 1 when key was deleted +int8_t SWSSDBConnector_del(SWSSDBConnector db, const char *key); + +void SWSSDBConnector_set(SWSSDBConnector db, const char *key, const char *value); + +// Returns NULL if key doesn't exist. +// Result must be freed using free() +char *SWSSDBConnector_get(SWSSDBConnector db, const char *key); + +// Returns 0 for false, 1 for true +int8_t SWSSDBConnector_exists(SWSSDBConnector db, const char *key); + +// Returns 0 when key or field doesn't exist, 1 when field was deleted +int8_t SWSSDBConnector_hdel(SWSSDBConnector db, const char *key, const char *field); + +void SWSSDBConnector_hset(SWSSDBConnector db, const char *key, const char *field, + const char *value); + +// Returns NULL if key or field doesn't exist. +// Result must be freed using free() +char *SWSSDBConnector_hget(SWSSDBConnector db, const char *key, const char *field); + +// Returns an empty map when the key doesn't exist. +// Result array and all of its elements must be freed using free() +SWSSFieldValueArray SWSSDBConnector_hgetall(SWSSDBConnector db, const char *key); + +// Returns 0 when key or field doesn't exist, 1 when field exists +int8_t SWSSDBConnector_hexists(SWSSDBConnector db, const char *key, const char *field); + +// std::vector keys(const std::string &key); + +// std::pair> scan(int cursor = 0, const char +// *match = "", uint32_t count = 10); + +// template +// void hmset(const std::string &key, InputIterator start, InputIterator stop); + +// void hmset(const std::unordered_map>>& multiHash); + +// std::shared_ptr get(const std::string &key); + +// std::shared_ptr hget(const std::string &key, const std::string +// &field); + +// int64_t incr(const std::string &key); + +// int64_t decr(const std::string &key); + +// int64_t rpush(const std::string &list, const std::string &item); + +// std::shared_ptr blpop(const std::string &list, int timeout); + +// void subscribe(const std::string &pattern); + +// void psubscribe(const std::string &pattern); + +// void punsubscribe(const std::string &pattern); + +// int64_t publish(const std::string &channel, const std::string &message); + +// void config_set(const std::string &key, const std::string &value); + +// Returns 1 on success, 0 on failure +int8_t SWSSDBConnector_flushdb(SWSSDBConnector db); + +// std::map>> getall(); +#ifdef __cplusplus +} +#endif + +#endif diff --git a/common/c-api/producerstatetable.cpp b/common/c-api/producerstatetable.cpp new file mode 100644 index 00000000..083536d7 --- /dev/null +++ b/common/c-api/producerstatetable.cpp @@ -0,0 +1,53 @@ +#include +#include + +#include "../dbconnector.h" +#include "../producerstatetable.h" +#include "dbconnector.h" +#include "producerstatetable.h" +#include "util.h" + +using namespace swss; +using namespace std; + +SWSSProducerStateTable SWSSProducerStateTable_new(SWSSDBConnector db, const char *tableName) { + SWSSTry(return (SWSSProducerStateTable) new ProducerStateTable((DBConnector *)db, + string(tableName))); +} + +void SWSSProducerStateTable_free(SWSSProducerStateTable tbl) { + SWSSTry(delete ((ProducerStateTable *)tbl)); +} + +void SWSSProducerStateTable_setBuffered(SWSSProducerStateTable tbl, uint8_t buffered) { + SWSSTry(((ProducerStateTable *)tbl)->setBuffered((bool)buffered)) +} + +void SWSSProducerStateTable_set(SWSSProducerStateTable tbl, const char *key, + SWSSFieldValueArray values) { + SWSSTry(((ProducerStateTable *)tbl)->set(string(key), takeFieldValueArray(values))); +} + +void SWSSProducerStateTable_del(SWSSProducerStateTable tbl, const char *key) { + SWSSTry(((ProducerStateTable *)tbl)->del(string(key))); +} + +void SWSSProducerStateTable_flush(SWSSProducerStateTable tbl) { + SWSSTry(((ProducerStateTable *)tbl)->flush()); +} + +int64_t SWSSProducerStateTable_count(SWSSProducerStateTable tbl) { + SWSSTry(return ((ProducerStateTable *)tbl)->count()); +} + +void SWSSProducerStateTable_clear(SWSSProducerStateTable tbl) { + SWSSTry(((ProducerStateTable *)tbl)->clear()); +} + +void SWSSProducerStateTable_create_temp_view(SWSSProducerStateTable tbl) { + SWSSTry(((ProducerStateTable *)tbl)->create_temp_view()); +} + +void SWSSProducerStateTable_apply_temp_view(SWSSProducerStateTable tbl) { + SWSSTry(((ProducerStateTable *)tbl)->apply_temp_view()); +} diff --git a/common/c-api/producerstatetable.h b/common/c-api/producerstatetable.h new file mode 100644 index 00000000..e8db2c65 --- /dev/null +++ b/common/c-api/producerstatetable.h @@ -0,0 +1,44 @@ +#ifndef SWSS_COMMON_C_API_PRODUCERSTATETABLE_H +#define SWSS_COMMON_C_API_PRODUCERSTATETABLE_H + +#include "dbconnector.h" +#include "util.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef struct SWSSProducerStateTableOpaque *SWSSProducerStateTable; + +SWSSProducerStateTable SWSSProducerStateTable_new(SWSSDBConnector db, const char *tableName); + +void SWSSProducerStateTable_free(SWSSProducerStateTable tbl); + +void SWSSProducerStateTable_setBuffered(SWSSProducerStateTable tbl, uint8_t buffered); + +void SWSSProducerStateTable_set(SWSSProducerStateTable tbl, const char *key, SWSSFieldValueArray values); + +void SWSSProducerStateTable_del(SWSSProducerStateTable tbl, const char *key); + +// Batched version of set() and del(). +// virtual void set(const std::vector& values); + +// virtual void del(const std::vector& keys); + +void SWSSProducerStateTable_flush(SWSSProducerStateTable tbl); + +int64_t SWSSProducerStateTable_count(SWSSProducerStateTable tbl); + +void SWSSProducerStateTable_clear(SWSSProducerStateTable tbl); + +void SWSSProducerStateTable_create_temp_view(SWSSProducerStateTable tbl); + +void SWSSProducerStateTable_apply_temp_view(SWSSProducerStateTable tbl); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/common/c-api/subscriberstatetable.cpp b/common/c-api/subscriberstatetable.cpp new file mode 100644 index 00000000..b6482911 --- /dev/null +++ b/common/c-api/subscriberstatetable.cpp @@ -0,0 +1,52 @@ +#include +#include +#include +#include + +#include "../dbconnector.h" +#include "../subscriberstatetable.h" +#include "../table.h" +#include "subscriberstatetable.h" +#include "util.h" + +using namespace swss; +using namespace std; + +SWSSSubscriberStateTable SWSSSubscriberStateTable_new(SWSSDBConnector db, const char *tableName, + const int32_t *p_popBatchSize, + const int32_t *p_pri) { + int popBatchSize = p_popBatchSize ? numeric_cast(*p_popBatchSize) + : TableConsumable::DEFAULT_POP_BATCH_SIZE; + int pri = p_pri ? numeric_cast(*p_pri) : 0; + SWSSTry(return (SWSSSubscriberStateTable) new SubscriberStateTable( + (DBConnector *)db, string(tableName), popBatchSize, pri)); +} + +void SWSSSubscriberStateTable_free(SWSSSubscriberStateTable tbl) { + delete (SubscriberStateTable *)tbl; +} + +SWSSKeyOpFieldValuesArray SWSSSubscriberStateTable_pops(SWSSSubscriberStateTable tbl) { + SWSSTry({ + deque vkco; + ((SubscriberStateTable *)tbl)->pops(vkco); + return makeKeyOpFieldValuesArray(vkco); + }); +} + +uint8_t SWSSSubscriberStateTable_hasData(SWSSSubscriberStateTable tbl) { + SWSSTry(return ((SubscriberStateTable *)tbl)->hasData() ? 1 : 0); +} + +uint8_t SWSSSubscriberStateTable_hasCachedData(SWSSSubscriberStateTable tbl) { + SWSSTry(return ((SubscriberStateTable *)tbl)->hasCachedData() ? 1 : 0); +} + +uint8_t SWSSSubscriberStateTable_initializedWithData(SWSSSubscriberStateTable tbl) { + SWSSTry(return ((SubscriberStateTable *)tbl)->initializedWithData() ? 1 : 0); +} + +SWSSSelectResult SWSSSubscriberStateTable_readData(SWSSSubscriberStateTable tbl, + uint32_t timeout_ms) { + SWSSTry(return selectOne((SubscriberStateTable *)tbl, timeout_ms)); +} diff --git a/common/c-api/subscriberstatetable.h b/common/c-api/subscriberstatetable.h new file mode 100644 index 00000000..4501a3af --- /dev/null +++ b/common/c-api/subscriberstatetable.h @@ -0,0 +1,43 @@ +#ifndef SWSS_COMMON_C_API_SUBSCRIBERSTATETABLE_H +#define SWSS_COMMON_C_API_SUBSCRIBERSTATETABLE_H + +#include "dbconnector.h" +#include "util.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef struct SWSSSubscriberStateTableOpaque *SWSSSubscriberStateTable; + +// Pass NULL for popBatchSize and/or pri to use the default values +SWSSSubscriberStateTable SWSSSubscriberStateTable_new(SWSSDBConnector db, const char *tableName, + const int32_t *popBatchSize, + const int32_t *pri); + +void SWSSSubscriberStateTable_free(SWSSSubscriberStateTable tbl); + +// Result array and all of its members must be freed using free() +SWSSKeyOpFieldValuesArray SWSSSubscriberStateTable_pops(SWSSSubscriberStateTable tbl); + +// Returns 0 for false, 1 for true +uint8_t SWSSSubscriberStateTable_hasData(SWSSSubscriberStateTable tbl); + +// Returns 0 for false, 1 for true +uint8_t SWSSSubscriberStateTable_hasCachedData(SWSSSubscriberStateTable tbl); + +// Returns 0 for false, 1 for true +uint8_t SWSSSubscriberStateTable_initializedWithData(SWSSSubscriberStateTable tbl); + +// Block until data is available to read or until a timeout elapses. +// A timeout of 0 means the call will return immediately. +SWSSSelectResult SWSSSubscriberStateTable_readData(SWSSSubscriberStateTable tbl, + uint32_t timeout_ms); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/common/c-api/util.cpp b/common/c-api/util.cpp new file mode 100644 index 00000000..fb983d5c --- /dev/null +++ b/common/c-api/util.cpp @@ -0,0 +1,3 @@ +#include "util.h" + +bool swss::cApiTestingDisableAbort = false; diff --git a/common/c-api/util.h b/common/c-api/util.h new file mode 100644 index 00000000..79eb93cf --- /dev/null +++ b/common/c-api/util.h @@ -0,0 +1,181 @@ +#ifndef SWSS_COMMON_C_API_UTIL_H +#define SWSS_COMMON_C_API_UTIL_H + +// External utilities (c-facing) +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef struct { + const char *field; + const char *value; +} SWSSFieldValuePair; + +typedef struct { + uint64_t len; + const SWSSFieldValuePair *data; +} SWSSFieldValueArray; + +typedef struct { + const char *key; + const char *operation; + SWSSFieldValueArray fieldValues; +} SWSSKeyOpFieldValues; + +typedef struct { + uint64_t len; + const SWSSKeyOpFieldValues *data; +} SWSSKeyOpFieldValuesArray; + +typedef enum { + SWSSSelectResult_DATA = 0, + SWSSSelectResult_TIMEOUT = 1, + SWSSSelectResult_SIGNAL = 2, +} SWSSSelectResult; + +#ifdef __cplusplus +} +#endif + +// Internal utilities (used to help define c-facing functions) +#ifdef __cplusplus +#include +#include +#include +#include +#include +#include + +#include "../logger.h" +#include "../rediscommand.h" +#include "../select.h" + +using boost::numeric_cast; + +namespace swss { + +extern bool cApiTestingDisableAbort; + +// In the catch block, we must abort because passing an exception across an ffi boundary is +// undefined behavior. It was also decided that no exceptions in swss-common are recoverable, so +// there is no reason to convert exceptions into a returnable type. +#define SWSSTry(...) \ + if (cApiTestingDisableAbort) { \ + { __VA_ARGS__; } \ + } else { \ + try { \ + { __VA_ARGS__; } \ + } catch (std::exception & e) { \ + std::cerr << "Aborting due to exception: " << e.what() << std::endl; \ + SWSS_LOG_ERROR("Aborting due to exception: %s", e.what()); \ + std::abort(); \ + } \ + } + +static inline SWSSSelectResult selectOne(swss::Selectable *s, uint32_t timeout_ms) { + Select select; + Selectable *sOut; + select.addSelectable(s); + int ret = select.select(&sOut, numeric_cast(timeout_ms)); + switch (ret) { + case Select::OBJECT: + return SWSSSelectResult_DATA; + case Select::ERROR: + throw std::system_error(errno, std::generic_category()); + case Select::TIMEOUT: + return SWSSSelectResult_TIMEOUT; + case Select::SIGNALINT: + return SWSSSelectResult_SIGNAL; + default: + SWSS_LOG_THROW("impossible: unhandled Select::select() return value: %d", ret); + } +} + +// malloc() with safe numeric casting of the size parameter +template static inline void *mallocN(N size) { + return malloc(numeric_cast(size)); +} + +// T is anything that has a .size() method and which can be iterated over for pair +// eg unordered_map or vector> +template static inline SWSSFieldValueArray makeFieldValueArray(const T &in) { + SWSSFieldValuePair *data = + (SWSSFieldValuePair *)mallocN(in.size() * sizeof(SWSSFieldValuePair)); + + size_t i = 0; + for (const auto &pair : in) { + SWSSFieldValuePair entry; + entry.field = strdup(pair.first.c_str()); + entry.value = strdup(pair.second.c_str()); + data[i++] = entry; + } + + SWSSFieldValueArray out; + out.len = (uint64_t)in.size(); + out.data = data; + return out; +} + +static inline std::vector +takeFieldValueArray(const SWSSFieldValueArray &in) { + std::vector out; + for (uint64_t i = 0; i < in.len; i++) { + auto field = std::string(in.data[i].field); + auto value = std::string(in.data[i].value); + out.push_back(std::make_pair(field, value)); + } + return out; +} + +static inline SWSSKeyOpFieldValues makeKeyOpFieldValues(const swss::KeyOpFieldsValuesTuple &in) { + SWSSKeyOpFieldValues out; + out.key = strdup(kfvKey(in).c_str()); + out.operation = strdup(kfvOp(in).c_str()); + out.fieldValues = makeFieldValueArray(kfvFieldsValues(in)); + return out; +} + +static inline swss::KeyOpFieldsValuesTuple takeKeyOpFieldValues(const SWSSKeyOpFieldValues &in) { + std::string key(in.key), op(in.operation); + auto fieldValues = takeFieldValueArray(in.fieldValues); + return std::make_tuple(key, op, fieldValues); +} + +template static inline const T &getReference(const T &t) { + return t; +} + +template static inline const T &getReference(const std::shared_ptr &t) { + return *t; +} + +// T is anything that has a .size() method and which can be iterated over for +// swss::KeyOpFieldValuesTuple +template static inline SWSSKeyOpFieldValuesArray makeKeyOpFieldValuesArray(const T &in) { + SWSSKeyOpFieldValues *data = + (SWSSKeyOpFieldValues *)mallocN(in.size() * sizeof(SWSSKeyOpFieldValues)); + + size_t i = 0; + for (const auto &kfv : in) + data[i++] = makeKeyOpFieldValues(getReference(kfv)); + + SWSSKeyOpFieldValuesArray out; + out.len = (uint64_t)in.size(); + out.data = data; + return out; +} + +static inline std::vector +takeKeyOpFieldValuesArray(const SWSSKeyOpFieldValuesArray &in) { + std::vector out; + for (uint64_t i = 0; i < in.len; i++) + out.push_back(takeKeyOpFieldValues(in.data[i])); + return out; +} + +} // namespace swss + +#endif +#endif diff --git a/common/c-api/zmqclient.cpp b/common/c-api/zmqclient.cpp new file mode 100644 index 00000000..7e4a58f8 --- /dev/null +++ b/common/c-api/zmqclient.cpp @@ -0,0 +1,35 @@ +#include "../zmqclient.h" +#include "../binaryserializer.h" +#include "util.h" +#include "zmqclient.h" + +using namespace swss; +using namespace std; + +SWSSZmqClient SWSSZmqClient_new(const char *endpoint) { + SWSSTry(return (SWSSZmqClient) new ZmqClient(endpoint)); +} + +void SWSSZmqClient_free(SWSSZmqClient zmqc) { + SWSSTry(delete (ZmqClient *)zmqc); +} + +// Returns 0 for false, 1 for true +int8_t SWSSZmqClient_isConnected(SWSSZmqClient zmqc) { + SWSSTry(return ((ZmqClient *)zmqc)->isConnected() ? 1 : 0); +} + +void SWSSZmqClient_connect(SWSSZmqClient zmqc) { + SWSSTry(((ZmqClient *)zmqc)->connect()); +} + +void SWSSZmqClient_sendMsg(SWSSZmqClient zmqc, const char *dbName, const char *tableName, + const SWSSKeyOpFieldValuesArray *arr) { + SWSSTry({ + vector kcos = takeKeyOpFieldValuesArray(*arr); + size_t bufSize = BinarySerializer::serializedSize(dbName, tableName, kcos); + vector v(bufSize); + ((ZmqClient *)zmqc) + ->sendMsg(string(dbName), string(tableName), kcos, v); + }); +} diff --git a/common/c-api/zmqclient.h b/common/c-api/zmqclient.h new file mode 100644 index 00000000..47cd1efb --- /dev/null +++ b/common/c-api/zmqclient.h @@ -0,0 +1,30 @@ +#ifndef SWSS_COMMON_C_API_ZMQCLIENT_H +#define SWSS_COMMON_C_API_ZMQCLIENT_H + +#include "util.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef struct SWSSZmqClientOpaque *SWSSZmqClient; + +SWSSZmqClient SWSSZmqClient_new(const char *endpoint); + +void SWSSZmqClient_free(SWSSZmqClient zmqc); + +// Returns 0 for false, 1 for true +int8_t SWSSZmqClient_isConnected(SWSSZmqClient zmqc); + +void SWSSZmqClient_connect(SWSSZmqClient zmqc); + +void SWSSZmqClient_sendMsg(SWSSZmqClient zmqc, const char *dbName, const char *tableName, + const SWSSKeyOpFieldValuesArray *kcos); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/common/c-api/zmqconsumerstatetable.cpp b/common/c-api/zmqconsumerstatetable.cpp new file mode 100644 index 00000000..38cd87f9 --- /dev/null +++ b/common/c-api/zmqconsumerstatetable.cpp @@ -0,0 +1,58 @@ +#include "../zmqconsumerstatetable.h" +#include "../table.h" +#include "util.h" +#include "zmqconsumerstatetable.h" +#include "zmqserver.h" + +using namespace swss; +using namespace std; + +// Pass NULL for popBatchSize and/or pri to use the default values +SWSSZmqConsumerStateTable SWSSZmqConsumerStateTable_new(SWSSDBConnector db, const char *tableName, + SWSSZmqServer zmqs, + const int32_t *p_popBatchSize, + const int32_t *p_pri) { + + int popBatchSize = p_popBatchSize ? numeric_cast(*p_popBatchSize) + : TableConsumable::DEFAULT_POP_BATCH_SIZE; + int pri = p_pri ? numeric_cast(*p_pri) : 0; + SWSSTry(return (SWSSZmqConsumerStateTable) new ZmqConsumerStateTable( + (DBConnector *)db, string(tableName), *(ZmqServer *)zmqs, popBatchSize, pri)); +} + +void SWSSZmqConsumerStateTable_free(SWSSZmqConsumerStateTable tbl) { + SWSSTry(delete (ZmqConsumerStateTable *)tbl); +} + +SWSSKeyOpFieldValuesArray SWSSZmqConsumerStateTable_pops(SWSSZmqConsumerStateTable tbl) { + SWSSTry({ + deque vkco; + ((ZmqConsumerStateTable *)tbl)->pops(vkco); + return makeKeyOpFieldValuesArray(vkco); + }); +} + +SWSSSelectResult SWSSZmqConsumerStateTable_readData(SWSSZmqConsumerStateTable tbl, + uint32_t timeout_ms) { + SWSSTry(return selectOne((ZmqConsumerStateTable *)tbl, timeout_ms)); +} + +// Returns 0 for false, 1 for true +uint8_t SWSSZmqConsumerStateTable_hasData(SWSSZmqConsumerStateTable tbl) { + SWSSTry(return ((ZmqConsumerStateTable *)tbl)->hasData() ? 1 : 0); +} + +// Returns 0 for false, 1 for true +uint8_t SWSSZmqConsumerStateTable_hasCachedData(SWSSZmqConsumerStateTable tbl) { + SWSSTry(return ((ZmqConsumerStateTable *)tbl)->hasCachedData() ? 1 : 0); +} + +// Returns 0 for false, 1 for true +uint8_t SWSSZmqConsumerStateTable_initializedWithData(SWSSZmqConsumerStateTable tbl) { + SWSSTry(return ((ZmqConsumerStateTable *)tbl)->initializedWithData() ? 1 : 0); +} + +const struct SWSSDBConnectorOpaque * +SWSSZmqConsumerStateTable_getDbConnector(SWSSZmqConsumerStateTable tbl) { + SWSSTry(return (const SWSSDBConnectorOpaque *)((ZmqConsumerStateTable *)tbl)->getDbConnector()); +} diff --git a/common/c-api/zmqconsumerstatetable.h b/common/c-api/zmqconsumerstatetable.h new file mode 100644 index 00000000..4810c3ef --- /dev/null +++ b/common/c-api/zmqconsumerstatetable.h @@ -0,0 +1,48 @@ +#ifndef SWSS_COMMON_C_API_ZMQCONSUMERSTATETABLE_H +#define SWSS_COMMON_C_API_ZMQCONSUMERSTATETABLE_H + +#include "dbconnector.h" +#include "util.h" +#include "zmqserver.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef struct SWSSZmqConsumerStateTableOpaque *SWSSZmqConsumerStateTable; + +// Pass NULL for popBatchSize and/or pri to use the default values +SWSSZmqConsumerStateTable SWSSZmqConsumerStateTable_new(SWSSDBConnector db, const char *tableName, + SWSSZmqServer zmqs, + const int32_t *popBatchSize, + const int32_t *pri); + +void SWSSZmqConsumerStateTable_free(SWSSZmqConsumerStateTable tbl); + +// Result array and all of its members must be freed using free() +SWSSKeyOpFieldValuesArray SWSSZmqConsumerStateTable_pops(SWSSZmqConsumerStateTable tbl); + +// Block until data is available to read or until a timeout elapses. +// A timeout of 0 means the call will return immediately. +SWSSSelectResult SWSSZmqConsumerStateTable_readData(SWSSZmqConsumerStateTable tbl, + uint32_t timeout_ms); + +// Returns 0 for false, 1 for true +uint8_t SWSSZmqConsumerStateTable_hasData(SWSSZmqConsumerStateTable tbl); + +// Returns 0 for false, 1 for true +uint8_t SWSSZmqConsumerStateTable_hasCachedData(SWSSZmqConsumerStateTable tbl); + +// Returns 0 for false, 1 for true +uint8_t SWSSZmqConsumerStateTable_initializedWithData(SWSSZmqConsumerStateTable tbl); + +const struct SWSSDBConnectorOpaque * +SWSSZmqConsumerStateTable_getDbConnector(SWSSZmqConsumerStateTable tbl); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/common/c-api/zmqproducerstatetable.cpp b/common/c-api/zmqproducerstatetable.cpp new file mode 100644 index 00000000..3e50916e --- /dev/null +++ b/common/c-api/zmqproducerstatetable.cpp @@ -0,0 +1,29 @@ +#include "zmqproducerstatetable.h" +#include "../zmqproducerstatetable.h" + +using namespace std; +using namespace swss; + +SWSSZmqProducerStateTable SWSSZmqProducerStateTable_new(SWSSDBConnector db, const char *tableName, + SWSSZmqClient zmqc, uint8_t dbPersistence) { + + SWSSTry(return (SWSSZmqProducerStateTable) new ZmqProducerStateTable( + (DBConnector *)db, string(tableName), *(ZmqClient *)zmqc, dbPersistence)); +} + +void SWSSZmqProducerStateTable_free(SWSSZmqProducerStateTable tbl) { + SWSSTry(delete (ZmqProducerStateTable *)tbl); +} + +void SWSSZmqProducerStateTable_set(SWSSZmqProducerStateTable tbl, const char *key, + SWSSFieldValueArray values) { + SWSSTry(((ZmqProducerStateTable *)tbl)->set(string(key), takeFieldValueArray(values))); +} + +void SWSSZmqProducerStateTable_del(SWSSZmqProducerStateTable tbl, const char *key) { + SWSSTry(((ZmqProducerStateTable *)tbl)->del(string(key))); +} + +uint64_t SWSSZmqProducerStateTable_dbUpdaterQueueSize(SWSSZmqProducerStateTable tbl) { + SWSSTry(return numeric_cast(((ZmqProducerStateTable *)tbl)->dbUpdaterQueueSize())); +} diff --git a/common/c-api/zmqproducerstatetable.h b/common/c-api/zmqproducerstatetable.h new file mode 100644 index 00000000..08d05918 --- /dev/null +++ b/common/c-api/zmqproducerstatetable.h @@ -0,0 +1,32 @@ +#ifndef SWSS_COMMON_C_API_ZMQPRODUCERSTATETABLE_H +#define SWSS_COMMON_C_API_ZMQPRODUCERSTATETABLE_H + +#include "dbconnector.h" +#include "util.h" +#include "zmqclient.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include "stdint.h" + +typedef struct SWSSZmqProducerStateTableOpaque *SWSSZmqProducerStateTable; + +SWSSZmqProducerStateTable SWSSZmqProducerStateTable_new(SWSSDBConnector db, const char *tableName, + SWSSZmqClient zmqc, uint8_t dbPersistence); + +void SWSSZmqProducerStateTable_free(SWSSZmqProducerStateTable tbl); + +void SWSSZmqProducerStateTable_set(SWSSZmqProducerStateTable tbl, const char *key, + SWSSFieldValueArray values); + +void SWSSZmqProducerStateTable_del(SWSSZmqProducerStateTable tbl, const char *key); + +uint64_t SWSSZmqProducerStateTable_dbUpdaterQueueSize(SWSSZmqProducerStateTable tbl); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/common/c-api/zmqserver.cpp b/common/c-api/zmqserver.cpp new file mode 100644 index 00000000..50452e22 --- /dev/null +++ b/common/c-api/zmqserver.cpp @@ -0,0 +1,14 @@ +#include "zmqserver.h" +#include "../zmqserver.h" +#include "util.h" + +using namespace swss; +using namespace std; + +SWSSZmqServer SWSSZmqServer_new(const char *endpoint) { + SWSSTry(return (SWSSZmqServer) new ZmqServer(string(endpoint))); +} + +void SWSSZmqServer_free(SWSSZmqServer zmqs) { + SWSSTry(delete (ZmqServer *)zmqs); +} diff --git a/common/c-api/zmqserver.h b/common/c-api/zmqserver.h new file mode 100644 index 00000000..decd0e0d --- /dev/null +++ b/common/c-api/zmqserver.h @@ -0,0 +1,20 @@ +#ifndef SWSS_COMMON_C_API_ZMQSERVER_H +#define SWSS_COMMON_C_API_ZMQSERVER_H + +#include "util.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct SWSSZmqServerOpaque *SWSSZmqServer; + +SWSSZmqServer SWSSZmqServer_new(const char *endpoint); + +void SWSSZmqServer_free(SWSSZmqServer zmqs); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/common/dbconnector.cpp b/common/dbconnector.cpp index 0e044f3e..96334780 100755 --- a/common/dbconnector.cpp +++ b/common/dbconnector.cpp @@ -645,39 +645,46 @@ DBConnector::DBConnector(int dbId, const RedisContext& ctx) select(this); } +static struct timeval ms_to_timeval(unsigned int ms) { + return { + .tv_sec = (time_t)ms / 1000, + .tv_usec = ((suseconds_t)ms % 1000) * 1000 + }; +} + DBConnector::DBConnector(int dbId, const string& hostname, int port, - unsigned int timeout) + unsigned int timeout_ms) : m_dbId(dbId) { - struct timeval tv = {0, (suseconds_t)timeout * 1000}; - struct timeval *ptv = timeout ? &tv : NULL; + struct timeval tv = ms_to_timeval(timeout_ms); + struct timeval *ptv = timeout_ms ? &tv : NULL; initContext(hostname.c_str(), port, ptv); select(this); } -DBConnector::DBConnector(int dbId, const string& unixPath, unsigned int timeout) +DBConnector::DBConnector(int dbId, const string& unixPath, unsigned int timeout_ms) : m_dbId(dbId) { - struct timeval tv = {0, (suseconds_t)timeout * 1000}; - struct timeval *ptv = timeout ? &tv : NULL; + struct timeval tv = ms_to_timeval(timeout_ms); + struct timeval *ptv = timeout_ms ? &tv : NULL; initContext(unixPath.c_str(), ptv); select(this); } -DBConnector::DBConnector(const string& dbName, unsigned int timeout, bool isTcpConn, const string& netns) - : DBConnector(dbName, timeout, isTcpConn, SonicDBKey(netns)) +DBConnector::DBConnector(const string& dbName, unsigned int timeout_ms, bool isTcpConn, const string& netns) + : DBConnector(dbName, timeout_ms, isTcpConn, SonicDBKey(netns)) { } -DBConnector::DBConnector(const string& dbName, unsigned int timeout, bool isTcpConn, const SonicDBKey &key) +DBConnector::DBConnector(const string& dbName, unsigned int timeout_ms, bool isTcpConn, const SonicDBKey &key) : m_dbId(SonicDBConfig::getDbId(dbName, key)) , m_dbName(dbName) , m_key(key) { - struct timeval tv = {0, (suseconds_t)timeout * 1000}; - struct timeval *ptv = timeout ? &tv : NULL; + struct timeval tv = ms_to_timeval(timeout_ms); + struct timeval *ptv = timeout_ms ? &tv : NULL; if (isTcpConn) { initContext(SonicDBConfig::getDbHostname(dbName, m_key).c_str(), SonicDBConfig::getDbPort(dbName, m_key), ptv); @@ -690,8 +697,8 @@ DBConnector::DBConnector(const string& dbName, unsigned int timeout, bool isTcpC select(this); } -DBConnector::DBConnector(const string& dbName, unsigned int timeout, bool isTcpConn) - : DBConnector(dbName, timeout, isTcpConn, SonicDBKey()) +DBConnector::DBConnector(const string& dbName, unsigned int timeout_ms, bool isTcpConn) + : DBConnector(dbName, timeout_ms, isTcpConn, SonicDBKey()) { // Empty constructor } diff --git a/common/dbconnector.h b/common/dbconnector.h index c5bd48ad..832983ed 100644 --- a/common/dbconnector.h +++ b/common/dbconnector.h @@ -213,11 +213,11 @@ class DBConnector : public RedisContext */ explicit DBConnector(const DBConnector &other); DBConnector(int dbId, const RedisContext &ctx); - DBConnector(int dbId, const std::string &hostname, int port, unsigned int timeout); - DBConnector(int dbId, const std::string &unixPath, unsigned int timeout); - DBConnector(const std::string &dbName, unsigned int timeout, bool isTcpConn = false); - DBConnector(const std::string &dbName, unsigned int timeout, bool isTcpConn, const std::string &netns); - DBConnector(const std::string &dbName, unsigned int timeout, bool isTcpConn, const SonicDBKey &key); + DBConnector(int dbId, const std::string &hostname, int port, unsigned int timeout_ms); + DBConnector(int dbId, const std::string &unixPath, unsigned int timeout_ms); + DBConnector(const std::string &dbName, unsigned int timeout_ms, bool isTcpConn = false); + DBConnector(const std::string &dbName, unsigned int timeout_ms, bool isTcpConn, const std::string &netns); + DBConnector(const std::string &dbName, unsigned int timeout_ms, bool isTcpConn, const SonicDBKey &key); DBConnector& operator=(const DBConnector&) = delete; int getDbId() const; diff --git a/common/zmqserver.cpp b/common/zmqserver.cpp index 4800b9ba..dca10740 100644 --- a/common/zmqserver.cpp +++ b/common/zmqserver.cpp @@ -106,9 +106,10 @@ void ZmqServer::mqPollThread() int rc = zmq_bind(socket, m_endpoint.c_str()); if (rc != 0) { - SWSS_LOG_THROW("zmq_bind failed on endpoint: %s, zmqerrno: %d", + SWSS_LOG_THROW("zmq_bind failed on endpoint: %s, zmqerrno: %d, message: %s", m_endpoint.c_str(), - zmq_errno()); + zmq_errno(), + strerror(zmq_errno())); } // zmq_poll will use less CPU diff --git a/debian/libswsscommon-dev.install b/debian/libswsscommon-dev.install index 1dd2670e..85e3c4bc 100644 --- a/debian/libswsscommon-dev.install +++ b/debian/libswsscommon-dev.install @@ -1,2 +1,3 @@ common/*.h usr/include/swss +common/c-api/*.h usr/include/swss/c-api pyext/swsscommon.i usr/share/swss diff --git a/tests/Makefile.am b/tests/Makefile.am index a07fadc2..39712b23 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -42,6 +42,7 @@ tests_tests_SOURCES = tests/redis_ut.cpp \ tests/binary_serializer_ut.cpp \ tests/zmq_state_ut.cpp \ tests/profileprovider_ut.cpp \ + tests/c_api_ut.cpp \ tests/main.cpp tests_tests_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(LIBNL_CFLAGS) diff --git a/tests/c_api_ut.cpp b/tests/c_api_ut.cpp new file mode 100644 index 00000000..d16dac7c --- /dev/null +++ b/tests/c_api_ut.cpp @@ -0,0 +1,325 @@ +#include +#include +#include + +#include "common/c-api/consumerstatetable.h" +#include "common/c-api/dbconnector.h" +#include "common/c-api/producerstatetable.h" +#include "common/c-api/subscriberstatetable.h" +#include "common/c-api/util.h" +#include "common/c-api/zmqclient.h" +#include "common/c-api/zmqconsumerstatetable.h" +#include "common/c-api/zmqproducerstatetable.h" +#include "common/c-api/zmqserver.h" +#include "common/select.h" +#include "common/subscriberstatetable.h" +#include "gtest/gtest.h" + +using namespace std; +using namespace swss; + +static void clearDB() { + DBConnector db("TEST_DB", 0, true); + RedisReply r(&db, "FLUSHALL", REDIS_REPLY_STATUS); + r.checkStatusOK(); +} + +static void sortKfvs(vector &kfvs) { + sort(kfvs.begin(), kfvs.end(), + [](const KeyOpFieldsValuesTuple &a, const KeyOpFieldsValuesTuple &b) { + return kfvKey(a) < kfvKey(b); + }); + + for (auto &kfv : kfvs) { + auto &fvs = kfvFieldsValues(kfv); + sort(fvs.begin(), fvs.end(), + [](const pair &a, const pair &b) { + return a.first < b.first; + }); + } +} + +template static void free(const T *ptr) { + std::free(const_cast(reinterpret_cast(ptr))); +} + +static void freeKeyOpFieldValuesArray(SWSSKeyOpFieldValuesArray arr) { + for (uint64_t i = 0; i < arr.len; i++) { + free(arr.data[i].key); + free(arr.data[i].operation); + for (uint64_t j = 0; j < arr.data[i].fieldValues.len; j++) { + free(arr.data[i].fieldValues.data[j].field); + free(arr.data[i].fieldValues.data[j].value); + } + free(arr.data[i].fieldValues.data); + } + free(arr.data); +} + +TEST(c_api, DBConnector) { + clearDB(); + + EXPECT_THROW(SWSSDBConnector_new_named("does not exist", 0, true), out_of_range); + SWSSDBConnector db = SWSSDBConnector_new_named("TEST_DB", 1000, true); + EXPECT_FALSE(SWSSDBConnector_get(db, "mykey")); + EXPECT_FALSE(SWSSDBConnector_exists(db, "mykey")); + SWSSDBConnector_set(db, "mykey", "myval"); + const char *val = SWSSDBConnector_get(db, "mykey"); + EXPECT_STREQ(val, "myval"); + free(val); + EXPECT_TRUE(SWSSDBConnector_exists(db, "mykey")); + EXPECT_TRUE(SWSSDBConnector_del(db, "mykey")); + EXPECT_FALSE(SWSSDBConnector_del(db, "mykey")); + + EXPECT_FALSE(SWSSDBConnector_hget(db, "mykey", "myfield")); + EXPECT_FALSE(SWSSDBConnector_hexists(db, "mykey", "myfield")); + SWSSDBConnector_hset(db, "mykey", "myfield", "myval"); + val = SWSSDBConnector_hget(db, "mykey", "myfield"); + EXPECT_STREQ(val, "myval"); + free(val); + EXPECT_TRUE(SWSSDBConnector_hexists(db, "mykey", "myfield")); + EXPECT_FALSE(SWSSDBConnector_hget(db, "mykey", "notmyfield")); + EXPECT_FALSE(SWSSDBConnector_hexists(db, "mykey", "notmyfield")); + EXPECT_TRUE(SWSSDBConnector_hdel(db, "mykey", "myfield")); + EXPECT_FALSE(SWSSDBConnector_hdel(db, "mykey", "myfield")); + + EXPECT_TRUE(SWSSDBConnector_flushdb(db)); + SWSSDBConnector_free(db); +} + +TEST(c_api, ConsumerProducerStateTables) { + clearDB(); + + SWSSDBConnector db = SWSSDBConnector_new_named("TEST_DB", 1000, true); + SWSSProducerStateTable pst = SWSSProducerStateTable_new(db, "mytable"); + SWSSConsumerStateTable cst = SWSSConsumerStateTable_new(db, "mytable", nullptr, nullptr); + + SWSSKeyOpFieldValuesArray arr = SWSSConsumerStateTable_pops(cst); + ASSERT_EQ(arr.len, 0); + freeKeyOpFieldValuesArray(arr); + + SWSSFieldValuePair data[2] = {{.field = "myfield1", .value = "myvalue1"}, + {.field = "myfield2", .value = "myvalue2"}}; + SWSSFieldValueArray values = { + .len = 2, + .data = data, + }; + SWSSProducerStateTable_set(pst, "mykey1", values); + + data[0] = {.field = "myfield3", .value = "myvalue3"}; + values.len = 1; + SWSSProducerStateTable_set(pst, "mykey2", values); + + arr = SWSSConsumerStateTable_pops(cst); + vector kfvs = takeKeyOpFieldValuesArray(arr); + sortKfvs(kfvs); + freeKeyOpFieldValuesArray(arr); + + ASSERT_EQ(kfvs.size(), 2); + EXPECT_EQ(kfvKey(kfvs[0]), "mykey1"); + EXPECT_EQ(kfvOp(kfvs[0]), "SET"); + vector> &fieldValues0 = kfvFieldsValues(kfvs[0]); + ASSERT_EQ(fieldValues0.size(), 2); + EXPECT_EQ(fieldValues0[0].first, "myfield1"); + EXPECT_EQ(fieldValues0[0].second, "myvalue1"); + EXPECT_EQ(fieldValues0[1].first, "myfield2"); + EXPECT_EQ(fieldValues0[1].second, "myvalue2"); + + EXPECT_EQ(kfvKey(kfvs[1]), "mykey2"); + EXPECT_EQ(kfvOp(kfvs[1]), "SET"); + vector> &fieldValues1 = kfvFieldsValues(kfvs[1]); + ASSERT_EQ(fieldValues1.size(), 1); + EXPECT_EQ(fieldValues1[0].first, "myfield3"); + EXPECT_EQ(fieldValues1[0].second, "myvalue3"); + + arr = SWSSConsumerStateTable_pops(cst); + EXPECT_EQ(arr.len, 0); + freeKeyOpFieldValuesArray(arr); + + SWSSProducerStateTable_del(pst, "mykey3"); + SWSSProducerStateTable_del(pst, "mykey4"); + SWSSProducerStateTable_del(pst, "mykey5"); + + arr = SWSSConsumerStateTable_pops(cst); + kfvs = takeKeyOpFieldValuesArray(arr); + sortKfvs(kfvs); + freeKeyOpFieldValuesArray(arr); + + ASSERT_EQ(kfvs.size(), 3); + EXPECT_EQ(kfvKey(kfvs[0]), "mykey3"); + EXPECT_EQ(kfvOp(kfvs[0]), "DEL"); + EXPECT_EQ(kfvFieldsValues(kfvs[0]).size(), 0); + EXPECT_EQ(kfvKey(kfvs[1]), "mykey4"); + EXPECT_EQ(kfvOp(kfvs[1]), "DEL"); + EXPECT_EQ(kfvFieldsValues(kfvs[1]).size(), 0); + EXPECT_EQ(kfvKey(kfvs[2]), "mykey5"); + EXPECT_EQ(kfvOp(kfvs[2]), "DEL"); + EXPECT_EQ(kfvFieldsValues(kfvs[2]).size(), 0); + + SWSSProducerStateTable_free(pst); + SWSSConsumerStateTable_free(cst); + SWSSDBConnector_flushdb(db); + SWSSDBConnector_free(db); +} + +TEST(c_api, SubscriberStateTable) { + clearDB(); + + SWSSDBConnector db = SWSSDBConnector_new_named("TEST_DB", 1000, true); + SWSSSubscriberStateTable sst = SWSSSubscriberStateTable_new(db, "mytable", nullptr, nullptr); + + EXPECT_EQ(SWSSSubscriberStateTable_readData(sst, 300), SWSSSelectResult_TIMEOUT); + EXPECT_FALSE(SWSSSubscriberStateTable_hasData(sst)); + SWSSKeyOpFieldValuesArray arr = SWSSSubscriberStateTable_pops(sst); + EXPECT_EQ(arr.len, 0); + freeKeyOpFieldValuesArray(arr); + + SWSSDBConnector_hset(db, "mytable:mykey", "myfield", "myvalue"); + EXPECT_EQ(SWSSSubscriberStateTable_readData(sst, 300), SWSSSelectResult_DATA); + EXPECT_EQ(SWSSSubscriberStateTable_readData(sst, 300), SWSSSelectResult_TIMEOUT); + EXPECT_TRUE(SWSSSubscriberStateTable_hasData(sst)); + + arr = SWSSSubscriberStateTable_pops(sst); + vector kfvs = takeKeyOpFieldValuesArray(arr); + sortKfvs(kfvs); + freeKeyOpFieldValuesArray(arr); + + EXPECT_FALSE(SWSSSubscriberStateTable_hasData(sst)); + ASSERT_EQ(kfvs.size(), 1); + EXPECT_EQ(kfvKey(kfvs[0]), "mykey"); + EXPECT_EQ(kfvOp(kfvs[0]), "SET"); + ASSERT_EQ(kfvFieldsValues(kfvs[0]).size(), 1); + EXPECT_EQ(kfvFieldsValues(kfvs[0])[0].first, "myfield"); + EXPECT_EQ(kfvFieldsValues(kfvs[0])[0].second, "myvalue"); + + SWSSSubscriberStateTable_free(sst); + SWSSDBConnector_flushdb(db); + SWSSDBConnector_free(db); +} + +TEST(c_api, ZmqConsumerProducerStateTable) { + clearDB(); + + SWSSDBConnector db = SWSSDBConnector_new_named("TEST_DB", 1000, true); + + SWSSZmqServer srv = SWSSZmqServer_new("tcp://127.0.0.1:42312"); + SWSSZmqClient cli = SWSSZmqClient_new("tcp://127.0.0.1:42312"); + EXPECT_TRUE(SWSSZmqClient_isConnected(cli)); + SWSSZmqClient_connect(cli); // This should be idempotent/not throw + + SWSSZmqProducerStateTable pst = SWSSZmqProducerStateTable_new(db, "mytable", cli, false); + SWSSZmqConsumerStateTable cst = + SWSSZmqConsumerStateTable_new(db, "mytable", srv, nullptr, nullptr); + + ASSERT_EQ(SWSSZmqConsumerStateTable_getDbConnector(cst), db); + + EXPECT_FALSE(SWSSZmqConsumerStateTable_hasData(cst)); + EXPECT_FALSE(SWSSZmqConsumerStateTable_hasCachedData(cst)); + EXPECT_FALSE(SWSSZmqConsumerStateTable_initializedWithData(cst)); + SWSSKeyOpFieldValuesArray arr = SWSSZmqConsumerStateTable_pops(cst); + ASSERT_EQ(arr.len, 0); + freeKeyOpFieldValuesArray(arr); + + // On flag = 0, we use the ZmqProducerStateTable + // On flag = 1, we use the ZmqClient directly + for (int flag = 0; flag < 2; flag++) { + SWSSFieldValuePair values_key1_data[2] = {{.field = "myfield1", .value = "myvalue1"}, + {.field = "myfield2", .value = "myvalue2"}}; + SWSSFieldValueArray values_key1 = { + .len = 2, + .data = values_key1_data, + }; + + SWSSFieldValuePair values_key2_data[1] = {{.field = "myfield3", .value = "myvalue3"}}; + SWSSFieldValueArray values_key2 = { + .len = 1, + .data = values_key2_data, + }; + + SWSSKeyOpFieldValues arr_data[2] = { + {.key = "mykey1", .operation = "SET", .fieldValues = values_key1}, + {.key = "mykey2", .operation = "SET", .fieldValues = values_key2}}; + arr = {.len = 2, .data = arr_data}; + + if (flag == 0) + for (uint64_t i = 0; i < arr.len; i++) + SWSSZmqProducerStateTable_set(pst, arr.data[i].key, arr.data[i].fieldValues); + else + SWSSZmqClient_sendMsg(cli, "TEST_DB", "mytable", &arr); + + ASSERT_EQ(SWSSZmqConsumerStateTable_readData(cst, 500), SWSSSelectResult_DATA); + EXPECT_TRUE(SWSSZmqConsumerStateTable_hasData(cst)); + EXPECT_TRUE(SWSSZmqConsumerStateTable_hasCachedData(cst)); + arr = SWSSZmqConsumerStateTable_pops(cst); + EXPECT_FALSE(SWSSZmqConsumerStateTable_hasData(cst)); + EXPECT_FALSE(SWSSZmqConsumerStateTable_hasCachedData(cst)); + EXPECT_EQ(SWSSZmqConsumerStateTable_readData(cst, 500), SWSSSelectResult_TIMEOUT); + + vector kfvs = takeKeyOpFieldValuesArray(arr); + sortKfvs(kfvs); + freeKeyOpFieldValuesArray(arr); + + ASSERT_EQ(kfvs.size(), 2); + EXPECT_EQ(kfvKey(kfvs[0]), "mykey1"); + EXPECT_EQ(kfvOp(kfvs[0]), "SET"); + vector> &fieldValues0 = kfvFieldsValues(kfvs[0]); + ASSERT_EQ(fieldValues0.size(), 2); + EXPECT_EQ(fieldValues0[0].first, "myfield1"); + EXPECT_EQ(fieldValues0[0].second, "myvalue1"); + EXPECT_EQ(fieldValues0[1].first, "myfield2"); + EXPECT_EQ(fieldValues0[1].second, "myvalue2"); + + EXPECT_EQ(kfvKey(kfvs[1]), "mykey2"); + EXPECT_EQ(kfvOp(kfvs[1]), "SET"); + vector> &fieldValues1 = kfvFieldsValues(kfvs[1]); + ASSERT_EQ(fieldValues1.size(), 1); + EXPECT_EQ(fieldValues1[0].first, "myfield3"); + EXPECT_EQ(fieldValues1[0].second, "myvalue3"); + + EXPECT_FALSE(SWSSZmqConsumerStateTable_hasData(cst)); + arr = SWSSZmqConsumerStateTable_pops(cst); + ASSERT_EQ(arr.len, 0); + freeKeyOpFieldValuesArray(arr); + + arr_data[0] = {.key = "mykey3", .operation = "DEL", .fieldValues = {}}; + arr_data[1] = {.key = "mykey4", .operation = "DEL", .fieldValues = {}}; + arr = { .len = 2, .data = arr_data }; + + if (flag == 0) + for (uint64_t i = 0; i < arr.len; i++) + SWSSZmqProducerStateTable_del(pst, arr.data[i].key); + else + SWSSZmqClient_sendMsg(cli, "TEST_DB", "mytable", &arr); + + ASSERT_EQ(SWSSZmqConsumerStateTable_readData(cst, 500), SWSSSelectResult_DATA); + EXPECT_TRUE(SWSSZmqConsumerStateTable_hasData(cst)); + EXPECT_TRUE(SWSSZmqConsumerStateTable_hasCachedData(cst)); + arr = SWSSZmqConsumerStateTable_pops(cst); + EXPECT_FALSE(SWSSZmqConsumerStateTable_hasData(cst)); + EXPECT_FALSE(SWSSZmqConsumerStateTable_hasCachedData(cst)); + EXPECT_EQ(SWSSZmqConsumerStateTable_readData(cst, 500), SWSSSelectResult_TIMEOUT); + + kfvs = takeKeyOpFieldValuesArray(arr); + sortKfvs(kfvs); + freeKeyOpFieldValuesArray(arr); + + ASSERT_EQ(kfvs.size(), 2); + EXPECT_EQ(kfvKey(kfvs[0]), "mykey3"); + EXPECT_EQ(kfvOp(kfvs[0]), "DEL"); + EXPECT_EQ(kfvFieldsValues(kfvs[0]).size(), 0); + EXPECT_EQ(kfvKey(kfvs[1]), "mykey4"); + EXPECT_EQ(kfvOp(kfvs[1]), "DEL"); + EXPECT_EQ(kfvFieldsValues(kfvs[1]).size(), 0); + } + + // Server must be freed first to safely release message handlers (ZmqConsumerStateTable) + SWSSZmqServer_free(srv); + + SWSSZmqProducerStateTable_free(pst); + SWSSZmqConsumerStateTable_free(cst); + + SWSSZmqClient_free(cli); + + SWSSDBConnector_flushdb(db); + SWSSDBConnector_free(db); +} diff --git a/tests/main.cpp b/tests/main.cpp index 6cbaf251..440978a4 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -1,5 +1,6 @@ #include "gtest/gtest.h" #include "common/dbconnector.h" +#include "common/c-api/util.h" #include using namespace std; @@ -84,6 +85,9 @@ class SwsscommonEnvironment : public ::testing::Environment { SonicDBConfig::initializeGlobalConfig(global_existing_file); cout<<"INIT: load global db config file, isInit = "< Date: Fri, 1 Nov 2024 07:55:03 +0800 Subject: [PATCH 2/6] Fix unit test for goext (#938) Why I did it #937 PR checker is blocked by goext build How I did it Remove go build, and keep go test to run unit test. How to verify it Pass all UT and E2E test cases. --- goext/Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/goext/Makefile b/goext/Makefile index d46d9908..50beb76d 100644 --- a/goext/Makefile +++ b/goext/Makefile @@ -17,7 +17,6 @@ all: $(SWIG) $(SWIG_FLAG) -I/usr/include/swss/ swsscommon.i check: - sudo CGO_LDFLAGS="$(CGO_LDFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" $(GO) build sudo CGO_LDFLAGS="$(CGO_LDFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" $(GO) test clean: From deee7d6b23e0619d868ba9cc2c3e27a120edd150 Mon Sep 17 00:00:00 2001 From: Shuai Shang <47099361+shuaishang@users.noreply.github.com> Date: Fri, 1 Nov 2024 17:40:38 +0800 Subject: [PATCH 3/6] add PIC_CONTEXT_TABLE to shcema.h (#919) Co-authored-by: wenwang <2437730491@qq.com> --- common/schema.h | 1 + 1 file changed, 1 insertion(+) diff --git a/common/schema.h b/common/schema.h index 27aecdb0..17dc3ca8 100644 --- a/common/schema.h +++ b/common/schema.h @@ -156,6 +156,7 @@ namespace swss { #define APP_SRV6_SID_LIST_TABLE_NAME "SRV6_SID_LIST_TABLE" #define APP_SRV6_MY_SID_TABLE_NAME "SRV6_MY_SID_TABLE" +#define APP_PIC_CONTEXT_TABLE_NAME "PIC_CONTEXT_TABLE" #define APP_DASH_VNET_TABLE_NAME "DASH_VNET_TABLE" #define APP_DASH_QOS_TABLE_NAME "DASH_QOS_TABLE" From c6364d1bace34f4431adcdba26983f1a45ce4e94 Mon Sep 17 00:00:00 2001 From: Yijiao Qin Date: Fri, 1 Nov 2024 08:40:42 -0700 Subject: [PATCH 4/6] [common] add PerformanceTimer (#893) What I did Extend the PerformanceIntervalTimer utility in sonic-sairedis repo to measure API performance. To optimize performance of a periodically invoked function, we not only pay attention to how long it takes to finish execution (busy time), but also care about how long it waits to get executed (idle time). The total time determines the actual throughput in the real world system. Hence we want to see data in 3 ways: 1. Idle time between two calls (a gap between this start and the last end, during which the thread just waits) 2. Busy time of each API call (interval between this start and this end, it's the true execution time) 3. Total time elapsed (idle + busy) | <- Initial Gap -> | <- - 1st **Execution** - -> | <- 2nd Gap -> | <- - 2nd **Execution** - -> | | <---------------- 1st **Total** --------------> | <------------ 2nd **Total**--------------> | Other features 1. calculate the number of tasks finished per call (Routes Per Second in the context of routes loading) 2. batch the logging output 3. adjust the SWSSLOGLEVEL using a file indicator, default level is "INFO", hence we could enable perf logs in a dynamic way. Why I did it original utility has limited functionality we need gap data to obtain more insight on the mutual influence of upstream and downstream modules original utility is in libsairedis, to use it in sonic-swss, there would be much Makefile.am change, not necessary. a utility tool for swss should be included in swss-common when enabled on-demand, it could help measure the API performance under scaled traffic one use case: help measuring perf in pr optimized bgp loading speed --- common/Makefile.am | 3 +- common/performancetimer.cpp | 133 ++++++++++++++++++++++++++++++++++ common/performancetimer.h | 63 ++++++++++++++++ tests/Makefile.am | 1 + tests/performancetimer_ut.cpp | 43 +++++++++++ 5 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 common/performancetimer.cpp create mode 100644 common/performancetimer.h create mode 100644 tests/performancetimer_ut.cpp diff --git a/common/Makefile.am b/common/Makefile.am index 5d1de753..df41c3be 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -77,7 +77,8 @@ common_libswsscommon_la_SOURCES = \ common/c-api/zmqclient.cpp \ common/c-api/zmqserver.cpp \ common/c-api/zmqconsumerstatetable.cpp \ - common/c-api/zmqproducerstatetable.cpp + common/c-api/zmqproducerstatetable.cpp \ + common/performancetimer.cpp common_libswsscommon_la_CXXFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(LIBNL_CFLAGS) $(CODE_COVERAGE_CXXFLAGS) common_libswsscommon_la_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(LIBNL_CPPFLAGS) $(CODE_COVERAGE_CPPFLAGS) diff --git a/common/performancetimer.cpp b/common/performancetimer.cpp new file mode 100644 index 00000000..400a55c8 --- /dev/null +++ b/common/performancetimer.cpp @@ -0,0 +1,133 @@ +#include "performancetimer.h" + +#include "logger.h" +#include +#include + +using namespace swss; + +bool PerformanceTimer::m_enable = true; +#define LIMIT 5 +#define INDICATOR "/var/log/syslog_notice_flag" + +PerformanceTimer::PerformanceTimer( + _In_ std::string funcName, + _In_ uint64_t threshold, + _In_ bool verbose): + m_name(funcName), + m_threshold(threshold), + m_verbose(verbose) +{ + reset(); + m_stop = std::chrono::steady_clock::now(); +} + +void PerformanceTimer::reset() +{ + SWSS_LOG_ENTER(); + + m_tasks = 0; + m_calls = 0; + m_busy = 0; + m_idle = 0; + + m_intervals.clear(); + m_gaps.clear(); + m_incs.clear(); +} + +void PerformanceTimer::start() +{ + SWSS_LOG_ENTER(); + + m_start = std::chrono::steady_clock::now(); + // measures the gap between this start() and the last stop() + m_gaps.push_back(std::chrono::duration_cast(m_start-m_stop).count()); +} + +void PerformanceTimer::stop() +{ + SWSS_LOG_ENTER(); + m_stop = std::chrono::steady_clock::now(); +} + +std::string PerformanceTimer::inc(uint64_t count) +{ + SWSS_LOG_ENTER(); + + std::string output = ""; + + m_calls += 1; + + m_tasks += count; + + m_idle += m_gaps.back(); + + uint64_t interval = std::chrono::duration_cast(m_stop - m_start).count(); + + m_busy += interval; + + if (count == 0) { + m_gaps.pop_back(); + m_calls -= 1; + return output; + } + + if (m_incs.size() <= LIMIT) { + m_incs.push_back(count); + m_intervals.push_back(interval/1000000); + } else { + m_gaps.pop_back(); + } + + if (m_tasks >= m_threshold) + { + uint64_t mseconds = m_busy/1000000; + + if (m_enable && mseconds > 0) + { + output = getTimerState(); + std::ifstream indicator(INDICATOR); + if (indicator.good()) { + SWSS_LOG_NOTICE("%s", output.c_str()); + } else { + SWSS_LOG_INFO("%s", output.c_str()); + } + } + + reset(); + } + + return output; +} + +std::string PerformanceTimer::getTimerState() +{ + nlohmann::json data; + data["API"] = m_name; + data["Tasks"] = m_tasks; + data["busy[ms]"] = m_busy/1000000; + data["idle[ms]"] = m_idle; + data["Total[ms]"] = m_busy/1000000 + m_idle; + double ratio = static_cast(m_tasks) / static_cast(m_busy/1000000 + m_idle); + data["RPS[k]"] = std::round(ratio * 10.0) / 10.0f; + if (m_verbose) { + data["m_intervals"] = m_intervals; + data["m_gaps"] = m_gaps; + data["m_incs"] = m_incs; + } + + return data.dump(); +} + +void PerformanceTimer::setTimerName(const std::string& funcName) { + m_name = funcName; +} + +void PerformanceTimer::setTimerThreshold(uint64_t threshold) { + m_threshold = threshold; +} + +void PerformanceTimer::setTimerVerbose(bool verbose) { + m_verbose = verbose; +} diff --git a/common/performancetimer.h b/common/performancetimer.h new file mode 100644 index 00000000..545aeeae --- /dev/null +++ b/common/performancetimer.h @@ -0,0 +1,63 @@ +#pragma once + +#include "sal.h" +#include + +#include +#include +#include +#include +namespace swss +{ + class PerformanceTimer + { + public: + + PerformanceTimer( + _In_ std::string funcName = "", + _In_ uint64_t threshold = 10000, + _In_ bool verbose = false + ); + + ~PerformanceTimer() = default; + + public: + + void start(); + + void stop(); + + std::string inc(uint64_t count = 1); + + void reset(); + + std::string getTimerState(); + + static bool m_enable; + + void setTimerName(const std::string& funcName); + void setTimerThreshold(uint64_t threshold); + void setTimerVerbose(bool verbose); + + private: + + std::string m_name; // records what this timer measures about + uint64_t m_threshold; // reset the timer when the m_tasks reachs m_threshold + bool m_verbose; // decides whether to print in verbose when m_threshold is reached + + std::chrono::time_point m_start; + std::chrono::time_point m_stop; + + /* records how long the timer has idled between last stop and this start */ + std::deque m_gaps; + /* records how long each call takes */ + std::deque m_intervals; + /* records how many tasks each call finishes */ + std::deque m_incs; + + uint64_t m_tasks; // sum of m_incs + uint64_t m_calls; // how many times the timer is used + uint64_t m_busy; // sum of m_intervals + uint64_t m_idle; // sum of m_gaps + }; +} diff --git a/tests/Makefile.am b/tests/Makefile.am index 39712b23..9642b09a 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -43,6 +43,7 @@ tests_tests_SOURCES = tests/redis_ut.cpp \ tests/zmq_state_ut.cpp \ tests/profileprovider_ut.cpp \ tests/c_api_ut.cpp \ + tests/performancetimer_ut.cpp \ tests/main.cpp tests_tests_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_GTEST) $(LIBNL_CFLAGS) diff --git a/tests/performancetimer_ut.cpp b/tests/performancetimer_ut.cpp new file mode 100644 index 00000000..4bdaf74e --- /dev/null +++ b/tests/performancetimer_ut.cpp @@ -0,0 +1,43 @@ +#include "common/performancetimer.h" +#include +#include "gtest/gtest.h" +#include + +using namespace std; + +#define PRINT_ALL 1 + +TEST(PerformancetimerTest, basic) +{ + std::string expected; + + static swss::PerformanceTimer timer("basic", PRINT_ALL); + timer.start(); + this_thread::sleep_for(chrono::milliseconds(100)); + timer.stop(); + std::string output = timer.inc(1000); + + expected = R"({"API":"basic","RPS[k]":10.0,"Tasks":1000,"Total[ms]":100,"busy[ms]":100,"idle[ms]":0})"; + EXPECT_EQ(output, expected); + + timer.setTimerName("basic_set_name"); + timer.setTimerVerbose(true); + timer.setTimerThreshold(3000); + + timer.start(); + this_thread::sleep_for(chrono::milliseconds(100)); + timer.stop(); + output = timer.inc(1000); + EXPECT_EQ(output, ""); + + this_thread::sleep_for(chrono::milliseconds(200)); + + timer.start(); + this_thread::sleep_for(chrono::milliseconds(300)); + timer.stop(); + output = timer.inc(2000); + + expected = R"({"API":"basic_set_name","RPS[k]":5.0,"Tasks":3000,"Total[ms]":600,"busy[ms]":400,"idle[ms]":200,"m_gaps":[0,200],"m_incs":[1000,2000],"m_intervals":[100,300]})"; + + EXPECT_EQ(output, expected); +} From ace2080462d6ef36ce52e69a1391e2d9ebcfdabc Mon Sep 17 00:00:00 2001 From: ganglv <88995770+ganglyu@users.noreply.github.com> Date: Sat, 2 Nov 2024 19:28:38 +0800 Subject: [PATCH 5/6] Fix goext ut for bookworm (#940) Why I did it #937 PR checker is blocked by goext build How I did it Remove go build, and keep go test to run unit test. How to verify it Pass all UT and E2E test cases. --- goext/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/goext/Makefile b/goext/Makefile index 50beb76d..76fddb7f 100644 --- a/goext/Makefile +++ b/goext/Makefile @@ -17,6 +17,7 @@ all: $(SWIG) $(SWIG_FLAG) -I/usr/include/swss/ swsscommon.i check: + $(GO) mod init goext sudo CGO_LDFLAGS="$(CGO_LDFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" $(GO) test clean: From bd0f2eb0613aba8a448645b3bf53a44a50bd763f Mon Sep 17 00:00:00 2001 From: Prince George <45705344+prgeor@users.noreply.github.com> Date: Sun, 3 Nov 2024 19:08:00 -0800 Subject: [PATCH 6/6] Added new PORT_OPERR_TABLE table in STATE_DB (#941) Signed-off-by: Prince George --- common/schema.h | 1 + 1 file changed, 1 insertion(+) diff --git a/common/schema.h b/common/schema.h index 17dc3ca8..009c3cf3 100644 --- a/common/schema.h +++ b/common/schema.h @@ -479,6 +479,7 @@ namespace swss { #define STATE_ACL_STAGE_CAPABILITY_TABLE_NAME "ACL_STAGE_CAPABILITY_TABLE" #define STATE_PBH_CAPABILITIES_TABLE_NAME "PBH_CAPABILITIES" #define STATE_PORT_TABLE_NAME "PORT_TABLE" +#define STATE_PORT_OPER_ERR_TABLE_NAME "PORT_OPERR_TABLE" #define STATE_LAG_TABLE_NAME "LAG_TABLE" #define STATE_VLAN_TABLE_NAME "VLAN_TABLE" #define STATE_VLAN_MEMBER_TABLE_NAME "VLAN_MEMBER_TABLE"