Liwati menyang isi utama
OpenAI

30 Juni 2026

Rekayasa

Epidemiologi core dump: ndandani bug 18 taun

Nggunakake analisis tingkat populasi kanggo debug crash angel ing infrastruktur data kita.

Lagi dimuat…

Model lan agen OpenAI saya ngandelake infrastruktur data sing bisa diskalakake kanggo nggoleki data sing relevan nalika inferensi: nalika model mikir babagan pitakonan sampeyan. Sawetara layanan iki ditulis nganggo C++, sing kontrol tingkat-andhap marang sistem ngidini kita nggedhekake kinerja lan nyuda panggunaan memori. Kauntungan efisiensi kuwi wigati nalika kita ngeskalakake, nanging C++ ora nduweni keamanan memori, mula bug bisa nyebabake crash amarga nulis menyang alamat memori sing salah utawa ora ana.

Sawetara wulan kepungkur, kita ndeleng sawetara crash saka njero layanan Rockset, bagean khusus saka infrastruktur data ChatGPT sing penting kanggo akeh plugin data lan kanggo nggoleki ing pacelathon. Ing saben crash iki, fungsi C++ normal katon rampung banjur bali menyang alamat palsu, nyebabake kernel mandhegake program amarga instruction pointer ora maneh nunjuk menyang kode. Kadhang slot alamat bali ing stack frame iku NULL. Kadhang register CPU stack pointer dhewe katon mleset 8 byte, kaya-kaya %rsp piye wae dikurangi ing tengah eksekusi normal. Ing loro kasus kasebut, crash kedadeyan nalika bali.

Iki dudu pola gagal normal kanggo kode aplikasi. Tulisan kesasar sing kena mung alamat bali sing disimpen iku bisa wae, nanging arang banget. Bug sing nggawe %rsp mleset 8 tanpa melu inline assembly, setcontext, utawa longjmp (ora ana sing kita gunakake) luwih aneh maneh, amarga kode kompilasi mung nyetel register kuwi langsung ing prolog lan epilog fungsi. Saben hipotesis sing bisa kita (utawa ChatGPT) pikirake nduweni bukti kuwat sing mbantah, mula bug kasebut katon mokal.

Sing wiwitane kita anggep siji masalah, pungkasane jebul loro bug sing ora ana gandhengane, kebetulan ditemokake bebarengan. Kaping pisan, korupsi hardware senyap ing siji host Azure, nalika CPU ora ngetung kanthi bener. Kaping pindho, race condition umur 18 taun ing GNU libunwind, bug sing ora katon ing pustaka open source sing akeh digunakake.

Tulisan iki nyritakake carane kita ngenali lan ndandani crash sing katon ora bisa diterangake kanthi mikir kaya epidemiolog lan mbangun kumpulan data bermutu babagan kabeh populasi crash.

Upaya debugging pisanan: mriksa sawetara core dump kanthi teliti

Kaping pisan, ayo luwih jero babagan Rockset. Iki sistem data cloud-native kanggo telusur lan analitik wektu nyata sing kita gunakake kanggo akeh kabutuhan internal ing OpenAI, kayata sync connector (Rockset diakuisisi OpenAI ing 2024). Update streaming digunakake kanggo njaga indeks paling anyar saka basis kawruh papan kerja supaya ChatGPT bisa nggoleki informasi relevan nalika mangsuli pitakonan utawa nindakake tumindak.

Lapisan eksekusi Rockset ditulis nganggo C++. Basa C++ menehi akses tingkat-andhap menyang CPU, apik kanggo kinerja lan efisiensi, nanging tegese bug aplikasi bisa nyebabake akses memori ora valid lan segfault. Kanggo mbantu nelusur, kita nganggo fatal signal handler saka folly kanggo nyathet stack trace nalika crash, lan ngunggah core dump sing cocog (snapshot kahanan program nalika crash) menyang Azure blob storage kanggo dianalisis mengko. Kabeh leaf pangolahan kueri Rockset direplikasi, supaya dampak crash marang klien minimal. Nanging saben segfault nuduhake ana bug sing kudu didandani supaya nggayuh target reliabilitas lan mutu kita.

Pendekatan awal kita yaiku nganggep core iki kaya masalah debugging konvensional: mriksa sawetara core dump kanthi cedhak, nggawe hipotesis, lan nyingkirake siji-siji.

