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

Last change on this file since 6619 was 6619, checked in by ldelgass, 3 years ago

merge r6576:6577 from nanovis trunk (timeout option)

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