Skip to content

Commit

Permalink
enhancement: user agent 2.1
Browse files Browse the repository at this point in the history
This change provides:
- A builder class for appending metrics
- Default initialization of a metrics builder within command instantiation
- Wraps user agent logic into a single middleware class
- Adds middlewares into the different features from where metrics can be gather, to acomplish this purpose.
  • Loading branch information
yenfryherrerafeliz committed Sep 20, 2024
1 parent 8cd4349 commit c1ae03a
Show file tree
Hide file tree
Showing 19 changed files with 950 additions and 332 deletions.
17 changes: 17 additions & 0 deletions src/AwsClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,11 @@ public function __construct(array $args)
if (isset($args['with_resolved'])) {
$args['with_resolved']($config);
}
MetricsBuilder::appendMetricsCaptureMiddleware(
$this->getHandlerList(),
MetricsBuilder::RESOURCE_MODEL
);
$this->addUserAgentMiddleware($args);
}

public function getHandlerList()
Expand Down Expand Up @@ -490,6 +495,11 @@ private function addSignatureMiddleware(array $args)
$region = $signingRegionSet
?? $commandSigningRegionSet
?? $region;

MetricsBuilder::appendMetricsCaptureMiddleware(
$this->getHandlerList(),
MetricsBuilder::SIGV4A_SIGNING
);
}

return SignatureProvider::resolve($provider, $signatureVersion, $name, $region);
Expand Down Expand Up @@ -595,6 +605,13 @@ private function addEndpointV2Middleware()
);
}

private function addUserAgentMiddleware($args)
{
$this->getHandlerList()->appendSign(
UserAgentMiddleware::wrap($args)
);
}

