Aller au contenu
getnextpdf.com

Enterprise édition

Metering — référence détaillée

L’espace de noms NextPDF\Enterprise\Metering fournit un metering d’utilisation au niveau de l’orchestration, pour la visibilité de facturation et l’audit. La surface publique compte six symboles : MeterCollector, MeterEntry, MeteringReporter, MeteringBackendInterface, PrometheusMeteringBackend et PrometheusPushgatewayException. Le collecteur met en tampon des entrées immuables en mémoire et les vide (flush) par lots. Le reporter diffuse chaque lot vers un ou plusieurs backends, avec réessai par backend et isolation des pannes. Le metering fonctionne au mieux (best-effort) et n’est jamais fatal : une panne d’un backend de metering dégrade l’observabilité, jamais le traitement des documents. Ce flux n’est pas la source de référence pour l’application des quotas. Pour le guide au niveau du flux de travail, voir Metering.

Cette capacité est fournie dans NextPDF Enterprise (nextpdf/enterprise) et s’active avec une enveloppe de licence de niveau Enterprise. Un déploiement sans ce droit ne charge pas les classes de la capacité. Compare les éditions et procure-toi une licence.

Le metering est une capacité Enterprise de base, disponible dès que le paquet Enterprise est installé ; il n’existe pas de drapeau distinct par fonctionnalité. NextPDF Core (Apache-2.0) et NextPDF Pro n’ont ni collecteur, ni reporter, ni surface de backend ; le contrat n’est fourni que dans nextpdf/enterprise.

SymboleParamètresComportement par défautRenvoieLève ou échoue avecNotes
MeterCollector::__constructMeteringReporter $reporter, int $bufferSize = 100Crée un collecteur avec un tampon vide en mémoireNouveau MeterCollectorNe lève pas$bufferSize est documenté positive-int
MeterCollector::recordstring $operation, int $count, string $tenantId, string $licenseId, int $pagesProcessed = 0, float $durationMs = 0.0, array $metadata = []Ajoute un MeterEntry immuable horodaté à l’instant courant ; se vide automatiquement quand le tampon atteint $bufferSizevoidNe lève pas ; un flush automatique délègue au reporter, qui ne lève jamaisL’horodatage est pris au moment de l’enregistrement
MeterCollector::flushRemet toutes les entrées en tampon au reporter ; un tampon vide est sans effetvoidNe lève pas ; les pannes de backend sont absorbées par le reporterLe tampon est échangé avant la remise ; sûr en réentrance
MeterCollector::bufferCountRenvoie le nombre d’entrées en tamponint<0, max>Ne lève pasDiagnostics et décisions de contre-pression
MeterCollector::registerShutdownFlushEnregistre flush() via register_shutdown_functionvoidNe lève pasÀ appeler une fois au bootstrap dans les déploiements PHP-FPM
MeterEntry::__constructstring $operation, int $count, DateTimeImmutable $timestamp, string $tenantId, string $licenseId, int $pagesProcessed = 0, float $durationMs = 0.0, array $metadata = []Stocke les valeurs fournies telles quellesNouveau MeterEntryAucun @throws déclaré ; PHP lève TypeError sur des types d’arguments incohérents sous strict_typesfinal readonly ; les huit propriétés promues sont toutes publiques
MeteringReporter::__constructlist<MeteringBackendInterface> $backends, int $maxRetries = 2, LoggerInterface $logger = new NullLogger()Valide et stocke la liste des backendsNouveau MeteringReporterInvalidArgumentException quand $backends est vide$maxRetries compte le nombre total de tentatives de livraison par backend
MeteringReporter::reportlist<MeterEntry> $entriesLivre le lot à chaque backend indépendamment, avec réessai par backendvoidNe lève pas ; les tentatives épuisées sont journalisées au niveau error et le lot de ce backend est abandonnéUne liste vide est sans effet
MeteringBackendInterface::reportlist<MeterEntry> $entriesLivre un lot au backendvoidRuntimeException quand le backend est injoignableLes implémentations DOIVENT être idempotentes (déduplication par timestamp + operation + tenantId)
MeteringBackendInterface::isHealthySonde d’accessibilitéboolAucun @throws déclaréDiagnostics uniquement ; le reporter ne s’y conditionne pas
MeteringBackendInterface::backendNameNom de backend pour diagnosticnon-empty-stringAucun @throws déclaréPar exemple "prometheus", "billing-api", "null"
PrometheusMeteringBackend::__constructClientInterface $httpClient, RequestFactoryInterface $requestFactory, StreamFactoryInterface $streamFactory, string $pushgatewayUrl, string $jobName = 'nextpdf_metering'Configure une cible de push PushgatewayNouveau PrometheusMeteringBackendNe lève pasLe client PSR-18 et les fabriques PSR-17 sont injectés
PrometheusMeteringBackend::reportlist<MeterEntry> $entriesAgrège le lot par série opération-et-tenant et envoie (POST) le texte d’exposition vers <pushgatewayUrl>/metrics/job/<jobName>voidPrometheusPushgatewayException sur un statut non-2xx ou un échec de transport PSR-18Une liste vide est sans effet
PrometheusMeteringBackend::isHealthySonde le point de terminaison de santé de Pushgateway ; true uniquement sur HTTP 200boolNe lève pas ; tout échec renvoie falseSonde GET en lecture seule
PrometheusMeteringBackend::backendNameRenvoie "prometheus"non-empty-stringNe lève pasConstante
PrometheusPushgatewayExceptionSignale un échec de livraison PushgatewayEst le throwablefinal ; étend RuntimeException
public function __construct(
private readonly MeteringReporter $reporter,
private readonly int $bufferSize = 100,
) {}
public function record(
string $operation,
int $count,
string $tenantId,
string $licenseId,
int $pagesProcessed = 0,
float $durationMs = 0.0,
array $metadata = [],
): void
public function flush(): void
public function bufferCount(): int
public function registerShutdownFlush(): void
public function __construct(
public string $operation,
public int $count,
public DateTimeImmutable $timestamp,
public string $tenantId,
public string $licenseId,
public int $pagesProcessed = 0,
public float $durationMs = 0.0,
public array $metadata = [],
) {}
public function report(array $entries): void;
public function isHealthy(): bool;
public function backendName(): string;
public function __construct(
array $backends,
private readonly int $maxRetries = 2,
private readonly LoggerInterface $logger = new NullLogger(),
)
public function report(array $entries): void
public function __construct(
private readonly ClientInterface $httpClient,
private readonly RequestFactoryInterface $requestFactory,
private readonly StreamFactoryInterface $streamFactory,
private readonly string $pushgatewayUrl,
private readonly string $jobName = self::DEFAULT_JOB_NAME,
) {}
final class PrometheusPushgatewayException extends RuntimeException {}

Propriétés publiques readonly de MeterEntry

PropriétéTypeSignification
$operationnon-empty-stringType d’opération, par exemple "parse", "compress", "embed", "rag_query"
$countpositive-intNombre d’unités consommées
$timestampDateTimeImmutableMoment où l’opération a eu lieu ; le collecteur l’horodate au moment de l’enregistrement
$tenantIdnon-empty-stringIdentifiant de tenant
$licenseIdnon-empty-stringIdentifiant de licence
$pagesProcessedint<0, max>Pages PDF traitées ; 0 pour les opérations non-PDF
$durationMsfloatDurée de l’opération en millisecondes
$metadataarray<string, mixed>Métadonnées libres spécifiques à l’opération
  • MeterCollector::record() construit un MeterEntry immuable, l’horodate à l’instant courant et l’ajoute au tampon en mémoire. Quand le tampon atteint $bufferSize entrées, le collecteur se vide automatiquement.
  • flush() est idempotent et sûr en réentrance. Un tampon vide est sans effet. Le tampon est échangé avant que le lot ne soit remis au reporter, de sorte qu’un flush réentrant ne peut pas envoyer deux fois.
  • MeteringReporter refuse la construction avec une liste de backends vide. Cette InvalidArgumentException est la seule exception sur le chemin collecteur/reporter.
  • MeteringReporter::report() livre chaque lot à chaque backend indépendamment. Un backend en échec n’empêche jamais un autre backend de recevoir le même lot.
  • $maxRetries compte le nombre total de tentatives de livraison par backend ; la valeur par défaut 2 signifie une tentative initiale plus un réessai. Chaque tentative échouée journalise un avertissement avec le nom du backend, le numéro de tentative et le nombre d’entrées.
  • Quand la dernière tentative pour un backend échoue, le reporter journalise en plus au niveau error avec le nombre d’entrées abandonnées, puis passe à la suite. Il ne lève jamais depuis report(), donc les appelants ne doivent pas déduire la livraison d’un retour normal.
  • Les backends DOIVENT être idempotents. Le contrat de l’interface exige une déduplication indexée sur le timestamp, l’opération et l’identifiant de tenant. Le reporter lui-même ne déduplique pas.
  • PrometheusMeteringBackend::report() agrège le lot en séries par opération et par tenant et envoie (POST) l’exposition texte Prometheus vers <pushgatewayUrl>/metrics/job/<jobName> avec le Content-Type text/plain; version=0.0.4. Le nom de job par défaut est nextpdf_metering.
  • La charge utile poussée porte trois compteurs — nextpdf_operations_total, nextpdf_pages_processed_total et nextpdf_operation_duration_ms_total — chacun étiqueté par opération et par tenant.
  • Ce flux de metering ne fait pas référence. L’application des quotas et le metering de calcul de référence consomment le chiffre d’utilisation de référence distinct du déploiement, jamais ce tampon. Une lacune dans le metering d’orchestration est une lacune d’observabilité, pas une lacune d’exactitude de facturation.
  • Lot dupliqué ou rejoué. Absorbé par l’idempotence du backend ; le reporter ne déduplique pas. Ne compte pas sur une livraison exactement-une-fois.
  • Réessais épuisés. Le lot destiné à ce backend est abandonné et journalisé au niveau error. Un retour normal de report() ou flush() n’implique jamais la livraison.
  • Sortie du processus avant le flush. Le tampon n’existe qu’en mémoire. Un crash, ou une sortie sans gestionnaire d’arrêt enregistré, perd les entrées en tampon.
  • Incompatibilité de modèle de worker. Les déploiements PHP-FPM appellent registerShutdownFlush() une fois au bootstrap pour que le reliquat se vide à la fin de la requête. Les workers de longue durée (Octane, worker Symfony, worker de file d’attente) doivent au contraire se vider sur un minuteur périodique ; sinon les entrées s’accumulent jusqu’à la sortie du processus worker.
  • $bufferSize inférieur à 1. Viole le contrat documenté positive-int ; le résultat observable est un flush à chaque appel de record().
  • Métadonnées sensibles. $metadata est libre et peut porter un contexte d’opération sensible. Le stockage, la rétention et le contrôle d’accès relèvent de la responsabilité de l’opérateur du backend.
  • Échec de livraison Pushgateway. Une réponse non-2xx lève PrometheusPushgatewayException portant le statut HTTP et le corps de la réponse ; un échec de transport PSR-18 est enveloppé dans le même type d’exception. La boucle de réessai et d’isolation du reporter absorbe les deux.
  • Sonde de santé. PrometheusMeteringBackend::isHealthy() émet un GET vers <pushgatewayUrl>/-/healthy et renvoie true uniquement sur HTTP 200. Toute erreur de transport renvoie false ; la sonde ne lève jamais.
  • Valeurs d’étiquette hostiles. Les caractères barre oblique inverse, guillemet double et saut de ligne dans les valeurs d’opération ou de tenant sont échappés à l’émission, de sorte qu’une valeur d’étiquette ne peut ni injecter des lignes d’exposition supplémentaires ni corrompre le bloc d’étiquettes.
  • Mode FIPS. Le collecteur et le reporter n’effectuent aucune opération cryptographique et n’ont aucun comportement propre à FIPS. Un backend qui signe ou chiffre en transit hérite de la posture FIPS de son fournisseur cryptographique hôte.

