Faster NodeInfo stats

master
Diogo Cordeiro 5 years ago
parent 6ee5c3eb6d
commit e38ce23cb7

@ -8,33 +8,173 @@ class NodeinfoPlugin extends Plugin
{
const VERSION = '0.0.1';
function onRouterInitialized($m)
public function onRouterInitialized($m)
{
$m->connect(
'.well-known/nodeinfo', array(
'action' => 'nodeinfojrd'
)
'.well-known/nodeinfo',
array(
'action' => 'nodeinfojrd'
)
);
$m->connect(
'main/nodeinfo/2.0', array(
'action' => 'nodeinfo_2_0'
)
'main/nodeinfo/2.0',
array(
'action' => 'nodeinfo_2_0'
)
);
return true;
}
function onPluginVersion(array &$versions)
/**
* Make sure necessary tables are filled out.
*
* @return boolean hook true
*/
public function onCheckSchema()
{
$versions[] = array('name' => 'Nodeinfo',
'version' => self::VERSION,
'author' => 'chimo',
'homepage' => 'https://github.com/chimo/gs-nodeinfo',
'description' =>
// Ensure schema
$schema = Schema::get();
$schema->ensureTable('usage_stats', Usage_stats::schemaDef());
// Ensure default rows
if (Usage_stats::getKV('type', 'users') == null) {
$us = new Usage_stats();
$us->type = 'users';
$us->insert();
}
if (Usage_stats::getKV('type', 'posts') == null) {
$us = new Usage_stats();
$us->type = 'posts';
$us->insert();
}
if (Usage_stats::getKV('type', 'comments') == null) {
$us = new Usage_stats();
$us->type = 'comments';
$us->insert();
}
return true;
}
/**
* Increment notices/replies counter
*
* @return boolean hook flag
* @author Diogo Cordeiro <diogo@fc.up.pt>
*/
public function onStartNoticeDistribute($notice)
{
assert($notice->id > 0); // Ignore if not a valid notice
$profile = $notice->getProfile();
if (!$profile->isLocal()) {
return true;
}
// Ignore for activity/non-post-verb notices
if (method_exists('ActivityUtils', 'compareVerbs')) {
$is_post_verb = ActivityUtils::compareVerbs(
$notice->verb,
[ActivityVerb::POST]
);
} else {
$is_post_verb = ($notice->verb == ActivityVerb::POST ? true : false);
}
if ($notice->source == 'activity' || !$is_post_verb) {
return true;
}
// Is a reply?
if ($notice->reply_to) {
$us = Usage_stats::getKV('type', 'comments');
$us->count += 1;
$us->update();
return true;
}
// Is an Announce?
if ($notice->isRepeat()) {
return true;
}
$us = Usage_stats::getKV('type', 'posts');
$us->count += 1;
$us->update();
// That was it
return true;
}
/**
* Decrement notices/replies counter
*
* @return boolean hook flag
* @author Diogo Cordeiro <diogo@fc.up.pt>
*/
public function onStartDeleteOwnNotice($user, $notice)
{
$profile = $user->getProfile();
// Only count local notices
if (!$profile->isLocal()) {
return true;
}
if ($notice->reply_to) {
$us = Usage_stats::getKV('type', 'comments');
$us->count -= 1;
$us->update();
return true;
}
$us = Usage_stats::getKV('type', 'posts');
$us->count -= 1;
$us->update();
return true;
}
/**
* Increment users counter
*
* @return boolean hook flag
* @author Diogo Cordeiro <diogo@fc.up.pt>
*/
public function onEndRegistrationTry()
{
$us = Usage_stats::getKV('type', 'users');
$us->count += 1;
$us->update();
return true;
}
/**
* Decrement users counter
*
* @return boolean hook flag
* @author Diogo Cordeiro <diogo@fc.up.pt>
*/
public function onEndDeleteUser()
{
$us = Usage_stats::getKV('type', 'users');
$us->count -= 1;
$us->update();
return true;
}
public function onPluginVersion(array &$versions)
{
$versions[] = ['name' => 'Nodeinfo',
'version' => self::VERSION,
'author' => 'chimo',
'homepage' => 'https://github.com/chimo/gs-nodeinfo',
'description' =>
// TRANS: Plugin description.
_m('')); // TODO
_m('Plugin that presents basic instance information using the NodeInfo standard.')]; // TODO
return true;
}
}

