diff --git a/include/crm/common/internal.h b/include/crm/common/internal.h
index 475128f2f4..c34b03b890 100644
--- a/include/crm/common/internal.h
+++ b/include/crm/common/internal.h
@@ -1,123 +1,124 @@
 /*
  * Copyright (C) 2015
  *     Andrew Beekhof <andrew@beekhof.net>
  *
  * This program 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 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 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 St, Fifth Floor, Boston, MA  02110-1301  USA
  */
 
 /*!
  * \file
  * \brief   internal common utilities
  * \ingroup core
  * \note    Public APIs are declared in util.h
  */
 
 #ifndef CRM_COMMON_INTERNAL__H
 #define CRM_COMMON_INTERNAL__H
 
 #include <glib.h>       /* for gboolean */
 #include <dirent.h>     /* for struct dirent */
 #include <sys/types.h>  /* for uid_t and gid_t */
 
 #include <crm/common/logging.h>
 
 /* internal I/O utilities (from io.c) */
 
 char *generate_series_filename(const char *directory, const char *series, int sequence,
                                gboolean bzip);
 int get_last_sequence(const char *directory, const char *series);
 void write_last_sequence(const char *directory, const char *series, int sequence, int max);
 int crm_chown_last_sequence(const char *directory, const char *series, uid_t uid, gid_t gid);
 
 gboolean crm_is_writable(const char *dir, const char *file, const char *user, const char *group,
                          gboolean need_both);
 
 void crm_sync_directory(const char *name);
 
 char *crm_read_contents(const char *filename);
 int crm_write_sync(int fd, const char *contents);
 
 
 /* internal procfs utilities (from procfs.c) */
 
 int crm_procfs_process_info(struct dirent *entry, char *name, int *pid);
 int crm_procfs_pid_of(const char *name);
+unsigned int crm_procfs_num_cores(void);
 
 
 /* internal XML schema functions (from xml.c) */
 
 void crm_schema_init(void);
 void crm_schema_cleanup(void);
 
 
 /* internal generic string functions (from strings.c) */
 
 char *crm_concat(const char *prefix, const char *suffix, char join);
 void g_hash_destroy_str(gpointer data);
 long long crm_int_helper(const char *text, char **end_text);
 gboolean crm_ends_with(const char *s, const char *match);
 char *add_list_element(char *list, const char *value);
 bool crm_compress_string(const char *data, int length, int max, char **result,
                          unsigned int *result_len);
 
 static inline int
 crm_strlen_zero(const char *s)
 {
     return !s || *s == '\0';
 }
 
 /* convenience functions for failure-related node attributes */
 
 #define CRM_FAIL_COUNT_PREFIX   "fail-count"
 #define CRM_LAST_FAILURE_PREFIX "last-failure"
 
 /*!
  * \internal
  * \brief Generate a failure-related node attribute name for a resource
  *
  * \param[in] prefix    Start of attribute name
  * \param[in] rsc_id    Resource name
  * \param[in] op        Operation name
  * \param[in] interval  Operation interval
  *
  * \return Newly allocated string with attribute name
  *
  * \note Failure attributes are named like PREFIX-RSC#OP_INTERVAL (for example,
  *       "fail-count-myrsc#monitor_30000"). The '#' is used because it is not
  *       a valid character in a resource ID, to reliably distinguish where the
  *       operation name begins. The '_' is used simply to be more comparable to
  *       action labels like "myrsc_monitor_30000".
  */
 static inline char *
 crm_fail_attr_name(const char *prefix, const char *rsc_id, const char *op,
                    int interval)
 {
     CRM_CHECK(prefix && rsc_id && op, return NULL);
     return crm_strdup_printf("%s-%s#%s_%d", prefix, rsc_id, op, interval);
 }
 
 static inline char *
 crm_failcount_name(const char *rsc_id, const char *op, int interval)
 {
     return crm_fail_attr_name(CRM_FAIL_COUNT_PREFIX, rsc_id, op, interval);
 }
 
 static inline char *
 crm_lastfailure_name(const char *rsc_id, const char *op, int interval)
 {
     return crm_fail_attr_name(CRM_LAST_FAILURE_PREFIX, rsc_id, op, interval);
 }
 
 #endif /* CRM_COMMON_INTERNAL__H */
