Skip to content

Commit f2daf20

Browse files
committed
Report relative memory usage data
Previously, only absolute memory usage data was reported. The memory usage as a percentage of the total available may often be even even more useful. For example, an increase of 10 bytes is much more significant on an ATtiny85 than it is on an ATSAMD21G18. In order to calculate relative memory usage, it's necessary to know the total available memory. Total available memory is determined in the same manner as the absolute memory usage: by a regular expression on the compilation output. Some platforms are not configured to provide this information. In that case, the maximum and relative memory usage data will be recorded as "N/A".
1 parent f969b20 commit f2daf20

File tree

2 files changed

+333
-71
lines changed

2 files changed

+333
-71
lines changed

compilesketches/compilesketches.py

Lines changed: 120 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ class RunCommandOutput(enum.Enum):
7171
ALWAYS = enum.auto()
7272

7373
not_applicable_indicator = "N/A"
74+
relative_size_report_decimal_places = 2
7475

7576
arduino_cli_installation_path = pathlib.Path.home().joinpath("bin")
7677
arduino_cli_user_directory_path = pathlib.Path.home().joinpath("Arduino")
@@ -88,6 +89,7 @@ class ReportKeys:
8889
sizes = "sizes"
8990
name = "name"
9091
absolute = "absolute"
92+
relative = "relative"
9193
current = "current"
9294
previous = "previous"
9395
delta = "delta"
@@ -837,11 +839,24 @@ def get_sizes_from_output(self, compilation_result):
837839
memory_types = [
838840
{
839841
"name": "flash",
840-
"regex": r"Sketch uses [0-9]+ bytes .*of program storage space\."
842+
# Use capturing parentheses to identify the location of the data in the regular expression
843+
"regex": {
844+
# The regular expression for the absolute memory usage
845+
self.ReportKeys.absolute: r"Sketch uses ([0-9]+) bytes .*of program storage space\.",
846+
# The regular expression for the total memory
847+
self.ReportKeys.maximum: (
848+
r"Sketch uses [0-9]+ bytes .*of program storage space\. Maximum is ([0-9]+) bytes."
849+
)
850+
}
841851
},
842852
{
843853
"name": "RAM for global variables",
844-
"regex": r"Global variables use [0-9]+ bytes .*of dynamic memory"
854+
"regex": {
855+
self.ReportKeys.absolute: r"Global variables use ([0-9]+) bytes .*of dynamic memory",
856+
self.ReportKeys.maximum: (
857+
r"Global variables use [0-9]+ bytes .*of dynamic memory.*\. Maximum is ([0-9]+) bytes."
858+
)
859+
}
845860
}
846861
]
847862

@@ -850,35 +865,64 @@ def get_sizes_from_output(self, compilation_result):
850865
size = {
851866
self.ReportKeys.name: memory_type["name"],
852867
# Set default memory usage value, to be used if memory usage can't be determined
853-
self.ReportKeys.absolute: self.not_applicable_indicator
868+
self.ReportKeys.absolute: self.not_applicable_indicator,
869+
self.ReportKeys.maximum: self.not_applicable_indicator,
870+
self.ReportKeys.relative: self.not_applicable_indicator
854871
}
855872

856873
if compilation_result.success is True:
857874
# Determine memory usage of the sketch by parsing Arduino CLI's output
858-
regex_match = re.search(pattern=memory_type["regex"], string=compilation_result.output)
859-
if regex_match:
860-
size[self.ReportKeys.absolute] = int(
861-
re.search(pattern="[0-9]+", string=regex_match.group(0)).group(0))
862-
else:
863-
# If any of the following:
864-
# - recipe.size.regex is not defined in platform.txt
865-
# - upload.maximum_size is not defined in boards.txt
866-
# flash usage will not be reported in the Arduino CLI output
867-
# If any of the following:
868-
# - recipe.size.regex.data is not defined in platform.txt (e.g., Arduino SAM Boards)
869-
# - recipe.size.regex is not defined in platform.txt
870-
# - upload.maximum_size is not defined in boards.txt
871-
# RAM usage will not be reported in the Arduino CLI output
872-
self.verbose_print(
873-
"::warning::Unable to determine",
874-
memory_type["name"],
875-
"memory usage. The board's platform may not have been configured to provide this information."
876-
)
875+
size_data = self.get_size_data_from_output(compilation_output=compilation_result.output,
876+
memory_type=memory_type,
877+
size_data_type=self.ReportKeys.absolute)
878+
if size_data:
879+
size[self.ReportKeys.absolute] = size_data
880+
881+
size_data = self.get_size_data_from_output(compilation_output=compilation_result.output,
882+
memory_type=memory_type,
883+
size_data_type=self.ReportKeys.maximum)
884+
if size_data:
885+
size[self.ReportKeys.maximum] = size_data
886+
887+
size[self.ReportKeys.relative] = round(
888+
(100 * size[self.ReportKeys.absolute] / size[self.ReportKeys.maximum]),
889+
self.relative_size_report_decimal_places
890+
)
877891

