https://www.opensips.org/Documentation/Development-Manual
目录
10. 管理接口 API
管理接口(MI)是一个抽象层,用于控制及监视OpenSIPS。MI接口支持多种实际的后台(比如:FIFO、数据报、JSON-RPC、 XML-RPC)。由于它的模块化,逻辑数据结构与传输数据结构的清晰分离,开发者只需要定义数据的析构函数,然后由脚本编写者选择使用哪种传输方式来控制OpenSIPS。
MI大量使用JSON来与传输层对接:
- 接口向用户定义的MI函数提供JSON (mi_item_t)格式的输入,携带要求的字段
- MI函数返回JSON,然后由传输层将它转换为适当的表现形式
在此基础上,我们将聚焦于核心MI函数,特别是log_level MI函数。请注意模块也可以导出MI函数(通常他们也是这样做的),关于这方面的信息,请模块开发MI函数相关的主题。
用于导出MI函数的数据结构,通常在 mi/mi.h 里:
typedef struct mi_export_ {
/* the name of the function users will invoke it from their transport of choice */
char *name;
/* short description of the usage of this function */
char *help;
/* flags for this function. The current options are :
- MI_ASYNC_RPL_FLAG - the function has an asynchronous behaviour (e.g: MI functions that send out SIP messages and do not wait for their reply)
- MI_NO_INPUT_FLAG - the function does not receive any parameters
*/
unsigned int flags;
/* an initialization function to be called by OpenSIPS (one time) */
mi_child_init_f *init_f;
/* the possible combinations of arguments which may be supplied to this function, along with their handlers */
mi_recipe_t recipes[MAX_MI_RECIPES];
} mi_export_t;
/* Example of core MI exported function */
static mi_export_t mi_core_cmds[] = {
...
{ "log_level", "gets/sets the per process or global log level in OpenSIPS",
0, 0, {
{w_log_level, {0}},
{w_log_level_1, {"level", 0}},
{w_log_level_2, {"level", "pid", 0}},
{EMPTY_MI_RECIPE}
}
},
...
};
/* For exporting the populated array of MI functions
Parameters :
mod_name : the name of the module exporting these functions
mis : the array of exported MI functions
Returns :
0 on success, negative in case of error
*/
int register_mi_mod( char *mod_name, mi_export_t *mis);
/* Example of usage */
if (register_mi_mod( "core", mi_core_cmds) < 0) {
LM_ERR("unable to register core MI cmds\n");
return -1;
}
用于实现MI功能的数据结构,通常可以在里mi/mi.h 和mi/item.h 找:
/*
Parameters:
params : the JSON tree which includes the input arguments
async_hdl : if the function has async capabilities, this is its async handler
Returns:
An mi_response_t JSON structure with the requested data or execution result
*/
typedef mi_response_t *(mi_cmd_f)(const mi_params_t *params,
struct mi_handler *async_hdl);
/* Both mi_item_t and mi_response_t point to the same cJSON struct, see lib/cJSON.h */
typedef struct cJSON
{
/* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
struct cJSON *next;
struct cJSON *prev;
/* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
struct cJSON *child;
/* The type of the item, as above. */
int type;
/* The item's string, if type==cJSON_String and type == cJSON_Raw */
char *valuestring;
/* The item's number, if type==cJSON_Number */
int valueint;
/* The item's number, if type==cJSON_Number */
double valuedouble;
/* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
char *string;
} cJSON;
为了输入JSON,mi/item.h 提供了以下函数:
/* Use for creating a new output reply tree
Parameters :
code : success code for this tree ( >=200<300 for success, anything else for errors )
reason : string reasons representation for the code
reason_len : length of the reason parameter
Returns :
A new mi_root tree, or NULL in case of error. Note that this function will allocate the node in PKG and it typically has to be returned - the freeing will be done in the MI core, after the output tree is written by the transport module */
struct mi_root *init_mi_tree(unsigned int code, char *reason, int reason_len);
/* Adding a new child node to our tree - typically first called to mi_root->node.kids
Parameters :
parent : the parent node for our newly added node
flags : Current options are :
MI_DUP_NAME : the name of this node needs to be duplicated in PKG
MI_DUP_VALUE : the value of the current node needs to be duplicated in PKG
name : the name of the current node
name_len : length of the node's name
value : the value of the current node
value_len : length of the node's value
*/
struct mi_node *add_mi_node_child(struct mi_node *parent, int flags,
char *name, int name_len, char *value, int value_len);
/* Adding a new sibling node to one of our nodes
Parameters :
brother : the brother node for our newly added node
flags : Current options are :
MI_DUP_NAME : the name of this node needs to be duplicated in PKG
MI_DUP_VALUE : the value of the current node needs to be duplicated in PKG
name : the name of the current node
name_len : length of the node's name
value : the value of the current node
value_len : length of the node's value
*/
struct mi_node *add_mi_node_sibling(struct mi_node *brother, int flags,
char *name, int name_len, char *value, int value_len);
/* Adding a new attribute to one of our nodes
node : the node we will be adding the key-value attribute to
flags : Current options are :
MI_DUP_NAME : the name of this attribute needs to be duplicated in PKG
MI_DUP_VALUE : the value of the current attribute needs to be duplicated in PKG
name : the name of the current attribute
name_len : length of the node's attribute name
value : the value of the current value
value_len : length of the node's attribute value
*/
struct mi_attr *add_mi_attr(struct mi_node *node, int flags,
char *name, int name_len, char *value, int value_len)
接下来,我们继续实现debug MI函数。不带参数调用时,返回OpenSIPS的调试级别。如果调用时传入一个整型参数,那么它将设置当前的的调试级别为参数所提供的值:
struct mi_root *mi_debug(struct mi_root *cmd, void *param)
{
struct mi_root *rpl_tree;
struct mi_node *node;
char *p;
int len;
int new_debug;
/* check the kids member of our root node -
if the input root node has kids, our command was called with parameters */
node = cmd->node.kids;
if (node!=NULL) {
/* take the node's value and convert it to int, to make sure the parameter is valid */
if (str2sint( &node->value, &new_debug) < 0)
/* if failed to convert to int, still return a RPL tree with an >=400 code and reason */
return init_mi_tree( 400, MI_SSTR(MI_BAD_PARM));
} else
new_debug = *debug;
/* all is good so far, initialize a new output ROOT tree which has a 200 OK code & reason */
rpl_tree = init_mi_tree( 200, MI_SSTR(MI_OK));
if (rpl_tree==0)
return 0;
p = sint2str((long)new_debug, &len);
/* add a new node to our output tree, which the current debug level */
node = add_mi_node_child( &rpl_tree->node, MI_DUP_VALUE,
MI_SSTR("DEBUG"),p, len);
if (node==0) {
free_mi_tree(rpl_tree);
return 0;
}
/* if all was successful, overwrite the actual debug level, and return our tree */
*debug = new_debug;
return rpl_tree;
}
如果需要MI相关的进一步信息,或opensipsctl 工具执行MI命令的实例说明,请参考以下页面:https://www.opensips.org/Documentation/Interface-MI-2-2
11. 统计API
OpenSIPS 提供了一套统计API,可以在核心或模块中使用。统计本质上就是一些计数器,可以由OpenSIPS 内部递增或递减,并允许外部世界获取这些信息(通过MI接口),以了解OpenSIPS 的负载、健康状态等等。
使用OpenSIPS 统计API而不是常规的计数器,优势在于:
- 可以从MI接口轻松获取
- 从支持的架构,统计API不使用显式的锁(其一致性由嵌入的汇编代码保证),因此性能更高
扩展统计API最重要的数据结构声明在 statistics.h 中:
typedef struct stat_export_ {
char* name; /* null terminated statistic name */
unsigned short flags; /* flags */
stat_var** stat_pointer; /* pointer to the variable's mem location *
* NOTE - it's in shm mem */
} stat_export_t;
比如说,OpenSIPS导出的核心统计信息定义在以下数组中:
stat_var* rcv_reqs;
stat_var* rcv_rpls;
stat_var* fwd_reqs;
stat_var* fwd_rpls;
stat_var* drp_reqs;
stat_var* drp_rpls;
stat_var* err_reqs;
stat_var* err_rpls;
stat_var* bad_URIs;
stat_var* unsupported_methods;
stat_var* bad_msg_hdr;
stat_export_t core_stats[] = {
{"rcv_requests" , 0, &rcv_reqs },
{"rcv_replies" , 0, &rcv_rpls },
{"fwd_requests" , 0, &fwd_reqs },
{"fwd_replies" , 0, &fwd_rpls },
{"drop_requests" , 0, &drp_reqs },
{"drop_replies" , 0, &drp_rpls },
{"err_requests" , 0, &err_reqs },
{"err_replies" , 0, &err_rpls },
{"bad_URIs_rcvd", 0, &bad_URIs },
{"unsupported_methods", 0, &unsupported_methods },
{"bad_msg_hdr", 0, &bad_msg_hdr },
{"timestamp", STAT_IS_FUNC, (stat_var**)get_ticks },
{0,0,0}
};
从上面代码结构中我们可以看出,统计既可以是一个简单的计数器(比如说rcv_requests),也可以是一个函数。当开发人员在最终输出之前,需要对原始数据进行处理时,函数就派上用场了。
在定义你希望导出的统计数组之后,应当用以下函数导出,以供其它人访问:
/*
Parameters :
module - a string describing the module the current statistics belong to. Will be used when fetching the statistics via MI
stats - the statistics to be registered
Returns :
0 in case of success, negative in case of error
*/
int register_module_stats(char *module, stat_export_t *stats;
注意:register_module_stats 不仅导出统计信息,而且在SHM里分配内存,因为它们对所有OpenSIPS 进程都是可见的
重要注意事项:上述所有统计相关的函数,必须在attendant 进程的上下文中调用,而且是在fork之前。
运行时,开发者可以通过以下函数操作统计:
/*
Parameters :
var : the statistics to be updated
n : the value ( if positive -> stat will be increment. negative -> stat will be decremented )
*/
void update_stat(stat_var* var, int n);
/*
Parameters :
var : the statistics to be reseted
*/
void reset_stat(stat_var* var);
/*
Parameters :
var : the statistics to be fetched
Returns :
statistic value
*/
unsigned long get_stat_val(stat_var* var)
所有统计相关的代码应当由宏#ifdef STATISTICS 保护,因为统计并不是OpenSIPS 核心的必要组成部分,它可以在menuconfig 中禁用。
要获取mynewmod 模块导出的mynewstat 统计信息,可以这样使用opensipsctl :opensipsctl fifo get_statistics mynewmod mynewstat;要获取mynewmod 模块导出的的所有统计信息,要以通过:opensipsctl fifo get_statistics mynewmod。