Skip to content

Commit

Permalink
Upload code
Browse files Browse the repository at this point in the history
  • Loading branch information
solodkiy committed Sep 29, 2024
1 parent e7cb553 commit b3aa953
Show file tree
Hide file tree
Showing 12 changed files with 720 additions and 1 deletion.
31 changes: 31 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: PHPUnit Test

on:
push:
branches:
- '**'

jobs:
phpunit:
runs-on: ubuntu-latest
strategy:
matrix:
php-version: ['8.0', '8.1', '8.2', '8.3']
steps:
# Checkout the code
- name: Checkout code
uses: actions/checkout@v3

# Set up PHP with matrix version
- name: Set up PHP ${{ matrix.php-version }}
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}

# Install dependencies via Composer
- name: Install dependencies
uses: php-actions/composer@v6

# Run PHPUnit tests
- name: Run PHPUnit
run: ./vendor/bin/phpunit
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/vendor/
/composer.lock
/.phpunit.result.cache
/.idea
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2024 belka-car
Copyright (c) 2024 BelkaCar

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Gauge Exporter PHP Client

PHP Client for [Gague Exporter](https://github.com/belka-car/gague-exporter).


## Usage example
```php
<?php

use Belkacar\GaugeExporterClient\GaugeExporterClient;
use Belkacar\GaugeExporterClient\MetricBag;
use GuzzleHttp\Client;

require_once "vendor/autoload.php";

$bag = new MetricBag('metric-name');
$bag->increment(['a' => 'b'], 100);
$bag->increment(['a' => 'b', 'c' => 'd'], 500);

$client = new GaugeExporterClient(new Client(), 'https://127.0.0.1:8181', ['env' => 'prod']);
$client->send($bag, 150);
```
32 changes: 32 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "belkacar/gauge-exporter-client",
"description": "Gauge Exporter PHP client",
"type": "library",
"license": "MIT",
"require": {
"php": "^8.0",
"ext-json": "*",
"guzzlehttp/psr7": "^2.6",
"psr/http-client": "^1.0",
"webmozart/assert": "^1.11"
},
"autoload": {
"psr-4": {
"Belkacar\\GaugeExporterClient\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Belkacar\\GaugeExporterClient\\Tests\\": "tests/"
}
},
"config": {
"allow-plugins": {
"php-http/discovery": true
}
},
"require-dev": {
"phpunit/phpunit": "^9.6",
"php-http/mock-client": "^1.6"
}
}
17 changes: 17 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php"
backupGlobals="false"
backupStaticAttributes="false"
colors="true"
verbose="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false">
<testsuites>
<testsuite name="Unit Tests">
<directory>tests</directory>
</testsuite>
</testsuites>
</phpunit>
24 changes: 24 additions & 0 deletions src/Exception/BadResponseException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Belkacar\GaugeExporterClient\Exception;

use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Message\ResponseInterface;

final class BadResponseException extends \RuntimeException implements ClientExceptionInterface
{
private ResponseInterface $response;

public function __construct(ResponseInterface $response)
{
$this->response = $response;
parent::__construct('');
}

public function getResponse(): ResponseInterface
{
return $this->response;
}
}
61 changes: 61 additions & 0 deletions src/GaugeExporterClient.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

declare(strict_types=1);

namespace Belkacar\GaugeExporterClient;

use Belkacar\GaugeExporterClient\Exception\BadResponseException;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Client\ClientInterface;
use GuzzleHttp\Psr7\Request;
use stdClass;

final class GaugeExporterClient
{
private string $apiDomain;
private ClientInterface $client;
private array $defaultLabels;

public function __construct(ClientInterface $client, string $apiDomain, array $defaultLabels = [])
{
$this->client = $client;
$this->apiDomain = trim($apiDomain, " \n\r\t\v\0/");
$this->defaultLabels = MetricLine::normalizeLabels($defaultLabels);
}

/**
* @throws BadResponseException
* @throws ClientExceptionInterface
*/
public function send(MetricBag $metricBag, int $ttlSec): void
{
$body = [
'ttl' => $ttlSec,
'data' => $this->generateData($metricBag),
];
$request = new Request(
'PUT',
sprintf('%s/gauge/%s', $this->apiDomain, $metricBag->getMetricName()),
['Content-Type' => 'application/json'],
json_encode($body),
);

$response = $this->client->sendRequest($request);
if ($response->getStatusCode() !== 200) {
throw new BadResponseException($response);
}
}

private function generateData(MetricBag $metricBag): array
{
$result = [];
foreach ($metricBag->getLogLines() as $logLine) {
$labels = array_merge($this->defaultLabels, $logLine->getLabels());
$result[] = [
'labels' => !empty($labels) ? $labels : new stdClass(),
'value' => $logLine->getValue()
];
}
return $result;
}
}
63 changes: 63 additions & 0 deletions src/MetricBag.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

declare(strict_types=1);

namespace Belkacar\GaugeExporterClient;

use InvalidArgumentException;

final class MetricBag
{
private const METRIC_NAME_PATTERN = '/^[a-zA-Z_:][a-zA-Z0-9_:.]*$/';

private string $metric;

/**
* @var array<string, float>
*/
private array $labelsToValuePair;

public function __construct(string $metricName)
{
if (!preg_match(self::METRIC_NAME_PATTERN, $metricName)) {
throw new InvalidArgumentException('Metric name "' . $metricName . '" does not match ' . self::METRIC_NAME_PATTERN);
}
$this->metric = $metricName;
$this->labelsToValuePair = [];
}

public function set(array $labels, float $value): void
{
$labels = MetricLine::normalizeLabels($labels);

$this->labelsToValuePair[json_encode($labels)] = $value;
}

public function increment(array $labels, float $value = 1): void
{
$labels = MetricLine::normalizeLabels($labels);

$key = json_encode($labels);
if (!array_key_exists($key, $this->labelsToValuePair)) {
$this->labelsToValuePair[$key] = 0;
}
$this->labelsToValuePair[$key] += $value;
}

public function getMetricName(): string
{
return $this->metric;
}

/**
* @return list<MetricLine>
*/
public function getLogLines(): array
{
$result = [];
foreach ($this->labelsToValuePair as $labels => $value) {
$result[] = new MetricLine(json_decode($labels, true), $value);
}
return $result;
}
}
39 changes: 39 additions & 0 deletions src/MetricLine.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace Belkacar\GaugeExporterClient;

use InvalidArgumentException;

final class MetricLine
{
private array $labels;
private float $value;

public function __construct(array $labels, float $value)
{
$this->labels = self::normalizeLabels($labels);
$this->value = $value;
}

public function getValue(): float
{
return $this->value;
}

public function getLabels(): array
{
return $this->labels;
}

public static function normalizeLabels(array $labels): array
{
if (count(array_filter(array_keys($labels), 'is_int')) > 0) {
throw new InvalidArgumentException('Labels must be specified as associative array');
}
asort($labels);

return $labels;
}
}
Loading

0 comments on commit b3aa953

Please sign in to comment.