/*
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
*/
#include "execio.h"
#include "options.h"
#include
#include
#include
#include
#include /* getopt */
#include
/* internally defined funcs */
void status_report_generator();
// Global Vars, try to cut down on these
int 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
/* ******************* 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;
// Stores Blender Job Info
struct blendjob {
struct blendjob *next; /* next will be NULL unless if there is another blendjob */
char *name;
char *submitter;
char *email;
unsigned int jobnum;
int priority; // 1 is lowest, 10 is highest, 0 means the job is done
int percent_done;
int completed_frames; // number of completed frames for stats/etc
int assigned_frames; // number of assigned frames (that are not yet completed) for stats/etc
int total_frames; // how many frames are in the animation for stats/etc (unassigned frames)
int avg_render_time; // average seconds it took to render a frame
unsigned int time_remaining; // estimated seconds remaining till render is complete (up to 49, 710 days)
// we can have the client computer convert it into days, hours, etc if they wish to view it
struct frameset *frameset;
};
// Frameset Structure
struct frameset {
int frame_num; // frame number to render
char slave_name; // user that frame is assigned to
int frame_status; // status of frame, 0= unassigned, 1= taken, 2= done
clock_t start_time; // time the frame was started
int time_to_render; // the total seconds it took to render the frame
} frameset[]; // Frameset array is generated by status_report_generator function
// Using this method to save memory, because if animation starts on a high frame number, it would waste a lot of RAM on empty structures
/* ********************** Functions ************************* */
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(int jobnum, int frame){
blendjob[jobnum].frameset[frame].frame_status = 2;
blendjob[jobnum].frameset[frame].time_to_render = (clock() - blendjob[jobnum].frameset[frame].start_time); // Consider changin 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(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[jobnum].name = name;
blendjob[jobnum].submitter = submitter;
blendjob[jobnum].email = email;
blendjob[jobnum].priority = priority;
blendjob[jobnum].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
// This uses pointers, so when it is run it updates certain values in memory
void status_report_generator(){
while(blendjob[(hcfjob+1)].priority == 0) //If the job after the highest consecutively finished job is finished
hcfjob++; // adds 1 to the highest consecutively finished job and checks the next one, till the job after the hcfjob is not done
int num1 = hcfjob+1; // to scan through jobs
unsigned short int workers_working; // used to count the total number of workers working
while(num1 <= highest_jobnum){
if(blendjob[num1].priority != 0){ // If the job is not done, scan it
int num2 = 0; // to scan through frames
float finished_frames = 0; // variable that counts the completed frames
int pending_frames = 0; // variable that counts the assigned frames
float percent = 0; // variable that stores the percent done of the blendjob
unsigned int total_time = 0; // total time taken to render all the completed frames for a job
while(num2 <= blendjob[num1].total_frames){ // scans through frames, based on their status it runs a statement(s)
if(blendjob[num1].frameset[num2].frame_status == 2){ // If the frame is done
finished_frames++;
total_time = total_time + blendjob[num1].frameset[num2].time_to_render;
}
if(blendjob[num1].frameset[num2].frame_status == 1){ // If the frame is assigned
pending_frames++;
workers_working++;
}
num2++;
}
// find the percent of completed frames
percent = (finished_frames / blendjob[num1].total_frames) * 100;
// updates values in the blendjob struct for jobnum held by the variable num1
blendjob[num1].completed_frames = finished_frames;
blendjob[num1].assigned_frames = pending_frames;
blendjob[num1].percent_done = percent;
blendjob[num1].avg_render_time = (total_time / finished_frames);
blendjob[num1].time_remaining = (blendjob[num1].avg_render_time * (blendjob[num1].total_frames - finished_frames));
if(finished_frames == blendjob[num1].total_frames){ // If all frames are complete
blendjob[num1].priority = 0; //set priority to zero to indicate job is complete
general_info.total_finished_jobs++; // add one to the total finished jobs
}
}
num1++;
general_info.rendering_clients = workers_working;
}
general_info.jobs_in_queue = (highest_jobnum - general_info.total_finished_jobs);
}
// **** 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 numframes) {
int jobnum_new = highest_jobnum + 1; /* global vars will someday leave us */
int counter;
job->frameset = malloc(sizeof(struct frameset) * numframes);
if(!job->frameset)
fprintf(stderr, "error allocating memory");
job->total_frames = total; // sets the total number of frames in animation for status purposes
job->jobnum = jobnum_new;
for(counter = 0; counter < total; counter ++)
/* This builds the array, with the array starting at zero and the frame_num starting at sframe */
job->frameset[counter].frame_num = counter + 1;
highest_jobnum++; // After it has created the job, it adds one to the highest_jobnum interger
}
// Frame Assigner: matches your computer up with a lovely frame to render
// Major issue here, the client needs to know the frame number, AND the job number!
int frame_finder(){
int your_frame = 0; // your_frame is an interger value that will be given to the client as the frame number to render
int finder_jobnum = 0;
int frameset_count = 0; // the frameset number, note* frames in an animation don't start at zero
short int done = 0;
short int priority = 10;
while(priority >= 1){ // start the scan for the next job with the highest priority, decreases priority before it loops
finder_jobnum = hcfjob + 1; // reset it to start scanning at first uncompleted job for the pass at each priority level
while(finder_jobnum <= highest_jobnum){ // This keeps increasing the finder_jobnum until it is higher than the highest_jobnum
if(blendjob[finder_jobnum].priority == priority){ // looks for a job with the current priority value
done = 1; // notice it starts by looking at the oldest job first
break;
}
if((done) == 1) // If it has found a job with the current priority value, it will break out of the loop
break; // If none is found it goes to the next job to see if it is of the current priority value
else
finder_jobnum++;
} // End of increasing finder_jobnum
if(done == 1) // if job has been found, it lets it out of the priority changer loop
break;
priority--; // end of decreasing priority
}
while(your_frame < blendjob[finder_jobnum].total_frames){ // Finds the frameset number with a frame that needs to be rendered
if (blendjob[finder_jobnum].frameset[frameset_count].frame_status == 0) // If frame that is not assigned has been found, frameset_count is not changed
break; // and frameset_count is used to give the frame number later in this funciton
frameset_count++; // If frame is assigned or done, it goes to next frame
}
blendjob[jobnum].frameset[frameset_count].frame_status++; // sets the value of the frame to 2, which means its taken
your_frame = blendjob[jobnum].frameset[frameset_count].frame_num; // Takes the frameset found in the while statement above, and extracts the frame number from it and assigns it to the int your_frame
if(your_frame == 0) // If that job had no open frames for some reason, run the status report generator so that
status_report_generator(); //the job priority can be changed to 0
blendjob[jobnum].frameset[frameset_count].start_time = clock();
return your_frame; // your_frame is returned as the frame to be rendered
}
void blend_frame_watchdog(){
short int watchdog_forgiveness = 3; // Hours to wait on a frame before re-assigning it
int num1 = hcfjob + 1;
while(num1 <= highest_jobnum){ // counts up through all the jobs
int num2 = 0;
while(num2 <= blendjob[num1].total_frames){ // Counts up through all the frames
if((blendjob[num1].frameset[num2].start_time + (watchdog_forgiveness * 3600)) < clock()) // If frame is not completed within the number of hours specified by watchdog_forgiveness
blendjob[num1].frameset[num2].frame_status = 0; // Then change the frame status to unassigned
num2++;
}
num1++;
}
}
/* ************************** Main ************************* */
int main(int argc, char *argv[])
{
//// Begin Getopt Sample, EDITME!Sometime
int aflag = 0;
int bflag = 0;
char *cvalue = NULL;
int index;
int c;
opterr = 0;
while ((c = getopt (argc, argv, "abc:")) != -1)
switch (c)
{
case 'a':
aflag = 1;
break;
case 'b':
bflag = 1;
break;
case 'c':
cvalue = optarg;
break;
case '?':
if (optopt == 'c')
fprintf (stderr, "Option -%c requires an argument.\n", optopt);
else if (isprint (optopt))
fprintf (stderr, "Unknown option `-%c'.\n", optopt);
else
fprintf (stderr,
"Unknown option character `\\x%x'.\n",
optopt);
return 1;
default:
abort ();
}
printf ("aflag = %d, bflag = %d, cvalue = %s\n",
aflag, bflag, cvalue);
for (index = optind; index < argc; index++)
printf ("Non-option argument %s\n", argv[index]);
//// End getopt sample
// 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 cont = 1;
struct distrend_listenset *listenset;
struct distrend_config *config;
distrend_do_config(argc, argv, config);
distrend_listen(&listenset, config);
/* This is called the ``main loop'' */
while(cont)
{
struct distren_action *action;
distrend_accept(&action);
cont = distrend_do(action);
distrend_action_free(action);
}
distrend_unlisten(listenset);
distrend_config_free(config);
return 0;
}
/*
* **********************************************************************************
* Slave functions / etc resides below. Wouldn't a seperate file make this easier??
*
* Slave listens on server for a command in the format of each function...
* We need if's for returns... ==> watchdog
* **********************************************************************************
*/
// Registration on server. Needs attention. Prevent account spamming.
// Key transfer?
// Set up something like: distrend -c username email@example.com
void registeruser(char *username, char *email){
// Logs into sandboxed user on zserver2 and registers a user. Should eventually generate a key on the server and return it to the user.
// All created user accounts should be sandboxed accordingly, requiring a different skel, and the default shell to be rbash. Also,
// a custom path defined in the .bash_profile of the skel is needed.
char buf[10];
struct execio *testrem;
char *execargv[] =
{
"ssh",
"distren_setup@protofusion.org",
"-i",
"setup.rsa", // default distributed key, account can only create users.
"-p",
"23",
"sudo /usr/sbin/useradd",
"-M",
"-c",
email,
"-d",
"/home/distren",
"--gid",
"541", // Add in shellscript to generate ssh key and return it to the user somehow
username,
(char *)NULL
};
size_t readlen;
fprintf(stderr, "Opening stream:\n", execio_open(&testrem, "ssh", execargv));
buf[9] = '\0'; // null-terminating the array...
while(!execio_read(testrem, buf, 9, &readlen)) // What's with the readlen stuff?
{
if(readlen > 9) {
fprintf(stderr, "!!!! Something is terribly wrong!\n");
}
buf[readlen] = '\0'; // Null-terminating the end of it again based on how large the data is?
fprintf(stderr, "read \"%s\"\n", buf);
}
execio_close(testrem);
}
void loginuser(char *username){
// Logs into sandboxed user on zserver2 as a client, currently does nothing
char buf[10];
struct execio *testrem;
char *execargv[] =
{
"ssh",
"username@protofusion.org", // username must be read from the conf
"-i",
"username.rsa", // Key created from registeruser()
"-p",
"23",
"echo",
"hello", // This should eventually open a non-terminating connection to the server for communication
(char *)NULL
};
size_t readlen;
fprintf(stderr, "Opening stream:\n", execio_open(&testrem, "ssh", execargv));
buf[9] = '\0'; // null-terminating the array...
while(!execio_read(testrem, buf, 9, &readlen)) // What's with the readlen stuff?
{
if(readlen > 9) {
fprintf(stderr, "!!!! Something is terribly wrong!\n");
}
buf[readlen] = '\0'; // Null-terminating the end of it again based on how large the data is?
fprintf(stderr, "read \"%s\"\n", buf);
}
execio_close(testrem);
}
// Executors
// It seems that the client will need to know the job number. Is finish_frame going to be on the client or the server? we gotta figure that out!
void exec_blender(char *input, char *output, int frame, int jobnum) {
char *frame_str;
asprintf(frame,frame_str); // GNU/*nix compatible only, fix before releasing win32, although dll for windows for asprintf exists!
int ret;
char *cmd[] = { "blender", "-b", "-o", output, input, "-f", frame_str, (char *)0 };
ret = execv("/usr/bin/blender", cmd);
finish_frame(jobnum, frame);
}