triton-model-analyzer 1.48.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. model_analyzer/__init__.py +15 -0
  2. model_analyzer/analyzer.py +448 -0
  3. model_analyzer/cli/__init__.py +15 -0
  4. model_analyzer/cli/cli.py +193 -0
  5. model_analyzer/config/__init__.py +15 -0
  6. model_analyzer/config/generate/__init__.py +15 -0
  7. model_analyzer/config/generate/automatic_model_config_generator.py +164 -0
  8. model_analyzer/config/generate/base_model_config_generator.py +352 -0
  9. model_analyzer/config/generate/brute_plus_binary_parameter_search_run_config_generator.py +164 -0
  10. model_analyzer/config/generate/brute_run_config_generator.py +154 -0
  11. model_analyzer/config/generate/concurrency_sweeper.py +75 -0
  12. model_analyzer/config/generate/config_generator_interface.py +52 -0
  13. model_analyzer/config/generate/coordinate.py +143 -0
  14. model_analyzer/config/generate/coordinate_data.py +86 -0
  15. model_analyzer/config/generate/generator_utils.py +116 -0
  16. model_analyzer/config/generate/manual_model_config_generator.py +187 -0
  17. model_analyzer/config/generate/model_config_generator_factory.py +92 -0
  18. model_analyzer/config/generate/model_profile_spec.py +74 -0
  19. model_analyzer/config/generate/model_run_config_generator.py +154 -0
  20. model_analyzer/config/generate/model_variant_name_manager.py +150 -0
  21. model_analyzer/config/generate/neighborhood.py +536 -0
  22. model_analyzer/config/generate/optuna_plus_concurrency_sweep_run_config_generator.py +141 -0
  23. model_analyzer/config/generate/optuna_run_config_generator.py +838 -0
  24. model_analyzer/config/generate/perf_analyzer_config_generator.py +312 -0
  25. model_analyzer/config/generate/quick_plus_concurrency_sweep_run_config_generator.py +130 -0
  26. model_analyzer/config/generate/quick_run_config_generator.py +753 -0
  27. model_analyzer/config/generate/run_config_generator_factory.py +329 -0
  28. model_analyzer/config/generate/search_config.py +112 -0
  29. model_analyzer/config/generate/search_dimension.py +73 -0
  30. model_analyzer/config/generate/search_dimensions.py +85 -0
  31. model_analyzer/config/generate/search_parameter.py +49 -0
  32. model_analyzer/config/generate/search_parameters.py +388 -0
  33. model_analyzer/config/input/__init__.py +15 -0
  34. model_analyzer/config/input/config_command.py +483 -0
  35. model_analyzer/config/input/config_command_profile.py +1747 -0
  36. model_analyzer/config/input/config_command_report.py +267 -0
  37. model_analyzer/config/input/config_defaults.py +236 -0
  38. model_analyzer/config/input/config_enum.py +83 -0
  39. model_analyzer/config/input/config_field.py +216 -0
  40. model_analyzer/config/input/config_list_generic.py +112 -0
  41. model_analyzer/config/input/config_list_numeric.py +151 -0
  42. model_analyzer/config/input/config_list_string.py +111 -0
  43. model_analyzer/config/input/config_none.py +71 -0
  44. model_analyzer/config/input/config_object.py +129 -0
  45. model_analyzer/config/input/config_primitive.py +81 -0
  46. model_analyzer/config/input/config_status.py +75 -0
  47. model_analyzer/config/input/config_sweep.py +83 -0
  48. model_analyzer/config/input/config_union.py +113 -0
  49. model_analyzer/config/input/config_utils.py +128 -0
  50. model_analyzer/config/input/config_value.py +243 -0
  51. model_analyzer/config/input/objects/__init__.py +15 -0
  52. model_analyzer/config/input/objects/config_model_profile_spec.py +325 -0
  53. model_analyzer/config/input/objects/config_model_report_spec.py +173 -0
  54. model_analyzer/config/input/objects/config_plot.py +198 -0
  55. model_analyzer/config/input/objects/config_protobuf_utils.py +101 -0
  56. model_analyzer/config/input/yaml_config_validator.py +82 -0
  57. model_analyzer/config/run/__init__.py +15 -0
  58. model_analyzer/config/run/model_run_config.py +313 -0
  59. model_analyzer/config/run/run_config.py +168 -0
  60. model_analyzer/constants.py +76 -0
  61. model_analyzer/device/__init__.py +15 -0
  62. model_analyzer/device/device.py +24 -0
  63. model_analyzer/device/gpu_device.py +87 -0
  64. model_analyzer/device/gpu_device_factory.py +248 -0
  65. model_analyzer/entrypoint.py +307 -0
  66. model_analyzer/log_formatter.py +65 -0
  67. model_analyzer/model_analyzer_exceptions.py +24 -0
  68. model_analyzer/model_manager.py +255 -0
  69. model_analyzer/monitor/__init__.py +15 -0
  70. model_analyzer/monitor/cpu_monitor.py +69 -0
  71. model_analyzer/monitor/dcgm/DcgmDiag.py +191 -0
  72. model_analyzer/monitor/dcgm/DcgmFieldGroup.py +83 -0
  73. model_analyzer/monitor/dcgm/DcgmGroup.py +815 -0
  74. model_analyzer/monitor/dcgm/DcgmHandle.py +141 -0
  75. model_analyzer/monitor/dcgm/DcgmJsonReader.py +69 -0
  76. model_analyzer/monitor/dcgm/DcgmReader.py +623 -0
  77. model_analyzer/monitor/dcgm/DcgmStatus.py +57 -0
  78. model_analyzer/monitor/dcgm/DcgmSystem.py +412 -0
  79. model_analyzer/monitor/dcgm/__init__.py +15 -0
  80. model_analyzer/monitor/dcgm/common/__init__.py +13 -0
  81. model_analyzer/monitor/dcgm/common/dcgm_client_cli_parser.py +194 -0
  82. model_analyzer/monitor/dcgm/common/dcgm_client_main.py +86 -0
  83. model_analyzer/monitor/dcgm/dcgm_agent.py +887 -0
  84. model_analyzer/monitor/dcgm/dcgm_collectd_plugin.py +369 -0
  85. model_analyzer/monitor/dcgm/dcgm_errors.py +395 -0
  86. model_analyzer/monitor/dcgm/dcgm_field_helpers.py +546 -0
  87. model_analyzer/monitor/dcgm/dcgm_fields.py +815 -0
  88. model_analyzer/monitor/dcgm/dcgm_fields_collectd.py +671 -0
  89. model_analyzer/monitor/dcgm/dcgm_fields_internal.py +29 -0
  90. model_analyzer/monitor/dcgm/dcgm_fluentd.py +45 -0
  91. model_analyzer/monitor/dcgm/dcgm_monitor.py +138 -0
  92. model_analyzer/monitor/dcgm/dcgm_prometheus.py +326 -0
  93. model_analyzer/monitor/dcgm/dcgm_structs.py +2357 -0
  94. model_analyzer/monitor/dcgm/dcgm_telegraf.py +65 -0
  95. model_analyzer/monitor/dcgm/dcgm_value.py +151 -0
  96. model_analyzer/monitor/dcgm/dcgmvalue.py +155 -0
  97. model_analyzer/monitor/dcgm/denylist_recommendations.py +573 -0
  98. model_analyzer/monitor/dcgm/pydcgm.py +47 -0
  99. model_analyzer/monitor/monitor.py +143 -0
  100. model_analyzer/monitor/remote_monitor.py +137 -0
  101. model_analyzer/output/__init__.py +15 -0
  102. model_analyzer/output/file_writer.py +63 -0
  103. model_analyzer/output/output_writer.py +42 -0
  104. model_analyzer/perf_analyzer/__init__.py +15 -0
  105. model_analyzer/perf_analyzer/genai_perf_config.py +206 -0
  106. model_analyzer/perf_analyzer/perf_analyzer.py +882 -0
  107. model_analyzer/perf_analyzer/perf_config.py +479 -0
  108. model_analyzer/plots/__init__.py +15 -0
  109. model_analyzer/plots/detailed_plot.py +266 -0
  110. model_analyzer/plots/plot_manager.py +224 -0
  111. model_analyzer/plots/simple_plot.py +213 -0
  112. model_analyzer/record/__init__.py +15 -0
  113. model_analyzer/record/gpu_record.py +68 -0
  114. model_analyzer/record/metrics_manager.py +887 -0
  115. model_analyzer/record/record.py +280 -0
  116. model_analyzer/record/record_aggregator.py +256 -0
  117. model_analyzer/record/types/__init__.py +15 -0
  118. model_analyzer/record/types/cpu_available_ram.py +93 -0
  119. model_analyzer/record/types/cpu_used_ram.py +93 -0
  120. model_analyzer/record/types/gpu_free_memory.py +96 -0
  121. model_analyzer/record/types/gpu_power_usage.py +107 -0
  122. model_analyzer/record/types/gpu_total_memory.py +96 -0
  123. model_analyzer/record/types/gpu_used_memory.py +96 -0
  124. model_analyzer/record/types/gpu_utilization.py +108 -0
  125. model_analyzer/record/types/inter_token_latency_avg.py +60 -0
  126. model_analyzer/record/types/inter_token_latency_base.py +74 -0
  127. model_analyzer/record/types/inter_token_latency_max.py +60 -0
  128. model_analyzer/record/types/inter_token_latency_min.py +60 -0
  129. model_analyzer/record/types/inter_token_latency_p25.py +60 -0
  130. model_analyzer/record/types/inter_token_latency_p50.py +60 -0
  131. model_analyzer/record/types/inter_token_latency_p75.py +60 -0
  132. model_analyzer/record/types/inter_token_latency_p90.py +60 -0
  133. model_analyzer/record/types/inter_token_latency_p95.py +60 -0
  134. model_analyzer/record/types/inter_token_latency_p99.py +60 -0
  135. model_analyzer/record/types/output_token_throughput.py +105 -0
  136. model_analyzer/record/types/perf_client_response_wait.py +97 -0
  137. model_analyzer/record/types/perf_client_send_recv.py +97 -0
  138. model_analyzer/record/types/perf_latency.py +111 -0
  139. model_analyzer/record/types/perf_latency_avg.py +60 -0
  140. model_analyzer/record/types/perf_latency_base.py +74 -0
  141. model_analyzer/record/types/perf_latency_p90.py +60 -0
  142. model_analyzer/record/types/perf_latency_p95.py +60 -0
  143. model_analyzer/record/types/perf_latency_p99.py +60 -0
  144. model_analyzer/record/types/perf_server_compute_infer.py +97 -0
  145. model_analyzer/record/types/perf_server_compute_input.py +97 -0
  146. model_analyzer/record/types/perf_server_compute_output.py +97 -0
  147. model_analyzer/record/types/perf_server_queue.py +97 -0
  148. model_analyzer/record/types/perf_throughput.py +105 -0
  149. model_analyzer/record/types/time_to_first_token_avg.py +60 -0
  150. model_analyzer/record/types/time_to_first_token_base.py +74 -0
  151. model_analyzer/record/types/time_to_first_token_max.py +60 -0
  152. model_analyzer/record/types/time_to_first_token_min.py +60 -0
  153. model_analyzer/record/types/time_to_first_token_p25.py +60 -0
  154. model_analyzer/record/types/time_to_first_token_p50.py +60 -0
  155. model_analyzer/record/types/time_to_first_token_p75.py +60 -0
  156. model_analyzer/record/types/time_to_first_token_p90.py +60 -0
  157. model_analyzer/record/types/time_to_first_token_p95.py +60 -0
  158. model_analyzer/record/types/time_to_first_token_p99.py +60 -0
  159. model_analyzer/reports/__init__.py +15 -0
  160. model_analyzer/reports/html_report.py +195 -0
  161. model_analyzer/reports/pdf_report.py +50 -0
  162. model_analyzer/reports/report.py +86 -0
  163. model_analyzer/reports/report_factory.py +62 -0
  164. model_analyzer/reports/report_manager.py +1376 -0
  165. model_analyzer/reports/report_utils.py +42 -0
  166. model_analyzer/result/__init__.py +15 -0
  167. model_analyzer/result/constraint_manager.py +150 -0
  168. model_analyzer/result/model_config_measurement.py +354 -0
  169. model_analyzer/result/model_constraints.py +105 -0
  170. model_analyzer/result/parameter_search.py +246 -0
  171. model_analyzer/result/result_manager.py +430 -0
  172. model_analyzer/result/result_statistics.py +159 -0
  173. model_analyzer/result/result_table.py +217 -0
  174. model_analyzer/result/result_table_manager.py +646 -0
  175. model_analyzer/result/result_utils.py +42 -0
  176. model_analyzer/result/results.py +277 -0
  177. model_analyzer/result/run_config_measurement.py +658 -0
  178. model_analyzer/result/run_config_result.py +210 -0
  179. model_analyzer/result/run_config_result_comparator.py +110 -0
  180. model_analyzer/result/sorted_results.py +151 -0
  181. model_analyzer/state/__init__.py +15 -0
  182. model_analyzer/state/analyzer_state.py +76 -0
  183. model_analyzer/state/analyzer_state_manager.py +215 -0
  184. model_analyzer/triton/__init__.py +15 -0
  185. model_analyzer/triton/client/__init__.py +15 -0
  186. model_analyzer/triton/client/client.py +234 -0
  187. model_analyzer/triton/client/client_factory.py +57 -0
  188. model_analyzer/triton/client/grpc_client.py +104 -0
  189. model_analyzer/triton/client/http_client.py +107 -0
  190. model_analyzer/triton/model/__init__.py +15 -0
  191. model_analyzer/triton/model/model_config.py +556 -0
  192. model_analyzer/triton/model/model_config_variant.py +29 -0
  193. model_analyzer/triton/server/__init__.py +15 -0
  194. model_analyzer/triton/server/server.py +76 -0
  195. model_analyzer/triton/server/server_config.py +269 -0
  196. model_analyzer/triton/server/server_docker.py +229 -0
  197. model_analyzer/triton/server/server_factory.py +306 -0
  198. model_analyzer/triton/server/server_local.py +158 -0
  199. triton_model_analyzer-1.48.0.dist-info/METADATA +52 -0
  200. triton_model_analyzer-1.48.0.dist-info/RECORD +204 -0
  201. triton_model_analyzer-1.48.0.dist-info/WHEEL +5 -0
  202. triton_model_analyzer-1.48.0.dist-info/entry_points.txt +2 -0
  203. triton_model_analyzer-1.48.0.dist-info/licenses/LICENSE +67 -0
  204. triton_model_analyzer-1.48.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,159 @@
