סקריפטים שימושיים
- summary:
תיקיית scripts/ מכילה כלים חד-פעמיים ותהליכי תחזוקה. לפני ההרצה ודאו שסביבת ה-DB היא סביבת ניסוי/פיתוח ושיש גיבוי עדכני.
תיקיית scripts/ מכילה כלים חד-פעמיים ותהליכי תחזוקה. לפני ההרצה ודאו שסביבת ה-DB היא סביבת ניסוי/פיתוח ושיש גיבוי עדכני.
scripts/migrate_reminder_acked_status.py
מיישר תזכורות פתקים שאושרו לפני שהמצב הסופי
ackedהיה קיים: מסמך עםack_atמלא שעדיין ב-pendingאוsnoozedמקבלstatus="acked". הכיוון היחיד הוא מ“אושר“ ל“אושר“, ולכן הרצה חוזרת בטוחה.כמו שאר סקריפטי המיגרציה: בלי דגל הוא רק מדווח;
--applyכותב. הפלט מתחיל בשם מסד הנתונים שנפתר ובמספר המסמכים באוסף, כדי ש-DATABASE_NAMEשגוי ייראה לפני ההודעה ”אין מה לעדכן“.האימות אחרי הכתיבה סופר מחדש מהמסד, ורק מסמכים שאושרו לפני תחילת הריצה — אישור שנחת באמצע (למשל מפוד ישן בזמן דיפלוי) אינו כשל של הריצה הזו.
דוגמת הרצה:
python scripts/migrate_reminder_acked_status.py # דיווח בלבד
python scripts/migrate_reminder_acked_status.py --apply # כותב
scripts/dev_seed.py
זורע סניפטים מתוך
SNIPPETS.mdאל ספריית הסניפטים (idempotent).בודק שה-DB מקומי כדי למנוע טעויות; ניתן לעקוף עם
--forceאוALLOW_SEED_NON_LOCAL=1.משייך סניפטים למשתמש המוגדר בקובץ (user_id=0) ומסמן אותם כמאושרים.
דוגמת הרצה:
# עבודה מול DB מקומי
MONGODB_URL="mongodb://localhost:27017/code_keeper_bot" python scripts/dev_seed.py
scripts/import_snippets_from_markdown.py
מייבא סניפטים מקובץ או URL (כולל GitHub/Gist) באמצעות ניתוח Markdown.
ניתן לבצע
--dry-runכדי לראות כמה סניפטים ייווצרו ללא כתיבה.ברירת המחדל מאשרת אוטומטית את הסניפטים החדשים; ניתן לבטל עם
--no-approve.
דוגמת הרצה:
scripts/import_snippets_from_markdown.py --source docs/new-snippets.md --user-id 42 --username "Ops Bot"
scripts/measure_md_parse_cost.py
השיא נמדד מאיפוס.
VmHWMהוא השיא של כל חיי התהליך, ולכן רגע לפני הפרסור הילד כותב5ל-/proc/self/clear_refs(CLEAR_REFS_PATH), שמוריד אותו לגובה ה-RSS הנוכחי, והשיא נספר מה-RSS שלפני הפרסור. בלי האיפוס — כך נמדד מ-#3429 ועד #3467 — הבסיס היה הגבוה מבין ה-RSS ושיא-העבר, וזיכרון שהתהליך כבר הגיע אליו ושחרר (בעיקר החוצץ של קריאת הקובץ) הסתיר את מה שהפרסור הקצה מתחתיו: כחצי MB בקלט של 512KB, ותמיד לכיוון ”נכנס“. האיפוס מוכח בכל מדידה — הקצאה שלRESET_PROBE_BYTESשמשוחררת מרימה את השיא לפניו, ואחריו נבדק שהוא ירד — ו-/proc/self/clear_refsקיים רק בלינוקס (הערך5מגרסה 4.0): אם הכתיבה נכשלת, או מתקבלת ולא מאפסת, הסקריפט עוצר עם הודעה שאומרת מה קרה, בלי ליפול לשיטה הישנה.כל מדידת זיכרון רצה על ``LAYOUT_SEEDS`` — 40 זרעים — והמספר שנשפט הוא חסם עליון. השיא של אותו פרסור על אותו קלט תלוי בסידור הזיכרון של התהליך רגע לפני הפרסור, וזרע הגיבוב (
PYTHONHASHSEED) משנה אותו: ריצה אחת היא הגרלה, והסידור היקר נדיר — ולכן 40 ולא 10 (הנימוק והפיזור שנמדד ב-docstring של הקבוע). השורה נושאת את המקסימום, את המינימום ואת כל הריצות, כדי שמי שמריץ פעם אחת ומקבל מספר נמוך מהמתועד יבין שפגש סידור נפוץ ולא שינוי בקוד. והמספר שנשפט הוא המקסימום ועוד הפיגור של מוני הקרנל (counter_lag_bound_bytes): הקרנל רושם את השיא ממונים שכל מעבד מעדכן במנות, כך שהשיא שנרשם יכול לפגר אחרי האמיתי, ותמיד כלפי מטה. כדי שהפיגור יהיה של מעבד אחד, כל ילד מוצמד למעבד אחד מרגע ה-fork ובודק בעצמו שהוא מוצמד — הצמדה של המדידה בלבד, לא התנהגות של השרת — ואם אי אפשר להצמיד בסביבה, הסקריפט עוצר, כמו באיפוס. והחסם הזה מוכח רק על לינוקס מ-LAG_BOUND_PROVEN_FROM_KERNEL: על קרנל אחר הסקריפט עוצר לפני שמדד משהו (למה גרסה ולא בדיקה של הקרנל עצמו — ב-docstring של הקבוע).מודד כמה זיכרון ומעבד מוסיף פרסור מסמך אחד — שיא ה-RSS בזמן הפרסור, וזמן המעבד של הפרסור עצמו — לשני הפרסרים:
services.md_parser, שעליו נמדדו הקבועים, ו-services.rst_parser, שהכלי הציבוריcodekeeper_docs_get_sectionמריץ על עמודי RST. כל פרסור בתהליך נקי משלו, עםRLIMIT_ASכדי שצורה שמתפוצצת תיפול ב-MemoryErrorולא תדחוק את המכונה. לכל פרסר: הקורפוס האמיתי של הריפו והמסמך הצפוף ביותר בו משוכפל — שניהם חתוכים לגדול ביותר שהכלי עוד מפרסר (fit_to_the_tool:MAX_FILE_SIZE_FOR_DISPLAYבבתים, ול-Markdown גםMAX_LINES) — וצורה עוינת בלי אף תקרה (Markdown: שורות-תבליט בודדות; RST: כותרת בת תו אחד בכל שורה), שמראה מה התקרות חוסכות. ל-RST נמדד גם מסלול ה-outline שלcodekeeper_get_repo_file— המסמך הצפוף ביותר משוכפל עדRANGE_READ_MAX_BYTESעם תקרת הסקשניםMAX_SYMBOLS.ול-Markdown, מאז #3391, גם הקלט העוין כמו שהכלי מקבל אותו, על ברירות המחדל של
MAX_LINESו-MAX_TOKENS: כל הצורות העוינות שלHOSTILE_SHAPES(הצורות שנמדדו לפני כן, טבלאות מעל תקרת התאים, הגדרות קישור, ציטוט מקונן,\r\nעם תו אסטרלי ועוד), הקלט המשולב שהוא הגרוע בזיכרון, והקלטים הגרועים במעבד — גם הם על כלLAYOUT_SEEDS, וזמן המעבד שלהם נשפט עלCPU_REPEATSהריצות הראשונות (הנימוק ליד הקבוע).זה המקור של
_PARSE_RSS_PER_INPUT_BYTEב-mcp_server/server.py(שממנו נגזר רוחב מאגר הקריאות) ושלWORST_CASE_CPU_SECONDSב-services/md_parser.py(שממנו נגזרות מגבלת הקצב והדדליין שלcodekeeper_read_batch). השורה האחרונה היא פסק דין, וגם קוד היציאה: 0 כשכל מדידה שרצה כמו הכלי באמת מדדה פרסור (MEASURED_OUTCOMES— לא סירוב לפני הפרסור, לאMemoryError, שהשיא שלו הוא רק מה שהספיק לתפוס לפני שנפל, ולא תוצאה ששונה בין הסידורים), כל מדידה כזו רצה על כלLAYOUT_SEEDS(כל אחת, ולא רק המכריעה — הנימוק ב-verdict), החסם העליון של המכריעה נכנס ב-_PARSE_COST_BYTES, וזמן המעבד הגבוה אינו עובר אתWORST_CASE_CPU_SECONDS; ו-1 כשאחד מהם נשבר. כשהמדידות תקינות וקבוע נשבר, החשבונות שנגזרים ממנו צריכים מבט חדש לפני שמשנים משהו. זמן מעבד תלוי במכונה, ולכן השורה נושאת את שני המספרים. מריצים אותו מחדש כשמשנים אתmd_parserאו אתrst_parser(ובכלל זה החלפת גרסתmarkdown-it-pyושינוי של תקרה), או כשנוסף מסמך צפוף במיוחד.``–doubling`` — בדיקת ההכפלה. כל צורה עוינת בלי אף תקרה, בשלושה גדלים שכל אחד כפול מקודמו, כל אחד בתהליך נקי, מוצמד ומאופס — על סידור אחד, כי כאן נשפט יחס בין גדלים ולא מספר מול תקציב. בפרסר ליניארי העלות לבית קלט נשארת קבועה; צורה שהעלות שלה לבית גדלה יותר מ-
SUPERLINEAR_GROWTHבין הגודל הקטן לגדול, או שנופלת ב-MemoryErrorאו בחריגה מהזמן, מסומנתsuperlinear, וקוד היציאה הוא 1. משאב שהמדידה שלו מתחת לרצפת הרעש שלו —MEMORY_NOISE_FLOOR_BYTESלשיא בגודל הקטן,CPU_NOISE_FLOOR_SECONDSלזמן בגודל הגדול — אינו נבדק לגדילה: יחס שנשען על מספר כזה הוא רעש, ושיא אפס בגודל הקטן היה מסמן כל צורה ליניארית כריבועית. זו הבדיקה שהייתה תופסת מראש את הריבועיות של הגדרות הקישור ב-markdown-it-py3.0.0 (upstream #367), ומריצים אותה בכל שדרוג של הפרסר.לא נוגע במסד ולא בריפו: הקבצים הזמניים נכתבים לתיקייה זמנית של המערכת. קובץ שאינו UTF-8 מדולג עם שורת
skippedב-stderr, ובלי אף קובץ בגודלMIN_DOC_BYTESומעלה הסקריפט יוצא עם הודעה שאומרת זאת. הטסטים שלו:tests/test_measure_md_parse_cost_script.py.
דוגמת הרצה:
python scripts/measure_md_parse_cost.py
python scripts/measure_md_parse_cost.py --doubling
scripts/md_parser_upgrade_zero_diff.py
מראה ששדרוג של
markdown-it-pyאו שלmdit-py-pluginsלא שינה את מה שהפארסרים של התיעוד מחזירים. הוא רץ פעמיים, בסביבה הישנה ובחדשה, מאותו עץ עבודה ועל אותו קורפוס, וכותב תצלום JSON בכל ריצה;compareמשווה את שני התצלומים. התצלום נושא את המפה של כל קובץ.mdו-.rstתחת השורשים שנמסרו ב---root, את תשובות הסירוב של הכלי דרךdocs_handlers.document_from_read, צורות עוינות, ואת כל משפחות הצורות שלtests/test_md_parser_oracle.pyמול cmark-gfm.פסק הדין: הכול זהה, חוץ ממשפחות באורקל שהוכרזו מראש ב-
--expected-change. משפחה שהוכרזה ולא השתנתה מפילה את ההשוואה גם היא, כי הכרזה שנשארת בפקודה בלי סיבה תסתיר את השינוי הבא באותה משפחה. כך שדרוג שמשנה מחלקה ידועה עובר, והסקריפט מוכיח ששום דבר אחר לא השתנה. קוד היציאה שלcompare: 0 — אפס דיף, 1 — הבדל, 2 — קלט שגוי.לא רץ ב-CI, בכוונה: הערך שלו הוא הדיף בין שתי סביבות, ו-CI רץ באחת. דורש בשתי הסביבות את
requirements/development.txt, כי האורקל נטען מקובץ הטסטים (בטוען שלscripts/compare_md_parser_to_cmark.py). כותב רק ל---out. הנימוק המלא ב-docstring שלו, והטסטים שלו:tests/test_md_parser_upgrade_zero_diff_script.py.
דוגמת הרצה — בשדרוג ל-4.2.0 שתי המשפחות שבדוגמה הן אלה שהשתנו:
<python של הסביבה הישנה> scripts/md_parser_upgrade_zero_diff.py snapshot --root repo=. --out /tmp/before.json
<python של הסביבה החדשה> scripts/md_parser_upgrade_zero_diff.py snapshot --root repo=. --out /tmp/after.json
python scripts/md_parser_upgrade_zero_diff.py compare /tmp/before.json /tmp/after.json \
--expected-change commonmark_0_31_block_tag_list --expected-change table_over_autocomplete_cap
scripts/migrate_workspace_collections.py
מייצר אוסף ”שולחן עבודה“ לכל משתמש שחסר לו אחד כזה (idempotent).
נשען על
CollectionsManagerומייבאget_dbבזמן ריצה כדי למנוע תלות מעגלית.מדפיס סיכום בסיום (כמה משתמשים נבדקו וכמה אוספים נוצרו).
scripts/run_log_aggregator.py
מפעיל את
monitoring.log_analyzer.LogEventAggregatorעל stdin ומנתח לוגים בזמן אמת.צורך קובצי חתימות וקונפיגורציית התראות:
ERROR_SIGNATURES_PATH(ברירת מחדלconfig/error_signatures.yml) ו-ALERTS_GROUPING_CONFIG(ברירת מחדלconfig/alerts.yml).תומך בטעינה מחודשת מחזורית של חתימות דרך
LOG_AGG_RELOAD_SECONDSובמצב debug שמדפיס התאמות עםLOG_AGG_ECHO=1.
שימוש אופייני:
tail -F logs/app.log | LOG_AGG_ECHO=1 python scripts/run_log_aggregator.py
scripts/start_webapp.sh
מעטפת ל-Gunicorn עבור webapp/ עם הפקת
ASSET_VERSIONאוטומטית והפעלת warmup best-effort ל-/healthz.מכבד
PORT(ברירת מחדל 5000),WEBAPP_WSGI_APPופרמטרי warmup (WEBAPP_ENABLE_WARMUP/WEBAPP_WARMUP_URL/WEBAPP_WARMUP_MAX_ATTEMPTS/WEBAPP_WARMUP_DELAY_SECONDS).ברירת המחדל היא worker יחיד עם
gevent; ניתן לשלוט ב-WEB_CONCURRENCY/WEBAPP_GUNICORN_WORKERS,WEBAPP_GUNICORN_WORKER_CLASS,WEBAPP_GUNICORN_WORKER_CONNECTIONS(ל-gevent) ו-WEBAPP_GUNICORN_THREADS(ל-gthread).משמש להפעלה מקומית או ב-Render/Heroku כאשר אין Supervisor חיצוני.
scripts/start_with_worker.sh
מפעיל את הבוט Python (
python main.py) ובמידת הצורך גם Worker מבוסס Node לטיפול ב-Web Push.קורא קובץ
.env.worker(לא מנוהל ב-git) כדי לטעון מפתחות VAPID פרטיים רק לתהליך ה-Worker.מגדיר
PUSH_DELIVERY_URLמקומי אם ה-Worker רץ על אותה מכונה וממתין ל-healthcheck קצר כדי למנוע race conditions.
scripts/run_all.sh
מריץ שני תהליכים באותו קונטיינר: ה-WebApp (Gunicorn דרך
scripts/start_webapp.sh) וגם שירותAI Explain(AioHTTP דרךpython -m services.webserver).מגדיר ברירת מחדל ל-
OBS_AI_EXPLAIN_URLל-http://127.0.0.1:<internal_port>/api/ai/explainכאשר המשתנה לא הוגדר, כדי שהדשבורד יפנה פנימה.אמינות: אם אחד מהתהליכים נסגר/נופל, הסקריפט עוצר גם את השני ויוצא עם קוד שגיאה (כדי שהקונטיינר לא ימשיך “חצי עובד”).
משתני סביבה שימושיים:
OBS_AI_EXPLAIN_INTERNAL_PORT(ברירת מחדל:11000)OBS_AI_EXPLAIN_INTERNAL_HOST(ברירת מחדל:127.0.0.1)OBS_AI_EXPLAIN_RUN_LOCAL_SERVICE(ברירת מחדל:true)WEBAPP_START_SCRIPT(ברירת מחדל:scripts/start_webapp.sh)