source: nanovis/trunk/nanovisServer.cpp @ 4795

Last change on this file since 4795 was 4794, checked in by ldelgass, 9 years ago

Don't add extra pid to stats log line

  • Property svn:eol-style set to native
File size: 16.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_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    std::string str = oss.str();
87    size_t numBytes = str.length();
88
89    TRACE("Sending OK for commands through %lu", g_stats.nCommands);
90#ifdef USE_THREADS
91    queueResponse(str.c_str(), numBytes, Response::VOLATILE, Response::OK);
92#else
93    if (write(g_fdOut, str.c_str(), numBytes) < 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_Obj *objPtr)
109{
110    Tcl_DString ds;
111    char fileName[33];
112    char pidstr[200];
113    const char *path;
114    md5_state_t state;
115    md5_byte_t digest[16];
116    char *string;
117    int length;
118
119    if ((objPtr == NULL) || (g_statsFile >= 0)) {
120        return g_statsFile;
121    }
122    /* By itself the client's key/value pairs aren't unique.  Add in the
123     * process id of this render server. */
124    sprintf(pidstr, "%ld", (long)g_stats.pid);
125
126    /* Create an md5 hash of the key/value pairs and use it as the file name. */
127    string = Tcl_GetStringFromObj(objPtr, &length);
128    md5_init(&state);
129    md5_append(&state, (const md5_byte_t *)string, strlen(string));
130    md5_append(&state, (const md5_byte_t *)pidstr, strlen(pidstr));
131    md5_finish(&state, digest);
132    for (int i = 0; i < 16; i++) {
133        sprintf(fileName + i * 2, "%02x", digest[i]);
134    }
135    Tcl_DStringInit(&ds);
136    Tcl_DStringAppend(&ds, STATSDIR, -1);
137    Tcl_DStringAppend(&ds, "/", 1);
138    Tcl_DStringAppend(&ds, fileName, 32);
139    path = Tcl_DStringValue(&ds);
140
141    g_statsFile = open(path, O_EXCL | O_CREAT | O_WRONLY, 0600);
142    Tcl_DStringFree(&ds);
143    if (g_statsFile < 0) {
144        ERROR("can't open \"%s\": %s", fileName, strerror(errno));
145        return -1;
146    }
147    return g_statsFile;
148}
149
150int
151nv::writeToStatsFile(int f, const char *s, size_t length)
152{
153    if (f >= 0) {
154        ssize_t numWritten;
155
156        numWritten = write(f, s, length);
157        if (numWritten == (ssize_t)length) {
158            close(dup(f));
159        }
160    }
161    return 0;
162}
163
164static int
165serverStats(int code)
166{
167    double start, finish;
168    char buf[BUFSIZ];
169    Tcl_DString ds;
170    int result;
171    int f;
172
173    {
174        struct timeval tv;
175
176        /* Get ending time.  */
177        gettimeofday(&tv, NULL);
178        finish = CVT2SECS(tv);
179        tv = g_stats.start;
180        start = CVT2SECS(tv);
181    }
182    /*
183     * Session information:
184     *   - Name of render server
185     *   - Process ID
186     *   - Hostname where server is running
187     *   - Start date of session
188     *   - Start date of session in seconds
189     *   - Number of frames returned
190     *   - Number of bytes total returned (in frames)
191     *   - Number of commands received
192     *   - Total elapsed time of all commands
193     *   - Total elapsed time of session
194     *   - Exit code of vizserver
195     *   - User time
196     *   - System time
197     *   - User time of children
198     *   - System time of children
199     */
200
201    Tcl_DStringInit(&ds);
202   
203    Tcl_DStringAppendElement(&ds, "render_stop");
204    /* renderer */
205    Tcl_DStringAppendElement(&ds, "renderer");
206    Tcl_DStringAppendElement(&ds, "nanovis");
207    /* pid */
208    Tcl_DStringAppendElement(&ds, "pid");
209    sprintf(buf, "%ld", (long)g_stats.pid);
210    Tcl_DStringAppendElement(&ds, buf);
211    /* host */
212    Tcl_DStringAppendElement(&ds, "host");
213    gethostname(buf, BUFSIZ-1);
214    buf[BUFSIZ-1] = '\0';
215    Tcl_DStringAppendElement(&ds, buf);
216    /* date */
217    Tcl_DStringAppendElement(&ds, "date");
218    strcpy(buf, ctime(&g_stats.start.tv_sec));
219    buf[strlen(buf) - 1] = '\0';
220    Tcl_DStringAppendElement(&ds, buf);
221    /* date_secs */
222    Tcl_DStringAppendElement(&ds, "date_secs");
223    sprintf(buf, "%ld", g_stats.start.tv_sec);
224    Tcl_DStringAppendElement(&ds, buf);
225    /* num_frames */
226    Tcl_DStringAppendElement(&ds, "num_frames");
227    sprintf(buf, "%lu", (unsigned long int)g_stats.nFrames);
228    Tcl_DStringAppendElement(&ds, buf);
229    /* frame_bytes */
230    Tcl_DStringAppendElement(&ds, "frame_bytes");
231    sprintf(buf, "%lu", (unsigned long int)g_stats.nFrameBytes);
232    Tcl_DStringAppendElement(&ds, buf);
233    /* num_commands */
234    Tcl_DStringAppendElement(&ds, "num_commands");
235    sprintf(buf, "%lu", (unsigned long int)g_stats.nCommands);
236    Tcl_DStringAppendElement(&ds, buf);
237    /* cmd_time */
238    Tcl_DStringAppendElement(&ds, "cmd_time");
239    sprintf(buf, "%g", g_stats.cmdTime);
240    Tcl_DStringAppendElement(&ds, buf);
241    /* session_time */
242    Tcl_DStringAppendElement(&ds, "session_time");
243    sprintf(buf, "%g", finish - start);
244    Tcl_DStringAppendElement(&ds, buf);
245    /* status */
246    Tcl_DStringAppendElement(&ds, "status");
247    sprintf(buf, "%d", code);
248    Tcl_DStringAppendElement(&ds, buf);
249    {
250        long clocksPerSec = sysconf(_SC_CLK_TCK);
251        double clockRes = 1.0 / clocksPerSec;
252        struct tms tms;
253
254        memset(&tms, 0, sizeof(tms));
255        times(&tms);
256        /* utime */
257        Tcl_DStringAppendElement(&ds, "utime");
258        sprintf(buf, "%g", tms.tms_utime * clockRes);
259        Tcl_DStringAppendElement(&ds, buf);
260        /* stime */
261        Tcl_DStringAppendElement(&ds, "stime");
262        sprintf(buf, "%g", tms.tms_stime * clockRes);
263        Tcl_DStringAppendElement(&ds, buf);
264        /* cutime */
265        Tcl_DStringAppendElement(&ds, "cutime");
266        sprintf(buf, "%g", tms.tms_cutime * clockRes);
267        Tcl_DStringAppendElement(&ds, buf);
268        /* cstime */
269        Tcl_DStringAppendElement(&ds, "cstime");
270        sprintf(buf, "%g", tms.tms_cstime * clockRes);
271        Tcl_DStringAppendElement(&ds, buf);
272    }
273    Tcl_DStringAppend(&ds, "\n", -1);
274    f = getStatsFile(NULL);
275    result = writeToStatsFile(f, Tcl_DStringValue(&ds),
276                              Tcl_DStringLength(&ds));
277    close(f);
278    Tcl_DStringFree(&ds);
279    return result;
280}
281
282#endif
283
284/**
285 * \brief Send a command with data payload
286 *
287 * data pointer is freed on completion of the response
288 */
289void
290nv::sendDataToClient(const char *command, char *data, size_t dlen)
291{
292#ifdef USE_THREADS
293    char *buf = (char *)malloc(strlen(command) + dlen);
294    memcpy(buf, command, strlen(command));
295    memcpy(buf + strlen(command), data, dlen);
296    queueResponse(buf, strlen(command) + dlen, Response::DYNAMIC);
297#else
298    size_t numRecords = 2;
299    struct iovec *iov = new iovec[numRecords];
300
301    // Write the nanovisviewer command, then the image header and data.
302    // Command
303    iov[0].iov_base = const_cast<char *>(command);
304    iov[0].iov_len = strlen(command);
305    // Data
306    iov[1].iov_base = data;
307    iov[1].iov_len = dlen;
308    if (writev(nv::g_fdOut, iov, numRecords) < 0) {
309        ERROR("write failed: %s", strerror(errno));
310    }
311    delete [] iov;
312    free(data);
313#endif
314}
315
316static void
317initService()
318{
319    // Create a stream associated with the output file descriptor
320    g_fOut = fdopen(g_fdOut, "w");
321    // If running without a socket, use stdout for debugging
322    if (g_fOut == NULL) {
323        g_fdOut = STDOUT_FILENO;
324        g_fOut = stdout;
325    }
326
327    const char* user = getenv("USER");
328    char* logName = NULL;
329    int logNameLen = 0;
330
331    if (user == NULL) {
332        logNameLen = 20+1;
333        logName = (char *)calloc(logNameLen, sizeof(char));
334        strncpy(logName, "/tmp/nanovis_log.txt", logNameLen);
335    } else {
336        logNameLen = 17+1+strlen(user);
337        logName = (char *)calloc(logNameLen, sizeof(char));
338        strncpy(logName, "/tmp/nanovis_log_", logNameLen);
339        strncat(logName, user, strlen(user));
340    }
341
342    // open log and map stderr to log file
343    g_fLog = fopen(logName, "w");
344    dup2(fileno(g_fLog), STDERR_FILENO);
345    // If we are writing to socket, map stdout to log
346    if (g_fdOut != STDOUT_FILENO) {
347        dup2(fileno(g_fLog), STDOUT_FILENO);
348    }
349
350    fflush(stdout);
351
352    // clean up malloc'd memory
353    if (logName != NULL) {
354        free(logName);
355    }
356}
357
358static void
359exitService(int code)
360{
361    TRACE("Enter: %d", code);
362
363    NanoVis::removeAllData();
364
365    Shader::exit();
366
367    //close log file
368    if (g_fLog != NULL) {
369        fclose(g_fLog);
370        g_fLog = NULL;
371    }
372
373#ifdef KEEPSTATS
374    serverStats(code);
375#endif
376    closelog();
377
378    exit(code);
379}
380
381static void
382idle()
383{
384    TRACE("Enter");
385
386    glutSetWindow(NanoVis::renderWindow);
387
388    if (processCommands(NanoVis::interp, g_inBufPtr, g_fdOut) < 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        static struct option long_options[] = {
470            {"debug",   no_argument,       NULL, 'd'},
471            {"path",    required_argument, NULL, 'p'},
472            {0, 0, 0, 0}
473        };
474        int option_index = 0;
475        int c = getopt_long(argc, argv, "dp:i:o:", long_options, &option_index);
476        if (c == -1) {
477            break;
478        }
479        switch (c) {
480        case 'd':
481            NanoVis::debugFlag = true;
482            break;
483        case 'p':
484            resourcePath = optarg;
485            break;
486        case 'i': {
487            int fd = atoi(optarg);
488            if (fd >=0 && fd < 5) {
489                g_fdIn = fd;
490            }
491        }
492            break;
493        case 'o': {
494            int fd = atoi(optarg);
495            if (fd >=0 && fd < 5) {
496                g_fdOut = fd;
497            }
498        }
499            break;
500        case '?':
501            break;
502        default:
503            return 1;
504        }
505    }
506
507    initService();
508    initLog();
509
510    memset(&g_stats, 0, sizeof(g_stats));
511    g_stats.pid = getpid();
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.