# # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2006 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # cmd/stat/Makefile # include ../Makefile.cmd SUBDIRS= arcstat iostat mpstat vmstat fsstat kstat all : TARGET = all install : TARGET = install clean : TARGET = clean clobber : TARGET = clobber _msg : TARGET = _msg .KEEP_STATE: all install clean clobber _msg: $(SUBDIRS) $(SUBDIRS): FRC @cd $@; pwd; $(MAKE) $(TARGET) FRC: # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright 2025 Hans Rosenfeld # # cmd/stat/Makefile.stat include $(SRC)/cmd/Makefile.ctf STATSRC = $(SRC)/cmd/stat STATCOMMONDIR = $(STATSRC)/common STAT_COMMON_OBJS = timestamp.o STAT_COMMON_SRCS = $(STAT_COMMON_OBJS:%.o=%.c) OBJS += $(STAT_COMMON_OBJS) SRCS += $(STAT_COMMON_SRCS) COMMON_OBJS = acquire.o walkers.o acquire_iodevs.o dsr.o mnt.o common.o COMMON_SRCS = $(COMMON_OBJS:%.o=$(STATCOMMONDIR)/%.c) CPPFLAGS += -I$(STATCOMMONDIR) # Hammerhead: Suppress pset_list pointer type mismatch warnings (acquire.c) CERRWARN += -Wno-incompatible-pointer-types # Hammerhead: Use vpath to find sources in stat/common (timestamp.c etc.) # instead of copying or explicit directory pattern rules. This allows both # .o compilation and .po message catalog generation to find the sources. vpath %.c $(STATCOMMONDIR) # # This file and its contents are supplied under the terms of the # Common Development and Distribution License ("CDDL"), version 1.0. # You may only use this file in accordance with the terms of version # 1.0 of the CDDL. # # A full copy of the text of the CDDL should have accompanied this # source. A copy of the CDDL is also available via the Internet at # http://www.illumos.org/license/CDDL. # # # Copyright 2025 Hans Rosenfeld # OBJS += $(COMMON_OBJS) .KEEP_STATE: all: $(PROG) install: all $(ROOTPROG) $(PROG): $(OBJS) $(LINK.c) -o $(PROG) $(OBJS) $(LDLIBS) $(POST_PROCESS) clean: -$(RM) $(CLEANFILES) include $(SRC)/cmd/Makefile.targ # # This file and its contents are supplied under the terms of the # Common Development and Distribution License ("CDDL"), version 1.0. # You may only use this file in accordance with the terms of version # 1.0 of the CDDL. # # A full copy of the text of the CDDL should have accompanied this # source. A copy of the CDDL is also available via the Internet at # http://www.illumos.org/license/CDDL. # # # Copyright 2014 Adam Stevko. All rights reserved. # include $(SRC)/cmd/Makefile.cmd PROG= arcstat .KEEP_STATE: all: $(PROG) install: all .WAIT $(ROOTPROG) clean: $(ROOTBINPROG): $(PROG) $(INS.file) include $(SRC)/cmd/Makefile.targ #!/usr/bin/perl -w # The above invocation line was changed in 0.5 to allow for # interoperability with linux. # # Print out ZFS ARC Statistics exported via kstat(1) # For a definition of fields, or usage, use arctstat.pl -v # # This script is a fork of the original arcstat.pl (0.1) by # Neelakanth Nadgir, originally published on his Sun blog on # 09/18/2007 # http://blogs.sun.com/realneel/entry/zfs_arc_statistics # # This version aims to improve upon the original by adding features # and fixing bugs as needed. This version is maintained by # Mike Harsch and is hosted in a public open source repository: # http://github.com/mharsch/arcstat # # Comments, Questions, or Suggestions are always welcome. # Contact the maintainer at ( mike at harschsystems dot com ) # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Fields have a fixed width. Every interval, we fill the "v" # hash with its corresponding value (v[field]=value) using calculate(). # @hdr is the array of fields that needs to be printed, so we # just iterate over this array and print the values using our pretty printer. # # Copyright (c) 2015 by Delphix. All rights reserved. # use strict; use warnings; use POSIX qw(strftime); use Sun::Solaris::Kstat; use Getopt::Long; use IO::Handle; my %cols = (# HDR => [Size, Scale, Description] "time" =>[8, -1, "Time"], "hits" =>[4, 1000, "ARC reads per second"], "miss" =>[4, 1000, "ARC misses per second"], "read" =>[4, 1000, "Total ARC accesses per second"], "hit%" =>[4, 100, "ARC Hit percentage"], "miss%" =>[5, 100, "ARC miss percentage"], "dhit" =>[4, 1000, "Demand Data hits per second"], "dmis" =>[4, 1000, "Demand Data misses per second"], "dh%" =>[3, 100, "Demand Data hit percentage"], "dm%" =>[3, 100, "Demand Data miss percentage"], "phit" =>[4, 1000, "Prefetch hits per second"], "pmis" =>[4, 1000, "Prefetch misses per second"], "ph%" =>[3, 100, "Prefetch hits percentage"], "pm%" =>[3, 100, "Prefetch miss percentage"], "mhit" =>[4, 1000, "Metadata hits per second"], "mmis" =>[4, 1000, "Metadata misses per second"], "mread" =>[4, 1000, "Metadata accesses per second"], "mh%" =>[3, 100, "Metadata hit percentage"], "mm%" =>[3, 100, "Metadata miss percentage"], "arcsz" =>[5, 1024, "ARC Size"], "c" =>[4, 1024, "ARC Target Size"], "mfu" =>[4, 1000, "MFU List hits per second"], "mru" =>[4, 1000, "MRU List hits per second"], "mfug" =>[4, 1000, "MFU Ghost List hits per second"], "mrug" =>[4, 1000, "MRU Ghost List hits per second"], "eskip" =>[5, 1000, "evict_skip per second"], "mtxmis" =>[6, 1000, "mutex_miss per second"], "dread" =>[5, 1000, "Demand data accesses per second"], "pread" =>[5, 1000, "Prefetch accesses per second"], "l2hits" =>[6, 1000, "L2ARC hits per second"], "l2miss" =>[6, 1000, "L2ARC misses per second"], "l2read" =>[6, 1000, "Total L2ARC accesses per second"], "l2hit%" =>[6, 100, "L2ARC access hit percentage"], "l2miss%" =>[7, 100, "L2ARC access miss percentage"], "l2asize" =>[7, 1024, "Actual (compressed) size of the L2ARC"], "l2size" =>[6, 1024, "Size of the L2ARC"], "l2bytes" =>[7, 1024, "bytes read per second from the L2ARC"], ); my %v=(); my @hdr = qw(time read miss miss% dmis dm% pmis pm% mmis mm% arcsz c); my @xhdr = qw(time mfu mru mfug mrug eskip mtxmis dread pread read); my $int = 1; # Default interval is 1 second my $count = 1; # Default count is 1 my $hdr_intr = 20; # Print header every 20 lines of output my $opfile = ""; my $sep = " "; # Default separator is 2 spaces my $raw_output; my $version = "0.5"; my $l2exist = 0; my $cmd = "Usage: arcstat [-hvxr] [-f fields] [-o file] [-s string] " . "[interval [count]]\n"; my %cur; my %d; my $out; my $kstat = Sun::Solaris::Kstat->new(); STDOUT->autoflush; sub detailed_usage { print STDERR "$cmd\n"; print STDERR "Field definitions are as follows:\n"; foreach my $hdr (keys %cols) { print STDERR sprintf("%11s : %s\n", $hdr, $cols{$hdr}[2]); } exit(1); } sub usage { print STDERR "$cmd\n"; print STDERR "\t -h : Print this help message\n"; print STDERR "\t -v : List all possible field headers " . "and definitions\n"; print STDERR "\t -x : Print extended stats\n"; print STDERR "\t -r : Raw output mode (values not scaled)\n"; print STDERR "\t -f : Specify specific fields to print (see -v)\n"; print STDERR "\t -o : Redirect output to the specified file\n"; print STDERR "\t -s : Override default field separator with custom " . "character or string\n"; print STDERR "\nExamples:\n"; print STDERR "\tarcstat -o /tmp/a.log 2 10\n"; print STDERR "\tarcstat -s \",\" -o /tmp/a.log 2 10\n"; print STDERR "\tarcstat -v\n"; print STDERR "\tarcstat -f time,hit%,dh%,ph%,mh% 1\n"; exit(1); } sub init { my $desired_cols; my $xflag = ''; my $hflag = ''; my $vflag; my $res = GetOptions('x' => \$xflag, 'o=s' => \$opfile, 'help|h|?' => \$hflag, 'v' => \$vflag, 's=s' => \$sep, 'f=s' => \$desired_cols, 'r' => \$raw_output); if (defined $ARGV[0] && defined $ARGV[1]) { $int = $ARGV[0]; $count = $ARGV[1]; } elsif (defined $ARGV[0]) { $int = $ARGV[0]; $count = 0; } usage() if !$res or $hflag or ($xflag and $desired_cols); detailed_usage() if $vflag; @hdr = @xhdr if $xflag; #reset headers to xhdr # we want to capture the stats here, so that we can use them to check # if an L2ARC device exists; but more importantly, so that we print # the stats since boot as the first line of output from main(). snap_stats(); if (defined $cur{"l2_size"}) { $l2exist = 1; } if ($desired_cols) { @hdr = split(/[ ,]+/, $desired_cols); # Now check if they are valid fields my @invalid = (); my @incompat = (); foreach my $ele (@hdr) { if (not exists($cols{$ele})) { push(@invalid, $ele); } elsif (($l2exist == 0) && ($ele =~ /^l2/)) { printf("No L2ARC here\n", $ele); push(@incompat, $ele); } } if (scalar @invalid > 0) { print STDERR "Invalid column definition! -- " . "@invalid\n\n"; usage(); } if (scalar @incompat > 0) { print STDERR "Incompatible field specified -- " . "@incompat\n\n"; usage(); } } if ($opfile) { open($out, ">$opfile") ||die "Cannot open $opfile for writing"; $out->autoflush; select $out; } } # Capture kstat statistics. We maintain 3 hashes, prev, cur, and # d (delta). As their names imply they maintain the previous, current, # and delta (cur - prev) statistics. sub snap_stats { my %prev = %cur; if ($kstat->update()) { printf("\n"); } my $hashref_cur = $kstat->{"zfs"}{0}{"arcstats"}; %cur = %$hashref_cur; foreach my $key (keys %cur) { next if $key =~ /class/; if (defined $prev{$key}) { $d{$key} = $cur{$key} - $prev{$key}; } else { $d{$key} = $cur{$key}; } } } # Pretty print num. Arguments are width, scale, and num sub prettynum { my @suffix = (' ', 'K', 'M', 'G', 'T'); my $num = $_[2] || 0; my $scale = $_[1]; my $sz = $_[0]; my $index = 0; my $save = 0; if ($scale == -1) { #special case for date field return sprintf("%s", $num); } elsif (($num > 0) && ($num < 1)) { #rounding error. return 0 $num = 0; } while ($num > $scale and $index < 5) { $save = $num; $num = $num/$scale; $index++; } return sprintf("%*d", $sz, $num) if ($index == 0); if (($save / $scale) < 10) { return sprintf("%*.1f%s", $sz - 1, $num,$suffix[$index]); } else { return sprintf("%*d%s", $sz - 1, $num,$suffix[$index]); } } sub print_values { foreach my $col (@hdr) { if (not $raw_output) { printf("%s%s", prettynum($cols{$col}[0], $cols{$col}[1], $v{$col}), $sep); } else { printf("%d%s", $v{$col} || 0, $sep); } } printf("\n"); } sub print_header { if (not $raw_output) { foreach my $col (@hdr) { printf("%*s%s", $cols{$col}[0], $col, $sep); } } else { # Don't try to align headers in raw mode foreach my $col (@hdr) { printf("%s%s", $col, $sep); } } printf("\n"); } sub calculate { %v = (); if ($raw_output) { $v{"time"} = strftime("%s", localtime); } else { $v{"time"} = strftime("%H:%M:%S", localtime); } $v{"hits"} = $d{"hits"}/$int; $v{"miss"} = $d{"misses"}/$int; $v{"read"} = $v{"hits"} + $v{"miss"}; $v{"hit%"} = 100 * ($v{"hits"} / $v{"read"}) if $v{"read"} > 0; $v{"miss%"} = 100 - $v{"hit%"} if $v{"read"} > 0; $v{"dhit"} = ($d{"demand_data_hits"} + $d{"demand_metadata_hits"})/$int; $v{"dmis"} = ($d{"demand_data_misses"} + $d{"demand_metadata_misses"})/$int; $v{"dread"} = $v{"dhit"} + $v{"dmis"}; $v{"dh%"} = 100 * ($v{"dhit"} / $v{"dread"}) if $v{"dread"} > 0; $v{"dm%"} = 100 - $v{"dh%"} if $v{"dread"} > 0; $v{"phit"} = ($d{"prefetch_data_hits"} + $d{"prefetch_metadata_hits"})/$int; $v{"pmis"} = ($d{"prefetch_data_misses"} + $d{"prefetch_metadata_misses"})/$int; $v{"pread"} = $v{"phit"} + $v{"pmis"}; $v{"ph%"} = 100 * ($v{"phit"} / $v{"pread"}) if $v{"pread"} > 0; $v{"pm%"} = 100 - $v{"ph%"} if $v{"pread"} > 0; $v{"mhit"} = ($d{"prefetch_metadata_hits"} + $d{"demand_metadata_hits"})/$int; $v{"mmis"} = ($d{"prefetch_metadata_misses"} + $d{"demand_metadata_misses"})/$int; $v{"mread"} = $v{"mhit"} + $v{"mmis"}; $v{"mh%"} = 100 * ($v{"mhit"} / $v{"mread"}) if $v{"mread"} > 0; $v{"mm%"} = 100 - $v{"mh%"} if $v{"mread"} > 0; $v{"arcsz"} = $cur{"size"}; $v{"c"} = $cur{"c"}; $v{"mfu"} = $d{"mfu_hits"}/$int; $v{"mru"} = $d{"mru_hits"}/$int; $v{"mrug"} = $d{"mru_ghost_hits"}/$int; $v{"mfug"} = $d{"mfu_ghost_hits"}/$int; $v{"eskip"} = $d{"evict_skip"}/$int; $v{"mtxmis"} = $d{"mutex_miss"}/$int; if ($l2exist) { $v{"l2hits"} = $d{"l2_hits"}/$int; $v{"l2miss"} = $d{"l2_misses"}/$int; $v{"l2read"} = $v{"l2hits"} + $v{"l2miss"}; $v{"l2hit%"} = 100 * ($v{"l2hits"} / $v{"l2read"}) if $v{"l2read"} > 0; $v{"l2miss%"} = 100 - $v{"l2hit%"} if $v{"l2read"} > 0; $v{"l2size"} = $cur{"l2_size"}; $v{"l2asize"} = $cur{"l2_asize"}; $v{"l2bytes"} = $d{"l2_read_bytes"}/$int; } } sub main { my $i = 0; my $count_flag = 0; init(); if ($count > 0) { $count_flag = 1; } while (1) { print_header() if ($i == 0); calculate(); print_values(); last if ($count_flag == 1 && $count-- <= 1); $i = (($i == $hdr_intr) && (not $raw_output)) ? 0 : $i+1; sleep($int); snap_stats(); } close($out) if defined $out; } &main; /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2012 by Delphix. All rights reserved. */ #include "statcommon.h" #include "dsr.h" #include #include #include #include #include #include #define ARRAY_SIZE(a) (sizeof (a) / sizeof (*a)) /* * The time we delay before retrying after an allocation * failure, in milliseconds */ #define RETRY_DELAY 200 static char *cpu_states[] = { "cpu_ticks_idle", "cpu_ticks_user", "cpu_ticks_kernel", "cpu_ticks_wait" }; static kstat_t * kstat_lookup_read(kstat_ctl_t *kc, char *module, int instance, char *name) { kstat_t *ksp = kstat_lookup(kc, module, instance, name); if (ksp == NULL) return (NULL); if (kstat_read(kc, ksp, NULL) == -1) return (NULL); return (ksp); } /* * Note: the following helpers do not clean up on the failure case, * because it is left to the free_snapshot() in the acquire_snapshot() * failure path. */ static int acquire_cpus(struct snapshot *ss, kstat_ctl_t *kc) { size_t i; ss->s_nr_cpus = sysconf(_SC_CPUID_MAX) + 1; ss->s_cpus = calloc(ss->s_nr_cpus, sizeof (struct cpu_snapshot)); if (ss->s_cpus == NULL) goto out; for (i = 0; i < ss->s_nr_cpus; i++) { kstat_t *ksp; ss->s_cpus[i].cs_id = ID_NO_CPU; ss->s_cpus[i].cs_state = p_online(i, P_STATUS); /* If no valid CPU is present, move on to the next one */ if (ss->s_cpus[i].cs_state == -1) continue; ss->s_cpus[i].cs_id = i; if ((ksp = kstat_lookup_read(kc, "cpu_info", i, NULL)) == NULL) goto out; (void) pset_assign(PS_QUERY, i, &ss->s_cpus[i].cs_pset_id); if (ss->s_cpus[i].cs_pset_id == PS_NONE) ss->s_cpus[i].cs_pset_id = ID_NO_PSET; if (!CPU_ACTIVE(&ss->s_cpus[i])) continue; if ((ksp = kstat_lookup_read(kc, "cpu", i, "vm")) == NULL) goto out; if (kstat_copy(ksp, &ss->s_cpus[i].cs_vm)) goto out; if ((ksp = kstat_lookup_read(kc, "cpu", i, "sys")) == NULL) goto out; if (kstat_copy(ksp, &ss->s_cpus[i].cs_sys)) goto out; } errno = 0; out: return (errno); } static int acquire_psets(struct snapshot *ss) { psetid_t *pids = NULL; struct pset_snapshot *ps; size_t pids_nr; size_t i, j; /* * Careful in this code. We have to use pset_list * twice, but inbetween pids_nr can change at will. * We delay the setting of s_nr_psets until we have * the "final" value of pids_nr. */ if (pset_list(NULL, &pids_nr) < 0) return (errno); if ((pids = calloc(pids_nr, sizeof (psetid_t))) == NULL) goto out; if (pset_list(pids, &pids_nr) < 0) goto out; ss->s_psets = calloc(pids_nr + 1, sizeof (struct pset_snapshot)); if (ss->s_psets == NULL) goto out; ss->s_nr_psets = pids_nr + 1; /* CPUs not in any actual pset */ ps = &ss->s_psets[0]; ps->ps_id = 0; ps->ps_cpus = calloc(ss->s_nr_cpus, sizeof (struct cpu_snapshot *)); if (ps->ps_cpus == NULL) goto out; /* CPUs in a a pset */ for (i = 1; i < ss->s_nr_psets; i++) { ps = &ss->s_psets[i]; ps->ps_id = pids[i - 1]; ps->ps_cpus = calloc(ss->s_nr_cpus, sizeof (struct cpu_snapshot *)); if (ps->ps_cpus == NULL) goto out; } for (i = 0; i < ss->s_nr_psets; i++) { ps = &ss->s_psets[i]; for (j = 0; j < ss->s_nr_cpus; j++) { if (!CPU_ACTIVE(&ss->s_cpus[j])) continue; if (ss->s_cpus[j].cs_pset_id != ps->ps_id) continue; ps->ps_cpus[ps->ps_nr_cpus++] = &ss->s_cpus[j]; } } errno = 0; out: free(pids); return (errno); } static int acquire_intrs(struct snapshot *ss, kstat_ctl_t *kc) { kstat_t *ksp; size_t i = 0; kstat_t *sys_misc; kstat_named_t *clock; /* clock interrupt */ ss->s_nr_intrs = 1; for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) { if (ksp->ks_type == KSTAT_TYPE_INTR) ss->s_nr_intrs++; } ss->s_intrs = calloc(ss->s_nr_intrs, sizeof (struct intr_snapshot)); if (ss->s_intrs == NULL) return (errno); sys_misc = kstat_lookup_read(kc, "unix", 0, "system_misc"); if (sys_misc == NULL) goto out; clock = (kstat_named_t *)kstat_data_lookup(sys_misc, "clk_intr"); if (clock == NULL) goto out; (void) strlcpy(ss->s_intrs[0].is_name, "clock", KSTAT_STRLEN); ss->s_intrs[0].is_total = clock->value.ui32; i = 1; for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) { kstat_intr_t *ki; int j; if (ksp->ks_type != KSTAT_TYPE_INTR) continue; if (kstat_read(kc, ksp, NULL) == -1) goto out; ki = KSTAT_INTR_PTR(ksp); (void) strlcpy(ss->s_intrs[i].is_name, ksp->ks_name, KSTAT_STRLEN); ss->s_intrs[i].is_total = 0; for (j = 0; j < KSTAT_NUM_INTRS; j++) ss->s_intrs[i].is_total += ki->intrs[j]; i++; } errno = 0; out: return (errno); } int acquire_sys(struct snapshot *ss, kstat_ctl_t *kc) { size_t i; kstat_named_t *knp; kstat_t *ksp; if ((ksp = kstat_lookup(kc, "unix", 0, "sysinfo")) == NULL) return (errno); if (kstat_read(kc, ksp, &ss->s_sys.ss_sysinfo) == -1) return (errno); if ((ksp = kstat_lookup(kc, "unix", 0, "vminfo")) == NULL) return (errno); if (kstat_read(kc, ksp, &ss->s_sys.ss_vminfo) == -1) return (errno); if ((ksp = kstat_lookup(kc, "unix", 0, "dnlcstats")) == NULL) return (errno); if (kstat_read(kc, ksp, &ss->s_sys.ss_nc) == -1) return (errno); if ((ksp = kstat_lookup(kc, "unix", 0, "system_misc")) == NULL) return (errno); if (kstat_read(kc, ksp, NULL) == -1) return (errno); knp = (kstat_named_t *)kstat_data_lookup(ksp, "clk_intr"); if (knp == NULL) return (errno); ss->s_sys.ss_ticks = knp->value.l; knp = (kstat_named_t *)kstat_data_lookup(ksp, "deficit"); if (knp == NULL) return (errno); ss->s_sys.ss_deficit = knp->value.l; for (i = 0; i < ss->s_nr_cpus; i++) { if (!CPU_ACTIVE(&ss->s_cpus[i])) continue; if (kstat_add(&ss->s_cpus[i].cs_sys, &ss->s_sys.ss_agg_sys)) return (errno); if (kstat_add(&ss->s_cpus[i].cs_vm, &ss->s_sys.ss_agg_vm)) return (errno); ss->s_nr_active_cpus++; } return (0); } struct snapshot * acquire_snapshot(kstat_ctl_t *kc, int types, struct iodev_filter *iodev_filter) { struct snapshot *ss = NULL; int err; retry: err = 0; /* ensure any partial resources are freed on a retry */ free_snapshot(ss); ss = safe_alloc(sizeof (struct snapshot)); (void) memset(ss, 0, sizeof (struct snapshot)); ss->s_types = types; /* wait for a possibly up-to-date chain */ while (kstat_chain_update(kc) == -1) { if (errno == EAGAIN) (void) poll(NULL, 0, RETRY_DELAY); else fail(1, "kstat_chain_update failed"); } if (!err && (types & SNAP_INTERRUPTS)) err = acquire_intrs(ss, kc); if (!err && (types & (SNAP_CPUS | SNAP_SYSTEM | SNAP_PSETS))) err = acquire_cpus(ss, kc); if (!err && (types & SNAP_PSETS)) err = acquire_psets(ss); if (!err && (types & (SNAP_IODEVS | SNAP_CONTROLLERS | SNAP_IOPATHS_LI | SNAP_IOPATHS_LTI))) err = acquire_iodevs(ss, kc, iodev_filter); if (!err && (types & SNAP_SYSTEM)) err = acquire_sys(ss, kc); switch (err) { case 0: break; case EAGAIN: (void) poll(NULL, 0, RETRY_DELAY); /* a kstat disappeared from under us */ /*FALLTHRU*/ case ENXIO: case ENOENT: goto retry; default: fail(1, "acquiring snapshot failed"); } return (ss); } void free_snapshot(struct snapshot *ss) { size_t i; if (ss == NULL) return; while (ss->s_iodevs) { struct iodev_snapshot *tmp = ss->s_iodevs; ss->s_iodevs = ss->s_iodevs->is_next; free_iodev(tmp); } if (ss->s_cpus) { for (i = 0; i < ss->s_nr_cpus; i++) { free(ss->s_cpus[i].cs_vm.ks_data); free(ss->s_cpus[i].cs_sys.ks_data); } free(ss->s_cpus); } if (ss->s_psets) { for (i = 0; i < ss->s_nr_psets; i++) free(ss->s_psets[i].ps_cpus); free(ss->s_psets); } free(ss->s_sys.ss_agg_sys.ks_data); free(ss->s_sys.ss_agg_vm.ks_data); free(ss); } kstat_ctl_t * open_kstat(void) { kstat_ctl_t *kc; while ((kc = kstat_open()) == NULL) { if (errno == EAGAIN) (void) poll(NULL, 0, RETRY_DELAY); else fail(1, "kstat_open failed"); } return (kc); } void * safe_alloc(size_t size) { void *ptr; while ((ptr = malloc(size)) == NULL) { if (errno == EAGAIN) (void) poll(NULL, 0, RETRY_DELAY); else fail(1, "malloc failed"); } return (ptr); } char * safe_strdup(char *str) { char *ret; if (str == NULL) return (NULL); while ((ret = strdup(str)) == NULL) { if (errno == EAGAIN) (void) poll(NULL, 0, RETRY_DELAY); else fail(1, "malloc failed"); } return (ret); } uint64_t kstat_delta(kstat_t *old, kstat_t *new, char *name) { kstat_named_t *knew = kstat_data_lookup(new, name); if (old && old->ks_data) { kstat_named_t *kold = kstat_data_lookup(old, name); return (knew->value.ui64 - kold->value.ui64); } return (knew->value.ui64); } int kstat_copy(const kstat_t *src, kstat_t *dst) { *dst = *src; if (src->ks_data != NULL) { if ((dst->ks_data = malloc(src->ks_data_size)) == NULL) return (-1); bcopy(src->ks_data, dst->ks_data, src->ks_data_size); } else { dst->ks_data = NULL; dst->ks_data_size = 0; } return (0); } int kstat_add(const kstat_t *src, kstat_t *dst) { size_t i; kstat_named_t *from; kstat_named_t *to; if (dst->ks_data == NULL) return (kstat_copy(src, dst)); from = src->ks_data; to = dst->ks_data; for (i = 0; i < src->ks_ndata; i++) { /* "addition" makes little sense for strings */ if (from->data_type != KSTAT_DATA_CHAR && from->data_type != KSTAT_DATA_STRING) (to)->value.ui64 += (from)->value.ui64; from++; to++; } return (0); } uint64_t cpu_ticks_delta(kstat_t *old, kstat_t *new) { uint64_t ticks = 0; size_t i; for (i = 0; i < ARRAY_SIZE(cpu_states); i++) ticks += kstat_delta(old, new, cpu_states[i]); return (ticks); } int nr_active_cpus(struct snapshot *ss) { size_t i; int count = 0; for (i = 0; i < ss->s_nr_cpus; i++) { if (CPU_ACTIVE(&ss->s_cpus[i])) count++; } return (count); } /* * Return the number of ticks delta between two hrtime_t * values. Attempt to cater for various kinds of overflow * in hrtime_t - no matter how improbable. */ uint64_t hrtime_delta(hrtime_t old, hrtime_t new) { uint64_t del; if ((new >= old) && (old >= 0L)) return (new - old); else { /* * We've overflowed the positive portion of an * hrtime_t. */ if (new < 0L) { /* * The new value is negative. Handle the * case where the old value is positive or * negative. */ uint64_t n1; uint64_t o1; n1 = -new; if (old > 0L) return (n1 - old); else { o1 = -old; del = n1 - o1; return (del); } } else { /* * Either we've just gone from being negative * to positive *or* the last entry was positive * and the new entry is also positive but *less* * than the old entry. This implies we waited * quite a few days on a very fast system between * iostat displays. */ if (old < 0L) { uint64_t o2; o2 = -old; del = UINT64_MAX - o2; } else { del = UINT64_MAX - old; } del += new; return (del); } } } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved. */ #include "statcommon.h" #include "dsr.h" #include #include #include #include #include #include #include #include #include static void insert_iodev(struct snapshot *ss, struct iodev_snapshot *iodev); static struct iodev_snapshot * make_controller(int cid) { struct iodev_snapshot *new; new = safe_alloc(sizeof (struct iodev_snapshot)); (void) memset(new, 0, sizeof (struct iodev_snapshot)); new->is_type = IODEV_CONTROLLER; new->is_id.id = cid; new->is_parent_id.id = IODEV_NO_ID; (void) snprintf(new->is_name, sizeof (new->is_name), "c%d", cid); return (new); } static struct iodev_snapshot * find_iodev_by_name(struct iodev_snapshot *list, const char *name) { struct iodev_snapshot *pos; struct iodev_snapshot *pos2; for (pos = list; pos; pos = pos->is_next) { if (strcmp(pos->is_name, name) == 0) return (pos); pos2 = find_iodev_by_name(pos->is_children, name); if (pos2 != NULL) return (pos2); } return (NULL); } static enum iodev_type parent_iodev_type(enum iodev_type type) { switch (type) { case IODEV_CONTROLLER: return (0); case IODEV_IOPATH_LT: return (0); case IODEV_IOPATH_LI: return (0); case IODEV_NFS: return (0); case IODEV_TAPE: return (0); case IODEV_IOPATH_LTI: return (IODEV_DISK); case IODEV_DISK: return (IODEV_CONTROLLER); case IODEV_PARTITION: return (IODEV_DISK); } return (IODEV_UNKNOWN); } static int id_match(struct iodev_id *id1, struct iodev_id *id2) { return (id1->id == id2->id && strcmp(id1->tid, id2->tid) == 0); } static struct iodev_snapshot * find_parent(struct snapshot *ss, struct iodev_snapshot *iodev) { enum iodev_type parent_type = parent_iodev_type(iodev->is_type); struct iodev_snapshot *pos; struct iodev_snapshot *pos2; if (parent_type == 0 || parent_type == IODEV_UNKNOWN) return (NULL); if (iodev->is_parent_id.id == IODEV_NO_ID && iodev->is_parent_id.tid[0] == '\0') return (NULL); if (parent_type == IODEV_CONTROLLER) { for (pos = ss->s_iodevs; pos; pos = pos->is_next) { if (pos->is_type != IODEV_CONTROLLER) continue; if (pos->is_id.id != iodev->is_parent_id.id) continue; return (pos); } if (!(ss->s_types & SNAP_CONTROLLERS)) return (NULL); pos = make_controller(iodev->is_parent_id.id); insert_iodev(ss, pos); return (pos); } /* IODEV_DISK parent */ for (pos = ss->s_iodevs; pos; pos = pos->is_next) { if (id_match(&iodev->is_parent_id, &pos->is_id) && pos->is_type == IODEV_DISK) return (pos); if (pos->is_type != IODEV_CONTROLLER) continue; for (pos2 = pos->is_children; pos2; pos2 = pos2->is_next) { if (pos2->is_type != IODEV_DISK) continue; if (id_match(&iodev->is_parent_id, &pos2->is_id)) return (pos2); } } return (NULL); } /* * Introduce an index into the list to speed up insert_into looking for the * right position in the list. This index is an AVL tree of all the * iodev_snapshot in the list. */ static int avl_iodev_cmp(const void* is1, const void* is2) { int c = iodev_cmp((struct iodev_snapshot *)is1, (struct iodev_snapshot *)is2); if (c > 0) return (1); if (c < 0) return (-1); return (0); } static void ix_new_list(struct iodev_snapshot *elem) { avl_tree_t *l = malloc(sizeof (avl_tree_t)); elem->avl_list = l; if (l == NULL) return; avl_create(l, avl_iodev_cmp, sizeof (struct iodev_snapshot), offsetof(struct iodev_snapshot, avl_link)); avl_add(l, elem); } static void ix_list_del(struct iodev_snapshot *elem) { avl_tree_t *l = elem->avl_list; if (l == NULL) return; elem->avl_list = NULL; avl_remove(l, elem); if (avl_numnodes(l) == 0) { avl_destroy(l); free(l); } } static void ix_insert_here(struct iodev_snapshot *pos, struct iodev_snapshot *elem, int ba) { avl_tree_t *l = pos->avl_list; elem->avl_list = l; if (l == NULL) return; avl_insert_here(l, elem, pos, ba); } static void list_del(struct iodev_snapshot **list, struct iodev_snapshot *pos) { ix_list_del(pos); if (*list == pos) *list = pos->is_next; if (pos->is_next) pos->is_next->is_prev = pos->is_prev; if (pos->is_prev) pos->is_prev->is_next = pos->is_next; pos->is_prev = pos->is_next = NULL; } static void insert_before(struct iodev_snapshot **list, struct iodev_snapshot *pos, struct iodev_snapshot *new) { if (pos == NULL) { new->is_prev = new->is_next = NULL; *list = new; ix_new_list(new); return; } new->is_next = pos; new->is_prev = pos->is_prev; if (pos->is_prev) pos->is_prev->is_next = new; else *list = new; pos->is_prev = new; ix_insert_here(pos, new, AVL_BEFORE); } static void insert_after(struct iodev_snapshot **list, struct iodev_snapshot *pos, struct iodev_snapshot *new) { if (pos == NULL) { new->is_prev = new->is_next = NULL; *list = new; ix_new_list(new); return; } new->is_next = pos->is_next; new->is_prev = pos; if (pos->is_next) pos->is_next->is_prev = new; pos->is_next = new; ix_insert_here(pos, new, AVL_AFTER); } static void insert_into(struct iodev_snapshot **list, struct iodev_snapshot *iodev) { struct iodev_snapshot *tmp = *list; avl_tree_t *l; void *p; avl_index_t where; if (*list == NULL) { *list = iodev; ix_new_list(iodev); return; } /* * Optimize the search: instead of walking the entire list * (which can contain thousands of nodes), search in the AVL * tree the nearest node and reposition the startup point to * this node rather than always starting from the beginning * of the list. */ l = tmp->avl_list; if (l != NULL) { p = avl_find(l, iodev, &where); if (p == NULL) { p = avl_nearest(l, where, AVL_BEFORE); } if (p != NULL) { tmp = (struct iodev_snapshot *)p; } } for (;;) { if (iodev_cmp(tmp, iodev) > 0) { insert_before(list, tmp, iodev); return; } if (tmp->is_next == NULL) break; tmp = tmp->is_next; } insert_after(list, tmp, iodev); } static int disk_or_partition(enum iodev_type type) { return (type == IODEV_DISK || type == IODEV_PARTITION); } static int disk_or_partition_or_iopath(enum iodev_type type) { return (type == IODEV_DISK || type == IODEV_PARTITION || type == IODEV_IOPATH_LTI); } static void insert_iodev(struct snapshot *ss, struct iodev_snapshot *iodev) { struct iodev_snapshot *parent = find_parent(ss, iodev); struct iodev_snapshot **list; if (parent != NULL) { list = &parent->is_children; parent->is_nr_children++; } else { list = &ss->s_iodevs; ss->s_nr_iodevs++; } insert_into(list, iodev); } /* return 1 if dev passes filter */ static int iodev_match(struct iodev_snapshot *dev, struct iodev_filter *df) { int is_floppy = (strncmp(dev->is_name, "fd", 2) == 0); char *isn, *ispn, *ifn; char *path; int ifnl; size_t i; /* no filter, pass */ if (df == NULL) return (1); /* pass */ /* no filtered names, pass if not floppy and skipped */ if (df->if_nr_names == 0) return (!(df->if_skip_floppy && is_floppy)); isn = dev->is_name; ispn = dev->is_pretty; for (i = 0; i < df->if_nr_names; i++) { ifn = df->if_names[i]; ifnl = strlen(ifn); path = strchr(ifn, '.'); if ((strcmp(isn, ifn) == 0) || (ispn && (strcmp(ispn, ifn) == 0))) return (1); /* pass */ /* if filter is a path allow partial match */ if (path && ((strncmp(isn, ifn, ifnl) == 0) || (ispn && (strncmp(ispn, ifn, ifnl) == 0)))) return (1); /* pass */ } return (0); /* fail */ } /* return 1 if path is an mpxio path associated with dev */ static int iodev_path_match(struct iodev_snapshot *dev, struct iodev_snapshot *path) { char *dn, *pn; int dnl; dn = dev->is_name; pn = path->is_name; dnl = strlen(dn); if ((strncmp(pn, dn, dnl) == 0) && (pn[dnl] == '.')) return (1); /* yes */ return (0); /* no */ } /* select which I/O devices to collect stats for */ static void choose_iodevs(struct snapshot *ss, struct iodev_snapshot *iodevs, struct iodev_filter *df) { struct iodev_snapshot *pos, *ppos, *tmp, *ptmp; int nr_iodevs; int nr_iodevs_orig; nr_iodevs = df ? df->if_max_iodevs : UNLIMITED_IODEVS; nr_iodevs_orig = nr_iodevs; if (nr_iodevs == UNLIMITED_IODEVS) nr_iodevs = INT_MAX; /* add the full matches */ pos = iodevs; while (pos && nr_iodevs) { tmp = pos; pos = pos->is_next; if (!iodev_match(tmp, df)) continue; /* failed full match */ list_del(&iodevs, tmp); insert_iodev(ss, tmp); /* * Add all mpxio paths associated with match above. Added * paths don't count against nr_iodevs. */ if (strchr(tmp->is_name, '.') == NULL) { ppos = iodevs; while (ppos) { ptmp = ppos; ppos = ppos->is_next; if (!iodev_path_match(tmp, ptmp)) continue; /* not an mpxio path */ list_del(&iodevs, ptmp); insert_iodev(ss, ptmp); if (pos == ptmp) pos = ppos; } } nr_iodevs--; } /* * If we had a filter, and *nothing* passed the filter then we * don't want to fill the remaining slots - it is just confusing * if we don that, it makes it look like the filter code is broken. */ if ((df->if_nr_names == 0) || (nr_iodevs != nr_iodevs_orig)) { /* now insert any iodevs into the remaining slots */ pos = iodevs; while (pos && nr_iodevs) { tmp = pos; pos = pos->is_next; if (df->if_skip_floppy && strncmp(tmp->is_name, "fd", 2) == 0) continue; list_del(&iodevs, tmp); insert_iodev(ss, tmp); --nr_iodevs; } } /* clear the unwanted ones */ pos = iodevs; while (pos) { struct iodev_snapshot *tmp = pos; pos = pos->is_next; free_iodev(tmp); } } static int collate_controller(struct iodev_snapshot *controller, struct iodev_snapshot *disk) { controller->is_stats.nread += disk->is_stats.nread; controller->is_stats.nwritten += disk->is_stats.nwritten; controller->is_stats.reads += disk->is_stats.reads; controller->is_stats.writes += disk->is_stats.writes; controller->is_stats.wtime += disk->is_stats.wtime; controller->is_stats.wlentime += disk->is_stats.wlentime; controller->is_stats.rtime += disk->is_stats.rtime; controller->is_stats.rlentime += disk->is_stats.rlentime; controller->is_crtime += disk->is_crtime; controller->is_snaptime += disk->is_snaptime; if (kstat_add(&disk->is_errors, &controller->is_errors)) return (errno); return (0); } static int acquire_iodev_stats(struct iodev_snapshot *list, kstat_ctl_t *kc) { struct iodev_snapshot *pos; int err = 0; for (pos = list; pos; pos = pos->is_next) { /* controllers don't have stats (yet) */ if (pos->is_ksp != NULL) { if (kstat_read(kc, pos->is_ksp, &pos->is_stats) == -1) return (errno); /* make sure crtime/snaptime is updated */ pos->is_crtime = pos->is_ksp->ks_crtime; pos->is_snaptime = pos->is_ksp->ks_snaptime; } if ((err = acquire_iodev_stats(pos->is_children, kc))) return (err); if (pos->is_type == IODEV_CONTROLLER) { struct iodev_snapshot *pos2 = pos->is_children; for (; pos2; pos2 = pos2->is_next) { if ((err = collate_controller(pos, pos2))) return (err); } } } return (0); } static int acquire_iodev_errors(struct snapshot *ss, kstat_ctl_t *kc) { kstat_t *ksp; for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) { char kstat_name[KSTAT_STRLEN]; char *dname = kstat_name; char *ename = ksp->ks_name; struct iodev_snapshot *iodev; if (ksp->ks_type != KSTAT_TYPE_NAMED) continue; if (strncmp(ksp->ks_class, "device_error", 12) != 0 && strncmp(ksp->ks_class, "iopath_error", 12) != 0) continue; /* * Some drivers may not follow the naming convention * for error kstats (i.e., drivername,err) so * be sure we don't walk off the end. */ while (*ename && *ename != ',') { *dname = *ename; dname++; ename++; } *dname = '\0'; iodev = find_iodev_by_name(ss->s_iodevs, kstat_name); if (iodev == NULL) continue; if (kstat_read(kc, ksp, NULL) == -1) return (errno); if (kstat_copy(ksp, &iodev->is_errors) == -1) return (errno); } return (0); } static void get_ids(struct iodev_snapshot *iodev, const char *pretty) { int ctr, disk, slice, ret; char *target; const char *p1; const char *p2; if (pretty == NULL) return; if (sscanf(pretty, "c%d", &ctr) != 1) return; p1 = pretty; while (*p1 && *p1 != 't') ++p1; if (!*p1) return; ++p1; p2 = p1; while (*p2 && *p2 != 'd') ++p2; if (!*p2 || p2 == p1) return; target = safe_alloc(1 + p2 - p1); (void) strlcpy(target, p1, 1 + p2 - p1); ret = sscanf(p2, "d%d%*[sp]%d", &disk, &slice); if (ret == 2 && iodev->is_type == IODEV_PARTITION) { iodev->is_id.id = slice; iodev->is_parent_id.id = disk; (void) strlcpy(iodev->is_parent_id.tid, target, KSTAT_STRLEN); } else if (ret == 1) { if (iodev->is_type == IODEV_DISK) { iodev->is_id.id = disk; (void) strlcpy(iodev->is_id.tid, target, KSTAT_STRLEN); iodev->is_parent_id.id = ctr; } else if (iodev->is_type == IODEV_IOPATH_LTI) { iodev->is_parent_id.id = disk; (void) strlcpy(iodev->is_parent_id.tid, target, KSTAT_STRLEN); } } free(target); } static void get_pretty_name(enum snapshot_types types, struct iodev_snapshot *iodev, kstat_ctl_t *kc) { disk_list_t *dl; char *pretty = NULL; if (iodev->is_type == IODEV_NFS) { if (!(types & SNAP_IODEV_PRETTY)) return; iodev->is_pretty = lookup_nfs_name(iodev->is_name, kc); return; } /* lookup/translate the kstat name */ dl = lookup_ks_name(iodev->is_name, (types & SNAP_IODEV_DEVID) ? 1 : 0); if (dl == NULL) return; if (dl->dsk) pretty = safe_strdup(dl->dsk); if (types & SNAP_IODEV_PRETTY) { if (dl->dname) iodev->is_dname = safe_strdup(dl->dname); } if (dl->devidstr) iodev->is_devid = safe_strdup(dl->devidstr); get_ids(iodev, pretty); /* * we fill in pretty name wether it is asked for or not because * it could be used in a filter by match_iodevs. */ iodev->is_pretty = pretty; } static enum iodev_type get_iodev_type(kstat_t *ksp) { if (strcmp(ksp->ks_class, "disk") == 0) return (IODEV_DISK); if (strcmp(ksp->ks_class, "partition") == 0) return (IODEV_PARTITION); if (strcmp(ksp->ks_class, "nfs") == 0) return (IODEV_NFS); if (strcmp(ksp->ks_class, "iopath") == 0) return (IODEV_IOPATH_LTI); if (strcmp(ksp->ks_class, "tape") == 0) return (IODEV_TAPE); return (IODEV_UNKNOWN); } /* get the lun/target/initiator from the name, return 1 on success */ static int get_lti(char *s, char *lname, int *l, char *tname, int *t, char *iname, int *i) { int num = 0; num = sscanf(s, "%[a-z]%d%*[.]%[a-z]%d%*[.]%[a-z_]%d", lname, l, tname, t, iname, i); return ((num == 6) ? 1 : 0); } /* get the lun, target, and initiator name and instance */ static void get_path_info(struct iodev_snapshot *io, char *mod, size_t modlen, int *type, int *inst, char *name, size_t size) { /* * If it is iopath or ssd then pad the name with i/t/l so we can sort * by alpha order and set type for IOPATH to DISK since we want to * have it grouped with its ssd parent. The lun can be 5 digits, * the target can be 4 digits, and the initiator can be 3 digits and * the padding is done appropriately for string comparisons. */ if (disk_or_partition_or_iopath(io->is_type)) { int i1, t1, l1; char tname[KSTAT_STRLEN], iname[KSTAT_STRLEN]; char *ptr, lname[KSTAT_STRLEN]; i1 = t1 = l1 = 0; (void) get_lti(io->is_name, lname, &l1, tname, &t1, iname, &i1); *type = io->is_type; if (io->is_type == IODEV_DISK) { (void) snprintf(name, size, "%s%05d", lname, l1); } else if (io->is_type == IODEV_PARTITION) { ptr = strchr(io->is_name, ','); (void) snprintf(name, size, "%s%05d%s", lname, l1, ptr); } else { (void) snprintf(name, size, "%s%05d.%s%04d.%s%03d", lname, l1, tname, t1, iname, i1); /* set to disk so we sort with disks */ *type = IODEV_DISK; } (void) strlcpy(mod, lname, modlen); *inst = l1; } else { (void) strlcpy(mod, io->is_module, modlen); (void) strlcpy(name, io->is_name, size); *type = io->is_type; *inst = io->is_instance; } } int iodev_cmp(struct iodev_snapshot *io1, struct iodev_snapshot *io2) { int type1, type2; int inst1, inst2; char name1[KSTAT_STRLEN], name2[KSTAT_STRLEN]; char mod1[KSTAT_STRLEN], mod2[KSTAT_STRLEN]; get_path_info(io1, mod1, sizeof (mod1), &type1, &inst1, name1, sizeof (name1)); get_path_info(io2, mod2, sizeof (mod2), &type2, &inst2, name2, sizeof (name2)); if ((!disk_or_partition(type1)) || (!disk_or_partition(type2))) { /* neutral sort order between disk and part */ if (type1 < type2) { return (-1); } if (type1 > type2) { return (1); } } /* controller doesn't have ksp */ if (io1->is_ksp && io2->is_ksp) { if (strcmp(mod1, mod2) != 0) { return (strcmp(mod1, mod2)); } if (inst1 < inst2) { return (-1); } if (inst1 > inst2) { return (1); } } else { if (io1->is_id.id < io2->is_id.id) { return (-1); } if (io1->is_id.id > io2->is_id.id) { return (1); } } return (strcmp(name1, name2)); } /* update the target reads and writes */ static void update_target(struct iodev_snapshot *tgt, struct iodev_snapshot *path) { tgt->is_stats.reads += path->is_stats.reads; tgt->is_stats.writes += path->is_stats.writes; tgt->is_stats.nread += path->is_stats.nread; tgt->is_stats.nwritten += path->is_stats.nwritten; tgt->is_stats.wcnt += path->is_stats.wcnt; tgt->is_stats.rcnt += path->is_stats.rcnt; /* * Stash the t_delta in the crtime for use in show_disk * NOTE: this can't be done in show_disk because the * itl entry is removed for the old format */ tgt->is_crtime += hrtime_delta(path->is_crtime, path->is_snaptime); tgt->is_snaptime += path->is_snaptime; tgt->is_nr_children += 1; } /* * Create a new synthetic device entry of the specified type. The supported * synthetic types are IODEV_IOPATH_LT and IODEV_IOPATH_LI. */ static struct iodev_snapshot * make_extended_device(int type, struct iodev_snapshot *old) { struct iodev_snapshot *tptr = NULL; char *ptr; int lun, tgt, initiator; char lun_name[KSTAT_STRLEN]; char tgt_name[KSTAT_STRLEN]; char initiator_name[KSTAT_STRLEN]; if (old == NULL) return (NULL); if (get_lti(old->is_name, lun_name, &lun, tgt_name, &tgt, initiator_name, &initiator) != 1) { return (NULL); } tptr = safe_alloc(sizeof (*old)); bzero(tptr, sizeof (*old)); if (old->is_pretty != NULL) { tptr->is_pretty = safe_alloc(strlen(old->is_pretty) + 1); (void) strcpy(tptr->is_pretty, old->is_pretty); } bcopy(&old->is_parent_id, &tptr->is_parent_id, sizeof (old->is_parent_id)); tptr->is_type = type; if (type == IODEV_IOPATH_LT) { /* make new synthetic entry that is the LT */ /* set the id to the target id */ tptr->is_id.id = tgt; (void) snprintf(tptr->is_id.tid, sizeof (tptr->is_id.tid), "%s%d", tgt_name, tgt); (void) snprintf(tptr->is_name, sizeof (tptr->is_name), "%s%d.%s%d", lun_name, lun, tgt_name, tgt); if (old->is_pretty) { ptr = strrchr(tptr->is_pretty, '.'); if (ptr) *ptr = '\0'; } } else if (type == IODEV_IOPATH_LI) { /* make new synthetic entry that is the LI */ /* set the id to the initiator number */ tptr->is_id.id = initiator; (void) snprintf(tptr->is_id.tid, sizeof (tptr->is_id.tid), "%s%d", initiator_name, initiator); (void) snprintf(tptr->is_name, sizeof (tptr->is_name), "%s%d.%s%d", lun_name, lun, initiator_name, initiator); if (old->is_pretty) { ptr = strchr(tptr->is_pretty, '.'); if (ptr) (void) snprintf(ptr + 1, strlen(tptr->is_pretty) + 1, "%s%d", initiator_name, initiator); } } return (tptr); } /* * This is to get the original -X LI format (e.g. ssd1.fp0). When an LTI kstat * is found - traverse the children looking for the same initiator and sum * them up. Add an LI entry and delete all of the LTI entries with the same * initiator. */ static int create_li_delete_lti(struct snapshot *ss, struct iodev_snapshot *list) { struct iodev_snapshot *pos, *entry, *parent; int lun, tgt, initiator; char lun_name[KSTAT_STRLEN]; char tgt_name[KSTAT_STRLEN]; char initiator_name[KSTAT_STRLEN]; int err; for (entry = list; entry; entry = entry->is_next) { if ((err = create_li_delete_lti(ss, entry->is_children)) != 0) return (err); if (entry->is_type == IODEV_IOPATH_LTI) { parent = find_parent(ss, entry); if (get_lti(entry->is_name, lun_name, &lun, tgt_name, &tgt, initiator_name, &initiator) != 1) { return (1); } pos = (parent == NULL) ? NULL : parent->is_children; for (; pos; pos = pos->is_next) { if (pos->is_id.id != -1 && pos->is_id.id == initiator && pos->is_type == IODEV_IOPATH_LI) { /* found the same initiator */ update_target(pos, entry); list_del(&parent->is_children, entry); free_iodev(entry); parent->is_nr_children--; entry = pos; break; } } if (!pos) { /* make the first LI entry */ pos = make_extended_device( IODEV_IOPATH_LI, entry); update_target(pos, entry); if (parent) { insert_before(&parent->is_children, entry, pos); list_del(&parent->is_children, entry); free_iodev(entry); } else { insert_before(&ss->s_iodevs, entry, pos); list_del(&ss->s_iodevs, entry); free_iodev(entry); } entry = pos; } } } return (0); } /* * We have the LTI kstat, now add an entry for the LT that sums up all of * the LTI's with the same target(t). */ static int create_lt(struct snapshot *ss, struct iodev_snapshot *list) { struct iodev_snapshot *entry, *parent, *pos; int lun, tgt, initiator; char lun_name[KSTAT_STRLEN]; char tgt_name[KSTAT_STRLEN]; char initiator_name[KSTAT_STRLEN]; int err; for (entry = list; entry; entry = entry->is_next) { if ((err = create_lt(ss, entry->is_children)) != 0) return (err); if (entry->is_type == IODEV_IOPATH_LTI) { parent = find_parent(ss, entry); if (get_lti(entry->is_name, lun_name, &lun, tgt_name, &tgt, initiator_name, &initiator) != 1) { return (1); } pos = (parent == NULL) ? NULL : parent->is_children; for (; pos; pos = pos->is_next) { if (pos->is_id.id != -1 && pos->is_id.id == tgt && pos->is_type == IODEV_IOPATH_LT) { /* found the same target */ update_target(pos, entry); break; } } if (!pos) { pos = make_extended_device( IODEV_IOPATH_LT, entry); update_target(pos, entry); if (parent) { insert_before(&parent->is_children, entry, pos); parent->is_nr_children++; } else { insert_before(&ss->s_iodevs, entry, pos); } } } } return (0); } /* Find the longest is_name field to aid formatting of output */ static int iodevs_is_name_maxlen(struct iodev_snapshot *list) { struct iodev_snapshot *entry; int max = 0, cmax, len; for (entry = list; entry; entry = entry->is_next) { cmax = iodevs_is_name_maxlen(entry->is_children); max = (cmax > max) ? cmax : max; len = strlen(entry->is_name); max = (len > max) ? len : max; } return (max); } int acquire_iodevs(struct snapshot *ss, kstat_ctl_t *kc, struct iodev_filter *df) { kstat_t *ksp; struct iodev_snapshot *pos; struct iodev_snapshot *list = NULL; int err = 0; ss->s_nr_iodevs = 0; ss->s_iodevs_is_name_maxlen = 0; /* * Call cleanup_iodevs_snapshot() so that a cache miss in * lookup_ks_name() will result in a fresh snapshot. */ cleanup_iodevs_snapshot(); for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) { enum iodev_type type; if (ksp->ks_type != KSTAT_TYPE_IO) continue; /* e.g. "usb_byte_count" is not handled */ if ((type = get_iodev_type(ksp)) == IODEV_UNKNOWN) continue; if (df && !(type & df->if_allowed_types)) continue; if ((pos = malloc(sizeof (struct iodev_snapshot))) == NULL) { err = errno; goto out; } (void) memset(pos, 0, sizeof (struct iodev_snapshot)); pos->is_type = type; pos->is_crtime = ksp->ks_crtime; pos->is_snaptime = ksp->ks_snaptime; pos->is_id.id = IODEV_NO_ID; pos->is_parent_id.id = IODEV_NO_ID; pos->is_ksp = ksp; pos->is_instance = ksp->ks_instance; (void) strlcpy(pos->is_module, ksp->ks_module, KSTAT_STRLEN); (void) strlcpy(pos->is_name, ksp->ks_name, KSTAT_STRLEN); get_pretty_name(ss->s_types, pos, kc); /* * We must insert in sort order so e.g. vmstat -l * chooses in order. */ insert_into(&list, pos); } choose_iodevs(ss, list, df); /* before acquire_stats for collate_controller()'s benefit */ if (ss->s_types & SNAP_IODEV_ERRORS) { if ((err = acquire_iodev_errors(ss, kc)) != 0) goto out; } if ((err = acquire_iodev_stats(ss->s_iodevs, kc)) != 0) goto out; if (ss->s_types & SNAP_IOPATHS_LTI) { /* * -Y: kstats are LTI, need to create a synthetic LT * for -Y output. */ if ((err = create_lt(ss, ss->s_iodevs)) != 0) { return (err); } } if (ss->s_types & SNAP_IOPATHS_LI) { /* * -X: kstats are LTI, need to create a synthetic LI and * delete the LTI for -X output */ if ((err = create_li_delete_lti(ss, ss->s_iodevs)) != 0) { return (err); } } /* determine width of longest is_name */ ss->s_iodevs_is_name_maxlen = iodevs_is_name_maxlen(ss->s_iodevs); err = 0; out: return (err); } void free_iodev(struct iodev_snapshot *iodev) { while (iodev->is_children) { struct iodev_snapshot *tmp = iodev->is_children; iodev->is_children = iodev->is_children->is_next; free_iodev(tmp); } if (iodev->avl_list) { avl_remove(iodev->avl_list, iodev); if (avl_numnodes(iodev->avl_list) == 0) { avl_destroy(iodev->avl_list); free(iodev->avl_list); } } free(iodev->is_errors.ks_data); free(iodev->is_pretty); free(iodev->is_dname); free(iodev->is_devid); free(iodev); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include "statcommon.h" #include #include #include #include #include extern char *cmdname; extern int caught_cont; /*PRINTFLIKE2*/ void fail(int do_perror, char *message, ...) { va_list args; int save_errno = errno; va_start(args, message); (void) fprintf(stderr, "%s: ", cmdname); (void) vfprintf(stderr, message, args); va_end(args); if (do_perror) (void) fprintf(stderr, ": %s", strerror(save_errno)); (void) fprintf(stderr, "\n"); exit(2); } /* * Sleep until *wakeup + interval, keeping cadence where desired * * *wakeup - The time we last wanted to wake up. Updated. * interval - We want to sleep until *wakeup + interval * forever - Running for infinite periods, so cadence not important * *caught_cont - Global set by signal handler if we got a SIGCONT */ void sleep_until(hrtime_t *wakeup, hrtime_t interval, int forever, int *caught_cont) { hrtime_t now, pause, pause_left; struct timespec pause_tv; int status; now = gethrtime(); pause = *wakeup + interval - now; if (pause <= 0 || pause < (interval / 4)) if (forever || *caught_cont) { /* Reset our cadence (see comment below) */ *wakeup = now + interval; pause = interval; } else { /* * If we got here, then the time between the * output we just did, and the scheduled time * for the next output is < 1/4 of our requested * interval AND the number of intervals has been * requested AND we have never caught a SIGCONT * (so we have never been suspended). In this * case, we'll try to stay to the desired * cadence, and we will pause for 1/2 the normal * interval this time. */ pause = interval / 2; *wakeup += interval; } else *wakeup += interval; if (pause < 1000) /* Near enough */ return; /* Now do the actual sleep */ pause_left = pause; do { pause_tv.tv_sec = pause_left / NANOSEC; pause_tv.tv_nsec = pause_left % NANOSEC; status = nanosleep(&pause_tv, (struct timespec *)NULL); if (status < 0) if (errno == EINTR) { now = gethrtime(); pause_left = *wakeup - now; if (pause_left < 1000) /* Near enough */ return; } else { fail(1, "nanosleep failed"); } } while (status != 0); } /* * Signal handler - so we can be aware of SIGCONT */ void cont_handler(int sig_number) { /* Re-set the signal handler */ (void) signal(sig_number, cont_handler); caught_cont = 1; } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright 2016 Nexenta Systems, Inc. */ #include #include /* * Dependent on types.h, but not including it... */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dsr.h" #include "statcommon.h" /* where we get kstat name translation information from */ static di_node_t di_root; /* from di_init: for devid */ static di_dim_t di_dim; /* from di_dim_init: for /dev names */ static int scsi_vhci_fd = -1; /* from scsi_vhci: for mpxio path */ /* disk/tape/misc info */ typedef struct { char *minor_name; int minor_isdisk; } minor_match_t; static minor_match_t mm_disk = {"a", 1}; static minor_match_t mm_tape = {"", 0}; static minor_match_t mm_misc = {"0", 0}; static minor_match_t *mma_disk_tape_misc[] = {&mm_disk, &mm_tape, &mm_misc, NULL}; #define DISKLIST_MOD 256 /* ^2 instance mod hash */ static disk_list_t *disklist[DISKLIST_MOD]; /* nfs info */ extern kstat_ctl_t *kc; extern mnt_t *nfs; static int nfs_tried; static char *get_nfs_by_minor(uint_t); static char *cur_hostname(uint_t, kstat_ctl_t *); static char *cur_special(char *, char *); /* * Clear the snapshot so a cache miss in lookup_ks_name() will cause a fresh * snapshot in drvinstpart2dev(). */ void cleanup_iodevs_snapshot() { if (di_dim) { di_dim_fini(di_dim); di_dim = NULL; } if (di_root) { di_fini(di_root); di_root = DI_NODE_NIL; } nfs_tried = 0; } /* * Find information for (driver, instance) device: return zero on failure. * * NOTE: Failure of drvinstpart2dev works out OK for the caller if the kstat * name is the same as public name: the caller will just use kstat name. */ static int drvinstpart2dev(char *driver, int instance, char *part, char **devpathp, char **adevpathp, char **devidp) { minor_match_t *mm = NULL, **mma = mma_disk_tape_misc; char *devpath = NULL; char *devid; char *devicespath; di_node_t node; /* setup "no result" return values */ if (devpathp != NULL) *devpathp = NULL; if (adevpathp != NULL) *adevpathp = NULL; if (devidp != NULL) *devidp = NULL; /* take snapshot if not established */ if (di_dim == NULL) { di_dim = di_dim_init(); if (di_dim == NULL) return (0); } if (part != NULL) { devpath = di_dim_path_dev(di_dim, driver, instance, part); } else { /* Try to find a minor_match that works */ for (mm = *mma++; mm != NULL; mm = *mma++) { if ((devpath = di_dim_path_dev(di_dim, driver, instance, mm->minor_name)) != NULL) break; } } if (devpath == NULL) return (0); /* * At this point we have a devpath result. Return the information about * the result that the caller is asking for. */ if (devpathp != NULL) /* devpath */ *devpathp = safe_strdup(devpath); if (adevpathp != NULL) { /* abbreviated devpath */ char *a; a = strrchr(devpath, '/'); if (a == NULL) { free(devpath); return (0); } a++; if (part == NULL && mm->minor_isdisk) { /* * For disk kstats without a partition we return the * last component with trailing "s#" or "p#" stripped * off (i.e. partition/slice information is removed). * For example for devpath of "/dev/dsk/c0t0d0s0" the * abbreviated devpath would be "c0t0d0". */ char *s; if ((s = strrchr(a, 's')) == NULL && (s = strrchr(a, 'p')) == NULL) { free(devpath); return (0); } /* don't include slice information in devpath */ *s = '\0'; } *adevpathp = safe_strdup(a); } if (devidp != NULL) { /* lookup the devid */ /* take snapshot if not established */ if (di_root == DI_NODE_NIL) di_root = di_init("/", DINFOCACHE); if (di_root != NULL) { /* get path to /devices devinfo node */ devicespath = di_dim_path_devices(di_dim, driver, instance, NULL); if (devicespath) { /* find the node in the snapshot */ node = di_lookup_node(di_root, devicespath); free(devicespath); /* and lookup devid property on the node */ if (di_prop_lookup_strings(DDI_DEV_T_ANY, node, DEVID_PROP_NAME, &devid) != -1) *devidp = devid; } } } free(devpath); return (1); /* success */ } /* * Do to 'target-port' translation */ static int drvpid2port(uint_t pid, char **target_portp) { sv_iocdata_t ioc; char target_port[MAXNAMELEN]; /* setup "no result" return values */ *target_portp = NULL; /* open scsi_vhci if not already done */ if (scsi_vhci_fd == -1) { scsi_vhci_fd = open("/devices/scsi_vhci:devctl", O_RDONLY); if (scsi_vhci_fd == -1) return (0); /* failure */ } /* * Perform ioctl for -> 'target-port' translation. * * NOTE: it is legimite for this ioctl to fail for transports * that use mpxio, but don't set a 'target-port' pathinfo property. * On failure we return the the "" as the target port string. */ bzero(&ioc, sizeof (sv_iocdata_t)); ioc.buf_elem = pid; ioc.addr = target_port; if (ioctl(scsi_vhci_fd, SCSI_VHCI_GET_TARGET_LONGNAME, &ioc) < 0) { (void) snprintf(target_port, sizeof (target_port), "%d", pid); } *target_portp = safe_strdup(target_port); return (1); /* success */ } /* * Find/create a disk_list entry for the given kstat name. * The basic format of a kstat name is * * "[..][,]". * * The is a decimal number. The "..", * which describes mpxio path stat information, and "," parts are * optional. The consists of the letter 't' followed by a decimal number. * When available, we use the to find the 'target-port' via ioctls to * the scsi_vhci driver. */ disk_list_t * lookup_ks_name(char *ks_name, int want_devid) { char *pidp; /* ".... */ char *part; /* ",partition... */ char *initiator; /* ".... */ char *p; int len; char driver[KSTAT_STRLEN]; int instance; disk_list_t **dlhp; /* disklist head */ disk_list_t *entry; char *devpath = NULL; char *adevpath = NULL; char *devid = NULL; int pid; char *target_port = NULL; char portform[MAXPATHLEN]; /* Filter out illegal forms (like all digits) */ if (ks_name == NULL || *ks_name == '\0' || strspn(ks_name, "0123456789") == strlen(ks_name)) goto fail; /* parse ks_name to create new entry */ pidp = strchr(ks_name, '.'); /* start of "." */ initiator = strrchr(ks_name, '.'); /* start of "." */ if (pidp != NULL && pidp == initiator) /* can't have same start */ goto fail; part = strchr(ks_name, ','); /* start of "," */ p = strchr(ks_name, ':'); /* start of ":" */ if (part != NULL && p != NULL) goto fail; /* can't have both */ if (p != NULL) part = p; if (part != NULL && pidp != NULL) goto fail; /* and partition: bad */ p = (part != NULL) ? part : pidp; if (p == NULL) p = &ks_name[strlen(ks_name) - 1]; /* last char */ else p--; /* before ',' or '.' */ while ((p >= ks_name) && isdigit(*p)) p--; /* backwards over digits */ p++; /* start of instance */ if ((*p == '\0') || (*p == ',') || (*p == '.') || (*p == ':')) goto fail; /* no */ len = p - ks_name; (void) strncpy(driver, ks_name, len); driver[len] = '\0'; instance = atoi(p); if (part != NULL) part++; /* skip ',' */ /* hash by instance and search for existing entry */ dlhp = &disklist[instance & (DISKLIST_MOD - 1)]; for (entry = *dlhp; entry; entry = entry->next) { if (strcmp(entry->ks_name, ks_name) == 0) return (entry); } /* not found, translate kstat_name components and create new entry */ /* translate kstat_name dev information */ if (drvinstpart2dev(driver, instance, part, &devpath, &adevpath, want_devid ? &devid : NULL) == 0) goto fail; /* parse and translate path information */ if (pidp != NULL) { /* parse path information: ".t#." */ pidp++; /* skip '.' */ initiator++; /* skip '.' */ if (*pidp != 't' || !isdigit(pidp[1])) goto fail; /* not ".t#" */ pid = atoi(&pidp[1]); /* translate to 'target-port' */ if (drvpid2port(pid, &target_port) == 0) goto fail; /* Establish 'target-port' form. */ (void) snprintf(portform, sizeof (portform), "%s.t%s.%s", adevpath, target_port, initiator); free(target_port); free(adevpath); adevpath = strdup(portform); } /* make a new entry ... */ entry = safe_alloc(sizeof (disk_list_t)); entry->ks_name = safe_strdup(ks_name); entry->dname = devpath; entry->dsk = adevpath; entry->devidstr = devid; #ifdef DEBUG (void) printf("lookup_ks_name: new: %s %s\n", ks_name, entry->dsk ? entry->dsk : "NULL"); #endif /* DEBUG */ /* add new entry to head of hashed list */ entry->next = *dlhp; *dlhp = entry; return (entry); fail: free(devpath); free(adevpath); free(devid); #ifdef DEBUG (void) printf("lookup_ks_name: failed: %s\n", ks_name); #endif /* DEBUG */ return (NULL); } char * lookup_nfs_name(char *ks, kstat_ctl_t *kc) { uint_t minor; char *host, *path; char *cp; char *rstr = 0; size_t len; if (sscanf(ks, "nfs%u", &minor) == 1) { retry: cp = get_nfs_by_minor(minor); if (cp) { if (strchr(cp, ',') == NULL) { rstr = safe_strdup(cp); return (rstr); } host = cur_hostname(minor, kc); if (host) { if (*host) { path = cur_special(host, cp); if (path) { len = strlen(host); len += strlen(path); len += 2; rstr = safe_alloc(len); (void) snprintf(rstr, len, "%s:%s", host, path); } else { rstr = safe_strdup(cp); } } else { rstr = safe_strdup(ks); } free(host); } else { rstr = safe_strdup(cp); } } else if (nfs_tried == 0) { nfs_tried = 1; do_mnttab(); goto retry; } } return (rstr); } static char * get_nfs_by_minor(uint_t minor) { mnt_t *localnfs; localnfs = nfs; while (localnfs) { if (localnfs->minor == minor) { return (localnfs->device_name); } localnfs = localnfs->next; } return (0); } /* * Read the cur_hostname from the mntinfo kstat */ static char * cur_hostname(uint_t minor, kstat_ctl_t *kc) { kstat_t *ksp; static struct mntinfo_kstat mik; char *rstr; for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) { if (ksp->ks_type != KSTAT_TYPE_RAW) continue; if (ksp->ks_instance != minor) continue; if (strcmp(ksp->ks_module, "nfs")) continue; if (strcmp(ksp->ks_name, "mntinfo")) continue; if (ksp->ks_flags & KSTAT_FLAG_INVALID) return (NULL); if (kstat_read(kc, ksp, &mik) == -1) return (NULL); rstr = safe_strdup(mik.mik_curserver); return (rstr); } return (NULL); } /* * Given the hostname of the mounted server, extract the server * mount point from the mnttab string. * * Common forms: * server1,server2,server3:/path * server1:/path,server2:/path * or a hybrid of the two */ static char * cur_special(char *hostname, char *special) { char *cp; char *path; size_t hlen = strlen(hostname); /* * find hostname in string */ again: if ((cp = strstr(special, hostname)) == NULL) return (NULL); /* * hostname must be followed by ',' or ':' */ if (cp[hlen] != ',' && cp[hlen] != ':') { special = &cp[hlen]; goto again; } /* * If hostname is followed by a ',' eat all characters until a ':' */ cp = &cp[hlen]; if (*cp == ',') { cp++; while (*cp != ':') { if (*cp == '\0') return (NULL); cp++; } } path = ++cp; /* skip ':' */ /* * path is terminated by either 0, or space or ',' */ while (*cp) { if (isspace(*cp) || *cp == ',') { *cp = '\0'; return (path); } cp++; } return (path); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _STAT_DSR_H #define _STAT_DSR_H #ifdef __cplusplus extern "C" { #endif /* * Description of each device identified */ typedef struct list_of_disks { char *ks_name; /* untranslated kstat name */ char *dsk; /* in form of cNtNdN */ char *dname; /* in form of /dev/dsk/cNtNdN */ char *devidstr; /* in form of "id1,sd@XXXX" */ struct list_of_disks *next; /* link to next one */ } disk_list_t; /* * Description of each mount point currently existing on the system. */ typedef struct mnt_info { char *device_name; char *mount_point; char *devinfo; uint_t minor; struct mnt_info *next; } mnt_t; void do_mnttab(void); mnt_t *lookup_mntent_byname(char *); disk_list_t *lookup_ks_name(char *, int); char *lookup_nfs_name(char *, kstat_ctl_t *); void cleanup_iodevs_snapshot(); #ifdef __cplusplus } #endif #endif /* _STAT_DSR_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include "statcommon.h" #include "dsr.h" static time_t mtime; mnt_t *nfs; static mnt_t *ufs; static void build_mnt_list(FILE *); mnt_t * lookup_mntent_byname(char *nm) { mnt_t *rv = 0; mnt_t *minfo; uint_t did_nfs; if (nm) { minfo = ufs; did_nfs = 0; while (minfo) { if (strcmp(nm, minfo->device_name)) { if (minfo->next != 0) minfo = minfo->next; else if (did_nfs == 0) { minfo = nfs; did_nfs = 1; } else minfo = 0; } else { rv = minfo; break; } } } return (rv); } void do_mnttab(void) { struct stat buf; FILE *mpt; struct flock lb; if (stat(MNTTAB, &buf) == 0) { if (buf.st_mtime != mtime) { /* * File has changed. Get the new file. */ if ((mpt = fopen(MNTTAB, "r"))) { lb.l_type = F_RDLCK; lb.l_whence = 0; lb.l_start = 0; lb.l_len = 0; (void) fcntl(fileno(mpt), F_SETLKW, &lb); build_mnt_list(mpt); mtime = buf.st_mtime; /* * Lock goes away when we close the file. */ (void) fclose(mpt); } } } } static void build_mnt_list(FILE *mpt) { mnt_t *item; mnt_t **which; mnt_t *tmp; int found; struct extmnttab mnt; if (mpt) { while (nfs) { free(nfs->device_name); free(nfs->mount_point); free(nfs->devinfo); tmp = nfs; nfs = nfs->next; free(tmp); } while (ufs) { free(ufs->device_name); free(ufs->mount_point); free(ufs->devinfo); tmp = ufs; ufs = ufs->next; free(tmp); } (void) memset(&mnt, 0, sizeof (struct extmnttab)); resetmnttab(mpt); while ((found = getextmntent(mpt, &mnt, sizeof (struct extmnttab))) != -1) { if (found == 0) { if (strcmp(mnt.mnt_fstype, MNTTYPE_UFS) == 0) which = &ufs; else if (strcmp(mnt.mnt_fstype, MNTTYPE_NFS) == 0) which = &nfs; else which = 0; if (which) { item = safe_alloc(sizeof (mnt_t)); item->device_name = safe_strdup(mnt.mnt_special); item->mount_point = safe_strdup(mnt.mnt_mountp); item->devinfo = safe_strdup(mnt.mnt_mntopts); item->minor = mnt.mnt_minor; item->next = *which; *which = item; } } } } } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2012 by Delphix. All rights reserved. * Copyright 2019 OmniOS Community Edition (OmniOSce) Association. */ /* * Common routines for acquiring snapshots of kstats for * iostat, mpstat, and vmstat. */ #ifndef _STATCOMMON_H #define _STATCOMMON_H #ifdef __cplusplus extern "C" { #endif #include #include #include #include #include #include #include #include #include #include #include /* No CPU present at this CPU position */ #define ID_NO_CPU -1 /* CPU belongs to no pset (we number this as "pset 0") */ #define ID_NO_PSET 0 /* CPU is usable */ #define CPU_ONLINE(s) ((s) == P_ONLINE || (s) == P_NOINTR) /* will the CPU have kstats */ #define CPU_ACTIVE(c) (CPU_ONLINE((c)->cs_state) && (c)->cs_id != ID_NO_CPU) /* IO device has no identified ID */ #define IODEV_NO_ID -1 /* no limit to iodevs to collect */ #define UNLIMITED_IODEVS ((size_t)-1) #define NODATE 0 /* Default: No time stamp */ #define DDATE 1 /* Standard date format */ #define UDATE 2 /* Internal representation of Unix time */ enum snapshot_types { /* All CPUs separately */ SNAP_CPUS = 1 << 0, /* Aggregated processor sets */ SNAP_PSETS = 1 << 1, /* sys-wide stats including aggregated CPU stats */ SNAP_SYSTEM = 1 << 2, /* interrupt sources and counts */ SNAP_INTERRUPTS = 1 << 3, /* disk etc. stats */ SNAP_IODEVS = 1 << 4, /* disk controller aggregates */ SNAP_CONTROLLERS = 1 << 5, /* mpxio L I (multipath) paths: -X: Lun,LunInitiator */ SNAP_IOPATHS_LI = 1 << 6, /* mpxio LTI (multipath) paths: -Y: Lun,LunTarget,LunTargetInitiator */ SNAP_IOPATHS_LTI = 1 << 7, /* disk error stats */ SNAP_IODEV_ERRORS = 1 << 8, /* pretty names for iodevs */ SNAP_IODEV_PRETTY = 1 << 9, /* devid for iodevs */ SNAP_IODEV_DEVID = 1 << 10 }; struct cpu_snapshot { /* may be ID_NO_CPU if no CPU present */ processorid_t cs_id; /* may be ID_NO_PSET if no pset */ psetid_t cs_pset_id; /* as in p_online(2) */ int cs_state; /* stats for this CPU */ kstat_t cs_vm; kstat_t cs_sys; }; struct pset_snapshot { /* ID may be zero to indicate the "none set" */ psetid_t ps_id; /* number of CPUs in set */ size_t ps_nr_cpus; /* the CPUs in this set */ struct cpu_snapshot **ps_cpus; }; struct intr_snapshot { /* name of interrupt source */ char is_name[KSTAT_STRLEN]; /* total number of interrupts from this source */ ulong_t is_total; }; struct sys_snapshot { sysinfo_t ss_sysinfo; vminfo_t ss_vminfo; struct nc_stats ss_nc; /* vm/sys stats aggregated across all CPUs */ kstat_t ss_agg_vm; kstat_t ss_agg_sys; /* ticks since boot */ ulong_t ss_ticks; long ss_deficit; }; /* order is significant (see sort_before()) */ enum iodev_type { IODEV_CONTROLLER = 1 << 0, IODEV_DISK = 1 << 1, IODEV_PARTITION = 1 << 2, IODEV_TAPE = 1 << 3, IODEV_NFS = 1 << 4, IODEV_IOPATH_LT = 1 << 5, /* synthetic LunTarget */ IODEV_IOPATH_LI = 1 << 6, /* synthetic LunInitiator */ IODEV_IOPATH_LTI = 1 << 7, /* LunTgtInitiator (pathinfo) */ IODEV_UNKNOWN = 1 << 8 }; /* identify a disk, partition, etc. */ struct iodev_id { int id; /* target id (for disks) */ char tid[KSTAT_STRLEN]; }; /* * Used for disks, partitions, tapes, nfs, controllers, iopaths * Each entry can be a branch of a tree; for example, the disks * of a controller constitute the children of the controller * iodev_snapshot. This relationship is not strictly maintained * if is_pretty can't be found. */ struct iodev_snapshot { /* original kstat name */ char is_name[KSTAT_STRLEN]; /* type of kstat */ enum iodev_type is_type; /* ID if meaningful */ struct iodev_id is_id; /* parent ID if meaningful */ struct iodev_id is_parent_id; /* user-friendly name if found */ char *is_pretty; /* device ID if applicable */ char *is_devid; /* mount-point if applicable */ char *is_dname; /* number of direct children */ int is_nr_children; /* children of this I/O device */ struct iodev_snapshot *is_children; /* standard I/O stats */ kstat_io_t is_stats; /* iodev error stats */ kstat_t is_errors; /* creation time of the stats */ hrtime_t is_crtime; /* time at which iodev snapshot was taken */ hrtime_t is_snaptime; /* kstat module */ char is_module[KSTAT_STRLEN]; /* kstat instance */ int is_instance; /* kstat (only used temporarily) */ kstat_t *is_ksp; struct iodev_snapshot *is_prev; struct iodev_snapshot *is_next; /* AVL structures to speedup insertion */ avl_tree_t *avl_list; /* list this element belongs to */ avl_node_t avl_link; }; /* which iodevs to show. */ struct iodev_filter { /* nr. of iodevs to choose */ size_t if_max_iodevs; /* bit mask of enum io_types to allow */ int if_allowed_types; /* should we show floppy ? if_names can override this */ int if_skip_floppy; /* nr. of named iodevs */ size_t if_nr_names; char **if_names; }; /* The primary structure of a system snapshot. */ struct snapshot { /* what types were *requested* */ enum snapshot_types s_types; size_t s_nr_cpus; struct cpu_snapshot *s_cpus; size_t s_nr_psets; struct pset_snapshot *s_psets; size_t s_nr_intrs; struct intr_snapshot *s_intrs; size_t s_nr_iodevs; struct iodev_snapshot *s_iodevs; size_t s_iodevs_is_name_maxlen; struct sys_snapshot s_sys; struct biostats s_biostats; size_t s_nr_active_cpus; }; /* print a message and exit with failure */ void fail(int do_perror, char *message, ...) __NORETURN; /* strdup str, or exit with failure */ char *safe_strdup(char *str); /* malloc successfully, or exit with failure */ void *safe_alloc(size_t size); /* * Copy a kstat from src to dst. If the source kstat contains no data, * then set the destination kstat data to NULL and size to zero. * Returns 0 on success. */ int kstat_copy(const kstat_t *src, kstat_t *dst); /* * Look up the named kstat, and give the ui64 difference i.e. * new - old, or if old is NULL, return new. */ uint64_t kstat_delta(kstat_t *old, kstat_t *new, char *name); /* Return the number of ticks delta between two hrtime_t values. */ uint64_t hrtime_delta(hrtime_t old, hrtime_t new); /* * Add the integer-valued stats from "src" to the * existing ones in "dst". If "dst" does not contain * stats, then a kstat_copy() is performed. */ int kstat_add(const kstat_t *src, kstat_t *dst); /* return the number of CPUs with kstats (i.e. present and online) */ int nr_active_cpus(struct snapshot *ss); /* * Return the difference in CPU ticks between the two sys * kstats. */ uint64_t cpu_ticks_delta(kstat_t *old, kstat_t *new); /* * Open the kstat chain. Cannot fail. */ kstat_ctl_t *open_kstat(void); /* * Return a struct snapshot based on the snapshot_types parameter * passed in. iodev_filter may be NULL in which case all iodevs * are selected if SNAP_IODEVS is passed. */ struct snapshot *acquire_snapshot(kstat_ctl_t *, int, struct iodev_filter *); /* free a snapshot */ void free_snapshot(struct snapshot *ss); typedef void (*snapshot_cb)(void *old, void *new, void *data); /* * Call the call back for each pair of data items of the given type, * passing the data pointer passed in as well. If an item has been * added, the first pointer will be NULL; if removed, the second pointer * will be NULL. * * A non-zero return value indicates configuration has changed. */ int snapshot_walk(enum snapshot_types type, struct snapshot *old, struct snapshot *new, snapshot_cb cb, void *data); /* * Output a line detailing any configuration changes such as a CPU * brought online, etc, bracketed by << >>. */ void snapshot_report_changes(struct snapshot *old, struct snapshot *new); /* Return non-zero if configuration has changed. */ int snapshot_has_changed(struct snapshot *old, struct snapshot *new); /* free the given iodev */ void free_iodev(struct iodev_snapshot *iodev); /* acquire the I/O devices */ int acquire_iodevs(struct snapshot *ss, kstat_ctl_t *kc, struct iodev_filter *df); /* strcmp-style I/O device comparator */ int iodev_cmp(struct iodev_snapshot *io1, struct iodev_snapshot *io2); /* sleep until *wakeup + interval, keeping cadence where desired */ void sleep_until(hrtime_t *wakeup, hrtime_t interval, int forever, int *caught_cont); /* signal handler - so we can be aware of SIGCONT */ void cont_handler(int sig_number); /* Print a timestamp in either Unix or standard format. */ void print_timestamp(uint_t); #ifdef __cplusplus } #endif #endif /* _STATCOMMON_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include "statcommon.h" #include /* * Print timestamp as decimal reprentation of time_t value (-T u was specified) * or in date(1) format (-T d was specified). */ void print_timestamp(uint_t timestamp_fmt) { time_t t = time(NULL); static char *fmt = NULL; /* We only need to retrieve this once per invocation */ if (fmt == NULL) fmt = nl_langinfo(_DATE_FMT); if (timestamp_fmt == UDATE) { (void) printf("%ld\n", t); } else if (timestamp_fmt == DDATE) { char dstr[64]; int len; len = strftime(dstr, sizeof (dstr), fmt, localtime(&t)); if (len > 0) (void) printf("%s\n", dstr); } } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include "statcommon.h" #include #include /* max size of report change annotations */ #define LIST_SIZE 512 static char cpus_added[LIST_SIZE]; static char cpus_removed[LIST_SIZE]; static int cpu_walk(struct snapshot *old, struct snapshot *new, snapshot_cb cb, void *data) { int changed = 0; int i; /* CPUs can change state but not re-order */ for (i = 0; i < new->s_nr_cpus; i++) { struct cpu_snapshot *cpu = NULL; struct cpu_snapshot *newcpu = &new->s_cpus[i]; if (old) cpu = &old->s_cpus[i]; cb(cpu, newcpu, data); if (cpu == NULL) changed = 1; else { /* * We only care about off/on line transitions */ if ((CPU_ACTIVE(cpu) && !CPU_ACTIVE(newcpu)) || (!CPU_ACTIVE(cpu) && CPU_ACTIVE(newcpu))) changed = 1; if ((new->s_types & SNAP_PSETS) && cpu->cs_pset_id != newcpu->cs_pset_id) changed = 1; } } return (changed); } static int pset_walk(struct snapshot *old, struct snapshot *new, snapshot_cb cb, void *data) { int i = 0; int j = 0; int changed = 0; while (old && i < old->s_nr_psets && j < new->s_nr_psets) { if (old->s_psets[i].ps_id < new->s_psets[j].ps_id) { cb(&old->s_psets[i], NULL, data); i++; changed = 1; } else if (old->s_psets[i].ps_id > new->s_psets[j].ps_id) { cb(NULL, &new->s_psets[j], data); j++; changed = 1; } else { cb(&old->s_psets[i], &new->s_psets[j], data); i++; j++; } } while (old && i < old->s_nr_psets) { cb(&old->s_psets[i], NULL, data); i++; changed = 1; } while (j < new->s_nr_psets) { cb(NULL, &new->s_psets[j], data); j++; changed = 1; } return (changed); } static int iodev_walk(struct iodev_snapshot *d1, struct iodev_snapshot *d2, snapshot_cb cb, void *data) { int changed = 0; while (d1 && d2) { if (strcmp(d1->is_name, d2->is_name) < 0) { changed = 1; cb(d1, NULL, data); (void) iodev_walk(d1->is_children, NULL, cb, data); d1 = d1->is_next; } else if (strcmp(d1->is_name, d2->is_name) > 0) { changed = 1; cb(NULL, d2, data); (void) iodev_walk(NULL, d2->is_children, cb, data); d2 = d2->is_next; } else { cb(d1, d2, data); changed |= iodev_walk(d1->is_children, d2->is_children, cb, data); d1 = d1->is_next; d2 = d2->is_next; } } while (d1) { changed = 1; cb(d1, NULL, data); (void) iodev_walk(d1->is_children, NULL, cb, data); d1 = d1->is_next; } while (d2) { changed = 1; cb(NULL, d2, data); (void) iodev_walk(NULL, d2->is_children, cb, data); d2 = d2->is_next; } return (changed); } int snapshot_walk(enum snapshot_types type, struct snapshot *old, struct snapshot *new, snapshot_cb cb, void *data) { int changed = 0; switch (type) { case SNAP_CPUS: changed = cpu_walk(old, new, cb, data); break; case SNAP_PSETS: changed = pset_walk(old, new, cb, data); break; case SNAP_CONTROLLERS: case SNAP_IODEVS: case SNAP_IOPATHS_LI: case SNAP_IOPATHS_LTI: changed = iodev_walk(old ? old->s_iodevs : NULL, new->s_iodevs, cb, data); break; default: break; } return (changed); } static void add_nr_to_list(char *buf, unsigned long nr) { char tmp[LIST_SIZE]; (void) snprintf(tmp, LIST_SIZE, "%lu", nr); if (strlen(buf)) (void) strlcat(buf, ", ", LIST_SIZE); (void) strlcat(buf, tmp, LIST_SIZE); } static void cpu_report(void *v1, void *v2, void *data) { int *pset = (int *)data; struct cpu_snapshot *c1 = (struct cpu_snapshot *)v1; struct cpu_snapshot *c2 = (struct cpu_snapshot *)v2; if (*pset && c1->cs_pset_id != c2->cs_pset_id) { (void) printf("<>\n", c1->cs_id, c1->cs_pset_id, c2->cs_pset_id); } if (c1->cs_state == c2->cs_state) return; if (CPU_ONLINE(c1->cs_state) && !CPU_ONLINE(c2->cs_state)) add_nr_to_list(cpus_removed, c1->cs_id); if (!CPU_ONLINE(c1->cs_state) && CPU_ONLINE(c2->cs_state)) add_nr_to_list(cpus_added, c2->cs_id); } /*ARGSUSED*/ static void pset_report(void *v1, void *v2, void *data) { struct pset_snapshot *p1 = (struct pset_snapshot *)v1; struct pset_snapshot *p2 = (struct pset_snapshot *)v2; if (p2 == NULL) { (void) printf("<>\n", p1->ps_id); return; } if (p1 == NULL) (void) printf("<>\n", p2->ps_id); } static void get_child_list(struct iodev_snapshot *iodev, char *buf) { char tmp[LIST_SIZE]; struct iodev_snapshot *pos = iodev->is_children; while (pos) { if (pos->is_type == IODEV_PARTITION) { add_nr_to_list(buf, pos->is_id.id); } else if (pos->is_type == IODEV_DISK) { if (strlen(buf)) (void) strlcat(buf, ", ", LIST_SIZE); (void) strlcat(buf, "t", LIST_SIZE); (void) strlcat(buf, pos->is_id.tid, LIST_SIZE); (void) strlcat(buf, "d", LIST_SIZE); *tmp = '\0'; add_nr_to_list(tmp, pos->is_id.id); (void) strlcat(buf, tmp, LIST_SIZE); } pos = pos->is_next; } } static void iodev_changed(struct iodev_snapshot *iodev, int added) { char tmp[LIST_SIZE]; int is_disk = iodev->is_type == IODEV_DISK; char *name = iodev->is_name; if (iodev->is_pretty) name = iodev->is_pretty; switch (iodev->is_type) { case IODEV_IOPATH_LT: case IODEV_IOPATH_LI: case IODEV_IOPATH_LTI: (void) printf("<>\n", added ? "added" : "removed", name); break; case IODEV_PARTITION: (void) printf("<>\n", added ? "added" : "removed", name); break; case IODEV_NFS: (void) printf("<>\n", added ? "mounted" : "unmounted", name); break; case IODEV_TAPE: (void) printf("<>\n", added ? "added" : "removed", name); break; case IODEV_CONTROLLER: case IODEV_DISK: *tmp = '\0'; get_child_list(iodev, tmp); (void) printf("<<%s %s: %s", is_disk ? "disk" : "controller", added ? "added" : "removed", name); if (!*tmp) { (void) printf(">>\n"); return; } (void) printf(" (%s %s)>>\n", is_disk ? "slices" : "disks", tmp); break; }; } static void iodev_report(struct iodev_snapshot *d1, struct iodev_snapshot *d2) { while (d1 && d2) { if (iodev_cmp(d1, d2) < 0) { iodev_changed(d1, 0); d1 = d1->is_next; } else if (iodev_cmp(d1, d2) > 0) { iodev_changed(d2, 1); d2 = d2->is_next; } else { iodev_report(d1->is_children, d2->is_children); d1 = d1->is_next; d2 = d2->is_next; } } while (d1) { iodev_changed(d1, 0); d1 = d1->is_next; } while (d2) { iodev_changed(d2, 1); d2 = d2->is_next; } } void snapshot_report_changes(struct snapshot *old, struct snapshot *new) { int pset; if (old == NULL || new == NULL) return; if (old->s_types != new->s_types) return; pset = old->s_types & SNAP_PSETS; cpus_removed[0] = '\0'; cpus_added[0] = '\0'; if (old->s_types & SNAP_CPUS) (void) snapshot_walk(SNAP_CPUS, old, new, cpu_report, &pset); if (cpus_added[0]) { (void) printf("<>\n", cpus_added); } if (cpus_removed[0]) { (void) printf("<>\n", cpus_removed); } if (pset) { (void) snapshot_walk(SNAP_PSETS, old, new, pset_report, NULL); } iodev_report(old->s_iodevs, new->s_iodevs); } /*ARGSUSED*/ static void dummy_cb(void *v1, void *v2, void *data) { } int snapshot_has_changed(struct snapshot *old, struct snapshot *new) { int ret = 0; int cpu_mask = SNAP_CPUS | SNAP_PSETS | SNAP_SYSTEM; int iodev_mask = SNAP_CONTROLLERS | SNAP_IODEVS | SNAP_IOPATHS_LI | SNAP_IOPATHS_LTI; if (old == NULL) return (1); if (new == NULL) return (EINVAL); if (old->s_types != new->s_types) return (EINVAL); if (!ret && (old->s_types & cpu_mask)) ret = snapshot_walk(SNAP_CPUS, old, new, dummy_cb, NULL); if (!ret && (old->s_types & SNAP_PSETS)) ret = snapshot_walk(SNAP_PSETS, old, new, dummy_cb, NULL); if (!ret && (old->s_types & iodev_mask)) ret = snapshot_walk(SNAP_IODEVS, old, new, dummy_cb, NULL); return (ret); } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 2018, Joyent, Inc. # Copyright 2025 Hans Rosenfeld PROG = fsstat OBJS = fsstat.o SRCS =$(OBJS:%.o=%.c) $(COMMON_SRCS) CLEANFILES = $(OBJS) $(COMMON_OBJS:%.o=%.c) include $(SRC)/cmd/Makefile.cmd include $(SRC)/cmd/stat/Makefile.stat COMMON_OBJS = common.o COMMON_SRCS = $(COMMON_OBJS:%.o=$(STATCOMMONDIR)/%.c) LDLIBS += -lkstat -lcmdutils CFLAGS += $(CCVERBOSE) CERRWARN += -Wno-parentheses FILEMODE= 0555 SMOFF += signed include $(SRC)/cmd/stat/Makefile.stat.targ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright (c) 2016 by Delphix. All rights reserved. * Copyright 2017 Jason King */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "statcommon.h" /* * For now, parsable output is turned off. Once we gather feedback and * stablize the output format, we'll turn it back on. This prevents * the situation where users build tools which depend on a specific * format before we declare the output stable. */ #define PARSABLE_OUTPUT 0 #if PARSABLE_OUTPUT #define OPTIONS "FPT:afginv" #else #define OPTIONS "FT:afginv" #endif /* Time stamp values */ #define NODATE 0 /* Default: No time stamp */ #define DDATE 1 /* Standard date format */ #define UDATE 2 /* Internal representation of Unix time */ #define RETRY_DELAY 250 /* Timeout for poll() */ #define HEADERLINES 12 /* Number of lines between display headers */ #define LBUFSZ 64 /* Generic size for local buffer */ CTASSERT(LBUFSZ >= NN_NUMBUF_SZ); #define NENTITY_INIT 1 /* Initial number of entities to allocate */ /* * We need to have a mechanism for an old/previous and new/current vopstat * structure. We only need two per entity and we can swap between them. */ #define VS_SIZE 2 /* Size of vopstat array */ #define CUR_INDEX (vs_i) #define PREV_INDEX ((vs_i == 0) ? 1 : 0) /* Opposite of CUR_INDEX */ #define BUMP_INDEX() vs_i = ((vs_i == 0) ? 1 : 0) /* * An "entity" is anything we're collecting statistics on, it could * be a mountpoint or an FS-type. * e_name is the name of the entity (e.g. mount point or FS-type) * e_ksname is the name of the associated kstat * e_vs is an array of vopstats. This is used to keep track of "previous" * and "current" vopstats. */ typedef struct entity { char *e_name; /* name of entity */ vopstats_t *e_vs; /* Array of vopstats */ ulong_t e_fsid; /* fsid for ENTYPE_MNTPT only */ int e_type; /* type of entity */ char e_ksname[KSTAT_STRLEN]; /* kstat name */ } entity_t; /* Types of entities (e_type) */ #define ENTYPE_UNKNOWN 0 /* UNKNOWN must be zero since we calloc() */ #define ENTYPE_FSTYPE 1 #define ENTYPE_MNTPT 2 char *cmdname; /* name of this command */ int caught_cont = 0; /* have caught a SIGCONT */ static uint_t timestamp_fmt = NODATE; /* print timestamp with stats */ static int vs_i = 0; /* Index of current vs[] slot */ static void usage() { (void) fprintf(stderr, gettext( "Usage: %s [-a|f|i|n|v] [-T d|u] {-F | {fstype | fspath}...} " "[interval [count]]\n"), cmdname); exit(2); } #define RAWVAL(ptr, member) ((ptr)->member.value.ui64) #define DELTA(member) \ (newvsp->member.value.ui64 - (oldvsp ? oldvsp->member.value.ui64 : 0)) #define PRINTSTAT(isnice, nicestring, rawstring, rawval, buf) \ (isnice) ? \ nicenum(rawval, buf, sizeof (buf)), \ (void) printf((nicestring), (buf)) \ : \ (void) printf((rawstring), (rawval)) /* Values for display flag */ #define DISP_HEADER 0x1 #define DISP_RAW 0x2 /* * The policy for dealing with multiple flags is dealt with here. * Currently, if we are displaying raw output, then don't allow * headers to be printed. */ int dispflag_policy(int printhdr, int dispflag) { /* If we're not displaying raw output, then allow headers to print */ if ((dispflag & DISP_RAW) == 0) { if (printhdr) { dispflag |= DISP_HEADER; } } return (dispflag); } static void dflt_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag) { int niceflag = ((dispflag & DISP_RAW) == 0); longlong_t nnewfile; longlong_t nnamerm; longlong_t nnamechg; longlong_t nattrret; longlong_t nattrchg; longlong_t nlookup; longlong_t nreaddir; longlong_t ndataread; longlong_t ndatawrite; longlong_t readthruput; longlong_t writethruput; char buf[LBUFSZ]; nnewfile = DELTA(ncreate) + DELTA(nmkdir) + DELTA(nsymlink); nnamerm = DELTA(nremove) + DELTA(nrmdir); nnamechg = DELTA(nrename) + DELTA(nlink) + DELTA(nsymlink); nattrret = DELTA(ngetattr) + DELTA(naccess) + DELTA(ngetsecattr) + DELTA(nfid); nattrchg = DELTA(nsetattr) + DELTA(nsetsecattr) + DELTA(nspace); nlookup = DELTA(nlookup); nreaddir = DELTA(nreaddir); ndataread = DELTA(nread); ndatawrite = DELTA(nwrite); readthruput = DELTA(read_bytes); writethruput = DELTA(write_bytes); if (dispflag & DISP_HEADER) { (void) printf( " new name name attr attr lookup rddir read read write write\n" " file remov chng get set ops ops ops bytes ops bytes\n"); } PRINTSTAT(niceflag, "%5s ", "%lld:", nnewfile, buf); PRINTSTAT(niceflag, "%5s ", "%lld:", nnamerm, buf); PRINTSTAT(niceflag, "%5s ", "%lld:", nnamechg, buf); PRINTSTAT(niceflag, "%5s ", "%lld:", nattrret, buf); PRINTSTAT(niceflag, "%5s ", "%lld:", nattrchg, buf); PRINTSTAT(niceflag, " %5s ", "%lld:", nlookup, buf); PRINTSTAT(niceflag, "%5s ", "%lld:", nreaddir, buf); PRINTSTAT(niceflag, "%5s ", "%lld:", ndataread, buf); PRINTSTAT(niceflag, "%5s ", "%lld:", readthruput, buf); PRINTSTAT(niceflag, "%5s ", "%lld:", ndatawrite, buf); PRINTSTAT(niceflag, "%5s ", "%lld:", writethruput, buf); (void) printf("%s\n", name); } static void io_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag) { int niceflag = ((dispflag & DISP_RAW) == 0); char buf[LBUFSZ]; if (dispflag & DISP_HEADER) { (void) printf( " read read write write rddir rddir rwlock rwulock\n" " ops bytes ops bytes ops bytes ops ops\n"); } PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nread), buf); PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(read_bytes), buf); PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nwrite), buf); PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(write_bytes), buf); PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nreaddir), buf); PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(readdir_bytes), buf); PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(nrwlock), buf); PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nrwunlock), buf); (void) printf("%s\n", name); } static void vm_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag) { int niceflag = ((dispflag & DISP_RAW) == 0); char buf[LBUFSZ]; if (dispflag & DISP_HEADER) { (void) printf(" map addmap delmap getpag putpag pagio\n"); } PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nmap), buf); PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(naddmap), buf); PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(ndelmap), buf); PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(ngetpage), buf); PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(nputpage), buf); PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(npageio), buf); (void) printf("%s\n", name); } static void attr_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag) { int niceflag = ((dispflag & DISP_RAW) == 0); char buf[LBUFSZ]; if (dispflag & DISP_HEADER) { (void) printf("getattr setattr getsec setsec\n"); } PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(ngetattr), buf); PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(nsetattr), buf); PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(ngetsecattr), buf); PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(nsetsecattr), buf); (void) printf("%s\n", name); } static void naming_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag) { int niceflag = ((dispflag & DISP_RAW) == 0); char buf[LBUFSZ]; if (dispflag & DISP_HEADER) { (void) printf( "lookup creat remov link renam mkdir rmdir rddir symlnk rdlnk\n"); } PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nlookup), buf); PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(ncreate), buf); PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nremove), buf); PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nlink), buf); PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nrename), buf); PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nmkdir), buf); PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nrmdir), buf); PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nreaddir), buf); PRINTSTAT(niceflag, " %5s ", "%lld:", DELTA(nsymlink), buf); PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(nreadlink), buf); (void) printf("%s\n", name); } #define PRINT_VOPSTAT_CMN(niceflag, vop) \ if (niceflag) \ (void) printf("%10s ", #vop); \ PRINTSTAT(niceflag, "%5s ", "%lld:", DELTA(n##vop), buf); #define PRINT_VOPSTAT(niceflag, vop) \ PRINT_VOPSTAT_CMN(niceflag, vop); \ if (niceflag) \ (void) printf("\n"); #define PRINT_VOPSTAT_IO(niceflag, vop) \ PRINT_VOPSTAT_CMN(niceflag, vop); \ PRINTSTAT(niceflag, " %5s\n", "%lld:", \ DELTA(vop##_bytes), buf); static void vop_display(char *name, vopstats_t *oldvsp, vopstats_t *newvsp, int dispflag) { int niceflag = ((dispflag & DISP_RAW) == 0); char buf[LBUFSZ]; if (niceflag) { (void) printf("%s\n", name); (void) printf(" operation #ops bytes\n"); } PRINT_VOPSTAT(niceflag, open); PRINT_VOPSTAT(niceflag, close); PRINT_VOPSTAT_IO(niceflag, read); PRINT_VOPSTAT_IO(niceflag, write); PRINT_VOPSTAT(niceflag, ioctl); PRINT_VOPSTAT(niceflag, setfl); PRINT_VOPSTAT(niceflag, getattr); PRINT_VOPSTAT(niceflag, setattr); PRINT_VOPSTAT(niceflag, access); PRINT_VOPSTAT(niceflag, lookup); PRINT_VOPSTAT(niceflag, create); PRINT_VOPSTAT(niceflag, remove); PRINT_VOPSTAT(niceflag, link); PRINT_VOPSTAT(niceflag, rename); PRINT_VOPSTAT(niceflag, mkdir); PRINT_VOPSTAT(niceflag, rmdir); PRINT_VOPSTAT_IO(niceflag, readdir); PRINT_VOPSTAT(niceflag, symlink); PRINT_VOPSTAT(niceflag, readlink); PRINT_VOPSTAT(niceflag, fsync); PRINT_VOPSTAT(niceflag, inactive); PRINT_VOPSTAT(niceflag, fid); PRINT_VOPSTAT(niceflag, rwlock); PRINT_VOPSTAT(niceflag, rwunlock); PRINT_VOPSTAT(niceflag, seek); PRINT_VOPSTAT(niceflag, cmp); PRINT_VOPSTAT(niceflag, frlock); PRINT_VOPSTAT(niceflag, space); PRINT_VOPSTAT(niceflag, realvp); PRINT_VOPSTAT(niceflag, getpage); PRINT_VOPSTAT(niceflag, putpage); PRINT_VOPSTAT(niceflag, map); PRINT_VOPSTAT(niceflag, addmap); PRINT_VOPSTAT(niceflag, delmap); PRINT_VOPSTAT(niceflag, poll); PRINT_VOPSTAT(niceflag, dump); PRINT_VOPSTAT(niceflag, pathconf); PRINT_VOPSTAT(niceflag, pageio); PRINT_VOPSTAT(niceflag, dumpctl); PRINT_VOPSTAT(niceflag, dispose); PRINT_VOPSTAT(niceflag, getsecattr); PRINT_VOPSTAT(niceflag, setsecattr); PRINT_VOPSTAT(niceflag, shrlock); PRINT_VOPSTAT(niceflag, vnevent); PRINT_VOPSTAT(niceflag, reqzcbuf); PRINT_VOPSTAT(niceflag, retzcbuf); if (niceflag) { /* Make it easier on the eyes */ (void) printf("\n"); } else { (void) printf("%s\n", name); } } /* * Retrieve the vopstats. If kspp (pointer to kstat_t pointer) is non-NULL, * then pass it back to the caller. * * Returns 0 on success, non-zero on failure. */ int get_vopstats(kstat_ctl_t *kc, char *ksname, vopstats_t *vsp, kstat_t **kspp) { kstat_t *ksp; if (ksname == NULL || *ksname == 0) return (1); errno = 0; /* wait for a possibly up-to-date chain */ while (kstat_chain_update(kc) == -1) { if (errno == EAGAIN) { errno = 0; (void) poll(NULL, 0, RETRY_DELAY); continue; } perror("kstat_chain_update"); exit(1); } if ((ksp = kstat_lookup(kc, NULL, -1, ksname)) == NULL) { return (1); } if (kstat_read(kc, ksp, vsp) == -1) { return (1); } if (kspp) *kspp = ksp; return (0); } /* * Given a file system type name, determine if it's part of the * exception list of file systems that are not to be displayed. */ int is_exception(char *fsname) { char **xlp; /* Pointer into the exception list */ static char *exception_list[] = { "specfs", "fifofs", "fd", "swapfs", "ctfs", "objfs", "nfsdyn", NULL }; for (xlp = &exception_list[0]; *xlp != NULL; xlp++) { if (strcmp(fsname, *xlp) == 0) return (1); } return (0); } /* * Plain and simple, build an array of names for fstypes * Returns 0, if it encounters a problem. */ int build_fstype_list(char ***fstypep) { int i; int nfstype; char buf[FSTYPSZ + 1]; if ((nfstype = sysfs(GETNFSTYP)) < 0) { perror("sysfs(GETNFSTYP)"); return (0); } if ((*fstypep = calloc(nfstype, sizeof (char *))) == NULL) { perror("calloc() fstypes"); return (0); } for (i = 1; i < nfstype; i++) { if (sysfs(GETFSTYP, i, buf) < 0) { perror("sysfs(GETFSTYP)"); return (0); } if (buf[0] == 0) continue; /* If this is part of the exception list, move on */ if (is_exception(buf)) continue; if (((*fstypep)[i] = strdup(buf)) == NULL) { perror("strdup() fstype name"); return (0); } } return (i); } /* * After we're done with getopts(), process the rest of the * operands. We have three cases and this is the priority: * * 1) [ operand... ] interval count * 2) [ operand... ] interval * 3) [ operand... ] * * The trick is that any of the operands might start with a number or even * be made up exclusively of numbers (and we have to handle negative numbers * in case a user/script gets out of line). If we find two operands at the * end of the list then we claim case 1. If we find only one operand at the * end made up only of number, then we claim case 2. Otherwise, case 3. * BTW, argc, argv don't change. */ int parse_operands( int argc, char **argv, int optind, long *interval, long *count, entity_t **entityp) /* Array of stat-able entities */ { int nentities = 0; /* Number of entities found */ int out_of_range; /* Set if 2nd-to-last operand out-of-range */ if (argc == optind) return (nentities); /* None found, returns 0 */ /* * We know exactly what the maximum number of entities is going * to be: argc - optind */ if ((*entityp = calloc((argc - optind), sizeof (entity_t))) == NULL) { perror("calloc() entities"); return (-1); } for (/* void */; argc > optind; optind++) { const char *errstr; /* If we have more than two operands left to process */ if ((argc - optind) > 2) { (*entityp)[nentities++].e_name = strdup(argv[optind]); continue; } /* If we're here, then we only have one or two operands left */ out_of_range = 0; *interval = strtonum(argv[optind], 1, MAXLONG, &errstr); if (errstr != NULL) { if (errno == EINVAL) { /* Operand was not a number */ (*entityp)[nentities++].e_name = strdup(argv[optind]); continue; } if (errno == ERANGE) { /* Operand was a number, just out of range */ out_of_range++; } } /* * The last operand we saw was a number. If it happened to * be the last operand, then it is the interval... */ if ((argc - optind) == 1) { /* ...but we need to check the range. */ if (out_of_range) { (void) fprintf(stderr, gettext( "interval must be between 1 and " "%ld (inclusive)\n"), MAXLONG); return (-1); } else { /* * The value of the interval is valid. Set * count to something really big so it goes * virtually forever. */ *count = MAXLONG; break; } } /* * At this point, we *might* have the interval, but if the * next operand isn't a number, then we don't have either * the interval nor the count. Both must be set to the * defaults. In that case, both the current and the previous * operands are stat-able entities. */ *count = strtonum(argv[optind + 1], 1, MAXLONG, &errstr); if (errstr != NULL) { if (errno == EINVAL) { /* * Faked out! The last operand wasn't a number * so the current and previous operands should * be stat-able entities. * We also need to reset interval. */ *interval = 0; (*entityp)[nentities++].e_name = strdup(argv[optind++]); (*entityp)[nentities++].e_name = strdup(argv[optind++]); } if (errno == ERANGE) out_of_range++; } if (out_of_range != 0) { (void) fprintf(stderr, gettext( "Both interval and count must be between 1 " "and %ld (inclusive)\n"), MAXLONG); return (-1); } break; /* Done! */ } return (nentities); } /* * set_mntpt() looks at the entity's name (e_name) and finds its * mountpoint. To do this, we need to build a list of mountpoints * from /etc/mnttab. We only need to do this once and we don't do it * if we don't need to look at any mountpoints. * Returns 0 on success, non-zero if it couldn't find a mount-point. */ int set_mntpt(entity_t *ep) { static struct mnt { struct mnt *m_next; char *m_mntpt; ulong_t m_fsid; /* From statvfs(), set only as needed */ } *mnt_list = NULL; /* Linked list of mount-points */ struct mnt *mntp; struct statvfs64 statvfsbuf; char *original_name = ep->e_name; char path[PATH_MAX]; if (original_name == NULL) /* Shouldn't happen */ return (1); /* We only set up mnt_list the first time this is called */ if (mnt_list == NULL) { FILE *fp; struct mnttab mnttab; if ((fp = fopen(MNTTAB, "r")) == NULL) { perror(MNTTAB); return (1); } resetmnttab(fp); /* * We insert at the front of the list so that when we * search entries we'll have the last mounted entries * first in the list so that we can match the longest * mountpoint. */ while (getmntent(fp, &mnttab) == 0) { if ((mntp = malloc(sizeof (*mntp))) == NULL) { perror("malloc() mount list"); return (1); } mntp->m_mntpt = strdup(mnttab.mnt_mountp); mntp->m_next = mnt_list; mnt_list = mntp; } (void) fclose(fp); } if (realpath(original_name, path) == NULL) { perror(original_name); return (1); } /* * Now that we have the path, walk through the mnt_list and * look for the first (best) match. */ for (mntp = mnt_list; mntp; mntp = mntp->m_next) { if (strncmp(path, mntp->m_mntpt, strlen(mntp->m_mntpt)) == 0) { if (mntp->m_fsid == 0) { if (statvfs64(mntp->m_mntpt, &statvfsbuf)) { /* Can't statvfs so no match */ continue; } else { mntp->m_fsid = statvfsbuf.f_fsid; } } if (ep->e_fsid != mntp->m_fsid) { /* No match - Move on */ continue; } break; } } if (mntp == NULL) { (void) fprintf(stderr, gettext( "Can't find mount point for %s\n"), path); return (1); } ep->e_name = strdup(mntp->m_mntpt); free(original_name); return (0); } /* * We have an array of entities that are potentially stat-able. Using * the name (e_name) of the entity, attempt to construct a ksname suitable * for use by kstat_lookup(3kstat) and fill it into the e_ksname member. * * We check the e_name against the list of file system types. If there is * no match then test to see if the path is valid. If the path is valid, * then determine the mountpoint. */ void set_ksnames(entity_t *entities, int nentities, char **fstypes, int nfstypes) { int i, j; struct statvfs64 statvfsbuf; for (i = 0; i < nentities; i++) { entity_t *ep = &entities[i]; /* Check the name against the list of fstypes */ for (j = 1; j < nfstypes; j++) { if (fstypes[j] && ep->e_name && strcmp(ep->e_name, fstypes[j]) == 0) { /* It's a file system type */ ep->e_type = ENTYPE_FSTYPE; (void) snprintf(ep->e_ksname, KSTAT_STRLEN, "%s%s", VOPSTATS_STR, ep->e_name); /* Now allocate the vopstats array */ ep->e_vs = calloc(VS_SIZE, sizeof (vopstats_t)); if (entities[i].e_vs == NULL) { perror("calloc() fstype vopstats"); exit(1); } break; } } if (j < nfstypes) /* Found it! */ continue; /* * If the entity in the exception list of fstypes, then * null out the entry so it isn't displayed and move along. */ if (is_exception(ep->e_name)) { ep->e_ksname[0] = 0; continue; } /* If we didn't find it, see if it's a path */ if (ep->e_name == NULL || statvfs64(ep->e_name, &statvfsbuf)) { /* Error - Make sure the entry is nulled out */ ep->e_ksname[0] = 0; continue; } (void) snprintf(ep->e_ksname, KSTAT_STRLEN, "%s%lx", VOPSTATS_STR, statvfsbuf.f_fsid); ep->e_fsid = statvfsbuf.f_fsid; if (set_mntpt(ep)) { (void) fprintf(stderr, gettext("Can't determine type of \"%s\"\n"), ep->e_name ? ep->e_name : gettext("")); } else { ep->e_type = ENTYPE_MNTPT; } /* Now allocate the vopstats array */ ep->e_vs = calloc(VS_SIZE, sizeof (vopstats_t)); if (entities[i].e_vs == NULL) { perror("calloc() vopstats array"); exit(1); } } } /* * The idea is that 'dspfunc' should only be modified from the default * once since the display options are mutually exclusive. If 'dspfunc' * only contains the default display function, then all is good and we * can set it to the new display function. Otherwise, bail. */ void set_dispfunc( void (**dspfunc)(char *, vopstats_t *, vopstats_t *, int), void (*newfunc)(char *, vopstats_t *, vopstats_t *, int)) { if (*dspfunc != dflt_display) { (void) fprintf(stderr, gettext( "%s: Display options -{a|f|i|n|v} are mutually exclusive\n"), cmdname); usage(); } *dspfunc = newfunc; } int main(int argc, char *argv[]) { int c; int i, j; /* Generic counters */ int nentities_found; int linesout = 0; /* Keeps track of lines printed */ int printhdr = 0; /* Print a header? 0 = no, 1 = yes */ int nfstypes; /* Number of fstypes */ int dispflag = 0; /* Flags for display control */ long count = 0; /* Number of iterations for display */ int forever; /* Run forever */ long interval = 0; boolean_t fstypes_only = B_FALSE; /* Display fstypes only */ char **fstypes; /* Array of names of all fstypes */ int nentities; /* Number of stat-able entities */ entity_t *entities; /* Array of stat-able entities */ kstat_ctl_t *kc; void (*dfunc)(char *, vopstats_t *, vopstats_t *, int) = dflt_display; hrtime_t start_n; /* Start time */ hrtime_t period_n; /* Interval in nanoseconds */ extern int optind; (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ #endif (void) textdomain(TEXT_DOMAIN); /* Don't let buffering interfere with piped output. */ (void) setvbuf(stdout, NULL, _IOLBF, 0); cmdname = argv[0]; while ((c = getopt(argc, argv, OPTIONS)) != EOF) { switch (c) { default: usage(); break; case 'F': /* Only display available FStypes */ fstypes_only = B_TRUE; break; #if PARSABLE_OUTPUT case 'P': /* Parsable output */ dispflag |= DISP_RAW; break; #endif /* PARSABLE_OUTPUT */ case 'T': /* Timestamp */ if (optarg) { if (strcmp(optarg, "u") == 0) { timestamp_fmt = UDATE; } else if (strcmp(optarg, "d") == 0) { timestamp_fmt = DDATE; } } /* If it was never set properly... */ if (timestamp_fmt == NODATE) { (void) fprintf(stderr, gettext("%s: -T option " "requires either 'u' or 'd'\n"), cmdname); usage(); } break; case 'a': set_dispfunc(&dfunc, attr_display); break; case 'f': set_dispfunc(&dfunc, vop_display); break; case 'i': set_dispfunc(&dfunc, io_display); break; case 'n': set_dispfunc(&dfunc, naming_display); break; case 'v': set_dispfunc(&dfunc, vm_display); break; } } #if PARSABLE_OUTPUT if ((dispflag & DISP_RAW) && (timestamp_fmt != NODATE)) { (void) fprintf(stderr, gettext( "-P and -T options are mutually exclusive\n")); usage(); } #endif /* PARSABLE_OUTPUT */ /* Gather the list of filesystem types */ if ((nfstypes = build_fstype_list(&fstypes)) == 0) { (void) fprintf(stderr, gettext("Can't build list of fstypes\n")); exit(1); } nentities = parse_operands( argc, argv, optind, &interval, &count, &entities); forever = count == MAXLONG; period_n = (hrtime_t)interval * NANOSEC; if (nentities == -1) /* Set of operands didn't parse properly */ usage(); if ((nentities == 0) && (fstypes_only == B_FALSE)) { (void) fprintf(stderr, gettext( "Must specify -F or at least one fstype or mount point\n")); usage(); } if ((nentities > 0) && (fstypes_only == B_TRUE)) { (void) fprintf(stderr, gettext( "Cannot use -F with fstypes or mount points\n")); usage(); } /* * If we had no operands (except for interval/count) and we * requested FStypes only (-F), then fill in the entities[] * array with all available fstypes. */ if ((nentities == 0) && (fstypes_only == B_TRUE)) { if ((entities = calloc(nfstypes, sizeof (entity_t))) == NULL) { perror("calloc() fstype stats"); exit(1); } for (i = 1; i < nfstypes; i++) { if (fstypes[i]) { entities[nentities].e_name = strdup(fstypes[i]); nentities++; } } } set_ksnames(entities, nentities, fstypes, nfstypes); if ((kc = kstat_open()) == NULL) { perror("kstat_open"); exit(1); } /* Set start time */ start_n = gethrtime(); /* Initial timestamp */ if (timestamp_fmt != NODATE) { print_timestamp(timestamp_fmt); linesout++; } /* * The following loop walks through the entities[] list to "prime * the pump" */ for (j = 0, printhdr = 1; j < nentities; j++) { entity_t *ent = &entities[j]; vopstats_t *vsp = &ent->e_vs[CUR_INDEX]; kstat_t *ksp = NULL; if (get_vopstats(kc, ent->e_ksname, vsp, &ksp) == 0) { (*dfunc)(ent->e_name, NULL, vsp, dispflag_policy(printhdr, dispflag)); linesout++; } else { /* * If we can't find it the first time through, then * get rid of it. */ entities[j].e_ksname[0] = 0; /* * If we're only displaying FStypes (-F) then don't * complain about any file systems that might not * be loaded. Otherwise, let the user know that * they chose poorly. */ if (fstypes_only == B_FALSE) { (void) fprintf(stderr, gettext( "No statistics available for %s\n"), entities[j].e_name); } } printhdr = 0; } if (count > 1) /* Set up signal handler for SIGCONT */ if (signal(SIGCONT, cont_handler) == SIG_ERR) fail(1, "signal failed"); BUMP_INDEX(); /* Swap the previous/current indices */ i = 1; while (forever || i++ <= count) { /* * No telling how many lines will be printed in any interval. * There should be a minimum of HEADERLINES between any * header. If we exceed that, no big deal. */ if (linesout > HEADERLINES) { linesout = 0; printhdr = 1; } /* Have a kip */ sleep_until(&start_n, period_n, forever, &caught_cont); if (timestamp_fmt != NODATE) { print_timestamp(timestamp_fmt); linesout++; } for (j = 0, nentities_found = 0; j < nentities; j++) { entity_t *ent = &entities[j]; /* * If this entry has been cleared, don't attempt * to process it. */ if (ent->e_ksname[0] == 0) { continue; } if (get_vopstats(kc, ent->e_ksname, &ent->e_vs[CUR_INDEX], NULL) == 0) { (*dfunc)(ent->e_name, &ent->e_vs[PREV_INDEX], &ent->e_vs[CUR_INDEX], dispflag_policy(printhdr, dispflag)); linesout++; nentities_found++; } else { if (ent->e_type == ENTYPE_MNTPT) { (void) printf(gettext( "<>\n"), ent->e_name); } else if (ent->e_type == ENTYPE_FSTYPE) { (void) printf(gettext( "<>\n"), ent->e_name); } else { (void) printf(gettext( "<<%s no longer available>>\n"), ent->e_name); } /* Disable this so it doesn't print again */ ent->e_ksname[0] = 0; } printhdr = 0; /* Always shut this off */ } BUMP_INDEX(); /* Bump the previous/current indices */ /* * If the entities we were observing are no longer there * (file system modules unloaded, file systems unmounted) * then we're done. */ if (nentities_found == 0) break; } return (0); } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright 2025 Hans Rosenfeld PROG = iostat OBJS = iostat.o SRCS =$(OBJS:%.o=%.c) $(COMMON_SRCS) CLEANFILES = $(OBJS) $(COMMON_OBJS:%.o=%.c) include $(SRC)/cmd/Makefile.cmd include $(SRC)/cmd/stat/Makefile.stat LDLIBS += -lkstat -ldevinfo -lavl CFLAGS += $(CCVERBOSE) CERRWARN += -Wno-switch CERRWARN += -Wno-parentheses FILEMODE= 0555 include $(SRC)/cmd/stat/Makefile.stat.targ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * rewritten from UCB 4.13 83/09/25 * rewritten from SunOS 4.1 SID 1.18 89/10/06 */ /* * Copyright (c) 2012 by Delphix. All rights reserved. * Copyright 2015 Nexenta Systems, Inc. All rights reserved. * Copyright 2016 James S. Blachly, MD. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dsr.h" #include "statcommon.h" #define DISK_OLD 0x0001 #define DISK_NEW 0x0002 #define DISK_EXTENDED 0x0004 #define DISK_ERRORS 0x0008 #define DISK_EXTENDED_ERRORS 0x0010 #define DISK_IOPATH_LI 0x0020 /* LunInitiator */ #define DISK_IOPATH_LTI 0x0040 /* LunTargetInitiator */ #define DISK_NORMAL (DISK_OLD | DISK_NEW) #define DISK_IO_MASK (DISK_OLD | DISK_NEW | DISK_EXTENDED) #define DISK_ERROR_MASK (DISK_ERRORS | DISK_EXTENDED_ERRORS) #define PRINT_VERTICAL (DISK_ERROR_MASK | DISK_EXTENDED) #define REPRINT 19 #define NUMBER_OF_ERR_COUNTERS 3 /* * It's really a pseudo-gigabyte. We use 1000000000 bytes so that the disk * labels don't look bad. 1GB is really 1073741824 bytes. */ #define DISK_GIGABYTE 1000000000.0 /* * Function desciptor to be called when extended * headers are used. */ typedef struct formatter { void (*nfunc)(void); struct formatter *next; } format_t; /* * Used to get formatting right when printing tty/cpu * data to the right of disk data */ enum show_disk_mode { SHOW_FIRST_ONLY, SHOW_SECOND_ONWARDS, SHOW_ALL }; enum show_disk_mode show_disk_mode = SHOW_ALL; char *cmdname = "iostat"; int caught_cont = 0; static char one_blank[] = " "; static char two_blanks[] = " "; /* * count for number of lines to be emitted before a header is * shown again. Only used for the basic format. */ static uint_t tohdr = 1; /* * If we're in raw format, have we printed a header? We only do it * once for raw but we emit it every REPRINT lines in non-raw format. * This applies only for the basic header. The extended header is * done only once in both formats. */ static uint_t hdr_out; /* * Flags representing arguments from command line */ static uint_t do_tty; /* show tty info (-t) */ static uint_t do_disk; /* show disk info per selected */ /* format (-d, -D, -e, -E, -x -X -Y) */ static uint_t do_cpu; /* show cpu info (-c) */ static uint_t do_interval; /* do intervals (-I) */ static int do_partitions; /* per-partition stats (-p) */ static int do_partitions_only; /* per-partition stats only (-P) */ /* no per-device stats for disks */ static uint_t do_conversions; /* display disks as cXtYdZ (-n) */ static uint_t do_megabytes; /* display data in MB/sec (-M) */ static uint_t do_controller; /* display controller info (-C) */ static uint_t do_raw; /* emit raw format (-r) */ static uint_t timestamp_fmt = NODATE; /* timestamp each display (-T) */ static uint_t do_devid; /* -E should show devid */ /* * Default number of disk drives to be displayed in basic format */ #define DEFAULT_LIMIT 4 struct iodev_filter df; static uint_t suppress_state; /* skip state change messages */ static uint_t suppress_zero; /* skip zero valued lines */ static uint_t show_mountpts; /* show mount points */ static int interval; /* interval (seconds) to output */ static int iter; /* iterations from command line */ #define SMALL_SCRATCH_BUFLEN MAXNAMELEN static int iodevs_nl; /* name field width */ #define IODEVS_NL_MIN 6 /* not too thin for "device" */ #define IODEVS_NL_MAX 24 /* but keep full width under 80 */ static char disk_header[132]; static uint_t dh_len; /* disk header length for centering */ static int lineout; /* data waiting to be printed? */ static struct snapshot *newss; static struct snapshot *oldss; static double getime; /* elapsed time */ static double percent; /* 100 / etime */ /* * List of functions to be called which will construct the desired output */ static format_t *formatter_list; static format_t *formatter_end; static u_longlong_t ull_delta(u_longlong_t, u_longlong_t); static uint_t u32_delta(uint_t, uint_t); static void setup(void (*nfunc)(void)); static void print_tty_hdr1(void); static void print_tty_hdr2(void); static void print_cpu_hdr1(void); static void print_cpu_hdr2(void); static void print_tty_data(void); static void print_cpu_data(void); static void print_err_hdr(void); static void print_disk_header(void); static void hdrout(void); static void disk_errors(void); static void do_newline(void); static void push_out(const char *, ...); static void printhdr(int); static void printxhdr(void); static void usage(void); static void do_args(int, char **); static void do_format(void); static void show_all_disks(void); static void show_first_disk(void); static void show_other_disks(void); static void show_disk_errors(void *, void *, void *); static void write_core_header(void); static int fzero(double value); static int safe_strtoi(char const *val, char *errmsg); int main(int argc, char **argv) { enum snapshot_types types = SNAP_SYSTEM; kstat_ctl_t *kc; long hz; int forever; hrtime_t start_n; hrtime_t period_n = 0; (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ #endif (void) textdomain(TEXT_DOMAIN); do_args(argc, argv); /* * iostat historically showed CPU changes, even though * it doesn't provide much useful information */ types |= SNAP_CPUS; if (do_disk) types |= SNAP_IODEVS; if (do_disk && !do_partitions_only) df.if_allowed_types |= IODEV_DISK; if (do_disk & DISK_IOPATH_LI) { df.if_allowed_types |= IODEV_IOPATH_LTI; types |= SNAP_IOPATHS_LI; } if (do_disk & DISK_IOPATH_LTI) { df.if_allowed_types |= IODEV_IOPATH_LTI; types |= SNAP_IOPATHS_LTI; } if (do_disk & DISK_ERROR_MASK) types |= SNAP_IODEV_ERRORS; if (do_partitions || do_partitions_only) df.if_allowed_types |= IODEV_PARTITION; if (do_conversions) types |= SNAP_IODEV_PRETTY; if (do_devid) types |= SNAP_IODEV_DEVID; if (do_controller) { if (!(do_disk & PRINT_VERTICAL) || (do_disk & DISK_EXTENDED_ERRORS)) fail(0, "-C can only be used with -e or -x."); types |= SNAP_CONTROLLERS; df.if_allowed_types |= IODEV_CONTROLLER; } hz = sysconf(_SC_CLK_TCK); /* * Undocumented behavior - sending a SIGCONT will result * in a new header being emitted. Used only if we're not * doing extended headers. This is a historical * artifact. */ if (!(do_disk & PRINT_VERTICAL)) (void) signal(SIGCONT, printhdr); if (interval) period_n = (hrtime_t)interval * NANOSEC; kc = open_kstat(); if (interval) start_n = gethrtime(); newss = acquire_snapshot(kc, types, &df); /* compute width of "device" field */ iodevs_nl = newss->s_iodevs_is_name_maxlen; iodevs_nl = (iodevs_nl < IODEVS_NL_MIN) ? IODEVS_NL_MIN : iodevs_nl; iodevs_nl = (iodevs_nl > IODEVS_NL_MAX) ? IODEVS_NL_MAX : iodevs_nl; do_format(); forever = (iter == 0); do { if (do_conversions && show_mountpts) do_mnttab(); if (do_tty || do_cpu) { kstat_t *oldks; oldks = oldss ? &oldss->s_sys.ss_agg_sys : NULL; getime = cpu_ticks_delta(oldks, &newss->s_sys.ss_agg_sys); percent = (getime > 0.0) ? 100.0 / getime : 0.0; getime = (getime / nr_active_cpus(newss)) / hz; if (getime == 0.0) getime = (double)interval; if (getime == 0.0 || do_interval) getime = 1.0; } if (formatter_list) { format_t *tmp; tmp = formatter_list; if (timestamp_fmt != NODATE) print_timestamp(timestamp_fmt); while (tmp) { (tmp->nfunc)(); tmp = tmp->next; } (void) fflush(stdout); } /* only remaining/doing a single iteration, we are done */ if (iter == 1) continue; if (interval > 0) /* Have a kip */ sleep_until(&start_n, period_n, forever, &caught_cont); free_snapshot(oldss); oldss = newss; newss = acquire_snapshot(kc, types, &df); iodevs_nl = (newss->s_iodevs_is_name_maxlen > iodevs_nl) ? newss->s_iodevs_is_name_maxlen : iodevs_nl; iodevs_nl = (iodevs_nl < IODEVS_NL_MIN) ? IODEVS_NL_MIN : iodevs_nl; iodevs_nl = (iodevs_nl > IODEVS_NL_MAX) ? IODEVS_NL_MAX : iodevs_nl; if (!suppress_state) snapshot_report_changes(oldss, newss); /* if config changed, show stats from boot */ if (snapshot_has_changed(oldss, newss)) { free_snapshot(oldss); oldss = NULL; } } while (--iter); free_snapshot(oldss); free_snapshot(newss); (void) kstat_close(kc); free(df.if_names); return (0); } /* * Some magic numbers used in header formatting. * * DISK_LEN = length of either "kps tps serv" or "wps rps util" * using 0 as the first position * * DISK_ERROR_LEN = length of "s/w h/w trn tot" with one space on * either side. Does not use zero as first pos. * * DEVICE_LEN = length of "device" + 1 character. */ #define DISK_LEN 11 #define DISK_ERROR_LEN 16 #define DEVICE_LEN 7 /*ARGSUSED*/ static void show_disk_name(void *v1, void *v2, void *data) { struct iodev_snapshot *dev = (struct iodev_snapshot *)v2; size_t slen; char *name; char fbuf[SMALL_SCRATCH_BUFLEN]; if (dev == NULL) return; name = do_conversions ? dev->is_pretty : dev->is_name; name = name ? name : dev->is_name; if (!do_raw) { uint_t width; slen = strlen(name); /* * The length is less * than the section * which will be displayed * on the next line. * Center the entry. */ width = (DISK_LEN + 1)/2 + (slen / 2); (void) snprintf(fbuf, sizeof (fbuf), "%*s", width, name); name = fbuf; push_out("%-13.13s ", name); } else { push_out(name); } } /*ARGSUSED*/ static void show_disk_header(void *v1, void *v2, void *data) { push_out(disk_header); } /* * Write out a two line header. What is written out depends on the flags * selected but in the worst case consists of a tty header, a disk header * providing information for 4 disks and a cpu header. * * The tty header consists of the word "tty" on the first line above the * words "tin tout" on the next line. If present the tty portion consumes * the first 10 characters of each line since "tin tout" is surrounded * by single spaces. * * Each of the disk sections is a 14 character "block" in which the name of * the disk is centered in the first 12 characters of the first line. * * The cpu section is an 11 character block with "cpu" centered over the * section. * * The worst case should look as follows: * * 0---------1--------2---------3---------4---------5---------6---------7------- * tty sd0 sd1 sd2 sd3 cpu * tin tout kps tps serv kps tps serv kps tps serv kps tps serv us sy dt id * NNN NNNN NNN NNN NNNN NNN NNN NNNN NNN NNN NNNN NNN NNN NNNN NN NN NN NN * * When -D is specified, the disk header looks as follows (worst case): * * 0---------1--------2---------3---------4---------5---------6---------7------- * tty sd0 sd1 sd2 sd3 cpu * tin tout rps wps util rps wps util rps wps util rps wps util us sy dt id * NNN NNNN NNN NNN NNNN NNN NNN NNNN NNN NNN NNNN NNN NNN NNNN NN NN NN NN */ static void printhdr(int sig) { /* * If we're here because a signal fired, reenable the * signal. */ if (sig) (void) signal(SIGCONT, printhdr); if (sig == SIGCONT) caught_cont = 1; /* * Horizontal mode headers * * First line */ if (do_tty) print_tty_hdr1(); if (do_disk & DISK_NORMAL) { (void) snapshot_walk(SNAP_IODEVS, NULL, newss, show_disk_name, NULL); } if (do_cpu) print_cpu_hdr1(); do_newline(); /* * Second line */ if (do_tty) print_tty_hdr2(); if (do_disk & DISK_NORMAL) { (void) snapshot_walk(SNAP_IODEVS, NULL, newss, show_disk_header, NULL); } if (do_cpu) print_cpu_hdr2(); do_newline(); tohdr = REPRINT; } /* * Write out the extended header centered over the core information. */ static void write_core_header(void) { char *edev = "extended device statistics"; uint_t lead_space_ct; uint_t follow_space_ct; size_t edevlen; if (do_raw == 0) { /* * The things we do to look nice... * * Center the core output header. Make sure we have the * right number of trailing spaces for follow-on headers * (i.e., cpu and/or tty and/or errors). */ edevlen = strlen(edev); lead_space_ct = dh_len - edevlen; lead_space_ct /= 2; if (lead_space_ct > 0) { follow_space_ct = dh_len - (lead_space_ct + edevlen); if (do_disk & DISK_ERRORS) follow_space_ct -= DISK_ERROR_LEN; if ((do_disk & DISK_EXTENDED) && do_conversions) follow_space_ct -= DEVICE_LEN; push_out("%1$*2$.*2$s%3$s%4$*5$.*5$s", one_blank, lead_space_ct, edev, one_blank, follow_space_ct); } else push_out("%56s", edev); } else push_out(edev); } /* * In extended mode headers, we don't want to reprint the header on * signals as they are printed every time anyways. */ static void printxhdr(void) { /* * Vertical mode headers */ if (do_disk & DISK_EXTENDED) setup(write_core_header); if (do_disk & DISK_ERRORS) setup(print_err_hdr); if (do_conversions) { setup(do_newline); if (do_disk & (DISK_EXTENDED | DISK_ERRORS)) setup(print_disk_header); setup(do_newline); } else { if (do_tty) setup(print_tty_hdr1); if (do_cpu) setup(print_cpu_hdr1); setup(do_newline); if (do_disk & (DISK_EXTENDED | DISK_ERRORS)) setup(print_disk_header); if (do_tty) setup(print_tty_hdr2); if (do_cpu) setup(print_cpu_hdr2); setup(do_newline); } } /* * Write out a line for this disk - note that show_disk writes out * full lines or blocks for each selected disk. */ static void show_disk(void *v1, void *v2, void *data) { uint32_t err_counters[NUMBER_OF_ERR_COUNTERS]; boolean_t display_err_counters = do_disk & DISK_ERRORS; struct iodev_snapshot *old = (struct iodev_snapshot *)v1; struct iodev_snapshot *new = (struct iodev_snapshot *)v2; int *count = (int *)data; double rps = 0, wps = 0, tps = 0, mtps, krps = 0, kwps = 0; double kps = 0, avw = 0, avr = 0, w_pct = 0, r_pct = 0; double wserv = 0, rserv = 0, serv = 0; double iosize; /* kb/sec or MB/sec */ double etime, hr_etime; char *disk_name; u_longlong_t ldeltas; uint_t udeltas; uint64_t t_delta; uint64_t w_delta; uint64_t r_delta; int doit = 1; uint_t toterrs; char *fstr; if (new == NULL) return; switch (show_disk_mode) { case SHOW_FIRST_ONLY: if (count != NULL && *count) return; break; case SHOW_SECOND_ONWARDS: if (count != NULL && !*count) { (*count)++; return; } break; default: break; } disk_name = do_conversions ? new->is_pretty : new->is_name; disk_name = disk_name ? disk_name : new->is_name; /* * Only do if we want IO stats - Avoids errors traveling this * section if that's all we want to see. */ if (do_disk & DISK_IO_MASK) { if (old) { t_delta = hrtime_delta(old->is_snaptime, new->is_snaptime); } else { t_delta = hrtime_delta(new->is_crtime, new->is_snaptime); } if (new->is_nr_children) { if (new->is_type == IODEV_CONTROLLER) { t_delta /= new->is_nr_children; } else if ((new->is_type == IODEV_IOPATH_LT) || (new->is_type == IODEV_IOPATH_LI)) { /* synthetic path */ if (!old) { t_delta = new->is_crtime; } t_delta /= new->is_nr_children; } } hr_etime = (double)t_delta; if (hr_etime == 0.0) hr_etime = (double)NANOSEC; etime = hr_etime / (double)NANOSEC; /* reads per second */ udeltas = u32_delta(old ? old->is_stats.reads : 0, new->is_stats.reads); rps = (double)udeltas; rps /= etime; /* writes per second */ udeltas = u32_delta(old ? old->is_stats.writes : 0, new->is_stats.writes); wps = (double)udeltas; wps /= etime; tps = rps + wps; /* transactions per second */ /* * report throughput as either kb/sec or MB/sec */ if (!do_megabytes) iosize = 1024.0; else iosize = 1048576.0; ldeltas = ull_delta(old ? old->is_stats.nread : 0, new->is_stats.nread); if (ldeltas) { krps = (double)ldeltas; krps /= etime; krps /= iosize; } else krps = 0.0; ldeltas = ull_delta(old ? old->is_stats.nwritten : 0, new->is_stats.nwritten); if (ldeltas) { kwps = (double)ldeltas; kwps /= etime; kwps /= iosize; } else kwps = 0.0; /* * Blocks transferred per second */ kps = krps + kwps; /* * Average number of wait transactions waiting */ w_delta = hrtime_delta((u_longlong_t) (old ? old->is_stats.wlentime : 0), new->is_stats.wlentime); if (w_delta) { avw = (double)w_delta; avw /= hr_etime; } else avw = 0.0; /* * Average number of run transactions waiting */ r_delta = hrtime_delta(old ? old->is_stats.rlentime : 0, new->is_stats.rlentime); if (r_delta) { avr = (double)r_delta; avr /= hr_etime; } else avr = 0.0; /* * Average wait service time in milliseconds */ if (tps > 0.0 && (avw != 0.0 || avr != 0.0)) { mtps = 1000.0 / tps; if (avw != 0.0) wserv = avw * mtps; else wserv = 0.0; if (avr != 0.0) rserv = avr * mtps; else rserv = 0.0; serv = rserv + wserv; } else { rserv = 0.0; wserv = 0.0; serv = 0.0; } /* % of time there is a transaction waiting for service */ t_delta = hrtime_delta(old ? old->is_stats.wtime : 0, new->is_stats.wtime); if (t_delta) { w_pct = (double)t_delta; w_pct /= hr_etime; w_pct *= 100.0; /* * Average the wait queue utilization over the * the controller's devices, if this is a controller. */ if (new->is_type == IODEV_CONTROLLER) w_pct /= new->is_nr_children; } else w_pct = 0.0; /* % of time there is a transaction running */ t_delta = hrtime_delta(old ? old->is_stats.rtime : 0, new->is_stats.rtime); if (t_delta) { r_pct = (double)t_delta; r_pct /= hr_etime; r_pct *= 100.0; /* * Average the percent busy over the controller's * devices, if this is a controller. */ if (new->is_type == IODEV_CONTROLLER) w_pct /= new->is_nr_children; } else { r_pct = 0.0; } /* % of time there is a transaction running */ if (do_interval) { rps *= etime; wps *= etime; tps *= etime; krps *= etime; kwps *= etime; kps *= etime; } } if (do_disk & (DISK_EXTENDED | DISK_ERRORS)) { if ((!do_conversions) && ((suppress_zero == 0) || ((do_disk & DISK_EXTENDED) == 0))) { if (do_raw == 0) { push_out("%-*.*s", iodevs_nl, iodevs_nl, disk_name); } else { push_out(disk_name); } } } /* * The error counters are read first (if asked for and if they are * available). */ bzero(err_counters, sizeof (err_counters)); toterrs = 0; if (display_err_counters && (new->is_errors.ks_data != NULL)) { kstat_named_t *knp; int i; knp = KSTAT_NAMED_PTR(&new->is_errors); for (i = 0; i < NUMBER_OF_ERR_COUNTERS; i++) { switch (knp[i].data_type) { case KSTAT_DATA_ULONG: #ifndef _LP64 /* Hammerhead: On LP64, KSTAT_DATA_ULONG == * KSTAT_DATA_ULONGLONG (both map to * KSTAT_DATA_UINT64), causing duplicate case. */ case KSTAT_DATA_ULONGLONG: #endif err_counters[i] = knp[i].value.ui32; toterrs += knp[i].value.ui32; break; default: break; } } } switch (do_disk & DISK_IO_MASK) { case DISK_OLD: if (do_raw == 0) fstr = "%3.0f %3.0f %4.0f "; else fstr = "%.0f,%.0f,%.0f"; push_out(fstr, kps, tps, serv); break; case DISK_NEW: if (do_raw == 0) fstr = "%3.0f %3.0f %4.1f "; else fstr = "%.0f,%.0f,%.1f"; push_out(fstr, rps, wps, r_pct); break; case DISK_EXTENDED: if (suppress_zero) { if (fzero(rps) && fzero(wps) && fzero(krps) && fzero(kwps) && fzero(avw) && fzero(avr) && fzero(serv) && fzero(w_pct) && fzero(r_pct) && (toterrs == 0)) { doit = 0; display_err_counters = B_FALSE; } else if (do_conversions == 0) { if (do_raw == 0) { push_out("%-*.*s", iodevs_nl, iodevs_nl, disk_name); } else { push_out(disk_name); } } } if (doit) { if (!do_conversions) { if (do_raw == 0) { fstr = " %6.1f %6.1f %6.1f %6.1f " "%4.1f %4.1f %6.1f %3.0f " "%3.0f "; } else { fstr = "%.1f,%.1f,%.1f,%.1f,%.1f,%.1f," "%.1f,%.0f,%.0f"; } push_out(fstr, rps, wps, krps, kwps, avw, avr, serv, w_pct, r_pct); } else { if (do_raw == 0) { fstr = " %6.1f %6.1f %6.1f %6.1f " "%4.1f %4.1f %6.1f %6.1f " "%3.0f %3.0f "; } else { fstr = "%.1f,%.1f,%.1f,%.1f,%.1f,%.1f," "%.1f,%.1f,%.0f,%.0f"; } push_out(fstr, rps, wps, krps, kwps, avw, avr, wserv, rserv, w_pct, r_pct); } } break; } if (display_err_counters) { char *efstr; int i; if (do_raw == 0) { if (do_disk == DISK_ERRORS) push_out(two_blanks); efstr = "%3u "; } else { efstr = "%u"; } for (i = 0; i < NUMBER_OF_ERR_COUNTERS; i++) push_out(efstr, err_counters[i]); push_out(efstr, toterrs); } if (suppress_zero == 0 || doit == 1) { if ((do_disk & (DISK_EXTENDED | DISK_ERRORS)) && do_conversions) { push_out("%s", disk_name); if (show_mountpts && new->is_dname) { mnt_t *mount_pt; char *lu; char *dnlu; char lub[SMALL_SCRATCH_BUFLEN]; lu = strrchr(new->is_dname, '/'); if (lu) { /* only the part after a possible '/' */ dnlu = strrchr(disk_name, '/'); if (dnlu != NULL && strcmp(dnlu, lu) == 0) lu = new->is_dname; else { *lu = 0; (void) strcpy(lub, new->is_dname); *lu = '/'; (void) strcat(lub, "/"); (void) strcat(lub, disk_name); lu = lub; } } else lu = disk_name; mount_pt = lookup_mntent_byname(lu); if (mount_pt) { if (do_raw == 0) push_out(" (%s)", mount_pt->mount_point); else push_out("(%s)", mount_pt->mount_point); } } } } if ((do_disk & PRINT_VERTICAL) && show_disk_mode != SHOW_FIRST_ONLY) do_newline(); if (count != NULL) (*count)++; } static void usage(void) { (void) fprintf(stderr, "Usage: iostat [-cCdDeEiImMnpPrstxXYz] " " [-l n] [-T d|u] [disk ...] [interval [count]]\n" "\t\t-c: report percentage of time system has spent\n" "\t\t\tin user/system/dtrace/idle mode\n" "\t\t-C: report disk statistics by controller\n" "\t\t-d: display disk Kb/sec, transfers/sec, avg. \n" "\t\t\tservice time in milliseconds \n" "\t\t-D: display disk reads/sec, writes/sec, \n" "\t\t\tpercentage disk utilization \n" "\t\t-e: report device error summary statistics\n" "\t\t-E: report extended device error statistics\n" "\t\t-i: show device IDs for -E output\n" "\t\t-I: report the counts in each interval,\n" "\t\t\tinstead of rates, where applicable\n" "\t\t-l n: Limit the number of disks to n\n" "\t\t-m: Display mount points (most useful with -p)\n" "\t\t-M: Display data throughput in MB/sec " "instead of Kb/sec\n" "\t\t-n: convert device names to cXdYtZ format\n" "\t\t-p: report per-partition disk statistics\n" "\t\t-P: report per-partition disk statistics only,\n" "\t\t\tno per-device disk statistics\n" "\t\t-r: Display data in comma separated format\n" "\t\t-s: Suppress state change messages\n" "\t\t-T d|u Display a timestamp in date (d) or unix " "time_t (u)\n" "\t\t-t: display chars read/written to terminals\n" "\t\t-x: display extended disk statistics\n" "\t\t-X: display I/O path statistics\n" "\t\t-Y: display I/O path (I/T/L) statistics\n" "\t\t-z: Suppress entries with all zero values\n"); exit(1); } /*ARGSUSED*/ static void show_disk_errors(void *v1, void *v2, void *d) { struct iodev_snapshot *disk = (struct iodev_snapshot *)v2; kstat_named_t *knp; size_t col; int i, len; char *dev_name; if (disk->is_errors.ks_ndata == 0) return; if (disk->is_type == IODEV_CONTROLLER) return; dev_name = do_conversions ? disk->is_pretty : disk->is_name; dev_name = dev_name ? dev_name : disk->is_name; len = strlen(dev_name); if (len > 20) push_out("%s ", dev_name); else if (len > 16) push_out("%-20.20s ", dev_name); else { if (do_conversions) push_out("%-16.16s ", dev_name); else push_out("%-9.9s ", dev_name); } col = 0; knp = KSTAT_NAMED_PTR(&disk->is_errors); for (i = 0; i < disk->is_errors.ks_ndata; i++) { /* skip kstats that the driver did not kstat_named_init */ if (knp[i].name[0] == 0) continue; col += strlen(knp[i].name); switch (knp[i].data_type) { case KSTAT_DATA_CHAR: case KSTAT_DATA_STRING: if ((strcmp(knp[i].name, "Serial No") == 0) && do_devid) { if (disk->is_devid) { push_out("Device Id: %s ", disk->is_devid); col += strlen(disk->is_devid); } else { push_out("Device Id: "); } break; } if (knp[i].data_type == KSTAT_DATA_CHAR) { push_out("%s: %-.16s ", knp[i].name, &knp[i].value.c[0]); col += strnlen(&knp[i].value.c[0], 16); } else { push_out("%s: %s ", knp[i].name, KSTAT_NAMED_STR_PTR(&knp[i])); col += KSTAT_NAMED_STR_BUFLEN(&knp[i]) - 1; } break; #ifndef _LP64 /* Hammerhead: On LP64, KSTAT_DATA_ULONG == * KSTAT_DATA_ULONGLONG (both KSTAT_DATA_UINT64), * causing duplicate case. Use ULONGLONG path on LP64. */ case KSTAT_DATA_ULONG: push_out("%s: %u ", knp[i].name, knp[i].value.ui32); col += 4; break; #endif case KSTAT_DATA_ULONGLONG: if (strcmp(knp[i].name, "Size") == 0) { do_newline(); push_out("%s: %2.2fGB <%llu bytes>", knp[i].name, (float)knp[i].value.ui64 / DISK_GIGABYTE, knp[i].value.ui64); do_newline(); col = 0; break; } push_out("%s: %u ", knp[i].name, knp[i].value.ui32); col += 4; break; } if ((col >= 62) || (i == 2)) { do_newline(); col = 0; } } if (col > 0) { do_newline(); } do_newline(); } void do_args(int argc, char **argv) { int c; int errflg = 0; extern char *optarg; extern int optind; while ((c = getopt(argc, argv, "tdDxXYCciIpPnmMeEszrT:l:")) != EOF) switch (c) { case 't': do_tty++; break; case 'd': do_disk |= DISK_OLD; break; case 'D': do_disk |= DISK_NEW; break; case 'x': do_disk |= DISK_EXTENDED; break; case 'X': if (do_disk & DISK_IOPATH_LTI) errflg++; /* -Y already used */ else do_disk |= DISK_IOPATH_LI; break; case 'Y': if (do_disk & DISK_IOPATH_LI) errflg++; /* -X already used */ else do_disk |= DISK_IOPATH_LTI; break; case 'C': do_controller++; break; case 'c': do_cpu++; break; case 'I': do_interval++; break; case 'p': do_partitions++; break; case 'P': do_partitions_only++; break; case 'n': do_conversions++; break; case 'M': do_megabytes++; break; case 'e': do_disk |= DISK_ERRORS; break; case 'E': do_disk |= DISK_EXTENDED_ERRORS; break; case 'i': do_devid = 1; break; case 's': suppress_state = 1; break; case 'z': suppress_zero = 1; break; case 'm': show_mountpts = 1; break; case 'T': if (optarg) { if (*optarg == 'u') timestamp_fmt = UDATE; else if (*optarg == 'd') timestamp_fmt = DDATE; else errflg++; } else { errflg++; } break; case 'r': do_raw = 1; break; case 'l': df.if_max_iodevs = safe_strtoi(optarg, "invalid limit"); if (df.if_max_iodevs < 1) usage(); break; case '?': errflg++; } if ((do_disk & DISK_OLD) && (do_disk & DISK_NEW)) { (void) fprintf(stderr, "-d and -D are incompatible.\n"); usage(); } if (errflg) { usage(); } /* if no output classes explicity specified, use defaults */ if (do_tty == 0 && do_disk == 0 && do_cpu == 0) do_tty = do_cpu = 1, do_disk = DISK_OLD; /* * multi-path options (-X, -Y) without a specific vertical * output format (-x, -e, -E) imply extended -x format */ if ((do_disk & (DISK_IOPATH_LI | DISK_IOPATH_LTI)) && !(do_disk & PRINT_VERTICAL)) do_disk |= DISK_EXTENDED; /* * If conflicting options take the preferred * -D and -x result in -x * -d or -D and -e or -E gives only whatever -d or -D was specified */ if ((do_disk & DISK_EXTENDED) && (do_disk & DISK_NORMAL)) do_disk &= ~DISK_NORMAL; if ((do_disk & DISK_NORMAL) && (do_disk & DISK_ERROR_MASK)) do_disk &= ~DISK_ERROR_MASK; /* nfs, tape, always shown */ df.if_allowed_types = IODEV_NFS | IODEV_TAPE; /* * If limit == 0 then no command line limit was set, else if any of * the flags that cause unlimited disks were not set, * use the default of 4 */ if (df.if_max_iodevs == 0) { df.if_max_iodevs = DEFAULT_LIMIT; df.if_skip_floppy = 1; if (do_disk & (DISK_EXTENDED | DISK_ERRORS | DISK_EXTENDED_ERRORS)) { df.if_max_iodevs = UNLIMITED_IODEVS; df.if_skip_floppy = 0; } } if (do_disk) { size_t count = 0; size_t i = optind; while (i < argc && !isdigit(argv[i][0])) { count++; i++; } /* * "Note: disks explicitly requested * are not subject to this disk limit" */ if ((count > df.if_max_iodevs) || (count && (df.if_max_iodevs == UNLIMITED_IODEVS))) df.if_max_iodevs = count; df.if_names = safe_alloc(count * sizeof (char *)); (void) memset(df.if_names, 0, count * sizeof (char *)); df.if_nr_names = 0; while (optind < argc && !isdigit(argv[optind][0])) df.if_names[df.if_nr_names++] = argv[optind++]; } if (optind < argc) { interval = safe_strtoi(argv[optind], "invalid interval"); if (interval < 1) fail(0, "invalid interval"); optind++; if (optind < argc) { iter = safe_strtoi(argv[optind], "invalid count"); if (iter < 1) fail(0, "invalid count"); optind++; } } if (interval == 0) iter = 1; if (optind < argc) usage(); } /* * Driver for doing the extended header formatting. Will produce * the function stack needed to output an extended header based * on the options selected. */ void do_format(void) { char header[SMALL_SCRATCH_BUFLEN] = {0}; char ch; char iosz; const char *fstr; disk_header[0] = 0; ch = (do_interval ? 'i' : 's'); iosz = (do_megabytes ? 'M' : 'k'); if (do_disk & DISK_ERRORS) { if (do_raw == 0) { (void) sprintf(header, "s/w h/w trn tot "); } else (void) sprintf(header, "s/w,h/w,trn,tot"); } switch (do_disk & DISK_IO_MASK) { case DISK_OLD: if (do_raw == 0) fstr = "%cp%c tp%c serv "; else fstr = "%cp%c,tp%c,serv"; (void) snprintf(disk_header, sizeof (disk_header), fstr, iosz, ch, ch); break; case DISK_NEW: if (do_raw == 0) fstr = "rp%c wp%c util "; else fstr = "%rp%c,wp%c,util"; (void) snprintf(disk_header, sizeof (disk_header), fstr, ch, ch); break; case DISK_EXTENDED: /* This is -x option */ if (!do_conversions) { /* without -n option */ if (do_raw == 0) { /* without -r option */ (void) snprintf(disk_header, sizeof (disk_header), "%-*.*s r/%c w/%c " "%cr/%c %cw/%c wait actv " "svc_t %%%%w %%%%b %s", iodevs_nl, iodevs_nl, "device", ch, ch, iosz, ch, iosz, ch, header); } else { /* with -r option */ (void) snprintf(disk_header, sizeof (disk_header), "device,r/%c,w/%c,%cr/%c,%cw/%c," "wait,actv,svc_t,%%%%w," "%%%%b%s%s", ch, ch, iosz, ch, iosz, ch, *header == '\0' ? "" : ",", header); /* * if no -e flag, header == '\0...' * Ternary operator above is to prevent * trailing comma in full disk_header */ } } else { /* with -n option */ if (do_raw == 0) { fstr = " r/%c w/%c %cr/%c " "%cw/%c wait actv wsvc_t asvc_t " "%%%%w %%%%b %sdevice"; } else { fstr = "r/%c,w/%c,%cr/%c,%cw/%c," "wait,actv,wsvc_t,asvc_t," "%%%%w,%%%%b,%sdevice"; /* * if -rnxe, "tot" (from -e) and * "device" are run together * due to lack of trailing comma * in 'header'. However, adding * trailing comma to header at * its definition leads to prob- * lems elsewhere so it's added * here in this edge case -rnxe */ if (*header != '\0') (void) strcat(header, ","); } (void) snprintf(disk_header, sizeof (disk_header), fstr, ch, ch, iosz, ch, iosz, ch, header); } break; default: break; } /* do DISK_ERRORS header (already added above for DISK_EXTENDED) */ if ((do_disk & DISK_ERRORS) && ((do_disk & DISK_IO_MASK) != DISK_EXTENDED)) { if (!do_conversions) { if (do_raw == 0) (void) snprintf(disk_header, sizeof (disk_header), "%-*.*s %s", iodevs_nl, iodevs_nl, "device", header); else (void) snprintf(disk_header, sizeof (disk_header), "device,%s", header); } else { if (do_raw == 0) { (void) snprintf(disk_header, sizeof (disk_header), " %sdevice", header); } else { (void) snprintf(disk_header, sizeof (disk_header), "%s,device", header); } } } else { /* * Need to subtract two characters for the % escape in * the string. */ dh_len = strlen(disk_header) - 2; } /* * -n *and* (-E *or* -e *or* -x) */ if (do_conversions && (do_disk & PRINT_VERTICAL)) { if (do_tty) setup(print_tty_hdr1); if (do_cpu) setup(print_cpu_hdr1); if (do_tty || do_cpu) setup(do_newline); if (do_tty) setup(print_tty_hdr2); if (do_cpu) setup(print_cpu_hdr2); if (do_tty || do_cpu) setup(do_newline); if (do_tty) setup(print_tty_data); if (do_cpu) setup(print_cpu_data); if (do_tty || do_cpu) setup(do_newline); printxhdr(); setup(show_all_disks); } else { /* * These unholy gymnastics are necessary to place CPU/tty * data to the right of the disks/errors for the first * line in vertical mode. */ if (do_disk & PRINT_VERTICAL) { printxhdr(); setup(show_first_disk); if (do_tty) setup(print_tty_data); if (do_cpu) setup(print_cpu_data); setup(do_newline); setup(show_other_disks); } else { setup(hdrout); if (do_tty) setup(print_tty_data); setup(show_all_disks); if (do_cpu) setup(print_cpu_data); } setup(do_newline); } if (do_disk & DISK_EXTENDED_ERRORS) setup(disk_errors); } /* * Add a new function to the list of functions * for this invocation. Once on the stack the * function is never removed nor does its place * change. */ void setup(void (*nfunc)(void)) { format_t *tmp; tmp = safe_alloc(sizeof (format_t)); tmp->nfunc = nfunc; tmp->next = 0; if (formatter_end) formatter_end->next = tmp; else formatter_list = tmp; formatter_end = tmp; } /* * The functions after this comment are devoted to printing * various parts of the header. They are selected based on the * options provided when the program was invoked. The functions * are either directly invoked in printhdr() or are indirectly * invoked by being placed on the list of functions used when * extended headers are used. */ void print_tty_hdr1(void) { char *fstr; char *dstr; if (do_raw == 0) { fstr = "%10.10s"; dstr = "tty "; } else { fstr = "%s"; dstr = "tty"; } push_out(fstr, dstr); } void print_tty_hdr2(void) { if (do_raw == 0) push_out("%-10.10s", " tin tout"); else push_out("tin,tout"); } void print_cpu_hdr1(void) { char *dstr; if (do_raw == 0) dstr = " cpu"; else dstr = "cpu"; push_out(dstr); } void print_cpu_hdr2(void) { char *dstr; if (do_raw == 0) dstr = " us sy dt id"; else dstr = "us,sy,dt,id"; push_out(dstr); } /* * Assumption is that tty data is always first - no need for raw mode leading * comma. */ void print_tty_data(void) { char *fstr; uint64_t deltas; double raw; double outch; kstat_t *oldks = NULL; if (oldss) oldks = &oldss->s_sys.ss_agg_sys; if (do_raw == 0) fstr = " %3.0f %4.0f "; else fstr = "%.0f,%.0f"; deltas = kstat_delta(oldks, &newss->s_sys.ss_agg_sys, "rawch"); raw = deltas; raw /= getime; deltas = kstat_delta(oldks, &newss->s_sys.ss_agg_sys, "outch"); outch = deltas; outch /= getime; push_out(fstr, raw, outch); } /* * Write out CPU data */ void print_cpu_data(void) { char *fstr; uint64_t idle; uint64_t user; uint64_t kern; uint64_t dtrace; uint64_t nsec_elapsed; kstat_t *oldks = NULL; if (oldss) oldks = &oldss->s_sys.ss_agg_sys; if (do_raw == 0) fstr = " %2.0f %2.0f %2.0f %2.0f"; else fstr = "%.0f,%.0f,%.0f,%.0f"; idle = kstat_delta(oldks, &newss->s_sys.ss_agg_sys, "cpu_ticks_idle"); user = kstat_delta(oldks, &newss->s_sys.ss_agg_sys, "cpu_ticks_user"); kern = kstat_delta(oldks, &newss->s_sys.ss_agg_sys, "cpu_ticks_kernel"); dtrace = kstat_delta(oldks, &newss->s_sys.ss_agg_sys, "cpu_nsec_dtrace"); nsec_elapsed = newss->s_sys.ss_agg_sys.ks_snaptime - (oldks == NULL ? 0 : oldks->ks_snaptime); push_out(fstr, user * percent, kern * percent, dtrace * 100.0 / nsec_elapsed / newss->s_nr_active_cpus, idle * percent); } /* * Emit the appropriate header. */ void hdrout(void) { if (do_raw == 0) { if (--tohdr == 0) printhdr(0); } else if (hdr_out == 0) { printhdr(0); hdr_out = 1; } } /* * Write out disk errors when -E is specified. */ void disk_errors(void) { (void) snapshot_walk(SNAP_IODEVS, oldss, newss, show_disk_errors, NULL); } void show_first_disk(void) { int count = 0; show_disk_mode = SHOW_FIRST_ONLY; (void) snapshot_walk(SNAP_IODEVS, oldss, newss, show_disk, &count); } void show_other_disks(void) { int count = 0; show_disk_mode = SHOW_SECOND_ONWARDS; (void) snapshot_walk(SNAP_IODEVS, oldss, newss, show_disk, &count); } void show_all_disks(void) { int count = 0; show_disk_mode = SHOW_ALL; (void) snapshot_walk(SNAP_IODEVS, oldss, newss, show_disk, &count); } /* * Write a newline out and clear the lineout flag. */ static void do_newline(void) { if (lineout) { (void) putchar('\n'); lineout = 0; } } /* * Generalized printf function that determines what extra * to print out if we're in raw mode. At this time we * don't care about errors. */ static void push_out(const char *message, ...) { va_list args; va_start(args, message); if (do_raw && lineout == 1) (void) putchar(','); (void) vprintf(message, args); va_end(args); lineout = 1; } /* * Emit the header string when -e is specified. */ static void print_err_hdr(void) { char obuf[SMALL_SCRATCH_BUFLEN]; if (do_raw) { push_out("errors"); return; } if (do_conversions == 0) { if (!(do_disk & DISK_EXTENDED)) { (void) snprintf(obuf, sizeof (obuf), "%11s", one_blank); push_out(obuf); } } else if (do_disk == DISK_ERRORS) push_out(two_blanks); else push_out(one_blank); push_out("---- errors --- "); } /* * Emit the header string when -e is specified. */ static void print_disk_header(void) { push_out(disk_header); } /* * No, UINTMAX_MAX isn't the right thing here since * it is #defined to be either INT32_MAX or INT64_MAX * depending on the whether _LP64 is defined. * * We want to handle the odd future case of having * ulonglong_t be more than 64 bits but we have * no nice #define MAX value we can drop in place * without having to change this code in the future. */ u_longlong_t ull_delta(u_longlong_t old, u_longlong_t new) { if (new >= old) return (new - old); else return ((UINT64_MAX - old) + new + 1); } /* * Take the difference of an unsigned 32 * bit int attempting to cater for * overflow. */ uint_t u32_delta(uint_t old, uint_t new) { if (new >= old) return (new - old); else return ((UINT32_MAX - old) + new + 1); } /* * This is exactly what is needed for standard iostat output, * but make sure to use it only for that */ #define EPSILON (0.1) static int fzero(double value) { return (value >= 0.0 && value < EPSILON); } static int safe_strtoi(char const *val, char *errmsg) { char *end; long tmp; errno = 0; tmp = strtol(val, &end, 10); if (*end != '\0' || errno) fail(0, "%s %s", errmsg, val); return ((int)tmp); } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright 2020 Peter tribble. # Copyright 2025 Hans Rosenfeld # PROG = kstat OBJS = kstat.o SRCS =$(OBJS:%.o=%.c) $(COMMON_SRCS) CLEANFILES = $(OBJS) $(COMMON_OBJS:%.o=%.c) include $(SRC)/cmd/Makefile.cmd include $(SRC)/cmd/stat/Makefile.stat LDLIBS += -lavl -lcmdutils -ldevinfo -lgen -lkstat CFLAGS += $(CCVERBOSE) CERRWARN += -Wno-switch CERRWARN += -Wno-parentheses FILEMODE= 0555 include $(SRC)/cmd/stat/Makefile.stat.targ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2013 David Hoeppner. All rights reserved. * Copyright 2013 Nexenta Systems, Inc. All rights reserved. * Copyright 2016 Joyent, Inc. * Copyright 2020 Peter Tribble. * Copyright 2025 Edgecast Cloud LLC. */ /* * Display kernel statistics * * This is a reimplementation of the perl kstat command originally found * under usr/src/cmd/kstat/kstat.pl * * Incompatibilities: * - perl regular expressions replaced with extended REs bracketed by '/' * * Flags added: * -C similar to the -p option but value is separated by a colon * -h display help * -j json format */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kstat.h" #include "statcommon.h" char *cmdname = "kstat"; /* Name of this command */ int caught_cont = 0; /* Have caught a SIGCONT */ static uint_t g_timestamp_fmt = NODATE; /* Helper flag - header was printed already? */ static boolean_t g_headerflg; /* Saved command line options */ static boolean_t g_cflg = B_FALSE; static boolean_t g_jflg = B_FALSE; static boolean_t g_lflg = B_FALSE; static boolean_t g_pflg = B_FALSE; static boolean_t g_qflg = B_FALSE; static boolean_t g_Vflg = B_FALSE; static ks_pattern_t g_ks_class = {"*", 0}; static boolean_t g_matched = B_FALSE; /* Sorted list of kstat instances */ static list_t instances_list; static list_t selector_list; static boolean_t invalid_value(char *str) { /* Empty string is same as "*" */ if (str == NULL) return (B_TRUE); if (strchr(str, '*') != NULL) return (B_TRUE); /* regex is enclosed between // */ if (str[0] == '/') { size_t s = strlen(str); if (str[s - 1] == '/') return (B_TRUE); } return (B_FALSE); } static boolean_t invalid_values(ks_selector_t *selector) { /* * -V needs exact match of module and statistics or * name and statistics. */ if ((!invalid_value(selector->ks_module.pstr) || !invalid_value(selector->ks_name.pstr)) && !invalid_value(selector->ks_statistic.pstr)) return (B_FALSE); (void) fprintf(stderr, gettext( "-V needs exact module or name, and statistics\n")); return (B_TRUE); } int main(int argc, char **argv) { ks_selector_t *nselector; ks_selector_t *uselector; kstat_ctl_t *kc; hrtime_t start_n; hrtime_t period_n; boolean_t errflg = B_FALSE; boolean_t nselflg = B_FALSE; boolean_t uselflg = B_FALSE; char *q; int count = 1; int infinite_cycles = 0; int interval = 0; int n = 0; int c, m, tmp; (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it wasn't */ #endif (void) textdomain(TEXT_DOMAIN); /* * Create the selector list and a dummy default selector to match * everything. While we process the cmdline options we will add * selectors to this list. */ list_create(&selector_list, sizeof (ks_selector_t), offsetof(ks_selector_t, ks_next)); nselector = new_selector(); /* * Parse named command line arguments. */ while ((c = getopt(argc, argv, "h?CqjlpT:Vm:i:n:s:c:")) != EOF) switch (c) { case 'h': case '?': usage(); exit(0); break; case 'C': g_pflg = g_cflg = B_TRUE; break; case 'q': g_qflg = B_TRUE; break; case 'j': /* * If we're printing JSON, we're going to force numeric * representation to be in the C locale to assure that * the decimal point is compliant with RFC 7159 (i.e., * ASCII 0x2e). */ (void) setlocale(LC_NUMERIC, "C"); g_jflg = B_TRUE; break; case 'l': g_pflg = g_lflg = B_TRUE; break; case 'p': g_pflg = B_TRUE; break; case 'T': switch (*optarg) { case 'd': g_timestamp_fmt = DDATE; break; case 'u': g_timestamp_fmt = UDATE; break; default: errflg = B_TRUE; } break; case 'V': g_Vflg = B_TRUE; break; case 'm': nselflg = B_TRUE; nselector->ks_module.pstr = (char *)ks_safe_strdup(optarg); break; case 'i': nselflg = B_TRUE; nselector->ks_instance.pstr = (char *)ks_safe_strdup(optarg); break; case 'n': nselflg = B_TRUE; nselector->ks_name.pstr = (char *)ks_safe_strdup(optarg); break; case 's': nselflg = B_TRUE; nselector->ks_statistic.pstr = (char *)ks_safe_strdup(optarg); break; case 'c': g_ks_class.pstr = (char *)ks_safe_strdup(optarg); break; default: errflg = B_TRUE; break; } if (g_Vflg && !g_pflg) { (void) fprintf(stderr, gettext( "-V can only be used with -p\n")); errflg = B_TRUE; } if (g_Vflg && (g_timestamp_fmt != NODATE || g_lflg || g_jflg || g_cflg)) { (void) fprintf(stderr, gettext( "-jlCT and -V are mutually exclusive\n")); errflg = B_TRUE; } if (g_qflg && (g_jflg || g_pflg)) { (void) fprintf(stderr, gettext( "-q and -lpj are mutually exclusive\n")); errflg = B_TRUE; } if (errflg) { usage(); exit(2); } argc -= optind; argv += optind; /* * Consume the rest of the command line. Parsing the * unnamed command line arguments. */ while (argc--) { errno = 0; tmp = strtoul(*argv, &q, 10); if (tmp == ULONG_MAX && errno == ERANGE) { if (n == 0) { (void) fprintf(stderr, gettext( "Interval is too large\n")); } else if (n == 1) { (void) fprintf(stderr, gettext( "Count is too large\n")); } usage(); exit(2); } if (errno != 0 || *q != '\0') { m = 0; uselector = new_selector(); while ((q = (char *)strsep(argv, ":")) != NULL) { m++; if (m > 4) { free(uselector); usage(); exit(2); } if (*q != '\0') { switch (m) { case 1: uselector->ks_module.pstr = (char *)ks_safe_strdup(q); break; case 2: uselector->ks_instance.pstr = (char *)ks_safe_strdup(q); break; case 3: uselector->ks_name.pstr = (char *)ks_safe_strdup(q); break; case 4: uselector->ks_statistic.pstr = (char *)ks_safe_strdup(q); break; default: assert(B_FALSE); } } } /* * With -V, only do allow exact matches. */ if (g_Vflg && invalid_values(uselector)) { exit(2); } uselflg = B_TRUE; list_insert_tail(&selector_list, uselector); } else { if (tmp < 1) { if (n == 0) { (void) fprintf(stderr, gettext( "Interval must be an " "integer >= 1")); } else if (n == 1) { (void) fprintf(stderr, gettext( "Count must be an integer >= 1")); } usage(); exit(2); } else { if (n == 0) { interval = tmp; count = -1; } else if (n == 1) { count = tmp; } else { usage(); exit(2); } } n++; } argv++; } /* * Check if we founded a named selector on the cmdline. */ if (uselflg) { if (nselflg) { (void) fprintf(stderr, gettext( "[module[:instance[:name[:statistic]]]] and " "-m -i -n -s are mutually exclusive")); usage(); exit(2); } else { free(nselector); } } else { /* * With -V, only do allow exact matches. */ if (g_Vflg && invalid_values(nselector)) { exit(2); } list_insert_tail(&selector_list, nselector); } assert(!list_is_empty(&selector_list)); list_create(&instances_list, sizeof (ks_instance_t), offsetof(ks_instance_t, ks_next)); while ((kc = kstat_open()) == NULL) { if (errno == EAGAIN) { (void) poll(NULL, 0, 200); } else { perror("kstat_open"); exit(3); } } if (count > 1) { if (signal(SIGCONT, cont_handler) == SIG_ERR) { (void) fprintf(stderr, gettext( "signal failed")); exit(3); } } period_n = (hrtime_t)interval * NANOSEC; start_n = gethrtime(); while (count == -1 || count-- > 0) { ks_instances_read(kc); ks_instances_print(); if (interval && count) { ks_sleep_until(&start_n, period_n, infinite_cycles, &caught_cont); (void) kstat_chain_update(kc); (void) putchar('\n'); } } (void) kstat_close(kc); /* * Return a non-zero exit code if we didn't match anything. */ return (g_matched ? 0 : 1); } /* * Print usage. */ static void usage(void) { (void) fprintf(stderr, gettext( "Usage:\n" "kstat [ -Cjlq ] [ -p [ -V ]] [ -T d|u ] [ -c class ]\n" " [ -m module ] [ -i instance ] [ -n name ] [ -s statistic ]\n" " [ interval [ count ] ]\n" "kstat [ -Cjlq ] [ -p [ -V ]] [ -T d|u ] [ -c class ]\n" " [ module[:instance[:name[:statistic]]] ... ]\n" " [ interval [ count ] ]\n")); } /* * Sort compare function. */ static int compare_instances(ks_instance_t *l_arg, ks_instance_t *r_arg) { int rval; rval = strcasecmp(l_arg->ks_module, r_arg->ks_module); if (rval == 0) { if (l_arg->ks_instance == r_arg->ks_instance) { return (strcasecmp(l_arg->ks_name, r_arg->ks_name)); } else if (l_arg->ks_instance < r_arg->ks_instance) { return (-1); } else { return (1); } } else { return (rval); } } static char * ks_safe_strdup(char *str) { char *ret; if (str == NULL) { return (NULL); } while ((ret = strdup(str)) == NULL) { if (errno == EAGAIN) { (void) poll(NULL, 0, 200); } else { perror("strdup"); exit(3); } } return (ret); } static void ks_sleep_until(hrtime_t *wakeup, hrtime_t interval, int forever, int *caught_cont) { hrtime_t now, pause, pause_left; struct timespec pause_tv; int status; now = gethrtime(); pause = *wakeup + interval - now; if (pause <= 0 || pause < (interval / 4)) { if (forever || *caught_cont) { *wakeup = now + interval; pause = interval; } else { pause = interval / 2; *wakeup += interval; } } else { *wakeup += interval; } if (pause < 1000) { return; } pause_left = pause; do { pause_tv.tv_sec = pause_left / NANOSEC; pause_tv.tv_nsec = pause_left % NANOSEC; status = nanosleep(&pause_tv, (struct timespec *)NULL); if (status < 0) { if (errno == EINTR) { now = gethrtime(); pause_left = *wakeup - now; if (pause_left < 1000) { return; } } else { perror("nanosleep"); exit(3); } } } while (status != 0); } /* * Inserts an instance in the per selector list. */ static void nvpair_insert(ks_instance_t *ksi, char *name, ks_value_t *value, uchar_t data_type) { ks_nvpair_t *instance; ks_nvpair_t *tmp; instance = (ks_nvpair_t *)malloc(sizeof (ks_nvpair_t)); if (instance == NULL) { perror("malloc"); exit(3); } (void) strlcpy(instance->name, name, KSTAT_STRLEN); (void) memcpy(&instance->value, value, sizeof (ks_value_t)); instance->data_type = data_type; tmp = list_head(&ksi->ks_nvlist); while (tmp != NULL && strcasecmp(instance->name, tmp->name) > 0) tmp = list_next(&ksi->ks_nvlist, tmp); list_insert_before(&ksi->ks_nvlist, tmp, instance); } /* * Allocates a new all-matching selector. */ static ks_selector_t * new_selector(void) { ks_selector_t *selector; selector = (ks_selector_t *)malloc(sizeof (ks_selector_t)); if (selector == NULL) { perror("malloc"); exit(3); } list_link_init(&selector->ks_next); selector->ks_module.pstr = "*"; selector->ks_instance.pstr = "*"; selector->ks_name.pstr = "*"; selector->ks_statistic.pstr = "*"; return (selector); } /* * This function was taken from the perl kstat module code - please * see for further comments there. */ static kstat_raw_reader_t lookup_raw_kstat_fn(char *module, char *name) { char key[KSTAT_STRLEN * 2]; register char *f, *t; int n = 0; for (f = module, t = key; *f != '\0'; f++, t++) { while (*f != '\0' && isdigit(*f)) f++; *t = *f; } *t++ = ':'; for (f = name; *f != '\0'; f++, t++) { while (*f != '\0' && isdigit(*f)) f++; *t = *f; } *t = '\0'; while (ks_raw_lookup[n].fn != NULL) { if (strncmp(ks_raw_lookup[n].name, key, strlen(key)) == 0) return (ks_raw_lookup[n].fn); n++; } return (0); } /* * Match a string against a shell glob or extended regular expression. */ static boolean_t ks_match(const char *str, ks_pattern_t *pattern) { int regcode; char *regstr; char *errbuf; size_t bufsz; if (pattern->pstr != NULL && gmatch(pattern->pstr, "/*/") != 0) { /* All regex patterns are strdup'd copies */ regstr = pattern->pstr + 1; *(strrchr(regstr, '/')) = '\0'; regcode = regcomp(&pattern->preg, regstr, REG_EXTENDED | REG_NOSUB); if (regcode != 0) { bufsz = regerror(regcode, NULL, NULL, 0); if (bufsz != 0) { errbuf = malloc(bufsz); if (errbuf == NULL) { perror("malloc"); exit(3); } (void) regerror(regcode, NULL, errbuf, bufsz); (void) fprintf(stderr, "kstat: %s\n", errbuf); } usage(); exit(2); } pattern->pstr = NULL; } if (pattern->pstr == NULL) { return (regexec(&pattern->preg, str, 0, NULL, 0) == 0); } return ((gmatch(str, pattern->pstr) != 0)); } /* * Iterate over all kernel statistics and save matches. */ static void ks_instances_read(kstat_ctl_t *kc) { kstat_raw_reader_t save_raw = NULL; kid_t id; ks_selector_t *selector; ks_instance_t *ksi; ks_instance_t *tmp; kstat_t *kp; boolean_t skip; for (kp = kc->kc_chain; kp != NULL; kp = kp->ks_next) { /* Don't bother storing the kstat headers */ if (strncmp(kp->ks_name, "kstat_", 6) == 0) { continue; } /* Don't bother storing raw stats we don't understand */ if (kp->ks_type == KSTAT_TYPE_RAW) { save_raw = lookup_raw_kstat_fn(kp->ks_module, kp->ks_name); if (save_raw == NULL) { #ifdef REPORT_UNKNOWN (void) fprintf(stderr, "Unknown kstat type %s:%d:%s - " "%d of size %d\n", kp->ks_module, kp->ks_instance, kp->ks_name, kp->ks_ndata, kp->ks_data_size); #endif continue; } } /* * Iterate over the list of selectors and skip * instances we dont want. We filter for statistics * later, as we dont know them yet. */ skip = B_TRUE; selector = list_head(&selector_list); while (selector != NULL) { if (ks_match(kp->ks_module, &selector->ks_module) && ks_match(kp->ks_name, &selector->ks_name)) { skip = B_FALSE; break; } selector = list_next(&selector_list, selector); } if (skip) { continue; } /* * Allocate a new instance and fill in the values * we know so far. */ ksi = (ks_instance_t *)malloc(sizeof (ks_instance_t)); if (ksi == NULL) { perror("malloc"); exit(3); } list_link_init(&ksi->ks_next); (void) strlcpy(ksi->ks_module, kp->ks_module, KSTAT_STRLEN); (void) strlcpy(ksi->ks_name, kp->ks_name, KSTAT_STRLEN); (void) strlcpy(ksi->ks_class, kp->ks_class, KSTAT_STRLEN); ksi->ks_instance = kp->ks_instance; ksi->ks_snaptime = kp->ks_snaptime; ksi->ks_type = kp->ks_type; list_create(&ksi->ks_nvlist, sizeof (ks_nvpair_t), offsetof(ks_nvpair_t, nv_next)); SAVE_HRTIME_X(ksi, "crtime", kp->ks_crtime); if (g_pflg) { SAVE_STRING_X(ksi, "class", kp->ks_class); } /* Insert this instance into a sorted list */ tmp = list_head(&instances_list); while (tmp != NULL && compare_instances(ksi, tmp) > 0) tmp = list_next(&instances_list, tmp); list_insert_before(&instances_list, tmp, ksi); /* Read the actual statistics */ id = kstat_read(kc, kp, NULL); if (id == -1) { #ifdef REPORT_UNKNOWN perror("kstat_read"); #endif continue; } SAVE_HRTIME_X(ksi, "snaptime", kp->ks_snaptime); switch (kp->ks_type) { case KSTAT_TYPE_RAW: save_raw(kp, ksi); break; case KSTAT_TYPE_NAMED: save_named(kp, ksi); break; case KSTAT_TYPE_INTR: save_intr(kp, ksi); break; case KSTAT_TYPE_IO: save_io(kp, ksi); break; case KSTAT_TYPE_TIMER: save_timer(kp, ksi); break; default: assert(B_FALSE); /* Invalid type */ break; } } } /* * Print the value of a name-value pair. */ static void ks_value_print(ks_nvpair_t *nvpair) { switch (nvpair->data_type) { case KSTAT_DATA_CHAR: (void) fprintf(stdout, "%s", nvpair->value.c); break; case KSTAT_DATA_INT32: (void) fprintf(stdout, "%d", nvpair->value.i32); break; case KSTAT_DATA_UINT32: (void) fprintf(stdout, "%u", nvpair->value.ui32); break; case KSTAT_DATA_INT64: (void) fprintf(stdout, "%lld", nvpair->value.i64); break; case KSTAT_DATA_UINT64: (void) fprintf(stdout, "%llu", nvpair->value.ui64); break; case KSTAT_DATA_STRING: (void) fprintf(stdout, "%s", KSTAT_NAMED_STR_PTR(nvpair)); break; case KSTAT_DATA_HRTIME: if (nvpair->value.ui64 == 0) (void) fprintf(stdout, "0"); else (void) fprintf(stdout, "%.9f", nvpair->value.ui64 / 1000000000.0); break; default: assert(B_FALSE); } } /* * Print a single instance. */ /*ARGSUSED*/ static void ks_instance_print(ks_instance_t *ksi, ks_nvpair_t *nvpair, boolean_t last) { if (g_headerflg) { if (!g_pflg) { (void) fprintf(stdout, DFLT_FMT, ksi->ks_module, ksi->ks_instance, ksi->ks_name, ksi->ks_class); } g_headerflg = B_FALSE; } if (g_pflg) { if (!g_Vflg) { (void) fprintf(stdout, KS_PFMT, ksi->ks_module, ksi->ks_instance, ksi->ks_name, nvpair->name); } if (!g_lflg) { if (!g_Vflg) (void) putchar(g_cflg ? ':': '\t'); ks_value_print(nvpair); } } else { (void) fprintf(stdout, KS_DFMT, nvpair->name); ks_value_print(nvpair); } (void) putchar('\n'); } /* * Print a C string as a JSON string. */ static void ks_print_json_string(const char *str) { char c; (void) putchar('"'); while ((c = *str++) != '\0') { /* * For readability, we use the allowed alternate escape * sequence for quote, question mark, reverse solidus (look * it up!), newline and tab -- and use the universal escape * sequence for all other control characters. */ switch (c) { case '"': case '?': case '\\': (void) fprintf(stdout, "\\%c", c); break; case '\n': (void) fprintf(stdout, "\\n"); break; case '\t': (void) fprintf(stdout, "\\t"); break; default: /* * By escaping those characters for which isprint(3C) * is false, we escape both the RFC 7159 mandated * escaped range of 0x01 through 0x1f as well as DEL * (0x7f -- the control character that RFC 7159 forgot) * and then everything else that's unprintable for * good measure. */ if (!isprint(c)) { (void) fprintf(stdout, "\\u%04hhx", (uint8_t)c); break; } (void) putchar(c); break; } } (void) putchar('"'); } /* * Print a single instance in JSON format. */ static void ks_instance_print_json(ks_instance_t *ksi, ks_nvpair_t *nvpair, boolean_t last) { static int headers; if (g_headerflg) { if (headers++ > 0) (void) fprintf(stdout, ", "); (void) fprintf(stdout, "{\n\t\"module\": "); ks_print_json_string(ksi->ks_module); (void) fprintf(stdout, ",\n\t\"instance\": %d,\n\t\"name\": ", ksi->ks_instance); ks_print_json_string(ksi->ks_name); (void) fprintf(stdout, ",\n\t\"class\": "); ks_print_json_string(ksi->ks_class); (void) fprintf(stdout, ",\n\t\"type\": %d,\n", ksi->ks_type); if (ksi->ks_snaptime == 0) (void) fprintf(stdout, "\t\"snaptime\": 0,\n"); else (void) fprintf(stdout, "\t\"snaptime\": %.9f,\n", ksi->ks_snaptime / 1000000000.0); (void) fprintf(stdout, "\t\"data\": {\n"); g_headerflg = B_FALSE; } (void) fprintf(stdout, "\t\t"); ks_print_json_string(nvpair->name); (void) fprintf(stdout, ": "); switch (nvpair->data_type) { case KSTAT_DATA_CHAR: ks_print_json_string(nvpair->value.c); break; case KSTAT_DATA_STRING: ks_print_json_string(KSTAT_NAMED_STR_PTR(nvpair)); break; default: ks_value_print(nvpair); break; } if (!last) (void) putchar(','); (void) putchar('\n'); } /* * Print all instances. */ static void ks_instances_print(void) { ks_selector_t *selector; ks_instance_t *ksi, *ktmp; ks_nvpair_t *nvpair, *ntmp, *next; void (*ks_print_fn)(ks_instance_t *, ks_nvpair_t *, boolean_t); char *ks_number; if (g_timestamp_fmt != NODATE) print_timestamp(g_timestamp_fmt); if (g_jflg) { ks_print_fn = &ks_instance_print_json; (void) putchar('['); } else { ks_print_fn = &ks_instance_print; } /* Iterate over each selector */ selector = list_head(&selector_list); while (selector != NULL) { /* Iterate over each instance */ for (ksi = list_head(&instances_list); ksi != NULL; ksi = list_next(&instances_list, ksi)) { (void) asprintf(&ks_number, "%d", ksi->ks_instance); if (!(ks_match(ksi->ks_module, &selector->ks_module) && ks_match(ksi->ks_name, &selector->ks_name) && ks_match(ks_number, &selector->ks_instance) && ks_match(ksi->ks_class, &g_ks_class))) { free(ks_number); continue; } free(ks_number); g_headerflg = B_TRUE; /* * Find our first statistic to print. */ for (nvpair = list_head(&ksi->ks_nvlist); nvpair != NULL; nvpair = list_next(&ksi->ks_nvlist, nvpair)) { if (ks_match(nvpair->name, &selector->ks_statistic)) break; } while (nvpair != NULL) { boolean_t last; /* * Find the next statistic to print so we can * indicate to the print function if this * statistic is the last to be printed for * this instance. */ for (next = list_next(&ksi->ks_nvlist, nvpair); next != NULL; next = list_next(&ksi->ks_nvlist, next)) { if (ks_match(next->name, &selector->ks_statistic)) break; } g_matched = B_TRUE; last = next == NULL ? B_TRUE : B_FALSE; if (!g_qflg) (*ks_print_fn)(ksi, nvpair, last); nvpair = next; } if (!g_headerflg) { if (g_jflg) { (void) fprintf(stdout, "\t}\n}"); } else if (!g_pflg) { (void) putchar('\n'); } } } selector = list_next(&selector_list, selector); } if (g_jflg) (void) fprintf(stdout, "]\n"); (void) fflush(stdout); /* Free the instances list */ ksi = list_head(&instances_list); while (ksi != NULL) { nvpair = list_head(&ksi->ks_nvlist); while (nvpair != NULL) { ntmp = nvpair; nvpair = list_next(&ksi->ks_nvlist, nvpair); list_remove(&ksi->ks_nvlist, ntmp); if (ntmp->data_type == KSTAT_DATA_STRING) free(ntmp->value.str.addr.ptr); free(ntmp); } ktmp = ksi; ksi = list_next(&instances_list, ksi); list_remove(&instances_list, ktmp); list_destroy(&ktmp->ks_nvlist); free(ktmp); } } static void save_cpu_stat(kstat_t *kp, ks_instance_t *ksi) { cpu_stat_t *stat; cpu_sysinfo_t *sysinfo; cpu_syswait_t *syswait; cpu_vminfo_t *vminfo; stat = (cpu_stat_t *)(kp->ks_data); sysinfo = &stat->cpu_sysinfo; syswait = &stat->cpu_syswait; vminfo = &stat->cpu_vminfo; SAVE_UINT32_X(ksi, "idle", sysinfo->cpu[CPU_IDLE]); SAVE_UINT32_X(ksi, "user", sysinfo->cpu[CPU_USER]); SAVE_UINT32_X(ksi, "kernel", sysinfo->cpu[CPU_KERNEL]); SAVE_UINT32_X(ksi, "wait", sysinfo->cpu[CPU_WAIT]); SAVE_UINT32_X(ksi, "wait_io", sysinfo->wait[W_IO]); SAVE_UINT32_X(ksi, "wait_swap", sysinfo->wait[W_SWAP]); SAVE_UINT32_X(ksi, "wait_pio", sysinfo->wait[W_PIO]); SAVE_UINT32(ksi, sysinfo, bread); SAVE_UINT32(ksi, sysinfo, bwrite); SAVE_UINT32(ksi, sysinfo, lread); SAVE_UINT32(ksi, sysinfo, lwrite); SAVE_UINT32(ksi, sysinfo, phread); SAVE_UINT32(ksi, sysinfo, phwrite); SAVE_UINT32(ksi, sysinfo, pswitch); SAVE_UINT32(ksi, sysinfo, trap); SAVE_UINT32(ksi, sysinfo, intr); SAVE_UINT32(ksi, sysinfo, syscall); SAVE_UINT32(ksi, sysinfo, sysread); SAVE_UINT32(ksi, sysinfo, syswrite); SAVE_UINT32(ksi, sysinfo, sysfork); SAVE_UINT32(ksi, sysinfo, sysvfork); SAVE_UINT32(ksi, sysinfo, sysexec); SAVE_UINT32(ksi, sysinfo, readch); SAVE_UINT32(ksi, sysinfo, writech); SAVE_UINT32(ksi, sysinfo, rcvint); SAVE_UINT32(ksi, sysinfo, xmtint); SAVE_UINT32(ksi, sysinfo, mdmint); SAVE_UINT32(ksi, sysinfo, rawch); SAVE_UINT32(ksi, sysinfo, canch); SAVE_UINT32(ksi, sysinfo, outch); SAVE_UINT32(ksi, sysinfo, msg); SAVE_UINT32(ksi, sysinfo, sema); SAVE_UINT32(ksi, sysinfo, namei); SAVE_UINT32(ksi, sysinfo, ufsiget); SAVE_UINT32(ksi, sysinfo, ufsdirblk); SAVE_UINT32(ksi, sysinfo, ufsipage); SAVE_UINT32(ksi, sysinfo, ufsinopage); SAVE_UINT32(ksi, sysinfo, inodeovf); SAVE_UINT32(ksi, sysinfo, fileovf); SAVE_UINT32(ksi, sysinfo, procovf); SAVE_UINT32(ksi, sysinfo, intrthread); SAVE_UINT32(ksi, sysinfo, intrblk); SAVE_UINT32(ksi, sysinfo, idlethread); SAVE_UINT32(ksi, sysinfo, inv_swtch); SAVE_UINT32(ksi, sysinfo, nthreads); SAVE_UINT32(ksi, sysinfo, cpumigrate); SAVE_UINT32(ksi, sysinfo, xcalls); SAVE_UINT32(ksi, sysinfo, mutex_adenters); SAVE_UINT32(ksi, sysinfo, rw_rdfails); SAVE_UINT32(ksi, sysinfo, rw_wrfails); SAVE_UINT32(ksi, sysinfo, modload); SAVE_UINT32(ksi, sysinfo, modunload); SAVE_UINT32(ksi, sysinfo, bawrite); #ifdef STATISTICS /* see header file */ SAVE_UINT32(ksi, sysinfo, rw_enters); SAVE_UINT32(ksi, sysinfo, win_uo_cnt); SAVE_UINT32(ksi, sysinfo, win_uu_cnt); SAVE_UINT32(ksi, sysinfo, win_so_cnt); SAVE_UINT32(ksi, sysinfo, win_su_cnt); SAVE_UINT32(ksi, sysinfo, win_suo_cnt); #endif SAVE_INT32(ksi, syswait, iowait); SAVE_INT32(ksi, syswait, swap); SAVE_INT32(ksi, syswait, physio); SAVE_UINT32(ksi, vminfo, pgrec); SAVE_UINT32(ksi, vminfo, pgfrec); SAVE_UINT32(ksi, vminfo, pgin); SAVE_UINT32(ksi, vminfo, pgpgin); SAVE_UINT32(ksi, vminfo, pgout); SAVE_UINT32(ksi, vminfo, pgpgout); SAVE_UINT32(ksi, vminfo, swapin); SAVE_UINT32(ksi, vminfo, pgswapin); SAVE_UINT32(ksi, vminfo, swapout); SAVE_UINT32(ksi, vminfo, pgswapout); SAVE_UINT32(ksi, vminfo, zfod); SAVE_UINT32(ksi, vminfo, dfree); SAVE_UINT32(ksi, vminfo, scan); SAVE_UINT32(ksi, vminfo, rev); SAVE_UINT32(ksi, vminfo, hat_fault); SAVE_UINT32(ksi, vminfo, as_fault); SAVE_UINT32(ksi, vminfo, maj_fault); SAVE_UINT32(ksi, vminfo, cow_fault); SAVE_UINT32(ksi, vminfo, prot_fault); SAVE_UINT32(ksi, vminfo, softlock); SAVE_UINT32(ksi, vminfo, kernel_asflt); SAVE_UINT32(ksi, vminfo, pgrrun); SAVE_UINT32(ksi, vminfo, execpgin); SAVE_UINT32(ksi, vminfo, execpgout); SAVE_UINT32(ksi, vminfo, execfree); SAVE_UINT32(ksi, vminfo, anonpgin); SAVE_UINT32(ksi, vminfo, anonpgout); SAVE_UINT32(ksi, vminfo, anonfree); SAVE_UINT32(ksi, vminfo, fspgin); SAVE_UINT32(ksi, vminfo, fspgout); SAVE_UINT32(ksi, vminfo, fsfree); } static void save_var(kstat_t *kp, ks_instance_t *ksi) { struct var *var = (struct var *)(kp->ks_data); assert(kp->ks_data_size == sizeof (struct var)); SAVE_INT32(ksi, var, v_buf); SAVE_INT32(ksi, var, v_call); SAVE_INT32(ksi, var, v_proc); SAVE_INT32(ksi, var, v_maxupttl); SAVE_INT32(ksi, var, v_nglobpris); SAVE_INT32(ksi, var, v_maxsyspri); SAVE_INT32(ksi, var, v_clist); SAVE_INT32(ksi, var, v_maxup); SAVE_INT32(ksi, var, v_hbuf); SAVE_INT32(ksi, var, v_hmask); SAVE_INT32(ksi, var, v_pbuf); SAVE_INT32(ksi, var, v_sptmap); SAVE_INT32(ksi, var, v_maxpmem); SAVE_INT32(ksi, var, v_autoup); SAVE_INT32(ksi, var, v_bufhwm); } static void save_ncstats(kstat_t *kp, ks_instance_t *ksi) { struct ncstats *ncstats = (struct ncstats *)(kp->ks_data); assert(kp->ks_data_size == sizeof (struct ncstats)); SAVE_INT32(ksi, ncstats, hits); SAVE_INT32(ksi, ncstats, misses); SAVE_INT32(ksi, ncstats, enters); SAVE_INT32(ksi, ncstats, dbl_enters); SAVE_INT32(ksi, ncstats, long_enter); SAVE_INT32(ksi, ncstats, long_look); SAVE_INT32(ksi, ncstats, move_to_front); SAVE_INT32(ksi, ncstats, purges); } static void save_sysinfo(kstat_t *kp, ks_instance_t *ksi) { sysinfo_t *sysinfo = (sysinfo_t *)(kp->ks_data); assert(kp->ks_data_size == sizeof (sysinfo_t)); SAVE_UINT32(ksi, sysinfo, updates); SAVE_UINT32(ksi, sysinfo, runque); SAVE_UINT32(ksi, sysinfo, runocc); SAVE_UINT32(ksi, sysinfo, swpque); SAVE_UINT32(ksi, sysinfo, swpocc); SAVE_UINT32(ksi, sysinfo, waiting); } static void save_vminfo(kstat_t *kp, ks_instance_t *ksi) { vminfo_t *vminfo = (vminfo_t *)(kp->ks_data); assert(kp->ks_data_size == sizeof (vminfo_t)); SAVE_UINT64(ksi, vminfo, freemem); SAVE_UINT64(ksi, vminfo, swap_resv); SAVE_UINT64(ksi, vminfo, swap_alloc); SAVE_UINT64(ksi, vminfo, swap_avail); SAVE_UINT64(ksi, vminfo, swap_free); SAVE_UINT64(ksi, vminfo, updates); } static void save_nfs(kstat_t *kp, ks_instance_t *ksi) { struct mntinfo_kstat *mntinfo = (struct mntinfo_kstat *)(kp->ks_data); assert(kp->ks_data_size == sizeof (struct mntinfo_kstat)); SAVE_STRING(ksi, mntinfo, mik_proto); SAVE_UINT32(ksi, mntinfo, mik_vers); SAVE_UINT32(ksi, mntinfo, mik_flags); SAVE_UINT32(ksi, mntinfo, mik_secmod); SAVE_UINT32(ksi, mntinfo, mik_curread); SAVE_UINT32(ksi, mntinfo, mik_curwrite); SAVE_INT32(ksi, mntinfo, mik_timeo); SAVE_INT32(ksi, mntinfo, mik_retrans); SAVE_UINT32(ksi, mntinfo, mik_acregmin); SAVE_UINT32(ksi, mntinfo, mik_acregmax); SAVE_UINT32(ksi, mntinfo, mik_acdirmin); SAVE_UINT32(ksi, mntinfo, mik_acdirmax); SAVE_UINT32_X(ksi, "lookup_srtt", mntinfo->mik_timers[0].srtt); SAVE_UINT32_X(ksi, "lookup_deviate", mntinfo->mik_timers[0].deviate); SAVE_UINT32_X(ksi, "lookup_rtxcur", mntinfo->mik_timers[0].rtxcur); SAVE_UINT32_X(ksi, "read_srtt", mntinfo->mik_timers[1].srtt); SAVE_UINT32_X(ksi, "read_deviate", mntinfo->mik_timers[1].deviate); SAVE_UINT32_X(ksi, "read_rtxcur", mntinfo->mik_timers[1].rtxcur); SAVE_UINT32_X(ksi, "write_srtt", mntinfo->mik_timers[2].srtt); SAVE_UINT32_X(ksi, "write_deviate", mntinfo->mik_timers[2].deviate); SAVE_UINT32_X(ksi, "write_rtxcur", mntinfo->mik_timers[2].rtxcur); SAVE_UINT32(ksi, mntinfo, mik_noresponse); SAVE_UINT32(ksi, mntinfo, mik_failover); SAVE_UINT32(ksi, mntinfo, mik_remap); SAVE_STRING(ksi, mntinfo, mik_curserver); } #ifdef __sparc static void save_sfmmu_global_stat(kstat_t *kp, ks_instance_t *ksi) { struct sfmmu_global_stat *sfmmug = (struct sfmmu_global_stat *)(kp->ks_data); assert(kp->ks_data_size == sizeof (struct sfmmu_global_stat)); SAVE_INT32(ksi, sfmmug, sf_tsb_exceptions); SAVE_INT32(ksi, sfmmug, sf_tsb_raise_exception); SAVE_INT32(ksi, sfmmug, sf_pagefaults); SAVE_INT32(ksi, sfmmug, sf_uhash_searches); SAVE_INT32(ksi, sfmmug, sf_uhash_links); SAVE_INT32(ksi, sfmmug, sf_khash_searches); SAVE_INT32(ksi, sfmmug, sf_khash_links); SAVE_INT32(ksi, sfmmug, sf_swapout); SAVE_INT32(ksi, sfmmug, sf_tsb_alloc); SAVE_INT32(ksi, sfmmug, sf_tsb_allocfail); SAVE_INT32(ksi, sfmmug, sf_tsb_sectsb_create); SAVE_INT32(ksi, sfmmug, sf_scd_1sttsb_alloc); SAVE_INT32(ksi, sfmmug, sf_scd_2ndtsb_alloc); SAVE_INT32(ksi, sfmmug, sf_scd_1sttsb_allocfail); SAVE_INT32(ksi, sfmmug, sf_scd_2ndtsb_allocfail); SAVE_INT32(ksi, sfmmug, sf_tteload8k); SAVE_INT32(ksi, sfmmug, sf_tteload64k); SAVE_INT32(ksi, sfmmug, sf_tteload512k); SAVE_INT32(ksi, sfmmug, sf_tteload4m); SAVE_INT32(ksi, sfmmug, sf_tteload32m); SAVE_INT32(ksi, sfmmug, sf_tteload256m); SAVE_INT32(ksi, sfmmug, sf_tsb_load8k); SAVE_INT32(ksi, sfmmug, sf_tsb_load4m); SAVE_INT32(ksi, sfmmug, sf_hblk_hit); SAVE_INT32(ksi, sfmmug, sf_hblk8_ncreate); SAVE_INT32(ksi, sfmmug, sf_hblk8_nalloc); SAVE_INT32(ksi, sfmmug, sf_hblk1_ncreate); SAVE_INT32(ksi, sfmmug, sf_hblk1_nalloc); SAVE_INT32(ksi, sfmmug, sf_hblk_slab_cnt); SAVE_INT32(ksi, sfmmug, sf_hblk_reserve_cnt); SAVE_INT32(ksi, sfmmug, sf_hblk_recurse_cnt); SAVE_INT32(ksi, sfmmug, sf_hblk_reserve_hit); SAVE_INT32(ksi, sfmmug, sf_get_free_success); SAVE_INT32(ksi, sfmmug, sf_get_free_throttle); SAVE_INT32(ksi, sfmmug, sf_get_free_fail); SAVE_INT32(ksi, sfmmug, sf_put_free_success); SAVE_INT32(ksi, sfmmug, sf_put_free_fail); SAVE_INT32(ksi, sfmmug, sf_pgcolor_conflict); SAVE_INT32(ksi, sfmmug, sf_uncache_conflict); SAVE_INT32(ksi, sfmmug, sf_unload_conflict); SAVE_INT32(ksi, sfmmug, sf_ism_uncache); SAVE_INT32(ksi, sfmmug, sf_ism_recache); SAVE_INT32(ksi, sfmmug, sf_recache); SAVE_INT32(ksi, sfmmug, sf_steal_count); SAVE_INT32(ksi, sfmmug, sf_pagesync); SAVE_INT32(ksi, sfmmug, sf_clrwrt); SAVE_INT32(ksi, sfmmug, sf_pagesync_invalid); SAVE_INT32(ksi, sfmmug, sf_kernel_xcalls); SAVE_INT32(ksi, sfmmug, sf_user_xcalls); SAVE_INT32(ksi, sfmmug, sf_tsb_grow); SAVE_INT32(ksi, sfmmug, sf_tsb_shrink); SAVE_INT32(ksi, sfmmug, sf_tsb_resize_failures); SAVE_INT32(ksi, sfmmug, sf_tsb_reloc); SAVE_INT32(ksi, sfmmug, sf_user_vtop); SAVE_INT32(ksi, sfmmug, sf_ctx_inv); SAVE_INT32(ksi, sfmmug, sf_tlb_reprog_pgsz); SAVE_INT32(ksi, sfmmug, sf_region_remap_demap); SAVE_INT32(ksi, sfmmug, sf_create_scd); SAVE_INT32(ksi, sfmmug, sf_join_scd); SAVE_INT32(ksi, sfmmug, sf_leave_scd); SAVE_INT32(ksi, sfmmug, sf_destroy_scd); } #endif #ifdef __sparc static void save_sfmmu_tsbsize_stat(kstat_t *kp, ks_instance_t *ksi) { struct sfmmu_tsbsize_stat *sfmmut; assert(kp->ks_data_size == sizeof (struct sfmmu_tsbsize_stat)); sfmmut = (struct sfmmu_tsbsize_stat *)(kp->ks_data); SAVE_INT32(ksi, sfmmut, sf_tsbsz_8k); SAVE_INT32(ksi, sfmmut, sf_tsbsz_16k); SAVE_INT32(ksi, sfmmut, sf_tsbsz_32k); SAVE_INT32(ksi, sfmmut, sf_tsbsz_64k); SAVE_INT32(ksi, sfmmut, sf_tsbsz_128k); SAVE_INT32(ksi, sfmmut, sf_tsbsz_256k); SAVE_INT32(ksi, sfmmut, sf_tsbsz_512k); SAVE_INT32(ksi, sfmmut, sf_tsbsz_1m); SAVE_INT32(ksi, sfmmut, sf_tsbsz_2m); SAVE_INT32(ksi, sfmmut, sf_tsbsz_4m); } #endif static void save_named(kstat_t *kp, ks_instance_t *ksi) { kstat_named_t *knp; int n; for (n = kp->ks_ndata, knp = KSTAT_NAMED_PTR(kp); n > 0; n--, knp++) { /* * Annoyingly, some drivers have kstats with uninitialized * members (which kstat_install(9F) is sadly powerless to * prevent, and kstat_read(3KSTAT) unfortunately does nothing * to stop). To prevent these from confusing us to be * KSTAT_DATA_CHAR statistics, we skip over them. */ if (knp->name[0] == '\0') continue; switch (knp->data_type) { case KSTAT_DATA_CHAR: nvpair_insert(ksi, knp->name, (ks_value_t *)&knp->value, KSTAT_DATA_CHAR); break; case KSTAT_DATA_INT32: nvpair_insert(ksi, knp->name, (ks_value_t *)&knp->value, KSTAT_DATA_INT32); break; case KSTAT_DATA_UINT32: nvpair_insert(ksi, knp->name, (ks_value_t *)&knp->value, KSTAT_DATA_UINT32); break; case KSTAT_DATA_INT64: nvpair_insert(ksi, knp->name, (ks_value_t *)&knp->value, KSTAT_DATA_INT64); break; case KSTAT_DATA_UINT64: nvpair_insert(ksi, knp->name, (ks_value_t *)&knp->value, KSTAT_DATA_UINT64); break; case KSTAT_DATA_STRING: SAVE_STRING_X(ksi, knp->name, KSTAT_NAMED_STR_PTR(knp)); break; default: assert(B_FALSE); /* Invalid data type */ break; } } } static void save_intr(kstat_t *kp, ks_instance_t *ksi) { kstat_intr_t *intr = KSTAT_INTR_PTR(kp); char *intr_names[] = {"hard", "soft", "watchdog", "spurious", "multiple_service"}; int n; for (n = 0; n < KSTAT_NUM_INTRS; n++) SAVE_UINT32_X(ksi, intr_names[n], intr->intrs[n]); } static void save_io(kstat_t *kp, ks_instance_t *ksi) { kstat_io_t *ksio = KSTAT_IO_PTR(kp); SAVE_UINT64(ksi, ksio, nread); SAVE_UINT64(ksi, ksio, nwritten); SAVE_UINT32(ksi, ksio, reads); SAVE_UINT32(ksi, ksio, writes); SAVE_HRTIME(ksi, ksio, wtime); SAVE_HRTIME(ksi, ksio, wlentime); SAVE_HRTIME(ksi, ksio, wlastupdate); SAVE_HRTIME(ksi, ksio, rtime); SAVE_HRTIME(ksi, ksio, rlentime); SAVE_HRTIME(ksi, ksio, rlastupdate); SAVE_UINT32(ksi, ksio, wcnt); SAVE_UINT32(ksi, ksio, rcnt); } static void save_timer(kstat_t *kp, ks_instance_t *ksi) { kstat_timer_t *ktimer = KSTAT_TIMER_PTR(kp); SAVE_STRING(ksi, ktimer, name); SAVE_UINT64(ksi, ktimer, num_events); SAVE_HRTIME(ksi, ktimer, elapsed_time); SAVE_HRTIME(ksi, ktimer, min_time); SAVE_HRTIME(ksi, ktimer, max_time); SAVE_HRTIME(ksi, ktimer, start_time); SAVE_HRTIME(ksi, ktimer, stop_time); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Copyright 2013 David Hoeppner. All rights reserved. * Copyright 2013 Nexenta Systems, Inc. All rights reserved. * Copyright 2020 Peter Tribble. */ #ifndef _STAT_KSTAT_H #define _STAT_KSTAT_H /* * Structures needed by the kstat reader functions. */ #include #include #include #include #include #include #include #include #ifdef __sparc #include #endif #define KSTAT_DATA_HRTIME (KSTAT_DATA_STRING + 1) typedef union ks_value { char c[16]; int32_t i32; uint32_t ui32; struct { union { char *ptr; char __pad[8]; } addr; uint32_t len; } str; int64_t i64; uint64_t ui64; } ks_value_t; #define SAVE_HRTIME(I, S, N) \ { \ ks_value_t v; \ v.ui64 = S->N; \ nvpair_insert(I, #N, &v, KSTAT_DATA_UINT64); \ } #define SAVE_INT32(I, S, N) \ { \ ks_value_t v; \ v.i32 = S->N; \ nvpair_insert(I, #N, &v, KSTAT_DATA_INT32); \ } #define SAVE_UINT32(I, S, N) \ { \ ks_value_t v; \ v.ui32 = S->N; \ nvpair_insert(I, #N, &v, KSTAT_DATA_UINT32); \ } #define SAVE_INT64(I, S, N) \ { \ ks_value_t v; \ v.i64 = S->N; \ nvpair_insert(I, #N, &v, KSTAT_DATA_INT64); \ } #define SAVE_UINT64(I, S, N) \ { \ ks_value_t v; \ v.ui64 = S->N; \ nvpair_insert(I, #N, &v, KSTAT_DATA_UINT64); \ } /* * We dont want const "strings" because we free * the instances later. */ #define SAVE_STRING(I, S, N) \ { \ ks_value_t v; \ v.str.addr.ptr = safe_strdup(S->N); \ v.str.len = strlen(S->N); \ nvpair_insert(I, #N, &v, KSTAT_DATA_STRING); \ } #define SAVE_HRTIME_X(I, N, V) \ { \ ks_value_t v; \ v.ui64 = V; \ nvpair_insert(I, N, &v, KSTAT_DATA_HRTIME); \ } #define SAVE_INT32_X(I, N, V) \ { \ ks_value_t v; \ v.i32 = V; \ nvpair_insert(I, N, &v, KSTAT_DATA_INT32); \ } #define SAVE_UINT32_X(I, N, V) \ { \ ks_value_t v; \ v.ui32 = V; \ nvpair_insert(I, N, &v, KSTAT_DATA_UINT32); \ } #define SAVE_UINT64_X(I, N, V) \ { \ ks_value_t v; \ v.ui64 = V; \ nvpair_insert(I, N, &v, KSTAT_DATA_UINT64); \ } #define SAVE_STRING_X(I, N, V) \ { \ ks_value_t v; \ v.str.addr.ptr = safe_strdup(V); \ v.str.len = (V) ? strlen(V) : 0; \ nvpair_insert(I, N, &v, KSTAT_DATA_STRING); \ } #define SAVE_CHAR_X(I, N, V) \ { \ ks_value_t v; \ (void) asprintf(&v.str.addr.ptr, "%c", V); \ v.str.len = 1; \ nvpair_insert(I, N, &v, KSTAT_DATA_STRING); \ } #define DFLT_FMT \ "module: %-30.30s instance: %-6d\n" \ "name: %-30.30s class: %-.30s\n" #define KS_DFMT "\t%-30s " #define KS_PFMT "%s:%d:%s:%s" typedef struct ks_instance { list_node_t ks_next; char ks_name[KSTAT_STRLEN]; char ks_module[KSTAT_STRLEN]; char ks_class[KSTAT_STRLEN]; int ks_instance; uchar_t ks_type; hrtime_t ks_snaptime; list_t ks_nvlist; } ks_instance_t; typedef struct ks_nvpair { list_node_t nv_next; char name[KSTAT_STRLEN]; uchar_t data_type; ks_value_t value; } ks_nvpair_t; typedef struct ks_pattern { char *pstr; regex_t preg; } ks_pattern_t; typedef struct ks_selector { list_node_t ks_next; ks_pattern_t ks_module; ks_pattern_t ks_instance; ks_pattern_t ks_name; ks_pattern_t ks_statistic; } ks_selector_t; static void usage(void); static int compare_instances(ks_instance_t *, ks_instance_t *); static void nvpair_insert(ks_instance_t *, char *, ks_value_t *, uchar_t); static boolean_t ks_match(const char *, ks_pattern_t *); static ks_selector_t *new_selector(void); static void ks_instances_read(kstat_ctl_t *); static void ks_value_print(ks_nvpair_t *); static void ks_instances_print(void); static char *ks_safe_strdup(char *); static void ks_sleep_until(hrtime_t *, hrtime_t, int, int *); /* Raw kstat readers */ static void save_cpu_stat(kstat_t *, ks_instance_t *); static void save_var(kstat_t *, ks_instance_t *); static void save_ncstats(kstat_t *, ks_instance_t *); static void save_sysinfo(kstat_t *, ks_instance_t *); static void save_vminfo(kstat_t *, ks_instance_t *); static void save_nfs(kstat_t *, ks_instance_t *); #ifdef __sparc static void save_sfmmu_global_stat(kstat_t *, ks_instance_t *); static void save_sfmmu_tsbsize_stat(kstat_t *, ks_instance_t *); #endif /* Named kstat readers */ static void save_named(kstat_t *, ks_instance_t *); static void save_intr(kstat_t *, ks_instance_t *); static void save_io(kstat_t *, ks_instance_t *); static void save_timer(kstat_t *, ks_instance_t *); /* Typedef for raw kstat reader functions */ typedef void (*kstat_raw_reader_t)(kstat_t *, ks_instance_t *); static struct { kstat_raw_reader_t fn; char *name; } ks_raw_lookup[] = { /* Function name kstat name */ {save_cpu_stat, "cpu_stat:cpu_stat"}, {save_var, "unix:var"}, {save_ncstats, "unix:ncstats"}, {save_sysinfo, "unix:sysinfo"}, {save_vminfo, "unix:vminfo"}, {save_nfs, "nfs:mntinfo"}, #ifdef __sparc {save_sfmmu_global_stat, "unix:sfmmu_global_stat"}, {save_sfmmu_tsbsize_stat, "unix:sfmmu_tsbsize_stat"}, #endif {NULL, NULL}, }; static kstat_raw_reader_t lookup_raw_kstat_fn(char *, char *); #endif /* _STAT_KSTAT_H */ # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright 2025 Hans Rosenfeld PROG = mpstat OBJS = mpstat.o SRCS =$(OBJS:%.o=%.c) $(COMMON_SRCS) CLEANFILES = $(OBJS) $(COMMON_OBJS:%.o=%.c) include $(SRC)/cmd/Makefile.cmd include $(SRC)/cmd/stat/Makefile.stat LDLIBS += -ldevinfo -lkstat -lavl CFLAGS += $(CCVERBOSE) CERRWARN += -Wno-switch CERRWARN += -Wno-parentheses FILEMODE= 0555 include $(SRC)/cmd/stat/Makefile.stat.targ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "statcommon.h" #define SNAP(s, i, l, n) ((s) ? agg_proc_snap(s, i, l, n) : 0) #define REPRINT 20 char *cmdname = "mpstat"; int caught_cont = 0; static uint_t timestamp_fmt = NODATE; static int hz; static int display_pset = -1; static int show_set = 0; static int suppress_state; static void print_header(int, int); static void show_cpu_usage(struct snapshot *, struct snapshot *, int); static void usage(void); int main(int argc, char **argv) { int c; int display_agg = 0; int iter = 1; int interval = 0; char *endptr; int infinite_cycles = 0; kstat_ctl_t *kc; struct snapshot *old = NULL; struct snapshot *new = NULL; enum snapshot_types types = SNAP_CPUS; hrtime_t start_n; hrtime_t period_n = 0; (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ #endif (void) textdomain(TEXT_DOMAIN); while ((c = getopt(argc, argv, "apP:qT:")) != (int)EOF) switch (c) { case 'a': /* * Display aggregate data for processor sets. */ display_agg = 1; break; case 'p': /* * Display all processor sets. */ if (display_pset != -1) usage(); show_set = 1; break; case 'P': /* * Display specific processor set. */ if (show_set == 1) usage(); display_pset = (int)strtol (optarg, &endptr, 10); if (*endptr != '\0') usage(); /* * Not valid to specify a negative processor * set value. */ if (display_pset < 0) usage(); break; case 'q': suppress_state = 1; break; case 'T': if (optarg) { if (*optarg == 'u') timestamp_fmt = UDATE; else if (*optarg == 'd') timestamp_fmt = DDATE; else usage(); } else { usage(); } break; case '?': usage(); break; } hz = sysconf(_SC_CLK_TCK); if (argc > optind) { interval = (int)strtol(argv[optind], &endptr, 10); if (*endptr != '\0') usage(); period_n = (hrtime_t)interval * NANOSEC; if (argc > optind + 1) { iter = (unsigned int)strtoul (argv[optind + 1], &endptr, 10); if (*endptr != '\0' || iter < 0) usage(); if (iter == 0) return (0); } else { infinite_cycles = 1; } } if (display_agg || show_set || display_pset != -1) types |= SNAP_PSETS; kc = open_kstat(); /* Set up handler for SIGCONT */ if (signal(SIGCONT, cont_handler) == SIG_ERR) fail(1, "signal failed"); start_n = gethrtime(); while (infinite_cycles || iter > 0) { free_snapshot(old); old = new; new = acquire_snapshot(kc, types, NULL); if (!suppress_state) snapshot_report_changes(old, new); /* if config changed, show stats from boot */ if (snapshot_has_changed(old, new)) { free_snapshot(old); old = NULL; } show_cpu_usage(old, new, display_agg); if (!infinite_cycles && --iter < 1) break; /* Have a kip */ sleep_until(&start_n, period_n, infinite_cycles, &caught_cont); } (void) kstat_close(kc); return (0); } /* * Print an mpstat output header. */ static void print_header(int display_agg, int show_set) { if (display_agg == 1) (void) printf("SET minf mjf xcal intr ithr csw icsw migr " "smtx srw syscl usr sys wt idl sze"); else { (void) printf("CPU minf mjf xcal intr ithr csw icsw migr " "smtx srw syscl usr sys wt idl"); if (show_set == 1) (void) printf(" set"); } (void) printf("\n"); } static void print_cpu(struct cpu_snapshot *c1, struct cpu_snapshot *c2) { uint64_t ticks = 0; double etime, percent; kstat_t *old_vm = NULL; kstat_t *old_sys = NULL; if (display_pset != -1 && display_pset != c2->cs_pset_id) return; /* * the first mpstat output will have c1 = NULL, to give * results since boot */ if (c1) { old_vm = &c1->cs_vm; old_sys = &c1->cs_sys; /* check there are stats to report */ if (!CPU_ACTIVE(c1)) return; } /* check there are stats to report */ if (!CPU_ACTIVE(c2)) return; ticks = cpu_ticks_delta(old_sys, &c2->cs_sys); etime = (double)ticks / hz; if (etime == 0.0) /* Prevent divide by zero errors */ etime = 1.0; percent = 100.0 / etime / hz; (void) printf("%3d %4.0f %3.0f %4.0f %5.0f %4.0f " "%4.0f %4.0f %4.0f %4.0f %4.0f %5.0f %3.0f %3.0f " "%3.0f %3.0f", c2->cs_id, (kstat_delta(old_vm, &c2->cs_vm, "hat_fault") + kstat_delta(old_vm, &c2->cs_vm, "as_fault")) / etime, kstat_delta(old_vm, &c2->cs_vm, "maj_fault") / etime, kstat_delta(old_sys, &c2->cs_sys, "xcalls") / etime, kstat_delta(old_sys, &c2->cs_sys, "intr") / etime, kstat_delta(old_sys, &c2->cs_sys, "intrthread") / etime, kstat_delta(old_sys, &c2->cs_sys, "pswitch") / etime, kstat_delta(old_sys, &c2->cs_sys, "inv_swtch") / etime, kstat_delta(old_sys, &c2->cs_sys, "cpumigrate") / etime, kstat_delta(old_sys, &c2->cs_sys, "mutex_adenters") / etime, (kstat_delta(old_sys, &c2->cs_sys, "rw_rdfails") + kstat_delta(old_sys, &c2->cs_sys, "rw_wrfails")) / etime, kstat_delta(old_sys, &c2->cs_sys, "syscall") / etime, kstat_delta(old_sys, &c2->cs_sys, "cpu_ticks_user") * percent, kstat_delta(old_sys, &c2->cs_sys, "cpu_ticks_kernel") * percent, kstat_delta(old_sys, &c2->cs_sys, "cpu_ticks_wait") * percent, kstat_delta(old_sys, &c2->cs_sys, "cpu_ticks_idle") * percent); if (show_set) (void) printf(" %3d", c2->cs_pset_id); (void) printf("\n"); } /*ARGSUSED*/ static void compare_cpu(void *v1, void *v2, void *data) { struct cpu_snapshot *c1 = (struct cpu_snapshot *)v1; struct cpu_snapshot *c2 = (struct cpu_snapshot *)v2; if (c2 == NULL) return; print_cpu(c1, c2); } static int pset_has_stats(struct pset_snapshot *p) { int count = 0; size_t i; for (i = 0; i < p->ps_nr_cpus; i++) { if (CPU_ACTIVE(p->ps_cpus[i])) count++; } return (count); } static void agg_stat(kstat_t *k1, kstat_t *k2, char *name) { kstat_named_t *ksn = kstat_data_lookup(k1, name); kstat_named_t *ksn2 = kstat_data_lookup(k2, name); ksn->value.ui64 += ksn2->value.ui64; } static kstat_t * agg_vm(struct pset_snapshot *p, kstat_t *ks) { size_t i; if (p->ps_nr_cpus == 0) return (NULL); if (kstat_copy(&p->ps_cpus[0]->cs_vm, ks)) return (NULL); for (i = 1; i < p->ps_nr_cpus; i++) { agg_stat(ks, &p->ps_cpus[i]->cs_vm, "hat_fault"); agg_stat(ks, &p->ps_cpus[i]->cs_vm, "as_fault"); agg_stat(ks, &p->ps_cpus[i]->cs_vm, "maj_fault"); } return (ks); } static kstat_t * agg_sys(struct pset_snapshot *p, kstat_t *ks) { size_t i; if (p->ps_nr_cpus == 0) return (NULL); if (kstat_copy(&p->ps_cpus[0]->cs_sys, ks)) return (NULL); for (i = 1; i < p->ps_nr_cpus; i++) { agg_stat(ks, &p->ps_cpus[i]->cs_sys, "xcalls"); agg_stat(ks, &p->ps_cpus[i]->cs_sys, "intr"); agg_stat(ks, &p->ps_cpus[i]->cs_sys, "intrthread"); agg_stat(ks, &p->ps_cpus[i]->cs_sys, "pswitch"); agg_stat(ks, &p->ps_cpus[i]->cs_sys, "inv_swtch"); agg_stat(ks, &p->ps_cpus[i]->cs_sys, "cpumigrate"); agg_stat(ks, &p->ps_cpus[i]->cs_sys, "mutex_adenters"); agg_stat(ks, &p->ps_cpus[i]->cs_sys, "rw_rdfails"); agg_stat(ks, &p->ps_cpus[i]->cs_sys, "rw_wrfails"); agg_stat(ks, &p->ps_cpus[i]->cs_sys, "syscall"); agg_stat(ks, &p->ps_cpus[i]->cs_sys, "cpu_ticks_user"); agg_stat(ks, &p->ps_cpus[i]->cs_sys, "cpu_ticks_kernel"); agg_stat(ks, &p->ps_cpus[i]->cs_sys, "cpu_ticks_wait"); agg_stat(ks, &p->ps_cpus[i]->cs_sys, "cpu_ticks_idle"); } return (ks); } static uint64_t get_nr_ticks(struct pset_snapshot *p1, struct pset_snapshot *p2) { kstat_t *old = NULL; kstat_t *new = NULL; size_t i = 0; for (i = 0; p1 && i < p1->ps_nr_cpus; i++) { if (p1->ps_cpus[i]->cs_sys.ks_data) { old = &p1->ps_cpus[i]->cs_sys; break; } } for (i = 0; p2 && i < p2->ps_nr_cpus; i++) { if (p2->ps_cpus[i]->cs_sys.ks_data) { new = &p2->ps_cpus[i]->cs_sys; break; } } if (old == NULL && new == NULL) return (0); if (new == NULL) { new = old; old = NULL; } return (cpu_ticks_delta(old, new)); } static void print_pset(struct pset_snapshot *p1, struct pset_snapshot *p2) { uint64_t ticks = 0; double etime, percent; kstat_t old_vm; kstat_t old_sys; kstat_t new_vm; kstat_t new_sys; if (display_pset != -1 && display_pset != p2->ps_id) return; if ((p1 && !pset_has_stats(p1)) || !pset_has_stats(p2)) return; old_vm.ks_data = old_sys.ks_data = NULL; new_vm.ks_data = new_sys.ks_data = NULL; /* * FIXME: these aggs will count "new" or disappeared cpus * in a set, leaving an apparent huge change. */ /* * the first mpstat output will have p1 = NULL, to give * results since boot */ if (p1) { if (!agg_vm(p1, &old_vm) || !agg_sys(p1, &old_sys)) goto out; } if (!agg_vm(p2, &new_vm) || !agg_sys(p2, &new_sys)) goto out; ticks = get_nr_ticks(p1, p2); etime = (double)ticks / hz; if (etime == 0.0) /* Prevent divide by zero errors */ etime = 1.0; percent = 100.0 / p2->ps_nr_cpus / etime / hz; (void) printf("%3d %4.0f %3.0f %4.0f %5.0f %4.0f " "%4.0f %4.0f %4.0f %4.0f %4.0f %5.0f %3.0f %3.0f " "%3.0f %3.0f %3d\n", p2->ps_id, (kstat_delta(&old_vm, &new_vm, "hat_fault") + kstat_delta(&old_vm, &new_vm, "as_fault")) / etime, kstat_delta(&old_vm, &new_vm, "maj_fault") / etime, kstat_delta(&old_sys, &new_sys, "xcalls") / etime, kstat_delta(&old_sys, &new_sys, "intr") / etime, kstat_delta(&old_sys, &new_sys, "intrthread") / etime, kstat_delta(&old_sys, &new_sys, "pswitch") / etime, kstat_delta(&old_sys, &new_sys, "inv_swtch") / etime, kstat_delta(&old_sys, &new_sys, "cpumigrate") / etime, kstat_delta(&old_sys, &new_sys, "mutex_adenters") / etime, (kstat_delta(&old_sys, &new_sys, "rw_rdfails") + kstat_delta(&old_sys, &new_sys, "rw_wrfails")) / etime, kstat_delta(&old_sys, &new_sys, "syscall") / etime, kstat_delta(&old_sys, &new_sys, "cpu_ticks_user") * percent, kstat_delta(&old_sys, &new_sys, "cpu_ticks_kernel") * percent, kstat_delta(&old_sys, &new_sys, "cpu_ticks_wait") * percent, kstat_delta(&old_sys, &new_sys, "cpu_ticks_idle") * percent, p2->ps_nr_cpus); out: free(old_vm.ks_data); free(old_sys.ks_data); free(new_vm.ks_data); free(new_sys.ks_data); } /*ARGSUSED*/ static void compare_pset(void *v1, void *v2, void *data) { struct pset_snapshot *p1 = (struct pset_snapshot *)v1; struct pset_snapshot *p2 = (struct pset_snapshot *)v2; if (p2 == NULL) return; print_pset(p1, p2); } /* * Report statistics for a sample interval. */ static void show_cpu_usage(struct snapshot *old, struct snapshot *new, int display_agg) { static int lines_until_reprint = 0; enum snapshot_types type = SNAP_CPUS; snapshot_cb cb = compare_cpu; if (timestamp_fmt != NODATE) print_timestamp(timestamp_fmt); if (lines_until_reprint == 0 || nr_active_cpus(new) > 1) { print_header(display_agg, show_set); lines_until_reprint = REPRINT; } lines_until_reprint--; if (display_agg) { type = SNAP_PSETS; cb = compare_pset; } /* print stats since boot the first time round */ (void) snapshot_walk(type, old, new, cb, NULL); (void) fflush(stdout); } /* * Usage message on error. */ static void usage(void) { (void) fprintf(stderr, "Usage: mpstat [-aq] [-p | -P processor_set] [-T d|u] " "[interval [count]]\n"); exit(1); } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 2018, Joyent, Inc. # Copyright 2025 Hans Rosenfeld PROG = vmstat OBJS = vmstat.o SRCS =$(OBJS:%.o=%.c) $(COMMON_SRCS) CLEANFILES = $(OBJS) $(COMMON_OBJS:%.o=%.c) include $(SRC)/cmd/Makefile.cmd include $(SRC)/cmd/stat/Makefile.stat LDLIBS += -ldevinfo -lkstat -lavl CFLAGS += $(CCVERBOSE) CERRWARN += -Wno-switch CERRWARN += -Wno-parentheses SMOFF += signed FILEMODE= 0555 include $(SRC)/cmd/stat/Makefile.stat.targ Copyright (c) 1980 Regents of the University of California. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. All advertising materials mentioning features or use of this software must display the following acknowledgement: This product includes software developed by the University of California, Berkeley and its contributors. 4. Neither the name of the University nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. PORTIONS OF VMSTAT COMMAND FUNCTIONALITY /* * Copyright (c) 1991, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * Copyright (c) 1980 Regents of the University of California. * All rights reserved. The Berkeley software License Agreement * specifies the terms and conditions for redistribution. */ /* from UCB 5.4 5/17/86 */ /* from SunOS 4.1, SID 1.31 */ /* * Copyright (c) 2018, Joyent, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "statcommon.h" char *cmdname = "vmstat"; int caught_cont = 0; static uint_t timestamp_fmt = NODATE; static int hz; static int pagesize; static double etime; static int lines = 1; static int swflag = 0, pflag = 0; static int suppress_state; static long iter = 0; static hrtime_t period_n = 0; static struct snapshot *ss; struct iodev_filter df; #define pgtok(a) ((a) * (pagesize >> 10)) #define denom(x) ((x) ? (x) : 1) #define REPRINT 19 static void dovmstats(struct snapshot *old, struct snapshot *new); static void printhdr(int); static void dosum(struct sys_snapshot *ss); static void dointr(struct snapshot *ss); static void usage(void); int main(int argc, char **argv) { struct snapshot *old = NULL; enum snapshot_types types = SNAP_SYSTEM; int summary = 0; int intr = 0; kstat_ctl_t *kc; int forever = 0; hrtime_t start_n; int c; (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ #endif (void) textdomain(TEXT_DOMAIN); pagesize = sysconf(_SC_PAGESIZE); hz = sysconf(_SC_CLK_TCK); while ((c = getopt(argc, argv, "ipqsST:")) != EOF) switch (c) { case 'S': swflag = !swflag; break; case 's': summary = 1; break; case 'i': intr = 1; break; case 'q': suppress_state = 1; break; case 'p': pflag++; /* detailed paging info */ break; case 'T': if (optarg) { if (*optarg == 'u') timestamp_fmt = UDATE; else if (*optarg == 'd') timestamp_fmt = DDATE; else usage(); } else { usage(); } break; default: usage(); } argc -= optind; argv += optind; /* consistency with iostat */ types |= SNAP_CPUS; if (intr) types |= SNAP_INTERRUPTS; if (!intr) types |= SNAP_IODEVS; /* max to fit in less than 80 characters */ df.if_max_iodevs = 4; df.if_allowed_types = IODEV_DISK; df.if_nr_names = 0; df.if_names = safe_alloc(df.if_max_iodevs * sizeof (char *)); (void) memset(df.if_names, 0, df.if_max_iodevs * sizeof (char *)); while (argc > 0 && !isdigit(argv[0][0]) && df.if_nr_names < df.if_max_iodevs) { df.if_names[df.if_nr_names] = *argv; df.if_nr_names++; argc--, argv++; } kc = open_kstat(); start_n = gethrtime(); ss = acquire_snapshot(kc, types, &df); /* time, in seconds, since boot */ etime = ss->s_sys.ss_ticks / hz; if (intr) { dointr(ss); free_snapshot(ss); exit(0); } if (summary) { dosum(&ss->s_sys); free_snapshot(ss); exit(0); } if (argc > 0) { long interval; const char *errstr; interval = strtonum(argv[0], 1, MAXINT, &errstr); if (errstr != NULL) usage(); period_n = (hrtime_t)interval * NANOSEC; if (period_n <= 0) usage(); iter = MAXLONG; if (argc > 1) { iter = strtonum(argv[1], 1, MAXLONG, &errstr); if (errstr != NULL) usage(); } else forever = 1; if (argc > 2) usage(); } (void) sigset(SIGCONT, printhdr); dovmstats(old, ss); while (forever || --iter > 0) { /* (void) poll(NULL, 0, poll_interval); */ /* Have a kip */ sleep_until(&start_n, period_n, forever, &caught_cont); free_snapshot(old); old = ss; ss = acquire_snapshot(kc, types, &df); if (!suppress_state) snapshot_report_changes(old, ss); /* if config changed, show stats from boot */ if (snapshot_has_changed(old, ss)) { free_snapshot(old); old = NULL; } dovmstats(old, ss); } free_snapshot(old); free_snapshot(ss); free(df.if_names); (void) kstat_close(kc); return (0); } #define DELTA(v) (new->v - (old ? old->v : 0)) #define ADJ(n) ((adj <= 0) ? n : (adj >= n) ? 1 : n - adj) #define adjprintf(fmt, n, val) adj -= (n + 1) - printf(fmt, ADJ(n), val) static int adj; /* number of excess columns */ /*ARGSUSED*/ static void show_disk(void *v1, void *v2, void *d) { struct iodev_snapshot *old = (struct iodev_snapshot *)v1; struct iodev_snapshot *new = (struct iodev_snapshot *)v2; hrtime_t oldtime = new->is_crtime; double hr_etime; double reads, writes; if (old) oldtime = old->is_stats.wlastupdate; hr_etime = new->is_stats.wlastupdate - oldtime; if (hr_etime == 0.0) hr_etime = NANOSEC; reads = new->is_stats.reads - (old ? old->is_stats.reads : 0); writes = new->is_stats.writes - (old ? old->is_stats.writes : 0); adjprintf(" %*.0f", 2, (reads + writes) / hr_etime * NANOSEC); } static void dovmstats(struct snapshot *old, struct snapshot *new) { kstat_t *oldsys = NULL; kstat_t *newsys = &new->s_sys.ss_agg_sys; kstat_t *oldvm = NULL; kstat_t *newvm = &new->s_sys.ss_agg_vm; double percent_factor; ulong_t sys_updates, vm_updates; int count; adj = 0; if (old) { oldsys = &old->s_sys.ss_agg_sys; oldvm = &old->s_sys.ss_agg_vm; } etime = cpu_ticks_delta(oldsys, newsys); percent_factor = 100.0 / denom(etime); /* * If any time has passed, convert etime to seconds per CPU */ etime = etime >= 1.0 ? (etime / nr_active_cpus(new)) / hz : 1.0; sys_updates = denom(DELTA(s_sys.ss_sysinfo.updates)); vm_updates = denom(DELTA(s_sys.ss_vminfo.updates)); if (timestamp_fmt != NODATE) { print_timestamp(timestamp_fmt); lines--; } if (--lines <= 0) printhdr(0); adj = 0; if (pflag) { adjprintf(" %*u", 6, pgtok((int)(DELTA(s_sys.ss_vminfo.swap_avail) / vm_updates))); adjprintf(" %*u", 5, pgtok((int)(DELTA(s_sys.ss_vminfo.freemem) / vm_updates))); adjprintf(" %*.0f", 3, kstat_delta(oldvm, newvm, "pgrec") / etime); adjprintf(" %*.0f", 3, (kstat_delta(oldvm, newvm, "hat_fault") + kstat_delta(oldvm, newvm, "as_fault")) / etime); adjprintf(" %*.0f", 3, pgtok(kstat_delta(oldvm, newvm, "dfree")) / etime); adjprintf(" %*ld", 3, pgtok(new->s_sys.ss_deficit)); adjprintf(" %*.0f", 3, kstat_delta(oldvm, newvm, "scan") / etime); adjprintf(" %*.0f", 4, pgtok(kstat_delta(oldvm, newvm, "execpgin")) / etime); adjprintf(" %*.0f", 4, pgtok(kstat_delta(oldvm, newvm, "execpgout")) / etime); adjprintf(" %*.0f", 4, pgtok(kstat_delta(oldvm, newvm, "execfree")) / etime); adjprintf(" %*.0f", 4, pgtok(kstat_delta(oldvm, newvm, "anonpgin")) / etime); adjprintf(" %*.0f", 4, pgtok(kstat_delta(oldvm, newvm, "anonpgout")) / etime); adjprintf(" %*.0f", 4, pgtok(kstat_delta(oldvm, newvm, "anonfree")) / etime); adjprintf(" %*.0f", 4, pgtok(kstat_delta(oldvm, newvm, "fspgin")) / etime); adjprintf(" %*.0f", 4, pgtok(kstat_delta(oldvm, newvm, "fspgout")) / etime); adjprintf(" %*.0f\n", 4, pgtok(kstat_delta(oldvm, newvm, "fsfree")) / etime); (void) fflush(stdout); return; } adjprintf(" %*lu", 1, DELTA(s_sys.ss_sysinfo.runque) / sys_updates); adjprintf(" %*lu", 1, DELTA(s_sys.ss_sysinfo.waiting) / sys_updates); adjprintf(" %*lu", 1, DELTA(s_sys.ss_sysinfo.swpque) / sys_updates); adjprintf(" %*u", 6, pgtok((int)(DELTA(s_sys.ss_vminfo.swap_avail) / vm_updates))); adjprintf(" %*u", 5, pgtok((int)(DELTA(s_sys.ss_vminfo.freemem) / vm_updates))); adjprintf(" %*.0f", 3, swflag? kstat_delta(oldvm, newvm, "swapin") / etime : kstat_delta(oldvm, newvm, "pgrec") / etime); adjprintf(" %*.0f", 3, swflag? kstat_delta(oldvm, newvm, "swapout") / etime : (kstat_delta(oldvm, newvm, "hat_fault") + kstat_delta(oldvm, newvm, "as_fault")) / etime); adjprintf(" %*.0f", 2, pgtok(kstat_delta(oldvm, newvm, "pgpgin")) / etime); adjprintf(" %*.0f", 2, pgtok(kstat_delta(oldvm, newvm, "pgpgout")) / etime); adjprintf(" %*.0f", 2, pgtok(kstat_delta(oldvm, newvm, "dfree")) / etime); adjprintf(" %*ld", 2, pgtok(new->s_sys.ss_deficit)); adjprintf(" %*.0f", 2, kstat_delta(oldvm, newvm, "scan") / etime); (void) snapshot_walk(SNAP_IODEVS, old, new, show_disk, NULL); count = df.if_max_iodevs - new->s_nr_iodevs; while (count-- > 0) adjprintf(" %*d", 2, 0); adjprintf(" %*.0f", 4, kstat_delta(oldsys, newsys, "intr") / etime); adjprintf(" %*.0f", 4, kstat_delta(oldsys, newsys, "syscall") / etime); adjprintf(" %*.0f", 4, kstat_delta(oldsys, newsys, "pswitch") / etime); adjprintf(" %*.0f", 2, kstat_delta(oldsys, newsys, "cpu_ticks_user") * percent_factor); adjprintf(" %*.0f", 2, kstat_delta(oldsys, newsys, "cpu_ticks_kernel") * percent_factor); adjprintf(" %*.0f\n", 2, (kstat_delta(oldsys, newsys, "cpu_ticks_idle") + kstat_delta(oldsys, newsys, "cpu_ticks_wait")) * percent_factor); (void) fflush(stdout); } /*ARGSUSED*/ static void print_disk(void *v, void *v2, void *d) { struct iodev_snapshot *iodev = (struct iodev_snapshot *)v2; if (iodev == NULL) return; (void) printf("%c%c ", iodev->is_name[0], iodev->is_name[2]); } /* ARGSUSED */ static void printhdr(int sig) { int i = df.if_max_iodevs - ss->s_nr_iodevs; if (sig == SIGCONT) caught_cont = 1; if (pflag) { (void) printf(" memory page "); (void) printf("executable anonymous filesystem \n"); (void) printf(" swap free re mf fr de sr "); (void) printf("epi epo epf api apo apf fpi fpo fpf\n"); lines = REPRINT; return; } (void) printf(" kthr memory page "); (void) printf("disk faults cpu\n"); if (swflag) (void) printf(" r b w swap free si so pi po fr de sr "); else (void) printf(" r b w swap free re mf pi po fr de sr "); (void) snapshot_walk(SNAP_IODEVS, NULL, ss, print_disk, NULL); while (i-- > 0) (void) printf("-- "); (void) printf(" in sy cs us sy id\n"); lines = REPRINT; } static void sum_out(char const *pretty, kstat_t *ks, char *name) { kstat_named_t *ksn = kstat_data_lookup(ks, name); if (ksn == NULL) { fail(0, "kstat_data_lookup('%s', '%s') failed", ks->ks_name, name); } (void) printf("%9llu %s\n", ksn->value.ui64, pretty); } static void dosum(struct sys_snapshot *ss) { uint64_t total_faults; kstat_named_t *ksn; long double nchtotal; uint64_t nchhits; sum_out("swap ins", &ss->ss_agg_vm, "swapin"); sum_out("swap outs", &ss->ss_agg_vm, "swapout"); sum_out("pages swapped in", &ss->ss_agg_vm, "pgswapin"); sum_out("pages swapped out", &ss->ss_agg_vm, "pgswapout"); ksn = kstat_data_lookup(&ss->ss_agg_vm, "hat_fault"); if (ksn == NULL) { fail(0, "kstat_data_lookup('%s', 'hat_fault') failed", ss->ss_agg_vm.ks_name); } total_faults = ksn->value.ui64; ksn = kstat_data_lookup(&ss->ss_agg_vm, "as_fault"); if (ksn == NULL) { fail(0, "kstat_data_lookup('%s', 'as_fault') failed", ss->ss_agg_vm.ks_name); } total_faults += ksn->value.ui64; (void) printf("%9llu total address trans. faults taken\n", total_faults); sum_out("page ins", &ss->ss_agg_vm, "pgin"); sum_out("page outs", &ss->ss_agg_vm, "pgout"); sum_out("pages paged in", &ss->ss_agg_vm, "pgpgin"); sum_out("pages paged out", &ss->ss_agg_vm, "pgpgout"); sum_out("total reclaims", &ss->ss_agg_vm, "pgrec"); sum_out("reclaims from free list", &ss->ss_agg_vm, "pgfrec"); sum_out("micro (hat) faults", &ss->ss_agg_vm, "hat_fault"); sum_out("minor (as) faults", &ss->ss_agg_vm, "as_fault"); sum_out("major faults", &ss->ss_agg_vm, "maj_fault"); sum_out("copy-on-write faults", &ss->ss_agg_vm, "cow_fault"); sum_out("zero fill page faults", &ss->ss_agg_vm, "zfod"); sum_out("pages examined by the clock daemon", &ss->ss_agg_vm, "scan"); sum_out("revolutions of the clock hand", &ss->ss_agg_vm, "rev"); sum_out("pages freed by the clock daemon", &ss->ss_agg_vm, "dfree"); sum_out("forks", &ss->ss_agg_sys, "sysfork"); sum_out("vforks", &ss->ss_agg_sys, "sysvfork"); sum_out("execs", &ss->ss_agg_sys, "sysexec"); sum_out("cpu context switches", &ss->ss_agg_sys, "pswitch"); sum_out("device interrupts", &ss->ss_agg_sys, "intr"); sum_out("traps", &ss->ss_agg_sys, "trap"); sum_out("system calls", &ss->ss_agg_sys, "syscall"); nchtotal = (long double) ss->ss_nc.ncs_hits.value.ui64 + (long double) ss->ss_nc.ncs_misses.value.ui64; nchhits = ss->ss_nc.ncs_hits.value.ui64; (void) printf("%9.0Lf total name lookups (cache hits %.0Lf%%)\n", nchtotal, nchhits / denom(nchtotal) * 100); sum_out("user cpu", &ss->ss_agg_sys, "cpu_ticks_user"); sum_out("system cpu", &ss->ss_agg_sys, "cpu_ticks_kernel"); sum_out("idle cpu", &ss->ss_agg_sys, "cpu_ticks_idle"); sum_out("wait cpu", &ss->ss_agg_sys, "cpu_ticks_wait"); } static void dointr(struct snapshot *ss) { size_t i; ulong_t total = 0; (void) printf("interrupt total rate\n"); (void) printf("--------------------------------\n"); for (i = 0; i < ss->s_nr_intrs; i++) { (void) printf("%-12.8s %10lu %8.0f\n", ss->s_intrs[i].is_name, ss->s_intrs[i].is_total, ss->s_intrs[i].is_total / etime); total += ss->s_intrs[i].is_total; } (void) printf("--------------------------------\n"); (void) printf("Total %10lu %8.0f\n", total, total / etime); } static void usage(void) { (void) fprintf(stderr, "Usage: vmstat [-ipqsS] [-T d|u] [disk ...] " "[interval [count]]\n"); exit(1); }