source: trunk/packages/vizservers/nanovis/nanovisServer.cpp @ 3628

Last change on this file since 3628 was 3614, checked in by ldelgass, 11 years ago

Move version string to nanovis server header

  • Property svn:eol-style set to native
File size: 15.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 "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_fIn = stdin;           ///< Input file handle
51FILE *nv::g_fOut = stdout;         ///< Output file handle
52FILE *nv::g_fLog = NULL;           ///< Trace logging file handle
53ReadBuffer *nv::g_inBufPtr = NULL; ///< Socket read buffer
54#ifdef USE_THREADS
55ResponseQueue *nv::g_queue = NULL;
56#endif
57
58#ifdef USE_THREADS
59
60static void
61queueFrame(ResponseQueue *queue, unsigned char *imgData)
62{
63    queuePPM(queue, "nv>image -type image -bytes",
64             imgData,
65             NanoVis::winWidth,
66             NanoVis::winHeight);
67}
68
69#else
70
71static void
72writeFrame(int fd, unsigned char *imgData)
73{
74    writePPM(fd, "nv>image -type image -bytes",
75             imgData,
76             NanoVis::winWidth,
77             NanoVis::winHeight);
78}
79
80#endif /*USE_THREADS*/
81
82static int
83sendAck()
84{
85    std::ostringstream oss;
86    oss << "nv>ok -token " << g_stats.nCommands <<  "\n";
87    int nBytes = oss.str().length();
88
89    TRACE("Sending OK for commands through %lu", g_stats.nCommands);
90#ifdef USE_THREADS
91    queueResponse(oss.str().c_str(), nBytes, Response::VOLATILE, Response::OK);
92#else
93    if (write(g_fdOut, oss.str().c_str(), nBytes) < 0) {
94        ERROR("write failed: %s", strerror(errno));
95        return -1;
96    }
97#endif
98    return 0;
99}
100
101#ifdef KEEPSTATS
102
103#ifndef STATSDIR
104#define STATSDIR        "/var/tmp/visservers"
105#endif  /*STATSDIR*/
106
107int
108nv::getStatsFile(Tcl_Interp *interp, Tcl_Obj *objPtr)
109{
110    Tcl_DString ds;
111    Tcl_Obj **objv;
112    int objc;
113    int i;
114    char fileName[33];
115    const char *path;
116    md5_state_t state;
117    md5_byte_t digest[16];
118    char *string;
119    int length;
120
121    if ((objPtr == NULL) || (g_statsFile >= 0)) {
122        return g_statsFile;
123    }
124    if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) {
125        return -1;
126    }
127    Tcl_ListObjAppendElement(interp, objPtr, Tcl_NewStringObj("pid", 3));
128    Tcl_ListObjAppendElement(interp, objPtr, Tcl_NewIntObj(getpid()));
129    string = Tcl_GetStringFromObj(objPtr, &length);
130
131    md5_init(&state);
132    md5_append(&state, (const md5_byte_t *)string, length);
133    md5_finish(&state, digest);
134    for (i = 0; i < 16; i++) {
135        sprintf(fileName + i * 2, "%02x", digest[i]);
136    }
137    Tcl_DStringInit(&ds);
138    Tcl_DStringAppend(&ds, STATSDIR, -1);
139    Tcl_DStringAppend(&ds, "/", 1);
140    Tcl_DStringAppend(&ds, fileName, 32);
141    path = Tcl_DStringValue(&ds);
142
143    g_statsFile = open(path, O_EXCL | O_CREAT | O_WRONLY, 0600);
144    Tcl_DStringFree(&ds);
145    if (g_statsFile < 0) {
146        ERROR("can't open \"%s\": %s", fileName, strerror(errno));
147        return -1;
148    }
149    return g_statsFile;
150}
151
152int
153nv::writeToStatsFile(int f, const char *s, size_t length)
154{
155    if (f >= 0) {
156        ssize_t numWritten;
157
158        numWritten = write(f, s, length);
159        if (numWritten == (ssize_t)length) {
160            close(dup(f));
161        }
162    }
163    return 0;
164}
165
166static int
167serverStats(int code)
168{
169    double start, finish;
170    char buf[BUFSIZ];
171    Tcl_DString ds;
172    int result;
173    int f;
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, "%d", getpid());
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 int)g_stats.nFrames);
230    Tcl_DStringAppendElement(&ds, buf);
231    /* frame_bytes */
232    Tcl_DStringAppendElement(&ds, "frame_bytes");
233    sprintf(buf, "%lu", (unsigned long int)g_stats.nFrameBytes);
234    Tcl_DStringAppendElement(&ds, buf);
235    /* num_commands */
236    Tcl_DStringAppendElement(&ds, "num_commands");
237    sprintf(buf, "%lu", (unsigned long int)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    f = getStatsFile(NULL, 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
286void
287nv::sendDataToClient(const char *command, const char *data, size_t dlen)
288{
289#ifdef USE_THREADS
290    char *buf = (char *)malloc(strlen(command) + dlen);
291    memcpy(buf, command, strlen(command));
292    memcpy(buf + strlen(command), data, dlen);
293    queueResponse(buf, strlen(command) + dlen, Response::DYNAMIC);
294#else
295    size_t numRecords = 2;
296    struct iovec *iov = new iovec[numRecords];
297
298    // Write the nanovisviewer command, then the image header and data.
299    // Command
300    iov[0].iov_base = const_cast<char *>(command);
301    iov[0].iov_len = strlen(command);
302    // Data
303    iov[1].iov_base = const_cast<char *>(data);
304    iov[1].iov_len = dlen;
305    if (writev(nv::g_fdOut, iov, numRecords) < 0) {
306        ERROR("write failed: %s", strerror(errno));
307    }
308    delete [] iov;
309#endif
310}
311
312static void
313initService()
314{
315    TRACE("Enter");
316
317    const char* user = getenv("USER");
318    char* logName = NULL;
319    int logNameLen = 0;
320
321    if (user == NULL) {
322        logNameLen = 20+1;
323        logName = (char *)calloc(logNameLen, sizeof(char));
324        strncpy(logName, "/tmp/nanovis_log.txt", logNameLen);
325    } else {
326        logNameLen = 17+1+strlen(user);
327        logName = (char *)calloc(logNameLen, sizeof(char));
328        strncpy(logName, "/tmp/nanovis_log_", logNameLen);
329        strncat(logName, user, strlen(user));
330    }
331
332    // open log and map stderr to log file
333    g_fLog = fopen(logName, "w");
334    close(STDERR_FILENO);
335    dup2(fileno(g_fLog), STDERR_FILENO);
336    // flush junk
337    fflush(stderr);
338
339    // clean up malloc'd memory
340    if (logName != NULL) {
341        free(logName);
342    }
343
344    TRACE("Leave");
345}
346
347static void
348exitService(int code)
349{
350    TRACE("Enter: %d", code);
351
352    NanoVis::removeAllData();
353
354    Shader::exitCg();
355
356    //close log file
357    if (g_fLog != NULL) {
358        fclose(g_fLog);
359        g_fLog = NULL;
360    }
361
362#ifdef KEEPSTATS
363    serverStats(code);
364#endif
365    closelog();
366
367    exit(code);
368}
369
370static void
371idle()
372{
373    TRACE("Enter");
374
375    glutSetWindow(NanoVis::renderWindow);
376
377    if (processCommands(NanoVis::interp, g_inBufPtr, g_fdOut) < 0) {
378        exitService(1);
379    }
380
381    NanoVis::update();
382    NanoVis::bindOffscreenBuffer();
383    NanoVis::render();
384    bool renderedSomething = true;
385    if (renderedSomething) {
386        TRACE("Rendering new frame");
387        NanoVis::readScreen();
388#ifdef USE_THREADS
389        queueFrame(g_queue, NanoVis::screenBuffer);
390#else
391        writeFrame(g_fdOut, NanoVis::screenBuffer);
392#endif
393        g_stats.nFrames++;
394        g_stats.nFrameBytes += NanoVis::winWidth * NanoVis::winHeight * 3;
395    } else {
396        TRACE("No render required");
397        sendAck();
398    }
399
400    if (g_inBufPtr->status() == ReadBuffer::ENDFILE) {
401        exitService(0);
402    }
403
404    TRACE("Leave");
405}
406
407#ifdef USE_THREADS
408
409static void *
410writerThread(void *clientData)
411{
412    ResponseQueue *queue = (ResponseQueue *)clientData;
413
414    TRACE("Starting writer thread");
415    for (;;) {
416        Response *response = queue->dequeue();
417        if (response == NULL)
418            continue;
419        if (fwrite(response->message(), sizeof(char), response->length(),
420                   g_fOut) != response->length()) {
421            ERROR("short write while trying to write %ld bytes",
422                  response->length());
423        }
424        fflush(g_fOut);
425        TRACE("Wrote response of type %d", response->type());
426        delete response;
427        if (feof(g_fOut))
428            break;
429    }   
430    return NULL;
431}
432
433#endif  /*USE_THREADS*/
434
435static
436void cgErrorCallback(void)
437{
438    if (!Shader::printErrorInfo()) {
439        TRACE("Cg error, exiting...");
440        exitService(1);
441    }
442}
443
444static
445void reshape(int width, int height)
446{
447    NanoVis::resizeOffscreenBuffer(width, height);
448}
449
450int
451main(int argc, char **argv)
452{
453    // Ignore SIGPIPE.  **Is this needed? **
454    signal(SIGPIPE, SIG_IGN);
455    initLog();
456
457    memset(&g_stats, 0, sizeof(g_stats));
458    gettimeofday(&g_stats.start, NULL);
459
460    TRACE("Starting NanoVis Server");
461
462    /* This synchronizes the client with the server, so that the client
463     * doesn't start writing commands before the server is ready. It could
464     * also be used to supply information about the server (version, memory
465     * size, etc). */
466    fprintf(stdout, "NanoVis %s (build %s)\n", NANOVIS_VERSION_STRING, SVN_VERSION);
467    fflush(stdout);
468
469    g_inBufPtr = new ReadBuffer(g_fdIn, 1<<12);
470
471    Tcl_Interp *interp = Tcl_CreateInterp();
472    ClientData clientData = NULL;
473#ifdef USE_THREADS
474    g_queue = new ResponseQueue();
475    clientData = (ClientData)g_queue;
476    initTcl(interp, clientData);
477
478    pthread_t writerThreadId;
479    if (pthread_create(&writerThreadId, NULL, &writerThread, g_queue) < 0) {
480        ERROR("Can't create writer thread: %s", strerror(errno));
481    }
482#else
483    initTcl(interp, clientData);
484#endif
485    NanoVis::interp = interp;
486
487    /* Initialize GLUT here so it can parse and remove GLUT-specific
488     * command-line options before we parse the command-line below. */
489    glutInit(&argc, argv);
490    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
491    glutInitWindowSize(NanoVis::winWidth, NanoVis::winHeight);
492    glutInitWindowPosition(10, 10);
493    NanoVis::renderWindow = glutCreateWindow("nanovis");
494
495    glutIdleFunc(idle);
496    glutDisplayFunc(NanoVis::render);
497    glutReshapeFunc(reshape);
498
499    const char *path = NULL;
500    char *newPath = NULL;
501
502    while (1) {
503        static struct option long_options[] = {
504            {"infile",  required_argument, NULL, 0},
505            {"path",    required_argument, NULL, 2},
506            {"debug",   no_argument,       NULL, 3},
507            {0, 0, 0, 0}
508        };
509        int option_index = 0;
510        int c;
511
512        c = getopt_long(argc, argv, ":dp:i:l:r:", long_options, &option_index);
513        if (c == -1) {
514            break;
515        }
516        switch (c) {
517        case '?':
518            fprintf(stderr, "unknown option -%c\n", optopt);
519            return 1;
520        case ':':
521            if (optopt < 4) {
522                fprintf(stderr, "argument missing for --%s option\n",
523                        long_options[optopt].name);
524            } else {
525                fprintf(stderr, "argument missing for -%c option\n", optopt);
526            }
527            return 1;
528        case 2:
529        case 'p':
530            path = optarg;
531            break;
532        case 3:
533        case 'd':
534            NanoVis::debugFlag = true;
535            break;
536        case 0:
537        case 'i':
538            g_fIn = fopen(optarg, "r");
539            if (g_fIn == NULL) {
540                perror(optarg);
541                return 2;
542            }
543            break;
544        default:
545            fprintf(stderr,"unknown option '%c'.\n", c);
546            return 1;
547        }
548    }     
549    if (path == NULL) {
550        char *p;
551
552        // See if we can derive the path from the location of the program.
553        // Assume program is in the form <path>/bin/nanovis.
554        path = argv[0];
555        p = strrchr((char *)path, '/');
556        if (p != NULL) {
557            *p = '\0';
558            p = strrchr((char *)path, '/');
559        }
560        if (p == NULL) {
561            TRACE("path not specified");
562            return 1;
563        }
564        *p = '\0';
565        newPath = new char[(strlen(path) + 15) * 2 + 1];
566        sprintf(newPath, "%s/lib/shaders:%s/lib/resources", path, path);
567        path = newPath;
568    }
569
570    FilePath::getInstance()->setWorkingDirectory(argc, (const char**) argv);
571
572    initService();
573
574    if (!NanoVis::init(path)) {
575        exitService(1);
576    }
577    if (newPath != NULL) {
578        delete [] newPath;
579    }
580
581    // Override callback with one that cleans up server on exit
582    Shader::setErrorCallback(cgErrorCallback);
583
584    if (!NanoVis::initGL()) {
585        exitService(1);
586    }
587
588    if (!NanoVis::resizeOffscreenBuffer(NanoVis::winWidth, NanoVis::winHeight)) {
589        exitService(1);
590    }
591
592    glutMainLoop();
593
594    exitService(0);
595}
Note: See TracBrowser for help on using the repository browser.