You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kamailio/lib/kcore/statistics.c

444 lines
12 KiB

/*
* $Id$
*
* Copyright (C) 2006 Voice Sistem SRL
*
* This file is part of Kamailio, a free SIP server.
*
* Kamailio is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Kamailio is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*
* History:
* ---------
* 2006-01-16 first version (bogdan)
* 2006-11-28 added get_stat_var_from_num_code() (Jeffrey Magder -
* SOMA Networks)
* 2010-08-08 removed all the parts emulated by kstats_wrapper.[ch] (andrei)
*/
/*!
* \file
* \brief Statistics support
*/
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "../../ut.h"
#include "../../dprint.h"
#include "../../socket_info.h"
#include "statistics.h"
#ifdef STATISTICS
/*! \brief
* Returns the statistic associated with 'numerical_code' and 'out_codes'.
* Specifically:
*
* - if out_codes is nonzero, then the stat_var for the number of messages
* _sent out_ with the 'numerical_code' will be returned if it exists.
* - otherwise, the stat_var for the number of messages _received_ with the
* 'numerical_code' will be returned, if the stat exists.
*/
stat_var *get_stat_var_from_num_code(unsigned int numerical_code, int out_codes)
{
static char msg_code[INT2STR_MAX_LEN+4];
str stat_name;
stat_name.s = int2bstr( (unsigned long)numerical_code, msg_code,
&stat_name.len);
stat_name.s[stat_name.len++] = '_';
if (out_codes) {
stat_name.s[stat_name.len++] = 'o';
stat_name.s[stat_name.len++] = 'u';
stat_name.s[stat_name.len++] = 't';
} else {
stat_name.s[stat_name.len++] = 'i';
stat_name.s[stat_name.len++] = 'n';
}
return get_stat(&stat_name);
}
#endif /*STATISTICS*/
#define MAX_PROC_BUFFER 256
/*!
* This function will retrieve a list of all ip addresses and ports that Kamailio
* is listening on, with respect to the transport protocol specified with
* 'protocol'.
*
* The first parameter, ipList, is a pointer to a pointer. It will be assigned a
* new block of memory holding the IP Addresses and ports being listened to with
* respect to 'protocol'. The array maps a 2D array into a 1 dimensional space,
* and is layed out as follows:
*
* The first NUM_IP_OCTETS indices will be the IP address, and the next index
* the port. So if NUM_IP_OCTETS is equal to 4 and there are two IP addresses
* found, then:
*
* - ipList[0] will be the first octet of the first ip address
* - ipList[3] will be the last octet of the first ip address.
* - iplist[4] will be the port of the first ip address
* -
* - iplist[5] will be the first octet of the first ip address,
* - and so on.
*
* The function will return the number of sockets which were found. This can be
* used to index into ipList.
*
* \note This function assigns a block of memory equal to:
*
* returnedValue * (NUM_IP_OCTETS + 1) * sizeof(int);
*
* Therefore it is CRUCIAL that you free ipList when you are done with its
* contents, to avoid a nasty memory leak.
*/
int get_socket_list_from_proto(int **ipList, int protocol) {
struct socket_info *si;
struct socket_info** list;
int num_ip_octets = 4;
int numberOfSockets = 0;
int currentRow = 0;
/* I hate to use #ifdefs, but this is necessary because of the way
* get_sock_info_list() is defined. */
#ifndef USE_TCP
if (protocol == PROTO_TCP)
{
return 0;
}
#endif
#ifndef USE_TLS
if (protocol == PROTO_TLS)
{
return 0;
}
#endif
if (protocol == PROTO_WS || protocol == PROTO_WSS)
return 0;
/* Retrieve the list of sockets with respect to the given protocol. */
list=get_sock_info_list(protocol);
/* Find out how many sockets are in the list. We need to know this so
* we can malloc an array to assign to ipList. */
for(si=list?*list:0; si; si=si->next){
/* We only support IPV4 at this point. */
if (si->address.af == AF_INET) {
numberOfSockets++;
}
}
/* There are no open sockets with respect to the given protocol. */
if (numberOfSockets == 0)
{
return 0;
}
*ipList = pkg_malloc(numberOfSockets * (num_ip_octets + 1) * sizeof(int));
/* We couldn't allocate memory for the IP List. So all we can do is
* fail. */
if (*ipList == NULL) {
LM_ERR("no more pkg memory");
return 0;
}
/* We need to search the list again. So find the front of the list. */
list=get_sock_info_list(protocol);
/* Extract out the IP Addresses and ports. */
for(si=list?*list:0; si; si=si->next){
/* We currently only support IPV4. */
if (si->address.af != AF_INET) {
continue;
}
(*ipList)[currentRow*(num_ip_octets + 1) ] =
si->address.u.addr[0];
(*ipList)[currentRow*(num_ip_octets + 1)+1] =
si->address.u.addr[1];
(*ipList)[currentRow*(num_ip_octets + 1)+2] =
si->address.u.addr[2];
(*ipList)[currentRow*(num_ip_octets + 1)+3] =
si->address.u.addr[3];
(*ipList)[currentRow*(num_ip_octets + 1)+4] =
si->port_no;
currentRow++;
}
return numberOfSockets;
}
/*!
* Takes a 'line' (from the proc file system), parses out the ipAddress,
* address, and stores the number of bytes waiting in 'rx_queue'
*
* Returns 1 on success, and 0 on a failed parse.
*
* Note: The format of ipAddress is as defined in the comments of
* get_socket_list_from_proto() in this file.
*
*/
static int parse_proc_net_line(char *line, int *ipAddress, int *rx_queue)
{
int i;
int ipOctetExtractionMask = 0xFF;
char *currColonLocation;
char *nextNonNumericalChar;
char *currentLocationInLine = line;
int parsedInteger[4];
/* Example line from /proc/net/tcp or /proc/net/udp:
*
* sl local_address rem_address st tx_queue rx_queue
* 21: 5A0A0B0A:CAC7 1C016E0A:0016 01 00000000:00000000
*
* Algorithm:
*
* 1) Find the location of the first ':'
* 2) Parse out the IP Address into an integer
* 3) Find the location of the second ':'
* 4) Parse out the port number.
* 5) Find the location of the fourth ':'
* 6) Parse out the rx_queue.
*/
for (i = 0; i < 4; i++) {
currColonLocation = strchr(currentLocationInLine, ':');
/* We didn't find all the needed ':', so fail. */
if (currColonLocation == NULL) {
return 0;
}
/* Parse out the integer, keeping the location of the next
* non-numerical character. */
parsedInteger[i] =
(int) strtol(++currColonLocation, &nextNonNumericalChar,
16);
/* strtol()'s specifications specify that the second parameter
* is set to the first parameter when a number couldn't be
* parsed out. This means the parse was unsuccesful. */
if (nextNonNumericalChar == currColonLocation) {
return 0;
}
/* Reset the currentLocationInLine to the last non-numerical
* character, so that next iteration of this loop, we can find
* the next colon location. */
currentLocationInLine = nextNonNumericalChar;
}
/* Extract out the segments of the IP Address. They are stored in
* reverse network byte order. */
for (i = 0; i < NUM_IP_OCTETS; i++) {
ipAddress[i] =
parsedInteger[0] & (ipOctetExtractionMask << i*8);
ipAddress[i] >>= i*8;
}
ipAddress[NUM_IP_OCTETS] = parsedInteger[1];
*rx_queue = parsedInteger[3];
return 1;
}
/*!
* Returns 1 if ipOne was found in ipArray, and 0 otherwise.
*
* The format of ipOne and ipArray are described in the comments of
* get_socket_list_from_proto() in this file.
*
* */
static int match_ip_and_port(int *ipOne, int *ipArray, int sizeOf_ipArray)
{
int curIPAddrIdx;
int curOctetIdx;
int ipArrayIndex;
/* Loop over every IP Address */
for (curIPAddrIdx = 0; curIPAddrIdx < sizeOf_ipArray; curIPAddrIdx++) {
/* Check for octets that don't match. If one is found, skip the
* rest. */
for (curOctetIdx = 0; curOctetIdx < NUM_IP_OCTETS + 1; curOctetIdx++) {
/* We've encoded a 2D array as a 1D array. So find out
* our position in the 1D array. */
ipArrayIndex =
curIPAddrIdx * (NUM_IP_OCTETS + 1) + curOctetIdx;
if (ipOne[curOctetIdx] != ipArray[ipArrayIndex]) {
break;
}
}
/* If the index from the inner loop is equal to NUM_IP_OCTETS
* + 1, then that means that every octet (and the port with the
* + 1) matched. */
if (curOctetIdx == NUM_IP_OCTETS + 1) {
return 1;
}
}
return 0;
}
/*!
* Returns the number of bytes waiting to be consumed on the network interfaces
* assigned the IP Addresses specified in interfaceList. The check will be
* limited to the TCP or UDP transport exclusively. Specifically:
*
* - If forTCP is non-zero, the check involves only the TCP transport.
* - if forTCP is zero, the check involves only the UDP transport.
*
* Note: This only works on linux systems supporting the /proc/net/[tcp|udp]
* interface. On other systems, zero will always be returned.
*/
static int get_used_waiting_queue(
int forTCP, int *interfaceList, int listSize)
{
FILE *fp;
char *fileToOpen;
char lineBuffer[MAX_PROC_BUFFER];
int ipAddress[NUM_IP_OCTETS+1];
int rx_queue;
int waitingQueueSize = 0;
/* Set up the file we want to open. */
if (forTCP) {
fileToOpen = "/proc/net/tcp";
} else {
fileToOpen = "/proc/net/udp";
}
fp = fopen(fileToOpen, "r");
if (fp == NULL) {
LM_ERR("Could not open %s. kamailioMsgQueueDepth and its related"
" alarms will not be available.\n", fileToOpen);
return 0;
}
/* Read in every line of the file, parse out the ip address, port, and
* rx_queue, and compare to our list of interfaces we are listening on.
* Add up rx_queue for those lines which match our known interfaces. */
while (fgets(lineBuffer, MAX_PROC_BUFFER, fp)!=NULL) {
/* Parse out the ip address, port, and rx_queue. */
if(parse_proc_net_line(lineBuffer, ipAddress, &rx_queue)) {
/* Only add rx_queue if the line just parsed corresponds
* to an interface we are listening on. We do this
* check because it is possible that this system has
* other network interfaces that Kamailio has been told
* to ignore. */
if (match_ip_and_port(ipAddress, interfaceList, listSize)) {
waitingQueueSize += rx_queue;
}
}
}
fclose(fp);
return waitingQueueSize;
}
/*!
* Returns the sum of the number of bytes waiting to be consumed on all network
* interfaces and transports that Kamailio is listening on.
*
* Note: This currently only works on systems supporting the /proc/net/[tcp|udp]
* interface. On other systems, zero will always be returned. To change
* this in the future, add an equivalent for get_used_waiting_queue().
*/
int get_total_bytes_waiting(void)
{
int bytesWaiting = 0;
int *UDPList = NULL;
int *TCPList = NULL;
int *TLSList = NULL;
int numUDPSockets = 0;
int numTCPSockets = 0;
int numTLSSockets = 0;
/* Extract out the IP address address for UDP, TCP, and TLS, keeping
* track of the number of IP addresses from each transport */
numUDPSockets = get_socket_list_from_proto(&UDPList, PROTO_UDP);
numTCPSockets = get_socket_list_from_proto(&TCPList, PROTO_TCP);
numTLSSockets = get_socket_list_from_proto(&TLSList, PROTO_TLS);
/* Deliberately not looking at PROTO_WS or PROTO_WSS here as they are
just upgraded TCP/TLS connections */
/* Find out the number of bytes waiting on our interface list over all
* UDP and TCP transports. */
bytesWaiting += get_used_waiting_queue(0, UDPList, numUDPSockets);
bytesWaiting += get_used_waiting_queue(1, TCPList, numTCPSockets);
bytesWaiting += get_used_waiting_queue(1, TLSList, numTLSSockets);
/* get_socket_list_from_proto() allocated a chunk of memory, so we need
* to free it. */
if (numUDPSockets > 0)
{
pkg_free(UDPList);
}
if (numTCPSockets > 0)
{
pkg_free(TCPList);
}
if (numTLSSockets > 0)
{
pkg_free(TLSList);
}
return bytesWaiting;
}