Skip to content

Commit

Permalink
Refactor user stats collector
Browse files Browse the repository at this point in the history
  • Loading branch information
t-wright committed Sep 4, 2023
1 parent 2b607b5 commit 8f844ca
Showing 1 changed file with 48 additions and 43 deletions.
91 changes: 48 additions & 43 deletions prometheus-collectors/class-user-stats-collector.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ class User_Stats_Collector implements CollectorInterface {
private Gauge $role_gauge;

private string $blog_id;
private const TFA_STATUS_ENABLED = 'enabled';
private const TFA_STATUS_DISABLED = 'disabled';
private const TFA_STATUS_DEFAULT = 'unknown';
private const METRIC_OPTION = 'vip-prom-users';
private const TFA_STATUS_ENABLED = 'enabled';
private const TFA_STATUS_DISABLED = 'disabled';
private const TFA_STATUS_DEFAULT = 'unknown';
private const METRIC_OPTION = 'vip-prom-users';
private const MAX_USERS_FOR_COUNT = 1_000_000;
private const MAX_USERS_FOR_2FA_STATS = 10_000;

public function initialize( RegistryInterface $registry ): void {
$this->blog_id = Plugin::get_instance()->get_site_label();
Expand All @@ -32,19 +34,20 @@ public function initialize( RegistryInterface $registry ): void {

public function collect_metrics(): void {
$metrics = get_option( self::METRIC_OPTION, [] );

if ( ! $metrics ) {
return;
}

foreach ( $metrics as $role => $counts ) {
foreach ( $counts['two-factor'] as $status => $count ) {
foreach ( $counts as $status => $count ) {
$this->role_gauge->set( $count, [ $this->blog_id, $role, $status ] );
}
}
}

public function process_metrics(): void {
global $wpdb;

if ( ! Context::is_wp_cli() ) {
return;
}
Expand All @@ -54,57 +57,59 @@ public function process_metrics(): void {
return;
}

// Limit this to sites without a huge number of users for now
if ( wp_is_large_user_count() ) {
// count_users() is intensive so skip collecting this metric if there are too many users
if ( get_user_count() > self::MAX_USERS_FOR_COUNT ) {
return;
}

// Order is not important to us and the query is faster without it
add_action( 'pre_user_query', [ $this, 'remove_user_query_orderby' ] );
// Get user count by role
$user_counts = count_users();

$users_per_page = 500;
$user_counts = [];
$current_page = 1;
$two_factor_minimum_cap = 'edit_posts';
// Get the count of users with 2FA meta to make sure we're not querying too many users with `get_users()`.
// This includes users on any network site, not just the current site.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
$two_factor_user_count = $wpdb->get_var(
$wpdb->prepare( "SELECT COUNT(user_id) FROM {$wpdb->usermeta} WHERE meta_key = %s", \Two_Factor_Core::ENABLED_PROVIDERS_USER_META_KEY )
);

do {
$args = [
'number' => $users_per_page,
'paged' => $current_page,
'count_total' => false,
$include_2fa_stats = $two_factor_user_count < self::MAX_USERS_FOR_2FA_STATS;

// Prepare the default response
foreach ( $user_counts['avail_roles'] as $role => $value ) {
$ret[ $role ] = [
$include_2fa_stats ? self::TFA_STATUS_DISABLED : self::TFA_STATUS_DEFAULT => $value,
];
}

$users = get_users( $args );
// If there aren't too many users with the 2FA meta, include 2FA stats
if ( $include_2fa_stats ) {
// Order is not important to us and the query is faster without the order by clause
add_action( 'pre_user_query', [ $this, 'remove_user_query_orderby' ] );

foreach ( $users as $user ) {
$two_factor_status = self::TFA_STATUS_DEFAULT;
$users = get_users( [
'count_total' => false,
'number' => -1,
'meta_key' => \Two_Factor_Core::ENABLED_PROVIDERS_USER_META_KEY,
] );

// Only get 2fa status for users with the specified cap
if ( $user->has_cap( $two_factor_minimum_cap ) ) {
$two_factor_status = \Two_Factor_Core::is_user_using_two_factor( $user->ID ) ? self::TFA_STATUS_ENABLED : self::TFA_STATUS_DISABLED;
}
foreach ( $users as $user ) {
// Double-check that 2FA is enabled for this user (the meta could be set to an invalid provider)
$two_factor_enabled = \Two_Factor_Core::is_user_using_two_factor( $user->ID );

foreach ( $user->roles as $role ) {
$user_counts[ $role ] ??= [
'total' => 0,
'two-factor' => [
self::TFA_STATUS_ENABLED => 0,
self::TFA_STATUS_DISABLED => 0,
self::TFA_STATUS_DEFAULT => 0,
],
];
$user_counts[ $role ]['total']++;
$user_counts[ $role ]['two-factor'][ $two_factor_status ]++;
if ( $two_factor_enabled ) {
$ret[ $role ][ self::TFA_STATUS_ENABLED ] ??= 0;
$ret[ $role ][ self::TFA_STATUS_ENABLED ]++;

if ( isset( $ret[ $role ][ self::TFA_STATUS_DISABLED ] ) ) {
$ret[ $role ][ self::TFA_STATUS_DISABLED ]--;
}
}
}
}
}

$current_page++;

// Clear the object cache to avoid memory issues
vip_reset_local_object_cache();
} while ( ! empty( $users ) );

update_option( self::METRIC_OPTION, $user_counts, false );
update_option( self::METRIC_OPTION, $ret, false );
}

public function remove_user_query_orderby( $user_query ) {
Expand Down

0 comments on commit 8f844ca

Please sign in to comment.