php - What design pattern to use for a time-measurement profiler service?

700

I have a symfony2 application. It abstracts a bunch of external APIs, all of them implementing anExternalApiInterface.

EachExternalApiInterface has a lot of methods, e.g.fetchFoo andfetchBar.

Now, I want to write a service that measures the time of each method call of an instance of anExternalApiInterface.

My current thinking is to implement aStopWatchExternalApiDecorator, that wraps each method call. Yet this approach leads, in my understanding, to code duplication.

I think I am going to use the for the time measurement, yet this feels odd:

class StopWatchExternalApiDecorator implements ExternalApiInterface {
    public function __construct(ExternalApiInterface $api, Stopwatch $stopWatch)
    {
       $this->api = $api;
       $this->stopWatch = $stopWatch;
    }

    public function fetchBar() {
         $this->stopWatch->start('fetchBar');
         $this->api->fetchBar()
         $this->stopWatch->stop('fetchBar');
    }

    public function fetchFoo() {
         $this->stopWatch->start('fetchFoo');
         $this->api->fetchFoo()
         $this->stopWatch->stop('fetchFoo');
    }
}

It seems like I am hurting the DNRY (do not repeat yourself) approach. Am I using the right pattern for this kind of problem, or is there something else more fit? More fit in the sense of: One place to do all the measurement, and no code duplication.

I also dislike of having to touch the decorator in case there will be a new method in the interface. In my mind, that should be independent.

222

Answer

Solution:

i am thinking of some apis i worked on that use one generic function for calls and a method parameter

heres some very basic pseudocode

public function call($method = 'fetchBar',$params=array()){
    $this->stopWatch->start($method);
    $this->{"$method"}($params);
    $this->stopWatch->stop($method);

}

private function fetchBar(){
    echo "yo";
}

maybe that helps

465

Answer

Solution:

I went with the decorator approach, just on a different level.

In my architecture, api service was using anHttpClientInterface, and each request was handled in the end with a call todoRequest. So there, the decorator made most sense without code duplication:

<?php
namespace Kopernikus\BookingService\Component\Http\Client;

use Kopernikus\BookingService\Component\Performance\PerformanceEntry;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Stopwatch\Stopwatch;

/**
 * ProfileClientDecorator
 **/
class ProfileClientDecorator implements HttpClientInterface
{
    /**
     * @var Stopwatch
     */
    private $stopwatch;
    /**
     * @var HttpClientInterface
     */
    private $client;
    /**
     * @var LoggerInterface
     */
    private $logger;

    /**
     * ProfileClientDecorator constructor.
     * @param HttpClientInterface $client
     * @param Stopwatch           $stopwatch
     * @param LoggerInterface     $logger
     */
    public function __construct(HttpClientInterface $client, Stopwatch $stopwatch, LoggerInterface $logger)
    {
        $this->client = $client;
        $this->stopwatch = $stopwatch;
        $this->logger = $logger;
    }

    /**
     * @param RequestInterface $request
     *
     * @return ResponseInterface
     */
    public function doRequest(RequestInterface $request)
    {
        $method =  $request->getMethod();
        $response = $this->doMeasuredRequest($request, $method);
        $performance = $this->getPerformance($method);
        $this->logPerformance($performance);

        return $response;
    }

    /**
     * @param RequestInterface $request
     * @param string           $method
     *
     * @return ResponseInterface
     */
    protected function doMeasuredRequest(RequestInterface $request, $method)
    {
        $this->stopwatch->start($method);
        $response = $this->client->doRequest($request);
        $this->stopwatch->stop($method);

        return $response;
    }

    /**
     * @param $method
     * @return PerformanceEntry
     */
    protected function getPerformance($method)
    {
        $event = $this->stopwatch->getEvent($method);
        $duration = $event->getDuration();

        return new PerformanceEntry($duration, $method);
    }

    /**
     * @param PerformanceEntry $performance
     */
    protected function logPerformance(PerformanceEntry $performance)
    {
        $context = [
            'performance' => [
                'duration_in_ms'       => $performance->getDurationInMs(),
                'request_name' => $performance->getRequestName(),
            ],
        ];

        $this->logger->info(
            "The request {$performance->getRequestName()} took {$performance->getDurationInMs()} ms",
            $context
        );
    }
}

And in myservices.yml:

performance_client_decorator:
    class: Kopernikus\Component\Http\Client\ProfileClientDecorator
        decorates: http.guzzle_client
            arguments:
                - @performance_client_decorator.inner
                - @stopwatch
                - @logger

People are also looking for solutions to the problem: Parse JSON & remove items in PHP

Source

Didn't find the answer?

Our community is visited by hundreds of web development professionals every day. Ask your question and get a quick answer for free.

Ask a Question

Write quick answer

Do you know the answer to this question? Write a quick response to it. With your help, we will make our community stronger.

Similar questions

Find the answer in similar questions on our website.