Skip to content

Feat/m7 flush addons - #4

Merged
Denisergocmen924 merged 5 commits into
masterfrom
feat/m7-flush-addons
Aug 27, 2026
Merged

Feat/m7 flush addons#4
Denisergocmen924 merged 5 commits into
masterfrom
feat/m7-flush-addons

Conversation

@Denisergocmen924

Copy link
Copy Markdown
Owner

Eşik aşıldığında (cpu>90 / ram>90 / disk>95 / error|critical log) spool 30 sn'lik
turu beklemeden gönderilir; cooldown seli önler. crash_processes açıksa flush anında
ilk 5 kaynak-yiyen süreç crash_snapshots'a yazılır.

Seçilebilir eklentiler: temperature, swap, load_avg, gpu, external_ip, crash_processes.
enabled_addons kapalıyken sütun null kalır ve ölçüm hiç yapılmaz (nvidia-smi çağrılmaz).

external_ip'i cihaz GÖNDERMEZ: InventoryIn'de alan yok, extra="forbid" sayesinde
göndermeye çalışan agent 422 alır. Değeri collector, Fly'ın proxy başlığından yazar
ve rızayı her istekte yeniden okur — eklenti kapatıldığında sütuna açıkça null yazılır.

56 yeni test (385 toplam). 73 sabotajın tamamı yakalandı.

Canlı doğrulandı (Multipass VM): flush 0.28 sn'de gitti (30 sn değil), cooldown iki
denemeyi eledi, swap=0 null'a düşmedi, sensörsüz makinede temperature null kaldı.

Not: external_ip yalnızca Fly proxy'sinin arkasında dolar — collector 0.5.0 deploy'undan
sonra doğrulanacak.

Eşik aşıldığında (cpu>90 / ram>90 / disk>95 / error|critical log) 30 saniyelik
gönderim turu beklenmez; spool anında boşaltılır. Projenin ana içgörüsünün
("çöküş anında göndermek çok geç") kod karşılığı budur.

flush.py yalnızca KARAR üretir, yan etki üretmez: evaluate() eşiği,
cooldown_active() sel korumasını, build_crash_snapshot() wire satırını verir.
Spool'a yazmak, shipper'ı çağırmak ve last_flush_at'i damgalamak döngünün işi.

Kararlar:
- ram eşiği yüzdedir ama şemada yalnızca ram_used_mb var. collect() artık
  MetricReading döndürüyor: sample (spool'a gider) + ram_percent (yalnızca
  eşik karşılaştırması, hiçbir yere yazılmaz). Yüzde MetricSample'a
  konamazdı — o nesne asdict() ile doğrudan wire gövdesi oluyor ve collector
  sözleşme dışı alanı extra="forbid" ile 422'liyor.
- crash_processes eklentisi kapalıyken de (varsayılan) her flush bir
  crash_snapshots satırı yazar, processes boş kalır. Metrikler "CPU %95'ti"
  der; bu satır "flush gerçekten attı" der. CLAUDE.md §7'nin lafzından sapma,
  bilinçli.
- Aynı anda birden çok eşik tutabilir ama sütun tek değer alır:
  log > ram > cpu > disk. log en üstte çünkü diğerleri "yük yüksek" derken
  log "bir şey bozuldu" der.
- cooldown duvar saatiyle ölçülür, monotonic ile değil: monotonic her yeniden
  başlatmada sıfırlanır, cooldown da onunla birlikte sıfırlanırdı.
- last_flush_at gönderimden ÖNCE damgalanır. Collector erişilemezken eşik her
  turda yeniden tutar; damga sonra yazılsaydı her tur yeni bir snapshot
  üretilir, spool kesinti boyunca boş yere şişerdi.
- Pause'da flush YOK. Acil gönderim de "buluta yükleme"dir; toplama ve spool
  sürer, gönderim durur. Komut poll'ünün aksine istisnası yok — flush
  telemetridir, kontrol mesajı değil.

seconds_since_iso() bilerek negatif de dönebiliyor: sistem saati geri
alındığında cooldown'ı süresiz kilitlememek için kararı çağıran veriyor.

