/* Copyright 2009 Nathan Phillip Brink, Ethan Zonca, Matthew Orlando This file is a part of DistRen. DistRen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. DistRen is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with DistRen. If not, see . */ /* This file contains the code which both processes (renders) jobs as a slave, and the code which distributes frames to slaves after receiving them from the client portion of the codebase. */ /* Just some notes -- Ethan Zonca * ++ Make data availible for other apps to parse * Server<==>Client Communication * Upload while rendering */ /* XML notes -- * * First off, is XML in our build environment? * Next, what files are we going to have?: * - XML file sent with (.blend/.pov/.lux(xml)/whatever) by distren telling the server what to do * + Includes submitter name, filename, emailaddress, etc * - XML file on server storing info on current and finished jobs * - XML file sent by distren when submitting a render to the server * * Overall, we could use XML to make our client/server communication load * a lot lighter, especially if clients are sent customized xml files telling * them what frame(s) to render... although that would be inefficient if rendering * single frames. Maybe not tell it what frames to render in the xml file, the server * can spit out that info. * * Please edit my ramblings if you please, * --ethanzonca * */ #include "execio.h" #include "options.h" #include "blendjob.h" #include "protocol.h" #include #include #include #include #include /* getopt */ #include /* ******************* Structs ************************ */ // Stores config info? editmycomment struct distrend_config { cfg_t *mycfg; struct options_common *options; struct distrend_listen **listens; /*< Null terminated array of structs */ }; /* frame[frame] Status Assignments: "NULL" - don't render me "0" - canceled "1" - unassigned "2" - assigned to slave "3" - completed by slave and uploaded Have a script crawl through each job in the arrays, priority-biased, and assign a frame to each slave. Then we will need some sort of watchdog to monitor slaves on the main server to check for stales. Maybe not worry about this for now. */ struct general_info { short int jobs_in_queue; // unsigned short int free_clients; unsigned short int rendering_clients;// unsigned short int total_finished_jobs; // unsigned int total_frames_rendered; // } general_info; /* internally defined funcs's prototypes */ void status_report_generator(struct blendjob **blendjobs_head); void blendjob_remove(struct blendjob **head, struct blendjob *bj); struct blendjob *blendjob_get(struct blendjob *head, jobnum_t jobnum); /* Global Vars, try to cut down on these */ jobnum_t jobnum = 0; // The next job number to create in the queue int hcfjob; // Highest consecutively finished job int highest_jobnum; // The job number of the most recently created job, this is used when creating new jobs /* ********************** Functions ************************* */ /* Functions to stubify */ /** Command interface for distren? */ int distrend_do(){ return 0; } /** Something? */ void distrend_accept(){ } /** Frees the action */ void distrend_action_free(){ } /** Start listening */ void distrend_listen(){ } /** Stop listening */ void distrend_unlisten(){ } /** This is probably just a placeholder for remotio */ void remotio_send_to_client(){ // I am futile! } /** Fill variables after crash / shutdown from XML dumps */ void start_data(){ if(1 == 0){ // retrieve total_finished_jobs and total_finished_frames from xml file } else{ general_info.total_finished_jobs = 0; general_info.total_frames_rendered = 0; } } // **** Finish-Setter: Sets a frame to the "completed" status. void finish_frame(struct blendjob *blendjob, int frame){ blendjob->frameset[frame].status = 2; blendjob->frameset[frame].time_to_render = (clock() - blendjob[jobnum].frameset[frame].start_time); // Consider changing time-to-render to time-for-frame or something? general_info.total_frames_rendered++; // Increase total frames var for stats } // **** Queuer: Adds files to the queue void queue(struct blendjob *blendjob, int type, char *name, char *submitter, char *email, int priority, int mode, int spp, struct frameset *frameset) { // Type: 1 = blender, add more types later // jobnum is the next available job number if(type == 1){ blendjob->name = name; blendjob->submitter = submitter; blendjob->email = email; blendjob->priority = priority; blendjob->frameset = frameset; } else{ // Throw error. } jobnum++; // Advance the jobnumber for the next queued job } /** Status Report Generator: -figures out how much of the job is done, where jobnum corresponds to the job number -removes finished jobs @param blendjobs_head a pointer to a pointer because the head of the blendjobs linked list may need to be changed by blendjob_remove */ void status_report_generator(struct blendjob **blendjobs_head) { struct blendjob *blendjob_ptr; unsigned short workers_working; /*< used to count the total number of workers working */ unsigned int numjobs; /*< used to track number of jobs */ blendjob_ptr = *blendjobs_head; workers_working = 0; numjobs = 0; while(blendjob_ptr) { if(blendjob_ptr->priority != 0) /* If the job is not done, scan it */ { unsigned int framecounter; /*< to scan through frames */ unsigned long finished_frames; /*< variable that counts the completed frames */ unsigned int pending_frames; /*< variable that counts the assigned frames */ float percent_frames_finished; /*< variable that stores the percent done of the blendjob */ unsigned int total_time; /*< total time taken to render all the completed frames for a job */ framecounter = 0; finished_frames = 0; pending_frames = 0; percent_frames_finished = 0; total_time = 0; while(framecounter < blendjob_ptr->total_frames) /* scans through frames, based on their status it runs a statement(s) */ { if(blendjob_ptr->frameset[framecounter].status == 2) /* If the frame is done */ { finished_frames ++; total_time += blendjob_ptr->frameset[framecounter].time_to_render; } if(blendjob_ptr->frameset[framecounter].status == 1) /* If the frame is assigned */ { pending_frames ++; workers_working ++; } framecounter ++; } /* while(framecounter < blendjob_ptr->total_frames) */ // find the percent of completed frames percent_frames_finished = (finished_frames / blendjob_ptr->total_frames) * 100; /*< @LordofWar: extraneous parentheses! */ /* updates values in the blendjob struct */ blendjob_ptr->completed_frames = finished_frames; blendjob_ptr->assigned_frames = pending_frames; blendjob_ptr->percent_done = percent_frames_finished; blendjob_ptr->avg_render_time = (total_time / finished_frames); /*< extraneous parentheses! */ blendjob_ptr->time_remaining = (blendjob_ptr->avg_render_time * (blendjob_ptr->total_frames - finished_frames)); /*< extraneous parentheses! */ if(finished_frames == blendjob_ptr->total_frames) /* If all frames are complete */ { blendjob_ptr->priority = 0; /*< set priority to zero to indicate job is complete */ blendjob_remove(blendjobs_head, blendjob_ptr); /*< remove this job from the linkedlist */ general_info.total_finished_jobs++; /*< add one to the total finished jobs */ } else if (finished_frames > blendjob_ptr->total_frames) /* just in case ;-) */ { fprintf(stderr, "%s:%d: finished_frames (%lu) > blendjob_ptr->total_frames (%d)", __FILE__, __LINE__, finished_frames, blendjob_ptr->total_frames); abort(); } } general_info.rendering_clients = workers_working; /*< should this be a +=? */ blendjob_ptr = blendjob_ptr->next; /*< This is the essence of linked lists and iterating through them */ numjobs ++; } /* while(blendjob_ptr) */ general_info.jobs_in_queue = (highest_jobnum - general_info.total_finished_jobs); /*< extraneous parentheses! */ } // **** Structure Builder: This function creates frame array based on the total number of frames to be rendered, which will then be parsed by function frame_farmer. void frame_num_struct_builder(struct blendjob *job, unsigned int startframe, unsigned int numframes) { int jobnum_new = highest_jobnum + 1; /* global vars will someday leave us */ unsigned int counter; job->frameset = malloc(sizeof(struct frameset) * numframes); if(!job->frameset) fprintf(stderr, "error allocating memory"); job->total_frames = numframes; // sets the total number of frames in animation for status purposes job->jobnum = jobnum_new; for(counter = 0; counter < numframes; counter ++) /* This builds the array, with the array starting at zero and the frameset.num starting at sframe */ job->frameset[counter].num = counter + startframe; highest_jobnum++; // After it has created the job, it adds one to the highest_jobnum interger } /** Frame Finder: matches your computer up with a lovely frame to render TODO: Major issue here, the client needs to know the frame number, AND the job number! Notice that this function starts by looking at the oldest job first TODO: Link this up with the main() function to check if there are frames available or not and provide jobnum/framenum to the client @return 0 success, other: error */ int frame_finder(struct blendjob *head, struct blendjob **job, struct frameset **frame) { int your_frame; // your_frame is an integer value that will be given to the client as the frame number to render // UNUSED: int your_job; // @TODO: Fixme: do we need this var? you_job is an integer value that must ALSO be given to the client unsigned short int found; unsigned short int priority; struct blendjob *blendjob_ptr; found = 0; while(!found) { /* enumerate through priority levels */ for(priority = 10; priority > 0 && !found; priority --) /* Find the job with the highest priority */ for(blendjob_ptr = head; blendjob_ptr != NULL && !found; blendjob_ptr = blendjob_ptr->next) if(blendjob_ptr->priority == priority) found = 1; if(!found) { fprintf(stderr, "out of jobs to render\n"); return 1; } found = 0; for(your_frame = 0; your_frame < blendjob_ptr->total_frames; your_frame ++) if(blendjob_ptr->frameset[your_frame].status == 0) found = 1; if(!found) { /* there are no frames left in this job */ blendjob_ptr->priority --; /* If that job had no open frames for some reason, run the status report generator so that */ status_report_generator(&head); /* should the job be removed now? */ fprintf(stderr, "Job %d is finished, this is probably the place to call the job-removal function\n", blendjob_ptr->jobnum); } } /* while(!found) */ fprintf(stderr, "Missing apostrophe !!!!!!!!!!!!!!\n"); abort(); /* sets the value of the frame to 1, which means its taken !!!!!! MISSSING APOSTROPHE!!!!!!! */ blendjob_ptr->frameset[your_frame].status++; blendjob_ptr->frameset[your_frame].start_time = clock(); *job = blendjob_ptr; *frame = &blendjob_ptr->frameset[your_frame]; return 0; } void blend_frame_watchdog(struct blendjob *blendjob_head) { unsigned short int watchdog_forgiveness; /*< seconds to wait on a frame before re-assigning it */ struct blendjob *blendjob_ptr; unsigned int counter; watchdog_forgiveness = 3; /*< hours of forgiveness before frame is re-assigned */ blendjob_ptr = blendjob_head; for(blendjob_ptr = blendjob_head; blendjob_ptr; blendjob_ptr = blendjob_ptr->next) /* iterate through jobs */ for(counter = 0; counter < blendjob_ptr->total_frames; counter ++) /* iterate through all frames for this job*/ { if((blendjob_ptr->frameset[counter].start_time + (watchdog_forgiveness * 3600)) < clock()) /* If frame is not completed within the number of hours specified by watchdog_forgiveness Then change the frame status to unassigned */ blendjob_ptr->frameset[counter].status = 0; } } /** Finds a blendjob struct based on the jobnum @arg jobnum job number to search for @return NULL on job doesn't exist */ struct blendjob *blendjob_get(struct blendjob *head, jobnum_t jobnum) { struct blendjob *blendjob_ptr; /* The conditions of the for loop will leave blendjob_ptr at NULL if the end of the list is reached. It will leave it pointing to the correct job if it is found. */ for(blendjob_ptr = head; blendjob_ptr && blendjob_ptr->jobnum != jobnum; blendjob_ptr = blendjob_ptr->next); return blendjob_ptr; } /** Removes a blendjob from the blendjob linkelist. @arg head a double pointer. the head pointer will have to be changed if blendjob == *head. Thus, make sure that the pointer points to the pointer to the head that all functions use. (I'm going to come back to this and misunderstand myself ;-)) */ void blendjob_remove(struct blendjob **head, struct blendjob *bj) { struct blendjob *previous_blendjob; if(bj == *head) *head = bj->next; else { for(previous_blendjob = *head; previous_blendjob && previous_blendjob->next != bj; /*< stop on the blendjob that comes before bj */ previous_blendjob = previous_blendjob->next) /* all of the action is in the definition of the for loop itself */; /* This removes references to bj from the linked list. I.E., we now skip bj when iterating through the list */ previous_blendjob->next = bj->next; } /* @lordofwar: the magic deallocation of memory ;-) */ free(bj); } /* ************************** Main ************************* */ // Begin non-working framework? int distrend_do_config(int argc, char *argv[], struct distrend_config *config) { cfg_opt_t myopts_listen[] = { CFG_SIMPLE_STR("type", NULL), CFG_SIMPLE_STR("path", NULL), CFG_SIMPLE_INT("port", NULL), CFG_END() }; cfg_opt_t myopts[] = { CFG_SEC("listen", /* this must be imported into struct listens (which must still be declared) */ myopts_listen , CFGF_MULTI), CFG_END() }; config = malloc(sizeof(struct distrend_config)); options_init(argc, argv, &config->mycfg, myopts, "server", &config->options); return 0; } int distrend_config_free(struct distrend_config *config) { options_free(config->options); free(config); return 0; } // End non-working framework? int main(int argc, char *argv[]) { /* TODO: Put some arg-grabbing code here */ struct blendjob *head; int cont; struct distrend_listenset *listenset; struct distrend_config *config; enum clientstatus { CLIENTSTATUS_UNINITIALIZED = 0, CLIENTSTATUS_BUSY = 1, CLIENTSTATUS_IDLE = 2 } clientstatus; head = NULL; cont = 1; distrend_do_config(argc, argv, config); distrend_listen(&listenset, config); /* This is called the ``main loop'' */ while(cont) { struct distren_action *action; int clientsays; /*< temporary example variable, will be replaced when we can handle messages */ distrend_accept(&action); cont = distrend_do(action); /* Somewhat Pseudo-code for basic server operation, should be more event-driven */ start_data(); status_report_generator(&head); blend_frame_watchdog(head); struct frameset *frame; struct blendjob *job; /* If the client is idle (meaning a client without the "busy" status connected via ssh), all clients should be idle by default. */ if(clientstatus == CLIENTSTATUS_IDLE) { /** normaldotcom: learn about ``return by pointer'' */ int returnnum = frame_finder(head, &job, &frame); // give framefinder args, framefinder should return job number and frame number somehow if(returnnum) { fprintf(stderr,"No frames are available to render at this time"); sleep(10); } else /* returnnum == 0 */ remotio_send_to_client(frame->num, job->jobnum); // Did you actually make this function, ohnobinki? --normaldotcom } /* If the client states that they finished the frame */ if(clientsays == DISTREN_REQUEST_DONEFRAME){ clientstatus = CLIENTSTATUS_IDLE; // Sets the client back to idle // finish_frame(jobnum, framenumprevious); // make it finish the previous frame or something, why framenumprevios? Is that the whole linked list thing coming in? finish_frame(head, jobnum); // @TODO: update so it fits the purpose of the previous line } /* End Somewhat Pseudo-code */ distrend_action_free(action); } distrend_unlisten(listenset); distrend_config_free(config); return 0; }