├── .editorconfig ├── .github └── workflows │ ├── phpunit.yml │ └── psalm.yml ├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── ebnf.pp2 ├── phpunit.xml ├── psalm.xml ├── src ├── Ast │ ├── CommentNode.php │ ├── HelpNode.php │ ├── LabelNode.php │ ├── LabelsNode.php │ ├── MetricDataNode.php │ ├── MetricNode.php │ ├── MetricTimestampNode.php │ ├── MetricValueNode.php │ ├── SchemaNode.php │ └── TypeNode.php ├── Exceptions │ └── GrammarFileNotFoundException.php ├── Parser.php ├── ParserFactory.php └── grammar.php └── tests ├── Ast ├── SchemaNodeTest.php └── TestCase.php ├── Parsers ├── HelpTest.php └── TestCase.php ├── TestCase.php └── schema.txt /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | indent_style = space 10 | indent_size = 4 11 | trim_trailing_whitespace = true 12 | -------------------------------------------------------------------------------- /.github/workflows/phpunit.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | 6 | name: phpunit 7 | 8 | jobs: 9 | phpunit: 10 | uses: spiral/gh-actions/.github/workflows/phpunit.yml@master 11 | with: 12 | os: >- 13 | ['ubuntu-latest'] 14 | php: >- 15 | ['8.1'] 16 | stability: >- 17 | ['prefer-stable'] 18 | -------------------------------------------------------------------------------- /.github/workflows/psalm.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | 6 | name: static analysis 7 | 8 | jobs: 9 | psalm: 10 | uses: spiral/gh-actions/.github/workflows/psalm.yml@master 11 | with: 12 | os: >- 13 | ['ubuntu-latest'] 14 | php: >- 15 | ['8.1'] 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | vendor 3 | composer.lock 4 | protoc-gen-php-grpc 5 | phpunit.result.cache 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Pavel Buchnev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Prometheus metrics parser for PHP 2 | 3 | [![PHP Version Require](https://poser.pugx.org/butschster/prometheus-parser/require/php)](https://packagist.org/packages/butschster/prometheus-parser) 4 | [![Latest Stable Version](https://poser.pugx.org/butschster/prometheus-parser/v/stable)](https://packagist.org/packages/butschster/prometheus-parser) 5 | [![phpunit](https://github.com/butschster/prometheus-parser/actions/workflows/phpunit.yml/badge.svg)](https://github.com/butschster/prometheus-parser/actions) 6 | [![psalm](https://github.com/butschster/prometheus-parser/actions/workflows/psalm.yml/badge.svg)](https://github.com/butschster/prometheus-parser/actions) 7 | [![Total Downloads](https://poser.pugx.org/butschster/prometheus-parser/downloads)](https://packagist.org/packages/butschster/prometheus-parser) 8 | 9 | ![Github cover Prometheus parser](https://user-images.githubusercontent.com/773481/199663705-3540ce54-086e-476e-bf91-cd607c98df9f.jpg) 10 | 11 | Welcome to the Prometheus Metrics Parser! This package makes it easy to extract valuable information from metrics in the Prometheus text-based format. Whether you're looking to analyze your metrics data, integrate it into other systems, or just want a better way to visualize it, this package has you covered. 12 | 13 | With just a few lines of code, you can easily extract valuable insights from your Prometheus metrics. 14 | 15 | ## Requirements 16 | 17 | - PHP 8.1 and above 18 | 19 | ## Quick start 20 | 21 | To install the package, run the following command from the root directory of your project: 22 | 23 | ```shell 24 | composer require butschster/prometheus-parser 25 | ``` 26 | 27 | That's it! 28 | 29 | 30 | ## Usage 31 | 32 | To get started, simply pass a string containing your Prometheus metric data to the `parse()` method. The method will return a schema object with metric objects, each of which contains the following properties: 33 | 34 | ```php 35 | use Butschster\Prometheus\ParserFactory; 36 | 37 | $parser = ParserFactory::create(); 38 | 39 | $schema = $parser->parse(<<getMetrics(); // array of Metric 84 | 85 | $metrics['http_requests_total']->description; // The total number of HTTP requests. 86 | $metrics['http_requests_total']->type; // counter 87 | $metrics['http_requests_total']->name; // http_requests_total 88 | 89 | foreach ($metrics['go_gc_duration_seconds'] as $metric) { 90 | $metric->name; // go_gc_duration_seconds 91 | $metric->value; // Value 92 | $metric->timestamp; // Timestamp 93 | $metric->lables; // Array of labels 94 | } 95 | ``` 96 | 97 | # Enjoy! 98 | 99 | --- 100 | 101 | ## License 102 | 103 | The MIT License (MIT). Please see [`LICENSE`](./LICENSE) for more information. 104 | 105 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "butschster/prometheus-parser", 3 | "description": "Prometheus parser written on PHP 8", 4 | "keywords": [ 5 | "php8", 6 | "php", 7 | "prometheus", 8 | "ddl", 9 | "prometheus-parser" 10 | ], 11 | "license": "MIT", 12 | "authors": [ 13 | { 14 | "name": "Pavel Buchnev", 15 | "email": "butschster@gmail.com" 16 | } 17 | ], 18 | "require": { 19 | "php": ">=8.1", 20 | "phplrt/runtime": "^3.2" 21 | }, 22 | "require-dev": { 23 | "phplrt/phplrt": "^3.2", 24 | "mockery/mockery": "^1.5", 25 | "phpunit/phpunit": "^9.5", 26 | "vimeo/psalm": "^4.9" 27 | }, 28 | "autoload": { 29 | "psr-4": { 30 | "Butschster\\Prometheus\\": "src/" 31 | } 32 | }, 33 | "autoload-dev": { 34 | "psr-4": { 35 | "Butschster\\Prometheus\\Tests\\": "tests/" 36 | } 37 | }, 38 | "minimum-stability": "dev", 39 | "prefer-stable": true 40 | } 41 | -------------------------------------------------------------------------------- /ebnf.pp2: -------------------------------------------------------------------------------- 1 | /** 2 | * Prometheus version 2.0 text-based format schema parser. 3 | */ 4 | 5 | 6 | /** 7 | * -------------------------------------------------------------------------- 8 | * Values 9 | * -------------------------------------------------------------------------- 10 | */ 11 | %token T_WHITESPACE \s+ 12 | 13 | %token T_HELP HELP 14 | %token T_TYPE TYPE 15 | %token T_COMMENT ^\#\s[a-zA-Z0-9\s\.\,\!\?\:]+\: 16 | 17 | /** 18 | * -------------------------------------------------------------------------- 19 | * Numbers 20 | * -------------------------------------------------------------------------- 21 | */ 22 | %token T_FLOAT [0-9]+\.[0-9]+(e(\+|\-)?[0-9]+)? 23 | %token T_INF (\+|\-)Inf 24 | %token T_INT (\-)?[0-9]+ 25 | 26 | %token T_METRIC_TYPE (summary|counter|gauge|histogram|untyped) 27 | %token T_METRIC_NAME [a-z_]+ 28 | %token T_COMMA \, 29 | %token T_TEXT [a-zA-Z0-9\s\.\,\!\?\:]+ 30 | %token T_QUOTED_STRING \"([a-zA-Z0-9\s\.\,\!\?\:\\\"\+]+)\" // "string" 31 | 32 | /** 33 | * -------------------------------------------------------------------------- 34 | * Syntax 35 | * -------------------------------------------------------------------------- 36 | */ 37 | %token T_EQUAL \= 38 | %token T_HASH \# 39 | %token T_LBRACE { 40 | %token T_RBRACE } 41 | %token T_DOT \. 42 | %token T_EOR \\r 43 | %token T_EOL \\n 44 | 45 | 46 | /** 47 | * -------------------------------------------------------------------------- 48 | * Prometheus Grammar 49 | * -------------------------------------------------------------------------- 50 | * @see https://prometheus.io/docs/instrumenting/exposition_formats/ 51 | */ 52 | #Document 53 | : 54 | Schema() 55 | ; 56 | 57 | #Schema -> { 58 | return new \Butschster\Prometheus\Ast\SchemaNode($children); 59 | } 60 | : 61 | MetricData()* 62 | ; 63 | 64 | #MetricData -> { 65 | return new \Butschster\Prometheus\Ast\MetricDataNode($children); 66 | } 67 | : 68 | ( 69 | Comment()* 70 | Help() 71 | Type() 72 | Metric()* 73 | ) 74 | 75 | /** 76 | * Possible value: 77 | * # A histogram, which has a pretty complex representation in the text format: 78 | */ 79 | #Comment -> { 80 | return new \Butschster\Prometheus\Ast\CommentNode($children); 81 | } 82 | : ( 83 | ::T_WHITESPACE::* 84 | 85 | Eol()? 86 | ) 87 | 88 | /** 89 | * Possible value: 90 | * # HELP http_request_duration_seconds A histogram of the request duration. 91 | */ 92 | #Help -> { 93 | return new \Butschster\Prometheus\Ast\HelpNode($children); 94 | } 95 | : ( 96 | ::T_WHITESPACE::* 97 | ::T_HASH:: 98 | ::T_WHITESPACE:: 99 | ::T_HELP:: 100 | ::T_WHITESPACE:: 101 | 102 | ::T_WHITESPACE:: 103 | 104 | Eol()? 105 | ) 106 | 107 | /** 108 | * Possible value: 109 | * # TYPE http_request_duration_seconds histogram 110 | */ 111 | #Type -> { 112 | return new \Butschster\Prometheus\Ast\TypeNode($children); 113 | } 114 | : ( 115 | ::T_WHITESPACE::* 116 | ::T_HASH:: 117 | ::T_WHITESPACE:: 118 | ::T_TYPE:: 119 | ::T_WHITESPACE:: 120 | 121 | ::T_WHITESPACE:: 122 | 123 | Eol()? 124 | ) 125 | 126 | /** 127 | * Possible values: 128 | * # Minimalistic line: 129 | * metric_without_timestamp_and_labels 12.47 130 | * http_requests_total{method="post",code="200"} 1027 1395066363000 131 | * http_request_duration_seconds_sum 53423 132 | */ 133 | #Metric -> { 134 | return new \Butschster\Prometheus\Ast\MetricNode($children); 135 | } 136 | : ( 137 | Comment()* 138 | ::T_WHITESPACE::*Labels()?::T_WHITESPACE::MetricValue()(::T_WHITESPACE::MetricTimestamp())?Eol()? 139 | ) 140 | 141 | /** 142 | * Possible values: 143 | * 1027 144 | * 3 145 | * +Inf 146 | * 12.47 147 | * 1.7560473e+07 148 | * 1.7560473e-07 149 | * 1.458255915e9 150 | */ 151 | #MetricValue -> { 152 | return new \Butschster\Prometheus\Ast\MetricValueNode($children); 153 | } 154 | : ( 155 | (||) 156 | ) 157 | 158 | /** 159 | * Possible values: 160 | * 1395066363000 161 | * -3982045 162 | */ 163 | #MetricTimestamp -> { 164 | return new \Butschster\Prometheus\Ast\MetricTimestampNode($children); 165 | } 166 | : ( 167 | 168 | ) 169 | 170 | /** 171 | * Possible values: 172 | * {quantile="0", test="0.25"} 173 | * {problem="division by zero"} 174 | * {path="C:\\DIR\\FILE.TXT",error="Cannot find file:\n\"FILE.TXT\""} 175 | */ 176 | #Labels -> { 177 | return new \Butschster\Prometheus\Ast\LabelsNode($children); 178 | } 179 | : ( 180 | ::T_LBRACE::(Label()(::T_COMMA::::T_WHITESPACE::*)?)*::T_RBRACE:: 181 | ) 182 | 183 | /** 184 | * Possible values: 185 | * quantile="0" 186 | * problem="division by zero" 187 | * path="C:\\DIR\\FILE.TXT" 188 | * error="Cannot find file:\n\"FILE.TXT\"" 189 | */ 190 | #Label -> { 191 | return new \Butschster\Prometheus\Ast\LabelNode($children); 192 | } 193 | : ( 194 | ::T_EQUAL:: 195 | ) 196 | 197 | #Eol: ( 198 | ::T_WHITESPACE::*::T_EOL:: 199 | ) 200 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ./src 6 | 7 | 8 | 9 | 10 | ./tests 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /psalm.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Ast/CommentNode.php: -------------------------------------------------------------------------------- 1 | getName() === 'T_COMMENT') { 16 | $this->comment = \substr(\trim($child->getValue()), 2); 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Ast/HelpNode.php: -------------------------------------------------------------------------------- 1 | getName() === 'T_METRIC_NAME') { 17 | $this->metric = \trim($child->getValue()); 18 | } elseif ($child->getName() === 'T_TEXT') { 19 | $this->description = \trim($child->getValue()); 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Ast/LabelNode.php: -------------------------------------------------------------------------------- 1 | getName() === 'T_METRIC_NAME') { 17 | $this->name = \trim($child->getValue()); 18 | } elseif ($child->getName() === 'T_QUOTED_STRING') { 19 | $this->value = \stripslashes(\substr($child->getValue(), 1, -1)); 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Ast/LabelsNode.php: -------------------------------------------------------------------------------- 1 | labels = $children; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Ast/MetricDataNode.php: -------------------------------------------------------------------------------- 1 | description = $child->description; 23 | $this->name = $child->metric; 24 | } elseif ($child instanceof TypeNode) { 25 | $this->type = $child->type; 26 | } elseif ($child instanceof MetricNode) { 27 | $this->metrics[] = $child; 28 | } 29 | } 30 | } 31 | 32 | public function getIterator(): Traversable 33 | { 34 | return new \IteratorIterator(new \ArrayIterator($this->metrics)); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Ast/MetricNode.php: -------------------------------------------------------------------------------- 1 | labels = $child->labels; 20 | } elseif ($child instanceof MetricValueNode) { 21 | $this->value = $child->value; 22 | } elseif ($child instanceof MetricTimestampNode) { 23 | $this->timestamp = $child->timestamp; 24 | } elseif ($child instanceof CommentNode) { 25 | $this->comment = $child->comment; 26 | } elseif ($child->getName() === 'T_METRIC_NAME') { 27 | $this->name = \trim($child->getValue()); 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Ast/MetricTimestampNode.php: -------------------------------------------------------------------------------- 1 | timestamp = (int) $value->getValue(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Ast/MetricValueNode.php: -------------------------------------------------------------------------------- 1 | value = \ctype_digit($value->getValue()) 16 | ? (int)$value->getValue() 17 | : (float)$value->getValue(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Ast/SchemaNode.php: -------------------------------------------------------------------------------- 1 | */ 15 | private array $metrics; 16 | 17 | /** 18 | * @param MetricDataNode[] $children 19 | */ 20 | public function __construct(array $children) 21 | { 22 | foreach ($children as $child) { 23 | $this->metrics[$child->name] = $child; 24 | } 25 | } 26 | 27 | /** 28 | * @return array 29 | */ 30 | public function getMetrics(): array 31 | { 32 | return $this->metrics; 33 | } 34 | 35 | public function getIterator(): Traversable 36 | { 37 | return new \IteratorIterator(new \ArrayIterator($this->metrics)); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Ast/TypeNode.php: -------------------------------------------------------------------------------- 1 | getName() === 'T_METRIC_NAME') { 17 | $this->metric = \trim($child->getValue()); 18 | } elseif ($child->getName() === 'T_METRIC_TYPE') { 19 | $this->type = \trim($child->getValue()); 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Exceptions/GrammarFileNotFoundException.php: -------------------------------------------------------------------------------- 1 | createLexer($grammar); 23 | $builder = $this->createBuilder($grammar['reducers']); 24 | 25 | $this->parser = $this->createParser($lexer, $grammar, $builder); 26 | } 27 | 28 | /** 29 | * Parse schema 30 | * @throws \Phplrt\Contracts\Exception\RuntimeExceptionInterface 31 | * @psalm-suppress PossiblyUndefinedMethod 32 | */ 33 | public function parse(string $schema, array $options = []): ?SchemaNode 34 | { 35 | return $this->parser->parse($schema, $options)[0] ?? null; 36 | } 37 | 38 | /** 39 | * Create Lexer from compiled data. 40 | */ 41 | private function createLexer(array $data): LexerInterface 42 | { 43 | \assert(\is_array($data['tokens']), 'Invalid tokens'); 44 | \assert(\is_array($data['tokens']['default']), 'Invalid default tokens'); 45 | \assert(\is_array($data['skip']), 'Invalid skip tokens'); 46 | 47 | return new Lexer( 48 | $data['tokens']['default'], 49 | $data['skip'] 50 | ); 51 | } 52 | 53 | /** 54 | * Create AST builder from compiled data. 55 | * @param array $reducers 56 | */ 57 | private function createBuilder(array $reducers): BuilderInterface 58 | { 59 | return new class($reducers) implements BuilderInterface { 60 | public function __construct( 61 | /** @var array*/ 62 | private readonly array $reducers 63 | ) { 64 | } 65 | 66 | public function build(ContextInterface $context, $result) 67 | { 68 | $state = $context->getState(); 69 | 70 | return isset($this->reducers[$state]) 71 | ? $this->reducers[$state]($context, $result) 72 | : $result; 73 | } 74 | }; 75 | } 76 | 77 | /** 78 | * Create Parser from compiled data. 79 | */ 80 | private function createParser(LexerInterface $lexer, array $data, BuilderInterface $builder): ParserInterface 81 | { 82 | \assert(\is_iterable($data['grammar']), 'Grammar is not defined'); 83 | \assert(isset($data['initial']), 'Grammar does not contain initial state.'); 84 | 85 | return new PhplrtParser($lexer, $data['grammar'], [ 86 | // Recognition will start from the specified rule. 87 | PhplrtParser::CONFIG_INITIAL_RULE => $data['initial'], 88 | 89 | // Rules for the abstract syntax tree builder. 90 | // In this case, we use the data found in the compiled grammar. 91 | PhplrtParser::CONFIG_AST_BUILDER => $builder, 92 | ]); 93 | } 94 | 95 | private function ensureGrammarFileExists(string $grammarFilePatch): void 96 | { 97 | if (!file_exists($grammarFilePatch)) { 98 | throw new GrammarFileNotFoundException( 99 | "File {$grammarFilePatch} not found" 100 | ); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/ParserFactory.php: -------------------------------------------------------------------------------- 1 | 'Document', 4 | 'tokens' => [ 5 | 'default' => [ 6 | 'T_WHITESPACE' => '\\s+', 7 | 'T_HELP' => 'HELP', 8 | 'T_TYPE' => 'TYPE', 9 | 'T_COMMENT' => '^\\#\\s[a-zA-Z0-9\\s\\.\\,\\!\\?\\:]+\\:', 10 | 'T_FLOAT' => '[0-9]+\\.[0-9]+(e(\\+|\\-)?[0-9]+)?', 11 | 'T_INF' => '(\\+|\\-)Inf', 12 | 'T_INT' => '(\\-)?[0-9]+', 13 | 'T_METRIC_TYPE' => '(summary|counter|gauge|histogram|untyped)', 14 | 'T_METRIC_NAME' => '[a-z_]+', 15 | 'T_COMMA' => '\\,', 16 | 'T_TEXT' => '[a-zA-Z0-9\\s\\.\\,\\!\\?\\:]+', 17 | 'T_QUOTED_STRING' => '\\"([a-zA-Z0-9\\s\\.\\,\\!\\?\\:\\\\\\"\\+]+)\\"', 18 | 'T_EQUAL' => '\\=', 19 | 'T_HASH' => '\\#', 20 | 'T_LBRACE' => '{', 21 | 'T_RBRACE' => '}', 22 | 'T_DOT' => '\\.', 23 | 'T_EOR' => '\\\\r', 24 | 'T_EOL' => '\\\\n', 25 | ], 26 | ], 27 | 'skip' => [ 28 | 29 | ], 30 | 'transitions' => [ 31 | 32 | ], 33 | 'grammar' => [ 34 | 'Document' => new \Phplrt\Parser\Grammar\Concatenation(['Schema']), 35 | 0 => new \Phplrt\Parser\Grammar\Repetition('Comment', 0, INF), 36 | 'Comment' => new \Phplrt\Parser\Grammar\Concatenation([3, 4, 5]), 37 | 'Help' => new \Phplrt\Parser\Grammar\Concatenation([7, 8, 9, 10, 11, 12, 13, 14, 15]), 38 | 'Label' => new \Phplrt\Parser\Grammar\Concatenation([48, 49, 50]), 39 | 'Labels' => new \Phplrt\Parser\Grammar\Concatenation([45, 46, 47]), 40 | 'Metric' => new \Phplrt\Parser\Grammar\Concatenation([29, 30, 31, 32, 33, 'MetricValue', 34, 35]), 41 | 'MetricData' => new \Phplrt\Parser\Grammar\Concatenation([0, 'Help', 'Type', 1]), 42 | 'MetricTimestamp' => new \Phplrt\Parser\Grammar\Lexeme('T_INT', true), 43 | 'MetricValue' => new \Phplrt\Parser\Grammar\Alternation([36, 37, 38]), 44 | 'Schema' => new \Phplrt\Parser\Grammar\Repetition('MetricData', 0, INF), 45 | 'Type' => new \Phplrt\Parser\Grammar\Concatenation([17, 18, 19, 20, 21, 22, 23, 24, 25]), 46 | 1 => new \Phplrt\Parser\Grammar\Repetition('Metric', 0, INF), 47 | 2 => new \Phplrt\Parser\Grammar\Lexeme('T_WHITESPACE', false), 48 | 3 => new \Phplrt\Parser\Grammar\Repetition(2, 0, INF), 49 | 4 => new \Phplrt\Parser\Grammar\Lexeme('T_COMMENT', true), 50 | 5 => new \Phplrt\Parser\Grammar\Optional('Eol'), 51 | 6 => new \Phplrt\Parser\Grammar\Lexeme('T_WHITESPACE', false), 52 | 7 => new \Phplrt\Parser\Grammar\Repetition(6, 0, INF), 53 | 8 => new \Phplrt\Parser\Grammar\Lexeme('T_HASH', false), 54 | 9 => new \Phplrt\Parser\Grammar\Lexeme('T_WHITESPACE', false), 55 | 10 => new \Phplrt\Parser\Grammar\Lexeme('T_HELP', false), 56 | 11 => new \Phplrt\Parser\Grammar\Lexeme('T_WHITESPACE', false), 57 | 12 => new \Phplrt\Parser\Grammar\Lexeme('T_METRIC_NAME', true), 58 | 13 => new \Phplrt\Parser\Grammar\Lexeme('T_WHITESPACE', false), 59 | 14 => new \Phplrt\Parser\Grammar\Lexeme('T_TEXT', true), 60 | 15 => new \Phplrt\Parser\Grammar\Optional('Eol'), 61 | 16 => new \Phplrt\Parser\Grammar\Lexeme('T_WHITESPACE', false), 62 | 17 => new \Phplrt\Parser\Grammar\Repetition(16, 0, INF), 63 | 18 => new \Phplrt\Parser\Grammar\Lexeme('T_HASH', false), 64 | 19 => new \Phplrt\Parser\Grammar\Lexeme('T_WHITESPACE', false), 65 | 20 => new \Phplrt\Parser\Grammar\Lexeme('T_TYPE', false), 66 | 21 => new \Phplrt\Parser\Grammar\Lexeme('T_WHITESPACE', false), 67 | 22 => new \Phplrt\Parser\Grammar\Lexeme('T_METRIC_NAME', true), 68 | 23 => new \Phplrt\Parser\Grammar\Lexeme('T_WHITESPACE', false), 69 | 24 => new \Phplrt\Parser\Grammar\Lexeme('T_METRIC_TYPE', true), 70 | 25 => new \Phplrt\Parser\Grammar\Optional('Eol'), 71 | 26 => new \Phplrt\Parser\Grammar\Lexeme('T_WHITESPACE', false), 72 | 27 => new \Phplrt\Parser\Grammar\Lexeme('T_WHITESPACE', false), 73 | 28 => new \Phplrt\Parser\Grammar\Concatenation([27, 'MetricTimestamp']), 74 | 29 => new \Phplrt\Parser\Grammar\Repetition('Comment', 0, INF), 75 | 30 => new \Phplrt\Parser\Grammar\Repetition(26, 0, INF), 76 | 31 => new \Phplrt\Parser\Grammar\Lexeme('T_METRIC_NAME', true), 77 | 32 => new \Phplrt\Parser\Grammar\Optional('Labels'), 78 | 33 => new \Phplrt\Parser\Grammar\Lexeme('T_WHITESPACE', false), 79 | 34 => new \Phplrt\Parser\Grammar\Optional(28), 80 | 35 => new \Phplrt\Parser\Grammar\Optional('Eol'), 81 | 36 => new \Phplrt\Parser\Grammar\Lexeme('T_FLOAT', true), 82 | 37 => new \Phplrt\Parser\Grammar\Lexeme('T_INT', true), 83 | 38 => new \Phplrt\Parser\Grammar\Lexeme('T_INF', true), 84 | 39 => new \Phplrt\Parser\Grammar\Lexeme('T_WHITESPACE', false), 85 | 40 => new \Phplrt\Parser\Grammar\Lexeme('T_COMMA', false), 86 | 41 => new \Phplrt\Parser\Grammar\Repetition(39, 0, INF), 87 | 42 => new \Phplrt\Parser\Grammar\Concatenation([40, 41]), 88 | 43 => new \Phplrt\Parser\Grammar\Optional(42), 89 | 44 => new \Phplrt\Parser\Grammar\Concatenation(['Label', 43]), 90 | 45 => new \Phplrt\Parser\Grammar\Lexeme('T_LBRACE', false), 91 | 46 => new \Phplrt\Parser\Grammar\Repetition(44, 0, INF), 92 | 47 => new \Phplrt\Parser\Grammar\Lexeme('T_RBRACE', false), 93 | 48 => new \Phplrt\Parser\Grammar\Lexeme('T_METRIC_NAME', true), 94 | 49 => new \Phplrt\Parser\Grammar\Lexeme('T_EQUAL', false), 95 | 50 => new \Phplrt\Parser\Grammar\Lexeme('T_QUOTED_STRING', true), 96 | 51 => new \Phplrt\Parser\Grammar\Lexeme('T_WHITESPACE', false), 97 | 52 => new \Phplrt\Parser\Grammar\Repetition(51, 0, INF), 98 | 53 => new \Phplrt\Parser\Grammar\Lexeme('T_EOL', false), 99 | 'Eol' => new \Phplrt\Parser\Grammar\Concatenation([52, 53]) 100 | ], 101 | 'reducers' => [ 102 | 'Schema' => function (\Phplrt\Parser\Context $ctx, $children) { 103 | return new \Butschster\Prometheus\Ast\SchemaNode($children); 104 | }, 105 | 'MetricData' => function (\Phplrt\Parser\Context $ctx, $children) { 106 | return new \Butschster\Prometheus\Ast\MetricDataNode($children); 107 | }, 108 | 'Comment' => function (\Phplrt\Parser\Context $ctx, $children) { 109 | return new \Butschster\Prometheus\Ast\CommentNode($children); 110 | }, 111 | 'Help' => function (\Phplrt\Parser\Context $ctx, $children) { 112 | return new \Butschster\Prometheus\Ast\HelpNode($children); 113 | }, 114 | 'Type' => function (\Phplrt\Parser\Context $ctx, $children) { 115 | return new \Butschster\Prometheus\Ast\TypeNode($children); 116 | }, 117 | 'Metric' => function (\Phplrt\Parser\Context $ctx, $children) { 118 | return new \Butschster\Prometheus\Ast\MetricNode($children); 119 | }, 120 | 'MetricValue' => function (\Phplrt\Parser\Context $ctx, $children) { 121 | return new \Butschster\Prometheus\Ast\MetricValueNode($children); 122 | }, 123 | 'MetricTimestamp' => function (\Phplrt\Parser\Context $ctx, $children) { 124 | return new \Butschster\Prometheus\Ast\MetricTimestampNode($children); 125 | }, 126 | 'Labels' => function (\Phplrt\Parser\Context $ctx, $children) { 127 | return new \Butschster\Prometheus\Ast\LabelsNode($children); 128 | }, 129 | 'Label' => function (\Phplrt\Parser\Context $ctx, $children) { 130 | return new \Butschster\Prometheus\Ast\LabelNode($children); 131 | } 132 | ] 133 | ]; -------------------------------------------------------------------------------- /tests/Ast/SchemaNodeTest.php: -------------------------------------------------------------------------------- 1 | node = $this->parser->parse(<<assertSame( 33 | 'A summary of the pause duration of garbage collection cycles.', 34 | $this->node->getMetrics()['go_gc_duration_seconds']->description 35 | ); 36 | } 37 | 38 | function testSchemaType(): void 39 | { 40 | $this->assertSame( 41 | 'summary', 42 | $this->node->getMetrics()['go_gc_duration_seconds']->type 43 | ); 44 | } 45 | 46 | function testSchemaValue(): void 47 | { 48 | $this->assertSame( 49 | 4.716E-5, 50 | $this->node->getMetrics()['go_gc_duration_seconds']->metrics[1]->value 51 | ); 52 | $this->assertSame( 53 | 'go_gc_duration_seconds', 54 | $this->node->getMetrics()['go_gc_duration_seconds']->metrics[1]->name 55 | ); 56 | 57 | $this->assertSame( 58 | 0.000298737, 59 | $this->node->getMetrics()['go_gc_duration_seconds']->metrics[3]->value 60 | ); 61 | 62 | $this->assertSame( 63 | 'go_gc_duration_seconds_sum', 64 | $this->node->getMetrics()['go_gc_duration_seconds']->metrics[3]->name 65 | ); 66 | 67 | $this->assertSame( 68 | 3, 69 | $this->node->getMetrics()['go_gc_duration_seconds']->metrics[4]->value 70 | ); 71 | 72 | $this->assertNull( 73 | $this->node->getMetrics()['go_gc_duration_seconds']->metrics[3]->timestamp 74 | ); 75 | 76 | $this->assertNull( 77 | $this->node->getMetrics()['go_gc_duration_seconds']->metrics[3]->comment 78 | ); 79 | 80 | $this->assertSame( 81 | 'go_gc_duration_seconds_count', 82 | $this->node->getMetrics()['go_gc_duration_seconds']->metrics[4]->name 83 | ); 84 | 85 | $this->assertSame( 86 | 'A weird metric from before the epoch:', 87 | $this->node->getMetrics()['go_gc_duration_seconds']->metrics[4]->comment 88 | ); 89 | 90 | $this->assertSame( 91 | 218257, 92 | $this->node->getMetrics()['go_gc_duration_seconds']->metrics[4]->timestamp 93 | ); 94 | } 95 | 96 | function testSchemaLabels(): void 97 | { 98 | $this->assertSame( 99 | 'quantile', 100 | $this->node->getMetrics()['go_gc_duration_seconds']->metrics[0]->labels[0]->name 101 | ); 102 | 103 | $this->assertSame( 104 | '0', 105 | $this->node->getMetrics()['go_gc_duration_seconds']->metrics[0]->labels[0]->value 106 | ); 107 | 108 | $this->assertSame( 109 | 'test', 110 | $this->node->getMetrics()['go_gc_duration_seconds']->metrics[0]->labels[1]->name 111 | ); 112 | 113 | $this->assertSame( 114 | '0.25', 115 | $this->node->getMetrics()['go_gc_duration_seconds']->metrics[0]->labels[1]->value 116 | ); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /tests/Ast/TestCase.php: -------------------------------------------------------------------------------- 1 | parser = ParserFactory::create(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/Parsers/HelpTest.php: -------------------------------------------------------------------------------- 1 | assertAst( 12 | << 53 | 54 | 55 | 56 | http_requests_total 57 | The total number of HTTP requests. 58 | 59 | 60 | 61 | http_requests_total 62 | counter 63 | 64 | 65 | http_requests_total 66 | 67 | 71 | 75 | 76 | 77 | 1027 78 | 79 | 80 | 1395066363000 81 | 82 | 83 | 84 | http_requests_total 85 | 86 | 90 | 94 | 95 | 96 | 3 97 | 98 | 99 | 1395066363000 100 | 101 | 102 | 103 | 104 | # Escaping in label values: 105 | 106 | msdos_file_access_time_seconds 107 | 108 | 112 | 117 | 118 | 119 | 1.458255915e9 120 | 121 | 122 | 123 | 124 | # Minimalistic line: 125 | 126 | metric_without_timestamp_and_labels 127 | 128 | 12.47 129 | 130 | 131 | 132 | 133 | # A weird metric from before the epoch: 134 | 135 | something_weird 136 | 137 | 141 | 142 | 143 | +Inf 144 | 145 | 146 | -3982045 147 | 148 | 149 | 150 | 151 | 152 | # A histogram, which has a pretty complex representation in the text format: 153 | 154 | 155 | http_request_duration_seconds 156 | A histogram of the request duration. 157 | 158 | 159 | 160 | http_request_duration_seconds 161 | histogram 162 | 163 | 164 | http_request_duration_seconds_bucket 165 | 166 | 170 | 171 | 172 | 24054 173 | 174 | 175 | 176 | http_request_duration_seconds_bucket 177 | 178 | 182 | 183 | 184 | 33444 185 | 186 | 187 | 188 | http_request_duration_seconds_bucket 189 | 190 | 194 | 195 | 196 | 100392 197 | 198 | 199 | 200 | http_request_duration_seconds_bucket 201 | 202 | 206 | 207 | 208 | 129389 209 | 210 | 211 | 212 | http_request_duration_seconds_bucket 213 | 214 | 218 | 219 | 220 | 133988 221 | 222 | 223 | 224 | http_request_duration_seconds_bucket 225 | 226 | 230 | 231 | 232 | 144320 233 | 234 | 235 | 236 | http_request_duration_seconds_sum 237 | 238 | 53423 239 | 240 | 241 | 242 | http_request_duration_seconds_count 243 | 244 | 144320 245 | 246 | 247 | 248 | 249 | 250 | # Finally a summary, which has a complex representation, too: 251 | 252 | 253 | rpc_duration_seconds 254 | A summary of the RPC duration in seconds. 255 | 256 | 257 | 258 | rpc_duration_seconds 259 | summary 260 | 261 | 262 | rpc_duration_seconds 263 | 264 | 268 | 269 | 270 | 3102 271 | 272 | 273 | 274 | rpc_duration_seconds 275 | 276 | 280 | 281 | 282 | 3272 283 | 284 | 285 | 286 | rpc_duration_seconds 287 | 288 | 292 | 293 | 294 | 4773 295 | 296 | 297 | 298 | rpc_duration_seconds 299 | 300 | 304 | 305 | 306 | 9001 307 | 308 | 309 | 310 | rpc_duration_seconds 311 | 312 | 316 | 317 | 318 | 76656 319 | 320 | 321 | 322 | rpc_duration_seconds_sum 323 | 324 | 1.7560473e+07 325 | 326 | 327 | 328 | rpc_duration_seconds_count 329 | 330 | 2693 331 | 332 | 333 | 334 | 335 | 336 | AST 337 | ); 338 | } 339 | } 340 | -------------------------------------------------------------------------------- /tests/Parsers/TestCase.php: -------------------------------------------------------------------------------- 1 | compiler = new Compiler(); 19 | $this->compiler->load(File::fromPathname(__DIR__ . static::EBNF_FILE_PATH)); 20 | 21 | \file_put_contents( 22 | __DIR__ . static::GRAMMAR_FILE_PATH, 23 | (string)$this->compiler->build() 24 | ); 25 | } 26 | 27 | public function assertAst(string $schema, string $ast) 28 | { 29 | $ast = \array_map(function (string $line) { 30 | if (empty($line)) { 31 | return $line; 32 | } 33 | return $line; 34 | }, \explode("\n", $ast)); 35 | 36 | $ast = \implode("\n", $ast); 37 | 38 | $this->assertEquals($ast, 39 | (string )$this->compiler->parse($schema) 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 |