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

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

set eol-style on new files

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