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

Last change on this file since 3630 was 3630, checked in by ldelgass, 7 years ago

Nanovis refactoring to fix problems with scaling and multiple results.
Do rendering in world space to properly place and scale multiple data sets.
Also fix flows to reduce resets of animations. More work toward removing
Cg dependency. Fix panning to convert viewport coords to world coords.

  • 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::exit();
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 shaderErrorCallback(void)
437{
438    if (!Shader::printErrorInfo()) {
439        TRACE("Shader 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    Shader::init();
582    // Override callback with one that cleans up server on exit
583    Shader::setErrorCallback(shaderErrorCallback);
584
585    if (!NanoVis::initGL()) {
586        exitService(1);
587    }
588
589    if (!NanoVis::resizeOffscreenBuffer(NanoVis::winWidth, NanoVis::winHeight)) {
590        exitService(1);
591    }
592
593    glutMainLoop();
594
595    exitService(0);
596}
Note: See TracBrowser for help on using the repository browser.