1
+ #!/usr/bin/env python3
2
+
3
+ # Copyright 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+
18
+ class ResultStatistics:
19
+ """
20
+ An object containing all statistics relevant
21
+ to an instance of Analyzer
22
+ """
23
+
24
+ def __init__(self):
25
+ self._total_configurations = {}
26
+ self._passing_measurements = {}
27
+ self._failing_measurements = {}
28
+
29
+ def total_configurations(self, model_name):
30
+ """
31
+ Parameters
32
+ ----------
33
+ model_name: str
34
+ The name of the model for which
35
+ we are getting the total number of
36
+ configurations
37
+
38
+ Returns
39
+ -------
40
+ int
41
+ total number of configuration
42
+ searched by the model analyzer
43
+ for a given model.
44
+ """
45
+
46
+ return self._total_configurations[model_name]
47
+
48
+ def set_total_configurations(self, model_name, total):
49
+ """
50
+ Parameters
51
+ ----------
52
+ model_name: str
53
+ The name of the model for which
54
+ we are getting the total number of
55
+ configurations
56
+ total : int
57
+ The number of configurations tried for
58
+ this model
59
+
60
+ Returns
61
+ -------
62
+ int
63
+ total number of configuration
64
+ searched by the model analyzer
65
+ for a given model.
66
+ """
67
+
68
+ self._total_configurations[model_name] = total
69
+
70
+ def total_measurements(self, model_name):
71
+ """
72
+ Parameters
73
+ ----------
74
+ model_name: str
75
+ The name of the model for which
76
+ we are getting the total number of
77
+ measurements
78
+
79
+ Returns
80
+ -------
81
+ int
82
+ total number of measurements
83
+ searched by the model analyzer
84
+ for a given model.
85
+ """
86
+
87
+ return self.passing_measurements(model_name) + self.failing_measurements(
88
+ model_name
89
+ )
90
+
91
+ def set_passing_measurements(self, model_name, passing):
92
+ """
93
+ Sets the number of passing configuration
94
+ searched by the model analyzer.
95
+
96
+ Parameters
97
+ ----------
98
+ model_name: str
99
+ The name of the model for which
100
+ we are setting the number of passing
101
+ measurements
102
+ passing : int
103
+ The number of passing measurements tried
104
+ """
105
+
106
+ self._passing_measurements[model_name] = passing
107
+
108
+ def passing_measurements(self, model_name):
109
+ """
110
+ Parameters
111
+ ----------
112
+ model_name: str
113
+ The name of the model for which
114
+ we are getting the number of passing
115
+ measurements
116
+
117
+ Returns
118
+ -------
119
+ int
120
+ passing number of measurements
121
+ searched by the model analyzer.
122
+ """
123
+
124
+ return self._passing_measurements[model_name]
125
+
126
+ def set_failing_measurements(self, model_name, failing):
127
+ """
128
+ Sets the number of failing configuration
129
+ searched by the model analyzer.
130
+
131
+ Parameters
132
+ ----------
133
+ model_name: str
134
+ The name of the model for which
135
+ we are setting the number of failing
136
+ measurements
137
+ failing : int
138
+ The total number of configs failing
139
+ """
140
+
141
+ self._failing_measurements[model_name] = failing
142
+
143
+ def failing_measurements(self, model_name):
144
+ """
145
+ Parameters
146
+ ----------
147
+ model_name: str
148
+ The name of the model for which
149
+ we are getting the number of failing
150
+ measurements
151
+
152
+ Returns
153
+ -------
154
+ int
155
+ total number of failing measurements
156
+ searched by the model analyzer.
157
+ """
158
+
159
+ return self._failing_measurements[model_name]
@@ -0,0 +1,217 @@
1
+ #!/usr/bin/env python3
2
+
3
+ # Copyright 2020-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ from model_analyzer.constants import RESULT_TABLE_COLUMN_PADDING
18
+ from model_analyzer.model_analyzer_exceptions import TritonModelAnalyzerException
19
+
20
+
21
+ class ResultTable:
22
+ """
23
+ A class that implements a generic table interface with headers rows
24
+ """
25
+
26
+ column_padding = RESULT_TABLE_COLUMN_PADDING
27
+
28
+ def __init__(self, headers, title=None):
29
+ """
30
+ Parameters
31
+ ----------
32
+ headers : list of str
33
+ Names of the columns of this table
34
+ title : str
35
+ Title of the table
36
+ """
37
+
38
+ self._headers = headers
39
+ self._title = title
40
+ self._column_widths = [len(header) + self.column_padding for header in headers]
41
+ self._rows = []
42
+
43
+ def title(self):
44
+ """
45
+ Get table title
46
+
47
+ Returns
48
+ -------
49
+ str
50
+ Title of the table
51
+ """
52
+ return self._title
53
+
54
+ def headers(self):
55
+ """
56
+ Returns
57
+ -------
58
+ list of str
59
+ names of the columns of this table
60
+ """
61
+
62
+ return self._headers
63
+
64
+ def size(self):
65
+ """
66
+ Returns
67
+ -------
68
+ int
69
+ number of rows in this
70
+ table
71
+ """
72
+
73
+ return len(self._rows)
74
+
75
+ def column_widths(self):
76
+ """
77
+ Returns
78
+ -------
79
+ list of ints
80
+ Current width in spaces of each column in table.
81
+ """
82
+
83
+ return self._column_widths
84
+
85
+ def empty(self):
86
+ """
87
+ Returns
88
+ -------
89
+ True if this table has no data
90
+ False if it does
91
+ """
92
+
93
+ return len(self._rows) == 0
94
+
95
+ def insert_row_by_index(self, row, index=None):
96
+ """
97
+ Adds a row to the table. Handles wrapping.
98
+
99
+ Parameters
100
+ ----------
101
+ row : list
102
+ A row of data to add to the ResultTable
103
+
104
+ Raises
105
+ ------
106
+ TritonModelAnalyzerException
107
+ if there is a mismatch between the table headers
108
+ and the row to be inserted.
109
+ """
110
+
111
+ if len(row) != len(self._headers):
112
+ raise TritonModelAnalyzerException(
113
+ f"Inserted row contains {len(row)} values."
114
+ f"There are {len(self._headers)} provided headers."
115
+ )
116
+ if index is None:
117
+ index = len(self._rows)
118
+ self._rows.insert(index, row[:])
119
+
120
+ for i in range(len(row)):
121
+ self._column_widths[i] = max(
122
+ len(str(row[i])) + self.column_padding, self._column_widths[i]
123
+ )
124
+
125
+ def get_row_by_index(self, index):
126
+ """
127
+ Returns the row at given index
128
+
129
+ Parameters
130
+ ----------
131
+ index : int
132
+ index of row to return
133
+
134
+ Returns
135
+ -------
136
+ list of vals
137
+ The contents of the desired column
138
+ """
139
+
140
+ if index < 0 or index >= len(self._rows):
141
+ raise TritonModelAnalyzerException(
142
+ f"Index {index} out of range for get_row"
143
+ )
144
+ return self._rows[index]
145
+
146
+ def remove_row_by_index(self, index):
147
+ """
148
+ Removes row at given index
149
+ from the table
150
+
151
+ Parameters
152
+ ----------
153
+ index : int
154
+ The index of the row to be removed
155
+ """
156
+
157
+ if len(self._rows) == 0:
158
+ raise TritonModelAnalyzerException(
159
+ "Attempting to remove result from an empty ResultTable!"
160
+ )
161
+ if index < 0 or index >= len(self._rows):
162
+ raise TritonModelAnalyzerException(
163
+ f"Index {index} out of range for remove_row_by_index"
164
+ )
165
+ self._rows.pop(index)
166
+
167
+ def to_formatted_string(self, separator="", ignore_widths=False):
168
+ """
169
+ Converts the table into its string representation
170
+ making it easy to write by a writer
171
+
172
+ Parameters
173
+ ----------
174
+ separator : str
175
+ The string that will separate columns of a row in th
176
+ table
177
+ ignore_widths : bool
178
+ Each cell is as wide as its content. Useful
179
+ for csv format.
180
+
181
+ Returns
182
+ -------
183
+ str
184
+ The formatted table as a string ready for writing
185
+ """
186
+
187
+ output_rows = []
188
+ for row in [self._headers] + self._rows:
189
+ output_rows.append(self._row_to_string(row, separator, ignore_widths))
190
+ return "\n".join(output_rows)
191
+
192
+ def _row_to_string(self, row, separator, ignore_widths):
193
+ """
194
+ Converts a single row to its string representation
195
+ """
196
+
197
+ if ignore_widths:
198
+ return separator.join([str(row[j]) for j in range(len(row))])
199
+ else:
200
+ return separator.join(
201
+ [
202
+ self._pad_or_trunc(str(row[j]), self._column_widths[j])
203
+ for j in range(len(row))
204
+ ]
205
+ )
206
+
207
+ def _pad_or_trunc(self, string, length):
208
+ """
209
+ Constructs a single cell of the table by either padding or truncating
210
+ the value inside
211
+ """
212
+
213
+ diff = length - len(string)
214
+ if diff >= 0:
215
+ return string + (" ") * diff
216
+ else:
217
+ return string[:diff]