Add step 607 operational SLO watch
This commit is contained in:
@@ -4342,4 +4342,13 @@ target_link_libraries(step606_test PRIVATE
|
||||
tree_sitter_javascript tree_sitter_typescript
|
||||
tree_sitter_java tree_sitter_rust tree_sitter_go)
|
||||
|
||||
add_executable(step607_test tests/step607_test.cpp)
|
||||
target_include_directories(step607_test PRIVATE src)
|
||||
target_link_libraries(step607_test PRIVATE
|
||||
nlohmann_json::nlohmann_json
|
||||
unofficial::tree-sitter::tree-sitter
|
||||
tree_sitter_python tree_sitter_cpp tree_sitter_elisp
|
||||
tree_sitter_javascript tree_sitter_typescript
|
||||
tree_sitter_java tree_sitter_rust tree_sitter_go)
|
||||
|
||||
# Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies)
|
||||
|
||||
64
editor/src/OperationalSLOWatch.h
Normal file
64
editor/src/OperationalSLOWatch.h
Normal file
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
// Step 607: Operational SLO Watch
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ValidationErrorUtil.h"
|
||||
|
||||
struct SLOIndicator {
|
||||
std::string indicatorId;
|
||||
std::string serviceId;
|
||||
int targetPercent = 0;
|
||||
int observedPercent = 0;
|
||||
int errorBudgetBurn = 0;
|
||||
};
|
||||
|
||||
class OperationalSLOWatch {
|
||||
public:
|
||||
bool upsert(const SLOIndicator& indicator, std::string* error) {
|
||||
if (!error) return false;
|
||||
error->clear();
|
||||
if (indicator.indicatorId.empty()) return failWith(error, "indicator_id_missing");
|
||||
if (indicator.serviceId.empty()) return failWith(error, "service_id_missing");
|
||||
if (indicator.targetPercent < 0 || indicator.targetPercent > 100) return failWith(error, "target_percent_invalid");
|
||||
if (indicator.observedPercent < 0 || indicator.observedPercent > 100) return failWith(error, "observed_percent_invalid");
|
||||
if (indicator.errorBudgetBurn < 0) return failWith(error, "error_budget_burn_invalid");
|
||||
if (items_.count(indicator.indicatorId) == 0) order_.push_back(indicator.indicatorId);
|
||||
items_[indicator.indicatorId] = indicator;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool breaching(const std::string& indicatorId, std::string* error) const {
|
||||
if (!error) return false;
|
||||
error->clear();
|
||||
auto it = items_.find(indicatorId);
|
||||
if (it == items_.end()) return failWith(error, "indicator_missing");
|
||||
const auto& i = it->second;
|
||||
return i.observedPercent < i.targetPercent || i.errorBudgetBurn > 100;
|
||||
}
|
||||
|
||||
int serviceBreachCount(const std::string& serviceId) const {
|
||||
int count = 0;
|
||||
for (const auto& id : order_) {
|
||||
const auto& i = items_.at(id);
|
||||
if (i.serviceId == serviceId &&
|
||||
(i.observedPercent < i.targetPercent || i.errorBudgetBurn > 100)) ++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
std::vector<SLOIndicator> byService(const std::string& serviceId) const {
|
||||
std::vector<SLOIndicator> out;
|
||||
for (const auto& id : order_) {
|
||||
const auto& i = items_.at(id);
|
||||
if (serviceId.empty() || i.serviceId == serviceId) out.push_back(i);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<std::string, SLOIndicator> items_;
|
||||
std::vector<std::string> order_;
|
||||
};
|
||||
150
editor/tests/step607_test.cpp
Normal file
150
editor/tests/step607_test.cpp
Normal file
@@ -0,0 +1,150 @@
|
||||
// Step 607: Operational SLO Watch (12 tests)
|
||||
|
||||
#include "OperationalSLOWatch.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
static int passed = 0, failed = 0;
|
||||
#define TEST(name) { std::cout << " " << #name << "... "; }
|
||||
#define PASS() { std::cout << "PASS\n"; ++passed; }
|
||||
#define FAIL(msg) { std::cout << "FAIL: " << msg << "\n"; ++failed; }
|
||||
#define CHECK(cond, msg) if (!(cond)) { FAIL(msg); return; } else {}
|
||||
|
||||
static SLOIndicator indicator(const std::string& id,
|
||||
const std::string& service,
|
||||
int target,
|
||||
int observed,
|
||||
int burn) {
|
||||
return {id, service, target, observed, burn};
|
||||
}
|
||||
|
||||
void test_upsert_success() {
|
||||
TEST(upsert_success);
|
||||
OperationalSLOWatch watch;
|
||||
std::string error;
|
||||
CHECK(watch.upsert(indicator("i1", "svc-a", 99, 99, 50), &error), "upsert should succeed");
|
||||
CHECK(watch.byService("svc-a").size() == 1, "service count mismatch");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_upsert_rejects_missing_indicator_id() {
|
||||
TEST(upsert_rejects_missing_indicator_id);
|
||||
OperationalSLOWatch watch;
|
||||
std::string error;
|
||||
CHECK(!watch.upsert(indicator("", "svc-a", 99, 99, 50), &error), "upsert should fail");
|
||||
CHECK(error == "indicator_id_missing", "wrong error");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_upsert_rejects_missing_service_id() {
|
||||
TEST(upsert_rejects_missing_service_id);
|
||||
OperationalSLOWatch watch;
|
||||
std::string error;
|
||||
CHECK(!watch.upsert(indicator("i1", "", 99, 99, 50), &error), "upsert should fail");
|
||||
CHECK(error == "service_id_missing", "wrong error");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_upsert_rejects_invalid_target_percent() {
|
||||
TEST(upsert_rejects_invalid_target_percent);
|
||||
OperationalSLOWatch watch;
|
||||
std::string error;
|
||||
CHECK(!watch.upsert(indicator("i1", "svc-a", 120, 99, 50), &error), "upsert should fail");
|
||||
CHECK(error == "target_percent_invalid", "wrong error");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_upsert_rejects_invalid_observed_percent() {
|
||||
TEST(upsert_rejects_invalid_observed_percent);
|
||||
OperationalSLOWatch watch;
|
||||
std::string error;
|
||||
CHECK(!watch.upsert(indicator("i1", "svc-a", 99, -1, 50), &error), "upsert should fail");
|
||||
CHECK(error == "observed_percent_invalid", "wrong error");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_upsert_rejects_negative_error_budget_burn() {
|
||||
TEST(upsert_rejects_negative_error_budget_burn);
|
||||
OperationalSLOWatch watch;
|
||||
std::string error;
|
||||
CHECK(!watch.upsert(indicator("i1", "svc-a", 99, 99, -1), &error), "upsert should fail");
|
||||
CHECK(error == "error_budget_burn_invalid", "wrong error");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_breaching_true_when_observed_below_target() {
|
||||
TEST(breaching_true_when_observed_below_target);
|
||||
OperationalSLOWatch watch;
|
||||
std::string error;
|
||||
CHECK(watch.upsert(indicator("i1", "svc-a", 99, 98, 20), &error), "upsert failed");
|
||||
CHECK(watch.breaching("i1", &error), "indicator should breach");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_breaching_true_when_burn_exceeds_hundred() {
|
||||
TEST(breaching_true_when_burn_exceeds_hundred);
|
||||
OperationalSLOWatch watch;
|
||||
std::string error;
|
||||
CHECK(watch.upsert(indicator("i1", "svc-a", 99, 100, 101), &error), "upsert failed");
|
||||
CHECK(watch.breaching("i1", &error), "indicator should breach");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_breaching_false_when_healthy() {
|
||||
TEST(breaching_false_when_healthy);
|
||||
OperationalSLOWatch watch;
|
||||
std::string error;
|
||||
CHECK(watch.upsert(indicator("i1", "svc-a", 99, 100, 40), &error), "upsert failed");
|
||||
CHECK(!watch.breaching("i1", &error), "indicator should be healthy");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_breaching_rejects_missing_indicator() {
|
||||
TEST(breaching_rejects_missing_indicator);
|
||||
OperationalSLOWatch watch;
|
||||
std::string error;
|
||||
CHECK(!watch.breaching("missing", &error), "breach should fail");
|
||||
CHECK(error == "indicator_missing", "wrong error");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_service_breach_count_counts_only_breaches() {
|
||||
TEST(service_breach_count_counts_only_breaches);
|
||||
OperationalSLOWatch watch;
|
||||
std::string error;
|
||||
CHECK(watch.upsert(indicator("i1", "svc-a", 99, 98, 10), &error), "upsert i1 failed");
|
||||
CHECK(watch.upsert(indicator("i2", "svc-a", 99, 100, 10), &error), "upsert i2 failed");
|
||||
CHECK(watch.upsert(indicator("i3", "svc-a", 99, 100, 110), &error), "upsert i3 failed");
|
||||
CHECK(watch.serviceBreachCount("svc-a") == 2, "breach count mismatch");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_by_service_empty_returns_all() {
|
||||
TEST(by_service_empty_returns_all);
|
||||
OperationalSLOWatch watch;
|
||||
std::string error;
|
||||
CHECK(watch.upsert(indicator("i1", "svc-a", 99, 99, 10), &error), "upsert i1 failed");
|
||||
CHECK(watch.upsert(indicator("i2", "svc-b", 99, 99, 10), &error), "upsert i2 failed");
|
||||
CHECK(watch.byService("").size() == 2, "empty service should return all");
|
||||
PASS();
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::cout << "Step 607: Operational SLO Watch\n";
|
||||
|
||||
test_upsert_success(); // 1
|
||||
test_upsert_rejects_missing_indicator_id(); // 2
|
||||
test_upsert_rejects_missing_service_id(); // 3
|
||||
test_upsert_rejects_invalid_target_percent(); // 4
|
||||
test_upsert_rejects_invalid_observed_percent(); // 5
|
||||
test_upsert_rejects_negative_error_budget_burn(); // 6
|
||||
test_breaching_true_when_observed_below_target(); // 7
|
||||
test_breaching_true_when_burn_exceeds_hundred(); // 8
|
||||
test_breaching_false_when_healthy(); // 9
|
||||
test_breaching_rejects_missing_indicator(); // 10
|
||||
test_service_breach_count_counts_only_breaches(); // 11
|
||||
test_by_service_empty_returns_all(); // 12
|
||||
|
||||
std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n";
|
||||
return failed == 0 ? 0 : 1;
|
||||
}
|
||||
30
progress.md
30
progress.md
@@ -11800,3 +11800,33 @@ recovery-time metrics.
|
||||
- `editor/src/RollbackDrillTracker.h` within header-size limit (`93` <= `600`)
|
||||
- `editor/tests/step606_test.cpp` within test-file size guidance (`153` lines)
|
||||
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`
|
||||
|
||||
### Step 607: Operational SLO Watch
|
||||
**Status:** PASS (12/12 tests)
|
||||
|
||||
Implements operational SLO indicator tracking with breach detection and
|
||||
service-level breach aggregation.
|
||||
|
||||
**Files added:**
|
||||
- `editor/src/OperationalSLOWatch.h` - SLO watch module:
|
||||
- indicator upsert with identifier/range validation
|
||||
- breach detection for target miss or budget over-burn
|
||||
- service breach counts and service filtering helpers
|
||||
- `editor/tests/step607_test.cpp` - 12 tests covering:
|
||||
- upsert success/failure validation behavior
|
||||
- breach decision behavior and missing indicator handling
|
||||
- service breach aggregation and filter behavior
|
||||
|
||||
**Files modified:**
|
||||
- `editor/CMakeLists.txt` - `step607_test` target
|
||||
|
||||
**Verification run:**
|
||||
- `cmake -S editor -B editor/build-native` - PASS
|
||||
- `cmake --build editor/build-native --target step607_test step606_test` - PASS
|
||||
- `./editor/build-native/step607_test` - PASS (12/12)
|
||||
- `./editor/build-native/step606_test` - PASS (12/12) regression coverage
|
||||
|
||||
**Architecture gate check:**
|
||||
- `editor/src/OperationalSLOWatch.h` within header-size limit (`64` <= `600`)
|
||||
- `editor/tests/step607_test.cpp` within test-file size guidance (`150` lines)
|
||||
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`
|
||||
|
||||
Reference in New Issue
Block a user