Umume crash kedadeyan ing metode sing diarani DocumentTree::updateDocument. Ing crash iki, katon updateDocument wis nelpon fungsi ora dingerteni X, stack dadi korup nalika X aktif, banjur X bali menyang alamat sing dudu kode eksekusi. Ing sawetara kasus, frame X sing lagi wae dipop katon valid kajaba alamat bali sing disimpen iku NULL. Ing kasus liyane, stack pointer dhewe katon salah, nanging frame valid sabanjure isih kaya updateDocument.

Kita ora ngerti kapan stack wiwit korup, saengga ruang telusure dadi gedhe banget. updateDocument iku metode gedhe sing akeh di-inline, mula calon kanggo X kakehan banget.

Apa iki bug ing kode C++ kita? Masalah compiler utawa linkage? Masalah ing salah siji pustaka runtime kita? Bug kernel Linux babagan pangiriman sinyal utawa context switching? Utawa sing luwih langka maneh? Yen iki tulisan kesasar, kenapa ora ketangkep dening lingkungan staging ASAN kita?

Kita nyoba nganggo log tingkat aplikasi kanggo ngenali kabeh kemunculan masalah iki, nanging bug korupsi stack angel diklasifikasikake saka log wae amarga stack trace sing dicathet dhewe korup utawa ilang. Kita ora bisa nggawe kueri log sing bebas saka false positive lan false negative. Kita mriksa luwih akeh core kanthi manual lan nemokake conto tambahan, nanging proses kuwi kakehan tenaga kanggo menehi kumpulan data sing bisa dipercaya.

Ing tahap investigasi iki, kita (kanthi salah) nyingkirake kemungkinan bug hardware, amarga kita ndeleng crash ing pirang-pirang wilayah lan jinis hardware, mula kita isih nggoleki sebab mung software. Sajrone sawetara dina, kita nyilem jero ing siji crash %rsp-mleset, nyusun maneh riwayat sadurunge crash nganggo isi stack lan register. Iki ngasilake sawetara pitunjuk, nanging amarga kita ora ngeculake kesimpulan awal manawa kabeh bug duwe sebab sing padha, iki ora nggawe kita maju.

Pitunjuk saka stack

Sadurunge tekan titik balik investigasi, penting nerangake jinis informasi sing kita jupuk saka file core.

Rockset dikompilasi nganggo -fno-omit-frame-pointer, mula stack frame aktif tansah bisa digayuh liwat %rbp, lan caller mbentuk linked list frame pointer.

Ing Linux x86_64, ABI AMD64 System V uga nyisihake 128 byte ing ngisor %rsp minangka red zone. Wilayah kuwi kasedhiya kanggo kode userspace lan, sing penting, kernel janji ora bakal ngrusak nalika ngirim sinyal, minangka bagean saka kontrak ABI.

Red zone dadi kunci debugging crash sawise return, amarga nyimpen sawetara informasi saka sadurunge return. Nalika SIGSEGV dipicu, fatal signal handler saka folly mlaku ing stack thread sing crash. Stack frame sing ora aktif maneh (amarga fungsine wis return) bakal ketimpa signal handler, kajaba 128 byte pungkasan. Mula kita bisa ngomong kaya “stack frame X sing lagi wae dipop katon valid, kajaba alamat bali NULL.” Red zone nyimpen sebagian frame ora aktif, utawa kadhang mung buntut siji frame ora aktif.

Diagram stack sing nuduhake stack frame korup sing bisa nimpa alamat bali lan nyebabake crash.

Kita nemokake siji crash stack-mleset nalika kabeh fungsi sing melu cilik banget. Kuwi nggawe kita weruh yen %rsp dadi mleset nalika eksekusi fungsi sing relatif prasaja, lan sawisé kuwi isih ana panggilan sing sukses. Program mung crash nalika fungsi aktif pungkasane nyoba return. Ora ana jalur kode kasebut sing nganggo exception, inline assembly, setcontext, utawa longjmp, mula yen stack pointer pancen owah kaya sing dituduhake core, ora ana bug userspace sing masuk akal bisa nerangake masalah iki.

Iki nyurung kita menyang kernel.

Rockset nggunakake sinyal luwih agresif tinimbang umume program. Eksekusi kueri dipecah dadi akeh tugas entheng sing ijol data. Iki penting kanggo nangani beban kerja QPS dhuwur kanthi efisien, nanging nggawe akuntansi CPU saben kueri dadi angel amarga kerja kanggo akeh kueri dimultipleks menyang thread pool sing padha.

