from __future__ import annotations from .collectors import collect_session_index, collect_usage, collect_usage_cost_cache from .config import HISTORY_DAYS, HISTORY_HOURS, TIMEZONE from .models import COST_FIELDS, SessionIndexReport, UsageCostCacheReport, UsageReport from .utils import as_float, as_int, metric_line, sanitize_label, today_in_tz def append_current_day_metrics(lines: list[str], usage: UsageReport, today: str, tz_name: str) -> None: lines.extend([ "# HELP openclaw_daily_total_tokens Total OpenClaw tokens recorded today.", "# TYPE openclaw_daily_total_tokens gauge", metric_line("openclaw_daily_total_tokens", usage.today.total, date=today, tz=tz_name), "# HELP openclaw_daily_input_tokens OpenClaw input tokens recorded today.", "# TYPE openclaw_daily_input_tokens gauge", metric_line("openclaw_daily_input_tokens", usage.today.input, date=today, tz=tz_name), "# HELP openclaw_daily_output_tokens OpenClaw output tokens recorded today.", "# TYPE openclaw_daily_output_tokens gauge", metric_line("openclaw_daily_output_tokens", usage.today.output, date=today, tz=tz_name), "# HELP openclaw_daily_cache_read_tokens OpenClaw cache-read tokens recorded today.", "# TYPE openclaw_daily_cache_read_tokens gauge", metric_line("openclaw_daily_cache_read_tokens", usage.today.cache_read, date=today, tz=tz_name), "# HELP openclaw_daily_cache_write_tokens OpenClaw cache-write tokens recorded today.", "# TYPE openclaw_daily_cache_write_tokens gauge", metric_line("openclaw_daily_cache_write_tokens", usage.today.cache_write, date=today, tz=tz_name), "# HELP openclaw_daily_reasoning_tokens OpenClaw reasoning tokens recorded today.", "# TYPE openclaw_daily_reasoning_tokens gauge", metric_line("openclaw_daily_reasoning_tokens", usage.today.reasoning, date=today, tz=tz_name), "# HELP openclaw_daily_usage_entries OpenClaw usage entries recorded today.", "# TYPE openclaw_daily_usage_entries gauge", metric_line("openclaw_daily_usage_entries", usage.today.entries, date=today, tz=tz_name), "# HELP openclaw_daily_cost_input OpenClaw input cost recorded today.", "# TYPE openclaw_daily_cost_input gauge", metric_line("openclaw_daily_cost_input", f"{usage.today.cost_input:.12g}", date=today, tz=tz_name), "# HELP openclaw_daily_cost_output OpenClaw output cost recorded today.", "# TYPE openclaw_daily_cost_output gauge", metric_line("openclaw_daily_cost_output", f"{usage.today.cost_output:.12g}", date=today, tz=tz_name), "# HELP openclaw_daily_cost_cache_read OpenClaw cache-read cost recorded today.", "# TYPE openclaw_daily_cost_cache_read gauge", metric_line("openclaw_daily_cost_cache_read", f"{usage.today.cost_cache_read:.12g}", date=today, tz=tz_name), "# HELP openclaw_daily_cost_cache_write OpenClaw cache-write cost recorded today.", "# TYPE openclaw_daily_cost_cache_write gauge", metric_line("openclaw_daily_cost_cache_write", f"{usage.today.cost_cache_write:.12g}", date=today, tz=tz_name), "# HELP openclaw_daily_cost_total OpenClaw total cost recorded today.", "# TYPE openclaw_daily_cost_total gauge", metric_line("openclaw_daily_cost_total", f"{usage.today.cost_total:.12g}", date=today, tz=tz_name), "# HELP openclaw_daily_message_records Total message records seen today.", "# TYPE openclaw_daily_message_records gauge", metric_line("openclaw_daily_message_records", usage.message_records_today, date=today, tz=tz_name), "# HELP openclaw_daily_non_message_records Total non-message records seen today.", "# TYPE openclaw_daily_non_message_records gauge", metric_line("openclaw_daily_non_message_records", usage.non_message_records_today, date=today, tz=tz_name), "# HELP openclaw_daily_unique_sessions Number of unique session ids seen today.", "# TYPE openclaw_daily_unique_sessions gauge", metric_line("openclaw_daily_unique_sessions", len(usage.unique_session_ids_today), date=today, tz=tz_name), "# HELP openclaw_daily_unique_models Number of unique models seen today.", "# TYPE openclaw_daily_unique_models gauge", metric_line("openclaw_daily_unique_models", len(usage.unique_models_today), date=today, tz=tz_name), "# HELP openclaw_daily_unique_providers Number of unique providers seen today.", "# TYPE openclaw_daily_unique_providers gauge", metric_line("openclaw_daily_unique_providers", len(usage.unique_providers_today), date=today, tz=tz_name), ]) def append_counter_metrics(lines: list[str], usage: UsageReport) -> None: lines.extend([ "# HELP openclaw_total_tokens_total All-time OpenClaw tokens from session usage logs.", "# TYPE openclaw_total_tokens_total counter", metric_line("openclaw_total_tokens_total", usage.all_time.total), "# HELP openclaw_input_tokens_total All-time OpenClaw input tokens from session usage logs.", "# TYPE openclaw_input_tokens_total counter", metric_line("openclaw_input_tokens_total", usage.all_time.input), "# HELP openclaw_output_tokens_total All-time OpenClaw output tokens from session usage logs.", "# TYPE openclaw_output_tokens_total counter", metric_line("openclaw_output_tokens_total", usage.all_time.output), "# HELP openclaw_cache_read_tokens_total All-time OpenClaw cache-read tokens from session usage logs.", "# TYPE openclaw_cache_read_tokens_total counter", metric_line("openclaw_cache_read_tokens_total", usage.all_time.cache_read), "# HELP openclaw_cache_write_tokens_total All-time OpenClaw cache-write tokens from session usage logs.", "# TYPE openclaw_cache_write_tokens_total counter", metric_line("openclaw_cache_write_tokens_total", usage.all_time.cache_write), "# HELP openclaw_reasoning_tokens_total All-time OpenClaw reasoning tokens from session usage logs.", "# TYPE openclaw_reasoning_tokens_total counter", metric_line("openclaw_reasoning_tokens_total", usage.all_time.reasoning), "# HELP openclaw_usage_entries_total All-time OpenClaw usage entries from session usage logs.", "# TYPE openclaw_usage_entries_total counter", metric_line("openclaw_usage_entries_total", usage.all_time.entries), "# HELP openclaw_cost_input_usd_total All-time OpenClaw input cost in USD from session usage logs.", "# TYPE openclaw_cost_input_usd_total counter", metric_line("openclaw_cost_input_usd_total", f"{usage.all_time.cost_input:.12g}"), "# HELP openclaw_cost_output_usd_total All-time OpenClaw output cost in USD from session usage logs.", "# TYPE openclaw_cost_output_usd_total counter", metric_line("openclaw_cost_output_usd_total", f"{usage.all_time.cost_output:.12g}"), "# HELP openclaw_cost_cache_read_usd_total All-time OpenClaw cache-read cost in USD from session usage logs.", "# TYPE openclaw_cost_cache_read_usd_total counter", metric_line("openclaw_cost_cache_read_usd_total", f"{usage.all_time.cost_cache_read:.12g}"), "# HELP openclaw_cost_cache_write_usd_total All-time OpenClaw cache-write cost in USD from session usage logs.", "# TYPE openclaw_cost_cache_write_usd_total counter", metric_line("openclaw_cost_cache_write_usd_total", f"{usage.all_time.cost_cache_write:.12g}"), "# HELP openclaw_cost_total_usd_total All-time OpenClaw total cost in USD from session usage logs.", "# TYPE openclaw_cost_total_usd_total counter", metric_line("openclaw_cost_total_usd_total", f"{usage.all_time.cost_total:.12g}"), "# HELP openclaw_message_records_total All-time message records seen in session logs.", "# TYPE openclaw_message_records_total counter", metric_line("openclaw_message_records_total", usage.message_records_all_time), "# HELP openclaw_non_message_records_total All-time non-message records seen in session logs.", "# TYPE openclaw_non_message_records_total counter", metric_line("openclaw_non_message_records_total", usage.non_message_records_all_time), ]) def append_bucket_metrics(lines: list[str], usage: UsageReport, tz_name: str) -> None: lines.extend([ "# HELP openclaw_day_total_tokens Daily token totals for recent retained days.", "# TYPE openclaw_day_total_tokens gauge", "# HELP openclaw_day_cost_total Daily cost totals for recent retained days.", "# TYPE openclaw_day_cost_total gauge", "# HELP openclaw_day_usage_entries Daily usage-entry totals for recent retained days.", "# TYPE openclaw_day_usage_entries gauge", ]) for bucket_date, totals in sorted(usage.day_buckets.items()): labels = {"date": bucket_date, "tz": tz_name} lines.append(metric_line("openclaw_day_total_tokens", totals.total, **labels)) lines.append(metric_line("openclaw_day_cost_total", f"{totals.cost_total:.12g}", **labels)) lines.append(metric_line("openclaw_day_usage_entries", totals.entries, **labels)) lines.extend([ "# HELP openclaw_hour_total_tokens Hourly token totals for recent retained hours.", "# TYPE openclaw_hour_total_tokens gauge", "# HELP openclaw_hour_cost_total Hourly cost totals for recent retained hours.", "# TYPE openclaw_hour_cost_total gauge", "# HELP openclaw_hour_usage_entries Hourly usage-entry totals for recent retained hours.", "# TYPE openclaw_hour_usage_entries gauge", ]) for bucket_hour, totals in sorted(usage.hour_buckets.items()): labels = {"hour": bucket_hour, "tz": tz_name} lines.append(metric_line("openclaw_hour_total_tokens", totals.total, **labels)) lines.append(metric_line("openclaw_hour_cost_total", f"{totals.cost_total:.12g}", **labels)) lines.append(metric_line("openclaw_hour_usage_entries", totals.entries, **labels)) def append_breakdown_metrics(lines: list[str], usage: UsageReport, today: str, tz_name: str) -> None: lines.extend([ "# HELP openclaw_daily_messages_total Daily message count by role.", "# TYPE openclaw_daily_messages_total gauge", ]) for role, count in sorted(usage.role_counts_today.items()): lines.append(metric_line("openclaw_daily_messages_total", count, date=today, tz=tz_name, role=role)) lines.extend([ "# HELP openclaw_daily_stop_reasons_total Daily assistant stop reasons by model/provider.", "# TYPE openclaw_daily_stop_reasons_total gauge", ]) for (stop_reason, model, provider), count in sorted(usage.stop_reason_counts_today.items()): lines.append(metric_line("openclaw_daily_stop_reasons_total", count, date=today, tz=tz_name, stop_reason=stop_reason, model=model, provider=provider)) lines.extend([ "# HELP openclaw_daily_tool_calls_total Daily tool call count by tool name.", "# TYPE openclaw_daily_tool_calls_total gauge", ]) for tool_name, count in sorted(usage.tool_call_counts_today.items()): lines.append(metric_line("openclaw_daily_tool_calls_total", count, date=today, tz=tz_name, tool_name=tool_name)) lines.extend([ "# HELP openclaw_daily_errors_total Daily message error count by error type.", "# TYPE openclaw_daily_errors_total gauge", ]) for error_type, count in sorted(usage.error_type_counts_today.items()): lines.append(metric_line("openclaw_daily_errors_total", count, date=today, tz=tz_name, error_type=error_type)) lines.extend([ "# HELP openclaw_daily_model_total_tokens Daily total tokens by model/provider.", "# TYPE openclaw_daily_model_total_tokens gauge", ]) for (model, provider), count in sorted(usage.tokens_by_model_today.items()): lines.append(metric_line("openclaw_daily_model_total_tokens", count, date=today, tz=tz_name, model=model, provider=provider)) lines.extend([ "# HELP openclaw_daily_model_input_tokens Daily input tokens by model/provider.", "# TYPE openclaw_daily_model_input_tokens gauge", ]) for (model, provider), count in sorted(usage.input_tokens_by_model_today.items()): lines.append(metric_line("openclaw_daily_model_input_tokens", count, date=today, tz=tz_name, model=model, provider=provider)) lines.extend([ "# HELP openclaw_daily_model_output_tokens Daily output tokens by model/provider.", "# TYPE openclaw_daily_model_output_tokens gauge", ]) for (model, provider), count in sorted(usage.output_tokens_by_model_today.items()): lines.append(metric_line("openclaw_daily_model_output_tokens", count, date=today, tz=tz_name, model=model, provider=provider)) lines.extend([ "# HELP openclaw_daily_model_reasoning_tokens Daily reasoning tokens by model/provider.", "# TYPE openclaw_daily_model_reasoning_tokens gauge", ]) for (model, provider), count in sorted(usage.reasoning_tokens_by_model_today.items()): lines.append(metric_line("openclaw_daily_model_reasoning_tokens", count, date=today, tz=tz_name, model=model, provider=provider)) lines.extend([ "# HELP openclaw_model_total_tokens_total All-time total tokens by model/provider.", "# TYPE openclaw_model_total_tokens_total counter", ]) for (model, provider), count in sorted(usage.tokens_by_model_all_time.items()): lines.append(metric_line("openclaw_model_total_tokens_total", count, model=model, provider=provider)) lines.extend([ "# HELP openclaw_model_input_tokens_total All-time input tokens by model/provider.", "# TYPE openclaw_model_input_tokens_total counter", ]) for (model, provider), count in sorted(usage.input_tokens_by_model_all_time.items()): lines.append(metric_line("openclaw_model_input_tokens_total", count, model=model, provider=provider)) lines.extend([ "# HELP openclaw_model_output_tokens_total All-time output tokens by model/provider.", "# TYPE openclaw_model_output_tokens_total counter", ]) for (model, provider), count in sorted(usage.output_tokens_by_model_all_time.items()): lines.append(metric_line("openclaw_model_output_tokens_total", count, model=model, provider=provider)) lines.extend([ "# HELP openclaw_daily_session_total_tokens Daily total tokens by session id.", "# TYPE openclaw_daily_session_total_tokens gauge", ]) for session_id, count in sorted(usage.session_tokens_today.items()): lines.append(metric_line("openclaw_daily_session_total_tokens", count, date=today, tz=tz_name, session_id=session_id)) lines.extend([ "# HELP openclaw_daily_session_input_tokens Daily input tokens by session id.", "# TYPE openclaw_daily_session_input_tokens gauge", ]) for session_id, count in sorted(usage.session_input_tokens_today.items()): lines.append(metric_line("openclaw_daily_session_input_tokens", count, date=today, tz=tz_name, session_id=session_id)) lines.extend([ "# HELP openclaw_daily_session_output_tokens Daily output tokens by session id.", "# TYPE openclaw_daily_session_output_tokens gauge", ]) for session_id, count in sorted(usage.session_output_tokens_today.items()): lines.append(metric_line("openclaw_daily_session_output_tokens", count, date=today, tz=tz_name, session_id=session_id)) lines.extend([ "# HELP openclaw_session_total_tokens_total All-time total tokens by session id.", "# TYPE openclaw_session_total_tokens_total counter", ]) for session_id, count in sorted(usage.session_tokens_all_time.items()): lines.append(metric_line("openclaw_session_total_tokens_total", count, session_id=session_id)) lines.extend([ "# HELP openclaw_daily_session_messages_total Daily message count by session id and role.", "# TYPE openclaw_daily_session_messages_total gauge", ]) for (session_id, role), count in sorted(usage.session_message_counts_today.items()): lines.append(metric_line("openclaw_daily_session_messages_total", count, date=today, tz=tz_name, session_id=session_id, role=role)) lines.extend([ "# HELP openclaw_daily_session_errors_total Daily error count by session id.", "# TYPE openclaw_daily_session_errors_total gauge", ]) for session_id, count in sorted(usage.session_error_counts_today.items()): lines.append(metric_line("openclaw_daily_session_errors_total", count, date=today, tz=tz_name, session_id=session_id)) lines.extend([ "# HELP openclaw_daily_session_tool_calls_total Daily tool call count by session id and tool name.", "# TYPE openclaw_daily_session_tool_calls_total gauge", ]) for (session_id, tool_name), count in sorted(usage.session_tool_call_counts_today.items()): lines.append(metric_line("openclaw_daily_session_tool_calls_total", count, date=today, tz=tz_name, session_id=session_id, tool_name=tool_name)) def append_exporter_metrics(lines: list[str], usage: UsageReport, session_index: SessionIndexReport, usage_cost_cache: UsageCostCacheReport, today: str, tz_name: str) -> None: lines.extend([ "# HELP openclaw_exporter_files_scanned Session files scanned for metrics generation.", "# TYPE openclaw_exporter_files_scanned gauge", metric_line("openclaw_exporter_files_scanned", usage.files_scanned, date=today, tz=tz_name), "# HELP openclaw_exporter_lines_scanned Session lines scanned for metrics generation.", "# TYPE openclaw_exporter_lines_scanned gauge", metric_line("openclaw_exporter_lines_scanned", usage.lines_scanned, date=today, tz=tz_name), "# HELP openclaw_exporter_parse_errors Parse or read errors while generating metrics.", "# TYPE openclaw_exporter_parse_errors gauge", metric_line("openclaw_exporter_parse_errors", usage.parse_errors, date=today, tz=tz_name), "# HELP openclaw_exporter_negative_cost_entries_skipped Usage entries whose negative cost values were ignored as invalid.", "# TYPE openclaw_exporter_negative_cost_entries_skipped gauge", metric_line("openclaw_exporter_negative_cost_entries_skipped", usage.negative_cost_entries_skipped), "# HELP openclaw_exporter_history_days_retained Retained day-bucket history window.", "# TYPE openclaw_exporter_history_days_retained gauge", metric_line("openclaw_exporter_history_days_retained", HISTORY_DAYS), "# HELP openclaw_exporter_history_hours_retained Retained hour-bucket history window.", "# TYPE openclaw_exporter_history_hours_retained gauge", metric_line("openclaw_exporter_history_hours_retained", HISTORY_HOURS), "# HELP openclaw_sessions_visible_count Session entries currently present in sessions.json output.", "# TYPE openclaw_sessions_visible_count gauge", metric_line("openclaw_sessions_visible_count", session_index.visible_count), "# HELP openclaw_sessions_total_count Total stored sessions reported by sessions.json.", "# TYPE openclaw_sessions_total_count gauge", metric_line("openclaw_sessions_total_count", session_index.total_count), "# HELP openclaw_sessions_has_more Whether sessions.json was truncated when written.", "# TYPE openclaw_sessions_has_more gauge", metric_line("openclaw_sessions_has_more", int(session_index.has_more)), "# HELP openclaw_usage_cost_cache_files_tracked Number of files tracked in .usage-cost-cache.json.", "# TYPE openclaw_usage_cost_cache_files_tracked gauge", metric_line("openclaw_usage_cost_cache_files_tracked", usage_cost_cache.files_tracked), ]) lines.extend([ "# HELP openclaw_exporter_negative_cost_fields_skipped Invalid negative cost fields ignored by field name.", "# TYPE openclaw_exporter_negative_cost_fields_skipped gauge", ]) for cost_field in COST_FIELDS: if usage.negative_cost_fields_skipped.get(cost_field): lines.append(metric_line("openclaw_exporter_negative_cost_fields_skipped", usage.negative_cost_fields_skipped[cost_field], cost_field=cost_field)) lines.extend([ "# HELP openclaw_exporter_negative_cost_amount_skipped Total absolute invalid negative cost amount ignored by field name.", "# TYPE openclaw_exporter_negative_cost_amount_skipped gauge", ]) for cost_field in COST_FIELDS: if usage.negative_cost_amounts_skipped.get(cost_field): lines.append(metric_line("openclaw_exporter_negative_cost_amount_skipped", f"{usage.negative_cost_amounts_skipped[cost_field]:.12g}", cost_field=cost_field)) if usage_cost_cache.version is not None: lines.extend([ "# HELP openclaw_usage_cost_cache_version Version field from .usage-cost-cache.json.", "# TYPE openclaw_usage_cost_cache_version gauge", metric_line("openclaw_usage_cost_cache_version", usage_cost_cache.version), ]) if usage_cost_cache.updated_at_ms is not None: lines.extend([ "# HELP openclaw_usage_cost_cache_updated_timestamp_seconds Last update time of .usage-cost-cache.json.", "# TYPE openclaw_usage_cost_cache_updated_timestamp_seconds gauge", metric_line("openclaw_usage_cost_cache_updated_timestamp_seconds", f"{usage_cost_cache.updated_at_ms / 1000:.3f}"), ]) def append_session_inventory_metrics(lines: list[str], session_index: SessionIndexReport) -> None: lines.extend([ "# HELP openclaw_session_info Non-sensitive session metadata from sessions.json. Session key is intentionally omitted.", "# TYPE openclaw_session_info gauge", "# HELP openclaw_session_age_seconds Current age of a stored session from sessions.json.", "# TYPE openclaw_session_age_seconds gauge", "# HELP openclaw_session_updated_timestamp_seconds Last update time of a stored session from sessions.json.", "# TYPE openclaw_session_updated_timestamp_seconds gauge", "# HELP openclaw_session_started_timestamp_seconds Session start time from sessions.json.", "# TYPE openclaw_session_started_timestamp_seconds gauge", "# HELP openclaw_session_last_interaction_timestamp_seconds Last interaction time from sessions.json.", "# TYPE openclaw_session_last_interaction_timestamp_seconds gauge", "# HELP openclaw_session_total_tokens Stored session totalTokens from sessions.json.", "# TYPE openclaw_session_total_tokens gauge", "# HELP openclaw_session_context_tokens Stored session contextTokens from sessions.json.", "# TYPE openclaw_session_context_tokens gauge", "# HELP openclaw_session_runtime_seconds Stored runtimeMs from sessions.json.", "# TYPE openclaw_session_runtime_seconds gauge", "# HELP openclaw_session_estimated_cost_usd Stored estimatedCostUsd from sessions.json.", "# TYPE openclaw_session_estimated_cost_usd gauge", "# HELP openclaw_session_compaction_count Stored compactionCount from sessions.json.", "# TYPE openclaw_session_compaction_count gauge", "# HELP openclaw_session_usage_family_session_count Stored usageFamilySessionIds count from sessions.json.", "# TYPE openclaw_session_usage_family_session_count gauge", ]) for session in sorted(session_index.sessions, key=lambda item: sanitize_label(item.get("sessionId"))): session_id = sanitize_label(session.get("sessionId")) usage_family_session_ids = session.get("usageFamilySessionIds") labels = { "session_id": session_id, "status": sanitize_label(session.get("status")), "chat_type": sanitize_label(session.get("chatType")), "last_channel": sanitize_label(session.get("lastChannel")), "model": sanitize_label(session.get("model")), "provider": sanitize_label(session.get("modelProvider")), "auth_profile_override": sanitize_label(session.get("authProfileOverride")), "system_sent": str(bool(session.get("systemSent"))).lower(), "aborted_last_run": str(bool(session.get("abortedLastRun"))).lower(), "total_tokens_fresh": str(bool(session.get("totalTokensFresh"))).lower(), } lines.append(metric_line("openclaw_session_info", 1, **labels)) lines.append(metric_line("openclaw_session_age_seconds", f"{as_int(session.get('ageMs')) / 1000:.3f}", session_id=session_id)) lines.append(metric_line("openclaw_session_updated_timestamp_seconds", f"{as_int(session.get('updatedAt')) / 1000:.3f}", session_id=session_id)) lines.append(metric_line("openclaw_session_started_timestamp_seconds", f"{as_int(session.get('sessionStartedAt')) / 1000:.3f}", session_id=session_id)) lines.append(metric_line("openclaw_session_last_interaction_timestamp_seconds", f"{as_int(session.get('lastInteractionAt')) / 1000:.3f}", session_id=session_id)) lines.append(metric_line("openclaw_session_total_tokens", as_int(session.get("totalTokens")), session_id=session_id)) lines.append(metric_line("openclaw_session_context_tokens", as_int(session.get("contextTokens")), session_id=session_id)) lines.append(metric_line("openclaw_session_runtime_seconds", f"{as_int(session.get('runtimeMs')) / 1000:.3f}", session_id=session_id)) lines.append(metric_line("openclaw_session_estimated_cost_usd", f"{as_float(session.get('estimatedCostUsd')):.12g}", session_id=session_id)) lines.append(metric_line("openclaw_session_compaction_count", as_int(session.get("compactionCount")), session_id=session_id)) lines.append(metric_line("openclaw_session_usage_family_session_count", len(usage_family_session_ids) if isinstance(usage_family_session_ids, list) else 0, session_id=session_id)) def render_metrics() -> str: usage = collect_usage() session_index = collect_session_index() usage_cost_cache = collect_usage_cost_cache() today = today_in_tz().isoformat() tz_name = TIMEZONE.key lines: list[str] = [] append_current_day_metrics(lines, usage, today, tz_name) append_counter_metrics(lines, usage) append_bucket_metrics(lines, usage, tz_name) append_breakdown_metrics(lines, usage, today, tz_name) append_exporter_metrics(lines, usage, session_index, usage_cost_cache, today, tz_name) append_session_inventory_metrics(lines, session_index) lines.append("") return "\n".join(lines)