Aucun standard externe ne régit le contrat en-processus du collecteur, du reporter ou du backend ; il n’existe aucune spécification normative à citer, donc cette page ne porte volontairement aucune citation RAG. Le backend Prometheus émet le format d’exposition texte de Prometheus et pousse avec le Content-Type text/plain; version=0.0.4 ; ce format est une convention d’écosystème plutôt qu’un standard ISO ou IETF, et l’affirmation s’appuie sur la source du produit. NextPDF ne formule aucune revendication de conformité ou de certification pour cette surface.

  • Toutes les classes déclarent strict_types=1 et sont final ; MeterEntry est final readonly avec des propriétés publiques promues. Des types d’arguments incohérents lèvent un TypeError PHP chez l’appelant.
  • Les classes du module portent une annotation de paquet @since 2.1.0 ; PrometheusPushgatewayException porte @since 3.2.0.
  • Le logger du reporter est par défaut un NullLogger PSR-3. Injecte un vrai logger en production, sinon les lots abandonnés ne laissent aucune trace.
  • Tests unitaires : implémente un faux MeteringBackendInterface et construis directement des valeurs MeterEntry. Le backend Prometheus prend des abstractions PSR-18/PSR-17, de sorte qu’un client HTTP simulé exerce hors ligne l’intégralité du chemin de push.
  • Tests aux limites recommandés : tampon exactement à $bufferSize, flush réentrant, flush de tampon vide, un backend en échec pendant qu’un second réussit, et journalisation d’épuisement des réessais.
  • Les implémenteurs de backend lèvent RuntimeException (ou une sous-classe) en cas d’échec de livraison ; le reporter l’absorbe. Respecte l’exigence d’idempotence avant d’ajouter d’autres réessais en amont.

Cette page ne documente que le comportement observable de l’extérieur et la surface publique de l’API prise en charge. Les chemins d’espaces de noms internes, les classes utilitaires, les tables de mécanismes, les noms de fichiers de runbook et les préfixes de ticket sont hors périmètre.