Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit ca47b59

Browse files
committedMar 7, 2024
Optimize out no-op writes in attribute-table.
This way we don't mark things dirty if they have not changed. Does not optimize out string values that are unchanged, because we have no good way to tell how much space we need to read the old value. Fixes project-chip#29136
1 parent 28da08f commit ca47b59

File tree

16 files changed

+11815
-2600
lines changed

16 files changed

+11815
-2600
lines changed
 

‎examples/chef/esp32/main/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ set(SRC_DIRS_LIST
7171
"${CMAKE_SOURCE_DIR}/../common/clusters/channel/"
7272
"${CMAKE_SOURCE_DIR}/../common/clusters/keypad-input/"
7373
"${CMAKE_SOURCE_DIR}/../common/clusters/audio-output/"
74+
"${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/zzz_generated/app-common/app-common/zap-generated"
7475
"${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/zzz_generated/app-common/app-common/zap-generated/attributes"
7576
"${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/server"
7677
"${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/util"

‎src/app/chip_data_model.cmake

+1
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ function(chip_configure_data_model APP_TARGET)
135135
target_sources(${APP_TARGET} ${SCOPE}
136136
${CHIP_APP_BASE_DIR}/../../zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp
137137
${CHIP_APP_BASE_DIR}/../../zzz_generated/app-common/app-common/zap-generated/cluster-objects.cpp
138+
${CHIP_APP_BASE_DIR}/../../zzz_generated/app-common/app-common/zap-generated/attribute-type.cpp
138139
${CHIP_APP_BASE_DIR}/util/attribute-storage.cpp
139140
${CHIP_APP_BASE_DIR}/util/attribute-table.cpp
140141
${CHIP_APP_BASE_DIR}/util/binding-table.cpp

‎src/app/chip_data_model.gni

+1
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ template("chip_data_model") {
199199
"${_app_root}/util/binding-table.h",
200200
"${_app_root}/util/generic-callback-stubs.cpp",
201201
"${_app_root}/util/privilege-storage.cpp",
202+
"${chip_root}/zzz_generated/app-common/app-common/zap-generated/attribute-type.cpp",
202203
"${chip_root}/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp",
203204
]
204205

‎src/app/common/templates/templates.json

+5
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@
4040
"name": "ZCL attribute-type header",
4141
"output": "attribute-type.h"
4242
},
43+
{
44+
"path": "../../zap-templates/templates/app/attribute-type-src.zapt",
45+
"name": "ZCL attribute-type implementation",
46+
"output": "attribute-type.cpp"
47+
},
4348
{
4449
"path": "../../zap-templates/templates/app/callback.zapt",
4550
"name": "ZCL callback header",

‎src/app/util/af.h

+5-3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
#include <app/util/af-types.h>
2626

27+
#include <app-common/zap-generated/attributes/Accessors.h>
2728
#include <app/util/endpoint-config-api.h>
2829

2930
#include <lib/core/DataModelTypes.h>
@@ -96,9 +97,10 @@ bool emberAfContainsClient(chip::EndpointId endpoint, chip::ClusterId clusterId)
9697
* data type (as Accessors.h/cpp have this correct by default).
9798
* TODO: this not checking seems off - what if this is run without Accessors.h ?
9899
*/
99-
chip::Protocols::InteractionModel::Status emberAfWriteAttribute(chip::EndpointId endpoint, chip::ClusterId cluster,
100-
chip::AttributeId attributeID, uint8_t * dataPtr,
101-
EmberAfAttributeType dataType);
100+
chip::Protocols::InteractionModel::Status
101+
emberAfWriteAttribute(chip::EndpointId endpoint, chip::ClusterId cluster, chip::AttributeId attributeID, uint8_t * dataPtr,
102+
EmberAfAttributeType dataType,
103+
chip::app::MarkAttributeDirty markDirty = chip::app::MarkAttributeDirty::IfChanged);
102104

103105
/**
104106
* @brief Read the attribute value, performing all the checks.

‎src/app/util/attribute-table.cpp

+38-4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717
#include <app/util/attribute-table.h>
1818

19+
#include <app-common/zap-generated/attribute-type.h>
1920
#include <app/util/attribute-storage.h>
2021
#include <app/util/config.h>
2122
#include <app/util/generic-callbacks.h>
@@ -28,6 +29,7 @@
2829
using chip::Protocols::InteractionModel::Status;
2930

3031
using namespace chip;
32+
using namespace chip::app;
3133

3234
Status emAfWriteAttributeExternal(EndpointId endpoint, ClusterId cluster, AttributeId attributeID, uint8_t * dataPtr,
3335
EmberAfAttributeType dataType)
@@ -36,9 +38,9 @@ Status emAfWriteAttributeExternal(EndpointId endpoint, ClusterId cluster, Attrib
3638
}
3739

3840
Status emberAfWriteAttribute(EndpointId endpoint, ClusterId cluster, AttributeId attributeID, uint8_t * dataPtr,
39-
EmberAfAttributeType dataType)
41+
EmberAfAttributeType dataType, MarkAttributeDirty markDirty)
4042
{
41-
return emAfWriteAttribute(endpoint, cluster, attributeID, dataPtr, dataType, true /* override read-only */);
43+
return emAfWriteAttribute(endpoint, cluster, attributeID, dataPtr, dataType, true /* override read-only */, markDirty);
4244
}
4345

