/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */ /* * Copyright (C) 2004-2013 HUBzero Foundation, LLC * */ #include #include #include #include #include #include #include #include #include #include #include // for pid_t #include // for readv/writev #include #include #include #include #include #include #include "md5.h" #include "nanovis.h" #include "nanovisServer.h" #include "define.h" #include "Command.h" #include "ReadBuffer.h" #include "NvShader.h" #include "Trace.h" using namespace nv; using namespace nv::util; Stats nv::g_stats; int nv::g_statsFile = -1; ///< Stats output file descriptor. int nv::g_fdIn = STDIN_FILENO; ///< Input file descriptor int nv::g_fdOut = STDOUT_FILENO; ///< Output file descriptor FILE *nv::g_fIn = NULL; ///< Input file handle FILE *nv::g_fOut = NULL; ///< Output file handle FILE *nv::g_fLog = NULL; ///< Trace logging file handle ReadBuffer *nv::g_inBufPtr = NULL; ///< Socket read buffer static void writeFrame(int fd, unsigned char *imgData) { #if 0 writePPM(fd, "nv>image -type image -bytes", imgData, NanoVis::winWidth, NanoVis::winHeight); #else NanoVis::ppmWrite("nv>image -type image -bytes"); #endif } static int sendAck() { std::ostringstream oss; oss << "nv>ok -token " << g_stats.nCommands << "\n"; std::string str = oss.str(); size_t numBytes = str.length(); TRACE("Sending OK for commands through %lu", g_stats.nCommands); if (write(g_fdOut, str.c_str(), numBytes) < 0) { ERROR("write failed: %s", strerror(errno)); return -1; } return 0; } #ifdef KEEPSTATS #ifndef STATSDIR #define STATSDIR "/var/tmp/visservers" #endif /*STATSDIR*/ int nv::getStatsFile(Tcl_Obj *objPtr) { Tcl_DString ds; char fileName[33]; char pidstr[200]; const char *path; md5_state_t state; md5_byte_t digest[16]; char *string; int length; if ((objPtr == NULL) || (g_statsFile >= 0)) { return g_statsFile; } /* By itself the client's key/value pairs aren't unique. Add in the * process id of this render server. */ sprintf(pidstr, "%ld", (long)g_stats.pid); /* Create an md5 hash of the key/value pairs and use it as the file name. */ string = Tcl_GetStringFromObj(objPtr, &length); md5_init(&state); md5_append(&state, (const md5_byte_t *)string, strlen(string)); md5_append(&state, (const md5_byte_t *)pidstr, strlen(pidstr)); md5_finish(&state, digest); for (int i = 0; i < 16; i++) { sprintf(fileName + i * 2, "%02x", digest[i]); } Tcl_DStringInit(&ds); Tcl_DStringAppend(&ds, STATSDIR, -1); Tcl_DStringAppend(&ds, "/", 1); Tcl_DStringAppend(&ds, fileName, 32); path = Tcl_DStringValue(&ds); g_statsFile = open(path, O_EXCL | O_CREAT | O_WRONLY, 0600); Tcl_DStringFree(&ds); if (g_statsFile < 0) { ERROR("can't open \"%s\": %s", fileName, strerror(errno)); return -1; } return g_statsFile; } int nv::writeToStatsFile(int f, const char *s, size_t length) { if (f >= 0) { ssize_t numWritten; numWritten = write(f, s, length); if (numWritten == (ssize_t)length) { close(dup(f)); } } return 0; } static int serverStats(int code) { double start, finish; char buf[BUFSIZ]; Tcl_DString ds; int result; { struct timeval tv; /* Get ending time. */ gettimeofday(&tv, NULL); finish = CVT2SECS(tv); tv = g_stats.start; start = CVT2SECS(tv); } /* * Session information: * - Name of render server * - Process ID * - Hostname where server is running * - Start date of session * - Start date of session in seconds * - Number of frames returned * - Number of bytes total returned (in frames) * - Number of commands received * - Total elapsed time of all commands * - Total elapsed time of session * - Exit code of vizserver * - User time * - System time * - User time of children * - System time of children */ Tcl_DStringInit(&ds); Tcl_DStringAppendElement(&ds, "render_stop"); /* renderer */ Tcl_DStringAppendElement(&ds, "renderer"); Tcl_DStringAppendElement(&ds, "nanovis"); /* pid */ Tcl_DStringAppendElement(&ds, "pid"); sprintf(buf, "%ld", (long)g_stats.pid); Tcl_DStringAppendElement(&ds, buf); /* host */ Tcl_DStringAppendElement(&ds, "host"); gethostname(buf, BUFSIZ-1); buf[BUFSIZ-1] = '\0'; Tcl_DStringAppendElement(&ds, buf); /* date */ Tcl_DStringAppendElement(&ds, "date"); strcpy(buf, ctime(&g_stats.start.tv_sec)); buf[strlen(buf) - 1] = '\0'; Tcl_DStringAppendElement(&ds, buf); /* date_secs */ Tcl_DStringAppendElement(&ds, "date_secs"); sprintf(buf, "%ld", g_stats.start.tv_sec); Tcl_DStringAppendElement(&ds, buf); /* num_frames */ Tcl_DStringAppendElement(&ds, "num_frames"); sprintf(buf, "%lu", (unsigned long)g_stats.nFrames); Tcl_DStringAppendElement(&ds, buf); /* frame_bytes */ Tcl_DStringAppendElement(&ds, "frame_bytes"); sprintf(buf, "%lu", (unsigned long)g_stats.nFrameBytes); Tcl_DStringAppendElement(&ds, buf); /* num_commands */ Tcl_DStringAppendElement(&ds, "num_commands"); sprintf(buf, "%lu", (unsigned long)g_stats.nCommands); Tcl_DStringAppendElement(&ds, buf); /* cmd_time */ Tcl_DStringAppendElement(&ds, "cmd_time"); sprintf(buf, "%g", g_stats.cmdTime); Tcl_DStringAppendElement(&ds, buf); /* session_time */ Tcl_DStringAppendElement(&ds, "session_time"); sprintf(buf, "%g", finish - start); Tcl_DStringAppendElement(&ds, buf); /* status */ Tcl_DStringAppendElement(&ds, "status"); sprintf(buf, "%d", code); Tcl_DStringAppendElement(&ds, buf); { long clocksPerSec = sysconf(_SC_CLK_TCK); double clockRes = 1.0 / clocksPerSec; struct tms tms; memset(&tms, 0, sizeof(tms)); times(&tms); /* utime */ Tcl_DStringAppendElement(&ds, "utime"); sprintf(buf, "%g", tms.tms_utime * clockRes); Tcl_DStringAppendElement(&ds, buf); /* stime */ Tcl_DStringAppendElement(&ds, "stime"); sprintf(buf, "%g", tms.tms_stime * clockRes); Tcl_DStringAppendElement(&ds, buf); /* cutime */ Tcl_DStringAppendElement(&ds, "cutime"); sprintf(buf, "%g", tms.tms_cutime * clockRes); Tcl_DStringAppendElement(&ds, buf); /* cstime */ Tcl_DStringAppendElement(&ds, "cstime"); sprintf(buf, "%g", tms.tms_cstime * clockRes); Tcl_DStringAppendElement(&ds, buf); } Tcl_DStringAppend(&ds, "\n", -1); int f = getStatsFile(NULL); result = writeToStatsFile(f, Tcl_DStringValue(&ds), Tcl_DStringLength(&ds)); close(f); Tcl_DStringFree(&ds); return result; } #endif /** * \brief Send a command with data payload */ void nv::sendDataToClient(const char *command, const char *data, size_t dlen) { size_t numRecords = 2; struct iovec *iov = new iovec[numRecords]; // Write the nanovisviewer command, then the image header and data. // Command iov[0].iov_base = const_cast(command); iov[0].iov_len = strlen(command); // Data iov[1].iov_base = const_cast(data); iov[1].iov_len = dlen; if (writev(g_fdOut, iov, numRecords) < 0) { ERROR("write failed: %s", strerror(errno)); } delete [] iov; } static void initService() { g_fIn = fdopen(g_fdIn, "r"); // Create a stream associated with the output file descriptor g_fOut = fdopen(g_fdOut, "w"); // If running without a socket, use stdout for debugging if (g_fOut == NULL) { g_fdOut = STDOUT_FILENO; g_fOut = stdout; } const char* user = getenv("USER"); char* logName = NULL; int logNameLen = 0; if (user == NULL) { logNameLen = 20+1; logName = (char *)calloc(logNameLen, sizeof(char)); strncpy(logName, "/tmp/nanovis_log.txt", logNameLen); } else { logNameLen = 17+1+strlen(user); logName = (char *)calloc(logNameLen, sizeof(char)); strncpy(logName, "/tmp/nanovis_log_", logNameLen); strncat(logName, user, strlen(user)); } // open log and map stderr to log file g_fLog = fopen(logName, "w"); dup2(fileno(g_fLog), STDERR_FILENO); // If we are writing to socket, map stdout to log if (g_fdOut != STDOUT_FILENO) { dup2(fileno(g_fLog), STDOUT_FILENO); } fflush(stdout); // clean up malloc'd memory if (logName != NULL) { free(logName); } } static void exitService(int code) { TRACE("Enter: %d", code); NanoVis::removeAllData(); NvShader::exitCg(); //close log file if (g_fLog != NULL) { fclose(g_fLog); g_fLog = NULL; } #ifdef KEEPSTATS serverStats(code); #endif closelog(); exit(code); } #ifndef USE_NEW_EVENT_LOOP static int executeCommand(Tcl_Interp *interp, Tcl_DString *dsPtr) { int result; #ifdef WANT_TRACE char *str = Tcl_DStringValue(dsPtr); std::string cmd(str); cmd.erase(cmd.find_last_not_of(" \n\r\t")+1); TRACE("command %lu: '%s'", g_stats.nCommands+1, cmd.c_str()); #endif result = Tcl_Eval(interp, Tcl_DStringValue(dsPtr)); Tcl_DStringSetLength(dsPtr, 0); if (result != TCL_OK) { TRACE("Error: %d", result); } return result; } static int processCommands(Tcl_Interp *interp, FILE *inFile, int fdOut) { int ret = 0; int status = TCL_OK; NanoVis::flags &= ~NanoVis::REDRAW_PENDING; Tcl_DString cmdbuffer; Tcl_DStringInit(&cmdbuffer); int flags = fcntl(0, F_GETFL, 0); fcntl(0, F_SETFL, flags & ~O_NONBLOCK); // Read and execute as many commands as we can from stdin... bool isComplete = false; while ((!feof(inFile)) && (status == TCL_OK)) { // // Read the next command from the buffer. First time through we // block here and wait if necessary until a command comes in. // // BE CAREFUL: Read only one command, up to a newline. The "volume // data follows" command needs to be able to read the data // immediately following the command, and we shouldn't consume it // here. // while (!feof(inFile)) { int c = fgetc(inFile); char ch; if (c <= 0) { if (errno == EWOULDBLOCK) { break; } return -1; } ch = (char)c; Tcl_DStringAppend(&cmdbuffer, &ch, 1); if (ch == '\n') { isComplete = Tcl_CommandComplete(Tcl_DStringValue(&cmdbuffer)); if (isComplete) { break; } } } // no command? then we're done for now if (Tcl_DStringLength(&cmdbuffer) == 0) { break; } if (isComplete) { // back to original flags during command evaluation... fcntl(0, F_SETFL, flags & ~O_NONBLOCK); struct timeval start, finish; gettimeofday(&start, NULL); status = executeCommand(interp, &cmdbuffer); gettimeofday(&finish, NULL); // non-blocking for next read -- we might not get anything fcntl(0, F_SETFL, flags | O_NONBLOCK); isComplete = false; g_stats.cmdTime += (MSECS_ELAPSED(start, finish) / 1.0e+3); g_stats.nCommands++; CHECK_FRAMEBUFFER_STATUS(); } } fcntl(0, F_SETFL, flags); if (status != TCL_OK) { if (handleError(interp, status, fdOut) < 0) { return -1; } TRACE("Leaving on ERROR"); } return ret; } #endif static void idle() { TRACE("Enter"); glutSetWindow(NanoVis::renderWindow); #ifdef USE_NEW_EVENT_LOOP if (nv::processCommands(NanoVis::interp, g_inBufPtr, g_fdOut) < 0) { exitService(1); } #else if (processCommands(NanoVis::interp, g_fIn, g_fdOut) < 0) { exitService(1); } #endif NanoVis::update(); NanoVis::bindOffscreenBuffer(); NanoVis::render(); bool renderedSomething = true; if (renderedSomething) { TRACE("Rendering new frame"); NanoVis::readScreen(); writeFrame(g_fdOut, NanoVis::screenBuffer); } else { TRACE("No render required"); sendAck(); } #ifdef USE_NEW_EVENT_LOOP if (g_inBufPtr->status() == ReadBuffer::ENDFILE) { exitService(0); } #else if (feof(g_fIn)) { exitService(0); } #endif TRACE("Leave"); } static void shaderErrorCallback(void) { if (!NvShader::printErrorInfo()) { TRACE("Shader error, exiting..."); exitService(1); } } static void reshape(int width, int height) { NanoVis::resizeOffscreenBuffer(width, height); } int main(int argc, char **argv) { // Ignore SIGPIPE. **Is this needed? ** signal(SIGPIPE, SIG_IGN); const char *resourcePath = NULL; while (1) { static struct option long_options[] = { {"debug", no_argument, NULL, 'd'}, {"path", required_argument, NULL, 'p'}, {0, 0, 0, 0} }; int option_index = 0; int c = getopt_long(argc, argv, "dp:i:o:", long_options, &option_index); if (c == -1) { break; } switch (c) { case 'd': NanoVis::debugFlag = true; break; case 'p': resourcePath = optarg; break; case 'i': { int fd = atoi(optarg); if (fd >=0 && fd < 5) { g_fdIn = fd; } } break; case 'o': { int fd = atoi(optarg); if (fd >=0 && fd < 5) { g_fdOut = fd; } } break; case '?': break; default: return 1; } } initService(); initLog(); memset(&g_stats, 0, sizeof(nv::Stats)); g_stats.pid = getpid(); gettimeofday(&g_stats.start, NULL); TRACE("Starting NanoVis Server"); if (NanoVis::debugFlag) { TRACE("Debugging on"); } // Sanity check: log descriptor can't be used for client IO if (fileno(g_fLog) == g_fdIn) { ERROR("Invalid input file descriptor"); return 1; } if (fileno(g_fLog) == g_fdOut) { ERROR("Invalid output file descriptor"); return 1; } TRACE("File descriptors: in %d out %d log %d", g_fdIn, g_fdOut, fileno(g_fLog)); fprintf(g_fOut, "NanoVis %s (build %s)\n", NANOVIS_VERSION_STRING, SVN_VERSION); fflush(g_fOut); #ifdef USE_NEW_EVENT_LOOP g_inBufPtr = new ReadBuffer(g_fdIn, 1<<12); #endif Tcl_Interp *interp = Tcl_CreateInterp(); ClientData clientData = NULL; initTcl(interp, clientData); NanoVis::interp = interp; /* Initialize GLUT here so it can parse and remove GLUT-specific * command-line options before we parse the command-line below. */ glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA); glutInitWindowSize(NanoVis::winWidth, NanoVis::winHeight); glutInitWindowPosition(10, 10); NanoVis::renderWindow = glutCreateWindow("nanovis"); glutIdleFunc(idle); glutDisplayFunc(NanoVis::render); glutReshapeFunc(reshape); char *newResourcePath = NULL; if (resourcePath == NULL) { char *p; // See if we can derive the path from the location of the program. // Assume program is in the form /bin/nanovis. resourcePath = argv[0]; p = strrchr((char *)resourcePath, '/'); if (p != NULL) { *p = '\0'; p = strrchr((char *)resourcePath, '/'); } if (p == NULL) { ERROR("Resource path not specified, and could not determine a valid path"); return 1; } *p = '\0'; newResourcePath = new char[(strlen(resourcePath) + 15) * 2 + 1]; sprintf(newResourcePath, "%s/lib/shaders:%s/lib/resources", resourcePath, resourcePath); resourcePath = newResourcePath; TRACE("No resource path specified, using: %s", resourcePath); } else { TRACE("Resource path: '%s'", resourcePath); } FilePath::getInstance()->setWorkingDirectory(argc, (const char**) argv); if (!NanoVis::init(resourcePath)) { exitService(1); } if (newResourcePath != NULL) { delete [] newResourcePath; } // Override callback with one that cleans up server on exit NvShader::setErrorCallback(shaderErrorCallback); if (!NanoVis::initGL()) { exitService(1); } if (!NanoVis::resizeOffscreenBuffer(NanoVis::winWidth, NanoVis::winHeight)) { exitService(1); } glutMainLoop(); exitService(0); }