diff options
author | Scott Anderson <saa@android.com> | 2012-04-09 14:08:22 -0700 |
---|---|---|
committer | Scott Anderson <saa@android.com> | 2012-04-10 11:12:16 -0700 |
commit | b0114cb9f332db144f65291211ae65f7f0e814e6 (patch) | |
tree | 48771941703bba0a537538ac493015c79bccf55e /src | |
parent | b0303471fba852a1f1172e749a1ac8ced1499f08 (diff) | |
download | stressapptest-b0114cb9f332db144f65291211ae65f7f0e814e6.tar.gz |
Initial version of stressapptest
From http://stressapptest.googlecode.com/files/stressapptest-1.0.4_autoconf.tar.gz
with the addition of MODULE_LICENSE_APACHE2 and NOTICE.
Change-Id: I1f3e80fce2c500766bcc7a67d7d42e485ddf57b4
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 31 | ||||
-rw-r--r-- | src/Makefile.in | 519 | ||||
-rw-r--r-- | src/adler32memcpy.cc | 406 | ||||
-rw-r--r-- | src/adler32memcpy.h | 59 | ||||
-rw-r--r-- | src/disk_blocks.cc | 313 | ||||
-rw-r--r-- | src/disk_blocks.h | 115 | ||||
-rw-r--r-- | src/error_diag.cc | 317 | ||||
-rw-r--r-- | src/error_diag.h | 167 | ||||
-rw-r--r-- | src/finelock_queue.cc | 448 | ||||
-rw-r--r-- | src/finelock_queue.h | 118 | ||||
-rw-r--r-- | src/logger.cc | 152 | ||||
-rw-r--r-- | src/logger.h | 142 | ||||
-rw-r--r-- | src/main.cc | 56 | ||||
-rw-r--r-- | src/os.cc | 849 | ||||
-rw-r--r-- | src/os.h | 290 | ||||
-rw-r--r-- | src/os_factory.cc | 40 | ||||
-rw-r--r-- | src/pattern.cc | 421 | ||||
-rw-r--r-- | src/pattern.h | 124 | ||||
-rw-r--r-- | src/queue.cc | 118 | ||||
-rw-r--r-- | src/queue.h | 85 | ||||
-rw-r--r-- | src/sat.cc | 1890 | ||||
-rw-r--r-- | src/sat.h | 310 | ||||
-rw-r--r-- | src/sat_factory.cc | 21 | ||||
-rw-r--r-- | src/sattypes.h | 187 | ||||
-rw-r--r-- | src/stressapptest_config.h.in | 222 | ||||
-rw-r--r-- | src/worker.cc | 3344 | ||||
-rw-r--r-- | src/worker.h | 804 |
27 files changed, 11548 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..e044974 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,31 @@ +bin_PROGRAMS = stressapptest + +AM_DEFAULT_SOURCE_EXT=.cc + +MAINFILES = main.cc +CFILES = os.cc +CFILES += os_factory.cc +CFILES += pattern.cc +CFILES += queue.cc +CFILES += sat.cc +CFILES += sat_factory.cc +CFILES += worker.cc +CFILES += finelock_queue.cc +CFILES += error_diag.cc +CFILES += disk_blocks.cc +CFILES += adler32memcpy.cc +CFILES += logger.cc + +HFILES = os.h +HFILES += pattern.h +HFILES += queue.h +HFILES += sat.h +HFILES += worker.h +HFILES += sattypes.h +HFILES += finelock_queue.h +HFILES += error_diag.h +HFILES += disk_blocks.h +HFILES += adler32memcpy.h +HFILES += logger.h + +stressapptest_SOURCES = $(MAINFILES) $(CFILES) $(HFILES) diff --git a/src/Makefile.in b/src/Makefile.in new file mode 100644 index 0000000..f62d1ac --- /dev/null +++ b/src/Makefile.in @@ -0,0 +1,519 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +# Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +target_triplet = @target@ +bin_PROGRAMS = stressapptest$(EXEEXT) +subdir = src +DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in \ + $(srcdir)/stressapptest_config.h.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = stressapptest_config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__installdirs = "$(DESTDIR)$(bindir)" +PROGRAMS = $(bin_PROGRAMS) +am__objects_1 = main.$(OBJEXT) +am__objects_2 = os.$(OBJEXT) os_factory.$(OBJEXT) pattern.$(OBJEXT) \ + queue.$(OBJEXT) sat.$(OBJEXT) sat_factory.$(OBJEXT) \ + worker.$(OBJEXT) finelock_queue.$(OBJEXT) error_diag.$(OBJEXT) \ + disk_blocks.$(OBJEXT) adler32memcpy.$(OBJEXT) logger.$(OBJEXT) +am__objects_3 = +am_stressapptest_OBJECTS = $(am__objects_1) $(am__objects_2) \ + $(am__objects_3) +stressapptest_OBJECTS = $(am_stressapptest_OBJECTS) +stressapptest_LDADD = $(LDADD) +DEFAULT_INCLUDES = -I.@am__isrc@ +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +CXXLD = $(CXX) +CXXLINK = $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) \ + -o $@ +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +SOURCES = $(stressapptest_SOURCES) +DIST_SOURCES = $(stressapptest_SOURCES) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target = @target@ +target_alias = @target_alias@ +target_cpu = @target_cpu@ +target_os = @target_os@ +target_vendor = @target_vendor@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_DEFAULT_SOURCE_EXT = .cc +MAINFILES = main.cc +CFILES = os.cc os_factory.cc pattern.cc queue.cc sat.cc sat_factory.cc \ + worker.cc finelock_queue.cc error_diag.cc disk_blocks.cc \ + adler32memcpy.cc logger.cc +HFILES = os.h pattern.h queue.h sat.h worker.h sattypes.h \ + finelock_queue.h error_diag.h disk_blocks.h adler32memcpy.h \ + logger.h +stressapptest_SOURCES = $(MAINFILES) $(CFILES) $(HFILES) +all: stressapptest_config.h + $(MAKE) $(AM_MAKEFLAGS) all-am + +.SUFFIXES: +.SUFFIXES: .cc .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +stressapptest_config.h: stamp-h1 + @if test ! -f $@; then \ + rm -f stamp-h1; \ + $(MAKE) $(AM_MAKEFLAGS) stamp-h1; \ + else :; fi + +stamp-h1: $(srcdir)/stressapptest_config.h.in $(top_builddir)/config.status + @rm -f stamp-h1 + cd $(top_builddir) && $(SHELL) ./config.status src/stressapptest_config.h +$(srcdir)/stressapptest_config.h.in: $(am__configure_deps) + ($(am__cd) $(top_srcdir) && $(AUTOHEADER)) + rm -f stamp-h1 + touch $@ + +distclean-hdr: + -rm -f stressapptest_config.h stamp-h1 +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)" + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p; \ + then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(bindir)" && rm -f $$files + +clean-binPROGRAMS: + -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS) +stressapptest$(EXEEXT): $(stressapptest_OBJECTS) $(stressapptest_DEPENDENCIES) + @rm -f stressapptest$(EXEEXT) + $(CXXLINK) $(stressapptest_OBJECTS) $(stressapptest_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/adler32memcpy.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/disk_blocks.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/error_diag.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/finelock_queue.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/logger.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/os.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/os_factory.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pattern.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/queue.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sat.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sat_factory.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/worker.Po@am__quote@ + +.cc.o: +@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(CXXCOMPILE) -c -o $@ $< + +.cc.obj: +@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCXX_TRUE@ $(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) stressapptest_config.h.in $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) stressapptest_config.h.in $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) stressapptest_config.h.in $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) stressapptest_config.h.in $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) stressapptest_config.h +installdirs: + for dir in "$(DESTDIR)$(bindir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-binPROGRAMS clean-generic mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-hdr distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-binPROGRAMS + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS + +.MAKE: all install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-binPROGRAMS \ + clean-generic ctags distclean distclean-compile \ + distclean-generic distclean-hdr distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-binPROGRAMS install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic pdf pdf-am ps ps-am \ + tags uninstall uninstall-am uninstall-binPROGRAMS + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/adler32memcpy.cc b/src/adler32memcpy.cc new file mode 100644 index 0000000..69324f7 --- /dev/null +++ b/src/adler32memcpy.cc @@ -0,0 +1,406 @@ +// Copyright 2008 Google Inc. All Rights Reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "adler32memcpy.h" + +// We are using (a modified form of) adler-32 checksum algorithm instead +// of CRC since adler-32 is faster than CRC. +// (Comparison: http://guru.multimedia.cx/crc32-vs-adler32/) +// This form of adler is bit modified, instead of treating the data in +// units of bytes, 32-bit data is taken as a unit and two 64-bit +// checksums are done (we could have one checksum but two checksums +// make the code run faster). + +// Adler-32 implementation: +// Data is treated as 1-byte numbers and, +// there are two 16-bit numbers a and b +// Initialize a with 1 and b with 0. +// for each data unit 'd' +// a += d +// b += a +// checksum = a<<16 + b +// This sum should never overflow. +// +// Adler-64+64 implementation: +// (applied in this code) +// Data is treated as 32-bit numbers and whole data is separated into two +// streams, and hence the two checksums a1, a2, b1 and b2. +// Initialize a1 and a2 with 1, b1 and b2 with 0 +// add first dataunit to a1 +// add a1 to b1 +// add second dataunit to a1 +// add a1 to b1 +// add third dataunit to a2 +// add a2 to b2 +// add fourth dataunit to a2 +// add a2 to b2 +// ... +// repeat the sequence back for next 4 dataunits +// +// variable A = XMM6 and variable B = XMM7. +// (a1 = lower 8 bytes of XMM6 and b1 = lower 8 bytes of XMM7) + +// Assumptions +// 1. size_in_bytes is a multiple of 16. +// 2. srcmem and dstmem are 16 byte aligned. +// 3. size_in_bytes is less than 2^19 bytes. + +// Assumption 3 ensures that there is no overflow when numbers are being +// added (we can remove this assumption by doing modulus with a prime +// number when it is just about to overflow but that would be a very costly +// exercise) + +// Returns true if the checksums are equal. +bool AdlerChecksum::Equals(const AdlerChecksum &other) const { + return ( (a1_ == other.a1_) && (a2_ == other.a2_) && + (b1_ == other.b1_) && (b2_ == other.b2_) ); +} + +// Returns string representation of the Adler checksum. +string AdlerChecksum::ToHexString() const { + char buffer[128]; + snprintf(buffer, sizeof(buffer), "%llx%llx%llx%llx", a1_, a2_, b1_, b2_); + return string(buffer); +} + +// Sets components of the Adler checksum. +void AdlerChecksum::Set(uint64 a1, uint64 a2, uint64 b1, uint64 b2) { + a1_ = a1; + a2_ = a2; + b1_ = b1; + b2_ = b2; +} + +// Calculates Adler checksum for supplied data. +bool CalculateAdlerChecksum(uint64 *data64, unsigned int size_in_bytes, + AdlerChecksum *checksum) { + // Use this data wrapper to access memory with 64bit read/write. + datacast_t data; + unsigned int count = size_in_bytes / sizeof(data); + + if (count > (1U) << 19) { + // Size is too large, must be strictly less than 512 KB. + return false; + } + + uint64 a1 = 1; + uint64 a2 = 1; + uint64 b1 = 0; + uint64 b2 = 0; + + unsigned int i = 0; + while (i < count) { + // Process 64 bits at a time. + data.l64 = data64[i]; + a1 = a1 + data.l32.l; + b1 = b1 + a1; + a1 = a1 + data.l32.h; + b1 = b1 + a1; + i++; + + data.l64 = data64[i]; + a2 = a2 + data.l32.l; + b2 = b2 + a2; + a2 = a2 + data.l32.h; + b2 = b2 + a2; + i++; + } + checksum->Set(a1, a2, b1, b2); + return true; +} + +// C implementation of Adler memory copy. +bool AdlerMemcpyC(uint64 *dstmem64, uint64 *srcmem64, + unsigned int size_in_bytes, AdlerChecksum *checksum) { + // Use this data wrapper to access memory with 64bit read/write. + datacast_t data; + unsigned int count = size_in_bytes / sizeof(data); + + if (count > ((1U) << 19)) { + // Size is too large, must be strictly less than 512 KB. + return false; + } + + uint64 a1 = 1; + uint64 a2 = 1; + uint64 b1 = 0; + uint64 b2 = 0; + + unsigned int i = 0; + while (i < count) { + // Process 64 bits at a time. + data.l64 = srcmem64[i]; + a1 = a1 + data.l32.l; + b1 = b1 + a1; + a1 = a1 + data.l32.h; + b1 = b1 + a1; + dstmem64[i] = data.l64; + i++; + + data.l64 = srcmem64[i]; + a2 = a2 + data.l32.l; + b2 = b2 + a2; + a2 = a2 + data.l32.h; + b2 = b2 + a2; + dstmem64[i] = data.l64; + i++; + } + checksum->Set(a1, a2, b1, b2); + return true; +} + +// C implementation of Adler memory copy with some float point ops, +// attempting to warm up the CPU. +bool AdlerMemcpyWarmC(uint64 *dstmem64, uint64 *srcmem64, + unsigned int size_in_bytes, AdlerChecksum *checksum) { + // Use this data wrapper to access memory with 64bit read/write. + datacast_t data; + unsigned int count = size_in_bytes / sizeof(data); + + if (count > ((1U) << 19)) { + // Size is too large, must be strictly less than 512 KB. + return false; + } + + uint64 a1 = 1; + uint64 a2 = 1; + uint64 b1 = 0; + uint64 b2 = 0; + + double a = 2.0 * static_cast<double>(srcmem64[0]); + double b = 5.0 * static_cast<double>(srcmem64[0]); + double c = 7.0 * static_cast<double>(srcmem64[0]); + double d = 9.0 * static_cast<double>(srcmem64[0]); + + unsigned int i = 0; + while (i < count) { + // Process 64 bits at a time. + data.l64 = srcmem64[i]; + a1 = a1 + data.l32.l; + b1 = b1 + a1; + a1 = a1 + data.l32.h; + b1 = b1 + a1; + dstmem64[i] = data.l64; + i++; + + // Warm cpu up. + a = a * b; + b = b + c; + + data.l64 = srcmem64[i]; + a2 = a2 + data.l32.l; + b2 = b2 + a2; + a2 = a2 + data.l32.h; + b2 = b2 + a2; + dstmem64[i] = data.l64; + i++; + + // Warm cpu up. + c = c * d; + d = d + d; + } + + // Warm cpu up. + d = a + b + c + d; + if (d == 1.0) { + // Reference the result so that it can't be discarded by the compiler. + printf("Log: This will probably never happen.\n"); + } + + checksum->Set(a1, a2, b1, b2); + return true; +} + +// x86_64 SSE2 assembly implementation of fast and stressful Adler memory copy. +bool AdlerMemcpyAsm(uint64 *dstmem64, uint64 *srcmem64, + unsigned int size_in_bytes, AdlerChecksum *checksum) { +// Use assembly implementation where supported. +#if defined(STRESSAPPTEST_CPU_X86_64) || defined(STRESSAPPTEST_CPU_I686) + +// Pull a bit of tricky preprocessing to make the inline asm both +// 32 bit and 64 bit. +#ifdef STRESSAPPTEST_CPU_I686 // Instead of coding both, x86... +#define rAX "%%eax" +#define rCX "%%ecx" +#define rDX "%%edx" +#define rBX "%%ebx" +#define rSP "%%esp" +#define rBP "%%ebp" +#define rSI "%%esi" +#define rDI "%%edi" +#endif + +#ifdef STRESSAPPTEST_CPU_X86_64 // ...and x64, we use rXX macros. +#define rAX "%%rax" +#define rCX "%%rcx" +#define rDX "%%rdx" +#define rBX "%%rbx" +#define rSP "%%rsp" +#define rBP "%%rbp" +#define rSI "%%rsi" +#define rDI "%%rdi" +#endif + + // Elements 0 to 3 are used for holding checksum terms a1, a2, + // b1, b2 respectively. These elements are filled by asm code. + // Elements 4 and 5 are used by asm code to for ANDing MMX data and removing + // 2 words from each MMX register (A MMX reg has 4 words, by ANDing we are + // setting word index 0 and word index 2 to zero). + // Element 6 and 7 are used for setting a1 and a2 to 1. + volatile uint64 checksum_arr[] __attribute__ ((aligned(16))) = + {0, 0, 0, 0, 0x00000000ffffffffUL, 0x00000000ffffffffUL, 1, 1}; + + if ((size_in_bytes >> 19) > 0) { + // Size is too large. Must be less than 2^19 bytes = 512 KB. + return false; + } + + // Number of 32-bit words which are not added to a1/a2 in the main loop. + uint32 remaining_words = (size_in_bytes % 48) / 4; + + // Since we are moving 48 bytes at a time number of iterations = total size/48 + // is value of counter. + uint32 num_of_48_byte_units = size_in_bytes / 48; + + asm volatile ( + // Source address is in ESI (extended source index) + // destination is in EDI (extended destination index) + // and counter is already in ECX (extended counter + // index). + "cmp $0, " rCX ";" // Compare counter to zero. + "jz END;" + + // XMM6 is initialized with 1 and XMM7 with 0. + "prefetchnta 0(" rSI ");" + "prefetchnta 64(" rSI ");" + "movdqu 48(" rAX "), %%xmm6;" + "xorps %%xmm7, %%xmm7;" + + // Start of the loop which copies 48 bytes from source to dst each time. + "TOP:\n" + + // Make 6 moves each of 16 bytes from srcmem to XMM registers. + // We are using 2 words out of 4 words in each XMM register, + // word index 0 and word index 2 + "movdqa 0(" rSI "), %%xmm0;" + "movdqu 4(" rSI "), %%xmm1;" // Be careful to use unaligned move here. + "movdqa 16(" rSI "), %%xmm2;" + "movdqu 20(" rSI "), %%xmm3;" + "movdqa 32(" rSI "), %%xmm4;" + "movdqu 36(" rSI "), %%xmm5;" + + // Move 3 * 16 bytes from XMM registers to dstmem. + // Note: this copy must be performed before pinsrw instructions since + // they will modify the XMM registers. + "movntdq %%xmm0, 0(" rDI ");" + "movntdq %%xmm2, 16(" rDI ");" + "movntdq %%xmm4, 32(" rDI ");" + + // Sets the word[1] and word[3] of XMM0 to XMM5 to zero. + "andps 32(" rAX "), %%xmm0;" + "andps 32(" rAX "), %%xmm1;" + "andps 32(" rAX "), %%xmm2;" + "andps 32(" rAX "), %%xmm3;" + "andps 32(" rAX "), %%xmm4;" + "andps 32(" rAX "), %%xmm5;" + + // Add XMM0 to XMM6 and then add XMM6 to XMM7. + // Repeat this for XMM1, ..., XMM5. + // Overflow(for XMM7) can occur only if there are more + // than 2^16 additions => more than 2^17 words => more than 2^19 bytes so + // if size_in_bytes > 2^19 than overflow occurs. + "paddq %%xmm0, %%xmm6;" + "paddq %%xmm6, %%xmm7;" + "paddq %%xmm1, %%xmm6;" + "paddq %%xmm6, %%xmm7;" + "paddq %%xmm2, %%xmm6;" + "paddq %%xmm6, %%xmm7;" + "paddq %%xmm3, %%xmm6;" + "paddq %%xmm6, %%xmm7;" + "paddq %%xmm4, %%xmm6;" + "paddq %%xmm6, %%xmm7;" + "paddq %%xmm5, %%xmm6;" + "paddq %%xmm6, %%xmm7;" + + // Increment ESI and EDI by 48 bytes and decrement counter by 1. + "add $48, " rSI ";" + "add $48, " rDI ";" + "prefetchnta 0(" rSI ");" + "prefetchnta 64(" rSI ");" + "dec " rCX ";" + "jnz TOP;" + + // Now only remaining_words 32-bit words are left. + // make a loop, add first two words to a1 and next two to a2 (just like + // above loop, the only extra thing we are doing is rechecking + // rDX (=remaining_words) everytime we add a number to a1/a2. + "REM_IS_STILL_NOT_ZERO:\n" + // Unless remaining_words becomes less than 4 words(16 bytes) + // there is not much issue and remaining_words will always + // be a multiple of four by assumption. + "cmp $4, " rDX ";" + // In case for some weird reasons if remaining_words becomes + // less than 4 but not zero then also break the code and go off to END. + "jl END;" + // Otherwise just go on and copy data in chunks of 4-words at a time till + // whole data (<48 bytes) is copied. + "movdqa 0(" rSI "), %%xmm0;" // Copy next 4-words to XMM0 and to XMM1. + + "movdqa 0(" rSI "), %%xmm5;" // Accomplish movdqu 4(%rSI) without + "pshufd $0x39, %%xmm5, %%xmm1;" // indexing off memory boundary. + + "movntdq %%xmm0, 0(" rDI ");" // Copy 4-words to destination. + "andps 32(" rAX "), %%xmm0;" + "andps 32(" rAX "), %%xmm1;" + "paddq %%xmm0, %%xmm6;" + "paddq %%xmm6, %%xmm7;" + "paddq %%xmm1, %%xmm6;" + "paddq %%xmm6, %%xmm7;" + "add $16, " rSI ";" + "add $16, " rDI ";" + "sub $4, " rDX ";" + // Decrement %rDX by 4 since %rDX is number of 32-bit + // words left after considering all 48-byte units. + "jmp REM_IS_STILL_NOT_ZERO;" + + "END:\n" + // Report checksum values A and B (both right now are two concatenated + // 64 bit numbers and have to be converted to 64 bit numbers) + // seems like Adler128 (since size of each part is 4 byte rather than + // 1 byte). + "movdqa %%xmm6, 0(" rAX ");" + "movdqa %%xmm7, 16(" rAX ");" + "sfence;" + + // No output registers. + : + // Input registers. + : "S" (srcmem64), "D" (dstmem64), "a" (checksum_arr), + "c" (num_of_48_byte_units), "d" (remaining_words) + ); // asm. + + if (checksum != NULL) { + checksum->Set(checksum_arr[0], checksum_arr[1], + checksum_arr[2], checksum_arr[3]); + } + + // Everything went fine, so return true (this does not mean + // that there is no problem with memory this just mean that data was copied + // from src to dst and checksum was calculated successfully). + return true; +#else + // Fall back to C implementation for anything else. + return AdlerMemcpyWarmC(dstmem64, srcmem64, size_in_bytes, checksum); +#endif +} diff --git a/src/adler32memcpy.h b/src/adler32memcpy.h new file mode 100644 index 0000000..d053340 --- /dev/null +++ b/src/adler32memcpy.h @@ -0,0 +1,59 @@ +// Copyright 2008 Google Inc. All Rights Reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STRESSAPPTEST_ADLER32MEMCPY_H_ +#define STRESSAPPTEST_ADLER32MEMCPY_H_ + +#include <string> +#include "sattypes.h" + +// Encapsulation for Adler checksum. Please see adler32memcpy.cc for more +// detail on the adler checksum algorithm. +class AdlerChecksum { + public: + AdlerChecksum() {} + ~AdlerChecksum() {} + // Returns true if the checksums are equal. + bool Equals(const AdlerChecksum &other) const; + // Returns string representation of the Adler checksum + string ToHexString() const; + // Sets components of the Adler checksum. + void Set(uint64 a1, uint64 a2, uint64 b1, uint64 b2); + + private: + // Components of Adler checksum. + uint64 a1_, a2_, b1_, b2_; + + DISALLOW_COPY_AND_ASSIGN(AdlerChecksum); +}; + +// Calculates Adler checksum for supplied data. +bool CalculateAdlerChecksum(uint64 *data64, unsigned int size_in_bytes, + AdlerChecksum *checksum); + +// C implementation of Adler memory copy. +bool AdlerMemcpyC(uint64 *dstmem64, uint64 *srcmem64, + unsigned int size_in_bytes, AdlerChecksum *checksum); + +// C implementation of Adler memory copy with some float point ops, +// attempting to warm up the CPU. +bool AdlerMemcpyWarmC(uint64 *dstmem64, uint64 *srcmem64, + unsigned int size_in_bytes, AdlerChecksum *checksum); + +// x86_64 SSE2 assembly implementation of fast and stressful Adler memory copy. +bool AdlerMemcpyAsm(uint64 *dstmem64, uint64 *srcmem64, + unsigned int size_in_bytes, AdlerChecksum *checksum); + + +#endif // STRESSAPPTEST_ADLER32MEMCPY_H_ diff --git a/src/disk_blocks.cc b/src/disk_blocks.cc new file mode 100644 index 0000000..c7860b0 --- /dev/null +++ b/src/disk_blocks.cc @@ -0,0 +1,313 @@ +// Copyright 2008 Google Inc. All Rights Reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Thread-safe container of disk blocks + +#include <utility> + +// This file must work with autoconf on its public version, +// so these includes are correct. +#include "disk_blocks.h" + +DiskBlockTable::DiskBlockTable() { + nelems_ = 0; + pthread_mutex_init(&data_mutex_, NULL); + pthread_mutex_init(¶meter_mutex_, NULL); + pthread_cond_init(&data_condition_, NULL); +} + +DiskBlockTable::~DiskBlockTable() { + CleanTable(); + pthread_mutex_destroy(&data_mutex_); + pthread_mutex_destroy(¶meter_mutex_); + pthread_cond_destroy(&data_condition_); +} + +void DiskBlockTable::CleanTable() { + pthread_mutex_lock(&data_mutex_); + for (map<int64, StorageData*>::iterator it = + addr_to_block_.begin(); it != addr_to_block_.end(); ++it) { + delete it->second; + } + addr_to_block_.erase(addr_to_block_.begin(), addr_to_block_.end()); + nelems_ = 0; + pthread_cond_broadcast(&data_condition_); + pthread_mutex_unlock(&data_mutex_); +} + +// 64-bit non-negative random number generator. Stolen from +// depot/google3/base/tracecontext_unittest.cc. +int64 DiskBlockTable::Random64() { + int64 x = random(); + x = (x << 30) ^ random(); + x = (x << 30) ^ random(); + if (x >= 0) + return x; + else + return -x; +} + +int64 DiskBlockTable::NumElems() { + unsigned int nelems; + pthread_mutex_lock(&data_mutex_); + nelems = nelems_; + pthread_mutex_unlock(&data_mutex_); + return nelems; +} + +void DiskBlockTable::InsertOnStructure(BlockData *block) { + int64 address = block->GetAddress(); + StorageData *sd = new StorageData(); + sd->block = block; + sd->pos = nelems_; + // Creating new block ... + pthread_mutex_lock(&data_mutex_); + if (pos_to_addr_.size() <= nelems_) { + pos_to_addr_.insert(pos_to_addr_.end(), address); + } else { + pos_to_addr_[nelems_] = address; + } + addr_to_block_.insert(std::make_pair(address, sd)); + nelems_++; + pthread_cond_broadcast(&data_condition_); + pthread_mutex_unlock(&data_mutex_); +} + +int DiskBlockTable::RemoveBlock(BlockData *block) { + // For write threads, check the reference counter and remove + // it from the structure. + int64 address = block->GetAddress(); + AddrToBlockMap::iterator it = addr_to_block_.find(address); + int ret = 1; + if (it != addr_to_block_.end()) { + int curr_pos = it->second->pos; + int last_pos = nelems_ - 1; + AddrToBlockMap::iterator last_it = addr_to_block_.find( + pos_to_addr_[last_pos]); + sat_assert(nelems_ > 0); + sat_assert(last_it != addr_to_block_.end()); + // Everything is fine, updating ... + pthread_mutex_lock(&data_mutex_); + pos_to_addr_[curr_pos] = pos_to_addr_[last_pos]; + last_it->second->pos = curr_pos; + delete it->second; + addr_to_block_.erase(it); + nelems_--; + block->DecreaseReferenceCounter(); + if (block->GetReferenceCounter() == 0) + delete block; + pthread_cond_broadcast(&data_condition_); + pthread_mutex_unlock(&data_mutex_); + } else { + ret = 0; + } + return ret; +} + +int DiskBlockTable::ReleaseBlock(BlockData *block) { + // If is a random thread, just check the reference counter. + int ret = 1; + pthread_mutex_lock(&data_mutex_); + int references = block->GetReferenceCounter(); + if (references > 0) { + if (references == 1) + delete block; + else + block->DecreaseReferenceCounter(); + } else { + ret = 0; + } + pthread_mutex_unlock(&data_mutex_); + return ret; +} + +BlockData *DiskBlockTable::GetRandomBlock() { + struct timespec ts; + struct timeval tp; + int result = 0; + gettimeofday(&tp, NULL); + ts.tv_sec = tp.tv_sec; + ts.tv_nsec = tp.tv_usec * 1000; + ts.tv_sec += 2; // Wait for 2 seconds. + pthread_mutex_lock(&data_mutex_); + while (!nelems_ && result != ETIMEDOUT) { + result = pthread_cond_timedwait(&data_condition_, &data_mutex_, &ts); + } + if (result == ETIMEDOUT) { + pthread_mutex_unlock(&data_mutex_); + return NULL; + } else { + int64 random_number = Random64(); + int64 random_pos = random_number % nelems_; + int64 address = pos_to_addr_[random_pos]; + AddrToBlockMap::const_iterator it = addr_to_block_.find(address); + sat_assert(it != addr_to_block_.end()); + BlockData *b = it->second->block; + // A block is returned only if its content is written on disk. + if (b->BlockIsInitialized()) { + b->IncreaseReferenceCounter(); + } else { + b = NULL; + } + pthread_mutex_unlock(&data_mutex_); + return b; + } +} + +void DiskBlockTable::SetParameters( + int sector_size, int write_block_size, int64 device_sectors, + int64 segment_size, string device_name) { + pthread_mutex_lock(¶meter_mutex_); + sector_size_ = sector_size; + write_block_size_ = write_block_size; + device_sectors_ = device_sectors; + segment_size_ = segment_size; + device_name_ = device_name; + CleanTable(); + pthread_mutex_unlock(¶meter_mutex_); +} + +BlockData *DiskBlockTable::GetUnusedBlock(int64 segment) { + int64 sector = 0; + BlockData *block = new BlockData(); + + bool good_sequence = false; + int num_sectors; + + if (block == NULL) { + logprintf(0, "Process Error: Unable to allocate memory " + "for sector data for disk %s.\n", device_name_.c_str()); + return NULL; + } + + pthread_mutex_lock(¶meter_mutex_); + + sat_assert(device_sectors_ != 0); + + // Align the first sector with the beginning of a write block + num_sectors = write_block_size_ / sector_size_; + + for (int i = 0; i < kBlockRetry && !good_sequence; i++) { + good_sequence = true; + + // Use the entire disk or a small segment of the disk to allocate the first + // sector in the block from. + + if (segment_size_ == -1) { + sector = (Random64() & 0x7FFFFFFFFFFFFFFFLL) % ( + device_sectors_ / num_sectors); + sector *= num_sectors; + } else { + sector = (Random64() & 0x7FFFFFFFFFFFFFFFLL) % ( + segment_size_ / num_sectors); + sector *= num_sectors; + sector += segment * segment_size_; + + // Make sure the block is within the segment. + if (sector + num_sectors > (segment + 1) * segment_size_) { + good_sequence = false; + continue; + } + } + // Make sure the entire block is in range. + if (sector + num_sectors > device_sectors_) { + good_sequence = false; + continue; + } + // Check to see if the block is free. Since the blocks are + // now aligned to the write_block_size, it is not necessary + // to check each sector, just the first block (a sector + // overlap will never occur). + + pthread_mutex_lock(&data_mutex_); + if (addr_to_block_.find(sector) != addr_to_block_.end()) { + good_sequence = false; + } + pthread_mutex_unlock(&data_mutex_); + } + + if (good_sequence) { + block->SetParameters(sector, write_block_size_); + block->IncreaseReferenceCounter(); + InsertOnStructure(block); + } else { + // No contiguous sequence of num_sectors sectors was found within + // kBlockRetry iterations so return an error value. + delete block; + block = NULL; + } + pthread_mutex_unlock(¶meter_mutex_); + + return block; +} + +// BlockData + +BlockData::BlockData() { + addr_ = 0; + size_ = 0; + references_ = 0; + initialized_ = false; + pthread_mutex_init(&data_mutex_, NULL); +} + +BlockData::~BlockData() { + pthread_mutex_destroy(&data_mutex_); +} + +void BlockData::SetParameters(int64 address, int64 size) { + addr_ = address; + size_ = size; +} + +void BlockData::IncreaseReferenceCounter() { + references_++; +} + +void BlockData::DecreaseReferenceCounter() { + references_--; +} + +int BlockData::GetReferenceCounter() { + return references_; +} + +void BlockData::SetBlockAsInitialized() { + pthread_mutex_lock(&data_mutex_); + initialized_ = true; + pthread_mutex_unlock(&data_mutex_); +} + +bool BlockData::BlockIsInitialized() { + pthread_mutex_lock(&data_mutex_); + bool initialized = initialized_; + pthread_mutex_unlock(&data_mutex_); + return initialized; +} + +int64 BlockData::GetAddress() { + return addr_; +} + +int64 BlockData::GetSize() { + return size_; +} + +Pattern *BlockData::GetPattern() { + return pattern_; +} + +void BlockData::SetPattern(Pattern *p) { + pattern_ = p; +} diff --git a/src/disk_blocks.h b/src/disk_blocks.h new file mode 100644 index 0000000..cb634c9 --- /dev/null +++ b/src/disk_blocks.h @@ -0,0 +1,115 @@ +// Copyright 2008 Google Inc. All Rights Reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Interface for a thread-safe container of disk blocks + +#ifndef STRESSAPPTEST_DISK_BLOCKS_H_ +#define STRESSAPPTEST_DISK_BLOCKS_H_ + +#include <sys/types.h> +#include <pthread.h> +#include <time.h> +#include <sys/time.h> +#include <errno.h> +#include <map> +#include <vector> +#include <string> +// This file must work with autoconf on its public version, +// so these includes are correct. +#include "pattern.h" + +// Data about a block written to disk so that it can be verified later. +class BlockData { + public: + BlockData(); + ~BlockData(); + void SetParameters(int64 address, int64 size); + void IncreaseReferenceCounter(); + void DecreaseReferenceCounter(); + int GetReferenceCounter(); + void SetBlockAsInitialized(); + bool BlockIsInitialized(); + int64 GetAddress(); + int64 GetSize(); + void SetPattern(Pattern *p); + Pattern *GetPattern(); + protected: + int64 addr_; // address of first sector in block + int64 size_; // size of block + int references_; // reference counter + bool initialized_; // flag indicating the block was written on disk + Pattern *pattern_; + pthread_mutex_t data_mutex_; + DISALLOW_COPY_AND_ASSIGN(BlockData); +}; + +// Disk Block table - store data from blocks to be write / read by +// a DiskThread +class DiskBlockTable { + public: + DiskBlockTable(); + virtual ~DiskBlockTable(); + + // Get Number of elements stored on table + int64 NumElems(); + // Clean all table data + void CleanTable(); + // Get a random block from the list. Only returns if a element + // is available (consider that other thread must have added them. + BlockData *GetRandomBlock(); + // Set all initial parameters. Assumes all existent data is + // invalid and, therefore, must be removed. + void SetParameters(int sector_size, int write_block_size, + int64 device_sectors, + int64 segment_size, + string device_name); + // Return a new block in a unused address. + BlockData *GetUnusedBlock(int64 segment); + // Remove block from structure (called by write threads) + int RemoveBlock(BlockData *block); + // Release block to be erased (called by random threads) + int ReleaseBlock(BlockData *block); + + protected: + + void InsertOnStructure(BlockData *block); + // Generate a random 64-bit integer (virtual so it could be + // override by the tests) + virtual int64 Random64(); + + struct StorageData { + BlockData *block; + int pos; + }; + + static const int kBlockRetry = 100; // Number of retries to allocate + // sectors. + + typedef map<int64, StorageData*> AddrToBlockMap; + typedef vector<int64> PosToAddrVector; + PosToAddrVector pos_to_addr_; + AddrToBlockMap addr_to_block_; + uint64 nelems_; + int sector_size_; // Sector size, in bytes + int write_block_size_; // Block size, in bytes + string device_name_; // Device name + int64 device_sectors_; // Number of sectors in device + int64 segment_size_; // Segment size, in bytes + pthread_mutex_t data_mutex_; + pthread_cond_t data_condition_; + pthread_mutex_t parameter_mutex_; + DISALLOW_COPY_AND_ASSIGN(DiskBlockTable); +}; + +#endif // STRESSAPPTEST_BLOCKS_H_ diff --git a/src/error_diag.cc b/src/error_diag.cc new file mode 100644 index 0000000..53f056f --- /dev/null +++ b/src/error_diag.cc @@ -0,0 +1,317 @@ +// Copyright 2008 Google Inc. All Rights Reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// error_diag.cc: Collects device errors for analysis to more accurately +// pin-point failed component. + +#include <set> +#include <list> +#include <map> + +// This file must work with autoconf on its public version, +// so these includes are correct. +#include "error_diag.h" +#include "sattypes.h" + + +// DeviceTree constructor. +DeviceTree::DeviceTree(string name) + : parent_(0), name_(name) { + pthread_mutex_init(&device_tree_mutex_, NULL); +} + +// DeviceTree destructor. +DeviceTree::~DeviceTree() { + // Deallocate subtree devices. + for (std::map<string, DeviceTree*>::iterator itr = subdevices_.begin(); + itr != subdevices_.end(); + ++itr) { + delete itr->second; + } + // Deallocate device errors. + for (std::list<ErrorInstance*>::iterator itr = errors_.begin(); + itr != errors_.end(); + ++itr) { + delete (*itr); + } + pthread_mutex_destroy(&device_tree_mutex_); +} + +// Atomically find named device in sub device tree. +// Returns 0 if not found +DeviceTree *DeviceTree::FindInSubTree(string name) { + DeviceTree *ret; + pthread_mutex_lock(&device_tree_mutex_); + ret = UnlockedFindInSubTree(name); + pthread_mutex_unlock(&device_tree_mutex_); + return ret; +} + +// Find named device in sub device tree (Non-atomic). +// Returns 0 if not found +DeviceTree *DeviceTree::UnlockedFindInSubTree(string name) { + std::map<string, DeviceTree*>::iterator itr = subdevices_.find(name); + if (itr != subdevices_.end()) { + return itr->second; + } else { + // Search sub-tree. + for (std::map<string, DeviceTree*>::iterator itr = subdevices_.begin(); + itr != subdevices_.end(); + ++itr) { + DeviceTree *result = itr->second->UnlockedFindInSubTree(name); + if (result != 0) + return result; + } + return 0; + } +} + +// Atomically add error instance to device. +void DeviceTree::AddErrorInstance(ErrorInstance *error_instance) { + pthread_mutex_lock(&device_tree_mutex_); + errors_.push_back(error_instance); + pthread_mutex_unlock(&device_tree_mutex_); +} + +// Find or add queried device as necessary. +DeviceTree *DeviceTree::FindOrAddDevice(string name) { + // Assume named device does not exist and try to insert the device anyway. + // No-op if named device already exists. + InsertSubDevice(name); + // Find and return sub device pointer. + return FindInSubTree(name); +} + +// Pretty prints device tree. +void DeviceTree::PrettyPrint(string spacer) { + for (std::map<string, DeviceTree*>::iterator itr = subdevices_.begin(); + itr != subdevices_.end(); + ++itr) { + printf("%s%s\n", spacer.c_str(), itr->first.c_str()); + itr->second->PrettyPrint(spacer+spacer); + } +} + +// Atomically add sub device. +// No-op if named device already exists. +void DeviceTree::InsertSubDevice(string name) { + pthread_mutex_lock(&device_tree_mutex_); + if (UnlockedFindInSubTree(name) != 0) { + pthread_mutex_unlock(&device_tree_mutex_); + return; + } + subdevices_[name] = new DeviceTree(name); + subdevices_[name]->parent_ = this; + pthread_mutex_unlock(&device_tree_mutex_); +} + + +// Returns true of any error associated with this device is fatal. +bool DeviceTree::KnownBad() { + pthread_mutex_lock(&device_tree_mutex_); + for (std::list<ErrorInstance*>::iterator itr = errors_.begin(); + itr != errors_.end(); + ++itr) { + if ((*itr)->severity_ == SAT_ERROR_FATAL) { + pthread_mutex_unlock(&device_tree_mutex_); + return true; + } + } + pthread_mutex_unlock(&device_tree_mutex_); + return false; +} + + +// ErrorDiag constructor. +ErrorDiag::ErrorDiag() { + os_ = 0; + system_tree_root_ = 0; +} + +// ErrorDiag destructor. +ErrorDiag::~ErrorDiag() { + if (system_tree_root_) + delete system_tree_root_; +} + +// Set platform specific handle and initialize device tree. +// Returns false on error. true otherwise. +bool ErrorDiag::set_os(OsLayer *os) { + os_ = os; + return(InitializeDeviceTree()); +} + +// Create and initialize system device tree. +// Returns false on error. true otherwise. +bool ErrorDiag::InitializeDeviceTree() { + system_tree_root_ = new DeviceTree("system_root"); + if (!system_tree_root_) + return false; + return true; +} + +// Logs info about a CECC. +// Returns -1 on error, 1 if diagnoser reports error externally; 0 otherwise. +int ErrorDiag::AddCeccError(string dimm_string) { + DeviceTree *dimm_device = system_tree_root_->FindOrAddDevice(dimm_string); + ECCErrorInstance *error = new ECCErrorInstance; + if (!error) + return -1; + error->severity_ = SAT_ERROR_CORRECTABLE; + dimm_device->AddErrorInstance(error); + return 0; +} + +// Logs info about a UECC. +// Returns -1 on error, 1 if diagnoser reports error externally; 0 otherwise. +int ErrorDiag::AddUeccError(string dimm_string) { + DeviceTree *dimm_device = system_tree_root_->FindOrAddDevice(dimm_string); + ECCErrorInstance *error = new ECCErrorInstance; + if (!error) + return -1; + error->severity_ = SAT_ERROR_FATAL; + dimm_device->AddErrorInstance(error); + return 0; +} + +// Logs info about a miscompare. +// Returns -1 on error, 1 if diagnoser reports error externally; 0 otherwise. +int ErrorDiag::AddMiscompareError(string dimm_string, uint64 addr, int count) { + DeviceTree *dimm_device = system_tree_root_->FindOrAddDevice(dimm_string); + MiscompareErrorInstance *error = new MiscompareErrorInstance; + if (!error) + return -1; + error->severity_ = SAT_ERROR_FATAL; + error->addr_ = addr; + dimm_device->AddErrorInstance(error); + os_->ErrorReport(dimm_string.c_str(), "miscompare", count); + return 1; +} + +// Utility Function to translate a virtual address to DIMM number. +// Returns -1 on error, 1 if diagnoser reports error externally; 0 otherwise. +string ErrorDiag::AddressToDimmString(OsLayer *os, void *addr, int offset) { + char dimm_string[256] = ""; + char *vbyteaddr = reinterpret_cast<char*>(addr) + offset; + uint64 paddr = os->VirtualToPhysical(vbyteaddr); + os->FindDimm(paddr, dimm_string, sizeof(dimm_string)); + return string(dimm_string); +} + +// Info about a miscompare from a drive. +// Returns -1 on error, 1 if diagnoser reports error externally; 0 otherwise. +int ErrorDiag::AddHDDMiscompareError(string devicename, int block, int offset, + void *src_addr, void *dst_addr) { + bool mask_hdd_error = false; + + HDDMiscompareErrorInstance *error = new HDDMiscompareErrorInstance; + if (!error) + return -1; + + error->addr_ = reinterpret_cast<uint64>(src_addr); + error->addr2_ = reinterpret_cast<uint64>(dst_addr); + error->offset_ = offset; + error->block_ = block; + + string src_dimm = AddressToDimmString(os_, src_addr, offset); + string dst_dimm = AddressToDimmString(os_, dst_addr, offset); + + // DIMM name look up success + if (src_dimm.compare("DIMM Unknown")) { + // Add src DIMM as possible miscompare cause. + DeviceTree *src_dimm_dev = system_tree_root_->FindOrAddDevice(src_dimm); + error->causes_.insert(src_dimm_dev); + if (src_dimm_dev->KnownBad()) { + mask_hdd_error = true; + logprintf(5, "Log: supressed %s miscompare report: " + "known bad source: %s\n", devicename.c_str(), src_dimm.c_str()); + } + } + if (dst_dimm.compare("DIMM Unknown")) { + // Add dst DIMM as possible miscompare cause. + DeviceTree *dst_dimm_dev = system_tree_root_->FindOrAddDevice(dst_dimm); + error->causes_.insert(dst_dimm_dev); + if (dst_dimm_dev->KnownBad()) { + mask_hdd_error = true; + logprintf(5, "Log: supressed %s miscompare report: " + "known bad destination: %s\n", devicename.c_str(), + dst_dimm.c_str()); + } + } + + DeviceTree *hdd_dev = system_tree_root_->FindOrAddDevice(devicename); + hdd_dev->AddErrorInstance(error); + + // HDD error was not masked by bad DIMMs: report bad HDD. + if (!mask_hdd_error) { + os_->ErrorReport(devicename.c_str(), "miscompare", 1); + error->severity_ = SAT_ERROR_FATAL; + return 1; + } + return 0; +} + +// Info about a sector tag miscompare from a drive. +// Returns -1 on error, 1 if diagnoser reports error externally; 0 otherwise. +int ErrorDiag::AddHDDSectorTagError(string devicename, int block, int offset, + int sector, void *src_addr, + void *dst_addr) { + bool mask_hdd_error = false; + + HDDSectorTagErrorInstance *error = new HDDSectorTagErrorInstance; + if (!error) + return -1; + + error->addr_ = reinterpret_cast<uint64>(src_addr); + error->addr2_ = reinterpret_cast<uint64>(dst_addr); + error->sector_ = sector; + error->block_ = block; + + string src_dimm = AddressToDimmString(os_, src_addr, offset); + string dst_dimm = AddressToDimmString(os_, dst_addr, offset); + + // DIMM name look up success + if (src_dimm.compare("DIMM Unknown")) { + // Add src DIMM as possible miscompare cause. + DeviceTree *src_dimm_dev = system_tree_root_->FindOrAddDevice(src_dimm); + error->causes_.insert(src_dimm_dev); + if (src_dimm_dev->KnownBad()) { + mask_hdd_error = true; + logprintf(5, "Log: supressed %s sector tag error report: " + "known bad source: %s\n", devicename.c_str(), src_dimm.c_str()); + } + } + if (dst_dimm.compare("DIMM Unknown")) { + // Add dst DIMM as possible miscompare cause. + DeviceTree *dst_dimm_dev = system_tree_root_->FindOrAddDevice(dst_dimm); + error->causes_.insert(dst_dimm_dev); + if (dst_dimm_dev->KnownBad()) { + mask_hdd_error = true; + logprintf(5, "Log: supressed %s sector tag error report: " + "known bad destination: %s\n", devicename.c_str(), + dst_dimm.c_str()); + } + } + + DeviceTree *hdd_dev = system_tree_root_->FindOrAddDevice(devicename); + hdd_dev->AddErrorInstance(error); + + // HDD error was not masked by bad DIMMs: report bad HDD. + if (!mask_hdd_error) { + os_->ErrorReport(devicename.c_str(), "sector", 1); + error->severity_ = SAT_ERROR_FATAL; + return 1; + } + return 0; +} diff --git a/src/error_diag.h b/src/error_diag.h new file mode 100644 index 0000000..7faedb8 --- /dev/null +++ b/src/error_diag.h @@ -0,0 +1,167 @@ +// Copyright 2008 Google Inc. All Rights Reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// error_diag.h: Ambiguous error diagnosis class + +#ifndef STRESSAPPTEST_ERROR_DIAG_H_ +#define STRESSAPPTEST_ERROR_DIAG_H_ + +#include <pthread.h> +#include <list> +#include <map> +#include <set> +#include <string> + +// This file must work with autoconf on its public version, +// so these includes are correct. +#include "sattypes.h" +#include "os.h" + +class ErrorInstance; + +// This describes the components of the system. +class DeviceTree { + public: + explicit DeviceTree(string name); + ~DeviceTree(); + + // Atomically find arbitrary device in subtree. + DeviceTree *FindInSubTree(string name); + // Find or add named device. + DeviceTree *FindOrAddDevice(string name); + // Atomically add sub device. + void InsertSubDevice(string name); + // Returns parent device. + DeviceTree *GetParent() { return parent_; } + // Pretty prints device tree. + void PrettyPrint(string spacer = " "); + // Atomically add error instance to device. + void AddErrorInstance(ErrorInstance *error_instance); + // Returns true of device is known to be bad. + bool KnownBad(); + // Returns number of direct sub devices. + int NumDirectSubDevices() { return subdevices_.size(); } + + private: + // Unlocked version of FindInSubTree. + DeviceTree *UnlockedFindInSubTree(string name); + + std::map<string, DeviceTree*> subdevices_; // Map of sub-devices. + std::list<ErrorInstance*> errors_; // Log of errors. + DeviceTree *parent_; // Pointer to parent device. + string name_; // Device name. + pthread_mutex_t device_tree_mutex_; // Mutex protecting device tree. +}; + + +// enum type for collected errors. +enum SATErrorType { + SAT_ERROR_NONE = 0, + SAT_ERROR_ECC, + SAT_ERROR_MISCOMPARE, + SAT_ERROR_SECTOR_TAG, +}; + +// enum type for error severity. +enum SATErrorSeverity { + SAT_ERROR_CORRECTABLE = 0, + SAT_ERROR_FATAL, +}; + +// This describes an error and it's likely causes. +class ErrorInstance { + public: + ErrorInstance(): type_(SAT_ERROR_NONE), severity_(SAT_ERROR_CORRECTABLE) {} + + SATErrorType type_; // Type of error: ECC, miscompare, sector. + SATErrorSeverity severity_; // Correctable, or fatal. + std::set<DeviceTree*> causes_; // Devices that can cause this type of error. +}; + +// This describes ECC errors. +class ECCErrorInstance: public ErrorInstance { + public: + ECCErrorInstance() { type_ = SAT_ERROR_ECC; } + + uint64 addr_; // Address where error occured. +}; + +// This describes miscompare errors. +class MiscompareErrorInstance: public ErrorInstance { + public: + MiscompareErrorInstance() { type_ = SAT_ERROR_MISCOMPARE; } + + uint64 addr_; // Address where miscompare occured. +}; + +// This describes HDD miscompare errors. +class HDDMiscompareErrorInstance: public MiscompareErrorInstance { + public: + uint64 addr2_; // addr_ and addr2_ are src and dst memory addr. + int offset_; // offset. + int block_; // error block. +}; + +// This describes HDD miscompare errors. +class HDDSectorTagErrorInstance: public ErrorInstance { + public: + HDDSectorTagErrorInstance() { type_ = SAT_ERROR_SECTOR_TAG; } + + uint64 addr_; + uint64 addr2_; // addr_ and addr2_ are src and dst memory addr. + int sector_; // error sector. + int block_; // error block. +}; + +// Generic error storage and sorting class. +class ErrorDiag { + public: + ErrorDiag(); + virtual ~ErrorDiag(); + + // Add info about a CECC. + virtual int AddCeccError(string dimm_string); + + // Add info about a UECC. + virtual int AddUeccError(string dimm_string); + + // Add info about a miscompare. + virtual int AddMiscompareError(string dimm_string, uint64 addr, int count); + + // Add info about a miscompare from a drive. + virtual int AddHDDMiscompareError(string devicename, int block, int offset, + void *src_addr, void *dst_addr); + + // Add info about a sector tag miscompare from a drive. + virtual int AddHDDSectorTagError(string devicename, int block, int offset, + int sector, void *src_addr, void *dst_addr); + + // Set platform specific handle and initialize device tree. + bool set_os(OsLayer *os); + + protected: + // Create and initialize system device tree. + virtual bool InitializeDeviceTree(); + + // Utility Function to translate a virtual address to DIMM number. + string AddressToDimmString(OsLayer *os, void *addr, int offset); + + DeviceTree *system_tree_root_; // System device tree. + OsLayer *os_; // Platform handle. + + private: + DISALLOW_COPY_AND_ASSIGN(ErrorDiag); +}; + +#endif // STRESSAPPTEST_ERROR_DIAG_H_ diff --git a/src/finelock_queue.cc b/src/finelock_queue.cc new file mode 100644 index 0000000..8d914b8 --- /dev/null +++ b/src/finelock_queue.cc @@ -0,0 +1,448 @@ +// Copyright 2007 Google Inc. All Rights Reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This is an interface to a simple thread safe container with fine-grain locks, +// used to hold data blocks and patterns. + +// This file must work with autoconf on its public version, +// so these includes are correct. +#include "finelock_queue.h" +#include "os.h" + +// Page entry queue implementation follows. +// Push and Get functions are analogous to lock and unlock operations on a given +// page entry, while preserving queue semantics. +// +// The actual 'queue' implementation is actually just an array. The entries are +// never shuffled or re-ordered like that of a real queue. Instead, Get +// functions return a random page entry of a given type and lock that particular +// page entry until it is unlocked by corresponding Put functions. +// +// In this implementation, a free page is those page entries where pattern is +// null (pe->pattern == 0) + + +// Constructor: Allocates memory and initialize locks. +FineLockPEQueue::FineLockPEQueue( + uint64 queuesize, int64 pagesize) { + q_size_ = queuesize; + pages_ = new struct page_entry[q_size_]; + pagelocks_ = new pthread_mutex_t[q_size_]; + page_size_ = pagesize; + + // What metric should we measure this run. + queue_metric_ = kTouch; + + { // Init all the page locks. + for (uint64 i = 0; i < q_size_; i++) { + pthread_mutex_init(&(pagelocks_[i]), NULL); + // Pages start out owned (locked) by Sat::InitializePages. + // A locked state indicates that the page state is unknown, + // and the lock should not be aquired. As InitializePages creates + // the page records, they will be inserted and unlocked, at which point + // they are ready to be aquired and filled by worker threads. + sat_assert(pthread_mutex_lock(&(pagelocks_[i])) == 0); + } + } + + { // Init the random number generator. + for (int i = 0; i < 4; i++) { + rand_seed_[i] = i + 0xbeef; + pthread_mutex_init(&(randlocks_[i]), NULL); + } + } + + // Try to make a linear congruential generator with our queue size. + // We need this to deterministically search all the queue (being able to find + // a single available element is a design requirement), but we don't want to + // cause any page to be more likley chosen than another. The previous + // sequential retry heavily biased pages at the beginning of a bunch, or + // isolated pages surrounded by unqualified ones. + int64 length = queuesize; + int64 modlength = length; + int64 a; + int64 c; + + if (length < 3) { + a = 1; + c = 1; + } else { + // Search for a nontrivial generator. + a = getA(length) % length; + // If this queue size doesn't have a nontrivial generator (where the + // multiplier is greater then one) we'll check increasing queue sizes, + // and discard out of bounds results. + while (a == 1) { + modlength++; + a = getA(modlength) % modlength; + } + c = getC(modlength); + } + + // This is our final generator. + a_ = a; + c_ = c; + modlength_ = modlength; +} + +// Part of building a linear congruential generator n1 = (a * n0 + c) % m +// Get 'a', where a - 1 must be divisible by all prime +// factors of 'm', our queue size. +int64 FineLockPEQueue::getA(int64 m) { + int64 remaining = m; + int64 a = 1; + if ((((remaining / 4) * 4) == remaining)) { + a = 2; + } + // For each number, let's see if it's divisible, + // then divide it out. + for (int64 i = 2; i <= m; i++) { + if (((remaining / i) * i) == remaining) { + remaining /= i; + // Keep dividing it out until there's no more. + while (((remaining / i) * i) == remaining) + remaining /= i; + a *= i; + } + } + + // Return 'a' as specified. + return (a + 1) % m; +} + + +// Part of building a linear congruential generator n1 = (a * n0 + c) % m +// Get a prime number approx 3/4 the size of our queue. +int64 FineLockPEQueue::getC(int64 m) { + // Start here at 3/4. + int64 start = (3 * m) / 4 + 1; + int64 possible_prime = start; + // Keep trying until we find a prime. + for (possible_prime = start; possible_prime > 1; possible_prime--) { + bool failed = false; + for (int64 i = 2; i < possible_prime; i++) { + if (((possible_prime / i) * i) == possible_prime) { + failed = true; + break; + } + } + if (!failed) { + return possible_prime; + } + } + // One is prime enough. + return 1; +} + +// Destructor: Clean-up allocated memory and destroy pthread locks. +FineLockPEQueue::~FineLockPEQueue() { + uint64 i; + for (i = 0; i < q_size_; i++) + pthread_mutex_destroy(&(pagelocks_[i])); + delete[] pagelocks_; + delete[] pages_; + for (i = 0; i < 4; i++) { + pthread_mutex_destroy(&(randlocks_[i])); + } +} + + +bool FineLockPEQueue::QueueAnalysis() { + const char *measurement = "Error"; + uint64 buckets[32]; + + if (queue_metric_ == kTries) + measurement = "Failed retrievals"; + else if (queue_metric_ == kTouch) + measurement = "Reads per page"; + + // Buckets for each log2 access counts. + for (int b = 0; b < 32; b++) { + buckets[b] = 0; + } + + // Bucketize the page counts by highest bit set. + for (uint64 i = 0; i < q_size_; i++) { + uint32 readcount = pages_[i].touch; + int b = 0; + for (b = 0; b < 31; b++) { + if (readcount < (1u << b)) + break; + } + + buckets[b]++; + } + + logprintf(12, "Log: %s histogram\n", measurement); + for (int b = 0; b < 32; b++) { + if (buckets[b]) + logprintf(12, "Log: %12d - %12d: %12d\n", + ((1 << b) >> 1), 1 << b, buckets[b]); + } + + return true; +} + +namespace { +// Callback mechanism for exporting last action. +OsLayer *g_os; +FineLockPEQueue *g_fpqueue = 0; + +// Global callback to hook into Os object. +bool err_log_callback(uint64 paddr, string *buf) { + if (g_fpqueue) { + return g_fpqueue->ErrorLogCallback(paddr, buf); + } + return false; +} +} + +// Setup global state for exporting callback. +void FineLockPEQueue::set_os(OsLayer *os) { + g_os = os; + g_fpqueue = this; +} + +OsLayer::ErrCallback FineLockPEQueue::get_err_log_callback() { + return err_log_callback; +} + +// This call is used to export last transaction info on a particular physical +// address. +bool FineLockPEQueue::ErrorLogCallback(uint64 paddr, string *message) { + struct page_entry pe; + OsLayer *os = g_os; + sat_assert(g_os); + char buf[256]; + + // Find the page of this paddr. + int gotpage = GetPageFromPhysical(paddr, &pe); + if (!gotpage) { + return false; + } + + // Find offset into the page. + uint64 addr_diff = paddr - pe.paddr; + + // Find vaddr of this paddr. Make sure it matches, + // as sometimes virtual memory is not contiguous. + char *vaddr = + reinterpret_cast<char*>(os->PrepareTestMem(pe.offset, page_size_)); + uint64 new_paddr = os->VirtualToPhysical(vaddr + addr_diff); + os->ReleaseTestMem(vaddr, pe.offset, page_size_); + + // Is the physical address at this page offset the same as + // the physical address we were given? + if (new_paddr != paddr) { + return false; + } + + // Print all the info associated with this page. + message->assign(" (Last Transaction:"); + + if (pe.lastpattern) { + int offset = addr_diff / 8; + datacast_t data; + + data.l32.l = pe.lastpattern->pattern(offset << 1); + data.l32.h = pe.lastpattern->pattern((offset << 1) + 1); + + snprintf(buf, sizeof(buf), " %s data=%#016llx", + pe.lastpattern->name(), data.l64); + message->append(buf); + } + snprintf(buf, sizeof(buf), " tsc=%#llx)", pe.ts); + message->append(buf); + return true; +} + +bool FineLockPEQueue::GetPageFromPhysical(uint64 paddr, + struct page_entry *pe) { + // Traverse through array until finding a page + // that contains the address we want.. + for (uint64 i = 0; i < q_size_; i++) { + uint64 page_addr = pages_[i].paddr; + // This assumes linear vaddr. + if ((page_addr <= paddr) && (page_addr + page_size_ > paddr)) { + *pe = pages_[i]; + return true; + } + } + return false; +} + + +// Get a random number from the slot we locked. +uint64 FineLockPEQueue::GetRandom64FromSlot(int slot) { + // 64 bit LCG numbers suggested on the internets by + // http://nuclear.llnl.gov/CNP/rng/rngman/node4.html and others. + uint64 result = 2862933555777941757ULL * rand_seed_[slot] + 3037000493ULL; + rand_seed_[slot] = result; + return result; +} + +// Get a random number, we have 4 generators to choose from so hopefully we +// won't be blocking on this. +uint64 FineLockPEQueue::GetRandom64() { + // Try each available slot. + for (int i = 0; i < 4; i++) { + if (pthread_mutex_trylock(&(randlocks_[i])) == 0) { + uint64 result = GetRandom64FromSlot(i); + pthread_mutex_unlock(&(randlocks_[i])); + return result; + } + } + // Forget it, just wait. + int i = 0; + if (pthread_mutex_lock(&(randlocks_[i])) == 0) { + uint64 result = GetRandom64FromSlot(i); + pthread_mutex_unlock(&(randlocks_[i])); + return result; + } + + logprintf(0, "Process Error: Could not acquire random lock.\n"); + sat_assert(0); + return 0; +} + + +// Helper function to get a random page entry with given predicate, +// ie, page_is_valid() or page_is_empty() as defined in finelock_queue.h. +// +// Setting tag to a value other than kDontCareTag (-1) +// indicates that we need a tag match, otherwise any tag will do. +// +// Returns true on success, false on failure. +bool FineLockPEQueue::GetRandomWithPredicateTag(struct page_entry *pe, + bool (*pred_func)(struct page_entry*), + int32 tag) { + if (!pe || !q_size_) + return false; + + // Randomly index into page entry array. + uint64 first_try = GetRandom64() % q_size_; + uint64 next_try = 1; + + // Traverse through array until finding a page meeting given predicate. + for (uint64 i = 0; i < q_size_; i++) { + uint64 index = (next_try + first_try) % q_size_; + // Go through the loop linear conguentially. We are offsetting by + // 'first_try' so this path will be a different sequence for every + // initioal value chosen. + next_try = (a_ * next_try + c_) % (modlength_); + while (next_try >= q_size_) { + // If we have chosen a modlength greater than the queue size, + // discard out of bounds results. + next_try = (a_ * next_try + c_) % (modlength_); + } + + // If page does not meet predicate, don't trylock (expensive). + if (!(pred_func)(&pages_[index])) + continue; + + // If page does not meet tag predicate, don't trylock (expensive). + if ((tag != kDontCareTag) && !(pages_[index].tag & tag)) + continue; + + if (pthread_mutex_trylock(&(pagelocks_[index])) == 0) { + // If page property (valid/empty) changes before successfully locking, + // release page and move on. + if (!(pred_func)(&pages_[index])) { + pthread_mutex_unlock(&(pagelocks_[index])); + continue; + } else { + // A page entry with given predicate is locked, returns success. + *pe = pages_[index]; + + // Add metrics as necessary. + if (pred_func == page_is_valid) { + // Measure time to fetch valid page. + if (queue_metric_ == kTries) + pe->touch = i; + // Measure number of times each page is read. + if (queue_metric_ == kTouch) + pe->touch++; + } + + return true; + } + } + } + + return false; +} + +// Without tag hint. +bool FineLockPEQueue::GetRandomWithPredicate(struct page_entry *pe, + bool (*pred_func)(struct page_entry*)) { + return GetRandomWithPredicateTag(pe, pred_func, kDontCareTag); +} + + +// GetValid() randomly finds a valid page, locks it and returns page entry by +// pointer. +// +// Returns true on success, false on failure. +bool FineLockPEQueue::GetValid(struct page_entry *pe) { + return GetRandomWithPredicate(pe, page_is_valid); +} + +bool FineLockPEQueue::GetValid(struct page_entry *pe, int32 mask) { + return GetRandomWithPredicateTag(pe, page_is_valid, mask); +} + +// GetEmpty() randomly finds an empty page, locks it and returns page entry by +// pointer. +// +// Returns true on success, false on failure. +bool FineLockPEQueue::GetEmpty(struct page_entry *pe, int32 mask) { + return GetRandomWithPredicateTag(pe, page_is_empty, mask); +} +bool FineLockPEQueue::GetEmpty(struct page_entry *pe) { + return GetRandomWithPredicate(pe, page_is_empty); +} + +// PutEmpty puts an empty page back into the queue, making it available by +// releasing the per-page-entry lock. +// +// Returns true on success, false on failure. +bool FineLockPEQueue::PutEmpty(struct page_entry *pe) { + if (!pe || !q_size_) + return false; + + int64 index = pe->offset / page_size_; + if (!valid_index(index)) + return false; + + pages_[index] = *pe; + // Enforce that page entry is indeed empty. + pages_[index].pattern = 0; + return (pthread_mutex_unlock(&(pagelocks_[index])) == 0); +} + +// PutValid puts a valid page back into the queue, making it available by +// releasing the per-page-entry lock. +// +// Returns true on success, false on failure. +bool FineLockPEQueue::PutValid(struct page_entry *pe) { + if (!pe || !page_is_valid(pe) || !q_size_) + return false; + + int64 index = pe->offset / page_size_; + if (!valid_index(index)) + return false; + + pages_[index] = *pe; + return (pthread_mutex_unlock(&(pagelocks_[index])) == 0); +} diff --git a/src/finelock_queue.h b/src/finelock_queue.h new file mode 100644 index 0000000..2de5a46 --- /dev/null +++ b/src/finelock_queue.h @@ -0,0 +1,118 @@ +// Copyright 2007 Google Inc. All Rights Reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This page entry queue implementation with fine grain locks aim to ease +// lock contention over previous queue implementation (with one lock protecting +// the entire queue). + +#ifndef STRESSAPPTEST_FINELOCK_QUEUE_H_ +#define STRESSAPPTEST_FINELOCK_QUEUE_H_ + +#include <string> + +// This file must work with autoconf on its public version, +// so these includes are correct. +#include "sattypes.h" +#include "pattern.h" +#include "queue.h" // Using page_entry struct. +#include "os.h" + +// This is a threadsafe randomized queue of pages with per-page entry lock +// for worker threads to use. +class FineLockPEQueue { + public: + FineLockPEQueue(uint64 queuesize, int64 pagesize); + ~FineLockPEQueue(); + + // Put and get functions for page entries. + bool GetEmpty(struct page_entry *pe); + bool GetValid(struct page_entry *pe); + bool PutEmpty(struct page_entry *pe); + bool PutValid(struct page_entry *pe); + + // Put and get functions for page entries, selecting on tags. + bool GetEmpty(struct page_entry *pe, int32 tag); + bool GetValid(struct page_entry *pe, int32 tag); + + bool QueueAnalysis(); + bool GetPageFromPhysical(uint64 paddr, struct page_entry *pe); + void set_os(OsLayer *os); + OsLayer::ErrCallback get_err_log_callback(); + bool ErrorLogCallback(uint64 paddr, string *buf); + + private: + // Not that much blocking random number generator. + uint64 GetRandom64(); + uint64 GetRandom64FromSlot(int slot); + + // Helper function to check index range, returns true if index is valid. + bool valid_index(int64 index) { + return index >= 0 && static_cast<uint64>(index) < q_size_; + } + + // Returns true if page entry is valid, false otherwise. + static bool page_is_valid(struct page_entry *pe) { + return pe->pattern != NULL; + } + // Returns true if page entry is empty, false otherwise. + static bool page_is_empty(struct page_entry *pe) { + return pe->pattern == NULL; + } + + // Helper function to get a random page entry with given predicate, + // ie, page_is_valid() or page_is_empty() as defined above. + bool GetRandomWithPredicate(struct page_entry *pe, + bool (*pred_func)(struct page_entry*)); + + // Helper function to get a random page entry with given predicate, + // ie, page_is_valid() or page_is_empty() as defined above. + bool GetRandomWithPredicateTag(struct page_entry *pe, + bool (*pred_func)(struct page_entry*), + int32 tag); + + // Used to make a linear congruential path through the queue. + int64 getA(int64 m); + int64 getC(int64 m); + + pthread_mutex_t *pagelocks_; // Per-page-entry locks. + struct page_entry *pages_; // Where page entries are held. + uint64 q_size_; // Size of the queue. + int64 page_size_; // For calculating array index from offset. + + enum { + kTries = 1, // Measure the number of attempts in the queue + // before getting a matching page. + kTouch = 2 } // Measure the number of touches on each page. + queue_metric_; // What to measure in the 'tries' field. + + // Progress pseudorandomly through the queue. It's required that we can find + // every value in the list, but progressing through the same order each time + // causes bunching of pages, leading to long seach times for the correct + // type of pages. + int64 a_; // 'a' multiplicative value for progressing + // linear congruentially through the list. + int64 c_; // 'c' additive value for prgressing randomly + // through the list. + int64 modlength_; // 'm' mod value for linear congruential + // generator. Used when q_size_ doesn't + // generate a good progression through the + // list. + + uint64 rand_seed_[4]; // Random number state for 4 generators. + pthread_mutex_t randlocks_[4]; // Per-random-generator locks. + + DISALLOW_COPY_AND_ASSIGN(FineLockPEQueue); +}; + +#endif // STRESSAPPTEST_FINELOCK_QUEUE_H_ diff --git a/src/logger.cc b/src/logger.cc new file mode 100644 index 0000000..e4ecb03 --- /dev/null +++ b/src/logger.cc @@ -0,0 +1,152 @@ +// Copyright 2009 Google Inc. All Rights Reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "logger.h" + +#include <pthread.h> +#include <stdarg.h> +#include <stdio.h> +#include <unistd.h> + +#include <string> +#include <vector> + +// This file must work with autoconf on its public version, +// so these includes are correct. +#include "sattypes.h" + + +Logger *Logger::GlobalLogger() { + static Logger logger; + return &logger; +} + +void Logger::VLogF(int priority, const char *format, va_list args) { + if (priority > verbosity_) { + return; + } + char buffer[4096]; + int length = vsnprintf(buffer, sizeof buffer, format, args); + if (static_cast<size_t>(length) >= sizeof buffer) { + length = sizeof buffer; + buffer[sizeof buffer - 1] = '\n'; + } + QueueLogLine(new string(buffer, length)); +} + +void Logger::StartThread() { + LOGGER_ASSERT(!thread_running_); + thread_running_ = true; + LOGGER_ASSERT(0 == pthread_create(&thread_, NULL, &StartRoutine, this)); +} + +void Logger::StopThread() { + LOGGER_ASSERT(thread_running_); + thread_running_ = false; + LOGGER_ASSERT(0 == pthread_mutex_lock(&queued_lines_mutex_)); + bool need_cond_signal = queued_lines_.empty(); + queued_lines_.push_back(NULL); + LOGGER_ASSERT(0 == pthread_mutex_unlock(&queued_lines_mutex_)); + if (need_cond_signal) { + LOGGER_ASSERT(0 == pthread_cond_signal(&queued_lines_cond_)); + } + LOGGER_ASSERT(0 == pthread_join(thread_, NULL)); +} + +Logger::Logger() : verbosity_(20), log_fd_(-1), thread_running_(false) { + LOGGER_ASSERT(0 == pthread_mutex_init(&queued_lines_mutex_, NULL)); + LOGGER_ASSERT(0 == pthread_cond_init(&queued_lines_cond_, NULL)); + LOGGER_ASSERT(0 == pthread_cond_init(&full_queue_cond_, NULL)); +} + +Logger::~Logger() { + LOGGER_ASSERT(0 == pthread_mutex_destroy(&queued_lines_mutex_)); + LOGGER_ASSERT(0 == pthread_cond_destroy(&queued_lines_cond_)); + LOGGER_ASSERT(0 == pthread_cond_destroy(&full_queue_cond_)); +} + +void Logger::QueueLogLine(string *line) { + LOGGER_ASSERT(line != NULL); + LOGGER_ASSERT(0 == pthread_mutex_lock(&queued_lines_mutex_)); + if (thread_running_) { + if (queued_lines_.size() >= kMaxQueueSize) { + LOGGER_ASSERT(0 == pthread_cond_wait(&full_queue_cond_, + &queued_lines_mutex_)); + } + if (queued_lines_.empty()) { + LOGGER_ASSERT(0 == pthread_cond_signal(&queued_lines_cond_)); + } + queued_lines_.push_back(line); + } else { + WriteAndDeleteLogLine(line); + } + LOGGER_ASSERT(0 == pthread_mutex_unlock(&queued_lines_mutex_)); +} + +namespace { +void WriteToFile(const string& line, int fd) { + LOGGER_ASSERT(write(fd, line.data(), line.size()) == + static_cast<ssize_t>(line.size())); +} +} + +void Logger::WriteAndDeleteLogLine(string *line) { + LOGGER_ASSERT(line != NULL); + if (log_fd_ >= 0) { + WriteToFile(*line, log_fd_); + } + WriteToFile(*line, 1); + delete line; +} + +void *Logger::StartRoutine(void *ptr) { + Logger *self = static_cast<Logger*>(ptr); + self->ThreadMain(); + return NULL; +} + +void Logger::ThreadMain() { + vector<string*> local_queue; + LOGGER_ASSERT(0 == pthread_mutex_lock(&queued_lines_mutex_)); + + for (;;) { + if (queued_lines_.empty()) { + LOGGER_ASSERT(0 == pthread_cond_wait(&queued_lines_cond_, + &queued_lines_mutex_)); + continue; + } + + // We move the log lines into a local queue so we can release the lock + // while writing them to disk, preventing other threads from blocking on + // our writes. + local_queue.swap(queued_lines_); + if (local_queue.size() >= kMaxQueueSize) { + LOGGER_ASSERT(0 == pthread_cond_broadcast(&full_queue_cond_)); + } + + // Unlock while we process our local queue. + LOGGER_ASSERT(0 == pthread_mutex_unlock(&queued_lines_mutex_)); + for (vector<string*>::const_iterator it = local_queue.begin(); + it != local_queue.end(); ++it) { + if (*it == NULL) { + // NULL is guaranteed to be at the end. + return; + } + WriteAndDeleteLogLine(*it); + } + local_queue.clear(); + // We must hold the lock at the start of each iteration of this for loop. + LOGGER_ASSERT(0 == pthread_mutex_lock(&queued_lines_mutex_)); + } +} diff --git a/src/logger.h b/src/logger.h new file mode 100644 index 0000000..1d70107 --- /dev/null +++ b/src/logger.h @@ -0,0 +1,142 @@ +// Copyright 2009 Google Inc. All Rights Reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STRESSAPPTEST_LOGGER_H_ +#define STRESSAPPTEST_LOGGER_H_ + +#include <pthread.h> +#include <stdarg.h> + +#include <string> +#include <vector> + +// This file must work with autoconf on its public version, +// so these includes are correct. +#include "sattypes.h" + +// Attempts to log additional lines will block when the queue reaches this size. +// Due to how the logging thread works, up to twice this many log lines may be +// outstanding at any point. +static const size_t kMaxQueueSize = 250; + + +// This is only for use by the Logger class, do not use it elsewhere! +// +// All Logger assertions should use this macro instead of sat_assert(). +// +// This is like sat_assert() from sattypes.h, but whereas sat_assert() tries to +// log the assertion after printing it to stderr, this only prints it to stderr. +// Logging from within the wrong part of the logger would trigger a deadlock, +// and even in places where it wouldn't there's a very good chance that the +// logger is in no condition to handle new log lines. +#define LOGGER_ASSERT(x) \ +{\ + if (!(x)) {\ + fprintf(stderr, "Assertion failed at %s:%d\n", __FILE__, __LINE__);\ + exit(1);\ + }\ +} + + +// This class handles logging in SAT. It is a singleton accessed via +// GlobalLogger(). +// +// By default log lines are written in the calling thread. Call StartThread() +// to launch a dedicated thread for the writes. +class Logger { + public: + // Returns a pointer to the single global Logger instance. Will not return + // NULL. + static Logger *GlobalLogger(); + + // Lines with a priority numerically greater than this will not be logged. + // May not be called while multiple threads are running. + void SetVerbosity(int verbosity) { + verbosity_ = verbosity; + } + + // Sets a file to log to, in addition to stdout. May not be called while + // multiple threads are running. + // + // Args: + // log_fd: The file descriptor to write to. Will not be closed by this + // object. + void SetLogFd(int log_fd) { + LOGGER_ASSERT(log_fd >= 0); + log_fd_ = log_fd; + } + + // Set output to be written to stdout only. This is the default mode. May + // not be called while multiple threads are running. + void SetStdoutOnly() { + log_fd_ = -1; + } + + // Logs a line, with a vprintf(3)-like interface. This will block on writing + // the line to stdout/disk iff the dedicated logging thread is not running. + // This will block on adding the line to the queue if doing so would exceed + // kMaxQueueSize. + // + // Args: + // priority: If this is numerically greater than the verbosity, the line + // will not be logged. + // format: see vprintf(3) + // args: see vprintf(3) + void VLogF(int priority, const char *format, va_list args); + + // Starts the dedicated logging thread. May not be called while multiple + // threads are already running. + void StartThread(); + + // Stops the dedicated logging thread. May only be called when the logging + // thread is the only other thread running. Any queued lines will be logged + // before this returns. Waits for the thread to finish before returning. + void StopThread(); + + private: + Logger(); + + ~Logger(); + + // Args: + // line: Must be non-NULL. This function takes ownership of it. + void QueueLogLine(string *line); + + // Args: + // line: Must be non-NULL. This function takes ownership of it. + void WriteAndDeleteLogLine(string *line); + + // Callback for pthread_create(3). + static void *StartRoutine(void *ptr); + + // Processes the log queue. + void ThreadMain(); + + pthread_t thread_; + int verbosity_; + int log_fd_; + bool thread_running_; + vector<string*> queued_lines_; + // This doubles as a mutex for log_fd_ when the logging thread is not running. + pthread_mutex_t queued_lines_mutex_; + // Lets the logging thread know that the queue is no longer empty. + pthread_cond_t queued_lines_cond_; + // Lets the threads blocked on the queue having reached kMaxQueueSize know + // that the queue has been emptied. + pthread_cond_t full_queue_cond_; + + DISALLOW_COPY_AND_ASSIGN(Logger); +}; + +#endif // STRESSAPPTEST_LOGGER_H_ diff --git a/src/main.cc b/src/main.cc new file mode 100644 index 0000000..04cd536 --- /dev/null +++ b/src/main.cc @@ -0,0 +1,56 @@ +// Copyright 2006 Google Inc. All Rights Reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// sat.cc : a stress test for stressful testing + +#include "sattypes.h" +#include "sat.h" + +int main(int argc, char **argv) { + Sat *sat = SatFactory(); + if (sat == NULL) { + logprintf(0, "Process Error: failed to allocate Sat object\n"); + return 255; + } + + if (!sat->ParseArgs(argc, argv)) { + logprintf(0, "Process Error: Sat::ParseArgs() failed\n"); + sat->bad_status(); + } else if (!sat->Initialize()) { + logprintf(0, "Process Error: Sat::Initialize() failed\n"); + sat->bad_status(); + } else if (!sat->Run()) { + logprintf(0, "Process Error: Sat::Run() failed\n"); + sat->bad_status(); + } + sat->PrintResults(); + if (!sat->Cleanup()) { + logprintf(0, "Process Error: Sat::Cleanup() failed\n"); + sat->bad_status(); + } + + int retval; + if (sat->status() != 0) { + logprintf(0, "Process Error: Fatal issue encountered. See above logs for " + "details.\n"); + retval = 1; + } else if (sat->errors() != 0) { + retval = 1; + } else { + retval = 0; + } + + delete sat; + return retval; +} diff --git a/src/os.cc b/src/os.cc new file mode 100644 index 0000000..1340d6b --- /dev/null +++ b/src/os.cc @@ -0,0 +1,849 @@ +// Copyright 2006 Google Inc. All Rights Reserved. +// Author: nsanders, menderico + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// os.cc : os and machine specific implementation +// This file includes an abstracted interface +// for linux-distro specific and HW specific +// interfaces. + +#include "os.h" + +#include <errno.h> +#include <fcntl.h> +#include <linux/types.h> +#include <malloc.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mman.h> +#include <sys/ioctl.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/ipc.h> +#include <sys/shm.h> +#include <unistd.h> + +#ifndef SHM_HUGETLB +#define SHM_HUGETLB 04000 // remove when glibc defines it +#endif + +#include <string> +#include <list> + +// This file must work with autoconf on its public version, +// so these includes are correct. +#include "sattypes.h" +#include "error_diag.h" + +// OsLayer initialization. +OsLayer::OsLayer() { + testmem_ = 0; + testmemsize_ = 0; + totalmemsize_ = 0; + min_hugepages_bytes_ = 0; + normal_mem_ = true; + use_hugepages_ = false; + use_posix_shm_ = false; + dynamic_mapped_shmem_ = false; + shmid_ = 0; + + time_initialized_ = 0; + + regionsize_ = 0; + regioncount_ = 1; + num_cpus_ = 0; + num_nodes_ = 0; + num_cpus_per_node_ = 0; + error_diagnoser_ = 0; + err_log_callback_ = 0; + error_injection_ = false; + + void *pvoid = 0; + address_mode_ = sizeof(pvoid) * 8; + + has_clflush_ = false; + has_sse2_ = false; +} + +// OsLayer cleanup. +OsLayer::~OsLayer() { + if (error_diagnoser_) + delete error_diagnoser_; +} + +// OsLayer initialization. +bool OsLayer::Initialize() { + time_initialized_ = time(NULL); + // Detect asm support. + GetFeatures(); + + if (num_cpus_ == 0) { + num_nodes_ = 1; + num_cpus_ = sysconf(_SC_NPROCESSORS_ONLN); + num_cpus_per_node_ = num_cpus_ / num_nodes_; + } + logprintf(5, "Log: %d nodes, %d cpus.\n", num_nodes_, num_cpus_); + sat_assert(CPU_SETSIZE >= num_cpus_); + cpu_sets_.resize(num_nodes_); + cpu_sets_valid_.resize(num_nodes_); + // Create error diagnoser. + error_diagnoser_ = new ErrorDiag(); + if (!error_diagnoser_->set_os(this)) + return false; + return true; +} + +// Machine type detected. Can we implement all these functions correctly? +bool OsLayer::IsSupported() { + if (kOpenSource) { + // There are no explicitly supported systems in open source version. + return true; + } + + // This is the default empty implementation. + // SAT won't report full error information. + return false; +} + +int OsLayer::AddressMode() { + // Detect 32/64 bit binary. + void *pvoid = 0; + return sizeof(pvoid) * 8; +} + +// Translates user virtual to physical address. +uint64 OsLayer::VirtualToPhysical(void *vaddr) { + // Needs platform specific implementation. + return 0; +} + +// Returns the HD device that contains this file. +string OsLayer::FindFileDevice(string filename) { + return "hdUnknown"; +} + +// Returns a list of locations corresponding to HD devices. +list<string> OsLayer::FindFileDevices() { + // No autodetection on unknown systems. + list<string> locations; + return locations; +} + + +// Get HW core features from cpuid instruction. +void OsLayer::GetFeatures() { +#if defined(STRESSAPPTEST_CPU_X86_64) || defined(STRESSAPPTEST_CPU_I686) + // CPUID features documented at: + // http://www.sandpile.org/ia32/cpuid.htm + int ax, bx, cx, dx; + __asm__ __volatile__ ( + "cpuid": "=a" (ax), "=b" (bx), "=c" (cx), "=d" (dx) : "a" (1)); + has_clflush_ = (dx >> 19) & 1; + has_sse2_ = (dx >> 26) & 1; + + logprintf(9, "Log: has clflush: %s, has sse2: %s\n", + has_clflush_ ? "true" : "false", + has_sse2_ ? "true" : "false"); +#elif defined(STRESSAPPTEST_CPU_PPC) + // All PPC implementations have cache flush instructions. + has_clflush_ = true; +#elif defined(STRESSAPPTEST_CPU_ARMV7A) +#warning "Unsupported CPU type ARMV7A: unable to determine feature set." +#else +#warning "Unsupported CPU type: unable to determine feature set." +#endif +} + + +// We need to flush the cacheline here. +void OsLayer::Flush(void *vaddr) { + // Use the generic flush. This function is just so we can override + // this if we are so inclined. + if (has_clflush_) + FastFlush(vaddr); +} + + +// Run C or ASM copy as appropriate.. +bool OsLayer::AdlerMemcpyWarm(uint64 *dstmem, uint64 *srcmem, + unsigned int size_in_bytes, + AdlerChecksum *checksum) { + if (has_sse2_) { + return AdlerMemcpyAsm(dstmem, srcmem, size_in_bytes, checksum); + } else { + return AdlerMemcpyWarmC(dstmem, srcmem, size_in_bytes, checksum); + } +} + + +// Translate user virtual to physical address. +int OsLayer::FindDimm(uint64 addr, char *buf, int len) { + char tmpbuf[256]; + snprintf(tmpbuf, sizeof(tmpbuf), "DIMM Unknown"); + snprintf(buf, len, "%s", tmpbuf); + return 0; +} + + +// Classifies addresses according to "regions" +// This isn't really implemented meaningfully here.. +int32 OsLayer::FindRegion(uint64 addr) { + static bool warned = false; + + if (regionsize_ == 0) { + regionsize_ = totalmemsize_ / 8; + if (regionsize_ < 512 * kMegabyte) + regionsize_ = 512 * kMegabyte; + regioncount_ = totalmemsize_ / regionsize_; + if (regioncount_ < 1) regioncount_ = 1; + } + + int32 region_num = addr / regionsize_; + if (region_num >= regioncount_) { + if (!warned) { + logprintf(0, "Log: region number %d exceeds region count %d\n", + region_num, regioncount_); + warned = true; + } + region_num = region_num % regioncount_; + } + return region_num; +} + +// Report which cores are associated with a given region. +cpu_set_t *OsLayer::FindCoreMask(int32 region) { + sat_assert(region >= 0); + region %= num_nodes_; + if (!cpu_sets_valid_[region]) { + CPU_ZERO(&cpu_sets_[region]); + for (int i = 0; i < num_cpus_per_node_; ++i) { + CPU_SET(i + region * num_cpus_per_node_, &cpu_sets_[region]); + } + cpu_sets_valid_[region] = true; + logprintf(5, "Log: Region %d mask 0x%s\n", + region, FindCoreMaskFormat(region).c_str()); + } + return &cpu_sets_[region]; +} + +// Return cores associated with a given region in hex string. +string OsLayer::FindCoreMaskFormat(int32 region) { + cpu_set_t* mask = FindCoreMask(region); + string format = cpuset_format(mask); + if (format.size() < 8) + format = string(8 - format.size(), '0') + format; + return format; +} + +// Report an error in an easily parseable way. +bool OsLayer::ErrorReport(const char *part, const char *symptom, int count) { + time_t now = time(NULL); + int ttf = now - time_initialized_; + logprintf(0, "Report Error: %s : %s : %d : %ds\n", symptom, part, count, ttf); + return true; +} + +// Read the number of hugepages out of the kernel interface in proc. +int64 OsLayer::FindHugePages() { + char buf[65] = "0"; + + // This is a kernel interface to query the numebr of hugepages + // available in the system. + static const char *hugepages_info_file = "/proc/sys/vm/nr_hugepages"; + int hpfile = open(hugepages_info_file, O_RDONLY); + + ssize_t bytes_read = read(hpfile, buf, 64); + close(hpfile); + + if (bytes_read <= 0) { + logprintf(12, "Log: /proc/sys/vm/nr_hugepages " + "read did not provide data\n"); + return 0; + } + + if (bytes_read == 64) { + logprintf(0, "Process Error: /proc/sys/vm/nr_hugepages " + "is surprisingly large\n"); + return 0; + } + + // Add a null termintation to be string safe. + buf[bytes_read] = '\0'; + // Read the page count. + int64 pages = strtoull(buf, NULL, 10); // NOLINT + + return pages; +} + +int64 OsLayer::FindFreeMemSize() { + int64 size = 0; + int64 minsize = 0; + if (totalmemsize_ > 0) + return totalmemsize_; + + int64 pages = sysconf(_SC_PHYS_PAGES); + int64 avpages = sysconf(_SC_AVPHYS_PAGES); + int64 pagesize = sysconf(_SC_PAGESIZE); + int64 physsize = pages * pagesize; + int64 avphyssize = avpages * pagesize; + + // Assume 2MB hugepages. + int64 hugepagesize = FindHugePages() * 2 * kMegabyte; + + if ((pages == -1) || (pagesize == -1)) { + logprintf(0, "Process Error: sysconf could not determine memory size.\n"); + return 0; + } + + // We want to leave enough stuff for things to run. + // If the user specified a minimum amount of memory to expect, require that. + // Otherwise, if more than 2GB is present, leave 192M + 5% for other stuff. + // If less than 2GB is present use 85% of what's available. + // These are fairly arbitrary numbers that seem to work OK. + // + // TODO(nsanders): is there a more correct way to determine target + // memory size? + if (hugepagesize > 0 && min_hugepages_bytes_ > 0) { + minsize = min_hugepages_bytes_; + } else if (physsize < 2048LL * kMegabyte) { + minsize = ((pages * 85) / 100) * pagesize; + } else { + minsize = ((pages * 95) / 100) * pagesize - (192 * kMegabyte); + } + + // Use hugepage sizing if available. + if (hugepagesize > 0) { + if (hugepagesize < minsize) { + logprintf(0, "Procedural Error: Not enough hugepages. " + "%lldMB available < %lldMB required.\n", + hugepagesize / kMegabyte, + minsize / kMegabyte); + // Require the calculated minimum amount of memory. + size = minsize; + } else { + // Require that we get all hugepages. + size = hugepagesize; + } + } else { + // Require the calculated minimum amount of memory. + size = minsize; + } + + logprintf(5, "Log: Total %lld MB. Free %lld MB. Hugepages %lld MB. " + "Targeting %lld MB (%lld%%)\n", + physsize / kMegabyte, + avphyssize / kMegabyte, + hugepagesize / kMegabyte, + size / kMegabyte, + size * 100 / physsize); + + totalmemsize_ = size; + return size; +} + +// Allocates all memory available. +int64 OsLayer::AllocateAllMem() { + int64 length = FindFreeMemSize(); + bool retval = AllocateTestMem(length, 0); + if (retval) + return length; + else + return 0; +} + +// Allocate the target memory. This may be from malloc, hugepage pool +// or other platform specific sources. +bool OsLayer::AllocateTestMem(int64 length, uint64 paddr_base) { + // Try hugepages first. + void *buf = 0; + + sat_assert(length >= 0); + + if (paddr_base) + logprintf(0, "Process Error: non zero paddr_base %#llx is not supported," + " ignore.\n", paddr_base); + + // Determine optimal memory allocation path. + bool prefer_hugepages = false; + bool prefer_posix_shm = false; + bool prefer_dynamic_mapping = false; + + // Are there enough hugepages? + int64 hugepagesize = FindHugePages() * 2 * kMegabyte; + // TODO(nsanders): Is there enough /dev/shm? Is there enough free memeory? + if ((length >= 1400LL * kMegabyte) && (address_mode_ == 32)) { + prefer_dynamic_mapping = true; + prefer_posix_shm = true; + logprintf(3, "Log: Prefer POSIX shared memory allocation.\n"); + logprintf(3, "Log: You may need to run " + "'sudo mount -o remount,size=100\% /dev/shm.'\n"); + } else if (hugepagesize >= length) { + prefer_hugepages = true; + logprintf(3, "Log: Prefer using hugepace allocation.\n"); + } else { + logprintf(3, "Log: Prefer plain malloc memory allocation.\n"); + } + + // Allocate hugepage mapped memory. + if (prefer_hugepages) { + do { // Allow break statement. + int shmid; + void *shmaddr; + + if ((shmid = shmget(2, length, + SHM_HUGETLB | IPC_CREAT | SHM_R | SHM_W)) < 0) { + int err = errno; + string errtxt = ErrorString(err); + logprintf(3, "Log: failed to allocate shared hugepage " + "object - err %d (%s)\n", + err, errtxt.c_str()); + logprintf(3, "Log: sysctl -w vm.nr_hugepages=XXX allows hugepages.\n"); + break; + } + + shmaddr = shmat(shmid, NULL, NULL); + if (shmaddr == reinterpret_cast<void*>(-1)) { + int err = errno; + string errtxt = ErrorString(err); + logprintf(0, "Log: failed to attach shared " + "hugepage object - err %d (%s).\n", + err, errtxt.c_str()); + if (shmctl(shmid, IPC_RMID, NULL) < 0) { + int err = errno; + string errtxt = ErrorString(err); + logprintf(0, "Log: failed to remove shared " + "hugepage object - err %d (%s).\n", + err, errtxt.c_str()); + } + break; + } + use_hugepages_ = true; + shmid_ = shmid; + buf = shmaddr; + logprintf(0, "Log: Using shared hugepage object 0x%x at %p.\n", + shmid, shmaddr); + } while (0); + } + + if ((!use_hugepages_) && prefer_posix_shm) { + do { + int shm_object; + void *shmaddr = NULL; + + shm_object = shm_open("/stressapptest", O_CREAT | O_RDWR, S_IRWXU); + if (shm_object < 0) { + int err = errno; + string errtxt = ErrorString(err); + logprintf(3, "Log: failed to allocate shared " + "smallpage object - err %d (%s)\n", + err, errtxt.c_str()); + break; + } + + if (0 > ftruncate(shm_object, length)) { + int err = errno; + string errtxt = ErrorString(err); + logprintf(3, "Log: failed to ftruncate shared " + "smallpage object - err %d (%s)\n", + err, errtxt.c_str()); + break; + } + + // 32 bit linux apps can only use ~1.4G of address space. + // Use dynamic mapping for allocations larger than that. + // Currently perf hit is ~10% for this. + if (prefer_dynamic_mapping) { + dynamic_mapped_shmem_ = true; + } else { + // Do a full mapping here otherwise. + shmaddr = mmap64(NULL, length, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_NORESERVE | MAP_LOCKED | MAP_POPULATE, + shm_object, NULL); + if (shmaddr == reinterpret_cast<void*>(-1)) { + int err = errno; + string errtxt = ErrorString(err); + logprintf(0, "Log: failed to map shared " + "smallpage object - err %d (%s).\n", + err, errtxt.c_str()); + break; + } + } + + use_posix_shm_ = true; + shmid_ = shm_object; + buf = shmaddr; + char location_message[256] = ""; + if (dynamic_mapped_shmem_) { + sprintf(location_message, "mapped as needed"); + } else { + sprintf(location_message, "at %p", shmaddr); + } + logprintf(0, "Log: Using posix shared memory object 0x%x %s.\n", + shm_object, location_message); + } while (0); + shm_unlink("/stressapptest"); + } + + if (!use_hugepages_ && !use_posix_shm_) { + // Use memalign to ensure that blocks are aligned enough for disk direct IO. + buf = static_cast<char*>(memalign(4096, length)); + if (buf) { + logprintf(0, "Log: Using memaligned allocation at %p.\n", buf); + } else { + logprintf(0, "Process Error: memalign returned 0\n"); + if ((length >= 1499LL * kMegabyte) && (address_mode_ == 32)) { + logprintf(0, "Log: You are trying to allocate > 1.4G on a 32 " + "bit process. Please setup shared memory.\n"); + } + } + } + + testmem_ = buf; + if (buf || dynamic_mapped_shmem_) { + testmemsize_ = length; + } else { + testmemsize_ = 0; + } + + return (buf != 0) || dynamic_mapped_shmem_; +} + +// Free the test memory. +void OsLayer::FreeTestMem() { + if (testmem_) { + if (use_hugepages_) { + shmdt(testmem_); + shmctl(shmid_, IPC_RMID, NULL); + } else if (use_posix_shm_) { + if (!dynamic_mapped_shmem_) { + munmap(testmem_, testmemsize_); + } + close(shmid_); + } else { + free(testmem_); + } + testmem_ = 0; + testmemsize_ = 0; + } +} + + +// Prepare the target memory. It may requre mapping in, or this may be a noop. +void *OsLayer::PrepareTestMem(uint64 offset, uint64 length) { + sat_assert((offset + length) <= testmemsize_); + if (dynamic_mapped_shmem_) { + // TODO(nsanders): Check if we can support MAP_NONBLOCK, + // and evaluate performance hit from not using it. + void * mapping = mmap64(NULL, length, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_NORESERVE | MAP_LOCKED | MAP_POPULATE, + shmid_, offset); + if (mapping == MAP_FAILED) { + string errtxt = ErrorString(errno); + logprintf(0, "Process Error: PrepareTestMem mmap64(%llx, %llx) failed. " + "error: %s.\n", + offset, length, errtxt.c_str()); + sat_assert(0); + } + return mapping; + } + + return reinterpret_cast<void*>(reinterpret_cast<char*>(testmem_) + offset); +} + +// Release the test memory resources, if any. +void OsLayer::ReleaseTestMem(void *addr, uint64 offset, uint64 length) { + if (dynamic_mapped_shmem_) { + int retval = munmap(addr, length); + if (retval == -1) { + string errtxt = ErrorString(errno); + logprintf(0, "Process Error: ReleaseTestMem munmap(%p, %llx) failed. " + "error: %s.\n", + addr, length, errtxt.c_str()); + sat_assert(0); + } + } +} + +// No error polling on unknown systems. +int OsLayer::ErrorPoll() { + return 0; +} + +// Generally, poll for errors once per second. +void OsLayer::ErrorWait() { + sat_sleep(1); + return; +} + +// Open a PCI bus-dev-func as a file and return its file descriptor. +// Error is indicated by return value less than zero. +int OsLayer::PciOpen(int bus, int device, int function) { + char dev_file[256]; + + snprintf(dev_file, sizeof(dev_file), "/proc/bus/pci/%02x/%02x.%x", + bus, device, function); + + int fd = open(dev_file, O_RDWR); + if (fd == -1) { + logprintf(0, "Process Error: Unable to open PCI bus %d, device %d, " + "function %d (errno %d).\n", + bus, device, function, errno); + return -1; + } + + return fd; +} + + +// Read and write functions to access PCI config. +uint32 OsLayer::PciRead(int fd, uint32 offset, int width) { + // Strict aliasing rules lawyers will cause data corruption + // on cast pointers in some gccs. + union { + uint32 l32; + uint16 l16; + uint8 l8; + } datacast; + datacast.l32 = 0; + uint32 size = width / 8; + + sat_assert((width == 32) || (width == 16) || (width == 8)); + sat_assert(offset <= (256 - size)); + + if (lseek(fd, offset, SEEK_SET) < 0) { + logprintf(0, "Process Error: Can't seek %x\n", offset); + return 0; + } + if (read(fd, &datacast, size) != static_cast<ssize_t>(size)) { + logprintf(0, "Process Error: Can't read %x\n", offset); + return 0; + } + + // Extract the data. + switch (width) { + case 8: + sat_assert(&(datacast.l8) == reinterpret_cast<uint8*>(&datacast)); + return datacast.l8; + case 16: + sat_assert(&(datacast.l16) == reinterpret_cast<uint16*>(&datacast)); + return datacast.l16; + case 32: + return datacast.l32; + } + return 0; +} + +void OsLayer::PciWrite(int fd, uint32 offset, uint32 value, int width) { + // Strict aliasing rules lawyers will cause data corruption + // on cast pointers in some gccs. + union { + uint32 l32; + uint16 l16; + uint8 l8; + } datacast; + datacast.l32 = 0; + uint32 size = width / 8; + + sat_assert((width == 32) || (width == 16) || (width == 8)); + sat_assert(offset <= (256 - size)); + + // Cram the data into the right alignment. + switch (width) { + case 8: + sat_assert(&(datacast.l8) == reinterpret_cast<uint8*>(&datacast)); + datacast.l8 = value; + case 16: + sat_assert(&(datacast.l16) == reinterpret_cast<uint16*>(&datacast)); + datacast.l16 = value; + case 32: + datacast.l32 = value; + } + + if (lseek(fd, offset, SEEK_SET) < 0) { + logprintf(0, "Process Error: Can't seek %x\n", offset); + return; + } + if (write(fd, &datacast, size) != static_cast<ssize_t>(size)) { + logprintf(0, "Process Error: Can't write %x to %x\n", datacast.l32, offset); + return; + } + + return; +} + + + +// Open dev msr. +int OsLayer::OpenMSR(uint32 core, uint32 address) { + char buf[256]; + snprintf(buf, sizeof(buf), "/dev/cpu/%d/msr", core); + int fd = open(buf, O_RDWR); + if (fd < 0) + return fd; + + uint32 pos = lseek(fd, address, SEEK_SET); + if (pos != address) { + close(fd); + logprintf(5, "Log: can't seek to msr %x, cpu %d\n", address, core); + return -1; + } + + return fd; +} + +bool OsLayer::ReadMSR(uint32 core, uint32 address, uint64 *data) { + int fd = OpenMSR(core, address); + if (fd < 0) + return false; + + // Read from the msr. + bool res = (sizeof(*data) == read(fd, data, sizeof(*data))); + + if (!res) + logprintf(5, "Log: Failed to read msr %x core %d\n", address, core); + + close(fd); + + return res; +} + +bool OsLayer::WriteMSR(uint32 core, uint32 address, uint64 *data) { + int fd = OpenMSR(core, address); + if (fd < 0) + return false; + + // Write to the msr + bool res = (sizeof(*data) == write(fd, data, sizeof(*data))); + + if (!res) + logprintf(5, "Log: Failed to write msr %x core %d\n", address, core); + + close(fd); + + return res; +} + +// Extract bits [n+len-1, n] from a 32 bit word. +// so GetBitField(0x0f00, 8, 4) == 0xf. +uint32 OsLayer::GetBitField(uint32 val, uint32 n, uint32 len) { + return (val >> n) & ((1<<len) - 1); +} + +// Generic CPU stress workload that would work on any CPU/Platform. +// Float-point array moving average calculation. +bool OsLayer::CpuStressWorkload() { + double float_arr[100]; + double sum = 0; + unsigned int seed = 12345; + + // Initialize array with random numbers. + for (int i = 0; i < 100; i++) { + float_arr[i] = rand_r(&seed); + if (rand_r(&seed) % 2) + float_arr[i] *= -1.0; + } + + // Calculate moving average. + for (int i = 0; i < 100000000; i++) { + float_arr[i % 100] = + (float_arr[i % 100] + float_arr[(i + 1) % 100] + + float_arr[(i + 99) % 100]) / 3; + sum += float_arr[i % 100]; + } + + // Artificial printf so the loops do not get optimized away. + if (sum == 0.0) + logprintf(12, "Log: I'm Feeling Lucky!\n"); + return true; +} + +PCIDevices OsLayer::GetPCIDevices() { + PCIDevices device_list; + DIR *dir; + struct dirent *buf = new struct dirent(); + struct dirent *entry; + dir = opendir(kSysfsPath); + if (!dir) + logprintf(0, "Process Error: Cannot open %s", kSysfsPath); + while (readdir_r(dir, buf, &entry) == 0 && entry) { + PCIDevice *device; + unsigned int dev, func; + // ".", ".." or a special non-device perhaps. + if (entry->d_name[0] == '.') + continue; + + device = new PCIDevice(); + if (sscanf(entry->d_name, "%04x:%02hx:%02x.%d", + &device->domain, &device->bus, &dev, &func) < 4) { + logprintf(0, "Process Error: Couldn't parse %s", entry->d_name); + free(device); + continue; + } + device->dev = dev; + device->func = func; + device->vendor_id = PCIGetValue(entry->d_name, "vendor"); + device->device_id = PCIGetValue(entry->d_name, "device"); + PCIGetResources(entry->d_name, device); + device_list.insert(device_list.end(), device); + } + closedir(dir); + delete buf; + return device_list; +} + +int OsLayer::PCIGetValue(string name, string object) { + int fd, len; + char filename[256]; + char buf[256]; + snprintf(filename, sizeof(filename), "%s/%s/%s", kSysfsPath, + name.c_str(), object.c_str()); + fd = open(filename, O_RDONLY); + if (fd < 0) + return 0; + len = read(fd, buf, 256); + close(fd); + buf[len] = '\0'; + return strtol(buf, NULL, 0); // NOLINT +} + +int OsLayer::PCIGetResources(string name, PCIDevice *device) { + char filename[256]; + char buf[256]; + FILE *file; + int64 start; + int64 end; + int64 size; + int i; + snprintf(filename, sizeof(filename), "%s/%s/%s", kSysfsPath, + name.c_str(), "resource"); + file = fopen(filename, "r"); + if (!file) { + logprintf(0, "Process Error: impossible to find resource file for %s", + filename); + return errno; + } + for (i = 0; i < 6; i++) { + if (!fgets(buf, 256, file)) + break; + sscanf(buf, "%llx %llx", &start, &end); // NOLINT + size = 0; + if (start) + size = end - start + 1; + device->base_addr[i] = start; + device->size[i] = size; + } + fclose(file); + return 0; +} diff --git a/src/os.h b/src/os.h new file mode 100644 index 0000000..28c8a2a --- /dev/null +++ b/src/os.h @@ -0,0 +1,290 @@ +// Copyright 2006 Google Inc. All Rights Reserved. +// Author: nsanders, menderico + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STRESSAPPTEST_OS_H_ // NOLINT +#define STRESSAPPTEST_OS_H_ + +#include <dirent.h> +#include <string> +#include <list> +#include <map> +#include <vector> + +// This file must work with autoconf on its public version, +// so these includes are correct. +#include "adler32memcpy.h" // NOLINT +#include "sattypes.h" // NOLINT + +const char kSysfsPath[] = "/sys/bus/pci/devices"; + +struct PCIDevice { + int32 domain; + uint16 bus; + uint8 dev; + uint8 func; + uint16 vendor_id; + uint16 device_id; + uint64 base_addr[6]; + uint64 size[6]; +}; + +typedef vector<PCIDevice*> PCIDevices; + +class ErrorDiag; + +// This class implements OS/Platform specific funtions. +class OsLayer { + public: + OsLayer(); + virtual ~OsLayer(); + + // Set the minimum amount of hugepages that should be available for testing. + // Must be set before Initialize(). + void SetMinimumHugepagesSize(int64 min_bytes) { + min_hugepages_bytes_ = min_bytes; + } + + // Initializes data strctures and open files. + // Returns false on error. + virtual bool Initialize(); + + // Virtual to physical. This implementation is optional for + // subclasses to implement. + // Takes a pointer, and returns the corresponding bus address. + virtual uint64 VirtualToPhysical(void *vaddr); + + // Prints failed dimm. This implementation is optional for + // subclasses to implement. + // Takes a bus address and string, and prints the DIMM name + // into the string. Returns error status. + virtual int FindDimm(uint64 addr, char *buf, int len); + // Print dimm info, plus more available info. + virtual int FindDimmExtended(uint64 addr, char *buf, int len) { + return FindDimm(addr, buf, len); + } + + + // Classifies addresses according to "regions" + // This may mean different things on different platforms. + virtual int32 FindRegion(uint64 paddr); + // Find cpu cores associated with a region. Either NUMA or arbitrary. + virtual cpu_set_t *FindCoreMask(int32 region); + // Return cpu cores associated with a region in a hex string. + virtual string FindCoreMaskFormat(int32 region); + + // Returns the HD device that contains this file. + virtual string FindFileDevice(string filename); + + // Returns a list of paths coresponding to HD devices found on this machine. + virtual list<string> FindFileDevices(); + + // Polls for errors. This implementation is optional. + // This will poll once for errors and return zero iff no errors were found. + virtual int ErrorPoll(); + + // Delay an appropriate amount of time between polling. + virtual void ErrorWait(); + + // Report errors. This implementation is mandatory. + // This will output a machine readable line regarding the error. + virtual bool ErrorReport(const char *part, const char *symptom, int count); + + // Flushes cacheline. Used to distinguish read or write errors. + // Subclasses may implement this in machine specific ways.. + // Takes a pointer, and flushed the cacheline containing that pointer. + virtual void Flush(void *vaddr); + + // Fast flush, for use in performance critical code. + // This is bound at compile time, and will not pick up + // any runtime machine configuration info. + inline static void FastFlush(void *vaddr) { +#ifdef STRESSAPPTEST_CPU_PPC + asm volatile("dcbf 0,%0; sync" : : "r" (vaddr)); +#elif defined(STRESSAPPTEST_CPU_X86_64) || defined(STRESSAPPTEST_CPU_I686) + // Put mfence before and after clflush to make sure: + // 1. The write before the clflush is committed to memory bus; + // 2. The read after the clflush is hitting the memory bus. + // + // From Intel manual: + // CLFLUSH is only ordered by the MFENCE instruction. It is not guaranteed + // to be ordered by any other fencing, serializing or other CLFLUSH + // instruction. For example, software can use an MFENCE instruction to + // insure that previous stores are included in the write-back. + asm volatile("mfence"); + asm volatile("clflush (%0)" :: "r" (vaddr)); + asm volatile("mfence"); +#elif defined(STRESSAPPTEST_CPU_ARMV7A) + #warning "Unsupported CPU type ARMV7A: Unable to force cache flushes." +#else + #warning "Unsupported CPU type: Unable to force cache flushes." +#endif + } + + // Get time in cpu timer ticks. Useful for matching MCEs with software + // actions. + inline static uint64 GetTimestamp(void) { + uint64 tsc; +#ifdef STRESSAPPTEST_CPU_PPC + uint32 tbl, tbu, temp; + __asm __volatile( + "1:\n" + "mftbu %2\n" + "mftb %0\n" + "mftbu %1\n" + "cmpw %2,%1\n" + "bne 1b\n" + : "=r"(tbl), "=r"(tbu), "=r"(temp) + : + : "cc"); + + tsc = (static_cast<uint64>(tbu) << 32) | static_cast<uint64>(tbl); +#elif defined(STRESSAPPTEST_CPU_X86_64) || defined(STRESSAPPTEST_CPU_I686) + datacast_t data; + __asm __volatile("rdtsc" : "=a" (data.l32.l), "=d"(data.l32.h)); + tsc = data.l64; +#elif defined(STRESSAPPTEST_CPU_ARMV7A) + #warning "Unsupported CPU type ARMV7A: your build may not function correctly" + tsc = 0; +#else + #warning "Unsupported CPU type: your build may not function correctly" + tsc = 0; +#endif + return (tsc); + } + + // Find the free memory on the machine. + virtual int64 FindFreeMemSize(); + + // Allocates test memory of length bytes. + // Subclasses must implement this. + // Call PepareTestMem to get a pointer. + virtual int64 AllocateAllMem(); // Returns length. + // Returns success. + virtual bool AllocateTestMem(int64 length, uint64 paddr_base); + virtual void FreeTestMem(); + + // Prepares the memory for use. You must call this + // before using test memory, and after you are done. + virtual void *PrepareTestMem(uint64 offset, uint64 length); + virtual void ReleaseTestMem(void *addr, uint64 offset, uint64 length); + + // Machine type detected. Can we implement all these functions correctly? + // Returns true if machine type is detected and implemented. + virtual bool IsSupported(); + + // Returns 32 for 32-bit, 64 for 64-bit. + virtual int AddressMode(); + // Update OsLayer state regarding cpu support for various features. + virtual void GetFeatures(); + + // Open, read, write pci cfg through /proc/bus/pci. fd is /proc/pci file. + virtual int PciOpen(int bus, int device, int function); + virtual void PciWrite(int fd, uint32 offset, uint32 value, int width); + virtual uint32 PciRead(int fd, uint32 offset, int width); + + // Read MSRs + virtual bool ReadMSR(uint32 core, uint32 address, uint64 *data); + virtual bool WriteMSR(uint32 core, uint32 address, uint64 *data); + + // Extract bits [n+len-1, n] from a 32 bit word. + // so GetBitField(0x0f00, 8, 4) == 0xf. + virtual uint32 GetBitField(uint32 val, uint32 n, uint32 len); + + // Platform and CPU specific CPU-stressing function. + // Returns true on success, false otherwise. + virtual bool CpuStressWorkload(); + + // Causes false errors for unittesting. + // Setting to "true" causes errors to be injected. + void set_error_injection(bool errors) { error_injection_ = errors; } + bool error_injection() const { return error_injection_; } + + // Is SAT using normal malloc'd memory, or exotic mmap'd memory. + bool normal_mem() const { return normal_mem_; } + + // Get numa config, if available.. + int num_nodes() const { return num_nodes_; } + int num_cpus() const { return num_cpus_; } + + // Handle to platform-specific error diagnoser. + ErrorDiag *error_diagnoser_; + + // Detect all PCI Devices. + virtual PCIDevices GetPCIDevices(); + + // Disambiguate between different "warm" memcopies. + virtual bool AdlerMemcpyWarm(uint64 *dstmem, uint64 *srcmem, + unsigned int size_in_bytes, + AdlerChecksum *checksum); + + // Store a callback to use to print + // app-specific info about the last error location. + // This call back is called with a physical address, and the app can fill in + // the most recent transaction that occurred at that address. + typedef bool (*ErrCallback)(uint64 paddr, string *buf); + void set_err_log_callback( + ErrCallback err_log_callback) { + err_log_callback_ = err_log_callback; + } + ErrCallback get_err_log_callback() { return err_log_callback_; } + + protected: + void *testmem_; // Location of test memory. + uint64 testmemsize_; // Size of test memory. + int64 totalmemsize_; // Size of available memory. + int64 min_hugepages_bytes_; // Minimum hugepages size. + bool error_injection_; // Do error injection? + bool normal_mem_; // Memory DMA capable? + bool use_hugepages_; // Use hugepage shmem? + bool use_posix_shm_; // Use 4k page shmem? + bool dynamic_mapped_shmem_; // Conserve virtual address space. + int shmid_; // Handle to shmem + + int64 regionsize_; // Size of memory "regions" + int regioncount_; // Number of memory "regions" + int num_cpus_; // Number of cpus in the system. + int num_nodes_; // Number of nodes in the system. + int num_cpus_per_node_; // Number of cpus per node in the system. + int address_mode_; // Are we running 32 or 64 bit? + bool has_sse2_; // Do we have sse2 instructions? + bool has_clflush_; // Do we have clflush instructions? + + + time_t time_initialized_; // Start time of test. + + vector<cpu_set_t> cpu_sets_; // Cache for cpu masks. + vector<bool> cpu_sets_valid_; // If the cpu mask cache is valid. + + // Get file descriptor for dev msr. + virtual int OpenMSR(uint32 core, uint32 address); + // Auxiliary methods for PCI device configuration + int PCIGetValue(string name, string object); + int PCIGetResources(string name, PCIDevice *device); + + // Look up how many hugepages there are. + virtual int64 FindHugePages(); + + // Link to find last transaction at an error location. + ErrCallback err_log_callback_; + + private: + DISALLOW_COPY_AND_ASSIGN(OsLayer); +}; + +// Selects and returns the proper OS and hardware interface. Does not call +// OsLayer::Initialize() on the new object. +OsLayer *OsLayerFactory(const std::map<std::string, std::string> &options); + +#endif // STRESSAPPTEST_OS_H_ NOLINT diff --git a/src/os_factory.cc b/src/os_factory.cc new file mode 100644 index 0000000..359f7ee --- /dev/null +++ b/src/os_factory.cc @@ -0,0 +1,40 @@ +// Copyright 2006 Google Inc. All Rights Reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file generates an OS interface class consistant with the +// current machine type. No machine type detection is currently done. + +#include <stdlib.h> +#include <stdio.h> +#include <sys/ioctl.h> +#include <string.h> + +#include <map> +#include <string> + +#include "os.h" + + +// Select the proper OS and hardware interface. +OsLayer *OsLayerFactory(const std::map<std::string, std::string> &options) { + OsLayer *os = 0; + os = new OsLayer(); + + // Check for memory allocation failure. + if (!os) { + logprintf(0, "Process Error: Can't allocate memory\n"); + return 0; + } + return os; +} diff --git a/src/pattern.cc b/src/pattern.cc new file mode 100644 index 0000000..9f22674 --- /dev/null +++ b/src/pattern.cc @@ -0,0 +1,421 @@ +// Copyright 2006 Google Inc. All Rights Reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// pattern.cc : library of stressful data patterns + +#include <sys/types.h> + +// This file must work with autoconf on its public version, +// so these includes are correct. +#include "pattern.h" +#include "sattypes.h" + +// Static data patterns. + +static unsigned int walkingOnes_data[] = { + 0x00000001, 0x00000002, 0x00000004, 0x00000008, + 0x00000010, 0x00000020, 0x00000040, 0x00000080, + 0x00000100, 0x00000200, 0x00000400, 0x00000800, + 0x00001000, 0x00002000, 0x00004000, 0x00008000, + 0x00010000, 0x00020000, 0x00040000, 0x00080000, + 0x00100000, 0x00200000, 0x00400000, 0x00800000, + 0x01000000, 0x02000000, 0x04000000, 0x08000000, + 0x10000000, 0x20000000, 0x40000000, 0x80000000, + 0x40000000, 0x20000000, 0x10000000, 0x08000000, + 0x04000000, 0x02000000, 0x01000000, 0x00800000, + 0x00400000, 0x00200000, 0x00100000, 0x00080000, + 0x00040000, 0x00020000, 0x00010000, 0x00008000, + 0x00004000, 0x00002000, 0x00001000, 0x00000800, + 0x00000400, 0x00000200, 0x00000100, 0x00000080, + 0x00000040, 0x00000020, 0x00000010, 0x00000008, + 0x00000004, 0x00000002, 0x00000001, 0x00000000 +}; +static const struct PatternData walkingOnes = { + "walkingOnes", + walkingOnes_data, + (sizeof walkingOnes_data / sizeof walkingOnes_data[0]) - 1, + {1, 1, 2, 1} // Weight for choosing 32/64/128/256 bit wide of this pattern +}; + +static unsigned int walkingInvOnes_data[] = { + 0x00000001, 0xfffffffe, 0x00000002, 0xfffffffd, + 0x00000004, 0xfffffffb, 0x00000008, 0xfffffff7, + 0x00000010, 0xffffffef, 0x00000020, 0xffffffdf, + 0x00000040, 0xffffffbf, 0x00000080, 0xffffff7f, + 0x00000100, 0xfffffeff, 0x00000200, 0xfffffdff, + 0x00000400, 0xfffffbff, 0x00000800, 0xfffff7ff, + 0x00001000, 0xffffefff, 0x00002000, 0xffffdfff, + 0x00004000, 0xffffbfff, 0x00008000, 0xffff7fff, + 0x00010000, 0xfffeffff, 0x00020000, 0xfffdffff, + 0x00040000, 0xfffbffff, 0x00080000, 0xfff7ffff, + 0x00100000, 0xffefffff, 0x00200000, 0xffdfffff, + 0x00400000, 0xffbfffff, 0x00800000, 0xff7fffff, + 0x01000000, 0xfeffffff, 0x02000000, 0xfdffffff, + 0x04000000, 0xfbffffff, 0x08000000, 0xf7ffffff, + 0x10000000, 0xefffffff, 0x20000000, 0xdfffffff, + 0x40000000, 0xbfffffff, 0x80000000, 0x7fffffff, + 0x40000000, 0xbfffffff, 0x20000000, 0xdfffffff, + 0x10000000, 0xefffffff, 0x08000000, 0xf7ffffff, + 0x04000000, 0xfbffffff, 0x02000000, 0xfdffffff, + 0x01000000, 0xfeffffff, 0x00800000, 0xff7fffff, + 0x00400000, 0xffbfffff, 0x00200000, 0xffdfffff, + 0x00100000, 0xffefffff, 0x00080000, 0xfff7ffff, + 0x00040000, 0xfffbffff, 0x00020000, 0xfffdffff, + 0x00010000, 0xfffeffff, 0x00008000, 0xffff7fff, + 0x00004000, 0xffffbfff, 0x00002000, 0xffffdfff, + 0x00001000, 0xffffefff, 0x00000800, 0xfffff7ff, + 0x00000400, 0xfffffbff, 0x00000200, 0xfffffdff, + 0x00000100, 0xfffffeff, 0x00000080, 0xffffff7f, + 0x00000040, 0xffffffbf, 0x00000020, 0xffffffdf, + 0x00000010, 0xffffffef, 0x00000008, 0xfffffff7, + 0x00000004, 0xfffffffb, 0x00000002, 0xfffffffd, + 0x00000001, 0xfffffffe, 0x00000000, 0xffffffff +}; +static const struct PatternData walkingInvOnes = { + "walkingInvOnes", + walkingInvOnes_data, + (sizeof walkingInvOnes_data / sizeof walkingInvOnes_data[0]) - 1, + {2, 2, 5, 5} +}; + +static unsigned int walkingZeros_data[] = { + 0xfffffffe, 0xfffffffd, 0xfffffffb, 0xfffffff7, + 0xffffffef, 0xffffffdf, 0xffffffbf, 0xffffff7f, + 0xfffffeff, 0xfffffdff, 0xfffffbff, 0xfffff7ff, + 0xffffefff, 0xffffdfff, 0xffffbfff, 0xffff7fff, + 0xfffeffff, 0xfffdffff, 0xfffbffff, 0xfff7ffff, + 0xffefffff, 0xffdfffff, 0xffbfffff, 0xff7fffff, + 0xfeffffff, 0xfdffffff, 0xfbffffff, 0xf7ffffff, + 0xefffffff, 0xdfffffff, 0xbfffffff, 0x7fffffff, + 0xbfffffff, 0xdfffffff, 0xefffffff, 0xf7ffffff, + 0xfbffffff, 0xfdffffff, 0xfeffffff, 0xff7fffff, + 0xffbfffff, 0xffdfffff, 0xffefffff, 0xfff7ffff, + 0xfffbffff, 0xfffdffff, 0xfffeffff, 0xffff7fff, + 0xffffbfff, 0xffffdfff, 0xffffefff, 0xfffff7ff, + 0xfffffbff, 0xfffffdff, 0xfffffeff, 0xffffff7f, + 0xffffffbf, 0xffffffdf, 0xffffffef, 0xfffffff7, + 0xfffffffb, 0xfffffffd, 0xfffffffe, 0xffffffff +}; +static const struct PatternData walkingZeros = { + "walkingZeros", + walkingZeros_data, + (sizeof walkingZeros_data / sizeof walkingZeros_data[0]) - 1, + {1, 1, 2, 1} +}; + +static unsigned int OneZero_data[] = { 0x00000000, 0xffffffff}; +static const struct PatternData OneZero = { + "OneZero", + OneZero_data, + (sizeof OneZero_data / sizeof OneZero_data[0]) - 1, + {5, 5, 15, 5} +}; + +static unsigned int JustZero_data[] = { 0x00000000, 0x00000000}; +static const struct PatternData JustZero = { + "JustZero", + JustZero_data, + (sizeof JustZero_data / sizeof JustZero_data[0]) - 1, + {2, 0, 0, 0} +}; + +static unsigned int JustOne_data[] = { 0xffffffff, 0xffffffff}; +static const struct PatternData JustOne = { + "JustOne", + JustOne_data, + (sizeof JustOne_data / sizeof JustOne_data[0]) - 1, + {2, 0, 0, 0} +}; + +static unsigned int JustFive_data[] = { 0x55555555, 0x55555555}; +static const struct PatternData JustFive = { + "JustFive", + JustFive_data, + (sizeof JustFive_data / sizeof JustFive_data[0]) - 1, + {2, 0, 0, 0} +}; + +static unsigned int JustA_data[] = { 0xaaaaaaaa, 0xaaaaaaaa}; +static const struct PatternData JustA = { + "JustA", + JustA_data, + (sizeof JustA_data / sizeof JustA_data[0]) - 1, + {2, 0, 0, 0} +}; + +static unsigned int FiveA_data[] = { 0x55555555, 0xaaaaaaaa}; +static const struct PatternData FiveA = { + "FiveA", + FiveA_data, + (sizeof FiveA_data / sizeof FiveA_data[0]) - 1, + {1, 1, 1, 1} +}; + +static unsigned int FiveA8_data[] = { + 0x5aa5a55a, 0xa55a5aa5, 0xa55a5aa5, 0x5aa5a55a +}; +static const struct PatternData FiveA8 = { + "FiveA8", + FiveA8_data, + (sizeof FiveA8_data / sizeof FiveA8_data[0]) - 1, + {1, 1, 1, 1} +}; + +static unsigned int Long8b10b_data[] = { 0x16161616, 0x16161616 }; +static const struct PatternData Long8b10b = { + "Long8b10b", + Long8b10b_data, + (sizeof Long8b10b_data / sizeof Long8b10b_data[0]) - 1, + {2, 0, 0, 0} +}; + +static unsigned int Short8b10b_data[] = { 0xb5b5b5b5, 0xb5b5b5b5 }; +static const struct PatternData Short8b10b = { + "Short8b10b", + Short8b10b_data, + (sizeof Short8b10b_data / sizeof Short8b10b_data[0]) - 1, + {2, 0, 0, 0} +}; + +static unsigned int Checker8b10b_data[] = { 0xb5b5b5b5, 0x4a4a4a4a }; +static const struct PatternData Checker8b10b = { + "Checker8b10b", + Checker8b10b_data, + (sizeof Checker8b10b_data / sizeof Checker8b10b_data[0]) - 1, + {1, 0, 0, 1} +}; + +static unsigned int Five7_data[] = { 0x55555557, 0x55575555 }; +static const struct PatternData Five7 = { + "Five7", + Five7_data, + (sizeof Five7_data / sizeof Five7_data[0]) - 1, + {0, 2, 0, 0} +}; + +static unsigned int Zero2fd_data[] = { 0x00020002, 0xfffdfffd }; +static const struct PatternData Zero2fd = { + "Zero2fd", + Zero2fd_data, + (sizeof Zero2fd_data / sizeof Zero2fd_data[0]) - 1, + {0, 2, 0, 0} +}; + +// Extern array of useable patterns. +static const struct PatternData pattern_array[] = { + walkingOnes, + walkingInvOnes, + walkingZeros, + OneZero, + JustZero, + JustOne, + JustFive, + JustA, + FiveA, + FiveA8, + Long8b10b, + Short8b10b, + Checker8b10b, + Five7, + Zero2fd, +}; +static const int pattern_array_size = + sizeof pattern_array / sizeof pattern_array[0]; + +Pattern::Pattern() { + crc_ = NULL; +} + +Pattern::~Pattern() { + if (crc_ != NULL) { + delete crc_; + } +} + +// Calculate CRC for this pattern. This must match +// the CRC calculation in worker.cc. +int Pattern::CalculateCrc() { + // TODO(johnhuang): + // Consider refactoring to the form: + // while (i < count) AdlerInc(uint64, uint64, AdlerChecksum*) + uint64 a1 = 1; + uint64 a2 = 1; + uint64 b1 = 0; + uint64 b2 = 0; + + // checksum is calculated using only the first 4096 bytes of data. + int i = 0; + int blocksize = 4096; + int count = blocksize / sizeof i; + while (i < count) { + a1 += pattern(i); + b1 += a1; + i++; + a1 += pattern(i); + b1 += a1; + i++; + + a2 += pattern(i); + b2 += a2; + i++; + a2 += pattern(i); + b2 += a2; + i++; + } + if (crc_ != NULL) { + delete crc_; + } + crc_ = new AdlerChecksum(); + crc_->Set(a1, a2, b1, b2); + return 0; +} + +// Initialize pattern's CRC. +int Pattern::Initialize(const struct PatternData &pattern_init, + int buswidth, + bool invert, + int weight) { + int result = 1; + + pattern_ = &pattern_init; + busshift_ = 2; + inverse_ = invert; + weight_ = weight; + + name_.clear(); + name_.append(pattern_->name); + + if (invert) + name_.append("~"); + + if (buswidth == 32) { + name_.append("32"); + busshift_ = 0; + } else if (buswidth == 64) { + name_.append("64"); + busshift_ = 1; + } else if (buswidth == 128) { + name_.append("128"); + busshift_ = 2; + } else if (buswidth == 256) { + name_.append("256"); + busshift_ = 3; + } else { + logprintf(0, "Process Error: Confused by bus width %d\n", + buswidth); + name_.append("Broken"); + result = 0; + } + + CalculateCrc(); + + return result; +} + + +PatternList::PatternList() { + size_= 0; + initialized_ = 0; +} + +PatternList::~PatternList() { + if (initialized_) { + Destroy(); + } +} + +// Fill in the class with references to the static data patterns +int PatternList::Initialize() { + int patterncount = 0; + int weightcount = 0; + + patterns_.resize(pattern_array_size * 8); + for (int i = 0; i < pattern_array_size; i++) { + // Non inverted. + weightcount += pattern_array[i].weight[0]; + patterns_[patterncount++].Initialize(pattern_array[i], 32, false, + pattern_array[i].weight[0]); + weightcount += pattern_array[i].weight[1]; + patterns_[patterncount++].Initialize(pattern_array[i], 64, false, + pattern_array[i].weight[1]); + weightcount += pattern_array[i].weight[2]; + patterns_[patterncount++].Initialize(pattern_array[i], 128, false, + pattern_array[i].weight[2]); + weightcount += pattern_array[i].weight[3]; + patterns_[patterncount++].Initialize(pattern_array[i], 256, false, + pattern_array[i].weight[3]); + + // Inverted. + weightcount += pattern_array[i].weight[0]; + patterns_[patterncount++].Initialize(pattern_array[i], 32, true, + pattern_array[i].weight[0]); + weightcount += pattern_array[i].weight[1]; + patterns_[patterncount++].Initialize(pattern_array[i], 64, true, + pattern_array[i].weight[1]); + weightcount += pattern_array[i].weight[2]; + patterns_[patterncount++].Initialize(pattern_array[i], 128, true, + pattern_array[i].weight[2]); + weightcount += pattern_array[i].weight[3]; + patterns_[patterncount++].Initialize(pattern_array[i], 256, true, + pattern_array[i].weight[3]); + } + size_ = patterncount; + weightcount_ = weightcount; + initialized_ = 1; + + logprintf(12, "Log: initialized %d data patterns\n", size_); + + return 1; +} + +// Free the stuff. +int PatternList::Destroy() { + if (!initialized_) + return 0; + + patterns_.clear(); + size_ = 0; + initialized_ = 0; + + return 1; +} + +// Return pattern numbered "i" +Pattern *PatternList::GetPattern(int i) { + if (static_cast<unsigned int>(i) < size_) { + return &patterns_[i]; + } + + logprintf(0, "Process Error: Out of bounds pattern access\n"); + return 0; +} + +// Return a randomly selected pattern. +Pattern *PatternList::GetRandomPattern() { + unsigned int target = random(); + target = target % weightcount_; + + unsigned int i = 0; + unsigned int sum = 0; + while (target > sum) { + sum += patterns_[i].weight(); + i++; + } + if (i < size_) { + return &patterns_[i]; + } + + logprintf(0, "Process Error: Out of bounds pattern access\n"); + return 0; +} diff --git a/src/pattern.h b/src/pattern.h new file mode 100644 index 0000000..181f839 --- /dev/null +++ b/src/pattern.h @@ -0,0 +1,124 @@ +// Copyright 2006 Google Inc. All Rights Reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// pattern.h : global pattern references and initialization + +// This file implements easy access to statically declared +// data patterns. + +#ifndef STRESSAPPTEST_PATTERN_H_ +#define STRESSAPPTEST_PATTERN_H_ + +#include <vector> +#include <string> + +// This file must work with autoconf on its public version, +// so these includes are correct. +#include "adler32memcpy.h" +#include "sattypes.h" + +// 2 = 128 bit bus, 1 = 64 bit bus, 0 = 32 bit bus +const int kBusShift = 2; + +// Pattern and CRC data structure +struct PatternData { + const char *name; // Name of this pattern. + unsigned int *pat; // Data array. + unsigned int mask; // Size - 1. data[index & mask] is always valid. + unsigned char weight[4]; // Weighted frequency of this pattern. + // Each pattern has 32,64,128,256 width versions. + // All weights are added up, a random number is + // chosen between 0-sum(weights), and the + // appropriate pattern is chosen. Thus a weight of + // 1 is rare, a weight of 10 is 2x as likely to be + // chosen as a weight of 5. +}; + +// Data structure to access data patterns. +class Pattern { + public: + Pattern(); + ~Pattern(); + // Fill pattern data and calculate CRC. + int Initialize(const struct PatternData &pattern_init, + int buswidth, + bool invert, + int weight); + + // Access data members. + // "busshift_" allows for repeating each pattern word 1, 2, 4, etc. times. + // in order to create patterns of different width. + unsigned int pattern(unsigned int offset) { + unsigned int data = pattern_->pat[(offset >> busshift_) & pattern_->mask]; + if (inverse_) + data = ~data; + return data; + } + const AdlerChecksum *crc() {return crc_;} + unsigned int mask() {return pattern_->mask;} + unsigned int weight() {return weight_;} + const char *name() {return name_.c_str();} + + private: + int CalculateCrc(); + const struct PatternData *pattern_; + int busshift_; // Target data bus width. + bool inverse_; // Invert the data from the original pattern. + AdlerChecksum *crc_; // CRC of this pattern. + string name_; // The human readable pattern name. + int weight_; // This is the likelihood that this + // pattern will be chosen. + // We want to copy this! + // DISALLOW_COPY_AND_ASSIGN(Pattern); +}; + +// Object used to access global pattern list. +class PatternList { + public: + PatternList(); + ~PatternList(); + // Initialize pointers to global data patterns, and calculate CRC. + int Initialize(); + int Destroy(); + + // Return the pattern designated by index i. + Pattern *GetPattern(int i); + // Return a random pattern according to the specified weighted probability. + Pattern *GetRandomPattern(); + // Return the number of patterns available. + int Size() {return size_;} + + private: + vector<class Pattern> patterns_; + int weightcount_; // Total count of pattern weights. + unsigned int size_; + int initialized_; + DISALLOW_COPY_AND_ASSIGN(PatternList); +}; + +// CrcIncrement allows an abstracted way to add a 32bit +// value into a running CRC. This function should be fast, and +// generate meaningful CRCs for the types of data patterns that +// we are using here. +// This CRC formula may not be optimal, but it does work. +// It may be improved in the future. +static inline uint32 CrcIncrement(uint32 crc, uint32 expected, int index) { + uint32 addition = (expected ^ index); + uint32 carry = (addition & crc) >> 31; + + return crc + addition + carry; +} + + +#endif // STRESSAPPTEST_PATTERN_H_ diff --git a/src/queue.cc b/src/queue.cc new file mode 100644 index 0000000..d735e68 --- /dev/null +++ b/src/queue.cc @@ -0,0 +1,118 @@ +// Copyright 2006 Google Inc. All Rights Reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// queue.cc : simple thread safe queue implementation + +#include <stdlib.h> + +// This file must work with autoconf on its public version, +// so these includes are correct. +#include "queue.h" +#include "sattypes.h" + +// Page entry queue implementation follows. +// Push inserts pages, pop returns a random entry. + + +PageEntryQueue::PageEntryQueue(uint64 queuesize) { + // There must always be one empty queue location, + // since in == out => empty. + q_size_ = queuesize + 1; + pages_ = new struct page_entry[q_size_]; + nextin_ = 0; + nextout_ = 0; + popped_ = 0; + pushed_ = 0; + pthread_mutex_init(&q_mutex_, NULL); +} +PageEntryQueue::~PageEntryQueue() { + delete[] pages_; + pthread_mutex_destroy(&q_mutex_); +} + +// Add a page into this queue. +int PageEntryQueue::Push(struct page_entry *pe) { + int result = 0; + int64 nextnextin; + + if (!pe) + return 0; + + pthread_mutex_lock(&q_mutex_); + nextnextin = (nextin_ + 1) % q_size_; + + if (nextnextin != nextout_) { + pages_[nextin_] = *pe; + + nextin_ = nextnextin; + result = 1; + + pushed_++; + } + + pthread_mutex_unlock(&q_mutex_); + + return result; +} + +// Retrieve a random page from this queue. +int PageEntryQueue::PopRandom(struct page_entry *pe) { + int result = 0; + int64 lastin; + int64 entries; + int64 newindex; + struct page_entry tmp; + + if (!pe) + return 0; + + // TODO(nsanders): we should improve random to get 64 bit randoms, and make + // it more thread friendly. + uint64 rand = random(); + + int retval = pthread_mutex_lock(&q_mutex_); + if (retval) + logprintf(0, "Process Error: pthreads mutex failure %d\n", retval); + + + if (nextin_ != nextout_) { + // Randomized fetch. + // Swap random entry with next out. + { + lastin = (nextin_ - 1 + q_size_) % q_size_; + entries = (lastin - nextout_ + q_size_) % q_size_; + + newindex = nextout_; + if (entries) + newindex = ((rand % entries) + nextout_) % q_size_; + + // Swap the pages. + tmp = pages_[nextout_]; + pages_[nextout_] = pages_[newindex]; + pages_[newindex] = tmp; + } + + // Return next out page. + *pe = pages_[nextout_]; + + nextout_ = (nextout_ + 1) % q_size_; + result = 1; + + popped_++; + } + + pthread_mutex_unlock(&q_mutex_); + + return result; +} diff --git a/src/queue.h b/src/queue.h new file mode 100644 index 0000000..a6296b1 --- /dev/null +++ b/src/queue.h @@ -0,0 +1,85 @@ +// Copyright 2006 Google Inc. All Rights Reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// queue.h : simple queue api + +// This is an interface to a simple thread safe queue, +// used to hold data blocks and patterns. +// The order in which the blocks are returned is random. + +#ifndef STRESSAPPTEST_QUEUE_H_ // NOLINT +#define STRESSAPPTEST_QUEUE_H_ + +#include <sys/types.h> +#include <pthread.h> + +// This file must work with autoconf on its public version, +// so these includes are correct. +#include "sattypes.h" // NOLINT +#include "pattern.h" // NOLINT + +// Tag indicating no preference. +static const int kDontCareTag = -1; +// Tag indicating no preference. +static const int kInvalidTag = 0xf001; + + +// This describes a block of memory, and the expected fill pattern. +struct page_entry { + uint64 offset; + void *addr; + uint64 paddr; + class Pattern *pattern; + int32 tag; // These are tags for use in NUMA affinity or other uses. + uint32 touch; // Counter of the number of reads from this page. + uint64 ts; // Timestamp of the last read from this page. + class Pattern *lastpattern; // Expected Pattern at last read. +}; + +static inline void init_pe(struct page_entry *pe) { + pe->offset = 0; + pe->addr = NULL; + pe->pattern = NULL; + pe->tag = kInvalidTag; + pe->touch = 0; + pe->ts = 0; + pe->lastpattern = NULL; +} + +// This is a threadsafe randomized queue of pages for +// worker threads to use. +class PageEntryQueue { + public: + explicit PageEntryQueue(uint64 queuesize); + ~PageEntryQueue(); + + // Push a page onto the list. + int Push(struct page_entry *pe); + // Pop a random page off of the list. + int PopRandom(struct page_entry *pe); + + private: + struct page_entry *pages_; // Where the pages are held. + int64 nextin_; + int64 nextout_; + int64 q_size_; // Size of the queue. + int64 pushed_; // Number of pages pushed, total. + int64 popped_; // Number of pages popped, total. + pthread_mutex_t q_mutex_; + + DISALLOW_COPY_AND_ASSIGN(PageEntryQueue); +}; + + +#endif // MILES_TESTS_SAT_QUEUE_H_ NOLINT diff --git a/src/sat.cc b/src/sat.cc new file mode 100644 index 0000000..bed62b7 --- /dev/null +++ b/src/sat.cc @@ -0,0 +1,1890 @@ +// Copyright 2006 Google Inc. All Rights Reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// sat.cc : a stress test for stressful testing + +// stressapptest (or SAT, from Stressful Application Test) is a test +// designed to stress the system, as well as provide a comprehensive +// memory interface test. + +// stressapptest can be run using memory only, or using many system components. + +#include <errno.h> +#include <pthread.h> +#include <signal.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <sys/stat.h> +#include <sys/times.h> + +// #define __USE_GNU +// #define __USE_LARGEFILE64 +#include <fcntl.h> + +#include <list> +#include <string> + +// This file must work with autoconf on its public version, +// so these includes are correct. +#include "disk_blocks.h" +#include "logger.h" +#include "os.h" +#include "sat.h" +#include "sattypes.h" +#include "worker.h" + +// stressapptest versioning here. +#ifndef PACKAGE_VERSION +static const char* kVersion = "1.0.0"; +#else +static const char* kVersion = PACKAGE_VERSION; +#endif + +// Global stressapptest reference, for use by signal handler. +// This makes Sat objects not safe for multiple instances. +namespace { + Sat *g_sat = NULL; + + // Signal handler for catching break or kill. + // + // This must be installed after g_sat is assigned and while there is a single + // thread. + // + // This must be uninstalled while there is only a single thread, and of course + // before g_sat is cleared or deleted. + void SatHandleBreak(int signal) { + g_sat->Break(); + } +} + +// Opens the logfile for writing if necessary +bool Sat::InitializeLogfile() { + // Open logfile. + if (use_logfile_) { + logfile_ = open(logfilename_, + O_WRONLY | O_CREAT | O_DSYNC, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (logfile_ < 0) { + printf("Fatal Error: cannot open file %s for logging\n", + logfilename_); + bad_status(); + return false; + } + // We seek to the end once instead of opening in append mode because no + // other processes should be writing to it while this one exists. + if (lseek(logfile_, 0, SEEK_END) == -1) { + printf("Fatal Error: cannot seek to end of logfile (%s)\n", + logfilename_); + bad_status(); + return false; + } + Logger::GlobalLogger()->SetLogFd(logfile_); + } + return true; +} + +// Check that the environment is known and safe to run on. +// Return 1 if good, 0 if unsuppported. +bool Sat::CheckEnvironment() { + // Check that this is not a debug build. Debug builds lack + // enough performance to stress the system. +#if !defined NDEBUG + if (run_on_anything_) { + logprintf(1, "Log: Running DEBUG version of SAT, " + "with significantly reduced coverage.\n"); + } else { + logprintf(0, "Process Error: Running DEBUG version of SAT, " + "with significantly reduced coverage.\n"); + logprintf(0, "Log: Command line option '-A' bypasses this error.\n"); + bad_status(); + return false; + } +#elif !defined CHECKOPTS + #error Build system regression - COPTS disregarded. +#endif + + // Use all CPUs if nothing is specified. + if (memory_threads_ == -1) { + memory_threads_ = os_->num_cpus(); + logprintf(7, "Log: Defaulting to %d copy threads\n", memory_threads_); + } + + // Use all memory if no size is specified. + if (size_mb_ == 0) + size_mb_ = os_->FindFreeMemSize() / kMegabyte; + size_ = static_cast<int64>(size_mb_) * kMegabyte; + + // Autodetect file locations. + if (findfiles_ && (file_threads_ == 0)) { + // Get a space separated sting of disk locations. + list<string> locations = os_->FindFileDevices(); + + // Extract each one. + while (!locations.empty()) { + // Copy and remove the disk name. + string disk = locations.back(); + locations.pop_back(); + + logprintf(12, "Log: disk at %s\n", disk.c_str()); + file_threads_++; + filename_.push_back(disk + "/sat_disk.a"); + file_threads_++; + filename_.push_back(disk + "/sat_disk.b"); + } + } + + // We'd better have some memory by this point. + if (size_ < 1) { + logprintf(0, "Process Error: No memory found to test.\n"); + bad_status(); + return false; + } + + if (tag_mode_ && ((file_threads_ > 0) || + (disk_threads_ > 0) || + (net_threads_ > 0))) { + logprintf(0, "Process Error: Memory tag mode incompatible " + "with disk/network DMA.\n"); + bad_status(); + return false; + } + + // If platform is 32 bit Xeon, floor memory size to multiple of 4. + if (address_mode_ == 32) { + size_mb_ = (size_mb_ / 4) * 4; + size_ = size_mb_ * kMegabyte; + logprintf(1, "Log: Flooring memory allocation to multiple of 4: %lldMB\n", + size_mb_); + } + + // Check if this system is on the whitelist for supported systems. + if (!os_->IsSupported()) { + if (run_on_anything_) { + logprintf(1, "Log: Unsupported system. Running with reduced coverage.\n"); + // This is ok, continue on. + } else { + logprintf(0, "Process Error: Unsupported system, " + "no error reporting available\n"); + logprintf(0, "Log: Command line option '-A' bypasses this error.\n"); + bad_status(); + return false; + } + } + + return true; +} + +// Allocates memory to run the test on +bool Sat::AllocateMemory() { + // Allocate our test memory. + bool result = os_->AllocateTestMem(size_, paddr_base_); + if (!result) { + logprintf(0, "Process Error: failed to allocate memory\n"); + bad_status(); + return false; + } + return true; +} + +// Sets up access to data patterns +bool Sat::InitializePatterns() { + // Initialize pattern data. + patternlist_ = new PatternList(); + if (!patternlist_) { + logprintf(0, "Process Error: failed to allocate patterns\n"); + bad_status(); + return false; + } + if (!patternlist_->Initialize()) { + logprintf(0, "Process Error: failed to initialize patternlist\n"); + bad_status(); + return false; + } + return true; +} + +// Get any valid page, no tag specified. +bool Sat::GetValid(struct page_entry *pe) { + return GetValid(pe, kDontCareTag); +} + + +// Fetch and return empty and full pages into the empty and full pools. +bool Sat::GetValid(struct page_entry *pe, int32 tag) { + bool result = false; + // Get valid page depending on implementation. + if (pe_q_implementation_ == SAT_FINELOCK) + result = finelock_q_->GetValid(pe, tag); + else if (pe_q_implementation_ == SAT_ONELOCK) + result = valid_->PopRandom(pe); + + if (result) { + pe->addr = os_->PrepareTestMem(pe->offset, page_length_); // Map it. + + // Tag this access and current pattern. + pe->ts = os_->GetTimestamp(); + pe->lastpattern = pe->pattern; + + return (pe->addr != 0); // Return success or failure. + } + return false; +} + +bool Sat::PutValid(struct page_entry *pe) { + if (pe->addr != 0) + os_->ReleaseTestMem(pe->addr, pe->offset, page_length_); // Unmap the page. + pe->addr = 0; + + // Put valid page depending on implementation. + if (pe_q_implementation_ == SAT_FINELOCK) + return finelock_q_->PutValid(pe); + else if (pe_q_implementation_ == SAT_ONELOCK) + return valid_->Push(pe); + else + return false; +} + +// Get an empty page with any tag. +bool Sat::GetEmpty(struct page_entry *pe) { + return GetEmpty(pe, kDontCareTag); +} + +bool Sat::GetEmpty(struct page_entry *pe, int32 tag) { + bool result = false; + // Get empty page depending on implementation. + if (pe_q_implementation_ == SAT_FINELOCK) + result = finelock_q_->GetEmpty(pe, tag); + else if (pe_q_implementation_ == SAT_ONELOCK) + result = empty_->PopRandom(pe); + + if (result) { + pe->addr = os_->PrepareTestMem(pe->offset, page_length_); // Map it. + return (pe->addr != 0); // Return success or failure. + } + return false; +} + +bool Sat::PutEmpty(struct page_entry *pe) { + if (pe->addr != 0) + os_->ReleaseTestMem(pe->addr, pe->offset, page_length_); // Unmap the page. + pe->addr = 0; + + // Put empty page depending on implementation. + if (pe_q_implementation_ == SAT_FINELOCK) + return finelock_q_->PutEmpty(pe); + else if (pe_q_implementation_ == SAT_ONELOCK) + return empty_->Push(pe); + else + return false; +} + +// Set up the bitmap of physical pages in case we want to see which pages were +// accessed under this run of SAT. +void Sat::AddrMapInit() { + if (!do_page_map_) + return; + // Find about how much physical mem is in the system. + // TODO(nsanders): Find some way to get the max + // and min phys addr in the system. + uint64 maxsize = os_->FindFreeMemSize() * 4; + sat_assert(maxsize != 0); + + // Make a bitmask of this many pages. Assume that the memory is relatively + // zero based. This is true on x86, typically. + // This is one bit per page. + uint64 arraysize = maxsize / 4096 / 8; + unsigned char *bitmap = new unsigned char[arraysize]; + sat_assert(bitmap); + + // Mark every page as 0, not seen. + memset(bitmap, 0, arraysize); + + page_bitmap_size_ = maxsize; + page_bitmap_ = bitmap; +} + +// Add the 4k pages in this block to the array of pages SAT has seen. +void Sat::AddrMapUpdate(struct page_entry *pe) { + if (!do_page_map_) + return; + + // Go through 4k page blocks. + uint64 arraysize = page_bitmap_size_ / 4096 / 8; + + char *base = reinterpret_cast<char*>(pe->addr); + for (int i = 0; i < page_length_; i += 4096) { + uint64 paddr = os_->VirtualToPhysical(base + i); + + uint32 offset = paddr / 4096 / 8; + unsigned char mask = 1 << ((paddr / 4096) % 8); + + if (offset >= arraysize) { + logprintf(0, "Process Error: Physical address %#llx is " + "greater than expected %#llx.\n", + paddr, page_bitmap_size_); + sat_assert(0); + } + page_bitmap_[offset] |= mask; + } +} + +// Print out the physical memory ranges that SAT has accessed. +void Sat::AddrMapPrint() { + if (!do_page_map_) + return; + + uint64 pages = page_bitmap_size_ / 4096; + + uint64 last_page = 0; + bool valid_range = false; + + logprintf(4, "Log: Printing tested physical ranges.\n"); + + for (uint64 i = 0; i < pages; i ++) { + int offset = i / 8; + unsigned char mask = 1 << (i % 8); + + bool touched = page_bitmap_[offset] & mask; + if (touched && !valid_range) { + valid_range = true; + last_page = i * 4096; + } else if (!touched && valid_range) { + valid_range = false; + logprintf(4, "Log: %#016llx - %#016llx\n", last_page, (i * 4096) - 1); + } + } + logprintf(4, "Log: Done printing physical ranges.\n"); +} + +// Initializes page lists and fills pages with data patterns. +bool Sat::InitializePages() { + int result = 1; + // Calculate needed page totals. + int64 neededpages = memory_threads_ + + invert_threads_ + + check_threads_ + + net_threads_ + + file_threads_; + + // Empty-valid page ratio is adjusted depending on queue implementation. + // since fine-grain-locked queue keeps both valid and empty entries in the + // same queue and randomly traverse to find pages, the empty-valid ratio + // should be more even. + if (pe_q_implementation_ == SAT_FINELOCK) + freepages_ = pages_ / 5 * 2; // Mark roughly 2/5 of all pages as Empty. + else + freepages_ = (pages_ / 100) + (2 * neededpages); + + if (freepages_ < neededpages) { + logprintf(0, "Process Error: freepages < neededpages.\n"); + logprintf(1, "Stats: Total: %lld, Needed: %lld, Marked free: %lld\n", + static_cast<int64>(pages_), + static_cast<int64>(neededpages), + static_cast<int64>(freepages_)); + bad_status(); + return false; + } + + if (freepages_ > pages_/2) { + logprintf(0, "Process Error: not enough pages for IO\n"); + logprintf(1, "Stats: Total: %lld, Needed: %lld, Available: %lld\n", + static_cast<int64>(pages_), + static_cast<int64>(freepages_), + static_cast<int64>(pages_/2)); + bad_status(); + return false; + } + logprintf(12, "Log: Allocating pages, Total: %lld Free: %lld\n", + pages_, + freepages_); + + // Initialize page locations. + for (int64 i = 0; i < pages_; i++) { + struct page_entry pe; + init_pe(&pe); + pe.offset = i * page_length_; + result &= PutEmpty(&pe); + } + + if (!result) { + logprintf(0, "Process Error: while initializing empty_ list\n"); + bad_status(); + return false; + } + + // Fill valid pages with test patterns. + // Use fill threads to do this. + WorkerStatus fill_status; + WorkerVector fill_vector; + + logprintf(12, "Starting Fill threads: %d threads, %d pages\n", + fill_threads_, pages_); + // Initialize the fill threads. + for (int i = 0; i < fill_threads_; i++) { + FillThread *thread = new FillThread(); + thread->InitThread(i, this, os_, patternlist_, &fill_status); + if (i != fill_threads_ - 1) { + logprintf(12, "Starting Fill Threads %d: %d pages\n", + i, pages_ / fill_threads_); + thread->SetFillPages(pages_ / fill_threads_); + // The last thread finishes up all the leftover pages. + } else { + logprintf(12, "Starting Fill Threads %d: %d pages\n", + i, pages_ - pages_ / fill_threads_ * i); + thread->SetFillPages(pages_ - pages_ / fill_threads_ * i); + } + fill_vector.push_back(thread); + } + + // Spawn the fill threads. + fill_status.Initialize(); + for (WorkerVector::const_iterator it = fill_vector.begin(); + it != fill_vector.end(); ++it) + (*it)->SpawnThread(); + + // Reap the finished fill threads. + for (WorkerVector::const_iterator it = fill_vector.begin(); + it != fill_vector.end(); ++it) { + (*it)->JoinThread(); + if ((*it)->GetStatus() != 1) { + logprintf(0, "Thread %d failed with status %d at %.2f seconds\n", + (*it)->ThreadID(), (*it)->GetStatus(), + (*it)->GetRunDurationUSec() * 1.0/1000000); + bad_status(); + return false; + } + delete (*it); + } + fill_vector.clear(); + fill_status.Destroy(); + logprintf(12, "Log: Done filling pages.\n"); + logprintf(12, "Log: Allocating pages.\n"); + + AddrMapInit(); + + // Initialize page locations. + for (int64 i = 0; i < pages_; i++) { + struct page_entry pe; + // Only get valid pages with uninitialized tags here. + char buf[256]; + if (GetValid(&pe, kInvalidTag)) { + int64 paddr = os_->VirtualToPhysical(pe.addr); + int32 region = os_->FindRegion(paddr); + + os_->FindDimm(paddr, buf, sizeof(buf)); + if (i < 256) { + logprintf(12, "Log: address: %#llx, %s\n", paddr, buf); + } + region_[region]++; + pe.paddr = paddr; + pe.tag = 1 << region; + region_mask_ |= pe.tag; + + // Generate a physical region map + AddrMapUpdate(&pe); + + // Note: this does not allocate free pages among all regions + // fairly. However, with large enough (thousands) random number + // of pages being marked free in each region, the free pages + // count in each region end up pretty balanced. + if (i < freepages_) { + result &= PutEmpty(&pe); + } else { + result &= PutValid(&pe); + } + } else { + logprintf(0, "Log: didn't tag all pages. %d - %d = %d\n", + pages_, i, pages_ - i); + return false; + } + } + logprintf(12, "Log: Done allocating pages.\n"); + + AddrMapPrint(); + + for (int i = 0; i < 32; i++) { + if (region_mask_ & (1 << i)) { + region_count_++; + logprintf(12, "Log: Region %d: %d.\n", i, region_[i]); + } + } + logprintf(5, "Log: Region mask: 0x%x\n", region_mask_); + + return true; +} + +// Print SAT version info. +bool Sat::PrintVersion() { + logprintf(1, "Stats: SAT revision %s, %d bit binary\n", + kVersion, address_mode_); + logprintf(5, "Log: %s from %s\n", Timestamp(), BuildChangelist()); + + return true; +} + + +// Initializes the resources that SAT needs to run. +// This needs to be called before Run(), and after ParseArgs(). +// Returns true on success, false on error, and will exit() on help message. +bool Sat::Initialize() { + g_sat = this; + + // Initializes sync'd log file to ensure output is saved. + if (!InitializeLogfile()) + return false; + Logger::GlobalLogger()->StartThread(); + + logprintf(5, "Log: Commandline - %s\n", cmdline_.c_str()); + PrintVersion(); + + std::map<std::string, std::string> options; + + GoogleOsOptions(&options); + + // Initialize OS/Hardware interface. + os_ = OsLayerFactory(options); + if (!os_) { + bad_status(); + return false; + } + + if (min_hugepages_mbytes_ > 0) + os_->SetMinimumHugepagesSize(min_hugepages_mbytes_ * kMegabyte); + + if (!os_->Initialize()) { + logprintf(0, "Process Error: Failed to initialize OS layer\n"); + bad_status(); + delete os_; + return false; + } + + // Checks that OS/Build/Platform is supported. + if (!CheckEnvironment()) + return false; + + if (error_injection_) + os_->set_error_injection(true); + + // Run SAT in monitor only mode, do not continue to allocate resources. + if (monitor_mode_) { + logprintf(5, "Log: Running in monitor-only mode. " + "Will not allocate any memory nor run any stress test. " + "Only polling ECC errors.\n"); + return true; + } + + // Allocate the memory to test. + if (!AllocateMemory()) + return false; + + logprintf(5, "Stats: Starting SAT, %dM, %d seconds\n", + static_cast<int>(size_/kMegabyte), + runtime_seconds_); + + if (!InitializePatterns()) + return false; + + // Initialize memory allocation. + pages_ = size_ / page_length_; + + // Allocate page queue depending on queue implementation switch. + if (pe_q_implementation_ == SAT_FINELOCK) { + finelock_q_ = new FineLockPEQueue(pages_, page_length_); + if (finelock_q_ == NULL) + return false; + finelock_q_->set_os(os_); + os_->set_err_log_callback(finelock_q_->get_err_log_callback()); + } else if (pe_q_implementation_ == SAT_ONELOCK) { + empty_ = new PageEntryQueue(pages_); + valid_ = new PageEntryQueue(pages_); + if ((empty_ == NULL) || (valid_ == NULL)) + return false; + } + + if (!InitializePages()) { + logprintf(0, "Process Error: Initialize Pages failed\n"); + return false; + } + + return true; +} + +// Constructor and destructor. +Sat::Sat() { + // Set defaults, command line might override these. + runtime_seconds_ = 20; + page_length_ = kSatPageSize; + disk_pages_ = kSatDiskPage; + pages_ = 0; + size_mb_ = 0; + size_ = size_mb_ * kMegabyte; + min_hugepages_mbytes_ = 0; + freepages_ = 0; + paddr_base_ = 0; + + user_break_ = false; + verbosity_ = 8; + Logger::GlobalLogger()->SetVerbosity(verbosity_); + strict_ = 1; + warm_ = 0; + run_on_anything_ = 0; + use_logfile_ = 0; + logfile_ = 0; + // Detect 32/64 bit binary. + void *pvoid = 0; + address_mode_ = sizeof(pvoid) * 8; + error_injection_ = false; + crazy_error_injection_ = false; + max_errorcount_ = 0; // Zero means no early exit. + stop_on_error_ = false; + error_poll_ = true; + findfiles_ = false; + + do_page_map_ = false; + page_bitmap_ = 0; + page_bitmap_size_ = 0; + + // Cache coherency data initialization. + cc_test_ = false; // Flag to trigger cc threads. + cc_cacheline_count_ = 2; // Two datastructures of cache line size. + cc_inc_count_ = 1000; // Number of times to increment the shared variable. + cc_cacheline_data_ = 0; // Cache Line size datastructure. + + sat_assert(0 == pthread_mutex_init(&worker_lock_, NULL)); + file_threads_ = 0; + net_threads_ = 0; + listen_threads_ = 0; + // Default to autodetect number of cpus, and run that many threads. + memory_threads_ = -1; + invert_threads_ = 0; + fill_threads_ = 8; + check_threads_ = 0; + cpu_stress_threads_ = 0; + disk_threads_ = 0; + total_threads_ = 0; + + region_mask_ = 0; + region_count_ = 0; + for (int i = 0; i < 32; i++) { + region_[i] = 0; + } + region_mode_ = 0; + + errorcount_ = 0; + statuscount_ = 0; + + valid_ = 0; + empty_ = 0; + finelock_q_ = 0; + // Default to use fine-grain lock for better performance. + pe_q_implementation_ = SAT_FINELOCK; + + os_ = 0; + patternlist_ = 0; + logfilename_[0] = 0; + + read_block_size_ = 512; + write_block_size_ = -1; + segment_size_ = -1; + cache_size_ = -1; + blocks_per_segment_ = -1; + read_threshold_ = -1; + write_threshold_ = -1; + non_destructive_ = 1; + monitor_mode_ = 0; + tag_mode_ = 0; + random_threads_ = 0; + + pause_delay_ = 600; + pause_duration_ = 15; +} + +// Destructor. +Sat::~Sat() { + // We need to have called Cleanup() at this point. + // We should probably enforce this. +} + + +#define ARG_KVALUE(argument, variable, value) \ + if (!strcmp(argv[i], argument)) { \ + variable = value; \ + continue; \ + } + +#define ARG_IVALUE(argument, variable) \ + if (!strcmp(argv[i], argument)) { \ + i++; \ + if (i < argc) \ + variable = strtoull(argv[i], NULL, 0); \ + continue; \ + } + +#define ARG_SVALUE(argument, variable) \ + if (!strcmp(argv[i], argument)) { \ + i++; \ + if (i < argc) \ + snprintf(variable, sizeof(variable), "%s", argv[i]); \ + continue; \ + } + +// Configures SAT from command line arguments. +// This will call exit() given a request for +// self-documentation or unexpected args. +bool Sat::ParseArgs(int argc, char **argv) { + int i; + uint64 filesize = page_length_ * disk_pages_; + + // Parse each argument. + for (i = 1; i < argc; i++) { + // Switch to fall back to corase-grain-lock queue. (for benchmarking) + ARG_KVALUE("--coarse_grain_lock", pe_q_implementation_, SAT_ONELOCK); + + // Set number of megabyte to use. + ARG_IVALUE("-M", size_mb_); + + // Set minimum megabytes of hugepages to require. + ARG_IVALUE("-H", min_hugepages_mbytes_); + + // Set number of seconds to run. + ARG_IVALUE("-s", runtime_seconds_); + + // Set number of memory copy threads. + ARG_IVALUE("-m", memory_threads_); + + // Set number of memory invert threads. + ARG_IVALUE("-i", invert_threads_); + + // Set number of check-only threads. + ARG_IVALUE("-c", check_threads_); + + // Set number of cache line size datastructures. + ARG_IVALUE("--cc_inc_count", cc_inc_count_); + + // Set number of cache line size datastructures + ARG_IVALUE("--cc_line_count", cc_cacheline_count_); + + // Flag set when cache coherency tests need to be run + ARG_KVALUE("--cc_test", cc_test_, 1); + + // Set number of CPU stress threads. + ARG_IVALUE("-C", cpu_stress_threads_); + + // Set logfile name. + ARG_SVALUE("-l", logfilename_); + + // Verbosity level. + ARG_IVALUE("-v", verbosity_); + + // Set maximum number of errors to collect. Stop running after this many. + ARG_IVALUE("--max_errors", max_errorcount_); + + // Set pattern block size. + ARG_IVALUE("-p", page_length_); + + // Set pattern block size. + ARG_IVALUE("--filesize", filesize); + + // NUMA options. + ARG_KVALUE("--local_numa", region_mode_, kLocalNuma); + ARG_KVALUE("--remote_numa", region_mode_, kRemoteNuma); + + // Autodetect tempfile locations. + ARG_KVALUE("--findfiles", findfiles_, 1); + + // Inject errors to force miscompare code paths + ARG_KVALUE("--force_errors", error_injection_, true); + ARG_KVALUE("--force_errors_like_crazy", crazy_error_injection_, true); + if (crazy_error_injection_) + error_injection_ = true; + + // Stop immediately on any arror, for debugging HW problems. + ARG_KVALUE("--stop_on_errors", stop_on_error_, 1); + + // Don't use internal error polling, allow external detection. + ARG_KVALUE("--no_errors", error_poll_, 0); + + // Never check data as you go. + ARG_KVALUE("-F", strict_, 0); + + // Warm the cpu as you go. + ARG_KVALUE("-W", warm_, 1); + + // Allow runnign on unknown systems with base unimplemented OsLayer + ARG_KVALUE("-A", run_on_anything_, 1); + + // Size of read blocks for disk test. + ARG_IVALUE("--read-block-size", read_block_size_); + + // Size of write blocks for disk test. + ARG_IVALUE("--write-block-size", write_block_size_); + + // Size of segment for disk test. + ARG_IVALUE("--segment-size", segment_size_); + + // Size of disk cache size for disk test. + ARG_IVALUE("--cache-size", cache_size_); + + // Number of blocks to test per segment. + ARG_IVALUE("--blocks-per-segment", blocks_per_segment_); + + // Maximum time a block read should take before warning. + ARG_IVALUE("--read-threshold", read_threshold_); + + // Maximum time a block write should take before warning. + ARG_IVALUE("--write-threshold", write_threshold_); + + // Do not write anything to disk in the disk test. + ARG_KVALUE("--destructive", non_destructive_, 0); + + // Run SAT in monitor mode. No test load at all. + ARG_KVALUE("--monitor_mode", monitor_mode_, true); + + // Run SAT in address mode. Tag all cachelines by virt addr. + ARG_KVALUE("--tag_mode", tag_mode_, true); + + // Dump range map of tested pages.. + ARG_KVALUE("--do_page_map", do_page_map_, true); + + // Specify the physical address base to test. + ARG_IVALUE("--paddr_base", paddr_base_); + + // Specify the frequency for power spikes. + ARG_IVALUE("--pause_delay", pause_delay_); + + // Specify the duration of each pause (for power spikes). + ARG_IVALUE("--pause_duration", pause_duration_); + + // Disk device names + if (!strcmp(argv[i], "-d")) { + i++; + if (i < argc) { + disk_threads_++; + diskfilename_.push_back(string(argv[i])); + blocktables_.push_back(new DiskBlockTable()); + } + continue; + } + + // Set number of disk random threads for each disk write thread. + ARG_IVALUE("--random-threads", random_threads_); + + // Set a tempfile to use in a file thread. + if (!strcmp(argv[i], "-f")) { + i++; + if (i < argc) { + file_threads_++; + filename_.push_back(string(argv[i])); + } + continue; + } + + // Set a hostname to use in a network thread. + if (!strcmp(argv[i], "-n")) { + i++; + if (i < argc) { + net_threads_++; + ipaddrs_.push_back(string(argv[i])); + } + continue; + } + + // Run threads that listen for incoming SAT net connections. + ARG_KVALUE("--listen", listen_threads_, 1); + + if (CheckGoogleSpecificArgs(argc, argv, &i)) { + continue; + } + + // Default: + PrintVersion(); + PrintHelp(); + if (strcmp(argv[i], "-h") && strcmp(argv[i], "--help")) { + printf("\n Unknown argument %s\n", argv[i]); + bad_status(); + exit(1); + } + // Forget it, we printed the help, just bail. + // We don't want to print test status, or any log parser stuff. + exit(0); + } + + Logger::GlobalLogger()->SetVerbosity(verbosity_); + + // Update relevant data members with parsed input. + // Translate MB into bytes. + size_ = static_cast<int64>(size_mb_) * kMegabyte; + + // Set logfile flag. + if (strcmp(logfilename_, "")) + use_logfile_ = 1; + // Checks valid page length. + if (page_length_ && + !(page_length_ & (page_length_ - 1)) && + (page_length_ > 1023)) { + // Prints if we have changed from default. + if (page_length_ != kSatPageSize) + logprintf(12, "Log: Updating page size to %d\n", page_length_); + } else { + // Revert to default page length. + logprintf(6, "Process Error: " + "Invalid page size %d\n", page_length_); + page_length_ = kSatPageSize; + return false; + } + + // Set disk_pages_ if filesize or page size changed. + if (filesize != static_cast<uint64>(page_length_) * + static_cast<uint64>(disk_pages_)) { + disk_pages_ = filesize / page_length_; + if (disk_pages_ == 0) + disk_pages_ = 1; + } + + // Print each argument. + for (int i = 0; i < argc; i++) { + if (i) + cmdline_ += " "; + cmdline_ += argv[i]; + } + + return true; +} + +void Sat::PrintHelp() { + printf("Usage: ./sat(32|64) [options]\n" + " -M mbytes megabytes of ram to test\n" + " -H mbytes minimum megabytes of hugepages to require\n" + " -s seconds number of seconds to run\n" + " -m threads number of memory copy threads to run\n" + " -i threads number of memory invert threads to run\n" + " -C threads number of memory CPU stress threads to run\n" + " --findfiles find locations to do disk IO automatically\n" + " -d device add a direct write disk thread with block " + "device (or file) 'device'\n" + " -f filename add a disk thread with " + "tempfile 'filename'\n" + " -l logfile log output to file 'logfile'\n" + " --max_errors n exit early after finding 'n' errors\n" + " -v level verbosity (0-20), default is 8\n" + " -W Use more CPU-stressful memory copy\n" + " -A run in degraded mode on incompatible systems\n" + " -p pagesize size in bytes of memory chunks\n" + " --filesize size size of disk IO tempfiles\n" + " -n ipaddr add a network thread connecting to " + "system at 'ipaddr'\n" + " --listen run a thread to listen for and respond " + "to network threads.\n" + " --no_errors run without checking for ECC or other errors\n" + " --force_errors inject false errors to test error handling\n" + " --force_errors_like_crazy inject a lot of false errors " + "to test error handling\n" + " -F don't result check each transaction\n" + " --stop_on_errors Stop after finding the first error.\n" + " --read-block-size size of block for reading (-d)\n" + " --write-block-size size of block for writing (-d). If not " + "defined, the size of block for writing will be defined as the " + "size of block for reading\n" + " --segment-size size of segments to split disk into (-d)\n" + " --cache-size size of disk cache (-d)\n" + " --blocks-per-segment number of blocks to read/write per " + "segment per iteration (-d)\n" + " --read-threshold maximum time (in us) a block read should " + "take (-d)\n" + " --write-threshold maximum time (in us) a block write " + "should take (-d)\n" + " --random-threads number of random threads for each disk " + "write thread (-d)\n" + " --destructive write/wipe disk partition (-d)\n" + " --monitor_mode only do ECC error polling, no stress load.\n" + " --cc_test do the cache coherency testing\n" + " --cc_inc_count number of times to increment the " + "cacheline's member\n" + " --cc_line_count number of cache line sized datastructures " + "to allocate for the cache coherency threads to operate\n" + " --paddr_base allocate memory starting from this address\n" + " --pause_delay delay (in seconds) between power spikes\n" + " --pause_duration duration (in seconds) of each pause\n" + " --local_numa : choose memory regions associated with " + "each CPU to be tested by that CPU\n" + " --remote_numa : choose memory regions not associated with " + "each CPU to be tested by that CPU\n"); +} + +bool Sat::CheckGoogleSpecificArgs(int argc, char **argv, int *i) { + // Do nothing, no google-specific argument on public stressapptest + return false; +} + +void Sat::GoogleOsOptions(std::map<std::string, std::string> *options) { + // Do nothing, no OS-specific argument on public stressapptest +} + +// Launch the SAT task threads. Returns 0 on error. +void Sat::InitializeThreads() { + // Memory copy threads. + AcquireWorkerLock(); + + logprintf(12, "Log: Starting worker threads\n"); + WorkerVector *memory_vector = new WorkerVector(); + + // Error polling thread. + // This may detect ECC corrected errors, disk problems, or + // any other errors normally hidden from userspace. + WorkerVector *error_vector = new WorkerVector(); + if (error_poll_) { + ErrorPollThread *thread = new ErrorPollThread(); + thread->InitThread(total_threads_++, this, os_, patternlist_, + &continuous_status_); + + error_vector->insert(error_vector->end(), thread); + } else { + logprintf(5, "Log: Skipping error poll thread due to --no_errors flag\n"); + } + workers_map_.insert(make_pair(kErrorType, error_vector)); + + // Only start error poll threads for monitor-mode SAT, + // skip all other types of worker threads. + if (monitor_mode_) { + ReleaseWorkerLock(); + return; + } + + for (int i = 0; i < memory_threads_; i++) { + CopyThread *thread = new CopyThread(); + thread->InitThread(total_threads_++, this, os_, patternlist_, + &power_spike_status_); + + if ((region_count_ > 1) && (region_mode_)) { + int32 region = region_find(i % region_count_); + cpu_set_t *cpuset = os_->FindCoreMask(region); + sat_assert(cpuset); + if (region_mode_ == kLocalNuma) { + // Choose regions associated with this CPU. + thread->set_cpu_mask(cpuset); + thread->set_tag(1 << region); + } else if (region_mode_ == kRemoteNuma) { + // Choose regions not associated with this CPU.. + thread->set_cpu_mask(cpuset); + thread->set_tag(region_mask_ & ~(1 << region)); + } + } else { + cpu_set_t available_cpus; + thread->AvailableCpus(&available_cpus); + int cores = cpuset_count(&available_cpus); + // Don't restrict thread location if we have more than one + // thread per core. Not so good for performance. + if (cpu_stress_threads_ + memory_threads_ <= cores) { + // Place a thread on alternating cores first. + // This assures interleaved core use with no overlap. + int nthcore = i; + int nthbit = (((2 * nthcore) % cores) + + (((2 * nthcore) / cores) % 2)) % cores; + cpu_set_t all_cores; + cpuset_set_ab(&all_cores, 0, cores); + if (!cpuset_isequal(&available_cpus, &all_cores)) { + // We are assuming the bits are contiguous. + // Complain if this is not so. + logprintf(0, "Log: cores = %s, expected %s\n", + cpuset_format(&available_cpus).c_str(), + cpuset_format(&all_cores).c_str()); + } + + // Set thread affinity. + thread->set_cpu_mask_to_cpu(nthbit); + } + } + memory_vector->insert(memory_vector->end(), thread); + } + workers_map_.insert(make_pair(kMemoryType, memory_vector)); + + // File IO threads. + WorkerVector *fileio_vector = new WorkerVector(); + for (int i = 0; i < file_threads_; i++) { + FileThread *thread = new FileThread(); + thread->InitThread(total_threads_++, this, os_, patternlist_, + &power_spike_status_); + thread->SetFile(filename_[i].c_str()); + // Set disk threads high priority. They don't take much processor time, + // but blocking them will delay disk IO. + thread->SetPriority(WorkerThread::High); + + fileio_vector->insert(fileio_vector->end(), thread); + } + workers_map_.insert(make_pair(kFileIOType, fileio_vector)); + + // Net IO threads. + WorkerVector *netio_vector = new WorkerVector(); + WorkerVector *netslave_vector = new WorkerVector(); + if (listen_threads_ > 0) { + // Create a network slave thread. This listens for connections. + NetworkListenThread *thread = new NetworkListenThread(); + thread->InitThread(total_threads_++, this, os_, patternlist_, + &continuous_status_); + + netslave_vector->insert(netslave_vector->end(), thread); + } + for (int i = 0; i < net_threads_; i++) { + NetworkThread *thread = new NetworkThread(); + thread->InitThread(total_threads_++, this, os_, patternlist_, + &continuous_status_); + thread->SetIP(ipaddrs_[i].c_str()); + + netio_vector->insert(netio_vector->end(), thread); + } + workers_map_.insert(make_pair(kNetIOType, netio_vector)); + workers_map_.insert(make_pair(kNetSlaveType, netslave_vector)); + + // Result check threads. + WorkerVector *check_vector = new WorkerVector(); + for (int i = 0; i < check_threads_; i++) { + CheckThread *thread = new CheckThread(); + thread->InitThread(total_threads_++, this, os_, patternlist_, + &continuous_status_); + + check_vector->insert(check_vector->end(), thread); + } + workers_map_.insert(make_pair(kCheckType, check_vector)); + + // Memory invert threads. + logprintf(12, "Log: Starting invert threads\n"); + WorkerVector *invert_vector = new WorkerVector(); + for (int i = 0; i < invert_threads_; i++) { + InvertThread *thread = new InvertThread(); + thread->InitThread(total_threads_++, this, os_, patternlist_, + &continuous_status_); + + invert_vector->insert(invert_vector->end(), thread); + } + workers_map_.insert(make_pair(kInvertType, invert_vector)); + + // Disk stress threads. + WorkerVector *disk_vector = new WorkerVector(); + WorkerVector *random_vector = new WorkerVector(); + logprintf(12, "Log: Starting disk stress threads\n"); + for (int i = 0; i < disk_threads_; i++) { + // Creating write threads + DiskThread *thread = new DiskThread(blocktables_[i]); + thread->InitThread(total_threads_++, this, os_, patternlist_, + &power_spike_status_); + thread->SetDevice(diskfilename_[i].c_str()); + if (thread->SetParameters(read_block_size_, write_block_size_, + segment_size_, cache_size_, + blocks_per_segment_, + read_threshold_, write_threshold_, + non_destructive_)) { + disk_vector->insert(disk_vector->end(), thread); + } else { + logprintf(12, "Log: DiskThread::SetParameters() failed\n"); + delete thread; + } + + for (int j = 0; j < random_threads_; j++) { + // Creating random threads + RandomDiskThread *rthread = new RandomDiskThread(blocktables_[i]); + rthread->InitThread(total_threads_++, this, os_, patternlist_, + &power_spike_status_); + rthread->SetDevice(diskfilename_[i].c_str()); + if (rthread->SetParameters(read_block_size_, write_block_size_, + segment_size_, cache_size_, + blocks_per_segment_, + read_threshold_, write_threshold_, + non_destructive_)) { + random_vector->insert(random_vector->end(), rthread); + } else { + logprintf(12, "Log: RandomDiskThread::SetParameters() failed\n"); + delete rthread; + } + } + } + + workers_map_.insert(make_pair(kDiskType, disk_vector)); + workers_map_.insert(make_pair(kRandomDiskType, random_vector)); + + // CPU stress threads. + WorkerVector *cpu_vector = new WorkerVector(); + logprintf(12, "Log: Starting cpu stress threads\n"); + for (int i = 0; i < cpu_stress_threads_; i++) { + CpuStressThread *thread = new CpuStressThread(); + thread->InitThread(total_threads_++, this, os_, patternlist_, + &continuous_status_); + + // Don't restrict thread location if we have more than one + // thread per core. Not so good for performance. + cpu_set_t available_cpus; + thread->AvailableCpus(&available_cpus); + int cores = cpuset_count(&available_cpus); + if (cpu_stress_threads_ + memory_threads_ <= cores) { + // Place a thread on alternating cores first. + // Go in reverse order for CPU stress threads. This assures interleaved + // core use with no overlap. + int nthcore = (cores - 1) - i; + int nthbit = (((2 * nthcore) % cores) + + (((2 * nthcore) / cores) % 2)) % cores; + cpu_set_t all_cores; + cpuset_set_ab(&all_cores, 0, cores); + if (!cpuset_isequal(&available_cpus, &all_cores)) { + logprintf(0, "Log: cores = %s, expected %s\n", + cpuset_format(&available_cpus).c_str(), + cpuset_format(&all_cores).c_str()); + } + + // Set thread affinity. + thread->set_cpu_mask_to_cpu(nthbit); + } + + + cpu_vector->insert(cpu_vector->end(), thread); + } + workers_map_.insert(make_pair(kCPUType, cpu_vector)); + + // CPU Cache Coherency Threads - one for each core available. + if (cc_test_) { + WorkerVector *cc_vector = new WorkerVector(); + logprintf(12, "Log: Starting cpu cache coherency threads\n"); + + // Allocate the shared datastructure to be worked on by the threads. + cc_cacheline_data_ = reinterpret_cast<cc_cacheline_data*>( + malloc(sizeof(cc_cacheline_data) * cc_cacheline_count_)); + sat_assert(cc_cacheline_data_ != NULL); + + // Initialize the strucutre. + memset(cc_cacheline_data_, 0, + sizeof(cc_cacheline_data) * cc_cacheline_count_); + + int num_cpus = CpuCount(); + // Allocate all the nums once so that we get a single chunk + // of contiguous memory. + int *num; + int err_result = posix_memalign( + reinterpret_cast<void**>(&num), + kCacheLineSize, sizeof(*num) * num_cpus * cc_cacheline_count_); + sat_assert(err_result == 0); + + int cline; + for (cline = 0; cline < cc_cacheline_count_; cline++) { + memset(num, 0, sizeof(num_cpus) * num_cpus); + cc_cacheline_data_[cline].num = num; + num += num_cpus; + } + + int tnum; + for (tnum = 0; tnum < num_cpus; tnum++) { + CpuCacheCoherencyThread *thread = + new CpuCacheCoherencyThread(cc_cacheline_data_, cc_cacheline_count_, + tnum, cc_inc_count_); + thread->InitThread(total_threads_++, this, os_, patternlist_, + &continuous_status_); + // Pin the thread to a particular core. + thread->set_cpu_mask_to_cpu(tnum); + + // Insert the thread into the vector. + cc_vector->insert(cc_vector->end(), thread); + } + workers_map_.insert(make_pair(kCCType, cc_vector)); + } + ReleaseWorkerLock(); +} + +// Return the number of cpus actually present in the machine. +int Sat::CpuCount() { + return sysconf(_SC_NPROCESSORS_CONF); +} + +// Notify and reap worker threads. +void Sat::JoinThreads() { + logprintf(12, "Log: Joining worker threads\n"); + power_spike_status_.StopWorkers(); + continuous_status_.StopWorkers(); + + AcquireWorkerLock(); + for (WorkerMap::const_iterator map_it = workers_map_.begin(); + map_it != workers_map_.end(); ++map_it) { + for (WorkerVector::const_iterator it = map_it->second->begin(); + it != map_it->second->end(); ++it) { + logprintf(12, "Log: Joining thread %d\n", (*it)->ThreadID()); + (*it)->JoinThread(); + } + } + ReleaseWorkerLock(); + + QueueStats(); + + // Finish up result checking. + // Spawn 4 check threads to minimize check time. + logprintf(12, "Log: Finished countdown, begin to result check\n"); + WorkerStatus reap_check_status; + WorkerVector reap_check_vector; + + // No need for check threads for monitor mode. + if (!monitor_mode_) { + // Initialize the check threads. + for (int i = 0; i < fill_threads_; i++) { + CheckThread *thread = new CheckThread(); + thread->InitThread(total_threads_++, this, os_, patternlist_, + &reap_check_status); + logprintf(12, "Log: Finished countdown, begin to result check\n"); + reap_check_vector.push_back(thread); + } + } + + reap_check_status.Initialize(); + // Check threads should be marked to stop ASAP. + reap_check_status.StopWorkers(); + + // Spawn the check threads. + for (WorkerVector::const_iterator it = reap_check_vector.begin(); + it != reap_check_vector.end(); ++it) { + logprintf(12, "Log: Spawning thread %d\n", (*it)->ThreadID()); + (*it)->SpawnThread(); + } + + // Join the check threads. + for (WorkerVector::const_iterator it = reap_check_vector.begin(); + it != reap_check_vector.end(); ++it) { + logprintf(12, "Log: Joining thread %d\n", (*it)->ThreadID()); + (*it)->JoinThread(); + } + + // Reap all children. Stopped threads should have already ended. + // Result checking threads will end when they have finished + // result checking. + logprintf(12, "Log: Join all outstanding threads\n"); + + // Find all errors. + errorcount_ = GetTotalErrorCount(); + + AcquireWorkerLock(); + for (WorkerMap::const_iterator map_it = workers_map_.begin(); + map_it != workers_map_.end(); ++map_it) { + for (WorkerVector::const_iterator it = map_it->second->begin(); + it != map_it->second->end(); ++it) { + logprintf(12, "Log: Reaping thread status %d\n", (*it)->ThreadID()); + if ((*it)->GetStatus() != 1) { + logprintf(0, "Process Error: Thread %d failed with status %d at " + "%.2f seconds\n", + (*it)->ThreadID(), (*it)->GetStatus(), + (*it)->GetRunDurationUSec()*1.0/1000000); + bad_status(); + } + int priority = 12; + if ((*it)->GetErrorCount()) + priority = 5; + logprintf(priority, "Log: Thread %d found %lld hardware incidents\n", + (*it)->ThreadID(), (*it)->GetErrorCount()); + } + } + ReleaseWorkerLock(); + + + // Add in any errors from check threads. + for (WorkerVector::const_iterator it = reap_check_vector.begin(); + it != reap_check_vector.end(); ++it) { + logprintf(12, "Log: Reaping thread status %d\n", (*it)->ThreadID()); + if ((*it)->GetStatus() != 1) { + logprintf(0, "Process Error: Thread %d failed with status %d at " + "%.2f seconds\n", + (*it)->ThreadID(), (*it)->GetStatus(), + (*it)->GetRunDurationUSec()*1.0/1000000); + bad_status(); + } + errorcount_ += (*it)->GetErrorCount(); + int priority = 12; + if ((*it)->GetErrorCount()) + priority = 5; + logprintf(priority, "Log: Thread %d found %lld hardware incidents\n", + (*it)->ThreadID(), (*it)->GetErrorCount()); + delete (*it); + } + reap_check_vector.clear(); + reap_check_status.Destroy(); +} + +// Print queuing information. +void Sat::QueueStats() { + finelock_q_->QueueAnalysis(); +} + +void Sat::AnalysisAllStats() { + float max_runtime_sec = 0.; + float total_data = 0.; + float total_bandwidth = 0.; + float thread_runtime_sec = 0.; + + for (WorkerMap::const_iterator map_it = workers_map_.begin(); + map_it != workers_map_.end(); ++map_it) { + for (WorkerVector::const_iterator it = map_it->second->begin(); + it != map_it->second->end(); ++it) { + thread_runtime_sec = (*it)->GetRunDurationUSec()*1.0/1000000; + total_data += (*it)->GetMemoryCopiedData(); + total_data += (*it)->GetDeviceCopiedData(); + if (thread_runtime_sec > max_runtime_sec) { + max_runtime_sec = thread_runtime_sec; + } + } + } + + total_bandwidth = total_data / max_runtime_sec; + + logprintf(0, "Stats: Completed: %.2fM in %.2fs %.2fMB/s, " + "with %d hardware incidents, %d errors\n", + total_data, + max_runtime_sec, + total_bandwidth, + errorcount_, + statuscount_); +} + +void Sat::MemoryStats() { + float memcopy_data = 0.; + float memcopy_bandwidth = 0.; + WorkerMap::const_iterator mem_it = workers_map_.find( + static_cast<int>(kMemoryType)); + WorkerMap::const_iterator file_it = workers_map_.find( + static_cast<int>(kFileIOType)); + sat_assert(mem_it != workers_map_.end()); + sat_assert(file_it != workers_map_.end()); + for (WorkerVector::const_iterator it = mem_it->second->begin(); + it != mem_it->second->end(); ++it) { + memcopy_data += (*it)->GetMemoryCopiedData(); + memcopy_bandwidth += (*it)->GetMemoryBandwidth(); + } + for (WorkerVector::const_iterator it = file_it->second->begin(); + it != file_it->second->end(); ++it) { + memcopy_data += (*it)->GetMemoryCopiedData(); + memcopy_bandwidth += (*it)->GetMemoryBandwidth(); + } + GoogleMemoryStats(&memcopy_data, &memcopy_bandwidth); + logprintf(4, "Stats: Memory Copy: %.2fM at %.2fMB/s\n", + memcopy_data, + memcopy_bandwidth); +} + +void Sat::GoogleMemoryStats(float *memcopy_data, + float *memcopy_bandwidth) { + // Do nothing, should be implemented by subclasses. +} + +void Sat::FileStats() { + float file_data = 0.; + float file_bandwidth = 0.; + WorkerMap::const_iterator file_it = workers_map_.find( + static_cast<int>(kFileIOType)); + sat_assert(file_it != workers_map_.end()); + for (WorkerVector::const_iterator it = file_it->second->begin(); + it != file_it->second->end(); ++it) { + file_data += (*it)->GetDeviceCopiedData(); + file_bandwidth += (*it)->GetDeviceBandwidth(); + } + logprintf(4, "Stats: File Copy: %.2fM at %.2fMB/s\n", + file_data, + file_bandwidth); +} + +void Sat::CheckStats() { + float check_data = 0.; + float check_bandwidth = 0.; + WorkerMap::const_iterator check_it = workers_map_.find( + static_cast<int>(kCheckType)); + sat_assert(check_it != workers_map_.end()); + for (WorkerVector::const_iterator it = check_it->second->begin(); + it != check_it->second->end(); ++it) { + check_data += (*it)->GetMemoryCopiedData(); + check_bandwidth += (*it)->GetMemoryBandwidth(); + } + logprintf(4, "Stats: Data Check: %.2fM at %.2fMB/s\n", + check_data, + check_bandwidth); +} + +void Sat::NetStats() { + float net_data = 0.; + float net_bandwidth = 0.; + WorkerMap::const_iterator netio_it = workers_map_.find( + static_cast<int>(kNetIOType)); + WorkerMap::const_iterator netslave_it = workers_map_.find( + static_cast<int>(kNetSlaveType)); + sat_assert(netio_it != workers_map_.end()); + sat_assert(netslave_it != workers_map_.end()); + for (WorkerVector::const_iterator it = netio_it->second->begin(); + it != netio_it->second->end(); ++it) { + net_data += (*it)->GetDeviceCopiedData(); + net_bandwidth += (*it)->GetDeviceBandwidth(); + } + for (WorkerVector::const_iterator it = netslave_it->second->begin(); + it != netslave_it->second->end(); ++it) { + net_data += (*it)->GetDeviceCopiedData(); + net_bandwidth += (*it)->GetDeviceBandwidth(); + } + logprintf(4, "Stats: Net Copy: %.2fM at %.2fMB/s\n", + net_data, + net_bandwidth); +} + +void Sat::InvertStats() { + float invert_data = 0.; + float invert_bandwidth = 0.; + WorkerMap::const_iterator invert_it = workers_map_.find( + static_cast<int>(kInvertType)); + sat_assert(invert_it != workers_map_.end()); + for (WorkerVector::const_iterator it = invert_it->second->begin(); + it != invert_it->second->end(); ++it) { + invert_data += (*it)->GetMemoryCopiedData(); + invert_bandwidth += (*it)->GetMemoryBandwidth(); + } + logprintf(4, "Stats: Invert Data: %.2fM at %.2fMB/s\n", + invert_data, + invert_bandwidth); +} + +void Sat::DiskStats() { + float disk_data = 0.; + float disk_bandwidth = 0.; + WorkerMap::const_iterator disk_it = workers_map_.find( + static_cast<int>(kDiskType)); + WorkerMap::const_iterator random_it = workers_map_.find( + static_cast<int>(kRandomDiskType)); + sat_assert(disk_it != workers_map_.end()); + sat_assert(random_it != workers_map_.end()); + for (WorkerVector::const_iterator it = disk_it->second->begin(); + it != disk_it->second->end(); ++it) { + disk_data += (*it)->GetDeviceCopiedData(); + disk_bandwidth += (*it)->GetDeviceBandwidth(); + } + for (WorkerVector::const_iterator it = random_it->second->begin(); + it != random_it->second->end(); ++it) { + disk_data += (*it)->GetDeviceCopiedData(); + disk_bandwidth += (*it)->GetDeviceBandwidth(); + } + + logprintf(4, "Stats: Disk: %.2fM at %.2fMB/s\n", + disk_data, + disk_bandwidth); +} + +// Process worker thread data for bandwidth information, and error results. +// You can add more methods here just subclassing SAT. +void Sat::RunAnalysis() { + AnalysisAllStats(); + MemoryStats(); + FileStats(); + NetStats(); + CheckStats(); + InvertStats(); + DiskStats(); +} + +// Get total error count, summing across all threads.. +int64 Sat::GetTotalErrorCount() { + int64 errors = 0; + + AcquireWorkerLock(); + for (WorkerMap::const_iterator map_it = workers_map_.begin(); + map_it != workers_map_.end(); ++map_it) { + for (WorkerVector::const_iterator it = map_it->second->begin(); + it != map_it->second->end(); ++it) { + errors += (*it)->GetErrorCount(); + } + } + ReleaseWorkerLock(); + return errors; +} + + +void Sat::SpawnThreads() { + logprintf(12, "Log: Initializing WorkerStatus objects\n"); + power_spike_status_.Initialize(); + continuous_status_.Initialize(); + logprintf(12, "Log: Spawning worker threads\n"); + for (WorkerMap::const_iterator map_it = workers_map_.begin(); + map_it != workers_map_.end(); ++map_it) { + for (WorkerVector::const_iterator it = map_it->second->begin(); + it != map_it->second->end(); ++it) { + logprintf(12, "Log: Spawning thread %d\n", (*it)->ThreadID()); + (*it)->SpawnThread(); + } + } +} + +// Delete used worker thread objects. +void Sat::DeleteThreads() { + logprintf(12, "Log: Deleting worker threads\n"); + for (WorkerMap::const_iterator map_it = workers_map_.begin(); + map_it != workers_map_.end(); ++map_it) { + for (WorkerVector::const_iterator it = map_it->second->begin(); + it != map_it->second->end(); ++it) { + logprintf(12, "Log: Deleting thread %d\n", (*it)->ThreadID()); + delete (*it); + } + delete map_it->second; + } + workers_map_.clear(); + logprintf(12, "Log: Destroying WorkerStatus objects\n"); + power_spike_status_.Destroy(); + continuous_status_.Destroy(); +} + +namespace { +// Calculates the next time an action in Sat::Run() should occur, based on a +// schedule derived from a start point and a regular frequency. +// +// Using frequencies instead of intervals with their accompanying drift allows +// users to better predict when the actions will occur throughout a run. +// +// Arguments: +// frequency: seconds +// start: unixtime +// now: unixtime +// +// Returns: unixtime +inline time_t NextOccurance(time_t frequency, time_t start, time_t now) { + return start + frequency + (((now - start) / frequency) * frequency); +} +} + +// Run the actual test. +bool Sat::Run() { + // Install signal handlers to gracefully exit in the middle of a run. + // + // Why go through this whole rigmarole? It's the only standards-compliant + // (C++ and POSIX) way to handle signals in a multithreaded program. + // Specifically: + // + // 1) (C++) The value of a variable not of type "volatile sig_atomic_t" is + // unspecified upon entering a signal handler and, if modified by the + // handler, is unspecified after leaving the handler. + // + // 2) (POSIX) After the value of a variable is changed in one thread, another + // thread is only guaranteed to see the new value after both threads have + // acquired or released the same mutex or rwlock, synchronized to the + // same barrier, or similar. + // + // #1 prevents the use of #2 in a signal handler, so the signal handler must + // be called in the same thread that reads the "volatile sig_atomic_t" + // variable it sets. We enforce that by blocking the signals in question in + // the worker threads, forcing them to be handled by this thread. + logprintf(12, "Log: Installing signal handlers\n"); + sigset_t new_blocked_signals; + sigemptyset(&new_blocked_signals); + sigaddset(&new_blocked_signals, SIGINT); + sigaddset(&new_blocked_signals, SIGTERM); + sigset_t prev_blocked_signals; + pthread_sigmask(SIG_BLOCK, &new_blocked_signals, &prev_blocked_signals); + sighandler_t prev_sigint_handler = signal(SIGINT, SatHandleBreak); + sighandler_t prev_sigterm_handler = signal(SIGTERM, SatHandleBreak); + + // Kick off all the worker threads. + logprintf(12, "Log: Launching worker threads\n"); + InitializeThreads(); + SpawnThreads(); + pthread_sigmask(SIG_SETMASK, &prev_blocked_signals, NULL); + + logprintf(12, "Log: Starting countdown with %d seconds\n", runtime_seconds_); + + // In seconds. + static const time_t kSleepFrequency = 5; + // All of these are in seconds. You probably want them to be >= + // kSleepFrequency and multiples of kSleepFrequency, but neither is necessary. + static const time_t kInjectionFrequency = 10; + static const time_t kPrintFrequency = 10; + + const time_t start = time(NULL); + const time_t end = start + runtime_seconds_; + time_t now = start; + time_t next_print = start + kPrintFrequency; + time_t next_pause = start + pause_delay_; + time_t next_resume = 0; + time_t next_injection; + if (crazy_error_injection_) { + next_injection = start + kInjectionFrequency; + } else { + next_injection = 0; + } + + while (now < end) { + // This is an int because it's for logprintf(). + const int seconds_remaining = end - now; + + if (user_break_) { + // Handle early exit. + logprintf(0, "Log: User exiting early (%d seconds remaining)\n", + seconds_remaining); + break; + } + + // If we have an error limit, check it here and see if we should exit. + if (max_errorcount_ != 0) { + uint64 errors = GetTotalErrorCount(); + if (errors > max_errorcount_) { + logprintf(0, "Log: Exiting early (%d seconds remaining) " + "due to excessive failures (%lld)\n", + seconds_remaining, + errors); + break; + } + } + + if (now >= next_print) { + // Print a count down message. + logprintf(5, "Log: Seconds remaining: %d\n", seconds_remaining); + next_print = NextOccurance(kPrintFrequency, start, now); + } + + if (next_injection && now >= next_injection) { + // Inject an error. + logprintf(4, "Log: Injecting error (%d seconds remaining)\n", + seconds_remaining); + struct page_entry src; + GetValid(&src); + src.pattern = patternlist_->GetPattern(0); + PutValid(&src); + next_injection = NextOccurance(kInjectionFrequency, start, now); + } + + if (next_pause && now >= next_pause) { + // Tell worker threads to pause in preparation for a power spike. + logprintf(4, "Log: Pausing worker threads in preparation for power spike " + "(%d seconds remaining)\n", seconds_remaining); + power_spike_status_.PauseWorkers(); + logprintf(12, "Log: Worker threads paused\n"); + next_pause = 0; + next_resume = now + pause_duration_; + } + + if (next_resume && now >= next_resume) { + // Tell worker threads to resume in order to cause a power spike. + logprintf(4, "Log: Resuming worker threads to cause a power spike (%d " + "seconds remaining)\n", seconds_remaining); + power_spike_status_.ResumeWorkers(); + logprintf(12, "Log: Worker threads resumed\n"); + next_pause = NextOccurance(pause_delay_, start, now); + next_resume = 0; + } + + sat_sleep(NextOccurance(kSleepFrequency, start, now) - now); + now = time(NULL); + } + + JoinThreads(); + + logprintf(0, "Stats: Found %lld hardware incidents\n", errorcount_); + + if (!monitor_mode_) + RunAnalysis(); + + DeleteThreads(); + + logprintf(12, "Log: Uninstalling signal handlers\n"); + signal(SIGINT, prev_sigint_handler); + signal(SIGTERM, prev_sigterm_handler); + + return true; +} + +// Clean up all resources. +bool Sat::Cleanup() { + g_sat = NULL; + Logger::GlobalLogger()->StopThread(); + Logger::GlobalLogger()->SetStdoutOnly(); + if (logfile_) { + close(logfile_); + logfile_ = 0; + } + if (patternlist_) { + patternlist_->Destroy(); + delete patternlist_; + patternlist_ = 0; + } + if (os_) { + os_->FreeTestMem(); + delete os_; + os_ = 0; + } + if (empty_) { + delete empty_; + empty_ = 0; + } + if (valid_) { + delete valid_; + valid_ = 0; + } + if (finelock_q_) { + delete finelock_q_; + finelock_q_ = 0; + } + if (page_bitmap_) { + delete[] page_bitmap_; + } + + for (size_t i = 0; i < blocktables_.size(); i++) { + delete blocktables_[i]; + } + + if (cc_cacheline_data_) { + // The num integer arrays for all the cacheline structures are + // allocated as a single chunk. The pointers in the cacheline struct + // are populated accordingly. Hence calling free on the first + // cacheline's num's address is going to free the entire array. + // TODO(aganti): Refactor this to have a class for the cacheline + // structure (currently defined in worker.h) and clean this up + // in the destructor of that class. + if (cc_cacheline_data_[0].num) { + free(cc_cacheline_data_[0].num); + } + free(cc_cacheline_data_); + } + + sat_assert(0 == pthread_mutex_destroy(&worker_lock_)); + + return true; +} + + +// Pretty print really obvious results. +bool Sat::PrintResults() { + bool result = true; + + logprintf(4, "\n"); + if (statuscount_) { + logprintf(4, "Status: FAIL - test encountered procedural errors\n"); + result = false; + } else if (errorcount_) { + logprintf(4, "Status: FAIL - test discovered HW problems\n"); + result = false; + } else { + logprintf(4, "Status: PASS - please verify no corrected errors\n"); + } + logprintf(4, "\n"); + + return result; +} + +// Helper functions. +void Sat::AcquireWorkerLock() { + sat_assert(0 == pthread_mutex_lock(&worker_lock_)); +} +void Sat::ReleaseWorkerLock() { + sat_assert(0 == pthread_mutex_unlock(&worker_lock_)); +} + +void logprintf(int priority, const char *format, ...) { + va_list args; + va_start(args, format); + Logger::GlobalLogger()->VLogF(priority, format, args); + va_end(args); +} diff --git a/src/sat.h b/src/sat.h new file mode 100644 index 0000000..b48f519 --- /dev/null +++ b/src/sat.h @@ -0,0 +1,310 @@ +// Copyright 2006 Google Inc. All Rights Reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// sat.h : sat stress test object interface and data structures + +#ifndef STRESSAPPTEST_SAT_H_ +#define STRESSAPPTEST_SAT_H_ + +#include <signal.h> + +#include <map> +#include <string> +#include <vector> + +// This file must work with autoconf on its public version, +// so these includes are correct. +#include "finelock_queue.h" +#include "queue.h" +#include "sattypes.h" +#include "worker.h" +#include "os.h" + +// SAT stress test class. +class Sat { + public: + // Enum for page queue implementation switch. + enum PageQueueType { SAT_ONELOCK, SAT_FINELOCK }; + + Sat(); + virtual ~Sat(); + + // Read configuration from arguments. Called first. + bool ParseArgs(int argc, char **argv); + virtual bool CheckGoogleSpecificArgs(int argc, char **argv, int *i); + // Initialize data structures, subclasses, and resources, + // based on command line args. + // Called after ParseArgs(). + bool Initialize(); + + // Execute the test. Initialize() and ParseArgs() must be called first. + // This must be called from a single-threaded program. + bool Run(); + + // Pretty print result summary. + // Called after Run(). + // Return value is success or failure of the SAT run, *not* of this function! + bool PrintResults(); + + // Pretty print version info. + bool PrintVersion(); + + // Pretty print help. + virtual void PrintHelp(); + + // Clean up allocations and resources. + // Called last. + bool Cleanup(); + + // Abort Run(). Only for use by Run()-installed signal handlers. + void Break() { user_break_ = true; } + + // Fetch and return empty and full pages into the empty and full pools. + bool GetValid(struct page_entry *pe); + bool PutValid(struct page_entry *pe); + bool GetEmpty(struct page_entry *pe); + bool PutEmpty(struct page_entry *pe); + + bool GetValid(struct page_entry *pe, int32 tag); + bool GetEmpty(struct page_entry *pe, int32 tag); + + // Accessor functions. + int verbosity() const { return verbosity_; } + int logfile() const { return logfile_; } + int page_length() const { return page_length_; } + int disk_pages() const { return disk_pages_; } + int strict() const { return strict_; } + int tag_mode() const { return tag_mode_; } + int status() const { return statuscount_; } + void bad_status() { statuscount_++; } + int errors() const { return errorcount_; } + int warm() const { return warm_; } + bool stop_on_error() const { return stop_on_error_; } + int32 region_mask() const { return region_mask_; } + // Semi-accessor to find the "nth" region to avoid replicated bit searching.. + int32 region_find(int32 num) const { + for (int i = 0; i < 32; i++) { + if ((1 << i) & region_mask_) { + if (num == 0) + return i; + num--; + } + } + return 0; + } + + // Causes false errors for unittesting. + // Setting to "true" causes errors to be injected. + void set_error_injection(bool errors) { error_injection_ = errors; } + bool error_injection() const { return error_injection_; } + + protected: + // Opens log file for writing. Returns 0 on failure. + bool InitializeLogfile(); + // Checks for supported environment. Returns 0 on failure. + bool CheckEnvironment(); + // Allocates size_ bytes of test memory. + bool AllocateMemory(); + // Initializes datapattern reference structures. + bool InitializePatterns(); + // Initializes test memory with datapatterns. + bool InitializePages(); + + // Start up worker threads. + virtual void InitializeThreads(); + // Spawn worker threads. + void SpawnThreads(); + // Reap worker threads. + void JoinThreads(); + // Run bandwidth and error analysis. + virtual void RunAnalysis(); + // Delete worker threads. + void DeleteThreads(); + + // Return the number of cpus in the system. + int CpuCount(); + + // Collect error counts from threads. + int64 GetTotalErrorCount(); + + // Command line arguments. + string cmdline_; + + // Memory and test configuration. + int runtime_seconds_; // Seconds to run. + int page_length_; // Length of each memory block. + int64 pages_; // Number of memory blocks. + int64 size_; // Size of memory tested, in bytes. + int64 size_mb_; // Size of memory tested, in MB. + int64 min_hugepages_mbytes_; // Minimum hugepages size. + int64 freepages_; // How many invalid pages we need. + int disk_pages_; // Number of pages per temp file. + uint64 paddr_base_; // Physical address base. + + // Control flags. + volatile sig_atomic_t user_break_; // User has signalled early exit. Used as + // a boolean. + int verbosity_; // How much to print. + int strict_; // Check results per transaction. + int warm_; // FPU warms CPU while coying. + int address_mode_; // 32 or 64 bit binary. + bool stop_on_error_; // Exit immendiately on any error. + bool findfiles_; // Autodetect tempfile locations. + + bool error_injection_; // Simulate errors, for unittests. + bool crazy_error_injection_; // Simulate lots of errors. + uint64 max_errorcount_; // Number of errors before forced exit. + int run_on_anything_; // Ignore unknown machine ereor. + int use_logfile_; // Log to a file. + char logfilename_[255]; // Name of file to log to. + int logfile_; // File handle to log to. + + // Disk thread options. + int read_block_size_; // Size of block to read from disk. + int write_block_size_; // Size of block to write to disk. + int64 segment_size_; // Size of segment to split disk into. + int cache_size_; // Size of disk cache. + int blocks_per_segment_; // Number of blocks to test per segment. + int read_threshold_; // Maximum time (in us) a read should take + // before warning of a slow read. + int write_threshold_; // Maximum time (in us) a write should + // take before warning of a slow write. + int non_destructive_; // Whether to use non-destructive mode for + // the disk test. + + // Generic Options. + int monitor_mode_; // Switch for monitor-only mode SAT. + // This switch trumps most of the other + // argument, as SAT will only run error + // polling threads. + int tag_mode_; // Do tagging of memory and strict + // checking for misplaced cachelines. + + bool do_page_map_; // Should we print a list of used pages? + unsigned char *page_bitmap_; // Store bitmap of physical pages seen. + uint64 page_bitmap_size_; // Length of physical memory represented. + + // Cpu Cache Coherency Options. + bool cc_test_; // Flag to decide whether to start the + // cache coherency threads. + int cc_cacheline_count_; // Number of cache line size structures. + int cc_inc_count_; // Number of times to increment the shared + // cache lines structure members. + + // Thread control. + int file_threads_; // Threads of file IO. + int net_threads_; // Threads of network IO. + int listen_threads_; // Threads for network IO to connect. + int memory_threads_; // Threads of memcpy. + int invert_threads_; // Threads of invert. + int fill_threads_; // Threads of memset. + int check_threads_; // Threads of strcmp. + int cpu_stress_threads_; // Threads of CPU stress workload. + int disk_threads_; // Threads of disk test. + int random_threads_; // Number of random disk threads. + int total_threads_; // Total threads used. + bool error_poll_; // Poll for system errors. + + // Resources. + cc_cacheline_data *cc_cacheline_data_; // The cache line sized datastructure + // used by the ccache threads + // (in worker.h). + vector<string> filename_; // Filenames for file IO. + vector<string> ipaddrs_; // Addresses for network IO. + vector<string> diskfilename_; // Filename for disk IO device. + // Block table for IO device. + vector<DiskBlockTable*> blocktables_; + + int32 region_mask_; // Bitmask of available NUMA regions. + int32 region_count_; // Count of available NUMA regions. + int32 region_[32]; // Pagecount per region. + int region_mode_; // What to do with NUMA hints? + static const int kLocalNuma = 1; // Target local memory. + static const int kRemoteNuma = 2; // Target remote memory. + + // Results. + int64 errorcount_; // Total hardware incidents seen. + int statuscount_; // Total test errors seen. + + // Thread type constants and types + enum ThreadType { + kMemoryType = 0, + kFileIOType = 1, + kNetIOType = 2, + kNetSlaveType = 3, + kCheckType = 4, + kInvertType = 5, + kDiskType = 6, + kRandomDiskType = 7, + kCPUType = 8, + kErrorType = 9, + kCCType = 10 + }; + + // Helper functions. + virtual void AcquireWorkerLock(); + virtual void ReleaseWorkerLock(); + pthread_mutex_t worker_lock_; // Lock access to the worker thread structure. + typedef vector<WorkerThread*> WorkerVector; + typedef map<int, WorkerVector*> WorkerMap; + // Contains all worker threads. + WorkerMap workers_map_; + // Delay between power spikes. + time_t pause_delay_; + // The duration of each pause (for power spikes). + time_t pause_duration_; + // For the workers we pause and resume to create power spikes. + WorkerStatus power_spike_status_; + // For the workers we never pause. + WorkerStatus continuous_status_; + + class OsLayer *os_; // Os abstraction: put hacks here. + class PatternList *patternlist_; // Access to global data patterns. + + // RunAnalysis methods + void AnalysisAllStats(); // Summary of all runs. + void MemoryStats(); + void FileStats(); + void NetStats(); + void CheckStats(); + void InvertStats(); + void DiskStats(); + + void QueueStats(); + + // Physical page use reporting. + void AddrMapInit(); + void AddrMapUpdate(struct page_entry *pe); + void AddrMapPrint(); + + // additional memory data from google-specific tests. + virtual void GoogleMemoryStats(float *memcopy_data, + float *memcopy_bandwidth); + + virtual void GoogleOsOptions(std::map<std::string, std::string> *options); + + // Page queues, only one of (valid_+empty_) or (finelock_q_) will be used + // at a time. A commandline switch controls which queue implementation will + // be used. + class PageEntryQueue *valid_; // Page queue structure, valid pages. + class PageEntryQueue *empty_; // Page queue structure, free pages. + class FineLockPEQueue *finelock_q_; // Page queue with fine-grain locks + Sat::PageQueueType pe_q_implementation_; // Queue implementation switch + + DISALLOW_COPY_AND_ASSIGN(Sat); +}; + +Sat *SatFactory(); + +#endif // STRESSAPPTEST_SAT_H_ diff --git a/src/sat_factory.cc b/src/sat_factory.cc new file mode 100644 index 0000000..5cf3e4c --- /dev/null +++ b/src/sat_factory.cc @@ -0,0 +1,21 @@ +// Copyright 2008 Google Inc. All Rights Reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// sat_factory.h : factory for SAT + +#include "sat.h" // NOLINT + +Sat *SatFactory() { + return new Sat(); +} diff --git a/src/sattypes.h b/src/sattypes.h new file mode 100644 index 0000000..96bf13b --- /dev/null +++ b/src/sattypes.h @@ -0,0 +1,187 @@ +// Copyright 2006 Google Inc. All Rights Reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STRESSAPPTEST_SATTYPES_H_ +#define STRESSAPPTEST_SATTYPES_H_ + +#include <arpa/inet.h> +#include <sched.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <time.h> +#include <string.h> +#include <algorithm> +#include <string> + +#ifdef HAVE_CONFIG_H // Built using autoconf +#include "stressapptest_config.h" +using namespace std; +using namespace __gnu_cxx; + +typedef signed long long int64; +typedef signed int int32; +typedef signed short int int16; +typedef signed char int8; + +typedef unsigned long long uint64; +typedef unsigned int uint32; +typedef unsigned short uint16; +typedef unsigned char uint8; + +#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&); \ + void operator=(const TypeName&) + +inline const char* Timestamp() { + return STRESSAPPTEST_TIMESTAMP; +} + +inline const char* BuildChangelist() { + return "open source release"; +} + +static const bool kOpenSource = true; +#else +static const bool kOpenSource = false; + #include "googlesattypes.h" +#endif +// Workaround to allow 32/64 bit conversion +// without running into strict aliasing problems. +union datacast_t { + uint64 l64; + struct { + uint32 l; + uint32 h; + } l32; +}; + + +// File sync'd print to console and log +void logprintf(int priority, const char *format, ...); + +// We print to stderr ourselves first in case we're in such a bad state that the +// logger can't work. +#define sat_assert(x) \ +{\ + if (!(x)) {\ + fprintf(stderr, "Assertion failed at %s:%d\n", __FILE__, __LINE__);\ + logprintf(0, "Assertion failed at %s:%d\n", __FILE__, __LINE__);\ + exit(1);\ + }\ +} + +#if !defined(CPU_SETSIZE) + // Define type and macros for cpu mask operations + // Note: this code is hacked together to deal with difference + // function signatures across versions of glibc, ie those that take + // cpu_set_t versus those that take unsigned long. -johnhuang + typedef uint64 cpu_set_t; + #define CPU_SETSIZE (sizeof(cpu_set_t) * 8) + #define CPU_ISSET(index, cpu_set_ptr) (*(cpu_set_ptr) & 1ull << (index)) + #define CPU_SET(index, cpu_set_ptr) (*(cpu_set_ptr) |= 1ull << (index)) + #define CPU_ZERO(cpu_set_ptr) (*(cpu_set_ptr) = 0) + #define CPU_CLR(index, cpu_set_ptr) (*(cpu_set_ptr) &= ~(1ull << (index))) +#endif + +static inline bool cpuset_isequal(const cpu_set_t *c1, const cpu_set_t *c2) { + for (int i = 0; i < CPU_SETSIZE; ++i) + if ((CPU_ISSET(i, c1) != 0) != (CPU_ISSET(i, c2) != 0)) + return false; + return true; +} + +static inline bool cpuset_issubset(const cpu_set_t *c1, const cpu_set_t *c2) { + for (int i = 0; i < CPU_SETSIZE; ++i) + if (CPU_ISSET(i, c1) && !CPU_ISSET(i, c2)) + return false; + return true; +} + +static inline int cpuset_count(const cpu_set_t *cpuset) { + int count = 0; + for (int i = 0; i < CPU_SETSIZE; ++i) + if (CPU_ISSET(i, cpuset)) + ++count; + return count; +} + +static inline void cpuset_set_ab(cpu_set_t *cpuset, int a, int b) { + CPU_ZERO(cpuset); + for (int i = a; i < b; ++i) + CPU_SET(i, cpuset); +} + +static inline string cpuset_format(const cpu_set_t *cpuset) { + string format; + int digit = 0, last_non_zero_size = 1; + for (int i = 0; i < CPU_SETSIZE; ++i) { + if (CPU_ISSET(i, cpuset)) { + digit |= 1 << (i & 3); + } + if ((i & 3) == 3) { + format += char(digit <= 9 ? '0' + digit: 'A' + digit - 10); + if (digit) { + last_non_zero_size = format.size(); + digit = 0; + } + } + } + if (digit) { + format += char(digit <= 9 ? '0' + digit: 'A' + digit - 10); + last_non_zero_size = format.size(); + } + format.erase(last_non_zero_size); + reverse(format.begin(), format.end()); + return format; +} + +static const int32 kUSleepOneSecond = 1000000; + +// This is guaranteed not to use signals. +inline bool sat_usleep(int32 microseconds) { + timespec req; + req.tv_sec = microseconds / 1000000; + // Convert microseconds argument to nano seconds. + req.tv_nsec = (microseconds % 1000000) * 1000; + return nanosleep(&req, NULL) == 0; +} + +// This is guaranteed not to use signals. +inline bool sat_sleep(time_t seconds) { + timespec req; + req.tv_sec = seconds; + req.tv_nsec = 0; + return nanosleep(&req, NULL) == 0; +} + +// Get an error code description for use in error messages. +// +// Args: +// error_num: an errno error code +inline string ErrorString(int error_num) { + char buf[256]; + return string(strerror_r(error_num, buf, sizeof buf)); +} + +// Define handy constants here +static const int kTicksPerSec = 100; +static const int kMegabyte = (1024LL*1024LL); +static const int kSatDiskPageMax = 32; +static const int kSatDiskPage = 8; +static const int kSatPageSize = (1024LL*1024LL); +static const int kCacheLineSize = 64; +static const uint16_t kNetworkPort = 19996; + +#endif // STRESSAPPTEST_SATTYPES_H_ diff --git a/src/stressapptest_config.h.in b/src/stressapptest_config.h.in new file mode 100644 index 0000000..6ae6e5a --- /dev/null +++ b/src/stressapptest_config.h.in @@ -0,0 +1,222 @@ +/* src/stressapptest_config.h.in. Generated from configure.ac by autoheader. */ + +/* Define to 1 if the `closedir' function returns void instead of `int'. */ +#undef CLOSEDIR_VOID + +/* Define to 1 if you have the <arpa/inet.h> header file. */ +#undef HAVE_ARPA_INET_H + +/* Define to 1 if you have the declaration of `strerror_r', and to 0 if you + don't. */ +#undef HAVE_DECL_STRERROR_R + +/* Define to 1 if you have the <dirent.h> header file, and it defines `DIR'. + */ +#undef HAVE_DIRENT_H + +/* Define to 1 if you don't have `vprintf' but do have `_doprnt.' */ +#undef HAVE_DOPRNT + +/* Define to 1 if you have the <fcntl.h> header file. */ +#undef HAVE_FCNTL_H + +/* Define to 1 if you have the `gettimeofday' function. */ +#undef HAVE_GETTIMEOFDAY + +/* Define to 1 if you have the <inttypes.h> header file. */ +#undef HAVE_INTTYPES_H + +/* Define to 1 if you have the <libaio.h> header file. */ +#undef HAVE_LIBAIO_H + +/* Define to 1 if you have the <malloc.h> header file. */ +#undef HAVE_MALLOC_H + +/* Define to 1 if you have the <memory.h> header file. */ +#undef HAVE_MEMORY_H + +/* Define to 1 if you have the `memset' function. */ +#undef HAVE_MEMSET + +/* Define to 1 if you have the <ndir.h> header file, and it defines `DIR'. */ +#undef HAVE_NDIR_H + +/* Define to 1 if you have the <netdb.h> header file. */ +#undef HAVE_NETDB_H + +/* Define to 1 if you have the <pthread.h> header file. */ +#undef HAVE_PTHREAD_H + +/* Define to 1 if you have the `select' function. */ +#undef HAVE_SELECT + +/* Define to 1 if you have the `socket' function. */ +#undef HAVE_SOCKET + +/* Define to 1 if stdbool.h conforms to C99. */ +#undef HAVE_STDBOOL_H + +/* Define to 1 if you have the <stdint.h> header file. */ +#undef HAVE_STDINT_H + +/* Define to 1 if you have the <stdlib.h> header file. */ +#undef HAVE_STDLIB_H + +/* Define to 1 if you have the `strerror_r' function. */ +#undef HAVE_STRERROR_R + +/* Define to 1 if you have the <strings.h> header file. */ +#undef HAVE_STRINGS_H + +/* Define to 1 if you have the <string.h> header file. */ +#undef HAVE_STRING_H + +/* Define to 1 if you have the `strtol' function. */ +#undef HAVE_STRTOL + +/* Define to 1 if you have the `strtoull' function. */ +#undef HAVE_STRTOULL + +/* Define to 1 if you have the <sys/dir.h> header file, and it defines `DIR'. + */ +#undef HAVE_SYS_DIR_H + +/* Define to 1 if you have the <sys/ioctl.h> header file. */ +#undef HAVE_SYS_IOCTL_H + +/* Define to 1 if you have the <sys/ndir.h> header file, and it defines `DIR'. + */ +#undef HAVE_SYS_NDIR_H + +/* Define to 1 if you have the <sys/select.h> header file. */ +#undef HAVE_SYS_SELECT_H + +/* Define to 1 if you have the <sys/shm.h> header file. */ +#undef HAVE_SYS_SHM_H + +/* Define to 1 if you have the <sys/socket.h> header file. */ +#undef HAVE_SYS_SOCKET_H + +/* Define to 1 if you have the <sys/stat.h> header file. */ +#undef HAVE_SYS_STAT_H + +/* Define to 1 if you have the <sys/time.h> header file. */ +#undef HAVE_SYS_TIME_H + +/* Define to 1 if you have the <sys/types.h> header file. */ +#undef HAVE_SYS_TYPES_H + +/* Define to 1 if you have the <unistd.h> header file. */ +#undef HAVE_UNISTD_H + +/* Define to 1 if you have the `vprintf' function. */ +#undef HAVE_VPRINTF + +/* Define to 1 if the system has the type `_Bool'. */ +#undef HAVE__BOOL + +/* Name of package */ +#undef PACKAGE + +/* Define to the address where bug reports for this package should be sent. */ +#undef PACKAGE_BUGREPORT + +/* Define to the full name of this package. */ +#undef PACKAGE_NAME + +/* Define to the full name and version of this package. */ +#undef PACKAGE_STRING + +/* Define to the one symbol short name of this package. */ +#undef PACKAGE_TARNAME + +/* Define to the home page for this package. */ +#undef PACKAGE_URL + +/* Define to the version of this package. */ +#undef PACKAGE_VERSION + +/* Define as the return type of signal handlers (`int' or `void'). */ +#undef RETSIGTYPE + +/* Define to the type of arg 1 for `select'. */ +#undef SELECT_TYPE_ARG1 + +/* Define to the type of args 2, 3 and 4 for `select'. */ +#undef SELECT_TYPE_ARG234 + +/* Define to the type of arg 5 for `select'. */ +#undef SELECT_TYPE_ARG5 + +/* Define to 1 if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* Define to 1 if strerror_r returns char *. */ +#undef STRERROR_R_CHAR_P + +/* Defined if the target CPU is armv7a */ +#undef STRESSAPPTEST_CPU_ARMV7A + +/* Defined if the target CPU is i686 */ +#undef STRESSAPPTEST_CPU_I686 + +/* Defined if the target CPU is PowerPC */ +#undef STRESSAPPTEST_CPU_PPC + +/* Defined if the target CPU is x86_64 */ +#undef STRESSAPPTEST_CPU_X86_64 + +/* Defined if the target OS is BSD based */ +#undef STRESSAPPTEST_OS_BSD + +/* Defined if the target OS is OSX */ +#undef STRESSAPPTEST_OS_DARWIN + +/* Defined if the target OS is Linux */ +#undef STRESSAPPTEST_OS_LINUX + +/* Timestamp when ./configure was executed */ +#undef STRESSAPPTEST_TIMESTAMP + +/* Define to 1 if you can safely include both <sys/time.h> and <time.h>. */ +#undef TIME_WITH_SYS_TIME + +/* Version number of package */ +#undef VERSION + +/* Define to empty if `const' does not conform to ANSI C. */ +#undef const + +/* Define to `__inline__' or `__inline' if that's what the C compiler + calls it, or to nothing if 'inline' is not supported under any name. */ +#ifndef __cplusplus +#undef inline +#endif + +/* Define to `int' if <sys/types.h> does not define. */ +#undef pid_t + +/* Define to the equivalent of the C99 'restrict' keyword, or to + nothing if this is not supported. Do not define if restrict is + supported directly. */ +#undef restrict +/* Work around a bug in Sun C++: it does not support _Restrict or + __restrict__, even though the corresponding Sun C compiler ends up with + "#define restrict _Restrict" or "#define restrict __restrict__" in the + previous line. Perhaps some future version of Sun C++ will work with + restrict; if so, hopefully it defines __RESTRICT like Sun C does. */ +#if defined __SUNPRO_CC && !defined __RESTRICT +# define _Restrict +# define __restrict__ +#endif + +/* Define to `int' if <sys/types.h> does not define. */ +#undef ssize_t + +/* Define to the type of an unsigned integer type of width exactly 16 bits if + such a type exists and the standard includes do not define it. */ +#undef uint16_t + +/* Define to empty if the keyword `volatile' does not work. Warning: valid + code using `volatile' can become incorrect without. Disable with care. */ +#undef volatile diff --git a/src/worker.cc b/src/worker.cc new file mode 100644 index 0000000..2fab28e --- /dev/null +++ b/src/worker.cc @@ -0,0 +1,3344 @@ +// Copyright 2006 Google Inc. All Rights Reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// worker.cc : individual tasks that can be run in combination to +// stress the system + +#include <errno.h> +#include <pthread.h> +#include <sched.h> +#include <signal.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdint.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include <sys/select.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/times.h> + +// These are necessary, but on by default +// #define __USE_GNU +// #define __USE_LARGEFILE64 +#include <fcntl.h> +#include <sys/socket.h> +#include <netdb.h> +#include <arpa/inet.h> +#include <linux/unistd.h> // for gettid + +// For size of block device +#include <sys/ioctl.h> +#include <linux/fs.h> +// For asynchronous I/O +#include <libaio.h> + +#include <sys/syscall.h> + +#include <set> +#include <string> + +// This file must work with autoconf on its public version, +// so these includes are correct. +#include "error_diag.h" // NOLINT +#include "os.h" // NOLINT +#include "pattern.h" // NOLINT +#include "queue.h" // NOLINT +#include "sat.h" // NOLINT +#include "sattypes.h" // NOLINT +#include "worker.h" // NOLINT + +// Syscalls +// Why ubuntu, do you hate gettid so bad? +#if !defined(__NR_gettid) + #define __NR_gettid 224 +#endif + +#define gettid() syscall(__NR_gettid) +#if !defined(CPU_SETSIZE) +_syscall3(int, sched_getaffinity, pid_t, pid, + unsigned int, len, cpu_set_t*, mask) +_syscall3(int, sched_setaffinity, pid_t, pid, + unsigned int, len, cpu_set_t*, mask) +#endif + +// Linux aio syscalls. +#if !defined(__NR_io_setup) +#error "No aio headers inculded, please install libaio." +#endif + +namespace { + // Get HW core ID from cpuid instruction. + inline int apicid(void) { + int cpu; +#if defined(STRESSAPPTEST_CPU_X86_64) || defined(STRESSAPPTEST_CPU_I686) + __asm __volatile("cpuid" : "=b" (cpu) : "a" (1) : "cx", "dx"); +#elif defined(STRESSAPPTEST_CPU_ARMV7A) + #warning "Unsupported CPU type ARMV7A: unable to determine core ID." + cpu = 0; +#else + #warning "Unsupported CPU type: unable to determine core ID." + cpu = 0; +#endif + return (cpu >> 24); + } + + // Work around the sad fact that there are two (gnu, xsi) incompatible + // versions of strerror_r floating around google. Awesome. + bool sat_strerror(int err, char *buf, int len) { + buf[0] = 0; + char *errmsg = reinterpret_cast<char*>(strerror_r(err, buf, len)); + int retval = reinterpret_cast<int64>(errmsg); + if (retval == 0) + return true; + if (retval == -1) + return false; + if (errmsg != buf) { + strncpy(buf, errmsg, len); + buf[len - 1] = 0; + } + return true; + } + + + inline uint64 addr_to_tag(void *address) { + return reinterpret_cast<uint64>(address); + } +} + +#if !defined(O_DIRECT) +// Sometimes this isn't available. +// Disregard if it's not defined. + #define O_DIRECT 0 +#endif + +// A struct to hold captured errors, for later reporting. +struct ErrorRecord { + uint64 actual; // This is the actual value read. + uint64 reread; // This is the actual value, reread. + uint64 expected; // This is what it should have been. + uint64 *vaddr; // This is where it was (or wasn't). + char *vbyteaddr; // This is byte specific where the data was (or wasn't). + uint64 paddr; // This is the bus address, if available. + uint64 *tagvaddr; // This holds the tag value if this data was tagged. + uint64 tagpaddr; // This holds the physical address corresponding to the tag. +}; + +// This is a helper function to create new threads with pthreads. +static void *ThreadSpawnerGeneric(void *ptr) { + WorkerThread *worker = static_cast<WorkerThread*>(ptr); + worker->StartRoutine(); + return NULL; +} + +void WorkerStatus::Initialize() { + sat_assert(0 == pthread_mutex_init(&num_workers_mutex_, NULL)); + sat_assert(0 == pthread_rwlock_init(&status_rwlock_, NULL)); + sat_assert(0 == pthread_barrier_init(&pause_barrier_, NULL, + num_workers_ + 1)); +} + +void WorkerStatus::Destroy() { + sat_assert(0 == pthread_mutex_destroy(&num_workers_mutex_)); + sat_assert(0 == pthread_rwlock_destroy(&status_rwlock_)); + sat_assert(0 == pthread_barrier_destroy(&pause_barrier_)); +} + +void WorkerStatus::PauseWorkers() { + if (SetStatus(PAUSE) != PAUSE) + WaitOnPauseBarrier(); +} + +void WorkerStatus::ResumeWorkers() { + if (SetStatus(RUN) == PAUSE) + WaitOnPauseBarrier(); +} + +void WorkerStatus::StopWorkers() { + if (SetStatus(STOP) == PAUSE) + WaitOnPauseBarrier(); +} + +bool WorkerStatus::ContinueRunning() { + // This loop is an optimization. We use it to immediately re-check the status + // after resuming from a pause, instead of returning and waiting for the next + // call to this function. + for (;;) { + switch (GetStatus()) { + case RUN: + return true; + case PAUSE: + // Wait for the other workers to call this function so that + // PauseWorkers() can return. + WaitOnPauseBarrier(); + // Wait for ResumeWorkers() to be called. + WaitOnPauseBarrier(); + break; + case STOP: + return false; + } + } +} + +bool WorkerStatus::ContinueRunningNoPause() { + return (GetStatus() != STOP); +} + +void WorkerStatus::RemoveSelf() { + // Acquire a read lock on status_rwlock_ while (status_ != PAUSE). + for (;;) { + AcquireStatusReadLock(); + if (status_ != PAUSE) + break; + // We need to obey PauseWorkers() just like ContinueRunning() would, so that + // the other threads won't wait on pause_barrier_ forever. + ReleaseStatusLock(); + // Wait for the other workers to call this function so that PauseWorkers() + // can return. + WaitOnPauseBarrier(); + // Wait for ResumeWorkers() to be called. + WaitOnPauseBarrier(); + } + + // This lock would be unnecessary if we held a write lock instead of a read + // lock on status_rwlock_, but that would also force all threads calling + // ContinueRunning() to wait on this one. Using a separate lock avoids that. + AcquireNumWorkersLock(); + // Decrement num_workers_ and reinitialize pause_barrier_, which we know isn't + // in use because (status != PAUSE). + sat_assert(0 == pthread_barrier_destroy(&pause_barrier_)); + sat_assert(0 == pthread_barrier_init(&pause_barrier_, NULL, num_workers_)); + --num_workers_; + ReleaseNumWorkersLock(); + + // Release status_rwlock_. + ReleaseStatusLock(); +} + + +// Parent thread class. +WorkerThread::WorkerThread() { + status_ = false; + pages_copied_ = 0; + errorcount_ = 0; + runduration_usec_ = 1; + priority_ = Normal; + worker_status_ = NULL; + thread_spawner_ = &ThreadSpawnerGeneric; + tag_mode_ = false; +} + +WorkerThread::~WorkerThread() {} + +// Constructors. Just init some default values. +FillThread::FillThread() { + num_pages_to_fill_ = 0; +} + +// Initialize file name to empty. +FileThread::FileThread() { + filename_ = ""; + devicename_ = ""; + pass_ = 0; + page_io_ = true; + crc_page_ = -1; + local_page_ = NULL; +} + +// If file thread used bounce buffer in memory, account for the extra +// copy for memory bandwidth calculation. +float FileThread::GetMemoryCopiedData() { + if (!os_->normal_mem()) + return GetCopiedData(); + else + return 0; +} + +// Initialize target hostname to be invalid. +NetworkThread::NetworkThread() { + snprintf(ipaddr_, sizeof(ipaddr_), "Unknown"); + sock_ = 0; +} + +// Initialize? +NetworkSlaveThread::NetworkSlaveThread() { +} + +// Initialize? +NetworkListenThread::NetworkListenThread() { +} + +// Init member variables. +void WorkerThread::InitThread(int thread_num_init, + class Sat *sat_init, + class OsLayer *os_init, + class PatternList *patternlist_init, + WorkerStatus *worker_status) { + sat_assert(worker_status); + worker_status->AddWorkers(1); + + thread_num_ = thread_num_init; + sat_ = sat_init; + os_ = os_init; + patternlist_ = patternlist_init; + worker_status_ = worker_status; + + AvailableCpus(&cpu_mask_); + tag_ = 0xffffffff; + + tag_mode_ = sat_->tag_mode(); +} + + +// Use pthreads to prioritize a system thread. +bool WorkerThread::InitPriority() { + // This doesn't affect performance that much, and may not be too safe. + + bool ret = BindToCpus(&cpu_mask_); + if (!ret) + logprintf(11, "Log: Bind to %s failed.\n", + cpuset_format(&cpu_mask_).c_str()); + + logprintf(11, "Log: Thread %d running on apic ID %d mask %s (%s).\n", + thread_num_, apicid(), + CurrentCpusFormat().c_str(), + cpuset_format(&cpu_mask_).c_str()); +#if 0 + if (priority_ == High) { + sched_param param; + param.sched_priority = 1; + // Set the priority; others are unchanged. + logprintf(0, "Log: Changing priority to SCHED_FIFO %d\n", + param.sched_priority); + if (sched_setscheduler(0, SCHED_FIFO, ¶m)) { + char buf[256]; + sat_strerror(errno, buf, sizeof(buf)); + logprintf(0, "Process Error: sched_setscheduler " + "failed - error %d %s\n", + errno, buf); + } + } +#endif + return true; +} + +// Use pthreads to create a system thread. +int WorkerThread::SpawnThread() { + // Create the new thread. + int result = pthread_create(&thread_, NULL, thread_spawner_, this); + if (result) { + char buf[256]; + sat_strerror(result, buf, sizeof(buf)); + logprintf(0, "Process Error: pthread_create " + "failed - error %d %s\n", result, + buf); + status_ = false; + return false; + } + + // 0 is pthreads success. + return true; +} + +// Kill the worker thread with SIGINT. +bool WorkerThread::KillThread() { + return (pthread_kill(thread_, SIGINT) == 0); +} + +// Block until thread has exited. +bool WorkerThread::JoinThread() { + int result = pthread_join(thread_, NULL); + + if (result) { + logprintf(0, "Process Error: pthread_join failed - error %d\n", result); + status_ = false; + } + + // 0 is pthreads success. + return (!result); +} + + +void WorkerThread::StartRoutine() { + InitPriority(); + StartThreadTimer(); + Work(); + StopThreadTimer(); + worker_status_->RemoveSelf(); +} + + +// Thread work loop. Execute until marked finished. +bool WorkerThread::Work() { + do { + logprintf(9, "Log: ...\n"); + // Sleep for 1 second. + sat_sleep(1); + } while (IsReadyToRun()); + + return false; +} + + +// Returns CPU mask of CPUs available to this process, +// Conceptually, each bit represents a logical CPU, ie: +// mask = 3 (11b): cpu0, 1 +// mask = 13 (1101b): cpu0, 2, 3 +bool WorkerThread::AvailableCpus(cpu_set_t *cpuset) { + CPU_ZERO(cpuset); + return sched_getaffinity(getppid(), sizeof(*cpuset), cpuset) == 0; +} + + +// Returns CPU mask of CPUs this thread is bound to, +// Conceptually, each bit represents a logical CPU, ie: +// mask = 3 (11b): cpu0, 1 +// mask = 13 (1101b): cpu0, 2, 3 +bool WorkerThread::CurrentCpus(cpu_set_t *cpuset) { + CPU_ZERO(cpuset); + return sched_getaffinity(0, sizeof(*cpuset), cpuset) == 0; +} + + +// Bind worker thread to specified CPU(s) +// Args: +// thread_mask: cpu_set_t representing CPUs, ie +// mask = 1 (01b): cpu0 +// mask = 3 (11b): cpu0, 1 +// mask = 13 (1101b): cpu0, 2, 3 +// +// Returns true on success, false otherwise. +bool WorkerThread::BindToCpus(const cpu_set_t *thread_mask) { + cpu_set_t process_mask; + AvailableCpus(&process_mask); + if (cpuset_isequal(thread_mask, &process_mask)) + return true; + + logprintf(11, "Log: available CPU mask - %s\n", + cpuset_format(&process_mask).c_str()); + if (!cpuset_issubset(thread_mask, &process_mask)) { + // Invalid cpu_mask, ie cpu not allocated to this process or doesn't exist. + logprintf(0, "Log: requested CPUs %s not a subset of available %s\n", + cpuset_format(thread_mask).c_str(), + cpuset_format(&process_mask).c_str()); + return false; + } + return (sched_setaffinity(gettid(), sizeof(*thread_mask), thread_mask) == 0); +} + + +// A worker thread can yield itself to give up CPU until it's scheduled again. +// Returns true on success, false on error. +bool WorkerThread::YieldSelf() { + return (sched_yield() == 0); +} + + +// Fill this page with its pattern. +bool WorkerThread::FillPage(struct page_entry *pe) { + // Error check arguments. + if (pe == 0) { + logprintf(0, "Process Error: Fill Page entry null\n"); + return 0; + } + + // Mask is the bitmask of indexes used by the pattern. + // It is the pattern size -1. Size is always a power of 2. + uint64 *memwords = static_cast<uint64*>(pe->addr); + int length = sat_->page_length(); + + if (tag_mode_) { + // Select tag or data as appropriate. + for (int i = 0; i < length / wordsize_; i++) { + datacast_t data; + + if ((i & 0x7) == 0) { + data.l64 = addr_to_tag(&memwords[i]); + } else { + data.l32.l = pe->pattern->pattern(i << 1); + data.l32.h = pe->pattern->pattern((i << 1) + 1); + } + memwords[i] = data.l64; + } + } else { + // Just fill in untagged data directly. + for (int i = 0; i < length / wordsize_; i++) { + datacast_t data; + + data.l32.l = pe->pattern->pattern(i << 1); + data.l32.h = pe->pattern->pattern((i << 1) + 1); + memwords[i] = data.l64; + } + } + + return 1; +} + + +// Tell the thread how many pages to fill. +void FillThread::SetFillPages(int64 num_pages_to_fill_init) { + num_pages_to_fill_ = num_pages_to_fill_init; +} + +// Fill this page with a random pattern. +bool FillThread::FillPageRandom(struct page_entry *pe) { + // Error check arguments. + if (pe == 0) { + logprintf(0, "Process Error: Fill Page entry null\n"); + return 0; + } + if ((patternlist_ == 0) || (patternlist_->Size() == 0)) { + logprintf(0, "Process Error: No data patterns available\n"); + return 0; + } + + // Choose a random pattern for this block. + pe->pattern = patternlist_->GetRandomPattern(); + if (pe->pattern == 0) { + logprintf(0, "Process Error: Null data pattern\n"); + return 0; + } + + // Actually fill the page. + return FillPage(pe); +} + + +// Memory fill work loop. Execute until alloted pages filled. +bool FillThread::Work() { + bool result = true; + + logprintf(9, "Log: Starting fill thread %d\n", thread_num_); + + // We want to fill num_pages_to_fill pages, and + // stop when we've filled that many. + // We also want to capture early break + struct page_entry pe; + int64 loops = 0; + while (IsReadyToRun() && (loops < num_pages_to_fill_)) { + result = result && sat_->GetEmpty(&pe); + if (!result) { + logprintf(0, "Process Error: fill_thread failed to pop pages, " + "bailing\n"); + break; + } + + // Fill the page with pattern + result = result && FillPageRandom(&pe); + if (!result) break; + + // Put the page back on the queue. + result = result && sat_->PutValid(&pe); + if (!result) { + logprintf(0, "Process Error: fill_thread failed to push pages, " + "bailing\n"); + break; + } + loops++; + } + + // Fill in thread status. + pages_copied_ = loops; + status_ = result; + logprintf(9, "Log: Completed %d: Fill thread. Status %d, %d pages filled\n", + thread_num_, status_, pages_copied_); + return result; +} + + +// Print error information about a data miscompare. +void WorkerThread::ProcessError(struct ErrorRecord *error, + int priority, + const char *message) { + char dimm_string[256] = ""; + + int apic_id = apicid(); + + // Determine if this is a write or read error. + os_->Flush(error->vaddr); + error->reread = *(error->vaddr); + + char *good = reinterpret_cast<char*>(&(error->expected)); + char *bad = reinterpret_cast<char*>(&(error->actual)); + + sat_assert(error->expected != error->actual); + unsigned int offset = 0; + for (offset = 0; offset < (sizeof(error->expected) - 1); offset++) { + if (good[offset] != bad[offset]) + break; + } + + error->vbyteaddr = reinterpret_cast<char*>(error->vaddr) + offset; + + // Find physical address if possible. + error->paddr = os_->VirtualToPhysical(error->vbyteaddr); + + // Pretty print DIMM mapping if available. + os_->FindDimm(error->paddr, dimm_string, sizeof(dimm_string)); + + // Report parseable error. + if (priority < 5) { + // Run miscompare error through diagnoser for logging and reporting. + os_->error_diagnoser_->AddMiscompareError(dimm_string, + reinterpret_cast<uint64> + (error->vaddr), 1); + + logprintf(priority, + "%s: miscompare on CPU %d(0x%s) at %p(0x%llx:%s): " + "read:0x%016llx, reread:0x%016llx expected:0x%016llx\n", + message, + apic_id, + CurrentCpusFormat().c_str(), + error->vaddr, + error->paddr, + dimm_string, + error->actual, + error->reread, + error->expected); + } + + + // Overwrite incorrect data with correct data to prevent + // future miscompares when this data is reused. + *(error->vaddr) = error->expected; + os_->Flush(error->vaddr); +} + + + +// Print error information about a data miscompare. +void FileThread::ProcessError(struct ErrorRecord *error, + int priority, + const char *message) { + char dimm_string[256] = ""; + + // Determine if this is a write or read error. + os_->Flush(error->vaddr); + error->reread = *(error->vaddr); + + char *good = reinterpret_cast<char*>(&(error->expected)); + char *bad = reinterpret_cast<char*>(&(error->actual)); + + sat_assert(error->expected != error->actual); + unsigned int offset = 0; + for (offset = 0; offset < (sizeof(error->expected) - 1); offset++) { + if (good[offset] != bad[offset]) + break; + } + + error->vbyteaddr = reinterpret_cast<char*>(error->vaddr) + offset; + + // Find physical address if possible. + error->paddr = os_->VirtualToPhysical(error->vbyteaddr); + + // Pretty print DIMM mapping if available. + os_->FindDimm(error->paddr, dimm_string, sizeof(dimm_string)); + + // If crc_page_ is valid, ie checking content read back from file, + // track src/dst memory addresses. Otherwise catagorize as general + // mememory miscompare for CRC checking everywhere else. + if (crc_page_ != -1) { + int miscompare_byteoffset = static_cast<char*>(error->vbyteaddr) - + static_cast<char*>(page_recs_[crc_page_].dst); + os_->error_diagnoser_->AddHDDMiscompareError(devicename_, + crc_page_, + miscompare_byteoffset, + page_recs_[crc_page_].src, + page_recs_[crc_page_].dst); + } else { + os_->error_diagnoser_->AddMiscompareError(dimm_string, + reinterpret_cast<uint64> + (error->vaddr), 1); + } + + logprintf(priority, + "%s: miscompare on %s at %p(0x%llx:%s): read:0x%016llx, " + "reread:0x%016llx expected:0x%016llx\n", + message, + devicename_.c_str(), + error->vaddr, + error->paddr, + dimm_string, + error->actual, + error->reread, + error->expected); + + // Overwrite incorrect data with correct data to prevent + // future miscompares when this data is reused. + *(error->vaddr) = error->expected; + os_->Flush(error->vaddr); +} + + +// Do a word by word result check of a region. +// Print errors on mismatches. +int WorkerThread::CheckRegion(void *addr, + class Pattern *pattern, + int64 length, + int offset, + int64 pattern_offset) { + uint64 *memblock = static_cast<uint64*>(addr); + const int kErrorLimit = 128; + int errors = 0; + int overflowerrors = 0; // Count of overflowed errors. + bool page_error = false; + string errormessage("Hardware Error"); + struct ErrorRecord + recorded[kErrorLimit]; // Queued errors for later printing. + + // For each word in the data region. + for (int i = 0; i < length / wordsize_; i++) { + uint64 actual = memblock[i]; + uint64 expected; + + // Determine the value that should be there. + datacast_t data; + int index = 2 * i + pattern_offset; + data.l32.l = pattern->pattern(index); + data.l32.h = pattern->pattern(index + 1); + expected = data.l64; + // Check tags if necessary. + if (tag_mode_ && ((reinterpret_cast<uint64>(&memblock[i]) & 0x3f) == 0)) { + expected = addr_to_tag(&memblock[i]); + } + + + // If the value is incorrect, save an error record for later printing. + if (actual != expected) { + if (errors < kErrorLimit) { + recorded[errors].actual = actual; + recorded[errors].expected = expected; + recorded[errors].vaddr = &memblock[i]; + errors++; + } else { + page_error = true; + // If we have overflowed the error queue, just print the errors now. + logprintf(10, "Log: Error record overflow, too many miscompares!\n"); + errormessage = "Page Error"; + break; + } + } + } + + // Find if this is a whole block corruption. + if (page_error && !tag_mode_) { + int patsize = patternlist_->Size(); + for (int pat = 0; pat < patsize; pat++) { + class Pattern *altpattern = patternlist_->GetPattern(pat); + const int kGood = 0; + const int kBad = 1; + const int kGoodAgain = 2; + const int kNoMatch = 3; + int state = kGood; + unsigned int badstart = 0; + unsigned int badend = 0; + + // Don't match against ourself! + if (pattern == altpattern) + continue; + + for (int i = 0; i < length / wordsize_; i++) { + uint64 actual = memblock[i]; + datacast_t expected; + datacast_t possible; + + // Determine the value that should be there. + int index = 2 * i + pattern_offset; + + expected.l32.l = pattern->pattern(index); + expected.l32.h = pattern->pattern(index + 1); + + possible.l32.l = pattern->pattern(index); + possible.l32.h = pattern->pattern(index + 1); + + if (state == kGood) { + if (actual == expected.l64) { + continue; + } else if (actual == possible.l64) { + badstart = i; + badend = i; + state = kBad; + continue; + } else { + state = kNoMatch; + break; + } + } else if (state == kBad) { + if (actual == possible.l64) { + badend = i; + continue; + } else if (actual == expected.l64) { + state = kGoodAgain; + continue; + } else { + state = kNoMatch; + break; + } + } else if (state == kGoodAgain) { + if (actual == expected.l64) { + continue; + } else { + state = kNoMatch; + break; + } + } + } + + if ((state == kGoodAgain) || (state == kBad)) { + unsigned int blockerrors = badend - badstart + 1; + errormessage = "Block Error"; + ProcessError(&recorded[0], 0, errormessage.c_str()); + logprintf(0, "Block Error: (%p) pattern %s instead of %s, " + "%d bytes from offset 0x%x to 0x%x\n", + &memblock[badstart], + altpattern->name(), pattern->name(), + blockerrors * wordsize_, + offset + badstart * wordsize_, + offset + badend * wordsize_); + errorcount_ += blockerrors; + return blockerrors; + } + } + } + + + // Process error queue after all errors have been recorded. + for (int err = 0; err < errors; err++) { + int priority = 5; + if (errorcount_ + err < 30) + priority = 0; // Bump up the priority for the first few errors. + ProcessError(&recorded[err], priority, errormessage.c_str()); + } + + if (page_error) { + // For each word in the data region. + int error_recount = 0; + for (int i = 0; i < length / wordsize_; i++) { + uint64 actual = memblock[i]; + uint64 expected; + datacast_t data; + // Determine the value that should be there. + int index = 2 * i + pattern_offset; + + data.l32.l = pattern->pattern(index); + data.l32.h = pattern->pattern(index + 1); + expected = data.l64; + + // Check tags if necessary. + if (tag_mode_ && ((reinterpret_cast<uint64>(&memblock[i]) & 0x3f) == 0)) { + expected = addr_to_tag(&memblock[i]); + } + + // If the value is incorrect, save an error record for later printing. + if (actual != expected) { + if (error_recount < kErrorLimit) { + // We already reported these. + error_recount++; + } else { + // If we have overflowed the error queue, print the errors now. + struct ErrorRecord er; + er.actual = actual; + er.expected = expected; + er.vaddr = &memblock[i]; + + // Do the error printout. This will take a long time and + // likely change the machine state. + ProcessError(&er, 12, errormessage.c_str()); + overflowerrors++; + } + } + } + } + + // Keep track of observed errors. + errorcount_ += errors + overflowerrors; + return errors + overflowerrors; +} + +float WorkerThread::GetCopiedData() { + return pages_copied_ * sat_->page_length() / kMegabyte; +} + +// Calculate the CRC of a region. +// Result check if the CRC mismatches. +int WorkerThread::CrcCheckPage(struct page_entry *srcpe) { + const int blocksize = 4096; + const int blockwords = blocksize / wordsize_; + int errors = 0; + + const AdlerChecksum *expectedcrc = srcpe->pattern->crc(); + uint64 *memblock = static_cast<uint64*>(srcpe->addr); + int blocks = sat_->page_length() / blocksize; + for (int currentblock = 0; currentblock < blocks; currentblock++) { + uint64 *memslice = memblock + currentblock * blockwords; + + AdlerChecksum crc; + if (tag_mode_) { + AdlerAddrCrcC(memslice, blocksize, &crc, srcpe); + } else { + CalculateAdlerChecksum(memslice, blocksize, &crc); + } + + // If the CRC does not match, we'd better look closer. + if (!crc.Equals(*expectedcrc)) { + logprintf(11, "Log: CrcCheckPage Falling through to slow compare, " + "CRC mismatch %s != %s\n", + crc.ToHexString().c_str(), + expectedcrc->ToHexString().c_str()); + int errorcount = CheckRegion(memslice, + srcpe->pattern, + blocksize, + currentblock * blocksize, 0); + if (errorcount == 0) { + logprintf(0, "Log: CrcCheckPage CRC mismatch %s != %s, " + "but no miscompares found.\n", + crc.ToHexString().c_str(), + expectedcrc->ToHexString().c_str()); + } + errors += errorcount; + } + } + + // For odd length transfers, we should never hit this. + int leftovers = sat_->page_length() % blocksize; + if (leftovers) { + uint64 *memslice = memblock + blocks * blockwords; + errors += CheckRegion(memslice, + srcpe->pattern, + leftovers, + blocks * blocksize, 0); + } + return errors; +} + + +// Print error information about a data miscompare. +void WorkerThread::ProcessTagError(struct ErrorRecord *error, + int priority, + const char *message) { + char dimm_string[256] = ""; + char tag_dimm_string[256] = ""; + bool read_error = false; + + int apic_id = apicid(); + + // Determine if this is a write or read error. + os_->Flush(error->vaddr); + error->reread = *(error->vaddr); + + // Distinguish read and write errors. + if (error->actual != error->reread) { + read_error = true; + } + + sat_assert(error->expected != error->actual); + + error->vbyteaddr = reinterpret_cast<char*>(error->vaddr); + + // Find physical address if possible. + error->paddr = os_->VirtualToPhysical(error->vbyteaddr); + error->tagpaddr = os_->VirtualToPhysical(error->tagvaddr); + + // Pretty print DIMM mapping if available. + os_->FindDimm(error->paddr, dimm_string, sizeof(dimm_string)); + // Pretty print DIMM mapping if available. + os_->FindDimm(error->tagpaddr, tag_dimm_string, sizeof(tag_dimm_string)); + + // Report parseable error. + if (priority < 5) { + logprintf(priority, + "%s: Tag from %p(0x%llx:%s) (%s) " + "miscompare on CPU %d(0x%s) at %p(0x%llx:%s): " + "read:0x%016llx, reread:0x%016llx expected:0x%016llx\n", + message, + error->tagvaddr, error->tagpaddr, + tag_dimm_string, + read_error ? "read error" : "write error", + apic_id, + CurrentCpusFormat().c_str(), + error->vaddr, + error->paddr, + dimm_string, + error->actual, + error->reread, + error->expected); + } + + errorcount_ += 1; + + // Overwrite incorrect data with correct data to prevent + // future miscompares when this data is reused. + *(error->vaddr) = error->expected; + os_->Flush(error->vaddr); +} + + +// Print out and log a tag error. +bool WorkerThread::ReportTagError( + uint64 *mem64, + uint64 actual, + uint64 tag) { + struct ErrorRecord er; + er.actual = actual; + + er.expected = tag; + er.vaddr = mem64; + + // Generate vaddr from tag. + er.tagvaddr = reinterpret_cast<uint64*>(actual); + + ProcessTagError(&er, 0, "Hardware Error"); + return true; +} + +// C implementation of Adler memory copy, with memory tagging. +bool WorkerThread::AdlerAddrMemcpyC(uint64 *dstmem64, + uint64 *srcmem64, + unsigned int size_in_bytes, + AdlerChecksum *checksum, + struct page_entry *pe) { + // Use this data wrapper to access memory with 64bit read/write. + datacast_t data; + datacast_t dstdata; + unsigned int count = size_in_bytes / sizeof(data); + + if (count > ((1U) << 19)) { + // Size is too large, must be strictly less than 512 KB. + return false; + } + + uint64 a1 = 1; + uint64 a2 = 1; + uint64 b1 = 0; + uint64 b2 = 0; + + class Pattern *pattern = pe->pattern; + + unsigned int i = 0; + while (i < count) { + // Process 64 bits at a time. + if ((i & 0x7) == 0) { + data.l64 = srcmem64[i]; + dstdata.l64 = dstmem64[i]; + uint64 src_tag = addr_to_tag(&srcmem64[i]); + uint64 dst_tag = addr_to_tag(&dstmem64[i]); + // Detect if tags have been corrupted. + if (data.l64 != src_tag) + ReportTagError(&srcmem64[i], data.l64, src_tag); + if (dstdata.l64 != dst_tag) + ReportTagError(&dstmem64[i], dstdata.l64, dst_tag); + + data.l32.l = pattern->pattern(i << 1); + data.l32.h = pattern->pattern((i << 1) + 1); + a1 = a1 + data.l32.l; + b1 = b1 + a1; + a1 = a1 + data.l32.h; + b1 = b1 + a1; + + data.l64 = dst_tag; + dstmem64[i] = data.l64; + + } else { + data.l64 = srcmem64[i]; + a1 = a1 + data.l32.l; + b1 = b1 + a1; + a1 = a1 + data.l32.h; + b1 = b1 + a1; + dstmem64[i] = data.l64; + } + i++; + + data.l64 = srcmem64[i]; + a2 = a2 + data.l32.l; + b2 = b2 + a2; + a2 = a2 + data.l32.h; + b2 = b2 + a2; + dstmem64[i] = data.l64; + i++; + } + checksum->Set(a1, a2, b1, b2); + return true; +} + +// x86_64 SSE2 assembly implementation of Adler memory copy, with address +// tagging added as a second step. This is useful for debugging failures +// that only occur when SSE / nontemporal writes are used. +bool WorkerThread::AdlerAddrMemcpyWarm(uint64 *dstmem64, + uint64 *srcmem64, + unsigned int size_in_bytes, + AdlerChecksum *checksum, + struct page_entry *pe) { + // Do ASM copy, ignore checksum. + AdlerChecksum ignored_checksum; + os_->AdlerMemcpyWarm(dstmem64, srcmem64, size_in_bytes, &ignored_checksum); + + // Force cache flush. + int length = size_in_bytes / sizeof(*dstmem64); + for (int i = 0; i < length; i += sizeof(*dstmem64)) { + os_->FastFlush(dstmem64 + i); + os_->FastFlush(srcmem64 + i); + } + // Check results. + AdlerAddrCrcC(srcmem64, size_in_bytes, checksum, pe); + // Patch up address tags. + TagAddrC(dstmem64, size_in_bytes); + return true; +} + +// Retag pages.. +bool WorkerThread::TagAddrC(uint64 *memwords, + unsigned int size_in_bytes) { + // Mask is the bitmask of indexes used by the pattern. + // It is the pattern size -1. Size is always a power of 2. + + // Select tag or data as appropriate. + int length = size_in_bytes / wordsize_; + for (int i = 0; i < length; i += 8) { + datacast_t data; + data.l64 = addr_to_tag(&memwords[i]); + memwords[i] = data.l64; + } + return true; +} + +// C implementation of Adler memory crc. +bool WorkerThread::AdlerAddrCrcC(uint64 *srcmem64, + unsigned int size_in_bytes, + AdlerChecksum *checksum, + struct page_entry *pe) { + // Use this data wrapper to access memory with 64bit read/write. + datacast_t data; + unsigned int count = size_in_bytes / sizeof(data); + + if (count > ((1U) << 19)) { + // Size is too large, must be strictly less than 512 KB. + return false; + } + + uint64 a1 = 1; + uint64 a2 = 1; + uint64 b1 = 0; + uint64 b2 = 0; + + class Pattern *pattern = pe->pattern; + + unsigned int i = 0; + while (i < count) { + // Process 64 bits at a time. + if ((i & 0x7) == 0) { + data.l64 = srcmem64[i]; + uint64 src_tag = addr_to_tag(&srcmem64[i]); + // Check that tags match expected. + if (data.l64 != src_tag) + ReportTagError(&srcmem64[i], data.l64, src_tag); + + data.l32.l = pattern->pattern(i << 1); + data.l32.h = pattern->pattern((i << 1) + 1); + a1 = a1 + data.l32.l; + b1 = b1 + a1; + a1 = a1 + data.l32.h; + b1 = b1 + a1; + } else { + data.l64 = srcmem64[i]; + a1 = a1 + data.l32.l; + b1 = b1 + a1; + a1 = a1 + data.l32.h; + b1 = b1 + a1; + } + i++; + + data.l64 = srcmem64[i]; + a2 = a2 + data.l32.l; + b2 = b2 + a2; + a2 = a2 + data.l32.h; + b2 = b2 + a2; + i++; + } + checksum->Set(a1, a2, b1, b2); + return true; +} + +// Copy a block of memory quickly, while keeping a CRC of the data. +// Result check if the CRC mismatches. +int WorkerThread::CrcCopyPage(struct page_entry *dstpe, + struct page_entry *srcpe) { + int errors = 0; + const int blocksize = 4096; + const int blockwords = blocksize / wordsize_; + int blocks = sat_->page_length() / blocksize; + + // Base addresses for memory copy + uint64 *targetmembase = static_cast<uint64*>(dstpe->addr); + uint64 *sourcemembase = static_cast<uint64*>(srcpe->addr); + // Remember the expected CRC + const AdlerChecksum *expectedcrc = srcpe->pattern->crc(); + + for (int currentblock = 0; currentblock < blocks; currentblock++) { + uint64 *targetmem = targetmembase + currentblock * blockwords; + uint64 *sourcemem = sourcemembase + currentblock * blockwords; + + AdlerChecksum crc; + if (tag_mode_) { + AdlerAddrMemcpyC(targetmem, sourcemem, blocksize, &crc, srcpe); + } else { + AdlerMemcpyC(targetmem, sourcemem, blocksize, &crc); + } + + // Investigate miscompares. + if (!crc.Equals(*expectedcrc)) { + logprintf(11, "Log: CrcCopyPage Falling through to slow compare, " + "CRC mismatch %s != %s\n", crc.ToHexString().c_str(), + expectedcrc->ToHexString().c_str()); + int errorcount = CheckRegion(sourcemem, + srcpe->pattern, + blocksize, + currentblock * blocksize, 0); + if (errorcount == 0) { + logprintf(0, "Log: CrcCopyPage CRC mismatch %s != %s, " + "but no miscompares found. Retrying with fresh data.\n", + crc.ToHexString().c_str(), + expectedcrc->ToHexString().c_str()); + if (!tag_mode_) { + // Copy the data originally read from this region back again. + // This data should have any corruption read originally while + // calculating the CRC. + memcpy(sourcemem, targetmem, blocksize); + errorcount = CheckRegion(sourcemem, + srcpe->pattern, + blocksize, + currentblock * blocksize, 0); + if (errorcount == 0) { + int apic_id = apicid(); + logprintf(0, "Process Error: CPU %d(0x%s) CrcCopyPage " + "CRC mismatch %s != %s, " + "but no miscompares found on second pass.\n", + apic_id, CurrentCpusFormat().c_str(), + crc.ToHexString().c_str(), + expectedcrc->ToHexString().c_str()); + struct ErrorRecord er; + er.actual = sourcemem[0]; + er.expected = 0x0; + er.vaddr = sourcemem; + ProcessError(&er, 0, "Hardware Error"); + } + } + } + errors += errorcount; + } + } + + // For odd length transfers, we should never hit this. + int leftovers = sat_->page_length() % blocksize; + if (leftovers) { + uint64 *targetmem = targetmembase + blocks * blockwords; + uint64 *sourcemem = sourcemembase + blocks * blockwords; + + errors += CheckRegion(sourcemem, + srcpe->pattern, + leftovers, + blocks * blocksize, 0); + int leftoverwords = leftovers / wordsize_; + for (int i = 0; i < leftoverwords; i++) { + targetmem[i] = sourcemem[i]; + } + } + + // Update pattern reference to reflect new contents. + dstpe->pattern = srcpe->pattern; + + // Clean clean clean the errors away. + if (errors) { + // TODO(nsanders): Maybe we should patch rather than fill? Filling may + // cause bad data to be propogated across the page. + FillPage(dstpe); + } + return errors; +} + + + +// Invert a block of memory quickly, traversing downwards. +int InvertThread::InvertPageDown(struct page_entry *srcpe) { + const int blocksize = 4096; + const int blockwords = blocksize / wordsize_; + int blocks = sat_->page_length() / blocksize; + + // Base addresses for memory copy + unsigned int *sourcemembase = static_cast<unsigned int *>(srcpe->addr); + + for (int currentblock = blocks-1; currentblock >= 0; currentblock--) { + unsigned int *sourcemem = sourcemembase + currentblock * blockwords; + for (int i = blockwords - 32; i >= 0; i -= 32) { + for (int index = i + 31; index >= i; --index) { + unsigned int actual = sourcemem[index]; + sourcemem[index] = ~actual; + } + OsLayer::FastFlush(&sourcemem[i]); + } + } + + return 0; +} + +// Invert a block of memory, traversing upwards. +int InvertThread::InvertPageUp(struct page_entry *srcpe) { + const int blocksize = 4096; + const int blockwords = blocksize / wordsize_; + int blocks = sat_->page_length() / blocksize; + + // Base addresses for memory copy + unsigned int *sourcemembase = static_cast<unsigned int *>(srcpe->addr); + + for (int currentblock = 0; currentblock < blocks; currentblock++) { + unsigned int *sourcemem = sourcemembase + currentblock * blockwords; + for (int i = 0; i < blockwords; i += 32) { + for (int index = i; index <= i + 31; ++index) { + unsigned int actual = sourcemem[index]; + sourcemem[index] = ~actual; + } + OsLayer::FastFlush(&sourcemem[i]); + } + } + return 0; +} + +// Copy a block of memory quickly, while keeping a CRC of the data. +// Result check if the CRC mismatches. Warm the CPU while running +int WorkerThread::CrcWarmCopyPage(struct page_entry *dstpe, + struct page_entry *srcpe) { + int errors = 0; + const int blocksize = 4096; + const int blockwords = blocksize / wordsize_; + int blocks = sat_->page_length() / blocksize; + + // Base addresses for memory copy + uint64 *targetmembase = static_cast<uint64*>(dstpe->addr); + uint64 *sourcemembase = static_cast<uint64*>(srcpe->addr); + // Remember the expected CRC + const AdlerChecksum *expectedcrc = srcpe->pattern->crc(); + + for (int currentblock = 0; currentblock < blocks; currentblock++) { + uint64 *targetmem = targetmembase + currentblock * blockwords; + uint64 *sourcemem = sourcemembase + currentblock * blockwords; + + AdlerChecksum crc; + if (tag_mode_) { + AdlerAddrMemcpyWarm(targetmem, sourcemem, blocksize, &crc, srcpe); + } else { + os_->AdlerMemcpyWarm(targetmem, sourcemem, blocksize, &crc); + } + + // Investigate miscompares. + if (!crc.Equals(*expectedcrc)) { + logprintf(11, "Log: CrcWarmCopyPage Falling through to slow compare, " + "CRC mismatch %s != %s\n", crc.ToHexString().c_str(), + expectedcrc->ToHexString().c_str()); + int errorcount = CheckRegion(sourcemem, + srcpe->pattern, + blocksize, + currentblock * blocksize, 0); + if (errorcount == 0) { + logprintf(0, "Log: CrcWarmCopyPage CRC mismatch %s != %s, " + "but no miscompares found. Retrying with fresh data.\n", + crc.ToHexString().c_str(), + expectedcrc->ToHexString().c_str()); + if (!tag_mode_) { + // Copy the data originally read from this region back again. + // This data should have any corruption read originally while + // calculating the CRC. + memcpy(sourcemem, targetmem, blocksize); + errorcount = CheckRegion(sourcemem, + srcpe->pattern, + blocksize, + currentblock * blocksize, 0); + if (errorcount == 0) { + int apic_id = apicid(); + logprintf(0, "Process Error: CPU %d(0x%s) CrciWarmCopyPage " + "CRC mismatch %s != %s, " + "but no miscompares found on second pass.\n", + apic_id, CurrentCpusFormat().c_str(), + crc.ToHexString().c_str(), + expectedcrc->ToHexString().c_str()); + struct ErrorRecord er; + er.actual = sourcemem[0]; + er.expected = 0x0; + er.vaddr = sourcemem; + ProcessError(&er, 0, "Hardware Error"); + } + } + } + errors += errorcount; + } + } + + // For odd length transfers, we should never hit this. + int leftovers = sat_->page_length() % blocksize; + if (leftovers) { + uint64 *targetmem = targetmembase + blocks * blockwords; + uint64 *sourcemem = sourcemembase + blocks * blockwords; + + errors += CheckRegion(sourcemem, + srcpe->pattern, + leftovers, + blocks * blocksize, 0); + int leftoverwords = leftovers / wordsize_; + for (int i = 0; i < leftoverwords; i++) { + targetmem[i] = sourcemem[i]; + } + } + + // Update pattern reference to reflect new contents. + dstpe->pattern = srcpe->pattern; + + // Clean clean clean the errors away. + if (errors) { + // TODO(nsanders): Maybe we should patch rather than fill? Filling may + // cause bad data to be propogated across the page. + FillPage(dstpe); + } + return errors; +} + + + +// Memory check work loop. Execute until done, then exhaust pages. +bool CheckThread::Work() { + struct page_entry pe; + bool result = true; + int64 loops = 0; + + logprintf(9, "Log: Starting Check thread %d\n", thread_num_); + + // We want to check all the pages, and + // stop when there aren't any left. + while (true) { + result = result && sat_->GetValid(&pe); + if (!result) { + if (IsReadyToRunNoPause()) + logprintf(0, "Process Error: check_thread failed to pop pages, " + "bailing\n"); + else + result = true; + break; + } + + // Do the result check. + CrcCheckPage(&pe); + + // Push pages back on the valid queue if we are still going, + // throw them out otherwise. + if (IsReadyToRunNoPause()) + result = result && sat_->PutValid(&pe); + else + result = result && sat_->PutEmpty(&pe); + if (!result) { + logprintf(0, "Process Error: check_thread failed to push pages, " + "bailing\n"); + break; + } + loops++; + } + + pages_copied_ = loops; + status_ = result; + logprintf(9, "Log: Completed %d: Check thread. Status %d, %d pages checked\n", + thread_num_, status_, pages_copied_); + return result; +} + + +// Memory copy work loop. Execute until marked done. +bool CopyThread::Work() { + struct page_entry src; + struct page_entry dst; + bool result = true; + int64 loops = 0; + + logprintf(9, "Log: Starting copy thread %d: cpu %s, mem %x\n", + thread_num_, cpuset_format(&cpu_mask_).c_str(), tag_); + + while (IsReadyToRun()) { + // Pop the needed pages. + result = result && sat_->GetValid(&src, tag_); + result = result && sat_->GetEmpty(&dst, tag_); + if (!result) { + logprintf(0, "Process Error: copy_thread failed to pop pages, " + "bailing\n"); + break; + } + + // Force errors for unittests. + if (sat_->error_injection()) { + if (loops == 8) { + char *addr = reinterpret_cast<char*>(src.addr); + int offset = random() % sat_->page_length(); + addr[offset] = 0xba; + } + } + + // We can use memcpy, or CRC check while we copy. + if (sat_->warm()) { + CrcWarmCopyPage(&dst, &src); + } else if (sat_->strict()) { + CrcCopyPage(&dst, &src); + } else { + memcpy(dst.addr, src.addr, sat_->page_length()); + dst.pattern = src.pattern; + } + + result = result && sat_->PutValid(&dst); + result = result && sat_->PutEmpty(&src); + + // Copy worker-threads yield themselves at the end of each copy loop, + // to avoid threads from preempting each other in the middle of the inner + // copy-loop. Cooperations between Copy worker-threads results in less + // unnecessary cache thrashing (which happens when context-switching in the + // middle of the inner copy-loop). + YieldSelf(); + + if (!result) { + logprintf(0, "Process Error: copy_thread failed to push pages, " + "bailing\n"); + break; + } + loops++; + } + + pages_copied_ = loops; + status_ = result; + logprintf(9, "Log: Completed %d: Copy thread. Status %d, %d pages copied\n", + thread_num_, status_, pages_copied_); + return result; +} + +// Memory invert work loop. Execute until marked done. +bool InvertThread::Work() { + struct page_entry src; + bool result = true; + int64 loops = 0; + + logprintf(9, "Log: Starting invert thread %d\n", thread_num_); + + while (IsReadyToRun()) { + // Pop the needed pages. + result = result && sat_->GetValid(&src); + if (!result) { + logprintf(0, "Process Error: invert_thread failed to pop pages, " + "bailing\n"); + break; + } + + if (sat_->strict()) + CrcCheckPage(&src); + + // For the same reason CopyThread yields itself (see YieldSelf comment + // in CopyThread::Work(), InvertThread yields itself after each invert + // operation to improve cooperation between different worker threads + // stressing the memory/cache. + InvertPageUp(&src); + YieldSelf(); + InvertPageDown(&src); + YieldSelf(); + InvertPageDown(&src); + YieldSelf(); + InvertPageUp(&src); + YieldSelf(); + + if (sat_->strict()) + CrcCheckPage(&src); + + result = result && sat_->PutValid(&src); + if (!result) { + logprintf(0, "Process Error: invert_thread failed to push pages, " + "bailing\n"); + break; + } + loops++; + } + + pages_copied_ = loops * 2; + status_ = result; + logprintf(9, "Log: Completed %d: Copy thread. Status %d, %d pages copied\n", + thread_num_, status_, pages_copied_); + return result; +} + + +// Set file name to use for File IO. +void FileThread::SetFile(const char *filename_init) { + filename_ = filename_init; + devicename_ = os_->FindFileDevice(filename_); +} + +// Open the file for access. +bool FileThread::OpenFile(int *pfile) { + int fd = open(filename_.c_str(), + O_RDWR | O_CREAT | O_SYNC | O_DIRECT, + 0644); + if (fd < 0) { + logprintf(0, "Process Error: Failed to create file %s!!\n", + filename_.c_str()); + pages_copied_ = 0; + return false; + } + *pfile = fd; + return true; +} + +// Close the file. +bool FileThread::CloseFile(int fd) { + close(fd); + return true; +} + +// Check sector tagging. +bool FileThread::SectorTagPage(struct page_entry *src, int block) { + int page_length = sat_->page_length(); + struct FileThread::SectorTag *tag = + (struct FileThread::SectorTag *)(src->addr); + + // Tag each sector. + unsigned char magic = ((0xba + thread_num_) & 0xff); + for (int sec = 0; sec < page_length / 512; sec++) { + tag[sec].magic = magic; + tag[sec].block = block & 0xff; + tag[sec].sector = sec & 0xff; + tag[sec].pass = pass_ & 0xff; + } + return true; +} + +bool FileThread::WritePageToFile(int fd, struct page_entry *src) { + int page_length = sat_->page_length(); + // Fill the file with our data. + int64 size = write(fd, src->addr, page_length); + + if (size != page_length) { + os_->ErrorReport(devicename_.c_str(), "write-error", 1); + errorcount_++; + logprintf(0, "Block Error: file_thread failed to write, " + "bailing\n"); + return false; + } + return true; +} + +// Write the data to the file. +bool FileThread::WritePages(int fd) { + int strict = sat_->strict(); + + // Start fresh at beginning of file for each batch of pages. + lseek64(fd, 0, SEEK_SET); + for (int i = 0; i < sat_->disk_pages(); i++) { + struct page_entry src; + if (!GetValidPage(&src)) + return false; + // Save expected pattern. + page_recs_[i].pattern = src.pattern; + page_recs_[i].src = src.addr; + + // Check data correctness. + if (strict) + CrcCheckPage(&src); + + SectorTagPage(&src, i); + + bool result = WritePageToFile(fd, &src); + + if (!PutEmptyPage(&src)) + return false; + + if (!result) + return false; + } + return true; +} + +// Copy data from file into memory block. +bool FileThread::ReadPageFromFile(int fd, struct page_entry *dst) { + int page_length = sat_->page_length(); + + // Do the actual read. + int64 size = read(fd, dst->addr, page_length); + if (size != page_length) { + os_->ErrorReport(devicename_.c_str(), "read-error", 1); + logprintf(0, "Block Error: file_thread failed to read, " + "bailing\n"); + errorcount_++; + return false; + } + return true; +} + +// Check sector tagging. +bool FileThread::SectorValidatePage(const struct PageRec &page, + struct page_entry *dst, int block) { + // Error injection. + static int calls = 0; + calls++; + + // Do sector tag compare. + int firstsector = -1; + int lastsector = -1; + bool badsector = false; + int page_length = sat_->page_length(); + + // Cast data block into an array of tagged sectors. + struct FileThread::SectorTag *tag = + (struct FileThread::SectorTag *)(dst->addr); + + sat_assert(sizeof(*tag) == 512); + + // Error injection. + if (sat_->error_injection()) { + if (calls == 2) { + for (int badsec = 8; badsec < 17; badsec++) + tag[badsec].pass = 27; + } + if (calls == 18) { + (static_cast<int32*>(dst->addr))[27] = 0xbadda7a; + } + } + + // Check each sector for the correct tag we added earlier, + // then revert the tag to the to normal data pattern. + unsigned char magic = ((0xba + thread_num_) & 0xff); + for (int sec = 0; sec < page_length / 512; sec++) { + // Check magic tag. + if ((tag[sec].magic != magic) || + (tag[sec].block != (block & 0xff)) || + (tag[sec].sector != (sec & 0xff)) || + (tag[sec].pass != (pass_ & 0xff))) { + // Offset calculation for tag location. + int offset = sec * sizeof(SectorTag); + if (tag[sec].block != (block & 0xff)) + offset += 1 * sizeof(uint8); + else if (tag[sec].sector != (sec & 0xff)) + offset += 2 * sizeof(uint8); + else if (tag[sec].pass != (pass_ & 0xff)) + offset += 3 * sizeof(uint8); + + // Run sector tag error through diagnoser for logging and reporting. + errorcount_ += 1; + os_->error_diagnoser_->AddHDDSectorTagError(devicename_, tag[sec].block, + offset, + tag[sec].sector, + page.src, page.dst); + + logprintf(5, "Sector Error: Sector tag @ 0x%x, pass %d/%d. " + "sec %x/%x, block %d/%d, magic %x/%x, File: %s \n", + block * page_length + 512 * sec, + (pass_ & 0xff), (unsigned int)tag[sec].pass, + sec, (unsigned int)tag[sec].sector, + block, (unsigned int)tag[sec].block, + magic, (unsigned int)tag[sec].magic, + filename_.c_str()); + + // Keep track of first and last bad sector. + if (firstsector == -1) + firstsector = (block * page_length / 512) + sec; + lastsector = (block * page_length / 512) + sec; + badsector = true; + } + // Patch tag back to proper pattern. + unsigned int *addr = (unsigned int *)(&tag[sec]); + *addr = dst->pattern->pattern(512 * sec / sizeof(*addr)); + } + + // If we found sector errors: + if (badsector == true) { + logprintf(5, "Log: file sector miscompare at offset %x-%x. File: %s\n", + firstsector * 512, + ((lastsector + 1) * 512) - 1, + filename_.c_str()); + + // Either exit immediately, or patch the data up and continue. + if (sat_->stop_on_error()) { + exit(1); + } else { + // Patch up bad pages. + for (int block = (firstsector * 512) / page_length; + block <= (lastsector * 512) / page_length; + block++) { + unsigned int *memblock = static_cast<unsigned int *>(dst->addr); + int length = page_length / wordsize_; + for (int i = 0; i < length; i++) { + memblock[i] = dst->pattern->pattern(i); + } + } + } + } + return true; +} + +// Get memory for an incoming data transfer.. +bool FileThread::PagePrepare() { + // We can only do direct IO to SAT pages if it is normal mem. + page_io_ = os_->normal_mem(); + + // Init a local buffer if we need it. + if (!page_io_) { + int result = posix_memalign(&local_page_, 512, sat_->page_length()); + if (result) { + logprintf(0, "Process Error: disk thread posix_memalign " + "returned %d (fail)\n", + result); + status_ = false; + return false; + } + } + return true; +} + + +// Remove memory allocated for data transfer. +bool FileThread::PageTeardown() { + // Free a local buffer if we need to. + if (!page_io_) { + free(local_page_); + } + return true; +} + + + +// Get memory for an incoming data transfer.. +bool FileThread::GetEmptyPage(struct page_entry *dst) { + if (page_io_) { + if (!sat_->GetEmpty(dst)) + return false; + } else { + dst->addr = local_page_; + dst->offset = 0; + dst->pattern = 0; + } + return true; +} + +// Get memory for an outgoing data transfer.. +bool FileThread::GetValidPage(struct page_entry *src) { + struct page_entry tmp; + if (!sat_->GetValid(&tmp)) + return false; + if (page_io_) { + *src = tmp; + return true; + } else { + src->addr = local_page_; + src->offset = 0; + CrcCopyPage(src, &tmp); + if (!sat_->PutValid(&tmp)) + return false; + } + return true; +} + + +// Throw out a used empty page. +bool FileThread::PutEmptyPage(struct page_entry *src) { + if (page_io_) { + if (!sat_->PutEmpty(src)) + return false; + } + return true; +} + +// Throw out a used, filled page. +bool FileThread::PutValidPage(struct page_entry *src) { + if (page_io_) { + if (!sat_->PutValid(src)) + return false; + } + return true; +} + +// Copy data from file into memory blocks. +bool FileThread::ReadPages(int fd) { + int page_length = sat_->page_length(); + int strict = sat_->strict(); + bool result = true; + + // Read our data back out of the file, into it's new location. + lseek64(fd, 0, SEEK_SET); + for (int i = 0; i < sat_->disk_pages(); i++) { + struct page_entry dst; + if (!GetEmptyPage(&dst)) + return false; + // Retrieve expected pattern. + dst.pattern = page_recs_[i].pattern; + // Update page recordpage record. + page_recs_[i].dst = dst.addr; + + // Read from the file into destination page. + if (!ReadPageFromFile(fd, &dst)) { + PutEmptyPage(&dst); + return false; + } + + SectorValidatePage(page_recs_[i], &dst, i); + + // Ensure that the transfer ended up with correct data. + if (strict) { + // Record page index currently CRC checked. + crc_page_ = i; + int errors = CrcCheckPage(&dst); + if (errors) { + logprintf(5, "Log: file miscompare at block %d, " + "offset %x-%x. File: %s\n", + i, i * page_length, ((i + 1) * page_length) - 1, + filename_.c_str()); + result = false; + } + crc_page_ = -1; + errorcount_ += errors; + } + if (!PutValidPage(&dst)) + return false; + } + return result; +} + +// File IO work loop. Execute until marked done. +bool FileThread::Work() { + bool result = true; + int64 loops = 0; + + logprintf(9, "Log: Starting file thread %d, file %s, device %s\n", + thread_num_, + filename_.c_str(), + devicename_.c_str()); + + if (!PagePrepare()) { + status_ = false; + return false; + } + + // Open the data IO file. + int fd = 0; + if (!OpenFile(&fd)) { + status_ = false; + return false; + } + + pass_ = 0; + + // Load patterns into page records. + page_recs_ = new struct PageRec[sat_->disk_pages()]; + for (int i = 0; i < sat_->disk_pages(); i++) { + page_recs_[i].pattern = new struct Pattern(); + } + + // Loop until done. + while (IsReadyToRun()) { + // Do the file write. + if (!(result = result && WritePages(fd))) + break; + + // Do the file read. + if (!(result = result && ReadPages(fd))) + break; + + loops++; + pass_ = loops; + } + + pages_copied_ = loops * sat_->disk_pages(); + + // Clean up. + CloseFile(fd); + PageTeardown(); + + logprintf(9, "Log: Completed %d: file thread status %d, %d pages copied\n", + thread_num_, status_, pages_copied_); + // Failure to read from device indicates hardware, + // rather than procedural SW error. + status_ = true; + return true; +} + +bool NetworkThread::IsNetworkStopSet() { + return !IsReadyToRunNoPause(); +} + +bool NetworkSlaveThread::IsNetworkStopSet() { + // This thread has no completion status. + // It finishes whever there is no more data to be + // passed back. + return true; +} + +// Set ip name to use for Network IO. +void NetworkThread::SetIP(const char *ipaddr_init) { + strncpy(ipaddr_, ipaddr_init, 256); +} + +// Create a socket. +// Return 0 on error. +bool NetworkThread::CreateSocket(int *psocket) { + int sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock == -1) { + logprintf(0, "Process Error: Cannot open socket\n"); + pages_copied_ = 0; + status_ = false; + return false; + } + *psocket = sock; + return true; +} + +// Close the socket. +bool NetworkThread::CloseSocket(int sock) { + close(sock); + return true; +} + +// Initiate the tcp connection. +bool NetworkThread::Connect(int sock) { + struct sockaddr_in dest_addr; + dest_addr.sin_family = AF_INET; + dest_addr.sin_port = htons(kNetworkPort); + memset(&(dest_addr.sin_zero), '\0', sizeof(dest_addr.sin_zero)); + + // Translate dot notation to u32. + if (inet_aton(ipaddr_, &dest_addr.sin_addr) == 0) { + logprintf(0, "Process Error: Cannot resolve %s\n", ipaddr_); + pages_copied_ = 0; + status_ = false; + return false; + } + + if (-1 == connect(sock, reinterpret_cast<struct sockaddr *>(&dest_addr), + sizeof(struct sockaddr))) { + logprintf(0, "Process Error: Cannot connect %s\n", ipaddr_); + pages_copied_ = 0; + status_ = false; + return false; + } + return true; +} + +// Initiate the tcp connection. +bool NetworkListenThread::Listen() { + struct sockaddr_in sa; + + memset(&(sa.sin_zero), '\0', sizeof(sa.sin_zero)); + + sa.sin_family = AF_INET; + sa.sin_addr.s_addr = INADDR_ANY; + sa.sin_port = htons(kNetworkPort); + + if (-1 == bind(sock_, (struct sockaddr*)&sa, sizeof(struct sockaddr))) { + char buf[256]; + sat_strerror(errno, buf, sizeof(buf)); + logprintf(0, "Process Error: Cannot bind socket: %s\n", buf); + pages_copied_ = 0; + status_ = false; + return false; + } + listen(sock_, 3); + return true; +} + +// Wait for a connection from a network traffic generation thread. +bool NetworkListenThread::Wait() { + fd_set rfds; + struct timeval tv; + int retval; + + // Watch sock_ to see when it has input. + FD_ZERO(&rfds); + FD_SET(sock_, &rfds); + // Wait up to five seconds. + tv.tv_sec = 5; + tv.tv_usec = 0; + + retval = select(sock_ + 1, &rfds, NULL, NULL, &tv); + + return (retval > 0); +} + +// Wait for a connection from a network traffic generation thread. +bool NetworkListenThread::GetConnection(int *pnewsock) { + struct sockaddr_in sa; + socklen_t size = sizeof(struct sockaddr_in); + + int newsock = accept(sock_, reinterpret_cast<struct sockaddr *>(&sa), &size); + if (newsock < 0) { + logprintf(0, "Process Error: Did not receive connection\n"); + pages_copied_ = 0; + status_ = false; + return false; + } + *pnewsock = newsock; + return true; +} + +// Send a page, return false if a page was not sent. +bool NetworkThread::SendPage(int sock, struct page_entry *src) { + int page_length = sat_->page_length(); + char *address = static_cast<char*>(src->addr); + + // Send our data over the network. + int size = page_length; + while (size) { + int transferred = send(sock, address + (page_length - size), size, 0); + if ((transferred == 0) || (transferred == -1)) { + if (!IsNetworkStopSet()) { + char buf[256] = ""; + sat_strerror(errno, buf, sizeof(buf)); + logprintf(0, "Process Error: Thread %d, " + "Network write failed, bailing. (%s)\n", + thread_num_, buf); + status_ = false; + } + return false; + } + size = size - transferred; + } + return true; +} + +// Receive a page. Return false if a page was not received. +bool NetworkThread::ReceivePage(int sock, struct page_entry *dst) { + int page_length = sat_->page_length(); + char *address = static_cast<char*>(dst->addr); + + // Maybe we will get our data back again, maybe not. + int size = page_length; + while (size) { + int transferred = recv(sock, address + (page_length - size), size, 0); + if ((transferred == 0) || (transferred == -1)) { + // Typically network slave thread should exit as network master + // thread stops sending data. + if (IsNetworkStopSet()) { + int err = errno; + if (transferred == 0 && err == 0) { + // Two system setups will not sync exactly, + // allow early exit, but log it. + logprintf(0, "Log: Net thread did not receive any data, exiting.\n"); + } else { + char buf[256] = ""; + sat_strerror(err, buf, sizeof(buf)); + // Print why we failed. + logprintf(0, "Process Error: Thread %d, " + "Network read failed, bailing (%s).\n", + thread_num_, buf); + status_ = false; + // Print arguments and results. + logprintf(0, "Log: recv(%d, address %x, size %x, 0) == %x, err %d\n", + sock, address + (page_length - size), + size, transferred, err); + if ((transferred == 0) && + (page_length - size < 512) && + (page_length - size > 0)) { + // Print null terminated data received, to see who's been + // sending us supicious unwanted data. + address[page_length - size] = 0; + logprintf(0, "Log: received %d bytes: '%s'\n", + page_length - size, address); + } + } + } + return false; + } + size = size - transferred; + } + return true; +} + +// Network IO work loop. Execute until marked done. +// Return true if the thread ran as expected. +bool NetworkThread::Work() { + logprintf(9, "Log: Starting network thread %d, ip %s\n", + thread_num_, + ipaddr_); + + // Make a socket. + int sock = 0; + if (!CreateSocket(&sock)) + return false; + + // Network IO loop requires network slave thread to have already initialized. + // We will sleep here for awhile to ensure that the slave thread will be + // listening by the time we connect. + // Sleep for 15 seconds. + sat_sleep(15); + logprintf(9, "Log: Starting execution of network thread %d, ip %s\n", + thread_num_, + ipaddr_); + + + // Connect to a slave thread. + if (!Connect(sock)) + return false; + + // Loop until done. + bool result = true; + int strict = sat_->strict(); + int64 loops = 0; + while (IsReadyToRun()) { + struct page_entry src; + struct page_entry dst; + result = result && sat_->GetValid(&src); + result = result && sat_->GetEmpty(&dst); + if (!result) { + logprintf(0, "Process Error: net_thread failed to pop pages, " + "bailing\n"); + break; + } + + // Check data correctness. + if (strict) + CrcCheckPage(&src); + + // Do the network write. + if (!(result = result && SendPage(sock, &src))) + break; + + // Update pattern reference to reflect new contents. + dst.pattern = src.pattern; + + // Do the network read. + if (!(result = result && ReceivePage(sock, &dst))) + break; + + // Ensure that the transfer ended up with correct data. + if (strict) + CrcCheckPage(&dst); + + // Return all of our pages to the queue. + result = result && sat_->PutValid(&dst); + result = result && sat_->PutEmpty(&src); + if (!result) { + logprintf(0, "Process Error: net_thread failed to push pages, " + "bailing\n"); + break; + } + loops++; + } + + pages_copied_ = loops; + status_ = result; + + // Clean up. + CloseSocket(sock); + + logprintf(9, "Log: Completed %d: network thread status %d, " + "%d pages copied\n", + thread_num_, status_, pages_copied_); + return result; +} + +// Spawn slave threads for incoming connections. +bool NetworkListenThread::SpawnSlave(int newsock, int threadid) { + logprintf(12, "Log: Listen thread spawning slave\n"); + + // Spawn slave thread, to reflect network traffic back to sender. + ChildWorker *child_worker = new ChildWorker; + child_worker->thread.SetSock(newsock); + child_worker->thread.InitThread(threadid, sat_, os_, patternlist_, + &child_worker->status); + child_worker->status.Initialize(); + child_worker->thread.SpawnThread(); + child_workers_.push_back(child_worker); + + return true; +} + +// Reap slave threads. +bool NetworkListenThread::ReapSlaves() { + bool result = true; + // Gather status and reap threads. + logprintf(12, "Log: Joining all outstanding threads\n"); + + for (size_t i = 0; i < child_workers_.size(); i++) { + NetworkSlaveThread& child_thread = child_workers_[i]->thread; + logprintf(12, "Log: Joining slave thread %d\n", i); + child_thread.JoinThread(); + if (child_thread.GetStatus() != 1) { + logprintf(0, "Process Error: Slave Thread %d failed with status %d\n", i, + child_thread.GetStatus()); + result = false; + } + errorcount_ += child_thread.GetErrorCount(); + logprintf(9, "Log: Slave Thread %d found %lld miscompares\n", i, + child_thread.GetErrorCount()); + pages_copied_ += child_thread.GetPageCount(); + } + + return result; +} + +// Network listener IO work loop. Execute until marked done. +// Return false on fatal software error. +bool NetworkListenThread::Work() { + logprintf(9, "Log: Starting network listen thread %d\n", + thread_num_); + + // Make a socket. + sock_ = 0; + if (!CreateSocket(&sock_)) { + status_ = false; + return false; + } + logprintf(9, "Log: Listen thread created sock\n"); + + // Allows incoming connections to be queued up by socket library. + int newsock = 0; + Listen(); + logprintf(12, "Log: Listen thread waiting for incoming connections\n"); + + // Wait on incoming connections, and spawn worker threads for them. + int threadcount = 0; + while (IsReadyToRun()) { + // Poll for connections that we can accept(). + if (Wait()) { + // Accept those connections. + logprintf(12, "Log: Listen thread found incoming connection\n"); + if (GetConnection(&newsock)) { + SpawnSlave(newsock, threadcount); + threadcount++; + } + } + } + + // Gather status and join spawned threads. + ReapSlaves(); + + // Delete the child workers. + for (ChildVector::iterator it = child_workers_.begin(); + it != child_workers_.end(); ++it) { + (*it)->status.Destroy(); + delete *it; + } + child_workers_.clear(); + + CloseSocket(sock_); + + status_ = true; + logprintf(9, + "Log: Completed %d: network listen thread status %d, " + "%d pages copied\n", + thread_num_, status_, pages_copied_); + return true; +} + +// Set network reflector socket struct. +void NetworkSlaveThread::SetSock(int sock) { + sock_ = sock; +} + +// Network reflector IO work loop. Execute until marked done. +// Return false on fatal software error. +bool NetworkSlaveThread::Work() { + logprintf(9, "Log: Starting network slave thread %d\n", + thread_num_); + + // Verify that we have a socket. + int sock = sock_; + if (!sock) { + status_ = false; + return false; + } + + // Loop until done. + int64 loops = 0; + // Init a local buffer for storing data. + void *local_page = NULL; + int result = posix_memalign(&local_page, 512, sat_->page_length()); + if (result) { + logprintf(0, "Process Error: net slave posix_memalign " + "returned %d (fail)\n", + result); + status_ = false; + return false; + } + + struct page_entry page; + page.addr = local_page; + + // This thread will continue to run as long as the thread on the other end of + // the socket is still sending and receiving data. + while (1) { + // Do the network read. + if (!ReceivePage(sock, &page)) + break; + + // Do the network write. + if (!SendPage(sock, &page)) + break; + + loops++; + } + + pages_copied_ = loops; + // No results provided from this type of thread. + status_ = true; + + // Clean up. + CloseSocket(sock); + + logprintf(9, + "Log: Completed %d: network slave thread status %d, " + "%d pages copied\n", + thread_num_, status_, pages_copied_); + return true; +} + +// Thread work loop. Execute until marked finished. +bool ErrorPollThread::Work() { + logprintf(9, "Log: Starting system error poll thread %d\n", thread_num_); + + // This calls a generic error polling function in the Os abstraction layer. + do { + errorcount_ += os_->ErrorPoll(); + os_->ErrorWait(); + } while (IsReadyToRun()); + + logprintf(9, "Log: Finished system error poll thread %d: %d errors\n", + thread_num_, errorcount_); + status_ = true; + return true; +} + +// Worker thread to heat up CPU. +// This thread does not evaluate pass/fail or software error. +bool CpuStressThread::Work() { + logprintf(9, "Log: Starting CPU stress thread %d\n", thread_num_); + + do { + // Run ludloff's platform/CPU-specific assembly workload. + os_->CpuStressWorkload(); + YieldSelf(); + } while (IsReadyToRun()); + + logprintf(9, "Log: Finished CPU stress thread %d:\n", + thread_num_); + status_ = true; + return true; +} + +CpuCacheCoherencyThread::CpuCacheCoherencyThread(cc_cacheline_data *data, + int cacheline_count, + int thread_num, + int inc_count) { + cc_cacheline_data_ = data; + cc_cacheline_count_ = cacheline_count; + cc_thread_num_ = thread_num; + cc_inc_count_ = inc_count; +} + +// Worked thread to test the cache coherency of the CPUs +// Return false on fatal sw error. +bool CpuCacheCoherencyThread::Work() { + logprintf(9, "Log: Starting the Cache Coherency thread %d\n", + cc_thread_num_); + uint64 time_start, time_end; + struct timeval tv; + + unsigned int seed = static_cast<unsigned int>(gettid()); + gettimeofday(&tv, NULL); // Get the timestamp before increments. + time_start = tv.tv_sec * 1000000ULL + tv.tv_usec; + + uint64 total_inc = 0; // Total increments done by the thread. + while (IsReadyToRun()) { + for (int i = 0; i < cc_inc_count_; i++) { + // Choose a datastructure in random and increment the appropriate + // member in that according to the offset (which is the same as the + // thread number. + int r = rand_r(&seed); + r = cc_cacheline_count_ * (r / (RAND_MAX + 1.0)); + // Increment the member of the randomely selected structure. + (cc_cacheline_data_[r].num[cc_thread_num_])++; + } + + total_inc += cc_inc_count_; + + // Calculate if the local counter matches with the global value + // in all the cache line structures for this particular thread. + int cc_global_num = 0; + for (int cline_num = 0; cline_num < cc_cacheline_count_; cline_num++) { + cc_global_num += cc_cacheline_data_[cline_num].num[cc_thread_num_]; + // Reset the cachline member's value for the next run. + cc_cacheline_data_[cline_num].num[cc_thread_num_] = 0; + } + if (sat_->error_injection()) + cc_global_num = -1; + + if (cc_global_num != cc_inc_count_) { + errorcount_++; + logprintf(0, "Hardware Error: global(%d) and local(%d) do not match\n", + cc_global_num, cc_inc_count_); + } + } + gettimeofday(&tv, NULL); // Get the timestamp at the end. + time_end = tv.tv_sec * 1000000ULL + tv.tv_usec; + + uint64 us_elapsed = time_end - time_start; + // inc_rate is the no. of increments per second. + double inc_rate = total_inc * 1e6 / us_elapsed; + + logprintf(4, "Stats: CC Thread(%d): Time=%llu us," + " Increments=%llu, Increments/sec = %.6lf\n", + cc_thread_num_, us_elapsed, total_inc, inc_rate); + logprintf(9, "Log: Finished CPU Cache Coherency thread %d:\n", + cc_thread_num_); + status_ = true; + return true; +} + +DiskThread::DiskThread(DiskBlockTable *block_table) { + read_block_size_ = kSectorSize; // default 1 sector (512 bytes) + write_block_size_ = kSectorSize; // this assumes read and write block size + // are the same + segment_size_ = -1; // use the entire disk as one segment + cache_size_ = 16 * 1024 * 1024; // assume 16MiB cache by default + // Use a queue such that 3/2 times as much data as the cache can hold + // is written before it is read so that there is little chance the read + // data is in the cache. + queue_size_ = ((cache_size_ / write_block_size_) * 3) / 2; + blocks_per_segment_ = 32; + + read_threshold_ = 100000; // 100ms is a reasonable limit for + write_threshold_ = 100000; // reading/writing a sector + + read_timeout_ = 5000000; // 5 seconds should be long enough for a + write_timeout_ = 5000000; // timout for reading/writing + + device_sectors_ = 0; + non_destructive_ = 0; + + aio_ctx_ = 0; + block_table_ = block_table; + update_block_table_ = 1; + + block_buffer_ = NULL; + + blocks_written_ = 0; + blocks_read_ = 0; +} + +DiskThread::~DiskThread() { + if (block_buffer_) + free(block_buffer_); +} + +// Set filename for device file (in /dev). +void DiskThread::SetDevice(const char *device_name) { + device_name_ = device_name; +} + +// Set various parameters that control the behaviour of the test. +// -1 is used as a sentinel value on each parameter (except non_destructive) +// to indicate that the parameter not be set. +bool DiskThread::SetParameters(int read_block_size, + int write_block_size, + int64 segment_size, + int64 cache_size, + int blocks_per_segment, + int64 read_threshold, + int64 write_threshold, + int non_destructive) { + if (read_block_size != -1) { + // Blocks must be aligned to the disk's sector size. + if (read_block_size % kSectorSize != 0) { + logprintf(0, "Process Error: Block size must be a multiple of %d " + "(thread %d).\n", kSectorSize, thread_num_); + return false; + } + + read_block_size_ = read_block_size; + } + + if (write_block_size != -1) { + // Write blocks must be aligned to the disk's sector size and to the + // block size. + if (write_block_size % kSectorSize != 0) { + logprintf(0, "Process Error: Write block size must be a multiple " + "of %d (thread %d).\n", kSectorSize, thread_num_); + return false; + } + if (write_block_size % read_block_size_ != 0) { + logprintf(0, "Process Error: Write block size must be a multiple " + "of the read block size, which is %d (thread %d).\n", + read_block_size_, thread_num_); + return false; + } + + write_block_size_ = write_block_size; + + } else { + // Make sure write_block_size_ is still valid. + if (read_block_size_ > write_block_size_) { + logprintf(5, "Log: Assuming write block size equal to read block size, " + "which is %d (thread %d).\n", read_block_size_, + thread_num_); + write_block_size_ = read_block_size_; + } else { + if (write_block_size_ % read_block_size_ != 0) { + logprintf(0, "Process Error: Write block size (defined as %d) must " + "be a multiple of the read block size, which is %d " + "(thread %d).\n", write_block_size_, read_block_size_, + thread_num_); + return false; + } + } + } + + if (cache_size != -1) { + cache_size_ = cache_size; + } + + if (blocks_per_segment != -1) { + if (blocks_per_segment <= 0) { + logprintf(0, "Process Error: Blocks per segment must be greater than " + "zero.\n (thread %d)", thread_num_); + return false; + } + + blocks_per_segment_ = blocks_per_segment; + } + + if (read_threshold != -1) { + if (read_threshold <= 0) { + logprintf(0, "Process Error: Read threshold must be greater than " + "zero (thread %d).\n", thread_num_); + return false; + } + + read_threshold_ = read_threshold; + } + + if (write_threshold != -1) { + if (write_threshold <= 0) { + logprintf(0, "Process Error: Write threshold must be greater than " + "zero (thread %d).\n", thread_num_); + return false; + } + + write_threshold_ = write_threshold; + } + + if (segment_size != -1) { + // Segments must be aligned to the disk's sector size. + if (segment_size % kSectorSize != 0) { + logprintf(0, "Process Error: Segment size must be a multiple of %d" + " (thread %d).\n", kSectorSize, thread_num_); + return false; + } + + segment_size_ = segment_size / kSectorSize; + } + + non_destructive_ = non_destructive; + + // Having a queue of 150% of blocks that will fit in the disk's cache + // should be enough to force out the oldest block before it is read and hence, + // making sure the data comes form the disk and not the cache. + queue_size_ = ((cache_size_ / write_block_size_) * 3) / 2; + // Updating DiskBlockTable parameters + if (update_block_table_) { + block_table_->SetParameters(kSectorSize, write_block_size_, + device_sectors_, segment_size_, + device_name_); + } + return true; +} + +// Open a device, return false on failure. +bool DiskThread::OpenDevice(int *pfile) { + int fd = open(device_name_.c_str(), + O_RDWR | O_SYNC | O_DIRECT | O_LARGEFILE, + 0); + if (fd < 0) { + logprintf(0, "Process Error: Failed to open device %s (thread %d)!!\n", + device_name_.c_str(), thread_num_); + return false; + } + *pfile = fd; + + return GetDiskSize(fd); +} + +// Retrieves the size (in bytes) of the disk/file. +// Return false on failure. +bool DiskThread::GetDiskSize(int fd) { + struct stat device_stat; + if (fstat(fd, &device_stat) == -1) { + logprintf(0, "Process Error: Unable to fstat disk %s (thread %d).\n", + device_name_.c_str(), thread_num_); + return false; + } + + // For a block device, an ioctl is needed to get the size since the size + // of the device file (i.e. /dev/sdb) is 0. + if (S_ISBLK(device_stat.st_mode)) { + uint64 block_size = 0; + + if (ioctl(fd, BLKGETSIZE64, &block_size) == -1) { + logprintf(0, "Process Error: Unable to ioctl disk %s (thread %d).\n", + device_name_.c_str(), thread_num_); + return false; + } + + // Zero size indicates nonworking device.. + if (block_size == 0) { + os_->ErrorReport(device_name_.c_str(), "device-size-zero", 1); + ++errorcount_; + status_ = true; // Avoid a procedural error. + return false; + } + + device_sectors_ = block_size / kSectorSize; + + } else if (S_ISREG(device_stat.st_mode)) { + device_sectors_ = device_stat.st_size / kSectorSize; + + } else { + logprintf(0, "Process Error: %s is not a regular file or block " + "device (thread %d).\n", device_name_.c_str(), + thread_num_); + return false; + } + + logprintf(12, "Log: Device sectors: %lld on disk %s (thread %d).\n", + device_sectors_, device_name_.c_str(), thread_num_); + + if (update_block_table_) { + block_table_->SetParameters(kSectorSize, write_block_size_, + device_sectors_, segment_size_, + device_name_); + } + + return true; +} + +bool DiskThread::CloseDevice(int fd) { + close(fd); + return true; +} + +// Return the time in microseconds. +int64 DiskThread::GetTime() { + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec * 1000000 + tv.tv_usec; +} + +// Do randomized reads and (possibly) writes on a device. +// Return false on fatal SW error, true on SW success, +// regardless of whether HW failed. +bool DiskThread::DoWork(int fd) { + int64 block_num = 0; + int64 num_segments; + + if (segment_size_ == -1) { + num_segments = 1; + } else { + num_segments = device_sectors_ / segment_size_; + if (device_sectors_ % segment_size_ != 0) + num_segments++; + } + + // Disk size should be at least 3x cache size. See comment later for + // details. + sat_assert(device_sectors_ * kSectorSize > 3 * cache_size_); + + // This disk test works by writing blocks with a certain pattern to + // disk, then reading them back and verifying it against the pattern + // at a later time. A failure happens when either the block cannot + // be written/read or when the read block is different than what was + // written. If a block takes too long to write/read, then a warning + // is given instead of an error since taking too long is not + // necessarily an error. + // + // To prevent the read blocks from coming from the disk cache, + // enough blocks are written before read such that a block would + // be ejected from the disk cache by the time it is read. + // + // TODO(amistry): Implement some sort of read/write throttling. The + // flood of asynchronous I/O requests when a drive is + // unplugged is causing the application and kernel to + // become unresponsive. + + while (IsReadyToRun()) { + // Write blocks to disk. + logprintf(16, "Log: Write phase %sfor disk %s (thread %d).\n", + non_destructive_ ? "(disabled) " : "", + device_name_.c_str(), thread_num_); + while (IsReadyToRunNoPause() && + in_flight_sectors_.size() < + static_cast<size_t>(queue_size_ + 1)) { + // Confine testing to a particular segment of the disk. + int64 segment = (block_num / blocks_per_segment_) % num_segments; + if (!non_destructive_ && + (block_num % blocks_per_segment_ == 0)) { + logprintf(20, "Log: Starting to write segment %lld out of " + "%lld on disk %s (thread %d).\n", + segment, num_segments, device_name_.c_str(), + thread_num_); + } + block_num++; + + BlockData *block = block_table_->GetUnusedBlock(segment); + + // If an unused sequence of sectors could not be found, skip to the + // next block to process. Soon, a new segment will come and new + // sectors will be able to be allocated. This effectively puts a + // minumim on the disk size at 3x the stated cache size, or 48MiB + // if a cache size is not given (since the cache is set as 16MiB + // by default). Given that todays caches are at the low MiB range + // and drive sizes at the mid GB, this shouldn't pose a problem. + // The 3x minimum comes from the following: + // 1. In order to allocate 'y' blocks from a segment, the + // segment must contain at least 2y blocks or else an + // allocation may not succeed. + // 2. Assume the entire disk is one segment. + // 3. A full write phase consists of writing blocks corresponding to + // 3/2 cache size. + // 4. Therefore, the one segment must have 2 * 3/2 * cache + // size worth of blocks = 3 * cache size worth of blocks + // to complete. + // In non-destructive mode, don't write anything to disk. + if (!non_destructive_) { + if (!WriteBlockToDisk(fd, block)) { + block_table_->RemoveBlock(block); + return true; + } + blocks_written_++; + } + + // Block is either initialized by writing, or in nondestructive case, + // initialized by being added into the datastructure for later reading. + block->SetBlockAsInitialized(); + + in_flight_sectors_.push(block); + } + + // Verify blocks on disk. + logprintf(20, "Log: Read phase for disk %s (thread %d).\n", + device_name_.c_str(), thread_num_); + while (IsReadyToRunNoPause() && !in_flight_sectors_.empty()) { + BlockData *block = in_flight_sectors_.front(); + in_flight_sectors_.pop(); + if (!ValidateBlockOnDisk(fd, block)) + return true; + block_table_->RemoveBlock(block); + blocks_read_++; + } + } + + pages_copied_ = blocks_written_ + blocks_read_; + return true; +} + +// Do an asynchronous disk I/O operation. +// Return false if the IO is not set up. +bool DiskThread::AsyncDiskIO(IoOp op, int fd, void *buf, int64 size, + int64 offset, int64 timeout) { + // Use the Linux native asynchronous I/O interface for reading/writing. + // A read/write consists of three basic steps: + // 1. create an io context. + // 2. prepare and submit an io request to the context + // 3. wait for an event on the context. + + struct { + const int opcode; + const char *op_str; + const char *error_str; + } operations[2] = { + { IO_CMD_PREAD, "read", "disk-read-error" }, + { IO_CMD_PWRITE, "write", "disk-write-error" } + }; + + struct iocb cb; + memset(&cb, 0, sizeof(cb)); + + cb.aio_fildes = fd; + cb.aio_lio_opcode = operations[op].opcode; + cb.u.c.buf = buf; + cb.u.c.nbytes = size; + cb.u.c.offset = offset; + + struct iocb *cbs[] = { &cb }; + if (io_submit(aio_ctx_, 1, cbs) != 1) { + int error = errno; + char buf[256]; + sat_strerror(error, buf, sizeof(buf)); + logprintf(0, "Process Error: Unable to submit async %s " + "on disk %s (thread %d). Error %d, %s\n", + operations[op].op_str, device_name_.c_str(), + thread_num_, error, buf); + return false; + } + + struct io_event event; + memset(&event, 0, sizeof(event)); + struct timespec tv; + tv.tv_sec = timeout / 1000000; + tv.tv_nsec = (timeout % 1000000) * 1000; + if (io_getevents(aio_ctx_, 1, 1, &event, &tv) != 1) { + // A ctrl-c from the keyboard will cause io_getevents to fail with an + // EINTR error code. This is not an error and so don't treat it as such, + // but still log it. + int error = errno; + if (error == EINTR) { + logprintf(5, "Log: %s interrupted on disk %s (thread %d).\n", + operations[op].op_str, device_name_.c_str(), + thread_num_); + } else { + os_->ErrorReport(device_name_.c_str(), operations[op].error_str, 1); + errorcount_ += 1; + logprintf(0, "Hardware Error: Timeout doing async %s to sectors " + "starting at %lld on disk %s (thread %d).\n", + operations[op].op_str, offset / kSectorSize, + device_name_.c_str(), thread_num_); + } + + // Don't bother checking return codes since io_cancel seems to always fail. + // Since io_cancel is always failing, destroying and recreating an I/O + // context is a workaround for canceling an in-progress I/O operation. + // TODO(amistry): Find out why io_cancel isn't working and make it work. + io_cancel(aio_ctx_, &cb, &event); + io_destroy(aio_ctx_); + aio_ctx_ = 0; + if (io_setup(5, &aio_ctx_)) { + int error = errno; + char buf[256]; + sat_strerror(error, buf, sizeof(buf)); + logprintf(0, "Process Error: Unable to create aio context on disk %s" + " (thread %d) Error %d, %s\n", + device_name_.c_str(), thread_num_, error, buf); + } + + return false; + } + + // event.res contains the number of bytes written/read or + // error if < 0, I think. + if (event.res != static_cast<uint64>(size)) { + errorcount_++; + os_->ErrorReport(device_name_.c_str(), operations[op].error_str, 1); + + if (event.res < 0) { + switch (event.res) { + case -EIO: + logprintf(0, "Hardware Error: Low-level I/O error while doing %s to " + "sectors starting at %lld on disk %s (thread %d).\n", + operations[op].op_str, offset / kSectorSize, + device_name_.c_str(), thread_num_); + break; + default: + logprintf(0, "Hardware Error: Unknown error while doing %s to " + "sectors starting at %lld on disk %s (thread %d).\n", + operations[op].op_str, offset / kSectorSize, + device_name_.c_str(), thread_num_); + } + } else { + logprintf(0, "Hardware Error: Unable to %s to sectors starting at " + "%lld on disk %s (thread %d).\n", + operations[op].op_str, offset / kSectorSize, + device_name_.c_str(), thread_num_); + } + return false; + } + + return true; +} + +// Write a block to disk. +// Return false if the block is not written. +bool DiskThread::WriteBlockToDisk(int fd, BlockData *block) { + memset(block_buffer_, 0, block->GetSize()); + + // Fill block buffer with a pattern + struct page_entry pe; + if (!sat_->GetValid(&pe)) { + // Even though a valid page could not be obatined, it is not an error + // since we can always fill in a pattern directly, albeit slower. + unsigned int *memblock = static_cast<unsigned int *>(block_buffer_); + block->SetPattern(patternlist_->GetRandomPattern()); + + logprintf(11, "Log: Warning, using pattern fill fallback in " + "DiskThread::WriteBlockToDisk on disk %s (thread %d).\n", + device_name_.c_str(), thread_num_); + + for (int i = 0; i < block->GetSize()/wordsize_; i++) { + memblock[i] = block->GetPattern()->pattern(i); + } + } else { + memcpy(block_buffer_, pe.addr, block->GetSize()); + block->SetPattern(pe.pattern); + sat_->PutValid(&pe); + } + + logprintf(12, "Log: Writing %lld sectors starting at %lld on disk %s" + " (thread %d).\n", + block->GetSize()/kSectorSize, block->GetAddress(), + device_name_.c_str(), thread_num_); + + int64 start_time = GetTime(); + + if (!AsyncDiskIO(ASYNC_IO_WRITE, fd, block_buffer_, block->GetSize(), + block->GetAddress() * kSectorSize, write_timeout_)) { + return false; + } + + int64 end_time = GetTime(); + logprintf(12, "Log: Writing time: %lld us (thread %d).\n", + end_time - start_time, thread_num_); + if (end_time - start_time > write_threshold_) { + logprintf(5, "Log: Write took %lld us which is longer than threshold " + "%lld us on disk %s (thread %d).\n", + end_time - start_time, write_threshold_, device_name_.c_str(), + thread_num_); + } + + return true; +} + +// Verify a block on disk. +// Return true if the block was read, also increment errorcount +// if the block had data errors or performance problems. +bool DiskThread::ValidateBlockOnDisk(int fd, BlockData *block) { + int64 blocks = block->GetSize() / read_block_size_; + int64 bytes_read = 0; + int64 current_blocks; + int64 current_bytes; + uint64 address = block->GetAddress(); + + logprintf(20, "Log: Reading sectors starting at %lld on disk %s " + "(thread %d).\n", + address, device_name_.c_str(), thread_num_); + + // Read block from disk and time the read. If it takes longer than the + // threshold, complain. + if (lseek64(fd, address * kSectorSize, SEEK_SET) == -1) { + logprintf(0, "Process Error: Unable to seek to sector %lld in " + "DiskThread::ValidateSectorsOnDisk on disk %s " + "(thread %d).\n", address, device_name_.c_str(), thread_num_); + return false; + } + int64 start_time = GetTime(); + + // Split a large write-sized block into small read-sized blocks and + // read them in groups of randomly-sized multiples of read block size. + // This assures all data written on disk by this particular block + // will be tested using a random reading pattern. + while (blocks != 0) { + // Test all read blocks in a written block. + current_blocks = (random() % blocks) + 1; + current_bytes = current_blocks * read_block_size_; + + memset(block_buffer_, 0, current_bytes); + + logprintf(20, "Log: Reading %lld sectors starting at sector %lld on " + "disk %s (thread %d)\n", + current_bytes / kSectorSize, + (address * kSectorSize + bytes_read) / kSectorSize, + device_name_.c_str(), thread_num_); + + if (!AsyncDiskIO(ASYNC_IO_READ, fd, block_buffer_, current_bytes, + address * kSectorSize + bytes_read, + write_timeout_)) { + return false; + } + + int64 end_time = GetTime(); + logprintf(20, "Log: Reading time: %lld us (thread %d).\n", + end_time - start_time, thread_num_); + if (end_time - start_time > read_threshold_) { + logprintf(5, "Log: Read took %lld us which is longer than threshold " + "%lld us on disk %s (thread %d).\n", + end_time - start_time, read_threshold_, + device_name_.c_str(), thread_num_); + } + + // In non-destructive mode, don't compare the block to the pattern since + // the block was never written to disk in the first place. + if (!non_destructive_) { + if (CheckRegion(block_buffer_, block->GetPattern(), current_bytes, + 0, bytes_read)) { + os_->ErrorReport(device_name_.c_str(), "disk-pattern-error", 1); + errorcount_ += 1; + logprintf(0, "Hardware Error: Pattern mismatch in block starting at " + "sector %lld in DiskThread::ValidateSectorsOnDisk on " + "disk %s (thread %d).\n", + address, device_name_.c_str(), thread_num_); + } + } + + bytes_read += current_blocks * read_block_size_; + blocks -= current_blocks; + } + + return true; +} + +// Direct device access thread. +// Return false on software error. +bool DiskThread::Work() { + int fd; + + logprintf(9, "Log: Starting disk thread %d, disk %s\n", + thread_num_, device_name_.c_str()); + + srandom(time(NULL)); + + if (!OpenDevice(&fd)) { + status_ = false; + return false; + } + + // Allocate a block buffer aligned to 512 bytes since the kernel requires it + // when using direst IO. + int memalign_result = posix_memalign(&block_buffer_, kBufferAlignment, + sat_->page_length()); + if (memalign_result) { + CloseDevice(fd); + logprintf(0, "Process Error: Unable to allocate memory for buffers " + "for disk %s (thread %d) posix memalign returned %d.\n", + device_name_.c_str(), thread_num_, memalign_result); + status_ = false; + return false; + } + + if (io_setup(5, &aio_ctx_)) { + CloseDevice(fd); + logprintf(0, "Process Error: Unable to create aio context for disk %s" + " (thread %d).\n", + device_name_.c_str(), thread_num_); + status_ = false; + return false; + } + + bool result = DoWork(fd); + + status_ = result; + + io_destroy(aio_ctx_); + CloseDevice(fd); + + logprintf(9, "Log: Completed %d (disk %s): disk thread status %d, " + "%d pages copied\n", + thread_num_, device_name_.c_str(), status_, pages_copied_); + return result; +} + +RandomDiskThread::RandomDiskThread(DiskBlockTable *block_table) + : DiskThread(block_table) { + update_block_table_ = 0; +} + +RandomDiskThread::~RandomDiskThread() { +} + +// Workload for random disk thread. +bool RandomDiskThread::DoWork(int fd) { + logprintf(11, "Log: Random phase for disk %s (thread %d).\n", + device_name_.c_str(), thread_num_); + while (IsReadyToRun()) { + BlockData *block = block_table_->GetRandomBlock(); + if (block == NULL) { + logprintf(12, "Log: No block available for device %s (thread %d).\n", + device_name_.c_str(), thread_num_); + } else { + ValidateBlockOnDisk(fd, block); + block_table_->ReleaseBlock(block); + blocks_read_++; + } + } + pages_copied_ = blocks_read_; + return true; +} + +MemoryRegionThread::MemoryRegionThread() { + error_injection_ = false; + pages_ = NULL; +} + +MemoryRegionThread::~MemoryRegionThread() { + if (pages_ != NULL) + delete pages_; +} + +// Set a region of memory or MMIO to be tested. +// Return false if region could not be mapped. +bool MemoryRegionThread::SetRegion(void *region, int64 size) { + int plength = sat_->page_length(); + int npages = size / plength; + if (size % plength) { + logprintf(0, "Process Error: region size is not a multiple of SAT " + "page length\n"); + return false; + } else { + if (pages_ != NULL) + delete pages_; + pages_ = new PageEntryQueue(npages); + char *base_addr = reinterpret_cast<char*>(region); + region_ = base_addr; + for (int i = 0; i < npages; i++) { + struct page_entry pe; + init_pe(&pe); + pe.addr = reinterpret_cast<void*>(base_addr + i * plength); + pe.offset = i * plength; + + pages_->Push(&pe); + } + return true; + } +} + +// More detailed error printout for hardware errors in memory or MMIO +// regions. +void MemoryRegionThread::ProcessError(struct ErrorRecord *error, + int priority, + const char *message) { + uint32 buffer_offset; + if (phase_ == kPhaseCopy) { + // If the error occurred on the Copy Phase, it means that + // the source data (i.e., the main memory) is wrong. so + // just pass it to the original ProcessError to call a + // bad-dimm error + WorkerThread::ProcessError(error, priority, message); + } else if (phase_ == kPhaseCheck) { + // A error on the Check Phase means that the memory region tested + // has an error. Gathering more information and then reporting + // the error. + // Determine if this is a write or read error. + os_->Flush(error->vaddr); + error->reread = *(error->vaddr); + char *good = reinterpret_cast<char*>(&(error->expected)); + char *bad = reinterpret_cast<char*>(&(error->actual)); + sat_assert(error->expected != error->actual); + unsigned int offset = 0; + for (offset = 0; offset < (sizeof(error->expected) - 1); offset++) { + if (good[offset] != bad[offset]) + break; + } + + error->vbyteaddr = reinterpret_cast<char*>(error->vaddr) + offset; + + buffer_offset = error->vbyteaddr - region_; + + // Find physical address if possible. + error->paddr = os_->VirtualToPhysical(error->vbyteaddr); + logprintf(priority, + "%s: miscompare on %s, CRC check at %p(0x%llx), " + "offset %llx: read:0x%016llx, reread:0x%016llx " + "expected:0x%016llx\n", + message, + identifier_.c_str(), + error->vaddr, + error->paddr, + buffer_offset, + error->actual, + error->reread, + error->expected); + } else { + logprintf(0, "Process Error: memory region thread raised an " + "unexpected error."); + } +} + +// Workload for testion memory or MMIO regions. +// Return false on software error. +bool MemoryRegionThread::Work() { + struct page_entry source_pe; + struct page_entry memregion_pe; + bool result = true; + int64 loops = 0; + const uint64 error_constant = 0x00ba00000000ba00LL; + + // For error injection. + int64 *addr = 0x0; + int offset = 0; + int64 data = 0; + + logprintf(9, "Log: Starting Memory Region thread %d\n", thread_num_); + + while (IsReadyToRun()) { + // Getting pages from SAT and queue. + phase_ = kPhaseNoPhase; + result = result && sat_->GetValid(&source_pe); + if (!result) { + logprintf(0, "Process Error: memory region thread failed to pop " + "pages from SAT, bailing\n"); + break; + } + + result = result && pages_->PopRandom(&memregion_pe); + if (!result) { + logprintf(0, "Process Error: memory region thread failed to pop " + "pages from queue, bailing\n"); + break; + } + + // Error injection for CRC copy. + if ((sat_->error_injection() || error_injection_) && loops == 1) { + addr = reinterpret_cast<int64*>(source_pe.addr); + offset = random() % (sat_->page_length() / wordsize_); + data = addr[offset]; + addr[offset] = error_constant; + } + + // Copying SAT page into memory region. + phase_ = kPhaseCopy; + CrcCopyPage(&memregion_pe, &source_pe); + memregion_pe.pattern = source_pe.pattern; + + // Error injection for CRC Check. + if ((sat_->error_injection() || error_injection_) && loops == 2) { + addr = reinterpret_cast<int64*>(memregion_pe.addr); + offset = random() % (sat_->page_length() / wordsize_); + data = addr[offset]; + addr[offset] = error_constant; + } + + // Checking page content in memory region. + phase_ = kPhaseCheck; + CrcCheckPage(&memregion_pe); + + phase_ = kPhaseNoPhase; + // Storing pages on their proper queues. + result = result && sat_->PutValid(&source_pe); + if (!result) { + logprintf(0, "Process Error: memory region thread failed to push " + "pages into SAT, bailing\n"); + break; + } + result = result && pages_->Push(&memregion_pe); + if (!result) { + logprintf(0, "Process Error: memory region thread failed to push " + "pages into queue, bailing\n"); + break; + } + + if ((sat_->error_injection() || error_injection_) && + loops >= 1 && loops <= 2) { + addr[offset] = data; + } + + loops++; + YieldSelf(); + } + + pages_copied_ = loops; + status_ = result; + logprintf(9, "Log: Completed %d: Memory Region thread. Status %d, %d " + "pages checked\n", thread_num_, status_, pages_copied_); + return result; +} diff --git a/src/worker.h b/src/worker.h new file mode 100644 index 0000000..7aae5f2 --- /dev/null +++ b/src/worker.h @@ -0,0 +1,804 @@ +// Copyright 2006 Google Inc. All Rights Reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// worker.h : worker thread interface + +// This file contains the Worker Thread class interface +// for the SAT test. Worker Threads implement a repetative +// task used to test or stress the system. + +#ifndef STRESSAPPTEST_WORKER_H_ +#define STRESSAPPTEST_WORKER_H_ + +#include <pthread.h> + +#include <sys/time.h> +#include <sys/types.h> + +#include <libaio.h> + +#include <queue> +#include <set> +#include <string> +#include <vector> + +// This file must work with autoconf on its public version, +// so these includes are correct. +#include "disk_blocks.h" +#include "queue.h" +#include "sattypes.h" + + +// Global Datastruture shared by the Cache Coherency Worker Threads. +struct cc_cacheline_data { + int *num; +}; + +// Typical usage: +// (Other workflows may be possible, see function comments for details.) +// - Control thread creates object. +// - Control thread calls AddWorkers(1) for each worker thread. +// - Control thread calls Initialize(). +// - Control thread launches worker threads. +// - Every worker thread frequently calls ContinueRunning(). +// - Control thread periodically calls PauseWorkers(), effectively sleeps, and +// then calls ResumeWorkers(). +// - Some worker threads may exit early, before StopWorkers() is called. They +// call RemoveSelf() after their last call to ContinueRunning(). +// - Control thread eventually calls StopWorkers(). +// - Worker threads exit. +// - Control thread joins worker threads. +// - Control thread calls Destroy(). +// - Control thread destroys object. +// +// Threadsafety: +// - ContinueRunning() may be called concurrently by different workers, but not +// by a single worker. +// - No other methods may ever be called concurrently, with themselves or +// eachother. +// - This object may be used by multiple threads only between Initialize() and +// Destroy(). +// +// TODO(matthewb): Move this class and its unittest to their own files. +class WorkerStatus { + public: + //-------------------------------- + // Methods for the control thread. + //-------------------------------- + + WorkerStatus() : num_workers_(0), status_(RUN) {} + + // Called by the control thread to increase the worker count. Must be called + // before Initialize(). The worker count is 0 upon object initialization. + void AddWorkers(int num_new_workers) { + // No need to lock num_workers_mutex_ because this is before Initialize(). + num_workers_ += num_new_workers; + } + + // Called by the control thread. May not be called multiple times. If + // called, Destroy() must be called before destruction. + void Initialize(); + + // Called by the control thread after joining all worker threads. Must be + // called iff Initialize() was called. No methods may be called after calling + // this. + void Destroy(); + + // Called by the control thread to tell the workers to pause. Does not return + // until all workers have called ContinueRunning() or RemoveSelf(). May only + // be called between Initialize() and Stop(). Must not be called multiple + // times without ResumeWorkers() having been called inbetween. + void PauseWorkers(); + + // Called by the control thread to tell the workers to resume from a pause. + // May only be called between Initialize() and Stop(). May only be called + // directly after PauseWorkers(). + void ResumeWorkers(); + + // Called by the control thread to tell the workers to stop. May only be + // called between Initialize() and Destroy(). May only be called once. + void StopWorkers(); + + //-------------------------------- + // Methods for the worker threads. + //-------------------------------- + + // Called by worker threads to decrease the worker count by one. May only be + // called between Initialize() and Destroy(). May wait for ResumeWorkers() + // when called after PauseWorkers(). + void RemoveSelf(); + + // Called by worker threads between Initialize() and Destroy(). May be called + // any number of times. Return value is whether or not the worker should + // continue running. When called after PauseWorkers(), does not return until + // ResumeWorkers() or StopWorkers() has been called. Number of distinct + // calling threads must match the worker count (see AddWorkers() and + // RemoveSelf()). + bool ContinueRunning(); + + // TODO(matthewb): Is this functionality really necessary? Remove it if not. + // + // This is a hack! It's like ContinueRunning(), except it won't pause. If + // any worker threads use this exclusively in place of ContinueRunning() then + // PauseWorkers() should never be used! + bool ContinueRunningNoPause(); + + private: + enum Status { RUN, PAUSE, STOP }; + + void WaitOnPauseBarrier() { + int error = pthread_barrier_wait(&pause_barrier_); + if (error != PTHREAD_BARRIER_SERIAL_THREAD) + sat_assert(error == 0); + } + + void AcquireNumWorkersLock() { + sat_assert(0 == pthread_mutex_lock(&num_workers_mutex_)); + } + + void ReleaseNumWorkersLock() { + sat_assert(0 == pthread_mutex_unlock(&num_workers_mutex_)); + } + + void AcquireStatusReadLock() { + sat_assert(0 == pthread_rwlock_rdlock(&status_rwlock_)); + } + + void AcquireStatusWriteLock() { + sat_assert(0 == pthread_rwlock_wrlock(&status_rwlock_)); + } + + void ReleaseStatusLock() { + sat_assert(0 == pthread_rwlock_unlock(&status_rwlock_)); + } + + Status GetStatus() { + AcquireStatusReadLock(); + Status status = status_; + ReleaseStatusLock(); + return status; + } + + // Returns the previous status. + Status SetStatus(Status status) { + AcquireStatusWriteLock(); + Status prev_status = status_; + status_ = status; + ReleaseStatusLock(); + return prev_status; + } + + pthread_mutex_t num_workers_mutex_; + int num_workers_; + + pthread_rwlock_t status_rwlock_; + Status status_; + + // Guaranteed to not be in use when (status_ != PAUSE). + pthread_barrier_t pause_barrier_; + + DISALLOW_COPY_AND_ASSIGN(WorkerStatus); +}; + + +// This is a base class for worker threads. +// Each thread repeats a specific +// task on various blocks of memory. +class WorkerThread { + public: + // Enum to mark a thread as low/med/high priority. + enum Priority { + Low, + Normal, + High, + }; + WorkerThread(); + virtual ~WorkerThread(); + + // Initialize values and thread ID number. + virtual void InitThread(int thread_num_init, + class Sat *sat_init, + class OsLayer *os_init, + class PatternList *patternlist_init, + WorkerStatus *worker_status); + + // This function is DEPRECATED, it does nothing. + void SetPriority(Priority priority) { priority_ = priority; } + // Spawn the worker thread, by running Work(). + int SpawnThread(); + // Only for ThreadSpawnerGeneric(). + void StartRoutine(); + bool InitPriority(); + + // Wait for the thread to complete its cleanup. + virtual bool JoinThread(); + // Kill worker thread with SIGINT. + virtual bool KillThread(); + + // This is the task function that the thread executes. + // This is implemented per subclass. + virtual bool Work(); + + // Starts per-WorkerThread timer. + void StartThreadTimer() {gettimeofday(&start_time_, NULL);} + // Reads current timer value and returns run duration without recording it. + int64 ReadThreadTimer() { + struct timeval end_time_; + gettimeofday(&end_time_, NULL); + return (end_time_.tv_sec - start_time_.tv_sec)*1000000 + + (end_time_.tv_usec - start_time_.tv_usec); + } + // Stops per-WorkerThread timer and records thread run duration. + // Start/Stop ThreadTimer repetitively has cumulative effect, ie the timer + // is effectively paused and restarted, so runduration_usec accumulates on. + void StopThreadTimer() { + runduration_usec_ += ReadThreadTimer(); + } + + // Acccess member variables. + bool GetStatus() {return status_;} + int64 GetErrorCount() {return errorcount_;} + int64 GetPageCount() {return pages_copied_;} + int64 GetRunDurationUSec() {return runduration_usec_;} + + // Returns bandwidth defined as pages_copied / thread_run_durations. + virtual float GetCopiedData(); + // Calculate worker thread specific copied data. + virtual float GetMemoryCopiedData() {return 0;} + virtual float GetDeviceCopiedData() {return 0;} + // Calculate worker thread specific bandwidth. + virtual float GetMemoryBandwidth() + {return GetMemoryCopiedData() / ( + runduration_usec_ * 1.0 / 1000000);} + virtual float GetDeviceBandwidth() + {return GetDeviceCopiedData() / ( + runduration_usec_ * 1.0 / 1000000);} + + void set_cpu_mask(cpu_set_t *mask) { + memcpy(&cpu_mask_, mask, sizeof(*mask)); + } + + void set_cpu_mask_to_cpu(int cpu_num) { + cpuset_set_ab(&cpu_mask_, cpu_num, cpu_num + 1); + } + + void set_tag(int32 tag) {tag_ = tag;} + + // Returns CPU mask, where each bit represents a logical cpu. + bool AvailableCpus(cpu_set_t *cpuset); + // Returns CPU mask of CPUs this thread is bound to, + bool CurrentCpus(cpu_set_t *cpuset); + // Returns Current Cpus mask as string. + string CurrentCpusFormat() { + cpu_set_t current_cpus; + CurrentCpus(¤t_cpus); + return cpuset_format(¤t_cpus); + } + + int ThreadID() {return thread_num_;} + + // Bind worker thread to specified CPU(s) + bool BindToCpus(const cpu_set_t *cpuset); + + protected: + // This function dictates whether the main work loop + // continues, waits, or terminates. + // All work loops should be of the form: + // do { + // // work. + // } while (IsReadyToRun()); + virtual bool IsReadyToRun() { return worker_status_->ContinueRunning(); } + // TODO(matthewb): Is this function really necessary? Remove it if not. + // + // Like IsReadyToRun(), except it won't pause. + virtual bool IsReadyToRunNoPause() { + return worker_status_->ContinueRunningNoPause(); + } + + // These are functions used by the various work loops. + // Pretty print and log a data miscompare. + virtual void ProcessError(struct ErrorRecord *er, + int priority, + const char *message); + + // Compare a region of memory with a known data patter, and report errors. + virtual int CheckRegion(void *addr, + class Pattern *pat, + int64 length, + int offset, + int64 patternoffset); + + // Fast compare a block of memory. + virtual int CrcCheckPage(struct page_entry *srcpe); + + // Fast copy a block of memory, while verifying correctness. + virtual int CrcCopyPage(struct page_entry *dstpe, + struct page_entry *srcpe); + + // Fast copy a block of memory, while verifying correctness, and heating CPU. + virtual int CrcWarmCopyPage(struct page_entry *dstpe, + struct page_entry *srcpe); + + // Fill a page with its specified pattern. + virtual bool FillPage(struct page_entry *pe); + + // Copy with address tagging. + virtual bool AdlerAddrMemcpyC(uint64 *dstmem64, + uint64 *srcmem64, + unsigned int size_in_bytes, + AdlerChecksum *checksum, + struct page_entry *pe); + // SSE copy with address tagging. + virtual bool AdlerAddrMemcpyWarm(uint64 *dstmem64, + uint64 *srcmem64, + unsigned int size_in_bytes, + AdlerChecksum *checksum, + struct page_entry *pe); + // Crc data with address tagging. + virtual bool AdlerAddrCrcC(uint64 *srcmem64, + unsigned int size_in_bytes, + AdlerChecksum *checksum, + struct page_entry *pe); + // Setup tagging on an existing page. + virtual bool TagAddrC(uint64 *memwords, + unsigned int size_in_bytes); + // Report a mistagged cacheline. + virtual bool ReportTagError(uint64 *mem64, + uint64 actual, + uint64 tag); + // Print out the error record of the tag mismatch. + virtual void ProcessTagError(struct ErrorRecord *error, + int priority, + const char *message); + + // A worker thread can yield itself to give up CPU until it's scheduled again + bool YieldSelf(); + + protected: + // General state variables that all subclasses need. + int thread_num_; // Thread ID. + volatile bool status_; // Error status. + volatile int64 pages_copied_; // Recorded for memory bandwidth calc. + volatile int64 errorcount_; // Miscompares seen by this thread. + + cpu_set_t cpu_mask_; // Cores this thread is allowed to run on. + volatile uint32 tag_; // Tag hint for memory this thread can use. + + bool tag_mode_; // Tag cachelines with vaddr. + + // Thread timing variables. + struct timeval start_time_; // Worker thread start time. + volatile int64 runduration_usec_; // Worker run duration in u-seconds. + + // Function passed to pthread_create. + void *(*thread_spawner_)(void *args); + pthread_t thread_; // Pthread thread ID. + Priority priority_; // Worker thread priority. + class Sat *sat_; // Reference to parent stest object. + class OsLayer *os_; // Os abstraction: put hacks here. + class PatternList *patternlist_; // Reference to data patterns. + + // Work around style guide ban on sizeof(int). + static const uint64 iamint_ = 0; + static const int wordsize_ = sizeof(iamint_); + + private: + WorkerStatus *worker_status_; + + DISALLOW_COPY_AND_ASSIGN(WorkerThread); +}; + +// Worker thread to perform File IO. +class FileThread : public WorkerThread { + public: + FileThread(); + // Set filename to use for file IO. + virtual void SetFile(const char *filename_init); + virtual bool Work(); + + // Calculate worker thread specific bandwidth. + virtual float GetDeviceCopiedData() + {return GetCopiedData()*2;} + virtual float GetMemoryCopiedData(); + + protected: + // Record of where these pages were sourced from, and what + // potentially broken components they passed through. + struct PageRec { + struct Pattern *pattern; // This is the data it should contain. + void *src; // This is the memory location the data was sourced from. + void *dst; // This is where it ended up. + }; + + // These are functions used by the various work loops. + // Pretty print and log a data miscompare. Disks require + // slightly different error handling. + virtual void ProcessError(struct ErrorRecord *er, + int priority, + const char *message); + + virtual bool OpenFile(int *pfile); + virtual bool CloseFile(int fd); + + // Read and write whole file to disk. + virtual bool WritePages(int fd); + virtual bool ReadPages(int fd); + + // Read and write pages to disk. + virtual bool WritePageToFile(int fd, struct page_entry *src); + virtual bool ReadPageFromFile(int fd, struct page_entry *dst); + + // Sector tagging support. + virtual bool SectorTagPage(struct page_entry *src, int block); + virtual bool SectorValidatePage(const struct PageRec &page, + struct page_entry *dst, + int block); + + // Get memory for an incoming data transfer.. + virtual bool PagePrepare(); + // Remove memory allocated for data transfer. + virtual bool PageTeardown(); + + // Get memory for an incoming data transfer.. + virtual bool GetEmptyPage(struct page_entry *dst); + // Get memory for an outgoing data transfer.. + virtual bool GetValidPage(struct page_entry *dst); + // Throw out a used empty page. + virtual bool PutEmptyPage(struct page_entry *src); + // Throw out a used, filled page. + virtual bool PutValidPage(struct page_entry *src); + + + struct PageRec *page_recs_; // Array of page records. + int crc_page_; // Page currently being CRC checked. + string filename_; // Name of file to access. + string devicename_; // Name of device file is on. + + bool page_io_; // Use page pool for IO. + void *local_page_; // malloc'd page fon non-pool IO. + int pass_; // Number of writes to the file so far. + + // Tag to detect file corruption. + struct SectorTag { + volatile uint8 magic; + volatile uint8 block; + volatile uint8 sector; + volatile uint8 pass; + char pad[512-4]; + }; + + DISALLOW_COPY_AND_ASSIGN(FileThread); +}; + + +// Worker thread to perform Network IO. +class NetworkThread : public WorkerThread { + public: + NetworkThread(); + // Set hostname to use for net IO. + virtual void SetIP(const char *ipaddr_init); + virtual bool Work(); + + // Calculate worker thread specific bandwidth. + virtual float GetDeviceCopiedData() + {return GetCopiedData()*2;} + + protected: + // IsReadyToRunNoPause() wrapper, for NetworkSlaveThread to override. + virtual bool IsNetworkStopSet(); + virtual bool CreateSocket(int *psocket); + virtual bool CloseSocket(int sock); + virtual bool Connect(int sock); + virtual bool SendPage(int sock, struct page_entry *src); + virtual bool ReceivePage(int sock, struct page_entry *dst); + char ipaddr_[256]; + int sock_; + + private: + DISALLOW_COPY_AND_ASSIGN(NetworkThread); +}; + +// Worker thread to reflect Network IO. +class NetworkSlaveThread : public NetworkThread { + public: + NetworkSlaveThread(); + // Set socket for IO. + virtual void SetSock(int sock); + virtual bool Work(); + + protected: + virtual bool IsNetworkStopSet(); + + private: + DISALLOW_COPY_AND_ASSIGN(NetworkSlaveThread); +}; + +// Worker thread to detect incoming Network IO. +class NetworkListenThread : public NetworkThread { + public: + NetworkListenThread(); + virtual bool Work(); + + private: + virtual bool Listen(); + virtual bool Wait(); + virtual bool GetConnection(int *pnewsock); + virtual bool SpawnSlave(int newsock, int threadid); + virtual bool ReapSlaves(); + + // For serviced incoming connections. + struct ChildWorker { + WorkerStatus status; + NetworkSlaveThread thread; + }; + typedef vector<ChildWorker*> ChildVector; + ChildVector child_workers_; + + DISALLOW_COPY_AND_ASSIGN(NetworkListenThread); +}; + +// Worker thread to perform Memory Copy. +class CopyThread : public WorkerThread { + public: + CopyThread() {} + virtual bool Work(); + // Calculate worker thread specific bandwidth. + virtual float GetMemoryCopiedData() + {return GetCopiedData()*2;} + + private: + DISALLOW_COPY_AND_ASSIGN(CopyThread); +}; + +// Worker thread to perform Memory Invert. +class InvertThread : public WorkerThread { + public: + InvertThread() {} + virtual bool Work(); + // Calculate worker thread specific bandwidth. + virtual float GetMemoryCopiedData() + {return GetCopiedData()*4;} + + private: + virtual int InvertPageUp(struct page_entry *srcpe); + virtual int InvertPageDown(struct page_entry *srcpe); + DISALLOW_COPY_AND_ASSIGN(InvertThread); +}; + +// Worker thread to fill blank pages on startup. +class FillThread : public WorkerThread { + public: + FillThread(); + // Set how many pages this thread should fill before exiting. + virtual void SetFillPages(int64 num_pages_to_fill_init); + virtual bool Work(); + + private: + // Fill a page with the data pattern in pe->pattern. + virtual bool FillPageRandom(struct page_entry *pe); + int64 num_pages_to_fill_; + DISALLOW_COPY_AND_ASSIGN(FillThread); +}; + +// Worker thread to verify page data matches pattern data. +// Thread will check and replace pages until "done" flag is set, +// then it will check and discard pages until no more remain. +class CheckThread : public WorkerThread { + public: + CheckThread() {} + virtual bool Work(); + // Calculate worker thread specific bandwidth. + virtual float GetMemoryCopiedData() + {return GetCopiedData();} + + private: + DISALLOW_COPY_AND_ASSIGN(CheckThread); +}; + + +// Worker thread to poll for system error messages. +// Thread will check for messages until "done" flag is set. +class ErrorPollThread : public WorkerThread { + public: + ErrorPollThread() {} + virtual bool Work(); + + private: + DISALLOW_COPY_AND_ASSIGN(ErrorPollThread); +}; + +// Computation intensive worker thread to stress CPU. +class CpuStressThread : public WorkerThread { + public: + CpuStressThread() {} + virtual bool Work(); + + private: + DISALLOW_COPY_AND_ASSIGN(CpuStressThread); +}; + +// Worker thread that tests the correctness of the +// CPU Cache Coherency Protocol. +class CpuCacheCoherencyThread : public WorkerThread { + public: + CpuCacheCoherencyThread(cc_cacheline_data *cc_data, + int cc_cacheline_count_, + int cc_thread_num_, + int cc_inc_count_); + virtual bool Work(); + + protected: + cc_cacheline_data *cc_cacheline_data_; // Datstructure for each cacheline. + int cc_local_num_; // Local counter for each thread. + int cc_cacheline_count_; // Number of cache lines to operate on. + int cc_thread_num_; // The integer id of the thread which is + // used as an index into the integer array + // of the cacheline datastructure. + int cc_inc_count_; // Number of times to increment the counter. + + private: + DISALLOW_COPY_AND_ASSIGN(CpuCacheCoherencyThread); +}; + +// Worker thread to perform disk test. +class DiskThread : public WorkerThread { + public: + explicit DiskThread(DiskBlockTable *block_table); + virtual ~DiskThread(); + // Calculate disk thread specific bandwidth. + virtual float GetDeviceCopiedData() { + return (blocks_written_ * write_block_size_ + + blocks_read_ * read_block_size_) / kMegabyte;} + + // Set filename for device file (in /dev). + virtual void SetDevice(const char *device_name); + // Set various parameters that control the behaviour of the test. + virtual bool SetParameters(int read_block_size, + int write_block_size, + int64 segment_size, + int64 cache_size, + int blocks_per_segment, + int64 read_threshold, + int64 write_threshold, + int non_destructive); + + virtual bool Work(); + + virtual float GetMemoryCopiedData() {return 0;} + + protected: + static const int kSectorSize = 512; // Size of sector on disk. + static const int kBufferAlignment = 512; // Buffer alignment required by the + // kernel. + static const int kBlockRetry = 100; // Number of retries to allocate + // sectors. + + enum IoOp { + ASYNC_IO_READ = 0, + ASYNC_IO_WRITE = 1 + }; + + virtual bool OpenDevice(int *pfile); + virtual bool CloseDevice(int fd); + + // Retrieves the size (in bytes) of the disk/file. + virtual bool GetDiskSize(int fd); + + // Retrieves the current time in microseconds. + virtual int64 GetTime(); + + // Do an asynchronous disk I/O operation. + virtual bool AsyncDiskIO(IoOp op, int fd, void *buf, int64 size, + int64 offset, int64 timeout); + + // Write a block to disk. + virtual bool WriteBlockToDisk(int fd, BlockData *block); + + // Verify a block on disk. + virtual bool ValidateBlockOnDisk(int fd, BlockData *block); + + // Main work loop. + virtual bool DoWork(int fd); + + int read_block_size_; // Size of blocks read from disk, in bytes. + int write_block_size_; // Size of blocks written to disk, in bytes. + int64 blocks_read_; // Number of blocks read in work loop. + int64 blocks_written_; // Number of blocks written in work loop. + int64 segment_size_; // Size of disk segments (in bytes) that the disk + // will be split into where testing can be + // confined to a particular segment. + // Allows for control of how evenly the disk will + // be tested. Smaller segments imply more even + // testing (less random). + int blocks_per_segment_; // Number of blocks that will be tested per + // segment. + int cache_size_; // Size of disk cache, in bytes. + int queue_size_; // Length of in-flight-blocks queue, in blocks. + int non_destructive_; // Use non-destructive mode or not. + int update_block_table_; // If true, assume this is the thread + // responsible for writing the data in the disk + // for this block device and, therefore, + // update the block table. If false, just use + // the block table to get data. + + // read/write times threshold for reporting a problem + int64 read_threshold_; // Maximum time a read should take (in us) before + // a warning is given. + int64 write_threshold_; // Maximum time a write should take (in us) before + // a warning is given. + int64 read_timeout_; // Maximum time a read can take before a timeout + // and the aborting of the read operation. + int64 write_timeout_; // Maximum time a write can take before a timeout + // and the aborting of the write operation. + + string device_name_; // Name of device file to access. + int64 device_sectors_; // Number of sectors on the device. + + std::queue<BlockData*> in_flight_sectors_; // Queue of sectors written but + // not verified. + void *block_buffer_; // Pointer to aligned block buffer. + + io_context_t aio_ctx_; // Asynchronous I/O context for Linux native AIO. + + DiskBlockTable *block_table_; // Disk Block Table, shared by all disk + // threads that read / write at the same + // device + + DISALLOW_COPY_AND_ASSIGN(DiskThread); +}; + +class RandomDiskThread : public DiskThread { + public: + explicit RandomDiskThread(DiskBlockTable *block_table); + virtual ~RandomDiskThread(); + // Main work loop. + virtual bool DoWork(int fd); + protected: + DISALLOW_COPY_AND_ASSIGN(RandomDiskThread); +}; + +// Worker thread to perform checks in a specific memory region. +class MemoryRegionThread : public WorkerThread { + public: + MemoryRegionThread(); + ~MemoryRegionThread(); + virtual bool Work(); + void ProcessError(struct ErrorRecord *error, int priority, + const char *message); + bool SetRegion(void *region, int64 size); + // Calculate worker thread specific bandwidth. + virtual float GetMemoryCopiedData() + {return GetCopiedData();} + virtual float GetDeviceCopiedData() + {return GetCopiedData() * 2;} + void SetIdentifier(string identifier) { + identifier_ = identifier; + } + + protected: + // Page queue for this particular memory region. + char *region_; + PageEntryQueue *pages_; + bool error_injection_; + int phase_; + string identifier_; + static const int kPhaseNoPhase = 0; + static const int kPhaseCopy = 1; + static const int kPhaseCheck = 2; + + private: + DISALLOW_COPY_AND_ASSIGN(MemoryRegionThread); +}; + +#endif // STRESSAPPTEST_WORKER_H_ |