@ -17,14 +17,14 @@ class Nodeinfo_2_0Action extends ApiAction
$this->showNodeInfo();
}
function getActivePluginList()
public function getActivePluginList()
{
$pluginversions = array();
$plugins = array();
Event::handle('PluginVersion', array(&$pluginversions));
foreach($pluginversions as $plugin) {
foreach ($pluginversions as $plugin) {
$plugins[strtolower($plugin['name'])] = 1;
}
@ -36,18 +36,58 @@ class Nodeinfo_2_0Action extends ApiAction
* but GNU social doesn't keep track of when users last logged in, so let's return
* the number of users that 'posted at least once', I guess.
*/
function getActiveUsers($days)
public function showNodeInfo()
{
$notices = new Notice();
$notices->joinAdd(array('profile_id', 'user:id'));
$notices->whereAdd('notice.created >= NOW() - INTERVAL ' . $days . ' DAY');
$openRegistrations = $this->getRegistrationsStatus();
$userCount = $this->getUserCount();
$postCount = $this->getPostCount();
$commentCount = $this->getCommentCount();
$activeUsersCount = $notices->count('distinct profile_id');
$usersActiveHalfyear = $this->getActiveUsers(180);
$usersActiveMonth = $this->getActiveUsers(30);
return $activeUsersCount;
$protocols = $this->getProtocols();
$inboundServices = $this->getInboundServices();
$outboundServices = $this->getOutboundServices();
$json = json_encode([
'version' => '2.0',
'software' => [
'name' => 'gnusocial',
'version' => GNUSOCIAL_VERSION
],
'protocols' => $protocols,
// TODO: Have plugins register services
'services' => [
'inbound' => $inboundServices,
'outbound' => $outboundServices
],
'openRegistrations' => $openRegistrations,
'usage' => [
'users' => [
'total' => $userCount,
'activeHalfyear' => $usersActiveHalfyear,
'activeMonth' => $usersActiveMonth
],
'localPosts' => $postCount,
'localComments' => $commentCount
],
'metadata' => new stdClass()
]);
$this->initDocument('json');
print $json;
$this->endDocument('json');
}
function getRegistrationsStatus()
public function getRegistrationsStatus()
{
$areRegistrationsClosed = (common_config('site', 'closed')) ? true : false;
$isSiteInviteOnly = (common_config('site', 'inviteonly')) ? true : false;
@ -55,58 +95,61 @@ class Nodeinfo_2_0Action extends ApiAction
return !($areRegistrationsClosed || $isSiteInviteOnly);
}
function getUserCount()
public function getUserCount()
{
$users = new User();
$userCount = $users->count();
$users = new Usage_stats();
$userCount = $users->getUserCount();
return $userCount;
}
function getPostCount()
public function getPostCount()
{
$notices = new Notice();
$notices->is_local = Notice::LOCAL_PUBLIC;
$notices->whereAdd('reply_to IS NULL');
$noticeCount = $notices->count();
$posts = new Usage_stats();
$postCount = $posts->getPostCount();
return $noticeCount;
return $postCount;
}
function getCommentCount()
public function getCommentCount()
{
$notices = new Notice();
$notices->is_local = Notice::LOCAL_PUBLIC;
$notices->whereAdd('reply_to IS NOT NULL');
$commentCount = $notices->count();
$comments = new Usage_stats();
$commentCount = $comments->getCommentCount();
return $commentCount;
}
function getProtocols()
public function getActiveUsers($days)
{
$notices = new Notice();
$notices->joinAdd(array('profile_id', 'user:id'));
$notices->whereAdd('notice.created >= NOW() - INTERVAL ' . $days . ' DAY');
$activeUsersCount = $notices->count('distinct profile_id');
return $activeUsersCount;
}
public function getProtocols()
{
$oStatusEnabled = array_key_exists('ostatus', $this->plugins);
$xmppEnabled = (array_key_exists('xmpp', $this->plugins) && common_config('xmpp', 'enabled')) ? true : false;
$protocols = array();
if (Event::handle('StartNodeInfoProtocols', array(&$protocols))) {
// Until the OStatus and XMPP plugins handle this themselves,
// try to figure out if they're enabled ourselves.
if ($oStatusEnabled) {
$protocols[] = 'ostatus';
}
if ($xmppEnabled) {
$protocols[] = 'xmpp';
}
}
Event::handle('EndNodeInfoProtocols', array(&$protocols));
return $protocols;
}
function getInboundServices()
public function getInboundServices()
{
// FIXME: Are those always on?
$inboundServices = array('atom1.0', 'rss2.0');
@ -122,7 +165,7 @@ class Nodeinfo_2_0Action extends ApiAction
return $inboundServices;
}
function getOutboundServices()
public function getOutboundServices()
{
$xmppEnabled = (array_key_exists('xmpp', $this->plugins) && common_config('xmpp', 'enabled')) ? true : false;
@ -143,55 +186,4 @@ class Nodeinfo_2_0Action extends ApiAction
return $outboundServices;
}
function showNodeInfo()
{
$openRegistrations = $this->getRegistrationsStatus();
$userCount = $this->getUserCount();
$postCount = $this->getPostCount();
$commentCount = $this->getCommentCount();
$usersActiveHalfyear = $this->getActiveUsers(180);
$usersActiveMonth = $this->getActiveUsers(30);
$protocols = $this->getProtocols();
$inboundServices = $this->getInboundServices();
$outboundServices = $this->getOutboundServices();
$json = json_encode([
'version' => '2.0',
'software' => [
'name' => 'gnusocial',
'version' => GNUSOCIAL_VERSION
],
'protocols' => $protocols,
// TODO: Have plugins register services
'services' => [
'inbound' => $inboundServices,
'outbound' => $outboundServices
],
'openRegistrations' => $openRegistrations,
'usage' => [
'users' => [
'total' => $userCount,
'activeHalfyear' => $usersActiveHalfyear,
'activeMonth' => $usersActiveMonth
],
'localPosts' => $postCount,
'localComments' => $commentCount
],
'metadata' => new stdClass()
]);
$this->initDocument('json');
print $json;
$this->endDocument('json');
}
}

@ -12,8 +12,6 @@ class NodeinfoJRDAction extends XrdAction
protected function setXRD()
{
$this->xrd->links[] = new XML_XRD_Element_link(self::NODEINFO_2_0_REL, common_local_url('nodeinfo_2_0'));
$this->xrd->links[] = new XML_XRD_Element_link(self::NODEINFO_2_0_REL, common_local_url('nodeinfo_2_0'));
}
}

