source: nanovis/branches/1.1/nanovisServer.cpp @ 4923

Last change on this file since 4923 was 4904, checked in by ldelgass, 5 years ago

Merge serveral changes from trunk. Does not include threading, world space
changes, etc.

  • Property svn:eol-style set to native
File size: 17.2 KB
Line 
1/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 * Copyright (C) 2004-2013  HUBzero Foundation, LLC
4 *
5 */
6
7#include <cassert>
8#include <cstdio>
9#include <cerrno>
10#include <cstring>
11#include <csignal>
12#include <ctime>
13
14#include <fcntl.h>
15#include <getopt.h>
16#include <sys/time.h>
17#include <sys/times.h>
18#include <sys/uio.h> // for readv/writev
19#include <unistd.h>
20
21#include <sstream>
22
23#include <tcl.h>
24
25#include <GL/glew.h>
26#include <GL/glut.h>
27
28#include <util/FilePath.h>
29
30#include "md5.h"
31
32#include "nanovis.h"
33#include "nanovisServer.h"
34#include "define.h"
35#include "Command.h"
36#include "ReadBuffer.h"
37#include "Shader.h"
38#include "Trace.h"
39
40using namespace nv;
41using namespace nv::util;
42
43Stats nv::g_stats;
44int nv::g_statsFile = -1; ///< Stats output file descriptor.
45int nv::g_fdIn = STDIN_FILENO;     ///< Input file descriptor
46int nv::g_fdOut = STDOUT_FILENO;   ///< Output file descriptor
47FILE *nv::g_fIn = NULL;            ///< Input file handle
48FILE *nv::g_fOut = NULL;           ///< Output file handle
49FILE *nv::g_fLog = NULL;           ///< Trace logging file handle
50ReadBuffer *nv::g_inBufPtr = NULL; ///< Socket read buffer
51
52static void
53writeFrame(int fd, unsigned char *imgData)
54{
55#if 0
56    writePPM(fd, "nv>image -type image -bytes",
57             imgData,
58             NanoVis::winWidth,
59             NanoVis::winHeight);
60#else
61    NanoVis::ppmWrite("nv>image -type image -bytes");
62#endif
63}
64
65static int
66sendAck()
67{
68    std::ostringstream oss;
69    oss << "nv>ok -token " << g_stats.nCommands <<  "\n";
70    std::string str = oss.str();
71    size_t numBytes = str.length();
72
73    TRACE("Sending OK for commands through %lu", g_stats.nCommands);
74    if (write(g_fdOut, str.c_str(), numBytes) < 0) {
75        ERROR("write failed: %s", strerror(errno));
76        return -1;
77    }
78    return 0;
79}
80
81#ifdef KEEPSTATS
82
83#ifndef STATSDIR
84#define STATSDIR        "/var/tmp/visservers"
85#endif  /*STATSDIR*/
86
87int
88nv::getStatsFile(Tcl_Obj *objPtr)
89{
90    Tcl_DString ds;
91    char fileName[33];
92    char pidstr[200];
93    const char *path;
94    md5_state_t state;
95    md5_byte_t digest[16];
96    char *string;
97    int length;
98
99    if ((objPtr == NULL) || (g_statsFile >= 0)) {
100        return g_statsFile;
101    }
102    /* By itself the client's key/value pairs aren't unique.  Add in the
103     * process id of this render server. */
104    sprintf(pidstr, "%ld", (long)g_stats.pid);
105
106    /* Create an md5 hash of the key/value pairs and use it as the file name. */
107    string = Tcl_GetStringFromObj(objPtr, &length);
108    md5_init(&state);
109    md5_append(&state, (const md5_byte_t *)string, strlen(string));
110    md5_append(&state, (const md5_byte_t *)pidstr, strlen(pidstr));
111    md5_finish(&state, digest);
112    for (int i = 0; i < 16; i++) {
113        sprintf(fileName + i * 2, "%02x", digest[i]);
114    }
115    Tcl_DStringInit(&ds);
116    Tcl_DStringAppend(&ds, STATSDIR, -1);
117    Tcl_DStringAppend(&ds, "/", 1);
118    Tcl_DStringAppend(&ds, fileName, 32);
119    path = Tcl_DStringValue(&ds);
120
121    g_statsFile = open(path, O_EXCL | O_CREAT | O_WRONLY, 0600);
122    Tcl_DStringFree(&ds);
123    if (g_statsFile < 0) {
124        ERROR("can't open \"%s\": %s", fileName, strerror(errno));
125        return -1;
126    }
127    return g_statsFile;
128}
129
130int
131nv::writeToStatsFile(int f, const char *s, size_t length)
132{
133    if (f >= 0) {
134        ssize_t numWritten;
135
136        numWritten = write(f, s, length);
137        if (numWritten == (ssize_t)length) {
138            close(dup(f));
139        }
140    }
141    return 0;
142}
143
144static int
145serverStats(int code) 
146{
147    double start, finish;
148    char buf[BUFSIZ];
149    Tcl_DString ds;
150    int result;
151
152    {
153        struct timeval tv;
154
155        /* Get ending time.  */
156        gettimeofday(&tv, NULL);
157        finish = CVT2SECS(tv);
158        tv = g_stats.start;
159        start = CVT2SECS(tv);
160    }
161    /*
162     * Session information:
163     *   - Name of render server
164     *   - Process ID
165     *   - Hostname where server is running
166     *   - Start date of session
167     *   - Start date of session in seconds
168     *   - Number of frames returned
169     *   - Number of bytes total returned (in frames)
170     *   - Number of commands received
171     *   - Total elapsed time of all commands
172     *   - Total elapsed time of session
173     *   - Exit code of vizserver
174     *   - User time
175     *   - System time
176     *   - User time of children
177     *   - System time of children
178     */ 
179
180    Tcl_DStringInit(&ds);
181   
182    Tcl_DStringAppendElement(&ds, "render_stop");
183    /* renderer */
184    Tcl_DStringAppendElement(&ds, "renderer");
185    Tcl_DStringAppendElement(&ds, "nanovis");
186    /* pid */
187    Tcl_DStringAppendElement(&ds, "pid");
188    sprintf(buf, "%ld", (long)g_stats.pid);
189    Tcl_DStringAppendElement(&ds, buf);
190    /* host */
191    Tcl_DStringAppendElement(&ds, "host");
192    gethostname(buf, BUFSIZ-1);
193    buf[BUFSIZ-1] = '\0';
194    Tcl_DStringAppendElement(&ds, buf);
195    /* date */
196    Tcl_DStringAppendElement(&ds, "date");
197    strcpy(buf, ctime(&g_stats.start.tv_sec));
198    buf[strlen(buf) - 1] = '\0';
199    Tcl_DStringAppendElement(&ds, buf);
200    /* date_secs */
201    Tcl_DStringAppendElement(&ds, "date_secs");
202    sprintf(buf, "%ld", g_stats.start.tv_sec);
203    Tcl_DStringAppendElement(&ds, buf);
204    /* num_frames */
205    Tcl_DStringAppendElement(&ds, "num_frames");
206    sprintf(buf, "%lu", (unsigned long)g_stats.nFrames);
207    Tcl_DStringAppendElement(&ds, buf);
208    /* frame_bytes */
209    Tcl_DStringAppendElement(&ds, "frame_bytes");
210    sprintf(buf, "%lu", (unsigned long)g_stats.nFrameBytes);
211    Tcl_DStringAppendElement(&ds, buf);
212    /* num_commands */
213    Tcl_DStringAppendElement(&ds, "num_commands");
214    sprintf(buf, "%lu", (unsigned long)g_stats.nCommands);
215    Tcl_DStringAppendElement(&ds, buf);
216    /* cmd_time */
217    Tcl_DStringAppendElement(&ds, "cmd_time");
218    sprintf(buf, "%g", g_stats.cmdTime);
219    Tcl_DStringAppendElement(&ds, buf);
220    /* session_time */
221    Tcl_DStringAppendElement(&ds, "session_time");
222    sprintf(buf, "%g", finish - start);
223    Tcl_DStringAppendElement(&ds, buf);
224    /* status */
225    Tcl_DStringAppendElement(&ds, "status");
226    sprintf(buf, "%d", code);
227    Tcl_DStringAppendElement(&ds, buf);
228    {
229        long clocksPerSec = sysconf(_SC_CLK_TCK);
230        double clockRes = 1.0 / clocksPerSec;
231        struct tms tms;
232
233        memset(&tms, 0, sizeof(tms));
234        times(&tms);
235        /* utime */
236        Tcl_DStringAppendElement(&ds, "utime");
237        sprintf(buf, "%g", tms.tms_utime * clockRes);
238        Tcl_DStringAppendElement(&ds, buf);
239        /* stime */
240        Tcl_DStringAppendElement(&ds, "stime");
241        sprintf(buf, "%g", tms.tms_stime * clockRes);
242        Tcl_DStringAppendElement(&ds, buf);
243        /* cutime */
244        Tcl_DStringAppendElement(&ds, "cutime");
245        sprintf(buf, "%g", tms.tms_cutime * clockRes);
246        Tcl_DStringAppendElement(&ds, buf);
247        /* cstime */
248        Tcl_DStringAppendElement(&ds, "cstime");
249        sprintf(buf, "%g", tms.tms_cstime * clockRes);
250        Tcl_DStringAppendElement(&ds, buf);
251    }
252    Tcl_DStringAppend(&ds, "\n", -1);
253    int f = getStatsFile(NULL);
254    result = writeToStatsFile(f, Tcl_DStringValue(&ds), 
255                              Tcl_DStringLength(&ds));
256    close(f);
257    Tcl_DStringFree(&ds);
258    return result;
259}
260
261#endif
262
263/**
264 * \brief Send a command with data payload
265 */
266void
267nv::sendDataToClient(const char *command, const char *data, size_t dlen)
268{
269    size_t numRecords = 2;
270    struct iovec *iov = new iovec[numRecords];
271
272    // Write the nanovisviewer command, then the image header and data.
273    // Command
274    iov[0].iov_base = const_cast<char *>(command);
275    iov[0].iov_len = strlen(command);
276    // Data
277    iov[1].iov_base = const_cast<char *>(data);
278    iov[1].iov_len = dlen;
279    if (writev(g_fdOut, iov, numRecords) < 0) {
280        ERROR("write failed: %s", strerror(errno));
281    }
282    delete [] iov;
283}
284
285static void
286initService()
287{
288    g_fIn = fdopen(g_fdIn, "r");
289    // Create a stream associated with the output file descriptor
290    g_fOut = fdopen(g_fdOut, "w");
291    // If running without a socket, use stdout for debugging
292    if (g_fOut == NULL) {
293        g_fdOut = STDOUT_FILENO;
294        g_fOut = stdout;
295    }
296
297    const char* user = getenv("USER");
298    char* logName = NULL;
299    int logNameLen = 0;
300
301    if (user == NULL) {
302        logNameLen = 20+1;
303        logName = (char *)calloc(logNameLen, sizeof(char));
304        strncpy(logName, "/tmp/nanovis_log.txt", logNameLen);
305    } else {
306        logNameLen = 17+1+strlen(user);
307        logName = (char *)calloc(logNameLen, sizeof(char));
308        strncpy(logName, "/tmp/nanovis_log_", logNameLen);
309        strncat(logName, user, strlen(user));
310    }
311
312    // open log and map stderr to log file
313    g_fLog = fopen(logName, "w");
314    dup2(fileno(g_fLog), STDERR_FILENO);
315    // If we are writing to socket, map stdout to log
316    if (g_fdOut != STDOUT_FILENO) {
317        dup2(fileno(g_fLog), STDOUT_FILENO);
318    }
319
320    fflush(stdout);
321
322    // clean up malloc'd memory
323    if (logName != NULL) {
324        free(logName);
325    }
326}
327
328static void
329exitService(int code)
330{
331    TRACE("Enter: %d", code);
332
333    NanoVis::removeAllData();
334
335    Shader::exit();
336
337    //close log file
338    if (g_fLog != NULL) {
339        fclose(g_fLog);
340        g_fLog = NULL;
341    }
342
343#ifdef KEEPSTATS
344    serverStats(code);
345#endif
346    closelog();
347
348    exit(code);
349}
350
351#ifndef USE_NEW_EVENT_LOOP
352
353static int
354executeCommand(Tcl_Interp *interp, Tcl_DString *dsPtr) 
355{
356    int result;
357#ifdef WANT_TRACE
358    char *str = Tcl_DStringValue(dsPtr);
359    std::string cmd(str);
360    cmd.erase(cmd.find_last_not_of(" \n\r\t")+1);
361    TRACE("command %lu: '%s'", g_stats.nCommands+1, cmd.c_str());
362#endif
363
364    result = Tcl_Eval(interp, Tcl_DStringValue(dsPtr));
365    Tcl_DStringSetLength(dsPtr, 0);
366
367    if (result != TCL_OK) {
368        TRACE("Error: %d", result);
369    }
370    return result;
371}
372
373static int
374processCommands(Tcl_Interp *interp, FILE *inFile, int fdOut)
375{
376    int ret = 0;
377    int status = TCL_OK;
378
379    Tcl_DString cmdbuffer;
380    Tcl_DStringInit(&cmdbuffer);
381
382    int flags = fcntl(0, F_GETFL, 0);
383    fcntl(0, F_SETFL, flags & ~O_NONBLOCK);
384
385    //  Read and execute as many commands as we can from stdin...
386    bool isComplete = false;
387    while ((!feof(inFile)) && (status == TCL_OK)) {
388        //
389        //  Read the next command from the buffer.  First time through we
390        //  block here and wait if necessary until a command comes in.
391        //
392        //  BE CAREFUL: Read only one command, up to a newline.  The "volume
393        //  data follows" command needs to be able to read the data
394        //  immediately following the command, and we shouldn't consume it
395        //  here.
396        //
397        while (!feof(inFile)) {
398            int c = fgetc(inFile);
399            char ch;
400            if (c <= 0) {
401                if (errno == EWOULDBLOCK) {
402                    break;
403                }
404                return -1;
405            }
406            ch = (char)c;
407            Tcl_DStringAppend(&cmdbuffer, &ch, 1);
408            if (ch == '\n') {
409                isComplete = Tcl_CommandComplete(Tcl_DStringValue(&cmdbuffer));
410                if (isComplete) {
411                    break;
412                }
413            }
414        }
415        // no command? then we're done for now
416        if (Tcl_DStringLength(&cmdbuffer) == 0) {
417            break;
418        }
419        if (isComplete) {
420            // back to original flags during command evaluation...
421            fcntl(0, F_SETFL, flags & ~O_NONBLOCK);
422            struct timeval start, finish;
423            gettimeofday(&start, NULL);
424            status = executeCommand(interp, &cmdbuffer);
425            gettimeofday(&finish, NULL);
426            // non-blocking for next read -- we might not get anything
427            fcntl(0, F_SETFL, flags | O_NONBLOCK);
428            isComplete = false;
429            g_stats.cmdTime += (MSECS_ELAPSED(start, finish) / 1.0e+3);
430            g_stats.nCommands++;
431            CHECK_FRAMEBUFFER_STATUS();
432        }
433    }
434    fcntl(0, F_SETFL, flags);
435
436    if (status != TCL_OK) {
437        if (handleError(interp, status, fdOut) < 0) {
438            return -1;
439        }
440        TRACE("Leaving on ERROR");
441    }
442
443    return ret;
444}
445#endif
446
447static void
448idle()
449{
450    TRACE("Enter");
451
452    glutSetWindow(NanoVis::renderWindow);
453
454#ifdef USE_NEW_EVENT_LOOP
455    if (nv::processCommands(NanoVis::interp, g_inBufPtr, g_fdOut) < 0) {
456        exitService(1);
457    }
458#else
459    if (processCommands(NanoVis::interp, g_fIn, g_fdOut) < 0) {
460        exitService(1);
461    }
462#endif
463
464    NanoVis::update();
465    NanoVis::bindOffscreenBuffer();
466    NanoVis::render();
467    bool renderedSomething = true;
468    if (renderedSomething) {
469        TRACE("Rendering new frame");
470        NanoVis::readScreen();
471        writeFrame(g_fdOut, NanoVis::screenBuffer);
472    } else {
473        TRACE("No render required");
474        sendAck();
475    }
476
477#ifdef USE_NEW_EVENT_LOOP
478    if (g_inBufPtr->status() == ReadBuffer::ENDFILE) {
479        exitService(0);
480    }
481#else
482    if (feof(g_fIn)) {
483        exitService(0);
484    }
485#endif
486
487    TRACE("Leave");
488}
489
490static
491void shaderErrorCallback(void)
492{
493    if (!Shader::printErrorInfo()) {
494        TRACE("Shader error, exiting...");
495        exitService(1);
496    }
497}
498
499static
500void reshape(int width, int height)
501{
502    NanoVis::resizeOffscreenBuffer(width, height);
503}
504
505int 
506main(int argc, char **argv)
507{
508    // Ignore SIGPIPE.  **Is this needed? **
509    signal(SIGPIPE, SIG_IGN);
510
511    const char *resourcePath = NULL;
512    while (1) {
513        static struct option long_options[] = {
514            {"debug",   no_argument,       NULL, 'd'},
515            {"path",    required_argument, NULL, 'p'},
516            {0, 0, 0, 0}
517        };
518        int option_index = 0;
519        int c = getopt_long(argc, argv, "dp:i:o:", long_options, &option_index);
520        if (c == -1) {
521            break;
522        }
523        switch (c) {
524        case 'd':
525            NanoVis::debugFlag = true;
526            break;
527        case 'p':
528            resourcePath = optarg;
529            break;
530        case 'i': {
531            int fd = atoi(optarg);
532            if (fd >=0 && fd < 5) {
533                g_fdIn = fd;
534            }
535        }
536            break;
537        case 'o': {
538            int fd = atoi(optarg);
539            if (fd >=0 && fd < 5) {
540                g_fdOut = fd;
541            }
542        }
543            break;
544        case '?':
545            break;
546        default:
547            return 1;
548        }
549    }
550
551    initService();
552    initLog();
553
554    memset(&g_stats, 0, sizeof(nv::Stats));
555    g_stats.pid = getpid();
556    gettimeofday(&g_stats.start, NULL);
557
558    TRACE("Starting NanoVis Server");
559    if (NanoVis::debugFlag) {
560        TRACE("Debugging on");
561    }
562
563    // Sanity check: log descriptor can't be used for client IO
564    if (fileno(g_fLog) == g_fdIn) {
565        ERROR("Invalid input file descriptor");
566        return 1;
567    }
568    if (fileno(g_fLog) == g_fdOut) {
569        ERROR("Invalid output file descriptor");
570        return 1;
571    }
572    TRACE("File descriptors: in %d out %d log %d", g_fdIn, g_fdOut, fileno(g_fLog));
573
574    /* This synchronizes the client with the server, so that the client
575     * doesn't start writing commands before the server is ready. It could
576     * also be used to supply information about the server (version, memory
577     * size, etc). */
578    fprintf(g_fOut, "NanoVis %s (build %s)\n", NANOVIS_VERSION_STRING, SVN_VERSION);
579    fflush(g_fOut);
580
581#ifdef USE_NEW_EVENT_LOOP
582    g_inBufPtr = new ReadBuffer(g_fdIn, 1<<12);
583#endif
584
585    Tcl_Interp *interp = Tcl_CreateInterp();
586    ClientData clientData = NULL;
587
588    initTcl(interp, clientData);
589    NanoVis::interp = interp;
590
591    /* Initialize GLUT here so it can parse and remove GLUT-specific
592     * command-line options before we parse the command-line below. */
593    glutInit(&argc, argv);
594    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
595    glutInitWindowSize(NanoVis::winWidth, NanoVis::winHeight);
596    glutInitWindowPosition(10, 10);
597    NanoVis::renderWindow = glutCreateWindow("nanovis");
598
599    glutIdleFunc(idle);
600    glutDisplayFunc(NanoVis::render);
601    glutReshapeFunc(reshape);
602
603    char *newResourcePath = NULL;
604    if (resourcePath == NULL) {
605        char *p;
606
607        // See if we can derive the path from the location of the program.
608        // Assume program is in the form <path>/bin/nanovis.
609        resourcePath = argv[0];
610        p = strrchr((char *)resourcePath, '/');
611        if (p != NULL) {
612            *p = '\0';
613            p = strrchr((char *)resourcePath, '/');
614        }
615        if (p == NULL) {
616            ERROR("Resource path not specified, and could not determine a valid path");
617            return 1;
618        }
619        *p = '\0';
620        newResourcePath = new char[(strlen(resourcePath) + 15) * 2 + 1];
621        sprintf(newResourcePath, "%s/lib/shaders:%s/lib/resources", resourcePath, resourcePath);
622        resourcePath = newResourcePath;
623        TRACE("No resource path specified, using: %s", resourcePath);
624    } else {
625        TRACE("Resource path: '%s'", resourcePath);
626    }
627
628    FilePath::getInstance()->setWorkingDirectory(argc, (const char**) argv);
629
630    if (!NanoVis::init(resourcePath)) {
631        exitService(1);
632    }
633    if (newResourcePath != NULL) {
634        delete [] newResourcePath;
635    }
636
637    Shader::init();
638    // Override callback with one that cleans up server on exit
639    Shader::setErrorCallback(shaderErrorCallback);
640
641    if (!NanoVis::initGL()) {
642        exitService(1);
643    }
644
645    if (!NanoVis::resizeOffscreenBuffer(NanoVis::winWidth, NanoVis::winHeight)) {
646        exitService(1);
647    }
648
649    glutMainLoop();
650
651    exitService(0);
652}
Note: See TracBrowser for help on using the repository browser.