Solusi kita yaiku sing diarani coarse_thread_cputime_clock, sing ngira-ira clock_gettime(CLOCK_THREAD_CPUTIME_ID, ...) kanthi cukup murah supaya bisa sampling ing saben wates tugas. API timer_create bisa digunakake kanggo njadwalake pangiriman sinyal periodik adhedhasar sawetara cara ngukur lumakune wektu, kalebu akumulasi wektu CPU. Kita njadwalake sinyal (SIGUSR2) dikirim saben sawetara milidetik wektu CPU, lan nalika iku signal handler nganyari nilai lokal-thread. Sanajan akeh tugas ora weruh coarse clock maju nalika dieksekusi, penjumlahan kabeh delta menehi perkiraan tanpa bias babagan wektu CPU nyata kanggo kueri.

Amarga kita kerep banget ngirim sinyal, bug kernel langka babagan context switching utawa pangiriman sinyal katon masuk akal. Kita ngentekake wektu maca laporan bug, kode sumber kernel, lan patch kernel khusus Azure. Kita nyoba stress test. Kita ora bisa nemokake apa wae sing katon gegandhengan.

Ing titik kuwi, kita mutusake mundur sedhela lan nyoba pendekatan liya.

Dokter utawa epidemiolog?

Ana rong cara umum kanggo debugging masalah kaya iki.

Siji yaiku tumindak kaya dokter: fokus ing siji pasien, nindakake akeh tes, lan nyoba ndiagnosis siji kasus saka bukti rinci.

Liyane yaiku luwih kaya epidemiolog: ndeleng kabeh populasi lan takon apa ana pola sing ora bisa dibukak dening siji kasus wae. Apa bug diwiwiti ing rilis tartamtu? Apa ana korelasi karo siji SKU hardware (CPU lan model server tartamtu), siji wilayah, utawa siji versi kernel? Apa ana pirang-pirang klaster beda sing ndhelik ing njero sing katon kaya siji sindrom?

Kita sadurunge luwih akeh ana ing mode dokter. Pergeseran kuncine yaiku mutusake yen kita kudu nglumpukake data populasi bermutu.

Ngresiki data

Upaya sadurunge kanggo nemokake kabeh conto masalah kanthi otomatis gagal amarga kita nyoba nggunakake telusuran teks ing log. Core dump dhewe nduweni informasi luwih akeh, nanging mriksa manual ora bisa diskalakake. Kita mutusake nandur usaha kanggo mbangun pipeline sing bisa nganalisis core dump kanthi otomatis.

Kita njaluk ChatGPT nulis skrip sing ngundhuh prefiks saben file core, ngekstrak register, nyaring false positive sing wis dingerteni nganggo log, lan otomatis menehi label crash minangka return-to-null, misaligned-stack, utawa liyane. Banjur skrip kasebut kita jalanake paralel ing saben core dump Rockset produksi saka taun sadurunge.

Iki titik baliké.

Sawise duwe kumpulan data resik, korelasi langsung katon. Sing sadurunge kita anggep siji bug aneh, sejatine loro populasi crash sing kapisah.

Core return-to-null kasebar ing akeh klaster lan wilayah geografis. Frekuensine bubar mundhak, nanging ora ana tanggal wiwitan sing cetha lan ora ana wates infrastruktur sing resik.

Crash misaligned-stack katon beda babar pisan. Kabeh saka siji wilayah, duwe tanggal wiwitan cetha, lan ora tau kedadeyan ing simpul sing wis mlaku suwe. Sanajan melu pirang-pirang VM Azure (mesin virtual sing di-host ing cloud), polane katon kaya siji mesin fisik kanthi hardware ala sing nyebabake masalah kanggo VM apa wae sing kebeneran mlebu ing kana.

Plot titik tingkat crash miturut klaster saka wektu ke wektu, nuduhake umume crash ngumpul ing klaster 2, 3, lan 6, kanthi lonjakan ing klaster 1 cedhak pungkasan periode.

Ing wektu kuwi kita sadar yen ing pikiran kita wis nyampur loro bug. Amarga kita nyampur conto bantahan saka loro bug, kita ora bisa nemokake siji panjelasan sing koheren.

Bug #1: host ala

