Skip to content

Commit 52e4882

Browse files
committed
fix(hook-frame): Rework framing hook to synchronize frame boundaries
Signed-off-by: Steffen Vogel <[email protected]>
1 parent d9f4aa2 commit 52e4882

File tree

4 files changed

+62
-189
lines changed

4 files changed

+62
-189
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# yaml-language-server: $schema=http://json-schema.org/draft-07/schema
2+
# SPDX-FileCopyrightText: 2014-2023 Institute for Automation of Complex Power Systems, RWTH Aachen University
3+
# SPDX-License-Identifier: Apache-2.0
4+
---
5+
oneOf:
6+
- type: string
7+
description: |
8+
Duration as a string, e.g., "1h30m", "45s", "200ms".
9+
pattern: (\d+d)?(\d+h)?(\d+m)?(\d+s)?(\d+ms)?(\d+us)?(\d+ns)?
10+
examples:
11+
- "6d23h30m50s40ms"
12+
- "45s"
13+
- "2d200ms"
14+
- type: integer
15+
description: |
16+
Duration as integer.
17+
minimum: 0
18+
examples:
19+
- 5400000
20+
- 45000
21+
- 200

doc/openapi/components/schemas/config/hooks/frame.yaml

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,14 @@ allOf:
88
trigger:
99
description: The trigger for new frames.
1010
type: string
11-
default: sequence
11+
default: timestamp
1212
enum:
1313
- sequence
1414
- timestamp
1515

16-
unit:
17-
description: The type of a timestamp trigger.
18-
type: string
19-
enum:
20-
- milliseconds
21-
- seconds
22-
- minutes
23-
- hours
24-
2516
interval:
2617
description: The interval in which frames are annotated.
27-
type: number
28-
default: 1
29-
30-
offset:
31-
description: An offset in the interval for the annotation of new frames.
32-
type: number
33-
default: 0
18+
default: "1s"
19+
$ref: ../../duration.yaml
3420

3521
- $ref: ../hook.yaml

etc/examples/hooks/frame.conf

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ paths = (
1313
type = "frame"
1414

1515
trigger = "timestamp"
16-
unit = "seconds"
17-
interval = 10
16+
interval = "3s"
1817
}
1918
)
2019
}

lib/hooks/frame.cpp

Lines changed: 37 additions & 170 deletions
Original file line numberDiff line numberDiff line change
@@ -5,225 +5,92 @@
55
* SPDX-License-Identifier: Apache-2.0
66
*/
77

8+
#include <chrono>
89
#include <cstdint>
910
#include <cstring>
10-
#include <limits>
11-
#include <memory>
12-
#include <optional>
11+
#include <variant>
1312

1413
#include <jansson.h>
1514

15+
#include <villas/config_helper.hpp>
1616
#include <villas/exceptions.hpp>
1717
#include <villas/hook.hpp>
1818
#include <villas/sample.hpp>
1919