878892
sizes.append(size)
879893

880894
return sizes
881895

896+
def get_size_data_from_output(self, compilation_output, memory_type, size_data_type):
897+
"""Parse the stdout from the compilation process for a specific datum and return it, or None if not found.
898+
899+
Keyword arguments:
900+
compilation_output -- stdout from the compilation process
901+
memory_type -- dictionary defining a memory type
902+
size_data_type -- the type of size data to get
903+
"""
904+
size_data = None
905+
regex_match = re.search(pattern=memory_type["regex"][size_data_type], string=compilation_output)
906+
if regex_match:
907+
size_data = int(regex_match.group(1))
908+
else:
909+
# If any of the following:
910+
# - recipe.size.regex is not defined in platform.txt
911+
# - upload.maximum_size is not defined in boards.txt
912+
# flash usage will not be reported in the Arduino CLI output
913+
# If any of the following:
914+
# - recipe.size.regex.data is not defined in platform.txt (e.g., Arduino SAM Boards)
915+
# - recipe.size.regex is not defined in platform.txt
916+
# - upload.maximum_size is not defined in boards.txt
917+
# RAM usage will not be reported in the Arduino CLI output
918+
self.verbose_print(
919+
"::warning::Unable to determine the: \"" + size_data_type + "\" value for memory type: \""
920+
+ memory_type["name"]
921+
+ "\". The board's platform may not have been configured to provide this information."
922+
)
923+
924+
return size_data
925+
882926
def do_size_deltas_report(self, compilation_result, current_sizes):
883927
"""Return whether size deltas reporting is enabled.
884928
@@ -934,8 +978,10 @@ def get_size_report(self, current_size, previous_size):
934978
"""
935979
size_report = {
936980
self.ReportKeys.name: current_size[self.ReportKeys.name],
981+
self.ReportKeys.maximum: current_size[self.ReportKeys.maximum],
937982
self.ReportKeys.current: {
938-
self.ReportKeys.absolute: current_size[self.ReportKeys.absolute]
983+
self.ReportKeys.absolute: current_size[self.ReportKeys.absolute],
984+
self.ReportKeys.relative: current_size[self.ReportKeys.relative]
939985
}
940986
}
941987

@@ -949,15 +995,30 @@ def get_size_report(self, current_size, previous_size):
949995
else:
950996
absolute_delta = (current_size[self.ReportKeys.absolute] - previous_size[self.ReportKeys.absolute])
951997

998+
if (
999+
absolute_delta == self.not_applicable_indicator
1000+
or size_report[self.ReportKeys.maximum] == self.not_applicable_indicator
1001+
):
1002+
relative_delta = self.not_applicable_indicator
1003+
else:
1004+
# Calculate from absolute values to avoid rounding errors
1005+
relative_delta = round((100 * absolute_delta / size_report[self.ReportKeys.maximum]),
1006+
self.relative_size_report_decimal_places)
1007+
9521008
# Size deltas reports are enabled
9531009
# Print the memory usage change data to the log
954-
print("Change in", current_size[self.ReportKeys.name] + ":", absolute_delta)
1010+
delta_message = "Change in " + str(current_size[self.ReportKeys.name]) + ": " + str(absolute_delta)
1011+
if relative_delta != self.not_applicable_indicator:
1012+
delta_message += " (" + str(relative_delta) + "%)"
1013+
print(delta_message)
9551014

9561015
size_report[self.ReportKeys.previous] = {
957-
self.ReportKeys.absolute: previous_size[self.ReportKeys.absolute]
1016+
self.ReportKeys.absolute: previous_size[self.ReportKeys.absolute],
1017+
self.ReportKeys.relative: previous_size[self.ReportKeys.relative]
9581018
}
9591019
size_report[self.ReportKeys.delta] = {
960-
self.ReportKeys.absolute: absolute_delta
1020+
self.ReportKeys.absolute: absolute_delta,
1021+
self.ReportKeys.relative: relative_delta
9611022
}
9621023

