499 lines
13 KiB
C++
499 lines
13 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 pStatClientImpl.cxx
|
|
* @author drose
|
|
* @date 2004-12-23
|
|
*/
|
|
|
|
#include "pStatClientImpl.h"
|
|
|
|
// This file only defines anything if DO_PSTATS is defined.
|
|
#ifdef DO_PSTATS
|
|
|
|
#include "pStatClient.h"
|
|
#include "pStatClientControlMessage.h"
|
|
#include "pStatServerControlMessage.h"
|
|
#include "pStatCollector.h"
|
|
#include "pStatThread.h"
|
|
#include "config_pstats.h"
|
|
#include "pStatProperties.h"
|
|
#include "cmath.h"
|
|
|
|
#include <algorithm>
|
|
|
|
#if defined(WIN32_VC) || defined(WIN64_VC)
|
|
#include <Winsock2.h>
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
/**
|
|
*
|
|
*/
|
|
PStatClientImpl::
|
|
PStatClientImpl(PStatClient *client) :
|
|
_clock(TrueClock::get_global_ptr()),
|
|
_delta(0.0),
|
|
_last_frame(0.0),
|
|
_client(client),
|
|
_reader(this, 0),
|
|
_writer(this, pstats_threaded_write ? 1 : 0)
|
|
{
|
|
_writer.set_max_queue_size(pstats_max_queue_size);
|
|
_reader.set_tcp_header_size(4);
|
|
_writer.set_tcp_header_size(4);
|
|
_is_connected = false;
|
|
_got_udp_port = false;
|
|
_collectors_reported = 0;
|
|
_threads_reported = 0;
|
|
|
|
_client_name = pstats_name;
|
|
_max_rate = pstats_max_rate;
|
|
|
|
_tcp_count = 1;
|
|
_udp_count = 1;
|
|
|
|
if (pstats_tcp_ratio >= 1.0f) {
|
|
_tcp_count_factor = 0.0f;
|
|
_udp_count_factor = 1.0f;
|
|
|
|
} else if (pstats_tcp_ratio <= 0.0f) {
|
|
_tcp_count_factor = 1.0f;
|
|
_udp_count_factor = 0.0f;
|
|
|
|
} else {
|
|
csincos(pstats_tcp_ratio * (3.14159265f / 2.0f),
|
|
&_udp_count_factor,
|
|
&_tcp_count_factor);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
PStatClientImpl::
|
|
~PStatClientImpl() {
|
|
nassertv(!_is_connected);
|
|
}
|
|
|
|
/**
|
|
* Called only by PStatClient::client_connect().
|
|
*/
|
|
bool PStatClientImpl::
|
|
client_connect(string hostname, int port) {
|
|
nassertr(!_is_connected, true);
|
|
|
|
if (hostname.empty()) {
|
|
hostname = pstats_host;
|
|
}
|
|
if (port < 0) {
|
|
port = pstats_port;
|
|
}
|
|
|
|
if (!_server.set_host(hostname, port)) {
|
|
pstats_cat.error()
|
|
<< "Unknown host: " << hostname << "\n";
|
|
return false;
|
|
}
|
|
|
|
_tcp_connection = open_TCP_client_connection(_server, 5000);
|
|
|
|
if (_tcp_connection.is_null()) {
|
|
pstats_cat.error()
|
|
<< "Couldn't connect to PStatServer at " << hostname << ":"
|
|
<< port << "\n";
|
|
return false;
|
|
}
|
|
// Make sure we're not queuing up multiple TCP sockets--we expect immediate
|
|
// writes of our TCP datagrams.
|
|
_tcp_connection->set_collect_tcp(false);
|
|
|
|
_reader.add_connection(_tcp_connection);
|
|
_is_connected = true;
|
|
|
|
_udp_connection = open_UDP_connection();
|
|
|
|
send_hello();
|
|
|
|
#ifdef DEBUG_THREADS
|
|
MutexDebug::increment_pstats();
|
|
#endif // DEBUG_THREADS
|
|
|
|
return _is_connected;
|
|
}
|
|
|
|
/**
|
|
* Called only by PStatClient::client_disconnect().
|
|
*/
|
|
void PStatClientImpl::
|
|
client_disconnect() {
|
|
if (_is_connected) {
|
|
#ifdef DEBUG_THREADS
|
|
MutexDebug::decrement_pstats();
|
|
#endif // DEBUG_THREADS
|
|
_reader.remove_connection(_tcp_connection);
|
|
close_connection(_tcp_connection);
|
|
close_connection(_udp_connection);
|
|
}
|
|
|
|
_tcp_connection.clear();
|
|
_udp_connection.clear();
|
|
|
|
_is_connected = false;
|
|
_got_udp_port = false;
|
|
|
|
_collectors_reported = 0;
|
|
_threads_reported = 0;
|
|
}
|
|
|
|
/**
|
|
* Called by the PStatThread interface at the beginning of every frame, for
|
|
* each thread. This resets the clocks for the new frame and transmits the
|
|
* data for the previous frame.
|
|
*/
|
|
void PStatClientImpl::
|
|
new_frame(int thread_index) {
|
|
nassertv(thread_index >= 0 && thread_index < _client->_num_threads);
|
|
|
|
PStatClient::InternalThread *pthread = _client->get_thread_ptr(thread_index);
|
|
|
|
// If we're the main thread, we should exchange control packets with the
|
|
// server.
|
|
if (thread_index == 0) {
|
|
transmit_control_data();
|
|
}
|
|
|
|
// If we've got the UDP port by the time the frame starts, it's time to
|
|
// become active and start actually tracking data.
|
|
if (_got_udp_port) {
|
|
pthread->_is_active = true;
|
|
}
|
|
|
|
if (!pthread->_is_active) {
|
|
return;
|
|
}
|
|
|
|
double frame_start = get_real_time();
|
|
int frame_number = -1;
|
|
PStatFrameData frame_data;
|
|
|
|
if (!pthread->_frame_data.is_empty()) {
|
|
// Collector 0 is the whole frame.
|
|
_client->stop(0, thread_index, frame_start);
|
|
|
|
// Fill up the level data for all the collectors who have level data for
|
|
// this pthread.
|
|
int num_collectors = _client->_num_collectors;
|
|
PStatClient::CollectorPointer *collectors =
|
|
(PStatClient::CollectorPointer *)_client->_collectors;
|
|
for (int i = 0; i < num_collectors; i++) {
|
|
const PStatClient::PerThreadData &ptd =
|
|
collectors[i]->_per_thread[thread_index];
|
|
if (ptd._has_level) {
|
|
pthread->_frame_data.add_level(i, ptd._level);
|
|
}
|
|
}
|
|
pthread->_frame_data.swap(frame_data);
|
|
frame_number = pthread->_frame_number;
|
|
}
|
|
|
|
pthread->_frame_data.clear();
|
|
pthread->_frame_number++;
|
|
_client->start(0, thread_index, frame_start);
|
|
|
|
// Also record the time for the PStats operation itself.
|
|
int current_thread_index = Thread::get_current_thread()->get_pstats_index();
|
|
int pstats_index = PStatClient::_pstats_pcollector.get_index();
|
|
_client->start(pstats_index, current_thread_index, frame_start);
|
|
|
|
if (frame_number != -1) {
|
|
transmit_frame_data(thread_index, frame_number, frame_data);
|
|
}
|
|
_client->stop(pstats_index, current_thread_index, get_real_time());
|
|
}
|
|
|
|
/**
|
|
* Slightly lower-level interface than new_frame that takes a set of frame
|
|
* data.
|
|
*/
|
|
void PStatClientImpl::
|
|
add_frame(int thread_index, const PStatFrameData &frame_data) {
|
|
nassertv(thread_index >= 0 && thread_index < _client->_num_threads);
|
|
|
|
PStatClient::InternalThread *pthread = _client->get_thread_ptr(thread_index);
|
|
|
|
// If we're the main thread, we should exchange control packets with the
|
|
// server.
|
|
if (thread_index == 0) {
|
|
transmit_control_data();
|
|
}
|
|
|
|
// If we've got the UDP port by the time the frame starts, it's time to
|
|
// become active and start actually tracking data.
|
|
if (_got_udp_port) {
|
|
pthread->_is_active = true;
|
|
}
|
|
|
|
if (!pthread->_is_active) {
|
|
return;
|
|
}
|
|
|
|
int frame_number = pthread->_frame_number++;
|
|
|
|
// Also record the time for the PStats operation itself.
|
|
int current_thread_index = Thread::get_current_thread()->get_pstats_index();
|
|
int pstats_index = PStatClient::_pstats_pcollector.get_index();
|
|
_client->start(pstats_index, current_thread_index);
|
|
|
|
if (frame_number != -1) {
|
|
transmit_frame_data(thread_index, frame_number, frame_data);
|
|
}
|
|
_client->stop(pstats_index, current_thread_index);
|
|
}
|
|
|
|
/**
|
|
* Should be called once per frame per thread to transmit the latest data to
|
|
* the PStatServer.
|
|
*/
|
|
void PStatClientImpl::
|
|
transmit_frame_data(int thread_index, int frame_number,
|
|
const PStatFrameData &frame_data) {
|
|
nassertv(thread_index >= 0 && thread_index < _client->_num_threads);
|
|
PStatClient::InternalThread *thread = _client->get_thread_ptr(thread_index);
|
|
if (_is_connected && thread->_is_active) {
|
|
|
|
// We don't want to send too many packets in a hurry and flood the server.
|
|
// Check that enough time has elapsed for us to send a new packet. If
|
|
// not, we'll drop this packet on the floor and send a new one next time
|
|
// around.
|
|
double now = get_real_time();
|
|
if (now >= thread->_next_packet) {
|
|
// We don't want to send more than _max_rate UDP-size packets per
|
|
// second, per thread.
|
|
double packet_delay = 1.0 / _max_rate;
|
|
|
|
// Send new data.
|
|
NetDatagram datagram;
|
|
// We always start with a zero byte, to differentiate it from a control
|
|
// message.
|
|
datagram.add_uint8(0);
|
|
|
|
datagram.add_uint16(thread_index);
|
|
datagram.add_uint32(frame_number);
|
|
|
|
bool sent;
|
|
|
|
if (!frame_data.write_datagram(datagram, _client)) {
|
|
// Too many events to fit in a single datagram. Maybe it was a long
|
|
// frame load or something. Just drop the datagram.
|
|
sent = false;
|
|
|
|
} else if (_writer.is_valid_for_udp(datagram)) {
|
|
if (_udp_count * _udp_count_factor < _tcp_count * _tcp_count_factor) {
|
|
// Send this one as a UDP packet.
|
|
nassertv(_got_udp_port);
|
|
sent = _writer.send(datagram, _udp_connection, _server);
|
|
_udp_count++;
|
|
|
|
if (_udp_count == 0) {
|
|
// Wraparound!
|
|
_udp_count = 1;
|
|
_tcp_count = 1;
|
|
}
|
|
|
|
} else {
|
|
// Send this one as a TCP packet.
|
|
sent = _writer.send(datagram, _tcp_connection);
|
|
_tcp_count++;
|
|
|
|
if (_tcp_count == 0) {
|
|
// Wraparound!
|
|
_udp_count = 1;
|
|
_tcp_count = 1;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
sent = _writer.send(datagram, _tcp_connection);
|
|
// If our packets are so large that we must ship them via TCP, then
|
|
// artificially slow down the packet rate even further.
|
|
int packet_ratio =
|
|
(datagram.get_length() + maximum_udp_datagram - 1) /
|
|
maximum_udp_datagram;
|
|
packet_delay *= (double)packet_ratio;
|
|
}
|
|
|
|
thread->_next_packet = now + packet_delay;
|
|
|
|
if (!sent) {
|
|
if (pstats_cat.is_debug()) {
|
|
pstats_cat.debug()
|
|
<< "Couldn't send packet.\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Should be called once a frame to exchange control information with the
|
|
* server.
|
|
*/
|
|
void PStatClientImpl::
|
|
transmit_control_data() {
|
|
// Check for new messages from the server.
|
|
while (_is_connected && _reader.data_available()) {
|
|
NetDatagram datagram;
|
|
|
|
if (_reader.get_data(datagram)) {
|
|
PStatServerControlMessage message;
|
|
if (message.decode(datagram)) {
|
|
handle_server_control_message(message);
|
|
|
|
} else {
|
|
pstats_cat.error()
|
|
<< "Got unexpected message from server.\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_is_connected) {
|
|
report_new_collectors();
|
|
report_new_threads();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the current machine's hostname.
|
|
*/
|
|
string PStatClientImpl::
|
|
get_hostname() {
|
|
if (_hostname.empty()) {
|
|
char temp_buff[1024];
|
|
if (gethostname(temp_buff, 1024) == 0) {
|
|
_hostname = temp_buff;
|
|
} else {
|
|
_hostname = "unknown";
|
|
}
|
|
}
|
|
return _hostname;
|
|
}
|
|
|
|
/**
|
|
* Sends the initial greeting message to the server.
|
|
*/
|
|
void PStatClientImpl::
|
|
send_hello() {
|
|
nassertv(_is_connected);
|
|
|
|
PStatClientControlMessage message;
|
|
message._type = PStatClientControlMessage::T_hello;
|
|
message._client_hostname = get_hostname();
|
|
message._client_progname = _client_name;
|
|
message._major_version = get_current_pstat_major_version();
|
|
message._minor_version = get_current_pstat_minor_version();
|
|
|
|
Datagram datagram;
|
|
message.encode(datagram);
|
|
_writer.send(datagram, _tcp_connection, true);
|
|
}
|
|
|
|
/**
|
|
* Sends over any information about new Collectors that the user code might
|
|
* have recently created.
|
|
*/
|
|
void PStatClientImpl::
|
|
report_new_collectors() {
|
|
// Empirically, we determined that you can't send more than about 1400
|
|
// collectors at once without exceeding the 64K limit on a single datagram.
|
|
// So we limit ourselves here to sending only half that many.
|
|
static const int max_collectors_at_once = 700;
|
|
|
|
while (_is_connected && _collectors_reported < _client->_num_collectors) {
|
|
PStatClientControlMessage message;
|
|
message._type = PStatClientControlMessage::T_define_collectors;
|
|
int i = 0;
|
|
while (_collectors_reported < _client->_num_collectors &&
|
|
i < max_collectors_at_once) {
|
|
message._collectors.push_back(_client->get_collector_def(_collectors_reported));
|
|
_collectors_reported++;
|
|
i++;
|
|
}
|
|
|
|
Datagram datagram;
|
|
message.encode(datagram);
|
|
_writer.send(datagram, _tcp_connection, true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sends over any information about new Threads that the user code might have
|
|
* recently created.
|
|
*/
|
|
void PStatClientImpl::
|
|
report_new_threads() {
|
|
while (_is_connected && _threads_reported < _client->_num_threads) {
|
|
PStatClientControlMessage message;
|
|
message._type = PStatClientControlMessage::T_define_threads;
|
|
message._first_thread_index = _threads_reported;
|
|
PStatClient::ThreadPointer *threads =
|
|
(PStatClient::ThreadPointer *)_client->_threads;
|
|
while (_threads_reported < _client->_num_threads) {
|
|
message._names.push_back(threads[_threads_reported]->_name);
|
|
_threads_reported++;
|
|
}
|
|
|
|
Datagram datagram;
|
|
message.encode(datagram);
|
|
_writer.send(datagram, _tcp_connection, true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called when a control message has been received by the server over the TCP
|
|
* connection.
|
|
*/
|
|
void PStatClientImpl::
|
|
handle_server_control_message(const PStatServerControlMessage &message) {
|
|
switch (message._type) {
|
|
case PStatServerControlMessage::T_hello:
|
|
pstats_cat.info()
|
|
<< "Connected to " << message._server_progname << " on "
|
|
<< message._server_hostname << "\n";
|
|
|
|
_server.set_port(message._udp_port);
|
|
_got_udp_port = true;
|
|
break;
|
|
|
|
default:
|
|
pstats_cat.error()
|
|
<< "Invalid control message received from server.\n";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called by the internal net code when the connection has been lost.
|
|
*/
|
|
void PStatClientImpl::
|
|
connection_reset(const PT(Connection) &connection, bool) {
|
|
if (connection == _tcp_connection) {
|
|
client_disconnect();
|
|
} else if (connection == _udp_connection) {
|
|
pstats_cat.warning()
|
|
<< "Trouble sending UDP; switching to TCP only.\n";
|
|
_tcp_count_factor = 0.0f;
|
|
_udp_count_factor = 1.0f;
|
|
} else {
|
|
pstats_cat.warning()
|
|
<< "Ignoring spurious connection_reset() message\n";
|
|
}
|
|
}
|
|
|
|
#endif // DO_PSTATS
|