source: nanovis/trunk/nanovisServer.cpp @ 6576

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

Add optional idle timeout to nanovis server. Allows server to close connection
and exit after a specified period without commands from the client.

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