Kanthi dhaptar simpul Kubernetes lan timestamp sing resik, kita bisa nelusuri crash stack-mleset bali menyang siji host fisik, sing gampang dilebokake denylist.

Kita ora bisa ngulang korupsi register ing host kasebut ing lingkungan terkontrol, sanajan sawise pirang-pirang minggu stress testing. Nanging, sawise host bermasalah dicopot saka layanan, crash stack-mleset ilang.

Mbusak host ala dudu solusi permanen, amarga ora nyegah kedadeyan anyar saka masalah sing padha. Nanging kita bisa ngganti software supaya yen masalah serupa mbaleni, gampang dideteksi lan ditangani. Kita ningkatake fatal signal handler supaya kalebu state register, saengga bisa ndeteksi kambuh mung saka log (ora perlu core dump). Kita ngganti control plane supaya VM biasane digunakake maneh, dudu didaur ulang, sing nggawe deteksi simpul ala luwih gampang ing tingkat stack infrastruktur kita. Kita uga nganyari runbook (lan model mental tim) supaya kalebu kemungkinan iki.

Sawise crash host ala dipisahake, core return-to-null sing isih ana dadi luwih gampang dipikirake. Sadurunge, kita wis nyingkirake exception unwinding amarga mikir duwe conto bantahan: crash ing jalur kode sing mesthi ora nganggo exception. Nanging conto bantahan kuwi kabeh saka klaster korupsi hardware.

Sawise mriksa maneh core sing isih ana kanthi pikiran kuwi, kita nemokake kesimpulan iki pas kebalik: kabeh crash kedadeyan nalika exception unwinding.

Penanganan exception minangka transfer kontrol dinamis

Nalika C++ mbuwang exception, runtime kudu nemokake blok catch endi sing kudu nampa lan destructor utawa handler pembersih endi sing kudu mlaku ing dalane. Compiler ngasilake metadata iki, nanging pencocokan nyata kedadeyan dinamis nalika runtime.

Exception unwinding sejatine ora ditindakake dening fungsi sing nyeluk throw, nanging dening fungsi pembantu sing diarani kode kompilasi asilé. Rutinitas runtime kasebut mriksa stack, njupuk metadata babagan fungsi ing stack, nggoleki handler pembersih lan blok catch kanthi dinamis, banjur mindhah kontrol menyang salah siji lokasi kasebut. Mindhah kontrol kalebu unwinding kabeh stack frame ing antarane (kalebu frame fungsi pembantu).

Saka sisi operasional, iki luwih cedhak karo longjmp utawa fiber switch tinimbang call lan return normal. Register callee-save kudu dipulihake, uga register stack frame %rbp lan %rsp.

Binary kita link menyang rong pustaka sing ngemot implementasi fungsi kanggo C++ exception unwinding: libgcc lan GNU libunwind. Definisi GNU libunwind sing dipilih dening dynamic linker. Iki nggumunake; kita ngarepake implementasi libgcc sing menang amarga aturan symbol versioning, nanging mriksa binary sing mlaku nuduhake ora kaya ngono.

Mbatalake asumsi pungkasan

Ing titik iki hipotesis kerja kita owah, nalika kita ngeculake asumsi liyane sing digawe nalika mikir mung ana siji bug.

Mbokmenawa sing kita deleng dudu fungsi biasa bali menyang NULL. Mbokmenawa kita ndeleng transfer unwind—praktise pemulihan register gaya setcontext—nalika instruction pointer tujuan wis dadi NULL sadurunge kontrol dipindhah. Tegese, data salah saka pustaka unwind, dudu slot alamat bali sing salah ing stack.

Iki nyempitake masalah kanthi drastis. GNU libunwind entuk state tujuan sing salah, utawa entuk state bener nanging ana sing ngrusak sadurunge bisa diterapake.

Kita maca sumber GNU libunwind lan nemokake yen dheweke nyintesis ucontext_t ing stack, ngisi state register sing dikarepake kanggo frame handler pembersih, banjur masrahake pointer menyang struct kasebut menyang rutin assembly internal: _Ux86_64_setcontext.

Ing titik iki, kabeh potongane wis ana.

ucontext_t sing disintesis urip ing salah siji stack frame sing di-unwind dening _Ux86_64_setcontext, sajrone eksekusi fungsi kasebut. Apa _Ux86_64_setcontext maca saka struct sawise ngganti %rsp, nalika struct kasebut wis ora dadi bagean saka stack aktif? Yen ngono, iki rentan ketimpa pangiriman sinyal, kayata SIGUSR2 kita sing kerep.