agent 0.2.0 -> 0.3.0. Testler M7 commit 2'de.
Buradaki bozuklukların hepsi SESSİZ: eşik yanlış alana bağlanırsa, cooldown
ters tarafa kayarsa ya da pause'da flush atarsa hata mesajı çıkmaz. Agent
çalışmaya devam eder; yalnızca çöküş anındaki veri ya hiç gelmez ya da
gereksiz sel olur.

Kapsam:
- evaluate — eşik `>` ile karşılaştırılır (`>=` değil: disk %95'te sabit duran
  makine her turda flush ederdi), None eşiği aşmış sayılmaz, ram eşiği YÜZDEye
  bağlı (ram_used_mb'ye değil), sebep önceliği log > ram > cpu > disk, eşikler
  config'ten okunur.
- cooldown — damga yoksa/çözülemiyorsa flush serbest, gelecekteki damga
  (saat geri alınmış) cooldown'ı kilitlemez.
- build_crash_snapshot — eklenti kapalıyken satır yine yazılır (processes
  boş), açıkken ilk 5 süreç; RAM tetiklemesinde belleğe göre sıralanır, ölen
  süreç atlanır, psutil hatası snapshot'ı düşürmez.
- loop._maybe_flush — pause'da hiç flush yok, cooldown hem snapshot'ı hem
  gönderimi bastırır, backoff'ta snapshot alınır ama gönderilmez, snapshot
  gönderimden ÖNCE spool'a girer, başarısız gönderim de cooldown'ı başlatır.
- _collect_logs yalnızca error|critical sayar; okuma hatasında 0 döner.

İki sözleşme testi, birbirini import etmeyen iki katmanı bağlar:
- flush.REASON_ORDER == collector'ın CrashSnapshotIn.trigger_reason Literal'ı,
- asdict(MetricSample) ⊆ collector'ın MetricIn alanları (extra="forbid").
İkisi de bozulursa hata yalnızca gerçek bir çöküş anında, 422 olarak görünürdü.

35 sabotaj denendi, 35'i yakalandı. Biri ilk turda kaçtı: "damga diske
yazılmaz" sabotajı başarılı gönderim yolunda görünmüyordu, çünkü _send_spool
last_send için state'i zaten kaydediyor — yani o testteki "diske yazıldı"
iddiası yanlışlıkla tatmin oluyordu. İddia, gönderimin yapılmadığı yollara
(backoff / 500) taşındı; kalıcılığın tek gerçek kanıtı orası.

345 test.
…d_addons filtresi

Çekirdek metrikler her zaman toplanır; eklentiler yalnızca config'in
enabled_addons listesinde adı geçiyorsa okunur, kapalıyken sütun null kalır.
Varsayılan kapalı olmasının sebebi ölçüm maliyeti değil ANLAM maliyeti: her
makinede olmayan bir sütun (sensör, NVIDIA kartı, Linux'a özgü load average)
açıkça istenmeden doldurulmaz.

Kararlar:
- Sıcaklık için "bulduğun ilk sensörü al" YOK. Bilinen CPU sensörleri sırayla
  denenir: coretemp (Intel) -> k10temp (AMD) -> cpu_thermal (ARM) -> acpitz
  (anakart). Sıra daralan güvenilirliktedir. Sebep şemada: sensör adını tutan
  sütun yok, yani temperature_c'nin neyi ölçtüğü satırdan okunamaz — aynı
  grafikteki iki nokta iki farklı şeyi anlatamaz.
- GPU nvidia-smi ile okunur, kütüphaneyle değil. journald için verilen kararın
  aynısı: sürücüyle birlikte zaten gelen programı çağırmak, kurulum
  maliyeti olan bir bağımlılık eklemekten iyidir.
- Yeni modül core/gpu.py (CLAUDE.md §3'teki dosya listesinde yok — bilinçli
  ekleme): GPU iki yere veri veriyor (model -> envanter, kullanım -> metrik)
  ve ikisi de aynı programa dayanıyor. Ayrı modül olmasaydı nvidia-smi'nin
  komut satırı ve hata halleri iki dosyada tekrarlanırdı.
- GpuReader "program hiç yok" halini HATIRLAR ve bir daha denemez; zaman
  aşımı gibi geçici hatalar hatırlanmaz. Olmayan bir programı 5 saniyede bir
  başlatmaya çalışmak sürekli ve boş bir maliyettir.
- Çoklu GPU'da yalnızca ilk kart okunur — şemada tek sütun var; kart başına
  satır tutmak zaman serisinin şeklini değiştirirdi.
- swap yoksa 0 döner, None değil: "swap'ı olmayan makine" ölçülemeyen değil,
  sıfır kullanan makinedir.
- external_ip'i agent GÖNDERMEZ (envanterde alan bile yok). Cihazın kendi dış
  IP'sini bildirmesi, doğruluğu cihazın insafına bırakırdı; değeri isteği
  gerçekten alan taraf yazacak (collector, sıradaki commit). Kullanıcının
  açık/kapalı tercihi yine payload'daki enabled_addons ile gidiyor.
- Eklenti adları artık tek yerde (config.KNOWN_ADDONS); tanınmayan ad HATA
  değil UYARI üretir. "temprature" yazan kullanıcı yüzünden agent
  başlamamalı, ama sütunun neden hep null olduğunu da günlerce aramamalı.

collect() artık config alıyor: kullanıcı bir eklentiyi açtığında değişiklik
servis yeniden başlatılmadan geçerli oluyor (döngü config'i her tick okuyor).

Dev makinesinde canlı doğrulandı: temperature_c=45.0, swap_used_mb=0,
load_avg 1.09/1.00/1.14, gpu_usage_percent=15.0, gpu_vram_used_mb=12,
gpu_model="NVIDIA GeForce RTX 4050 Laptop GPU". Testler sıradaki commit'te.
… IP sözleşmesi

Eklentilerin ortak riski hata üretmemesidir: kapalıyken sessizce açık olmak ya
da açıkken sessizce yanlış şeyi ölçmek. İkisi de log'a düşmez — biri
kullanıcının istemediği veriyi toplar, diğeri sütuna makineden makineye farklı
anlam taşıyan bir sayı yazar. Testler donanımı değil KARARI doğruluyor: hangi
sensör seçiliyor, hangi alan ne zaman doldu, hata nasıl yutuldu.

Kapsam:
- Filtre: kapalıyken yedi eklenti sütununun hepsi null; bir eklentiyi açmak
  diğerlerini açmıyor; liste her collect'te config'den yeniden okunuyor
  (eklenti açmak servisi yeniden başlatmayı gerektirmiyor).
- Sıcaklık: coretemp, acpitz'in önüne geçiyor; tanınmayan sensör HİÇ
  kullanılmıyor (şemada sensör adı sütunu olmadığı için "ne bulursan al"
  aynı sütuna iki farklı şeyi yazardı); okumasız sensör atlanıyor;
  sensors_temperatures'ın hiç tanımlı olmadığı platformda ve okuma hatasında
  ölçüm turu düşmüyor.
- swap: sıfır bir ÖLÇÜMDÜR, null değil. Grafikte null "ölçemedim", 0
  "kullanılmıyor" der.
- load_avg: üç alan tek çağrıdan geliyor, birlikte doluyor ya da birlikte
  null oluyor.
- GPU: CSV ayrıştırma, çoklu kartta ilk satır, [N/A] -> null, sıfırdan farklı
  çıkış kodu -> null, zaman aşımı -> null. Program yoksa BİR kez deneniyor ve
  bir daha denenmiyor; zaman aşımı ise kalıcı sayılmıyor. Eklenti kapalıyken
  nvidia-smi hiç çalıştırılmıyor — kapalı bir eklentinin bedeli sıfır olmalı,
  süreç başlatıp sonucu atmak "kapalı" demek değildir.
- Envanter: gpu_model yalnızca eklenti açıkken doluyor. external_ip envanterde
  ALAN OLARAK YOK — sözleşme testi, agent'ın kendi dış IP'sini bildirmesinin
  bir daha mümkün olmamasını sağlıyor; kullanıcının tercihi yine
  enabled_addons ile gidiyor.
- Config: tanınmayan ad uyarı üretiyor ama agent'ı durdurmuyor; tanınan adlar
  sessiz geçiyor; KNOWN_ADDONS'taki her ad config.example.toml'da anılıyor
  (koda eklenip örnekte anılmayan eklenti, kullanıcının varlığını hiç
  öğrenemeyeceği eklentidir).

Testte bilinçli bir ayrıntı: çıkış kodu testinde taklit çıktı ayrıştırılabilir
bırakıldı. Boş stdout ile, returncode denetimi tamamen kaldırılsa bile test
yeşil kalırdı — yani testin koruduğu şeyi korumazdı.

26/26 sabotaj yakalandı. Sabotaj koşucusuna PYTHONDONTWRITEBYTECODE eklendi:
aynı dosya saniyeler içinde defalarca yazıldığı için .pyc geçersizlemesi
(mtime+boyut) bir sabotajı görünmez kılıp sahte "kaçtı" üretiyordu. Mevcut
flush koşusu da bu düzeltmeyle yeniden çalıştırıldı: 35/35 duruyor.

Toplam 373 test.
external_ip artık InventoryIn'de bir alan DEĞİL. Cihaz kendi dış adresini
bildirseydi, doğruluğu cihazın insafına kalırdı: bir agent istediği adresi
yazabilirdi ve dashboard'daki "bu makine nereden bağlanıyor" sorusunun cevabı
cihazın beyanı olurdu. Bu, device_id'de verilen kararın (Boşluk A) aynısıdır —
kimlik anahtardan türetiliyor, adres de bağlantının kendisinden türetilir.
`extra="forbid"` sayesinde alanın yokluğu pasif değil aktif bir REDDİR:
göndermeye çalışan agent 422 alır.

Kararlar:
- Kaynak Fly-Client-IP. Fly'ın proxy'si bu başlığı KENDİSİ yazar ve istemcinin
  gönderdiğini ezer. X-Forwarded-For kullanılmadı: o bir listedir ve istemci
  başına istediğini ekleyebilir — reddedilen "gövdede gönder" yolunu başka bir
  adla geri açmak olurdu.
- Başlık yoksa sütun null kalır. Soketin karşı ucuna düşmek bir seçenek değil:
  proxy arkasında o adres proxy'nin kendisidir ve cihazın adresi diye
  kaydedilmesi boş bırakmaktan daha kötüdür.
- Değer ip_address ile ÇÖZÜLÜR ve normalleştirilmiş hali yazılır. Doğrulama,
  proxy zinciri varsayıldığı gibi değilse metin sütununa güvenilmeyen girdi
  yazılmasını engeller; normalleştirme, aynı IPv6 adresinin farklı yazımlarının
  dashboard'da "adres değişti" gibi görünmesini engeller.
- Rıza her gönderimde yeniden okunur ve eklenti KAPALIYKEN sütuna açıkça null
  yazılır. "Yazmamak" yetmezdi: eski adres satırda kalırdı. Kullanıcı eklentiyi
  kapattığında enabled_addons değiştiği için envanter zaten yeniden gönderilir,
  o gönderim eski değeri siler.
- Yalnızca /inventory yazar, /ingest yazmaz. /ingest gövdesinde enabled_addons
  yok; oradan yazılan bir adres rızayı hiç okumadan yazılmış olurdu. Adres
  "statik eklenti" olarak tanımlı, tazeliği envanterin tazeliği kadar
  (açılışta + değiştiğinde) — daha sık istenirse önce rızanın cihaz satırından
  okunması gerekir.
- Eklenti adı collector'da TEKRAR tanımlandı, agent'tan import edilmedi: ikisi
  ayrı deploy edilen ayrı programlar (collector imajında agent kodu yok),
  paylaştıkları şey Python nesnesi değil wire sözleşmesidir. Ayrışmayı test
  yakalıyor — log seviyeleri ve flush sebepleri için kullanılan yöntemin aynısı.
- DEVICE_WRITABLE_COLUMNS'ta external_ip artık "agent'ın bildirdikleri"
  grubunda değil, "sunucunun kendi bildiklerinden yazdıkları" grubunda.

12 test, 12/12 sabotaj yakalandı. Aralarında iki sözleşme testi var: eklenti
adının iki tarafta aynı kalması ve agent'ın Inventory'si ile InventoryIn'in
alan alan eşleşmesi (fazla alan 422 üretir, eksik alan sessizce null kalır —
ikisi de tek listenin bir tarafta unutulmasıyla oluşur).

Collector 0.4.0 -> 0.5.0. Toplam 385 test.
@Denisergocmen924
Denisergocmen924 merged commit d6eb1d5 into master Aug 27, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant