diff options
author | Igor Pashev <pashev.igor@gmail.com> | 2014-10-26 12:33:50 +0400 |
---|---|---|
committer | Igor Pashev <pashev.igor@gmail.com> | 2014-10-26 12:33:50 +0400 |
commit | 47e6e7c84f008a53061e661f31ae96629bc694ef (patch) | |
tree | 648a07f3b5b9d67ce19b0fd72e8caa1175c98f1a /src/pmdas/linux/interrupts.c | |
download | pcp-debian/3.9.10.tar.gz |
Debian 3.9.10debian/3.9.10debian
Diffstat (limited to 'src/pmdas/linux/interrupts.c')
-rw-r--r-- | src/pmdas/linux/interrupts.c | 394 |
1 files changed, 394 insertions, 0 deletions
diff --git a/src/pmdas/linux/interrupts.c b/src/pmdas/linux/interrupts.c new file mode 100644 index 0000000..47377e8 --- /dev/null +++ b/src/pmdas/linux/interrupts.c @@ -0,0 +1,394 @@ +/* + * Copyright (c) 2012-2014 Red Hat. + * Copyright (c) 2011 Aconex. All Rights Reserved. + * + * This program 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. + * + * This program 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. + */ + +#include "pmapi.h" +#include "impl.h" +#include "pmda.h" +#include "indom.h" +#include "filesys.h" +#include "clusters.h" +#include "interrupts.h" +#include <sys/stat.h> +#ifdef HAVE_STRINGS_H +#include <strings.h> +#endif +#include <ctype.h> + +typedef struct { + unsigned int id; /* becomes PMID item number */ + char *name; /* becomes PMNS sub-component */ + char *text; /* one-line metric help text */ + unsigned long long *values; /* per-CPU values for this counter */ +} interrupt_t; + +static unsigned int cpu_count; +static int *online_cpumap; /* maps input columns to CPU IDs */ +static unsigned int lines_count; +static interrupt_t *interrupt_lines; +static unsigned int other_count; +static interrupt_t *interrupt_other; + +static __pmnsTree *interrupt_tree; +unsigned int irq_err_count; + +static void +update_lines_pmns(int domain, unsigned int item, unsigned int id) +{ + char entry[128]; + pmID pmid = pmid_build(domain, CLUSTER_INTERRUPT_LINES, item); + + snprintf(entry, sizeof(entry), "kernel.percpu.interrupts.line%d", id); + __pmAddPMNSNode(interrupt_tree, pmid, entry); +} + +static void +update_other_pmns(int domain, unsigned int item, const char *name) +{ + char entry[128]; + pmID pmid = pmid_build(domain, CLUSTER_INTERRUPT_OTHER, item); + + snprintf(entry, sizeof(entry), "kernel.percpu.interrupts.%s", name); + __pmAddPMNSNode(interrupt_tree, pmid, entry); +} + +static int +map_online_cpus(char *buffer) +{ + unsigned long i = 0, cpuid; + char *s, *end; + + for (s = buffer; *s != '\0'; s++) { + if (!isdigit((int)*s)) + continue; + cpuid = strtoul(s, &end, 10); + if (end == s) + break; + online_cpumap[i++] = cpuid; + s = end; + } + return i; +} + +static int +column_to_cpuid(int column) +{ + int i; + + if (online_cpumap[column] == column) + return column; + for (i = 0; i < cpu_count; i++) + if (online_cpumap[i] == column) + return i; + return 0; +} + +static char * +extract_values(char *buffer, unsigned long long *values, int ncolumns) +{ + unsigned long i, value, cpuid; + char *s = buffer, *end = NULL; + + for (i = 0; i < ncolumns; i++) { + value = strtoul(s, &end, 10); + if (*end != ' ') + return NULL; + s = end; + cpuid = column_to_cpuid(i); + values[cpuid] = value; + } + return end; +} + +/* Create oneline help text - remove duplicates and end-of-line marker */ +static char * +oneline_reformat(char *buf) +{ + char *result, *start, *end; + + /* position end marker, and skip over whitespace at the start */ + for (start = end = buf; *end != '\n' && *end != '\0'; end++) + if (isspace((int)*start) && isspace((int)*end)) + start = end+1; + *end = '\0'; + + /* squash duplicate whitespace and remove trailing whitespace */ + for (result = start; *result != '\0'; result++) { + if (isspace((int)result[0]) && (isspace((int)result[1]) || result[1] == '\0')) { + memmove(&result[0], &result[1], end - &result[0]); + result--; + } + } + return start; +} + +static void +initialise_interrupt(interrupt_t *ip, unsigned int id, char *s, char *end) +{ + ip->id = id; + ip->name = strdup(s); + if (end) + ip->text = strdup(oneline_reformat(end)); +} + +static int +extend_interrupts(interrupt_t **interp, unsigned int *countp) +{ + int cnt = cpu_count * sizeof(unsigned long long); + unsigned long long *values = malloc(cnt); + interrupt_t *interrupt = *interp; + int count = *countp + 1; + + if (!values) + return 0; + + interrupt = realloc(interrupt, count * sizeof(interrupt_t)); + if (!interrupt) { + free(values); + return 0; + } + interrupt[count-1].values = values; + *interp = interrupt; + *countp = count; + return 1; +} + +static char * +extract_interrupt_name(char *buffer, char **suffix) +{ + char *s = buffer, *end; + + while (isspace((int)*s)) /* find start of name */ + s++; + for (end = s; *end && isalnum((int)*end); end++) { } + *end = '\0'; /* mark end of name */ + *suffix = end + 1; /* mark values start */ + return s; +} + +static int +extract_interrupt_lines(char *buffer, int ncolumns, int nlines) +{ + unsigned long id; + char *name, *end, *values; + int resize = (nlines >= lines_count); + + name = extract_interrupt_name(buffer, &values); + id = strtoul(name, &end, 10); + if (*end != '\0') + return 0; + if (resize && !extend_interrupts(&interrupt_lines, &lines_count)) + return 0; + end = extract_values(values, interrupt_lines[nlines].values, ncolumns); + if (resize) + initialise_interrupt(&interrupt_lines[nlines], id, name, end); + return 1; +} + +static int +extract_interrupt_errors(char *buffer) +{ + return (sscanf(buffer, " ERR: %u", &irq_err_count) == 1 || + sscanf(buffer, "Err: %u", &irq_err_count) == 1 || + sscanf(buffer, "BAD: %u", &irq_err_count) == 1); +} + +static int +extract_interrupt_misses(char *buffer) +{ + unsigned int irq_mis_count; /* not exported */ + return sscanf(buffer, " MIS: %u", &irq_mis_count) == 1; +} + +static int +extract_interrupt_other(char *buffer, int ncolumns, int nlines) +{ + char *name, *end, *values; + int resize = (nlines >= other_count); + + name = extract_interrupt_name(buffer, &values); + if (resize && !extend_interrupts(&interrupt_other, &other_count)) + return 0; + end = extract_values(values, interrupt_other[nlines].values, ncolumns); + if (resize) + initialise_interrupt(&interrupt_other[nlines], nlines, name, end); + return 1; +} + +int +refresh_interrupt_values(void) +{ + FILE *fp; + char buf[4096]; + int i, ncolumns; + + if (cpu_count == 0) { + long ncpus = sysconf(_SC_NPROCESSORS_CONF); + online_cpumap = malloc(ncpus * sizeof(int)); + if (!online_cpumap) + return -oserror(); + cpu_count = ncpus; + } + memset(online_cpumap, 0, cpu_count * sizeof(int)); + + if ((fp = linux_statsfile("/proc/interrupts", buf, sizeof(buf))) == NULL) + return -oserror(); + + /* first parse header, which maps online CPU number to column number */ + if (fgets(buf, sizeof(buf), fp)) { + ncolumns = map_online_cpus(buf); + } else { + fclose(fp); + return -EINVAL; /* unrecognised file format */ + } + + /* next we parse each interrupt line row (starting with a digit) */ + i = 0; + while (fgets(buf, sizeof(buf), fp)) + if (!extract_interrupt_lines(buf, ncolumns, i++)) + break; + + /* parse other per-CPU interrupt counter rows (starts non-digit) */ + i = 0; + while (fgets(buf, sizeof(buf), fp) != NULL) { + if (extract_interrupt_errors(buf)) + continue; + if (extract_interrupt_misses(buf)) + continue; + if (!extract_interrupt_other(buf, ncolumns, i++)) + break; + } + + fclose(fp); + return 0; +} + +static int +refresh_interrupts(pmdaExt *pmda, __pmnsTree **tree) +{ + int i, sts, dom = pmda->e_domain; + + if (interrupt_tree) { + *tree = interrupt_tree; + } else if ((sts = __pmNewPMNS(&interrupt_tree)) < 0) { + __pmNotifyErr(LOG_ERR, "%s: failed to create interrupt names: %s\n", + pmProgname, pmErrStr(sts)); + *tree = NULL; + } else if ((sts = refresh_interrupt_values()) < 0) { + __pmNotifyErr(LOG_ERR, "%s: failed to update interrupt values: %s\n", + pmProgname, pmErrStr(sts)); + *tree = NULL; + } else { + for (i = 0; i < lines_count; i++) + update_lines_pmns(dom, i, interrupt_lines[i].id); + for (i = 0; i < other_count; i++) + update_other_pmns(dom, i, interrupt_other[i].name); + *tree = interrupt_tree; + return 1; + } + return 0; +} + +int +interrupts_fetch(int cluster, int item, unsigned int inst, pmAtomValue *atom) +{ + if (inst >= cpu_count) + return PM_ERR_INST; + + switch (cluster) { + case CLUSTER_INTERRUPT_LINES: + if (item > lines_count) + return PM_ERR_PMID; + atom->ull = interrupt_lines[item].values[inst]; + return 1; + case CLUSTER_INTERRUPT_OTHER: + if (item > other_count) + return PM_ERR_PMID; + atom->ull = interrupt_other[item].values[inst]; + return 1; + } + return PM_ERR_PMID; +} + +/* + * Create a new metric table entry based on an existing one. + */ +static void +refresh_metrictable(pmdaMetric *source, pmdaMetric *dest, int id) +{ + int domain = pmid_domain(source->m_desc.pmid); + int cluster = pmid_cluster(source->m_desc.pmid); + + memcpy(dest, source, sizeof(pmdaMetric)); + dest->m_desc.pmid = pmid_build(domain, cluster, id); + + if (pmDebug & DBG_TRACE_LIBPMDA) + fprintf(stderr, "interrupts refresh_metrictable: (%p -> %p) " + "metric ID dup: %d.%d.%d -> %d.%d.%d\n", + source, dest, domain, cluster, + pmid_item(source->m_desc.pmid), domain, cluster, id); +} + +/* + * Needs to answer the question: how much extra space needs to be + * allocated in the metric table for (dynamic) interrupt metrics"? + * Return value is the number of additional entries/trees needed. + */ +static void +size_metrictable(int *total, int *trees) +{ + *total = 2; /* lines and other */ + *trees = lines_count > other_count ? lines_count : other_count; + + if (pmDebug & DBG_TRACE_LIBPMDA) + fprintf(stderr, "interrupts size_metrictable: %d total x %d trees\n", + *total, *trees); +} + +static int +interrupts_text(pmdaExt *pmda, pmID pmid, int type, char **buf) +{ + int item = pmid_item(pmid); + int cluster = pmid_cluster(pmid); + + switch (cluster) { + case CLUSTER_INTERRUPT_LINES: + if (item > lines_count) + return PM_ERR_PMID; + if (interrupt_lines[item].text == NULL) + return PM_ERR_TEXT; + *buf = interrupt_lines[item].text; + return 0; + case CLUSTER_INTERRUPT_OTHER: + if (item > other_count) + return PM_ERR_PMID; + if (interrupt_other[item].text == NULL) + return PM_ERR_TEXT; + *buf = interrupt_other[item].text; + return 0; + } + return PM_ERR_PMID; +} + +void +interrupts_init(pmdaMetric *metrictable, int nmetrics) +{ + int set[] = { CLUSTER_INTERRUPT_LINES, CLUSTER_INTERRUPT_OTHER }; + + pmdaDynamicPMNS("kernel.percpu.interrupts", + set, sizeof(set)/sizeof(int), + refresh_interrupts, interrupts_text, + refresh_metrictable, size_metrictable, + metrictable, nmetrics); +} |