Ders 19 / 21
Yazılım Malzeme Listesi
Yapı çıktısının içindeki bileşenler bir envanter olarak üretilir ve üç sayıyla ölçülür: envanterin gördüğü doğrudan, geçişli ve derleme zamanı bileşenleri, hiç göremediği üç sınıf, ve bir kusur duyurusu geldiğinde yanıtın kaç sorguya indiği.
İçindekiler
Önceki ders sırrın depoda şifreli durabildiğini ölçtü: anahtar bir yerde, çözebilen özne sayılı, döndürme belli sayıda kaydı yeniden yazıyor. Depoya giren yapılandırma artık bildirilmiştir — ama bildirilen yalnız yapılandırmadır. Hattın ürettiği çıktının içinde ne olduğu hâlâ hiçbir yerde yazmaz.
Bu ders o beyanı üretir. Ölçü üç sayıdır: envanter kaç bileşeni görüyor, kaçını hiç göremiyor, bir kusur duyurusu geldiğinde yanıt kaç sorguya iniyor. M22/K02 taramanın yakaladığı ve kaçırdığı kusurları saymıştı; o sayım tekrarlanmaz. Buradaki nesne envanterin kendisidir — hattın ürettiği bir çıktı olarak.
DD19. Kurgu bölgesel ölçüm ağı dört servistir ve 64 bileşenlik bir havuzu paylaşır; ağaç node
ile diske yazılır ve bir modeldir. DD20. Envanter üreteci yalnız beyan dosyalarını okur:
bildirim, çözüm ve derleme. DD21. Beyan edilmeyen üç yol vardır — kaynağa kopyalanmış, ikiliye
gömülü, koşum anında inen; üretecin tohumu 20260214 görünürdür.
Envanteri Üretmek
// olcum-agi/envanter.mjs — kurgu bagimlilik agaci diske yazilir, envanter uretilir import { mkdirSync, rmSync, writeFileSync, readFileSync, statSync } from "node:fs"; import { join, dirname } from "node:path"; const KOK = join(process.env.TMPDIR ?? "/tmp", "olcum-agi-envanter"); rmSync(KOK, { recursive: true, force: true }); const uretec = (tohum) => () => (tohum = (tohum * 1103515245 + 12345) % 2147483648) / 2147483648; const r = uretec(20260214); // tohum gorunur const sec = (d) => d[Math.floor(r() * d.length)]; // Kurgu bolgesel olcum agi: dort servis. Havuz paylasilir, ayni bilesen birden cok serviste gecer. const HAVUZ = Array.from({ length: 64 }, (_, i) => `bilesen-${String(i + 1).padStart(2, "0")}`); const SURUM = ["1.4", "2.0", "2.3", "2.6", "3.1", "3.4"]; const SERVIS = [ // kopya/gomulu/kosum: beyan edilmeyen bilesenler { ad: "toplayici", dogrudan: 9, arac: 5, kopya: 2, gomulu: 1, kosum: 1, ickopya: 1 }, { ad: "dogrulayici", dogrudan: 7, arac: 4, kopya: 1, gomulu: 1, kosum: 0, ickopya: 1 }, { ad: "faturalama", dogrudan: 12, arac: 6, kopya: 2, gomulu: 2, kosum: 2, ickopya: 1 }, { ad: "is-emri", dogrudan: 6, arac: 4, kopya: 1, gomulu: 0, kosum: 1, ickopya: 0 }, ]; const yaz = (yol, icerik) => { mkdirSync(dirname(yol), { recursive: true }); writeFileSync(yol, icerik); }; for (const s of SERVIS) { const alinan = new Set(); const yeni = () => { let a; do { a = sec(HAVUZ); } while (alinan.has(a)); alinan.add(a); return a; }; const dogrudan = {}, cozum = {}, araclar = {}; for (let i = 0; i < s.dogrudan; i++) { const ad = yeni(); cozum[ad] = { surum: (dogrudan[ad] = sec(SURUM)), gerektiren: "" }; for (let k = 0, n = Math.floor(r() * 4); k < n; k++) // gecisli cocuklar cozum[yeni()] = { surum: sec(SURUM), gerektiren: ad }; } for (let i = 0; i < s.arac; i++) araclar[yeni()] = sec(SURUM); yaz(join(KOK, s.ad, "bildirim.json"), JSON.stringify({ servis: s.ad, dogrudan }, null, 1)); yaz(join(KOK, s.ad, "cozum.json"), JSON.stringify(cozum, null, 1)); yaz(join(KOK, s.ad, "derleme.json"), JSON.stringify({ araclar }, null, 1)); for (let i = 0; i < s.kopya; i++) // kaynaga kopyalanmis ucuncu taraf yaz(join(KOK, s.ad, "kaynak", `k${i}.js`), `// kopyalanmis: ${yeni()}@${sec(SURUM)}\n`); for (let i = 0; i < s.ickopya; i++) // kendi kodu, ucuncu taraf degil yaz(join(KOK, s.ad, "kaynak", `i${i}.js`), `// kopyalanmis: [email protected]\n`); for (let i = 0; i < s.gomulu; i++) // ikilinin icine gomulu yaz(join(KOK, s.ad, "cikti", "gomulu", `g${i}.bin`), Buffer.concat( [Buffer.from(`GOMULU:${yeni()}@${sec(SURUM)}\n`), Buffer.alloc(2048, "x")])); const inen = Array.from({ length: s.kosum }, () => `${yeni()}@${sec(SURUM)}`); yaz(join(KOK, s.ad, "cikti", "indirilenler.json"), JSON.stringify({ inen }, null, 1)); yaz(join(KOK, s.ad, "cikti", "govde.js"), `// ${s.ad} yapi ciktisi\n`); } // Envanter ureteci YALNIZ beyan dosyalarini okur; okunan dosya ve bayt sayilir. let dosya = 0, bayt = 0; const oku = (y) => { dosya++; bayt += statSync(y).size; return JSON.parse(readFileSync(y)); }; const envanter = []; for (const s of SERVIS) { const bildirim = oku(join(KOK, s.ad, "bildirim.json")).dogrudan; const cozum = oku(join(KOK, s.ad, "cozum.json")); for (const [ad, k] of Object.entries(cozum)) envanter.push({ servis: s.ad, ad, surum: k.surum, tur: ad in bildirim ? "dogrudan" : "gecisli", gerektiren: k.gerektiren }); for (const [ad, sur] of Object.entries(oku(join(KOK, s.ad, "derleme.json")).araclar)) envanter.push({ servis: s.ad, ad, surum: sur, tur: "derleme", gerektiren: "(derleme)" }); } const yolEnv = join(KOK, "envanter.json"); yaz(yolEnv, JSON.stringify(envanter, null, 1)); yaz(join(KOK, "maliyet.json"), JSON.stringify({ dosya, bayt })); const say = (t) => envanter.filter((k) => k.tur === t).length; console.log(`tohum 20260214; servis ${SERVIS.length}, havuz ${HAVUZ.length} bilesen`); console.log(`envanteri uretmek icin okunan beyan dosyasi ${dosya}, ${bayt} bayt`); console.log(`envanter kaydi ${envanter.length} -> dogrudan ${say("dogrudan")}, ` + `gecisli ${say("gecisli")}, derleme zamani ${say("derleme")}`); console.log(`envanter dosyasi ${statSync(yolEnv).size} bayt, ` + `benzersiz bilesen ${new Set(envanter.map((k) => k.ad)).size}`);
tohum 20260214; servis 4, havuz 64 bilesen envanteri uretmek icin okunan beyan dosyasi 12, 7124 bayt envanter kaydi 107 -> dogrudan 34, gecisli 54, derleme zamani 19 envanter dosyasi 12659 bayt, benzersiz bilesen 56
Dört servis toplam 34 doğrudan bağımlılık bildirir. Envanter 107 kayıt üretir: o 34’ün yanına 54 geçişli bileşen ve 19 derleme zamanı aracı gelir. Beyan edilenle taşınan arasındaki oran buradadır — ekip 34 ad yazar, çıktıya 88 bileşen girer. Yalnız doğrudan bağımlılıklara bakan bir kapı, beyan edilen 88 koşum bileşeninin 34’ünü (%39) görür.
Üretimin maliyeti ortamdan bağımsız niceliklerle yazılır: 12 beyan dosyası, 7.124 bayt okuma, 107 kayıt, 12.659 baytlık envanter. Süre basılmaz: süre makineye bağlıdır, okunan dosya sayısı değildir.
107 kaydın arkasında 56 benzersiz bileşen vardır: aynı bileşen birden çok serviste geçer. “Hangi servisler etkileniyor” sorusunun tek bir dosyadan yanıtlanabilmesi buna dayanır.
Envanterin Görmediği
Üreteç beyan dosyalarını okur. Beyan dosyası niyeti anlatır: neyin çekilmesi istendiğini. Çıktı ağacı ise sonucu taşır. İkisi aynı olmak zorunda değildir.
DD22. Derleme zamanı araçları üretimde koşmaz; koşan bileşen kümesine girmez. DD23. Denetçi
ağacı gezer ve kopyalanmis: ya da GOMULU: başlığı taşıyan her dosyayı beyan dışı sayar; kendi
kodunun kopyası da aynı başlığı taşır.
// olcum-agi/kapsam.mjs — envanterin gormedigi: cikti agaci gezilir, beyanla karsilastirilir import { readdirSync, readFileSync, writeFileSync, statSync } from "node:fs"; import { join } from "node:path"; const KOK = join(process.env.TMPDIR ?? "/tmp", "olcum-agi-envanter"); const envanter = JSON.parse(readFileSync(join(KOK, "envanter.json"))); const maliyet = JSON.parse(readFileSync(join(KOK, "maliyet.json"))); // ureticin okudugu const SERVIS = ["toplayici", "dogrulayici", "faturalama", "is-emri"]; const dolas = (kok, alt = "") => readdirSync(join(kok, alt), { withFileTypes: true }) .flatMap((g) => g.isDirectory() ? dolas(kok, join(alt, g.name)) : [join(alt, g.name)]); // Denetci beyan dosyalarini degil, agacin kendisini okur. let gezilenDosya = 0, gezilenBayt = 0; const bulgu = []; for (const s of SERVIS) { for (const yol of dolas(join(KOK, s))) { if (yol.endsWith(".json")) continue; // beyan dosyalari denetcinin girdisi degil const tam = join(KOK, s, yol); gezilenDosya++; gezilenBayt += statSync(tam).size; const bas = readFileSync(tam).subarray(0, 120).toString("latin1"); const k = bas.match(/kopyalanmis: (\S+)@(\S+)/); if (k) bulgu.push({ servis: s, ad: k[1], surum: k[2], nasil: "kaynaga kopyalanmis" }); const g = bas.match(/GOMULU:(\S+)@(\S+)/); if (g) bulgu.push({ servis: s, ad: g[1], surum: g[2], nasil: "ikiliye gomulu" }); } const { inen } = JSON.parse(readFileSync(join(KOK, s, "cikti", "indirilenler.json"))); for (const kayit of inen) { const [ad, surum] = kayit.split("@"); bulgu.push({ servis: s, ad, surum, nasil: "kosum aninda inen" }); } } // Beyan edilmis kosum bileseni: derleme araclari uretimde kosmaz, kosum kumesine girmez. const beyanli = new Set(envanter.filter((k) => k.tur !== "derleme").map((k) => `${k.servis}|${k.ad}`)); const ucuncuTaraf = (ad) => ad.startsWith("bilesen-"); const beyansiz = bulgu.filter((b) => ucuncuTaraf(b.ad) && !beyanli.has(`${b.servis}|${b.ad}`)); const yanlisTutulan = bulgu.filter((b) => !ucuncuTaraf(b.ad)); const say = (nasil) => beyansiz.filter((b) => b.nasil === nasil).length; const kosanBeyanli = envanter.filter((k) => k.tur !== "derleme").length; const kosanToplam = kosanBeyanli + beyansiz.length; console.log(`envanterin gordugu kosum bileseni ${kosanBeyanli}, ` + `derleme zamani ${envanter.length - kosanBeyanli}`); console.log(`envanterin gormedigi ${beyansiz.length} -> ` + `kaynaga kopyalanmis ${say("kaynaga kopyalanmis")}, ` + `ikiliye gomulu ${say("ikiliye gomulu")}, kosum aninda inen ${say("kosum aninda inen")}`); console.log(`kapsam %${(100 * kosanBeyanli / kosanToplam).toFixed(0)} ` + `(${kosanBeyanli}/${kosanToplam} kosan bilesen)`); const SUTUN = [28, 22, 10, 16, 0]; const satir = (s) => console.log(s.map((v, i) => String(v).padEnd(SUTUN[i])).join("")); console.log(""); satir(["kapi", "okudugu", "tuttugu", "yalniz kendisi", "yanlis tuttugu"]); satir(["beyan dosyalarini okur", `${maliyet.dosya} dosya / ${maliyet.bayt} bayt`, 0, 0, 0]); satir(["cikti agacini gezer", `${gezilenDosya} dosya / ${gezilenBayt} bayt`, beyansiz.length, beyansiz.length, yanlisTutulan.length]); console.log(`yanlis tutulan ${yanlisTutulan.length} kayit: ` + `${yanlisTutulan.map((b) => b.servis).join(", ")} -> ${yanlisTutulan[0].ad}`); // Gercek kosan kume sonraki bloga birakilir: beyanli + beyansiz. writeFileSync(join(KOK, "gercek.json"), JSON.stringify([ ...envanter.filter((k) => k.tur !== "derleme") .map(({ servis, ad, surum }) => ({ servis, ad, surum, gorunur: true })), ...beyansiz.map(({ servis, ad, surum }) => ({ servis, ad, surum, gorunur: false })), ]));
envanterin gordugu kosum bileseni 88, derleme zamani 19 envanterin gormedigi 14 -> kaynaga kopyalanmis 6, ikiliye gomulu 4, kosum aninda inen 4 kapsam %86 (88/102 kosan bilesen) kapi okudugu tuttugu yalniz kendisi yanlis tuttugu beyan dosyalarini okur 12 dosya / 7124 bayt 0 0 0 cikti agacini gezer 17 dosya / 8685 bayt 14 14 3 yanlis tutulan 3 kayit: toplayici, dogrulayici, faturalama -> ic-kitaplik-olcum
Koşan 102 bileşenin 88’i envanterde görünür; 14’ü görünmez — 6’sı kaynağa kopyalanmış, 4’ü ikiliye gömülü, 4’ü koşum anında inen. Kapsam %86’dır. Eksik %14 ölçünün en pahalı parçasıdır, çünkü envanter bu bileşenleri “yok” diye değil, hiç göstermeyerek raporlar.
Tablo iki kapıyı yan yana koyar. Beyan dosyalarını okuyan kapı beyan dışı bileşenlerin sıfırını tutar; tutamaz, çünkü baktığı yer beyanın kendisidir. Kapı olarak marjinal değeri sıfırdır ve kapı olarak durması gerekmez: envanteri üretmesi yeterlidir, geçişe izin verme yetkisi ona hiçbir şey kazandırmaz. Kaldırılması daha iyi olan kapı budur.
Çıktı ağacını gezen kapı 14’ün 14’ünü tutar ve hepsini yalnız kendisi tutar — marjinal değeri tuttuğunun tamamıdır. Okuma bedeli küçüktür: 12 dosya ve 7.124 bayta karşı 17 dosya ve 8.685 bayt, farklı bir okuma, daha büyük bir okuma değil. Asıl bedel yanlış tutmadır: 17 bulgunun 3’ü kendi kodunun kopyasıdır. Her altı bulgudan biri boşuna bir incelemedir ve bu oranın ürettiği davranış bilinir — liste bir kez toptan “bilinen” diye işaretlenir, sonraki gerçek bulgu da aynı işaretin altında kalır.
Duyuru Geldiğinde
Envanterin değeri üretildiği anda değil, sorulduğu anda ölçülür. Soru hep aynı biçimdedir: şu bileşenin şu sürümünün altındakiler açık — bizde var mı, nerede?
DD24. Kusur duyurusu bir bileşen adı ve bir eşik sürümüdür; eşiğin altındaki sürümler açık
sayılır. Duyuru çekilişinin tohumu 884301 görünürdür.
// olcum-agi/duyuru.mjs — kusur duyurusu geldiginde yanit: envanterli ve envantersiz yol import { readFileSync } from "node:fs"; import { join } from "node:path"; const KOK = join(process.env.TMPDIR ?? "/tmp", "olcum-agi-envanter"); const SERVIS = ["toplayici", "dogrulayici", "faturalama", "is-emri"]; const SURUM = ["1.4", "2.0", "2.3", "2.6", "3.1", "3.4"]; const oku = (ad) => JSON.parse(readFileSync(join(KOK, ad))); const envanter = oku("envanter.json"), maliyet = oku("maliyet.json"); const gercek = oku("gercek.json"); // onceki blogun denetimi: beyanli + beyansiz const uretec = (tohum) => () => (tohum = (tohum * 1103515245 + 12345) % 2147483648) / 2147483648; const r = uretec(884301); // duyuru cekilisinin tohumu gorunur const ADLAR = [...new Set(gercek.map((k) => k.ad))].sort(); const DUYURU = Array.from({ length: 9 }, () => ({ ad: ADLAR[Math.floor(r() * ADLAR.length)], esik: SURUM[1 + Math.floor(r() * (SURUM.length - 1))], // bu surumun altindakiler acik })); const etkilenen = (kume, d) => new Set(kume .filter((k) => k.ad === d.ad && SURUM.indexOf(k.surum) < SURUM.indexOf(d.esik)) .map((k) => k.servis)); console.log(`gercek kosan kayit ${gercek.length} ` + `(gorunmeyen ${gercek.filter((k) => !k.gorunur).length}), duyuru ${DUYURU.length}; tohum 884301`); console.log(""); console.log("duyuru".padEnd(24) + "envanter yaniti".padEnd(17) + "gercek".padEnd(9) + "durum"); let dogru = 0, eksik = 0, etkiliDuyuru = 0; for (const d of DUYURU) { const yanit = etkilenen(gercek.filter((k) => k.gorunur), d), ger = etkilenen(gercek, d); if (ger.size) etkiliDuyuru++; yanit.size === ger.size ? dogru++ : eksik++; console.log(`${d.ad} < ${d.esik}`.padEnd(24) + `${yanit.size} servis`.padEnd(17) + `${ger.size} servis`.padEnd(9) + (yanit.size === ger.size ? "tam" : "eksik")); } const BEYAN = SERVIS.length * 3; // depo basina bildirim, cozum, derleme console.log(""); console.log("yol".padEnd(30) + "duyuru basina".padEnd(26) + `${DUYURU.length} duyuru icin`); console.log("envanter dosyasina sorgu".padEnd(30) + `1 sorgu / ${envanter.length} kayit`.padEnd(26) + `1 uretim (${maliyet.dosya} dosya) + ${DUYURU.length} sorgu`); console.log("depolari tek tek acmak".padEnd(30) + `${SERVIS.length} depo / ${BEYAN} dosya`.padEnd(26) + `${DUYURU.length * SERVIS.length} depo acilisi, ${DUYURU.length * BEYAN} dosya`); console.log(""); console.log(`etkilenen servisi olan duyuru ${etkiliDuyuru}/${DUYURU.length}; ` + `envanterin tam yanitladigi ${dogru}, eksik yanitladigi ${eksik}`);
gercek kosan kayit 102 (gorunmeyen 14), duyuru 9; tohum 884301 duyuru envanter yaniti gercek durum bilesen-47 < 2.6 0 servis 1 servis eksik bilesen-49 < 3.4 0 servis 1 servis eksik bilesen-59 < 2.3 0 servis 0 servis tam bilesen-41 < 2.0 0 servis 0 servis tam bilesen-12 < 2.0 0 servis 0 servis tam bilesen-12 < 2.3 0 servis 0 servis tam bilesen-05 < 3.1 1 servis 1 servis tam bilesen-58 < 2.3 0 servis 0 servis tam bilesen-15 < 2.6 1 servis 2 servis eksik yol duyuru basina 9 duyuru icin envanter dosyasina sorgu 1 sorgu / 107 kayit 1 uretim (12 dosya) + 9 sorgu depolari tek tek acmak 4 depo / 12 dosya 36 depo acilisi, 108 dosya etkilenen servisi olan duyuru 4/9; envanterin tam yanitladigi 6, eksik yanitladigi 3
Dokuz duyurunun beşi hiçbir servisi etkilemez. En sık verilen yanıt “bizde yok”tur ve envanterin değeri öncelikle buradadır: o yanıt tek sorguda gelir. Envantersiz yol aynı yanıta ulaşmak için her duyuruda dört depoyu açar, on iki beyan dosyası okur — dokuz duyuru için 36 depo açılışı ve 108 dosya.
Bu, kapının eklediği sürenin yönünü belirler. Envanter üretimi hatta bir aşama ekler ve o aşama her değişiklikte 12 dosya okur; buna karşılık yanıt duyuru başına dört depo açılışından bir sorguya düşer. Kapının bedeli değişiklik başına, getirisi duyuru başınadır; oranı duyuru sıklığı belirler, kapının kendisi değil.
Son satır kapsamın bedelidir. Envanter 9 duyurunun 6’sını tam, 3’ünü eksik yanıtlar. İkisinde
yanıt “0 servis”, gerçek “1 servis”tir; duyuru ilgisiz diye kapatılır. Üçüncüsü daha kötüdür:
bilesen-15 için envanter “1 servis” der, gerçek “2 servis”tir. Sıfır bir yanıt bazen sorgulanır,
bir sayı sorgulanmaz — iş yapılmış görünür. Görünmeyen 14 bileşenin bedeli budur ve çıktı
ağacını gezen kapı kaldırıldığında geri gelir.
Özet
- Dört servis 34 doğrudan bağımlılık bildirir; envanter 107 kayıt üretir — 34 doğrudan, 54 geçişli, 19 derleme zamanı aracı. Yalnız doğrudan bağımlılığa bakan bir kapı beyan edilen 88 koşum bileşeninin 34’ünü görür.
- Üretim maliyeti ortamdan bağımsız yazılır: 12 beyan dosyası, 7.124 bayt okuma, 107 kayıt, 12.659 baytlık envanter, 56 benzersiz bileşen.
- Koşan 102 bileşenin 88’i envanterde görünür, 14’ü görünmez: 6 kaynağa kopyalanmış, 4 ikiliye gömülü, 4 koşum anında inen. Kapsam %86’dır.
- Beyan dosyalarını okuyan kapı beyan dışı bileşenin sıfırını tutar ve kapı olarak kaldırılabilir; çıktı ağacını gezen kapı 14’ünü tutar, hepsini yalnız kendisi tutar, bedeli 17 bulgunun 3’ünü yanlış tutmasıdır.
- Yanıt duyuru başına 4 depo açılışından 1 sorguya iner: 9 duyuru için 36 depo açılışına karşı bir üretim ve 9 sorgu. Envanter 6 duyuruyu tam, 3’ünü eksik yanıtlar; eksiklerden biri “0” değil “1” der.
Sonraki Adım
Envanter artık var, kapsamı sayılı ve sorgulanabilir. Ama 12.659 baytlık bu dosya ile tarif ettiği çıktı arasında hiçbir bağ yoktur. Envanter ayrı bir dosyadır: yanına başka bir envanter konabilir, içindeki bir kayıt değiştirilebilir, bambaşka bir yapı çıktısı aynı envanterle eşleştirilebilir. 107 kaydın hiçbiri, tarif ettiği baytların gerçekten o baytlar olduğunu söylemez.
Aynı boşluk zincirin öteki halkalarında da vardır: kaynak işlemesi ile yapı çıktısı arasında, yapı çıktısı ile dağıtılan şey arasında. Bir sonraki ders bu halkaları gerçek bir anahtar çiftiyle bağlar ve iki soruyu sayıya çevirir: doğrulama zincirin hangi noktasında yapılırsa kaç sahte çıktı geçer, ve o anahtarı elinde tutan kaç özne vardır?
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.