diff --git a/README.licensing b/README.licensing index 3fcd56d09..d9af192e2 100644 --- a/README.licensing +++ b/README.licensing @@ -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) diff --git a/po/POTFILES.in b/po/POTFILES.in index 559d92433..b73d3a45d 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -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 diff --git a/sys-utils/.gitignore b/sys-utils/.gitignore index 32346b494..02d1ee439 100644 --- a/sys-utils/.gitignore +++ b/sys-utils/.gitignore @@ -8,6 +8,7 @@ ldattach readprofile renice rtcwake +lscpu setarch setsid tunelp diff --git a/sys-utils/Makefile.am b/sys-utils/Makefile.am index 4129c2012..73888cde4 100644 --- a/sys-utils/Makefile.am +++ b/sys-utils/Makefile.am @@ -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 index 000000000..78da8a1d4 --- /dev/null +++ b/sys-utils/lscpu.1 @@ -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 +.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 index 000000000..275d4c763 --- /dev/null +++ b/sys-utils/lscpu.c @@ -0,0 +1,529 @@ +/* + * lscpu - CPU architecture information helper + * + * Copyright (C) 2008 Cai Qian + * Copyright (C) 2008 Karel Zak + * + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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: + * + * " : " + */ +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 " "*/ +#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; +}