Reporting#

ECLYPSE provides a reporting pipeline with three moving parts:

  1. Define what to report through metrics,

  2. choose how to persist it through report formats and reporters,

  3. load and query the results through Report.

Defining Metrics#

Metrics are reportable events that collect and return structured data during the simulation. Technically, they are events with the METRIC role, exposed through the @metric.<type> decorators provided in eclypse.report.metrics.

Metrics, callbacks, and regular events all share the same event engine, but they serve different purposes:

  • regular events drive the workflow,

  • callbacks attach post-event logic without making reporting the primary goal,

  • metrics attach post-event logic whose output is intended to be written by reporters.

This distinction matters because a metric is both:

  • a post-event hook, so it can access the triggering event payload,

  • a reporting primitive, so its return value is prepared for the reporting pipeline.

By contrast, a plain callback can inspect the same context, but it is mainly meant for workflow composition or transient post-processing.

There are 7 decorators corresponding to different metric types:

See Scheduled event decorators for details on scheduled event helpers. A metric lets you specify:

  • What data to collect

  • How often to report (using triggers)

  • How to report it (via the report argument)

Example:

Example of an application metric returning the number of its services#
from eclypse.report.metrics import metric

@metric.application(activates_on=["step"], report=["csv", "json"])
def my_metric(application, placement, infrastructure):
    return len(application.nodes)

Note

Metrics are executed like events, and use the same underlying logic, including support for cascade triggers and trigger conditions.

Custom metric recipe#

Use the metric decorator that matches the object you want to inspect, choose when it activates, and return either a scalar or a mapping.

Custom service metric#
from eclypse.report.metrics import metric

@metric.service(activates_on="step", report=["csv"])
def requested_cpu(service_id, requirements, placement, infrastructure):
    return {
        "service": service_id,
        "value": requirements["cpu"],
    }

The reporting pipeline records the event metadata and stores the returned value. For multi-field mappings, keep a stable value key for the main measurement and use the other keys as context.

Metric and callback types#

The metric decorators mirror the same event types used by regular events. The event type still determines what your logic receives:

  • simulation metrics receive triggering_event,

  • application metrics receive application, placement, and infrastructure,

  • service metrics receive service_id, requirements, placement, and infrastructure,

  • service metrics with remote=True receive a remote Service instance,

  • interaction metrics receive source_id, target_id, requirements, placement, and infrastructure,

  • infrastructure metrics receive infrastructure and placement_view,

  • node metrics receive node_id, resources, placements, infrastructure, and placement_view,

  • link metrics receive source_id, target_id, resources, placements, infrastructure, and placement_view.

Callbacks follow the same type-based signatures. The difference is therefore not the arguments they receive, but the role they play in the workflow and whether their output is meant to be reported.

Report formats and reporters#

The report argument on an event or metric selects one or more reporter types. Built-in reporters cover the most common outputs:

  • csv for human-readable tabular files,

  • json for JSONL outputs,

  • parquet for analytical workloads and larger runs,

  • gml for graph exports,

  • tensorboard for TensorBoard-compatible metrics.

You can also implement your own Reporter subclass if you want to persist data to a different sink such as a database or a live dashboard.

Default Reporters#

ECLYPSE includes a set of built-in reporters:

In most cases you do not instantiate them manually. Instead, you select the desired reporter types through metric/event configuration and let the simulation resolve the built-in reporters automatically.

Important

When implementing custom reporters that write to the filesystem, use aiofiles for asynchronous file operations. This keeps reporting from blocking the simulation loop.

Example: usage of aiofiles#
import aiofiles

async with aiofiles.open(self.report_path / "data.csv", "a") as f:
    await f.write("some,data,to,write\n")

Choosing a report backend#

The Report class can use multiple dataframe backends:

  • pandas

  • polars

  • polars_lazy

Choose the backend through SimulationConfig or directly when loading a report from disk.

Accessing reports#

After the simulation, you can access the results using the Report object.

Example usage:

Accessing reported data#
from eclypse.report import Report

report = Report("./output")
df = report.service(application_ids="app1", service_ids="srv2")

For a quick run summary, call describe():

print(report.describe())
# 84 rows x 10 steps x 12 metrics | 5 applications | ...

Each accessor method supports filtering by:

  • report_range (e.g., only events between 10 and 100)

  • report_step (e.g., one point every N events)

  • event IDs, application IDs, node/service/link IDs, etc.

Tip

If you need a generic entry point, use frame():

report.frame("application")