/*
Copyright (C) 2000 by Sean David Fleming
sean@ivec.org
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
The GNU GPL can also be found at http://www.gnu.org
*/
/* irix */
#define _BSD_SIGNALS 1
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "gdis.h"
#include "task.h"
#include "file.h"
#include "parse.h"
#include "shortcuts.h"
#include "interface.h"
#include "dialog.h"
/* top level data structure */
extern struct sysenv_pak sysenv;
#ifndef __WIN32
#include <sys/wait.h>
#endif
/* task manager globals */
gdouble max_threads=1;
gint task_show_running = 1;
gint task_show_queued = 0;
gint task_show_completed = 1;
GtkWidget *task_list=NULL;
gint selected_task = -1;
gint task_info_pid = 0;
gint task_thread_pid = -1;
GArray *psarray = NULL;
GtkListStore *task_list_ls=NULL;
GtkWidget *task_list_tv=NULL, *task_text_view;
char *strptime(const char *, const char *, struct tm *);
/**************************/
/* a simple sleep routine */
/**************************/
void delay(gint usecs)
{
#ifndef __WIN32
struct timespec req;
req.tv_sec = usecs / 1000000;
req.tv_nsec = 1000*(usecs - 1000000*req.tv_sec);
/* TODO - if terminated early - call again with remainder */
nanosleep(&req, NULL);
#endif
}
#define EXIST(idx) ((idx) != -1)
gint find_pid(struct task_pak *curr_process, gint *pid)
{
if (curr_process->ppid == GPOINTER_TO_INT(pid))
return (0);
else
return(-1);
}
gint find_pid_index(gint pid)
{
gint i;
for (i = psarray->len - 1; i >= 0 && g_array_index(psarray, struct task_pak, i).pid != pid; i--);
return(i);
}
#define DEBUG_ADD_FROM_TREE 0
void add_from_tree(struct task_pak *tdata, gint idx)
{
struct task_pak *process_ptr;
gint child;
div_t h_sec, sec, min;
gchar *h_sec_pos, *sec_pos, *min_pos, *hour_pos;
/* add in current process */
process_ptr = &g_array_index(psarray, struct task_pak, idx);
tdata->pcpu += process_ptr->pcpu;
tdata->pmem += process_ptr->pmem;
/* parse the cpu time */
#if defined(__APPLE__) && defined(__MACH__)
h_sec_pos = g_strrstr(process_ptr->time, ".") + 1;
sec_pos = h_sec_pos - 3;
min_pos = process_ptr->time;
hour_pos = NULL;
#else
hour_pos = process_ptr->time;
min_pos = hour_pos + 3;
sec_pos = min_pos + 3;
h_sec_pos = NULL;
#endif
tdata->h_sec += (gint) str_to_float(h_sec_pos);
h_sec = div(tdata->h_sec, 100);
tdata->h_sec = h_sec.rem;
tdata->sec =tdata->sec + h_sec.quot + (gint) str_to_float(sec_pos);
sec = div(tdata->sec, 60);
tdata->sec = sec.rem;
tdata->min = tdata->min + sec.quot + (gint) str_to_float(min_pos);
min = div(tdata->min, 60);
tdata->min = min.rem;
tdata->hour = tdata->hour + min.quot + (gint) str_to_float(hour_pos);
#if DEBUG_ADD_FROM_TREE
printf("%s hour=%d min=%d, sec=%d, hsec=%d\n", process_ptr->time, tdata->hour, tdata->min, tdata->sec, tdata->h_sec);
#endif
/* process children */
for (child = process_ptr->child; EXIST(child); child = g_array_index(psarray, struct task_pak, child).sister)
add_from_tree(tdata, child);
}
/*****************************************************/
/* get the results of a ps on the requested process */
/*****************************************************/
#define DEBUG_CALC_TASK_INFO 0
void calc_task_info(struct task_pak *tdata)
{
gint num_tokens;
gint me, parent, sister;
gint found;
gchar line[LINELEN], *line_no_time, **buff, *cmd;
struct task_pak curr_process, *process_ptr;
struct tm start_tm;
time_t oldest_time;
FILE *fp;
/* dispose of task list if it exists */
if (psarray != NULL)
g_array_free(psarray, TRUE);
/* initialise process record */
curr_process.parent = curr_process.child = curr_process.sister = -1;
/* setup ps command */
/*
* SG's - 'ps -Ao "pid ppid pcpu vsz etime comm"
* (unfortunately, no %mem - and vsz is absolute :-(
*/
#ifdef __sgi
cmd = g_strdup_printf("ps -Ao \"pid ppid pcpu vsz etime comm\"");
#else
cmd = g_strdup_printf("ps axo \"lstart pid ppid %%cpu %%mem time ucomm\"");
#endif
/* run ps command */
fp = popen(cmd, "r");
g_free(cmd);
if (!fp)
{
gui_text_show(ERROR, "unable to launch ps command\n");
return;
}
/* skip title line */
if (fgetline(fp, line))
{
gui_text_show(ERROR, "unable to read first line of output from ps command\n");
return;
}
/* load data into array */
psarray = g_array_new(FALSE, FALSE, sizeof(struct task_pak));
while (!fgetline(fp, line))
{
#if DEBUG_CALC_TASK_INFO
printf("%s", line);
#endif
/* extract what we want */
#ifdef __WIN32
/* TODO - win32 replacement? */
line_no_time = NULL;
#else
/* first get start time */
line_no_time = strptime(line, "%c", &start_tm);
#endif
if (line_no_time == NULL)
{
gui_text_show(ERROR, "file produced by ps not understood\n");
return;
}
curr_process.start_time = mktime(&start_tm);
buff = tokenize(line_no_time, &num_tokens);
if (num_tokens >= 5)
{
curr_process.pid = (int) str_to_float(*(buff+0));
curr_process.ppid = (int) str_to_float(*(buff+1));
curr_process.pcpu = str_to_float(*(buff+2));
curr_process.pmem = str_to_float(*(buff+3));
curr_process.time = g_strdup(*(buff+4));
g_array_append_val(psarray, curr_process);
}
g_strfreev(buff);
}
pclose(fp);
/* Build the process hierarchy. Every process marks itself as first child */
/* of it's parent or as sister of first child of its parent */
/* algorithm taken from pstree.c by Fred Hucht */
for (me = 0; me < psarray->len; me++)
{
parent = find_pid_index(g_array_index(psarray, struct task_pak, me).ppid);
if (parent != me && parent != -1)
{ /* valid process, not me */
g_array_index(psarray, struct task_pak, me).parent = parent;
if (g_array_index(psarray, struct task_pak, parent).child == -1) /* first child */
g_array_index(psarray, struct task_pak, parent).child = me;
else
{
for (sister = g_array_index(psarray, struct task_pak, parent).child; EXIST(g_array_index(psarray, struct task_pak, sister).sister); sister = g_array_index(psarray, struct task_pak, sister).sister);
g_array_index(psarray, struct task_pak, sister).sister = me;
}
}
}
#if DEBUG_CALC_TASK_INFO
printf("process list\n");
for (me = 0; me < psarray->len; me++)
{
process_ptr = &g_array_index(psarray, struct task_pak, me);
printf("pid: %d ppid: %d pcpu: %.1f pmem: %.1f date: %s parent: %d child: %d sister: %d time: %s", process_ptr->pid, process_ptr->ppid, process_ptr->pcpu, process_ptr->pmem, process_ptr->time, process_ptr->parent, process_ptr->child, process_ptr->sister, ctime(&process_ptr->start_time));
}
#endif
/* set tdata's pid to 0 so if this routine fails, it won't kill gdis! */
tdata->pcpu = tdata->pmem = 0;
tdata->h_sec = tdata->sec = tdata->min = tdata->hour = 0;
/* scan through the running processes and find the task thread */
oldest_time = time(NULL);
found = FALSE;
for (me = 0; me < psarray->len; me++)
{
process_ptr = &g_array_index(psarray, struct task_pak, me);
if (process_ptr->pid == tdata->pid)
{
found = TRUE;
#if DEBUG_CALC_TASK_INFO
printf("pid: %d time:%s\n", child_pid, ctime(&oldest_time));
#endif
break;
}
}
if (found)
{
/* find child */
/* process_ptr = &g_array_index(psarray, struct task_pak, child_idx);
child_idx = process_ptr->child;
process_ptr = &g_array_index(psarray, struct task_pak, child_idx);
child_pid = tdata->pid = process_ptr->pid;
#if DEBUG_CALC_TASK_INFO
printf("pid: %d time:%s\n", child_pid, ctime(&oldest_time));
#endif*/
add_from_tree(tdata, me);
}
/* FIXME - core dumps here in analysis (esp. if >1 jobs queued) */
if (tdata->time)
g_free(tdata->time);
/*
sprintf(line, "%02d:%02d:%02d", tdata->hour, tdata->min, tdata->sec);
tdata->time = g_strdup(line);
*/
tdata->time = g_strdup_printf("%02d:%02d:%02d", tdata->hour, tdata->min, tdata->sec);
/* search through any children of the the oldest child */
/*
pid = child_pid;
while ((list = g_slist_find_custom(palist, GINT_TO_POINTER(pid), (gpointer) find_pid)) != NULL)
{
curr_process = (struct task_pak *) list->data;
pid = tdata->pid = curr_process->pid;
tdata->pcpu += curr_process->pcpu;
tdata->pmem += curr_process->pmem;
if (tdata->time)
g_free(tdata->time);
tdata->time = g_strdup(curr_process->time);
}
#if DEBUG_CALC_TASK_INFO
printf("FINAL TOTALS\n");
printf("pid: %d pcpu: %.1f pmem: %.1f time: %s\n", tdata->pid, tdata->pcpu, tdata->pmem, tdata->time);
#endif
free_slist(palist);
*/
/* prevent rounding errors giving >100% */
if (tdata->pcpu > 100.0)
tdata->pcpu = 100.0;
if (tdata->pmem > 100.0)
tdata->pmem = 100.0;
}
/**********************************************/
/* get pointer to the currently selected task */
/**********************************************/
gpointer task_selected(void)
{
gpointer task;
GtkTreeModel *treemodel;
GtkTreeSelection *selection;
GtkTreeIter iter;
treemodel = gtk_tree_view_get_model(GTK_TREE_VIEW(task_list_tv));
selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(task_list_tv));
if (gtk_tree_model_get_iter_first(treemodel, &iter))
{
do
{
if (gtk_tree_selection_iter_is_selected(selection, &iter))
{
gtk_tree_model_get(treemodel, &iter, 6, &task, -1);
return(task);
}
}
while (gtk_tree_model_iter_next(treemodel, &iter));
}
return(NULL);
}
/**********************************/
/* get the currently selected row */
/**********************************/
gint task_selected_row(void)
{
gint row=0;
GtkTreeModel *treemodel;
GtkTreeSelection *selection;
GtkTreeIter iter;
treemodel = gtk_tree_view_get_model(GTK_TREE_VIEW(task_list_tv));
selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(task_list_tv));
if (gtk_tree_model_get_iter_first(treemodel, &iter))
{
do
{
if (gtk_tree_selection_iter_is_selected(selection, &iter))
return(row);
row++;
}
while (gtk_tree_model_iter_next(treemodel, &iter));
}
return(-1);
}
/*********************************/
/* rewrite the current task list */
/*********************************/
gint update_task_info(void)
{
gint i, type, update, row, num_rows=0;
gchar *txt;
GSList *tlist=NULL;
GtkTreeIter iter;
GtkTreeModel *treemodel;
GtkTreeSelection *selection;
GtkTextMark *mark;
GtkTextIter text_iter;
GtkTextBuffer *buffer;
struct task_pak *task=NULL;
static gpointer previous_task=NULL;
/* checks */
if (!dialog_exists(TASKMAN, NULL))
return(TRUE);
if (!task_list_ls)
return(TRUE);
/* NEW - update using control file */
task = task_selected();
buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(task_text_view));
if (task)
{
if (previous_task != task)
{
/* complete refresh */
task->status_index = 0;
gtk_text_buffer_set_text(buffer, "", 0);
update = TRUE;
}
else
update = FALSE;
switch (task->status)
{
case RUNNING:
/* running - always update */
update = TRUE;
break;
case KILLED:
case COMPLETED:
if (task->status_fp)
{
/* completed - only update if status file is still open */
task->status_index = 0;
update = TRUE;
}
break;
}
if (update)
{
/* refresh the status message and print */
task_status_update(task);
txt = (task->status_text)->str;
if (task->status_index >= 0)
{
i = strlen(txt+task->status_index);
if (i)
{
/* display only most recent lines */
/*
gtk_text_buffer_set_text(buffer, txt+task->status_index, i);
*/
/* append to the whole thing */
gtk_text_buffer_insert_at_cursor(buffer, txt+task->status_index, i);
task->status_index += i;
/* force scroll to end of buffer by default */
mark = gtk_text_buffer_get_insert(buffer);
gtk_text_buffer_get_end_iter(buffer, &text_iter);
gtk_text_buffer_move_mark(buffer, mark, &text_iter);
gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(task_text_view),
mark, 0.0, FALSE, 0, 0);
}
}
}
previous_task = task;
}
else
{
gtk_text_buffer_set_text(buffer, "", 0);
previous_task = NULL;
}
/* remove deleted tasks */
/* CURRENT - NEVER allow this? so user can also look at status file of completed jobs */
tlist = sysenv.task_list;
while (tlist)
{
task = tlist->data;
tlist = g_slist_next(tlist);
if (task->status == REMOVED)
{
sysenv.task_list = g_slist_remove(sysenv.task_list, task);
task_free(task);
}
}
/* clear list and re-populate according to status */
row = task_selected_row();
gtk_list_store_clear(task_list_ls);
for (type=RUNNING ; type<=COMPLETED ; type++)
{
/* fill out the list with current status type */
for (tlist=sysenv.task_list ; tlist ; tlist=g_slist_next(tlist))
{
task = tlist->data;
if (task->status != type)
continue;
gtk_list_store_append(task_list_ls, &iter);
if (task->label)
gtk_list_store_set(task_list_ls, &iter, 1, task->label, -1);
else
gtk_list_store_set(task_list_ls, &iter, 1, "Unknown", -1);
switch(task->status)
{
case QUEUED:
gtk_list_store_set(task_list_ls, &iter, 2, "Queued", -1);
break;
case RUNNING:
calc_task_info(task);
txt = g_strdup_printf("%d", task->pid);
gtk_list_store_set(task_list_ls, &iter, 0, txt, -1);
g_free(txt);
if (task->progress > 0.0)
txt = g_strdup_printf("%5.1f%%", task->progress);
else
txt = g_strdup("Running");
gtk_list_store_set(task_list_ls, &iter, 2, txt, -1);
g_free(txt);
txt = g_strdup_printf("%5.1f", task->pcpu);
gtk_list_store_set(task_list_ls, &iter, 3, txt, -1);
g_free(txt);
txt = g_strdup_printf("%5.1f", task->pmem);
gtk_list_store_set(task_list_ls, &iter, 4, txt, -1);
g_free(txt);
txt = g_strdup_printf(" %s", task->time);
gtk_list_store_set(task_list_ls, &iter, 5, txt, -1);
g_free(txt);
break;
case KILLED:
gtk_list_store_set(task_list_ls, &iter, 2, "Killed", -1);
break;
case COMPLETED:
gtk_list_store_set(task_list_ls, &iter, 2, "Completed", -1);
break;
}
/* NEW - add the task pointer itself */
gtk_list_store_set(task_list_ls, &iter, 6, task, -1);
num_rows++;
}
}
/* restore selected row, or the previous (if possible) if deleted */
if (row >= num_rows && row)
row--;
if (row >= 0)
{
treemodel = gtk_tree_view_get_model(GTK_TREE_VIEW(task_list_tv));
if (gtk_tree_model_iter_nth_child(treemodel, &iter, NULL, row))
{
selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(task_list_tv));
if (selection)
gtk_tree_selection_select_iter(selection, &iter);
}
}
/* NEW */
sysenv.max_threads = max_threads;
g_thread_pool_set_max_threads(sysenv.thread_pool, sysenv.max_threads, NULL);
g_thread_pool_stop_unused_threads();
return(TRUE);
}
/***********************************/
/* recursively kill a process tree */
/***********************************/
void task_kill_tree(gint child)
{
gchar *cmd;
struct task_pak *process;
while (EXIST(child))
{
process = &g_array_index(psarray, struct task_pak, child);
/* recurse down the tree */
task_kill_tree(process->child);
/* terminate process */
cmd = g_strdup_printf("kill TERM %d >& /dev/null", process->pid);
system(cmd);
g_free(cmd);
/* get next process at this level */
child = process->sister;
}
}
/***********************/
/* stop a running task */
/***********************/
void task_kill_running(struct task_pak *task)
{
gint i;
struct task_pak *process;
/* get task and bottom level process pid */
if (task && psarray)
{
for (i=psarray->len ; i-- ; )
{
process = &g_array_index(psarray, struct task_pak, i);
if (process->pid == task->pid)
{
task_kill_tree(process->child);
break;
}
}
task->status = KILLED;
}
}
/***************************************/
/* remove and free all completed tasks */
/***************************************/
void task_remove_completed(GtkWidget *w, gpointer data)
{
GSList *list;
struct task_pak *task;
list = sysenv.task_list;
while (list)
{
task = list->data;
list = g_slist_next(list);
if (task->status == COMPLETED || task->status == KILLED)
{
sysenv.task_list = g_slist_remove(sysenv.task_list, task);
task_free(task);
}
}
}
/**********************************************/
/* terminate selected running or queued tasks */
/**********************************************/
void task_kill_selected(void)
{
struct task_pak *task;
task = task_selected();
if (task)
{
switch (task->status)
{
case RUNNING:
task_kill_running(task);
break;
case QUEUED:
task->status = KILLED;
break;
}
}
}
/*****************************************/
/* terminate all running or queued tasks */
/*****************************************/
void task_kill_all(void)
{
GSList *list;
struct task_pak *task;
/* remove all queued first - to ensure they aren't executed */
for (list=sysenv.task_list ; list ; list=g_slist_next(list))
{
task = list->data;
if (task->status == QUEUED)
task->status = KILLED;
}
for (list=sysenv.task_list ; list ; list=g_slist_next(list))
{
task = list->data;
if (task->status == RUNNING)
task_kill_running(task);
}
}
/***********************/
/* task dialog cleanup */
/***********************/
void task_cleanup(void)
{
gtk_list_store_clear(task_list_ls);
task_list_ls = NULL;
task_list_tv = NULL;
}
/****************************************************/
/* a task dialog for viewing/killing existing tasks */
/****************************************************/
void task_dialog(void)
{
gint i;
gchar *titles[6] = {" PID ", " Job ", " Status ", " \% CPU ", " \% Mem ", " Time "};
gpointer dialog;
GtkWidget *window, *swin, *frame, *vbox, *hbox;
GtkCellRenderer *renderer;
GtkTreeViewColumn *column;
/* request a new dialog */
dialog = dialog_request(TASKMAN, "Task Manager", NULL, task_cleanup, NULL);
if (!dialog)
return;
window = dialog_window(dialog);
gtk_window_set_default_size(GTK_WINDOW(window), 600, 600);
/* task thread setup */
hbox = gtk_hbox_new(FALSE, 0);
gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->vbox), hbox, FALSE, FALSE, 0);
gtk_container_set_border_width(GTK_CONTAINER(hbox), PANEL_SPACING);
gui_direct_spin("Maximum number of running tasks", &max_threads, 1, 32, 1, NULL, NULL, hbox);
#if _WIN32
gtk_widget_set_sensitive(GTK_WIDGET(hbox), FALSE);
#endif
/* job list frame */
frame = gtk_frame_new(NULL);
gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->vbox), frame, TRUE, TRUE, 0);
gtk_container_set_border_width(GTK_CONTAINER(frame), PANEL_SPACING);
/* create a vbox in the frame */
vbox = gtk_vbox_new(FALSE, PANEL_SPACING/2);
gtk_container_add(GTK_CONTAINER(frame), vbox);
gtk_container_set_border_width(GTK_CONTAINER(GTK_BOX(vbox)), PANEL_SPACING);
/* scrolled model pane */
swin = gtk_scrolled_window_new(NULL, NULL);
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (swin),
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
gtk_box_pack_start(GTK_BOX(vbox), swin, TRUE, TRUE, 0);
task_list_ls = gtk_list_store_new(7, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
G_TYPE_POINTER);
task_list_tv = gtk_tree_view_new_with_model(GTK_TREE_MODEL(task_list_ls));
gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(swin), task_list_tv);
for (i=0 ; i<6 ; i++)
{
renderer = gtk_cell_renderer_text_new();
column = gtk_tree_view_column_new_with_attributes(titles[i], renderer, "text", i, NULL);
gtk_tree_view_append_column(GTK_TREE_VIEW(task_list_tv), column);
}
/* setup the selection handler */
/*
{
GtkTreeSelection *select;
select = gtk_tree_view_get_selection(GTK_TREE_VIEW(task_list_tv));
g_signal_connect(G_OBJECT(select), "changed",
G_CALLBACK(task_selection_changed),
NULL);
}
*/
/* selected task status file frame */
gtk_box_pack_start(GTK_BOX(vbox), gtk_hseparator_new(), FALSE, FALSE, 0);
swin = gtk_scrolled_window_new(NULL, NULL);
gtk_box_pack_start(GTK_BOX(vbox), swin, TRUE, TRUE, 0);
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin),
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
task_text_view = gtk_text_view_new();
gtk_text_view_set_editable(GTK_TEXT_VIEW(task_text_view), FALSE);
gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(task_text_view), FALSE);
gtk_container_add(GTK_CONTAINER(swin), task_text_view);
/* control buttons */
gui_icon_button(GTK_STOCK_REMOVE, "Kill selected",
task_kill_selected, NULL,
GTK_DIALOG(window)->action_area);
gui_icon_button(GTK_STOCK_DELETE, "Kill all",
task_kill_all, NULL,
GTK_DIALOG(window)->action_area);
gui_icon_button(GTK_STOCK_CLEAR, "Remove completed",
task_remove_completed, NULL,
GTK_DIALOG(window)->action_area);
gui_stock_button(GTK_STOCK_CLOSE,
dialog_destroy, dialog,
GTK_DIALOG(window)->action_area);
/* done */
gtk_widget_show_all(window);
/* refresh labels */
update_task_info();
}
syntax highlighted by Code2HTML, v. 0.9.1