Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
81 / 81
100.00% covered (success)
100.00%
14 / 14
CRAP
100.00% covered (success)
100.00%
1 / 1
MemoryHandler
100.00% covered (success)
100.00%
81 / 81
100.00% covered (success)
100.00%
14 / 14
43
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getLimit
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 updateUsage
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 updateMemoryUsage
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 hasUsages
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getUsages
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 updatePeakMemoryUsage
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 hasPeakUsages
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPeakUsages
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 prepare
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
6
 prepareMessage
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
6
 log
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
12
 formatMemoryToString
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
6
 formatMemoryToInt
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2declare(strict_types=1);
3/**
4 * Pop PHP Framework (https://www.popphp.org/)
5 *
6 * @link       https://github.com/popphp/popphp-framework
7 * @author     Nick Sagona, III <dev@noladev.com>
8 * @copyright  Copyright (c) 2009-2027 NOLA Interactive, LLC.
9 * @license    https://www.popphp.org/license     New BSD License
10 */
11
12/**
13 * @namespace
14 */
15namespace Pop\Debug\Handler;
16
17use Psr\Log\LoggerInterface;
18
19/**
20 * Debug memory handler class
21 *
22 * @category   Pop
23 * @package    Pop\Debug
24 * @author     Nick Sagona, III <dev@noladev.com>
25 * @copyright  Copyright (c) 2009-2027 NOLA Interactive, LLC.
26 * @license    https://www.popphp.org/license     New BSD License
27 * @version    4.0.0
28 */
29class MemoryHandler extends AbstractHandler
30{
31
32    /**
33     * Actual bytes flag
34     * @var bool
35     */
36    protected bool $actualBytes = false;
37
38    /**
39     * Memory limit
40     * @var int
41     */
42    protected int $limit = 0;
43
44    /**
45     * Memory usage snapshots
46     * @var array
47     */
48    protected array $usages = [];
49
50    /**
51     * Peak memory usage snapshots
52     * @var array
53     */
54    protected array $peaks = [];
55
56    /**
57     * Constructor
58     *
59     * Instantiate a memory handler object
60     *
61     * @param bool             $actualBytes
62     * @param ?string          $name
63     * @param ?LoggerInterface $logger
64     * @param array            $loggingParams
65     */
66    public function __construct(bool $actualBytes = false, ?string $name = null, ?LoggerInterface $logger = null, array $loggingParams = [])
67    {
68        parent::__construct($name, $logger, $loggingParams);
69        $this->actualBytes = $actualBytes;
70        $this->limit       = $this->formatMemoryToInt(ini_get('memory_limit'));
71    }
72
73    /**
74     * Get memory limit
75     *
76     * @return int
77     */
78    public function getLimit(): int
79    {
80        return $this->limit;
81    }
82
83    /**
84     * Take both a memory usage and peak usage snapshot
85     *
86     * @param  bool $real
87     * @return MemoryHandler
88     */
89    public function updateUsage(bool $real = false): MemoryHandler
90    {
91        $this->updateMemoryUsage($real)
92            ->updatePeakMemoryUsage($real);
93
94        return $this;
95    }
96
97    /**
98     * Take a memory usage snapshot
99     *
100     * @param  bool $real
101     * @return MemoryHandler
102     */
103    public function updateMemoryUsage(bool $real = false): MemoryHandler
104    {
105        $this->usages[] = ['memory' => memory_get_usage($real), 'timestamp' => (string)microtime(true)];
106        return $this;
107    }
108
109    /**
110     * Determine if the handler has memory usages snapshots
111     *
112     * @return bool
113     */
114    public function hasUsages(): bool
115    {
116        return (count($this->usages) > 0);
117    }
118
119    /**
120     * Get memory usages snapshots
121     *
122     * @return array
123     */
124    public function getUsages(): array
125    {
126        return $this->usages;
127    }
128
129    /**
130     * Take a peak memory usage snapshot
131     *
132     * @param  bool $real
133     * @return MemoryHandler
134     */
135    public function updatePeakMemoryUsage(bool $real = false): MemoryHandler
136    {
137        $this->peaks[] = ['memory' => memory_get_peak_usage($real), 'timestamp' => (string)microtime(true)];
138        return $this;
139    }
140
141    /**
142     * Determine if the handler has peak memory usages snapshots
143     *
144     * @return bool
145     */
146    public function hasPeakUsages(): bool
147    {
148        return (count($this->peaks) > 0);
149    }
150
151    /**
152     * Get peak memory usages snapshots
153     *
154     * @return array
155     */
156    public function getPeakUsages(): array
157    {
158        return $this->peaks;
159    }
160
161    /**
162     * Prepare handler data for storage
163     *
164     * @return array
165     */
166    public function prepare(): array
167    {
168        $data = [
169            'limit'  => (!$this->actualBytes) ? $this->formatMemoryToString($this->limit) : $this->limit,
170            'usages' => [],
171            'peaks'  => []
172        ];
173
174        foreach ($this->usages as $usage) {
175            $data['usages'][] = [
176                'memory'    => (!$this->actualBytes) ? $this->formatMemoryToString($usage['memory']) : $usage['memory'],
177                'timestamp' => number_format((float)$usage['timestamp'], 5, '.', '')
178            ];
179        }
180
181        foreach ($this->peaks as $peak) {
182            $data['peaks'][] = [
183                'memory'    => (!$this->actualBytes) ? $this->formatMemoryToString($peak['memory']) : $peak['memory'],
184                'timestamp' => number_format((float)$peak['timestamp'], 5, '.', '')
185            ];
186        }
187
188        return $data;
189    }
190
191    /**
192     * Prepare handler message
193     *
194     * @param  ?array $context
195     * @return string
196     */
197    public function prepareMessage(?array $context = null): string
198    {
199        if ($context === null) {
200            $context = $this->prepare();
201        }
202
203        $message = 'Memory limit: ' . $context['limit'];
204        if (!empty($context['usages'])) {
205            $message .= '; ' . ((count($context['usages']) > 1) ?
206                '(' . count($context['usages']) . ') memory usages have been logged.' :
207                '(1) memory usage has been logged.');
208        }
209        if (!empty($context['peaks'])) {
210            $message .= '; ' . ((count($context['peaks']) > 1) ?
211                '(' . count($context['peaks']) . ') memory peaks have been logged.' :
212                '(1) memory peak has been logged.');
213        }
214
215        return $message;
216    }
217
218    /**
219     * Trigger handler logging
220     *
221     * @throws Exception
222     * @return void
223     */
224    public function log(): void
225    {
226        $logLevel = $this->resolveLogLevel();
227        if ($logLevel === null) {
228            return;
229        }
230
231        $usageLimit = $this->loggingParams['usage_limit'] ?? null;
232        $peakLimit  = $this->loggingParams['peak_limit'] ?? null;
233        $context    = $this->prepare();
234
235        // Log general usage
236        if (($usageLimit === null) && ($peakLimit === null)) {
237            foreach ($this->usages as $usage) {
238                $this->logger->log($logLevel, 'Memory Usage: ' . $usage['memory'] . ' bytes.', $usage);
239            }
240            foreach ($this->peaks as $peak) {
241                $this->logger->log($logLevel, 'Peak Memory Usage: ' . $peak['memory'] . ' bytes.', $context);
242            }
243        // Log if limits are exceeded
244        } else {
245            if ($usageLimit !== null)  {
246                foreach ($this->usages as $usage) {
247                    if ($usage['memory'] >= $usageLimit) {
248                        $this->logger->log($logLevel, 'Memory usage limit of ' . $usageLimit . ' has been exceeded by ' .
249                            $usage['memory'] - $usageLimit. ' bytes. ' . $usage['memory'] . ' bytes were used.', $usage);
250                    }
251                }
252            }
253            if ($peakLimit !== null) {
254                foreach ($this->peaks as $peak) {
255                    if ($peak['memory'] >= $peakLimit) {
256                        $this->logger->log($logLevel, 'Memory peak limit of ' . $peakLimit . ' has been exceeded by ' .
257                            $peak['memory'] - $peakLimit. ' bytes. ' . $peak['memory'] . ' bytes were used at the peak.', $peak);
258                    }
259                }
260            }
261        }
262    }
263
264    /**
265     * Format memory amount into readable string
266     *
267     * @param  int|string $memory
268     * @param  int $bytes
269     * @return string
270     */
271    public function formatMemoryToString(int|string $memory, int $bytes = 1024): string
272    {
273        if ($memory >= pow($bytes, 3)) {
274            $memory = round(($memory / pow($bytes, 3)), 2) . 'GB';
275        } else if ($memory >= pow($bytes, 2)) {
276            $memory = round(($memory / pow($bytes, 2)), 2) . 'MB';
277        } else if (($memory < pow($bytes, 2)) && ($memory >= $bytes)) {
278            $memory = round(($memory / $bytes), 2) . 'KB';
279        } else if ($memory < $bytes) {
280            $memory = $memory . 'B';
281        }
282
283        return $memory;
284    }
285
286    /**
287     * Format memory amount into integer
288     *
289     * @param  int|string $memory
290     * @param  int $bytes
291     * @return int
292     */
293    public function formatMemoryToInt(int|string $memory, int $bytes = 1024): int
294    {
295        $factor = 1;
296
297        if (stripos($memory, 'G') !== false) {
298            $factor = pow($bytes, 3);
299        } else if (stripos($memory, 'M') !== false) {
300            $factor = pow($bytes, 2);
301        } else if (stripos($memory, 'K') !== false) {
302            $factor = $bytes;
303        }
304
305        return (int)$memory * $factor;
306    }
307
308}