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

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

Add prelimilary skeleton for geovis map rendering server. Not functional, not
integrated into configure, etc.

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