source: nanovis/branches/1.2/nanovisServer.cpp @ 4937

Last change on this file since 4937 was 4937, checked in by ldelgass, 8 years ago

merge threading

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