# scope_profiler.likwid_data

*module*

Collection and storage of LIKWID marker results.

Counter collection happens at ``finalize()``, on top of a run that has already
completed. It is a bonus, never the point of the job, so no failure here may
cost the user their timing data. That constraint shapes the whole module:
there are three sources of counter data, tried richest first, and the risky
one is fenced off in a child process.

* **Full API, out of process** (:func:`collect_marker_results_isolated`) ---
  the richest source. After ``markerclose()`` has written the marker file,
  re-initializing the perfmon module and calling ``markerreadfile()`` exposes
  every region, for every thread, with event names, counter registers and
  LIKWID's derived metrics (``Clock [MHz]``, ``CPI``, ``Energy [J]``, ...).

  Re-initializing perfmon is also the one step that can take the interpreter
  down rather than raise: on hosts where LIKWID cannot really count (a
  virtualized CI runner with an unreadable TSC and counters disabled by
  HyperThreading, say) it has been observed to segfault. It therefore runs in
  a subprocess, where a crash costs nothing but the enrichment.

* **Marker file** (:func:`parse_marker_file`) --- the file LIKWID writes is
  plain text, so it can be read with no LIKWID calls at all and cannot fail
  catastrophically. It yields every region, thread, call count, runtime and
  raw counter value; only the *names* and the derived metrics are missing.
  This is the fallback whenever the subprocess above does not come back.

* **Marker API** (:func:`collect_region_snapshots`) --- ``markergetregion(tag)``
  read while the markers are still open. Last resort, for when the marker file
  is absent entirely; reports only the calling thread.

All three require the process to have been started under ``likwid-perfctr -m``
(or ``likwid-mpirun ... -marker``). Without that, LIKWID sets no environment
and there is nothing to collect.

