nginx-lens 0.4.0__py3-none-any.whl → 0.5.0__py3-none-any.whl
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.
- commands/cli.py +6 -0
- commands/completion.py +174 -0
- commands/health.py +41 -6
- commands/logs.py +10 -2
- commands/metrics.py +495 -0
- commands/resolve.py +32 -2
- commands/validate.py +451 -0
- config/__init__.py +4 -0
- config/config_loader.py +200 -0
- {nginx_lens-0.4.0.dist-info → nginx_lens-0.5.0.dist-info}/METADATA +1 -1
- {nginx_lens-0.4.0.dist-info → nginx_lens-0.5.0.dist-info}/RECORD +19 -11
- {nginx_lens-0.4.0.dist-info → nginx_lens-0.5.0.dist-info}/top_level.txt +2 -0
- upstream_checker/checker.py +47 -7
- upstream_checker/dns_cache.py +216 -0
- utils/__init__.py +4 -0
- utils/progress.py +120 -0
- {nginx_lens-0.4.0.dist-info → nginx_lens-0.5.0.dist-info}/WHEEL +0 -0
- {nginx_lens-0.4.0.dist-info → nginx_lens-0.5.0.dist-info}/entry_points.txt +0 -0
- {nginx_lens-0.4.0.dist-info → nginx_lens-0.5.0.dist-info}/licenses/LICENSE +0 -0
commands/metrics.py
ADDED
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from typing import Optional, Dict, Any, List
|
|
3
|
+
import typer
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
from rich.table import Table
|
|
6
|
+
from rich.panel import Panel
|
|
7
|
+
from rich import box
|
|
8
|
+
from collections import Counter, defaultdict
|
|
9
|
+
|
|
10
|
+
from parser.nginx_parser import parse_nginx_config
|
|
11
|
+
|
|
12
|
+
app = typer.Typer()
|
|
13
|
+
console = Console()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _count_blocks(tree, block_type: str) -> int:
|
|
17
|
+
"""
|
|
18
|
+
Подсчитывает количество блоков определенного типа в дереве конфигурации.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
tree: Дерево конфигурации Nginx
|
|
22
|
+
block_type: Тип блока (например, 'server', 'location', 'upstream')
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Количество блоков
|
|
26
|
+
"""
|
|
27
|
+
# Для upstream используем get_upstreams()
|
|
28
|
+
if block_type == 'upstream':
|
|
29
|
+
if hasattr(tree, 'get_upstreams'):
|
|
30
|
+
upstreams = tree.get_upstreams()
|
|
31
|
+
return len(upstreams)
|
|
32
|
+
return 0
|
|
33
|
+
|
|
34
|
+
count = 0
|
|
35
|
+
|
|
36
|
+
def traverse(directives):
|
|
37
|
+
nonlocal count
|
|
38
|
+
for directive in directives:
|
|
39
|
+
if directive.get('block') == block_type:
|
|
40
|
+
count += 1
|
|
41
|
+
if 'directives' in directive:
|
|
42
|
+
traverse(directive['directives'])
|
|
43
|
+
|
|
44
|
+
if hasattr(tree, 'directives'):
|
|
45
|
+
traverse(tree.directives)
|
|
46
|
+
|
|
47
|
+
return count
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _count_directives(tree) -> Dict[str, int]:
|
|
51
|
+
"""
|
|
52
|
+
Подсчитывает количество директив каждого типа в конфигурации.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
tree: Дерево конфигурации Nginx
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Словарь {directive_name: count}
|
|
59
|
+
"""
|
|
60
|
+
directive_counts = Counter()
|
|
61
|
+
|
|
62
|
+
def traverse(directives):
|
|
63
|
+
for directive in directives:
|
|
64
|
+
if 'directive' in directive:
|
|
65
|
+
directive_counts[directive['directive']] += 1
|
|
66
|
+
if 'directives' in directive:
|
|
67
|
+
traverse(directive['directives'])
|
|
68
|
+
|
|
69
|
+
if hasattr(tree, 'directives'):
|
|
70
|
+
traverse(tree.directives)
|
|
71
|
+
|
|
72
|
+
return dict(directive_counts)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _get_upstream_servers_count(tree) -> Dict[str, int]:
|
|
76
|
+
"""
|
|
77
|
+
Подсчитывает количество серверов в каждом upstream.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
tree: Дерево конфигурации Nginx
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Словарь {upstream_name: server_count}
|
|
84
|
+
"""
|
|
85
|
+
upstreams = tree.get_upstreams() if hasattr(tree, 'get_upstreams') else {}
|
|
86
|
+
return {name: len(servers) for name, servers in upstreams.items()}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _collect_metrics(config_path: str) -> Dict[str, Any]:
|
|
90
|
+
"""
|
|
91
|
+
Собирает метрики о конфигурации Nginx.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
config_path: Путь к конфигурационному файлу
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Словарь с метриками
|
|
98
|
+
"""
|
|
99
|
+
try:
|
|
100
|
+
tree = parse_nginx_config(config_path)
|
|
101
|
+
except FileNotFoundError:
|
|
102
|
+
console.print(f"[red]Файл {config_path} не найден. Проверьте путь к конфигу.[/red]")
|
|
103
|
+
sys.exit(1)
|
|
104
|
+
except Exception as e:
|
|
105
|
+
console.print(f"[red]Ошибка при разборе {config_path}: {e}[/red]")
|
|
106
|
+
sys.exit(1)
|
|
107
|
+
|
|
108
|
+
# Подсчет блоков
|
|
109
|
+
upstream_count = _count_blocks(tree, 'upstream')
|
|
110
|
+
server_count = _count_blocks(tree, 'server')
|
|
111
|
+
location_count = _count_blocks(tree, 'location')
|
|
112
|
+
|
|
113
|
+
# Подсчет директив
|
|
114
|
+
directive_counts = _count_directives(tree)
|
|
115
|
+
|
|
116
|
+
# Подсчет серверов в upstream
|
|
117
|
+
upstream_servers = _get_upstream_servers_count(tree)
|
|
118
|
+
total_upstream_servers = sum(upstream_servers.values())
|
|
119
|
+
|
|
120
|
+
# Статистика по server блокам
|
|
121
|
+
server_stats = _get_server_stats(tree)
|
|
122
|
+
|
|
123
|
+
metrics = {
|
|
124
|
+
"config_path": config_path,
|
|
125
|
+
"blocks": {
|
|
126
|
+
"upstream": upstream_count,
|
|
127
|
+
"server": server_count,
|
|
128
|
+
"location": location_count,
|
|
129
|
+
},
|
|
130
|
+
"upstream_servers": {
|
|
131
|
+
"total": total_upstream_servers,
|
|
132
|
+
"by_upstream": upstream_servers,
|
|
133
|
+
},
|
|
134
|
+
"directives": directive_counts,
|
|
135
|
+
"server_stats": server_stats,
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return metrics
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _get_server_stats(tree) -> Dict[str, Any]:
|
|
142
|
+
"""
|
|
143
|
+
Собирает статистику по server блокам.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
tree: Дерево конфигурации Nginx
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Словарь со статистикой
|
|
150
|
+
"""
|
|
151
|
+
stats = {
|
|
152
|
+
"with_ssl": 0,
|
|
153
|
+
"with_http2": 0,
|
|
154
|
+
"listen_ports": Counter(),
|
|
155
|
+
"server_names": [],
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
def traverse(directives):
|
|
159
|
+
current_server = None
|
|
160
|
+
for directive in directives:
|
|
161
|
+
if directive.get('block') == 'server':
|
|
162
|
+
current_server = {
|
|
163
|
+
'has_ssl': False,
|
|
164
|
+
'has_http2': False,
|
|
165
|
+
'listen_ports': [],
|
|
166
|
+
'server_names': [],
|
|
167
|
+
}
|
|
168
|
+
elif current_server is not None:
|
|
169
|
+
if directive.get('directive') == 'listen':
|
|
170
|
+
args = directive.get('args', '')
|
|
171
|
+
if 'ssl' in args:
|
|
172
|
+
current_server['has_ssl'] = True
|
|
173
|
+
if 'http2' in args:
|
|
174
|
+
current_server['has_http2'] = True
|
|
175
|
+
# Извлекаем порт
|
|
176
|
+
port = _extract_port(args)
|
|
177
|
+
if port:
|
|
178
|
+
current_server['listen_ports'].append(port)
|
|
179
|
+
stats["listen_ports"][port] += 1
|
|
180
|
+
elif directive.get('directive') == 'server_name':
|
|
181
|
+
server_names = directive.get('args', '').split()
|
|
182
|
+
current_server['server_names'].extend(server_names)
|
|
183
|
+
stats["server_names"].extend(server_names)
|
|
184
|
+
|
|
185
|
+
if 'directives' in directive:
|
|
186
|
+
traverse(directive['directives'])
|
|
187
|
+
|
|
188
|
+
if hasattr(tree, 'directives'):
|
|
189
|
+
traverse(tree.directives)
|
|
190
|
+
|
|
191
|
+
# Подсчитываем серверы с SSL и HTTP2
|
|
192
|
+
def count_servers(directives):
|
|
193
|
+
for directive in directives:
|
|
194
|
+
if directive.get('block') == 'server':
|
|
195
|
+
has_ssl = False
|
|
196
|
+
has_http2 = False
|
|
197
|
+
for d in directive.get('directives', []):
|
|
198
|
+
if d.get('directive') == 'listen':
|
|
199
|
+
args = d.get('args', '')
|
|
200
|
+
if 'ssl' in args:
|
|
201
|
+
has_ssl = True
|
|
202
|
+
if 'http2' in args:
|
|
203
|
+
has_http2 = True
|
|
204
|
+
if has_ssl:
|
|
205
|
+
stats["with_ssl"] += 1
|
|
206
|
+
if has_http2:
|
|
207
|
+
stats["with_http2"] += 1
|
|
208
|
+
if 'directives' in directive:
|
|
209
|
+
count_servers(directive['directives'])
|
|
210
|
+
|
|
211
|
+
if hasattr(tree, 'directives'):
|
|
212
|
+
count_servers(tree.directives)
|
|
213
|
+
|
|
214
|
+
stats["listen_ports"] = dict(stats["listen_ports"])
|
|
215
|
+
stats["server_names"] = list(set(stats["server_names"]))
|
|
216
|
+
|
|
217
|
+
return stats
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _extract_port(listen_args: str) -> Optional[int]:
|
|
221
|
+
"""
|
|
222
|
+
Извлекает порт из директивы listen.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
listen_args: Аргументы директивы listen
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
Номер порта или None
|
|
229
|
+
"""
|
|
230
|
+
import re
|
|
231
|
+
# Ищем порт в формате :80, :443, listen 80, listen 443
|
|
232
|
+
match = re.search(r':(\d+)', listen_args)
|
|
233
|
+
if match:
|
|
234
|
+
return int(match.group(1))
|
|
235
|
+
match = re.search(r'\b(\d+)\b', listen_args)
|
|
236
|
+
if match:
|
|
237
|
+
return int(match.group(1))
|
|
238
|
+
return None
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _export_prometheus(metrics: Dict[str, Any]) -> str:
|
|
242
|
+
"""
|
|
243
|
+
Экспортирует метрики в формате Prometheus.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
metrics: Словарь с метриками
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
Строка в формате Prometheus
|
|
250
|
+
"""
|
|
251
|
+
lines = []
|
|
252
|
+
lines.append("# HELP nginx_lens_upstream_count Total number of upstream blocks")
|
|
253
|
+
lines.append("# TYPE nginx_lens_upstream_count gauge")
|
|
254
|
+
lines.append(f'nginx_lens_upstream_count{{config="{metrics["config_path"]}"}} {metrics["blocks"]["upstream"]}')
|
|
255
|
+
|
|
256
|
+
lines.append("# HELP nginx_lens_server_count Total number of server blocks")
|
|
257
|
+
lines.append("# TYPE nginx_lens_server_count gauge")
|
|
258
|
+
lines.append(f'nginx_lens_server_count{{config="{metrics["config_path"]}"}} {metrics["blocks"]["server"]}')
|
|
259
|
+
|
|
260
|
+
lines.append("# HELP nginx_lens_location_count Total number of location blocks")
|
|
261
|
+
lines.append("# TYPE nginx_lens_location_count gauge")
|
|
262
|
+
lines.append(f'nginx_lens_location_count{{config="{metrics["config_path"]}"}} {metrics["blocks"]["location"]}')
|
|
263
|
+
|
|
264
|
+
lines.append("# HELP nginx_lens_upstream_servers_total Total number of upstream servers")
|
|
265
|
+
lines.append("# TYPE nginx_lens_upstream_servers_total gauge")
|
|
266
|
+
lines.append(f'nginx_lens_upstream_servers_total{{config="{metrics["config_path"]}"}} {metrics["upstream_servers"]["total"]}')
|
|
267
|
+
|
|
268
|
+
# Метрики по upstream
|
|
269
|
+
for upstream_name, server_count in metrics["upstream_servers"]["by_upstream"].items():
|
|
270
|
+
lines.append(f'nginx_lens_upstream_servers{{config="{metrics["config_path"]}",upstream="{upstream_name}"}} {server_count}')
|
|
271
|
+
|
|
272
|
+
# Метрики по директивам (топ-10)
|
|
273
|
+
top_directives = sorted(metrics["directives"].items(), key=lambda x: x[1], reverse=True)[:10]
|
|
274
|
+
for directive, count in top_directives:
|
|
275
|
+
safe_directive = directive.replace('-', '_').replace('.', '_')
|
|
276
|
+
lines.append(f'nginx_lens_directive_count{{config="{metrics["config_path"]}",directive="{directive}"}} {count}')
|
|
277
|
+
|
|
278
|
+
# Метрики по портам
|
|
279
|
+
for port, count in metrics["server_stats"]["listen_ports"].items():
|
|
280
|
+
lines.append(f'nginx_lens_listen_port_count{{config="{metrics["config_path"]}",port="{port}"}} {count}')
|
|
281
|
+
|
|
282
|
+
# Метрики SSL и HTTP2
|
|
283
|
+
lines.append(f'nginx_lens_servers_with_ssl{{config="{metrics["config_path"]}"}} {metrics["server_stats"]["with_ssl"]}')
|
|
284
|
+
lines.append(f'nginx_lens_servers_with_http2{{config="{metrics["config_path"]}"}} {metrics["server_stats"]["with_http2"]}')
|
|
285
|
+
|
|
286
|
+
return '\n'.join(lines)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def metrics(
|
|
290
|
+
config_path: str = typer.Argument(..., help="Путь к nginx.conf"),
|
|
291
|
+
compare_with: Optional[str] = typer.Option(None, "--compare", "-c", help="Путь к другому конфигу для сравнения"),
|
|
292
|
+
prometheus: bool = typer.Option(False, "--prometheus", "-p", help="Экспортировать в формате Prometheus"),
|
|
293
|
+
json: bool = typer.Option(False, "--json", help="Экспортировать результаты в JSON"),
|
|
294
|
+
yaml: bool = typer.Option(False, "--yaml", help="Экспортировать результаты в YAML"),
|
|
295
|
+
):
|
|
296
|
+
"""
|
|
297
|
+
Собирает метрики о конфигурации Nginx.
|
|
298
|
+
|
|
299
|
+
Показывает:
|
|
300
|
+
- Количество upstream, server, location блоков
|
|
301
|
+
- Количество серверов в каждом upstream
|
|
302
|
+
- Статистику по директивам
|
|
303
|
+
- Статистику по server блокам (SSL, HTTP2, порты)
|
|
304
|
+
|
|
305
|
+
Поддерживает сравнение метрик между двумя версиями конфига.
|
|
306
|
+
Экспорт в Prometheus format для интеграции с мониторингом.
|
|
307
|
+
|
|
308
|
+
Пример:
|
|
309
|
+
nginx-lens metrics /etc/nginx/nginx.conf
|
|
310
|
+
nginx-lens metrics /etc/nginx/nginx.conf --prometheus
|
|
311
|
+
nginx-lens metrics /etc/nginx/nginx.conf --compare /etc/nginx/nginx.conf.old
|
|
312
|
+
"""
|
|
313
|
+
# Собираем метрики
|
|
314
|
+
metrics_data = _collect_metrics(config_path)
|
|
315
|
+
|
|
316
|
+
# Если нужно сравнить с другим конфигом
|
|
317
|
+
if compare_with:
|
|
318
|
+
compare_metrics = _collect_metrics(compare_with)
|
|
319
|
+
metrics_data["comparison"] = _compare_metrics(metrics_data, compare_metrics)
|
|
320
|
+
|
|
321
|
+
# Экспорт в Prometheus
|
|
322
|
+
if prometheus:
|
|
323
|
+
prometheus_output = _export_prometheus(metrics_data)
|
|
324
|
+
console.print(prometheus_output)
|
|
325
|
+
sys.exit(0)
|
|
326
|
+
|
|
327
|
+
# Экспорт в JSON/YAML
|
|
328
|
+
if json or yaml:
|
|
329
|
+
from exporter.json_yaml import print_export
|
|
330
|
+
export_data = {
|
|
331
|
+
"timestamp": __import__('datetime').datetime.now().isoformat(),
|
|
332
|
+
"metrics": metrics_data
|
|
333
|
+
}
|
|
334
|
+
format_type = 'json' if json else 'yaml'
|
|
335
|
+
print_export(export_data, format_type)
|
|
336
|
+
sys.exit(0)
|
|
337
|
+
|
|
338
|
+
# Обычный вывод
|
|
339
|
+
_print_metrics_table(metrics_data, compare_with is not None)
|
|
340
|
+
sys.exit(0)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def _compare_metrics(metrics1: Dict[str, Any], metrics2: Dict[str, Any]) -> Dict[str, Any]:
|
|
344
|
+
"""
|
|
345
|
+
Сравнивает метрики двух конфигураций.
|
|
346
|
+
|
|
347
|
+
Args:
|
|
348
|
+
metrics1: Метрики первой конфигурации
|
|
349
|
+
metrics2: Метрики второй конфигурации
|
|
350
|
+
|
|
351
|
+
Returns:
|
|
352
|
+
Словарь с различиями
|
|
353
|
+
"""
|
|
354
|
+
comparison = {
|
|
355
|
+
"blocks": {},
|
|
356
|
+
"upstream_servers": {},
|
|
357
|
+
"directives": {},
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
# Сравнение блоков
|
|
361
|
+
for block_type in ["upstream", "server", "location"]:
|
|
362
|
+
count1 = metrics1["blocks"][block_type]
|
|
363
|
+
count2 = metrics2["blocks"][block_type]
|
|
364
|
+
diff = count1 - count2
|
|
365
|
+
comparison["blocks"][block_type] = {
|
|
366
|
+
"current": count1,
|
|
367
|
+
"previous": count2,
|
|
368
|
+
"diff": diff,
|
|
369
|
+
"percent_change": (diff / count2 * 100) if count2 > 0 else 0
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
# Сравнение upstream серверов
|
|
373
|
+
total1 = metrics1["upstream_servers"]["total"]
|
|
374
|
+
total2 = metrics2["upstream_servers"]["total"]
|
|
375
|
+
comparison["upstream_servers"]["total"] = {
|
|
376
|
+
"current": total1,
|
|
377
|
+
"previous": total2,
|
|
378
|
+
"diff": total1 - total2,
|
|
379
|
+
"percent_change": ((total1 - total2) / total2 * 100) if total2 > 0 else 0
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
# Сравнение директив (топ-10)
|
|
383
|
+
all_directives = set(metrics1["directives"].keys()) | set(metrics2["directives"].keys())
|
|
384
|
+
top_directives = sorted(all_directives, key=lambda d: metrics1["directives"].get(d, 0) + metrics2["directives"].get(d, 0), reverse=True)[:10]
|
|
385
|
+
|
|
386
|
+
for directive in top_directives:
|
|
387
|
+
count1 = metrics1["directives"].get(directive, 0)
|
|
388
|
+
count2 = metrics2["directives"].get(directive, 0)
|
|
389
|
+
diff = count1 - count2
|
|
390
|
+
comparison["directives"][directive] = {
|
|
391
|
+
"current": count1,
|
|
392
|
+
"previous": count2,
|
|
393
|
+
"diff": diff,
|
|
394
|
+
"percent_change": (diff / count2 * 100) if count2 > 0 else 0
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return comparison
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def _print_metrics_table(metrics_data: Dict[str, Any], show_comparison: bool = False):
|
|
401
|
+
"""
|
|
402
|
+
Выводит метрики в виде таблицы.
|
|
403
|
+
|
|
404
|
+
Args:
|
|
405
|
+
metrics_data: Словарь с метриками
|
|
406
|
+
show_comparison: Показывать ли сравнение
|
|
407
|
+
"""
|
|
408
|
+
console.print(Panel("[bold blue]Метрики конфигурации Nginx[/bold blue]", box=box.ROUNDED))
|
|
409
|
+
|
|
410
|
+
# Таблица блоков
|
|
411
|
+
table = Table(title="Блоки", show_header=True, header_style="bold blue")
|
|
412
|
+
table.add_column("Тип блока")
|
|
413
|
+
table.add_column("Количество")
|
|
414
|
+
|
|
415
|
+
if show_comparison:
|
|
416
|
+
table.add_column("Предыдущее")
|
|
417
|
+
table.add_column("Изменение")
|
|
418
|
+
|
|
419
|
+
blocks = metrics_data["blocks"]
|
|
420
|
+
comparison = metrics_data.get("comparison", {})
|
|
421
|
+
|
|
422
|
+
for block_type in ["upstream", "server", "location"]:
|
|
423
|
+
count = blocks[block_type]
|
|
424
|
+
if show_comparison and "blocks" in comparison:
|
|
425
|
+
prev = comparison["blocks"].get(block_type, {}).get("previous", 0)
|
|
426
|
+
diff = comparison["blocks"].get(block_type, {}).get("diff", 0)
|
|
427
|
+
color = "green" if diff >= 0 else "red"
|
|
428
|
+
table.add_row(
|
|
429
|
+
block_type,
|
|
430
|
+
str(count),
|
|
431
|
+
str(prev),
|
|
432
|
+
f"[{color}]{diff:+d}[/{color}]"
|
|
433
|
+
)
|
|
434
|
+
else:
|
|
435
|
+
table.add_row(block_type, str(count))
|
|
436
|
+
|
|
437
|
+
console.print(table)
|
|
438
|
+
|
|
439
|
+
# Таблица upstream серверов
|
|
440
|
+
if metrics_data["upstream_servers"]["by_upstream"]:
|
|
441
|
+
table = Table(title="Upstream серверы", show_header=True, header_style="bold blue")
|
|
442
|
+
table.add_column("Upstream")
|
|
443
|
+
table.add_column("Количество серверов")
|
|
444
|
+
|
|
445
|
+
for upstream_name, server_count in sorted(metrics_data["upstream_servers"]["by_upstream"].items()):
|
|
446
|
+
table.add_row(upstream_name, str(server_count))
|
|
447
|
+
|
|
448
|
+
table.add_row("[bold]Всего[/bold]", f"[bold]{metrics_data['upstream_servers']['total']}[/bold]")
|
|
449
|
+
console.print(table)
|
|
450
|
+
|
|
451
|
+
# Таблица топ-директив
|
|
452
|
+
top_directives = sorted(metrics_data["directives"].items(), key=lambda x: x[1], reverse=True)[:10]
|
|
453
|
+
if top_directives:
|
|
454
|
+
table = Table(title="Топ-10 директив", show_header=True, header_style="bold blue")
|
|
455
|
+
table.add_column("Директива")
|
|
456
|
+
table.add_column("Количество")
|
|
457
|
+
|
|
458
|
+
if show_comparison and "directives" in comparison:
|
|
459
|
+
table.add_column("Предыдущее")
|
|
460
|
+
table.add_column("Изменение")
|
|
461
|
+
|
|
462
|
+
for directive, count in top_directives:
|
|
463
|
+
if show_comparison and directive in comparison["directives"]:
|
|
464
|
+
comp = comparison["directives"][directive]
|
|
465
|
+
prev = comp["previous"]
|
|
466
|
+
diff = comp["diff"]
|
|
467
|
+
color = "green" if diff >= 0 else "red"
|
|
468
|
+
table.add_row(
|
|
469
|
+
directive,
|
|
470
|
+
str(count),
|
|
471
|
+
str(prev),
|
|
472
|
+
f"[{color}]{diff:+d}[/{color}]"
|
|
473
|
+
)
|
|
474
|
+
else:
|
|
475
|
+
table.add_row(directive, str(count))
|
|
476
|
+
|
|
477
|
+
console.print(table)
|
|
478
|
+
|
|
479
|
+
# Статистика по server блокам
|
|
480
|
+
server_stats = metrics_data["server_stats"]
|
|
481
|
+
if server_stats["listen_ports"] or server_stats["with_ssl"] > 0 or server_stats["with_http2"] > 0:
|
|
482
|
+
table = Table(title="Статистика server блоков", show_header=True, header_style="bold blue")
|
|
483
|
+
table.add_column("Метрика")
|
|
484
|
+
table.add_column("Значение")
|
|
485
|
+
|
|
486
|
+
table.add_row("С SSL", str(server_stats["with_ssl"]))
|
|
487
|
+
table.add_row("С HTTP/2", str(server_stats["with_http2"]))
|
|
488
|
+
|
|
489
|
+
if server_stats["listen_ports"]:
|
|
490
|
+
top_ports = sorted(server_stats["listen_ports"].items(), key=lambda x: x[1], reverse=True)[:5]
|
|
491
|
+
for port, count in top_ports:
|
|
492
|
+
table.add_row(f"Порт {port}", str(count))
|
|
493
|
+
|
|
494
|
+
console.print(table)
|
|
495
|
+
|
commands/resolve.py
CHANGED
|
@@ -1,30 +1,55 @@
|
|
|
1
1
|
import sys
|
|
2
|
+
from typing import Optional
|
|
2
3
|
import typer
|
|
3
4
|
from rich.console import Console
|
|
4
5
|
from rich.table import Table
|
|
5
6
|
from upstream_checker.checker import resolve_upstreams
|
|
7
|
+
from upstream_checker.dns_cache import disable_cache, enable_cache, clear_cache
|
|
6
8
|
from parser.nginx_parser import parse_nginx_config
|
|
7
9
|
from exporter.json_yaml import format_resolve_results, print_export
|
|
10
|
+
from config.config_loader import get_config
|
|
11
|
+
from utils.progress import ProgressManager
|
|
8
12
|
|
|
9
13
|
app = typer.Typer()
|
|
10
14
|
console = Console()
|
|
11
15
|
|
|
12
16
|
def resolve(
|
|
13
17
|
config_path: str = typer.Argument(..., help="Путь к nginx.conf"),
|
|
14
|
-
max_workers: int = typer.Option(
|
|
18
|
+
max_workers: Optional[int] = typer.Option(None, "--max-workers", "-w", help="Максимальное количество потоков для параллельной обработки"),
|
|
15
19
|
json: bool = typer.Option(False, "--json", help="Экспортировать результаты в JSON"),
|
|
16
20
|
yaml: bool = typer.Option(False, "--yaml", help="Экспортировать результаты в YAML"),
|
|
21
|
+
no_cache: bool = typer.Option(False, "--no-cache", help="Отключить кэширование DNS резолвинга"),
|
|
22
|
+
cache_ttl: Optional[int] = typer.Option(None, "--cache-ttl", help="Время жизни кэша в секундах"),
|
|
17
23
|
):
|
|
18
24
|
"""
|
|
19
25
|
Резолвит DNS имена upstream-серверов в IP-адреса.
|
|
20
26
|
Использует параллельную обработку для ускорения резолвинга множества upstream серверов.
|
|
27
|
+
Поддерживает кэширование результатов DNS резолвинга для ускорения повторных запусков.
|
|
21
28
|
|
|
22
29
|
Пример:
|
|
23
30
|
nginx-lens resolve /etc/nginx/nginx.conf
|
|
24
31
|
nginx-lens resolve /etc/nginx/nginx.conf --max-workers 20
|
|
32
|
+
nginx-lens resolve /etc/nginx/nginx.conf --no-cache
|
|
33
|
+
nginx-lens resolve /etc/nginx/nginx.conf --cache-ttl 600
|
|
25
34
|
"""
|
|
26
35
|
exit_code = 0
|
|
27
36
|
|
|
37
|
+
# Загружаем конфигурацию
|
|
38
|
+
config = get_config()
|
|
39
|
+
defaults = config.get_defaults()
|
|
40
|
+
cache_config = config.get_cache_config()
|
|
41
|
+
|
|
42
|
+
# Применяем значения из конфига, если не указаны через CLI
|
|
43
|
+
max_workers = max_workers if max_workers is not None else defaults.get("max_workers", 10)
|
|
44
|
+
cache_ttl = cache_ttl if cache_ttl is not None else cache_config.get("ttl", defaults.get("dns_cache_ttl", 300))
|
|
45
|
+
|
|
46
|
+
# Управление кэшем
|
|
47
|
+
use_cache = not no_cache and cache_config.get("enabled", True)
|
|
48
|
+
if no_cache:
|
|
49
|
+
disable_cache()
|
|
50
|
+
else:
|
|
51
|
+
enable_cache()
|
|
52
|
+
|
|
28
53
|
try:
|
|
29
54
|
tree = parse_nginx_config(config_path)
|
|
30
55
|
except FileNotFoundError:
|
|
@@ -48,7 +73,12 @@ def resolve(
|
|
|
48
73
|
console.print("[yellow]Не найдено ни одного upstream в конфигурации.[/yellow]")
|
|
49
74
|
sys.exit(0) # Нет upstream - это не ошибка, просто нет чего проверять
|
|
50
75
|
|
|
51
|
-
|
|
76
|
+
# Подсчитываем общее количество серверов для прогресс-бара
|
|
77
|
+
total_servers = sum(len(servers) for servers in upstreams.values())
|
|
78
|
+
|
|
79
|
+
# Резолвинг с прогресс-баром
|
|
80
|
+
with ProgressManager(description="Резолвинг DNS", show_progress=total_servers > 5) as pm:
|
|
81
|
+
results = resolve_upstreams(upstreams, max_workers=max_workers, use_cache=use_cache, cache_ttl=cache_ttl, progress_manager=pm)
|
|
52
82
|
|
|
53
83
|
# Экспорт в JSON/YAML
|
|
54
84
|
if json or yaml:
|