// systemtap grapher // Copyright (C) 2009 Red Hat Inc. // // This file is part of systemtap, and is free software. You can // redistribute it and/or modify it under the terms of the GNU General // Public License (GPL); either version 2, or (at your option) any // later version. #include "GraphWidget.hxx" #include "StapParser.hxx" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; using namespace tr1; using namespace systemtap; // magic for noticing that the child stap process has died. int signalPipe[2] = {-1, -1}; struct ChildInfo { pid_t pid; int waitInfo; }; extern "C" { void handleChild(int signum, siginfo_t* info, void* context) { struct ChildInfo childInfo; ssize_t err; // Loop doing waitpid because the SIGCHLD signal isn't queued; // multiple signals might get lost. If we get an error because // there are no zombie children (return value <= 0 because of // WNOHANG or no children exist at all), assume that an earlier // invocation of handleChild already cleaned them up. while ((childInfo.pid = waitpid(-1, &childInfo.waitInfo, WNOHANG))) { if (childInfo.pid < 0 && errno != ECHILD) { char errbuf[256]; strerror_r(errno, errbuf, sizeof(errbuf)); err = write(STDERR_FILENO, errbuf, strlen(errbuf)); err = write(STDERR_FILENO, "\n", 1); return; } else if (childInfo.pid > 0) { err = write(signalPipe[1], &childInfo, sizeof(childInfo)); } else return; } } } // Waits for a gtk I/O signal, indicating that a child has died, then // performs an action class ChildDeathReader { public: ChildDeathReader() : sigfd(-1) {} ChildDeathReader(int sigfd_) : sigfd(sigfd_) {} int getSigfd() { return sigfd; } void setSigfd(int sigfd_) { sigfd = sigfd_; } bool ioCallback(Glib::IOCondition ioCondition) { if ((ioCondition & Glib::IO_IN) == 0) return true; ChildInfo info; if (read(sigfd, &info, sizeof(info)) < static_cast(sizeof(info))) cerr << "couldn't read ChildInfo from signal handler\n"; else childDiedSignal().emit(info.pid); return true; } private: int sigfd; }; struct PidPred { PidPred(pid_t pid_) : pid(pid_) {} bool operator()(const shared_ptr& parser) const { return parser->getPid() == pid; } pid_t pid; }; // Depending on how args are passed, either launch stap directly or // use the shell to parse arguments class StapLauncher : public ChildDeathReader { public: StapLauncher() : _argv(0), _childPid(-1) { childDiedSignal().connect(sigc::mem_fun(*this, &StapLauncher::onChildDied)); } StapLauncher(char** argv) : _argv(argv), _childPid(-1) { childDiedSignal().connect(sigc::mem_fun(*this, &StapLauncher::onChildDied)); } StapLauncher(const string& stapArgs, const string& script, const string& scriptArgs) : _childPid(-1) { setArgs(stapArgs, script, scriptArgs); } void setArgv(char** argv) { _argv = argv; } char** getArgv() { return _argv; } void setArgs(const string& stapArgs, const string& script, const string& scriptArgs) { _argv = 0; _stapArgs = stapArgs; _script = script; _scriptArgs = scriptArgs; } void getArgs(string& stapArgs, string& script, string& scriptArgs) { stapArgs = _stapArgs; script = _script; scriptArgs = _scriptArgs; } void reset() { _argv = 0; _stapArgs.clear(); _script.clear(); _scriptArgs.clear(); } int launch(); int launchUsingParser(shared_ptr parser); shared_ptr makeStapParser() { shared_ptr result(new StapParser); parsers.push_back(result); parserListChangedSignal().emit(); return result; } public: void onChildDied(pid_t pid) { ParserList::iterator itr = find_if(parsers.begin(), parsers.end(), PidPred(pid)); if (itr != parsers.end()) { (*itr)->disconnect(); tr1::shared_ptr sp = (*itr)->getProcess(); if (sp) { sp->pid = -1; parserListChangedSignal().emit(); } } } void killAll() { ParserList parsersCopy(parsers.begin(), parsers.end()); for (ParserList::iterator itr = parsersCopy.begin(), end = parsersCopy.end(); itr != end; ++itr) { if ((*itr)->getPid() >= 0) kill((*itr)->getPid(), SIGTERM); } } protected: char** _argv; string _stapArgs; string _script; string _scriptArgs; int _childPid; }; int StapLauncher::launch() { tr1::shared_ptr sp(new StapParser); shared_ptr proc(new StapProcess(-1)); if (_argv) proc->argv = _argv; else { proc->stapArgs = _stapArgs; proc->script = _script; proc->scriptArgs = _scriptArgs; } sp->setProcess(proc); pid_t childPid = launchUsingParser(sp); if (childPid >= 0) { parsers.push_back(sp); parserListChangedSignal().emit(); } return childPid; } int StapLauncher::launchUsingParser(shared_ptr sp) { shared_ptr proc = sp->getProcess(); if (!proc) { cerr << "Can't launch parser with null process structure\n"; return -1; } proc->pid = -1; if (signalPipe[0] < 0) { if (pipe(&signalPipe[0]) < 0) { std::perror("pipe"); exit(1); } setSigfd(signalPipe[0]); if (signalPipe[0] >= 0) { Glib::signal_io().connect(sigc::mem_fun(*this, &ChildDeathReader::ioCallback), signalPipe[0], Glib::IO_IN); } struct sigaction action; action.sa_sigaction = handleChild; sigemptyset(&action.sa_mask); action.sa_flags = SA_SIGINFO | SA_NOCLDSTOP; sigaction(SIGCLD, &action, 0); } int pipefd[4]; if (pipe(&pipefd[0]) < 0 || pipe(&pipefd[2]) < 0) { std::perror("pipe"); exit(1); } if ((proc->pid = fork()) == -1) { exit(1); } else if (proc->pid) { close(pipefd[1]); close(pipefd[3]); } else { dup2(pipefd[1], STDOUT_FILENO); dup2(pipefd[3], STDERR_FILENO); for_each(&pipefd[0], &pipefd[4], close); for_each(&signalPipe[0], &signalPipe[2], close); if (proc->argv) { char argv0[] = "stap"; char** argvEnd = proc->argv; for (; *argvEnd; ++argvEnd) ; char** realArgv = new char*[argvEnd - proc->argv + 2]; realArgv[0] = argv0; std::copy(_argv, argvEnd + 1, &realArgv[1]); execvp("stap", realArgv); } else { string argString = "stap" + proc->stapArgs + " " + proc->script + " " + proc->scriptArgs; execl("/bin/sh", "sh", "-c", argString.c_str(), static_cast(0)); } _exit(1); } sp->initIo(pipefd[0], pipefd[2], false); return proc->pid; } class GraphicalStapLauncher : public StapLauncher { public: GraphicalStapLauncher(); bool runDialog(); void onLaunch(); void onLaunchCancel(); private: Glib::RefPtr _launchStapDialog; Gtk::Window* _scriptWindow; Gtk::FileChooserButton* _chooserButton; Gtk::Entry* _stapArgEntry; Gtk::Entry* _scriptArgEntry; }; GraphicalStapLauncher *graphicalLauncher = 0; class ProcModelColumns : public Gtk::TreeModelColumnRecord { public: ProcModelColumns() { add(_iconName); add(_scriptName); add(_parser); } Gtk::TreeModelColumn _iconName; Gtk::TreeModelColumn _scriptName; Gtk::TreeModelColumn > _parser; }; // This should probably be a Gtk window, with the appropriate glade magic class ProcWindow { public: ProcWindow(); ProcModelColumns _modelColumns; Glib::RefPtr _xml; Gtk::Window* _window; Gtk::TreeView* _dataTreeView; Gtk::Button* _killButton; Gtk::Button* _restartButton; Gtk::Label* _stapArgsLabel; Gtk::Label* _scriptArgsLabel; Glib::RefPtr _listStore; Glib::RefPtr _listSelection; void onClose(); void show(); void hide(); void onParserListChanged(); void onSelectionChanged(); void onKill(); void onRestart(); private: bool _open; void refresh(); }; ProcWindow::ProcWindow() : _open(false) { try { _xml = Gnome::Glade::Xml::create(PKGDATADIR "/processwindow.glade"); _xml->get_widget("window1", _window); _xml->get_widget("treeview1", _dataTreeView); } catch (const Gnome::Glade::XmlError& ex ) { std::cerr << ex.what() << std::endl; throw; } _listStore = Gtk::ListStore::create(_modelColumns); _dataTreeView->set_model(_listStore); // Display a nice icon for the state of the process Gtk::CellRendererPixbuf* cell = Gtk::manage(new Gtk::CellRendererPixbuf); _dataTreeView->append_column("State", *cell); Gtk::TreeViewColumn* column = _dataTreeView->get_column(0); if (column) column->add_attribute(cell->property_icon_name(), _modelColumns._iconName); _dataTreeView->append_column("Script", _modelColumns._scriptName); Gtk::Button* button = 0; _xml->get_widget("button5", button); button->signal_clicked().connect(sigc::mem_fun(*this, &ProcWindow::onClose), false); _xml->get_widget("button1", _killButton); _killButton->signal_clicked() .connect(sigc::mem_fun(*this, &ProcWindow::onKill), false); _killButton->set_sensitive(false); _xml->get_widget("button2", _restartButton); _restartButton->signal_clicked() .connect(sigc::mem_fun(*this, &ProcWindow::onRestart), false); _restartButton->set_sensitive(false); parserListChangedSignal() .connect(sigc::mem_fun(*this, &ProcWindow::onParserListChanged)); _listSelection = _dataTreeView->get_selection(); _listSelection->signal_changed() .connect(sigc::mem_fun(*this, &ProcWindow::onSelectionChanged)); _xml->get_widget("label7", _stapArgsLabel); _xml->get_widget("label8", _scriptArgsLabel); } void ProcWindow::onClose() { _window->hide(); } void ProcWindow::show() { _open = true; refresh(); _window->show(); } void ProcWindow::hide() { _open = false; _window->hide(); } void ProcWindow::refresh() { // If a process is already selected, try to leave it selected after // the list is reconstructed. shared_ptr selectedParser; { Gtk::TreeModel::iterator itr = _listSelection->get_selected(); if (itr) { Gtk::TreeModel::Row row = *itr; selectedParser = row[_modelColumns._parser]; } } _listStore->clear(); for (ParserList::iterator spitr = parsers.begin(), end = parsers.end(); spitr != end; ++spitr) { shared_ptr parser = *spitr; shared_ptr sp = parser->getProcess(); Gtk::TreeModel::iterator litr = _listStore->append(); Gtk::TreeModel::Row row = *litr; if (sp) { row[_modelColumns._iconName] = sp->pid >= 0 ? "gtk-yes" : "gtk-no"; row[_modelColumns._scriptName] = sp->script; } else { row[_modelColumns._iconName] = "gtk-yes"; row[_modelColumns._scriptName] = "standard input"; } row[_modelColumns._parser] = parser; } if (selectedParser) { Gtk::TreeModel::Children children = _listStore->children(); for (Gtk::TreeModel::Children::const_iterator itr = children.begin(), end = children.end(); itr != end; ++itr) { Gtk::TreeModel::Row row = *itr; if (row[_modelColumns._parser] == selectedParser) { _listSelection->select(row); break; } } } } void ProcWindow::onParserListChanged() { if (_open) { refresh(); _window->queue_draw(); } } void ProcWindow::onSelectionChanged() { Gtk::TreeModel::iterator itr = _listSelection->get_selected(); shared_ptr parser; shared_ptr proc; if (itr) { Gtk::TreeModel::Row row = *itr; parser = row[_modelColumns._parser]; proc = parser->getProcess(); } if (proc) { bool procRunning = proc->pid >= 0; _killButton->set_sensitive(procRunning); _restartButton->set_sensitive(!procRunning); _stapArgsLabel->set_text(proc->stapArgs); _scriptArgsLabel->set_text(proc->scriptArgs); } else { _killButton->set_sensitive(false); _restartButton->set_sensitive(false); _stapArgsLabel->set_text(""); _scriptArgsLabel->set_text(""); } } void ProcWindow::onKill() { Gtk::TreeModel::iterator itr = _listSelection->get_selected(); if (!itr) return; Gtk::TreeModel::Row row = *itr; shared_ptr parser = row[_modelColumns._parser]; shared_ptr proc = parser->getProcess(); if (proc && proc->pid >= 0) kill(proc->pid, SIGTERM); } void ProcWindow::onRestart() { Gtk::TreeModel::iterator itr = _listSelection->get_selected(); if (!itr) return; Gtk::TreeModel::Row row = *itr; shared_ptr parser = row[_modelColumns._parser]; shared_ptr proc = parser->getProcess(); if (!proc) return; if (graphicalLauncher->launchUsingParser(parser) > 0) parserListChangedSignal().emit(); } class GrapherWindow : public Gtk::Window { public: GrapherWindow(); virtual ~GrapherWindow() {} Gtk::VBox m_Box; Gtk::ScrolledWindow scrolled; GraphWidget w; protected: virtual void on_menu_file_quit(); virtual void on_menu_script_start(); virtual void on_menu_proc_window(); void addGraph(); void onParserListChanged(); // menu support Glib::RefPtr m_refUIManager; Glib::RefPtr m_refActionGroup; shared_ptr _procWindow; bool _quitting; }; GrapherWindow::GrapherWindow() : _procWindow(new ProcWindow), _quitting(false) { set_title("systemtap grapher"); add(m_Box); parserListChangedSignal() .connect(sigc::mem_fun(*this, &GrapherWindow::onParserListChanged)); //Create actions for menus and toolbars: m_refActionGroup = Gtk::ActionGroup::create(); //File menu: m_refActionGroup->add(Gtk::Action::create("FileMenu", "File")); m_refActionGroup->add(Gtk::Action::create("StartScript", "Start script..."), sigc::mem_fun(*this, &GrapherWindow::on_menu_script_start)); m_refActionGroup->add(Gtk::Action::create("AddGraph", "Add graph"), sigc::mem_fun(*this, &GrapherWindow::addGraph)); m_refActionGroup->add(Gtk::Action::create("FileQuit", Gtk::Stock::QUIT), sigc::mem_fun(*this, &GrapherWindow::on_menu_file_quit)); // Window menu m_refActionGroup->add(Gtk::Action::create("WindowMenu", "Window")); m_refActionGroup->add(Gtk::Action::create("ProcessWindow", "Stap processes..."), sigc::mem_fun(*this, &GrapherWindow::on_menu_proc_window)); m_refUIManager = Gtk::UIManager::create(); m_refUIManager->insert_action_group(m_refActionGroup); add_accel_group(m_refUIManager->get_accel_group()); //Layout the actions in a menubar and toolbar: Glib::ustring ui_info = "" " " " " " " " " " " " " " " " " " " " " ""; try { m_refUIManager->add_ui_from_string(ui_info); } catch(const Glib::Error& ex) { std::cerr << "building menus failed: " << ex.what(); } Gtk::Widget* pMenubar = m_refUIManager->get_widget("/MenuBar"); scrolled.add(w); if(pMenubar) m_Box.pack_start(*pMenubar, Gtk::PACK_SHRINK); m_Box.pack_start(scrolled, Gtk::PACK_EXPAND_WIDGET); scrolled.show(); show_all_children(); } void GrapherWindow::on_menu_file_quit() { using namespace boost; _quitting = true; if (find_if(parsers.begin(), parsers.end(), !bind(PidPred(-1), _1)) != parsers.end()) graphicalLauncher->killAll(); else hide(); } void GrapherWindow::on_menu_script_start() { graphicalLauncher->runDialog(); } void GrapherWindow::on_menu_proc_window() { _procWindow->show(); } void GrapherWindow::onParserListChanged() { using namespace boost; if (_quitting && (find_if(parsers.begin(), parsers.end(), !bind(PidPred(-1), _1)) == parsers.end())) hide(); } int main(int argc, char** argv) { Gtk::Main app(argc, argv); graphicalLauncher = new GraphicalStapLauncher; GrapherWindow win; win.set_title("Grapher"); win.set_default_size(600, 250); if (argc == 2 && !std::strcmp(argv[1], "-")) { tr1::shared_ptr sp = graphicalLauncher->makeStapParser(); sp->initIo(STDIN_FILENO, -1, true); } else if (argc > 1) { graphicalLauncher->setArgv(argv + 1); graphicalLauncher->launch(); } Gtk::Main::run(win); return 0; } void GrapherWindow::addGraph() { w.addGraph(); } GraphicalStapLauncher::GraphicalStapLauncher() { try { _launchStapDialog = Gnome::Glade::Xml::create(PKGDATADIR "/stap-start.glade"); _launchStapDialog->get_widget("window1", _scriptWindow); _launchStapDialog->get_widget("scriptChooserButton", _chooserButton); _launchStapDialog->get_widget("stapEntry", _stapArgEntry); _launchStapDialog->get_widget("scriptEntry", _scriptArgEntry); Gtk::Button* button = 0; _launchStapDialog->get_widget("launchButton", button); button->signal_clicked() .connect(sigc::mem_fun(*this, &GraphicalStapLauncher::onLaunch), false); _launchStapDialog->get_widget("cancelButton", button); button->signal_clicked() .connect(sigc::mem_fun(*this, &GraphicalStapLauncher::onLaunchCancel), false); } catch (const Gnome::Glade::XmlError& ex ) { std::cerr << ex.what() << std::endl; throw; } } bool GraphicalStapLauncher::runDialog() { _scriptWindow->show(); return true; } void GraphicalStapLauncher::onLaunch() { setArgs(_stapArgEntry->get_text(), _chooserButton->get_filename(), _scriptArgEntry->get_text()); _scriptWindow->hide(); launch(); } void GraphicalStapLauncher::onLaunchCancel() { _scriptWindow->hide(); }