Watchdog de capacidade para Power BI / Microsoft Fabric — detecta consumo anômalo de CU por item e reage em degraus.
Project description
pbi-watchdog
Watchdog de capacidade para Power BI / Microsoft Fabric. Detecta itens consumindo CU muito acima do próprio padrão histórico e reage em degraus — alertar, cancelar carga em background, derrubar sessões — com travas de segurança pensadas para organizações com várias capacidades e políticas diferentes por capacidade.
Roda em qualquer lugar: notebook Fabric, Azure Function, container, GitHub Actions ou laptop.
O núcleo é Python puro sobre as APIs REST; Spark e sempy são opcionais.
Em 5 minutos
pip install pbi-watchdog
pbi-watchdog init # gera watchdog.yaml comentado
export PBI_CLIENT_ID=... PBI_CLIENT_SECRET=...
pbi-watchdog discover --capacities # descobre os GUIDs das capacidades
pbi-watchdog discover --metrics # acha o dataset do Capacity Metrics App
pbi-watchdog doctor --deep # diz exatamente o que ainda falta
pbi-watchdog run --dry-run -v # primeiro ciclo, sem agir
Depois agende pbi-watchdog run a cada 15 minutos.
Como funciona
A cada ciclo, por capacidade:
snapshot → intervalo → baseline → avaliação → travas → ação → auditoria
- Snapshot — lê do Capacity Metrics App o CU acumulado do dia por item.
- Intervalo — subtrai o snapshot anterior. O consumo do período é o que interessa,
e é normalizado para
interval_minutes: se o agendador atrasar, um intervalo de 45 min não vira um falso pico de 3x. - Baseline — mediana do consumo daquele item, naquele mesmo bucket horário, nos últimos N dias, aparando o topo 10% para que um incidente passado não vire "normal".
- Avaliação —
consumo ÷ baselinecai num degrau:alert/throttle/kill. - Travas — o que separa um watchdog de um gerador de incidentes (abaixo).
- Ação — cancela refreshes, cancela jobs Fabric, derruba sessões XMLA.
- Auditoria — cada decisão vira um
Event, inclusive o que se decidiu não fazer e por quê.
As travas de segurança
Um detector de anomalias com poder de matar processos precisa de mais desconfiança do que um detector que só alerta. Todas são configuráveis, e nenhuma delas suprime o alerta — só limitam a ação:
| Trava | O que evita |
|---|---|
min_cu_seconds |
Item ocioso que sai de 1 para 3 CU·s é "3x" e irrelevante |
consecutive_breaches |
Pico instantâneo. Com 2, exige anomalia sustentada por 2 ciclos |
cooldown_minutes |
Refresh que retenta em loop virando metralhadora de cancelamentos |
max_actions_per_run |
Muitos itens anômalos de uma vez é sintoma sistêmico, não culpa deles — acima do orçamento, o ciclo só alerta |
min_capacity_utilization_percent |
Matar carga numa capacidade que está a 20% de uso |
protect |
Regulatório e executivo alertam, nunca são mortos |
freeze_windows |
Fechamento contábil e janela de carga noturna |
mode: observe |
Tudo acima. É o padrão, e deve continuar sendo por 2–4 semanas |
Várias capacidades, políticas diferentes
defaults define a política; cada capacidade sobrescreve o que precisa. O merge é por bloco,
então mexer em thresholds.alert preserva throttle e kill.
defaults:
mode: observe
thresholds: { alert: 1.2, throttle: 1.5, kill: 1.8 }
capacities:
- key: F128_PROD
id: "..."
sku: F128
overrides:
guards: { consecutive_breaches: 3, max_actions_per_run: 3 }
- key: F64_SANDBOX
id: "..."
sku: F64
overrides:
mode: enforce # o enforcement estreia aqui
thresholds: { alert: 1.15 }
freeze_windows: []
Calibração
A fase de observação existe para responder a uma pergunta: com estes limiares, quantas vezes eu teria matado alguma coisa na semana passada, e o quê?
pbi-watchdog calibrate --days 14
Faz replay do histórico com a política atual e devolve quantos alertas / throttles / kills
teriam ocorrido, sugestões de threshold a partir da distribuição observada, e os itens que
disparariam ação repetidamente — normalmente cargas legitimamente irregulares que pertencem
a protect.item_ids, não abusos.
Só depois disso troque mode para enforce, e comece pela capacidade menos crítica.
Arquitetura
src/pbi_watchdog/
core/ baseline.py, detect.py ← funções puras, sem I/O. É onde os testes moram
config.py schema pydantic ← o contrato com o usuário
auth/ SPN, managed identity, notebook
sources/ metrics_app_rest | metrics_app_sempy | fake + perfis de DAX
storage/ sqlite | delta
actions/ cancel_refresh, cancel_fabric_jobs, kill_xmla_sessions
notify/ teams, slack, webhook, console
runner.py orquestração
doctor.py diagnóstico pré-voo
calibrate.py replay do histórico
cli.py
Cada camada é um protocolo. Trocar SQLite por outro backend é implementar 9 métodos de
storage.StateStore; adicionar um canal de notificação é uma classe com um método send.
Perfis de DAX
O modelo do Capacity Metrics App muda de nome entre versões — é a causa número um de
"funcionou na minha tenant e quebrou na sua". Em vez de embutir um DAX fixo, a lib declara
perfis com as tabelas/colunas que cada um exige e detecta qual bate, consultando o modelo
via INFO.TABLES() / INFO.COLUMNS().
pbi-watchdog inspect-model -v # mostra o modelo e quais perfis são compatíveis
Se nenhum bater, metrics_source.dax_override aceita a sua query. Ela só precisa devolver
item_id, item_name, item_kind, workspace_id, workspace_name, cu_seconds_today.
Uso como biblioteca
from pbi_watchdog import WatchdogConfig, Watchdog
config = WatchdogConfig.from_file("watchdog.yaml")
for summary in Watchdog(config, dry_run=True).run_once():
print(summary.capacity_key, summary.anomalies, summary.actions_taken)
for event in summary.events:
print(event.item_name, event.tier, "→", event.effective_tier, event.suppressions)
O núcleo também é usável isolado, sem config nem storage:
from pbi_watchdog.core import baseline, detect
intervalos = baseline.derive_intervals(snapshots_atuais, snapshots_anteriores)
baselines = baseline.compute_baselines(historico, cfg, target_bucket="h14", ...)
veredito = detect.assess_one(intervalo, baselines["item-x"], policy, estado, now=agora)
O que você precisa provisionar
Resumo; o detalhe com passo a passo está em docs/PERMISSIONS.md, e o
doctor verifica cada item.
| Item | Para quê | Obrigatório? |
|---|---|---|
| Capacity Metrics App instalado | fonte das métricas | sim |
| Service principal (app registration) | autenticação | sim (fora do Fabric) |
| SPN como Viewer no workspace do Metrics App | ler consumo | sim |
| Tenant setting: Service principals can use Fabric APIs | tudo | sim |
| Tenant setting: Dataset Execute Queries REST API | fonte metrics_app_rest |
sim |
SPN em grupo de read-only admin APIs + Tenant.Read.All |
validar GUIDs, discover |
recomendado |
| SPN como Member/Admin dos workspaces monitorados | cancelar refresh e jobs | só para enforce |
| XMLA read-write na capacidade | kill_xmla_sessions |
só para tier kill |
Volume persistente para storage.path |
manter a baseline | sim |
Só alertar não exige permissão de escrita em lugar nenhum. Se a organização não quiser
dar poder de cancelamento ao watchdog, mode: observe entrega valor sem isso.
Limitações conhecidas
- O Capacity Metrics App tem latência de alguns minutos. A contenção nunca é instantânea — o watchdog reduz o rabo do incidente, não o previne.
- A granularidade é o intervalo entre execuções. Um pico de 3 minutos entre dois snapshots de 15 minutos aparece diluído.
kill_xmla_sessionsderruba usuários no meio do relatório e exigesempy/ADOMD, ou seja, só roda dentro do Fabric. Fora dele a ação falha explicitamente e o alerta continua saindo.cancel_refreshsó enxerga refreshes com statusUnknown(o indicador de "em andamento" na API do Power BI).- Perder o arquivo de
storagesignifica perder a baseline: o watchdog volta a só observar até reacumular histórico. Monte em volume persistente. - Rodar o watchdog na capacidade monitorada faz dele parte do consumo que ele mede. É leve, mas prefira uma capacidade diferente ou um runtime externo.
Desenvolvimento
pip install -e ".[dev]"
pytest # núcleo, config, CLI, doctor e end-to-end com fonte sintética
ruff check src tests
Os testes não tocam a rede: a fonte sintética e o SQLite temporário cobrem o ciclo completo, incluindo bootstrap, streak, cooldown, circuit breaker, gap do agendador e falha de ação.
Publicação
O pacote é um wheel py3-none-any padrão, sem extensão compilada.
python -m build # gera dist/*.whl e dist/*.tar.gz
twine check dist/*
Publicar é criar uma tag — o workflow release.yml usa Trusted Publishing (OIDC), sem token guardado no repositório:
git tag v0.1.0 && git push origin v0.1.0
Antes da primeira publicação, registre o trusted publisher em
https://pypi.org/manage/account/publishing/ com workflow release.yml e environment pypi.
Para validar o fluxo inteiro sem queimar a versão no PyPI (versões publicadas são imutáveis e o nome não é liberável), publique antes no TestPyPI:
twine upload --repository testpypi dist/*
pip install --index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple/ pbi-watchdog
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pbi_watchdog-0.1.2.tar.gz.
File metadata
- Download URL: pbi_watchdog-0.1.2.tar.gz
- Upload date:
- Size: 79.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
665ea3a3aa562a676b208109d6ae59f89df4ed6a17cd65a96547cfcb4a90c568
|
|
| MD5 |
3cce339eb849269179783099562dd876
|
|
| BLAKE2b-256 |
4f303c5ac6e5e3ae9cac2dfccf0f1ca08308e9e192dc50dca3003d9815a251a8
|
Provenance
The following attestation bundles were made for pbi_watchdog-0.1.2.tar.gz:
Publisher:
release.yml on devrenanferrari/Watchdog-powerbi
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pbi_watchdog-0.1.2.tar.gz -
Subject digest:
665ea3a3aa562a676b208109d6ae59f89df4ed6a17cd65a96547cfcb4a90c568 - Sigstore transparency entry: 2230401348
- Sigstore integration time:
-
Permalink:
devrenanferrari/Watchdog-powerbi@2180309384e9a111d716e90f73b6841bd78f139a -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/devrenanferrari
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@2180309384e9a111d716e90f73b6841bd78f139a -
Trigger Event:
push
-
Statement type:
File details
Details for the file pbi_watchdog-0.1.2-py3-none-any.whl.
File metadata
- Download URL: pbi_watchdog-0.1.2-py3-none-any.whl
- Upload date:
- Size: 63.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3a1d5c59ffdcc842686080011781b3dfab7c4d0342aef2c69d1e45139c1bb360
|
|
| MD5 |
b78a78daebb36650026c08944c34c6bf
|
|
| BLAKE2b-256 |
78d40970b17a52d52e15d55cf06a6350b0bc2a2afab52e819c236a88ce9c645a
|
Provenance
The following attestation bundles were made for pbi_watchdog-0.1.2-py3-none-any.whl:
Publisher:
release.yml on devrenanferrari/Watchdog-powerbi
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pbi_watchdog-0.1.2-py3-none-any.whl -
Subject digest:
3a1d5c59ffdcc842686080011781b3dfab7c4d0342aef2c69d1e45139c1bb360 - Sigstore transparency entry: 2230401876
- Sigstore integration time:
-
Permalink:
devrenanferrari/Watchdog-powerbi@2180309384e9a111d716e90f73b6841bd78f139a -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/devrenanferrari
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@2180309384e9a111d716e90f73b6841bd78f139a -
Trigger Event:
push
-
Statement type: