Skip to content

Commit da3dbfe

Browse files
committed
Feat: memory profiler mode
1 parent 25ab5d1 commit da3dbfe

File tree

11 files changed

+270
-35
lines changed

11 files changed

+270
-35
lines changed

README.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ Options:
3737
Pyroscope Auth Token.
3838
Example: psx-BWlqy_dW1Wxg6oBjuCWD28HxGCkB1Jfzt-jjtqHzrkzI
3939
40+
-m, --memory Enable memory traces mode
41+
Get memory usage from 'mem' tag and send average memory usage for trace group
42+
Do not provide this option for CPU mode
43+
4044
-a, --app=STRING Name of app.
4145
All samples will be saved under given app name.
4246
Example: app
@@ -71,7 +75,21 @@ phpspy --max-depth=-1 --time-limit-ms=59000 --threads=1024 --rate-hz=4 --buffer-
7175
phpspy --max-depth=-1 --time-limit-ms=59000 --threads=100 --rate-hz=25 --buffer-size=65536 -J m -P '-x "php-fpm|php-fpm[0-9]\.[0-9]" | shuf' 2> error_log.log | php pyrospy.php run --pyroscope=https://pyroscope.yourdomain.com --rateHz=25 --app=testApp --tags=host=server39 --tags=role=web
7276
```
7377

74-
## Plugins
78+
### Memory profiling
79+
- add `--memory-usage` option to phpspy command to add current and maximum memory usage to tags
80+
- add `--memory` option to pyrospy command to send average memory usage for traces instead of sum count
81+
```shell
82+
phpspy --max-depth=-1 --time-limit-ms=59000 --threads=1024 --memory-usage --rate-hz=4 --buffer-size=65536 -J m -P '-x "php|php[0-9]\.[0-9]" | shuf' 2> error_log.log | php pyrospy.php run --pyroscope=https://pyroscope.yourdomain.com --rateHz=4 --app=testAppMemory --tags=host=server39 --tags=role=cli --memory
83+
84+
phpspy --max-depth=-1 --time-limit-ms=59000 --threads=100 --memory-usage --rate-hz=25 --buffer-size=65536 -J m -P '-x "php-fpm|php-fpm[0-9]\.[0-9]" | shuf' 2> error_log.log | php pyrospy.php run --pyroscope=https://pyroscope.yourdomain.com --rateHz=25 --app=testAppMemory --tags=host=server39 --tags=role=web --memory
85+
```
86+
87+
Tee can be used to get memory usage and cpu samples from same phpspy stdout
88+
```shell
89+
phpspy --max-depth=-1 --time-limit-ms=59000 --threads=1024 --memory-usage --rate-hz=4 --buffer-size=65536 -J m -P '-x "php|php[0-9]\.[0-9]" | shuf' 2> error_log.log | tee >(php pyrospy.php run --pyroscope=https://pyroscope.yourdomain.com --rateHz=4 --app=testAppMemory --tags=host=server39 --tags=role=cli --memory) >(php pyrospy.php run --pyroscope=https://pyroscope.yourdomain.com --rateHz=4 --app=testApp --tags=host=server39 --tags=role=cli) >/dev/null
90+
```
91+
92+
### Plugins
7593

7694
1. Create `.php` plugin class. Put it in any place. Make sure it has `namespace Zoon\PyroSpy\Plugins;` and classname match filename.
7795
```php

app/Commands/RunCommand.php

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,13 @@
88
use Symfony\Component\Console\Input\InputInterface;
99
use Symfony\Component\Console\Input\InputOption;
1010
use Symfony\Component\Console\Output\OutputInterface;
11+
use Zoon\PyroSpy\CpuTraceAggregator;
12+
use Zoon\PyroSpy\MemoryTraceAggregator;
1113
use Zoon\PyroSpy\Plugins\PluginInterface;
1214
use Zoon\PyroSpy\Processor;
1315
use Zoon\PyroSpy\SampleSender;
16+
use Zoon\PyroSpy\SenderAggregationEnum;
17+
use Zoon\PyroSpy\SenderUnitsEnum;
1418

1519
class RunCommand extends Command
1620
{
@@ -33,6 +37,12 @@ protected function configure(): void
3337
InputOption::VALUE_OPTIONAL,
3438
'Pyroscope Auth Token. Example: psx-BWlqy_dW1Wxg6oBjuCWD28HxGCkB1Jfzt-jjtqHzrkzI',
3539
),
40+
new InputOption(
41+
'memory',
42+
'm',
43+
InputOption::VALUE_NONE,
44+
'Process memory traces instead of CPU traces'
45+
),
3646
new InputOption(
3747
'app',
3848
'a',
@@ -151,10 +161,27 @@ protected function execute(InputInterface $input, OutputInterface $output): int
151161
}
152162
}
153163

