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

Last change on this file since 4105 was 4105, checked in by ldelgass, 11 years ago

Prep servers for accepting arguments to change file descriptors for IO. This
will allow us to send stdout to the trace log along with stderr so chatty
libraries won't send garbage to the socket. Fall back to stdin/stdout to allow
debugging without nanoscale.

File size: 14.7 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    initService();
447    initLog();
448
449    memset(&g_stats, 0, sizeof(g_stats));
450    gettimeofday(&g_stats.start, NULL);
451
452    TRACE("Starting GeoVis Server");
453
454    /* This synchronizes the client with the server, so that the client
455     * doesn't start writing commands before the server is ready. It could
456     * also be used to supply information about the server (version, memory
457     * size, etc). */
458    fprintf(g_fOut, "GeoVis %s (build %s)\n", GEOVIS_VERSION_STRING, SVN_VERSION);
459    fflush(g_fOut);
460
461    g_renderer = new Renderer();
462    g_inBufPtr = new ReadBuffer(g_fdIn, 1<<12);
463
464    Tcl_Interp *interp = Tcl_CreateInterp();
465
466#ifdef USE_THREADS
467    g_outQueue = new ResponseQueue();
468
469    pthread_t writerThreadId;
470    if (pthread_create(&writerThreadId, NULL, &writerThread, g_outQueue) < 0) {
471        ERROR("Can't create writer thread: %s", strerror(errno));
472    }
473#endif
474    initTcl(interp, NULL);
475
476    osg::ref_ptr<osg::Image> imgData;
477
478    // Start main server loop
479    for (;;) {
480        long timeout = g_renderer->getTimeout();
481        int cmdStatus = processCommands(interp, NULL, g_inBufPtr, g_fdOut, timeout);
482        if (cmdStatus < 0)
483            break;
484
485        if (g_renderer->render()) {
486            TRACE("Rendered new frame");
487            imgData = g_renderer->getRenderedFrame();
488            if (imgData == NULL) {
489                ERROR("Empty image");
490            } else {
491                TRACE("Image: %d x %d", imgData->s(), imgData->t());
492            }
493            if (imgData->s() == g_renderer->getWindowWidth() &&
494                imgData->t() == g_renderer->getWindowHeight()) {
495#ifdef USE_THREADS
496                queueFrame(g_outQueue, imgData->data());
497#else
498                writeFrame(g_fdOut, imgData->data());
499#endif
500            }
501            g_stats.nFrames++;
502            g_stats.nFrameBytes += imgData->s() * imgData->t() * 3;
503        } else {
504            //TRACE("No render required");
505            if (cmdStatus > 1) {
506                sendAck();
507            }
508        }
509
510        double x, y, z;
511        if (g_renderer->getMousePoint(&x, &y, &z)) {
512            // send coords to client
513            size_t length;
514            char mesg[256];
515
516            length = snprintf(mesg, sizeof(mesg),
517                              "nv>dataset coords %g %g %g\n", x, y, z);
518
519            queueResponse(mesg, length, Response::VOLATILE);
520        }
521
522        if (g_inBufPtr->status() == ReadBuffer::ENDFILE)
523            break;
524    }
525#ifdef USE_THREADS
526    // Writer thread is probably blocked on sem_wait, so cancel instead
527    // of joining
528    if (pthread_cancel(writerThreadId) < 0) {
529        ERROR("Can't cancel writer thread: %s", strerror(errno));
530    } else {
531        TRACE("Cancelled writer thread");
532    }
533
534    TRACE("Deleting ResponseQueue");
535    delete g_outQueue;
536    g_outQueue = NULL;
537#endif
538
539    TRACE("Stopping Tcl interpreter");
540    exitTcl(interp);
541    interp = NULL;
542
543    TRACE("Deleting ReadBuffer");
544    delete g_inBufPtr;
545    g_inBufPtr = NULL;
546
547    TRACE("Deleting renderer");
548    delete g_renderer;
549    g_renderer = NULL;
550
551    TRACE("Exiting GeoVis Server");
552
553    closeLog();
554    exitService();
555
556    return 0;
557}
Note: See TracBrowser for help on using the repository browser.