procps/proc/readstat.c
Craig Small bf97da3059 Replace %Lu with standard %llu
Multiple scanf()s use the GNU-permitted %Lu. This is not supported in
other libraries and isn't to the POSIX specification. The L modifier
is only used for floats in POSIX.

Replacing %Lu with %llu is the same for GNU libc (scanf(3) says as much)
but means other libraries will work fine.

From master commit da715e3

References:
 http://pubs.opengroup.org/onlinepubs/009695399/functions/fscanf.html
2016-04-14 21:21:27 +10:00

564 lines
16 KiB
C

/*
* libprocps - Library to read proc filesystem
*
* 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 <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <proc/readstat.h>
#include "procps-private.h"
#define STAT_FILE "/proc/stat"
struct stat_data {
struct procps_jiffs cpu;
unsigned int intr;
unsigned int ctxt;
unsigned int btime;
unsigned int procs;
unsigned int procs_blocked;
unsigned int procs_running;
};
struct procps_jiffs_private {
struct procps_jiffs_hist cpu;
// future additions go here, please
};
struct procps_stat {
int refcount;
int stat_fd;
struct stat_data data;
int jiff_hists_alloc;
int jiff_hists_inuse;
struct procps_jiffs_private cpu_summary;
struct procps_jiffs_private *jiff_hists;
};
/*
* procps_stat_new:
*
* Create a new container to hold the stat information
*
* The initial refcount is 1, and needs to be decremented
* to release the resources of the structure.
*
* Returns: a new stat info container
*/
PROCPS_EXPORT int procps_stat_new (
struct procps_stat **info)
{
struct procps_stat *v;
v = calloc(1, sizeof(struct procps_stat));
if (!v)
return -ENOMEM;
v->refcount = 1;
v->stat_fd = -1;
/* v->jiff_hists_alloc = 0; unnecessary with calloc, */
/* v->jiff_hists_inuse = 0; but serves as a reminder */
*info = v;
return 0;
}
/*
* procps_stat_read:
*
* Read the data out of /proc/stat putting the information
* into the supplied info structure.
*
* If CPU stats only needed, set cpu_only to non-zero
*/
PROCPS_EXPORT int procps_stat_read (
struct procps_stat *info,
const int cpu_only)
{
char buf[8192];
char *head, *tail;
int size;
if (info == NULL)
return -EINVAL;
memset(&(info->data), 0, sizeof(struct stat_data));
/* read in the data */
if (-1 == info->stat_fd && (info->stat_fd = open(STAT_FILE, O_RDONLY)) == -1) {
return -errno;
}
if (lseek(info->stat_fd, 0L, SEEK_SET) == -1) {
return -errno;
}
for (;;) {
if ((size = read(info->stat_fd, buf, sizeof(buf)-1)) < 0) {
if (errno == EINTR || errno == EAGAIN)
continue;
return -errno;
}
break;
}
if (size == 0)
return 0;
buf[size] = '\0';
/* Scan the file */
head = buf;
do {
tail = strchr(head, ' ');
if (!tail)
break;
*tail = '\0';
if (0 == strcmp(head, "cpu")) {
if (sscanf(tail+1, "%llu %llu %llu %llu %llu %llu %llu %llu %llu %llu",
&(info->data.cpu.user),
&(info->data.cpu.nice),
&(info->data.cpu.system),
&(info->data.cpu.idle),
&(info->data.cpu.iowait),
&(info->data.cpu.irq),
&(info->data.cpu.sirq),
&(info->data.cpu.stolen),
&(info->data.cpu.guest),
&(info->data.cpu.nice)
) != 10)
return -1;
if (cpu_only)
return 0; // we got what we need
} else if (0 == strcmp(head, "intr")) {
info->data.intr = strtoul(tail+1, &tail, 10);
} else if (0 == strcmp(head, "ctxt")) {
info->data.ctxt = strtoul(tail+1, &tail, 10);
} else if (0 == strcmp(head, "btime")) {
info->data.btime = strtoul(tail+1, &tail, 10);
} else if (0 == strcmp(head, "processes")) {
info->data.procs = strtoul(tail+1, &tail, 10);
} else if (0 == strcmp(head, "procs_blocked")) {
info->data.procs_blocked = strtoul(tail+1, &tail, 10);
} else if (0 == strcmp(head, "procs_running")) {
info->data.procs_running = strtoul(tail+1, &tail, 10);
}
if (tail[0] != '\n')
tail = strchr(tail+1, '\n');
if (!tail)
break;
head = tail + 1;
} while (tail);
if (info->data.procs)
info->data.procs--; // exclude this process
return 0;
}
PROCPS_EXPORT int procps_stat_ref (
struct procps_stat *info)
{
if (info == NULL)
return -EINVAL;
info->refcount++;
return info->refcount;
}
PROCPS_EXPORT int procps_stat_unref (
struct procps_stat **info)
{
if (info == NULL || *info == NULL)
return -EINVAL;
(*info)->refcount--;
if ((*info)->refcount == 0) {
if ((*info)->jiff_hists != NULL)
free((*info)->jiff_hists);
free(*info);
*info = NULL;
return 0;
}
return (*info)->refcount;
}
PROCPS_EXPORT jiff procps_stat_cpu_get (
struct procps_stat *info,
enum procps_cpu_item item)
{
switch (item) {
case PROCPS_CPU_USER:
return info->data.cpu.user;
case PROCPS_CPU_NICE:
return info->data.cpu.nice;
case PROCPS_CPU_SYSTEM:
return info->data.cpu.system;
case PROCPS_CPU_IDLE:
return info->data.cpu.idle;
case PROCPS_CPU_IOWAIT:
return info->data.cpu.iowait;
case PROCPS_CPU_IRQ:
return info->data.cpu.irq;
case PROCPS_CPU_SIRQ:
return info->data.cpu.sirq;
case PROCPS_CPU_STOLEN:
return info->data.cpu.stolen;
case PROCPS_CPU_GUEST:
return info->data.cpu.guest;
case PROCPS_CPU_GNICE:
return info->data.cpu.gnice;
default:
return 0;
}
}
PROCPS_EXPORT int procps_stat_cpu_getstack (
struct procps_stat *info,
struct stat_result *these)
{
if (these == NULL)
return -EINVAL;
for (;;) {
switch (these->item) {
case PROCPS_CPU_USER:
these->result.jiff = info->data.cpu.user;
break;
case PROCPS_CPU_NICE:
these->result.jiff = info->data.cpu.nice;
break;
case PROCPS_CPU_SYSTEM:
these->result.jiff = info->data.cpu.system;
break;
case PROCPS_CPU_IDLE:
these->result.jiff = info->data.cpu.idle;
break;
case PROCPS_CPU_IOWAIT:
these->result.jiff = info->data.cpu.iowait;
break;
case PROCPS_CPU_IRQ:
these->result.jiff = info->data.cpu.irq;
break;
case PROCPS_CPU_SIRQ:
these->result.jiff = info->data.cpu.sirq;
break;
case PROCPS_CPU_STOLEN:
these->result.jiff = info->data.cpu.stolen;
break;
case PROCPS_CPU_GUEST:
these->result.jiff = info->data.cpu.guest;
break;
case PROCPS_CPU_GNICE:
these->result.jiff = info->data.cpu.gnice;
break;
case PROCPS_CPU_noop:
// don't disturb potential user data in the result struct
break;
case PROCPS_CPU_stack_end:
return 0;
default:
return -EINVAL;
}
++these;
}
}
PROCPS_EXPORT unsigned int procps_stat_sys_get (
struct procps_stat *info,
enum procps_stat_item item)
{
switch (item) {
case PROCPS_STAT_INTR:
return info->data.intr;
case PROCPS_STAT_CTXT:
return info->data.ctxt;
case PROCPS_STAT_BTIME:
return info->data.btime;
case PROCPS_STAT_PROCS:
return info->data.procs;
case PROCPS_STAT_PROCS_BLK:
return info->data.procs_blocked;
case PROCPS_STAT_PROCS_RUN:
return info->data.procs_running;
default:
return 0;
}
}
PROCPS_EXPORT int procps_stat_sys_getstack (
struct procps_stat *info,
struct stat_result *these)
{
if (these == NULL)
return -EINVAL;
for (;;) {
switch (these->item) {
case PROCPS_STAT_INTR:
these->result.u_int = info->data.intr;
break;
case PROCPS_STAT_CTXT:
these->result.u_int = info->data.ctxt;
break;
case PROCPS_STAT_BTIME:
these->result.u_int = info->data.btime;
break;
case PROCPS_STAT_PROCS:
these->result.u_int = info->data.procs;
break;
case PROCPS_STAT_PROCS_BLK:
these->result.u_int = info->data.procs_blocked;
break;
case PROCPS_STAT_PROCS_RUN:
these->result.u_int = info->data.procs_running;
break;
case PROCPS_STAT_noop:
// don't disturb potential user data in the result struct
break;
case PROCPS_STAT_stack_end:
return 0;
default:
return -EINVAL;
}
++these;
}
}
/*
* procps_stat_read_jiffs:
*
* Read the cpu data out of /proc/stat putting the information
* into the dynamically acquired 'jiff_hists' hanging off the
* info structure. Along the way we gather historical stats and
* and embed the cpu summary line in the info structure as well
* ( since we had to read that first /proc/stat line anyway ).
*
* This is all private information but can be copied to caller
* supplied structures upon request.
*/
PROCPS_EXPORT int procps_stat_read_jiffs (
struct procps_stat *info)
{
#define ALLOCincr 32
struct procps_jiffs_private *sum_ptr, *cpu_ptr;
char buf[8192], *bp;
int i, rc, size;
if (info == NULL)
return -EINVAL;
if (!info->jiff_hists_alloc) {
info->jiff_hists = calloc(ALLOCincr, sizeof(struct procps_jiffs_private));
if (!(info->jiff_hists))
return -ENOMEM;
info->jiff_hists_alloc = ALLOCincr;
info->jiff_hists_inuse = 0;
}
if (-1 == info->stat_fd && (info->stat_fd = open(STAT_FILE, O_RDONLY)) == -1)
return -errno;
if (lseek(info->stat_fd, 0L, SEEK_SET) == -1)
return -errno;
for (;;) {
if ((size = read(info->stat_fd, buf, sizeof(buf)-1)) < 0) {
if (errno == EINTR || errno == EAGAIN)
continue;
return -errno;
}
break;
}
if (size == 0)
return 0;
buf[size] = '\0';
bp = buf;
sum_ptr = &info->cpu_summary;
// remember from last time around
memcpy(&sum_ptr->cpu.old, &sum_ptr->cpu.new, sizeof(struct procps_jiffs));
// then value the summary line
if (8 > sscanf(bp, "cpu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu"
, &sum_ptr->cpu.new.user, &sum_ptr->cpu.new.nice, &sum_ptr->cpu.new.system
, &sum_ptr->cpu.new.idle, &sum_ptr->cpu.new.iowait, &sum_ptr->cpu.new.irq
, &sum_ptr->cpu.new.sirq, &sum_ptr->cpu.new.stolen
, &sum_ptr->cpu.new.guest, &sum_ptr->cpu.new.gnice))
return -1;
sum_ptr->cpu.id = -1; // mark as summary
i = 0;
reap_em_again:
cpu_ptr = info->jiff_hists + i; // adapt to relocated if reap_em_again
do {
bp = 1 + strchr(bp, '\n');
// remember from last time around
memcpy(&cpu_ptr->cpu.old, &cpu_ptr->cpu.new, sizeof(struct procps_jiffs));
if (8 > (rc = sscanf(bp, "cpu%d %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu"
, &cpu_ptr->cpu.id
, &cpu_ptr->cpu.new.user, &cpu_ptr->cpu.new.nice, &cpu_ptr->cpu.new.system
, &cpu_ptr->cpu.new.idle, &cpu_ptr->cpu.new.iowait, &cpu_ptr->cpu.new.irq
, &cpu_ptr->cpu.new.sirq, &cpu_ptr->cpu.new.stolen
, &cpu_ptr->cpu.new.guest, &cpu_ptr->cpu.new.gnice))) {
memmove(cpu_ptr, sum_ptr, sizeof(struct procps_jiffs_hist));
break; // we must tolerate cpus taken offline
}
++i;
++cpu_ptr;
} while (i < info->jiff_hists_alloc);
if (i == info->jiff_hists_alloc && rc >= 8) {
info->jiff_hists_alloc += ALLOCincr;
info->jiff_hists = realloc(info->jiff_hists, info->jiff_hists_alloc * sizeof(struct procps_jiffs_private));
if (!(info->jiff_hists))
return -ENOMEM;
goto reap_em_again;
}
info->jiff_hists_inuse = i;
return i;
#undef ALLOCincr
}
/*
* procps_stat_jiffs_get:
*
* Return the designated cpu data in the caller supplied structure.
* A negative 'which' denotes the cpu_summary, not a real cpu.
*
* This function deals only with the 'current' jiffs counts.
*/
PROCPS_EXPORT int procps_stat_jiffs_get (
struct procps_stat *info,
struct procps_jiffs *dest,
int which)
{
struct procps_jiffs_private *p;
int i;
if (info == NULL || dest == NULL)
return -EINVAL;
if (which < 0) {
// note, we're just copying the 'new' portion of our procps_jiffs_private
memcpy(dest, &info->cpu_summary, sizeof(struct procps_jiffs));
return 0;
}
p = info->jiff_hists;
for (i = 0; i < info->jiff_hists_inuse; i++) {
if (p->cpu.id == which) {
// note, we're just copying the 'new' portion of our procps_jiffs_private
memcpy(dest, p, sizeof(struct procps_jiffs));
return 0;
}
++p;
}
return -1;
}
/*
* procps_stat_jiffs_fill:
*
* Refresh available cpu data, then return all cpu data in the caller
* supplied structures, up to the lesser of maxdests or total available.
*
* We tolerate a maxdests greater than the total available, and
* the caller had better tolerate fewer returned than requested.
*
* This function deals only with the 'current' jiffs counts.
*/
PROCPS_EXPORT int procps_stat_jiffs_fill (
struct procps_stat *info,
struct procps_jiffs *dests,
int maxdests)
{
int i, rc;
if (info == NULL || dests == NULL)
return -EINVAL;
if ((rc = procps_stat_read_jiffs(info)) < 0)
return rc;
if (!info->jiff_hists_inuse)
return -1;
for (i = 0; i < info->jiff_hists_inuse && i < maxdests; i++) {
// note, we're just copying the 'new' portion of our procps_jiffs_private
memcpy(dests + i, info->jiff_hists + i, sizeof(struct procps_jiffs));
}
return i;
}
/*
* procps_stat_jiffs_hist_get:
*
* Return the designated cpu data in the caller supplied structure.
* A negative 'which' denotes the cpu_summary, not a real cpu.
*
* This function provides both 'new' and 'old' jiffs counts.
*/
PROCPS_EXPORT int procps_stat_jiffs_hist_get (
struct procps_stat *info,
struct procps_jiffs_hist *dest,
int which)
{
struct procps_jiffs_private *p;
int i;
if (info == NULL || dest == NULL)
return -EINVAL;
if (which < 0) {
memcpy(dest, &info->cpu_summary, sizeof(struct procps_jiffs_hist));
return 0;
}
p = info->jiff_hists;
for (i = 0; i < info->jiff_hists_inuse; i++) {
if (p->cpu.id == which) {
memcpy(dest, p, sizeof(struct procps_jiffs_hist));
return 0;
}
++p;
}
return -1;
}
/*
* procps_stat_jiffs_hist_fill:
*
* Refresh available cpu data, then return all cpu data in the caller
* supplied structures, up to the lesser of maxdests or total available.
*
* We tolerate a maxdests greater than the total available, and
* the caller had better tolerate fewer returned than requested.
*
* This function provides both 'new' and 'old' jiffs counts.
*/
PROCPS_EXPORT int procps_stat_jiffs_hist_fill (
struct procps_stat *info,
struct procps_jiffs_hist *dests,
int maxdests)
{
int i, rc;
if (info == NULL || dests == NULL)
return -EINVAL;
if ((rc = procps_stat_read_jiffs(info)) < 0)
return rc;
if (!info->jiff_hists_inuse)
return -1;
for (i = 0; i < info->jiff_hists_inuse && i < maxdests; i++) {
memcpy(dests + i, info->jiff_hists + i, sizeof(struct procps_jiffs_hist));
}
return i;
}