Bug #2: bug libunwind

Wangsulane ya.

Iki enem instruksi pungkasan saka _Ux86_64_setcontext ing versi GNU libunwind sing kita gunakake, sing umume dumadi saka instruksi mov sing ngemot saka memori menyang register tujuan:

Teks Polos

1
74: mov UC_MCONTEXT_GREGS_RSP(%rdi),%rsp
2
75:
3
76: /* push the return address on the stack */
4
77: mov UC_MCONTEXT_GREGS_RIP(%rdi),%rcx
5
78: push %rcx
6
79:
7
80: mov UC_MCONTEXT_GREGS_RCX(%rdi),%rcx
8
81: mov UC_MCONTEXT_GREGS_RDI(%rdi),%rdi
9
82: retq

(%rdi nunjuk menyang ucontext_t sing dialokasikan ing stack, lan makro UC_MCONTEXT_* mung ngembang dadi offset tetep ing ngendi register tartamtu disimpen.)

Instruksi pisanan yaiku wiwitan jendela race. Iki nganyari %rsp supaya nunjuk menyang dasar anyar saka stack aktif. Sanalika iki kedadeyan, struct sing ditunjuk %rdi ora maneh dadi bagean saka stack aktif (utawa red zone), lan ora maneh dilarang kanggo kernel.

Biasane iki ora nyebabake masalah, nanging yen sinyal teka pas ing wektu sing bener (utawa salah?), kernel bakal mbangun signal frame ing %rsp-128. Iki bisa nimpa memori sing ditunjuk dening %rdi.

Yen kedadeyan sadurunge instruksi sabanjure maca UC_MCONTEXT_GREGS_RIP(%rdi), instruction pointer sing dipulihake bisa korup. Ing crash kita, iki dadi NULL.

Kuwi bug-e.

Napa core katon kaya return ala biasa

Assembly iki uga nerangake salah siji pengamatan sing nggawe kita bingung: kenapa fungsi X nduweni NULL ing slot alamat bali saka stack frame sadurunge.

setcontext ditulis kanggo mulihake kabeh register, kalebu %rdi, mula ora bisa nggunakake register kuwi kanggo maca UC_MCONTEXT_GREGS_RIP(%rdi) ing wektu pungkasan transfer kontrol. Kosok baline, nilai kasebut diwaca luwih dhisik, disimpen menyang stack, mulihake sawetara register maneh, banjur nggunakake retq kanggo maca nilai sing disimpen lan mindhah kontrol.

Sing ing core katon kaya “fungsi bali menyang NULL” sejatine “unwinder nyintesis alamat bali target ing stack, nanging target kuwi korup sadurunge transfer rampung.” Kita nganggep korupsi slot alamat bali mesthi kedadeyan ing papan sing padha, amarga kita ora ngerti ana panggonan sing sengaja nulis data (sing bisa korup) menyang slot alamat bali.

Jendela race siji instruksi

Sing nggawe bug iki katon absurd yaiku sempite jendela race iki. Ing race condition kaya iki, kedadeyan eksternal (sinyal) kudu kedadeyan ing antarane rong langkah sing ditindakake thread liyane. Saya cedhak rong langkah kasebut, saya cilik kemungkinan race condition kedadeyan.

Ing kasus iki, jendela rentane sakjerone mung siji instruksi! Sinyal kudu dikirim sawise %rsp diganti, nanging sadurunge instruksi sabanjure ngemot %rip. Sawetara instruksi prasaja kaya iki bisa mlaku saben siklus ing CPU modern super-scalar out-of-order, mula jendela race kira-kira satus pikodetik.

Nalika nemokake race iki, reaksi pisanan kita: iki mesthine kakehan langka kanggo nerangake tingkat crash sing katon. Kita ndeleng luwih saka selusin crash return-to-null saben dina ing saindenging fleet. Apa race siji instruksi nalika ngresiki exception pancen bisa nyebabake kuwi?

Kita banjur nggunakake estimasi Fermat. Yen jendela rentan kira-kira 101010^{-10} detik lan SIGUSR2 teka saben 10210^{-2} detik wektu CPU, saben handler pembersih exception utawa blok catch duwe probabilitas kira-kira 10810^{-8} kanggo kalah race.

