source: trunk/packages/vizservers/geovis/RenderServer.cpp @ 4328

Last change on this file since 4328 was 4328, checked in by ldelgass, 10 years ago

Let's try this: automatically set the client stored viewpoint on every image
response. Currently this uses a 'camera set' right before the image response
rather than adding the parameters to the image response itself.

File size: 16.6 KB
Line 
1/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 * Copyright (C) 2013  HUBzero Foundation, LLC
4 *
5 * Author: Leif Delgass <ldelgass@purdue.edu>
6 */
7
8#include <cstdio>
9#include <cstring>
10#include <cstdlib>
11#include <cerrno>
12#include <unistd.h>
13#include <signal.h>
14#include <sys/types.h>
15#include <sys/stat.h>
16#include <fcntl.h>
17#include <sys/time.h>
18#include <sys/times.h>
19
20#include <string>
21#include <sstream>
22
23#include <tcl.h>
24
25#include "Trace.h"
26#include "ReadBuffer.h"
27#include "RenderServer.h"
28#include "RendererCmd.h"
29#include "Renderer.h"
30#include "PPMWriter.h"
31#include "TGAWriter.h"
32#ifdef USE_THREADS
33#include <pthread.h>
34#include "ResponseQueue.h"
35#ifdef USE_READ_THREAD
36#include "CommandQueue.h"
37#endif
38#endif
39#include <md5.h>
40
41using namespace GeoVis;
42
43Stats GeoVis::g_stats;
44
45int GeoVis::g_statsFile = -1; ///< Stats output file descriptor.
46int GeoVis::g_fdIn = STDIN_FILENO; ///< Input file descriptor
47int GeoVis::g_fdOut = STDOUT_FILENO; ///< Output file descriptor
48FILE *GeoVis::g_fOut = NULL; ///< Output file handle
49FILE *GeoVis::g_fLog = NULL; ///< Trace logging file handle
50Renderer *GeoVis::g_renderer = NULL; ///< Main render worker
51ReadBuffer *GeoVis::g_inBufPtr = NULL; ///< Socket read buffer
52#ifdef USE_THREADS
53ResponseQueue *GeoVis::g_outQueue = NULL;
54#ifdef USE_READER_THREAD
55CommandQueue *GeoVis::g_inQueue = NULL;
56#endif
57#endif
58
59static int
60queueViewpoint()
61{
62    osgEarth::Viewpoint view = g_renderer->getViewpoint();
63
64    std::ostringstream oss;
65    size_t len = 0;
66    oss << "nv>camera get "
67        << view.x() << " "
68        << view.y() << " "
69        << view.z() << " "
70        << view.getHeading() << " "
71        << view.getPitch() << " "
72        << view.getRange()
73        << " {" << ((view.getSRS() == NULL) ? "" : view.getSRS()->getHorizInitString()) << "}"
74        << " {" << ((view.getSRS() == NULL) ? "" : view.getSRS()->getVertInitString()) << "}"
75        << "\n";
76    len = oss.str().size();
77#ifdef USE_THREADS
78    queueResponse(oss.str().c_str(), len, Response::VOLATILE);
79#else
80    ssize_t bytesWritten = SocketWrite(oss.str().c_str(), len);
81
82    if (bytesWritten < 0) {
83        return TCL_ERROR;
84    }
85#endif /*USE_THREADS*/
86    return TCL_OK;
87}
88
89#ifdef USE_THREADS
90static void
91queueFrame(ResponseQueue *queue, const unsigned char *imgData)
92{
93#ifdef DEBUG_WRITE_FRAME_FILE
94
95#ifdef RENDER_TARGA
96    writeTGAFile("/tmp/frame.tga",
97                 imgData,
98                 g_renderer->getWindowWidth(),
99                 g_renderer->getWindowHeight(),
100                 TARGA_BYTES_PER_PIXEL);
101#else
102    writeTGAFile("/tmp/frame.tga",
103                 imgData,
104                 g_renderer->getWindowWidth(),
105                 g_renderer->getWindowHeight(),
106                 TARGA_BYTES_PER_PIXEL,
107                 true);
108#endif  /*RENDER_TARGA*/
109
110#else
111 
112#ifdef RENDER_TARGA
113    queueTGA(queue, "nv>image -type image -bytes",
114             imgData,
115             g_renderer->getWindowWidth(),
116             g_renderer->getWindowHeight(),
117             TARGA_BYTES_PER_PIXEL);
118#else
119    queuePPM(queue, "nv>image -type image -bytes",
120             imgData,
121             g_renderer->getWindowWidth(),
122             g_renderer->getWindowHeight());
123#endif  /*RENDER_TARGA*/
124#endif  /*DEBUG_WRITE_FRAME_FILE*/
125}
126
127#else
128
129static void
130writeFrame(int fd, const unsigned char *imgData)
131{
132#ifdef DEBUG_WRITE_FRAME_FILE
133
134#ifdef RENDER_TARGA
135    writeTGAFile("/tmp/frame.tga",
136                 imgData,
137                 g_renderer->getWindowWidth(),
138                 g_renderer->getWindowHeight(),
139                 TARGA_BYTES_PER_PIXEL);
140#else
141    writeTGAFile("/tmp/frame.tga",
142                 imgData,
143                 g_renderer->getWindowWidth(),
144                 g_renderer->getWindowHeight(),
145                 TARGA_BYTES_PER_PIXEL,
146                 true);
147#endif  /*RENDER_TARGA*/
148
149#else
150
151#ifdef RENDER_TARGA
152    writeTGA(fd, "nv>image -type image -bytes",
153             imgData,
154             g_renderer->getWindowWidth(),
155             g_renderer->getWindowHeight(),
156             TARGA_BYTES_PER_PIXEL);
157#else
158    writePPM(fd, "nv>image -type image -bytes",
159             imgData,
160             g_renderer->getWindowWidth(),
161             g_renderer->getWindowHeight());
162#endif  /*RENDER_TARGA*/
163#endif  /*DEBUG_WRITE_FRAME_FILE*/
164}
165#endif /*USE_THREADS*/
166
167static int
168sendAck()
169{
170    std::ostringstream oss;
171    oss << "nv>ok -token " << g_stats.nCommands <<  "\n";
172    int nBytes = oss.str().length();
173
174    TRACE("Sending OK for commands through %lu", g_stats.nCommands);
175#ifdef USE_THREADS
176    queueResponse(oss.str().c_str(), nBytes, Response::VOLATILE, Response::OK);
177#else
178    if (write(g_fdOut, oss.str().c_str(), nBytes) < 0) {
179        ERROR("write failed: %s", strerror(errno));
180        return -1;
181    }
182#endif
183    return 0;
184}
185
186int
187GeoVis::getStatsFile(Tcl_Interp *interp, Tcl_Obj *objPtr)
188{
189    Tcl_DString ds;
190    Tcl_Obj **objv;
191    int objc;
192    int i;
193    char fileName[33];
194    const char *path;
195    md5_state_t state;
196    md5_byte_t digest[16];
197    char *string;
198    int length;
199
200    if ((objPtr == NULL) || (g_statsFile >= 0)) {
201        return g_statsFile;
202    }
203    if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) {
204        return -1;
205    }
206    Tcl_ListObjAppendElement(interp, objPtr, Tcl_NewStringObj("pid", 3));
207    Tcl_ListObjAppendElement(interp, objPtr, Tcl_NewIntObj(getpid()));
208    string = Tcl_GetStringFromObj(objPtr, &length);
209
210    md5_init(&state);
211    md5_append(&state, (const md5_byte_t *)string, length);
212    md5_finish(&state, digest);
213    for (i = 0; i < 16; i++) {
214        sprintf(fileName + i * 2, "%02x", digest[i]);
215    }
216    Tcl_DStringInit(&ds);
217    Tcl_DStringAppend(&ds, STATSDIR, -1);
218    Tcl_DStringAppend(&ds, "/", 1);
219    Tcl_DStringAppend(&ds, fileName, 32);
220    path = Tcl_DStringValue(&ds);
221
222    g_statsFile = open(path, O_EXCL | O_CREAT | O_WRONLY, 0600);
223    Tcl_DStringFree(&ds);
224    if (g_statsFile < 0) {
225        ERROR("can't open \"%s\": %s", fileName, strerror(errno));
226        return -1;
227    }
228    return g_statsFile;
229}
230
231int
232GeoVis::writeToStatsFile(int f, const char *s, size_t length)
233{
234    if (f >= 0) {
235        ssize_t numWritten;
236
237        numWritten = write(f, s, length);
238        if (numWritten == (ssize_t)length) {
239            close(dup(f));
240        }
241    }
242    return 0;
243}
244
245static int
246serverStats(int code)
247{
248    double start, finish;
249    char buf[BUFSIZ];
250    Tcl_DString ds;
251    int result;
252    int f;
253
254    {
255        struct timeval tv;
256
257        /* Get ending time.  */
258        gettimeofday(&tv, NULL);
259        finish = CVT2SECS(tv);
260        tv = g_stats.start;
261        start = CVT2SECS(tv);
262    }
263    /*
264     * Session information:
265     *   - Name of render server
266     *   - Process ID
267     *   - Hostname where server is running
268     *   - Start date of session
269     *   - Start date of session in seconds
270     *   - Number of data sets loaded from client
271     *   - Number of data bytes total loaded from client
272     *   - Number of frames returned
273     *   - Number of bytes total returned (in frames)
274     *   - Number of commands received
275     *   - Total elapsed time of all commands
276     *   - Total elapsed time of session
277     *   - Exit code of vizserver
278     *   - User time
279     *   - System time
280     *   - User time of children
281     *   - System time of children
282     */
283
284    Tcl_DStringInit(&ds);
285   
286    Tcl_DStringAppendElement(&ds, "render_stop");
287    /* renderer */
288    Tcl_DStringAppendElement(&ds, "renderer");
289    Tcl_DStringAppendElement(&ds, "geovis");
290    /* pid */
291    Tcl_DStringAppendElement(&ds, "pid");
292    sprintf(buf, "%d", getpid());
293    Tcl_DStringAppendElement(&ds, buf);
294    /* host */
295    Tcl_DStringAppendElement(&ds, "host");
296    gethostname(buf, BUFSIZ-1);
297    buf[BUFSIZ-1] = '\0';
298    Tcl_DStringAppendElement(&ds, buf);
299    /* date */
300    Tcl_DStringAppendElement(&ds, "date");
301    strcpy(buf, ctime(&g_stats.start.tv_sec));
302    buf[strlen(buf) - 1] = '\0';
303    Tcl_DStringAppendElement(&ds, buf);
304    /* date_secs */
305    Tcl_DStringAppendElement(&ds, "date_secs");
306    sprintf(buf, "%ld", g_stats.start.tv_sec);
307    Tcl_DStringAppendElement(&ds, buf);
308    /* num_data_sets */
309    Tcl_DStringAppendElement(&ds, "num_data_sets");
310    sprintf(buf, "%lu", (unsigned long int)g_stats.nDataSets);
311    Tcl_DStringAppendElement(&ds, buf);
312    /* data_set_bytes */
313    Tcl_DStringAppendElement(&ds, "data_set_bytes");
314    sprintf(buf, "%lu", (unsigned long int)g_stats.nDataBytes);
315    Tcl_DStringAppendElement(&ds, buf);
316    /* num_frames */
317    Tcl_DStringAppendElement(&ds, "num_frames");
318    sprintf(buf, "%lu", (unsigned long int)g_stats.nFrames);
319    Tcl_DStringAppendElement(&ds, buf);
320    /* frame_bytes */
321    Tcl_DStringAppendElement(&ds, "frame_bytes");
322    sprintf(buf, "%lu", (unsigned long int)g_stats.nFrameBytes);
323    Tcl_DStringAppendElement(&ds, buf);
324    /* num_commands */
325    Tcl_DStringAppendElement(&ds, "num_commands");
326    sprintf(buf, "%lu", (unsigned long int)g_stats.nCommands);
327    Tcl_DStringAppendElement(&ds, buf);
328    /* cmd_time */
329    Tcl_DStringAppendElement(&ds, "cmd_time");
330    sprintf(buf, "%g", g_stats.cmdTime);
331    Tcl_DStringAppendElement(&ds, buf);
332    /* session_time */
333    Tcl_DStringAppendElement(&ds, "session_time");
334    sprintf(buf, "%g", finish - start);
335    Tcl_DStringAppendElement(&ds, buf);
336    /* status */
337    Tcl_DStringAppendElement(&ds, "status");
338    sprintf(buf, "%d", code);
339    Tcl_DStringAppendElement(&ds, buf);
340    {
341        long clocksPerSec = sysconf(_SC_CLK_TCK);
342        double clockRes = 1.0 / clocksPerSec;
343        struct tms tms;
344
345        memset(&tms, 0, sizeof(tms));
346        times(&tms);
347        /* utime */
348        Tcl_DStringAppendElement(&ds, "utime");
349        sprintf(buf, "%g", tms.tms_utime * clockRes);
350        Tcl_DStringAppendElement(&ds, buf);
351        /* stime */
352        Tcl_DStringAppendElement(&ds, "stime");
353        sprintf(buf, "%g", tms.tms_stime * clockRes);
354        Tcl_DStringAppendElement(&ds, buf);
355        /* cutime */
356        Tcl_DStringAppendElement(&ds, "cutime");
357        sprintf(buf, "%g", tms.tms_cutime * clockRes);
358        Tcl_DStringAppendElement(&ds, buf);
359        /* cstime */
360        Tcl_DStringAppendElement(&ds, "cstime");
361        sprintf(buf, "%g", tms.tms_cstime * clockRes);
362        Tcl_DStringAppendElement(&ds, buf);
363    }
364    Tcl_DStringAppend(&ds, "\n", -1);
365    f = getStatsFile(NULL, NULL);
366    result = writeToStatsFile(f, Tcl_DStringValue(&ds),
367                              Tcl_DStringLength(&ds));
368    close(f);
369    Tcl_DStringFree(&ds);
370    return result;
371}
372
373static void
374initService()
375{
376    g_fOut = fdopen(g_fdOut, "w");
377    // If running without socket, use stdout for debugging
378    if (g_fOut == NULL && g_fdOut != STDOUT_FILENO) {
379        g_fdOut = STDOUT_FILENO;
380        g_fOut = fdopen(g_fdOut, "w");
381    }
382
383    const char *user = getenv("USER");
384    char *logName = NULL;
385    int logNameLen = 0;
386
387    if (user == NULL) {
388        logNameLen = 19+1;
389        logName = (char *)calloc(logNameLen, sizeof(char));
390        strncpy(logName, "/tmp/geovis_log.txt", logNameLen);
391    } else {
392        logNameLen = 16+strlen(user)+4+1;
393        logName = (char *)calloc(logNameLen, sizeof(char));
394        strncpy(logName, "/tmp/geovis_log_", logNameLen);
395        strncat(logName, user, strlen(user));
396        strncat(logName, ".txt", 4);
397    }
398
399    // open log and map stderr to log file
400    g_fLog = fopen(logName, "w");
401    dup2(fileno(g_fLog), STDERR_FILENO);
402    // If we are writing to socket, map stdout to log
403    if (g_fdOut != STDOUT_FILENO) {
404        dup2(fileno(g_fLog), STDOUT_FILENO);
405    }
406
407    fflush(stdout);
408
409    // clean up malloc'd memory
410    if (logName != NULL) {
411        free(logName);
412    }
413}
414
415static void
416exitService()
417{
418    TRACE("Enter");
419
420    serverStats(0);
421
422    // close log file
423    if (g_fLog != NULL) {
424        fclose(g_fLog);
425        g_fLog = NULL;
426    }
427}
428
429#ifdef USE_THREADS
430
431#ifdef USE_READ_THREAD
432static void *
433readerThread(void *clientData)
434{
435    Tcl_Interp *interp = (Tcl_Interp *)clientData;
436
437    TRACE("Starting reader thread");
438
439    queueCommands(interp, NULL, g_inBufPtr);
440
441    return NULL;
442}
443#endif
444
445static void *
446writerThread(void *clientData)
447{
448    ResponseQueue *queue = (ResponseQueue *)clientData;
449
450    TRACE("Starting writer thread");
451    for (;;) {
452        Response *response = queue->dequeue();
453        if (response == NULL)
454            continue;
455        if (fwrite(response->message(), sizeof(unsigned char), response->length(),
456                   g_fOut) != response->length()) {
457            ERROR("short write while trying to write %ld bytes",
458                  response->length());
459        }
460        fflush(g_fOut);
461        TRACE("Wrote response of type %d", response->type());
462        delete response;
463        if (feof(g_fOut))
464            break;
465    }   
466    return NULL;
467}
468
469#endif  /*USE_THREADS*/
470
471int
472main(int argc, char *argv[])
473{
474    // Ignore SIGPIPE.  **Is this needed? **
475    signal(SIGPIPE, SIG_IGN);
476
477    //const char *resourcePath = NULL;
478    while (1) {
479        int c = getopt(argc, argv, "p:i:o:");
480        if (c == -1) {
481            break;
482        }
483        switch (c) {
484        case 'p':
485            //resourcePath = optarg;
486            break;
487        case 'i': {
488            int fd = atoi(optarg);
489            if (fd >=0 && fd < 5) {
490                g_fdIn = fd;
491            }
492        }
493            break;
494        case 'o': {
495            int fd = atoi(optarg);
496            if (fd >=0 && fd < 5) {
497                g_fdOut = fd;
498            }
499        }
500            break;
501        case '?':
502            break;
503        default:
504            return 1;
505        }
506    }
507
508    initService();
509    initLog();
510
511    memset(&g_stats, 0, sizeof(g_stats));
512    gettimeofday(&g_stats.start, NULL);
513
514    TRACE("Starting GeoVis Server");
515
516    // Sanity check: log descriptor can't be used for client IO
517    if (fileno(g_fLog) == g_fdIn) {
518        ERROR("Invalid input file descriptor");
519        return 1;
520    }
521    if (fileno(g_fLog) == g_fdOut) {
522        ERROR("Invalid output file descriptor");
523        return 1;
524    }
525    TRACE("File descriptors: in %d out %d log %d", g_fdIn, g_fdOut, fileno(g_fLog));
526
527    /* This synchronizes the client with the server, so that the client
528     * doesn't start writing commands before the server is ready. It could
529     * also be used to supply information about the server (version, memory
530     * size, etc). */
531    fprintf(g_fOut, "GeoVis %s (build %s)\n", GEOVIS_VERSION_STRING, SVN_VERSION);
532    fflush(g_fOut);
533
534    g_renderer = new Renderer();
535    g_inBufPtr = new ReadBuffer(g_fdIn, 1<<12);
536
537    Tcl_Interp *interp = Tcl_CreateInterp();
538
539#ifdef USE_THREADS
540    g_outQueue = new ResponseQueue();
541
542    pthread_t writerThreadId;
543    if (pthread_create(&writerThreadId, NULL, &writerThread, g_outQueue) < 0) {
544        ERROR("Can't create writer thread: %s", strerror(errno));
545    }
546#endif
547    initTcl(interp, NULL);
548
549    osg::ref_ptr<osg::Image> imgData;
550
551    // Start main server loop
552    for (;;) {
553        long timeout = g_renderer->getTimeout();
554        int cmdStatus = processCommands(interp, NULL, g_inBufPtr, g_fdOut, timeout);
555        if (cmdStatus < 0)
556            break;
557
558        if (g_renderer->render()) {
559            TRACE("Rendered new frame");
560            imgData = g_renderer->getRenderedFrame();
561            if (imgData == NULL) {
562                ERROR("Empty image");
563            } else {
564                TRACE("Image: %d x %d", imgData->s(), imgData->t());
565
566                if (imgData->s() == g_renderer->getWindowWidth() &&
567                    imgData->t() == g_renderer->getWindowHeight()) {
568                    queueViewpoint();
569#ifdef USE_THREADS
570                    queueFrame(g_outQueue, imgData->data());
571#else
572                    writeFrame(g_fdOut, imgData->data());
573#endif
574                }
575                g_stats.nFrames++;
576                g_stats.nFrameBytes += imgData->s() * imgData->t() * 3;
577            }
578        } else {
579            //TRACE("No render required");
580            if (cmdStatus > 1) {
581                sendAck();
582            }
583        }
584
585#if 0
586        double x, y, z;
587        if (g_renderer->getMousePoint(&x, &y, &z)) {
588            // send coords to client
589            size_t length;
590            char mesg[256];
591
592            length = snprintf(mesg, sizeof(mesg),
593                              "nv>map coords %g %g %g\n", x, y, z);
594
595            queueResponse(mesg, length, Response::VOLATILE);
596        }
597#endif
598
599        if (g_inBufPtr->status() == ReadBuffer::ENDFILE)
600            break;
601    }
602#ifdef USE_THREADS
603    // Writer thread is probably blocked on sem_wait, so cancel instead
604    // of joining
605    if (pthread_cancel(writerThreadId) < 0) {
606        ERROR("Can't cancel writer thread: %s", strerror(errno));
607    } else {
608        TRACE("Cancelled writer thread");
609    }
610
611    TRACE("Deleting ResponseQueue");
612    delete g_outQueue;
613    g_outQueue = NULL;
614#endif
615
616    TRACE("Stopping Tcl interpreter");
617    exitTcl(interp);
618    interp = NULL;
619
620    TRACE("Deleting ReadBuffer");
621    delete g_inBufPtr;
622    g_inBufPtr = NULL;
623
624    TRACE("Deleting renderer");
625    delete g_renderer;
626    g_renderer = NULL;
627
628    TRACE("Exiting GeoVis Server");
629
630    closeLog();
631    exitService();
632
633    return 0;
634}
Note: See TracBrowser for help on using the repository browser.