Skip to content

Commit 4bf774b

Browse files
authored
Action metrics (#85)
**New Features** - A new `REGOCPP_ACTION_METRICS` option is available. When turned on, it will compile rego-cpp with instrumentation to measure the number of times a Trieste action is executed and how long is spent in each. - A new internal `join` method. **Improvements** - `ValueDef` now caches its `str` and `json` representations instead of computing them each time. This has had a significant impact on unification (~5x speedup) - Use of the new `join` method reduces allocations and copies for common string operations - The unifier can now act over `Object`, `Set`, and `Array` nodes directly. - The `functions` pass has been refactored to produce fewer temporary variables and to perform fewer rewrites, reducing its runtime by half. - The local variable fixing that used to happen in `implicit_enums` now happens in its own pass, `enum_locals` resulting in a speedup for both. --------- Signed-off-by: Matthew A Johnson <[email protected]>
1 parent 710e2bb commit 4bf774b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+1968
-618
lines changed

CHANGELOG

+17
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,22 @@
11
# Changelog
22

3+
## 2023-09-21 - Version 0.3.10
4+
Instrumentation and optimization.
5+
6+
**New Features**
7+
- A new `REGOCPP_ACTION_METRICS` option is available. When turned on, it will compile rego-cpp with
8+
instrumentation to measure the number of times a Trieste action is executed and how long is spent
9+
in each.
10+
- A new internal `join` method.
11+
12+
**Improvements**
13+
- `ValueDef` now caches its `str` and `json` representations instead of computing them each time.
14+
This has had a significant impact on unification (~5x speedup)
15+
- Use of the new `join` method reduces allocations and copies for common string operations
16+
- The unifier can now act over `Object`, `Set`, and `Array` nodes directly.
17+
- The `functions` pass has been refactored to produce fewer temporary variables and to perform
18+
fewer rewrites, reducing its runtime by half.
19+
320
## 2023-09-20 - Version 0.3.9
421
Fixing several impactful bugs.
522

CMakeLists.txt

+4-3
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,11 @@ option(REGOCPP_BUILD_DOCS "Specifies whether to build the documentation" OFF)
6666
option(REGOCPP_OPA_TESTS "Specifies whether to load and run the OPA built-ins tests" OFF)
6767
option(REGOCPP_SPECULATIVE_LOAD_HARDENING "Specifies whether to enable speculative load hardening, only supported with Clang" OFF)
6868
option(REGOCPP_COPY_EXAMPLES "Specifies whether to copy the examples to the install directory" OFF)
69-
option(REGOCPP_USE_CXX_17 "Specifies whether to use C++17" OFF)
69+
option(REGOCPP_USE_CXX17 "Specifies whether to use C++17" OFF)
70+
option(REGOCPP_ACTION_METRICS "Specifies whether to metricate Trieste Actions" OFF)
7071
set(REGOCPP_SANITIZE "" CACHE STRING "Argument to pass to sanitize (disabled by default)")
7172

72-
if(REGOCPP_USE_CXX_17)
73+
if(REGOCPP_USE_CXX17)
7374
set(CMAKE_CXX_STANDARD 17)
7475
else()
7576
set(CMAKE_CXX_STANDARD 20)
@@ -87,7 +88,7 @@ FetchContent_Declare(
8788

8889
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
8990

90-
set(TRIESTE_USE_CXX_17 ${REGOCPP_USE_CXX_17})
91+
set(TRIESTE_USE_CXX17 ${REGOCPP_USE_CXX17})
9192
set(TRIESTE_BUILD_SAMPLES OFF)
9293

9394
if (REGOCPP_SPECULATIVE_LOAD_HARDENING)

VERSION

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.3.9
1+
0.3.10

examples/rust/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ edition = "2021"
66
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
77

88
[dependencies]
9-
regorust = "0.3.9"
9+
regorust = "0.3.10"
1010
clap = { version = "4.0", features = ["derive"] }

include/rego/rego.hh

+4-3
Original file line numberDiff line numberDiff line change
@@ -713,12 +713,13 @@ namespace rego
713713
// clang-format on
714714

715715
inline const auto wf_pass_implicit_enums = wf_pass_init;
716+
inline const auto wf_pass_enum_locals = wf_pass_implicit_enums;
716717

717718
inline const auto wf_rulebody_exprs = (wf_assign_exprs - AssignInfix);
718719

719720
// clang-format off
720721
inline const auto wf_pass_rulebody =
721-
wf_pass_implicit_enums
722+
wf_pass_enum_locals
722723
| (Module <<= (Import | RuleComp | RuleFunc | RuleSet | RuleObj | Submodule)++)
723724
| (UnifyExpr <<= Var * (Val >>= Expr))
724725
| (Expr <<= wf_rulebody_exprs)
@@ -750,9 +751,9 @@ namespace rego
750751
// clang-format off
751752
inline const auto wf_pass_functions =
752753
wf_pass_lift_to_rule
753-
| (UnifyExpr <<= Var * (Val >>= Var | Scalar | Function))
754+
| (UnifyExpr <<= Var * (Val >>= Var | Scalar | Array | Set | Object | Function))
754755
| (Function <<= JSONString * ArgSeq)
755-
| (ArgSeq <<= (Scalar | Var | wf_arith_op | wf_bin_op | wf_bool_op | NestedBody | VarSeq)++)
756+
| (ArgSeq <<= (Scalar | Object | Array | Set | Var | wf_arith_op | wf_bin_op | wf_bool_op | NestedBody | VarSeq)++)
756757
| (Input <<= Key * (Val >>= Term | Undefined))[Key]
757758
| (Array <<= Term++)
758759
| (Set <<= Term++)

src/CMakeLists.txt

+6-2
Original file line numberDiff line numberDiff line change
@@ -97,13 +97,17 @@ if (REGOCPP_SANITIZE)
9797
target_link_libraries(rego PUBLIC -fsanitize=${REGOCPP_SANITIZE})
9898
endif()
9999

100-
if(REGOCPP_USE_CXX_17)
100+
if(REGOCPP_USE_CXX17)
101101
target_compile_features(rego PUBLIC cxx_std_17)
102-
target_compile_definitions(rego PUBLIC REGOCPP_USE_CXX_17)
102+
target_compile_definitions(rego PUBLIC REGOCPP_USE_CXX17)
103103
else()
104104
target_compile_features(rego PUBLIC cxx_std_20)
105105
endif()
106106

107+
if(REGOCPP_ACTION_METRICS)
108+
target_compile_definitions(rego PUBLIC REGOCPP_ACTION_METRICS)
109+
endif()
110+
107111
target_include_directories( rego
108112
PUBLIC
109113
$<INSTALL_INTERFACE:include>

src/builtins.cc

+11-6
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,24 @@ namespace
1616

1717
Node print(const Nodes& args)
1818
{
19-
std::ostringstream buf;
20-
std::string sep = "";
2119
for (auto arg : args)
2220
{
2321
if (arg->type() == Undefined)
2422
{
2523
return Resolver::scalar(false);
2624
}
27-
buf << sep << to_json(arg);
28-
sep = " ";
2925
}
30-
buf << std::endl;
31-
std::cout << buf.str();
26+
27+
join(
28+
std::cout,
29+
args.begin(),
30+
args.end(),
31+
" ",
32+
[](std::ostream& stream, const Node& n) {
33+
stream << to_json(n, true);
34+
return true;
35+
})
36+
<< std::endl;
3237
return Resolver::scalar(true);
3338
}
3439

src/builtins/aggregates.cc

+6-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,12 @@ namespace
7676
return collection;
7777
}
7878

79-
Nodes items(collection->begin(), collection->end());
79+
Nodes items;
80+
for (const Node& item : *collection)
81+
{
82+
items.push_back(item->clone());
83+
}
84+
8085
std::sort(items.begin(), items.end(), [](const Node& a, const Node& b) {
8186
return to_json(a) < to_json(b);
8287
});

src/builtins/strings.cc

+6-4
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,13 @@ namespace
5151
}
5252

5353
std::ostringstream result_str;
54-
std::string sep = "";
55-
for (auto& item : items)
54+
for (auto it = items.begin(); it != items.end(); ++it)
5655
{
57-
result_str << sep << item;
58-
sep = delim_str;
56+
if (it != items.begin())
57+
{
58+
result_str << delim_str;
59+
}
60+
result_str << *it;
5961
}
6062

6163
return Resolver::scalar(result_str.str());

src/encoding.cc

+38-27
Original file line numberDiff line numberDiff line change
@@ -176,12 +176,15 @@ namespace rego
176176
}
177177