@ -0,0 +1,73 @@
<?php
/**
* GNU social - a federating social network
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
if (!defined('GNUSOCIAL')) {
exit(1);
}
/**
* Table Definition for Usage_stats
*/
class Usage_stats extends Managed_DataObject
{
###START_AUTOCODE
/* the code below is auto generated do not remove the above tag */
public $__table = 'usage_stats'; // table name
public $type; // varchar(191) unique_key not 255 because utf8mb4 takes more space
public $count; // int(4)
public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
public static function schemaDef()
{
return [
'description' => 'node stats',
'fields' => [
'type' => ['type' => 'varchar', 'length' => 191, 'description' => 'Type of countable entity'],
'count' => ['type' => 'int', 'size' => 'int', 'default' => 0, 'description' => 'Number of entities of this type'],
'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'],
],
'primary key' => ['type'],
'unique keys' => [
'usage_stats_key' => ['type'],
],
'indexes' => [
'user_stats_idx' => ['type'],
],
];
}
public function getUserCount()
{
return intval(Usage_stats::getKV('type', 'users')->count);
}
public function getPostCount()
{
return intval(Usage_stats::getKV('type', 'posts')->count);
}
public function getCommentCount()
{
return intval(Usage_stats::getKV('type', 'comments')->count);
}
}

@ -0,0 +1,117 @@
#!/usr/bin/env php
<?php
/**
* GNU social - a federating social network
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Plugin
* @package GNUsocial
* @copyright 2018 Free Software Foundation http://fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link https://www.gnu.org/software/social/
*/
define('INSTALLDIR', realpath(__DIR__ . '/../../..'));
$longoptions = ['type='];
$helptext = <<<END_OF_HELP
fix_stats.php [options]
Counts the stats from database values and updates the table.
--type Optional flag to specify the type to update. They all are updated by default.
Type: can be 'users', 'posts' or 'comments'. Or 'all', to update all the types.
END_OF_HELP;
require_once INSTALLDIR . '/scripts/commandline.inc';
$valid_types = ['all', 'users', 'posts', 'comments'];
$verbose = have_option('v', 'verbose');
$type_to_fix = get_option_value('type');
if (!in_array($type_to_fix, $valid_types)) {
echo "You must provide a valid type!\n\n";
show_help();
exit(1);
}
if ($verbose) {
echo "Started.\n\n";
}
if ($type_to_fix == 'all' || $type_to_fix == 'users') {
if ($verbose) {
echo "[+] Updating Users stats...\n";
}
$us = Usage_stats::getKV('users');
$us->count = getUserCount();
$us->update();
}
if ($type_to_fix == 'all' || $type_to_fix == 'posts') {
if ($verbose) {
echo "[+] Updating Posts stats...\n";
}
$us = Usage_stats::getKV('posts');
$us->count = getPostCount();
$us->update();
}
if ($type_to_fix == 'all' || $type_to_fix == 'comments') {
if ($verbose) {
echo "[+] Updating Comments stats...\n";
}
$us = Usage_stats::getKV('comments');
$us->count = getCommentCount();
$us->update();
}
if ($verbose) {
echo "\nDONE.\n";
}
/*
* Counting functions
*/
function getUserCount()
{
$users = new User();
$userCount = $users->count();
return $userCount;
}
function getPostCount()
{
$notices = new Notice();
$notices->is_local = Notice::LOCAL_PUBLIC;
$notices->whereAdd('reply_to IS NULL');
$noticeCount = $notices->count();
return $noticeCount;
}
function getCommentCount()
{
$notices = new Notice();
$notices->is_local = Notice::LOCAL_PUBLIC;
$notices->whereAdd('reply_to IS NOT NULL');
$commentCount = $notices->count();
return $commentCount;
}