diff --git a/lib/common/procfs.c b/lib/common/procfs.c
index 12d01ff7a7..fbbf9ebdd1 100644
--- a/lib/common/procfs.c
+++ b/lib/common/procfs.c
@@ -1,142 +1,172 @@
 /*
  * Copyright (C) 2015 Andrew Beekhof <andrew@beekhof.net>
  *
  * 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 St, Fifth Floor, Boston, MA  02110-1301  USA
  */
 
 #include <crm_internal.h>
 
 #ifndef _GNU_SOURCE
 #  define _GNU_SOURCE
 #endif
 
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <dirent.h>
+#include <ctype.h>
 
 /*!
  * \internal
  * \brief Get process ID and name associated with a /proc directory entry
  *
  * \param[in]  entry    Directory entry (must be result of readdir() on /proc)
  * \param[out] name     If not NULL, a char[64] to hold the process name
  * \param[out] pid      If not NULL, will be set to process ID of entry
  *
  * \return 0 on success, -1 if entry is not for a process or info not found
  *
  * \note This should be called only on Linux systems, as not all systems that
  *       support /proc store process names and IDs in the same way.
  */
 int
 crm_procfs_process_info(struct dirent *entry, char *name, int *pid)
 {
     int fd, local_pid;
     FILE *file;
     struct stat statbuf;
     char key[16] = { 0 }, procpath[128] = { 0 };
 
     /* We're only interested in entries whose name is a PID,
      * so skip anything non-numeric or that is too long.
      *
      * 114 = 128 - strlen("/proc/") - strlen("/status") - 1
      */
     local_pid = atoi(entry->d_name);
     if ((local_pid <= 0) || (strlen(entry->d_name) > 114)) {
         return -1;
     }
     if (pid) {
         *pid = local_pid;
     }
 
     /* Get this entry's file information */
     strcpy(procpath, "/proc/");
     strcat(procpath, entry->d_name);
     fd = open(procpath, O_RDONLY);
     if (fd < 0 ) {
         return -1;
     }
     if (fstat(fd, &statbuf) < 0) {
         close(fd);
         return -1;
     }
     close(fd);
 
     /* We're only interested in subdirectories */
     if (!S_ISDIR(statbuf.st_mode)) {
         return -1;
     }
 
     /* Read the first entry ("Name:") from the process's status file.
      * We could handle the valgrind case if we parsed the cmdline file
      * instead, but that's more of a pain than it's worth.
      */
     if (name != NULL) {
         strcat(procpath, "/status");
         file = fopen(procpath, "r");
         if (!file) {
             return -1;
         }
         if ((fscanf(file, "%15s%63s", key, name) != 2)
             || safe_str_neq(key, "Name:")) {
             fclose(file);
             return -1;
         }
         fclose(file);
     }
 
     return 0;
 }
 
 /*!
  * \internal
  * \brief Return process ID of a named process
  *
  * \param[in] name  Process name (as used in /proc/.../status)
  *
  * \return Process ID of named process if running, 0 otherwise
  *
  * \note This will return 0 if the process is being run via valgrind.
  *       This should be called only on Linux systems.
  */
 int
 crm_procfs_pid_of(const char *name)
 {
     DIR *dp;
     struct dirent *entry;
     int pid = 0;
     char entry_name[64] = { 0 };
 
     dp = opendir("/proc");
     if (dp == NULL) {
         crm_notice("Can not read /proc directory to track existing components");
         return 0;
     }
 
     while ((entry = readdir(dp)) != NULL) {
         if ((crm_procfs_process_info(entry, entry_name, &pid) == 0)
             && safe_str_eq(entry_name, name)
             && (crm_pid_active(pid, NULL) == 1)) {
 
             crm_info("Found %s active as process %d", name, pid);
             break;
         }
         pid = 0;
     }
     closedir(dp);
     return pid;
 }
+
+/*!
+ * \internal
+ * \brief Calculate number of logical CPU cores from procfs
+ *
+ * \return Number of cores (or 1 if unable to determine)
+ */
+unsigned int
+crm_procfs_num_cores(void)
+{
+    int cores = 0;
+    FILE *stream = NULL;
+
+    /* Parse /proc/stat instead of /proc/cpuinfo because it's smaller */
+    stream = fopen("/proc/stat", "r");
+    if (stream == NULL) {
+        crm_perror(LOG_INFO, "Could not open /proc/stat");
+    } else {
+        char buffer[2048];
+
+        while (fgets(buffer, sizeof(buffer), stream)) {
+            if (!strncmp(buffer, "cpu", 3) && isdigit(buffer[3])) {
+                ++cores;
+            }
+        }
+        fclose(stream);
+    }
+    return cores? cores : 1;
+}