procps/proc/diskstat.c

328 lines
8.6 KiB
C
Raw Normal View History

/*
* diskstat - Disk statistics - part of procps
*
* Copyright (C) 2003 Fabian Frederick
* Copyright (C) 2003 Albert Cahalan
* Copyright (C) 2015 Craig Small <csmall@enc.com.au>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <errno.h>
#include <proc/diskstat.h>
#include "procps-private.h"
#define DISKSTAT_LINE_LEN 1024
#define DISKSTAT_NAME_LEN 15
#define DISKSTAT_FILE "/proc/diskstats"
#define SYSBLOCK_DIR "/sys/block"
struct procps_diskstat_dev {
char name[DISKSTAT_NAME_LEN];
int is_disk;
unsigned long reads;
unsigned long reads_merged;
unsigned long read_sectors;
unsigned long read_time;
unsigned long writes;
unsigned long writes_merged;
unsigned long write_sectors;
unsigned long write_time;
unsigned long io_inprogress;
unsigned long io_time;
unsigned long io_wtime;
};
struct procps_diskstat {
int refcount;
FILE *diskstat_fp;
struct procps_diskstat_dev *devs;
int devs_alloc;
int devs_used;
};
/*
* scan_for_disks:
*
* All disks start off as partitions. This function
* scans /sys/block and changes all devices found there
* into disks. If /sys/block cannot have the directory
* read, all devices are disks
*/
static int scan_for_disks(struct procps_diskstat *info)
{
DIR *dirp;
struct dirent *dent;
int myerrno, i;
if ((dirp = opendir(SYSBLOCK_DIR)) == NULL) {
myerrno = errno;
if (myerrno != ENOENT && myerrno != EACCES)
return -myerrno;
/* Cannot open the dir or perm denied, make all devs disks */
for (i=0 ; i < info->devs_used; i++)
info->devs[i].is_disk = 1;
return 0;
}
while((dent = readdir(dirp)) != NULL) {
for (i=0; i < info->devs_used; i++)
if (strcmp(dent->d_name, info->devs[i].name) == 0) {
info->devs[i].is_disk = 1;
break;
}
}
return 0;
}
static void diskstat_clear(struct procps_diskstat *info)
{
if (info == NULL)
return;
if (info->devs != NULL && info->devs_alloc > 0) {
memset(info->devs, 0, sizeof(struct procps_diskstat_dev)*info->devs_alloc);
info->devs_used = 0;
}
}
static int diskdata_alloc(
struct procps_diskstat *info)
{
struct procps_diskstat_dev *new_disks;
int new_count;
if (info == NULL)
return -EINVAL;
if (info->devs_used < info->devs_alloc)
return 0;
/* Increment the allocated number of slabs */
new_count = info->devs_alloc * 5/4+30;
if ((new_disks = realloc(info->devs,
sizeof(struct procps_diskstat_dev)*new_count)) == NULL)
return -ENOMEM;
info->devs = new_disks;
info->devs_alloc = new_count;
return 0;
}
static int get_disk(
struct procps_diskstat *info,
struct procps_diskstat_dev **node)
{
int retval;
if (!info)
return -EINVAL;
if (info->devs_used == info->devs_alloc) {
if ((retval = diskdata_alloc(info)) < 0)
return retval;
}
*node = &(info->devs[info->devs_used++]);
return 0;
}
/*
* procps_diskstat_new:
*
* Create a new container to hold the diskstat information
*
* The initial refcount is 1, and needs to be decremented
* to release the resources of the structure.
*
* Returns: a new procps_diskstat container
*/
PROCPS_EXPORT int procps_diskstat_new (
struct procps_diskstat **info)
{
struct procps_diskstat *ds;
ds = calloc(1, sizeof(struct procps_diskstat));
if (!ds)
return -ENOMEM;
ds->refcount = 1;
ds->diskstat_fp = NULL;
*info = ds;
return 0;
}
/*
* procps_diskstat_read:
*
* @info: info structure created at procps_diskstat_new
*
* Read the data out of /proc/diskstats putting the information
* into the supplied info structure
*
* Returns: 0 on success, negative on error
*/
PROCPS_EXPORT int procps_diskstat_read (
struct procps_diskstat *info)
{
int retval, is_disk;
char buf[DISKSTAT_LINE_LEN];
char devname[DISKSTAT_NAME_LEN];
struct procps_diskstat_dev *disk;
/* clear/zero structures */
diskstat_clear(info);
if (NULL == info->diskstat_fp &&
NULL == (info->diskstat_fp = fopen(DISKSTAT_FILE, "r")))
return -errno;
if (fseek(info->diskstat_fp, 0L, SEEK_SET) == -1)
return -errno;
while (fgets(buf, DISKSTAT_LINE_LEN, info->diskstat_fp)) {
if (sscanf(buf,
"%*d %*d %" STRINGIFY(DISKSTAT_NAME_LEN) "s",
devname) < 1) {
if (errno != 0)
return -errno;
return -EINVAL;
}
if ((retval = get_disk(info, &disk)) < 0)
return retval;
if (sscanf(buf, " %*d %*d %*s %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu",
&disk->reads, &disk->reads_merged, &disk->read_sectors,
&disk->read_time,
&disk->writes, &disk->writes_merged, &disk->write_sectors,
&disk->write_time,
&disk->io_inprogress, &disk->io_time, &disk->io_wtime) != 11) {
if (errno != 0)
return -errno;
return -EINVAL;
}
disk->is_disk = 0; /* default to no until we scan */
strcpy(disk->name, devname);
}
if ((retval = scan_for_disks(info)) < 0)
return retval;
return 0;
}
/*
* procps_diskstat_dev_count:
*
* @info: structure that has been read
*
* Returns: number of devices (disk+partitions)
*/
PROCPS_EXPORT int procps_diskstat_dev_count (
const struct procps_diskstat *info)
{
if (!info)
return -EINVAL;
return info->devs_used;
}
/*
* procps_diskstat_dev_getbyname
*
* @info: diskstat data already read
* @name: name of partition
*
* Find the device index by the name
*
* Returns: devid if found
* -1 if not found
*/
PROCPS_EXPORT int procps_diskstat_dev_getbyname(
const struct procps_diskstat *info,
const char *name)
{
int i;
if (info == NULL || info->devs_used == 0)
return -1;
for (i=0; i < info->devs_used; i++)
if (strcmp(info->devs[i].name, name) == 0)
return i;
return -1;
}
PROCPS_EXPORT unsigned long procps_diskstat_dev_get(
const struct procps_diskstat *info,
const enum procps_diskstat_devitem item,
const unsigned int devid)
{
if (info == NULL)
return 0;
if (devid > info->devs_used)
return 0;
switch(item) {
case PROCPS_DISKSTAT_READS:
return info->devs[devid].reads;
case PROCPS_DISKSTAT_READS_MERGED:
return info->devs[devid].reads_merged;
case PROCPS_DISKSTAT_READ_SECTORS:
return info->devs[devid].read_sectors;
case PROCPS_DISKSTAT_READ_TIME:
return info->devs[devid].read_time;
case PROCPS_DISKSTAT_WRITES:
return info->devs[devid].writes;
case PROCPS_DISKSTAT_WRITES_MERGED:
return info->devs[devid].writes_merged;
case PROCPS_DISKSTAT_WRITE_SECTORS:
return info->devs[devid].write_sectors;
case PROCPS_DISKSTAT_WRITE_TIME:
return info->devs[devid].write_time;
case PROCPS_DISKSTAT_IO_INPROGRESS:
return info->devs[devid].io_inprogress;
case PROCPS_DISKSTAT_IO_TIME:
return info->devs[devid].io_time;
case PROCPS_DISKSTAT_IO_WTIME:
return info->devs[devid].io_wtime;
default:
return 0;
}
}
PROCPS_EXPORT char* procps_diskstat_dev_getname(
const struct procps_diskstat *info,
const unsigned int devid)
{
if (info == NULL)
return 0;
if (devid > info->devs_used)
return 0;
return info->devs[devid].name;
}
PROCPS_EXPORT int procps_diskstat_dev_isdisk(
const struct procps_diskstat *info,
const unsigned int devid)
{
if (info == NULL || devid > info->devs_used)
return -EINVAL;
return info->devs[devid].is_disk;
}