]> err.no Git - util-linux/commitdiff
lscpu: new command
authorCai Qian <qcai@redhat.com>
Thu, 17 Jul 2008 12:19:40 +0000 (14:19 +0200)
committerKarel Zak <kzak@redhat.com>
Fri, 18 Jul 2008 01:01:49 +0000 (03:01 +0200)
Add a lscpu(1) utility program.

[kzak@redhat.com:
- indent by linux-2.6/scripts/Lindent
- add lscpu.{c,1} to sys-utils/Makefile.am
- add NLS suport
- complete code refactoring
]

Co-Author: Karel Zak <kzak@redhat.com>
Signed-off-by: Cai Qian <qcai@redhat.com>
Signed-off-by: Karel Zak <kzak@redhat.com>
README.licensing
po/POTFILES.in
sys-utils/.gitignore
sys-utils/Makefile.am
sys-utils/lscpu.1 [new file with mode: 0644]
sys-utils/lscpu.c [new file with mode: 0644]

index 3fcd56d09bab219cbc10a7d5c95085462d9f7155..d9af192e2a2372645f19beb4fae17a621e32f5e4 100644 (file)
@@ -2,6 +2,8 @@
 The project utils-linux-ng doesn't use same license for all code. There are
 code with:
 
+   * GPLv3+ (GNU General Public License version 3, or any later version)
+
    * GPLv2+ (GNU General Public License version 2, or any later version)
 
    * GPLv2 (GNU General Public License version 2)
index 559d92433729bb113d69829aa115553accd823aa..b73d3a45dbd7a3a2f07f8dbcaaddd44b984977a2 100644 (file)
@@ -98,6 +98,7 @@ sys-utils/flock.c
 sys-utils/ipcrm.c
 sys-utils/ipcs.c
 sys-utils/ldattach.c
+sys-utils/lscpu.c
 sys-utils/rdev.c
 sys-utils/readprofile.c
 sys-utils/renice.c
index 32346b49408fb43160b39cbe2a80723b8baa3dc0..02d1ee439ac5cefa341a5658846dee184163b1cc 100644 (file)
@@ -8,6 +8,7 @@ ldattach
 readprofile
 renice
 rtcwake
+lscpu
 setarch
 setsid
 tunelp
index 4129c201244901601786293a9866a5baa4c9156f..73888cde44e01cc17da155ea95bea5be3efd41fb 100644 (file)
@@ -7,7 +7,7 @@ endif
 
 usrbinexec_PROGRAMS = flock ipcrm ipcs renice setsid
 if LINUX
-usrbinexec_PROGRAMS += cytune setarch
+usrbinexec_PROGRAMS += cytune setarch lscpu
 endif
 
 cytune_SOURCES = cytune.c cyclades.h
@@ -25,7 +25,7 @@ tunelp_SOURCES = tunelp.c lp.h
 
 dist_man_MANS = flock.1 readprofile.1 \
        ctrlaltdel.8 cytune.8 dmesg.1 ipcrm.1 ipcs.1 ldattach.8 renice.1 \
-       setsid.1 tunelp.8 setarch.8 rtcwake.8
+       setsid.1 tunelp.8 setarch.8 rtcwake.8 lscpu.1
 
 info_TEXINFOS = ipc.texi
 
