Summary::ranked()   A
last analyzed

Complexity

Conditions 5
Paths 5

Size

Total Lines 21
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 13
nc 5
nop 1
dl 0
loc 21
rs 9.5222
c 0
b 0
f 0
1
<?php
2
/**
3
 * Metrics type "summary"
4
 * User: moyo
5
 * Date: 2018/5/17
6
 * Time: 11:06 PM
7
 * @see http://infolab.stanford.edu/~datar/courses/cs361a/papers/quantiles.pdf
8
 */
9
10
namespace Carno\Monitor\Metrics;
11
12
use Carno\Monitor\Chips\Metrical\Labeled;
13
use Carno\Monitor\Chips\Metrical\Named;
14
use Carno\Monitor\Chips\Metrical\Reporting;
15
use Carno\Monitor\Chips\Metrical\SExporter;
16
use Carno\Monitor\Chips\Metrical\SQuantiles;
17
use Carno\Monitor\Contracts\HMRegistry;
18
use Carno\Monitor\Contracts\RAWMetrics;
19
use Carno\Monitor\Contracts\Telemetry;
20
21
class Summary implements RAWMetrics, HMRegistry, Telemetry
22
{
23
    use Named, Labeled, SQuantiles, SExporter, Reporting;
24
25
    /**
26
     * @var float
27
     */
28
    private $epsilon = 0;
29
30
    /**
31
     * @var int
32
     */
33
    private $threshold = 0;
34
35
    /**
36
     * @var int
37
     */
38
    private $count = 0;
39
40
    /**
41
     * @var float
42
     */
43
    private $sum = 0;
44
45
    /**
46
     * @var SItem[]
47
     */
48
    private $samples = [];
49
50
    /**
51
     * Summary constructor.
52
     * @param float $epsilon
53
     * @param int $threshold
54
     */
55
    public function __construct(float $epsilon = 0.001, int $threshold = 2000)
56
    {
57
        $this->epsilon = $epsilon;
58
        $this->threshold = $threshold;
59
    }
60
61
    /**
62
     * @param float $v
63
     */
64
    public function observe(float $v) : void
65
    {
66
        $rank = $this->ranked($v);
67
68
        $delta = $rank === 0 || $rank === count($this->samples) ? 0 : floor(2 * $this->epsilon * $this->count);
69
70
        array_splice($this->samples, $rank, 0, [new SItem($v, 1, $delta)]);
0 ignored issues
show
Bug introduced by
It seems like $delta can also be of type double; however, parameter $d of Carno\Monitor\Metrics\SItem::__construct() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

70
        array_splice($this->samples, $rank, 0, [new SItem($v, 1, /** @scrutinizer ignore-type */ $delta)]);
Loading history...
71
72
        count($this->samples) > $this->threshold && $this->compress();
73
74
        $this->count ++;
75
        $this->sum += $v;
76
    }
77
78
    /**
79
     * @param float $quantile
80
     * @return float
81
     */
82
    private function query(float $quantile) : float
0 ignored issues
show
Unused Code introduced by
The method query() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
83
    {
84
        $rank = 0;
85
86
        $desired = intval($quantile * $this->count);
87
88
        for ($i = 1; $i < count($this->samples); $i ++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
89
            $sp = $this->samples[$i - 1];
90
            $sc = $this->samples[$i];
91
92
            if (($rank += $sp->g) + $sc->g + $sc->d > $desired + (2 * $this->epsilon * $this->count)) {
93
                return $sp->v;
94
            }
95
        }
96
97
        return end($this->samples)->v;
98
    }
99
100
    /**
101
     */
102
    private function compress() : void
103
    {
104
        for ($i = 0; $i < count($this->samples) - 1; $i ++) {
105
            $sa = $this->samples[$i];
106
            $sb = $this->samples[$i + 1];
107
108
            if ($sa->g + $sb->g + $sb->d <= floor(2 * $this->epsilon * $this->count)) {
109
                $sb->g += $sa->g;
110
                array_splice($this->samples, $i, 1);
111
            }
112
        }
113
    }
114
115
    /**
116
     * @param float $v
117
     * @return int
118
     */
119
    private function ranked(float $v) : int
120
    {
121
        if (empty($this->samples)) {
122
            return 0;
123
        }
124
125
        $start = $rank = 0;
126
        $end = count($this->samples) - 1;
127
128
        while ($start <= $end) {
129
            $rank = intval(($start + $end) / 2);
130
            if (($curr = $this->samples[$rank]->v) < $v) {
131
                $start = $rank + 1;
132
            } elseif ($curr > $v) {
133
                $end = $rank - 1;
134
            } else {
135
                return $rank;
136
            }
137
        }
138
139
        return $rank;
140
    }
141
}
142