diff --git a/dash-pipeline/SAI/sai_api_gen.py b/dash-pipeline/SAI/sai_api_gen.py index 4279c3cf3..3b94645eb 100755 --- a/dash-pipeline/SAI/sai_api_gen.py +++ b/dash-pipeline/SAI/sai_api_gen.py @@ -515,7 +515,7 @@ def __init__(self): self.bitwidth: int = 64 self.isreadonly: str = "true" self.counter_type: str = "bytes" - self.as_attr: bool = False + self.attr_type: str = "stats" self.param_actions: List[str] = [] def parse_p4rt(self, p4rt_counter: Dict[str, Any], var_ref_graph: P4VarRefGraph) -> None: @@ -539,9 +539,8 @@ def parse_p4rt(self, p4rt_counter: Dict[str, Any], var_ref_graph: P4VarRefGraph) print("Parsing counter: " + self.name) self.__parse_sai_counter_annotation(p4rt_counter) - # If this counter is marked as SAI attributes, then we need to generate dedicated SAI attributes for this counter. - # In this case, the type needs to be created based on the size. - if self.as_attr: + # If this counter needs to be generated as SAI attributes, we need to figure out the data type for the counter value. + if self.attr_type != "counter_id": counter_storage_type = SAITypeSolver.get_object_sai_type(self.bitwidth) # Otherwise, this counter should be linked to a SAI counter using an object ID. @@ -594,28 +593,32 @@ def __parse_sai_counter_annotation(self, p4rt_counter: Dict[str, Any]) -> None: continue elif kv['key'] == 'action_names': self.param_actions = str(kv['value']['stringValue']).split(",") - elif kv['key'] == 'as_attr': - self.as_attr = True if kv['value']['stringValue'] == "true" else False + elif kv['key'] == 'attr_type': + self.attr_type = str(kv['value']['stringValue']) + if self.attr_type not in ["counter_attr", "counter_id", "stats"]: + raise ValueError(f'Unknown counter attribute type: attr_type={self.attr_type}') else: raise ValueError("Unknown attr annotation " + kv['key']) - def generate_final_counter_on_type(self) -> 'Iterator[SAICounter]': - # If this counter is not used as an attribute, then we need to treat it as a counter object id. - if not self.as_attr: + def generate_counter_sai_attributes(self) -> 'Iterator[SAICounter]': + # If the SAI attribute type is counter id, we generate as standard SAI counter ID attributes, hence return as it is. + if self.attr_type == "counter_id": yield self - # Otherwise, we need to generate dedicated SAI attributes for this counter. - else: - if self.counter_type != 'both': - self.name = f"{self.name}_{self.counter_type}_counter" - yield self + counter_types = ['bytes', 'packets'] if self.counter_type == 'both' else [self.counter_type] + + for index, counter_type in enumerate(counter_types): + counter = self + if index != len(counter_types) - 1: + counter = copy.deepcopy(self) + + counter.counter_type = counter_type + if counter.attr_type == "counter_attr": + counter.name = f"{counter.name}_{counter.counter_type}_counter" else: - packets_counter = copy.deepcopy(self) - packets_counter.name = f"{packets_counter.name}_packets_counter" - yield packets_counter + counter.name = f"{counter.name}_{counter.counter_type}" - self.name = f"{self.name}_bytes_counter" - yield self + yield counter @sai_parser_from_p4rt @@ -764,6 +767,7 @@ def __init__(self): self.counters: List[SAICounter] = [] self.with_counters: str = 'false' self.sai_attributes: List[SAIAPITableAttribute] = [] + self.sai_stats: List[SAIAPITableAttribute] = [] # Extra properties from annotations self.stage: Optional[str] = None @@ -940,21 +944,31 @@ def __update_table_param_object_name_reference(self, all_table_names) -> None: key.object_name = table_name def __build_sai_attributes_after_parsing(self): - # Group all actions parameters and counters with as_attr set by order with sequence kept the same. + # Group all actions parameters and counters set by order with sequence kept the same. # Then merge them into a single list. sai_attributes_by_order = {} + sai_stats_by_order = {} + for action_param in self.action_params: if action_param.skipattr != "true": sai_attributes_by_order.setdefault(action_param.order, []).append(action_param) for counter in self.counters: - sai_attributes_by_order.setdefault(counter.order, []).append(counter) + if counter.attr_type != "stats": + sai_attributes_by_order.setdefault(counter.order, []).append(counter) + else: + sai_stats_by_order.setdefault(counter.order, []).append(counter) - # Merge all attributes into a single list. + # Merge all attributes into a single list by their order. self.sai_attributes = [] for order in sorted(sai_attributes_by_order.keys()): self.sai_attributes.extend(sai_attributes_by_order[order]) + # Merge all stat counters into a single list by their order. + self.sai_stats = [] + for order in sorted(sai_stats_by_order.keys()): + self.sai_stats.extend(sai_stats_by_order[order]) + class DASHAPISet(SAIObject): ''' @@ -1010,7 +1024,7 @@ def __parse_sai_counters_from_p4rt(self, p4rt_value: Dict[str, Any], var_ref_gra all_p4rt_counters = p4rt_value[COUNTERS_TAG] for p4rt_counter in all_p4rt_counters: counter = SAICounter.from_p4rt(p4rt_counter, var_ref_graph) - self.sai_counters.extend(counter.generate_final_counter_on_type()) + self.sai_counters.extend(counter.generate_counter_sai_attributes()) def __parse_sai_apis_from_p4rt(self, program: Dict[str, Any], ignore_tables: List[str]) -> None: # Group all counters by action name. @@ -1219,25 +1233,40 @@ def generate_sai_port_extensions(self) -> None: # If any counter doesn't have any table assigned, they should be added as port attributes and track globally. new_port_counters: List[SAICounter] = [] + new_port_stats: List[SAICounter] = [] is_first_attr = False + is_first_stat = False with open('SAI/experimental/saiportextensions.h', 'r') as f: content = f.read() all_port_attrs = re.findall(r'SAI_PORT_ATTR_\w+', content) is_first_attr = len(all_port_attrs) == 3 + all_port_stats = re.findall(r'SAI_PORT_STAT_\w+', content) + is_first_stat = len(all_port_stats) == 3 + for sai_counter in self.dash_sai_ext.sai_counters: - if len(sai_counter.param_actions) == 0 and sai_counter.as_attr == False: - sai_counter_port_attr_name = f"SAI_PORT_ATTR_{sai_counter.name.upper()}" - if sai_counter_port_attr_name not in all_port_attrs: - new_port_counters.append(sai_counter) + if len(sai_counter.param_actions) == 0: + if sai_counter.attr_type != "stats": + sai_counter_port_attr_name = f"SAI_PORT_ATTR_{sai_counter.name.upper()}" + if sai_counter_port_attr_name not in all_port_attrs: + new_port_counters.append(sai_counter) + else: + sai_counter_port_stat_name = f"SAI_PORT_STAT_{sai_counter.name.upper()}" + if sai_counter_port_stat_name not in all_port_stats: + new_port_stats.append(sai_counter) sai_counters_str = SAITemplateRender('templates/saicounter.j2').render(table_name = "port", sai_counters = new_port_counters, is_first_attr = is_first_attr) sai_counters_lines = [s.rstrip(" \n") for s in sai_counters_str.split('\n')] sai_counters_lines = sai_counters_lines[:-1] # Remove the last empty line, so we won't add extra empty line to the file. + sai_stats_str = SAITemplateRender('templates/headers/sai_stats_extensions.j2').render(table_name = "port", sai_stats = new_port_stats, is_first_attr = is_first_stat) + sai_stats_lines = [s.rstrip(" \n") for s in sai_stats_str.split('\n')] + sai_stats_lines = sai_stats_lines[:-1] # Remove the last empty line, so we won't add extra empty line to the file. + with SAIFileUpdater('SAI/experimental/saiportextensions.h') as f: f.insert_before('Add new experimental port attributes above this line', sai_counters_lines) + f.insert_before('Add new experimental port stats above this line', sai_stats_lines) def generate_sai_object_extensions(self) -> None: print("\nGenerating SAI object entry extensions ...") diff --git a/dash-pipeline/SAI/templates/headers/sai_api_comment_object_id.j2 b/dash-pipeline/SAI/templates/headers/sai_api_comment_object_id.j2 new file mode 100644 index 000000000..8c01f9816 --- /dev/null +++ b/dash-pipeline/SAI/templates/headers/sai_api_comment_object_id.j2 @@ -0,0 +1,5 @@ +{% if table.is_object == 'true' %} + * @param[in] {{ table.name }}_id Entry id +{% else %} + * @param[in] {{ table.name }} Entry +{% endif %} \ No newline at end of file diff --git a/dash-pipeline/SAI/templates/headers/sai_api_param_object_id.j2 b/dash-pipeline/SAI/templates/headers/sai_api_param_object_id.j2 new file mode 100644 index 000000000..b233281ba --- /dev/null +++ b/dash-pipeline/SAI/templates/headers/sai_api_param_object_id.j2 @@ -0,0 +1,5 @@ +{% if table.is_object == 'true' %} + _In_ sai_object_id_t {{ table.name }}_id, +{% else %} + _In_ const sai_{{ table.name }}_t *{{ table.name }}, +{% endif %} \ No newline at end of file diff --git a/dash-pipeline/SAI/templates/headers/sai_stats_api.j2 b/dash-pipeline/SAI/templates/headers/sai_stats_api.j2 new file mode 100644 index 000000000..001d14d4a --- /dev/null +++ b/dash-pipeline/SAI/templates/headers/sai_stats_api.j2 @@ -0,0 +1,50 @@ +{% if table.sai_stats | length > 0 %} +/** + * @brief Get {{ table.name }} statistics counters. Deprecated for backward compatibility. + * +{% include 'templates/headers/sai_api_comment_object_id.j2' %} + * @param[in] number_of_counters Number of counters in the array + * @param[in] counter_ids Specifies the array of counter ids + * @param[out] counters Array of resulting counter values. + * + * @return #SAI_STATUS_SUCCESS on success, failure status code on error + */ +typedef sai_status_t (*sai_get_{{ table.name }}_stats_fn)( +{% include 'templates/headers/sai_api_param_object_id.j2' %} + _In_ uint32_t number_of_counters, + _In_ const sai_stat_id_t *counter_ids, + _Out_ uint64_t *counters); + +/** + * @brief Get {{ table.name }} statistics counters extended. + * +{% include 'templates/headers/sai_api_comment_object_id.j2' %} + * @param[in] number_of_counters Number of counters in the array + * @param[in] counter_ids Specifies the array of counter ids + * @param[in] mode Statistics mode + * @param[out] counters Array of resulting counter values. + * + * @return #SAI_STATUS_SUCCESS on success, failure status code on error + */ +typedef sai_status_t (*sai_get_{{ table.name }}_stats_ext_fn)( +{% include 'templates/headers/sai_api_param_object_id.j2' %} + _In_ uint32_t number_of_counters, + _In_ const sai_stat_id_t *counter_ids, + _In_ sai_stats_mode_t mode, + _Out_ uint64_t *counters); + +/** + * @brief Clear {{ table.name }} statistics counters. + * +{% include 'templates/headers/sai_api_comment_object_id.j2' %} + * @param[in] number_of_counters Number of counters in the array + * @param[in] counter_ids Specifies the array of counter ids + * + * @return #SAI_STATUS_SUCCESS on success, failure status code on error + */ +typedef sai_status_t (*sai_clear_{{ table.name }}_stats_fn)( +{% include 'templates/headers/sai_api_param_object_id.j2' %} + _In_ uint32_t number_of_counters, + _In_ const sai_stat_id_t *counter_ids); + +{% endif %} \ No newline at end of file diff --git a/dash-pipeline/SAI/templates/headers/sai_stats_api_list.j2 b/dash-pipeline/SAI/templates/headers/sai_stats_api_list.j2 new file mode 100644 index 000000000..bb3ab7ba1 --- /dev/null +++ b/dash-pipeline/SAI/templates/headers/sai_stats_api_list.j2 @@ -0,0 +1,5 @@ +{% if table.sai_stats | length > 0 %} + sai_get_{{ table.name }}_stats_fn {{ " " * space_offset }}get_{{ table.name }}_stats; + sai_get_{{ table.name }}_stats_ext_fn {{ " " * space_offset }}get_{{ table.name }}_stats_ext; + sai_clear_{{ table.name }}_stats_fn {{ " " * space_offset }}clear_{{ table.name }}_stats; +{% endif %} \ No newline at end of file diff --git a/dash-pipeline/SAI/templates/headers/sai_stats_enum.j2 b/dash-pipeline/SAI/templates/headers/sai_stats_enum.j2 new file mode 100644 index 000000000..89ae86fa7 --- /dev/null +++ b/dash-pipeline/SAI/templates/headers/sai_stats_enum.j2 @@ -0,0 +1,14 @@ +{% if table.sai_stats | length > 0 %} +/** + * @brief Counter IDs for {{ table.name }} in sai_get_{{ table.name }}_stats() call + */ +typedef enum _sai_{{ table.name }}_stat_t +{ + {% for stat in table.sai_stats %} + /** DASH {{ table.name | lower }} {{ stat.name | upper }} stat count */ + SAI_{{ table.name | upper }}_STAT_{{ stat.name | upper }}, + + {% endfor %} +} sai_{{ table.name }}_stat_t; + +{% endif %} \ No newline at end of file diff --git a/dash-pipeline/SAI/templates/headers/sai_stats_extensions.j2 b/dash-pipeline/SAI/templates/headers/sai_stats_extensions.j2 new file mode 100644 index 000000000..6898ebec2 --- /dev/null +++ b/dash-pipeline/SAI/templates/headers/sai_stats_extensions.j2 @@ -0,0 +1,11 @@ +{% set sai_stats_extensions_ns = namespace(is_first_attr = is_first_attr) %} +{% for stat in sai_stats %} + /** DASH {{ table_name | lower }} {{ stat.name | upper }} stat count */ + {% if sai_stats_extensions_ns.is_first_attr == true %} + SAI_{{ table_name | upper }}_STAT_{{ stat.name | upper }} = SAI_{{ table_name | upper }}_STAT_EXTENSIONS_RANGE_START, + {% else %} + SAI_{{ table_name | upper }}_STAT_{{ stat.name | upper }}, + {% endif %} + {% set sai_stats_extensions_ns.is_first_attr = false %} + +{% endfor %} \ No newline at end of file diff --git a/dash-pipeline/SAI/templates/saiapi.cpp.j2 b/dash-pipeline/SAI/templates/saiapi.cpp.j2 index 59890bf46..6f650f4d9 100644 --- a/dash-pipeline/SAI/templates/saiapi.cpp.j2 +++ b/dash-pipeline/SAI/templates/saiapi.cpp.j2 @@ -323,6 +323,41 @@ static sai_status_t dash_sai_get_{{ table.name }}_attribute( assert(0 && "sai_get_{{ table.name }}_attribute NYI"); return SAI_STATUS_FAILURE; } + +{% if table.sai_stats | length > 0 %} +static sai_status_t dash_sai_get_{{ table.name }}_stats( + _In_ sai_object_id_t {{ table.name }}_id, + _In_ uint32_t number_of_counters, + _In_ const sai_stat_id_t *counter_ids, + _Out_ uint64_t *counters) +{ + DASH_LOG_ENTER(); + assert(0 && "sai_get_{{ table.name }}_stats NYI"); + return SAI_STATUS_FAILURE; +} + +static sai_status_t dash_sai_get_{{ table.name }}_stats_ext( + _In_ sai_object_id_t {{ table.name }}_id, + _In_ uint32_t number_of_counters, + _In_ const sai_stat_id_t *counter_ids, + _In_ sai_stats_mode_t mode, + _Out_ uint64_t *counters) +{ + DASH_LOG_ENTER(); + assert(0 && "sai_get_{{ table.name }}_stats_ext NYI"); + return SAI_STATUS_FAILURE; +} + +static sai_status_t dash_sai_clear_{{ table.name }}_stats( + _In_ sai_object_id_t {{ table.name }}_id, + _In_ uint32_t number_of_counters, + _In_ const sai_stat_id_t *counter_ids) +{ + DASH_LOG_ENTER(); + assert(0 && "sai_clear_{{ table.name }}_stats NYI"); + return SAI_STATUS_FAILURE; +} +{% endif %} {% else %} static sai_status_t dash_sai_create_{{ table.name }}( _In_ const sai_{{ table.name }}_t *{{ table.name }}, @@ -625,6 +660,40 @@ static sai_status_t dash_sai_get_{{ table.name }}_attribute( return SAI_STATUS_FAILURE; } +{% if table.sai_stats | length > 0 %} +static sai_status_t dash_sai_get_{{ table.name }}_stats( + _In_ const sai_{{ table.name }}_t *{{ table.name }}, + _In_ uint32_t number_of_counters, + _In_ const sai_stat_id_t *counter_ids, + _Out_ uint64_t *counters) +{ + DASH_LOG_ENTER(); + assert(0 && "sai_get_{{ table.name }}_stats NYI"); + return SAI_STATUS_FAILURE; +} + +static sai_status_t dash_sai_get_{{ table.name }}_stats_ext( + _In_ const sai_{{ table.name }}_t *{{ table.name }}, + _In_ uint32_t number_of_counters, + _In_ const sai_stat_id_t *counter_ids, + _In_ sai_stats_mode_t mode, + _Out_ uint64_t *counters) +{ + DASH_LOG_ENTER(); + assert(0 && "sai_get_{{ table.name }}_stats_ext NYI"); + return SAI_STATUS_FAILURE; +} + +static sai_status_t dash_sai_clear_{{ table.name }}_stats( + _In_ const sai_{{ table.name }}_t *{{ table.name }}, + _In_ uint32_t number_of_counters, + _In_ const sai_stat_id_t *counter_ids) +{ + DASH_LOG_ENTER(); + assert(0 && "sai_clear_{{ table.name }}_stats NYI"); + return SAI_STATUS_FAILURE; +} +{% endif %} {% if table.name == 'route_entry' %} static sai_status_t dash_sai_set_{{ table.name | replace("entry", "entries") }}_attribute( @@ -666,6 +735,11 @@ sai_{{ app_name }}_api_t dash_sai_{{ app_name }}_api_impl = { .remove_{{ table.name }} = dash_sai_remove_{{ table.name }}, .set_{{ table.name }}_attribute = dash_sai_set_{{ table.name }}_attribute, .get_{{ table.name }}_attribute = dash_sai_get_{{ table.name }}_attribute, +{% if table.sai_stats | length > 0 %} + .get_{{ table.name }}_stats = dash_sai_get_{{ table.name }}_stats, + .get_{{ table.name }}_stats_ext = dash_sai_get_{{ table.name }}_stats_ext, + .clear_{{ table.name }}_stats = dash_sai_clear_{{ table.name }}_stats, +{% endif %} {% if table.is_object == 'true' %} .create_{{ table.name }}s = dash_sai_create_{{ table.name }}s, .remove_{{ table.name }}s = dash_sai_remove_{{ table.name }}s, diff --git a/dash-pipeline/SAI/templates/saiapi.h.j2 b/dash-pipeline/SAI/templates/saiapi.h.j2 index 16954b2ae..7f5443df7 100644 --- a/dash-pipeline/SAI/templates/saiapi.h.j2 +++ b/dash-pipeline/SAI/templates/saiapi.h.j2 @@ -249,6 +249,7 @@ typedef enum _sai_{{ table.name }}_attr_t } sai_{{ table.name }}_attr_t; +{% include 'templates/headers/sai_stats_enum.j2' %} {% endfor %} {% for table in sai_api.tables %} /** @@ -335,6 +336,7 @@ typedef sai_status_t (*sai_get_{{ table.name }}_attribute_fn)( _In_ uint32_t attr_count, _Inout_ sai_attribute_t *attr_list); +{% include 'templates/headers/sai_stats_api.j2' %} {% if table.is_object == 'false' %} /** * @brief Bulk create {{ sai_api.app_name }}_{{ table.name }} @@ -392,6 +394,7 @@ typedef struct _sai_{{ sai_api.app_name }}_api_t sai_remove_{{ table.name }}_fn {{ " " * space_offset }}remove_{{ table.name }}; sai_set_{{ table.name }}_attribute_fn {{ " " * space_offset }}set_{{ table.name }}_attribute; sai_get_{{ table.name }}_attribute_fn {{ " " * space_offset }}get_{{ table.name }}_attribute; +{% include 'templates/headers/sai_stats_api_list.j2' %} {% if table.is_object == 'true' %} sai_bulk_object_create_fn{{ " " * table_name_max }}create_{{ table.name }}s; sai_bulk_object_remove_fn{{ " " * table_name_max }}remove_{{ table.name }}s; diff --git a/dash-pipeline/SAI/templates/saicounter.j2 b/dash-pipeline/SAI/templates/saicounter.j2 index 23f799c34..dd7c18a3f 100644 --- a/dash-pipeline/SAI/templates/saicounter.j2 +++ b/dash-pipeline/SAI/templates/saicounter.j2 @@ -1,3 +1,4 @@ +{% set sai_counter_ns = namespace(is_first_attr = is_first_attr) %} {% for counter in sai_counters %} /** * @brief DASH counter {{ counter.name | upper }} @@ -8,9 +9,9 @@ * @allownull true * @default SAI_NULL_OBJECT_ID */ - {% if is_first_attr == true %} + {% if sai_counter_ns.is_first_attr == true %} SAI_{{ table_name | upper }}_ATTR_{{ counter.name | upper }} = SAI_{{ table_name | upper }}_ATTR_EXTENSIONS_RANGE_START, - {% set is_first_attr = true %} + {% set sai_counter_ns.is_first_attr = false %} {% else %} SAI_{{ table_name | upper }}_ATTR_{{ counter.name | upper }}, {% endif %} diff --git a/dash-pipeline/bmv2/README.md b/dash-pipeline/bmv2/README.md index d4abe645c..56b8d49fd 100644 --- a/dash-pipeline/bmv2/README.md +++ b/dash-pipeline/bmv2/README.md @@ -42,8 +42,11 @@ Available tags are: - `name`: Specify the preferred counter name in SAI API generation, e.g. `outbound_bytes_counter`. - `action_names`: The counters are usually updated in actions whenever a table is matched. However, v1model doesn't support conditional statements (if-else) in action blocks. Hence, to workaround, sometimes counters should be updated in the actions are updated in the control blocks after the action is called. This tag is used to specify the name of the actions that was supposed to update this counter. e.g. `action1,action2,...` -- `as_attr`: When set to "true", the counters will be generated as an attribute of the SAI object. This is not a normal behavior in SAI, since SAI usually either use get stats APIs or directly use counter IDs. Currently, generating get stats APIs is not supported yet, hence when this is not set, the attribute will be ignored. -- `order`: Specify the order of the generated attributes in the SAI API header file. This will be particularly useful, when a table has multiple actions, and we add parameters to one of them. The new attributes might be generated in the middle of existing attributes, which breaks ABI compatibility with older version of SAI APIs. When `as_attr` is set, it will be compared with the order of other attributes from match keys and action parameters in the same object too. +- `attr_type`: Attribute type can be set to multiple values: `counter_attr`, `counter_id` and `stats`. + - `counter_attr`: The counters will be generated as attributes for directly fetching the counter value on the target SAI object. + - `counter_id`: The counters will be generated as counter ids on the target SAI objects. + - `stats`: The counters will be generated as SAI stats attributes. If any SAI objects has any stats attributes, SAI get stats API will be generated. +- `order`: Specify the order of the generated attributes in the SAI API header file. This will be particularly useful, when a table has multiple actions, and we add parameters to one of them. The new attributes might be generated in the middle of existing attributes, which breaks ABI compatibility with older version of SAI APIs. When `attr_type` is set, it will be compared with the order of other attributes from match keys and action parameters in the same object too. #### `@SaiTable`: Tables diff --git a/dash-pipeline/bmv2/dash_pipeline.p4 b/dash-pipeline/bmv2/dash_pipeline.p4 index 19a08acab..deb46b906 100644 --- a/dash-pipeline/bmv2/dash_pipeline.p4 +++ b/dash-pipeline/bmv2/dash_pipeline.p4 @@ -285,9 +285,9 @@ control dash_ingress( // MAX_METER_BUCKET = MAX_ENI(64) * NUM_BUCKETS_PER_ENI(4096) #define MAX_METER_BUCKETS 262144 #ifdef TARGET_BMV2_V1MODEL - @SaiCounter[name="outbound", action_names="meter_bucket_action", as_attr="true"] + @SaiCounter[name="outbound", action_names="meter_bucket_action", attr_type="counter_attr"] counter(MAX_METER_BUCKETS, CounterType.bytes) meter_bucket_outbound; - @SaiCounter[name="inbound", action_names="meter_bucket_action", as_attr="true"] + @SaiCounter[name="inbound", action_names="meter_bucket_action", attr_type="counter_attr"] counter(MAX_METER_BUCKETS, CounterType.bytes) meter_bucket_inbound; #endif // TARGET_BMV2_V1MODEL action meter_bucket_action(@SaiVal[type="sai_uint32_t", skipattr="true"] bit<32> meter_bucket_index) {