diff --git a/sys-utils/lscpu.1 b/sys-utils/lscpu.1
new file mode 100644 (file)
index 0000000..78da8a1
--- /dev/null
@@ -0,0 +1,29 @@
+.\" Process this file with
+.\" groff -man -Tascii lscpu.1
+.\"
+.TH LSCPU 1 "JULY 2008" Linux "User Manuals"
+.SH NAME
+lscpu \- CPU architecture information helper
+.SH SYNOPSIS
+.B lscpu [-hp]
+.SH DESCRIPTION
+.B lscpu
+gathers CPU architecture information like number of CPUs, threads,
+cores, sockets, NUMA nodes, information about CPU caches, CPU family,
+model and stepping from sysfs and /proc/cpuinfo, and prints it in
+human-readable format. Alternatively, it can print out in parsable
+format including how different caches are shared by different CPUs,
+which can also be fed to other programs.
+.SH OPTIONS
+.IP -h, --help
+Print a help message.
+.IP -p, --parse
+Print out in parsable instead of printable format.
+.SH BUGS
+The program at the moment does not handle the system installed with
+different types of physical processors.
+.SH AUTHOR
+Cai Qian <qcai@redhat.com>
+.SH AVAILABILITY
+The setarch command is part of the util-linux-ng package and is available from
+ftp://ftp.kernel.org/pub/linux/utils/util-linux-ng/.
diff --git a/sys-utils/lscpu.c b/sys-utils/lscpu.c
new file mode 100644 (file)
index 0000000..275d4c7
--- /dev/null
@@ -0,0 +1,529 @@
+/*
+ * lscpu - CPU architecture information helper
+ *
+ * Copyright (C) 2008 Cai Qian <qcai@redhat.com>
+ * Copyright (C) 2008 Karel Zak <kzak@redhat.com>
+ *
+ * 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 3 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <ctype.h>
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/utsname.h>
+#include <unistd.h>
+#include <stdarg.h>
+
+#include "nls.h"
+
+#define CACHE_MAX 100
+
+/* /sys paths */
+#define _PATH_SYS_SYSTEM       "/sys/devices/system"
+#define _PATH_SYS_CPU0         _PATH_SYS_SYSTEM "/cpu/cpu0"
+#define _PATH_PROC_XENCAP      "/proc/xen/capabilities"
+#define _PATH_PROC_CPUINFO     "/proc/cpuinfo"
+
+int have_topology;
+int have_cache;
+int have_node;
+
+/* CPU(s) description */
+struct cpu_decs {
+       /* counters */
+       int     ct_cpu;
+       int     ct_thread;
+       int     ct_core;
+       int     ct_socket;
+       int     ct_node;
+       int     ct_cache;
+
+       /* who is who */
+       char    *arch;
+       char    *vendor;
+       char    *family;
+       char    *model;
+
+       /* caches */
+       char    *caname[CACHE_MAX];
+       char    *casize[CACHE_MAX];
+       int     camap[CACHE_MAX];
+
+       /* misc */
+       char    *mhz;
+       char    *stepping;
+       char    *flags;
+
+       /* NUMA */
+       int     *nodecpu;
+};
+
+char pathbuf[PATH_MAX];
+
+static void path_scanstr(char *result, const char *path, ...)
+               __attribute__ ((__format__ (__printf__, 2, 3)));
+static int path_exist(const char *path, ...)
+               __attribute__ ((__format__ (__printf__, 1, 2)));
+static int path_sibling(const char *path, ...)
+               __attribute__ ((__format__ (__printf__, 1, 2)));
+
+static FILE *
+xfopen(const char *path, const char *mode)
+{
+       FILE *fd = fopen(path, mode);
+       if (!fd)
+               err(EXIT_FAILURE, _("error: %s"), path);
+       return fd;
+}
+
+static FILE *
+path_vfopen(const char *mode, const char *path, va_list ap)
+{
+       vsnprintf(pathbuf, sizeof(pathbuf), path, ap);
+       return xfopen(pathbuf, mode);
+}
+
+static void
+path_scanstr(char *result, const char *path, ...)
+{
+       FILE *fd;
+       va_list ap;
+
+       va_start(ap, path);
+       fd = path_vfopen("r", path, ap);
+       va_end(ap);
+
+       if (fscanf(fd, "%s", result) != 1) {
+               if (ferror(fd))
+                       err(EXIT_FAILURE, _("error: %s"), pathbuf);
+               else
+                       errx(EXIT_FAILURE, _("error parse: %s"), pathbuf);
+       }
+       fclose(fd);
+}
+
+static int
+path_exist(const char *path, ...)
+{
+       va_list ap;
+
+       va_start(ap, path);
+       vsnprintf(pathbuf, sizeof(pathbuf), path, ap);
+       va_end(ap);
+
+       return access(pathbuf, F_OK) == 0;
+}
+
+char *
+xstrdup(const char *str)
+{
+       char *s = strdup(str);
+       if (!s)
+               err(EXIT_FAILURE, _("error: strdup failed"));
+       return s;
+}
+
+/* count the set bit in a mapping file */
+static int
+path_sibling(const char *path, ...)
+{
+       int c, n;
+       int result = 0;
+       char s[2];
+       FILE *fp;
+       va_list ap;
+
+       va_start(ap, path);
+       fp = path_vfopen("r", path, ap);
+       va_end(ap);
+
+       while ((c = fgetc(fp)) != EOF) {
+               if (isxdigit(c)) {
+                       s[0] = c;
+                       s[1] = '\0';
+                       for (n = strtol(s, NULL, 16); n > 0; n /= 2) {
+                               if (n % 2)
+                                       result++;
+                       }
+               }
+       }
+       fclose(fp);
+
+       return result;
+}
+
+/* Lookup a pattern and get the value from cpuinfo.
+ * Format is:
+ *
+ *     "<pattern>   : <key>"
+ */
+int lookup(char *line, char *pattern, char **value)
+{
+       char *p, *v;
+       int len = strlen(pattern);
+
+       if (!*line)
+               return 0;
+
+       /* pattern */
+       if (strncmp(line, pattern, len))
+               return 0;
+
+       /* white spaces */
+       for (p = line + len; isspace(*p); p++);
+
+       /* separator */
+       if (*p != ':')
+               return 0;
+
+       /* white spaces */
+       for (++p; isspace(*p); p++);
+
+       /* value */
+       if (!*p)
+               return 0;
+       v = p;
+
+       /* end of value */
+       len = strlen(line) - 1;
+       for (p = line + len; isspace(*(p-1)); p--);
+       *p = '\0';
+
+       *value = xstrdup(v);
+       return 1;
+}
+
+static void
+read_basicinfo(struct cpu_decs *cpu)
+{
+       FILE *fp = xfopen(_PATH_PROC_CPUINFO, "r");
+       char buf[BUFSIZ];
+       struct utsname utsbuf;
+
+       /* architecture */
+       if (uname(&utsbuf) == -1)
+               err(EXIT_FAILURE, _("error: uname failed"));
+       cpu->arch = xstrdup(utsbuf.machine);
+
+       /* count CPU(s) */
+       while(path_exist(_PATH_SYS_SYSTEM "/cpu/cpu%d", cpu->ct_cpu))
+               cpu->ct_cpu++;
+
+       /* details */
+       while (fgets(buf, sizeof(buf), fp) != NULL) {
+               /* IA64 */
+               if (lookup(buf, "vendor", &cpu->vendor)) ;
+               else if (lookup(buf, "vendor_id", &cpu->vendor)) ;
+               /* IA64 */
+               else if (lookup(buf, "family", &cpu->family)) ;
+               else if (lookup(buf, "cpu family", &cpu->family)) ;
+               else if (lookup(buf, "model", &cpu->model)) ;
+               else if (lookup(buf, "stepping", &cpu->stepping)) ;
+               else if (lookup(buf, "cpu MHz", &cpu->mhz)) ;
+               else if (lookup(buf, "flags", &cpu->flags)) ;
+               else
+                       continue;
+       }
+       fclose(fp);
+}
+
+static void
+read_topology(struct cpu_decs *cpu)
+{
+       /* number of threads */
+       cpu->ct_thread = path_sibling(
+                               _PATH_SYS_CPU0 "/topology/thread_siblings");
+
+       /* number of cores */
+       cpu->ct_core = path_sibling(
+                               _PATH_SYS_CPU0 "/topology/core_siblings")
+                       / cpu->ct_thread;
+
+       /* number of sockets */
+       cpu->ct_socket = cpu->ct_cpu / cpu->ct_core / cpu->ct_thread;
+}
+
+static void
+read_cache(struct cpu_decs *cpu)
+{
+       char buf[256];
+       DIR *dp;
+       struct dirent *dir;
+       int level, type;
+
+       dp = opendir(_PATH_SYS_CPU0 "/cache");
+       if (dp == NULL)
+               err(EXIT_FAILURE, _("error: %s"), _PATH_SYS_CPU0 "/cache");
+
+       while ((dir = readdir(dp)) != NULL) {
+               if (!strcmp(dir->d_name, ".")
+                   || !strcmp(dir->d_name, ".."))
+                       continue;
+
+               /* cache type */
+               path_scanstr(buf, _PATH_SYS_CPU0 "/cache/%s/type", dir->d_name);
+               if (!strcmp(buf, "Data"))
+                       type = 'd';
+               else if (!strcmp(buf, "Instruction"))
+                       type = 'i';
+               else
+                       type = 0;
+
+               /* cache level */
+               path_scanstr(buf, _PATH_SYS_CPU0 "/cache/%s/level", dir->d_name);
+               level = atoi(buf);
+
+               if (type)
+                       snprintf(buf, sizeof(buf), "L%d%c", level, type);
+               else
+                       snprintf(buf, sizeof(buf), "L%d", level);
+
+               cpu->caname[cpu->ct_cache] = xstrdup(buf);
+
+               /* cache size */
+               path_scanstr(buf, _PATH_SYS_CPU0 "/cache/%s/size", dir->d_name);
+               cpu->casize[cpu->ct_cache] = xstrdup(buf);
+
+               /* information about how CPUs share different caches */
+               cpu->camap[cpu->ct_cache] = path_sibling(
+                               _PATH_SYS_CPU0 "/cache/%s/shared_cpu_map",
+                               dir->d_name);
+               cpu->ct_cache++;
+       }
+}
+
+static void
+read_nodes(struct cpu_decs *cpu)
+{
+       int i;
+
+       /* number of NUMA node */
+       while (path_exist(_PATH_SYS_SYSTEM "/node/node%d", cpu->ct_node))
+               cpu->ct_node++;
+
+       cpu->nodecpu = (int *) malloc(cpu->ct_node * sizeof(int));
+       if (!cpu->nodecpu)
+               err(EXIT_FAILURE, _("error: malloc failed"));
+
+       /* information about how nodes share different CPUs */
+       for (i = 0; i < cpu->ct_node; i++)
+               cpu->nodecpu[i] = path_sibling(
+                                       _PATH_SYS_SYSTEM "/node/node%d/cpumap",
+                                       i);
+}
+
+static void
+check_system(void)
+{
+       FILE *fd;
+       char buf[256];
+
+       /* Dom0 Kernel gives wrong information. */
+       fd = fopen(_PATH_PROC_XENCAP, "r");
+       if (fd) {
+               if (fscanf(fd, "%s", buf) == 1 && !strcmp(buf, "control_d"))
+                       errx(EXIT_FAILURE,
+                            _("error: Dom0 Kernel is unsupported."));
+               fclose(fd);
+       }
+
+       /* Read through sysfs. */
+       if (access(_PATH_SYS_SYSTEM, F_OK))
+               errx(1, _("error: /sys filesystem is not accessable."));
+
+       if (!access(_PATH_SYS_SYSTEM "/node", F_OK))
+               have_node = 1;
+
+       if (!access(_PATH_SYS_CPU0 "/topology/thread_siblings", F_OK))
+               have_topology = 1;
+
+       if (!access(_PATH_SYS_CPU0 "/cache", F_OK))
+               have_cache = 1;
+}
+
+static void
+print_parsable(struct cpu_decs *cpu)
+{
+       int i, j;
+
+       puts(
+       "# The following is the parsable format, which can be fed to other\n"
+       "# programs. Each different item in every column has a unique ID\n"
+       "# starting from zero.\n"
+       "# CPU,Core,Socket,Node");
+
+       if (have_cache) {
+               /* separator between CPU topology and cache information */
+               putchar(',');
+
+               for (i = cpu->ct_cache - 1; i >= 0; i--)
+                       printf(",%s", cpu->caname[i]);
+       }
+       putchar('\n');
+
+       for (i = 0; i < cpu->ct_cpu; i++) {
+               printf("%d", i);
+
+               if (have_topology)
+                       printf(",%d,%d",
+                               i / cpu->ct_thread,
+                               i / cpu->ct_core / cpu->ct_thread);
+               else
+                       printf(",,");
+
+               if (have_node) {
+                       int c = 0;
+
+                       for (j = 0; j < cpu->ct_node; j++) {
+                               c += cpu->nodecpu[j];
+                               if (i < c) {
+                                       printf(",%d", j);
+                                       break;
+                               }
+                       }
+               } else
+                       putchar(',');
+
+               if (have_cache) {
+                       putchar(',');
+
+                       for (j = cpu->ct_cache - 1; j >= 0; j--) {
+                               /* If shared_cpu_map is 0, all CPUs share the same
+                                  cache. */
+                               if (cpu->camap[j] == 0)
+                                       cpu->camap[j] = cpu->ct_core *
+                                                       cpu->ct_thread;
+
+                               printf(",%d", i / cpu->camap[j]);
+                       }
+               }
+               putchar('\n');
+       }
+}
+
+
+/* output formats "<key>  <value>"*/
+#define print_s(_key, _val)    printf("%-23s%s\n", _key, _val)
+#define print_n(_key, _val)    printf("%-23s%d\n", _key, _val)
+
+static void
+print_readable(struct cpu_decs *cpu)
+{
+       char buf[BUFSIZ];
+
+       print_s("Architecture:", cpu->arch);
+       print_n("CPU(s):", cpu->ct_cpu);
+
+       if (have_topology) {
+               print_n(_("Thread(s) per core:"), cpu->ct_thread);
+               print_n(_("Core(s) per socket:"), cpu->ct_core);
+               print_n(_("CPU socket(s):"), cpu->ct_socket);
+       }
+
+       if (have_node)
+               print_n(_("NUMA node(s):"), cpu->ct_node);
+       if (cpu->vendor)
+               print_s(_("Vendor ID:"), cpu->vendor);
+       if (cpu->family)
+               print_s(_("CPU family:"), cpu->family);
+       if (cpu->model)
+               print_s(_("Model:"), cpu->model);
+       if (cpu->stepping)
+               print_s(_("Stepping:"), cpu->stepping);
+       if (cpu->mhz)
+               print_s(_("CPU MHz:"), cpu->mhz);
+       if (cpu->flags) {
+               snprintf(buf, sizeof(buf), " %s ", cpu->flags);
+               if (strstr(buf, " svm "))
+                       print_s(_("Virtualization:"), "AMD-V");
+               else if (strstr(buf, " vmx "))
+                       print_s(_("Virtualization:"), "VT-x");
+       }
+
+       if (have_cache) {
+               int i;
+
+               for (i = cpu->ct_cache - 1; i >= 0; i--) {
+                       snprintf(buf, sizeof(buf),
+                                       _("%s cache:"), cpu->caname[i]);
+                       print_s(buf, cpu->casize[i]);
+               }
+       }
+}
+
+void usage(int rc)
+{
+       printf(_("Usage: %s [option]\n"),
+                       program_invocation_short_name);
+
+       puts(_( "CPU architecture information helper\n\n"
+               "  -h, --help     usage information\n"
+               "  -p, --parse    print out in parsable instead of printable format.\n"));
+       exit(rc);
+}
+
+int main(int argc, char *argv[])
+{
+       struct cpu_decs _cpu, *cpu = &_cpu;
+       int parsable = 0, c;
+
+       struct option longopts[] = {
+               { "help",       no_argument, 0, 'h' },
+               { "parse",      no_argument, 0, 'p' },
+               { NULL,         0, 0, 0 }
+       };
+
+       setlocale(LC_MESSAGES, "");
+       bindtextdomain(PACKAGE, LOCALEDIR);
+       textdomain(PACKAGE);
+
+       while((c = getopt_long(argc, argv, "hp", longopts, NULL)) != -1) {
+               switch (c) {
+               case 'h':
+                       usage(EXIT_SUCCESS);
+               case 'p':
+                       parsable = 1;
+                       break;
+               default:
+                       usage(EXIT_FAILURE);
+               }
+       }
+
+       memset(cpu, 0, sizeof(*cpu));
+
+       check_system();
+
+       read_basicinfo(cpu);
+
+       if (have_topology)
+               read_topology(cpu);
+       if (have_cache)
+               read_cache(cpu);
+       if (have_node)
+               read_nodes(cpu);
+
+       /* Show time! */
+       if (parsable)
+               print_parsable(cpu);
+       else
+               print_readable(cpu);
+
+       return EXIT_FAILURE;
+}