164+
if ($input->getOption('memory')) {
165+
$aggregator = new MemoryTraceAggregator();
166+
$sender = new SampleSender(
167+
$pyroscope,
168+
$app,
169+
$rateHz,
170+
$tags,
171+
$pyroscopeAuthToken,
172+
SenderUnitsEnum::Bytes,
173+
SenderAggregationEnum::Average
174+
);
175+
} else {
176+
$aggregator = new CpuTraceAggregator();
177+
$sender = new SampleSender($pyroscope, $app, $rateHz, $tags, $pyroscopeAuthToken);
178+
}
179+
154180
$processor = new Processor(
155181
$interval,
156182
$batch,
157-
new SampleSender($pyroscope, $app, $rateHz, $tags, $pyroscopeAuthToken),
183+
$aggregator,
184+
$sender,
158185
array_values(array_filter($plugins)),
159186
$sendSampleFutureLimit,
160187
$concurrentRequestLimit,

app/CpuTraceAggregator.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace Zoon\PyroSpy;
4+
5+
class CpuTraceAggregator extends TraceAggregatorAbstract
6+
{
7+
/**
8+
* @inheritDoc
9+
*/
10+
public function addTrace(array $tags, string $key): void
11+
{
12+
ksort($tags);
13+
unset($tags['mem']);
14+
$tagsKey = serialize($tags);
15+
if (!array_key_exists($tagsKey, $this->results)) {
16+
$this->results[$tagsKey] = [];
17+
}
18+
if (!array_key_exists($key, $this->results[$tagsKey])) {
19+
$this->results[$tagsKey][$key] = 0;
20+
}
21+
$this->results[$tagsKey][$key]++;
22+
}
23+
}

app/MemoryTraceAggregator.php

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
namespace Zoon\PyroSpy;
4+
5+
/**
6+
* @psalm-import-type ResultsArray from TraceAggregatorAbstract
7+
*/
8+
class MemoryTraceAggregator extends TraceAggregatorAbstract
9+
{
10+
/**
11+
* @var array<string, array<string, list<int>>>
12+
*/
13+
protected array $results;
14+
/**
15+
* @inheritDoc
16+
*/
17+
public function addTrace(array $tags, string $key): void
18+
{
19+
ksort($tags);
20+
if (!array_key_exists('mem', $tags)) {
21+
return;
22+
}
23+
$memSize = $tags['mem'];
24+
unset($tags['mem']);
25+
26+
$tagsKey = serialize($tags);
27+
if (!array_key_exists($tagsKey, $this->results)) {
28+
$this->results[$tagsKey] = [];
29+
}
30+
if (!array_key_exists($key, $this->results[$tagsKey])) {
31+
$this->results[$tagsKey][$key] = [];
32+
}
33+
$this->results[$tagsKey][$key][] = $memSize;
34+
}
35+
36+
/**
37+
* @return ResultsArray
38+
*/
39+
public function getGrouppedTraces(): array
40+
{
41+
$results = [];
42+
43+
foreach ($this->results as $tagsKey => $tagResuts) {
44+
foreach ($tagResuts as $key => $traces) {
45+
if ($traces) {
46+
$results[$tagsKey][$key] = (int)(array_sum($traces)/count($traces));
47+
} else {
48+
$results[$tagsKey][$key] = 0;
49+
}
50+
}
51+
}
52+
53+
return $results;
54+
}
55+
}

app/Plugins/ClearEmptyTags.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ class ClearEmptyTags implements PluginInterface
66
{
77
public function process(array $tags, array $trace): array
88
{
9-
unset($tags['ts']);
10-
119
foreach ($tags as $name => $value) {
1210
$value = trim($value);
1311
if (!$value || $value === '-') {

app/Processor.php

Lines changed: 18 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ final class Processor
3737
public function __construct(
3838
private readonly int $interval,
3939
private readonly int $batchLimit,
40+
private readonly TraceAggregatorAbstract $aggregator,
4041
private readonly SampleSenderInterface $sender,
4142
private readonly array $plugins,
4243
int $sendSampleFutureLimit,
@@ -52,7 +53,7 @@ public function __construct(
5253

5354
private function init(): void
5455
{
55-
$this->results = [];
56+
$this->aggregator->clear();
5657
$this->tsStart = time();
5758
$this->tsEnd = $this->tsStart + $this->interval;
5859
}
@@ -103,15 +104,15 @@ private function runProducer(): Future
103104
}
104105

105106
$key = self::stringifyTrace($tracePrepared);
106-
$this->groupTrace($tags, $key);
107+
$this->aggregator->addTrace($tags, $key);
107108

108109
$currentTime = time();
109110

110-
if ($currentTime < $this->tsEnd && $this->countResults() < $this->batchLimit) {
111+
if ($currentTime < $this->tsEnd && $this->aggregator->countGrouppedTraces() < $this->batchLimit) {
111112
continue;
112113
}
113114

114-
foreach ($this->results as $tagSerialized => $results) {
115+
foreach ($this->aggregator->getGrouppedTraces() as $tagSerialized => $results) {
115116
$this->queue->push(new Sample($this->tsStart, $currentTime, $results, unserialize($tagSerialized)));
116117
}
117118

@@ -123,7 +124,7 @@ private function runProducer(): Future
123124
}
124125

125126
$currentTime = time();
126-
foreach ($this->results as $tagSerialized => $results) {
127+
foreach ($this->aggregator->getGrouppedTraces() as $tagSerialized => $results) {
127128
$this->queue->push(new Sample($this->tsStart, $currentTime, $results, unserialize($tagSerialized)));
128129
}
129130

@@ -184,7 +185,19 @@ private static function extractTags(array $sample): array
184185
if (count($item) !== 4) {
185186
continue;
186187
}
188+
189+
//# uri = /stat.php
187190
[$hashtag, $tag, $equalsign, $value] = $item;
191+
192+
if ($tag === 'ts') {
193+
continue;
194+
}
195+
if ($tag === 'mem') {
196+
// # mem 1653800 1659424
197+
[$hashtag, $tag, $value, $maxUsage] = $item;
198+
$value = (int) $value;
199+
}
200+
188201
$tags[$tag] = $value;
189202
}
190203
return $tags;
@@ -236,32 +249,6 @@ private function getLine(): Generator
236249
}
237250
}
238251

239-
/**
240-
* @param TagsArray $tags
241-
* @param string $key
242-
*/
243-
private function groupTrace(array $tags, string $key): void
244-
{
245-
ksort($tags);
246-
$tagsKey = serialize($tags);
247-
if (!array_key_exists($tagsKey, $this->results)) {
248-
$this->results[$tagsKey] = [];
249-
}
250-
if (!array_key_exists($key, $this->results[$tagsKey])) {
251-
$this->results[$tagsKey][$key] = 0;
252-
}
253-
$this->results[$tagsKey][$key]++;
254-
}
255-
256-
private function countResults(): int
257-
{
258-
$count = 0;
259-
foreach ($this->results as $tagResuts) {
260-
$count += count($tagResuts);
261-
}
262-
return $count;
263-
}
264-
265252
private static function fixEvalLine(string $line): string
266253
{
267254

app/SampleSender.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ public function __construct(
2323
private readonly int $rateHz,
2424
private readonly array $tags,
2525
private readonly string $authToken = '',
26+
private readonly SenderUnitsEnum $units = SenderUnitsEnum::Samples,
27+
private readonly SenderAggregationEnum $aggregation = SenderAggregationEnum::Sum,
2628
) {
2729
$this->client = (new HttpClientBuilder())
2830
->retry(0)
@@ -95,6 +97,8 @@ private function getUrl(array $tags, int $fromTs, int $toTs): string
9597
'until' => $toTs,
9698
'sampleRate' => $this->rateHz,
9799
'format' => 'folded',
100+
'units' => $this->units->value,
101+
'aggregationType' => $this->aggregation->value,
98102
];
99103
return $this->pyroscopeHost . "/ingest?" . http_build_query($params);
100104
}

app/SenderAggregationEnum.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace Zoon\PyroSpy;
4+
5+
enum SenderAggregationEnum: string
6+
{
7+
case Sum = 'sum';
8+
case Average = 'average';
9+
}

app/SenderUnitsEnum.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Zoon\PyroSpy;
4+
5+
enum SenderUnitsEnum: string
6+
{
7+
case Samples = 'samples';
8+
case Objects = 'objects';
9+
case Bytes = 'bytes';
10+
}

app/TraceAggregatorAbstract.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace Zoon\PyroSpy;
4+
5+
/**
6+
* @psalm-import-type TagsArray from Sample
7+
* @psalm-type ResultsArray=array<string, array<string, int>>
8+
*/
9+
abstract class TraceAggregatorAbstract
10+
{
11+
/**
12+
* @var ResultsArray
13+
*/
14+
protected array $results;
15+
16+
public function clear(): void
17+
{
18+
$this->results = [];
19+
}
20+
21+
/**
22+
* @param TagsArray $tags
23+
* @param string $key
24+
*/
25+
abstract public function addTrace(array $tags, string $key): void;
26+
27+
public function countGrouppedTraces(): int {
28+
$count = 0;
29+
foreach ($this->results as $tagResuts) {
30+
$count += count($tagResuts);
31+
}
32+
return $count;
33+
}
34+
35+
/**
36+
* @return ResultsArray
37+
*/
38+
public function getGrouppedTraces(): array {
39+
return $this->results;
40+
}
41+
}

0 commit comments

Comments
 (0)