xref: /dpdk/lib/telemetry/telemetry.c (revision 65b078dcc6a38aeff9f417d4b5d0ed021c10f4a6)
1 /* SPDX-License-Identifier: BSD-3-Clause
2  * Copyright(c) 2020 Intel Corporation
3  */
4 
5 #include <ctype.h>
6 #include <errno.h>
7 #include <stdlib.h>
8 #ifndef RTE_EXEC_ENV_WINDOWS
9 #include <unistd.h>
10 #include <pthread.h>
11 #include <sys/socket.h>
12 #include <sys/un.h>
13 #include <sys/stat.h>
14 #endif /* !RTE_EXEC_ENV_WINDOWS */
15 
16 /* we won't link against libbsd, so just always use DPDKs-specific strlcpy */
17 #undef RTE_USE_LIBBSD
18 #include <rte_string_fns.h>
19 #include <rte_common.h>
20 #include <rte_spinlock.h>
21 #include <rte_log.h>
22 
23 #include "rte_telemetry.h"
24 #include "telemetry_json.h"
25 #include "telemetry_data.h"
26 #include "telemetry_internal.h"
27 
28 #define MAX_CMD_LEN 56
29 #define MAX_OUTPUT_LEN (1024 * 16)
30 #define MAX_CONNECTIONS 10
31 
32 #ifndef RTE_EXEC_ENV_WINDOWS
33 static void *
34 client_handler(void *socket);
35 #endif /* !RTE_EXEC_ENV_WINDOWS */
36 
37 struct cmd_callback {
38 	char cmd[MAX_CMD_LEN];
39 	telemetry_cb fn;
40 	char help[RTE_TEL_MAX_STRING_LEN];
41 };
42 
43 #ifndef RTE_EXEC_ENV_WINDOWS
44 struct socket {
45 	int sock;
46 	char path[sizeof(((struct sockaddr_un *)0)->sun_path)];
47 	handler fn;
48 	RTE_ATOMIC(uint16_t) *num_clients;
49 };
50 static struct socket v2_socket; /* socket for v2 telemetry */
51 static struct socket v1_socket; /* socket for v1 telemetry */
52 #endif /* !RTE_EXEC_ENV_WINDOWS */
53 
54 static const char *telemetry_version; /* save rte_version */
55 static const char *socket_dir;        /* runtime directory */
56 static rte_cpuset_t *thread_cpuset;
57 
58 RTE_LOG_REGISTER_DEFAULT(logtype, WARNING);
59 #define RTE_LOGTYPE_TELEMETRY logtype
60 #define TMTY_LOG_LINE(l, ...) RTE_LOG_LINE(l, TELEMETRY, "" __VA_ARGS__)
61 
62 /* list of command callbacks, with one command registered by default */
63 static struct cmd_callback *callbacks;
64 static int num_callbacks; /* How many commands are registered */
65 /* Used when accessing or modifying list of command callbacks */
66 static rte_spinlock_t callback_sl = RTE_SPINLOCK_INITIALIZER;
67 #ifndef RTE_EXEC_ENV_WINDOWS
68 static RTE_ATOMIC(uint16_t) v2_clients;
69 #endif /* !RTE_EXEC_ENV_WINDOWS */
70 
71 int
72 rte_telemetry_register_cmd(const char *cmd, telemetry_cb fn, const char *help)
73 {
74 	struct cmd_callback *new_callbacks;
75 	const char *cmdp = cmd;
76 	int i = 0;
77 
78 	if (strlen(cmd) >= MAX_CMD_LEN || fn == NULL || cmd[0] != '/'
79 			|| strlen(help) >= RTE_TEL_MAX_STRING_LEN)
80 		return -EINVAL;
81 
82 	while (*cmdp != '\0') {
83 		if (!isalnum(*cmdp) && *cmdp != '_' && *cmdp != '/')
84 			return -EINVAL;
85 		cmdp++;
86 	}
87 
88 	rte_spinlock_lock(&callback_sl);
89 	new_callbacks = realloc(callbacks, sizeof(callbacks[0]) * (num_callbacks + 1));
90 	if (new_callbacks == NULL) {
91 		rte_spinlock_unlock(&callback_sl);
92 		return -ENOMEM;
93 	}
94 	callbacks = new_callbacks;
95 
96 	while (i < num_callbacks && strcmp(cmd, callbacks[i].cmd) > 0)
97 		i++;
98 	if (i != num_callbacks)
99 		/* Move elements to keep the list alphabetical */
100 		memmove(callbacks + i + 1, callbacks + i,
101 			sizeof(struct cmd_callback) * (num_callbacks - i));
102 
103 	strlcpy(callbacks[i].cmd, cmd, MAX_CMD_LEN);
104 	callbacks[i].fn = fn;
105 	strlcpy(callbacks[i].help, help, RTE_TEL_MAX_STRING_LEN);
106 	num_callbacks++;
107 	rte_spinlock_unlock(&callback_sl);
108 
109 	return 0;
110 }
111 
112 #ifndef RTE_EXEC_ENV_WINDOWS
113 
114 static int
115 list_commands(const char *cmd __rte_unused, const char *params __rte_unused,
116 		struct rte_tel_data *d)
117 {
118 	int i;
119 
120 	rte_tel_data_start_array(d, RTE_TEL_STRING_VAL);
121 	rte_spinlock_lock(&callback_sl);
122 	for (i = 0; i < num_callbacks; i++)
123 		rte_tel_data_add_array_string(d, callbacks[i].cmd);
124 	rte_spinlock_unlock(&callback_sl);
125 	return 0;
126 }
127 
128 static int
129 json_info(const char *cmd __rte_unused, const char *params __rte_unused,
130 		struct rte_tel_data *d)
131 {
132 	rte_tel_data_start_dict(d);
133 	rte_tel_data_add_dict_string(d, "version", telemetry_version);
134 	rte_tel_data_add_dict_int(d, "pid", getpid());
135 	rte_tel_data_add_dict_int(d, "max_output_len", MAX_OUTPUT_LEN);
136 	return 0;
137 }
138 
139 static int
140 command_help(const char *cmd __rte_unused, const char *params,
141 		struct rte_tel_data *d)
142 {
143 	int i;
144 	/* if no parameters return our own help text */
145 	const char *to_lookup = (params == NULL ? cmd : params);
146 
147 	rte_tel_data_start_dict(d);
148 	rte_spinlock_lock(&callback_sl);
149 	for (i = 0; i < num_callbacks; i++)
150 		if (strcmp(to_lookup, callbacks[i].cmd) == 0) {
151 			if (params == NULL)
152 				rte_tel_data_string(d, callbacks[i].help);
153 			else
154 				rte_tel_data_add_dict_string(d, params,	callbacks[i].help);
155 			break;
156 		}
157 	rte_spinlock_unlock(&callback_sl);
158 	if (i == num_callbacks)
159 		return -1;
160 	return 0;
161 }
162 
163 static int
164 container_to_json(const struct rte_tel_data *d, char *out_buf, size_t buf_len)
165 {
166 	size_t used = 0;
167 	unsigned int i;
168 
169 	if (d->type != TEL_DICT && d->type != TEL_ARRAY_UINT &&
170 		d->type != TEL_ARRAY_INT && d->type != TEL_ARRAY_STRING)
171 		return snprintf(out_buf, buf_len, "null");
172 
173 	used = rte_tel_json_empty_array(out_buf, buf_len, 0);
174 	if (d->type == TEL_ARRAY_UINT)
175 		for (i = 0; i < d->data_len; i++)
176 			used = rte_tel_json_add_array_uint(out_buf,
177 				buf_len, used,
178 				d->data.array[i].uval);
179 	if (d->type == TEL_ARRAY_INT)
180 		for (i = 0; i < d->data_len; i++)
181 			used = rte_tel_json_add_array_int(out_buf,
182 				buf_len, used,
183 				d->data.array[i].ival);
184 	if (d->type == TEL_ARRAY_STRING)
185 		for (i = 0; i < d->data_len; i++)
186 			used = rte_tel_json_add_array_string(out_buf,
187 				buf_len, used,
188 				d->data.array[i].sval);
189 	if (d->type == TEL_DICT)
190 		for (i = 0; i < d->data_len; i++) {
191 			const struct tel_dict_entry *v = &d->data.dict[i];
192 			switch (v->type) {
193 			case RTE_TEL_STRING_VAL:
194 				used = rte_tel_json_add_obj_str(out_buf,
195 						buf_len, used,
196 						v->name, v->value.sval);
197 				break;
198 			case RTE_TEL_INT_VAL:
199 				used = rte_tel_json_add_obj_int(out_buf,
200 						buf_len, used,
201 						v->name, v->value.ival);
202 				break;
203 			case RTE_TEL_UINT_VAL:
204 				used = rte_tel_json_add_obj_uint(out_buf,
205 						buf_len, used,
206 						v->name, v->value.uval);
207 				break;
208 			case RTE_TEL_CONTAINER:
209 			{
210 				char *temp = malloc(buf_len);
211 				if (temp == NULL)
212 					break;
213 				*temp = '\0';  /* ensure valid string */
214 
215 				const struct container *cont =
216 						&v->value.container;
217 				if (container_to_json(cont->data,
218 						temp, buf_len) != 0)
219 					used = rte_tel_json_add_obj_json(
220 							out_buf,
221 							buf_len, used,
222 							v->name, temp);
223 				if (!cont->keep)
224 					rte_tel_data_free(cont->data);
225 				free(temp);
226 				break;
227 			}
228 			}
229 		}
230 
231 	return used;
232 }
233 
234 static void
235 output_json(const char *cmd, const struct rte_tel_data *d, int s)
236 {
237 	char out_buf[MAX_OUTPUT_LEN];
238 
239 	char *cb_data_buf;
240 	size_t buf_len, prefix_used, used = 0;
241 	unsigned int i;
242 
243 	RTE_BUILD_BUG_ON(sizeof(out_buf) < MAX_CMD_LEN +
244 			RTE_TEL_MAX_SINGLE_STRING_LEN + 10);
245 
246 	prefix_used = snprintf(out_buf, sizeof(out_buf), "{\"%.*s\":",
247 			MAX_CMD_LEN, cmd);
248 	cb_data_buf = &out_buf[prefix_used];
249 	buf_len = sizeof(out_buf) - prefix_used - 1; /* space for '}' */
250 
251 	switch (d->type) {
252 	case TEL_NULL:
253 		used = strlcpy(cb_data_buf, "null", buf_len);
254 		break;
255 
256 	case TEL_STRING:
257 		used = rte_tel_json_str(cb_data_buf, buf_len, 0, d->data.str);
258 		break;
259 
260 	case TEL_DICT:
261 		used = rte_tel_json_empty_obj(cb_data_buf, buf_len, 0);
262 		for (i = 0; i < d->data_len; i++) {
263 			const struct tel_dict_entry *v = &d->data.dict[i];
264 			switch (v->type) {
265 			case RTE_TEL_STRING_VAL:
266 				used = rte_tel_json_add_obj_str(cb_data_buf,
267 						buf_len, used,
268 						v->name, v->value.sval);
269 				break;
270 			case RTE_TEL_INT_VAL:
271 				used = rte_tel_json_add_obj_int(cb_data_buf,
272 						buf_len, used,
273 						v->name, v->value.ival);
274 				break;
275 			case RTE_TEL_UINT_VAL:
276 				used = rte_tel_json_add_obj_uint(cb_data_buf,
277 						buf_len, used,
278 						v->name, v->value.uval);
279 				break;
280 			case RTE_TEL_CONTAINER:
281 			{
282 				char *temp = malloc(buf_len);
283 				if (temp == NULL)
284 					break;
285 				*temp = '\0';  /* ensure valid string */
286 
287 				const struct container *cont =
288 						&v->value.container;
289 				if (container_to_json(cont->data,
290 						temp, buf_len) != 0)
291 					used = rte_tel_json_add_obj_json(
292 							cb_data_buf,
293 							buf_len, used,
294 							v->name, temp);
295 				if (!cont->keep)
296 					rte_tel_data_free(cont->data);
297 				free(temp);
298 			}
299 			}
300 		}
301 		break;
302 
303 	case TEL_ARRAY_STRING:
304 	case TEL_ARRAY_INT:
305 	case TEL_ARRAY_UINT:
306 	case TEL_ARRAY_CONTAINER:
307 		used = rte_tel_json_empty_array(cb_data_buf, buf_len, 0);
308 		for (i = 0; i < d->data_len; i++)
309 			if (d->type == TEL_ARRAY_STRING)
310 				used = rte_tel_json_add_array_string(
311 						cb_data_buf,
312 						buf_len, used,
313 						d->data.array[i].sval);
314 			else if (d->type == TEL_ARRAY_INT)
315 				used = rte_tel_json_add_array_int(cb_data_buf,
316 						buf_len, used,
317 						d->data.array[i].ival);
318 			else if (d->type == TEL_ARRAY_UINT)
319 				used = rte_tel_json_add_array_uint(cb_data_buf,
320 						buf_len, used,
321 						d->data.array[i].uval);
322 			else if (d->type == TEL_ARRAY_CONTAINER) {
323 				char *temp = malloc(buf_len);
324 				if (temp == NULL)
325 					break;
326 				*temp = '\0';  /* ensure valid string */
327 
328 				const struct container *rec_data =
329 						&d->data.array[i].container;
330 				if (container_to_json(rec_data->data,
331 						temp, buf_len) != 0)
332 					used = rte_tel_json_add_array_json(
333 							cb_data_buf,
334 							buf_len, used, temp);
335 				if (!rec_data->keep)
336 					rte_tel_data_free(rec_data->data);
337 				free(temp);
338 			}
339 		break;
340 	}
341 	used += prefix_used;
342 	used += strlcat(out_buf + used, "}", sizeof(out_buf) - used);
343 	if (write(s, out_buf, used) < 0)
344 		perror("Error writing to socket");
345 }
346 
347 static void
348 perform_command(telemetry_cb fn, const char *cmd, const char *param, int s)
349 {
350 	struct rte_tel_data data = {0};
351 
352 	int ret = fn(cmd, param, &data);
353 	if (ret < 0) {
354 		char out_buf[MAX_CMD_LEN + 10];
355 		int used = snprintf(out_buf, sizeof(out_buf), "{\"%.*s\":null}",
356 				MAX_CMD_LEN, cmd ? cmd : "none");
357 		if (write(s, out_buf, used) < 0)
358 			perror("Error writing to socket");
359 		return;
360 	}
361 	output_json(cmd, &data, s);
362 }
363 
364 static int
365 unknown_command(const char *cmd __rte_unused, const char *params __rte_unused,
366 		struct rte_tel_data *d)
367 {
368 	return d->type = TEL_NULL;
369 }
370 
371 static void *
372 client_handler(void *sock_id)
373 {
374 	int s = (int)(uintptr_t)sock_id;
375 	char buffer[1024];
376 	char info_str[1024];
377 	snprintf(info_str, sizeof(info_str),
378 			"{\"version\":\"%s\",\"pid\":%d,\"max_output_len\":%d}",
379 			telemetry_version, getpid(), MAX_OUTPUT_LEN);
380 	if (write(s, info_str, strlen(info_str)) < 0) {
381 		close(s);
382 		return NULL;
383 	}
384 
385 	/* receive data is not null terminated */
386 	int bytes = read(s, buffer, sizeof(buffer) - 1);
387 	while (bytes > 0) {
388 		buffer[bytes] = 0;
389 		const char *cmd = strtok(buffer, ",");
390 		const char *param = strtok(NULL, "\0");
391 		telemetry_cb fn = unknown_command;
392 		int i;
393 
394 		if (cmd && strlen(cmd) < MAX_CMD_LEN) {
395 			rte_spinlock_lock(&callback_sl);
396 			for (i = 0; i < num_callbacks; i++)
397 				if (strcmp(cmd, callbacks[i].cmd) == 0) {
398 					fn = callbacks[i].fn;
399 					break;
400 				}
401 			rte_spinlock_unlock(&callback_sl);
402 		}
403 		perform_command(fn, cmd, param, s);
404 
405 		bytes = read(s, buffer, sizeof(buffer) - 1);
406 	}
407 	close(s);
408 	rte_atomic_fetch_sub_explicit(&v2_clients, 1, rte_memory_order_relaxed);
409 	return NULL;
410 }
411 
412 static void *
413 socket_listener(void *socket)
414 {
415 	while (1) {
416 		pthread_t th;
417 		int rc;
418 		struct socket *s = (struct socket *)socket;
419 		int s_accepted = accept(s->sock, NULL, NULL);
420 		if (s_accepted < 0) {
421 			TMTY_LOG_LINE(ERR, "Error with accept, telemetry thread quitting");
422 			return NULL;
423 		}
424 		if (s->num_clients != NULL) {
425 			uint16_t conns = rte_atomic_load_explicit(s->num_clients,
426 					rte_memory_order_relaxed);
427 			if (conns >= MAX_CONNECTIONS) {
428 				close(s_accepted);
429 				continue;
430 			}
431 			rte_atomic_fetch_add_explicit(s->num_clients, 1,
432 					rte_memory_order_relaxed);
433 		}
434 		rc = pthread_create(&th, NULL, s->fn,
435 				    (void *)(uintptr_t)s_accepted);
436 		if (rc != 0) {
437 			TMTY_LOG_LINE(ERR, "Error with create client thread: %s",
438 				 strerror(rc));
439 			close(s_accepted);
440 			if (s->num_clients != NULL)
441 				rte_atomic_fetch_sub_explicit(s->num_clients, 1,
442 						   rte_memory_order_relaxed);
443 			continue;
444 		}
445 		pthread_detach(th);
446 	}
447 	return NULL;
448 }
449 
450 static inline char *
451 get_socket_path(const char *runtime_dir, const int version)
452 {
453 	static char path[PATH_MAX];
454 	snprintf(path, sizeof(path), "%s/dpdk_telemetry.v%d",
455 			strlen(runtime_dir) ? runtime_dir : "/tmp", version);
456 	return path;
457 }
458 
459 static void
460 unlink_sockets(void)
461 {
462 	if (v2_socket.path[0])
463 		unlink(v2_socket.path);
464 	if (v1_socket.path[0])
465 		unlink(v1_socket.path);
466 }
467 
468 static int
469 create_socket(char *path)
470 {
471 	int sock = socket(AF_UNIX, SOCK_SEQPACKET, 0);
472 	if (sock < 0) {
473 		TMTY_LOG_LINE(ERR, "Error with socket creation, %s", strerror(errno));
474 		return -1;
475 	}
476 
477 	struct sockaddr_un sun = {.sun_family = AF_UNIX};
478 	strlcpy(sun.sun_path, path, sizeof(sun.sun_path));
479 	TMTY_LOG_LINE(DEBUG, "Attempting socket bind to path '%s'", path);
480 
481 	if (bind(sock, (void *) &sun, sizeof(sun)) < 0) {
482 		struct stat st;
483 
484 		TMTY_LOG_LINE(DEBUG, "Initial bind to socket '%s' failed.", path);
485 
486 		/* first check if we have a runtime dir */
487 		if (stat(socket_dir, &st) < 0 || !S_ISDIR(st.st_mode)) {
488 			TMTY_LOG_LINE(ERR, "Cannot access DPDK runtime directory: %s", socket_dir);
489 			close(sock);
490 			return -ENOENT;
491 		}
492 
493 		/* check if current socket is active */
494 		if (connect(sock, (void *)&sun, sizeof(sun)) == 0) {
495 			close(sock);
496 			return -EADDRINUSE;
497 		}
498 
499 		/* socket is not active, delete and attempt rebind */
500 		TMTY_LOG_LINE(DEBUG, "Attempting unlink and retrying bind");
501 		unlink(sun.sun_path);
502 		if (bind(sock, (void *) &sun, sizeof(sun)) < 0) {
503 			TMTY_LOG_LINE(ERR, "Error binding socket: %s", strerror(errno));
504 			close(sock);
505 			return -errno; /* if unlink failed, this will be -EADDRINUSE as above */
506 		}
507 	}
508 
509 	if (listen(sock, 1) < 0) {
510 		TMTY_LOG_LINE(ERR, "Error calling listen for socket: %s", strerror(errno));
511 		unlink(sun.sun_path);
512 		close(sock);
513 		return -errno;
514 	}
515 	TMTY_LOG_LINE(DEBUG, "Socket creation and binding ok");
516 
517 	return sock;
518 }
519 
520 static void
521 set_thread_name(pthread_t id __rte_unused, const char *name __rte_unused)
522 {
523 #if defined RTE_EXEC_ENV_LINUX && defined __GLIBC__ && defined __GLIBC_PREREQ
524 #if __GLIBC_PREREQ(2, 12)
525 	pthread_setname_np(id, name);
526 #endif
527 #elif defined RTE_EXEC_ENV_FREEBSD
528 	pthread_set_name_np(id, name);
529 #endif
530 }
531 
532 static int
533 telemetry_legacy_init(void)
534 {
535 	pthread_t t_old;
536 	int rc;
537 
538 	if (num_legacy_callbacks == 1) {
539 		TMTY_LOG_LINE(WARNING, "No legacy callbacks, legacy socket not created");
540 		return -1;
541 	}
542 
543 	v1_socket.fn = legacy_client_handler;
544 	if ((size_t) snprintf(v1_socket.path, sizeof(v1_socket.path),
545 			"%s/telemetry", socket_dir) >= sizeof(v1_socket.path)) {
546 		TMTY_LOG_LINE(ERR, "Error with socket binding, path too long");
547 		return -1;
548 	}
549 	v1_socket.sock = create_socket(v1_socket.path);
550 	if (v1_socket.sock < 0) {
551 		v1_socket.path[0] = '\0';
552 		return -1;
553 	}
554 	rc = pthread_create(&t_old, NULL, socket_listener, &v1_socket);
555 	if (rc != 0) {
556 		TMTY_LOG_LINE(ERR, "Error with create legacy socket thread: %s",
557 			 strerror(rc));
558 		close(v1_socket.sock);
559 		v1_socket.sock = -1;
560 		unlink(v1_socket.path);
561 		v1_socket.path[0] = '\0';
562 		return -1;
563 	}
564 	pthread_setaffinity_np(t_old, sizeof(*thread_cpuset), thread_cpuset);
565 	set_thread_name(t_old, "dpdk-telemet-v1");
566 	TMTY_LOG_LINE(DEBUG, "Legacy telemetry socket initialized ok");
567 	pthread_detach(t_old);
568 	return 0;
569 }
570 
571 static int
572 telemetry_v2_init(void)
573 {
574 	char spath[sizeof(v2_socket.path)];
575 	pthread_t t_new;
576 	short suffix = 0;
577 	int rc;
578 
579 	v2_socket.num_clients = &v2_clients;
580 	rte_telemetry_register_cmd("/", list_commands,
581 			"Returns list of available commands, Takes no parameters");
582 	rte_telemetry_register_cmd("/info", json_info,
583 			"Returns DPDK Telemetry information. Takes no parameters");
584 	rte_telemetry_register_cmd("/help", command_help,
585 			"Returns help text for a command. Parameters: string command");
586 	v2_socket.fn = client_handler;
587 	if (strlcpy(spath, get_socket_path(socket_dir, 2), sizeof(spath)) >= sizeof(spath)) {
588 		TMTY_LOG_LINE(ERR, "Error with socket binding, path too long");
589 		return -1;
590 	}
591 	memcpy(v2_socket.path, spath, sizeof(v2_socket.path));
592 
593 	v2_socket.sock = create_socket(v2_socket.path);
594 	while (v2_socket.sock < 0) {
595 		/* bail out on unexpected error, or suffix wrap-around */
596 		if (v2_socket.sock != -EADDRINUSE || suffix < 0) {
597 			v2_socket.path[0] = '\0'; /* clear socket path */
598 			return -1;
599 		}
600 		/* add a suffix to the path if the basic version fails */
601 		if (snprintf(v2_socket.path, sizeof(v2_socket.path), "%s:%d",
602 				spath, ++suffix) >= (int)sizeof(v2_socket.path)) {
603 			TMTY_LOG_LINE(ERR, "Error with socket binding, path too long");
604 			return -1;
605 		}
606 		v2_socket.sock = create_socket(v2_socket.path);
607 	}
608 	rc = pthread_create(&t_new, NULL, socket_listener, &v2_socket);
609 	if (rc != 0) {
610 		TMTY_LOG_LINE(ERR, "Error with create socket thread: %s",
611 			 strerror(rc));
612 		close(v2_socket.sock);
613 		v2_socket.sock = -1;
614 		unlink(v2_socket.path);
615 		v2_socket.path[0] = '\0';
616 		return -1;
617 	}
618 	pthread_setaffinity_np(t_new, sizeof(*thread_cpuset), thread_cpuset);
619 	set_thread_name(t_new, "dpdk-telemet-v2");
620 	pthread_detach(t_new);
621 	atexit(unlink_sockets);
622 
623 	return 0;
624 }
625 
626 #endif /* !RTE_EXEC_ENV_WINDOWS */
627 
628 int32_t
629 rte_telemetry_init(const char *runtime_dir, const char *rte_version, rte_cpuset_t *cpuset)
630 {
631 	telemetry_version = rte_version;
632 	socket_dir = runtime_dir;
633 	thread_cpuset = cpuset;
634 
635 #ifndef RTE_EXEC_ENV_WINDOWS
636 	if (telemetry_v2_init() != 0)
637 		return -1;
638 	TMTY_LOG_LINE(DEBUG, "Telemetry initialized ok");
639 	telemetry_legacy_init();
640 #endif /* RTE_EXEC_ENV_WINDOWS */
641 
642 	return 0;
643 }
644