Skip to content

Commit 1d0ea20

Browse files
Merge pull request #179 from dhis2/DHIS2-21778
perf: Reduce memory allocation for rules evaluation [DHIS2-21778]
2 parents 3f0096c + fefd344 commit 1d0ea20

13 files changed

Lines changed: 271 additions & 263 deletions

File tree

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ repositories {
1010
maven { url = uri("https://central.sonatype.com/repository/maven-snapshots/") }
1111
}
1212

13-
version = "3.7.1-SNAPSHOT"
13+
version = "3.7.2-SNAPSHOT"
1414
group = "org.hisp.dhis.rules"
1515

1616
if (project.hasProperty("removeSnapshotSuffix")) {

src/commonMain/kotlin/org/hisp/dhis/rules/engine/DefaultRuleEngine.kt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@ internal class DefaultRuleEngine : RuleEngine {
3131
TrackerObjectType.EVENT,
3232
target.event,
3333
valueMap,
34-
executionContext.ruleSupplementaryData,
35-
filterRules(executionContext.rules, target),
34+
RuleConditionEvaluator.convertSupplementaryData(executionContext.ruleSupplementaryData),
35+
filterRules(executionContext.rules, target).sorted(),
36+
AttributeType.DATA_ELEMENT,
3637
)
3738
}
3839

@@ -52,8 +53,9 @@ internal class DefaultRuleEngine : RuleEngine {
5253
TrackerObjectType.ENROLLMENT,
5354
target.enrollment,
5455
valueMap,
55-
executionContext.ruleSupplementaryData,
56-
filterRules(executionContext.rules),
56+
RuleConditionEvaluator.convertSupplementaryData(executionContext.ruleSupplementaryData),
57+
filterRules(executionContext.rules).sorted(),
58+
AttributeType.TRACKED_ENTITY_ATTRIBUTE,
5759
)
5860
}
5961

src/commonMain/kotlin/org/hisp/dhis/rules/engine/RuleConditionEvaluator.kt

Lines changed: 49 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package org.hisp.dhis.rules.engine
22