4446
//------------------------------------------------------------------------------
@@ -101,7 +103,7 @@ static bool IsNullValue(const uint8_t * data, uint16_t dataLen, bool isAttribute
101103
}
102104

103105
Status emAfWriteAttribute(EndpointId endpoint, ClusterId cluster, AttributeId attributeID, uint8_t * data,
104-
EmberAfAttributeType dataType, bool overrideReadOnlyAndDataType)
106+
EmberAfAttributeType dataType, bool overrideReadOnlyAndDataType, MarkAttributeDirty markDirty)
105107
{
106108
const EmberAfAttributeMetadata * metadata = nullptr;
107109
EmberAfAttributeSearchRecord record;
@@ -179,6 +181,35 @@ Status emAfWriteAttribute(EndpointId endpoint, ClusterId cluster, AttributeId at
179181
}
180182
}
181183

184+
// Check whether anything is actually changing, before we do any work here.
185+
size_t valueSize = AttributeTypeSize(dataType);
186+
187+
constexpr size_t maxValueSize = 16; // ipv6adr
188+
if (valueSize > maxValueSize)
189+
{
190+
// Very much unexpected
191+
ChipLogError(Zcl, "Attribute type %d has too-large size %u", dataType, static_cast<unsigned>(valueSize));
192+
return Status::ConstraintError;
193+
}
194+
195+
// valueSize will be 0 when we have no size information for dataType.
196+
// In that case, we can't usefully read the current value, since we
197+
// don't know how big it is.
198+
if (valueSize != 0)
199+
{
200+
uint8_t oldValueBuffer[maxValueSize];
201+
// Cast to uint16_t is safe, because we checked valueSize <= maxValueSize above.
202+
if (emberAfReadAttribute(endpoint, cluster, attributeID, oldValueBuffer, static_cast<uint16_t>(valueSize)) ==
203+
Status::Success)
204+
{
205+
if (memcmp(data, oldValueBuffer, valueSize) == 0)
206+
{
207+
// Value has not changed. Just do nothing.
208+
return Status::Success;
209+
}
210+
}
211+
}
212+
182213
const app::ConcreteAttributePath attributePath(endpoint, cluster, attributeID);
183214

