source: trunk/packages/vizservers/vtkvis/RenderServer.cpp @ 4107

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

Add command-line options to nanovis/geovis/vtkvis servers to allow setting
file descriptors to use for client IO (i.e. which fds are used by nanoscale
to dup the socket). Must be 0-4 and check that trace log fd isn't used for
client IO.

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