From fa206b76edb1b710cb0585203f18721529d2ac3d Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Tue, 5 Nov 2002 02:17:56 +0000 Subject: [PATCH] Metrics! Added support for a "state of the server" report, in which managers can participate, reporting lies, damned lies and useful statistics. Initial participants include the client manager, the connection manager and the distributed object manager. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@1894 542714f4-19e9-0310-aa3c-eee0fc999fb1 --- .../presents/server/ClientManager.java | 18 +++- .../presents/server/PresentsDObjectMgr.java | 72 +++++++++++++- .../presents/server/PresentsServer.java | 98 ++++++++++++++++++- .../presents/server/net/Connection.java | 11 ++- .../server/net/ConnectionManager.java | 64 ++++++++++-- .../presents/server/net/NetEventHandler.java | 7 +- 6 files changed, 252 insertions(+), 18 deletions(-) diff --git a/src/java/com/threerings/presents/server/ClientManager.java b/src/java/com/threerings/presents/server/ClientManager.java index 18cae9f3e..06afd3abb 100644 --- a/src/java/com/threerings/presents/server/ClientManager.java +++ b/src/java/com/threerings/presents/server/ClientManager.java @@ -1,5 +1,5 @@ // -// $Id: ClientManager.java,v 1.25 2002/10/31 21:04:19 mdb Exp $ +// $Id: ClientManager.java,v 1.26 2002/11/05 02:17:56 mdb Exp $ package com.threerings.presents.server; @@ -30,7 +30,8 @@ import com.threerings.presents.server.util.SafeInterval; * going away) and from the dobjmgr thread (when clients are given the * boot for application-defined reasons). */ -public class ClientManager implements ConnectionObserver +public class ClientManager + implements ConnectionObserver, PresentsServer.Reporter { /** * Constructs a client manager that will interact with the supplied @@ -49,6 +50,8 @@ public class ClientManager implements ConnectionObserver } }, CLIENT_FLUSH_INTERVAL, null, true); + // register as a "state of server" reporter + PresentsServer.registerReporter(this); } /** @@ -325,6 +328,17 @@ public class ClientManager implements ConnectionObserver } } + // documentation inherited from interface PresentsServer.Reporter + public void appendReport (StringBuffer report, long now, long sinceLast) + { + report.append("* presents.ClientManager:\n"); + report.append("- Sessions: "); + report.append(_usermap.size()).append(" total, "); + report.append(_conmap.size()).append(" connected, "); + report.append(_penders.size()).append(" pending\n"); + report.append("- Mapped users: ").append(_objmap.size()).append("\n"); + } + /** * Called by the client instance when the client requests a logoff. * This is called from the conmgr thread. diff --git a/src/java/com/threerings/presents/server/PresentsDObjectMgr.java b/src/java/com/threerings/presents/server/PresentsDObjectMgr.java index 855901ffa..e93e95ffc 100644 --- a/src/java/com/threerings/presents/server/PresentsDObjectMgr.java +++ b/src/java/com/threerings/presents/server/PresentsDObjectMgr.java @@ -1,14 +1,16 @@ // -// $Id: PresentsDObjectMgr.java,v 1.25 2002/07/17 01:54:33 mdb Exp $ +// $Id: PresentsDObjectMgr.java,v 1.26 2002/11/05 02:17:56 mdb Exp $ package com.threerings.presents.server; import java.lang.reflect.*; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import com.samskivert.util.HashIntMap; import com.samskivert.util.Queue; +import com.samskivert.util.SortableArrayList; import com.samskivert.util.StringUtil; import com.threerings.presents.Log; @@ -27,7 +29,8 @@ import com.threerings.presents.dobj.*; * application main thread which won't return until the manager has been * requested to shut down. */ -public class PresentsDObjectMgr implements RootDObjectManager +public class PresentsDObjectMgr + implements RootDObjectManager, PresentsServer.Reporter { /** * Creates the dobjmgr and prepares it for operation. @@ -40,6 +43,9 @@ public class PresentsDObjectMgr implements RootDObjectManager dummy.setOid(0); dummy.setManager(this); _objects.put(0, new DObject()); + + // register ourselves as a state of server reporter + PresentsServer.registerReporter(this); } /** @@ -174,8 +180,8 @@ public class PresentsDObjectMgr implements RootDObjectManager // look up the target object DObject target = (DObject)_objects.get(event.getTargetOid()); if (target == null) { - Log.warning("Event target no longer exists " + - "[event=" + event + "]."); + Log.debug("Event target no longer exists " + + "[event=" + event + "]."); return; } @@ -215,6 +221,9 @@ public class PresentsDObjectMgr implements RootDObjectManager ", target=" + target + "]."); Log.logStackTrace(e); } + + // track the number of events dispatched + ++_eventCount; } /** @@ -462,6 +471,45 @@ public class PresentsDObjectMgr implements RootDObjectManager return _nextOid; } + // documentation inherited from interface PresentsServer.Reporter + public void appendReport (StringBuffer report, long now, long sinceLast) + { + report.append("* presents.PresentsDObjectMgr:\n"); + + long processed = (_eventCount - _lastEventCount); + report.append("- Events since last report: ").append(processed); + report.append("\n"); + _lastEventCount = _eventCount; + + // summarize the objects in our dobject table + HashMap ccount = new HashMap(); + SortableArrayList clist = new SortableArrayList(); + Iterator iter = _objects.values().iterator(); + while (iter.hasNext()) { + DObject obj = (DObject)iter.next(); + String clazz = obj.getClass().getName(); + int[] count = (int[])ccount.get(clazz); + if (count == null) { + count = new int[] { 0 }; + ccount.put(clazz, count); + clist.add(clazz); + } + count[0]++; + } + + // sort our list of dobject types + clist.sort(); + + report.append("- DObject count: ").append(_objects.size()); + report.append("\n"); + for (int ii = 0; ii < clist.size(); ii++) { + String clazz = (String)clist.get(ii); + int count = ((int[])ccount.get(clazz))[0]; + report.append(" ").append(clazz).append(": ").append(count); + report.append("\n"); + } + } + /** * Calls {@link Subscriber#objectAvailable} and catches and logs any * exception thrown by the subscriber during the call. @@ -696,6 +744,15 @@ public class PresentsDObjectMgr implements RootDObjectManager /** Used to assign a unique oid to each distributed object. */ protected int _nextOid = 0; + /** Used to track the number of events dispatched over time. */ + protected long _eventCount = 0; + + /** Used to track the number of events dispatched over of time. */ + protected long _lastEventCount = 0; + + /** The last time at which we generated a report. */ + protected long _lastReportStamp; + /** Used to track oid list references of distributed objects. */ protected HashIntMap _refs = new HashIntMap(); @@ -703,6 +760,13 @@ public class PresentsDObjectMgr implements RootDObjectManager * objects. */ protected AccessController _defaultController; + /** Check whether we should generate a report every 100 events. */ + protected static final long REPORT_CHECK_PERIOD = 100; + + /** Generate a report no more frequently than once per five + * minutes. */ + protected static final long REPORT_PERIOD = 5 * 60 * 1000L; + /** The default size of an oid list refs vector. */ protected static final int DEFREFVEC_SIZE = 4; diff --git a/src/java/com/threerings/presents/server/PresentsServer.java b/src/java/com/threerings/presents/server/PresentsServer.java index e8b6074d3..0186a8b7b 100644 --- a/src/java/com/threerings/presents/server/PresentsServer.java +++ b/src/java/com/threerings/presents/server/PresentsServer.java @@ -1,12 +1,18 @@ // -// $Id: PresentsServer.java,v 1.26 2002/10/21 20:56:20 mdb Exp $ +// $Id: PresentsServer.java,v 1.27 2002/11/05 02:17:56 mdb Exp $ package com.threerings.presents.server; +import java.util.ArrayList; + +import com.samskivert.util.IntervalManager; +import com.samskivert.util.StringUtil; + import com.threerings.presents.Log; import com.threerings.presents.client.Client; import com.threerings.presents.dobj.DObjectManager; import com.threerings.presents.server.net.ConnectionManager; +import com.threerings.presents.server.util.SafeInterval; import com.threerings.presents.util.Invoker; /** @@ -21,6 +27,25 @@ import com.threerings.presents.util.Invoker; */ public class PresentsServer { + /** Used to generate "state of the server" reports. See {@link + * #registerReporter}. */ + public static interface Reporter + { + /** + * Requests that this reporter append its report to the supplied + * string buffer. + * + * @param buffer the string buffer to which the report text should + * be appended. + * @param now the time at which the report generation began, in + * epoch millis. + * @param sinceLast number of milliseconds since the last time we + * generated a report. + */ + public void appendReport ( + StringBuffer buffer, long now, long sinceLast); + } + /** The manager of network connections. */ public static ConnectionManager conmgr; @@ -63,6 +88,13 @@ public class PresentsServer // initialize the time base services TimeBaseProvider.init(invmgr, omgr); + + // queue up an interval which will generate reports + IntervalManager.register(new SafeInterval(omgr) { + public void run () { + generateReport(System.currentTimeMillis()); + } + }, REPORT_INTERVAL, null, true); } /** @@ -92,6 +124,58 @@ public class PresentsServer omgr.run(); } + /** + * A report is generated by the presents server periodically in which + * server entities can participate by registering a {@link Reporter} + * with this method. + */ + public static void registerReporter (Reporter reporter) + { + _reporters.add(reporter); + } + + /** + * Generates and logs a "state of server" report. + */ + protected void generateReport (long now) + { + long sinceLast = now - _lastReportStamp; + long uptime = now - _serverStartTime; + StringBuffer report = new StringBuffer("State of server report:\n"); + + report.append("- Uptime: "); + report.append(StringUtil.intervalToString(uptime)).append(", "); + report.append(StringUtil.intervalToString(sinceLast)); + report.append(" since last report\n"); + + // report on the state of memory + Runtime rt = Runtime.getRuntime(); + long total = rt.totalMemory(), max = rt.maxMemory(); + long used = (total - rt.freeMemory()); + report.append("- Memory: ").append(used/1024).append("k used, "); + report.append(total/1024).append("k total, "); + report.append(max/1024).append("k max\n"); + + for (int ii = 0; ii < _reporters.size(); ii++) { + Reporter rptr = (Reporter)_reporters.get(ii); + try { + rptr.appendReport(report, now, sinceLast); + } catch (Throwable t) { + Log.warning("Reporter choked [rptr=" + rptr + "]."); + Log.logStackTrace(t); + } + } + + // strip off the final newline + int blen = report.length(); + if (report.charAt(blen-1) == '\n') { + report.delete(blen-1, blen); + } + + Log.info(report.toString()); + _lastReportStamp = now; + } + /** * Requests that the server shut down. */ @@ -137,4 +221,16 @@ public class PresentsServer System.exit(-1); } } + + /** The time at which the server was started. */ + protected long _serverStartTime = System.currentTimeMillis(); + + /** The last time at which {@link #generateReport} was run. */ + protected long _lastReportStamp = _serverStartTime; + + /** Used to generate "state of server" reports. */ + protected static ArrayList _reporters = new ArrayList(); + + /** The frequency with which we generate "state of server" reports. */ + protected static final long REPORT_INTERVAL = 5 * 60 * 1000L; } diff --git a/src/java/com/threerings/presents/server/net/Connection.java b/src/java/com/threerings/presents/server/net/Connection.java index 2e8a37bdd..7a0325471 100644 --- a/src/java/com/threerings/presents/server/net/Connection.java +++ b/src/java/com/threerings/presents/server/net/Connection.java @@ -1,5 +1,5 @@ // -// $Id: Connection.java,v 1.10 2002/10/29 23:51:26 mdb Exp $ +// $Id: Connection.java,v 1.11 2002/11/05 02:17:56 mdb Exp $ package com.threerings.presents.server.net; @@ -213,11 +213,12 @@ public abstract class Connection implements NetEventHandler /** * Called when our client socket has data available for reading. */ - public void handleEvent (long when, Selectable source, short events) + public int handleEvent (long when, Selectable source, short events) { // make a note that we received an event as of this time _lastEvent = when; + int bytesIn = 0; try { // we're lazy about creating our input streams because we may // be inheriting them from our authing connection and we don't @@ -229,6 +230,10 @@ public abstract class Connection implements NetEventHandler // read the available data and see if we have a whole frame if (_fin.readFrame(_in)) { + // make a note of how many bytes are in this frame + // (including the frame length bytes which aren't reported + // in available()) + bytesIn = _fin.available() + 4; // parse the message and pass it on UpstreamMessage msg = (UpstreamMessage)_oin.readObject(); // Log.info("Read message " + msg + "."); @@ -252,6 +257,8 @@ public abstract class Connection implements NetEventHandler // deal with the failure handleFailure(ioe); } + + return bytesIn; } // documentation inherited from interface diff --git a/src/java/com/threerings/presents/server/net/ConnectionManager.java b/src/java/com/threerings/presents/server/net/ConnectionManager.java index 86dd9d35c..4c0823299 100644 --- a/src/java/com/threerings/presents/server/net/ConnectionManager.java +++ b/src/java/com/threerings/presents/server/net/ConnectionManager.java @@ -1,5 +1,5 @@ // -// $Id: ConnectionManager.java,v 1.23 2002/10/29 23:51:26 mdb Exp $ +// $Id: ConnectionManager.java,v 1.24 2002/11/05 02:17:56 mdb Exp $ package com.threerings.presents.server.net; @@ -31,6 +31,7 @@ import com.threerings.presents.server.PresentsServer; * mechanism rather than via threads. */ public class ConnectionManager extends LoopingThread + implements PresentsServer.Reporter { /** * Constructs and initialized a connection manager (binding the socket @@ -42,6 +43,9 @@ public class ConnectionManager extends LoopingThread _port = port; _selset = new SelectSet(); + // register as a "state of server" reporter + PresentsServer.registerReporter(this); + try { // create our listening socket and add it to the select set _listener = new NonblockingServerSocket(_port); @@ -54,9 +58,11 @@ public class ConnectionManager extends LoopingThread _litem = new SelectItem(_listener, Selectable.ACCEPT_READY); // when an ACCEPT_READY event happens, we do this: _litem.obj = new NetEventHandler() { - public void handleEvent ( - long when, Selectable item, short events) { + public int handleEvent (long when, Selectable item, short events) { acceptConnection(); + // there's no easy way to measure bytes read when + // accepting a connection, so we claim nothing + return 0; } public void checkIdle (long now) { // we're never idle @@ -131,6 +137,32 @@ public class ConnectionManager extends LoopingThread _authq.append(conn); } + // documentation inherited from interface PresentsServer.Reporter + public void appendReport (StringBuffer report, long now, long sinceLast) + { + long bytesIn, bytesOut, msgsIn, msgsOut; + synchronized (this) { + bytesIn = _bytesIn; _bytesIn = 0L; + bytesOut = _bytesOut; _bytesOut = 0L; + msgsIn = _msgsIn; _msgsIn = 0; + msgsOut = _msgsOut; _msgsOut = 0; + } + + report.append("* presents.net.ConnectionManager:\n"); + report.append("- Network input: "); + report.append(bytesIn).append(" bytes, "); + report.append(msgsIn).append(" msgs, "); + long avgIn = (msgsIn == 0) ? 0 : (bytesIn/msgsIn); + report.append(avgIn).append(" avg size, "); + report.append(bytesIn*1000/sinceLast).append(" bps\n"); + report.append("- Network output: "); + report.append(bytesOut).append(" bytes, "); + report.append(msgsOut).append(" msgs, "); + long avgOut = (msgsOut == 0) ? 0 : (bytesOut/msgsOut); + report.append(avgOut).append(" avg size, "); + report.append(bytesOut*1000/sinceLast).append(" bps\n"); + } + /** * Notifies the connection observers of a connection event. Used * internally. @@ -217,7 +249,11 @@ public class ConnectionManager extends LoopingThread oout.flush(); // then write framed message to real output stream - _framer.writeFrameAndReset(conn.getOutputStream()); + int out = _framer.writeFrameAndReset(conn.getOutputStream()); + synchronized (this) { + _bytesOut += out; + _msgsOut++; + } } catch (IOException ioe) { // instruct the connection to deal with its failure @@ -266,8 +302,17 @@ public class ConnectionManager extends LoopingThread try { SelectItem item = active[i]; NetEventHandler handler = (NetEventHandler)item.obj; - handler.handleEvent( + int got = handler.handleEvent( iterStamp, item.getSelectable(), item.revents); + if (got != 0) { + synchronized (this) { + _bytesIn += got; + // we know that the handlers only report having + // read bytes when they have a whole message, so + // we can count thusly + _msgsIn++; + } + } } catch (Exception e) { Log.warning("Error processing network data."); @@ -377,14 +422,19 @@ public class ConnectionManager extends LoopingThread protected SelectItem _litem; protected Queue _deathq = new Queue(); + protected Queue _authq = new Queue(); protected Queue _outq = new Queue(); protected FramingOutputStream _framer; - protected Queue _authq = new Queue(); - protected ArrayList _observers = new ArrayList(); + /** Bytes in and out in the last reporting period. */ + protected long _bytesIn, _bytesOut; + + /** Messages read and written in the last reporting period. */ + protected int _msgsIn, _msgsOut; + /** * How long we wait for network events before checking our running * flag to see if we should still be running. diff --git a/src/java/com/threerings/presents/server/net/NetEventHandler.java b/src/java/com/threerings/presents/server/net/NetEventHandler.java index 0929f6342..96b232a58 100644 --- a/src/java/com/threerings/presents/server/net/NetEventHandler.java +++ b/src/java/com/threerings/presents/server/net/NetEventHandler.java @@ -1,5 +1,5 @@ // -// $Id: NetEventHandler.java,v 1.4 2002/10/29 23:51:26 mdb Exp $ +// $Id: NetEventHandler.java,v 1.5 2002/11/05 02:17:56 mdb Exp $ package com.threerings.presents.server.net; @@ -22,8 +22,11 @@ public interface NetEventHandler * Called when a network event has occurred on the supplied source. * The events parameter indicates which event or events * have occurred. + * + * @return the number of bytes read from the network as a result of + * handling this event. */ - public void handleEvent (long when, Selectable source, short events); + public int handleEvent (long when, Selectable source, short events); /** * Called to ensure that this selectable has not been idle for longer