From bc0a39238ecee5c8a3fb76071ef9f61668673a0d Mon Sep 17 00:00:00 2001 From: Ben Gras Date: Wed, 21 Sep 2011 19:16:34 +0000 Subject: [PATCH] worldstone benchmark script . also imports seq(1) to help it . add -C option to time(1) to print tsc difference . increase col width for ministat for tsc numbers --- .gitignore | 2 + commands/Makefile | 2 +- commands/time/Makefile | 1 + commands/time/time.c | 29 +- commands/worldstone/Makefile | 4 + commands/worldstone/worldstone.1 | 85 ++++++ commands/worldstone/worldstone.sh | 56 ++++ man/man1/time.1 | 4 +- tools/nbsd_ports | 1 + usr.bin/Makefile | 2 +- usr.bin/ministat/ministat.c | 4 +- usr.bin/seq/Makefile | 7 + usr.bin/seq/seq.1 | 187 ++++++++++++ usr.bin/seq/seq.c | 474 ++++++++++++++++++++++++++++++ 14 files changed, 850 insertions(+), 8 deletions(-) create mode 100644 commands/worldstone/Makefile create mode 100644 commands/worldstone/worldstone.1 create mode 100644 commands/worldstone/worldstone.sh create mode 100644 usr.bin/seq/Makefile create mode 100644 usr.bin/seq/seq.1 create mode 100644 usr.bin/seq/seq.c diff --git a/.gitignore b/.gitignore index fa82f95d8..52fb026e6 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,5 @@ CVS .gitignore .svn minix-port.patch +*.worldstone.log +.worldstone* diff --git a/commands/Makefile b/commands/Makefile index 29683f98a..b051cdcb4 100644 --- a/commands/Makefile +++ b/commands/Makefile @@ -33,7 +33,7 @@ SUBDIR= aal add_route adduser arp ash at autil awk \ unstack update uud uue version vol wc \ whereis which who write writeisofs fetch \ xargs yacc yes zdump zic zmodem pkgin_cd \ - mktemp + mktemp worldstone .if ${COMPILER_TYPE} == "gnu" SUBDIR += elf2aout diff --git a/commands/time/Makefile b/commands/time/Makefile index 34f79b863..73d1566eb 100644 --- a/commands/time/Makefile +++ b/commands/time/Makefile @@ -1,4 +1,5 @@ PROG= time MAN= +NEED_NBSDLIBC= yes .include diff --git a/commands/time/time.c b/commands/time/time.c index a2e038265..c941c790c 100644 --- a/commands/time/time.c +++ b/commands/time/time.c @@ -11,6 +11,7 @@ #include #include #include +#include #include /* -DNEW prints time to 0.01 sec. */ @@ -33,7 +34,7 @@ int main(argc, argv) int argc; char *argv[]; { - + int cycles = 0; struct tms pre_buf, post_buf; int status, pid; #if _VMD_EXT @@ -42,12 +43,28 @@ char *argv[]; struct tms dummy; int start_time, end_time; #endif + u64_t start_tsc, end_tsc, spent_tsc; clock_t real_time; + int c; if (argc == 1) exit(0); - args = &argv[1]; - name = argv[1]; + while((c=getopt(argc, argv, "C")) != EOF) { + switch(c) { + case 'C': + cycles = 1; + break; + default: + fprintf(stderr, "usage: time [-C] \n"); + exit(1); + } + } + + argv += optind; + argc -= optind; + + args = &argv[0]; + name = argv[0]; /* Get real time at start of run. */ #if _VMD_EXT @@ -55,6 +72,7 @@ char *argv[]; #else start_time = times(&dummy); #endif + read_tsc_64(&start_tsc); /* Fork off child. */ if ((pid = fork()) < 0) { @@ -70,6 +88,8 @@ char *argv[]; do { times(&pre_buf); } while (wait(&status) != pid); + read_tsc_64(&end_tsc); + spent_tsc = sub64(end_tsc, start_tsc); #if _VMD_EXT (void) sysutime(UTIME_TIMEOFDAY, &end_time); real_time = (end_time.tv_sec - start_time.tv_sec) * CLOCKS_PER_SEC @@ -82,6 +102,9 @@ char *argv[]; if ((status & 0377) != 0) std_err("Command terminated abnormally.\n"); times(&post_buf); + if(cycles) { + fprintf(stderr, "%qd tsc ", spent_tsc); + } /* Print results. -DNEW enables time on one line to 0.01 sec */ #ifndef NEW std_err("real "); diff --git a/commands/worldstone/Makefile b/commands/worldstone/Makefile new file mode 100644 index 000000000..b79627d13 --- /dev/null +++ b/commands/worldstone/Makefile @@ -0,0 +1,4 @@ +SCRIPTS= worldstone.sh +MAN= worldstone.1 + +.include diff --git a/commands/worldstone/worldstone.1 b/commands/worldstone/worldstone.1 new file mode 100644 index 000000000..b21384d5f --- /dev/null +++ b/commands/worldstone/worldstone.1 @@ -0,0 +1,85 @@ +.Dd $Mdocdate: September 22 2011 $ +.Dt WORLDSTONE 1 +.Os +.Sh NAME +.Nm worldstone +.Nd shell script to consistently execute benchmarks +.Sh SYNOPSIS +.Nm worldstone +.Op Fl n Ar iterations +.Op Fl c Ar command +.Op Fl p Ar command +.Op Fl t Ar tag +.Sh DESCRIPTION +The +.Nm +utility is a shell script and takes care of some of the +grunt work around benchmarking, in order to make it easier +to get consistent and comparable benchmark results. Its basic +operation is: execute the precommand, then execute and time +the command, and do this a set number of iterations, and record +the times in a logfile. + +Its features are: +.Bl -tag -width Ds +.It - +It executes the precommand and command once without timing it +in order to mitigate cold cache effects. +.It - +It allows a precommand to run before the command, so that the initial +state can be set up by the precommand without it being part of the timing +(e.g. make clean). +.It - +It redirects the stdout and stderr to /dev/null so that lots of output +going over a network connection doesn't influence timing. +.It - +It does a sync before running the timed command, and makes sure a final +sync is part of the timed command, to make the i/o more consistent. +.It - +It logs the times of each iteration in an easy-to-parse logfile. +.It - +It tries to guess a sensible log file name based on the current git +branch in /usr/src. +.El + +The options are as follows: +.Bl -tag -width Ds +.It Fl n Ar iterations +Set the number of iterations to perform, after the initial run. +The default is 5. +.It Fl c Ar command +Set the command to run to be timed. This is passed to sh -c, so shell constructs +like loops etc. are okay to do. Default: make all. +.It Fl p Ar command +Set the pre-command to run. This command gets run before the timed command in order +to make the timed command get a consistent state before it starts. +Default: make clean. +.It Fl t Ar tag +Use the given tag name to modify the logfile that the utility uses +to write its results in. The default is just 'time' plus the git branch you +are currently on in /usr/src. In order for this to be useful you have to make sure the +git branch you are on reflects the system you wish to benchmark of course. +The script checks /usr/src/.git even if you are outside the /usr/src hierarchy +(such as in pkgsrc). +.El + +The script executes the commands the set number of iterations, redirecting stdout +and stderr to /dev/null, and records the timed results in the logfile tagged with +the given tag. +.Nm +executes +.Xr time 1 +with the -C option, resulting in printing the 64-bit cpu cycle counter +for both HZ-independent high resolution and an easy way not to have to convert minutes +and seconds to seconds when parsing the results. +.Pp +You can feed the two separate logfiles directly to +.Xr ministat 1 +to have it tell you the statistical properties of the two datasets, and judge whether +there is a statistically significant difference. +.Sh ENVIRONMENT +The default commands, i.e. make all and make clean, can be modified by supplying a MAKE +environment variable, so e.g. MAKE=bmake worldstone still does something sensible +by default in /usr/pkgsrc directories. +.Sh SEE ALSO +.Xr ministat 1 diff --git a/commands/worldstone/worldstone.sh b/commands/worldstone/worldstone.sh new file mode 100644 index 000000000..54dfafc40 --- /dev/null +++ b/commands/worldstone/worldstone.sh @@ -0,0 +1,56 @@ + +if [ ! "$MAKE" ]; then MAKE=make; fi +ITERATIONS=5 +PRECMD="$MAKE clean" +COMMAND="$MAKE all" +TAG=time.$(basename $(git --git-dir=/usr/src/.git describe --all --dirty)) + +set -e + +while getopts "n:d:p:c:r:" c +do + case "$c" in + n) ITERATIONS=$OPTARG ;; + p) PRECMD="$OPTARG" ;; + c) COMMAND="$OPTARG" ;; + t) TAG=$OPTARG ;; + r) echo "Reading settings from $OPTARG"; cat $OPTARG; . $OPTARG ; echo "Reading done.";; + *) exit 1 ;; + esac +done + +CONFIGPREFIX=".worldstone" +CONFIGVARS="ITERATIONS PRECMD COMMAND MAKE" +TMPF=.worldstone.tmpconfig.$$ +rm -f $TMPF +for d in $CONFIGVARS +do eval "echo $d=\\\"\$$d\\\"" >>$TMPF +done +CONFIGTAG=`crc <$TMPF | awk '{ print $1 }'` +CONFIGFILE=$CONFIGPREFIX.$CONFIGTAG +mv -f $TMPF $CONFIGFILE + +LOGFILE=$TAG.worldstone.log + +while [ -f $LOGFILE ] +do echo "$0: WARNING: $LOGFILE already exists, appending." + LOGFILE=$LOGFILE.next +done + +echo "Logging to $LOGFILE." + +echo "First run." +sh -c "$PRECMD" +sh -c "$COMMAND" + +for n in `seq 1 $ITERATIONS` +do echo -n "$n" + sh -c "$PRECMD >/dev/null 2>&1" + echo -n "." + sync + time -C sh -c "$COMMAND >/dev/null 2>&1; sync" 2>>$LOGFILE + echo -n " " +done +echo "Done." +echo "Time measurements logfile is $LOGFILE." +echo "Config file is $CONFIGFILE." diff --git a/man/man1/time.1 b/man/man1/time.1 index db0c42623..68c2bc2c2 100644 --- a/man/man1/time.1 +++ b/man/man1/time.1 @@ -2,7 +2,7 @@ .SH NAME time \- report how long a command takes .SH SYNOPSIS -\fBtime \fIcommand\fR +\fBtime [-C] \fIcommand\fR .br .de FL .TP @@ -14,6 +14,8 @@ time \- report how long a command takes \\fB\\$1\\fR # \\$2 .. +The -C option tells time to report the cpu cycle counter +difference. .SH EXAMPLES .EX "time a.out" "Report how long \fIa.out\fR takes" .EX "time ls \-l *.c" "Report how long \fIls\fR takes" diff --git a/tools/nbsd_ports b/tools/nbsd_ports index b51eced35..05a3bae5b 100644 --- a/tools/nbsd_ports +++ b/tools/nbsd_ports @@ -13,3 +13,4 @@ usr.bin/stat src/usr.bin/stat usr.bin/tic src/usr.bin/tic usr.bin/mkdep src/usr.bin/mkdep usr.bin/uniq src/usr.bin/uniq +usr.bin/seq src/usr.bin/seq diff --git a/usr.bin/Makefile b/usr.bin/Makefile index cd042c552..09dda125b 100644 --- a/usr.bin/Makefile +++ b/usr.bin/Makefile @@ -3,7 +3,7 @@ .include # NetBSD imports -SUBDIR= indent m4 stat tic sed mkdep uniq +SUBDIR= indent m4 stat tic sed mkdep uniq seq # Non-NetBSD imports SUBDIR+= ministat diff --git a/usr.bin/ministat/ministat.c b/usr.bin/ministat/ministat.c index e892579e1..252d3f99f 100644 --- a/usr.bin/ministat/ministat.c +++ b/usr.bin/ministat/ministat.c @@ -215,14 +215,14 @@ static void VitalsHead(void) { - printf(" N Min Max Median Avg Stddev\n"); + printf(" N Min Max Median Avg Stddev\n"); } static void Vitals(struct dataset *ds, int flag) { - printf("%c %3d %13.8g %13.8g %13.8g %13.8g %13.8g", symbol[flag], + printf("%c %3d %17.12g %17.12g %17.12g %17.12g %17.12g", symbol[flag], ds->n, Min(ds), Max(ds), Median(ds), Avg(ds), Stddev(ds)); printf("\n"); } diff --git a/usr.bin/seq/Makefile b/usr.bin/seq/Makefile new file mode 100644 index 000000000..54d4bfac3 --- /dev/null +++ b/usr.bin/seq/Makefile @@ -0,0 +1,7 @@ +# $NetBSD: Makefile,v 1.3 2009/04/14 22:15:26 lukem Exp $ + +PROG= seq +DPADD= ${LIBMATH} +LDADD= -lm + +.include diff --git a/usr.bin/seq/seq.1 b/usr.bin/seq/seq.1 new file mode 100644 index 000000000..6971e629b --- /dev/null +++ b/usr.bin/seq/seq.1 @@ -0,0 +1,187 @@ +.\" $NetBSD: seq.1,v 1.7 2010/05/27 08:30:35 dholland Exp $ +.\" +.\" Copyright (c) 2005 The NetBSD Foundation, Inc. +.\" All rights reserved. +.\" +.\" This code is derived from software contributed to The NetBSD Foundation +.\" by Brian Ginsbach. +.\" +.\" 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. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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. +.\" +.\" +.Dd May 27, 2010 +.Dt SEQ 1 +.Os +.Sh NAME +.Nm seq +.Nd print sequences of numbers +.Sh SYNOPSIS +.Nm +.Op Fl w +.Op Fl f Ar format +.Op Fl s Ar string +.Op Fl t Ar string +.Op Ar first Op Ar incr +.Ar last +.Sh DESCRIPTION +The +.Nm +utility prints a sequence of numbers, one per line +.Pq default , +from +.Ar first +.Pq default 1 , +to near +.Ar last +as possible, in increments of +.Ar incr +.Pq default 1 . +When +.Ar first +is larger than +.Ar last +the default +.Ar incr +is -1. +.Pp +All numbers are interpreted as floating point. +.Pp +Normally integer values are printed as decimal integers. +.Pp +The +.Nm +utility accepts the following options: +.Bl -tag -width Ar +.It Fl f Ar format +Use a +.Xr printf 3 +style +.Ar format +to print each number. +Only the +.Cm A , +.Cm a , +.Cm E , +.Cm e , +.Cm F , +.Cm f , +.Cm G , +.Cm g , +and +.Cm % +conversion characters are valid, along with any optional +flags and an optional numeric mimimum field width or precision. +The +.Ar format +can contain character escape sequences in backslash notation as +defined in +.St -ansiC . +The default is +.Cm %g . +.It Fl s Ar string +Use +.Ar string +to separate numbers. +The +.Ar string +can contain character escape sequences in backslash notation as +defined in +.St -ansiC . +The default is +.Cm \en . +.It Fl t Ar string +Use +.Ar string +to terminate sequence of numbers. +The +.Ar string +can contain character escape sequences in backslash notation as +defined in +.St -ansiC . +This option is useful when the default separator +does not contain a +.Cm \en . +.It Fl w +Equalize the widths of all numbers by padding with zeros as necessary. +This option has no effect with the +.Fl f +option. +If any sequence numbers will be printed in exponential notation, +the default conversion is changed to +.Cm %e . +.El +.Pp +The +.Nm +utility exits 0 on success and non-zero if an error occurs. +.Sh EXAMPLES +.Bd -literal -offset indent +# seq 1 3 +1 +2 +3 + +# seq 3 1 +3 +2 +1 + +# seq -w 0 .05 .1 +0.00 +0.05 +0.10 +.Ed +.Sh SEE ALSO +.Xr jot 1 , +.Xr printf 1 , +.Xr printf 3 +.Sh HISTORY +The +.Nm +command first appeared in +.Tn "Plan 9 from Bell Labs" . +A +.Nm +command appeared in +.Nx 3.0 . +This command was based on the command of the same name in +.Tn "Plan 9 from Bell Labs" +and the +.Tn GNU +core utilities. +The +.Tn GNU +.Nm +command first appeared in the 1.13 shell utilities release. +.Sh BUGS +The +.Fl w +option does not handle the transition from pure floating point +to exponent representation very well. +The +.Nm +command is not bug for bug compatible with the +.Tn "Plan 9 from Bell Labs" +or +.Tn GNU +versions of +.Nm . diff --git a/usr.bin/seq/seq.c b/usr.bin/seq/seq.c new file mode 100644 index 000000000..fc5a15ccd --- /dev/null +++ b/usr.bin/seq/seq.c @@ -0,0 +1,474 @@ +/* + * Copyright (c) 2005 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Brian Ginsbach. + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 2005\ + The NetBSD Foundation, Inc. All rights reserved."); +__RCSID("$NetBSD: seq.c,v 1.7 2010/05/27 08:40:19 dholland Exp $"); +#endif /* not lint */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ZERO '0' +#define SPACE ' ' + +#define MAX(a, b) (((a) < (b))? (b) : (a)) +#define ISSIGN(c) ((int)(c) == '-' || (int)(c) == '+') +#define ISEXP(c) ((int)(c) == 'e' || (int)(c) == 'E') +#define ISODIGIT(c) ((int)(c) >= '0' && (int)(c) <= '7') + +/* Globals */ + +const char *decimal_point = "."; /* default */ +char default_format[] = { "%g" }; /* default */ + +/* Prototypes */ + +double e_atof(const char *); + +int decimal_places(const char *); +int main(int, char *[]); +int numeric(const char *); +int valid_format(const char *); + +char *generate_format(double, double, double, int, char); +char *unescape(char *); + +/* + * The seq command will print out a numeric sequence from 1, the default, + * to a user specified upper limit by 1. The lower bound and increment + * maybe indicated by the user on the command line. The sequence can + * be either whole, the default, or decimal numbers. + */ +int +main(int argc, char *argv[]) +{ + int c = 0, errflg = 0; + int equalize = 0; + double first = 1.0; + double last = 0.0; + double incr = 0.0; + struct lconv *locale; + char *fmt = NULL; + const char *sep = "\n"; + const char *term = NULL; + char pad = ZERO; + + /* Determine the locale's decimal point. */ + locale = localeconv(); + if (locale && locale->decimal_point && locale->decimal_point[0] != '\0') + decimal_point = locale->decimal_point; + + /* + * Process options, but handle negative numbers separately + * least they trip up getopt(3). + */ + while ((optind < argc) && !numeric(argv[optind]) && + (c = getopt(argc, argv, "f:hs:t:w")) != -1) { + + switch (c) { + case 'f': /* format (plan9) */ + fmt = optarg; + equalize = 0; + break; + case 's': /* separator (GNU) */ + sep = unescape(optarg); + break; + case 't': /* terminator (new) */ + term = unescape(optarg); + break; + case 'w': /* equal width (plan9) */ + if (!fmt) + if (equalize++) + pad = SPACE; + break; + case 'h': /* help (GNU) */ + default: + errflg++; + break; + } + } + + argc -= optind; + argv += optind; + if (argc < 1 || argc > 3) + errflg++; + + if (errflg) { + fprintf(stderr, + "usage: %s [-w] [-f format] [-s string] [-t string] [first [incr]] last\n", + getprogname()); + exit(1); + } + + last = e_atof(argv[argc - 1]); + + if (argc > 1) + first = e_atof(argv[0]); + + if (argc > 2) { + incr = e_atof(argv[1]); + /* Plan 9/GNU don't do zero */ + if (incr == 0.0) + errx(1, "zero %screment", (first < last)? "in" : "de"); + } + + /* default is one for Plan 9/GNU work alike */ + if (incr == 0.0) + incr = (first < last) ? 1.0 : -1.0; + + if (incr <= 0.0 && first < last) + errx(1, "needs positive increment"); + + if (incr >= 0.0 && first > last) + errx(1, "needs negative decrement"); + + if (fmt != NULL) { + if (!valid_format(fmt)) + errx(1, "invalid format string: `%s'", fmt); + fmt = unescape(fmt); + if (!valid_format(fmt)) + errx(1, "invalid format string"); + /* + * XXX to be bug for bug compatible with Plan 9 add a + * newline if none found at the end of the format string. + */ + } else + fmt = generate_format(first, incr, last, equalize, pad); + + if (incr > 0) { + for (; first <= last; first += incr) { + printf(fmt, first); + fputs(sep, stdout); + } + } else { + for (; first >= last; first += incr) { + printf(fmt, first); + fputs(sep, stdout); + } + } + if (term != NULL) + fputs(term, stdout); + + return (0); +} + +/* + * numeric - verify that string is numeric + */ +int +numeric(const char *s) +{ + int seen_decimal_pt, decimal_pt_len; + + /* skip any sign */ + if (ISSIGN((unsigned char)*s)) + s++; + + seen_decimal_pt = 0; + decimal_pt_len = strlen(decimal_point); + while (*s) { + if (!isdigit((unsigned char)*s)) { + if (!seen_decimal_pt && + strncmp(s, decimal_point, decimal_pt_len) == 0) { + s += decimal_pt_len; + seen_decimal_pt = 1; + continue; + } + if (ISEXP((unsigned char)*s)) { + s++; + if (ISSIGN((unsigned char)*s)) { + s++; + continue; + } + } + break; + } + s++; + } + return (*s == '\0'); +} + +/* + * valid_format - validate user specified format string + */ +int +valid_format(const char *fmt) +{ + unsigned conversions = 0; + + while (*fmt != '\0') { + /* scan for conversions */ + if (*fmt != '%') { + fmt++; + continue; + } + fmt++; + + /* allow %% but not things like %10% */ + if (*fmt == '%') { + fmt++; + continue; + } + + /* flags */ + while (*fmt != '\0' && strchr("#0- +'", *fmt)) { + fmt++; + } + + /* field width */ + while (*fmt != '\0' && strchr("0123456789", *fmt)) { + fmt++; + } + + /* precision */ + if (*fmt == '.') { + fmt++; + while (*fmt != '\0' && strchr("0123456789", *fmt)) { + fmt++; + } + } + + /* conversion */ + switch (*fmt) { + case 'A': + case 'a': + case 'E': + case 'e': + case 'F': + case 'f': + case 'G': + case 'g': + /* floating point formats are accepted */ + conversions++; + break; + default: + /* anything else is not */ + return 0; + } + } + + return (conversions <= 1); +} + +/* + * unescape - handle C escapes in a string + */ +char * +unescape(char *orig) +{ + char c, *cp, *new = orig; + int i; + + for (cp = orig; (*orig = *cp); cp++, orig++) { + if (*cp != '\\') + continue; + + switch (*++cp) { + case 'a': /* alert (bell) */ + *orig = '\a'; + continue; + case 'b': /* backspace */ + *orig = '\b'; + continue; + case 'e': /* escape */ + *orig = '\e'; + continue; + case 'f': /* formfeed */ + *orig = '\f'; + continue; + case 'n': /* newline */ + *orig = '\n'; + continue; + case 'r': /* carriage return */ + *orig = '\r'; + continue; + case 't': /* horizontal tab */ + *orig = '\t'; + continue; + case 'v': /* vertical tab */ + *orig = '\v'; + continue; + case '\\': /* backslash */ + *orig = '\\'; + continue; + case '\'': /* single quote */ + *orig = '\''; + continue; + case '\"': /* double quote */ + *orig = '"'; + continue; + case '0': + case '1': + case '2': + case '3': /* octal */ + case '4': + case '5': + case '6': + case '7': /* number */ + for (i = 0, c = 0; + ISODIGIT((unsigned char)*cp) && i < 3; + i++, cp++) { + c <<= 3; + c |= (*cp - '0'); + } + *orig = c; + --cp; + continue; + case 'x': /* hexidecimal number */ + cp++; /* skip 'x' */ + for (i = 0, c = 0; + isxdigit((unsigned char)*cp) && i < 2; + i++, cp++) { + c <<= 4; + if (isdigit((unsigned char)*cp)) + c |= (*cp - '0'); + else + c |= ((toupper((unsigned char)*cp) - + 'A') + 10); + } + *orig = c; + --cp; + continue; + default: + --cp; + break; + } + } + + return (new); +} + +/* + * e_atof - convert an ASCII string to a double + * exit if string is not a valid double, or if converted value would + * cause overflow or underflow + */ +double +e_atof(const char *num) +{ + char *endp; + double dbl; + + errno = 0; + dbl = strtod(num, &endp); + + if (errno == ERANGE) + /* under or overflow */ + err(2, "%s", num); + else if (*endp != '\0') + /* "junk" left in number */ + errx(2, "invalid floating point argument: %s", num); + + /* zero shall have no sign */ + if (dbl == -0.0) + dbl = 0; + return (dbl); +} + +/* + * decimal_places - count decimal places in a number (string) + */ +int +decimal_places(const char *number) +{ + int places = 0; + char *dp; + + /* look for a decimal point */ + if ((dp = strstr(number, decimal_point))) { + dp += strlen(decimal_point); + + while (isdigit((unsigned char)*dp++)) + places++; + } + return (places); +} + +/* + * generate_format - create a format string + * + * XXX to be bug for bug compatable with Plan9 and GNU return "%g" + * when "%g" prints as "%e" (this way no width adjustments are made) + */ +char * +generate_format(double first, double incr, double last, int equalize, char pad) +{ + static char buf[256]; + char cc = '\0'; + int precision, width1, width2, places; + + if (equalize == 0) + return (default_format); + + /* figure out "last" value printed */ + if (first > last) + last = first - incr * floor((first - last) / incr); + else + last = first + incr * floor((last - first) / incr); + + sprintf(buf, "%g", incr); + if (strchr(buf, 'e')) + cc = 'e'; + precision = decimal_places(buf); + + width1 = sprintf(buf, "%g", first); + if (strchr(buf, 'e')) + cc = 'e'; + if ((places = decimal_places(buf))) + width1 -= (places + strlen(decimal_point)); + + precision = MAX(places, precision); + + width2 = sprintf(buf, "%g", last); + if (strchr(buf, 'e')) + cc = 'e'; + if ((places = decimal_places(buf))) + width2 -= (places + strlen(decimal_point)); + + if (precision) { + sprintf(buf, "%%%c%d.%d%c", pad, + MAX(width1, width2) + (int) strlen(decimal_point) + + precision, precision, (cc) ? cc : 'f'); + } else { + sprintf(buf, "%%%c%d%c", pad, MAX(width1, width2), + (cc) ? cc : 'g'); + } + + return (buf); +} -- 2.44.0