From 5ddc5963ce06ecea574e90ca503a3ee598522d8f Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Fri, 4 Dec 2009 13:08:01 +0100 Subject: support multiline data output from scripts run under the grapher This is accompanied by support for multiline output in hover text. * grapher/StapParser.cxx (ioCallback): Read data 'til the end of line character, not just '\n'. Be careful to use I/O functions that don't treat '\n' specially. * grapher/StapParser.hxx: ditto * grapher/CairoWidget.cxx (CairoTextBox::draw): Perform line breaks for hover text. * testsuite/systemtap.examples/general/grapher.stp: Do multiline output of keyboard events. Also, fix longstanding breaking in the pty probe. --- grapher/StapParser.cxx | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) (limited to 'grapher/StapParser.cxx') diff --git a/grapher/StapParser.cxx b/grapher/StapParser.cxx index 6218e229..2a246475 100644 --- a/grapher/StapParser.cxx +++ b/grapher/StapParser.cxx @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -76,14 +77,14 @@ vector commaSplit(const boost::sub_range& range) _win->hide(); return true; } - buf[bytes_read] = '\0'; - _buffer += buf; + _buffer.append(buf, bytes_read); string::size_type ret = string::npos; - while ((ret = _buffer.find('\n')) != string::npos) + while ((ret = _buffer.find(_lineEndChar)) != string::npos) { Glib::ustring dataString(_buffer, 0, ret); - // %DataSet and %CSV declare a data set; all other statements begin with - // the name of a data set. + // %DataSet and %CSV declare a data set; all other + // statements begin with the name of a data set. + // Except %LineEnd :) sub_range found; if (dataString[0] == '%') { @@ -142,6 +143,15 @@ vector commaSplit(const boost::sub_range& range) setIter->second)); } } + else if ((found = find_first(dataString, "%LineEnd:"))) + { + istringstream stream(Glib::ustring(found.end(), + dataString.end())); + int charAsInt = 0; + // parse hex and octal numbers too + stream >> std::setbase(0) >> charAsInt; + _lineEndChar = static_cast(charAsInt); + } else { cerr << "Unknown declaration " << dataString << endl; @@ -199,9 +209,10 @@ vector commaSplit(const boost::sub_range& range) else { int64_t time; - string data; - stream >> time >> data; - parseData(itr->second, time, data); + stringbuf data; + stream >> time; + stream.get(data, _lineEndChar); + parseData(itr->second, time, data.str()); } } } -- cgit From f669d095ba7fe5a623b31abc05b4f6664059803b Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Mon, 7 Dec 2009 19:19:45 +0100 Subject: add copyright and license to grapher files --- grapher/StapParser.cxx | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'grapher/StapParser.cxx') diff --git a/grapher/StapParser.cxx b/grapher/StapParser.cxx index 2a246475..edb7f5f2 100644 --- a/grapher/StapParser.cxx +++ b/grapher/StapParser.cxx @@ -1,3 +1,11 @@ +// 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 "StapParser.hxx" #include -- cgit From cfd482078cd4805076cc6fd7e4e8642b97a03b25 Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Tue, 8 Dec 2009 12:44:03 +0100 Subject: refactor list of data sets out of GraphWidget into a global data structure. Also, add a sigc signal for broadcasting data set changes. * grapher/GraphData.hxx (GraphDataList) new typedef (getGraphData, graphDataSignal): new functions * grapher/Graph.hxx (DatasetList): remove typedef in favor of systemtap::GraphDataList * grapher/GraphWidget.cxx (GraphWidget, onGraphDataChanged): Use graphDataSignal to notice new data sets that need to be added. Use the global data set list instead of one embedded in GraphWidget. * grapher/StapParser.hxx: delete the _widget member; use signals to broadcast that there are new data sets instead of calling into the widget. --- grapher/StapParser.cxx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'grapher/StapParser.cxx') diff --git a/grapher/StapParser.cxx b/grapher/StapParser.cxx index edb7f5f2..653c00de 100644 --- a/grapher/StapParser.cxx +++ b/grapher/StapParser.cxx @@ -118,7 +118,8 @@ vector commaSplit(const boost::sub_range& range) dataSet->color[2] = (hexColor & 0xff) / 255.0; dataSet->scale = scale; _dataSets.insert(std::make_pair(setName, dataSet)); - _widget->addGraphData(dataSet); + getGraphData().push_back(dataSet); + graphDataSignal().emit(); } else if (style == "discreet") { @@ -131,7 +132,8 @@ vector commaSplit(const boost::sub_range& range) dataSet->color[2] = (hexColor & 0xff) / 255.0; dataSet->scale = scale; _dataSets.insert(std::make_pair(setName, dataSet)); - _widget->addGraphData(dataSet); + getGraphData().push_back(dataSet); + graphDataSignal().emit(); } } else if ((found = find_first(dataString, "%CSV:"))) -- cgit From 8cabc8bcdcd22e8726734f1a18f23ed7d9c19e9f Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Tue, 8 Dec 2009 23:17:47 +0100 Subject: grapher: start of a dialog for displaying active stap processes The names of active scripts are displayed in a list; mock buttons suggest being able to stop and restart them. * grapher/processwindow.glade: new file * grapher/Makefile.am: add processwindow.glade to installed files * grapher/StapParser.hxx (StapProcess): new class (StapParser): factor out members that are now in StapProcess (ioCallback): Use the new childDied signal instead of aborting the whole grapher when a child dies. * grapher/grapher.cxx (ProcModelColumns, ProcWindow): classes for displaying stap process window. (GrapherWindow::on_menu_proc_window): new function --- grapher/StapParser.cxx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'grapher/StapParser.cxx') diff --git a/grapher/StapParser.cxx b/grapher/StapParser.cxx index 653c00de..b82cc024 100644 --- a/grapher/StapParser.cxx +++ b/grapher/StapParser.cxx @@ -25,6 +25,12 @@ namespace systemtap using namespace std; using namespace std::tr1; + sigc::signal& childDiedSignal() + { + static sigc::signal deathSignal; + return deathSignal; + } + vector commaSplit(const boost::sub_range& range) { using namespace boost; @@ -72,7 +78,7 @@ vector commaSplit(const boost::sub_range& range) using namespace boost; if (ioCondition & Glib::IO_HUP) { - _win->hide(); + childDiedSignal().emit(getPid()); return true; } if ((ioCondition & Glib::IO_IN) == 0) @@ -82,7 +88,7 @@ vector commaSplit(const boost::sub_range& range) bytes_read = read(_inFd, buf, sizeof(buf) - 1); if (bytes_read <= 0) { - _win->hide(); + childDiedSignal().emit(getPid()); return true; } _buffer.append(buf, bytes_read); @@ -242,7 +248,7 @@ vector commaSplit(const boost::sub_range& range) bytes_read = read(_errFd, buf, sizeof(buf) - 1); if (bytes_read <= 0) { - _win->hide(); + cerr << "StapParser: error reading from stderr!\n"; return true; } if (write(STDOUT_FILENO, buf, bytes_read) < 0) -- cgit From 3e1613e1f7ab589089e8ed5a504330bb9cb128db Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Wed, 9 Dec 2009 22:09:39 +0100 Subject: show the status of stap processes in the process window Also, the "kill" button now works. * grapher/StapParser.hxx (_ioConnection, _errIoConnection): new members for sigc connections. (initIo): New function (parsers, parserListChangedSignal): new variables * grapher/StapParser.cxx (ioCallback): disconnect signals when child dies (initIo): new function * grapher/grapher.cxx (StapLauncher): eliminate death callback in favor of childDied signal. Use global parsers list (ProcWindow::_listSelection): new member (ProcWindow::show, hide, onParserListChanged, onSelectionChanged, onKill): new functions (ProcWindow::ProcWindow): Set up cell renderer for status icon * grapher/processwindow.glade: labels for display script and stap arguments --- grapher/StapParser.cxx | 338 ++++++++++++++++++++++++++----------------------- 1 file changed, 181 insertions(+), 157 deletions(-) (limited to 'grapher/StapParser.cxx') diff --git a/grapher/StapParser.cxx b/grapher/StapParser.cxx index b82cc024..a9a5109a 100644 --- a/grapher/StapParser.cxx +++ b/grapher/StapParser.cxx @@ -30,7 +30,15 @@ namespace systemtap static sigc::signal deathSignal; return deathSignal; } - + + ParserList parsers; + + sigc::signal& parserListChangedSignal() + { + static sigc::signal listChangedSignal; + return listChangedSignal; + } + vector commaSplit(const boost::sub_range& range) { using namespace boost; @@ -72,171 +80,173 @@ vector commaSplit(const boost::sub_range& range) } bool StapParser::ioCallback(Glib::IOCondition ioCondition) - { - using namespace std; - using std::tr1::shared_ptr; - using namespace boost; - if (ioCondition & Glib::IO_HUP) - { - childDiedSignal().emit(getPid()); - return true; - } - if ((ioCondition & Glib::IO_IN) == 0) + { + using namespace std; + using std::tr1::shared_ptr; + using namespace boost; + if (ioCondition & Glib::IO_HUP) + { + childDiedSignal().emit(getPid()); + _ioConnection.disconnect(); + _errIoConnection.disconnect(); + return true; + } + if ((ioCondition & Glib::IO_IN) == 0) + return true; + char buf[256]; + ssize_t bytes_read = 0; + bytes_read = read(_inFd, buf, sizeof(buf) - 1); + if (bytes_read <= 0) + { + childDiedSignal().emit(getPid()); return true; - char buf[256]; - ssize_t bytes_read = 0; - bytes_read = read(_inFd, buf, sizeof(buf) - 1); - if (bytes_read <= 0) - { - childDiedSignal().emit(getPid()); - return true; - } - _buffer.append(buf, bytes_read); - string::size_type ret = string::npos; - while ((ret = _buffer.find(_lineEndChar)) != string::npos) - { - Glib::ustring dataString(_buffer, 0, ret); - // %DataSet and %CSV declare a data set; all other - // statements begin with the name of a data set. - // Except %LineEnd :) - sub_range found; - if (dataString[0] == '%') - { - if ((found = find_first(dataString, "%DataSet:"))) - { - string setName; - int hexColor; - double scale; - string style; - istringstream stream(Glib::ustring(found.end(), - dataString.end())); - stream >> setName >> scale >> std::hex >> hexColor - >> style; - if (style == "bar" || style == "dot") - { - std::tr1::shared_ptr > - dataSet(new GraphData); - dataSet->name = setName; - if (style == "dot") - dataSet->style = &GraphStyleDot::instance; - dataSet->color[0] = (hexColor >> 16) / 255.0; - dataSet->color[1] = ((hexColor >> 8) & 0xff) / 255.0; - dataSet->color[2] = (hexColor & 0xff) / 255.0; - dataSet->scale = scale; - _dataSets.insert(std::make_pair(setName, dataSet)); - getGraphData().push_back(dataSet); - graphDataSignal().emit(); - } - else if (style == "discreet") - { - std::tr1::shared_ptr > - dataSet(new GraphData); - dataSet->name = setName; - dataSet->style = &GraphStyleEvent::instance; - dataSet->color[0] = (hexColor >> 16) / 255.0; - dataSet->color[1] = ((hexColor >> 8) & 0xff) / 255.0; - dataSet->color[2] = (hexColor & 0xff) / 255.0; - dataSet->scale = scale; - _dataSets.insert(std::make_pair(setName, dataSet)); - getGraphData().push_back(dataSet); - graphDataSignal().emit(); - } - } - else if ((found = find_first(dataString, "%CSV:"))) - { - vector tokens - = commaSplit(sub_range(found.end(), - dataString.end())); - for (vector::iterator tokIter = tokens.begin(), - e = tokens.end(); - tokIter != e; - ++tokIter) - { - DataMap::iterator setIter = _dataSets.find(*tokIter); - if (setIter != _dataSets.end()) - _csv.elements - .push_back(CSVData::Element(*tokIter, - setIter->second)); - } - } - else if ((found = find_first(dataString, "%LineEnd:"))) - { - istringstream stream(Glib::ustring(found.end(), - dataString.end())); - int charAsInt = 0; - // parse hex and octal numbers too - stream >> std::setbase(0) >> charAsInt; - _lineEndChar = static_cast(charAsInt); - } - else - { - cerr << "Unknown declaration " << dataString << endl; - } - } - else - { - std::istringstream stream(dataString); - string setName; - stream >> setName; - DataMap::iterator itr = _dataSets.find(setName); - if (itr != _dataSets.end()) - { - shared_ptr gdata = itr->second; - string decl; - // Hack: scan from the beginning of dataString again - if (findTaggedValue(dataString, "%Title:", decl)) - { - gdata->title = decl; - } - else if (findTaggedValue(dataString, "%XAxisTitle:", decl)) - { - gdata->xAxisText = decl; - } - else if (findTaggedValue(dataString, "%YAxisTitle:", decl)) - { - gdata->yAxisText = decl; - } - else if ((found = find_first(dataString, "%YMax:"))) - { - double ymax; - std::istringstream - stream(Glib::ustring(found.end(), dataString.end())); - stream >> ymax; - gdata->scale = ymax; - } - else + } + _buffer.append(buf, bytes_read); + string::size_type ret = string::npos; + while ((ret = _buffer.find(_lineEndChar)) != string::npos) + { + Glib::ustring dataString(_buffer, 0, ret); + // %DataSet and %CSV declare a data set; all other + // statements begin with the name of a data set. + // Except %LineEnd :) + sub_range found; + if (dataString[0] == '%') + { + if ((found = find_first(dataString, "%DataSet:"))) + { + string setName; + int hexColor; + double scale; + string style; + istringstream stream(Glib::ustring(found.end(), + dataString.end())); + stream >> setName >> scale >> std::hex >> hexColor + >> style; + if (style == "bar" || style == "dot") + { + std::tr1::shared_ptr > + dataSet(new GraphData); + dataSet->name = setName; + if (style == "dot") + dataSet->style = &GraphStyleDot::instance; + dataSet->color[0] = (hexColor >> 16) / 255.0; + dataSet->color[1] = ((hexColor >> 8) & 0xff) / 255.0; + dataSet->color[2] = (hexColor & 0xff) / 255.0; + dataSet->scale = scale; + _dataSets.insert(std::make_pair(setName, dataSet)); + getGraphData().push_back(dataSet); + graphDataSignal().emit(); + } + else if (style == "discreet") { - if (!_csv.elements.empty()) + std::tr1::shared_ptr > + dataSet(new GraphData); + dataSet->name = setName; + dataSet->style = &GraphStyleEvent::instance; + dataSet->color[0] = (hexColor >> 16) / 255.0; + dataSet->color[1] = ((hexColor >> 8) & 0xff) / 255.0; + dataSet->color[2] = (hexColor & 0xff) / 255.0; + dataSet->scale = scale; + _dataSets.insert(std::make_pair(setName, dataSet)); + getGraphData().push_back(dataSet); + graphDataSignal().emit(); + } + } + else if ((found = find_first(dataString, "%CSV:"))) + { + vector tokens + = commaSplit(sub_range(found.end(), + dataString.end())); + for (vector::iterator tokIter = tokens.begin(), + e = tokens.end(); + tokIter != e; + ++tokIter) + { + DataMap::iterator setIter = _dataSets.find(*tokIter); + if (setIter != _dataSets.end()) + _csv.elements + .push_back(CSVData::Element(*tokIter, + setIter->second)); + } + } + else if ((found = find_first(dataString, "%LineEnd:"))) + { + istringstream stream(Glib::ustring(found.end(), + dataString.end())); + int charAsInt = 0; + // parse hex and octal numbers too + stream >> std::setbase(0) >> charAsInt; + _lineEndChar = static_cast(charAsInt); + } + else + { + cerr << "Unknown declaration " << dataString << endl; + } + } + else + { + std::istringstream stream(dataString); + string setName; + stream >> setName; + DataMap::iterator itr = _dataSets.find(setName); + if (itr != _dataSets.end()) + { + shared_ptr gdata = itr->second; + string decl; + // Hack: scan from the beginning of dataString again + if (findTaggedValue(dataString, "%Title:", decl)) + { + gdata->title = decl; + } + else if (findTaggedValue(dataString, "%XAxisTitle:", decl)) + { + gdata->xAxisText = decl; + } + else if (findTaggedValue(dataString, "%YAxisTitle:", decl)) + { + gdata->yAxisText = decl; + } + else if ((found = find_first(dataString, "%YMax:"))) + { + double ymax; + std::istringstream + stream(Glib::ustring(found.end(), dataString.end())); + stream >> ymax; + gdata->scale = ymax; + } + else + { + if (!_csv.elements.empty()) { - vector tokens = commaSplit(dataString); - int i = 0; - int64_t time; - vector::iterator tokIter = tokens.begin(); - std::istringstream timeStream(*tokIter++); - timeStream >> time; - for (vector::iterator e = tokens.end(); - tokIter != e; - ++tokIter, ++i) + vector tokens = commaSplit(dataString); + int i = 0; + int64_t time; + vector::iterator tokIter = tokens.begin(); + std::istringstream timeStream(*tokIter++); + timeStream >> time; + for (vector::iterator e = tokens.end(); + tokIter != e; + ++tokIter, ++i) { - parseData(_csv.elements[i].second, time, - *tokIter); + parseData(_csv.elements[i].second, time, + *tokIter); } } - else + else { - int64_t time; - stringbuf data; - stream >> time; - stream.get(data, _lineEndChar); - parseData(itr->second, time, data.str()); + int64_t time; + stringbuf data; + stream >> time; + stream.get(data, _lineEndChar); + parseData(itr->second, time, data.str()); } } - } - } - _buffer.erase(0, ret + 1); - } - return true; - } + } + } + _buffer.erase(0, ret + 1); + } + return true; + } bool StapParser::errIoCallback(Glib::IOCondition ioCondition) { @@ -255,4 +265,18 @@ vector commaSplit(const boost::sub_range& range) ; return true; } + + void StapParser::initIo(int inFd, int errFd) + { + _inFd = inFd; + _errFd = errFd; + _ioConnection = Glib::signal_io() + .connect(sigc::mem_fun(*this, &StapParser::errIoCallback), + _errFd, + Glib::IO_IN); + _errIoConnection = Glib::signal_io() + .connect(sigc::mem_fun(*this, &StapParser::ioCallback), + _inFd, + Glib::IO_IN | Glib::IO_HUP); + } } -- cgit From 1b9fad80af5504ef03c2a88504dbc47bea003721 Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Thu, 10 Dec 2009 21:34:27 +0100 Subject: grapher: integrate graph events from stdin with the stap process framework. This was the original way to do graphing and had bitrotted some. * grapher/StapParser.cxx (initIO): new catchHUP argument (ioCallback): Only disconnect signals, etc. on IN_HUP if catchHUP is true. (errIoCallback): Write error messages to stderr, not stdout. * grapher/grapher.cxx (StapLauncher::launch): Don't catchHUP on our stap process children. (ProcWindow::refresh): Display something reasonable for the stap "process" that is feeding stdin. (main): Use StapParser::initIO to initialize parser reading from stdin. --- grapher/StapParser.cxx | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) (limited to 'grapher/StapParser.cxx') diff --git a/grapher/StapParser.cxx b/grapher/StapParser.cxx index a9a5109a..2513680b 100644 --- a/grapher/StapParser.cxx +++ b/grapher/StapParser.cxx @@ -86,9 +86,12 @@ vector commaSplit(const boost::sub_range& range) using namespace boost; if (ioCondition & Glib::IO_HUP) { - childDiedSignal().emit(getPid()); - _ioConnection.disconnect(); - _errIoConnection.disconnect(); + if (_catchHUP) + { + childDiedSignal().emit(getPid()); + _ioConnection.disconnect(); + _errIoConnection.disconnect(); + } return true; } if ((ioCondition & Glib::IO_IN) == 0) @@ -261,22 +264,28 @@ vector commaSplit(const boost::sub_range& range) cerr << "StapParser: error reading from stderr!\n"; return true; } - if (write(STDOUT_FILENO, buf, bytes_read) < 0) + if (write(STDERR_FILENO, buf, bytes_read) < 0) ; return true; } - void StapParser::initIo(int inFd, int errFd) + void StapParser::initIo(int inFd, int errFd, bool catchHUP) { _inFd = inFd; _errFd = errFd; + _catchHUP = catchHUP; + Glib::IOCondition inCond = Glib::IO_IN; + if (catchHUP) + inCond |= Glib::IO_HUP; + if (_errFd >= 0) + { + _errIoConnection = Glib::signal_io() + .connect(sigc::mem_fun(*this, &StapParser::errIoCallback), + _errFd, Glib::IO_IN); + } _ioConnection = Glib::signal_io() - .connect(sigc::mem_fun(*this, &StapParser::errIoCallback), - _errFd, - Glib::IO_IN); - _errIoConnection = Glib::signal_io() .connect(sigc::mem_fun(*this, &StapParser::ioCallback), - _inFd, - Glib::IO_IN | Glib::IO_HUP); + _inFd, inCond); + } } -- cgit From e47f92ea31a605802c59541ca325ffd567c45ca4 Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Fri, 11 Dec 2009 14:03:47 +0100 Subject: grapher: implement restarting a stap process * grapher/StapParser.cxx (StapParser::disconnect): new function * grapher/StapParser.hxx (StapProcess::StapProcess): initialize argv to 0 * grapher/grapher.cxx (StapLauncher::setArgs): Set argv to 0 (StapLauncher launch, launchUsingParser): Refactor launch(), extracting function a that (re)launches a stap process using an existing parser. (StapLauncher::onChildDied): call disconnect() on dead parser. (GrapherWindow::_graphicalLauncher, setGraphicalLauncher): delete member, replacing with... (graphicalLauncher): new variable (ProcModelColumns): Store parser object in the list model instead of just a StapProcess object. (ProcWindow::onRestart): new function (ProcWindow::refresh): Preserve the list selection when the process list is refreshed. (ProcWindow::onSelectionChanged): Manage the restart button's state. --- grapher/StapParser.cxx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'grapher/StapParser.cxx') diff --git a/grapher/StapParser.cxx b/grapher/StapParser.cxx index 2513680b..2595e8cc 100644 --- a/grapher/StapParser.cxx +++ b/grapher/StapParser.cxx @@ -288,4 +288,22 @@ vector commaSplit(const boost::sub_range& range) _inFd, inCond); } + + void StapParser::disconnect() + { + if (_ioConnection.connected()) + _ioConnection.disconnect(); + if (_errIoConnection.connected()) + _errIoConnection.disconnect(); + if (_inFd >= 0) + { + close(_inFd); + _inFd = -1; + } + if (_errFd >= 0) + { + close(_errFd); + _errFd = -1; + } + } } -- cgit