178178
buf << "[";
179-
std::string sep = "";
180-
for (const auto& item : items)
181-
{
182-
buf << sep << item;
183-
sep = ", ";
184-
}
179+
join(
180+
buf,
181+
items.begin(),
182+
items.end(),
183+
", ",
184+
[](std::ostream& stream, const std::string& item) {
185+
stream << item;
186+
return true;
187+
});
185188
buf << "]";
186189
}
187190
else if (node->type() == Set || node->type() == DataSet)
@@ -202,13 +205,17 @@ namespace rego
202205
{
203206
buf << "<";
204207
}
205-
std::string sep = "";
206-
for (const auto& node_key : node_keys)
207-
{
208-
std::string key_str = to_json(node_key.node, set_as_array, sort_arrays);
209-
buf << sep << key_str;
210-
sep = ", ";
211-
}
208+
209+
join(
210+
buf,
211+
node_keys.begin(),
212+
node_keys.end(),
213+
", ",
214+
[set_as_array,
215+
sort_arrays](std::ostream& stream, const NodeKey& node_key) {
216+
stream << to_json(node_key.node, set_as_array, sort_arrays);
217+
return true;
218+
});
212219

213220
if (set_as_array)
214221
{
@@ -222,7 +229,6 @@ namespace rego
222229
else if (node->type() == Object || node->type() == DataObject)
223230
{
224231
std::map<std::string, std::string> items;
225-
std::vector<NodeKey> keys;
226232
for (const auto& child : *node)
227233
{
228234
auto key = child / Key;
@@ -236,13 +242,15 @@ namespace rego
236242
}
237243

238244
buf << "{";
239-
std::string sep = "";
240-
for (const auto& [key, value] : items)
241-
{
242-
buf << sep << key << ":" << value;
243-
sep = ", ";
244-
}
245-
245+
join(
246+
buf,
247+
items.begin(),
248+
items.end(),
249+
", ",
250+
[](std::ostream& stream, const auto& item) {
251+
stream << item.first << ":" << item.second;
252+
return true;
253+
});
246254
buf << "}";
247255
}
248256
else if (
@@ -259,12 +267,15 @@ namespace rego
259267
else if (node->type() == TermSet)
260268
{
261269
buf << "termset{";
262-
std::string sep = "";
263-
for (const auto& child : *node)
264-
{
265-
buf << sep << to_json(child, set_as_array, sort_arrays);
266-
sep = ", ";
267-
}
270+
join(
271+
buf,
272+
node->begin(),
273+
node->end(),
274+
", ",
275+
[set_as_array, sort_arrays](std::ostream& stream, const Node& n) {
276+
stream << to_json(n, set_as_array, sort_arrays);
277+
return true;
278+
});
268279
buf << "}";
269280
}
270281
else if (node->type() == BuiltInHook)

src/internal.cc

+68-18
Original file line numberDiff line numberDiff line change
@@ -206,26 +206,32 @@ namespace rego
206206

207207
std::ostream& operator<<(std::ostream& os, const std::set<Location>& locs)
208208
{
209-
std::string sep = "";
210209
os << "{";
211-
for (const auto& loc : locs)
212-
{
213-
os << sep << loc.view();
214-
sep = ", ";
215-
}
210+
join(
211+
os,
212+
locs.begin(),
213+
locs.end(),
214+
", ",
215+
[](std::ostream& stream, const Location& loc) {
216+
stream << loc.view();
217+
return true;
218+
});
216219
os << "}";
217220
return os;
218221
}
219222

220223
std::ostream& operator<<(std::ostream& os, const std::vector<Location>& locs)
221224
{
222-
std::string sep = "";
223225
os << "[";
224-
for (const auto& loc : locs)
225-
{
226-
os << sep << loc.source->origin() << ":" << loc.view();
227-
sep = ", ";
228-
}
226+
join(
227+
os,
228+
locs.begin(),
229+
locs.end(),
230+
", ",
231+
[](std::ostream& stream, const Location& loc) {
232+
stream << loc.view();
233+
return true;
234+
});
229235
os << "]";
230236
return os;
231237
}
@@ -564,12 +570,15 @@ namespace rego
564570
if (m_types.size() > 1)
565571
{
566572
error << "must be one of {";
567-
std::string sep = "";
568-
for (auto& type : m_types)
569-
{
570-
error << sep << type_name(type, m_specify_number);
571-
sep = ", ";
572-
}
573+
join(
574+
error,
575+
m_types.begin(),
576+
m_types.end(),
577+
", ",
578+
[&](std::ostream& stream, const Token& type) {
579+
stream << type_name(type, m_specify_number);
580+
return true;
581+
});
573582
error << "}";
574583
}
575584
else if (m_types.size() == 1)
@@ -784,4 +793,45 @@ namespace rego
784793
{
785794
return var->type().in({Submodule, DataItem, Data});
786795
}
796+
797+
ActionMetrics::ActionMetrics(const char* file, std::size_t line) :
798+
m_key{file, line}, m_start(ActionMetrics::clock::now())
799+
{}
800+
801+
ActionMetrics::~ActionMetrics()
802+
{
803+
if (!contains(ActionMetrics::s_action_info, m_key))
804+
{
805+
ActionMetrics::s_action_info.insert(
806+
{m_key, {0, ActionMetrics::duration::zero()}});
807+
}
808+
809+
auto elapsed = ActionMetrics::clock::now() - m_start;
810+
ActionMetrics::s_action_info[m_key].count++;
811+
ActionMetrics::s_action_info[m_key].time_spent += elapsed;
812+
}
813+
814+
std::map<ActionMetrics::key_t, ActionMetrics::info_t>
815+
ActionMetrics::s_action_info = {};
816+
817+
void ActionMetrics::print()
818+
{
819+
std::cout << "Action\tCount\tTime(ms)" << std::endl;
820+
for (auto& [key, info] : ActionMetrics::s_action_info)
821+
{
822+
std::chrono::duration<double, std::milli> fp_ms = info.time_spent;
823+
std::cout << key.file << ":" << key.line << "\t" << info.count << "\t"
824+
<< fp_ms.count() << std::endl;
825+
}
826+
}
827+
828+
bool ActionMetrics::key_t::operator<(const ActionMetrics::key_t& other) const
829+
{
830+
if (file != other.file)
831+
{
832+
return file < other.file;
833+
}
834+
835+
return line < other.line;
836+
}
787837
}

0 commit comments

Comments
 (0)