Completed
Push — master ( 1dc5de...8eb9c7 )
by Fabrice
01:44
created

FlowMap::getNodeStat()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
/*
4
 * This file is part of NodalFlow.
5
 *     (c) Fabrice de Stefanis / https://github.com/fab2s/NodalFlow
6
 * This source file is licensed under the MIT license which you will
7
 * find in the LICENSE file or at https://opensource.org/licenses/MIT
8
 */
9
10
namespace fab2s\NodalFlow\Flows;
11
12
use fab2s\NodalFlow\NodalFlowException;
13
use fab2s\NodalFlow\Nodes\AggregateNodeInterface;
14
use fab2s\NodalFlow\Nodes\BranchNodeInterface;
15
use fab2s\NodalFlow\Nodes\NodeInterface;
16
17
/**
18
 * class FlowMap
19
 * This implementation aims at being fast and therefore does a lot
20
 * by reference
21
 */
22
class FlowMap implements FlowMapInterface
23
{
24
    /**
25
     * Flow map
26
     *
27
     * @var array
28
     */
29
    protected $nodeMap = [];
30
31
    /**
32
     * @var array
33
     */
34
    protected $reverseMap = [];
35
36
    /**
37
     * The default Node Map values
38
     *
39
     * @var array
40
     */
41
    protected $nodeMapDefault = [
42
        'class'           => null,
43
        'flowId'          => null,
44
        'hash'            => null,
45
        'index'           => null,
46
        'isATraversable'  => null,
47
        'isAReturningVal' => null,
48
        'isAFlow'         => null,
49
        'num_exec'        => 0,
50
        'num_iterate'     => 0,
51
        'num_break'       => 0,
52
        'num_continue'    => 0,
53
    ];
54
55
    /**
56
     * The default Node stats values
57
     *
58
     * @var array
59
     */
60
    protected $incrementStatsDefault = [
61
        'num_exec'     => 0,
62
        'num_iterate'  => 0,
63
        'num_break'    => 0,
64
        'num_continue' => 0,
65
    ];
66
67
    /**
68
     * The Flow stats default values
69
     *
70
     * @var array
71
     */
72
    protected $flowStatsDefault = [
73
        'class'    => null,
74
        'id'       => null,
75
        'start'    => null,
76
        'end'      => null,
77
        'elapsed'  => null,
78
        'duration' => null,
79
        'mib'      => null,
80
    ];
81
82
    /**
83
     * @var array
84
     */
85
    protected $defaultFlowStats;
86
87
    /**
88
     * @var array
89
     */
90
    protected $flowStats;
91
92
    /**
93
     * @var bool
94
     */
95
    protected $resetOnRestart = false;
96
97
    /**
98
     * @var FlowInterface
99
     */
100
    protected $flow;
101
102
    /**
103
     * Instantiate a Flow Status
104
     *
105
     * @param FlowInterface $flow
106
     * @param array         $flowIncrements
107
     */
108
    public function __construct(FlowInterface $flow, array $flowIncrements = [])
109
    {
110
        $this->flow             = $flow;
111
        $this->defaultFlowStats = array_replace($this->flowStatsDefault, $this->incrementStatsDefault, [
112
            'class' => get_class($this->flow),
113
            'id'    => $this->flow->getId(),
114
        ]);
115
        $this->flowStats = $this->defaultFlowStats;
116
117
        $this->setFlowIncrement($flowIncrements);
118
    }
119
120
    /**
121
     * @param NodeInterface $node
122
     * @param int           $index
123
     *
124
     * @throws NodalFlowException
125
     */
126
    public function register(NodeInterface $node, $index)
127
    {
128
        $this->enforceUniqueness($node);
129
        $nodeHash                 = $node->getNodeHash();
130
        $this->nodeMap[$nodeHash] = array_replace($this->nodeMapDefault, [
131
            'class'           => get_class($node),
132
            'flowId'          => $node->getCarrier()->getId(),
133
            'hash'            => $nodeHash,
134
            'index'           => $index,
135
            'isATraversable'  => $node->isTraversable(),
136
            'isAReturningVal' => $node->isReturningVal(),
137
            'isAFlow'         => $node->isFlow(),
138
        ]);
139
140
        $this->setNodeIncrement($node);
141
142
        if (isset($this->reverseMap[$index])) {
143
            // replacing a note, maintain nodeMap accordingly
144
            unset($this->nodeMap[$this->reverseMap[$index]]);
145
        }
146
147
        $this->reverseMap[$index] = $nodeHash;
148
    }
149
150
    /**
151
     * Triggered right before the flow starts
152
     *
153
     * @return $this
154
     */
155
    public function flowStart()
156
    {
157
        $this->flowStats['start'] = microtime(true);
158
159
        return $this;
160
    }
161
162
    /**
163
     * Triggered right after the flow stops
164
     *
165
     * @return $this
166
     */
167
    public function flowEnd()
168
    {
169
        $this->flowStats['end']     = microtime(true);
170
        $this->flowStats['mib']     = memory_get_peak_usage(true) / 1048576;
171
        $this->flowStats['elapsed'] = $this->flowStats['end'] - $this->flowStats['start'];
172
173
        $this->flowStats = array_replace($this->flowStats, $this->duration($this->flowStats['elapsed']));
174
175
        return $this;
176
    }
177
178
    /**
179
     * Let's be fast at incrementing while we are at it
180
     *
181
     * @param NodeInterface $node
182
     *
183
     * @return array
184
     */
185
    public function &getNodeStat(NodeInterface $node)
186
    {
187
        return $this->nodeMap[$node->getNodeHash()];
188
    }
189
190
    /**
191
     * Get/Generate Node Map
192
     *
193
     * @throws NodalFlowException
194
     *
195
     * @return array
196
     */
197
    public function getNodeMap()
198
    {
199
        foreach ($this->flow->getNodes() as $node) {
200
            if ($node instanceof BranchNodeInterface) {
201
                $this->nodeMap[$node->getNodeHash()]['nodes'] = $node->getPayload()->getNodeMap();
202
                continue;
203
            }
204
205
            if ($node instanceof AggregateNodeInterface) {
206
                $flowId = $node->getCarrier()->getId();
207
                foreach ($node->getNodeCollection() as $aggregatedNode) {
208
                    $this->nodeMap[$node->getNodeHash()]['nodes'][$aggregatedNode->getNodeHash()] = array_replace($this->nodeMapDefault, [
209
                        'class'  => get_class($aggregatedNode),
210
                        'flowId' => $flowId,
211
                        'hash'   => $aggregatedNode->getNodeHash(),
212
                    ]);
213
                }
214
                continue;
215
            }
216
        }
217
218
        return $this->nodeMap;
219
    }
220
221
    /**
222
     * Get the latest Node stats
223
     *
224
     * @return array
225
     */
226
    public function getStats()
227
    {
228
        $result = array_intersect_key($this->flowStats, $this->defaultFlowStats);
229
        foreach ($this->flow->getNodes() as $node) {
230
            if ($node instanceof BranchNodeInterface) {
231
                $result['branches'][$node->getPayload()->getId()] = $node->getPayload()->getStats();
232
            }
233
        }
234
235
        return $result;
236
    }
237
238
    /**
239
     * @param string $nodeHash
240
     * @param string $key
241
     *
242
     * @return $this
243
     */
244
    public function incrementNode($nodeHash, $key)
245
    {
246
        ++$this->nodeMap[$nodeHash][$key];
247
248
        if (isset($this->flowStats[$key . '_total'])) {
249
            ++$this->flowStats[$key . '_total'];
250
        }
251
252
        return $this;
253
    }
254
255
    /**
256
     * @param string $key
257
     *
258
     * @return $this
259
     */
260
    public function incrementFlow($key)
261
    {
262
        ++$this->flowStats[$key];
263
264
        return $this;
265
    }
266
267
    /**
268
     * Resets Nodes stats, can be used prior to Flow's re-exec
269
     *
270
     * @return $this
271
     */
272
    public function resetNodeStats()
273
    {
274
        foreach ($this->nodeMap as &$nodeStat) {
275
            foreach ($this->incrementStatsDefault as $key => $value) {
276
                if (isset($nodeStat[$key])) {
277
                    $nodeStat[$key] = $value;
278
                }
279
            }
280
        }
281
282
        return $this;
283
    }
284
285
    /**
286
     * Computes a human readable duration string from floating seconds
287
     *
288
     * @param float $seconds
289
     *
290
     * @return array<string,integer|string>
291
     */
292
    public function duration($seconds)
293
    {
294
        $result = [
295
            'hour'     => (int) floor($seconds / 3600),
296
            'min'      => (int) floor(($seconds / 60) % 60),
297
            'sec'      => $seconds % 60,
298
            'ms'       => (int) round(\fmod($seconds, 1) * 1000),
299
        ];
300
301
        $duration = '';
302
        foreach ($result as $unit => $value) {
303
            if (!empty($value) || $unit === 'ms') {
304
                $duration .= $value . "$unit ";
305
            }
306
        }
307
308
        $result['duration'] = trim($duration);
309
310
        return $result;
311
    }
312
313
    /**
314
     * Set additional increment keys, use :
315
     *      'keyName' => int
316
     * to add keyName as increment, starting at int
317
     * or :
318
     *      'keyName' => 'existingIncrement'
319
     * to assign keyName as a reference to existingIncrement
320
     *
321
     * @param array $flowIncrements
322
     *
323
     * @throws NodalFlowException
324
     *
325
     * @return $this
326
     */
327
    protected function setFlowIncrement(array $flowIncrements)
328
    {
329
        foreach ($flowIncrements as $incrementKey => $target) {
330
            if (is_string($target)) {
331
                if (!isset($this->flowStats[$target])) {
332
                    throw new NodalFlowException('Cannot set reference on unset target');
333
                }
334
335
                $this->flowStats[$incrementKey] = &$this->flowStats[$target];
336
                continue;
337
            }
338
339
            $this->defaultFlowStats[$incrementKey] = $target;
340
            $this->flowStats[$incrementKey]        = $target;
341
        }
342
343
        return $this;
344
    }
345
346
    /**
347
     * @param NodeInterface $node
348
     *
349
     * @throws NodalFlowException
350
     *
351
     * @return $this
352
     */
353
    protected function setNodeIncrement(NodeInterface $node)
354
    {
355
        $nodeHash = $node->getNodeHash();
356
        foreach ($node->getNodeIncrements() as $incrementKey => $target) {
357
            if (is_string($target)) {
358
                if (!isset($this->incrementStatsDefault[$target])) {
359
                    throw new NodalFlowException('Tried to set an increment alias to an un-registered increment', 1, null, [
360
                        'aliasKey'  => $incrementKey,
361
                        'targetKey' => $target,
362
                    ]);
363
                }
364
365
                $this->nodeMap[$nodeHash][$incrementKey] = &$this->nodeMap[$nodeHash][$target];
366
                continue;
367
            }
368
369
            $this->incrementStatsDefault[$incrementKey] = $target;
370
            $this->nodeMap[$nodeHash][$incrementKey]    = $target;
371
        }
372
373
        return $this;
374
    }
375
376
    /**
377
     * @param NodeInterface $node
378
     *
379
     * @throws NodalFlowException
380
     *
381
     * @return $this
382
     */
383
    protected function enforceUniqueness(NodeInterface $node)
384
    {
385
        if (isset($this->nodeMap[$node->getNodeHash()])) {
386
            throw new NodalFlowException('Cannot reuse Node instances within a Flow', 1, null, [
387
                'duplicate_node' => get_class($node),
388
                'hash'           => $node->getNodeHash(),
389
            ]);
390
        }
391
392
        return $this;
393
    }
394
}
395