33
import org.hisp.dhis.lib.expression.Expression
4-
import org.hisp.dhis.lib.expression.ExpressionMode
54
import org.hisp.dhis.lib.expression.spi.ExpressionData
65
import org.hisp.dhis.lib.expression.spi.IllegalExpressionException
6+
import org.hisp.dhis.lib.expression.spi.ValueType
7+
import org.hisp.dhis.lib.expression.spi.VariableValue
78
import org.hisp.dhis.rules.api.RuleSupplementaryData
89
import org.hisp.dhis.rules.createLogger
910
import org.hisp.dhis.rules.engine.RuleEvaluationResult.Companion.errorRule
@@ -16,29 +17,32 @@ internal class RuleConditionEvaluator {
1617
fun getEvaluatedAndErrorRuleEffects(
1718
targetType: TrackerObjectType,
1819
targetUid: String,
19-
valueMap: Map<String, RuleVariableValue>,
20-
ruleSupplementaryData: RuleSupplementaryData,
20+
valueMap: MutableMap<String, VariableValue>,
21+
supplementaryMap: Map<String, List<String>>,
2122
rules: List<Rule>,
23+
attributeType: AttributeType,
2224
): List<RuleEffect> {
23-
val ruleEvaluationResults = getRuleEvaluationResults(targetType, targetUid, valueMap, ruleSupplementaryData, rules)
25+
val ruleEvaluationResults = getRuleEvaluationResults(targetType, targetUid, valueMap, supplementaryMap, rules, attributeType)
2426
return ruleEvaluationResults
2527
.flatMap { result -> result.ruleEffects }
2628
}
2729

2830
fun getRuleEffects(
2931
targetType: TrackerObjectType,
3032
targetUid: String,
31-
valueMap: Map<String, RuleVariableValue>,
32-
ruleSupplementaryData: RuleSupplementaryData,
33+
valueMap: MutableMap<String, VariableValue>,
34+
supplementaryMap: Map<String, List<String>>,
3335
rules: List<Rule>,
36+
attributeType: AttributeType,
3437
): List<RuleEffect> {
3538
val ruleEvaluationResults =
3639
getRuleEvaluationResults(
3740
targetType,
3841
targetUid,
3942
valueMap,
40-
ruleSupplementaryData,
43+
supplementaryMap,
4144
rules,
45+
attributeType,
4246
)
4347
return ruleEvaluationResults
4448
.filter { result -> !result.error }
@@ -48,39 +52,38 @@ internal class RuleConditionEvaluator {
4852
fun getRuleEvaluationResults(
4953
targetType: TrackerObjectType,
5054
targetUid: String,
51-
valueMap: Map<String, RuleVariableValue>,
52-
ruleSupplementaryData: RuleSupplementaryData,
55+
valueMap: MutableMap<String, VariableValue>,
56+
supplementaryMap: Map<String, List<String>>,
5357
rules: List<Rule>,
58+
attributeType: AttributeType,
5459
): List<RuleEvaluationResult> {
55-
val mutableValueMap = valueMap.toMutableMap()
5660
val ruleEvaluationResults: MutableList<RuleEvaluationResult> = ArrayList()
57-
for (rule in rules.sorted()) {
61+
for (rule in rules) {
5862
log.fine("Evaluating programrule: " + rule.name)
5963
try {
6064
val ruleEffects: MutableList<RuleEffect> = ArrayList()
6165
if (process(
62-
rule.condition,
63-
mutableValueMap,
64-
ruleSupplementaryData,
65-
ExpressionMode.RULE_ENGINE_CONDITION,
66+
rule.conditionExpression,
67+
valueMap,
68+
supplementaryMap,
6669
).toBoolean()
6770
) {
68-
for (action in rule.actions.sorted()) {
71+
for (action in if (attributeType == AttributeType.DATA_ELEMENT) rule.actionsForDataElement else rule.actionsForEnrollment) {
6972
try {
7073
// Check if action is assigning value to calculated variable
7174
if (isAssignToCalculatedValue(action)) {
7275
updateValueMap(
7376
unwrapVariableName(action.content()!!),
74-
RuleVariableValue(
75-
RuleValueType.TEXT,
76-
process(action.data, mutableValueMap, ruleSupplementaryData, ExpressionMode.RULE_ENGINE_ACTION),
77+
VariableValue(
78+
ValueType.STRING,
79+
process(action.dataExpression, valueMap, supplementaryMap),
7780
listOf(),
7881
null,
7982
),
80-
mutableValueMap,
83+
valueMap,
8184
)
8285
} else {
83-
ruleEffects.add(create(rule, action, mutableValueMap, ruleSupplementaryData))
86+
ruleEffects.add(create(rule, action, valueMap, supplementaryMap))
8487
}
8588
} catch (e: Exception) {
8689
addRuleErrorResult(rule, action, e, targetType, targetUid, ruleEvaluationResults)
@@ -143,21 +146,16 @@ internal class RuleConditionEvaluator {
143146
}
144147

145148
private fun process(
146-
condition: String?,
147-
valueMap: Map<String, RuleVariableValue>,
148-
ruleSupplementaryData: RuleSupplementaryData,
149-
mode: ExpressionMode,
149+
expressionResult: Result<Expression?>,
150+
valueMap: Map<String, VariableValue>,
151+
supplementaryMap: Map<String, List<String>>,
150152
): String? {
151-
if (condition.isNullOrEmpty()) {
152-
return ""
153-
}
154-
val expression = Expression(condition, mode, false)
155-
153+
val expression = expressionResult.getOrThrow() ?: return ""
156154
val build =
157155
ExpressionData(
158-
valueMap.mapValues { (_,v) -> v.toVariableValue() },
156+
valueMap,
159157
emptyMap(),
160-
convertSupplementaryData(ruleSupplementaryData),
158+
supplementaryMap,
161159
emptyMap(),
162160
emptyMap(),
163161
)
@@ -170,16 +168,6 @@ internal class RuleConditionEvaluator {
170168
)?.toString()
171169
}
172170

173-
private fun convertSupplementaryData(ruleSupplementaryData: RuleSupplementaryData): Map<String, List<String>> {
174-
val supplementaryValues: MutableMap<String, List<String>> = HashMap()
175-
176-
supplementaryValues["USER_GROUPS"] = ruleSupplementaryData.userGroups
177-
supplementaryValues["USER_ROLES"] = ruleSupplementaryData.userRoles
178-
supplementaryValues.putAll(ruleSupplementaryData.orgUnitGroups)
179-
180-
return supplementaryValues
181-
}
182-
183171
private fun convertInteger(result: Any?): Any? =
184172
if (result is Double && result % 1 == 0.0) {
185173
result.toInt()
@@ -191,21 +179,21 @@ internal class RuleConditionEvaluator {
191179

192180
private fun updateValueMap(
193181
variable: String,
194-
value: RuleVariableValue,
195-
valueMap: MutableMap<String, RuleVariableValue>,
182+
value: VariableValue,
183+
valueMap: MutableMap<String, VariableValue>,
196184
) {
197185
valueMap[variable] = value
198186
}
199187

200188
private fun create(
201189
rule: Rule,
202190
ruleAction: RuleAction,
203-
valueMap: MutableMap<String, RuleVariableValue>,
204-
ruleSupplementaryData: RuleSupplementaryData,
191+
valueMap: MutableMap<String, VariableValue>,
192+
supplementaryMap: Map<String, List<String>>,
205193
): RuleEffect {
206194
if (ruleAction.type == "ASSIGN") {
207-
val data = processRuleAction(rule, ruleAction, valueMap, ruleSupplementaryData)
208-
updateValueMap(ruleAction.field()!!, RuleVariableValue(RuleValueType.TEXT, data, listOf(), null), valueMap)
195+
val data = processRuleAction(rule, ruleAction, valueMap, supplementaryMap)
196+
updateValueMap(ruleAction.field()!!, VariableValue(ValueType.STRING, data, listOf(), null), valueMap)
209197
return if (data.isNullOrEmpty()) {
210198
RuleEffect(rule.uid, ruleAction, null)
211199
} else {
@@ -218,7 +206,7 @@ internal class RuleConditionEvaluator {
218206
rule,
219207
ruleAction,
220208
valueMap,
221-
ruleSupplementaryData,
209+
supplementaryMap,
222210
)
223211
} else {
224212
""
@@ -233,14 +221,13 @@ internal class RuleConditionEvaluator {
233221
private fun processRuleAction(
234222
rule: Rule,
235223
ruleAction: RuleAction,
236-
valueMap: MutableMap<String, RuleVariableValue>,
237-
supplementaryData: RuleSupplementaryData,
224+
valueMap: MutableMap<String, VariableValue>,
225+
supplementaryMap: Map<String, List<String>>,
238226
): String? {
239227
val data = process(
240-
ruleAction.data,
228+
ruleAction.dataExpression,
241229
valueMap,
242-
supplementaryData,
243-
ExpressionMode.RULE_ENGINE_ACTION,
230+
supplementaryMap,
244231
)
245232
log.fine(
246233
"Action " + ruleAction.type +
@@ -253,5 +240,13 @@ internal class RuleConditionEvaluator {
253240

254241
companion object {
255242
private val log = createLogger("org.hisp.dhis.rules.engine.RuleConditionEvaluator")
243+
244+
fun convertSupplementaryData(ruleSupplementaryData: RuleSupplementaryData): Map<String, List<String>> {
245+
val supplementaryValues: MutableMap<String, List<String>> = HashMap()
246+
supplementaryValues["USER_GROUPS"] = ruleSupplementaryData.userGroups
247+
supplementaryValues["USER_ROLES"] = ruleSupplementaryData.userRoles
248+
supplementaryValues.putAll(ruleSupplementaryData.orgUnitGroups)
249+
return supplementaryValues
250+
}
256251
}
257252
}

src/commonMain/kotlin/org/hisp/dhis/rules/engine/RuleEngineMultipleExecution.kt

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,24 @@ internal class RuleEngineMultipleExecution {
1010
ruleVariableValueMap: RuleVariableValueMap,
1111
ruleSupplementaryData: RuleSupplementaryData,
1212
): List<RuleEffects> {
13+
val supplementaryMap = RuleConditionEvaluator.convertSupplementaryData(ruleSupplementaryData)
14+
val evaluator = RuleConditionEvaluator()
1315
val ruleEffects: MutableList<RuleEffects> = ArrayList()
16+
val enrollmentRules: List<Rule> = filterRules(rules).sorted()
17+
val rulesByStage: Map<String, List<Rule>> = rules
18+
.filter { !it.programStage.isNullOrEmpty() }
19+
.groupBy { it.programStage!! }
20+
.mapValues { (_, stageRules) -> (enrollmentRules + stageRules).sorted() }
1421
for ((enrollment, valueMap) in ruleVariableValueMap.enrollmentMap) {
1522
val enrollmentRuleEffects =
16-
RuleConditionEvaluator()
17-
.getEvaluatedAndErrorRuleEffects(
18-
TrackerObjectType.ENROLLMENT,
19-
enrollment.enrollment,
20-
valueMap,
21-
ruleSupplementaryData,
22-
filterRules(rules),
23-
)
23+
evaluator.getEvaluatedAndErrorRuleEffects(
24+
TrackerObjectType.ENROLLMENT,
25+
enrollment.enrollment,
26+
valueMap,
27+
supplementaryMap,
28+
enrollmentRules,
29+
AttributeType.TRACKED_ENTITY_ATTRIBUTE,
30+
)
2431
ruleEffects.add(
2532
RuleEffects(
2633
TrackerObjectType.ENROLLMENT,
@@ -30,16 +37,18 @@ internal class RuleEngineMultipleExecution {
3037
)
3138
}
3239
for ((event, valueMap) in ruleVariableValueMap.eventMap) {
40+
val eventRules = rulesByStage[event.programStage] ?: enrollmentRules
3341
ruleEffects.add(
3442
RuleEffects(
3543
TrackerObjectType.EVENT,
3644
event.event,
37-
RuleConditionEvaluator().getEvaluatedAndErrorRuleEffects(
45+
evaluator.getEvaluatedAndErrorRuleEffects(
3846
TrackerObjectType.EVENT,
3947
event.event,
4048
valueMap,
41-
ruleSupplementaryData,
42-
filterRules(rules, event),
49+
supplementaryMap,
50+
eventRules,
51+
AttributeType.DATA_ELEMENT,
4352
),
4453
),
4554
)
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package org.hisp.dhis.rules.engine
22

3+
import org.hisp.dhis.lib.expression.spi.VariableValue
34
import org.hisp.dhis.rules.models.RuleEnrollment
45
import org.hisp.dhis.rules.models.RuleEvent
56

67
internal data class RuleVariableValueMap(
7-
val enrollmentMap: Map<RuleEnrollment, Map<String, RuleVariableValue>>,
8-
val eventMap: Map<RuleEvent, Map<String, RuleVariableValue>>,
8+
val enrollmentMap: Map<RuleEnrollment, MutableMap<String, VariableValue>>,
9+
val eventMap: Map<RuleEvent, MutableMap<String, VariableValue>>,
910
)

0 commit comments

Comments
 (0)