/**
* Retrieves client context param definition from service model,
* creates mapping of client context param names with client-provided
Expand Down
71 changes: 1 addition & 70 deletions src/ClientResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -980,37 +980,8 @@ public static function _default_app_id(array $args)

public static function _apply_user_agent($inputUserAgent, array &$args, HandlerList $list)
{
// Add SDK version
$userAgent = ['aws-sdk-php/' . Sdk::VERSION];

// User Agent Metadata
$userAgent[] = 'ua/2.0';

// If on HHVM add the HHVM version
if (defined('HHVM_VERSION')) {
$userAgent []= 'HHVM/' . HHVM_VERSION;
}

// Add OS version
$disabledFunctions = explode(',', ini_get('disable_functions'));
if (function_exists('php_uname')
&& !in_array('php_uname', $disabledFunctions, true)
) {
$osName = "OS/" . php_uname('s') . '#' . php_uname('r');
if (!empty($osName)) {
$userAgent []= $osName;
}
}

// Add the language version
$userAgent []= 'lang/php#' . phpversion();

// Add exec environment if present
if ($executionEnvironment = getenv('AWS_EXECUTION_ENV')) {
$userAgent []= $executionEnvironment;
}

// Add endpoint discovery if set
$userAgent = [];
if (isset($args['endpoint_discovery'])) {
if (($args['endpoint_discovery'] instanceof \Aws\EndpointDiscovery\Configuration
&& $args['endpoint_discovery']->isEnabled())
Expand All @@ -1024,22 +995,6 @@ public static function _apply_user_agent($inputUserAgent, array &$args, HandlerL
}
}

// Add retry mode if set
if (isset($args['retries'])) {
if ($args['retries'] instanceof \Aws\Retry\Configuration) {
$userAgent []= 'cfg/retry-mode#' . $args["retries"]->getMode();
} elseif (is_array($args['retries'])
&& isset($args["retries"]["mode"])
) {
$userAgent []= 'cfg/retry-mode#' . $args["retries"]["mode"];
}
}

// AppID Metadata
if (!empty($args['app_id'])) {
$userAgent[] = 'app/' . $args['app_id'];
}

// Add the input to the end
if ($inputUserAgent){
if (!is_array($inputUserAgent)) {
Expand All @@ -1050,30 +1005,6 @@ public static function _apply_user_agent($inputUserAgent, array &$args, HandlerL
}

$args['ua_append'] = $userAgent;

$list->appendBuild(static function (callable $handler) use ($userAgent) {
return function (
CommandInterface $command,
RequestInterface $request
) use ($handler, $userAgent) {
return $handler(
$command,
$request->withHeader(
'X-Amz-User-Agent',
implode(' ', array_merge(
$userAgent,
$request->getHeader('X-Amz-User-Agent')
))
)->withHeader(
'User-Agent',
implode(' ', array_merge(
$userAgent,
$request->getHeader('User-Agent')
))
)
);
};
});
}

public static function _apply_endpoint($value, array &$args, HandlerList $list)
Expand Down
3 changes: 3 additions & 0 deletions src/Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ public function __construct($name, array $args = [], HandlerList $list = null)
if (!isset($this->data['@context'])) {
$this->data['@context'] = [];
}
$this->data['@context'][
MetricsBuilder::COMMAND_METRICS_BUILDER
] = new MetricsBuilder();
}

public function __clone()
Expand Down
29 changes: 28 additions & 1 deletion src/EndpointV2/EndpointV2Middleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Aws\Api\Service;
use Aws\Auth\Exception\UnresolvedAuthSchemeException;
use Aws\CommandInterface;
use Aws\MetricsBuilder;
use Closure;
use GuzzleHttp\Promise\Promise;
use function JmesPath\search;
Expand Down Expand Up @@ -98,8 +99,15 @@ public function __invoke(CommandInterface $command)
$operation = $this->api->getOperation($command->getName());
$commandArgs = $command->toArray();
$providerArgs = $this->resolveArgs($commandArgs, $operation);
$this->hookAccountIdMetric(
$providerArgs[self::ACCOUNT_ID_PARAM] ?? null,
$command
);
$endpoint = $this->endpointProvider->resolveEndpoint($providerArgs);

$this->hookAccountIdEndpointMetric(
$endpoint,
$command
);
if (!empty($authSchemes = $endpoint->getProperty('authSchemes'))) {
$this->applyAuthScheme(
$authSchemes,
Expand Down Expand Up @@ -394,4 +402,23 @@ private function resolveAccountId(): ?string

return $identity->getAccountId();
}

private function hookAccountIdMetric($accountId, &$command)
{
if (!empty($accountId)) {
MetricsBuilder::fromCommand($command)->append(
MetricsBuilder::RESOLVED_ACCOUNT_ID
);
}
}

private function hookAccountIdEndpointMetric($endpoint, &$command)
{
$regex = "/^(https?:\/\/\d{12}\.[^\s\/$.?#].\S*)$/";
if (preg_match($regex, $endpoint->getUrl())) {
MetricsBuilder::fromCommand($command)->append(
MetricsBuilder::ACCOUNT_ID_ENDPOINT
);
}
}
}
117 changes: 117 additions & 0 deletions src/MetricsBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?php

namespace Aws;

/**
* @internal
*/
final class MetricsBuilder
{
/** @var string */
const COMMAND_METRICS_BUILDER = "CommandMetricsBuilder";
const RESOURCE_MODEL = "A";
const WAITER = "B";
const PAGINATOR = "C";
const RETRY_MODE_LEGACY = "D";
const RETRY_MODE_STANDARD = "E";
const RETRY_MODE_ADAPTIVE = "F";
const S3_TRANSFER = "G";
const S3_CRYPTO_V1N = "H";
const S3_CRYPTO_V2 = "I";
const ENDPOINT_OVERRIDE = "N";
const ACCOUNT_ID_ENDPOINT = "O";
const ACCOUNT_ID_MODE_PREFERRED = "P";
const ACCOUNT_ID_MODE_DISABLED = "Q";
const ACCOUNT_ID_MODE_REQUIRED = "R";
const SIGV4A_SIGNING = "S";
const RESOLVED_ACCOUNT_ID = "T";
const FLEXIBLE_CHECKSUMS_REQ_CRC32 = "U";
const FLEXIBLE_CHECKSUMS_REQ_CRC32C = "V";
const FLEXIBLE_CHECKSUMS_REQ_CRC64 = "W";
const FLEXIBLE_CHECKSUMS_REQ_SHA1 = "X";
const FLEXIBLE_CHECKSUMS_REQ_SHA256 = "Y";


/** @var int */
private static $MAX_METRICS_SIZE = 1024; // 1KB or 1024 B

/** @var array $metrics */
private $metrics;
/** @var int $metricsSize */
private $metricsSize;

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

public function build(): string
{
if (empty($this->metrics)) {
return "";
}

return $this->encode();
}

public function append(string $metric): void
{
if (!$this->canMetricBeAppended($metric)) {
return;
}

$this->metrics[$metric] = true;
$this->metricsSize += strlen($metric);
}

private function encode(): string
{
return implode(',', array_keys($this->metrics));
}

private function canMetricBeAppended(string $newMetric): bool
{
if ((strlen($newMetric) + $this->metricsSize) >
self::$MAX_METRICS_SIZE) {
trigger_error(
'The metric ' . $newMetric
. ' can not be added due to size constraints',
E_USER_WARNING
);

return false;
}

if (isset($this->metrics[$newMetric])) {
trigger_error(
'The metric ' . $newMetric. ' is already appended!',
E_USER_WARNING
);

return false;
}

return true;
}

public static function fromCommand(CommandInterface $command): MetricsBuilder
{
return $command['@context'][MetricsBuilder::COMMAND_METRICS_BUILDER];
}

public static function appendMetricsCaptureMiddleware(
HandlerList $handlerList,
$metric
): void
{
$handlerList->appendBuild(
Middleware::tap(
function (CommandInterface $command) use ($metric) {
self::fromCommand($command)->append(
$metric
);
}
)
);
}
}
4 changes: 4 additions & 0 deletions src/ResultPaginator.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ public function __construct(
$this->operation = $operation;
$this->args = $args;
$this->config = $config;
MetricsBuilder::appendMetricsCaptureMiddleware(
$this->client->getHandlerList(),
MetricsBuilder::PAGINATOR
);
}

