01=上期内容回顾
在往期的内容中,我们介绍了思科VPP软件初始化内存的核心函数vlib_log_init()函数的业务逻辑介绍,
Ubuntu系统运行VPP24.02系列:startup.conf配置文件解读
深入浅出思科VPP24.02系列:vlib_unix_main初始化介绍
深入浅出思科VPP24.02系列:thread0函数业务逻辑介绍
深入浅出思科VPP24.02系列:vlib_main函数业务逻辑介绍
深入浅出思科VPP24.02系列:内存初始化vlib_physmem_init逻辑介绍
深入浅出思科VPP24.02系列:日志模块vlib_log_init逻辑介绍
本期我们将继续深入浅出思科vpp24.02系列专题,介绍VPP的统计功能初始化的函数的业务逻辑介绍。
02=vlib_stats_init函数介绍
在往期的内容中,我们介绍了思科VPP软件对日志功能的初始化的函数vlib_stats_init()的业务逻辑介绍,其在vlib_main()中备调用的。
统计模块初始化流程如下所示:
clib_error_t *vlib_stats_init (vlib_main_t *vm)
{
vlib_stats_segment_t *sm = vlib_stats_get_segment ();
vlib_stats_shared_header_t *shared_header;
vlib_stats_collector_reg_t reg = {};
uword memory_size, sys_page_sz;
int mfd;
char *mem_name = "stat segment";
void *heap, *memaddr;
memory_size = sm->memory_size;
if (memory_size == 0)
memory_size = STAT_SEGMENT_DEFAULT_SIZE;
if (sm->log2_page_sz == CLIB_MEM_PAGE_SZ_UNKNOWN)
sm->log2_page_sz = CLIB_MEM_PAGE_SZ_DEFAULT;
mfd = clib_mem_vm_create_fd (sm->log2_page_sz, mem_name);
if (mfd == -1)
return clib_error_return (0, "stat segment memory fd failure: %U",
format_clib_error, clib_mem_get_last_error ());
/* Set size */
if ((ftruncate (mfd, memory_size)) == -1)
{
close (mfd);
return clib_error_return (0, "stat segment ftruncate failure");
}
memaddr = clib_mem_vm_map_shared (0, memory_size, mfd, 0, mem_name);
if (memaddr == CLIB_MEM_VM_MAP_FAILED)
return clib_error_return (0, "stat segment mmap failure");
sys_page_sz = clib_mem_get_page_size ();
heap =
clib_mem_create_heap (((u8 *) memaddr) + sys_page_sz,
memory_size - sys_page_sz, 1 /* locked */, mem_name);
sm->heap = heap;
sm->memfd = mfd;
sm->directory_vector_by_name = hash_create_string (0, sizeof (uword));
sm->shared_header = shared_header = memaddr;
shared_header->version = STAT_SEGMENT_VERSION;
shared_header->base = memaddr;
sm->stat_segment_lockp = clib_mem_alloc (sizeof (clib_spinlock_t));
sm->locking_thread_index = ~0;
sm->n_locks = 0;
clib_spinlock_init (sm->stat_segment_lockp);
/* Set up the name to counter-vector hash table */
sm->directory_vector =
vec_new_heap (typeof (sm->directory_vector[0]), STAT_COUNTERS, heap);
sm->dir_vector_first_free_elt = CLIB_U32_MAX;
shared_header->epoch = 1;
/* Scalar stats and node counters */
#define _(E, t, n, p) \
strcpy (sm->directory_vector[STAT_COUNTER_##E].name, p "/" #n); \
sm->directory_vector[STAT_COUNTER_##E].type = STAT_DIR_TYPE_##t;
foreach_stat_segment_counter_name
#undef _
/* Save the vector in the shared segment, for clients */
shared_header->directory_vector = sm->directory_vector;
vlib_stats_register_mem_heap (heap);
reg.collect_fn = vector_rate_collector_fn;
reg.private_data = vlib_stats_add_gauge ("/sys/vector_rate");
reg.entry_index =
vlib_stats_add_counter_vector ("/sys/vector_rate_per_worker");
vlib_loops_stats_counter_index =
vlib_stats_add_counter_vector ("/sys/loops_per_worker");
vlib_stats_register_collector_fn (®);
vlib_stats_validate (reg.entry_index, 0, vlib_get_n_threads ());
vlib_stats_validate (vlib_loops_stats_counter_index, 0,
vlib_get_n_threads ());
return 0;
}
函数声明:clib_error_t *vlib_stats_init (vlib_main_t *vm);
返回值:此处应该返回clib_error_t 类型的更佳。
03=函数工程意义分析
1、统计功能的主要结构体:首先,通过vlib_stats_get_segment()
获取统计信息段的引用(vlib_stats_segment_t *sm
)。这个段用于存储统计信息。
vlib_stats_segment_t *sm = vlib_stats_get_segment ();
vlib_stats_shared_header_t *shared_header;
vlib_stats_collector_reg_t reg = {};
//结构体释义
typedef struct
{
/* 这是一个指向 vlib_stats_collector_t 类型,
用于收集和管理统计数据的采集器。*/
vlib_stats_collector_t *collectors;
/* 通过名称快速查找统计信息目录项 */
uword *directory_vector_by_name;
/* 表示统计信息目录向量*/
vlib_stats_entry_t *directory_vector;
/* 表示目录向量中第一个空闲元素的位置*/
u32 dir_vector_first_free_elt;
/* 表示统计信息更新的时间间隔*/
f64 update_interval;
/* 用于统计信息段的自旋锁,用于多线程环境下的同步*/
clib_spinlock_t *stat_segment_lockp;
u32 locking_thread_index;/* 表示当前持有锁的线程索引*/
u32 n_locks; /* 表示锁的数量*/
clib_socket_t *socket; /* socket*/
u8 *socket_name; /* socket name*/
ssize_t memory_size; /* 分配的空间大小*/
clib_mem_page_sz_t log2_page_sz; /* 内存page的大小*/
u8 node_counters_enabled; /* 使能开关标识*/
void *heap; /* 指向堆内存区域的指针 */
vlib_stats_shared_header_t
*shared_header; /* pointer to shared memory segment */
int memfd; /* 内存文件描述符*/
} vlib_stats_segment_t;
2、设置内存大小和页面大小:如果统计信息段的内存大小,在未设置时,则使用默认值。同样,如果页面大小未知,则使用默认页面大小。
uword memory_size, sys_page_sz;
int mfd;
char *mem_name = "stat segment";
void *heap, *memaddr;
memory_size = sm->memory_size;
if (memory_size == 0)
memory_size = STAT_SEGMENT_DEFAULT_SIZE;
if (sm->log2_page_sz == CLIB_MEM_PAGE_SZ_UNKNOWN)
sm->log2_page_sz = CLIB_MEM_PAGE_SZ_DEFAULT;
3、创建内存文件描述符:通过clib_mem_vm_create_fd()创建一个内存文件描述符(mfd),用于映射共享内存。如果创建失败,则返回错误。
mfd = clib_mem_vm_create_fd (sm->log2_page_sz, mem_name);
if (mfd == -1)
return clib_error_return (0, "stat segment memory fd failure: %U",
format_clib_error, clib_mem_get_last_error ());
4、设置内存大小:使用ftruncate()设置文件描述符对应文件的大小为memory_size。如果失败,则关闭文件描述符并返回错误。
/* Set size */
if ((ftruncate (mfd, memory_size)) == -1)
{
close (mfd);
return clib_error_return (0, "stat segment ftruncate failure");
}
函数ftruncate 注释:
ftruncate()会将参数fd指定的文件大小改为参数length指定的大小。
参数fd为已打开的文件描述词,而且必须是以写入模式打开的文件。
如果原来的文件大小比参数length大,则超过的部分会被删去。
5、映射共享内存:通过clib_mem_vm_map_shared()将共享内存映射到进程的地址空间。如果映射失败,则返回错误。
memaddr = clib_mem_vm_map_shared (0, memory_size, mfd, 0, mem_name);
if (memaddr == CLIB_MEM_VM_MAP_FAILED)
return clib_error_return (0, "stat segment mmap failure");
6、创建内存堆:在共享内存的基础上,创建一个内存堆(heap
),用于动态内存分配。内存堆的起始地址跳过了一个系统页面大小(sys_page_sz
),以避免潜在的地址空间对齐问题。
sys_page_sz = clib_mem_get_page_size ();
heap = clib_mem_create_heap (((u8 *) memaddr) + sys_page_sz,
memory_size - sys_page_sz, 1 /* locked */, mem_name);
7、初始化统计信息段:设置统计信息段的各种参数,包括内存堆、文件描述符、锁、目录向量等。
sm->heap = heap;
sm->memfd = mfd;
sm->directory_vector_by_name = hash_create_string (0, sizeof (uword));
sm->shared_header = shared_header = memaddr;
shared_header->version = STAT_SEGMENT_VERSION;
shared_header->base = memaddr;
sm->stat_segment_lockp = clib_mem_alloc (sizeof (clib_spinlock_t));
sm->locking_thread_index = ~0;
sm->n_locks = 0;
clib_spinlock_init (sm->stat_segment_lockp);
8、初始化目录向量:创建一个字符串哈希表(directory_vector_by_name),用于根据名称快速查找统计信息。同时,初始化一个数组(directory_vector),用于存储统计信息的元数据。
/* Set up the name to counter-vector hash table */
sm->directory_vector =
vec_new_heap (typeof (sm->directory_vector[0]), STAT_COUNTERS, heap);
sm->dir_vector_first_free_elt = CLIB_U32_MAX;
shared_header->epoch = 1;
9、设置共享头部:在共享内存的起始位置设置共享头部(shared_header),包括版本号、基地址等信息。
/* Scalar stats and node counters */
#define _(E, t, n, p) \
strcpy (sm->directory_vector[STAT_COUNTER_##E].name, p "/" #n); \
sm->directory_vector[STAT_COUNTER_##E].type = STAT_DIR_TYPE_##t;
foreach_stat_segment_counter_name
#undef _
/* Save the vector in the shared segment, for clients */
shared_header->directory_vector = sm->directory_vector;
10、注册统计信息收集器:通过vlib_stats_register_collector_fn()
注册一个统计信息收集器,该收集器定期收集并更新统计信息。
vlib_stats_register_mem_heap (heap);
reg.collect_fn = vector_rate_collector_fn;
11、添加统计项:使用宏foreach_stat_segment_counter_name
遍历并添加所有预定义的统计项到目录向量中。这些统计项包括各种性能指标,如向量处理速率、循环次数等。
reg.collect_fn = vector_rate_collector_fn;
reg.private_data = vlib_stats_add_gauge ("/sys/vector_rate");
reg.entry_index =
vlib_stats_add_counter_vector ("/sys/vector_rate_per_worker");
vlib_loops_stats_counter_index =
vlib_stats_add_counter_vector ("/sys/loops_per_worker");
vlib_stats_register_collector_fn (®);
vlib_stats_validate (reg.entry_index, 0, vlib_get_n_threads ());
vlib_stats_validate (vlib_loops_stats_counter_index, 0, vlib_get_n_threads ());
12、注册内存堆:通过vlib_stats_register_mem_heap()
注册内存堆,以便统计信息收集系统可以监控内存使用情况。
13返回值:如果所有步骤都成功完成,函数返回0表示成功。
建议修改为返回clib_error_t 里面的参数
04=文章总结介绍
本文章介绍了VPP统计信息收集系统的核心初始化过程,其逻辑函数vlib_stats_init()函数,它设置了所需的内存结构、初始化了相关的数据结构和变量,并注册了统计信息收集器,为后续的章节我们会继续介绍相关功能的使用场景。
好了,本次介绍就到此为止。小伙伴们,你们学会了吗?
作者简介
作者:通信行业搬砖工
前深圳某大型通信设备厂商从业人员
前H3Com骨干网核心交换路由器人员
前某全球500强通信公司通信砖家