Rockset nggunakake exception minangka bagean saka mekanisme backpressure ingest internal. Siji host sing kakehan beban bisa mbuwang exception watara 10410^{4} saben detik. Iki ateges mean time between failures kanggo host sing nganggo backpressure yaiku 10410^{4} detik, utawa siji crash saben sawetara jam. Ing skala fleet, kuwi luwih saka cukup kanggo nerangake frekuensi crash sing katon.

Napa bug libunwind muncul saiki?

Bug GNU libunwind iku lawas—luwih saka 18 taun, wis ana ing versi x86_64 pisanan sing ndhukung C++ exception unwinding.

Dadi, napa saiki katon?

Tingkat crash kira-kira sebanding karo jumlah exception sing dibuwang lan jumlah sinyal sing dikirim. Iki uga gumantung marang sepira akeh stack sing dikonsumsi signal handler.

Rockset ora umum ing telung sumbu kasebut. Kita mbuwang exception kanthi tingkat dhuwur minangka bagean saka kontrol overload normal; kita ngirim SIGUSR2 luar biasa kerep amarga coarse_thread_cputime_clock; lan awal taun iki kita nggawe handler SIGUSR2 nganggo stack luwih akeh kanthi nambah panggilan menyang timer_getoverrun, supaya bisa ngitung sinyal sing kegabung.

Owah-owahan pungkasan kuwi katon penting. Yen handler nganggo stack cukup sithik, bisa uga ora tekan lan nimpa memori ucontext_t sing wis basi. Sadurunge owah-owahan kuwi, kita ora weruh crash iki babar pisan. Sawise owah-owahan, tingkate tetep endhek nganti kita nambah beban kanggo sawetara use case sing nekanisme backpressure.

Tegese, bug libunwind wis mesthi ana, nanging asil gabungan tingkat exception, tingkat sinyal, lan panggunaan stack handler kita mung bubar ngliwati ambang sing nggawe katon ing operasi.

Mekanisme iki uga nerangake kebetulan yen bug hardware lan bug libunwind padha-padha crash utamane ing DocumentTree::updateDocument. Crash saka libunwind cenderung banget menyang metode iki, amarga metode iki tansah aktif nalika kita mbuwang exception kanggo ngetrapake backpressure ingest. Crash mlesete %rsp uga banget kapilih ing kene amarga simpul hardware ala iku saka SKU sing kita gunakake kanggo bulk ingest, sing mayoritas wektu CPU-ne ana ing metode kasebut.

Mitigasi langsung kita yaiku ngalih saka GNU libunwind menyang unwinder libgcc. Iki pilihan sing apik dhewe: implementasi libgcc wis entuk akeh perbaikan kanggo nyuda lock contention, penting nalika skala menyang VM gedhe.

Kita uga upstream reproducer mandiri lan perbaikan(mbukak ing jendhela anyar) menyang GNU libunwind, lan mriksa manawa unwinder liyane ora duwe masalah serupa.

Kuwate diagnosis tingkat populasi

Perjalanan debugging iki ngajari kita akeh babagan rincian khusus dynamic linking, metadata unwind DWARF, pangiriman sinyal Linux, ABI System V, lan mekanisme exception C++. Nanging piwulang utamane luwih prasaja tinimbang kabeh kuwi.

Langkah paling penting dudu maca assembly sing pinter utawa kawruh rinci sing jero. Nanging mbangun kumpulan data bermutu. Tanpa kumpulan data iki, kita nyampur rong fenomena beda dadi siji crita lan nyoba mikir metu saka kebingungan. Sawise duwe data populasi sing akurat lan lengkap, struktur masalah dadi cetha: siji populasi crash kagungane host ala, lan liyane kagungane race ing libunwind. Nalika datane luwih apik, debugging dadi luwih gampang.

Kanggo sistem infrastruktur kaya Rockset, kuwi penting banget. Investigasi iki nguatake komitmen kita marang instrumentasi jero, investigasi otomatis, lan perbaikan terus-terusan kanggo tooling operasional. Reliabilitas ora mung babagan ndandani bug sawise kedadeyan—nanging babagan mbangun data, alur kerja, lan katrampilan sing ngowahi masalah mokal dadi bisa didiagnosis lan dipecahake.

Para panulis

By Nathan Bronson, Member of Technical Staff