diff --git a/xwords4/android/jni/utilwrapper.c b/xwords4/android/jni/utilwrapper.c index 79061e28f..29a28553a 100644 --- a/xwords4/android/jni/utilwrapper.c +++ b/xwords4/android/jni/utilwrapper.c @@ -1092,7 +1092,7 @@ makeDUtil( MPFORMAL JNIEnv* env, { AndDUtil* dutil = (AndDUtil*)XP_CALLOC( mpool, sizeof(*dutil) ); XW_DUtilCtxt* super = &dutil->dutil; - dutil_super_init( MPPARM(mpool) super, env ); + dutil_super_init( MPPARM(mpool) super ); #ifdef MAP_THREAD_TO_ENV dutil->ti = ti; #endif diff --git a/xwords4/common/comtypes.h b/xwords4/common/comtypes.h index 7920b4dc7..432592199 100644 --- a/xwords4/common/comtypes.h +++ b/xwords4/common/comtypes.h @@ -259,6 +259,7 @@ typedef enum _TileValueType { #define SUFFIX_LEGAL_PHONIES "legalPhonies" #define SUFFIX_MQTT_DEVID "mqtt_devid_key" #define SUFFIX_KNOWN_PLAYERS "known_players_key_dev1" +#define SUFFIX_STATS "stats_key_dev1" #define FULL_KEY(PARTIAL) "persist_key:" PARTIAL @@ -268,6 +269,7 @@ typedef enum _TileValueType { #define KEY_LEGAL_PHONIES FULL_KEY(SUFFIX_LEGAL_PHONIES) #define MQTT_DEVID_KEY FULL_KEY(SUFFIX_MQTT_DEVID) #define KNOWN_PLAYERS_KEY FULL_KEY(SUFFIX_KNOWN_PLAYERS) +#define STATS_KEY FULL_KEY(SUFFIX_STATS) /* I need a way to communiate prefs to common/ code. For now, though, I'll * leave storage of these values up to the platforms. First, because I don't diff --git a/xwords4/common/device.c b/xwords4/common/device.c index c5bd3553f..194cd1601 100644 --- a/xwords4/common/device.c +++ b/xwords4/common/device.c @@ -41,7 +41,7 @@ #define PD_VERSION_1 2 -static XWStreamCtxt* +XWStreamCtxt* mkStream( XW_DUtilCtxt* dutil ) { XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(dutil->mpool) diff --git a/xwords4/common/device.h b/xwords4/common/device.h index 464f20717..f9f8a0bce 100644 --- a/xwords4/common/device.h +++ b/xwords4/common/device.h @@ -81,4 +81,7 @@ void dvc_getPhoniesFor( XW_DUtilCtxt* dutil, XWEnv env, const XP_UCHAR* code, void dvc_init( XW_DUtilCtxt* dutil, XWEnv xwe ); void dvc_cleanup( XW_DUtilCtxt* dutil, XWEnv xwe ); +/* Utility functions; not an API to count on!! */ +XWStreamCtxt* mkStream( XW_DUtilCtxt* dutil ); + #endif diff --git a/xwords4/common/dutil.c b/xwords4/common/dutil.c index 1edcd512b..e8b95105f 100644 --- a/xwords4/common/dutil.c +++ b/xwords4/common/dutil.c @@ -55,7 +55,7 @@ super_dutil_loadStream( XW_DUtilCtxt* duc, XWEnv xwe, } void -dutil_super_init( MPFORMAL XW_DUtilCtxt* dutil, XWEnv xwe ) +dutil_super_init( MPFORMAL XW_DUtilCtxt* dutil ) { #ifdef XWFEATURE_KNOWNPLAYERS pthread_mutex_init( &dutil->kpMutex, NULL ); @@ -66,13 +66,13 @@ dutil_super_init( MPFORMAL XW_DUtilCtxt* dutil, XWEnv xwe ) SET_VTABLE_ENTRY( &dutil->vtable, dutil_loadStream, super ); SET_VTABLE_ENTRY( &dutil->vtable, dutil_storeStream, super ); - sts_init( dutil, xwe ); + sts_init( dutil ); } void dutil_super_cleanup( XW_DUtilCtxt* dutil, XWEnv xwe ) { kplr_cleanup( dutil ); - sts_cleanup( dutil ); + sts_cleanup( dutil, xwe ); dvc_cleanup( dutil, xwe ); } diff --git a/xwords4/common/dutil.h b/xwords4/common/dutil.h index 70b7f08e3..d750a6ccf 100644 --- a/xwords4/common/dutil.h +++ b/xwords4/common/dutil.h @@ -129,7 +129,7 @@ struct XW_DUtilCtxt { MPSLOT }; -void dutil_super_init( MPFORMAL XW_DUtilCtxt* dutil, XWEnv xwe ); +void dutil_super_init( MPFORMAL XW_DUtilCtxt* dutil ); void dutil_super_cleanup( XW_DUtilCtxt* dutil, XWEnv xwe ); /* This one cheats: direct access */ @@ -184,7 +184,7 @@ void dutil_super_cleanup( XW_DUtilCtxt* dutil, XWEnv xwe ); #define dutil_notifyPause( duc, e, id, ip, p, n, m ) \ (duc)->vtable.m_dutil_notifyPause( (duc), (e), (id), (ip), (p), (n), (m) ) -#define dutil_haveGame( duc, xwe, gameID,channel ) \ +#define dutil_haveGame( duc, xwe, gameID, channel ) \ (duc)->vtable.m_dutil_haveGame( (duc), (xwe), (gameID), (channel) ) #define dutil_onDupTimerChanged(duc, e, id, ov, nv) \ diff --git a/xwords4/common/stats.c b/xwords4/common/stats.c index 7961821a0..47f582af3 100644 --- a/xwords4/common/stats.c +++ b/xwords4/common/stats.c @@ -18,30 +18,70 @@ */ #include "stats.h" +#include "xwmutex.h" +#include "device.h" +#include "xwstream.h" +#include "strutils.h" typedef struct StatsState { - // XP_U32 stats[STS_KEY_COUNT]; + XP_U32* statsVals; + pthread_mutex_t mutex; } StatsState; +static const XP_UCHAR* STATtoStr(STAT stat); +static XP_U32* loadCounts( XW_DUtilCtxt* dutil, XWEnv xwe ); +static void storeCounts( XW_DUtilCtxt* dutil, XWEnv xwe ); + void -sts_init( XW_DUtilCtxt* duc, XWEnv XP_UNUSED(xwe) ) +sts_init( XW_DUtilCtxt* dutil ) { - LOG_FUNC(); - StatsState* ss = XP_CALLOC( duc->mpool, sizeof(*ss) ); - duc->statsState = ss; + StatsState* ss = XP_CALLOC( dutil->mpool, sizeof(*ss) ); + initMutex( &ss->mutex, XP_TRUE ); + dutil->statsState = ss; } void -sts_cleanup( XW_DUtilCtxt* dutil ) +sts_cleanup( XW_DUtilCtxt* dutil, XWEnv xwe ) { - XP_USE( dutil ); - XP_FREEP( dutil->mpool, &dutil->statsState ); + StatsState* ss = dutil->statsState; + storeCounts( dutil, xwe ); + XP_ASSERT( !!ss ); + XP_FREEP( dutil->mpool, &ss->statsVals ); + XP_FREEP( dutil->mpool, &ss ); +} + +void +sts_increment( XW_DUtilCtxt* dutil, STAT stat, XWEnv xwe ) +{ + StatsState* ss = dutil->statsState; + XP_ASSERT( !!ss ); + WITH_MUTEX( &ss->mutex ); + if ( !ss->statsVals ) { + ss->statsVals = loadCounts( dutil, xwe ); + } + ++ss->statsVals[stat]; + END_WITH_MUTEX(); } cJSON* -sts_export( XW_DUtilCtxt* XP_UNUSED(dutil), XWEnv XP_UNUSED(xwe) ) +sts_export( XW_DUtilCtxt* dutil, XWEnv xwe ) { + StatsState* ss = dutil->statsState; + XP_ASSERT( !!ss ); cJSON* result = cJSON_CreateObject(); + + WITH_MUTEX( &ss->mutex ); + if ( !ss->statsVals ) { + ss->statsVals = loadCounts( dutil, xwe ); + } + for ( int ii = 0; ii < STAT_NSTATS; ++ii ) { + XP_U32 val = ss->statsVals[ii]; + if ( 0 != val ) { + const XP_UCHAR* nam = STATtoStr(ii); + cJSON_AddNumberToObject(result, nam, val); + } + } + END_WITH_MUTEX(); return result; } @@ -56,3 +96,66 @@ void sts_clearAll( XW_DUtilCtxt* XP_UNUSED(dutil), XWEnv XP_UNUSED(xwe) ) { } + +static const XP_UCHAR* +STATtoStr(STAT stat) +{ +#define CASESTR(s) case (s): return #s + switch (stat) { + CASESTR(STAT_MQTT_RCVD); + CASESTR(STAT_MQTT_SENT); + default: + XP_ASSERT(0); + } +#undef CASESTR + return NULL; +} + +static void +storeCounts( XW_DUtilCtxt* dutil, XWEnv xwe ) +{ + StatsState* ss = dutil->statsState; + XP_ASSERT( !!ss ); + + XWStreamCtxt* stream = mkStream( dutil ); + stream_putU8( stream, 0 ); /* version */ + + WITH_MUTEX( &ss->mutex ); + if ( !!ss->statsVals ) { + for ( int ii = 0; ii < STAT_NSTATS; ++ii ) { + XP_U32 val = ss->statsVals[ii]; + if ( 0 != val ) { + stream_putU8( stream, ii ); + stream_putU32VL( stream, val ); + } + } + } + END_WITH_MUTEX(); + + const XP_UCHAR* keys[] = { STATS_KEY, NULL }; + dutil_storeStream( dutil, xwe, keys, stream ); + stream_destroy( stream ); +} + +static XP_U32* +loadCounts( XW_DUtilCtxt* dutil, XWEnv xwe ) +{ + XWStreamCtxt* stream = mkStream( dutil ); + const XP_UCHAR* keys[] = { STATS_KEY, NULL }; + dutil_loadStream( dutil, xwe, keys, stream ); + + XP_U32* statsVals + = XP_CALLOC( dutil->mpool, sizeof(*statsVals) * STAT_NSTATS ); + + XP_U8 version; + if ( stream_gotU8( stream, &version ) ) { + XP_U8 stat; + while ( stream_gotU8( stream, &stat ) ) { + XP_U32 value = stream_getU32VL( stream ); + statsVals[stat] = value; + } + } + stream_destroy( stream ); + return statsVals; +} + diff --git a/xwords4/common/stats.h b/xwords4/common/stats.h index 862825851..d546a8185 100644 --- a/xwords4/common/stats.h +++ b/xwords4/common/stats.h @@ -22,8 +22,18 @@ #include "dutil.h" -void sts_init( XW_DUtilCtxt* duc, XWEnv xwe ); -void sts_cleanup( XW_DUtilCtxt* dutil ); +void sts_init( XW_DUtilCtxt* duc ); +void sts_cleanup( XW_DUtilCtxt* dutil, XWEnv xwe ); + +typedef enum { + STAT_MQTT_RCVD, + STAT_MQTT_SENT, + + + STAT_NSTATS, +} STAT; + +void sts_increment( XW_DUtilCtxt* dutil, STAT stat, XWEnv xwe ); cJSON* sts_export( XW_DUtilCtxt* duc, XWEnv xwe ); void sts_clearAll( XW_DUtilCtxt* duc, XWEnv xwe ); diff --git a/xwords4/linux/extcmds.c b/xwords4/linux/extcmds.c index 07caea406..4411fbbec 100644 --- a/xwords4/linux/extcmds.c +++ b/xwords4/linux/extcmds.c @@ -27,6 +27,7 @@ #include "linuxmain.h" #include "gamesdb.h" #include "dbgutil.h" +#include "stats.h" static XP_U32 castGid( cJSON* obj ) @@ -131,6 +132,14 @@ moveifFromArgs( CmdWrapper* wr, cJSON* args ) return (*wr->procs.makeMoveIf)( wr->closure, gameID, tryTrade ); } +static cJSON* +getStats( CmdWrapper* wr ) +{ + XW_DUtilCtxt* dutil = wr->params->dutil; + cJSON* json = sts_export( dutil, NULL_XWE ); + return json; +} + static XP_Bool chatFromArgs( CmdWrapper* wr, cJSON* args ) { @@ -418,6 +427,12 @@ on_incoming_signal( GSocketService* XP_UNUSED(service), if ( success ) { addGIDToObject( &response, newGameID, "newGid" ); } + } else if ( 0 == strcmp( cmdStr, "stats" ) ) { + cJSON* stats = getStats( wr ); + success = !!stats; + if ( success ) { + addObjectToObject( &response, "stats", stats ); + } } else if ( 0 == strcmp( cmdStr, "getStates" ) ) { cJSON* gids; cJSON* orders; diff --git a/xwords4/linux/lindutil.c b/xwords4/linux/lindutil.c index 4159e4735..a466b1ebc 100644 --- a/xwords4/linux/lindutil.c +++ b/xwords4/linux/lindutil.c @@ -402,8 +402,6 @@ linux_dutils_init( MPFORMAL VTableMgr* vtMgr, void* closure ) XW_DUtilCtxt* super = &lduc->super; - dutil_super_init( MPPARM(mpool) super, NULL_XWE ); - super->vtMgr = vtMgr; super->closure = closure; @@ -445,6 +443,8 @@ linux_dutils_init( MPFORMAL VTableMgr* vtMgr, void* closure ) # undef SET_PROC + dutil_super_init( MPPARM(mpool) super ); + assertTableFull( &super->vtable, sizeof(super->vtable), "lindutil" ); return super; diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index 63e09101f..3f49b43ed 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -2488,14 +2488,15 @@ testPhonies( LaunchParams* params ) static void freeParams( LaunchParams* params ) { - gdb_close( params->pDb ); - params->pDb = NULL; - - vtmgr_destroy( MPPARM(params->mpool) params->vtMgr ); linux_dutils_free( ¶ms->dutil ); + vtmgr_destroy( MPPARM(params->mpool) params->vtMgr ); dmgr_destroy( params->dictMgr, NULL_XWE ); gi_disposePlayerInfo( MPPARM(params->mpool) ¶ms->pgi ); + + gdb_close( params->pDb ); + params->pDb = NULL; + mpool_destroy( params->mpool ); } diff --git a/xwords4/linux/mqttcon.c b/xwords4/linux/mqttcon.c index 63cb5bbcb..99355a8ed 100644 --- a/xwords4/linux/mqttcon.c +++ b/xwords4/linux/mqttcon.c @@ -24,6 +24,7 @@ #include "gsrcwrap.h" #include "device.h" #include "strutils.h" +#include "stats.h" typedef struct _MQTTConStorage { LaunchParams* params; @@ -60,6 +61,7 @@ sendQueueHead( MQTTConStorage* storage ) XP_LOGFF( "mosquitto_publish(topic=%s, msgLen=%d) => %s; mid=%d", elem->topic, elem->len, mosquitto_strerror(err), elem->mid ); /* Remove this so all are resent together? */ + sts_increment( storage->params->dutil, STAT_MQTT_SENT, NULL_XWE ); break; } } @@ -293,9 +295,11 @@ log_callback( struct mosquitto *mosq, void *userdata, int level, static gboolean handle_gotmsg( GIOChannel* source, GIOCondition XP_UNUSED(condition), gpointer data ) { + MQTTConStorage* storage = (MQTTConStorage*)data; // XP_LOGFF( "(len=%d)", message->payloadlen ); LOG_FUNC(); - MQTTConStorage* storage = (MQTTConStorage*)data; + XW_DUtilCtxt* dutil = storage->params->dutil; + sts_increment( dutil, STAT_MQTT_RCVD, NULL_XWE ); int pipe = g_io_channel_unix_get_fd( source ); XP_ASSERT( pipe == storage->msgPipe[0] ); diff --git a/xwords4/linux/scripts/netGamesTest.py b/xwords4/linux/scripts/netGamesTest.py index 6568bcb02..1b410a626 100755 --- a/xwords4/linux/scripts/netGamesTest.py +++ b/xwords4/linux/scripts/netGamesTest.py @@ -527,7 +527,12 @@ class Device(): if game and not game.haveOrder(): orders.append(gid) - response = self._sendWaitReply('getStates', gids=gids, orders=orders) + # PENDING. Don't print this, but include in summary on exit + response = self._sendWaitReply('stats') + print('stats => {}'.format(response)) + + response = self \ + ._sendWaitReply('getStates', gids=gids, orders=orders) for order in response.get('orders', []): gid = order.get('gid')