[View source](https://github.com/max-models/scope-profiler/blob/devel/src/scope_profiler/likwid_data.py#L1-L1)

## scope_profiler.likwid_data.LIKWID_GROUP

*attribute* · *module attribute*

```python
LIKWID_GROUP = 'likwid'
```

[View source](https://github.com/max-models/scope-profiler/blob/devel/src/scope_profiler/likwid_data.py#L57-L57)

## scope_profiler.likwid_data.LikwidRegionResult

*class* · *dataclass*

```python
class LikwidRegionResult
```

Counter results for a single LIKWID marker region.

The per-thread arrays are all indexed by the same thread axis, so
``events[e, t]`` and ``metrics[m, t]`` refer to the thread whose runtime is
``times[t]`` and whose CPU is ``cpus[t]``.

Attributes
----------
tag : str
    Region name as passed to ``markerstartregion``.
group_id : int
    Index of the LIKWID event group this region was measured with.
group_name : str
    Name of that group (e.g. ``"CLOCK"``), empty if unknown.
cpus : list of int
    Hardware threads that took part in the region.
times : numpy.ndarray
    Per-thread accumulated runtime in seconds, shape ``(nthreads,)``.
call_counts : numpy.ndarray
    Per-thread number of times the region was entered, shape ``(nthreads,)``.
event_names : list of str
    Names of the raw hardware events, length ``nevents``. **Not unique**:
    a group may program one event on several counters (see
    :attr:`event_labels`).
counter_names : list of str
    Hardware counter register each event was programmed on (``FIXC0``,
    ``PMC1``, ``MBOX3C0``, ...), length ``nevents``. Empty for files
    written before counter names were recorded.
events : numpy.ndarray
    Raw counter values, shape ``(nevents, nthreads)``.
metric_names : list of str
    Names of LIKWID's derived metrics, length ``nmetrics``.
metrics : numpy.ndarray
    Derived metric values, shape ``(nmetrics, nthreads)``.
source : str
    Which collection path produced this result: ``"full_api"`` (perfmon
    read-back, the only one with real event names and metrics),
    ``"marker_file"`` (LIKWID's marker file parsed directly --- real
    values, placeholder event names, no metrics) or ``"marker_api"`` (a
    ``markergetregion`` snapshot of the calling thread only).

[View source](https://github.com/max-models/scope-profiler/blob/devel/src/scope_profiler/likwid_data.py#L60-L163)

### scope_profiler.likwid_data.LikwidRegionResult.call_counts

*attribute* · *class attribute* · *instance attribute*

```python
call_counts: np.ndarray = field(default_factory=lambda : np.zeros(0, dtype=np.int64))
```

[View source](https://github.com/max-models/scope-profiler/blob/devel/src/scope_profiler/likwid_data.py#L109-L109)

### scope_profiler.likwid_data.LikwidRegionResult.counter_names

*attribute* · *class attribute* · *instance attribute*

```python
counter_names: list[str] = field(default_factory=list)
```

[View source](https://github.com/max-models/scope-profiler/blob/devel/src/scope_profiler/likwid_data.py#L111-L111)

### scope_profiler.likwid_data.LikwidRegionResult.cpus

*attribute* · *class attribute* · *instance attribute*

```python
cpus: list[int] = field(default_factory=list)
```

[View source](https://github.com/max-models/scope-profiler/blob/devel/src/scope_profiler/likwid_data.py#L107-L107)

### scope_profiler.likwid_data.LikwidRegionResult.event_names

*attribute* · *class attribute* · *instance attribute*

```python
event_names: list[str] = field(default_factory=list)
```

[View source](https://github.com/max-models/scope-profiler/blob/devel/src/scope_profiler/likwid_data.py#L110-L110)

### scope_profiler.likwid_data.LikwidRegionResult.events

*attribute* · *class attribute* · *instance attribute*

```python
events: np.ndarray = field(default_factory=lambda : np.zeros((0, 0)))
```

[View source](https://github.com/max-models/scope-profiler/blob/devel/src/scope_profiler/likwid_data.py#L112-L112)

### scope_profiler.likwid_data.LikwidRegionResult.group_id

*attribute* · *class attribute* · *instance attribute*

```python
group_id: int = -1
```

[View source](https://github.com/max-models/scope-profiler/blob/devel/src/scope_profiler/likwid_data.py#L105-L105)

### scope_profiler.likwid_data.LikwidRegionResult.group_name

*attribute* · *class attribute* · *instance attribute*

```python
group_name: str = ''
```

[View source](https://github.com/max-models/scope-profiler/blob/devel/src/scope_profiler/likwid_data.py#L106-L106)

### scope_profiler.likwid_data.LikwidRegionResult.metric_names

*attribute* · *class attribute* · *instance attribute*

```python
metric_names: list[str] = field(default_factory=list)
```

[View source](https://github.com/max-models/scope-profiler/blob/devel/src/scope_profiler/likwid_data.py#L113-L113)

### scope_profiler.likwid_data.LikwidRegionResult.metrics

*attribute* · *class attribute* · *instance attribute*

```python
metrics: np.ndarray = field(default_factory=lambda : np.zeros((0, 0)))
```

[View source](https://github.com/max-models/scope-profiler/blob/devel/src/scope_profiler/likwid_data.py#L114-L114)

### scope_profiler.likwid_data.LikwidRegionResult.source

*attribute* · *class attribute* · *instance attribute*

```python
source: str = 'full_api'
```

[View source](https://github.com/max-models/scope-profiler/blob/devel/src/scope_profiler/likwid_data.py#L115-L115)

### scope_profiler.likwid_data.LikwidRegionResult.tag

*attribute* · *instance attribute*

```python
tag: str
```

[View source](https://github.com/max-models/scope-profiler/blob/devel/src/scope_profiler/likwid_data.py#L104-L104)

### scope_profiler.likwid_data.LikwidRegionResult.times

*attribute* · *class attribute* · *instance attribute*

```python
times: np.ndarray = field(default_factory=lambda : np.zeros(0))
```

[View source](https://github.com/max-models/scope-profiler/blob/devel/src/scope_profiler/likwid_data.py#L108-L108)

### scope_profiler.likwid_data.LikwidRegionResult.event_labels

*property*

```python
event_labels: list[str]
```

Unique per-event labels, safe to use as dict or column keys.

``event_names`` alone is not unique. A group such as ``MEM_DP``
programs the same event on one counter per memory channel, so
``CAS_COUNT_RD`` legitimately appears eight times on a socket with
eight channels (the ones reading zero are unpopulated). Keying
anything by the bare name silently keeps only the last channel.

Names that occur once are returned unchanged; repeated ones get the
hardware counter appended --- ``CAS_COUNT_RD:MBOX0C0``,
``CAS_COUNT_RD:MBOX1C0``, ... --- or a positional suffix if the file
predates counter names being recorded.

[View source](https://github.com/max-models/scope-profiler/blob/devel/src/scope_profiler/likwid_data.py#L118-L141)

### scope_profiler.likwid_data.LikwidRegionResult.as_dict

*method*

```python
def as_dict() -> dict
```

Return the region's results as a plain dictionary.

Convenience for callers that want to build a DataFrame or dump the
numbers without touching the array layout.

[View source](https://github.com/max-models/scope-profiler/blob/devel/src/scope_profiler/likwid_data.py#L143-L163)

## scope_profiler.likwid_data.collect_marker_results

*function*

```python
def collect_marker_results(pylikwid) -> list[LikwidRegionResult]
```

Read every region of the run back from LIKWID's marker file.

Must be called *after* ``markerclose()``, which is what writes the file.
Because ``markerclose()`` also tears the perfmon module down, the event
sets named in ``LIKWID_EVENTS`` are re-registered first; that is what makes
event and metric names available for the values in the file.

Parameters
----------
pylikwid : module
    The imported ``pylikwid`` module.

Returns
-------
list of LikwidRegionResult
    One entry per region recorded by LIKWID, in file order. Empty when the
    process is not running under the marker API, or when the performance
    counters cannot be re-opened.

[View source](https://github.com/max-models/scope-profiler/blob/devel/src/scope_profiler/likwid_data.py#L359-L474)

## scope_profiler.likwid_data.collect_marker_results_isolated

*function*

```python
def collect_marker_results_isolated(timeout: float = 120.0)
```

Run the perfmon read-back in a child process and return its results.

:func:`collect_marker_results` has to re-initialize LIKWID's perfmon
module, and on hosts where the counters are not really usable that can
abort the process outright instead of raising --- which at ``finalize()``
time would destroy a completed run's output. Running it behind a process
boundary turns that worst case into a missing enrichment.

Parameters
----------
timeout : float, optional
    Seconds to wait for the child before giving up (default: 120).

Returns
-------
list of LikwidRegionResult or None
    ``None`` when the child could not deliver results (crashed, timed out,
    or LIKWID refused to re-open the counters), which tells the caller to
    fall back to :func:`parse_marker_file`.

[View source](https://github.com/max-models/scope-profiler/blob/devel/src/scope_profiler/likwid_data.py#L549-L604)

## scope_profiler.likwid_data.collect_region_snapshots

*function*

```python
def collect_region_snapshots(pylikwid, region_names: Iterable[str]) -> list[dict]
```

Read the current counter values for each named region.

Uses the marker API's ``markergetregion``, so it must be called *before*
``markerclose()`` and reports only the calling thread. Regions that LIKWID
does not know about (never entered, or measured under a different name) are
skipped.

Parameters
----------
pylikwid : module
    The imported ``pylikwid`` module.
region_names : iterable of str
    Region tags to query.

Returns
-------
list of dict
    One entry per region that returned data, with keys ``tag``,
    ``nevents``, ``events``, ``time`` and ``count``.

[View source](https://github.com/max-models/scope-profiler/blob/devel/src/scope_profiler/likwid_data.py#L201-L241)

## scope_profiler.likwid_data.likwid_environment

*function*

```python
def likwid_environment() -> dict
```

Return the LIKWID environment of the current process.

Returns
-------
dict
    The ``LIKWID_*`` variables set by the launcher. Empty when the process
    was not started under ``likwid-perfctr -m``.

[View source](https://github.com/max-models/scope-profiler/blob/devel/src/scope_profiler/likwid_data.py#L166-L175)

## scope_profiler.likwid_data.markers_available

*function*

```python
def markers_available() -> bool
```

Whether this process runs under the LIKWID marker API.

``markerinit()`` silently degrades to a no-op when LIKWID's environment is
absent, so this is what distinguishes "there will be counter data" from
"the script was started as a plain ``python script.py``".

[View source](https://github.com/max-models/scope-profiler/blob/devel/src/scope_profiler/likwid_data.py#L178-L185)

## scope_profiler.likwid_data.parse_marker_file

*function*

```python
def parse_marker_file(path=None) -> list[LikwidRegionResult]
```

Read LIKWID's marker file directly, without calling into LIKWID.

The file ``markerclose()`` writes is plain text::

    <nthreads> <nregions> <ngroups>
    <region_id>:<tag>-<group_id>          (one line per region)
    <region_id> <group_id> <cpu> <call_count> <time> <nevents> <values...>

Parsing it here is the crash-proof path: it touches no counters and calls
no LIKWID function, so it cannot take the interpreter down the way
re-initializing perfmon can. The cost is that event names, counter
registers and derived metrics are not in the file --- those exist only
inside LIKWID's group definitions --- so events come back positionally
named and the metric list is empty.

Parameters
----------
path : str, optional
    Marker file to read (default: ``$LIKWID_FILEPATH``).

Returns
-------
list of LikwidRegionResult
    One entry per region, ordered by region id. Empty if the file is
    missing or malformed.

[View source](https://github.com/max-models/scope-profiler/blob/devel/src/scope_profiler/likwid_data.py#L269-L356)

## scope_profiler.likwid_data.snapshots_to_results

*function*

```python
def snapshots_to_results(snapshots: Iterable[dict]) -> list[LikwidRegionResult]
```

Convert marker-API snapshots into the common result structure.

Used as the fallback when the full API cannot re-open the counters, so the
snapshots taken before ``markerclose()`` can still be written to HDF5.

[View source](https://github.com/max-models/scope-profiler/blob/devel/src/scope_profiler/likwid_data.py#L244-L266)

## scope_profiler.likwid_data.write_likwid_results

*function*

```python
def write_likwid_results(h5file, results: Iterable[LikwidRegionResult], environment: dict | None = None) -> None
```

Write collected LIKWID results into an open HDF5 file.

Creates (replacing any previous copy) a ``likwid`` group holding one
subgroup per region under ``likwid/regions/<tag>``.

Parameters
----------
h5file : h5py.File or h5py.Group
    Destination, typically the per-rank profiling file.
results : iterable of LikwidRegionResult
    Regions to store.
environment : dict, optional
    LIKWID environment variables to record as attributes on the group, so
    the event set a file was measured with stays with the data.

[View source](https://github.com/max-models/scope-profiler/blob/devel/src/scope_profiler/likwid_data.py#L639-L699)
