1 /*
   2  * This file and its contents are supplied under the terms of the
   3  * Common Development and Distribution License ("CDDL"), version 1.0.
   4  * You may only use this file in accordance with the terms of version
   5  * 1.0 of the CDDL.
   6  *
   7  * A full copy of the text of the CDDL should have accompanied this
   8  * source.  A copy of the CDDL is also available via the Internet at
   9  * http://www.illumos.org/license/CDDL.
  10  */
  11 
  12 /*
  13  * Copyright (c) 2012 DEY Storage Systems, Inc.  All rights reserved.
  14  * Copyright 2012 Nexenta Systems, Inc.  All rights reserved.
  15  */
  16 
  17 /*
  18  * This implements psrinfo(1M), a utility to report various information
  19  * about processors, cores, and threads (virtual cpus).  This is mostly
  20  * intended for human consumption - this utility doesn't do much more than
  21  * simply process kstats for human readability.
  22  *
  23  * All the relevant kstats are in the cpu_info kstat module.
  24  */
  25 
  26 #include <stdio.h>
  27 #include <stdlib.h>
  28 #include <unistd.h>
  29 #include <string.h>
  30 #include <kstat.h>
  31 #include <libintl.h>
  32 #include <locale.h>
  33 #include <libgen.h>
  34 #include <ctype.h>
  35 #include <errno.h>
  36 
  37 #define _(x)    gettext(x)
  38 #if XGETTEXT
  39 /* These CPU states are here for benefit of xgettext */
  40 _("on-line")
  41 _("off-line")
  42 _("faulted")
  43 _("powered-off")
  44 _("no-intr")
  45 _("spare")
  46 _("unknown")
  47 #endif
  48 
  49 /*
  50  * We deal with sorted linked lists, where the sort key is usually the
  51  * cpu id, core id, or chip id.  We generalize this with simple node.
  52  */
  53 struct link {
  54         long            l_id;
  55         struct link     *l_next;
  56         void            *l_ptr;
  57 };
  58 
  59 /*
  60  * A physical chip.  A chip can contain multiple cores and virtual cpus.
  61  */
  62 struct pchip {
  63         struct link     p_link;
  64         int             p_ncore;
  65         int             p_nvcpu;
  66         struct link     *p_cores;
  67         struct link     *p_vcpus;
  68         int             p_doit;
  69 };
  70 
  71 struct core {
  72         struct link     c_link;
  73         struct link     c_link_pchip;
  74 
  75         int             c_nvcpu;
  76         int             c_doit;
  77 
  78         struct pchip    *c_pchip;
  79         struct link     *c_vcpus;
  80 };
  81 
  82 struct vcpu {
  83         struct link     v_link;
  84 
  85         struct link     v_link_core;
  86         struct link     v_link_pchip;
  87 
  88         int             v_doit;
  89 
  90         struct pchip    *v_pchip;
  91         struct core     *v_core;
  92 
  93         char            *v_state;
  94         long            v_state_begin;
  95         char            *v_cpu_type;
  96         char            *v_fpu_type;
  97         long            v_clock_mhz;
  98         long            v_pchip_id;     /* 1 per socket */
  99         char            *v_impl;
 100         char            *v_brand;
 101         char            *v_socket;
 102         long            v_core_id;      /* n per chip_id */
 103 };
 104 
 105 static struct link *pchips = NULL;
 106 static struct link *cores = NULL;
 107 static struct link *vcpus = NULL;
 108 
 109 static const char *cmdname;
 110 
 111 static void
 112 usage(char *msg)
 113 {
 114         if (msg != NULL)
 115                 (void) fprintf(stderr, "%s: %s\n", cmdname, msg);
 116         (void) fprintf(stderr, _("usage: \n" \
 117             "\t%s [-v] [-p] [processor_id ...]\n" \
 118             "\t%s -s [-p] processor_id\n"), cmdname, cmdname);
 119         exit(2);
 120 }
 121 
 122 /* like perror, but includes the command name */
 123 static void
 124 die(const char *msg)
 125 {
 126         (void) fprintf(stderr, "%s: %s: %s\n", cmdname, msg, strerror(errno));
 127         exit(2);
 128 }
 129 
 130 static char *
 131 mystrdup(const char *src)
 132 {
 133         char *dst;
 134 
 135         if ((dst = strdup(src)) == NULL)
 136                 die(_("strdup() failed"));
 137         return (dst);
 138 }
 139 
 140 static void *
 141 zalloc(size_t size)
 142 {
 143         void *ptr;
 144 
 145         if ((ptr = calloc(1, size)) == NULL)
 146                 die(_("calloc() failed"));
 147         return (ptr);
 148 }
 149 
 150 /*
 151  * Insert a new node on a list, at the insertion point given.
 152  */
 153 static void
 154 ins_link(struct link **ins, struct link *item)
 155 {
 156         item->l_next = *ins;
 157         *ins = item;
 158 }
 159 
 160 /*
 161  * Find an id on a sorted list.  If the requested id is not found,
 162  * then the insertpt will be set (if not null) to the location where
 163  * a new node should be inserted with ins_link (see above).
 164  */
 165 static void *
 166 find_link(void *list, int id, struct link ***insertpt)
 167 {
 168         struct link **ins = list;
 169         struct link *l;
 170 
 171         while ((l = *ins) != NULL) {
 172                 if (l->l_id == id)
 173                         return (l->l_ptr);
 174                 if (l->l_id > id)
 175                         break;
 176                 ins = &l->l_next;
 177         }
 178         if (insertpt != NULL)
 179                 *insertpt = ins;
 180         return (NULL);
 181 }
 182 
 183 /*
 184  * Print the linked list of ids in parens, taking care to collapse
 185  * ranges, so instead of (0 1 2 3) it should print (0-3).
 186  */
 187 static void
 188 print_links(struct link *l)
 189 {
 190         int     start = -1;
 191         int     end = 0;
 192 
 193         (void) printf(" (");
 194         while (l != NULL) {
 195                 if (start < 0) {
 196                         start = l->l_id;
 197                 }
 198                 end = l->l_id;
 199                 if ((l->l_next == NULL) ||
 200                     (l->l_next->l_id > (l->l_id + 1))) {
 201                         /* end of the contiguous group */
 202                         if (start == end) {
 203                                 (void) printf("%d", start);
 204                         } else {
 205                                 (void) printf("%d-%d", start, end);
 206                         }
 207                         if (l->l_next)
 208                                 (void) printf(" ");
 209                         start = -1;
 210                 }
 211                 l = l->l_next;
 212         }
 213         (void) printf(")");
 214 }
 215 
 216 static const char *
 217 timestr(long t)
 218 {
 219         static char buffer[256];
 220         (void) strftime(buffer, sizeof (buffer), _("%m/%d/%Y %T"),
 221             localtime(&t));
 222         return (buffer);
 223 }
 224 
 225 static void
 226 print_vp(int nspec)
 227 {
 228         struct pchip *chip;
 229         struct core *core;
 230         struct vcpu *vcpu;
 231         struct link *l1, *l2;
 232         int len;
 233         for (l1 = pchips; l1; l1 = l1->l_next) {
 234 
 235                 chip = l1->l_ptr;
 236 
 237                 if ((nspec != 0) && (chip->p_doit == 0))
 238                         continue;
 239 
 240                 vcpu = chip->p_vcpus->l_ptr;
 241 
 242                 /*
 243                  * Note that some of the way these strings are broken up are
 244                  * to accommodate the legacy translations so that we won't
 245                  * have to retranslate for this utility.
 246                  */
 247                 if ((chip->p_ncore == 1) || (chip->p_ncore == chip->p_nvcpu)) {
 248                         (void) printf(_("%s has %d virtual %s"),
 249                             _("The physical processor"),
 250                             chip->p_nvcpu,
 251                             chip->p_nvcpu > 1 ?
 252                             _("processors") :
 253                             _("processor"));
 254                 } else {
 255                         (void) printf(_("%s has %d %s and %d virtual %s"),
 256                             _("The physical processor"),
 257                             chip->p_ncore, _("cores"),
 258                             chip->p_nvcpu,
 259                             chip->p_nvcpu > 1 ?
 260                             _("processors") : _("processor"));
 261                 }
 262 
 263                 print_links(chip->p_vcpus);
 264                 (void) putchar('\n');
 265 
 266                 if ((chip->p_ncore == 1) || (chip->p_ncore == chip->p_nvcpu)) {
 267                         if (strlen(vcpu->v_impl)) {
 268                                 (void) printf("  %s\n", vcpu->v_impl);
 269                         }
 270                         if (((len = strlen(vcpu->v_brand)) != 0) &&
 271                             (strncmp(vcpu->v_brand, vcpu->v_impl, len) != 0))
 272                                 (void) printf("\t%s", vcpu->v_brand);
 273                         if (strcmp(vcpu->v_socket, "Unknown") != 0)
 274                                 (void) printf("\t[ %s: %s ]", _("Socket"),
 275                                     vcpu->v_socket);
 276                         (void) putchar('\n');
 277                 } else {
 278                         for (l2 = chip->p_cores; l2; l2 = l2->l_next) {
 279                                 core = l2->l_ptr;
 280                                 (void) printf(_("  %s has %d virtual %s"),
 281                                     _("The core"),
 282                                     core->c_nvcpu,
 283                                     chip->p_nvcpu > 1 ?
 284                                     _("processors") : _("processor"));
 285                                 print_links(core->c_vcpus);
 286                                 (void) putchar('\n');
 287                         }
 288                         if (strlen(vcpu->v_impl)) {
 289                                 (void) printf("    %s\n", vcpu->v_impl);
 290                         }
 291                         if (((len = strlen(vcpu->v_brand)) != 0) &&
 292                             (strncmp(vcpu->v_brand, vcpu->v_impl, len) != 0))
 293                                 (void) printf("      %s\n", vcpu->v_brand);
 294                 }
 295         }
 296 }
 297 
 298 static void
 299 print_ps(void)
 300 {
 301         int online = 1;
 302         struct pchip *p;
 303         struct vcpu *v;
 304         struct link *l;
 305 
 306         /*
 307          * Report "1" if all cpus colocated on the same chip are online.
 308          */
 309         for (l = pchips; l != NULL; l = l->l_next) {
 310                 p = l->l_ptr;
 311                 if (p->p_doit)
 312                         break;
 313         }
 314         if (p == NULL)
 315                 return; /* should never happen! */
 316         for (l = p->p_vcpus; l != NULL; l = l->l_next) {
 317                 v = l->l_ptr;
 318                 if (strcmp(v->v_state, "on-line") != 0) {
 319                         online = 0;
 320                         break;
 321                 }
 322         }
 323 
 324         (void) printf("%d\n", online);
 325 }
 326 
 327 static void
 328 print_s(void)
 329 {
 330         struct link *l;
 331 
 332         /*
 333          * Find the processor (there will be only one) that we selected,
 334          * and report whether or not it is online.
 335          */
 336         for (l = vcpus; l != NULL; l = l->l_next) {
 337                 struct vcpu *v = l->l_ptr;
 338                 if (v->v_doit) {
 339                         (void) printf("%d\n",
 340                             strcmp(v->v_state, "on-line") == 0 ? 1 : 0);
 341                         return;
 342                 }
 343         }
 344 }
 345 
 346 static void
 347 print_p(int nspec)
 348 {
 349         struct          link *l1, *l2;
 350         int             online = 0;
 351 
 352         /*
 353          * Print the number of physical packages with at least one processor
 354          * online.
 355          */
 356         for (l1 = pchips; l1 != NULL; l1 = l1->l_next) {
 357                 struct pchip *p = l1->l_ptr;
 358                 if ((nspec == 0) || (p->p_doit)) {
 359 
 360                         for (l2 = p->p_vcpus; l2 != NULL; l2 = l2->l_next) {
 361                                 struct vcpu *v = l2->l_ptr;
 362                                 if (strcmp(v->v_state, "on-line") == 0) {
 363                                         online++;
 364                                         break;
 365                                 }
 366                         }
 367                 }
 368         }
 369         (void) printf("%d\n", online);
 370 }
 371 
 372 static void
 373 print_v(int nspec)
 374 {
 375         struct link     *l;
 376 
 377         for (l = vcpus; l != NULL; l = l->l_next) {
 378                 struct vcpu *v = l->l_ptr;
 379 
 380                 if ((nspec != 0) && (!v->v_doit))
 381                         continue;
 382                 (void) printf(_("Status of virtual processor %d as of: "),
 383                     l->l_id);
 384                 (void) printf("%s\n", timestr(time(NULL)));
 385                 (void) printf(_("  %s since %s.\n"),
 386                     _(v->v_state), timestr(v->v_state_begin));
 387                 if (v->v_clock_mhz) {
 388                         (void) printf(
 389                             _("  The %s processor operates at %llu MHz,\n"),
 390                             v->v_cpu_type, (unsigned long long)v->v_clock_mhz);
 391                 } else {
 392                         (void) printf(
 393                             _("  The %s processor operates at " \
 394                             "an unknown frequency,\n"), v->v_cpu_type);
 395                 }
 396                 switch (*v->v_fpu_type) {
 397                 case '\0':
 398                         (void) printf(
 399                             _("\tand has no floating point processor.\n"));
 400                         break;
 401                 case 'a': case 'A':
 402                 case 'e': case 'E':
 403                 case 'i': case 'I':
 404                 case 'o': case 'O':
 405                 case 'u': case 'U':
 406                 case 'y': case 'Y':
 407                         (void) printf(
 408                             _("\tand has an %s floating point processor.\n"),
 409                             v->v_fpu_type);
 410                         break;
 411                 default:
 412                         (void) printf(
 413                             _("\tand has a %s floating point processor.\n"),
 414                             v->v_fpu_type);
 415                         break;
 416                 }
 417         }
 418 }
 419 
 420 static void
 421 print_normal(int nspec)
 422 {
 423         struct link     *l;
 424         struct vcpu     *v;
 425 
 426         for (l = vcpus; l != NULL; l = l->l_next) {
 427                 v = l->l_ptr;
 428                 if ((nspec == 0) || (v->v_doit)) {
 429                         (void) printf(_("%d\t%-8s  since %s\n"),
 430                             l->l_id, _(v->v_state), timestr(v->v_state_begin));
 431                 }
 432         }
 433 }
 434 
 435 int
 436 main(int argc, char **argv)
 437 {
 438         kstat_ctl_t     *kc;
 439         kstat_t         *ksp;
 440         kstat_named_t   *knp;
 441         struct vcpu     *vc;
 442         struct core     *core;
 443         struct pchip    *chip;
 444         struct link     **ins;
 445         char            *s;
 446         int             nspec;
 447         int             optc;
 448         int             opt_s = 0;
 449         int             opt_p = 0;
 450         int             opt_v = 0;
 451         int             ex = 0;
 452 
 453         cmdname = basename(argv[0]);
 454 
 455 
 456         (void) setlocale(LC_ALL, "");
 457 #if !defined(TEXT_DOMAIN)
 458 #define TEXT_DOMAIN     "SYS_TEST"
 459 #endif
 460         (void) textdomain(TEXT_DOMAIN);
 461 
 462         /* collect the kstats */
 463         if ((kc = kstat_open()) == NULL)
 464                 die(_("kstat_open() failed"));
 465 
 466         if ((ksp = kstat_lookup(kc, "cpu_info", -1, NULL)) == NULL)
 467                 die(_("kstat_lookup() failed"));
 468 
 469         for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) {
 470 
 471                 if (strcmp(ksp->ks_module, "cpu_info") != 0)
 472                         continue;
 473                 if (kstat_read(kc, ksp, NULL) == NULL)
 474                         die(_("kstat_read() failed"));
 475 
 476                 vc = find_link(&vcpus, ksp->ks_instance, &ins);
 477                 if (vc == NULL) {
 478                         vc = zalloc(sizeof (struct vcpu));
 479                         vc->v_link.l_id = ksp->ks_instance;
 480                         vc->v_link_core.l_id = ksp->ks_instance;
 481                         vc->v_link_pchip.l_id = ksp->ks_instance;
 482                         vc->v_link.l_ptr = vc;
 483                         vc->v_link_core.l_ptr = vc;
 484                         vc->v_link_pchip.l_ptr = vc;
 485                         ins_link(ins, &vc->v_link);
 486                 }
 487 
 488                 if ((knp = kstat_data_lookup(ksp, "state")) != NULL) {
 489                         vc->v_state = mystrdup(knp->value.c);
 490                 } else {
 491                         vc->v_state = "unknown";
 492                 }
 493 
 494                 if ((knp = kstat_data_lookup(ksp, "cpu_type")) != NULL) {
 495                         vc->v_cpu_type = mystrdup(knp->value.c);
 496                 }
 497                 if ((knp = kstat_data_lookup(ksp, "fpu_type")) != NULL) {
 498                         vc->v_fpu_type = mystrdup(knp->value.c);
 499                 }
 500 
 501                 if ((knp = kstat_data_lookup(ksp, "state_begin")) != NULL) {
 502                         vc->v_state_begin = knp->value.l;
 503                 }
 504 
 505                 if ((knp = kstat_data_lookup(ksp, "clock_MHz")) != NULL) {
 506                         vc->v_clock_mhz = knp->value.l;
 507                 }
 508 
 509                 if ((knp = kstat_data_lookup(ksp, "brand")) == NULL) {
 510                         vc->v_brand = _("(unknown)");
 511                 } else {
 512                         vc->v_brand = mystrdup(knp->value.str.addr.ptr);
 513                 }
 514 
 515                 if ((knp = kstat_data_lookup(ksp, "socket_type")) == NULL) {
 516                         vc->v_socket = "Unknown";
 517                 } else {
 518                         vc->v_socket = mystrdup(knp->value.str.addr.ptr);
 519                 }
 520 
 521                 if ((knp = kstat_data_lookup(ksp, "implementation")) == NULL) {
 522                         vc->v_impl = _("(unknown)");
 523                 } else {
 524                         vc->v_impl = mystrdup(knp->value.str.addr.ptr);
 525                 }
 526                 /*
 527                  * Legacy code removed the chipid and cpuid fields... we
 528                  * do the same for compatibility.  Note that the original
 529                  * pattern is a bit strange, and we have to emulate this because
 530                  * on SPARC we *do* emit these.  The original pattern we are
 531                  * emulating is: $impl =~ s/(cpuid|chipid)\s*\w+\s+//;
 532                  */
 533                 if ((s = strstr(vc->v_impl, "chipid")) != NULL) {
 534                         char *x = s + strlen("chipid");
 535                         while (isspace(*x))
 536                                 x++;
 537                         if ((!isalnum(*x)) && (*x != '_'))
 538                                 goto nochipid;
 539                         while (isalnum(*x) || (*x == '_'))
 540                                 x++;
 541                         if (!isspace(*x))
 542                                 goto nochipid;
 543                         while (isspace(*x))
 544                                 x++;
 545                         (void) strcpy(s, x);
 546                 }
 547 nochipid:
 548                 if ((s = strstr(vc->v_impl, "cpuid")) != NULL) {
 549                         char *x = s + strlen("cpuid");
 550                         while (isspace(*x))
 551                                 x++;
 552                         if ((!isalnum(*x)) && (*x != '_'))
 553                                 goto nocpuid;
 554                         while (isalnum(*x) || (*x == '_'))
 555                                 x++;
 556                         if (!isspace(*x))
 557                                 goto nocpuid;
 558                         while (isspace(*x))
 559                                 x++;
 560                         (void) strcpy(s, x);
 561                 }
 562 nocpuid:
 563 
 564                 if ((knp = kstat_data_lookup(ksp, "chip_id")) != NULL)
 565                         vc->v_pchip_id = knp->value.l;
 566                 chip = find_link(&pchips, vc->v_pchip_id, &ins);
 567                 if (chip == NULL) {
 568                         chip = zalloc(sizeof (struct pchip));
 569                         chip->p_link.l_id = vc->v_pchip_id;
 570                         chip->p_link.l_ptr = chip;
 571                         ins_link(ins, &chip->p_link);
 572                 }
 573                 vc->v_pchip = chip;
 574 
 575                 if ((knp = kstat_data_lookup(ksp, "core_id")) != NULL)
 576                         vc->v_core_id = knp->value.l;
 577                 core = find_link(&cores, vc->v_core_id, &ins);
 578                 if (core == NULL) {
 579                         core = zalloc(sizeof (struct core));
 580                         core->c_link.l_id = vc->v_core_id;
 581                         core->c_link.l_ptr = core;
 582                         core->c_link_pchip.l_id = vc->v_core_id;
 583                         core->c_link_pchip.l_ptr = core;
 584                         core->c_pchip = chip;
 585                         ins_link(ins, &core->c_link);
 586                         chip->p_ncore++;
 587                         (void) find_link(&chip->p_cores, core->c_link.l_id,
 588                             &ins);
 589                         ins_link(ins, &core->c_link_pchip);
 590                 }
 591                 vc->v_core = core;
 592 
 593 
 594 
 595                 /* now put other linkages in place */
 596                 (void) find_link(&chip->p_vcpus, vc->v_link.l_id, &ins);
 597                 ins_link(ins, &vc->v_link_pchip);
 598                 chip->p_nvcpu++;
 599 
 600                 (void) find_link(&core->c_vcpus, vc->v_link.l_id, &ins);
 601                 ins_link(ins, &vc->v_link_core);
 602                 core->c_nvcpu++;
 603         }
 604 
 605         (void) kstat_close(kc);
 606 
 607         nspec = 0;
 608 
 609         while ((optc = getopt(argc, argv, "pvs")) != EOF) {
 610                 switch (optc) {
 611                 case 's':
 612                         opt_s = 1;
 613                         break;
 614                 case 'p':
 615                         opt_p = 1;
 616                         break;
 617                 case 'v':
 618                         opt_v = 1;
 619                         break;
 620                 default:
 621                         usage(NULL);
 622                 }
 623         }
 624 
 625         while (optind < argc) {
 626                 long id;
 627                 char *eptr;
 628                 struct link *l;
 629                 id = strtol(argv[optind], &eptr, 10);
 630                 l = find_link(&vcpus, id, NULL);
 631                 if ((*eptr != '\0') || (l == NULL)) {
 632                         (void) fprintf(stderr,
 633                             _("%s: processor %s: Invalid argument\n"),
 634                             cmdname, argv[optind]);
 635                         ex = 2;
 636                 } else {
 637                         ((struct vcpu *)l->l_ptr)->v_doit = 1;
 638                         ((struct vcpu *)l->l_ptr)->v_pchip->p_doit = 1;
 639                         ((struct vcpu *)l->l_ptr)->v_core->c_doit = 1;
 640                 }
 641                 nspec++;
 642                 optind++;
 643         }
 644 
 645         if (opt_s && opt_v) {
 646                 usage(_("options -s and -v are mutually exclusive"));
 647         }
 648         if (opt_s && nspec != 1) {
 649                 usage(_("must specify exactly one processor if -s used"));
 650         }
 651         if (opt_v && opt_p) {
 652                 print_vp(nspec);
 653         } else if (opt_s && opt_p) {
 654                 print_ps();
 655         } else if (opt_p) {
 656                 print_p(nspec);
 657         } else if (opt_v) {
 658                 print_v(nspec);
 659         } else if (opt_s) {
 660                 print_s();
 661         } else {
 662                 print_normal(nspec);
 663         }
 664 
 665         return (ex);
 666 }