20+
#include "villas/timing.hpp"
21+
2022
namespace villas {
2123
namespace node {
2224

23-
enum class Trigger {
24-
TIMESTAMP,
25-
SEQUENCE,
26-
};
27-
28-
enum class Unit {
29-
MILLISECONDS,
30-
SECONDS,
31-
MINUTES,
32-
HOURS,
33-
};
34-
35-
uint64_t unit_wrap(std::optional<Unit> unit) {
36-
if (unit) {
37-
switch (*unit) {
38-
case Unit::HOURS:
39-
return 24;
40-
case Unit::MINUTES:
41-
return 60;
42-
case Unit::SECONDS:
43-
return 60;
44-
case Unit::MILLISECONDS:
45-
return 1000;
46-
}
47-
}
48-
49-
return std::numeric_limits<uint64_t>::max();
50-
}
51-
5225
class FrameHook : public Hook {
53-
using sample_ptr = std::unique_ptr<Sample, decltype(&sample_decref)>;
26+
using TimeInterval = std::chrono::milliseconds;
27+
using SequenceInterval = uint64_t;
5428

5529
private:
56-
Trigger trigger;
57-
uint64_t interval;
58-
uint64_t offset;
59-
std::optional<Unit> unit;
60-
sample_ptr last_smp;
30+
std::variant<TimeInterval, SequenceInterval> interval;
31+
Sample::PtrUnique last_smp;
6132

6233
bool updateInterval(Sample const *next_smp) {
6334
bool changed = false;
6435

6536
if (!last_smp.get() ||
6637
(next_smp->flags & (int)SampleFlags::NEW_SIMULATION)) {
6738
changed = true;
68-
} else if (trigger == Trigger::SEQUENCE) {
39+
} else if (auto *i = std::get_if<SequenceInterval>(&interval)) {
6940
if (!(next_smp->flags & (int)SampleFlags::HAS_SEQUENCE))
70-
throw RuntimeError{"Missing sequence number."};
41+
throw RuntimeError("Missing sequence number");
42+
43+
auto last_interval = (last_smp->sequence + *i) / *i;
44+
auto next_interval = (next_smp->sequence + *i) / *i;
7145

72-
auto last_interval = (last_smp->sequence + interval - offset) / interval;
73-
auto next_interval = (next_smp->sequence + interval - offset) / interval;
7446
changed = last_interval != next_interval;
75-
} else {
47+
} else if (auto *i = std::get_if<TimeInterval>(&interval)) {
7648
if (!(next_smp->flags & (int)SampleFlags::HAS_TS_ORIGIN))
77-
throw RuntimeError{"Missing origin timestamp."};
78-
79-
switch (unit.value()) {
80-
case Unit::HOURS: {
81-
auto last_hour = last_smp->ts.origin.tv_sec / 3'600;
82-
auto next_hour = next_smp->ts.origin.tv_sec / 3'600;
83-
84-
auto last_day = last_hour / 24;
85-
auto next_day = next_hour / 24;
86-
if (last_day != next_day) {
87-
changed = true;
88-
break;
89-
}
90-
91-
auto last_hour_of_day = last_hour - 24 * last_day;
92-
auto next_hour_of_day = next_hour - 24 * next_day;
93-
auto last_interval_of_day =
94-
(last_hour_of_day + interval - offset) / interval;
95-
auto next_interval_of_day =
96-
(next_hour_of_day + interval - offset) / interval;
97-
changed = last_interval_of_day != next_interval_of_day;
98-
break;
99-
}
49+
throw RuntimeError("Missing origin timestamp");
10050

101-
case Unit::MINUTES: {
102-
auto last_minute = last_smp->ts.origin.tv_sec / 60;
103-
auto next_minute = next_smp->ts.origin.tv_sec / 60;
51+
auto last_ts = time_to_timepoint<TimeInterval>(&last_smp->ts.origin);
52+
auto next_ts = time_to_timepoint<TimeInterval>(&next_smp->ts.origin);
10453

105-
auto last_hour = last_minute / 60;
106-
auto next_hour = next_minute / 60;
107-
if (last_hour != next_hour) {
108-
changed = true;
109-
break;
110-
}
54+
auto last_interval = (last_ts.time_since_epoch() + *i) / *i;
55+
auto next_interval = (next_ts.time_since_epoch() + *i) / *i;
11156

112-
auto last_minute_of_hour = last_minute - 60 * last_hour;
113-
auto next_minute_of_hour = next_minute - 60 * next_hour;
114-
auto last_interval_of_hour =
115-
(last_minute_of_hour + interval - offset) / interval;
116-
auto next_interval_of_hour =
117-
(next_minute_of_hour + interval - offset) / interval;
118-
changed = last_interval_of_hour != next_interval_of_hour;
119-
break;
120-
}
121-
122-
case Unit::SECONDS: {
123-
auto last_second = last_smp->ts.origin.tv_sec;
124-
auto next_second = next_smp->ts.origin.tv_sec;
125-
126-
auto last_minute = last_second / 60;
127-
auto next_minute = next_second / 60;
128-
if (last_minute != next_minute) {
129-
changed = true;
130-
break;
131-
}
132-
133-
auto last_second_of_minute = last_second - 60 * last_minute;
134-
auto next_second_of_minute = next_second - 60 * next_minute;
135-
auto last_interval_of_minute =
136-
(last_second_of_minute + interval - offset) / interval;
137-
auto next_interval_of_minute =
138-
(next_second_of_minute + interval - offset) / interval;
139-
changed = last_interval_of_minute != next_interval_of_minute;
140-
break;
141-
}
142-
143-
case Unit::MILLISECONDS: {
144-
auto last_second = last_smp->ts.origin.tv_sec;
145-
auto next_second = next_smp->ts.origin.tv_sec;
146-
if (last_second != next_second) {
147-
changed = true;
148-
break;
149-
}
150-
151-
auto last_millisecond_of_second =
152-
last_smp->ts.origin.tv_nsec / 1'000'000;
153-
auto next_millisecond_of_second =
154-
next_smp->ts.origin.tv_nsec / 1'000'000;
155-
auto last_interval_of_second =
156-
(last_millisecond_of_second + interval - offset) / interval;
157-
auto next_interval_of_second =
158-
(next_millisecond_of_second + interval - offset) / interval;
159-
changed = last_interval_of_second != next_interval_of_second;
160-
break;
161-
}
162-
}
57+
changed = last_interval != next_interval;
16358
}
16459

165-
if (changed)
166-
logger->debug("new frame");
167-
16860
return changed;
16961
}
17062

17163
public:
17264
FrameHook(Path *p, Node *n, int fl, int prio, bool en = true)
173-
: Hook(p, n, fl, prio, en), trigger(Trigger::SEQUENCE), interval(1),
174-
offset(0), unit{std::nullopt}, last_smp{nullptr, &sample_decref} {}
65+
: Hook(p, n, fl, prio, en), interval(TimeInterval(0)),
66+
last_smp{nullptr, &sample_decref} {}
17567

17668
virtual ~FrameHook() { (void)last_smp.release(); }
17769

17870
void parse(json_t *json) override {
17971
Hook::parse(json);
18072

18173
char *trigger_str = nullptr;
182-
char *unit_str = nullptr;
183-
int interval_int = -1;
184-
int offset_int = -1;
74+
json_t *json_interval = nullptr;
18575

18676
json_error_t err;
187-
auto ret = json_unpack_ex(json, &err, 0, "{ s?: s, s?: s, s?: i, s?: i }",
188-
"trigger", &trigger_str, "unit", &unit_str,
189-
"interval", &interval_int, "offset", &offset_int);
77+
auto ret = json_unpack_ex(json, &err, 0, "{ s?: s, s: o }", "trigger",
78+
&trigger_str, "interval", &json_interval);
19079
if (ret)
19180
throw ConfigError(json, err, "node-config-hook-frame");
19281

193-
if (trigger_str) {
194-
if (!strcmp(trigger_str, "sequence"))
195-
trigger = Trigger::SEQUENCE;
196-
else if (!strcmp(trigger_str, "timestamp"))
197-
trigger = Trigger::TIMESTAMP;
198-
else
199-
throw ConfigError(json, "node-config-hook-frame-unit");
200-
}
201-
202-
if (trigger == Trigger::TIMESTAMP) {
203-
if (!strcmp(unit_str, "milliseconds"))
204-
unit = Unit::MILLISECONDS;
205-
else if (!strcmp(unit_str, "seconds"))
206-
unit = Unit::SECONDS;
207-
else if (!strcmp(unit_str, "minutes"))
208-
unit = Unit::MINUTES;
209-
else if (!strcmp(unit_str, "hours"))
210-
unit = Unit::HOURS;
211-
else
212-
throw ConfigError(json, "node-config-hook-frame-unit");
213-
}
214-
215-
if (interval_int != -1) {
216-
if (interval_int <= 0 || (uint64_t)interval_int > unit_wrap(unit))
217-
throw ConfigError(json, "node-config-hook-frame-interval");
218-
219-
interval = interval_int;
220-
}
82+
if (trigger_str == nullptr || !strcmp(trigger_str, "timestamp")) {
83+
interval = parse_duration<TimeInterval>(json_interval);
22184

222-
if (offset_int != -1) {
223-
if (offset_int < 0 || (uint64_t)offset_int >= unit_wrap(unit))
224-
throw ConfigError(json, "node-config-hook-frame-offset");
85+
if (std::get<TimeInterval>(interval) == TimeInterval::zero())
86+
throw ConfigError(json, "node-config-hook-frame-interval",
87+
"Interval must be greater than zero");
88+
} else if (!strcmp(trigger_str, "sequence")) {
89+
if (!json_is_integer(json))
90+
throw ConfigError(json, "node-config-hook-frame-interval",
91+
"Interval must be an integer");
22592

226-
offset = offset_int;
93+
interval = SequenceInterval(json_integer_value(json_interval));
22794
}
22895
}
22996

@@ -236,7 +103,7 @@ class FrameHook : public Hook {
236103
smp->flags &= ~(int)SampleFlags::NEW_FRAME;
237104

238105
sample_incref(smp);
239-
last_smp = sample_ptr{smp, &sample_decref};
106+
last_smp = Sample::PtrUnique{smp, &sample_decref};
240107

241108
return Reason::OK;
242109
}

0 commit comments

Comments
 (0)