<?php /** * Vibes shared memory * * Handles all shared memory operations. * * @package Features * @author Pierre Lannoy <https://pierre.lannoy.fr/>. * @since 2.0.0 */ namespace Vibes\Plugin\Feature; use Vibes\System\Blog; use Vibes\System\Option; use Vibes\System\Database; use Vibes\System\Http; use Vibes\System\Favicon; use Vibes\System\Cache; use Vibes\System\GeoIP; use Vibes\System\Environment; use Vibes\System\SharedMemory; use malkusch\lock\mutex\FlockMutex; use Vibes\System\WebVitals; /** * Define the shared memory functionality. * * Handles all shared memory operations. * * @package Features * @author Pierre Lannoy <https://pierre.lannoy.fr/>. * @since 2.0.0 */ class Memory { /** * Messages buffer. * * @since 2.0.0 * @var array $statistics The statistics buffer. */ private static $messages_buffer = []; /** * The buffer size. * * @since 2.0.0 * @var integer $buffer The number of messages in buffer. */ private static $buffer = 4000; /** * The read index. * * @since 2.0.0 * @var string $index The index for data. */ private static $index = ''; /** * Initialize the class and set its properties. * * @since 2.0.0 */ public function __construct() { } /** * Initialize static properties and hooks. * * @since 2.0.0 */ public static function init() { add_action( 'shutdown', [ 'Vibes\Plugin\Feature\Memory', 'write' ], DECALOG_MAX_SHUTDOWN_PRIORITY, 0 ); add_action( 'shutdown', [ 'Vibes\Plugin\Feature\Memory', 'collate_metrics' ], DECALOG_MAX_SHUTDOWN_PRIORITY - 1, 0 ); } /** * Verify if auto-logging is enabled. * * @since 2.0.0 */ public static function is_enabled() { return Option::network_get( 'livelog' ); } /** * Write all buffers to shared memory. * * @since 2.0.0 */ public static function write() { if ( self::is_enabled() ) { self::write_records_to_memory(); } } /** * Get relevant ftok. * * @since 2.0.0 */ private static function ftok() { if ( 1 === Environment::exec_mode() ) { return ftok( __FILE__, 'c' ); } else { return ftok( __FILE__, 'w' ); } } /** * Effectively write the message buffer to shared memory. * * @since 2.0.0 */ private static function write_records_to_memory() { $messages = self::$messages_buffer; // phpcs:ignore $mutex = new FlockMutex( fopen( __FILE__, 'r' ), 1 ); $ftok = self::ftok(); $mutex->synchronized( function () use ( $messages, $ftok ) { $sm = new SharedMemory( $ftok ); $data = $sm->read(); foreach ( $messages as $key => $message ) { if ( is_array( $message ) ) { $data[ $key ] = $message; } } $data = array_slice( $data, -self::$buffer ); if ( false === $sm->write( $data ) ) { //error_log( 'ERROR' ); } } ); } /** * Read the current records. * * @return array The current records, ordered. * @since 2.0.0 */ public static function read(): array { try { // phpcs:ignore $mutex = new FlockMutex( fopen( __FILE__, 'r' ), 1 ); $ftok = ftok( __FILE__, 'w' ); $data1 = $mutex->synchronized( function () use ( $ftok ) { $log = new SharedMemory( $ftok ); $data = $log->read(); return $data; } ); $ftok = ftok( __FILE__, 'c' ); $data2 = $mutex->synchronized( function () use ( $ftok ) { $log = new SharedMemory( $ftok ); $data = $log->read(); return $data; } ); $data = array_merge( $data1, $data2 ); uksort( $data, 'strcmp' ); } catch ( \Throwable $e ) { $data = []; } $result = []; foreach ( $data as $key => $line ) { if ( 0 < strcmp( $key, self::$index ) ) { $result[ $key ] = $line; self::$index = $key; } } return $result; } /** * Store statistics in buffer. * * @param array $record The record to bufferize. * @since 2.0.0 */ public static function store_statistics( $record ) { $date = new \DateTime(); $record['timestamp'] = $date->format( 'H:i:s.u' ); self::$messages_buffer[ $date->format( 'YmdHisu' ) ] = $record; } /** * Publish metrics. * * @since 2.3.0 */ public static function collate_metrics() { $span = \DecaLog\Engine::tracesLogger( VIBES_SLUG )->startSpan( 'Metrics collation', DECALOG_SPAN_SHUTDOWN ); $values = Cache::get( 'webvitals', true ); if ( ! is_array( $values ) ) { $values = []; } else { $limit = time() - Option::network_get( 'twindow' ); $new = []; foreach ( $values as $value ) { if ( array_key_exists( 'timestamp', $value ) && $limit < $value['timestamp'] ) { $new[] = $value; } } $values = $new; } $time = time(); foreach ( self::$messages_buffer as $message ) { if ( 'webvital' === $message['type'] ) { foreach ( array_merge( WebVitals::$rated_metrics, WebVitals::$unrated_metrics ) as $metric ) { if ( array_key_exists( $metric . '_sum', $message ) ) { $values[] = [ 'timestamp' => $time, 'metric' => $metric, 'value' => $message[ $metric . '_sum' ], ]; } } } } Cache::set( 'webvitals', $values, 'infinite', true ); $stats = []; foreach ( array_merge( WebVitals::$rated_metrics, WebVitals::$unrated_metrics ) as $metric ) { $stats[ $metric ] = [ 'counter' => 0, 'value' => 0, ]; } foreach ( $values as $value ) { if ( array_key_exists( 'metric', $value ) && array_key_exists( 'value', $value ) && array_key_exists( $value['metric'], $stats ) ) { $stats[ $value['metric'] ]['counter'] += 1; $stats[ $value['metric'] ]['value'] += $value['value']; } } if ( \DecaLog\Engine::isDecalogActivated() && Option::network_get( 'metrics' ) && Option::network_get( 'capture' ) && ! in_array( Environment::exec_mode(), [ 1, 3, 4 ], true ) ) { $span2 = \DecaLog\Engine::tracesLogger( VIBES_SLUG )->startSpan( 'Metrics publication', $span ); foreach ( $stats as $metric => $stat ) { if ( 0 < $stat['counter'] ) { \DecaLog\Engine::metricsLogger( VIBES_SLUG )->setProdGauge( 'webvitals_' . strtolower( $metric ), round( $stat['value'] / ( ( 'CLS' === $metric ? 1000000 : 1000 ) * $stat['counter'] ), ( 'CLS' === $metric ? 2 : 3 ) ) ); } } \DecaLog\Engine::tracesLogger( VIBES_SLUG )->endSpan( $span2 ); } \DecaLog\Engine::tracesLogger( VIBES_SLUG )->endSpan( $span ); } } if ( ! defined( 'DECALOG_MAX_SHUTDOWN_PRIORITY' ) ) { define( 'DECALOG_MAX_SHUTDOWN_PRIORITY', PHP_INT_MAX - 1000 ); } Memory::init();