Skip to content
This repository has been archived by the owner on Aug 17, 2022. It is now read-only.

Commit

Permalink
Merge pull request #25 from loveOSS/introduced-monitoring
Browse files Browse the repository at this point in the history
Introduced Monitoring feature
  • Loading branch information
mickaelandrieu authored Sep 20, 2019
2 parents ac71241 + 956ef4d commit 411abb0
Show file tree
Hide file tree
Showing 27 changed files with 482 additions and 97 deletions.
1 change: 1 addition & 0 deletions .php_cs.dist
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ return PhpCsFixer\Config::create()
'yoda_style' => null,
'non_printable_character' => true,
'phpdoc_no_empty_return' => false,
'php_unit_test_case_static_method_calls' => ['call_type' => 'self'],
])
->setFinder($finder)
->setCacheFile(__DIR__.'/.php_cs.cache')
Expand Down
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ $mainSystem = MainSystem::createFromArray([

$storage = new SimpleArray();

// Any PSR-13 Event Dispatcher implementation.
// Any PSR-14 Event Dispatcher implementation.
$dispatcher = new Symfony\Component\EventDispatcher\EventDispatcher;

$circuitBreaker = new MainCircuitBreaker(
Expand Down Expand Up @@ -75,6 +75,23 @@ $circuitBreaker->call(
> For the Guzzle implementation, the Client options are described
> in the [HttpGuzzle documentation](http://docs.guzzlephp.org/en/stable/index.html).
### Monitoring

This library is shipped with a minimalist system to help you monitor your circuits.

```php
$monitor = new SimpleMonitor();

// on some circuit breaker events...
function listener(Event $event) {
$monitor->add($event);
};

// retrieve a complete report for analysis or storage
$report = $monitor->getReport();

```

## Tests

```
Expand Down
22 changes: 22 additions & 0 deletions src/Contracts/Monitoring/Monitor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace Resiliency\Contracts\Monitoring;

use Resiliency\Contracts\Event;

/**
* If you need to monitor and collect information about the circuit breaker
* use the monitor to collect the information when events are dispatched.
*/
interface Monitor
{
/**
* @param Event $event the dispatched event
*/
public function collect(Event $event): void;

/**
* @return Report the Monitor Report
*/
public function getReport(): Report;
}
21 changes: 21 additions & 0 deletions src/Contracts/Monitoring/Report.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Resiliency\Contracts\Monitoring;

/**
* The Monitor report
*/
interface Report
{
/**
* @var ReportEntry the report entry
*
* @return self
*/
public function add(ReportEntry $reportEntry): self;

/**
* @return array the list of report entries
*/
public function all(): array;
}
28 changes: 28 additions & 0 deletions src/Contracts/Monitoring/ReportEntry.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace Resiliency\Contracts\Monitoring;

use Resiliency\Contracts\CircuitBreaker;
use Resiliency\Contracts\Service;

/**
* A report entry store information used to generate a report
* about Circuit Breaker activity over time.
*/
interface ReportEntry
{
/**
* @return Service the service called
*/
public function getService(): Service;

/**
* @return CircuitBreaker the circuit breaker used
*/
public function getCircuitBreaker(): CircuitBreaker;

/**
* @return string the current transition of the circuit breaker
*/
public function getTransition(): string;
}
42 changes: 42 additions & 0 deletions src/Monitors/SimpleMonitor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace Resiliency\Monitors;

use Resiliency\Contracts\Monitoring\Monitor;
use Resiliency\Contracts\Monitoring\Report;
use Resiliency\Contracts\Event;

final class SimpleMonitor implements Monitor
{
/**
* @var Report the report
*/
private $report;

public function __construct()
{
$this->report = new SimpleReport();
}

/**
* {@inheritdoc}
*/
public function collect(Event $event): void
{
$reportEntry = new SimpleReportEntry(
$event->getService(),
$event->getCircuitBreaker(),
get_class($event)
);

$this->report->add($reportEntry);
}

/**
* {@inheritdoc}
*/
public function getReport(): Report
{
return $this->report;
}
}
37 changes: 37 additions & 0 deletions src/Monitors/SimpleReport.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace Resiliency\Monitors;

use Resiliency\Contracts\Monitoring\Report;
use Resiliency\Contracts\Monitoring\ReportEntry;

final class SimpleReport implements Report
{
/**
* @var array the report entries
*/
private $reportEntries;

public function __construct()
{
$this->reportEntries = [];
}

/**
* {@inheritdoc}
*/
public function add(ReportEntry $reportEntry): Report
{
$this->reportEntries[] = $reportEntry;

return $this;
}

/**
* {@inheritdoc}
*/
public function all(): array
{
return $this->reportEntries;
}
}
56 changes: 56 additions & 0 deletions src/Monitors/SimpleReportEntry.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

namespace Resiliency\Monitors;

use Resiliency\Contracts\CircuitBreaker;
use Resiliency\Contracts\Monitoring\ReportEntry;
use Resiliency\Contracts\Service;

final class SimpleReportEntry implements ReportEntry
{
/**
* @var Service the Service called
*/
private $service;

/**
* @var CircuitBreaker the Circuit Breaker called
*/
private $circuitBreaker;

/**
* @var string the Circuit Breaker transition
*/
private $transition;

public function __construct(Service $service, CircuitBreaker $circuitBreaker, string $transition)
{
$this->service = $service;
$this->circuitBreaker = $circuitBreaker;
$this->transition = $transition;
}

/**
* {@inheritdoc}
*/
public function getService(): Service
{
return $this->service;
}

/**
* {@inheritdoc}
*/
public function getCircuitBreaker(): CircuitBreaker
{
return $this->circuitBreaker;
}

/**
* {@inheritdoc}
*/
public function getTransition(): string
{
return $this->transition;
}
}
3 changes: 2 additions & 1 deletion tests/CircuitBreakerTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ protected function getTestClient(): GuzzleClient
{
$mock = new MockHandler([
new RequestException('Service unavailable', new Request('GET', 'test')),
new RequestException('Service unavailable', new Request('GET', 'test')),
new RequestException('Service unavailable', new Request('GET', 'test1')),
new RequestException('Service unavailable', new Request('GET', 'test2')),
new Response(200, [], '{"hello": "world"}'),
]);

Expand Down
50 changes: 32 additions & 18 deletions tests/CircuitBreakerWorkflowTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ class CircuitBreakerWorkflowTest extends CircuitBreakerTestCase
*/
public function testCircuitBreakerIsInClosedStateAtStart(): void
{
$this->assertInstanceOf(Closed::class, $this->circuitBreaker->getState());
self::assertInstanceOf(Closed::class, $this->circuitBreaker->getState());

$this->assertSame(
self::assertSame(
'{"uri": https://httpbin.org/get/foo"}',
$this->circuitBreaker->call(
'https://httpbin.org/get/foo',
Expand All @@ -52,13 +52,13 @@ public function testCircuitBreakerIsInClosedStateAtStart(): void
public function testCircuitBreakerWillBeOpenInCaseOfFailures(): void
{
// CLOSED
$this->assertInstanceOf(Closed::class, $this->circuitBreaker->getState());
self::assertInstanceOf(Closed::class, $this->circuitBreaker->getState());
$response = $this->circuitBreaker->call('https://httpbin.org/get/foo', $this->createFallbackResponse());
$this->assertSame('{"uri": https://httpbin.org/get/foo"}', $response);
self::assertSame('{"uri": https://httpbin.org/get/foo"}', $response);

//After two failed calls switch to OPEN state
$this->assertInstanceOf(Opened::class, $this->circuitBreaker->getState());
$this->assertSame(
self::assertInstanceOf(Opened::class, $this->circuitBreaker->getState());
self::assertSame(
'{"uri": https://httpbin.org/get/foo"}',
$this->circuitBreaker->call(
'https://httpbin.org/get/foo',
Expand All @@ -79,26 +79,40 @@ public function testCircuitBreakerWillBeOpenInCaseOfFailures(): void
public function testOnceInHalfOpenModeServiceIsFinallyReachable(): void
{
// CLOSED - first call fails (twice)
$this->assertInstanceOf(Closed::class, $this->circuitBreaker->getState());
self::assertInstanceOf(Closed::class, $this->circuitBreaker->getState());
$response = $this->circuitBreaker->call('https://httpbin.org/get/foo', $this->createFallbackResponse());
$this->assertSame('{"uri": https://httpbin.org/get/foo"}', $response);
$this->assertInstanceOf(Opened::class, $this->circuitBreaker->getState());
self::assertSame('{"uri": https://httpbin.org/get/foo"}', $response);
self::assertInstanceOf(Opened::class, $this->circuitBreaker->getState());

// OPEN - no call to client
$response = $this->circuitBreaker->call('https://httpbin.org/get/foo', $this->createFallbackResponse());
$this->assertSame('{"uri": https://httpbin.org/get/foo"}', $response);
$this->assertInstanceOf(Opened::class, $this->circuitBreaker->getState());
self::assertSame('{"uri": https://httpbin.org/get/foo"}', $response);
self::assertInstanceOf(Opened::class, $this->circuitBreaker->getState());
$this->waitFor(2 * self::OPEN_THRESHOLD);

// SWITCH TO HALF OPEN and retry to call the service (still in failure)
$this->assertSame(
self::assertSame(
'{"uri": https://httpbin.org/get/foo"}',
$this->circuitBreaker->call(
'https://httpbin.org/get/foo',
$this->createFallbackResponse()
)
);

// SWITCH BACK TO OPENED
self::assertInstanceOf(Opened::class, $this->circuitBreaker->getState());

$this->waitFor(2 * self::OPEN_THRESHOLD);

// FINALLY, THE RESPONSE IS AVAILABLE
self::assertSame(
'{"hello": "world"}',
$this->circuitBreaker->call(
'https://httpbin.org/get/foo',
$this->createFallbackResponse()
)
);
$this->assertInstanceOf(Closed::class, $this->circuitBreaker->getState());
self::assertInstanceOf(Closed::class, $this->circuitBreaker->getState());
}

/**
Expand All @@ -114,20 +128,20 @@ public function testOnceCircuitBreakerIsIsolatedNoTrialsAreDone(): void
$this->circuitBreaker->isolate($uri);

$response = $this->circuitBreaker->call($uri, $this->createFallbackResponse());
$this->assertSame('{"uri": https://httpbin.org/get/foo"}', $response);
$this->assertInstanceOf(Isolated::class, $this->circuitBreaker->getState());
self::assertSame('{"uri": https://httpbin.org/get/foo"}', $response);
self::assertInstanceOf(Isolated::class, $this->circuitBreaker->getState());

// Let's do 5 calls!

for ($i = 0; $i < 5; ++$i) {
$this->circuitBreaker->call($uri, $this->createFallbackResponse());
$this->assertSame('{"uri": https://httpbin.org/get/foo"}', $response);
$this->assertInstanceOf(Isolated::class, $this->circuitBreaker->getState());
self::assertSame('{"uri": https://httpbin.org/get/foo"}', $response);
self::assertInstanceOf(Isolated::class, $this->circuitBreaker->getState());
}

$this->circuitBreaker->reset($uri);

$this->assertInstanceOf(Closed::class, $this->circuitBreaker->getState());
self::assertInstanceOf(Closed::class, $this->circuitBreaker->getState());
}

/**
Expand Down
4 changes: 2 additions & 2 deletions tests/Clients/GuzzleClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public function testRequestWorksAsExpected()
$client = new GuzzleClient();
$service = $this->getService('https://www.google.com', ['method' => 'GET']);

$this->assertNotNull($client->request($service, $this->createMock(Place::class)));
self::assertNotNull($client->request($service, $this->createMock(Place::class)));
}

public function testWrongRequestThrowsAnException()
Expand All @@ -35,7 +35,7 @@ public function testTheClientAcceptsHttpMethodOverride()

$service = $this->getService('https://www.google.fr');

$this->assertEmpty(
self::assertEmpty(
$client->request(
$service,
$this->createMock(Place::class)
Expand Down
6 changes: 3 additions & 3 deletions tests/Events/TransitionEventTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ public function checkEventIsValid($className)
$this->createService('bar', [])
);

$this->assertInstanceOf(Event::class, $event);
$this->assertInstanceOf(Service::class, $event->getService());
$this->assertInstanceOf(CircuitBreaker::class, $event->getCircuitBreaker());
self::assertInstanceOf(Event::class, $event);
self::assertInstanceOf(Service::class, $event->getService());
self::assertInstanceOf(CircuitBreaker::class, $event->getCircuitBreaker());
}

/**
Expand Down
Loading

0 comments on commit 411abb0

Please sign in to comment.