9631024
return size_report
@@ -1013,19 +1074,33 @@ def get_sizes_summary_report(self, sketch_report_list):
10131074
sizes_summary_report.append(
10141075
{
10151076
self.ReportKeys.name: size_report[self.ReportKeys.name],
1077+
self.ReportKeys.maximum: size_report[self.ReportKeys.maximum],
10161078
self.ReportKeys.delta: {
10171079
self.ReportKeys.absolute: {
10181080
self.ReportKeys.minimum: size_report[self.ReportKeys.delta][
10191081
self.ReportKeys.absolute],
10201082
self.ReportKeys.maximum: size_report[self.ReportKeys.delta][
10211083
self.ReportKeys.absolute]
1022-
}
1084+
},
1085+
self.ReportKeys.relative: {
1086+
self.ReportKeys.minimum: size_report[self.ReportKeys.delta][
1087+
self.ReportKeys.relative],
1088+
self.ReportKeys.maximum: size_report[self.ReportKeys.delta][
1089+
self.ReportKeys.relative]
1090+
},
10231091
}
10241092
}
10251093
)
10261094
else:
10271095
size_summary_report_index = size_summary_report_index_list[0]
10281096

1097+
if (
1098+
sizes_summary_report[size_summary_report_index][
1099+
self.ReportKeys.maximum] == self.not_applicable_indicator
1100+
):
1101+
sizes_summary_report[size_summary_report_index][
1102+
self.ReportKeys.maximum] = size_report[self.ReportKeys.maximum]
1103+
10291104
if (
10301105
sizes_summary_report[size_summary_report_index][self.ReportKeys.delta][
10311106
self.ReportKeys.absolute][self.ReportKeys.minimum] == self.not_applicable_indicator
@@ -1034,10 +1109,18 @@ def get_sizes_summary_report(self, sketch_report_list):
10341109
self.ReportKeys.absolute][self.ReportKeys.minimum] = size_report[self.ReportKeys.delta][
10351110
self.ReportKeys.absolute]
10361111

1112+
sizes_summary_report[size_summary_report_index][self.ReportKeys.delta][
1113+
self.ReportKeys.relative][self.ReportKeys.minimum] = size_report[self.ReportKeys.delta][
1114+
self.ReportKeys.relative]
1115+
10371116
sizes_summary_report[size_summary_report_index][self.ReportKeys.delta][
10381117
self.ReportKeys.absolute][self.ReportKeys.maximum] = size_report[self.ReportKeys.delta][
10391118
self.ReportKeys.absolute]
10401119

1120+
sizes_summary_report[size_summary_report_index][self.ReportKeys.delta][
1121+
self.ReportKeys.relative][self.ReportKeys.maximum] = size_report[self.ReportKeys.delta][
1122+
self.ReportKeys.relative]
1123+
10411124
elif size_report[self.ReportKeys.delta][self.ReportKeys.absolute] != (
10421125
self.not_applicable_indicator
10431126
):
@@ -1049,6 +1132,11 @@ def get_sizes_summary_report(self, sketch_report_list):
10491132
size_report[self.ReportKeys.delta][self.ReportKeys.absolute]
10501133
)
10511134

1135+
sizes_summary_report[size_summary_report_index][self.ReportKeys.delta][
1136+
self.ReportKeys.relative][self.ReportKeys.minimum] = (
1137+
size_report[self.ReportKeys.delta][self.ReportKeys.relative]
1138+
)
1139+
10521140
if (size_report[self.ReportKeys.delta][self.ReportKeys.absolute]
10531141
> sizes_summary_report[size_summary_report_index][self.ReportKeys.delta][
10541142
self.ReportKeys.absolute][self.ReportKeys.maximum]):
@@ -1057,6 +1145,11 @@ def get_sizes_summary_report(self, sketch_report_list):
10571145
size_report[self.ReportKeys.delta][self.ReportKeys.absolute]
10581146
)
10591147

1148+
sizes_summary_report[size_summary_report_index][self.ReportKeys.delta][
1149+
self.ReportKeys.relative][self.ReportKeys.maximum] = (
1150+
size_report[self.ReportKeys.delta][self.ReportKeys.relative]
1151+
)
1152+
10601153
return sizes_summary_report
10611154

10621155
def create_sketches_report_file(self, sketches_report):

0 commit comments

Comments
 (0)