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

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

add Lat/Long? readout

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