boundary-analyzer 0.2.0__tar.gz
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.
- boundary_analyzer-0.2.0/CHANGELOG.md +34 -0
- boundary_analyzer-0.2.0/PKG-INFO +50 -0
- boundary_analyzer-0.2.0/README.md +269 -0
- boundary_analyzer-0.2.0/pyproject.toml +29 -0
- boundary_analyzer-0.2.0/setup.cfg +4 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/__init__.py +0 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/__main__.py +7 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/auto_setup/__init__.py +4 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/auto_setup/django_wrapper.py +48 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/auto_setup/djangorest_wrapper.py +44 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/auto_setup/fastapi_wrapper.py +59 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/auto_setup/flask_wrapper.py +53 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/auto_setup/setup_instrumentation.py +805 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/auto_setup/starlette_wrapper.py +43 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/auto_setup/tornado_wrapper.py +38 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/cli.py +390 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/dashboard/__init__.py +0 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/dashboard/app.py +1778 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/dashboard/charts.py +910 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/detection/db_table_extractor.py +241 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/detection/endpoint_extractor.py +150 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/detection/endpoint_normalizer.py +142 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/detection/mapping_builder.py +137 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/llm/__init__.py +11 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/llm/analysis.py +323 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/llm/client.py +100 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/llm/context.py +229 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/llm/instrumentation.py +52 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/llm/prompts.py +152 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/metrics/cohesion_rules.py +20 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/metrics/scom.py +207 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/metrics/threshold_ultimate.py +121 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/parsing/trace_reader.py +114 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/pipeline/__init__.py +0 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/pipeline/run_pipeline.py +189 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/pipeline/step_01_collect_traces.py +88 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/pipeline/step_02_read_traces.py +29 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/pipeline/step_03_find_endpoints.py +38 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/pipeline/step_04_find_db_tables.py +43 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/pipeline/step_05_build_mapping.py +55 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/pipeline/step_06_compute_scom.py +74 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/pipeline/step_07_rank_and_flag.py +99 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/pipeline/step_08_make_report.py +80 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/reporting/__init__.py +0 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/reporting/report_builder.py +122 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/settings_loader.py +166 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/test_data/__init__.py +0 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/test_data/generate_test_traces.py +306 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/validation/__init__.py +0 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer/validation/compare_metrics.py +250 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer.egg-info/PKG-INFO +50 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer.egg-info/SOURCES.txt +63 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer.egg-info/dependency_links.txt +1 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer.egg-info/entry_points.txt +3 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer.egg-info/requires.txt +5 -0
- boundary_analyzer-0.2.0/src/boundary_analyzer.egg-info/top_level.txt +1 -0
- boundary_analyzer-0.2.0/tests/test_dashboard_smoke.py +21 -0
- boundary_analyzer-0.2.0/tests/test_llm_analysis.py +103 -0
- boundary_analyzer-0.2.0/tests/test_llm_client.py +137 -0
- boundary_analyzer-0.2.0/tests/test_llm_context.py +179 -0
- boundary_analyzer-0.2.0/tests/test_llm_instrumentation.py +57 -0
- boundary_analyzer-0.2.0/tests/test_llm_prompts.py +37 -0
- boundary_analyzer-0.2.0/tests/test_pipeline_integration.py +149 -0
- boundary_analyzer-0.2.0/tests/test_setup_instructions.py +19 -0
- boundary_analyzer-0.2.0/tests/test_teastore_pipeline.py +172 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## v1.0.0 (2026-06-11)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
- **SCOM pipeline** : computes Service-COhesion Metric from Jaeger traces (health filtering, endpoint extraction, DB table detection, endpoint-table mapping, threshold analysis, report generation)
|
|
7
|
+
- **CLI tool** : `mba` / `boundary-analyzer` commands (`run`, `setup`, `dashboard`, `teastore`)
|
|
8
|
+
- **Auto-instrumentation** : auto-detects Python microservices (FastAPI, Flask, Django), injects OpenTelemetry, collects traces via Jaeger, runs SCOM analysis
|
|
9
|
+
- **TeaStore support** : Docker Compose deployment with OTel Java agent, traffic generator, trace exporter, full SCOM pipeline
|
|
10
|
+
- **Dashboard** : interactive Dash web UI for SCOM results
|
|
11
|
+
- **LLM analysis** (optional) : AI-powered narrative report via OpenRouter (Qwen), disabled by default
|
|
12
|
+
|
|
13
|
+
### Improvements
|
|
14
|
+
- Segment-based health matching (`HEALTH_KEYWORDS`) instead of fragile `endswith` — `/health/all`, `/auth/health`, `/ready/isready`, `/metrics` (via `http.target`) correctly filtered
|
|
15
|
+
- `--skip-no-db-services` flag to exclude stateless services (proxy, orchestrator, etc.) from SCOM ranking
|
|
16
|
+
- `run_teastore()` function extracted for programmatic access
|
|
17
|
+
|
|
18
|
+
### Bug fixes
|
|
19
|
+
- MissingGreenlet in classroom-repository (added `selectinload`)
|
|
20
|
+
- datetime timezone-aware comparison in enrollment-service
|
|
21
|
+
- `academic_year` int→str conversion in enrollment-service
|
|
22
|
+
- Scope bug in `cleaned_parts` variable in CLI cleanup logic
|
|
23
|
+
- SQLAlchemy duplicate instrumentation (event listeners only, no `SQLAlchemyInstrumentor`/`AsyncPGInstrumentor`)
|
|
24
|
+
- `[project.scripts]` whitespace in pyproject.toml
|
|
25
|
+
|
|
26
|
+
### Tests
|
|
27
|
+
- 74 tests total (58 existing + 16 TeaStore)
|
|
28
|
+
- TeaStore synthetic fixtures (persistence-service with 5 tables, auth-service without DB)
|
|
29
|
+
- 3 test classes : TeaStorePipelineTest, TeaStoreSkipNoDbTest, TeaStoreNoFilterTest
|
|
30
|
+
|
|
31
|
+
### Infrastructure
|
|
32
|
+
- CI via GitHub Actions (`.github/workflows/ci.yml`) — Python 3.11 × 3.12
|
|
33
|
+
- `mba` CLI alias alongside `boundary-analyzer`
|
|
34
|
+
- Version bump to 0.2.0
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: boundary-analyzer
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: SCOM-based microservice boundary analysis from Jaeger traces
|
|
5
|
+
Author-email: Ray Ague <rayague03@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/rayague/measure-automation
|
|
8
|
+
Project-URL: Repository, https://github.com/rayague/measure-automation
|
|
9
|
+
Requires-Python: >=3.11
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: requests>=2.31.0
|
|
12
|
+
Requires-Dist: pandas>=2.2.0
|
|
13
|
+
Requires-Dist: PyYAML>=6.0.1
|
|
14
|
+
Requires-Dist: dash>=2.14.0
|
|
15
|
+
Requires-Dist: plotly>=5.18.0
|
|
16
|
+
|
|
17
|
+
# Changelog
|
|
18
|
+
|
|
19
|
+
## v1.0.0 (2026-06-11)
|
|
20
|
+
|
|
21
|
+
### Features
|
|
22
|
+
- **SCOM pipeline** : computes Service-COhesion Metric from Jaeger traces (health filtering, endpoint extraction, DB table detection, endpoint-table mapping, threshold analysis, report generation)
|
|
23
|
+
- **CLI tool** : `mba` / `boundary-analyzer` commands (`run`, `setup`, `dashboard`, `teastore`)
|
|
24
|
+
- **Auto-instrumentation** : auto-detects Python microservices (FastAPI, Flask, Django), injects OpenTelemetry, collects traces via Jaeger, runs SCOM analysis
|
|
25
|
+
- **TeaStore support** : Docker Compose deployment with OTel Java agent, traffic generator, trace exporter, full SCOM pipeline
|
|
26
|
+
- **Dashboard** : interactive Dash web UI for SCOM results
|
|
27
|
+
- **LLM analysis** (optional) : AI-powered narrative report via OpenRouter (Qwen), disabled by default
|
|
28
|
+
|
|
29
|
+
### Improvements
|
|
30
|
+
- Segment-based health matching (`HEALTH_KEYWORDS`) instead of fragile `endswith` — `/health/all`, `/auth/health`, `/ready/isready`, `/metrics` (via `http.target`) correctly filtered
|
|
31
|
+
- `--skip-no-db-services` flag to exclude stateless services (proxy, orchestrator, etc.) from SCOM ranking
|
|
32
|
+
- `run_teastore()` function extracted for programmatic access
|
|
33
|
+
|
|
34
|
+
### Bug fixes
|
|
35
|
+
- MissingGreenlet in classroom-repository (added `selectinload`)
|
|
36
|
+
- datetime timezone-aware comparison in enrollment-service
|
|
37
|
+
- `academic_year` int→str conversion in enrollment-service
|
|
38
|
+
- Scope bug in `cleaned_parts` variable in CLI cleanup logic
|
|
39
|
+
- SQLAlchemy duplicate instrumentation (event listeners only, no `SQLAlchemyInstrumentor`/`AsyncPGInstrumentor`)
|
|
40
|
+
- `[project.scripts]` whitespace in pyproject.toml
|
|
41
|
+
|
|
42
|
+
### Tests
|
|
43
|
+
- 74 tests total (58 existing + 16 TeaStore)
|
|
44
|
+
- TeaStore synthetic fixtures (persistence-service with 5 tables, auth-service without DB)
|
|
45
|
+
- 3 test classes : TeaStorePipelineTest, TeaStoreSkipNoDbTest, TeaStoreNoFilterTest
|
|
46
|
+
|
|
47
|
+
### Infrastructure
|
|
48
|
+
- CI via GitHub Actions (`.github/workflows/ci.yml`) — Python 3.11 × 3.12
|
|
49
|
+
- `mba` CLI alias alongside `boundary-analyzer`
|
|
50
|
+
- Version bump to 0.2.0
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
# measure-automation
|
|
2
|
+
|
|
3
|
+
A tool for analyzing microservice boundaries using runtime traces from OpenTelemetry/Jaeger. It computes Service Cohesion Measure (SCOM) to detect services with low cohesion that may have wrong boundaries.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- Python 3.11+
|
|
8
|
+
- Jaeger instance running (http://localhost:16686 by default)
|
|
9
|
+
- Your services instrumented with OpenTelemetry
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```powershell
|
|
14
|
+
python -m pip install -e .
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Or install dependencies manually:
|
|
18
|
+
|
|
19
|
+
```powershell
|
|
20
|
+
python -m pip install requests pandas pyyaml dash plotly
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Configuration
|
|
24
|
+
|
|
25
|
+
Edit `config/settings.yaml`:
|
|
26
|
+
|
|
27
|
+
```yaml
|
|
28
|
+
jaeger_base_url: "http://localhost:16686"
|
|
29
|
+
service_name: "YOUR_SERVICE_NAME" # Set to a real service from Jaeger
|
|
30
|
+
lookback_minutes: 10
|
|
31
|
+
limit_traces: 20
|
|
32
|
+
output_dir: "data/raw/traces"
|
|
33
|
+
|
|
34
|
+
# SCOM calculation method
|
|
35
|
+
# - "paper": CI/CImax normalization from the paper (endpoints < 2 => 0)
|
|
36
|
+
# - "weighted": weighted Jaccard (legacy)
|
|
37
|
+
# - "simple": unweighted Jaccard (legacy)
|
|
38
|
+
scom_method: "weighted" # Options: "paper", "weighted" or "simple"
|
|
39
|
+
table_weighting: true
|
|
40
|
+
endpoint_weighting: true
|
|
41
|
+
|
|
42
|
+
# Threshold method for suspicious services
|
|
43
|
+
threshold_method: "percentile" # Options: "percentile", "zscore", or "fixed"
|
|
44
|
+
threshold_percentile: 25.0
|
|
45
|
+
threshold_zscore: -1.5
|
|
46
|
+
scom_threshold: 0.5
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Pipeline Steps
|
|
50
|
+
|
|
51
|
+
Run the pipeline in order:
|
|
52
|
+
|
|
53
|
+
## One-command (Professional) Usage
|
|
54
|
+
|
|
55
|
+
After installation, you can run the full pipeline with a single command.
|
|
56
|
+
|
|
57
|
+
```powershell
|
|
58
|
+
boundary-analyzer run
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Equivalent:
|
|
62
|
+
|
|
63
|
+
```powershell
|
|
64
|
+
python -m boundary_analyzer run
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Options
|
|
68
|
+
|
|
69
|
+
- **`--skip-collect`**
|
|
70
|
+
Skips Step 01 (Jaeger trace collection) and reuses the existing traces in the folder configured by `output_dir` in `config/settings.yaml`.
|
|
71
|
+
|
|
72
|
+
- **`--dashboard`**
|
|
73
|
+
Launches the dashboard after the pipeline completes.
|
|
74
|
+
|
|
75
|
+
- **`--data-dir <path>`**
|
|
76
|
+
Base directory containing `interim/` and `processed/` for the dashboard (default: `data`).
|
|
77
|
+
|
|
78
|
+
- **`--dash-host <host>`**
|
|
79
|
+
Dashboard bind host (default: `127.0.0.1`). Use `0.0.0.0` to expose on LAN.
|
|
80
|
+
|
|
81
|
+
- **`--dash-port <port>`**
|
|
82
|
+
Dashboard port (default: `8050`).
|
|
83
|
+
|
|
84
|
+
- **`--settings <path>`**
|
|
85
|
+
Path to `settings.yaml`. This applies to all pipeline steps.
|
|
86
|
+
|
|
87
|
+
Note: for MongoDB spans, the tool counts collections as "tables" (for backward compatibility in CSV/report columns).
|
|
88
|
+
|
|
89
|
+
### Examples
|
|
90
|
+
|
|
91
|
+
Run everything (collect traces + compute results + report):
|
|
92
|
+
|
|
93
|
+
```powershell
|
|
94
|
+
boundary-analyzer run
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Reuse traces already collected and open the dashboard:
|
|
98
|
+
|
|
99
|
+
```powershell
|
|
100
|
+
boundary-analyzer run --skip-collect --dashboard
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Launch only the dashboard:
|
|
104
|
+
|
|
105
|
+
```powershell
|
|
106
|
+
boundary-analyzer dashboard
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The dashboard is available by default at:
|
|
110
|
+
|
|
111
|
+
`http://127.0.0.1:8050`
|
|
112
|
+
|
|
113
|
+
Launch the dashboard for a different results folder:
|
|
114
|
+
|
|
115
|
+
```powershell
|
|
116
|
+
boundary-analyzer dashboard --data-dir .\demo-service\scom_report
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Setup mode (when your project has no Jaeger / OpenTelemetry)
|
|
120
|
+
|
|
121
|
+
If your target project is not instrumented yet (no OpenTelemetry, no Jaeger), you can use the auto-setup command.
|
|
122
|
+
It will:
|
|
123
|
+
- detect the framework
|
|
124
|
+
- install OpenTelemetry packages (unless you pass `--no-install`)
|
|
125
|
+
- generate an instrumentation file for your app
|
|
126
|
+
- start Jaeger (unless you pass `--no-jaeger`)
|
|
127
|
+
- ask you to restart your app and send some traffic
|
|
128
|
+
- collect traces and run the analysis
|
|
129
|
+
|
|
130
|
+
```powershell
|
|
131
|
+
boundary-analyzer setup --project-path .\path\to\your-service
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Common options:
|
|
135
|
+
|
|
136
|
+
- **`--framework <name>`**: force a framework instead of auto-detect
|
|
137
|
+
- **`--service-name <name>`**: set the Jaeger service name
|
|
138
|
+
- **`--no-jaeger`**: skip starting Jaeger (use if already running)
|
|
139
|
+
- **`--no-install`**: skip installing OpenTelemetry packages
|
|
140
|
+
- **`--jaeger-host <host>`**: Jaeger host (default: `localhost`)
|
|
141
|
+
|
|
142
|
+
Example:
|
|
143
|
+
|
|
144
|
+
```powershell
|
|
145
|
+
boundary-analyzer setup --project-path .\demo-service --service-name demo-service
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Run setup and open the dashboard on the generated results:
|
|
149
|
+
|
|
150
|
+
```powershell
|
|
151
|
+
boundary-analyzer setup --project-path .\demo-service --service-name demo-service --dashboard
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Step 01: Collect traces from Jaeger
|
|
155
|
+
|
|
156
|
+
Collects trace data from Jaeger API for a specific service.
|
|
157
|
+
|
|
158
|
+
```powershell
|
|
159
|
+
python .\src\boundary_analyzer\pipeline\step_01_collect_traces.py
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**Output:** `data/raw/traces/jaeger_traces_{service}_{timestamp}.json`
|
|
163
|
+
|
|
164
|
+
### Step 02: Read and flatten traces
|
|
165
|
+
|
|
166
|
+
Reads all trace files and flattens spans into a CSV format.
|
|
167
|
+
|
|
168
|
+
```powershell
|
|
169
|
+
python .\src\boundary_analyzer\pipeline\step_02_read_traces.py
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**Output:** `data/interim/spans.csv`
|
|
173
|
+
|
|
174
|
+
### Step 03: Find endpoints
|
|
175
|
+
|
|
176
|
+
Extracts HTTP endpoints from spans (method + route normalization).
|
|
177
|
+
|
|
178
|
+
```powershell
|
|
179
|
+
python .\src\boundary_analyzer\pipeline\step_03_find_endpoints.py
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**Output:** `data/interim/endpoints.csv`
|
|
183
|
+
|
|
184
|
+
### Step 04: Find database tables
|
|
185
|
+
|
|
186
|
+
Extracts database table names from SQL operations in spans.
|
|
187
|
+
|
|
188
|
+
```powershell
|
|
189
|
+
python .\src\boundary_analyzer\pipeline\step_04_find_db_tables.py
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**Output:** `data/interim/db_operations.csv`
|
|
193
|
+
|
|
194
|
+
### Step 05: Build endpoint-table mapping
|
|
195
|
+
|
|
196
|
+
Links endpoints to database tables by walking the span parent chain.
|
|
197
|
+
|
|
198
|
+
```powershell
|
|
199
|
+
python .\src\boundary_analyzer\pipeline\step_05_build_mapping.py
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**Output:** `data/interim/endpoint_table_map.csv`
|
|
203
|
+
|
|
204
|
+
### Step 06: Compute SCOM scores
|
|
205
|
+
|
|
206
|
+
Calculates Service Cohesion Measure for each service using weighted Jaccard similarity.
|
|
207
|
+
|
|
208
|
+
```powershell
|
|
209
|
+
python .\src\boundary_analyzer\pipeline\step_06_compute_scom.py
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**Output:** `data/processed/service_scom.csv`
|
|
213
|
+
|
|
214
|
+
### Step 07: Rank and flag suspicious services
|
|
215
|
+
|
|
216
|
+
Applies statistical threshold (percentile, Z-score, or fixed) to flag services with low cohesion.
|
|
217
|
+
|
|
218
|
+
```powershell
|
|
219
|
+
python .\src\boundary_analyzer\pipeline\step_07_rank_and_flag.py
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**Output:**
|
|
223
|
+
- `data/processed/service_rank.csv`
|
|
224
|
+
- `data/processed/suspicious_services.csv`
|
|
225
|
+
|
|
226
|
+
### Step 08: Generate report
|
|
227
|
+
|
|
228
|
+
Creates a Markdown report with analysis results.
|
|
229
|
+
|
|
230
|
+
```powershell
|
|
231
|
+
python .\src\boundary_analyzer\pipeline\step_08_make_report.py
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**Output:** `reports/latest/report.md`
|
|
235
|
+
|
|
236
|
+
## Dashboard
|
|
237
|
+
|
|
238
|
+
Launch the interactive dashboard to visualize results:
|
|
239
|
+
|
|
240
|
+
```powershell
|
|
241
|
+
python .\src\boundary_analyzer\dashboard\app.py
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
The dashboard will be available at `http://localhost:8050`
|
|
245
|
+
|
|
246
|
+
## Quick Start
|
|
247
|
+
|
|
248
|
+
Run all steps in sequence:
|
|
249
|
+
|
|
250
|
+
```powershell
|
|
251
|
+
python .\src\boundary_analyzer\pipeline\step_01_collect_traces.py
|
|
252
|
+
python .\src\boundary_analyzer\pipeline\step_02_read_traces.py
|
|
253
|
+
python .\src\boundary_analyzer\pipeline\step_03_find_endpoints.py
|
|
254
|
+
python .\src\boundary_analyzer\pipeline\step_04_find_db_tables.py
|
|
255
|
+
python .\src\boundary_analyzer\pipeline\step_05_build_mapping.py
|
|
256
|
+
python .\src\boundary_analyzer\pipeline\step_06_compute_scom.py
|
|
257
|
+
python .\src\boundary_analyzer\pipeline\step_07_rank_and_flag.py
|
|
258
|
+
python .\src\boundary_analyzer\pipeline\step_08_make_report.py
|
|
259
|
+
python .\src\boundary_analyzer\dashboard\app.py
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## Documentation
|
|
263
|
+
|
|
264
|
+
See `docs/research_method.md` for detailed information about:
|
|
265
|
+
- Core concepts (coupling, cohesion, wrong cuts)
|
|
266
|
+
- Analysis method and limitations
|
|
267
|
+
- SCOM calculation formula
|
|
268
|
+
- Threshold selection methods
|
|
269
|
+
- Future improvements
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "boundary-analyzer"
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
description = "SCOM-based microservice boundary analysis from Jaeger traces"
|
|
5
|
+
readme = "CHANGELOG.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
authors = [{ name = "Ray Ague", email = "rayague03@gmail.com" }]
|
|
8
|
+
requires-python = ">=3.11"
|
|
9
|
+
dependencies = [
|
|
10
|
+
"requests>=2.31.0",
|
|
11
|
+
"pandas>=2.2.0",
|
|
12
|
+
"PyYAML>=6.0.1",
|
|
13
|
+
"dash>=2.14.0",
|
|
14
|
+
"plotly>=5.18.0",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.urls]
|
|
18
|
+
Homepage = "https://github.com/rayague/measure-automation"
|
|
19
|
+
Repository = "https://github.com/rayague/measure-automation"
|
|
20
|
+
|
|
21
|
+
[project.scripts]
|
|
22
|
+
boundary-analyzer = "boundary_analyzer.cli:main"
|
|
23
|
+
mba = "boundary_analyzer.cli:main"
|
|
24
|
+
|
|
25
|
+
[tool.setuptools]
|
|
26
|
+
package-dir = {"" = "src"}
|
|
27
|
+
|
|
28
|
+
[tool.setuptools.packages.find]
|
|
29
|
+
where = ["src"]
|
|
File without changes
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""
|
|
2
|
+
otel_instrumentation.py – Django / Django REST Framework
|
|
3
|
+
==========================================================
|
|
4
|
+
This file sets up OpenTelemetry tracing for your Django application.
|
|
5
|
+
|
|
6
|
+
HOW IT WORKS:
|
|
7
|
+
- Every HTTP request Django handles becomes a span.
|
|
8
|
+
- Every ORM database query also becomes a span.
|
|
9
|
+
- All spans are sent to Jaeger.
|
|
10
|
+
|
|
11
|
+
YOU DO NOT NEED TO EDIT THIS FILE.
|
|
12
|
+
Add these 2 lines BEFORE django.setup() in your manage.py or wsgi.py:
|
|
13
|
+
|
|
14
|
+
from otel_instrumentation import init_tracing
|
|
15
|
+
init_tracing()
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from opentelemetry import trace
|
|
19
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
20
|
+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
21
|
+
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
|
|
22
|
+
from opentelemetry.sdk.resources import Resource
|
|
23
|
+
from opentelemetry.instrumentation.django import DjangoInstrumentor
|
|
24
|
+
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def init_tracing():
|
|
28
|
+
"""
|
|
29
|
+
Call this function ONCE, before django.setup() is called.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
resource = Resource.create({"service.name": "{{SERVICE_NAME}}"})
|
|
33
|
+
|
|
34
|
+
exporter = OTLPSpanExporter(
|
|
35
|
+
endpoint="http://{{JAEGER_HOST}}:{{JAEGER_GRPC_PORT}}"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
provider = TracerProvider(resource=resource)
|
|
39
|
+
provider.add_span_processor(BatchSpanProcessor(exporter))
|
|
40
|
+
trace.set_tracer_provider(provider)
|
|
41
|
+
|
|
42
|
+
# Automatically trace every Django view / URL
|
|
43
|
+
DjangoInstrumentor().instrument()
|
|
44
|
+
|
|
45
|
+
# Automatically trace every ORM query
|
|
46
|
+
SQLAlchemyInstrumentor().instrument()
|
|
47
|
+
|
|
48
|
+
print(f"[OTel] Tracing enabled → Jaeger at {{JAEGER_HOST}}:{{JAEGER_GRPC_PORT}}")
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""
|
|
2
|
+
otel_instrumentation.py – Django REST Framework
|
|
3
|
+
=================================================
|
|
4
|
+
Same setup as Django — DRF sits on top of Django,
|
|
5
|
+
so we instrument at the Django level.
|
|
6
|
+
|
|
7
|
+
Add these 2 lines BEFORE django.setup():
|
|
8
|
+
|
|
9
|
+
from otel_instrumentation import init_tracing
|
|
10
|
+
init_tracing()
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
# Django REST Framework uses the same Django instrumentation
|
|
14
|
+
from opentelemetry import trace
|
|
15
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
16
|
+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
17
|
+
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
|
|
18
|
+
from opentelemetry.sdk.resources import Resource
|
|
19
|
+
from opentelemetry.instrumentation.django import DjangoInstrumentor
|
|
20
|
+
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def init_tracing():
|
|
24
|
+
"""
|
|
25
|
+
Initialize tracing for Django REST Framework.
|
|
26
|
+
DRF is built on top of Django, so we instrument Django directly.
|
|
27
|
+
"""
|
|
28
|
+
resource = Resource.create({"service.name": "{{SERVICE_NAME}}"})
|
|
29
|
+
|
|
30
|
+
exporter = OTLPSpanExporter(
|
|
31
|
+
endpoint="http://{{JAEGER_HOST}}:{{JAEGER_GRPC_PORT}}"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
provider = TracerProvider(resource=resource)
|
|
35
|
+
provider.add_span_processor(BatchSpanProcessor(exporter))
|
|
36
|
+
trace.set_tracer_provider(provider)
|
|
37
|
+
|
|
38
|
+
# Instrument every DRF/Django view automatically
|
|
39
|
+
DjangoInstrumentor().instrument()
|
|
40
|
+
|
|
41
|
+
# Instrument every database query automatically
|
|
42
|
+
SQLAlchemyInstrumentor().instrument()
|
|
43
|
+
|
|
44
|
+
print(f"[OTel] Tracing enabled → Jaeger at {{JAEGER_HOST}}:{{JAEGER_GRPC_PORT}}")
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""
|
|
2
|
+
otel_instrumentation.py – FastAPI
|
|
3
|
+
===================================
|
|
4
|
+
This file sets up OpenTelemetry tracing for your FastAPI application.
|
|
5
|
+
|
|
6
|
+
HOW IT WORKS:
|
|
7
|
+
- Every HTTP request becomes a span (recorded event).
|
|
8
|
+
- Every database query (via SQLAlchemy) also becomes a span.
|
|
9
|
+
- All spans are sent to Jaeger so we can see and analyze them.
|
|
10
|
+
|
|
11
|
+
YOU DO NOT NEED TO EDIT THIS FILE.
|
|
12
|
+
Just call init_tracing() at the top of your main.py (before app = FastAPI()).
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from opentelemetry import trace
|
|
16
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
17
|
+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
18
|
+
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
|
|
19
|
+
from opentelemetry.sdk.resources import Resource
|
|
20
|
+
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
|
|
21
|
+
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def init_tracing(app=None):
|
|
25
|
+
"""
|
|
26
|
+
Call this function ONCE at the start of your app.
|
|
27
|
+
|
|
28
|
+
If you pass your FastAPI app object, HTTP routes will be traced automatically.
|
|
29
|
+
Example:
|
|
30
|
+
app = FastAPI()
|
|
31
|
+
init_tracing(app)
|
|
32
|
+
|
|
33
|
+
If you do not pass the app, call FastAPIInstrumentor().instrument_app(app)
|
|
34
|
+
after creating it.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
# Tell Jaeger the name of this service
|
|
38
|
+
resource = Resource.create({"service.name": "{{SERVICE_NAME}}"})
|
|
39
|
+
|
|
40
|
+
# Send traces to Jaeger via gRPC
|
|
41
|
+
exporter = OTLPSpanExporter(
|
|
42
|
+
endpoint="http://{{JAEGER_HOST}}:{{JAEGER_GRPC_PORT}}"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
provider = TracerProvider(resource=resource)
|
|
46
|
+
provider.add_span_processor(BatchSpanProcessor(exporter))
|
|
47
|
+
trace.set_tracer_provider(provider)
|
|
48
|
+
|
|
49
|
+
# Instrument database queries automatically
|
|
50
|
+
SQLAlchemyInstrumentor().instrument()
|
|
51
|
+
|
|
52
|
+
# Instrument the FastAPI app if provided
|
|
53
|
+
if app is not None:
|
|
54
|
+
FastAPIInstrumentor.instrument_app(app)
|
|
55
|
+
else:
|
|
56
|
+
# Will instrument the first FastAPI app created after this call
|
|
57
|
+
FastAPIInstrumentor().instrument()
|
|
58
|
+
|
|
59
|
+
print(f"[OTel] Tracing enabled → Jaeger at {{JAEGER_HOST}}:{{JAEGER_GRPC_PORT}}")
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""
|
|
2
|
+
otel_instrumentation.py – Flask
|
|
3
|
+
================================
|
|
4
|
+
This file sets up OpenTelemetry tracing for your Flask application.
|
|
5
|
+
|
|
6
|
+
HOW IT WORKS:
|
|
7
|
+
- Every HTTP request your app receives becomes a "span" (a recorded event).
|
|
8
|
+
- Every database query also becomes a span.
|
|
9
|
+
- All spans are sent to Jaeger so we can see them.
|
|
10
|
+
|
|
11
|
+
YOU DO NOT NEED TO EDIT THIS FILE.
|
|
12
|
+
Just call init_tracing() at the top of your main app file.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from opentelemetry import trace
|
|
16
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
17
|
+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
18
|
+
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
|
|
19
|
+
from opentelemetry.sdk.resources import Resource
|
|
20
|
+
from opentelemetry.instrumentation.flask import FlaskInstrumentor
|
|
21
|
+
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def init_tracing():
|
|
25
|
+
"""
|
|
26
|
+
Call this function ONCE at the start of your app.
|
|
27
|
+
It configures OpenTelemetry to send traces to Jaeger.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
# 'resource' tells Jaeger which service these traces belong to
|
|
31
|
+
resource = Resource.create({"service.name": "{{SERVICE_NAME}}"})
|
|
32
|
+
|
|
33
|
+
# 'exporter' sends the traces to Jaeger over gRPC
|
|
34
|
+
exporter = OTLPSpanExporter(
|
|
35
|
+
endpoint="http://{{JAEGER_HOST}}:{{JAEGER_GRPC_PORT}}"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# 'provider' is the main tracing engine
|
|
39
|
+
provider = TracerProvider(resource=resource)
|
|
40
|
+
|
|
41
|
+
# 'processor' batches spans and sends them to the exporter
|
|
42
|
+
provider.add_span_processor(BatchSpanProcessor(exporter))
|
|
43
|
+
|
|
44
|
+
# Register the provider globally
|
|
45
|
+
trace.set_tracer_provider(provider)
|
|
46
|
+
|
|
47
|
+
# Automatically trace every Flask HTTP request
|
|
48
|
+
FlaskInstrumentor().instrument()
|
|
49
|
+
|
|
50
|
+
# Automatically trace every SQLAlchemy database query
|
|
51
|
+
SQLAlchemyInstrumentor().instrument()
|
|
52
|
+
|
|
53
|
+
print(f"[OTel] Tracing enabled → Jaeger at {{JAEGER_HOST}}:{{JAEGER_GRPC_PORT}}")
|