diff --git a/genai-perf/genai_perf/export_data/console_exporter.py b/genai-perf/genai_perf/export_data/console_exporter.py index 1dcb65e4..cf22f045 100644 --- a/genai-perf/genai_perf/export_data/console_exporter.py +++ b/genai-perf/genai_perf/export_data/console_exporter.py @@ -26,12 +26,13 @@ import genai_perf.logging as logging -from genai_perf.export_data import telemetry_data_exporter_util as telem_utils -from genai_perf.export_data.exporter_config import ExporterConfig -from genai_perf.metrics import MetricMetadata from rich.console import Console from rich.table import Table +from . import exporter_utils +from . import telemetry_data_exporter_util as telem_utils +from .exporter_config import ExporterConfig + logger = logging.getLogger(__name__) @@ -85,23 +86,27 @@ def _construct_table(self, table: Table) -> None: if self._should_skip(metric.name): continue - metric_str = self.format_metric_name(metric.name, metric.unit) + metric_str = exporter_utils.format_metric_name(metric.name, metric.unit) row_values = [metric_str] for stat in self.STAT_COLUMN_KEYS: - row_values.append(self.fetch_stat(metric.name, stat)) + row_values.append( + exporter_utils.fetch_stat(self._stats, metric.name, stat) + ) table.add_row(*row_values) for metric in self._metrics.system_metrics: - metric_str = self.format_metric_name(metric.name, metric.unit) + metric_str = exporter_utils.format_metric_name(metric.name, metric.unit) if metric.name == "request_goodput" and not self._args.goodput: continue row_values = [metric_str] for stat in self.STAT_COLUMN_KEYS: if stat == "avg": - row_values.append(self.fetch_stat(metric.name, "avg")) + row_values.append( + exporter_utils.fetch_stat(self._stats, metric.name, "avg") + ) else: row_values.append("N/A") @@ -129,40 +134,3 @@ def _should_skip(self, metric_name: str) -> bool: if not self._args.streaming and metric_name in streaming_metrics: return True return False - - def format_metric_name(self, name, unit): - """Helper to format metric name with its unit.""" - metric_str = name.replace("_", " ").capitalize() - return f"{metric_str} ({unit})" if unit and unit != "tokens" else metric_str - - def format_stat_value(self, value): - """Helper to format a statistic value for printing.""" - return f"{value:,.2f}" if isinstance(value, (int, float)) else "N/A" - - def fetch_stat(self, metric_name: str, stat: str): - """ - Fetches a statistic value for a metric. - Logs errors and returns 'N/A' if the value is missing - """ - if metric_name not in self._stats: - logger.error( - f"Metric '{metric_name}' is missing in the provided statistics." - ) - return "N/A" - - metric_stats = self._stats[metric_name] - if not isinstance(metric_stats, dict): - logger.error( - f"Expected statistics for metric '{metric_name}' to be a dictionary. " - f"Got: {type(metric_stats).__name__}." - ) - return "N/A" - - if stat not in metric_stats: - logger.error( - f"Statistic '{stat}' for metric '{metric_name}' is missing. " - f"Available stats: {list(metric_stats.keys())}." - ) - return "N/A" - - return self.format_stat_value(metric_stats[stat]) diff --git a/genai-perf/genai_perf/export_data/csv_exporter.py b/genai-perf/genai_perf/export_data/csv_exporter.py index a177e76d..ffbc5e9b 100644 --- a/genai-perf/genai_perf/export_data/csv_exporter.py +++ b/genai-perf/genai_perf/export_data/csv_exporter.py @@ -28,8 +28,10 @@ import csv import genai_perf.logging as logging -from genai_perf.export_data import telemetry_data_exporter_util as telem_utils -from genai_perf.export_data.exporter_config import ExporterConfig + +from . import exporter_utils +from . import telemetry_data_exporter_util as telem_utils +from .exporter_config import ExporterConfig logger = logging.getLogger(__name__) @@ -83,23 +85,25 @@ def _write_request_metrics(self, csv_writer) -> None: if self._should_skip(metric.name): continue - metric_str = self.format_metric_name(metric.name, metric.unit) + metric_str = exporter_utils.format_metric_name(metric.name, metric.unit) row_values = [metric_str] for stat in self.REQUEST_METRICS_HEADER[1:]: - row_values.append(self.fetch_stat(metric.name, stat)) + row_values.append( + exporter_utils.fetch_stat(self._stats, metric.name, stat) + ) csv_writer.writerow(row_values) def _write_system_metrics(self, csv_writer) -> None: csv_writer.writerow(self.SYSTEM_METRICS_HEADER) for metric in self._metrics.system_metrics: - metric_str = self.format_metric_name(metric.name, metric.unit) + metric_str = exporter_utils.format_metric_name(metric.name, metric.unit) if metric.name == "request_goodput" and not self._args.goodput: continue - value = self.fetch_stat(metric.name, "avg") - row = [metric_str, self.format_stat_value(value)] + value = exporter_utils.fetch_stat(self._stats, metric.name, "avg") + row = [metric_str, exporter_utils.format_stat_value(value)] print(row) - csv_writer.writerow([metric_str, self.format_stat_value(value)]) + csv_writer.writerow([metric_str, exporter_utils.format_stat_value(value)]) def _should_skip(self, metric_name: str) -> bool: if self._args.endpoint_type == "embeddings": @@ -122,40 +126,3 @@ def _should_skip(self, metric_name: str) -> bool: if not self._args.streaming and metric_name in streaming_metrics: return True return False - - def format_metric_name(self, name, unit): - """Helper to format metric name with its unit.""" - metric_str = name.replace("_", " ").title() - return f"{metric_str} ({unit})" if unit else metric_str - - def format_stat_value(self, value): - """Helper to format a statistic value for printing.""" - return f"{value:,.2f}" if isinstance(value, (int, float)) else str(value) - - def fetch_stat(self, metric_name: str, stat: str): - """ - Fetches a statistic value for a metric. - Logs errors and returns 'N/A' if the value is missing - """ - if metric_name not in self._stats: - logger.error( - f"Metric '{metric_name}' is missing in the provided statistics." - ) - return "N/A" - - metric_stats = self._stats[metric_name] - if not isinstance(metric_stats, dict): - logger.error( - f"Expected statistics for metric '{metric_name}' to be a dictionary. " - f"Got: {type(metric_stats).__name__}." - ) - return "N/A" - - if stat not in metric_stats: - logger.error( - f"Statistic '{stat}' for metric '{metric_name}' is missing. " - f"Available stats: {list(metric_stats.keys())}." - ) - return "N/A" - - return self.format_stat_value(metric_stats[stat]) diff --git a/genai-perf/genai_perf/export_data/exporter_utils.py b/genai-perf/genai_perf/export_data/exporter_utils.py new file mode 100644 index 00000000..bef26e4d --- /dev/null +++ b/genai-perf/genai_perf/export_data/exporter_utils.py @@ -0,0 +1,85 @@ +# Copyright 2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of NVIDIA CORPORATION nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import logging +from typing import Any, Dict, Optional + +logger = logging.getLogger(__name__) + + +def format_metric_name(name: str, unit: Optional[str]) -> str: + """ + Format a metric name with its unit. + Example: "example_metric" becomes "Example Metric (ms)" if the unit is "ms". + """ + metric_str = name.replace("_", " ").title() + return f"{metric_str} ({unit})" if unit else metric_str + + +def format_stat_value(value: Any) -> str: + """ + Format a statistic value for printing. + Example: 1234.56 becomes "1,234.56". + """ + return f"{value:,.2f}" if isinstance(value, (int, float)) else str(value) + + +def fetch_stat( + stats: Dict[str, Dict[str, float]], + metric_name: str, + stat: str, +) -> str: + """ + Fetches a statistic value for a metric. + Logs warnings for missing metrics or stats and returns 'N/A' if the value is missing. + + Args: + stats (dict): Dictionary containing statistics for metrics. + metric_name (str): The name of the metric. + stat (str): The statistic to fetch (e.g., 'avg', 'min', 'max'). + + Returns: + str: The formatted statistic value or 'N/A' if missing. + """ + if metric_name not in stats: + logger.error(f"Metric '{metric_name}' is missing in the provided statistics.") + return "N/A" + + metric_stats = stats[metric_name] + if not isinstance(metric_stats, dict): + logger.error( + f"Expected statistics for metric '{metric_name}' to be a dictionary. Got: {type(metric_stats).__name__}." + ) + return "N/A" + + if stat not in metric_stats: + logger.error( + f"Statistic '{stat}' for metric '{metric_name}' is missing. " + f"Available stats: {list(metric_stats.keys())}." + ) + return "N/A" + + return format_stat_value(metric_stats[stat]) diff --git a/genai-perf/genai_perf/export_data/telemetry_data_exporter_util.py b/genai-perf/genai_perf/export_data/telemetry_data_exporter_util.py index 3b573113..e818fdd5 100644 --- a/genai-perf/genai_perf/export_data/telemetry_data_exporter_util.py +++ b/genai-perf/genai_perf/export_data/telemetry_data_exporter_util.py @@ -124,7 +124,7 @@ def _construct_telemetry_stats_subtable( value = str(int(round(value))) else: value = f"{value:,.2f}" - row.append(value) + row.append(str(value)) table.add_row(*row) diff --git a/genai-perf/tests/test_exporters/test_console_exporter.py b/genai-perf/tests/test_exporters/test_console_exporter.py index 1706b1d8..e1680c9d 100644 --- a/genai-perf/tests/test_exporters/test_console_exporter.py +++ b/genai-perf/tests/test_exporters/test_console_exporter.py @@ -115,15 +115,15 @@ def test_streaming_llm_output(self, monkeypatch, capsys) -> None: "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━┳━━━━━━┳━━━━━━┳━━━━━━┳━━━━━━━┳━━━━━━┓\n" "┃ Statistic ┃ avg ┃ min ┃ max ┃ p99 ┃ p90 ┃ p75 ┃\n" "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━╇━━━━━━╇━━━━━━╇━━━━━━╇━━━━━━━╇━━━━━━┩\n" - "│ Time to first token (ms) │ 8.00 │ 7.00 │ 9.00 │ 8.98 │ 8.80 │ 8.50 │\n" - "│ Time to second token (ms) │ 2.00 │ 1.00 │ 3.00 │ 2.98 │ 2.80 │ 2.50 │\n" - "│ Request latency (ms) │ 5.00 │ 4.00 │ 6.00 │ 5.98 │ 5.80 │ 5.50 │\n" - "│ Inter token latency (ms) │ 11.… │ 10.… │ 12.… │ 11.… │ 11.80 │ 11.… │\n" - "│ Output sequence length │ 2.00 │ 1.00 │ 3.00 │ 2.98 │ 2.80 │ 2.50 │\n" - "│ Input sequence length │ 6.00 │ 5.00 │ 7.00 │ 6.98 │ 6.80 │ 6.50 │\n" - "│ Output token throughput (per sec) │ 456… │ N/A │ N/A │ N/A │ N/A │ N/A │\n" - "│ Request throughput (per sec) │ 123… │ N/A │ N/A │ N/A │ N/A │ N/A │\n" - "│ Request count (count) │ 3.00 │ N/A │ N/A │ N/A │ N/A │ N/A │\n" + "│ Time To First Token (ms) │ 8.00 │ 7.00 │ 9.00 │ 8.98 │ 8.80 │ 8.50 │\n" + "│ Time To Second Token (ms) │ 2.00 │ 1.00 │ 3.00 │ 2.98 │ 2.80 │ 2.50 │\n" + "│ Request Latency (ms) │ 5.00 │ 4.00 │ 6.00 │ 5.98 │ 5.80 │ 5.50 │\n" + "│ Inter Token Latency (ms) │ 11.… │ 10.… │ 12.… │ 11.… │ 11.80 │ 11.… │\n" + "│ Output Sequence Length (tokens) │ 2.00 │ 1.00 │ 3.00 │ 2.98 │ 2.80 │ 2.50 │\n" + "│ Input Sequence Length (tokens) │ 6.00 │ 5.00 │ 7.00 │ 6.98 │ 6.80 │ 6.50 │\n" + "│ Output Token Throughput (per sec) │ 456… │ N/A │ N/A │ N/A │ N/A │ N/A │\n" + "│ Request Throughput (per sec) │ 123… │ N/A │ N/A │ N/A │ N/A │ N/A │\n" + "│ Request Count (count) │ 3.00 │ N/A │ N/A │ N/A │ N/A │ N/A │\n" "└───────────────────────────────────┴──────┴──────┴──────┴──────┴───────┴──────┘\n" ) @@ -170,12 +170,12 @@ def test_nonstreaming_llm_output(self, monkeypatch, capsys) -> None: "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━┳━━━━━━┳━━━━━━┳━━━━━━┳━━━━━━┓\n" "┃ Statistic ┃ avg ┃ min ┃ max ┃ p99 ┃ p90 ┃ p75 ┃\n" "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━╇━━━━━━╇━━━━━━╇━━━━━━╇━━━━━━┩\n" - "│ Request latency (ms) │ 5.00 │ 4.00 │ 6.00 │ 5.98 │ 5.80 │ 5.50 │\n" - "│ Output sequence length │ 2.00 │ 1.00 │ 3.00 │ 2.98 │ 2.80 │ 2.50 │\n" - "│ Input sequence length │ 6.00 │ 5.00 │ 7.00 │ 6.98 │ 6.80 │ 6.50 │\n" - "│ Output token throughput (per sec) │ 456.… │ N/A │ N/A │ N/A │ N/A │ N/A │\n" - "│ Request throughput (per sec) │ 123.… │ N/A │ N/A │ N/A │ N/A │ N/A │\n" - "│ Request count (count) │ 3.00 │ N/A │ N/A │ N/A │ N/A │ N/A │\n" + "│ Request Latency (ms) │ 5.00 │ 4.00 │ 6.00 │ 5.98 │ 5.80 │ 5.50 │\n" + "│ Output Sequence Length (tokens) │ 2.00 │ 1.00 │ 3.00 │ 2.98 │ 2.80 │ 2.50 │\n" + "│ Input Sequence Length (tokens) │ 6.00 │ 5.00 │ 7.00 │ 6.98 │ 6.80 │ 6.50 │\n" + "│ Output Token Throughput (per sec) │ 456.… │ N/A │ N/A │ N/A │ N/A │ N/A │\n" + "│ Request Throughput (per sec) │ 123.… │ N/A │ N/A │ N/A │ N/A │ N/A │\n" + "│ Request Count (count) │ 3.00 │ N/A │ N/A │ N/A │ N/A │ N/A │\n" "└───────────────────────────────────┴───────┴──────┴──────┴──────┴──────┴──────┘\n" ) @@ -215,9 +215,9 @@ def test_embedding_output(self, monkeypatch, capsys) -> None: "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━┳━━━━━━┳━━━━━━┳━━━━━━┳━━━━━━┓\n" "┃ Statistic ┃ avg ┃ min ┃ max ┃ p99 ┃ p90 ┃ p75 ┃\n" "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━╇━━━━━━╇━━━━━━╇━━━━━━╇━━━━━━┩\n" - "│ Request latency (ms) │ 5.00 │ 4.00 │ 6.00 │ 5.98 │ 5.80 │ 5.50 │\n" - "│ Request throughput (per sec) │ 123.00 │ N/A │ N/A │ N/A │ N/A │ N/A │\n" - "│ Request count (count) │ 3.00 │ N/A │ N/A │ N/A │ N/A │ N/A │\n" + "│ Request Latency (ms) │ 5.00 │ 4.00 │ 6.00 │ 5.98 │ 5.80 │ 5.50 │\n" + "│ Request Throughput (per sec) │ 123.00 │ N/A │ N/A │ N/A │ N/A │ N/A │\n" + "│ Request Count (count) │ 3.00 │ N/A │ N/A │ N/A │ N/A │ N/A │\n" "└──────────────────────────────┴────────┴──────┴──────┴──────┴──────┴──────┘\n" ) @@ -267,16 +267,16 @@ def test_valid_goodput(self, monkeypatch, capsys) -> None: "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━┳━━━━━━┳━━━━━━┳━━━━━━┳━━━━━━━┳━━━━━━┓\n" "┃ Statistic ┃ avg ┃ min ┃ max ┃ p99 ┃ p90 ┃ p75 ┃\n" "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━╇━━━━━━╇━━━━━━╇━━━━━━╇━━━━━━━╇━━━━━━┩\n" - "│ Time to first token (ms) │ 8.00 │ 7.00 │ 9.00 │ 8.98 │ 8.80 │ 8.50 │\n" - "│ Time to second token (ms) │ 2.00 │ 1.00 │ 3.00 │ 2.98 │ 2.80 │ 2.50 │\n" - "│ Request latency (ms) │ 5.00 │ 4.00 │ 6.00 │ 5.98 │ 5.80 │ 5.50 │\n" - "│ Inter token latency (ms) │ 11.… │ 10.… │ 12.… │ 11.… │ 11.80 │ 11.… │\n" - "│ Output sequence length │ 2.00 │ 1.00 │ 3.00 │ 2.98 │ 2.80 │ 2.50 │\n" - "│ Input sequence length │ 6.00 │ 5.00 │ 7.00 │ 6.98 │ 6.80 │ 6.50 │\n" - "│ Output token throughput (per sec) │ 456… │ N/A │ N/A │ N/A │ N/A │ N/A │\n" - "│ Request throughput (per sec) │ 123… │ N/A │ N/A │ N/A │ N/A │ N/A │\n" - "│ Request goodput (per sec) │ 100… │ N/A │ N/A │ N/A │ N/A │ N/A │\n" - "│ Request count (count) │ 3.00 │ N/A │ N/A │ N/A │ N/A │ N/A │\n" + "│ Time To First Token (ms) │ 8.00 │ 7.00 │ 9.00 │ 8.98 │ 8.80 │ 8.50 │\n" + "│ Time To Second Token (ms) │ 2.00 │ 1.00 │ 3.00 │ 2.98 │ 2.80 │ 2.50 │\n" + "│ Request Latency (ms) │ 5.00 │ 4.00 │ 6.00 │ 5.98 │ 5.80 │ 5.50 │\n" + "│ Inter Token Latency (ms) │ 11.… │ 10.… │ 12.… │ 11.… │ 11.80 │ 11.… │\n" + "│ Output Sequence Length (tokens) │ 2.00 │ 1.00 │ 3.00 │ 2.98 │ 2.80 │ 2.50 │\n" + "│ Input Sequence Length (tokens) │ 6.00 │ 5.00 │ 7.00 │ 6.98 │ 6.80 │ 6.50 │\n" + "│ Output Token Throughput (per sec) │ 456… │ N/A │ N/A │ N/A │ N/A │ N/A │\n" + "│ Request Throughput (per sec) │ 123… │ N/A │ N/A │ N/A │ N/A │ N/A │\n" + "│ Request Goodput (per sec) │ 100… │ N/A │ N/A │ N/A │ N/A │ N/A │\n" + "│ Request Count (count) │ 3.00 │ N/A │ N/A │ N/A │ N/A │ N/A │\n" "└───────────────────────────────────┴──────┴──────┴──────┴──────┴───────┴──────┘\n" ) returned_data = capsys.readouterr().out @@ -326,16 +326,16 @@ def test_invalid_goodput_output(self, monkeypatch, capsys) -> None: "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━┳━━━━━━┳━━━━━━┳━━━━━━┳━━━━━━━┳━━━━━━┓\n" "┃ Statistic ┃ avg ┃ min ┃ max ┃ p99 ┃ p90 ┃ p75 ┃\n" "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━╇━━━━━━╇━━━━━━╇━━━━━━╇━━━━━━━╇━━━━━━┩\n" - "│ Time to first token (ms) │ 8.00 │ 7.00 │ 9.00 │ 8.98 │ 8.80 │ 8.50 │\n" - "│ Time to second token (ms) │ 2.00 │ 1.00 │ 3.00 │ 2.98 │ 2.80 │ 2.50 │\n" - "│ Request latency (ms) │ 5.00 │ 4.00 │ 6.00 │ 5.98 │ 5.80 │ 5.50 │\n" - "│ Inter token latency (ms) │ 11.… │ 10.… │ 12.… │ 11.… │ 11.80 │ 11.… │\n" - "│ Output sequence length │ 2.00 │ 1.00 │ 3.00 │ 2.98 │ 2.80 │ 2.50 │\n" - "│ Input sequence length │ 6.00 │ 5.00 │ 7.00 │ 6.98 │ 6.80 │ 6.50 │\n" - "│ Output token throughput (per sec) │ 456… │ N/A │ N/A │ N/A │ N/A │ N/A │\n" - "│ Request throughput (per sec) │ 123… │ N/A │ N/A │ N/A │ N/A │ N/A │\n" - "│ Request goodput (per sec) │ -1.… │ N/A │ N/A │ N/A │ N/A │ N/A │\n" - "│ Request count (count) │ 3.00 │ N/A │ N/A │ N/A │ N/A │ N/A │\n" + "│ Time To First Token (ms) │ 8.00 │ 7.00 │ 9.00 │ 8.98 │ 8.80 │ 8.50 │\n" + "│ Time To Second Token (ms) │ 2.00 │ 1.00 │ 3.00 │ 2.98 │ 2.80 │ 2.50 │\n" + "│ Request Latency (ms) │ 5.00 │ 4.00 │ 6.00 │ 5.98 │ 5.80 │ 5.50 │\n" + "│ Inter Token Latency (ms) │ 11.… │ 10.… │ 12.… │ 11.… │ 11.80 │ 11.… │\n" + "│ Output Sequence Length (tokens) │ 2.00 │ 1.00 │ 3.00 │ 2.98 │ 2.80 │ 2.50 │\n" + "│ Input Sequence Length (tokens) │ 6.00 │ 5.00 │ 7.00 │ 6.98 │ 6.80 │ 6.50 │\n" + "│ Output Token Throughput (per sec) │ 456… │ N/A │ N/A │ N/A │ N/A │ N/A │\n" + "│ Request Throughput (per sec) │ 123… │ N/A │ N/A │ N/A │ N/A │ N/A │\n" + "│ Request Goodput (per sec) │ -1.… │ N/A │ N/A │ N/A │ N/A │ N/A │\n" + "│ Request Count (count) │ 3.00 │ N/A │ N/A │ N/A │ N/A │ N/A │\n" "└───────────────────────────────────┴──────┴──────┴──────┴──────┴───────┴──────┘\n" ) returned_data = capsys.readouterr().out @@ -443,15 +443,15 @@ def test_valid_telemetry_verbose(self, monkeypatch, capsys) -> None: "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━┳━━━━━━┳━━━━━━┳━━━━━━┳━━━━━━━┳━━━━━━┓\n" "┃ Statistic ┃ avg ┃ min ┃ max ┃ p99 ┃ p90 ┃ p75 ┃\n" "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━╇━━━━━━╇━━━━━━╇━━━━━━╇━━━━━━━╇━━━━━━┩\n" - "│ Time to first token (ms) │ 8.00 │ 7.00 │ 9.00 │ 8.98 │ 8.80 │ 8.50 │\n" - "│ Time to second token (ms) │ 2.00 │ 1.00 │ 3.00 │ 2.98 │ 2.80 │ 2.50 │\n" - "│ Request latency (ms) │ 5.00 │ 4.00 │ 6.00 │ 5.98 │ 5.80 │ 5.50 │\n" - "│ Inter token latency (ms) │ 11.… │ 10.… │ 12.… │ 11.… │ 11.80 │ 11.… │\n" - "│ Output sequence length │ 2.00 │ 1.00 │ 3.00 │ 2.98 │ 2.80 │ 2.50 │\n" - "│ Input sequence length │ 6.00 │ 5.00 │ 7.00 │ 6.98 │ 6.80 │ 6.50 │\n" - "│ Output token throughput (per sec) │ 456… │ N/A │ N/A │ N/A │ N/A │ N/A │\n" - "│ Request throughput (per sec) │ 123… │ N/A │ N/A │ N/A │ N/A │ N/A │\n" - "│ Request count (count) │ 3.00 │ N/A │ N/A │ N/A │ N/A │ N/A │\n" + "│ Time To First Token (ms) │ 8.00 │ 7.00 │ 9.00 │ 8.98 │ 8.80 │ 8.50 │\n" + "│ Time To Second Token (ms) │ 2.00 │ 1.00 │ 3.00 │ 2.98 │ 2.80 │ 2.50 │\n" + "│ Request Latency (ms) │ 5.00 │ 4.00 │ 6.00 │ 5.98 │ 5.80 │ 5.50 │\n" + "│ Inter Token Latency (ms) │ 11.… │ 10.… │ 12.… │ 11.… │ 11.80 │ 11.… │\n" + "│ Output Sequence Length (tokens) │ 2.00 │ 1.00 │ 3.00 │ 2.98 │ 2.80 │ 2.50 │\n" + "│ Input Sequence Length (tokens) │ 6.00 │ 5.00 │ 7.00 │ 6.98 │ 6.80 │ 6.50 │\n" + "│ Output Token Throughput (per sec) │ 456… │ N/A │ N/A │ N/A │ N/A │ N/A │\n" + "│ Request Throughput (per sec) │ 123… │ N/A │ N/A │ N/A │ N/A │ N/A │\n" + "│ Request Count (count) │ 3.00 │ N/A │ N/A │ N/A │ N/A │ N/A │\n" "└───────────────────────────────────┴──────┴──────┴──────┴──────┴───────┴──────┘\n" " NVIDIA GenAI-Perf | Power Metrics \n" "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n" @@ -554,19 +554,19 @@ def test_missing_data(self, monkeypatch, capsys) -> None: "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━┳━━━━━━┳━━━━━━┳━━━━━━┳━━━━━━┓\n" "┃ Statistic ┃ avg ┃ min ┃ max ┃ p99 ┃ p90 ┃ p75 ┃\n" "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━╇━━━━━━╇━━━━━━╇━━━━━━╇━━━━━━┩\n" - "│ Request latency (ms) │ N/A │ 4.00 │ 6.00 │ 5.98 │ 5.80 │ 5.50 │\n" - "│ Output sequence length │ 2.00 │ 1.00 │ N/A │ 2.98 │ 2.80 │ 2.50 │\n" - "│ Input sequence length │ N/A │ N/A │ N/A │ N/A │ N/A │ N/A │\n" - "│ Output token throughput (per sec) │ 456.… │ N/A │ N/A │ N/A │ N/A │ N/A │\n" - "│ Request throughput (per sec) │ 123.… │ N/A │ N/A │ N/A │ N/A │ N/A │\n" - "│ Request count (count) │ 3.00 │ N/A │ N/A │ N/A │ N/A │ N/A │\n" + "│ Request Latency (ms) │ N/A │ 4.00 │ 6.00 │ 5.98 │ 5.80 │ 5.50 │\n" + "│ Output Sequence Length (tokens) │ 2.00 │ 1.00 │ N/A │ 2.98 │ 2.80 │ 2.50 │\n" + "│ Input Sequence Length (tokens) │ N/A │ N/A │ N/A │ N/A │ N/A │ N/A │\n" + "│ Output Token Throughput (per sec) │ 456.… │ N/A │ N/A │ N/A │ N/A │ N/A │\n" + "│ Request Throughput (per sec) │ 123.… │ N/A │ N/A │ N/A │ N/A │ N/A │\n" + "│ Request Count (count) │ 3.00 │ N/A │ N/A │ N/A │ N/A │ N/A │\n" "└───────────────────────────────────┴───────┴──────┴──────┴──────┴──────┴──────┘\n" ) returned_data = capsys.readouterr().out assert returned_data == expected_content - @patch("genai_perf.export_data.console_exporter.logger") + @patch("genai_perf.export_data.exporter_utils.logger") def test_missing_statistics(self, mock_logger, exporter_config, capsys): """ Test behavior when specific statistics are missing from the stats dictionary. @@ -595,18 +595,18 @@ def test_missing_statistics(self, mock_logger, exporter_config, capsys): "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━┳━━━━━━┳━━━━━━┳━━━━━━┳━━━━━━┓\n" "┃ Statistic ┃ avg ┃ min ┃ max ┃ p99 ┃ p90 ┃ p75 ┃\n" "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━╇━━━━━━╇━━━━━━╇━━━━━━╇━━━━━━┩\n" - "│ Request latency (ms) │ N/A │ 4.00 │ 6.00 │ 5.98 │ 5.80 │ 5.50 │\n" - "│ Output sequence length │ 2.00 │ 1.00 │ N/A │ 2.98 │ 2.80 │ 2.50 │\n" - "│ Input sequence length │ 6.00 │ 5.00 │ 7.00 │ 6.98 │ 6.80 │ 6.50 │\n" - "│ Output token throughput (per sec) │ 456.… │ N/A │ N/A │ N/A │ N/A │ N/A │\n" - "│ Request throughput (per sec) │ 123.… │ N/A │ N/A │ N/A │ N/A │ N/A │\n" - "│ Request count (count) │ 3.00 │ N/A │ N/A │ N/A │ N/A │ N/A │\n" + "│ Request Latency (ms) │ N/A │ 4.00 │ 6.00 │ 5.98 │ 5.80 │ 5.50 │\n" + "│ Output Sequence Length (tokens) │ 2.00 │ 1.00 │ N/A │ 2.98 │ 2.80 │ 2.50 │\n" + "│ Input Sequence Length (tokens) │ 6.00 │ 5.00 │ 7.00 │ 6.98 │ 6.80 │ 6.50 │\n" + "│ Output Token Throughput (per sec) │ 456.… │ N/A │ N/A │ N/A │ N/A │ N/A │\n" + "│ Request Throughput (per sec) │ 123.… │ N/A │ N/A │ N/A │ N/A │ N/A │\n" + "│ Request Count (count) │ 3.00 │ N/A │ N/A │ N/A │ N/A │ N/A │\n" "└───────────────────────────────────┴───────┴──────┴──────┴──────┴──────┴──────┘\n" ) assert returned_data == expected_output - @patch("genai_perf.export_data.console_exporter.logger") + @patch("genai_perf.export_data.exporter_utils.logger") def test_invalid_stat_structure(self, mock_logger, exporter_config, capsys): """ Test behavior when the stats structure is invalid. @@ -630,12 +630,12 @@ def test_invalid_stat_structure(self, mock_logger, exporter_config, capsys): "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━┳━━━━━━┳━━━━━━┳━━━━━━┳━━━━━━┓\n" "┃ Statistic ┃ avg ┃ min ┃ max ┃ p99 ┃ p90 ┃ p75 ┃\n" "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━╇━━━━━━╇━━━━━━╇━━━━━━╇━━━━━━┩\n" - "│ Request latency (ms) │ N/A │ N/A │ N/A │ N/A │ N/A │ N/A │\n" - "│ Output sequence length │ 2.00 │ 1.00 │ 3.00 │ 2.98 │ 2.80 │ 2.50 │\n" - "│ Input sequence length │ 6.00 │ 5.00 │ 7.00 │ 6.98 │ 6.80 │ 6.50 │\n" - "│ Output token throughput (per sec) │ 456.… │ N/A │ N/A │ N/A │ N/A │ N/A │\n" - "│ Request throughput (per sec) │ 123.… │ N/A │ N/A │ N/A │ N/A │ N/A │\n" - "│ Request count (count) │ 3.00 │ N/A │ N/A │ N/A │ N/A │ N/A │\n" + "│ Request Latency (ms) │ N/A │ N/A │ N/A │ N/A │ N/A │ N/A │\n" + "│ Output Sequence Length (tokens) │ 2.00 │ 1.00 │ 3.00 │ 2.98 │ 2.80 │ 2.50 │\n" + "│ Input Sequence Length (tokens) │ 6.00 │ 5.00 │ 7.00 │ 6.98 │ 6.80 │ 6.50 │\n" + "│ Output Token Throughput (per sec) │ 456.… │ N/A │ N/A │ N/A │ N/A │ N/A │\n" + "│ Request Throughput (per sec) │ 123.… │ N/A │ N/A │ N/A │ N/A │ N/A │\n" + "│ Request Count (count) │ 3.00 │ N/A │ N/A │ N/A │ N/A │ N/A │\n" "└───────────────────────────────────┴───────┴──────┴──────┴──────┴──────┴──────┘\n" ) diff --git a/genai-perf/tests/test_exporters/test_csv_exporter.py b/genai-perf/tests/test_exporters/test_csv_exporter.py index 857d3793..752cc2d1 100644 --- a/genai-perf/tests/test_exporters/test_csv_exporter.py +++ b/genai-perf/tests/test_exporters/test_csv_exporter.py @@ -401,7 +401,7 @@ def test_triton_telemetry_output( assert returned_data == expected_content - @patch("genai_perf.export_data.csv_exporter.logger") + @patch("genai_perf.export_data.exporter_utils.logger") def test_missing_data( self, mock_logger, diff --git a/genai-perf/tests/test_exporters/test_json_exporter.py b/genai-perf/tests/test_exporters/test_json_exporter.py index 02fe0b58..62d40dcb 100644 --- a/genai-perf/tests/test_exporters/test_json_exporter.py +++ b/genai-perf/tests/test_exporters/test_json_exporter.py @@ -275,7 +275,7 @@ def mock_read_write(self, monkeypatch: pytest.MonkeyPatch) -> List[Tuple[str, st def custom_open(filename, *args, **kwargs): def write(self: Any, content: str) -> int: - print(f"Writing to {filename}") + print(f"Writing to {filename}") # To help with debugging failures written_data.append((str(filename), content)) return len(content)