184215
// Pre write attribute callback for all attribute changes,
@@ -221,7 +252,10 @@ Status emAfWriteAttribute(EndpointId endpoint, ClusterId cluster, AttributeId at
221252
// The callee will weed out attributes that do not need to be stored.
222253
emAfSaveAttributeToStorageIfNeeded(data, endpoint, cluster, metadata);
223254

224-
MatterReportingAttributeChangeCallback(endpoint, cluster, attributeID);
255+
if (markDirty != MarkAttributeDirty::No)
256+
{
257+
MatterReportingAttributeChangeCallback(endpoint, cluster, attributeID);
258+
}
225259

226260
// Post write attribute callback for all attributes changes, regardless
227261
// of cluster.

‎src/app/util/attribute-table.h

+4-3
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ chip::Protocols::InteractionModel::Status emAfWriteAttributeExternal(chip::Endpo
6161
* the attribute
6262
* - Status::Success: if the attribute was found and successfully written
6363
*/
64-
chip::Protocols::InteractionModel::Status emAfWriteAttribute(chip::EndpointId endpoint, chip::ClusterId cluster,
65-
chip::AttributeId attributeID, uint8_t * data,
66-
EmberAfAttributeType dataType, bool overrideReadOnlyAndDataType);
64+
chip::Protocols::InteractionModel::Status
65+
emAfWriteAttribute(chip::EndpointId endpoint, chip::ClusterId cluster, chip::AttributeId attributeID, uint8_t * data,
66+
EmberAfAttributeType dataType, bool overrideReadOnlyAndDataType,
67+
chip::app::MarkAttributeDirty markDirty = chip::app::MarkAttributeDirty::IfChanged);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{{> header}}
2+
3+
#include <app-common/zap-generated/attribute-type.h>
4+
5+
namespace chip {
6+
namespace app {
7+
8+
size_t AttributeTypeSize(uint8_t attributeType)
9+
{
10+
switch (attributeType)
11+
{
12+
{{#zcl_atomics}}
13+
case ZCL_{{asDelimitedMacro name}}_ATTRIBUTE_TYPE:
14+
{{#if size}}
15+
return {{size}};
16+
{{else}}
17+
return 0;
18+
{{/if}}
19+
{{/zcl_atomics}}
20+
default:
21+
return 0;
22+
}
23+
}
24+
25+
} // namespace app
26+
} // namespace chip

‎src/app/zap-templates/templates/app/attribute-type.zapt

+13
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,22 @@
33
// Prevent multiple inclusion
44
#pragma once
55

6+
#include <cstddef>
7+
#include <cstdint>
8+
69
// ZCL attribute types
710
enum {
811
{{#zcl_atomics}}
912
{{ident}}ZCL_{{asDelimitedMacro name}}_ATTRIBUTE_TYPE = {{asHex atomicId 2}}, // {{description}}
1013
{{/zcl_atomics}}
1114
};
15+
16+
namespace chip {
17+
namespace app {
18+
19+
// Returns 0 for attribute types without a well-defined size (like strings).
20+
{{! TODO: Figure out whether we can use EmberAfAttributeType here? }}
21+
size_t AttributeTypeSize(uint8_t attributeType);
22+
23+
} // namespace app
24+
} // namespace chip

‎src/app/zap-templates/templates/app/attributes/Accessors-src.zapt

+25-9
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,8 @@ Protocols::InteractionModel::Status Get(chip::EndpointId endpoint, {{accessorGet
8585
return status;
8686
{{/if}}
8787
}
88-
Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, {{zapTypeToEncodableClusterObjectType type ns=parent.name forceNotNullable=true forceNotOptional=true}} value)
88+
89+
Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, {{zapTypeToEncodableClusterObjectType type ns=parent.name forceNotNullable=true forceNotOptional=true}} value, MarkAttributeDirty markDirty)
8990
{
9091
{{~#if (isString type)}}
9192
{{~#*inline "lengthType"}}uint{{#if (isShortString type)}}8{{else}}16{{/if}}_t{{/inline}}
@@ -100,7 +101,7 @@ Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, {{zapTypeToEn
100101
Encoding::LittleEndian::Put16(zclString, length);
101102
{{/if}}
102103
memcpy(&zclString[{{>sizingBytes}}], value.data(), value.size());
103-
return emberAfWriteAttribute(endpoint, {{>clusterId}}, Id, zclString, ZCL_{{typeAsDelimitedMacro type}}_ATTRIBUTE_TYPE);
104+
return emberAfWriteAttribute(endpoint, {{>clusterId}}, Id, zclString, ZCL_{{typeAsDelimitedMacro type}}_ATTRIBUTE_TYPE, markDirty);
104105
{{else}}
105106
using Traits = NumericAttributeTraits<{{accessorTraitType type}}>;
106107
if (!Traits::CanRepresentValue(/* isNullable = */ {{isNullable}}, value))
@@ -110,32 +111,47 @@ Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, {{zapTypeToEn
110111
Traits::StorageType storageValue;
111112
Traits::WorkingToStorage(value, storageValue);
112113
uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue);
113-
return emberAfWriteAttribute(endpoint, {{>clusterId}}, Id, writable, ZCL_{{typeAsDelimitedMacro type}}_ATTRIBUTE_TYPE);
114+
return emberAfWriteAttribute(endpoint, {{>clusterId}}, Id, writable, ZCL_{{typeAsDelimitedMacro type}}_ATTRIBUTE_TYPE, markDirty);
114115
{{/if}}
115116
}
116117

118+
Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, {{zapTypeToEncodableClusterObjectType type ns=parent.name forceNotNullable=true forceNotOptional=true}} value)
119+
{
120+
return Set(endpoint, value, MarkAttributeDirty::IfChanged);
121+
}
122+
117123
{{#if isNullable}}
118-
Protocols::InteractionModel::Status SetNull(chip::EndpointId endpoint)
124+
Protocols::InteractionModel::Status SetNull(chip::EndpointId endpoint, MarkAttributeDirty markDirty)
119125
{
120126
{{#if (isString type)}}
121127
uint8_t zclString[{{>sizingBytes}}] = { {{#if (isShortString type)}}0xFF{{else}}0xFF, 0xFF{{/if}} };
122-
return emberAfWriteAttribute(endpoint, {{>clusterId}}, Id, zclString, ZCL_{{typeAsDelimitedMacro type}}_ATTRIBUTE_TYPE);
128+
return emberAfWriteAttribute(endpoint, {{>clusterId}}, Id, zclString, ZCL_{{typeAsDelimitedMacro type}}_ATTRIBUTE_TYPE, markDirty);
123129
{{else}}
124130
using Traits = NumericAttributeTraits<{{accessorTraitType type}}>;
125131
Traits::StorageType value;
126132
Traits::SetNull(value);
127133
uint8_t * writable = Traits::ToAttributeStoreRepresentation(value);
128-
return emberAfWriteAttribute(endpoint, {{>clusterId}}, Id, writable, ZCL_{{typeAsDelimitedMacro type}}_ATTRIBUTE_TYPE);
134+
return emberAfWriteAttribute(endpoint, {{>clusterId}}, Id, writable, ZCL_{{typeAsDelimitedMacro type}}_ATTRIBUTE_TYPE, markDirty);
129135
{{/if}}
130136
}
131137

132-
Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, {{zapTypeToEncodableClusterObjectType type ns=parent.name isArgument=true forceNotOptional=true}} value)
138+
Protocols::InteractionModel::Status SetNull(chip::EndpointId endpoint)
139+
{
140+
return SetNull(endpoint, MarkAttributeDirty::IfChanged);
141+
}
142+
143+
Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, {{zapTypeToEncodableClusterObjectType type ns=parent.name isArgument=true forceNotOptional=true}} value, MarkAttributeDirty markDirty)
133144
{
134145
if (value.IsNull()) {
135-
return SetNull(endpoint);
146+
return SetNull(endpoint, markDirty);
136147
}
137148

138-
return Set(endpoint, value.Value());
149+
return Set(endpoint, value.Value(), markDirty);
150+
}
151+
152+
Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, {{zapTypeToEncodableClusterObjectType type ns=parent.name isArgument=true forceNotOptional=true}} value)
153+
{
154+
return Set(endpoint, value, MarkAttributeDirty::IfChanged);
139155
}
140156
{{/if}}
141157

‎src/app/zap-templates/templates/app/attributes/Accessors.zapt

+11
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@
1616

1717
namespace chip {
1818
namespace app {
19+
20+
enum class MarkAttributeDirty {
21+
IfChanged,
22+
No,
23+
};
24+
1925
namespace Clusters {
2026

2127
{{#zcl_clusters}}
@@ -28,10 +34,15 @@ namespace Attributes {
2834
{{#unless (isStrEqual storagePolicy "attributeAccessInterface")}}
2935
namespace {{asUpperCamelCase label}} {
3036
Protocols::InteractionModel::Status Get(chip::EndpointId endpoint, {{accessorGetterType this}} value); // {{type}}
37+
{{! NOTE: Adding an optional arg instead of an overload can break API
38+
consumers that are using the function type (e.g. in templates). }}
3139
Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, {{zapTypeToEncodableClusterObjectType type ns=parent.name forceNotNullable=true forceNotOptional=true}} value);
40+
Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, {{zapTypeToEncodableClusterObjectType type ns=parent.name forceNotNullable=true forceNotOptional=true}} value, MarkAttributeDirty markDirty);
3241
{{#if isNullable}}
3342
Protocols::InteractionModel::Status SetNull(chip::EndpointId endpoint);
43+
Protocols::InteractionModel::Status SetNull(chip::EndpointId endpoint, MarkAttributeDirty markDirty);
3444
Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, {{zapTypeToEncodableClusterObjectType type ns=parent.name isArgument=true forceNotOptional=true}} value);
45+
Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, {{zapTypeToEncodableClusterObjectType type ns=parent.name isArgument=true forceNotOptional=true}} value, MarkAttributeDirty markDirty);
3546
{{/if}}
3647
} // namespace {{asUpperCamelCase label}}
3748

‎src/controller/python/test/test_scripts/base.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -749,9 +749,9 @@ def OnValueChange(path: Attribute.TypedAttributePath, transaction: Attribute.Sub
749749

750750
#
751751
# Now write the attribute from fabric2, give it some time before checking if the report
752-
# was received.
752+
# was received. Use a different value from before, so there is an actual change.
753753
#
754-
await self.devCtrl2.WriteAttribute(nodeid, [(1, Clusters.UnitTesting.Attributes.Int8u(4))])
754+
await self.devCtrl2.WriteAttribute(nodeid, [(1, Clusters.UnitTesting.Attributes.Int8u(5))])
755755
time.sleep(2)
756756

757757
sub.Shutdown()
@@ -761,7 +761,8 @@ def OnValueChange(path: Attribute.TypedAttributePath, transaction: Attribute.Sub
761761
return False
762762

763763
#
764-
# Do the same test again, but reversing the roles of fabric1 and fabric2.
764+
# Do the same test again, but reversing the roles of fabric1 and fabric2. And again
765+
# writing a different value, so there is an actual value change.
765766
#
766767
self.logger.info("Testing fabric-isolated CASE eviction (reverse)")
767768

@@ -773,7 +774,7 @@ def OnValueChange(path: Attribute.TypedAttributePath, transaction: Attribute.Sub
773774
self.devCtrl.CloseSession(nodeid)
774775
await self.devCtrl.ReadAttribute(nodeid, [(Clusters.BasicInformation.Attributes.ClusterRevision)])
775776

776-
await self.devCtrl.WriteAttribute(nodeid, [(1, Clusters.UnitTesting.Attributes.Int8u(4))])
777+
await self.devCtrl.WriteAttribute(nodeid, [(1, Clusters.UnitTesting.Attributes.Int8u(6))])
777778
time.sleep(2)
778779

779780
sub.Shutdown()

0 commit comments

Comments
 (0)
Please sign in to comment.