source: nanovis/trunk/nanovisServer.cpp @ 4790

Last change on this file since 4790 was 4111, checked in by ldelgass, 10 years ago

Revert some changes from r4108: nanovis and vtkvis redirect stdout/stderr to
the trace log. Default to using stdin/stdout in nanovis/vtkvis when no command
line option is given. Add command line options to nanoscale config file to
set socket descriptor for nanovis/vtkvis/geovis.

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