source: nanovis/trunk/nanovisServer.cpp @ 6394

Last change on this file since 6394 was 6394, checked in by ldelgass, 4 years ago

fix var name

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