/**
Expand Down
35 changes: 35 additions & 0 deletions src/S3/ApplyChecksumMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

use Aws\Api\Service;
use Aws\CommandInterface;
use Aws\MetricsBuilder;
use GuzzleHttp\Psr7;
use InvalidArgumentException;
use Psr\Http\Message\RequestInterface;
Expand Down Expand Up @@ -82,6 +83,9 @@ public function __invoke(
. implode(", ", $supportedAlgorithms) . "."
);
}

$this->hookChecksumAlgorithmMetric($requestedAlgorithm, $command);

return $next($command, $request);
}

Expand All @@ -94,6 +98,7 @@ public function __invoke(
//S3Express doesn't support MD5; default to crc32 instead
if ($this->isS3Express($command)) {
$request = $this->addAlgorithmHeader('crc32', $request, $body);
$this->hookChecksumAlgorithmMetric('crc32', $command);
} elseif (!$request->hasHeader('Content-MD5')) {
// Set the content MD5 header for operations that require it.
$request = $request->withHeader(
Expand Down Expand Up @@ -144,4 +149,34 @@ private function isS3Express(CommandInterface $command): bool
return isset($command['@context']['signing_service'])
&& $command['@context']['signing_service'] === 's3express';
}

private function hookChecksumAlgorithmMetric($algorithm, &$command)
{
if (empty($algorithm)) {
return;
}

if ($algorithm === 'crc32') {
MetricsBuilder::fromCommand($command)->append(
MetricsBuilder::FLEXIBLE_CHECKSUMS_REQ_CRC32
);
} elseif ($algorithm === 'crc32c') {
MetricsBuilder::fromCommand($command)->append(
MetricsBuilder::FLEXIBLE_CHECKSUMS_REQ_CRC32C
);
} elseif ($algorithm === 'crc64') {
MetricsBuilder::fromCommand($command)->append(
MetricsBuilder::FLEXIBLE_CHECKSUMS_REQ_CRC64
);
} elseif ($algorithm === 'sha1') {
MetricsBuilder::fromCommand($command)->append(
MetricsBuilder::FLEXIBLE_CHECKSUMS_REQ_SHA1
);
} elseif ($algorithm === 'sha256') {
MetricsBuilder::fromCommand($command)->append(
MetricsBuilder::FLEXIBLE_CHECKSUMS_REQ_SHA256
);
}

}
}
Loading

0 comments on commit c1ae03a

Please sign in to comment.