From 00f52811b818e751245303378a97db59fae083c1 Mon Sep 17 00:00:00 2001 From: David Addison Date: Fri, 17 Oct 2025 12:01:25 -0700 Subject: [PATCH] Add support for JSON output to perf test framework This adds support for writing structured information about the run to a JSON file. Enable with -J .json If the target JSON filename already exists then an incrementing numeric suffix will be added to create .json. --- src/Makefile | 8 +- src/common.cu | 221 +++++++++-------- src/util.cu | 668 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/util.h | 37 +++ 4 files changed, 832 insertions(+), 102 deletions(-) create mode 100644 src/util.cu create mode 100644 src/util.h diff --git a/src/Makefile b/src/Makefile index b097765..c8f787a 100644 --- a/src/Makefile +++ b/src/Makefile @@ -48,12 +48,12 @@ include ../verifiable/verifiable.mk .PRECIOUS: ${DST_DIR}/%.o -${DST_DIR}/%.o: %.cu common.h $(TEST_VERIFIABLE_HDRS) +${DST_DIR}/%.o: %.cu common.h util.h $(TEST_VERIFIABLE_HDRS) @printf "Compiling %-35s > %s\n" $< $@ @mkdir -p ${DST_DIR} $(NVCC) -o $@ $(NVCUFLAGS) -c $< -${DST_DIR}/%$(NAME_SUFFIX).o: %.cu common.h $(TEST_VERIFIABLE_HDRS) +${DST_DIR}/%$(NAME_SUFFIX).o: %.cu common.h util.h $(TEST_VERIFIABLE_HDRS) @printf "Compiling %-35s > %s\n" $< $@ @mkdir -p ${DST_DIR} $(NVCC) -o $@ $(NVCUFLAGS) -c $< @@ -64,12 +64,12 @@ ${DST_DIR}/timer.o: timer.cc timer.h $(CXX) $(CXXFLAGS) -o $@ -c $< ifeq ($(DSO), 1) -${DST_DIR}/%_perf$(NAME_SUFFIX): ${DST_DIR}/%.o ${DST_DIR}/common$(NAME_SUFFIX).o ${DST_DIR}/timer.o $(TEST_VERIFIABLE_LIBS) +${DST_DIR}/%_perf$(NAME_SUFFIX): ${DST_DIR}/%.o ${DST_DIR}/common$(NAME_SUFFIX).o ${DST_DIR}/util$(NAME_SUFFIX).o ${DST_DIR}/timer.o $(TEST_VERIFIABLE_LIBS) @printf "Linking %-35s > %s\n" $< $@ @mkdir -p ${DST_DIR} $(NVCC) -o $@ $(NVCUFLAGS) $^ -L$(TEST_VERIFIABLE_BUILDDIR) -lverifiable ${NVLDFLAGS} -Xlinker "--enable-new-dtags" -Xlinker "-rpath,\$$ORIGIN:\$$ORIGIN/verifiable" else -${DST_DIR}/%_perf$(NAME_SUFFIX):${DST_DIR}/%.o ${DST_DIR}/common$(NAME_SUFFIX).o ${DST_DIR}/timer.o $(TEST_VERIFIABLE_OBJS) +${DST_DIR}/%_perf$(NAME_SUFFIX):${DST_DIR}/%.o ${DST_DIR}/common$(NAME_SUFFIX).o ${DST_DIR}/util$(NAME_SUFFIX).o ${DST_DIR}/timer.o $(TEST_VERIFIABLE_OBJS) @printf "Linking %-35s > %s\n" $< $@ @mkdir -p ${DST_DIR} $(NVCC) -o $@ $(NVCUFLAGS) $^ ${NVLDFLAGS} diff --git a/src/common.cu b/src/common.cu index 4b8e8c0..e1e91a9 100644 --- a/src/common.cu +++ b/src/common.cu @@ -15,6 +15,7 @@ #include "cuda.h" #include /* program_invocation_short_name */ +#include "util.h" #include "../verifiable/verifiable.h" #pragma weak ncclCommWindowRegister @@ -76,25 +77,25 @@ int is_main_proc = 0; thread_local int is_main_thread = 0; // Command line parameter defaults -static int nThreads = 1; -static int nGpus = 1; -static size_t minBytes = 32*1024*1024; -static size_t maxBytes = 32*1024*1024; -static size_t stepBytes = 1*1024*1024; -static size_t stepFactor = 1; -static int datacheck = 1; -static int warmup_iters = 1; -static int iters = 20; -static int agg_iters = 1; +int nThreads = 1; +int nGpus = 1; +size_t minBytes = 32*1024*1024; +size_t maxBytes = 32*1024*1024; +size_t stepBytes = 1*1024*1024; +size_t stepFactor = 1; +int datacheck = 1; +int warmup_iters = 1; +int iters = 20; +int agg_iters = 1; static int run_cycles = 1; static int ncclop = ncclSum; static int nccltype = ncclFloat; static int ncclroot = 0; -static int parallel_init = 0; -static int blocking_coll = 0; +int parallel_init = 0; +int blocking_coll = 0; static int streamnull = 0; static int timeout = 0; -static int cudaGraphLaunches = 0; +int cudaGraphLaunches = 0; static int report_cputime = 0; static int deviceImpl = 0; @@ -111,7 +112,74 @@ static int ctaPolicy = -1; #endif static int minCudaArch = 1<<30; -#define NUM_BLOCKS 32 +enum output_file_type_t { + JSON_FILE_OUTPUT, + UNSPECIFIED_FILE_OUTPUT +}; + +// Return pointer to extension in `path` if one is found. An extension +// is the last `.` in the `path`, if there is no `/` following the `.` +// and there are characters after `.`. +// +// Therefore: returns 0 if no meaningful extension was found, or returns offset +// into string where extension begins +static const char *getExtension(const char *path) { + if (path == nullptr) return nullptr; + int last_dot = -1; + int last_slash = -1; + + int pos; + for (pos = 0; path[pos] != '\0'; ++pos) { + switch (path[pos]) { + case '.': + last_dot = pos; + break; + case '/': + last_slash = pos; + break; + default: + break; + } + } + + if (last_dot > last_slash && last_dot + 1 != pos) { + return path + last_dot + 1; + } + + return nullptr; +} + +static output_file_type_t classifyOutputFile(const char *filename) { + const char *extension = getExtension(filename); + if (extension != nullptr && strcasecmp(extension, "json") == 0) { + return JSON_FILE_OUTPUT; + } + + return UNSPECIFIED_FILE_OUTPUT; +} + +static void outputFileInit(output_file_type_t output_file_type, + const char *output_file, char argc, char **argv, char **envp) { + switch (output_file_type) { + case JSON_FILE_OUTPUT: + jsonOutputInit(output_file, argc, argv, envp); + break; + case UNSPECIFIED_FILE_OUTPUT: + default: + break; + } +} + +static void outputFileFinalize(output_file_type_t output_file_type) { + switch (output_file_type) { + case JSON_FILE_OUTPUT: + jsonOutputFinalize(); + break; + case UNSPECIFIED_FILE_OUTPUT: + default: + break; + } +} static double parsesize(const char *value) { long long int units; @@ -593,19 +661,7 @@ testResult_t BenchTime(struct threadArgs* args, ncclDataType_t type, ncclRedOp_t } double timeUsec = (report_cputime ? cputimeSec : deltaSec)*1.0E6; - char timeStr[100]; - if (timeUsec >= 10000.0) { - sprintf(timeStr, "%7.0f", timeUsec); - } else if (timeUsec >= 100.0) { - sprintf(timeStr, "%7.1f", timeUsec); - } else { - sprintf(timeStr, "%7.2f", timeUsec); - } - if (args->reportErrors) { - PRINT(" %7s %6.2f %6.2f %5g", timeStr, algBw, busBw, (double)wrongElts); - } else { - PRINT(" %7s %6.2f %6.2f %5s", timeStr, algBw, busBw, "N/A"); - } + writeBenchmarkLineBody(timeUsec, algBw, busBw, args->reportErrors, wrongElts, report_cputime, in_place==0); args->bw[0] += busBw; args->bw_count[0]++; @@ -644,12 +700,10 @@ testResult_t TimeTest(struct threadArgs* args, ncclDataType_t type, const char* do { for (size_t size = args->minbytes; size<=args->maxbytes; size = ((args->stepfactor > 1) ? size*args->stepfactor : size+args->stepbytes)) { setupArgs(size, type, args); - char rootName[100]; - sprintf(rootName, "%6i", root); - PRINT("%12li %12li %8s %6s %6s", max(args->sendBytes, args->expectedBytes), args->nbytes / wordSize(type), typeName, opName, rootName); + writeBenchmarkLinePreamble(max(args->sendBytes, args->expectedBytes), args->nbytes / wordSize(type), typeName, opName, root); TESTCHECK(BenchTime(args, type, op, root, 0)); TESTCHECK(BenchTime(args, type, op, root, 1)); - PRINT("\n"); + writeBenchmarkLineTerminator(iters, ""); } } while (--repeat); @@ -673,6 +727,8 @@ testResult_t threadInit(struct threadArgs* args) { //set main thread again is_main_thread = (is_main_proc && args->thread == 0) ? 1 : 0; + jsonIdentifyWriter(is_main_thread); + #if NCCL_VERSION_CODE >= NCCL_VERSION(2,14,0) ncclConfig_t config = NCCL_CONFIG_INITIALIZER; #if NCCL_VERSION_CODE >= NCCL_VERSION(2,27,0) @@ -790,7 +846,7 @@ testResult_t AllocateBuffs(void **sendbuff, size_t sendBytes, void **recvbuff, s testResult_t run(); // Main function -int main(int argc, char* argv[]) { +int main(int argc, char* argv[], char **envp) { // Make sure everyline is flushed so that we see the progress of the test setlinebuf(stdout); @@ -824,6 +880,8 @@ int main(int argc, char* argv[]) { // Parse args double parsed; int longindex; + char *output_file = nullptr; + static struct option longopts[] = { {"nthreads", required_argument, 0, 't'}, {"ngpus", required_argument, 0, 'g'}, @@ -845,6 +903,7 @@ int main(int argc, char* argv[]) { {"timeout", required_argument, 0, 'T'}, {"cudagraph", required_argument, 0, 'G'}, {"report_cputime", required_argument, 0, 'C'}, + {"output_file", required_argument, 0, 'J'}, {"average", required_argument, 0, 'a'}, {"local_register", required_argument, 0, 'R'}, {"cta_policy", required_argument, 0, 'x'}, @@ -856,7 +915,7 @@ int main(int argc, char* argv[]) { while(1) { int c; - c = getopt_long(argc, argv, "t:g:b:e:i:f:n:m:w:N:p:c:o:d:r:z:y:T:hG:C:a:R:x:D:V:", longopts, &longindex); + c = getopt_long(argc, argv, "t:g:b:e:i:f:n:m:w:N:p:c:o:d:r:z:y:T:hG:C:a:R:x:D:V:J:", longopts, &longindex); if (c == -1) break; @@ -945,6 +1004,9 @@ int main(int argc, char* argv[]) { case 'C': report_cputime = strtol(optarg, NULL, 0); break; + case 'J': + output_file = strdup(optarg); + break; case 'a': average = (int)strtol(optarg, NULL, 0); break; @@ -1023,6 +1085,7 @@ int main(int argc, char* argv[]) { "[-T,--timeout