@@ -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