MediaWiki master
Pingback.php
Go to the documentation of this file.
1<?php
7namespace MediaWiki\Installer;
8
14use Psr\Log\LoggerInterface;
19use Wikimedia\Timestamp\ConvertibleTimestamp;
20use Wikimedia\Timestamp\TimestampFormat as TS;
21
35class Pingback {
36
40 private const LEGACY_EVENTLOGGING_SCHEMA = 'MediaWikiPingback';
41
48 private const EVENT_PLATFORM_SCHEMA_ID = '/analytics/legacy/mediawikipingback/1.0.0';
49
60 private const EVENT_PLATFORM_STREAM = 'eventlogging_MediaWikiPingback';
61
63 private const EVENT_PLATFORM_EVENT_INTAKE_SERVICE_URI =
64 'https://intake-analytics.wikimedia.org/v1/events?hasty=true';
65
67 protected $logger;
69 protected $config;
71 protected $dbProvider;
73 protected $cache;
75 protected $http;
77 protected $key;
79 protected $eventIntakeUri;
80 private ILockManager $lockManager;
81
89 public function __construct(
94 LoggerInterface $logger,
95 ILockManager $lockManager,
96 string $eventIntakeUrl = self::EVENT_PLATFORM_EVENT_INTAKE_SERVICE_URI
97 ) {
98 $this->config = $config;
99 $this->dbProvider = $dbProvider;
100 $this->cache = $cache;
101 $this->http = $http;
102 $this->logger = $logger;
103 $this->key = 'Pingback-' . MW_VERSION;
104 $this->eventIntakeUri = $eventIntakeUrl;
105 $this->lockManager = $lockManager;
106 }
107
114 public function run(): void {
115 if ( !$this->config->get( MainConfigNames::Pingback ) ) {
116 // disabled
117 return;
118 }
119 if ( $this->wasRecentlySent() ) {
120 // already sent recently
121 return;
122 }
123 if ( !$this->acquireLock() ) {
124 $this->logger->debug( __METHOD__ . ": couldn't acquire lock" );
125 return;
126 }
127
128 $data = $this->getData();
129 if ( !$this->postPingback( $data ) ) {
130 $this->logger->warning( __METHOD__ . ": failed to send; check 'http' log channel" );
131 return;
132 }
133
134 // Record the fact that we have sent a pingback for this MediaWiki version,
135 // so we don't submit data multiple times.
136 $dbw = $this->dbProvider->getPrimaryDatabase();
137 $timestamp = ConvertibleTimestamp::time();
138 $dbw->newInsertQueryBuilder()
139 ->insertInto( 'updatelog' )
140 ->row( [ 'ul_key' => $this->key, 'ul_value' => $timestamp ] )
141 ->onDuplicateKeyUpdate()
142 ->uniqueIndexFields( [ 'ul_key' ] )
143 ->set( [ 'ul_value' => $timestamp ] )
144 ->caller( __METHOD__ )->execute();
145 $this->logger->debug( __METHOD__ . ": pingback sent OK ({$this->key})" );
146 }
147
151 private function wasRecentlySent(): bool {
152 $timestamp = $this->dbProvider->getReplicaDatabase()->newSelectQueryBuilder()
153 ->select( 'ul_value' )
154 ->from( 'updatelog' )
155 ->where( [ 'ul_key' => $this->key ] )
156 ->caller( __METHOD__ )->fetchField();
157 if ( $timestamp === false ) {
158 return false;
159 }
160 // send heartbeat ping if the last ping was over a month ago
161 if ( ConvertibleTimestamp::time() - (int)$timestamp > 60 * 60 * 24 * 30 ) {
162 return false;
163 }
164 return true;
165 }
166
175 private function acquireLock(): bool {
176 $cacheKey = $this->cache->makeKey( 'pingback', $this->key );
177 if ( !$this->cache->add( $cacheKey, 1, $this->cache::TTL_HOUR ) ) {
178 // throttled
179 return false;
180 }
181
182 if ( !$this->lockManager->lockKey( $this->key ) ) {
183 // already in progress
184 return false;
185 }
186
187 return true;
188 }
189
199 protected function getData(): array {
200 $wiki = $this->fetchOrInsertId();
201
202 return [
203 'event' => self::getSystemInfo( $this->config ),
204 'schema' => self::LEGACY_EVENTLOGGING_SCHEMA,
205 'wiki' => $wiki,
206
207 // This would be added by
208 // https://gerrit.wikimedia.org/g/mediawiki/extensions/EventLogging/+/d47dbc10455bcb6dbc98a49fa169f75d6131c3da/includes/EventLogging.php#274
209 // onwards.
210 '$schema' => self::EVENT_PLATFORM_SCHEMA_ID,
211 'client_dt' => ConvertibleTimestamp::now( TS::ISO_8601 ),
212
213 // This would be added by
214 // https://gerrit.wikimedia.org/r/plugins/gitiles/mediawiki/extensions/EventLogging/+/d47dbc10455bcb6dbc98a49fa169f75d6131c3da/includes/EventSubmitter/EventBusEventSubmitter.php#81
215 // onwards.
216 'meta' => [
217 'stream' => self::EVENT_PLATFORM_STREAM,
218 ],
219 ];
220 }
221
234 public static function getSystemInfo( Config $config ): array {
235 $event = [
236 'database' => $config->get( MainConfigNames::DBtype ),
237 'MediaWiki' => MW_VERSION,
238 'PHP' => PHP_VERSION,
239 'OS' => PHP_OS . ' ' . php_uname( 'r' ),
240 'arch' => PHP_INT_SIZE === 8 ? 64 : 32,
241 'machine' => php_uname( 'm' ),
242 ];
243
244 if ( isset( $_SERVER['SERVER_SOFTWARE'] ) ) {
245 $event['serverSoftware'] = $_SERVER['SERVER_SOFTWARE'];
246 }
247
248 $limit = ini_get( 'memory_limit' );
249 if ( $limit && $limit !== "-1" ) {
250 $event['memoryLimit'] = $limit;
251 }
252
253 return $event;
254 }
255
265 private function fetchOrInsertId(): string {
266 // We've already obtained a primary connection for the lock, and plan to do a write.
267 // But, still prefer reading this immutable value from a replica to reduce load.
268 $id = $this->dbProvider->getReplicaDatabase()->newSelectQueryBuilder()
269 ->select( 'ul_value' )
270 ->from( 'updatelog' )
271 ->where( [ 'ul_key' => 'PingBack' ] )
272 ->caller( __METHOD__ )->fetchField();
273 if ( $id !== false ) {
274 return $id;
275 }
276
277 $dbw = $this->dbProvider->getPrimaryDatabase();
278 $id = $dbw->newSelectQueryBuilder()
279 ->select( 'ul_value' )
280 ->from( 'updatelog' )
281 ->where( [ 'ul_key' => 'PingBack' ] )
282 ->caller( __METHOD__ )->fetchField();
283 if ( $id !== false ) {
284 return $id;
285 }
286
287 $id = MWCryptRand::generateHex( 32 );
288 $dbw->newInsertQueryBuilder()
289 ->insertInto( 'updatelog' )
290 ->row( [ 'ul_key' => 'PingBack', 'ul_value' => $id ] )
291 ->caller( __METHOD__ )->execute();
292 return $id;
293 }
294
308 private function postPingback( array $data ): bool {
309 $request = $this->http->create( $this->eventIntakeUri, [
310 'method' => 'POST',
311 'postData' => FormatJson::encode( $data ),
312 ], __METHOD__ );
313 $request->setHeader( 'Content-Type', 'application/json' );
314
315 $result = $request->execute();
316
317 return $result->isGood();
318 }
319}
320
322class_alias( Pingback::class, 'Pingback' );
const MW_VERSION
The running version of MediaWiki.
Definition Defines.php:23
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:71
Factory creating MWHttpRequest objects.
Send information about this MediaWiki instance to mediawiki.org.
Definition Pingback.php:35
run()
Maybe send a ping.
Definition Pingback.php:114
__construct(Config $config, IConnectionProvider $dbProvider, BagOStuff $cache, HttpRequestFactory $http, LoggerInterface $logger, ILockManager $lockManager, string $eventIntakeUrl=self::EVENT_PLATFORM_EVENT_INTAKE_SERVICE_URI)
Definition Pingback.php:89
LoggerInterface $logger
Definition Pingback.php:67
getData()
Get the event to be sent to the server.
Definition Pingback.php:199
IConnectionProvider $dbProvider
Definition Pingback.php:71
HttpRequestFactory $http
Definition Pingback.php:75
string $key
updatelog key (also used as cache/db lock key)
Definition Pingback.php:77
static getSystemInfo(Config $config)
Collect basic data about this MediaWiki installation and return it as an associative array conforming...
Definition Pingback.php:234
JSON formatter wrapper class.
A class containing constants representing the names of configuration variables.
A cryptographic random generator class used for generating secret keys.
Abstract class for any ephemeral data store.
Definition BagOStuff.php:73
Database error base class.
Definition DBError.php:22
Interface for configuration instances.
Definition Config.php:18
Interface representing MediaWiki's LockManager.
Provide primary and replica IDatabase connections.