@kaikybrofc/omnizap-system 2.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/.env.example +534 -0
  2. package/LICENSE +21 -0
  3. package/README.md +431 -0
  4. package/RELEASE-v2.1.2.md +83 -0
  5. package/app/config/adminIdentity.js +87 -0
  6. package/app/config/baileysConfig.js +693 -0
  7. package/app/config/groupUtils.js +388 -0
  8. package/app/connection/socketController.js +992 -0
  9. package/app/controllers/messageController.js +354 -0
  10. package/app/modules/adminModule/groupCommandHandlers.js +1294 -0
  11. package/app/modules/adminModule/groupEventHandlers.js +355 -0
  12. package/app/modules/aiModule/catCommand.js +1006 -0
  13. package/app/modules/broadcastModule/noticeCommand.js +416 -0
  14. package/app/modules/gameModule/diceCommand.js +67 -0
  15. package/app/modules/menuModule/common.js +311 -0
  16. package/app/modules/menuModule/menus.js +59 -0
  17. package/app/modules/playModule/playCommand.js +1615 -0
  18. package/app/modules/quoteModule/quoteCommand.js +851 -0
  19. package/app/modules/rpgPokemonModule/rpgBattleCanvasRenderer.js +786 -0
  20. package/app/modules/rpgPokemonModule/rpgBattleService.js +2082 -0
  21. package/app/modules/rpgPokemonModule/rpgBattleService.test.js +760 -0
  22. package/app/modules/rpgPokemonModule/rpgEvolutionUtils.js +22 -0
  23. package/app/modules/rpgPokemonModule/rpgPokemonCommand.js +172 -0
  24. package/app/modules/rpgPokemonModule/rpgPokemonDomain.js +192 -0
  25. package/app/modules/rpgPokemonModule/rpgPokemonDomain.test.js +93 -0
  26. package/app/modules/rpgPokemonModule/rpgPokemonEvolution.test.js +46 -0
  27. package/app/modules/rpgPokemonModule/rpgPokemonMessages.js +746 -0
  28. package/app/modules/rpgPokemonModule/rpgPokemonRepository.js +1859 -0
  29. package/app/modules/rpgPokemonModule/rpgPokemonService.js +6738 -0
  30. package/app/modules/rpgPokemonModule/rpgProfileCanvasRenderer.js +354 -0
  31. package/app/modules/statsModule/globalRankingCommand.js +65 -0
  32. package/app/modules/statsModule/noMessageCommand.js +288 -0
  33. package/app/modules/statsModule/rankingCommand.js +60 -0
  34. package/app/modules/statsModule/rankingCommon.js +889 -0
  35. package/app/modules/stickerModule/addStickerMetadata.js +239 -0
  36. package/app/modules/stickerModule/convertToWebp.js +390 -0
  37. package/app/modules/stickerModule/stickerCommand.js +454 -0
  38. package/app/modules/stickerModule/stickerConvertCommand.js +156 -0
  39. package/app/modules/stickerModule/stickerTextCommand.js +657 -0
  40. package/app/modules/stickerPackModule/autoPackCollectorRuntime.js +20 -0
  41. package/app/modules/stickerPackModule/autoPackCollectorService.js +284 -0
  42. package/app/modules/stickerPackModule/semanticReclassificationEngine.js +466 -0
  43. package/app/modules/stickerPackModule/semanticReclassificationEngine.test.js +88 -0
  44. package/app/modules/stickerPackModule/semanticThemeClusterService.js +571 -0
  45. package/app/modules/stickerPackModule/stickerAssetClassificationRepository.js +449 -0
  46. package/app/modules/stickerPackModule/stickerAssetRepository.js +400 -0
  47. package/app/modules/stickerPackModule/stickerAssetReprocessQueueRepository.js +180 -0
  48. package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +4078 -0
  49. package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +598 -0
  50. package/app/modules/stickerPackModule/stickerClassificationService.js +588 -0
  51. package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +102 -0
  52. package/app/modules/stickerPackModule/stickerPackCatalogHttp.js +7506 -0
  53. package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +1095 -0
  54. package/app/modules/stickerPackModule/stickerPackEngagementRepository.js +108 -0
  55. package/app/modules/stickerPackModule/stickerPackErrors.js +30 -0
  56. package/app/modules/stickerPackModule/stickerPackInteractionEventRepository.js +110 -0
  57. package/app/modules/stickerPackModule/stickerPackItemRepository.js +440 -0
  58. package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +337 -0
  59. package/app/modules/stickerPackModule/stickerPackMessageService.js +296 -0
  60. package/app/modules/stickerPackModule/stickerPackRepository.js +442 -0
  61. package/app/modules/stickerPackModule/stickerPackService.js +788 -0
  62. package/app/modules/stickerPackModule/stickerPackServiceRuntime.js +51 -0
  63. package/app/modules/stickerPackModule/stickerPackUtils.js +97 -0
  64. package/app/modules/stickerPackModule/stickerStorageService.js +507 -0
  65. package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +233 -0
  66. package/app/modules/stickerPackModule/stickerWorkerTaskQueueRepository.js +205 -0
  67. package/app/modules/systemMetricsModule/pingCommand.js +421 -0
  68. package/app/modules/tiktokModule/tiktokCommand.js +798 -0
  69. package/app/modules/userModule/userCommand.js +1217 -0
  70. package/app/modules/waifuPicsModule/waifuPicsCommand.js +177 -0
  71. package/app/observability/metrics.js +734 -0
  72. package/app/services/captchaService.js +492 -0
  73. package/app/services/dbWriteQueue.js +572 -0
  74. package/app/services/groupMetadataService.js +279 -0
  75. package/app/services/lidMapService.js +663 -0
  76. package/app/services/messagePersistenceService.js +56 -0
  77. package/app/services/newsBroadcastService.js +351 -0
  78. package/app/services/pokeApiService.js +398 -0
  79. package/app/services/queueUtils.js +57 -0
  80. package/app/services/socketState.js +7 -0
  81. package/app/store/aiPromptStore.js +38 -0
  82. package/app/store/groupConfigStore.js +58 -0
  83. package/app/store/premiumUserStore.js +36 -0
  84. package/app/utils/antiLink/antiLinkModule.js +804 -0
  85. package/app/utils/http/getImageBufferModule.js +18 -0
  86. package/app/utils/json/jsonSanitizer.js +113 -0
  87. package/app/utils/json/jsonSanitizer.test.js +40 -0
  88. package/app/utils/logger/loggerModule.js +262 -0
  89. package/app/utils/systemMetrics/systemMetricsModule.js +91 -0
  90. package/database/index.js +2052 -0
  91. package/database/init.js +516 -0
  92. package/database/migrations/20260203_0001_sticker_packs.sql +54 -0
  93. package/database/migrations/20260210_0003_rpg_pokemon.sql +58 -0
  94. package/database/migrations/20260210_0004_rpg_shiny_biome.sql +9 -0
  95. package/database/migrations/20260210_0005_rpg_missions.sql +14 -0
  96. package/database/migrations/20260210_0006_rpg_world_pokedex_traits.sql +27 -0
  97. package/database/migrations/20260210_0007_rpg_raid_pvp.sql +56 -0
  98. package/database/migrations/20260210_0008_rpg_social_system.sql +195 -0
  99. package/database/migrations/20260211_0009_rpg_social_xp.sql +36 -0
  100. package/database/migrations/20260222_0010_remove_message_xp.sql +2 -0
  101. package/database/migrations/20260226_0011_sticker_asset_classification.sql +17 -0
  102. package/database/migrations/20260226_0012_sticker_pack_engagement.sql +16 -0
  103. package/database/migrations/20260226_0013_sticker_marketplace_intelligence.sql +19 -0
  104. package/database/migrations/20260226_0014_sticker_pack_publish_flow.sql +30 -0
  105. package/database/migrations/20260226_0014_sticker_worker_queues.sql +42 -0
  106. package/database/migrations/20260226_0015_sticker_auto_pack_curation_integrity.sql +18 -0
  107. package/database/migrations/20260226_0016_sticker_web_google_auth_persistence.sql +34 -0
  108. package/database/migrations/20260226_0017_sticker_web_admin_ban.sql +22 -0
  109. package/database/migrations/20260226_0018_sticker_web_admin_moderator.sql +18 -0
  110. package/database/migrations/20260227_0019_sticker_classification_v2_signals.sql +12 -0
  111. package/database/migrations/20260227_0020_semantic_theme_clusters.sql +35 -0
  112. package/docker-compose.yml +103 -0
  113. package/ecosystem.prod.config.cjs +35 -0
  114. package/eslint.config.js +61 -0
  115. package/index.js +437 -0
  116. package/ml/clip_classifier/Dockerfile +16 -0
  117. package/ml/clip_classifier/README.md +120 -0
  118. package/ml/clip_classifier/adaptive_scoring.py +40 -0
  119. package/ml/clip_classifier/classifier.py +654 -0
  120. package/ml/clip_classifier/embedding_store.py +481 -0
  121. package/ml/clip_classifier/env_loader.py +15 -0
  122. package/ml/clip_classifier/llm_label_expander.py +144 -0
  123. package/ml/clip_classifier/main.py +213 -0
  124. package/ml/clip_classifier/requirements.txt +10 -0
  125. package/ml/clip_classifier/similarity_engine.py +74 -0
  126. package/observability/alert-rules.yml +60 -0
  127. package/observability/grafana/dashboards/omnizap-mysql.json +136 -0
  128. package/observability/grafana/dashboards/omnizap-overview.json +170 -0
  129. package/observability/grafana/provisioning/dashboards/dashboards.yml +11 -0
  130. package/observability/grafana/provisioning/datasources/datasources.yml +15 -0
  131. package/observability/loki-config.yml +38 -0
  132. package/observability/mysql-exporter.cnf +5 -0
  133. package/observability/mysql-setup.sql +46 -0
  134. package/observability/prometheus.yml +32 -0
  135. package/observability/promtail-config.yml +84 -0
  136. package/package.json +109 -0
  137. package/public/api-docs/index.html +144 -0
  138. package/public/css/github-project-panel.css +297 -0
  139. package/public/css/stickers-admin.css +1272 -0
  140. package/public/css/styles.css +671 -0
  141. package/public/index.html +1311 -0
  142. package/public/js/apps/apiDocsApp.js +310 -0
  143. package/public/js/apps/createPackApp.js +2069 -0
  144. package/public/js/apps/homeApp.js +396 -0
  145. package/public/js/apps/stickersAdminApp.js +1744 -0
  146. package/public/js/apps/stickersApp.js +4830 -0
  147. package/public/js/catalog.js +1019 -0
  148. package/public/js/github-panel/components/CommitList.js +34 -0
  149. package/public/js/github-panel/components/ErrorState.js +16 -0
  150. package/public/js/github-panel/components/GithubProjectPanel.js +106 -0
  151. package/public/js/github-panel/components/ReleaseList.js +38 -0
  152. package/public/js/github-panel/components/SkeletonPanel.js +22 -0
  153. package/public/js/github-panel/components/StatCard.js +15 -0
  154. package/public/js/github-panel/index.js +15 -0
  155. package/public/js/github-panel/useGithubRepoData.js +154 -0
  156. package/public/js/github-panel/vendor/react.js +11 -0
  157. package/public/js/runtime/react-runtime.js +19 -0
  158. package/public/licenca/index.html +106 -0
  159. package/public/stickers/admin/index.html +23 -0
  160. package/public/stickers/create/index.html +47 -0
  161. package/public/stickers/index.html +48 -0
  162. package/public/termos-de-uso/index.html +125 -0
  163. package/scripts/cache-bust.mjs +107 -0
  164. package/scripts/deploy.sh +458 -0
  165. package/scripts/github-deploy-notify.mjs +174 -0
  166. package/scripts/release.sh +129 -0
