2007-11-14 11:58:36 +00:00
|
|
|
/*
|
|
|
|
* Linux block and network stats.
|
|
|
|
*
|
2008-04-25 14:53:05 +00:00
|
|
|
* Copyright (C) 2007, 2008 Red Hat, Inc.
|
2007-11-14 11:58:36 +00:00
|
|
|
*
|
|
|
|
* See COPYING.LIB for the License of this software
|
|
|
|
*
|
|
|
|
* Richard W.M. Jones <rjones@redhat.com>
|
|
|
|
*/
|
|
|
|
|
2008-01-29 18:15:54 +00:00
|
|
|
#include <config.h>
|
2007-11-14 11:58:36 +00:00
|
|
|
|
|
|
|
/* This file only applies on Linux. */
|
|
|
|
#ifdef __linux__
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <unistd.h>
|
2008-05-09 13:50:14 +00:00
|
|
|
#include <c-ctype.h>
|
2007-11-14 11:58:36 +00:00
|
|
|
|
2007-11-15 10:56:24 +00:00
|
|
|
#ifdef WITH_XEN
|
2007-11-14 11:58:36 +00:00
|
|
|
#include <xs.h>
|
2007-11-15 10:56:24 +00:00
|
|
|
#endif
|
2007-11-14 11:58:36 +00:00
|
|
|
|
2008-02-08 09:15:16 +00:00
|
|
|
#include "util.h"
|
2007-11-14 11:58:36 +00:00
|
|
|
#include "xen_unified.h"
|
|
|
|
#include "stats_linux.h"
|
|
|
|
|
|
|
|
/**
|
|
|
|
* statsErrorFunc:
|
|
|
|
* @conn: the connection
|
|
|
|
* @error: the error number
|
|
|
|
* @func: the function failing
|
|
|
|
* @info: extra information string
|
|
|
|
* @value: extra information number
|
|
|
|
*
|
|
|
|
* Handle a stats error.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
statsErrorFunc (virConnectPtr conn,
|
|
|
|
virErrorNumber error, const char *func, const char *info,
|
|
|
|
int value)
|
|
|
|
{
|
|
|
|
char fullinfo[1000];
|
|
|
|
const char *errmsg;
|
|
|
|
|
|
|
|
errmsg = __virErrorMsg(error, info);
|
|
|
|
if (func != NULL) {
|
|
|
|
snprintf(fullinfo, sizeof (fullinfo) - 1, "%s: %s", func, info);
|
|
|
|
fullinfo[sizeof (fullinfo) - 1] = 0;
|
|
|
|
info = fullinfo;
|
|
|
|
}
|
2007-11-20 10:58:21 +00:00
|
|
|
__virRaiseError(conn, NULL, NULL, VIR_FROM_STATS_LINUX, error,
|
|
|
|
VIR_ERR_ERROR,
|
2007-11-14 11:58:36 +00:00
|
|
|
errmsg, info, NULL, value, 0, errmsg, info,
|
|
|
|
value);
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef WITH_XEN
|
|
|
|
/*-------------------- Xen: block stats --------------------*/
|
|
|
|
|
|
|
|
#include <linux/major.h>
|
|
|
|
|
|
|
|
/* This is normally defined in <linux/major.h> but previously we
|
|
|
|
* hard-coded it. So if it's not defined, hard-code again.
|
|
|
|
*/
|
|
|
|
#ifndef XENVBD_MAJOR
|
|
|
|
#define XENVBD_MAJOR 202
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static int
|
|
|
|
xstrtoint64 (char const *s, int base, int64_t *result)
|
|
|
|
{
|
|
|
|
long long int lli;
|
|
|
|
char *p;
|
|
|
|
|
|
|
|
errno = 0;
|
|
|
|
lli = strtoll (s, &p, base);
|
|
|
|
if (errno || !(*p == 0 || *p == '\n') || p == s || (int64_t) lli != lli)
|
|
|
|
return -1;
|
|
|
|
*result = lli;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int64_t
|
|
|
|
read_stat (const char *path)
|
|
|
|
{
|
|
|
|
char str[64];
|
|
|
|
int64_t r;
|
|
|
|
int i;
|
|
|
|
FILE *fp;
|
|
|
|
|
|
|
|
fp = fopen (path, "r");
|
|
|
|
if (!fp)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
/* read, but don't bail out before closing */
|
|
|
|
i = fread (str, 1, sizeof str - 1, fp);
|
|
|
|
|
|
|
|
if (fclose (fp) != 0 /* disk error */
|
|
|
|
|| i < 1) /* ensure we read at least one byte */
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
str[i] = '\0'; /* make sure the string is nul-terminated */
|
|
|
|
if (xstrtoint64 (str, 10, &r) == -1)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int64_t
|
|
|
|
read_bd_stat (int device, int domid, const char *str)
|
|
|
|
{
|
|
|
|
char path[PATH_MAX];
|
|
|
|
int64_t r;
|
|
|
|
|
|
|
|
snprintf (path, sizeof path,
|
|
|
|
"/sys/devices/xen-backend/vbd-%d-%d/statistics/%s",
|
|
|
|
domid, device, str);
|
|
|
|
r = read_stat (path);
|
|
|
|
if (r >= 0) return r;
|
|
|
|
|
|
|
|
snprintf (path, sizeof path,
|
|
|
|
"/sys/devices/xen-backend/tap-%d-%d/statistics/%s",
|
|
|
|
domid, device, str);
|
|
|
|
r = read_stat (path);
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* In Xenstore, /local/domain/0/backend/vbd/<domid>/<device>/state,
|
|
|
|
* if available, must be XenbusStateConnected (= 4), otherwise there
|
|
|
|
* is no connected device.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
check_bd_connected (xenUnifiedPrivatePtr priv, int device, int domid)
|
|
|
|
{
|
|
|
|
char s[256], *rs;
|
|
|
|
int r;
|
|
|
|
unsigned len = 0;
|
|
|
|
|
|
|
|
/* This code assumes we're connected if we can't get to
|
|
|
|
* xenstore, etc.
|
|
|
|
*/
|
|
|
|
if (!priv->xshandle) return 1;
|
|
|
|
snprintf (s, sizeof s, "/local/domain/0/backend/vbd/%d/%d/state",
|
|
|
|
domid, device);
|
|
|
|
s[sizeof s - 1] = '\0';
|
|
|
|
|
|
|
|
rs = xs_read (priv->xshandle, 0, s, &len);
|
|
|
|
if (!rs) return 1;
|
|
|
|
if (len == 0) {
|
|
|
|
/* Hmmm ... we can get to xenstore but it returns an empty
|
|
|
|
* string instead of an error. Assume it's not connected
|
|
|
|
* in this case.
|
|
|
|
*/
|
|
|
|
free (rs);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
r = STREQ (rs, "4");
|
|
|
|
free (rs);
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
read_bd_stats (virConnectPtr conn, xenUnifiedPrivatePtr priv,
|
|
|
|
int device, int domid, struct _virDomainBlockStats *stats)
|
|
|
|
{
|
|
|
|
stats->rd_req = read_bd_stat (device, domid, "rd_req");
|
|
|
|
stats->rd_bytes = read_bd_stat (device, domid, "rd_sect");
|
|
|
|
stats->wr_req = read_bd_stat (device, domid, "wr_req");
|
|
|
|
stats->wr_bytes = read_bd_stat (device, domid, "wr_sect");
|
|
|
|
stats->errs = read_bd_stat (device, domid, "oo_req");
|
|
|
|
|
|
|
|
/* None of the files were found - it's likely that this version
|
|
|
|
* of Xen is an old one which just doesn't support stats collection.
|
|
|
|
*/
|
|
|
|
if (stats->rd_req == -1 && stats->rd_bytes == -1 &&
|
|
|
|
stats->wr_req == -1 && stats->wr_bytes == -1 &&
|
|
|
|
stats->errs == -1) {
|
2008-01-29 18:36:00 +00:00
|
|
|
statsErrorFunc (conn, VIR_ERR_INTERNAL_ERROR, __FUNCTION__,
|
2007-11-14 11:58:36 +00:00
|
|
|
"Failed to read any block statistics", domid);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If stats are all zero then either there really isn't any block
|
|
|
|
* device activity, or there is no connected front end device
|
|
|
|
* in which case there are no stats.
|
|
|
|
*/
|
|
|
|
if (stats->rd_req == 0 && stats->rd_bytes == 0 &&
|
|
|
|
stats->wr_req == 0 && stats->wr_bytes == 0 &&
|
|
|
|
stats->errs == 0 &&
|
|
|
|
!check_bd_connected (priv, device, domid)) {
|
2008-01-29 18:36:00 +00:00
|
|
|
statsErrorFunc (conn, VIR_ERR_INTERNAL_ERROR, __FUNCTION__,
|
2007-11-14 11:58:36 +00:00
|
|
|
"Frontend block device not connected", domid);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 'Bytes' was really sectors when we read it. Scale up by
|
|
|
|
* an assumed sector size.
|
|
|
|
*/
|
|
|
|
if (stats->rd_bytes > 0) {
|
2007-11-17 11:53:44 +00:00
|
|
|
if (stats->rd_bytes >= ((unsigned long long)1)<<(63-9)) {
|
2008-01-29 18:36:00 +00:00
|
|
|
statsErrorFunc (conn, VIR_ERR_INTERNAL_ERROR, __FUNCTION__,
|
2007-11-14 11:58:36 +00:00
|
|
|
"stats->rd_bytes would overflow 64 bit counter",
|
|
|
|
domid);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
stats->rd_bytes *= 512;
|
|
|
|
}
|
|
|
|
if (stats->wr_bytes > 0) {
|
2007-11-17 11:53:44 +00:00
|
|
|
if (stats->wr_bytes >= ((unsigned long long)1)<<(63-9)) {
|
2008-01-29 18:36:00 +00:00
|
|
|
statsErrorFunc (conn, VIR_ERR_INTERNAL_ERROR, __FUNCTION__,
|
2007-11-14 11:58:36 +00:00
|
|
|
"stats->wr_bytes would overflow 64 bit counter",
|
|
|
|
domid);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
stats->wr_bytes *= 512;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
2008-01-29 18:36:00 +00:00
|
|
|
xenLinuxDomainDeviceID(virConnectPtr conn, int domid, const char *path)
|
2007-11-14 11:58:36 +00:00
|
|
|
{
|
2008-01-29 18:36:00 +00:00
|
|
|
int disk, part = 0;
|
|
|
|
|
|
|
|
/* Strip leading path if any */
|
|
|
|
if (strlen(path) > 5 &&
|
|
|
|
STREQLEN(path, "/dev/", 5))
|
|
|
|
path += 5;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Possible block device majors & partition ranges. This
|
|
|
|
* matches the ranges supported in Xend xen/util/blkif.py
|
|
|
|
*
|
|
|
|
* hdNM: N=a-t, M=1-63, major={IDE0_MAJOR -> IDE9_MAJOR}
|
|
|
|
* sdNM: N=a-z,aa-iv, M=1-15, major={SCSI_DISK0_MAJOR -> SCSI_DISK15_MAJOR}
|
|
|
|
* xvdNM: N=a-p, M=1-15, major=XENVBD_MAJOR
|
|
|
|
*
|
|
|
|
* NB, the SCSI major isn't technically correct, as XenD only knows
|
|
|
|
* about major=8. We cope with all SCSI majors in anticipation of
|
|
|
|
* XenD perhaps being fixed one day....
|
|
|
|
*
|
|
|
|
* The path for statistics will be
|
2007-11-14 11:58:36 +00:00
|
|
|
*
|
2008-01-29 18:36:00 +00:00
|
|
|
* /sys/devices/xen-backend/(vbd|tap)-{domid}-{devid}/statistics/{...}
|
2007-11-14 11:58:36 +00:00
|
|
|
*/
|
2008-01-29 18:36:00 +00:00
|
|
|
|
|
|
|
if (strlen (path) >= 4 &&
|
2007-11-14 11:58:36 +00:00
|
|
|
STREQLEN (path, "xvd", 3)) {
|
2008-01-29 18:36:00 +00:00
|
|
|
/* Xen paravirt device handling */
|
|
|
|
disk = (path[3] - 'a');
|
|
|
|
if (disk < 0 || disk > 15) {
|
|
|
|
statsErrorFunc (conn, VIR_ERR_INVALID_ARG, __FUNCTION__,
|
|
|
|
"invalid path, device names must be in range xvda - xvdp",
|
|
|
|
domid);
|
2007-11-14 11:58:36 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2008-01-29 18:36:00 +00:00
|
|
|
if (path[4] != '\0') {
|
2008-05-09 13:50:14 +00:00
|
|
|
if (!c_isdigit(path[4]) || path[4] == '0' ||
|
2008-02-08 09:15:16 +00:00
|
|
|
virStrToLong_i(path+4, NULL, 10, &part) < 0 ||
|
2008-01-29 18:36:00 +00:00
|
|
|
part < 1 || part > 15) {
|
|
|
|
statsErrorFunc (conn, VIR_ERR_INVALID_ARG, __FUNCTION__,
|
|
|
|
"invalid path, partition numbers for xvdN must be in range 1 - 15",
|
|
|
|
domid);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return (XENVBD_MAJOR * 256) + (disk * 16) + part;
|
|
|
|
} else if (strlen (path) >= 3 &&
|
|
|
|
STREQLEN (path, "sd", 2)) {
|
|
|
|
/* SCSI device handling */
|
|
|
|
int majors[] = { SCSI_DISK0_MAJOR, SCSI_DISK1_MAJOR, SCSI_DISK2_MAJOR,
|
|
|
|
SCSI_DISK3_MAJOR, SCSI_DISK4_MAJOR, SCSI_DISK5_MAJOR,
|
|
|
|
SCSI_DISK6_MAJOR, SCSI_DISK7_MAJOR, SCSI_DISK8_MAJOR,
|
|
|
|
SCSI_DISK9_MAJOR, SCSI_DISK10_MAJOR, SCSI_DISK11_MAJOR,
|
|
|
|
SCSI_DISK12_MAJOR, SCSI_DISK13_MAJOR, SCSI_DISK14_MAJOR,
|
|
|
|
SCSI_DISK15_MAJOR };
|
|
|
|
|
|
|
|
disk = (path[2] - 'a');
|
|
|
|
if (disk < 0 || disk > 25) {
|
|
|
|
statsErrorFunc (conn, VIR_ERR_INVALID_ARG, __FUNCTION__,
|
|
|
|
"invalid path, device names must be in range sda - sdiv",
|
|
|
|
domid);
|
2007-11-14 11:58:36 +00:00
|
|
|
return -1;
|
|
|
|
}
|
2008-01-29 18:36:00 +00:00
|
|
|
if (path[3] != '\0') {
|
|
|
|
const char *p = NULL;
|
|
|
|
if (path[3] >= 'a' && path[3] <= 'z') {
|
|
|
|
disk = ((disk + 1) * 26) + (path[3] - 'a');
|
|
|
|
if (disk < 0 || disk > 255) {
|
|
|
|
statsErrorFunc (conn, VIR_ERR_INVALID_ARG, __FUNCTION__,
|
|
|
|
"invalid path, device names must be in range sda - sdiv",
|
|
|
|
domid);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (path[4] != '\0')
|
|
|
|
p = path + 4;
|
|
|
|
} else {
|
|
|
|
p = path + 3;
|
|
|
|
}
|
2008-05-09 13:50:14 +00:00
|
|
|
if (p && (!c_isdigit(*p) || *p == '0' ||
|
2008-02-08 09:15:16 +00:00
|
|
|
virStrToLong_i(p, NULL, 10, &part) < 0 ||
|
2008-01-29 18:36:00 +00:00
|
|
|
part < 1 || part > 15)) {
|
|
|
|
statsErrorFunc (conn, VIR_ERR_INVALID_ARG, __FUNCTION__,
|
|
|
|
"invalid path, partition numbers for sdN must be in range 1 - 15",
|
|
|
|
domid);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
2007-11-14 11:58:36 +00:00
|
|
|
|
2008-01-29 18:36:00 +00:00
|
|
|
return (majors[disk/16] * 256) + ((disk%16) * 16) + part;
|
|
|
|
} else if (strlen (path) >= 3 &&
|
|
|
|
STREQLEN (path, "hd", 2)) {
|
|
|
|
/* IDE device handling */
|
|
|
|
int majors[] = { IDE0_MAJOR, IDE1_MAJOR, IDE2_MAJOR, IDE3_MAJOR,
|
|
|
|
IDE4_MAJOR, IDE5_MAJOR, IDE6_MAJOR, IDE7_MAJOR,
|
|
|
|
IDE8_MAJOR, IDE9_MAJOR };
|
|
|
|
disk = (path[2] - 'a');
|
|
|
|
if (disk < 0 || disk > 19) {
|
|
|
|
statsErrorFunc (conn, VIR_ERR_INVALID_ARG, __FUNCTION__,
|
|
|
|
"invalid path, device names must be in range hda - hdt",
|
|
|
|
domid);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (path[3] != '\0') {
|
2008-05-09 13:50:14 +00:00
|
|
|
if (!c_isdigit(path[3]) || path[3] == '0' ||
|
2008-02-08 09:15:16 +00:00
|
|
|
virStrToLong_i(path+3, NULL, 10, &part) < 0 ||
|
2008-01-29 18:36:00 +00:00
|
|
|
part < 1 || part > 63) {
|
|
|
|
statsErrorFunc (conn, VIR_ERR_INVALID_ARG, __FUNCTION__,
|
|
|
|
"invalid path, partition numbers for hdN must be in range 1 - 63",
|
|
|
|
domid);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return (majors[disk/2] * 256) + ((disk % 2) * 63) + part;
|
2007-11-14 11:58:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Otherwise, unsupported device name. */
|
2008-01-29 18:36:00 +00:00
|
|
|
statsErrorFunc (conn, VIR_ERR_INVALID_ARG, __FUNCTION__,
|
|
|
|
"unsupported path, use xvdN, hdN, or sdN", domid);
|
2007-11-14 11:58:36 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2008-01-29 18:36:00 +00:00
|
|
|
int
|
|
|
|
xenLinuxDomainBlockStats (xenUnifiedPrivatePtr priv,
|
|
|
|
virDomainPtr dom,
|
|
|
|
const char *path,
|
|
|
|
struct _virDomainBlockStats *stats)
|
|
|
|
{
|
|
|
|
int device = xenLinuxDomainDeviceID(dom->conn, dom->id, path);
|
|
|
|
|
|
|
|
if (device < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return read_bd_stats (dom->conn, priv, device, dom->id, stats);
|
|
|
|
}
|
|
|
|
|
2007-11-14 11:58:36 +00:00
|
|
|
#endif /* WITH_XEN */
|
|
|
|
|
|
|
|
/*-------------------- interface stats --------------------*/
|
|
|
|
/* Just reads the named interface, so not Xen or QEMU-specific.
|
|
|
|
* NB. Caller must check that libvirt user is trying to query
|
|
|
|
* the interface of a domain they own. We do no such checking.
|
|
|
|
*/
|
|
|
|
|
|
|
|
int
|
|
|
|
linuxDomainInterfaceStats (virConnectPtr conn, const char *path,
|
|
|
|
struct _virDomainInterfaceStats *stats)
|
|
|
|
{
|
|
|
|
int path_len;
|
|
|
|
FILE *fp;
|
2007-11-15 17:45:44 +00:00
|
|
|
char line[256], *colon;
|
2007-11-14 11:58:36 +00:00
|
|
|
|
|
|
|
fp = fopen ("/proc/net/dev", "r");
|
|
|
|
if (!fp) {
|
2008-01-29 18:36:00 +00:00
|
|
|
statsErrorFunc (conn, VIR_ERR_INTERNAL_ERROR, __FUNCTION__,
|
2007-11-14 11:58:36 +00:00
|
|
|
"/proc/net/dev", errno);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
path_len = strlen (path);
|
|
|
|
|
|
|
|
while (fgets (line, sizeof line, fp)) {
|
|
|
|
long long dummy;
|
|
|
|
long long rx_bytes;
|
|
|
|
long long rx_packets;
|
|
|
|
long long rx_errs;
|
|
|
|
long long rx_drop;
|
|
|
|
long long tx_bytes;
|
|
|
|
long long tx_packets;
|
|
|
|
long long tx_errs;
|
|
|
|
long long tx_drop;
|
|
|
|
|
2007-11-15 17:45:44 +00:00
|
|
|
/* The line looks like:
|
|
|
|
* " eth0:..."
|
|
|
|
* Split it at the colon.
|
|
|
|
*/
|
|
|
|
colon = strchr (line, ':');
|
|
|
|
if (!colon) continue;
|
|
|
|
*colon = '\0';
|
|
|
|
if (colon-path_len >= line &&
|
|
|
|
STREQ (colon-path_len, path)) {
|
2007-11-14 11:58:36 +00:00
|
|
|
/* IMPORTANT NOTE!
|
|
|
|
* /proc/net/dev vif<domid>.nn sees the network from the point
|
|
|
|
* of view of dom0 / hypervisor. So bytes TRANSMITTED by dom0
|
|
|
|
* are bytes RECEIVED by the domain. That's why the TX/RX fields
|
|
|
|
* appear to be swapped here.
|
|
|
|
*/
|
2007-11-15 17:45:44 +00:00
|
|
|
if (sscanf (colon+1,
|
2007-11-14 11:58:36 +00:00
|
|
|
"%lld %lld %lld %lld %lld %lld %lld %lld %lld %lld %lld %lld %lld %lld %lld %lld",
|
|
|
|
&tx_bytes, &tx_packets, &tx_errs, &tx_drop,
|
|
|
|
&dummy, &dummy, &dummy, &dummy,
|
|
|
|
&rx_bytes, &rx_packets, &rx_errs, &rx_drop,
|
|
|
|
&dummy, &dummy, &dummy, &dummy) != 16)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
stats->rx_bytes = rx_bytes;
|
|
|
|
stats->rx_packets = rx_packets;
|
|
|
|
stats->rx_errs = rx_errs;
|
|
|
|
stats->rx_drop = rx_drop;
|
|
|
|
stats->tx_bytes = tx_bytes;
|
|
|
|
stats->tx_packets = tx_packets;
|
|
|
|
stats->tx_errs = tx_errs;
|
|
|
|
stats->tx_drop = tx_drop;
|
|
|
|
fclose (fp);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fclose (fp);
|
|
|
|
|
2008-01-29 18:36:00 +00:00
|
|
|
statsErrorFunc (conn, VIR_ERR_INTERNAL_ERROR, __FUNCTION__,
|
2007-11-14 11:58:36 +00:00
|
|
|
"/proc/net/dev: Interface not found", 0);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif /* __linux__ */
|