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 8d081b1
Show file tree
Hide file tree
Showing 19 changed files with 1,115 additions and 345 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($config);
}

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
84 changes: 1 addition & 83 deletions src/ClientResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -980,66 +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
if (isset($args['endpoint_discovery'])) {
if (($args['endpoint_discovery'] instanceof \Aws\EndpointDiscovery\Configuration
&& $args['endpoint_discovery']->isEnabled())
) {
$userAgent []= 'cfg/endpoint-discovery';
} elseif (is_array($args['endpoint_discovery'])
&& isset($args['endpoint_discovery']['enabled'])
&& $args['endpoint_discovery']['enabled']
) {
$userAgent []= 'cfg/endpoint-discovery';
}
}

// 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'];
}

$userAgent = [];
// Add the input to the end
if ($inputUserAgent){
if (!is_array($inputUserAgent)) {
Expand All @@ -1050,30 +992,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
);
}
}
}
163 changes: 163 additions & 0 deletions src/MetricsBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
<?php

namespace Aws;

/**
* @internal
*/
final class MetricsBuilder
{
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 string */
private static $METRIC_SEPARATOR = ",";
/** @var array $metrics */
private $metrics;
/** @var int $metricsSize */
private $metricsSize;

public function __construct()
{
$this->metrics = [];
// The first metrics does not include the separator
// therefore it is reduced by default.
$this->metricsSize = -(strlen(self::$METRIC_SEPARATOR));
}

/**
* Build the metrics string value.
*
* @return string
*/
public function build(): string
{
if (empty($this->metrics)) {
return "";
}

return $this->encode();
}

/**
* Encodes the metrics by separating each metric
* with a comma. Example: for the metrics[A,B,C] then
* the output would be "A,B,C".
*
* @return string
*/
private function encode(): string
{
return implode(self::$METRIC_SEPARATOR, array_keys($this->metrics));
}

/**
* Appends a metric into the internal metrics holder.
* It checks if the metric can be appended before doing so.
* If the metric can be appended then, it is added into the
* metrics holder and the current metrics size is increased
* by summing the length of the metric being appended plus the length
* of the separator used for encoding.
* Example: $currentSize = $currentSize + len($newMetric) + len($separator)
*
* @param string $metric
*
* @return void
*/
public function append(string $metric): void
{
if (!$this->canMetricBeAppended($metric)) {
return;
}

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

/**
* Validates if a metric can be appended by verifying if the current
* metrics size plus the new metric plus the length of the separator
* exceeds the metrics size limit. It also checks if the metric already
* exists, if so then it returns false.
* Example: metric can be appended just if:
* $currentSize + len($newMetric) + len($metricSeparator) <= MAX_SIZE
* and:
* $newMetric not in $existentMetrics
*
* @param string $newMetric
*
* @return bool
*/
private function canMetricBeAppended(string $newMetric): bool
{
if ($this->metricsSize
+ (strlen($newMetric) + strlen(self::$METRIC_SEPARATOR))
> 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;
}

/**
* Returns the metrics builder from
* @param CommandInterface $command
* @return MetricsBuilder
*/
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
Loading

0 comments on commit 8d081b1

Please sign in to comment.