@redpanda-data/docs-extensions-and-macros 4.12.3 → 4.12.5
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.
- package/extensions/process-context-switcher.js +1 -1
- package/package.json +1 -1
- package/tools/property-extractor/Makefile +2 -1
- package/tools/property-extractor/helpers/capitalize.js +13 -0
- package/tools/property-extractor/helpers/index.js +2 -0
- package/tools/property-extractor/helpers/length.js +11 -0
- package/tools/property-extractor/templates/property.hbs +69 -26
- package/tools/property-extractor/templates/topic-property-mappings.hbs +3 -6
- package/tools/property-extractor/templates/topic-property.hbs +57 -24
- package/tools/property-extractor/topic_property_extractor.py +10 -3
- package/tools/property-extractor/transformers.py +117 -64
- package/tools/property-extractor/COMPUTED_CONSTANTS.md +0 -173
- package/tools/property-extractor/tests/test_known_values.py +0 -617
- package/tools/property-extractor/tests/transformers_test.py +0 -451
|
@@ -80,24 +80,45 @@ Redpanda configuration properties are C++ objects with constructor signatures th
|
|
|
80
80
|
based on feature requirements. Understanding these "arities" (parameter counts) is crucial
|
|
81
81
|
for correctly extracting property metadata.
|
|
82
82
|
|
|
83
|
+
┌─────────────────────────────────────────────────────────────────────────────
|
|
84
|
+
│ ALL properties have a runtime mutability flag as their FIRST parameter
|
|
85
|
+
│ (after *this which is skipped by the parser). This boolean indicates whether
|
|
86
|
+
│ the property can be changed at runtime without restarting Redpanda.
|
|
87
|
+
│
|
|
88
|
+
│ C++ signature: property<T>(*this, runtime_mutable, name, description, ...)
|
|
89
|
+
│ After parsing: params[0] = runtime_mutable, params[1] = name, ...
|
|
90
|
+
│ params[-1] = default OR validator (validator is skipped)
|
|
91
|
+
│
|
|
92
|
+
│ Note: Some properties include validators as the last parameter. These are
|
|
93
|
+
│ automatically detected and skipped when extracting the default value.
|
|
94
|
+
│ The default is the last NON-VALIDATOR parameter.
|
|
95
|
+
└─────────────────────────────────────────────────────────────────────────────
|
|
96
|
+
|
|
83
97
|
BASIC PROPERTY PATTERNS:
|
|
84
98
|
┌─────────────────────────────────────────────────────────────────────────────
|
|
85
|
-
│
|
|
86
|
-
│ Example: property<bool>(*this, "enable_feature", "Enable the feature")
|
|
99
|
+
│ 3-PARAMETER: property<T>(runtime_mutable, name, description)
|
|
100
|
+
│ Example: property<bool>(*this, true, "enable_feature", "Enable the feature")
|
|
87
101
|
│ Used for: Simple properties with no metadata or custom defaults
|
|
88
|
-
│ Extraction: [0] =
|
|
102
|
+
│ Extraction: [0] = runtime_mutable, [1] = name, [2] = description
|
|
89
103
|
└─────────────────────────────────────────────────────────────────────────────
|
|
90
104
|
┌─────────────────────────────────────────────────────────────────────────────
|
|
91
|
-
│
|
|
92
|
-
│ Example: property<int>(*this, "port", "Server port", 9092)
|
|
105
|
+
│ 4-PARAMETER: property<T>(runtime_mutable, name, description, default)
|
|
106
|
+
│ Example: property<int>(*this, false, "port", "Server port", 9092)
|
|
93
107
|
│ Used for: Properties with simple custom default values
|
|
94
|
-
│ Extraction: [0] =
|
|
108
|
+
│ Extraction: [0] = runtime_mutable, [1] = name, [2] = description, [3] = default
|
|
95
109
|
└─────────────────────────────────────────────────────────────────────────────
|
|
96
110
|
┌─────────────────────────────────────────────────────────────────────────────
|
|
97
|
-
│
|
|
98
|
-
│ Example: property<bool>(*this, "flag", "
|
|
111
|
+
│ 5-PARAMETER: property<T>(runtime_mutable, name, description, meta, default)
|
|
112
|
+
│ Example: property<bool>(*this, true, "flag", "Desc", meta{.needs_restart=yes}, true)
|
|
99
113
|
│ Used for: Properties with metadata (restart requirements, visibility, etc.)
|
|
100
|
-
│ Extraction: [0] =
|
|
114
|
+
│ Extraction: [0] = runtime_mutable, [1] = name, [2] = description, [3] = meta{}, [4] = default
|
|
115
|
+
└─────────────────────────────────────────────────────────────────────────────
|
|
116
|
+
┌─────────────────────────────────────────────────────────────────────────────
|
|
117
|
+
│ WITH VALIDATOR: property<T>(runtime_mutable, name, description, [meta], default, validator)
|
|
118
|
+
│ Example: property<int>(*this, true, "port", "Port", 9092, [](int v){return v > 0;})
|
|
119
|
+
│ Used for: Properties with runtime validation functions
|
|
120
|
+
│ Extraction: Validator is automatically skipped; default is found by searching backwards
|
|
121
|
+
│ Note: Validators detected by is_validator_param() and excluded from default extraction
|
|
101
122
|
└─────────────────────────────────────────────────────────────────────────────
|
|
102
123
|
|
|
103
124
|
ENTERPRISE PROPERTY PATTERNS (More Complex):
|
|
@@ -517,6 +538,51 @@ def get_meta_value(info, key, default=None):
|
|
|
517
538
|
return val
|
|
518
539
|
|
|
519
540
|
|
|
541
|
+
def resolve_cpp_literal(value_str):
|
|
542
|
+
"""
|
|
543
|
+
Resolve C++ literal expressions to their actual values.
|
|
544
|
+
|
|
545
|
+
Handles:
|
|
546
|
+
- Size literals: 5_MiB → 5242880
|
|
547
|
+
- std::to_string() wrapper: std::to_string(5_MiB) → 5242880
|
|
548
|
+
- Plain integers: 42 → 42
|
|
549
|
+
- Plain floats: 3.14 → 3.14
|
|
550
|
+
|
|
551
|
+
Args:
|
|
552
|
+
value_str: String containing C++ literal expression
|
|
553
|
+
|
|
554
|
+
Returns:
|
|
555
|
+
Resolved value (int, float, or original string if can't resolve)
|
|
556
|
+
"""
|
|
557
|
+
if not isinstance(value_str, str):
|
|
558
|
+
return value_str
|
|
559
|
+
|
|
560
|
+
val = value_str.strip()
|
|
561
|
+
|
|
562
|
+
# Handle std::to_string() wrapper
|
|
563
|
+
to_string_match = re.match(r"std::to_string\((.+)\)", val)
|
|
564
|
+
if to_string_match:
|
|
565
|
+
val = to_string_match.group(1).strip()
|
|
566
|
+
|
|
567
|
+
# Try size literals like 5_MiB
|
|
568
|
+
size_match = re.match(r"(\d+)_([KMGTP])iB", val)
|
|
569
|
+
if size_match:
|
|
570
|
+
num, unit = int(size_match.group(1)), size_match.group(2)
|
|
571
|
+
mult = {"K": 1024, "M": 1024**2, "G": 1024**3, "T": 1024**4, "P": 1024**5}[unit]
|
|
572
|
+
return num * mult
|
|
573
|
+
|
|
574
|
+
# Try plain integer
|
|
575
|
+
if re.fullmatch(r"-?\d+", val):
|
|
576
|
+
return int(val)
|
|
577
|
+
|
|
578
|
+
# Try plain float
|
|
579
|
+
if re.fullmatch(r"-?\d+\.\d+", val):
|
|
580
|
+
return float(val)
|
|
581
|
+
|
|
582
|
+
# Return original if can't resolve
|
|
583
|
+
return value_str
|
|
584
|
+
|
|
585
|
+
|
|
520
586
|
def get_resolve_constexpr_identifier():
|
|
521
587
|
"""
|
|
522
588
|
Lazily import constexpr identifier resolution function to avoid circular imports.
|
|
@@ -1391,31 +1457,37 @@ class SimpleDefaultValuesTransformer:
|
|
|
1391
1457
|
return property
|
|
1392
1458
|
|
|
1393
1459
|
# Find where the meta{} param is
|
|
1460
|
+
# MetaParamTransformer converts meta strings to dicts with "type": "initializer_list"
|
|
1394
1461
|
meta_index = next(
|
|
1395
1462
|
(i for i, p in enumerate(params)
|
|
1396
1463
|
if isinstance(p.get("value"), (dict, str))
|
|
1397
1464
|
and ("meta{" in str(p["value"]) or
|
|
1398
|
-
(isinstance(p["value"], dict) and
|
|
1399
|
-
|
|
1465
|
+
(isinstance(p["value"], dict) and p["value"].get("type") == "initializer_list"))
|
|
1466
|
+
),
|
|
1467
|
+
None
|
|
1400
1468
|
)
|
|
1401
1469
|
|
|
1402
|
-
#
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1470
|
+
# The default value is the LAST NON-VALIDATOR parameter in the constructor
|
|
1471
|
+
# Property structure: (*this, runtime_mutability_flag, name, description, [meta], default, [validator])
|
|
1472
|
+
# After *this is skipped: [0]=runtime_flag, [1]=name, [2]=desc, [3]=meta?, [4]=default, [5]=validator?
|
|
1473
|
+
# We need to search backwards to find the last parameter that's not a validator
|
|
1474
|
+
if len(params) < 2:
|
|
1475
|
+
return property
|
|
1407
1476
|
|
|
1408
|
-
|
|
1477
|
+
# Search backwards for the last non-validator parameter
|
|
1478
|
+
default_index = None
|
|
1479
|
+
for i in range(len(params) - 1, -1, -1):
|
|
1480
|
+
if not is_validator_param(params[i]):
|
|
1481
|
+
default_index = i
|
|
1482
|
+
break
|
|
1483
|
+
|
|
1484
|
+
if default_index is None:
|
|
1409
1485
|
return property
|
|
1410
1486
|
|
|
1411
1487
|
# Candidate default param
|
|
1412
1488
|
default_param = params[default_index]
|
|
1413
1489
|
default = default_param.get("value")
|
|
1414
1490
|
|
|
1415
|
-
# Skip obvious validator params
|
|
1416
|
-
if is_validator_param(default_param):
|
|
1417
|
-
return property
|
|
1418
|
-
|
|
1419
1491
|
# std::nullopt means "no default"
|
|
1420
1492
|
if isinstance(default, str) and "std::nullopt" in default:
|
|
1421
1493
|
property["default"] = None
|
|
@@ -1938,9 +2010,12 @@ class EnterpriseTransformer:
|
|
|
1938
2010
|
info["params"] = params[:-1]
|
|
1939
2011
|
|
|
1940
2012
|
# --- restricted_with_sanctioned (scalar form) ---
|
|
2013
|
+
# Pattern: (restricted, sanctioned, name, description, meta, default)
|
|
2014
|
+
# Must check that params[1] is NOT the property name (which would make it restricted_only)
|
|
1941
2015
|
elif (
|
|
1942
2016
|
len(params) >= 6
|
|
1943
2017
|
and all(p["type"] in ("true", "false", "integer_literal", "string_literal", "qualified_identifier") for p in params[:2])
|
|
2018
|
+
and self._clean_value(params[1]["value"]) != info.get("name_in_file")
|
|
1944
2019
|
):
|
|
1945
2020
|
enterprise_constructor = "restricted_with_sanctioned"
|
|
1946
2021
|
restricted_vals = [self._clean_value(params[0]["value"])]
|
|
@@ -1955,13 +2030,15 @@ class EnterpriseTransformer:
|
|
|
1955
2030
|
restricted_vals = [self._clean_value(params[0]["value"])]
|
|
1956
2031
|
info["params"] = params[1:]
|
|
1957
2032
|
|
|
1958
|
-
# --- simple enterprise property (lambda validator pattern) ---
|
|
1959
|
-
|
|
1960
|
-
|
|
2033
|
+
# --- simple enterprise property (function/lambda validator pattern) ---
|
|
2034
|
+
# Pattern: (validator_function, name, description, meta, default, ...)
|
|
2035
|
+
# The validator can be a lambda or a function identifier
|
|
2036
|
+
elif (len(params) >= 3 and
|
|
2037
|
+
params[0].get("type") in ("lambda_expression", "identifier", "qualified_identifier") and
|
|
1961
2038
|
params[1].get("type") == "string_literal"):
|
|
1962
2039
|
enterprise_constructor = "simple"
|
|
1963
2040
|
# Don't modify params for simple enterprise properties - they have normal structure
|
|
1964
|
-
# Remove the
|
|
2041
|
+
# Remove the validator from parameters as it's not needed for documentation
|
|
1965
2042
|
info["params"] = params[1:]
|
|
1966
2043
|
|
|
1967
2044
|
if not enterprise_constructor:
|
|
@@ -1980,28 +2057,6 @@ class EnterpriseTransformer:
|
|
|
1980
2057
|
if sanctioned_vals is not None:
|
|
1981
2058
|
property["enterprise_sanctioned_value"] = sanctioned_vals
|
|
1982
2059
|
|
|
1983
|
-
# Add friendly description (values are already cleaned by _clean_value)
|
|
1984
|
-
if enterprise_constructor == "restricted_with_sanctioned":
|
|
1985
|
-
r = restricted_vals[0]
|
|
1986
|
-
s = sanctioned_vals[0]
|
|
1987
|
-
property["enterprise_default_description"] = (
|
|
1988
|
-
f"Default: `{s}` (Community) or `{r}` (Enterprise)"
|
|
1989
|
-
)
|
|
1990
|
-
elif enterprise_constructor == "restricted_only":
|
|
1991
|
-
if len(restricted_vals) > 1:
|
|
1992
|
-
vals = ", ".join(f"`{v}`" for v in restricted_vals)
|
|
1993
|
-
property["enterprise_default_description"] = (
|
|
1994
|
-
f"Available only with Enterprise license: {vals}"
|
|
1995
|
-
)
|
|
1996
|
-
else:
|
|
1997
|
-
property["enterprise_default_description"] = (
|
|
1998
|
-
f"Available only with Enterprise license: `{restricted_vals[0]}`"
|
|
1999
|
-
)
|
|
2000
|
-
elif enterprise_constructor == "sanctioned_only":
|
|
2001
|
-
property["enterprise_default_description"] = (
|
|
2002
|
-
f"Community-only configuration. Sanctioned value: `{sanctioned_vals[0]}`"
|
|
2003
|
-
)
|
|
2004
|
-
|
|
2005
2060
|
return property
|
|
2006
2061
|
|
|
2007
2062
|
# --- Helper: clean literal/identifier values ---
|
|
@@ -2107,6 +2162,8 @@ class MetaParamTransformer:
|
|
|
2107
2162
|
|
|
2108
2163
|
key, value = [s.strip() for s in field.split("=", 1)]
|
|
2109
2164
|
clean_key = key.replace(".", "")
|
|
2165
|
+
# Strip trailing commas from value (last field in meta block has trailing comma)
|
|
2166
|
+
value = value.rstrip(",").strip()
|
|
2110
2167
|
meta_dict[clean_key] = value
|
|
2111
2168
|
|
|
2112
2169
|
# 🔹 Inline special handlers for known meta keys
|
|
@@ -2115,13 +2172,12 @@ class MetaParamTransformer:
|
|
|
2115
2172
|
# Example values
|
|
2116
2173
|
if clean_key == "example":
|
|
2117
2174
|
val_clean = value.strip().strip('"')
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
property["example"] = float(val_clean)
|
|
2175
|
+
resolved = resolve_cpp_literal(val_clean)
|
|
2176
|
+
# Wrap in backticks for inline code formatting (handles overrides with AsciiDoc blocks)
|
|
2177
|
+
if isinstance(resolved, str):
|
|
2178
|
+
property["example"] = f"`{normalize_string(resolved)}`"
|
|
2123
2179
|
else:
|
|
2124
|
-
property["example"] =
|
|
2180
|
+
property["example"] = f"`{resolved}`"
|
|
2125
2181
|
|
|
2126
2182
|
# Gets_restored / restored flags
|
|
2127
2183
|
elif clean_key in ("gets_restored", "restored"):
|
|
@@ -2204,21 +2260,18 @@ class ExampleTransformer:
|
|
|
2204
2260
|
meta_dict = param["value"]
|
|
2205
2261
|
if "example" in meta_dict:
|
|
2206
2262
|
example_val = meta_dict["example"]
|
|
2207
|
-
|
|
2263
|
+
|
|
2208
2264
|
# Clean up the value (remove quotes, etc.)
|
|
2209
2265
|
if isinstance(example_val, str):
|
|
2210
2266
|
example_val = example_val.strip().strip('"\'')
|
|
2211
|
-
|
|
2212
|
-
#
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
property["example"] = float(example_val)
|
|
2218
|
-
else:
|
|
2219
|
-
property["example"] = example_val
|
|
2267
|
+
|
|
2268
|
+
# Use shared resolution logic
|
|
2269
|
+
resolved = resolve_cpp_literal(example_val)
|
|
2270
|
+
# Wrap in backticks for inline code formatting
|
|
2271
|
+
if isinstance(resolved, str):
|
|
2272
|
+
property["example"] = f"`{normalize_string(resolved)}`"
|
|
2220
2273
|
else:
|
|
2221
|
-
property["example"] =
|
|
2274
|
+
property["example"] = f"`{resolved}`"
|
|
2222
2275
|
break
|
|
2223
2276
|
|
|
2224
2277
|
return property
|
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
# Computed C++ Constants Resolution
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
Some C++ constants in Redpanda are defined with complex compile-time expressions that cannot be easily parsed by the property-extractor. These constants need to be pre-computed and mapped to their actual values.
|
|
6
|
-
|
|
7
|
-
## Problem Statement
|
|
8
|
-
|
|
9
|
-
Properties like `log_message_timestamp_before_max_ms` use constants like `max_serializable_ms` as their default values. These constants are defined with complex expressions:
|
|
10
|
-
|
|
11
|
-
```cpp
|
|
12
|
-
// From src/v/serde/rw/chrono.h:20
|
|
13
|
-
inline constexpr auto max_serializable_ms
|
|
14
|
-
= std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
15
|
-
std::chrono::nanoseconds::max());
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
Without resolution, the extracted schema would show:
|
|
19
|
-
```json
|
|
20
|
-
{
|
|
21
|
-
"log_message_timestamp_before_max_ms": {
|
|
22
|
-
"default": "max_serializable_ms" // ❌ String instead of numeric value
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## Solution
|
|
28
|
-
|
|
29
|
-
### 1. COMPUTED_CONSTANTS Dictionary
|
|
30
|
-
|
|
31
|
-
Added a dictionary in `transformers.py` that maps constant names to their computed values:
|
|
32
|
-
|
|
33
|
-
```python
|
|
34
|
-
COMPUTED_CONSTANTS = {
|
|
35
|
-
# From src/v/serde/rw/chrono.h:20
|
|
36
|
-
# inline constexpr auto max_serializable_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::nanoseconds::max());
|
|
37
|
-
# Calculation: std::numeric_limits<int64_t>::max() / 1,000,000 = 9223372036854775807 / 1000000 = 9223372036854 ms
|
|
38
|
-
"max_serializable_ms": 9223372036854, # ~292 years in milliseconds
|
|
39
|
-
}
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
### 2. FriendlyDefaultTransformer Enhancement
|
|
43
|
-
|
|
44
|
-
Updated the `FriendlyDefaultTransformer` to check the `COMPUTED_CONSTANTS` dictionary before falling back to string normalization:
|
|
45
|
-
|
|
46
|
-
```python
|
|
47
|
-
# ------------------------------------------------------------------
|
|
48
|
-
# Computed C++ constants (max_serializable_ms, etc.)
|
|
49
|
-
# ------------------------------------------------------------------
|
|
50
|
-
if d in COMPUTED_CONSTANTS:
|
|
51
|
-
property["default"] = COMPUTED_CONSTANTS[d]
|
|
52
|
-
return property
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
### 3. Test Coverage
|
|
56
|
-
|
|
57
|
-
Added comprehensive test in `tests/test_known_values.py`:
|
|
58
|
-
|
|
59
|
-
```python
|
|
60
|
-
def test_max_serializable_ms_constant_resolution(self):
|
|
61
|
-
"""Test that max_serializable_ms constant is resolved to actual numeric value"""
|
|
62
|
-
info = create_complete_property_info(
|
|
63
|
-
name="log_message_timestamp_before_max_ms",
|
|
64
|
-
description="Maximum timestamp difference for record validation",
|
|
65
|
-
declaration="property<std::chrono::milliseconds> log_message_timestamp_before_max_ms;",
|
|
66
|
-
metadata="meta{.needs_restart = needs_restart::no, .visibility = visibility::user}",
|
|
67
|
-
default_value="max_serializable_ms"
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
property = apply_transformer_pipeline(info)
|
|
71
|
-
|
|
72
|
-
self.assertEqual(property["name"], "log_message_timestamp_before_max_ms")
|
|
73
|
-
self.assertEqual(property["type"], "integer")
|
|
74
|
-
# max_serializable_ms = std::numeric_limits<int64_t>::max() / 1,000,000 = 9223372036854 ms
|
|
75
|
-
self.assertEqual(property["default"], 9223372036854)
|
|
76
|
-
self.assertFalse(property["needs_restart"])
|
|
77
|
-
self.assertEqual(property["visibility"], "user")
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
## Calculation Details
|
|
81
|
-
|
|
82
|
-
### max_serializable_ms
|
|
83
|
-
|
|
84
|
-
**Definition Location:** `src/v/serde/rw/chrono.h:20`
|
|
85
|
-
|
|
86
|
-
**C++ Expression:**
|
|
87
|
-
```cpp
|
|
88
|
-
std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
89
|
-
std::chrono::nanoseconds::max()
|
|
90
|
-
)
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
**Calculation:**
|
|
94
|
-
1. `std::chrono::nanoseconds::max()` = `std::numeric_limits<int64_t>::max()` = `9223372036854775807` nanoseconds
|
|
95
|
-
2. Convert to milliseconds (truncating division): `9223372036854775807 / 1000000` = `9223372036854` milliseconds
|
|
96
|
-
3. This is approximately **292.47 years**
|
|
97
|
-
|
|
98
|
-
**Verification:**
|
|
99
|
-
```python
|
|
100
|
-
import sys
|
|
101
|
-
|
|
102
|
-
# std::chrono::nanoseconds uses int64_t for rep
|
|
103
|
-
max_int64 = 9223372036854775807
|
|
104
|
-
|
|
105
|
-
# Convert nanoseconds to milliseconds (duration_cast truncates)
|
|
106
|
-
max_ms = max_int64 // 1000000
|
|
107
|
-
|
|
108
|
-
print(f'max_serializable_ms = {max_ms} ms')
|
|
109
|
-
print(f'Which is approximately {max_ms / (1000 * 60 * 60 * 24 * 365):.2f} years')
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
Output:
|
|
113
|
-
```
|
|
114
|
-
max_serializable_ms = 9223372036854 ms
|
|
115
|
-
Which is approximately 292.47 years
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
## Result
|
|
119
|
-
|
|
120
|
-
After the fix, the extracted schema correctly shows:
|
|
121
|
-
```json
|
|
122
|
-
{
|
|
123
|
-
"log_message_timestamp_before_max_ms": {
|
|
124
|
-
"default": 9223372036854, // ✅ Correct numeric value
|
|
125
|
-
"type": "integer"
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
## Adding New Computed Constants
|
|
131
|
-
|
|
132
|
-
To add support for new computed constants:
|
|
133
|
-
|
|
134
|
-
1. **Find the definition** in the Redpanda source code
|
|
135
|
-
2. **Compute the value** - either manually or with a Python/C++ test
|
|
136
|
-
3. **Add to COMPUTED_CONSTANTS** dictionary in `transformers.py`:
|
|
137
|
-
```python
|
|
138
|
-
COMPUTED_CONSTANTS = {
|
|
139
|
-
"existing_constant": 12345,
|
|
140
|
-
"new_constant_name": computed_value, # Add comment with source location
|
|
141
|
-
}
|
|
142
|
-
```
|
|
143
|
-
4. **Add a test** in `tests/test_known_values.py` to verify resolution
|
|
144
|
-
5. **Document the calculation** with comments including:
|
|
145
|
-
- Source file location
|
|
146
|
-
- C++ expression
|
|
147
|
-
- Calculation steps
|
|
148
|
-
- Human-readable interpretation
|
|
149
|
-
|
|
150
|
-
## Test Coverage
|
|
151
|
-
|
|
152
|
-
All 53 tests pass, including the new test for `max_serializable_ms` resolution:
|
|
153
|
-
|
|
154
|
-
```bash
|
|
155
|
-
cd tools/property-extractor
|
|
156
|
-
python -m pytest tests/ -v --tb=short
|
|
157
|
-
# ================================================= 53 passed =================================================
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
## Benefits
|
|
161
|
-
|
|
162
|
-
✅ **Accurate defaults** - Properties show actual numeric values instead of symbolic names
|
|
163
|
-
✅ **Type safety** - Numeric properties have numeric defaults, not strings
|
|
164
|
-
✅ **Documentation quality** - Users see real values they can use
|
|
165
|
-
✅ **Maintainability** - Centralized mapping makes updates easy
|
|
166
|
-
✅ **Test coverage** - Ensures constants resolve correctly
|
|
167
|
-
|
|
168
|
-
## Related Files
|
|
169
|
-
|
|
170
|
-
- `tools/property-extractor/transformers.py` - COMPUTED_CONSTANTS dictionary and resolution logic
|
|
171
|
-
- `tools/property-extractor/tests/test_known_values.py` - Test for constant resolution
|
|
172
|
-
- `src/v/serde/rw/chrono.h` - Source definition of max_serializable_ms
|
|
173
|
-
- `tools/property-extractor/CI_INTEGRATION.md` - Updated test count documentation
|