EG/pandatool/src/pstatserver/pStatMonitor.cxx

800 lines
23 KiB
C++

/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file pStatMonitor.cxx
* @author drose
* @date 2000-07-09
*/
#include "pStatMonitor.h"
#include "datagramInputFile.h"
#include "datagramOutputFile.h"
#include "pandaVersion.h"
#include "pStatCollectorDef.h"
#include "pStatTimeline.h"
#include "pStatStripChart.h"
#include "pStatFlameGraph.h"
#include "pStatPianoRoll.h"
using std::string;
static const string session_file_header("pstat\0\n\r", 8);
static const string layout_file_header("pslyt\0\n\r", 8);
static const Filename layout_filename = Filename::binary_filename(
#ifdef _WIN32
Filename::expand_from("$USER_APPDATA/Panda3D-" PANDA_ABI_VERSION_STR "/pstats-layout")
#elif defined(__APPLE__)
Filename::expand_from("$HOME/Library/Caches/Panda3D-" PANDA_ABI_VERSION_STR "/pstats-layout")
#else
Filename::expand_from("$XDG_STATE_HOME/panda3d/pstats-layout")
#endif
);
/**
*
*/
PStatMonitor::
PStatMonitor(PStatServer *server) : _server(server) {
_client_known = false;
}
/**
*
*/
PStatMonitor::
~PStatMonitor() {
close();
}
/**
* Called shortly after startup time with the greeting from the client. This
* indicates the client's reported hostname and program name.
*/
void PStatMonitor::
hello_from(const string &hostname, const string &progname, int pid) {
_client_known = true;
_client_hostname = hostname;
_client_progname = progname;
_client_pid = pid;
got_hello();
}
/**
* Called shortly after startup time with the greeting from the client. In
* this case, the client seems to have an incompatible version and will be
* automatically disconnected; the server should issue a message to that
* effect.
*/
void PStatMonitor::
bad_version(const string &hostname, const string &progname, int pid,
int client_major, int client_minor,
int server_major, int server_minor) {
_client_known = true;
_client_hostname = hostname;
_client_progname = progname;
_client_pid = 0;
got_bad_version(client_major, client_minor,
server_major, server_minor);
}
/**
* Called by the PStatServer at setup time to set the new data pointer for the
* first time.
*/
void PStatMonitor::
set_client_data(PStatClientData *client_data) {
_client_data = client_data;
initialized();
}
/**
* Writes the data and the UI state to the given file.
*/
bool PStatMonitor::
write(const Filename &fn) const {
DatagramOutputFile dof;
if (!dof.open(fn)) {
return false;
}
if (!dof.write_header(session_file_header)) {
return false;
}
Datagram dg;
dg.set_stdfloat_double(false);
dg.add_uint16(1);
dg.add_uint16(1);
write_datagram(dg);
dof.put_datagram(dg);
dof.close();
return true;
}
/**
* Reads the data and the UI state from the given file.
*/
bool PStatMonitor::
read(const Filename &fn) {
close();
DatagramInputFile dif;
if (!dif.open(fn)) {
nout << "Failed to open " << fn << " for reading.\n";
return false;
}
string header;
if (!dif.read_header(header, 8) || header != session_file_header) {
nout << "Session file contains invalid header.\n";
return false;
}
Datagram dg;
dg.set_stdfloat_double(false);
if (!dif.get_datagram(dg)) {
nout << "Failed to read datagram from session file.\n";
return false;
}
DatagramIterator scan(dg);
int version = scan.get_uint16();
if (version != 1) {
nout << "Unsupported session file version " << version << ".\n";
return false;
}
// Room for a minor version number
scan.get_uint16();
read_datagram(scan);
dif.close();
idle();
_read_filename = fn;
_client_data->clear_dirty();
return true;
}
/**
* Opens the default set of graphs.
*/
void PStatMonitor::
open_default_graphs() {
DatagramInputFile dif;
if (!dif.open(layout_filename)) {
open_strip_chart(0, 0, false);
return;
}
string header;
if (!dif.read_header(header, 8) || header != layout_file_header) {
nout << "Layout file contains invalid header.\n";
open_strip_chart(0, 0, false);
return;
}
Datagram dg;
dg.set_stdfloat_double(false);
if (!dif.get_datagram(dg)) {
nout << "Failed to read datagram from layout file.\n";
open_strip_chart(0, 0, false);
return;
}
DatagramIterator scan(dg);
int version = scan.get_uint16();
if (version != 1) {
nout << "Unsupported layout file version " << version << ".\n";
open_strip_chart(0, 0, false);
return;
}
// Room for a minor version number
scan.get_uint16();
size_t num_colors = scan.get_uint32();
for (size_t i = 0; i < num_colors; ++i) {
int key = scan.get_int32();
LRGBColor &color = _colors[key];
color[0] = scan.get_float32();
color[1] = scan.get_float32();
color[2] = scan.get_float32();
}
PStatGraph *graph;
size_t num_timelines = scan.get_uint16();
for (size_t i = 0; i < num_timelines; ++i) {
int x = scan.get_int32();
int y = scan.get_int32();
int width = scan.get_int32();
int height = scan.get_int32();
bool minimized = scan.get_bool();
bool maximized = scan.get_bool();
graph = open_timeline();
graph->set_window_state(x, y, width, height, minimized, maximized);
}
size_t num_strip_charts = scan.get_uint16();
for (size_t i = 0; i < num_strip_charts; ++i) {
std::string thread_name = scan.get_string();
std::string collector_name = scan.get_string();
bool show_level = scan.get_bool();
int thread_index = _client_data->find_thread(thread_name);
int collector_index = _client_data->find_collector(collector_name);
int x = scan.get_int32();
int y = scan.get_int32();
int width = scan.get_int32();
int height = scan.get_int32();
bool minimized = scan.get_bool();
bool maximized = scan.get_bool();
if (thread_index != -1) {
graph = open_strip_chart(thread_index, collector_index, show_level);
graph->set_window_state(x, y, width, height, minimized, maximized);
}
}
size_t num_flame_graphs = scan.get_uint16();
for (size_t i = 0; i < num_flame_graphs; ++i) {
std::string thread_name = scan.get_string();
std::string collector_name = scan.get_string();
int thread_index = _client_data->find_thread(thread_name);
int collector_index = collector_name.empty() ? -1 : _client_data->find_collector(collector_name);
int x = scan.get_int32();
int y = scan.get_int32();
int width = scan.get_int32();
int height = scan.get_int32();
bool minimized = scan.get_bool();
bool maximized = scan.get_bool();
if (thread_index != -1) {
graph = open_flame_graph(thread_index, collector_index);
graph->set_window_state(x, y, width, height, minimized, maximized);
}
}
size_t num_piano_rolls = scan.get_uint16();
for (size_t i = 0; i < num_piano_rolls; ++i) {
std::string thread_name = scan.get_string();
int thread_index = _client_data->find_thread(thread_name);
int x = scan.get_int32();
int y = scan.get_int32();
int width = scan.get_int32();
int height = scan.get_int32();
bool minimized = scan.get_bool();
bool maximized = scan.get_bool();
if (thread_index != -1) {
graph = open_piano_roll(thread_index);
graph->set_window_state(x, y, width, height, minimized, maximized);
}
}
dif.close();
if (num_timelines + num_strip_charts + num_flame_graphs + num_piano_rolls == 0) {
open_strip_chart(0, 0, false);
}
}
/**
* Saves the current graph layout as the default graph layout.
*/
bool PStatMonitor::
save_default_graphs() const {
layout_filename.make_dir();
DatagramOutputFile dof;
if (!dof.open(layout_filename)) {
return false;
}
if (!dof.write_header(layout_file_header)) {
return false;
}
Datagram dg;
dg.set_stdfloat_double(false);
dg.add_uint16(1);
dg.add_uint16(1);
dg.add_uint32((uint32_t)_colors.size());
for (const auto &item : _colors) {
dg.add_int32(item.first);
dg.add_float32(item.second[0]);
dg.add_float32(item.second[1]);
dg.add_float32(item.second[2]);
}
dg.add_uint16(_timelines.size());
for (PStatGraph *graph : _timelines) {
int x, y, width, height;
bool minimized, maximized;
if (graph->get_window_state(x, y, width, height, minimized, maximized)) {
dg.add_int32(x);
dg.add_int32(y);
dg.add_int32(width);
dg.add_int32(height);
dg.add_bool(minimized);
dg.add_bool(maximized);
}
}
dg.add_uint16(_strip_charts.size());
for (PStatGraph *graph : _strip_charts) {
int x, y, width, height;
bool minimized, maximized;
if (graph->get_window_state(x, y, width, height, minimized, maximized)) {
dg.add_string(_client_data->get_thread_name(((PStatStripChart *)graph)->get_thread_index()));
dg.add_string(_client_data->get_collector_fullname(((PStatStripChart *)graph)->get_collector_index()));
dg.add_bool(((PStatStripChart *)graph)->get_view().get_show_level());
dg.add_int32(x);
dg.add_int32(y);
dg.add_int32(width);
dg.add_int32(height);
dg.add_bool(minimized);
dg.add_bool(maximized);
}
}
dg.add_uint16(_flame_graphs.size());
for (PStatGraph *graph : _flame_graphs) {
int x, y, width, height;
bool minimized, maximized;
if (graph->get_window_state(x, y, width, height, minimized, maximized)) {
int collector_index = ((PStatFlameGraph *)graph)->get_collector_index();
dg.add_string(_client_data->get_thread_name(((PStatFlameGraph *)graph)->get_thread_index()));
dg.add_string(collector_index >= 0 ? _client_data->get_collector_fullname(collector_index) : "");
dg.add_int32(x);
dg.add_int32(y);
dg.add_int32(width);
dg.add_int32(height);
dg.add_bool(minimized);
dg.add_bool(maximized);
}
}
dg.add_uint16(_piano_rolls.size());
for (PStatGraph *graph : _piano_rolls) {
int x, y, width, height;
bool minimized, maximized;
if (graph->get_window_state(x, y, width, height, minimized, maximized)) {
dg.add_string(_client_data->get_thread_name(((PStatPianoRoll *)graph)->get_thread_index()));
dg.add_int32(x);
dg.add_int32(y);
dg.add_int32(width);
dg.add_int32(height);
dg.add_bool(minimized);
dg.add_bool(maximized);
}
}
// Reserved for future graph type
dg.add_uint16(0);
dof.put_datagram(dg);
dof.close();
return true;
}
/**
* Returns true if the client is alive and connected, false otherwise.
*/
bool PStatMonitor::
is_alive() const {
if (_client_data.is_null()) {
// Not yet, but in a second probably.
return false;
}
return _client_data->is_alive();
}
/**
* Closes the client connection if it is active.
*/
void PStatMonitor::
close() {
if (!_client_data.is_null()) {
_client_data->close();
}
}
/**
* Returns the color associated with the indicated collector. If the
* collector has no associated color, or is unknown, a new color will be made
* up on the spot and associated with this collector for the rest of the
* session.
*/
const LRGBColor &PStatMonitor::
get_collector_color(int collector_index) {
Colors::iterator ci;
ci = _colors.find(collector_index);
if (ci != _colors.end()) {
return (*ci).second;
}
// Ask the client data about the color.
if (!_client_data.is_null() &&
_client_data->has_collector(collector_index)) {
const PStatCollectorDef &def =
_client_data->get_collector_def(collector_index);
LRGBColor sc(def._suggested_color.r,
def._suggested_color.g,
def._suggested_color.b);
if (sc != LRGBColor::zero()) {
ci = _colors.insert(Colors::value_type(collector_index, sc)).first;
return (*ci).second;
}
// Use the fullname of the collector as a hash to seed the random number
// generator (consulted below), so we get the same color for a given name
// across sessions.
string fullname = _client_data->get_collector_fullname(collector_index);
unsigned int hash = 0;
for (string::const_iterator ci = fullname.begin(); ci != fullname.end(); ++ci) {
hash = hash * 37 + (unsigned int)(*ci);
}
srand(hash);
}
// We didn't have a color for the collector; make one up.
LRGBColor random_color;
random_color[0] = (double)rand() / (double)RAND_MAX;
random_color[1] = (double)rand() / (double)RAND_MAX;
random_color[2] = (double)rand() / (double)RAND_MAX;
ci = _colors.insert(Colors::value_type(collector_index, random_color)).first;
return (*ci).second;
}
/**
* Sets a custom color associated with the given collector.
*/
void PStatMonitor::
set_collector_color(int collector_index, const LRGBColor &color) {
_colors[collector_index] = color;
}
/**
* Clears any custom custom color associated with the given collector.
*/
void PStatMonitor::
clear_collector_color(int collector_index) {
_colors.erase(collector_index);
}
/**
* Returns a view on the given thread index. If there is no such view already
* for the indicated thread, this will create one. This view can be used to
* examine the accumulated data for the given thread.
*/
PStatView &PStatMonitor::
get_view(int thread_index) {
Views::iterator vi;
vi = _views.find(thread_index);
if (vi == _views.end()) {
vi = _views.insert(Views::value_type(thread_index, PStatView())).first;
(*vi).second.set_thread_data(_client_data->get_thread_data(thread_index));
}
return (*vi).second;
}
/**
* Returns a view on the level value (as opposed to elapsed time) for the
* given collector over the given thread. If there is no such view already
* for the indicated thread, this will create one.
*/
PStatView &PStatMonitor::
get_level_view(int collector_index, int thread_index) {
LevelViews::iterator lvi;
lvi = _level_views.find(collector_index);
if (lvi == _level_views.end()) {
lvi = _level_views.insert(LevelViews::value_type(collector_index, Views())).first;
}
Views &views = (*lvi).second;
Views::iterator vi;
vi = views.find(thread_index);
if (vi == views.end()) {
vi = views.insert(Views::value_type(thread_index, PStatView())).first;
(*vi).second.set_thread_data(_client_data->get_thread_data(thread_index));
(*vi).second.constrain(collector_index, true);
}
return (*vi).second;
}
/**
* Called after the monitor has been fully set up. At this time, it will have
* a valid _client_data pointer, and things like is_alive() and close() will
* be meaningful. However, we may not yet know who we're connected to
* (is_client_known() may return false), and we may not know anything about
* the threads or collectors we're about to get data on.
*/
void PStatMonitor::
initialized() {
}
/**
* Called when the "hello" message has been received from the client. At this
* time, the client's hostname and program name will be known.
*/
void PStatMonitor::
got_hello() {
}
/**
* Like got_hello(), this is called when the "hello" message has been received
* from the client. At this time, the client's hostname and program name will
* be known. However, the client appears to be an incompatible version and
* the connection will be terminated; the monitor should issue a message to
* that effect.
*/
void PStatMonitor::
got_bad_version(int, int, int, int) {
}
/**
* Called whenever a new Collector definition is received from the client.
* Generally, the client will send all of its collectors over shortly after
* connecting, but there's no guarantee that they will all be received before
* the first frames are received. The monitor should be prepared to accept
* new Collector definitions midstream.
*/
void PStatMonitor::
new_collector(int) {
}
/**
* Called whenever a new Thread definition is received from the client.
* Generally, the client will send all of its threads over shortly after
* connecting, but there's no guarantee that they will all be received before
* the first frames are received. The monitor should be prepared to accept
* new Thread definitions midstream.
*/
void PStatMonitor::
new_thread(int) {
}
/**
* Called as each frame's data is made available. There is no guarantee the
* frames will arrive in order, or that all of them will arrive at all. The
* monitor should be prepared to accept frames received out-of-order or
* missing. The use of the PStatFrameData / PStatView objects to report the
* data will facilitate this.
*/
void PStatMonitor::
new_data(int thread_index, int frame_number) {
const PStatClientData *client_data = get_client_data();
// Don't bother to update the thread data until we know at least something
// about the collectors and threads.
if (client_data->get_num_collectors() != 0 &&
client_data->get_num_threads() != 0) {
PStatView &view = get_view(thread_index);
const PStatThreadData *thread_data = view.get_thread_data();
if (!thread_data->is_empty()) {
int latest = thread_data->get_latest_frame_number();
if (frame_number == latest) {
view.set_to_frame(thread_data->get_frame(frame_number));
}
}
}
}
/**
* Called when a thread should be removed from the list of threads.
*/
void PStatMonitor::
remove_thread(int) {
}
/**
* Called whenever the connection to the client has been lost. This is a
* permanent state change. The monitor should update its display to represent
* this, and may choose to close down automatically.
*/
void PStatMonitor::
lost_connection() {
}
/**
* If has_idle() returns true, this will be called periodically to allow the
* monitor to update its display or whatever it needs to do.
*/
void PStatMonitor::
idle() {
}
/**
* Should be redefined to return true if you want to redefine idle() and
* expect it to be called.
*/
bool PStatMonitor::
has_idle() {
return false;
}
/**
* Should be redefined to return true if this monitor class can handle running
* in a sub-thread.
*
* This is not related to the question of whether it can handle multiple
* different PStatThreadDatas; this is strictly a question of whether or not
* the monitor itself wants to run in a sub-thread.
*/
bool PStatMonitor::
is_thread_safe() {
return false;
}
/**
* Called when the user guide bars have been changed.
*/
void PStatMonitor::
user_guide_bars_changed() {
}
/**
* Opens a new timeline.
*/
PStatGraph *PStatMonitor::
open_timeline() {
return nullptr;
}
/**
* Opens a new strip chart showing the indicated data.
*/
PStatGraph *PStatMonitor::
open_strip_chart(int thread_index, int collector_index, bool show_level) {
return nullptr;
}
/**
* Opens a new flame graph showing the indicated data.
*/
PStatGraph *PStatMonitor::
open_flame_graph(int thread_index, int collector_index, int frame_number) {
return nullptr;
}
/**
* Opens a new piano roll showing the indicated data.
*/
PStatGraph *PStatMonitor::
open_piano_roll(int thread_index) {
return nullptr;
}
/**
* Writes the client data and open graphs to a datagram.
*/
void PStatMonitor::
write_datagram(Datagram &dg) const {
dg.add_bool(_client_known);
dg.add_string(_client_hostname);
dg.add_string(_client_progname);
dg.add_int32(_client_pid);
get_client_data()->write_datagram(dg);
dg.add_uint32((uint32_t)_colors.size());
for (const auto &item : _colors) {
dg.add_int32(item.first);
dg.add_float32(item.second[0]);
dg.add_float32(item.second[1]);
dg.add_float32(item.second[2]);
}
dg.add_uint16(_timelines.size());
for (PStatGraph *graph : _timelines) {
graph->write_datagram(dg);
}
dg.add_uint16(_strip_charts.size());
for (PStatGraph *graph : _strip_charts) {
dg.add_int16(((PStatStripChart *)graph)->get_thread_index());
dg.add_int16(((PStatStripChart *)graph)->get_collector_index());
dg.add_bool(((PStatStripChart *)graph)->get_view().get_show_level());
graph->write_datagram(dg);
}
dg.add_uint16(_flame_graphs.size());
for (PStatGraph *graph : _flame_graphs) {
dg.add_int16(((PStatFlameGraph *)graph)->get_thread_index());
dg.add_int16(((PStatFlameGraph *)graph)->get_collector_index());
graph->write_datagram(dg);
}
dg.add_uint16(_piano_rolls.size());
for (PStatGraph *graph : _piano_rolls) {
dg.add_int16(((PStatPianoRoll *)graph)->get_thread_index());
graph->write_datagram(dg);
}
// Reserved for future graph type
dg.add_uint16(0);
}
/**
* Restores the client data and open graphs from a datagram.
*/
void PStatMonitor::
read_datagram(DatagramIterator &scan) {
_client_known = scan.get_bool();
_client_hostname = scan.get_string();
_client_progname = scan.get_string();
_client_pid = scan.get_int32();
PStatClientData *client_data = new PStatClientData;
client_data->read_datagram(scan);
set_client_data(client_data);
size_t num_colors = scan.get_uint32();
for (size_t i = 0; i < num_colors; ++i) {
int key = scan.get_int32();
LRGBColor &color = _colors[key];
color[0] = scan.get_float32();
color[1] = scan.get_float32();
color[2] = scan.get_float32();
}
int num_collectors = client_data->get_num_collectors();
for (int collector_index = 0; collector_index < num_collectors; ++collector_index) {
if (client_data->has_collector(collector_index)) {
new_collector(collector_index);
}
}
int num_threads = client_data->get_num_threads();
for (int thread_index = 0; thread_index < num_threads; ++thread_index) {
if (client_data->has_thread(thread_index)) {
const PStatThreadData *thread_data = client_data->get_thread_data(thread_index);
if (!thread_data->is_empty()) {
const PStatFrameData &frame_data = thread_data->get_latest_frame();
get_view(thread_index).set_to_frame(frame_data);
int num_collectors = client_data->get_num_toplevel_collectors();
for (int i = 0; i < num_collectors; ++i) {
int collector_index = client_data->get_toplevel_collector(i);
if (client_data->has_collector(collector_index)) {
get_level_view(collector_index, thread_index).set_to_frame(thread_data->get_latest_frame());
}
}
}
new_thread(thread_index);
}
}
PStatGraph *graph;
size_t num_timelines = scan.get_uint16();
for (size_t i = 0; i < num_timelines; ++i) {
graph = open_timeline();
graph->read_datagram(scan);
}
size_t num_strip_charts = scan.get_uint16();
for (size_t i = 0; i < num_strip_charts; ++i) {
int thread_index = scan.get_int16();
int collector_index = scan.get_int16();
graph = open_strip_chart(thread_index, collector_index, scan.get_bool());
graph->read_datagram(scan);
}
size_t num_flame_graphs = scan.get_uint16();
for (size_t i = 0; i < num_flame_graphs; ++i) {
int thread_index = scan.get_int16();
int collector_index = scan.get_int16();
graph = open_flame_graph(thread_index, collector_index);
graph->read_datagram(scan);
}
size_t num_piano_rolls = scan.get_uint16();
for (size_t i = 0; i < num_piano_rolls; ++i) {
int thread_index = scan.get_int16();
graph = open_piano_roll(thread_index);
graph->read_datagram(scan);
}
}