detectkit 0.1.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.
- detectkit/__init__.py +17 -0
- detectkit/alerting/__init__.py +13 -0
- detectkit/alerting/channels/__init__.py +21 -0
- detectkit/alerting/channels/base.py +191 -0
- detectkit/alerting/channels/email.py +146 -0
- detectkit/alerting/channels/factory.py +193 -0
- detectkit/alerting/channels/mattermost.py +53 -0
- detectkit/alerting/channels/slack.py +55 -0
- detectkit/alerting/channels/telegram.py +110 -0
- detectkit/alerting/channels/webhook.py +139 -0
- detectkit/alerting/orchestrator.py +368 -0
- detectkit/cli/__init__.py +1 -0
- detectkit/cli/commands/__init__.py +1 -0
- detectkit/cli/commands/init.py +282 -0
- detectkit/cli/commands/run.py +427 -0
- detectkit/cli/commands/test_alert.py +184 -0
- detectkit/cli/main.py +186 -0
- detectkit/config/__init__.py +30 -0
- detectkit/config/metric_config.py +467 -0
- detectkit/config/profile.py +285 -0
- detectkit/config/project_config.py +164 -0
- detectkit/core/__init__.py +6 -0
- detectkit/core/interval.py +132 -0
- detectkit/core/models.py +106 -0
- detectkit/database/__init__.py +27 -0
- detectkit/database/clickhouse_manager.py +385 -0
- detectkit/database/internal_tables.py +581 -0
- detectkit/database/manager.py +324 -0
- detectkit/database/tables.py +134 -0
- detectkit/detectors/__init__.py +6 -0
- detectkit/detectors/base.py +222 -0
- detectkit/detectors/factory.py +138 -0
- detectkit/detectors/statistical/__init__.py +8 -0
- detectkit/detectors/statistical/iqr.py +230 -0
- detectkit/detectors/statistical/mad.py +423 -0
- detectkit/detectors/statistical/manual_bounds.py +177 -0
- detectkit/detectors/statistical/zscore.py +225 -0
- detectkit/loaders/__init__.py +6 -0
- detectkit/loaders/metric_loader.py +470 -0
- detectkit/loaders/query_template.py +164 -0
- detectkit/orchestration/__init__.py +9 -0
- detectkit/orchestration/task_manager.py +698 -0
- detectkit/utils/__init__.py +1 -0
- detectkit-0.1.0.dist-info/METADATA +231 -0
- detectkit-0.1.0.dist-info/RECORD +49 -0
- detectkit-0.1.0.dist-info/WHEEL +5 -0
- detectkit-0.1.0.dist-info/entry_points.txt +2 -0
- detectkit-0.1.0.dist-info/licenses/LICENSE +21 -0
- detectkit-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test alert command - send test alert to configured channels.
|
|
3
|
+
|
|
4
|
+
Allows testing alert rendering and channel delivery without real anomalies.
|
|
5
|
+
Useful for:
|
|
6
|
+
- Verifying Mattermost/Slack message formatting
|
|
7
|
+
- Testing webhook connectivity
|
|
8
|
+
- Previewing alert templates
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from datetime import datetime, timezone
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Optional
|
|
14
|
+
|
|
15
|
+
import numpy as np
|
|
16
|
+
|
|
17
|
+
from detectkit.alerting.channels.base import AlertData
|
|
18
|
+
from detectkit.alerting.channels.factory import AlertChannelFactory
|
|
19
|
+
from detectkit.config.metric_config import MetricConfig
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def create_mock_alert_data(
|
|
23
|
+
metric_config: MetricConfig,
|
|
24
|
+
timezone_display: str = "UTC",
|
|
25
|
+
) -> AlertData:
|
|
26
|
+
"""
|
|
27
|
+
Create realistic mock AlertData for testing.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
metric_config: Metric configuration
|
|
31
|
+
timezone_display: Timezone for display
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
AlertData with mock anomaly data
|
|
35
|
+
"""
|
|
36
|
+
# Use current time
|
|
37
|
+
now = datetime.now(timezone.utc)
|
|
38
|
+
|
|
39
|
+
# Create realistic mock data
|
|
40
|
+
return AlertData(
|
|
41
|
+
metric_name=metric_config.name,
|
|
42
|
+
timestamp=np.datetime64(now, "ms"),
|
|
43
|
+
timezone=timezone_display,
|
|
44
|
+
value=0.8532, # Mock anomalous value
|
|
45
|
+
confidence_lower=0.4521,
|
|
46
|
+
confidence_upper=0.6234,
|
|
47
|
+
detector_name="MADDetector:threshold=3.0",
|
|
48
|
+
detector_params='{"threshold": 3.0, "window_size": 8640}',
|
|
49
|
+
direction="above",
|
|
50
|
+
severity=4.52,
|
|
51
|
+
detection_metadata={
|
|
52
|
+
"global_median": 0.5123,
|
|
53
|
+
"adjusted_median": 0.5234,
|
|
54
|
+
"seasonality_groups": [
|
|
55
|
+
{
|
|
56
|
+
"group": ["offset_10minutes", "league_day"],
|
|
57
|
+
"median_multiplier": 1.023,
|
|
58
|
+
"mad_multiplier": 0.876,
|
|
59
|
+
"group_size": 23,
|
|
60
|
+
}
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
consecutive_count=3,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def run_test_alert(metric_name: str, profile: Optional[str] = None):
|
|
68
|
+
"""
|
|
69
|
+
Send test alert for specified metric.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
metric_name: Name of metric to test alert for
|
|
73
|
+
profile: Optional profile override
|
|
74
|
+
"""
|
|
75
|
+
# Load project config
|
|
76
|
+
project_root = Path.cwd()
|
|
77
|
+
project_config_path = project_root / "detectkit_project.yml"
|
|
78
|
+
|
|
79
|
+
if not project_config_path.exists():
|
|
80
|
+
print("Error: No detectkit_project.yml found in current directory")
|
|
81
|
+
print("Run this command from your detectkit project root")
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
# Load project config manually (avoid validation issues)
|
|
85
|
+
import yaml
|
|
86
|
+
|
|
87
|
+
with open(project_config_path) as f:
|
|
88
|
+
project_data = yaml.safe_load(f)
|
|
89
|
+
|
|
90
|
+
metrics_dir_name = project_data.get("metrics_path", "metrics")
|
|
91
|
+
|
|
92
|
+
# Find metric config
|
|
93
|
+
metrics_dir = project_root / metrics_dir_name
|
|
94
|
+
metric_files = list(metrics_dir.glob("**/*.yml")) + list(
|
|
95
|
+
metrics_dir.glob("**/*.yaml")
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
metric_config = None
|
|
99
|
+
for metric_file in metric_files:
|
|
100
|
+
try:
|
|
101
|
+
config = MetricConfig.from_yaml_file(metric_file)
|
|
102
|
+
if config.name == metric_name:
|
|
103
|
+
metric_config = config
|
|
104
|
+
break
|
|
105
|
+
except Exception:
|
|
106
|
+
continue
|
|
107
|
+
|
|
108
|
+
if not metric_config:
|
|
109
|
+
print(f"Error: Metric '{metric_name}' not found")
|
|
110
|
+
print(f"Searched in: {metrics_dir}")
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
# Check if alerting is configured
|
|
114
|
+
if not metric_config.alerting or not metric_config.alerting.enabled:
|
|
115
|
+
print(f"Error: Alerting not enabled for metric '{metric_name}'")
|
|
116
|
+
print("Enable alerting in metric config (alerting.enabled: true)")
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
if not metric_config.alerting.channels:
|
|
120
|
+
print(f"Error: No alert channels configured for metric '{metric_name}'")
|
|
121
|
+
print("Add channels in metric config (alerting.channels: [...])")
|
|
122
|
+
return
|
|
123
|
+
|
|
124
|
+
# Load profiles
|
|
125
|
+
profiles_path = project_root / "profiles.yml"
|
|
126
|
+
if not profiles_path.exists():
|
|
127
|
+
print("Error: profiles.yml not found")
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
import yaml
|
|
131
|
+
|
|
132
|
+
with open(profiles_path) as f:
|
|
133
|
+
profiles_data = yaml.safe_load(f)
|
|
134
|
+
|
|
135
|
+
alert_channels_config = profiles_data.get("alert_channels", {})
|
|
136
|
+
|
|
137
|
+
# Get timezone for display
|
|
138
|
+
timezone_display = metric_config.alerting.timezone or "UTC"
|
|
139
|
+
|
|
140
|
+
# Create mock alert data
|
|
141
|
+
print(f"\n📨 Sending test alert for metric: {metric_name}")
|
|
142
|
+
print(f" Timezone: {timezone_display}")
|
|
143
|
+
print(f" Channels: {', '.join(metric_config.alerting.channels)}\n")
|
|
144
|
+
|
|
145
|
+
alert_data = create_mock_alert_data(metric_config, timezone_display)
|
|
146
|
+
|
|
147
|
+
# Send to each configured channel
|
|
148
|
+
success_count = 0
|
|
149
|
+
for channel_name in metric_config.alerting.channels:
|
|
150
|
+
if channel_name not in alert_channels_config:
|
|
151
|
+
print(f"⚠️ Channel '{channel_name}' not found in profiles.yml - skipping")
|
|
152
|
+
continue
|
|
153
|
+
|
|
154
|
+
channel_config = alert_channels_config[channel_name]
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
# Create channel instance
|
|
158
|
+
# channel_config должен содержать 'type' + остальные параметры
|
|
159
|
+
channel = AlertChannelFactory.create_from_config(channel_config)
|
|
160
|
+
|
|
161
|
+
# Get custom template if configured
|
|
162
|
+
template = None
|
|
163
|
+
if metric_config.alerting.template_consecutive:
|
|
164
|
+
template = metric_config.alerting.template_consecutive
|
|
165
|
+
|
|
166
|
+
# Send alert
|
|
167
|
+
print(f" → Sending to {channel_name}...", end=" ")
|
|
168
|
+
success = channel.send(alert_data, template=template)
|
|
169
|
+
|
|
170
|
+
if success:
|
|
171
|
+
print("✓ SUCCESS")
|
|
172
|
+
success_count += 1
|
|
173
|
+
else:
|
|
174
|
+
print("✗ FAILED")
|
|
175
|
+
|
|
176
|
+
except Exception as e:
|
|
177
|
+
print(f"✗ ERROR: {e}")
|
|
178
|
+
|
|
179
|
+
# Summary
|
|
180
|
+
print(f"\n{'✓' if success_count > 0 else '✗'} Sent test alert to {success_count}/{len(metric_config.alerting.channels)} channels")
|
|
181
|
+
|
|
182
|
+
if success_count > 0:
|
|
183
|
+
print("\n💡 Check your configured channels to verify message formatting")
|
|
184
|
+
print(f" Mock data used: value=0.8532, confidence=[0.4521, 0.6234], severity=4.52")
|
detectkit/cli/main.py
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Main CLI entry point for detectkit.
|
|
3
|
+
|
|
4
|
+
Provides dbt-like commands:
|
|
5
|
+
- dtk init <project_name>
|
|
6
|
+
- dtk run --select <selector>
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@click.group()
|
|
13
|
+
@click.version_option(version="0.1.0", prog_name="detectkit")
|
|
14
|
+
def cli():
|
|
15
|
+
"""
|
|
16
|
+
detectkit - Metric monitoring with automatic anomaly detection.
|
|
17
|
+
|
|
18
|
+
A dbt-like tool for monitoring time-series metrics with anomaly detection
|
|
19
|
+
and alerting.
|
|
20
|
+
|
|
21
|
+
Examples:
|
|
22
|
+
dtk init my_project
|
|
23
|
+
dtk run --select cpu_usage
|
|
24
|
+
dtk run --select tag:critical --steps load,detect
|
|
25
|
+
"""
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@cli.command()
|
|
30
|
+
@click.argument("project_name")
|
|
31
|
+
@click.option(
|
|
32
|
+
"--target-dir",
|
|
33
|
+
"-d",
|
|
34
|
+
default=".",
|
|
35
|
+
help="Directory to create project in (default: current directory)",
|
|
36
|
+
)
|
|
37
|
+
def init(project_name: str, target_dir: str):
|
|
38
|
+
"""
|
|
39
|
+
Initialize a new detectkit project.
|
|
40
|
+
|
|
41
|
+
Creates project structure with configuration files and directories:
|
|
42
|
+
- detectkit_project.yml (project config)
|
|
43
|
+
- profiles.yml (database connections)
|
|
44
|
+
- metrics/ (metric definitions)
|
|
45
|
+
- sql/ (SQL queries)
|
|
46
|
+
|
|
47
|
+
Example:
|
|
48
|
+
dtk init my_monitoring_project
|
|
49
|
+
dtk init analytics --target-dir /opt/projects
|
|
50
|
+
"""
|
|
51
|
+
from detectkit.cli.commands.init import run_init
|
|
52
|
+
|
|
53
|
+
run_init(project_name, target_dir)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@cli.command()
|
|
57
|
+
@click.option(
|
|
58
|
+
"--select",
|
|
59
|
+
"-s",
|
|
60
|
+
help="Selector for metrics to run (metric name, path, or tag)",
|
|
61
|
+
required=True,
|
|
62
|
+
)
|
|
63
|
+
@click.option(
|
|
64
|
+
"--exclude",
|
|
65
|
+
"-e",
|
|
66
|
+
help="Selector for metrics to exclude (metric name, path, or tag)",
|
|
67
|
+
)
|
|
68
|
+
@click.option(
|
|
69
|
+
"--steps",
|
|
70
|
+
default="load,detect,alert",
|
|
71
|
+
help="Pipeline steps to execute (default: load,detect,alert)",
|
|
72
|
+
)
|
|
73
|
+
@click.option(
|
|
74
|
+
"--from",
|
|
75
|
+
"from_date",
|
|
76
|
+
help="Start date for data loading (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)",
|
|
77
|
+
)
|
|
78
|
+
@click.option(
|
|
79
|
+
"--to",
|
|
80
|
+
"to_date",
|
|
81
|
+
help="End date for data loading (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)",
|
|
82
|
+
)
|
|
83
|
+
@click.option(
|
|
84
|
+
"--full-refresh",
|
|
85
|
+
is_flag=True,
|
|
86
|
+
help="Delete all existing data and reload from scratch",
|
|
87
|
+
)
|
|
88
|
+
@click.option(
|
|
89
|
+
"--force",
|
|
90
|
+
is_flag=True,
|
|
91
|
+
help="Ignore task locks (use with caution)",
|
|
92
|
+
)
|
|
93
|
+
@click.option(
|
|
94
|
+
"--profile",
|
|
95
|
+
help="Profile to use (default: from project config)",
|
|
96
|
+
)
|
|
97
|
+
def run(
|
|
98
|
+
select: str,
|
|
99
|
+
exclude: str,
|
|
100
|
+
steps: str,
|
|
101
|
+
from_date: str,
|
|
102
|
+
to_date: str,
|
|
103
|
+
full_refresh: bool,
|
|
104
|
+
force: bool,
|
|
105
|
+
profile: str,
|
|
106
|
+
):
|
|
107
|
+
"""
|
|
108
|
+
Run metric processing pipeline.
|
|
109
|
+
|
|
110
|
+
Select metrics to process using --select:
|
|
111
|
+
- Metric name: --select cpu_usage
|
|
112
|
+
- Path pattern: --select metrics/critical/*.yml
|
|
113
|
+
- Tag: --select tag:critical
|
|
114
|
+
|
|
115
|
+
Control pipeline steps with --steps:
|
|
116
|
+
- All steps: --steps load,detect,alert (default)
|
|
117
|
+
- Load only: --steps load
|
|
118
|
+
- Detect and alert: --steps detect,alert
|
|
119
|
+
|
|
120
|
+
Examples:
|
|
121
|
+
# Run all steps for single metric
|
|
122
|
+
dtk run --select cpu_usage
|
|
123
|
+
|
|
124
|
+
# Load data only for multiple metrics
|
|
125
|
+
dtk run --select "tag:critical" --steps load
|
|
126
|
+
|
|
127
|
+
# Reload data from specific date
|
|
128
|
+
dtk run --select cpu_usage --from 2024-01-01
|
|
129
|
+
|
|
130
|
+
# Full refresh (delete and reload all data)
|
|
131
|
+
dtk run --select cpu_usage --full-refresh
|
|
132
|
+
|
|
133
|
+
# Force run (ignore locks)
|
|
134
|
+
dtk run --select cpu_usage --force
|
|
135
|
+
"""
|
|
136
|
+
from detectkit.cli.commands.run import run_command
|
|
137
|
+
|
|
138
|
+
run_command(
|
|
139
|
+
select=select,
|
|
140
|
+
exclude=exclude,
|
|
141
|
+
steps=steps,
|
|
142
|
+
from_date=from_date,
|
|
143
|
+
to_date=to_date,
|
|
144
|
+
full_refresh=full_refresh,
|
|
145
|
+
force=force,
|
|
146
|
+
profile=profile,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@cli.command()
|
|
151
|
+
@click.argument("metric_name")
|
|
152
|
+
@click.option(
|
|
153
|
+
"--profile",
|
|
154
|
+
help="Profile to use (default: from project config)",
|
|
155
|
+
)
|
|
156
|
+
def test_alert(metric_name: str, profile: str):
|
|
157
|
+
"""
|
|
158
|
+
Send test alert for a metric.
|
|
159
|
+
|
|
160
|
+
Sends a test alert with mock anomaly data to all configured channels
|
|
161
|
+
for the specified metric. Useful for:
|
|
162
|
+
- Testing alert channel connectivity
|
|
163
|
+
- Verifying message formatting and rendering
|
|
164
|
+
- Previewing custom alert templates
|
|
165
|
+
|
|
166
|
+
The test alert uses realistic mock data:
|
|
167
|
+
- Current timestamp
|
|
168
|
+
- Mock anomaly value (0.8532)
|
|
169
|
+
- Mock confidence interval [0.4521, 0.6234]
|
|
170
|
+
- Mock severity (4.52)
|
|
171
|
+
- 3 consecutive anomalies
|
|
172
|
+
|
|
173
|
+
Examples:
|
|
174
|
+
# Test alert for single metric
|
|
175
|
+
dtk test-alert cpu_usage
|
|
176
|
+
|
|
177
|
+
# Test with specific profile
|
|
178
|
+
dtk test-alert cpu_usage --profile production
|
|
179
|
+
"""
|
|
180
|
+
from detectkit.cli.commands.test_alert import run_test_alert
|
|
181
|
+
|
|
182
|
+
run_test_alert(metric_name=metric_name, profile=profile)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
if __name__ == "__main__":
|
|
186
|
+
cli()
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Configuration management for detectkit."""
|
|
2
|
+
|
|
3
|
+
from detectkit.config.profile import ProfileConfig, ProfilesConfig
|
|
4
|
+
from detectkit.config.metric_config import (
|
|
5
|
+
MetricConfig,
|
|
6
|
+
DetectorConfig,
|
|
7
|
+
AlertConfig,
|
|
8
|
+
QueryColumnsConfig,
|
|
9
|
+
TablesConfig,
|
|
10
|
+
)
|
|
11
|
+
from detectkit.config.project_config import (
|
|
12
|
+
ProjectConfig,
|
|
13
|
+
ProjectPathsConfig,
|
|
14
|
+
ProjectTablesConfig,
|
|
15
|
+
ProjectTimeoutsConfig,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"ProfileConfig",
|
|
20
|
+
"ProfilesConfig",
|
|
21
|
+
"MetricConfig",
|
|
22
|
+
"DetectorConfig",
|
|
23
|
+
"AlertConfig",
|
|
24
|
+
"QueryColumnsConfig",
|
|
25
|
+
"TablesConfig",
|
|
26
|
+
"ProjectConfig",
|
|
27
|
+
"ProjectPathsConfig",
|
|
28
|
+
"ProjectTablesConfig",
|
|
29
|
+
"ProjectTimeoutsConfig",
|
|
30
|
+
]
|