@@ -0,0 +1,213 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from typing import Any
5
+
6
+ from fastapi import FastAPI, File, Form, HTTPException, UploadFile
7
+ from pydantic import BaseModel, Field
8
+
9
+ from classifier import (
10
+ ADAPTIVE_ALPHA,
11
+ AFFINITY_WEIGHT_CAP,
12
+ CLIP_TOP_K,
13
+ DEFAULT_LABELS,
14
+ ENABLE_ADAPTIVE_SCORING,
15
+ ENABLE_LLM_EXPANSION_GATING,
16
+ ENABLE_CLUSTERING,
17
+ ENABLE_EMBEDDING_CACHE,
18
+ ENABLE_LLM_LABEL_EXPANSION,
19
+ ENTROPY_NORMALIZED_THRESHOLD,
20
+ ENTROPY_THRESHOLD,
21
+ LLM_EXPANSION_MAX_ENTROPY_NORMALIZED,
22
+ LLM_EXPANSION_MIN_MARGIN,
23
+ NSFW_THRESHOLD,
24
+ classify_image_bytes,
25
+ get_classifier,
26
+ register_pack_feedback,
27
+ )
28
+
29
+ app = FastAPI(
30
+ title="OmniZap MobileCLIP Classifier API",
31
+ description="Classificação de imagens com MobileCLIP (via OpenCLIP) para categorizar stickers/packs.",
32
+ version="2.0.0",
33
+ )
34
+
35
+
36
+ class TopLabelEntry(BaseModel):
37
+ label: str
38
+ score: float
39
+ logit: float
40
+ clip_score: float
41
+
42
+
43
+ class SimilarImageEntry(BaseModel):
44
+ image_hash: str
45
+ asset_id: str | None = None
46
+ similarity: float
47
+
48
+
49
+ class LlmExpansionPayload(BaseModel):
50
+ subtags: list[str] = Field(default_factory=list)
51
+ style_traits: list[str] = Field(default_factory=list)
52
+ emotions: list[str] = Field(default_factory=list)
53
+ pack_suggestions: list[str] = Field(default_factory=list)
54
+
55
+
56
+ class ClassificationResponse(BaseModel):
57
+ category: str
58
+ confidence: float
59
+ all_scores: dict[str, float]
60
+ raw_logits: dict[str, float] = Field(default_factory=dict)
61
+ top_labels: list[TopLabelEntry] = Field(default_factory=list)
62
+ entropy: float
63
+ entropy_top_k: float = 0.0
64
+ entropy_normalized: float = 0.0
65
+ confidence_margin: float
66
+ nsfw_score: float
67
+ is_nsfw: bool
68
+ ambiguous: bool
69
+ affinity_weight: float
70
+ affinity_weight_raw: float = 0.0
71
+ llm_expansion_used: bool = False
72
+ llm_expansion_gate_reason: str = "enabled"
73
+ llm_expansion: LlmExpansionPayload = Field(default_factory=LlmExpansionPayload)
74
+ similar_images: list[SimilarImageEntry] = Field(default_factory=list)
75
+ image_hash: str | None = None
76
+ model: str
77
+ device: str
78
+ labels: list[str]
79
+ filename: str | None = None
80
+ content_type: str | None = None
81
+
82
+
83
+ class FeedbackRequest(BaseModel):
84
+ image_hash: str
85
+ theme: str
86
+ accepted: bool = True
87
+ asset_id: str | None = None
88
+
89
+
90
+ class FeedbackResponse(BaseModel):
91
+ ok: bool
92
+
93
+
94
+ def _parse_labels(raw: str | None) -> list[str] | None:
95
+ if raw is None:
96
+ return None
97
+
98
+ value = raw.strip()
99
+ if not value:
100
+ return None
101
+
102
+ if value.startswith("["):
103
+ try:
104
+ parsed = json.loads(value)
105
+ except json.JSONDecodeError as error:
106
+ raise HTTPException(status_code=422, detail=f"labels JSON inválido: {error.msg}") from error
107
+
108
+ if not isinstance(parsed, list):
109
+ raise HTTPException(status_code=422, detail="labels JSON deve ser uma lista de strings.")
110
+ return [str(item).strip() for item in parsed if str(item).strip()]
111
+
112
+ return [item.strip() for item in value.split(",") if item.strip()]
113
+
114
+
115
+ @app.get("/health")
116
+ def health() -> dict[str, Any]:
117
+ runtime = get_classifier().runtime_info
118
+ return {
119
+ "ok": True,
120
+ "status": "ready",
121
+ "model": runtime.model_name,
122
+ "device": runtime.device,
123
+ }
124
+
125
+
126
+ @app.get("/labels")
127
+ def labels() -> dict[str, Any]:
128
+ return {
129
+ "ok": True,
130
+ "default_labels": DEFAULT_LABELS,
131
+ "nsfw_threshold": NSFW_THRESHOLD,
132
+ "top_k": CLIP_TOP_K,
133
+ "entropy_threshold": ENTROPY_THRESHOLD,
134
+ "entropy_normalized_threshold": ENTROPY_NORMALIZED_THRESHOLD,
135
+ "adaptive_alpha": ADAPTIVE_ALPHA,
136
+ "affinity_weight_cap": AFFINITY_WEIGHT_CAP,
137
+ "llm_expansion_min_margin": LLM_EXPANSION_MIN_MARGIN,
138
+ "llm_expansion_max_entropy_normalized": LLM_EXPANSION_MAX_ENTROPY_NORMALIZED,
139
+ "features": {
140
+ "embedding_cache": ENABLE_EMBEDDING_CACHE,
141
+ "clustering": ENABLE_CLUSTERING,
142
+ "adaptive_scoring": ENABLE_ADAPTIVE_SCORING,
143
+ "llm_label_expansion": ENABLE_LLM_LABEL_EXPANSION,
144
+ "llm_expansion_gating": ENABLE_LLM_EXPANSION_GATING,
145
+ },
146
+ }
147
+
148
+
149
+ @app.post("/classify", response_model=ClassificationResponse)
150
+ async def classify(
151
+ file: UploadFile = File(...),
152
+ labels: str | None = Form(None),
153
+ nsfw_threshold: float = Form(NSFW_THRESHOLD),
154
+ asset_id: str | None = Form(None),
155
+ asset_sha256: str | None = Form(None),
156
+ theme: str | None = Form(None),
157
+ similar_threshold: float | None = Form(None),
158
+ similar_limit: int | None = Form(None),
159
+ ) -> ClassificationResponse:
160
+ if not file.filename:
161
+ raise HTTPException(status_code=422, detail="Arquivo sem nome.")
162
+
163
+ if file.content_type and not file.content_type.lower().startswith("image/"):
164
+ raise HTTPException(status_code=415, detail=f"Tipo de arquivo não suportado: {file.content_type}")
165
+
166
+ file_bytes = await file.read()
167
+ custom_labels = _parse_labels(labels)
168
+
169
+ try:
170
+ result = classify_image_bytes(
171
+ file_bytes,
172
+ labels=custom_labels,
173
+ nsfw_threshold=nsfw_threshold,
174
+ asset_id=(asset_id or None),
175
+ asset_sha256=(asset_sha256 or None),
176
+ theme=(theme or None),
177
+ similar_threshold=similar_threshold,
178
+ similar_limit=similar_limit,
179
+ )
180
+ except ValueError as error:
181
+ raise HTTPException(status_code=422, detail=str(error)) from error
182
+ except Exception as error:
183
+ raise HTTPException(status_code=500, detail=f"Falha interna na classificação: {error}") from error
184
+
185
+ runtime = get_classifier().runtime_info
186
+ return ClassificationResponse(
187
+ **result,
188
+ model=runtime.model_name,
189
+ device=runtime.device,
190
+ labels=custom_labels or list(DEFAULT_LABELS),
191
+ filename=file.filename,
192
+ content_type=file.content_type,
193
+ )
194
+
195
+
196
+ @app.post("/feedback", response_model=FeedbackResponse)
197
+ def feedback(payload: FeedbackRequest) -> FeedbackResponse:
198
+ if not payload.image_hash.strip():
199
+ raise HTTPException(status_code=422, detail="image_hash é obrigatório.")
200
+ if not payload.theme.strip():
201
+ raise HTTPException(status_code=422, detail="theme é obrigatório.")
202
+
203
+ try:
204
+ register_pack_feedback(
205
+ image_hash=payload.image_hash.strip().lower(),
206
+ theme=payload.theme.strip(),
207
+ accepted=bool(payload.accepted),
208
+ asset_id=payload.asset_id,
209
+ )
210
+ except Exception as error:
211
+ raise HTTPException(status_code=500, detail=f"Falha ao registrar feedback: {error}") from error
212
+
213
+ return FeedbackResponse(ok=True)
@@ -0,0 +1,10 @@
1
+ fastapi==0.116.1
2
+ uvicorn[standard]==0.35.0
3
+ python-multipart==0.0.20
4
+ Pillow==11.3.0
5
+ numpy==2.3.2
6
+ torch>=2.2.0
7
+ open-clip-torch>=2.26.1
8
+ python-dotenv>=1.0.1
9
+ PyMySQL>=1.1.1
10
+ openai>=1.63.0
@@ -0,0 +1,74 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ import numpy as np
6
+
7
+ from embedding_store import EmbeddingStore
8
+
9
+
10
+ def cosine_similarity_matrix(a: np.ndarray, b: np.ndarray) -> np.ndarray:
11
+ """Compute pairwise cosine similarity between 2-D arrays."""
12
+ if a.ndim != 2 or b.ndim != 2:
13
+ raise ValueError("cosine_similarity_matrix requires 2-D arrays")
14
+ if a.shape[1] != b.shape[1]:
15
+ raise ValueError("Embedding dimensions must match")
16
+
17
+ a_norm = np.linalg.norm(a, axis=1, keepdims=True)
18
+ b_norm = np.linalg.norm(b, axis=1, keepdims=True)
19
+
20
+ a_safe = a / np.clip(a_norm, 1e-12, None)
21
+ b_safe = b / np.clip(b_norm, 1e-12, None)
22
+ return a_safe @ b_safe.T
23
+
24
+
25
+ def find_similar_images(
26
+ *,
27
+ store: EmbeddingStore,
28
+ image_embedding: np.ndarray,
29
+ model_name: str,
30
+ threshold: float = 0.85,
31
+ limit: int = 25,
32
+ scan_limit: int = 3000,
33
+ source_image_hash: str | None = None,
34
+ ) -> list[dict[str, Any]]:
35
+ """Find semantically similar images from persisted embedding cache."""
36
+ if image_embedding.ndim != 1:
37
+ image_embedding = image_embedding.reshape(-1)
38
+
39
+ rows = store.list_image_embeddings(model_name=model_name, limit=scan_limit)
40
+ if not rows:
41
+ return []
42
+
43
+ candidates = []
44
+ vectors = []
45
+ for row in rows:
46
+ if source_image_hash and row.image_hash == source_image_hash:
47
+ continue
48
+ if row.embedding.size == 0:
49
+ continue
50
+ candidates.append(row)
51
+ vectors.append(row.embedding)
52
+
53
+ if not vectors:
54
+ return []
55
+
56
+ matrix_a = np.asarray([image_embedding.astype(np.float32)], dtype=np.float32)
57
+ matrix_b = np.asarray(vectors, dtype=np.float32)
58
+ scores = cosine_similarity_matrix(matrix_a, matrix_b).reshape(-1)
59
+
60
+ hits = []
61
+ for idx, score in enumerate(scores.tolist()):
62
+ if float(score) < float(threshold):
63
+ continue
64
+ candidate = candidates[idx]
65
+ hits.append(
66
+ {
67
+ "image_hash": candidate.image_hash,
68
+ "asset_id": candidate.asset_id,
69
+ "similarity": round(float(score), 6),
70
+ }
71
+ )
72
+
73
+ hits.sort(key=lambda entry: float(entry["similarity"]), reverse=True)
74
+ return hits[: max(1, min(int(limit or 25), 100))]
@@ -0,0 +1,60 @@
1
+ groups:
2
+ - name: omnizap-db
3
+ rules:
4
+ - alert: OmniZapDBQueryOver500ms
5
+ expr: increase(omnizap_db_query_over_ms_total{threshold="500"}[5m]) > 0
6
+ for: 2m
7
+ labels:
8
+ severity: warning
9
+ annotations:
10
+ summary: "Query acima de 500ms detectada"
11
+ description: "Uma ou mais queries excederam 500ms nos últimos 5 minutos."
12
+
13
+ - alert: OmniZapLidMapUpsertOver1000ms
14
+ expr: increase(omnizap_db_query_over_ms_total{threshold="1000",table="lid_map"}[5m]) > 0
15
+ for: 2m
16
+ labels:
17
+ severity: critical
18
+ annotations:
19
+ summary: "UPSERT lid_map acima de 1000ms"
20
+ description: "UPSERTs em lid_map excederam 1000ms nos últimos 5 minutos."
21
+
22
+ - alert: OmniZapMySQLLockWaitsHigh
23
+ expr: rate(mysql_global_status_innodb_row_lock_waits[5m]) > 5
24
+ for: 5m
25
+ labels:
26
+ severity: warning
27
+ annotations:
28
+ summary: "Lock waits elevados no InnoDB"
29
+ description: "Taxa de lock waits acima de 5/s nos últimos 5 minutos."
30
+
31
+ - name: omnizap-queues
32
+ rules:
33
+ - alert: OmniZapMessageQueueHigh
34
+ expr: omnizap_queue_depth{queue="messages"} > 2000
35
+ for: 5m
36
+ labels:
37
+ severity: warning
38
+ annotations:
39
+ summary: "Fila de mensagens alta"
40
+ description: "Backlog da fila de mensagens acima de 2000."
41
+
42
+ - name: omnizap-host
43
+ rules:
44
+ - alert: OmniZapDiskUsageHigh
45
+ expr: (1 - (node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"} / node_filesystem_size_bytes{fstype!~"tmpfs|overlay"})) > 0.80
46
+ for: 10m
47
+ labels:
48
+ severity: critical
49
+ annotations:
50
+ summary: "Disco acima de 80%"
51
+ description: "Uso de disco acima de 80% por 10 minutos."
52
+
53
+ - alert: OmniZapMemoryUsageHigh
54
+ expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) > 0.80
55
+ for: 10m
56
+ labels:
57
+ severity: warning
58
+ annotations:
59
+ summary: "Memória acima de 80%"
60
+ description: "Uso de memória acima de 80% por 10 minutos."
@@ -0,0 +1,136 @@
1
+ {
2
+ "uid": "omnizap-mysql",
3
+ "title": "OmniZap MySQL",
4
+ "timezone": "browser",
5
+ "schemaVersion": 38,
6
+ "version": 3,
7
+ "refresh": "10s",
8
+ "time": {
9
+ "from": "now-6h",
10
+ "to": "now"
11
+ },
12
+ "tags": ["omnizap", "mysql"],
13
+ "panels": [
14
+ {
15
+ "type": "timeseries",
16
+ "title": "Queries/sec",
17
+ "datasource": "Prometheus",
18
+ "gridPos": { "x": 0, "y": 0, "w": 8, "h": 8 },
19
+ "targets": [
20
+ {
21
+ "expr": "rate(mysql_global_status_queries[5m])",
22
+ "legendFormat": "qps",
23
+ "refId": "A"
24
+ }
25
+ ]
26
+ },
27
+ {
28
+ "type": "timeseries",
29
+ "title": "Writes/sec",
30
+ "datasource": "Prometheus",
31
+ "gridPos": { "x": 8, "y": 0, "w": 8, "h": 8 },
32
+ "targets": [
33
+ {
34
+ "expr": "sum(rate(mysql_global_status_commands_total{command=~\"insert|update|delete\"}[5m])) or (sum(rate(mysql_global_status_com_insert[5m])) + sum(rate(mysql_global_status_com_update[5m])) + sum(rate(mysql_global_status_com_delete[5m]))) or vector(0)",
35
+ "legendFormat": "writes",
36
+ "refId": "A"
37
+ }
38
+ ]
39
+ },
40
+ {
41
+ "type": "timeseries",
42
+ "title": "Slow Queries/sec",
43
+ "datasource": "Prometheus",
44
+ "gridPos": { "x": 16, "y": 0, "w": 8, "h": 8 },
45
+ "targets": [
46
+ {
47
+ "expr": "rate(mysql_global_status_slow_queries[5m])",
48
+ "legendFormat": "slow",
49
+ "refId": "A"
50
+ }
51
+ ]
52
+ },
53
+ {
54
+ "type": "timeseries",
55
+ "title": "InnoDB Row Lock Waits/sec",
56
+ "datasource": "Prometheus",
57
+ "gridPos": { "x": 0, "y": 8, "w": 8, "h": 8 },
58
+ "targets": [
59
+ {
60
+ "expr": "rate(mysql_global_status_innodb_row_lock_waits[5m])",
61
+ "legendFormat": "waits",
62
+ "refId": "A"
63
+ }
64
+ ]
65
+ },
66
+ {
67
+ "type": "timeseries",
68
+ "title": "InnoDB Row Lock Time (ms/sec)",
69
+ "datasource": "Prometheus",
70
+ "gridPos": { "x": 8, "y": 8, "w": 8, "h": 8 },
71
+ "targets": [
72
+ {
73
+ "expr": "rate(mysql_global_status_innodb_row_lock_time[5m])",
74
+ "legendFormat": "lock_time",
75
+ "refId": "A"
76
+ }
77
+ ]
78
+ },
79
+ {
80
+ "type": "timeseries",
81
+ "title": "Buffer Pool Hit Ratio",
82
+ "datasource": "Prometheus",
83
+ "gridPos": { "x": 16, "y": 8, "w": 8, "h": 8 },
84
+ "targets": [
85
+ {
86
+ "expr": "1 - (rate(mysql_global_status_innodb_buffer_pool_reads[5m]) / rate(mysql_global_status_innodb_buffer_pool_read_requests[5m]))",
87
+ "legendFormat": "hit_ratio",
88
+ "refId": "A"
89
+ }
90
+ ]
91
+ },
92
+ {
93
+ "type": "timeseries",
94
+ "title": "InnoDB IO (bytes/sec)",
95
+ "datasource": "Prometheus",
96
+ "gridPos": { "x": 0, "y": 16, "w": 12, "h": 8 },
97
+ "targets": [
98
+ {
99
+ "expr": "rate(mysql_global_status_innodb_data_read[5m])",
100
+ "legendFormat": "read",
101
+ "refId": "A"
102
+ },
103
+ {
104
+ "expr": "rate(mysql_global_status_innodb_data_written[5m])",
105
+ "legendFormat": "write",
106
+ "refId": "B"
107
+ }
108
+ ]
109
+ },
110
+ {
111
+ "type": "timeseries",
112
+ "title": "Table Latency P99 (ms)",
113
+ "datasource": "Prometheus",
114
+ "gridPos": { "x": 12, "y": 16, "w": 12, "h": 8 },
115
+ "targets": [
116
+ {
117
+ "expr": "histogram_quantile(0.99, sum(rate(omnizap_db_query_duration_ms_bucket[5m])) by (le, table))",
118
+ "legendFormat": "{{table}}",
119
+ "refId": "A"
120
+ }
121
+ ]
122
+ },
123
+ {
124
+ "type": "logs",
125
+ "title": "MySQL Slow Log",
126
+ "datasource": "Loki",
127
+ "gridPos": { "x": 0, "y": 24, "w": 24, "h": 10 },
128
+ "targets": [
129
+ {
130
+ "expr": "{job=\"mysql-slow\"}",
131
+ "refId": "A"
132
+ }
133
+ ]
134
+ }
135
+ ]
136
+ }
@@ -0,0 +1,170 @@
1
+ {
2
+ "uid": "omnizap-overview",
3
+ "title": "OmniZap Overview",
4
+ "timezone": "browser",
5
+ "schemaVersion": 38,
6
+ "version": 1,
7
+ "refresh": "10s",
8
+ "time": {
9
+ "from": "now-6h",
10
+ "to": "now"
11
+ },
12
+ "tags": ["omnizap", "overview"],
13
+ "panels": [
14
+ {
15
+ "type": "timeseries",
16
+ "title": "Messages/sec",
17
+ "datasource": "Prometheus",
18
+ "gridPos": { "x": 0, "y": 0, "w": 8, "h": 8 },
19
+ "targets": [
20
+ {
21
+ "expr": "rate(omnizap_messages_upsert_messages_total[1m])",
22
+ "legendFormat": "{{type}}",
23
+ "refId": "A"
24
+ }
25
+ ]
26
+ },
27
+ {
28
+ "type": "timeseries",
29
+ "title": "messages.upsert P99 (ms)",
30
+ "datasource": "Prometheus",
31
+ "gridPos": { "x": 8, "y": 0, "w": 8, "h": 8 },
32
+ "targets": [
33
+ {
34
+ "expr": "histogram_quantile(0.99, sum(rate(omnizap_messages_upsert_duration_ms_bucket[5m])) by (le))",
35
+ "legendFormat": "p99",
36
+ "refId": "A"
37
+ }
38
+ ]
39
+ },
40
+ {
41
+ "type": "timeseries",
42
+ "title": "Queue Depth",
43
+ "datasource": "Prometheus",
44
+ "gridPos": { "x": 16, "y": 0, "w": 8, "h": 8 },
45
+ "targets": [
46
+ {
47
+ "expr": "omnizap_queue_depth",
48
+ "legendFormat": "{{queue}}",
49
+ "refId": "A"
50
+ }
51
+ ]
52
+ },
53
+ {
54
+ "type": "timeseries",
55
+ "title": "DB Query P99 by Table (ms)",
56
+ "datasource": "Prometheus",
57
+ "gridPos": { "x": 0, "y": 8, "w": 12, "h": 9 },
58
+ "targets": [
59
+ {
60
+ "expr": "histogram_quantile(0.99, sum(rate(omnizap_db_query_duration_ms_bucket[5m])) by (le, table))",
61
+ "legendFormat": "{{table}}",
62
+ "refId": "A"
63
+ }
64
+ ]
65
+ },
66
+ {
67
+ "type": "timeseries",
68
+ "title": "DB Errors/s",
69
+ "datasource": "Prometheus",
70
+ "gridPos": { "x": 12, "y": 8, "w": 6, "h": 9 },
71
+ "targets": [
72
+ {
73
+ "expr": "rate(omnizap_errors_total{scope=\"db\"}[5m])",
74
+ "legendFormat": "db",
75
+ "refId": "A"
76
+ }
77
+ ]
78
+ },
79
+ {
80
+ "type": "timeseries",
81
+ "title": "DB Slow Queries (>=500ms)",
82
+ "datasource": "Prometheus",
83
+ "gridPos": { "x": 18, "y": 8, "w": 6, "h": 9 },
84
+ "targets": [
85
+ {
86
+ "expr": "increase(omnizap_db_query_over_ms_total{threshold=\"500\"}[5m])",
87
+ "legendFormat": "{{table}}",
88
+ "refId": "A"
89
+ }
90
+ ]
91
+ },
92
+ {
93
+ "type": "timeseries",
94
+ "title": "CPU Usage %",
95
+ "datasource": "Prometheus",
96
+ "gridPos": { "x": 0, "y": 17, "w": 8, "h": 8 },
97
+ "targets": [
98
+ {
99
+ "expr": "100 - (avg(rate(node_cpu_seconds_total{mode=\"idle\"}[5m])) * 100)",
100
+ "legendFormat": "cpu",
101
+ "refId": "A"
102
+ }
103
+ ]
104
+ },
105
+ {
106
+ "type": "timeseries",
107
+ "title": "Memory Usage %",
108
+ "datasource": "Prometheus",
109
+ "gridPos": { "x": 8, "y": 17, "w": 8, "h": 8 },
110
+ "targets": [
111
+ {
112
+ "expr": "(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100",
113
+ "legendFormat": "memory",
114
+ "refId": "A"
115
+ }
116
+ ]
117
+ },
118
+ {
119
+ "type": "timeseries",
120
+ "title": "Disk Usage %",
121
+ "datasource": "Prometheus",
122
+ "gridPos": { "x": 16, "y": 17, "w": 8, "h": 8 },
123
+ "targets": [
124
+ {
125
+ "expr": "(1 - (node_filesystem_avail_bytes{fstype!~\"tmpfs|overlay\"} / node_filesystem_size_bytes{fstype!~\"tmpfs|overlay\"})) * 100",
126
+ "legendFormat": "{{mountpoint}}",
127
+ "refId": "A"
128
+ }
129
+ ]
130
+ },
131
+ {
132
+ "type": "timeseries",
133
+ "title": "Writes/sec by Table",
134
+ "datasource": "Prometheus",
135
+ "gridPos": { "x": 0, "y": 25, "w": 12, "h": 8 },
136
+ "targets": [
137
+ {
138
+ "expr": "rate(omnizap_db_write_total[5m])",
139
+ "legendFormat": "{{operation}} {{table}}",
140
+ "refId": "A"
141
+ }
142
+ ]
143
+ },
144
+ {
145
+ "type": "timeseries",
146
+ "title": "Inserts/Upserts/sec (messages, chats, lid_map)",
147
+ "datasource": "Prometheus",
148
+ "gridPos": { "x": 12, "y": 25, "w": 12, "h": 8 },
149
+ "targets": [
150
+ {
151
+ "expr": "rate(omnizap_db_write_total{operation=~\"insert|upsert\",table=~\"messages|chats|lid_map\"}[5m])",
152
+ "legendFormat": "{{operation}} {{table}}",
153
+ "refId": "A"
154
+ }
155
+ ]
156
+ },
157
+ {
158
+ "type": "logs",
159
+ "title": "Top Slow Queries (Loki)",
160
+ "datasource": "Loki",
161
+ "gridPos": { "x": 0, "y": 33, "w": 24, "h": 10 },
162
+ "targets": [
163
+ {
164
+ "expr": "{job=\"db-monitor\"} |= \"slow\" | json | line_format \"{{.ts}} {{.durationMs}}ms {{.table}} {{.normalizedSql}}\"",
165
+ "refId": "A"
166
+ }
167
+ ]
168
+ }
169
+ ]
170
+ }
@@ -0,0 +1,11 @@
1
+ apiVersion: 1
2
+
3
+ providers:
4
+ - name: omnizap
5
+ orgId: 1
6
+ folder: "OmniZap"
7
+ type: file
8
+ disableDeletion: false
9
+ updateIntervalSeconds: 30
10
+ options:
11
+ path: /var/lib/grafana/dashboards
@@ -0,0 +1,15 @@
1
+ apiVersion: 1
2
+
3
+ datasources:
4
+ - name: Prometheus
5
+ type: prometheus
6
+ access: proxy
7
+ url: http://prometheus:9090
8
+ isDefault: true
9
+ editable: false
10
+
11
+ - name: Loki
12
+ type: loki
13
+ access: proxy
14
+ url: http://loki:3100
15
+ editable: false