summaryrefslogtreecommitdiff
path: root/src/pmdas/linux/interrupts.c
diff options
context:
space:
mode:
authorIgor Pashev <pashev.igor@gmail.com>2014-10-26 12:33:50 +0400
committerIgor Pashev <pashev.igor@gmail.com>2014-10-26 12:33:50 +0400
commit47e6e7c84f008a53061e661f31ae96629bc694ef (patch)
tree648a07f3b5b9d67ce19b0fd72e8caa1175c98f1a /src/pmdas/linux/interrupts.c
downloadpcp-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.c394
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);
+}