# Measure throughput and efficiency

Break wall-clock time into queue, setup, compute and verification so you optimize the real bottleneck.

Measure **useful throughput**—how many validated results your workload produces per hour—and break wall-clock time into queue, setup, compute, and verification phases before tuning worker capacity.

## 1. Break wall-clock time into phases

When a calculation or benchmark feels slow, separate where the time actually goes before changing code or worker sizes:

| Phase                         | What happens                                                                  | How to measure it                                                            |
| ----------------------------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| **1. Queue wait**             | Waiting for a compatible worker with free CPU/GPU capacity                    | Job admission timestamp → attempt start timestamp (`ct-cli status JOB_UUID`) |
| **2. Environment setup**      | Checking out code, syncing `uv` dependencies, and loading model checkpoints   | Attempt start → start of your calculation function                           |
| **3. Scientific compute**     | Geometry optimization, ML potential inference, MD integration, or ODE solving | `time.perf_counter()` around the solver loop                                 |
| **4. Verification & scoring** | Independent Hessian/IRC check or benchmark evaluator job                      | Verification/evaluator job duration                                          |
| **5. Artifact upload & save** | Uploading `results/` files to storage and writing project records             | Attempt completion and `save` job duration                                   |

## 2. Record phase timings inside your job

The platform tracks job queueing and attempt duration automatically. To measure internal phases (like model loading vs. inference), record `time.perf_counter()` durations to a JSON file under `results/`:

```python
import json
import time
from pathlib import Path

t0 = time.perf_counter()
model = load_model()
t_load = time.perf_counter()

output = run_calculation(model)
t_compute = time.perf_counter()

Path("results/timing.json").write_text(
    json.dumps({
        "model_load_s": round(t_load - t0, 3),
        "compute_s": round(t_compute - t_load, 3),
        "total_s": round(t_compute - t0, 3),
    })
)
```

Because `results/timing.json` is saved as a job artifact, your [benchmark](/test/benchmarks/) evaluator or reducer can aggregate `model_load_s` and `compute_s` across every item in a dataset.

## 3. Use throughput metrics that include failures

A candidate that runs 2× faster but fails verification 80% of the time reduces your real throughput. Always pair throughput rates with success/failure counts:

- **Transition-state search:** Validated TSs per GPU-hour (alongside total attempts and failed verifications).
- **Structure optimization:** Converged structures per minute.
- **Molecular dynamics:** Trajectory integration steps per second and parsed frames per CPU-second.

## 4. Fix the actual bottleneck

Once you know which phase dominates wall-clock time, target that specific phase:

- **High setup/install time?** Use the pre-installed native runtime profile (`compute/runtime`) instead of resolving environments per job (see [Choose code and dependencies](/operate/environments/)).
- **High queue wait time?** Check worker pool capacity or connect additional machines (see [Connect a worker](/operate/workers/)).
- **High per-job startup overhead on tiny tasks?** Batch more items per allocation or tune concurrency one variable at a time against a frozen benchmark dataset.
