[spp] [PATCH 02/57] spp_vf: support multi process
x-fn-spp at sl.ntt-tx.co.jp
x-fn-spp at sl.ntt-tx.co.jp
Thu Dec 28 05:55:09 CET 2017
From: Hiroyuki Nakamura <nakamura.hioryuki at po.ntt-tx.co.jp>
spp_vf was only a single process so far, but spp_vf supported
multi process. Following modification has been made.
* Change naming machanism to allow hashtable on shared memory
to be operated by multiple processes.
* Get config file path from command line argument.
And following modification has been made.
* Modify comment.
* Add and modify log message.
* Add function to remove vhost socket file.
Signed-off-by: Daiki Yamashita <yamashita.daiki.z01 at as.ntt-tx.co.jp>
Signed-off-by: Yasufum Ogawa <ogawa.yasufumi at lab.ntt.co.jp>
---
src/vf/classifier_mac.c | 79 ++++++++++++++++++------
src/vf/ringlatencystats.c | 6 +-
src/vf/spp_config.c | 14 ++---
src/vf/spp_config.h | 2 +-
src/vf/spp_forward.c | 5 +-
src/vf/spp_vf.c | 154 ++++++++++++++++++++++++++++++++++++++--------
src/vf/spp_vf.h | 4 +-
7 files changed, 206 insertions(+), 58 deletions(-)
diff --git a/src/vf/classifier_mac.c b/src/vf/classifier_mac.c
index da03905..374f164 100644
--- a/src/vf/classifier_mac.c
+++ b/src/vf/classifier_mac.c
@@ -5,6 +5,7 @@
#include <stddef.h>
#include <math.h>
+#include <rte_common.h>
#include <rte_mbuf.h>
#include <rte_log.h>
#include <rte_cycles.h>
@@ -38,18 +39,36 @@
nano second */
#define DRAIN_TX_PACKET_INTERVAL 100
+/* hash table name buffer size
+ [reson for value]
+ in dpdk's lib/librte_hash/rte_cuckoo_hash.c
+ snprintf(ring_name, sizeof(ring_name), "HT_%s", params->name);
+ snprintf(hash_name, sizeof(hash_name), "HT_%s", params->name);
+ ring_name buffer size is RTE_RING_NAMESIZE
+ hash_name buffer size is RTE_HASH_NAMESIZE */
+static const size_t HASH_TABLE_NAME_BUF_SZ =
+ ((RTE_HASH_NAMESIZE < RTE_RING_NAMESIZE) ?
+ RTE_HASH_NAMESIZE: RTE_RING_NAMESIZE) - 3;
+
/* mac address string(xx:xx:xx:xx:xx:xx) buffer size */
static const size_t ETHER_ADDR_STR_BUF_SZ =
ETHER_ADDR_LEN * 2 + (ETHER_ADDR_LEN - 1) + 1;
/* classified data (destination port, target packets, etc) */
struct classified_data {
- int if_no;
- uint8_t tx_port;
- uint16_t num_pkt;
+ enum port_type if_type;
+ int if_no;
+ uint8_t tx_port;
+ uint16_t num_pkt;
struct rte_mbuf *pkts[MAX_PKT_BURST];
};
+/* hash table count. use to make hash table name.
+ [reason for value]
+ it is incremented at the time of use,
+ but since we want to start at 0. */
+static rte_atomic16_t g_hash_table_count = RTE_ATOMIC16_INIT(0xff);
+
/* initialize classifier. */
static int
init_classifier(const struct spp_core_info *core_info,
@@ -58,11 +77,19 @@ init_classifier(const struct spp_core_info *core_info,
int ret = -1;
int i;
struct ether_addr eth_addr;
+ char hash_table_name[HASH_TABLE_NAME_BUF_SZ];
char mac_addr_str[ETHER_ADDR_STR_BUF_SZ];
+ /* make hash table name(require uniqueness between processes) */
+ sprintf(hash_table_name, "cmtab_%08x%02hx",
+ getpid(), rte_atomic16_add_return(&g_hash_table_count, 1));
+
+ RTE_LOG(INFO, SPP_CLASSIFIER_MAC, "Create table. name=%s, bufsz=%lu\n",
+ hash_table_name, HASH_TABLE_NAME_BUF_SZ);
+
/* set hash creating parameters */
struct rte_hash_parameters hash_params = {
- .name = "classifier_mac_table",
+ .name = hash_table_name,
.entries = NUM_CLASSIFIER_MAC_TABLE_ENTRY,
.key_len = sizeof(struct ether_addr),
.hash_func = DEFAULT_HASH_FUNC,
@@ -100,6 +127,7 @@ init_classifier(const struct spp_core_info *core_info,
}
/* set value */
+ classified_data[i].if_type = core_info->tx_ports[i].if_type;
classified_data[i].if_no = i;
classified_data[i].tx_port = core_info->tx_ports[i].dpdk_port;
classified_data[i].num_pkt = 0;
@@ -108,22 +136,25 @@ init_classifier(const struct spp_core_info *core_info,
return 0;
}
-/* transimit packet to one destination. */
+/* transmit packet to one destination. */
static inline void
-transimit_packet(struct classified_data *classified_data)
+transmit_packet(struct classified_data *classified_data)
{
int i;
uint16_t n_tx;
- /* set ringlatencystats */
- spp_ringlatencystats_add_time_stamp(classified_data->if_no,
- classified_data->pkts, classified_data->num_pkt);
+#ifdef SPP_RINGLATENCYSTATS_ENABLE
+ if (classified_data->if_type == RING)
+ /* if tx-if is ring, set ringlatencystats */
+ spp_ringlatencystats_add_time_stamp(classified_data->if_no,
+ classified_data->pkts, classified_data->num_pkt);
+#endif
- /* transimit packets */
+ /* transmit packets */
n_tx = rte_eth_tx_burst(classified_data->tx_port, 0,
classified_data->pkts, classified_data->num_pkt);
- /* free cannnot transiit packets */
+ /* free cannnot transmit packets */
if (unlikely(n_tx != classified_data->num_pkt)) {
for (i = n_tx; i < classified_data->num_pkt; i++)
rte_pktmbuf_free(classified_data->pkts[i]);
@@ -136,7 +167,7 @@ transimit_packet(struct classified_data *classified_data)
}
/* classify packet by destination mac address,
- and transimit packet (conditional). */
+ and transmit packet (conditional). */
static inline void
classify_packet(struct rte_mbuf **rx_pkts, uint16_t n_rx,
struct rte_hash *classifier_mac_table, struct classified_data *classified_data)
@@ -166,9 +197,12 @@ classify_packet(struct rte_mbuf **rx_pkts, uint16_t n_rx,
cd = classified_data + (long)lookup_data;
cd->pkts[cd->num_pkt++] = rx_pkts[i];
- /* transimit packet, if buffer is filled */
- if (unlikely(cd->num_pkt == MAX_PKT_BURST))
- transimit_packet(cd);
+ /* transmit packet, if buffer is filled */
+ if (unlikely(cd->num_pkt == MAX_PKT_BURST)) {
+ RTE_LOG(DEBUG, SPP_CLASSIFIER_MAC,
+ "transimit packets (buffer is filled). index=%ld, num_pkt=%hu\n", (long)lookup_data, cd->num_pkt);
+ transmit_packet(cd);
+ }
}
}
@@ -205,8 +239,11 @@ spp_classifier_mac_do(void *arg)
cur_tsc = rte_rdtsc();
if (unlikely(cur_tsc - prev_tsc > drain_tsc)) {
for (i = 0; i < n_classified_data; i++) {
- if (unlikely(classified_data[i].num_pkt != 0))
- transimit_packet(&classified_data[i]);
+ if (unlikely(classified_data[i].num_pkt != 0)) {
+ RTE_LOG(DEBUG, SPP_CLASSIFIER_MAC,
+ "transimit packets (drain). index=%d, num_pkt=%hu, interval=%lu\n", i, classified_data[i].num_pkt, cur_tsc - prev_tsc);
+ transmit_packet(&classified_data[i]);
+ }
}
prev_tsc = cur_tsc;
}
@@ -217,7 +254,13 @@ spp_classifier_mac_do(void *arg)
if (unlikely(n_rx == 0))
continue;
- /* classify and transimit (filled) */
+#ifdef SPP_RINGLATENCYSTATS_ENABLE
+ if (core_info->rx_ports[0].if_type == RING)
+ spp_ringlatencystats_calculate_latency(
+ core_info->rx_ports[0].if_no, rx_pkts, n_rx);
+#endif
+
+ /* classify and transmit (filled) */
classify_packet(rx_pkts, n_rx, classifier_mac_table, classified_data);
}
}
diff --git a/src/vf/ringlatencystats.c b/src/vf/ringlatencystats.c
index 8f44020..32ff55c 100644
--- a/src/vf/ringlatencystats.c
+++ b/src/vf/ringlatencystats.c
@@ -59,8 +59,8 @@ spp_ringlatencystats_init(uint64_t samp_intvl, uint16_t stats_count)
g_stats_count = stats_count;
RTE_LOG(DEBUG, SPP_RING_LATENCY_STATS,
- "g_samp_intvl=%lu, g_stats_count=%hu, cpns=%lu\n",
- g_samp_intvl, g_stats_count, cycles_per_ns());
+ "g_samp_intvl=%lu, g_stats_count=%hu, cpns=%lu, NS_PER_SEC=%f\n",
+ g_samp_intvl, g_stats_count, cycles_per_ns(), NS_PER_SEC);
return 0;
}
@@ -95,6 +95,8 @@ spp_ringlatencystats_add_time_stamp(int ring_id,
/* when it is over sampling interval */
/* set tsc to mbuf::timestamp */
if (unlikely(stats_info->timer_tsc >= g_samp_intvl)) {
+ RTE_LOG(DEBUG, SPP_RING_LATENCY_STATS,
+ "Set timestamp. ring_id=%d, pkts_index=%u, timestamp=%lu\n", ring_id, i, now);
pkts[i]->timestamp = now;
stats_info->timer_tsc = 0;
}
diff --git a/src/vf/spp_config.c b/src/vf/spp_config.c
index 63b85f2..a4d8a67 100644
--- a/src/vf/spp_config.c
+++ b/src/vf/spp_config.c
@@ -150,7 +150,7 @@ config_get_if_info(const char *port, enum port_type *if_type, int *if_no)
/*
* MAC addressを文字列から数値へ変換
*/
-int64_t
+static int64_t
config_change_mac_str_to_int64(const char *mac)
{
int64_t ret_mac = 0;
@@ -237,7 +237,7 @@ config_load_classifier_table(const json_t *obj,
/* table用オブジェクトの要素数取得 */
int array_num = json_array_size(array_obj);
if (unlikely(array_num <= 0) ||
- unlikely(array_num >= SPP_CONFIG_MAC_TABLE_MAX)) {
+ unlikely(array_num > SPP_CONFIG_MAC_TABLE_MAX)) {
RTE_LOG(ERR, APP, "Table size out of range. (path = %s, size = %d)\n",
JSONPATH_TABLE, array_num);
return -1;
@@ -356,7 +356,7 @@ config_set_rx_port(enum spp_core_type type, json_t *obj,
/* 受信ポート用オブジェクトの要素数取得 */
int port_num = json_array_size(array_obj);
if (unlikely(port_num <= 0) ||
- unlikely(port_num >= RTE_MAX_ETHPORTS)) {
+ unlikely(port_num > RTE_MAX_ETHPORTS)) {
RTE_LOG(ERR, APP, "RX port out of range. (path = %s, port = %d, route = merge)\n",
JSONPATH_RX_PORT, port_num);
return -1;
@@ -551,7 +551,7 @@ config_load_proc_info(const json_t *obj, int node_id, struct spp_config_area *co
/* functions用オブジェクトの要素数取得 */
int array_num = json_array_size(array_obj);
if (unlikely(array_num <= 0) ||
- unlikely(array_num >= SPP_CONFIG_CORE_MAX)) {
+ unlikely(array_num > SPP_CONFIG_CORE_MAX)) {
RTE_LOG(ERR, APP, "Functions size out of range. (path = %s, size = %d)\n",
JSONPATH_TABLE, array_num);
return -1;
@@ -630,18 +630,18 @@ config_load_proc_info(const json_t *obj, int node_id, struct spp_config_area *co
* NG : -1
*/
int
-spp_config_load_file(int node_id, struct spp_config_area *config)
+spp_config_load_file(const char* config_file_path, int node_id, struct spp_config_area *config)
{
/* Config initialize */
config_init_data(config);
/* Config load */
json_error_t json_error;
- json_t *conf_obj = json_load_file(SPP_CONFIG_FILE_PATH, 0, &json_error);
+ json_t *conf_obj = json_load_file(config_file_path, 0, &json_error);
if (unlikely(conf_obj == NULL)) {
/* Load error */
RTE_LOG(ERR, APP, "Config load failed. (path = %s, text = %s)\n",
- SPP_CONFIG_FILE_PATH, json_error.text);
+ config_file_path, json_error.text);
return -1;
}
diff --git a/src/vf/spp_config.h b/src/vf/spp_config.h
index d2a6a4b..24ddd32 100644
--- a/src/vf/spp_config.h
+++ b/src/vf/spp_config.h
@@ -85,6 +85,6 @@ struct spp_config_area {
* OK : 0
* NG : -1
*/
-int spp_config_load_file(int node_id, struct spp_config_area *config);
+int spp_config_load_file(const char* config_file_path, int node_id, struct spp_config_area *config);
#endif /* __SPP_CONFIG_H__ */
diff --git a/src/vf/spp_forward.c b/src/vf/spp_forward.c
index 4396929..e153e85 100644
--- a/src/vf/spp_forward.c
+++ b/src/vf/spp_forward.c
@@ -1,7 +1,8 @@
#include "spp_vf.h"
#include "ringlatencystats.h"
+#include "spp_forward.h"
-#define RTE_LOGTYPE_SPP_FORWARD RTE_LOGTYPE_USER1
+#define RTE_LOGTYPE_FORWARD RTE_LOGTYPE_USER1
/*
* 送受信ポートの経路情報
@@ -14,7 +15,7 @@ struct rxtx {
/*
* 使用するIF情報を移し替える
*/
-void
+static void
set_use_interface(struct spp_core_port_info *dst,
struct spp_core_port_info *src)
{
diff --git a/src/vf/spp_vf.c b/src/vf/spp_vf.c
index ee5cf63..f035d07 100644
--- a/src/vf/spp_vf.c
+++ b/src/vf/spp_vf.c
@@ -14,6 +14,15 @@
#define SPP_CORE_STATUS_CHECK_MAX 5
#define SPP_RING_LATENCY_STATS_SAMPLING_INTERVAL 1000000
+/* getopt_long return value for long option */
+enum SPP_LONGOPT_RETVAL {
+ SPP_LONGOPT_RETVAL__ = 127,
+
+ /* add below */
+
+ SPP_LONGOPT_RETVAL_CONFIG,
+};
+
/* struct */
struct startup_param {
uint64_t cpu;
@@ -41,13 +50,18 @@ static struct startup_param g_startup_param;
static struct if_info g_if_info;
static struct spp_core_info g_core_info[SPP_CONFIG_CORE_MAX];
+static char config_file_path[PATH_MAX];
+
/*
* print a usage message
*/
static void
usage(const char *progname)
{
- RTE_LOG(INFO, APP, "Usage: %s [EAL args]\n\n", progname);
+ RTE_LOG(INFO, APP, "Usage: %s [EAL args] -- [--config CONFIG_FILE_PATH]\n"
+ " --config CONFIG_FILE_PATH: specific config file path\n"
+ "\n"
+ , progname);
}
/*
@@ -208,7 +222,7 @@ set_core_status(enum spp_core_status status)
/*
* Process stop
*/
-void
+static void
stop_process(int signal) {
if (unlikely(signal != SIGTERM) &&
unlikely(signal != SIGINT)) {
@@ -239,16 +253,23 @@ parse_cpu_bit(uint64_t *cpu, const char *cpu_bit)
}
/*
- * Parse the application arguments to the client app.
+ * Parse the dpdk arguments for use in client app.
*/
static int
-parse_app_args(int argc, char *argv[])
+parse_dpdk_args(int argc, char *argv[])
{
+ int cnt;
int option_index, opt;
- char **argvopt = argv;
+ const int argcopt = argc;
+ char *argvopt[argcopt];
const char *progname = argv[0];
static struct option lgopts[] = { {0} };
+ /* getoptを使用するとargvが並び変わるみたいなので、コピーを実施 */
+ for (cnt = 0; cnt < argcopt; cnt++) {
+ argvopt[cnt] = argv[cnt];
+ }
+
/* Check DPDK parameter */
optind = 0;
opterr = 0;
@@ -273,6 +294,48 @@ parse_app_args(int argc, char *argv[])
}
/*
+ * Parse the application arguments to the client app.
+ */
+static int
+parse_app_args(int argc, char *argv[])
+{
+ int cnt;
+ int option_index, opt;
+ const int argcopt = argc;
+ char *argvopt[argcopt];
+ const char *progname = argv[0];
+ static struct option lgopts[] = {
+ { "config", required_argument, NULL, SPP_LONGOPT_RETVAL_CONFIG },
+ { 0 },
+ };
+
+ /* getoptを使用するとargvが並び変わるみたいなので、コピーを実施 */
+ for (cnt = 0; cnt < argcopt; cnt++) {
+ argvopt[cnt] = argv[cnt];
+ }
+
+ /* Check application parameter */
+ optind = 0;
+ opterr = 0;
+ while ((opt = getopt_long(argc, argvopt, "", lgopts,
+ &option_index)) != EOF) {
+ switch (opt) {
+ case SPP_LONGOPT_RETVAL_CONFIG:
+ if (optarg[0] == '\0' || strlen(optarg) >= sizeof(config_file_path)) {
+ usage(progname);
+ return -1;
+ }
+ strcpy(config_file_path, optarg);
+ break;
+ default:
+ break;
+ }
+ }
+
+ return 0;
+}
+
+/*
* IF種別&IF番号のIF情報の領域取得
*/
static struct patch_info *
@@ -299,7 +362,7 @@ get_if_area(enum port_type if_type, int if_no)
* IF情報初期化
*/
static void
-init_if_info()
+init_if_info(void)
{
memset(&g_if_info, 0x00, sizeof(g_if_info));
}
@@ -308,7 +371,7 @@ init_if_info()
* CORE情報初期化
*/
static void
-init_core_info()
+init_core_info(void)
{
memset(&g_core_info, 0x00, sizeof(g_core_info));
int core_cnt, port_cnt;
@@ -330,7 +393,7 @@ set_form_proc_info(struct spp_config_area *config)
int core_cnt, rx_start, rx_cnt, tx_start, tx_cnt;
enum port_type if_type;
int if_no;
- int64_t cpu_bit = 0;
+ uint64_t cpu_bit = 0;
struct spp_config_functions *core_func = NULL;
struct spp_core_info *core_info = NULL;
struct patch_info *patch_info = NULL;
@@ -344,7 +407,7 @@ set_form_proc_info(struct spp_config_area *config)
/* Forwardをまとめる事は可、他種別は不可 */
if ((core_info->type != SPP_CONFIG_UNUSE) &&
- ((core_info->type != SPP_CONFIG_FORWARD) &&
+ ((core_info->type != SPP_CONFIG_FORWARD) ||
(core_func->type != SPP_CONFIG_FORWARD))) {
RTE_LOG(ERR, APP, "Core in use. (core = %d, type = %d/%d)\n",
core_func->core_no,
@@ -371,8 +434,8 @@ set_form_proc_info(struct spp_config_area *config)
patch_info->use_flg = 1;
if (unlikely(patch_info->rx_core != NULL)) {
- RTE_LOG(ERR, APP, "Used RX port (if_type = %d, if_no = %d)\n",
- if_type, if_no);
+ RTE_LOG(ERR, APP, "Used RX port (core = %d, if_type = %d, if_no = %d)\n",
+ core_func->core_no, if_type, if_no);
return -1;
}
@@ -395,8 +458,8 @@ set_form_proc_info(struct spp_config_area *config)
patch_info->use_flg = 1;
if (unlikely(patch_info->tx_core != NULL)) {
- RTE_LOG(ERR, APP, "Used TX port (if_type = %d, if_no = %d)\n",
- if_type, if_no);
+ RTE_LOG(ERR, APP, "Used TX port (core = %d, if_type = %d, if_no = %d)\n",
+ core_func->core_no, if_type, if_no);
return -1;
}
@@ -456,7 +519,7 @@ set_from_classifier_table(struct spp_config_area *config)
* NIC用の情報設定
*/
static int
-set_nic_interface(struct spp_config_area *config)
+set_nic_interface(struct spp_config_area *config __attribute__ ((unused)))
{
/* NIC Setting */
g_if_info.num_nic = rte_eth_dev_count();
@@ -590,8 +653,8 @@ static int
init_manage_data(struct spp_config_area *config)
{
/* Initialize */
- init_if_info(config);
- init_core_info(config);
+ init_if_info();
+ init_core_info();
/* Set config data */
int ret_proc = set_form_proc_info(config);
@@ -629,7 +692,7 @@ init_manage_data(struct spp_config_area *config)
#ifdef SPP_RINGLATENCYSTATS_ENABLE /* RING滞留時間 */
static void
-print_ring_latency_stats()
+print_ring_latency_stats(void)
{
/* Clear screen and move to top left */
const char topLeft[] = { 27, '[', '1', ';', '1', 'H', '\0' };
@@ -669,6 +732,24 @@ print_ring_latency_stats()
#endif /* SPP_RINGLATENCYSTATS_ENABLE */
/*
+ * VHOST用ソケットファイル削除
+ */
+static void
+del_vhost_sockfile(struct patch_info *vhost_patchs)
+{
+ int cnt;
+ for (cnt = 0; cnt < RTE_MAX_ETHPORTS; cnt++) {
+ if (likely(vhost_patchs[cnt].use_flg == 0)) {
+ /* VHOST未使用はスキップ */
+ continue;
+ }
+
+ /* 使用していたVHOSTについて削除を行う */
+ remove(get_vhost_iface_name(cnt));
+ }
+}
+
+/*
* main
*/
int
@@ -692,25 +773,44 @@ ut_main(int argc, char *argv[])
signal(SIGTERM, stop_process);
signal(SIGINT, stop_process);
+ /* set default config file path */
+ strcpy(config_file_path, SPP_CONFIG_FILE_PATH);
+
unsigned int main_lcore_id = 0xffffffff;
while(1) {
- /* Parse parameters */
- int ret_parse = parse_app_args(argc, argv);
+ /* Parse dpdk parameters */
+ int ret_parse = parse_dpdk_args(argc, argv);
if (unlikely(ret_parse != 0)) {
break;
}
- /* Load config */
- int ret_config = spp_config_load_file(0, &g_config);
- if (unlikely(ret_config != 0)) {
- break;
- }
-
/* DPDK initialize */
int ret_dpdk = rte_eal_init(argc, argv);
if (unlikely(ret_dpdk < 0)) {
break;
}
+
+ /* Skip dpdk parameters */
+ argc -= ret_dpdk;
+ argv += ret_dpdk;
+
+ /* Set log level */
+ rte_log_set_global_level(RTE_LOG_LEVEL);
+
+ /* Parse application parameters */
+ ret_parse = parse_app_args(argc, argv);
+ if (unlikely(ret_parse != 0)) {
+ break;
+ }
+
+ RTE_LOG(INFO, APP, "Load config file(%s)\n", config_file_path);
+
+ /* Load config */
+ int ret_config = spp_config_load_file(config_file_path, 0, &g_config);
+ if (unlikely(ret_config != 0)) {
+ break;
+ }
+
/* Get core id. */
main_lcore_id = rte_lcore_id();
@@ -775,7 +875,6 @@ ut_main(int argc, char *argv[])
}
/* exit */
- stop_process(SIGINT);
if (main_lcore_id == rte_lcore_id())
{
g_core_info[main_lcore_id].status = SPP_CORE_STOP;
@@ -783,6 +882,9 @@ ut_main(int argc, char *argv[])
if (unlikely(ret_core_end != 0)) {
RTE_LOG(ERR, APP, "Core did not stop.\n");
}
+
+ /* 使用していたVHOSTのソケットファイルを削除 */
+ del_vhost_sockfile(g_if_info.vhost_patchs);
}
/* 他機能部終了処理 */
diff --git a/src/vf/spp_vf.h b/src/vf/spp_vf.h
index feb59b6..c0faa57 100644
--- a/src/vf/spp_vf.h
+++ b/src/vf/spp_vf.h
@@ -29,8 +29,8 @@ struct spp_core_port_info {
* Core info
*/
struct spp_core_info {
- enum spp_core_status status;
- enum spp_core_type type;
+ volatile enum spp_core_status status;
+ enum spp_core_type type;
int num_rx_port;
int num_tx_port;
struct spp_core_port_info rx_ports[RTE_MAX_ETHPORTS];
--
1.9.1
More information about the spp
mailing list