# UNDF: UNDF-2026-000000411
# grpc-0001: channelz PropertyGrid/PropertyTable GetIndex() std::find O(C²) → O(1) with absl::flat_hash_map

## File
`src/core/channelz/property_list.cc`

## Lines
28–36 (`GetIndex`), called from lines 259, 260, 270, 272–273, 280–281, 314, 326

## Severity
**MEDIUM** — Channelz monitoring path. `GetIndex()` uses `std::find` on `std::vector<std::string>` to find or insert a column/row name. Every call to `PropertyGrid::Set()`, `SetColumn()`, `SetRow()`, or `PropertyTable::Set()`/`SetRow()` triggers a linear scan of all previously registered column/row names. When a `PropertyGrid` or `PropertyTable` is built with C distinct columns and R distinct rows, the column/row registration is O(C²) + O(R²) in total. In high-frequency channelz monitoring (hundreds of RPC calls/second × per-call property recording), this degrades O(calls²) per collection interval.

## Complexity Analysis

**Defective:**
- `GetIndex(columns_, column)` called once per `Set(col, row, val)`: O(C) scan of `columns_` vector, C times → O(C²) total column registration
- Same for `rows_` → O(R²) total row registration
- `SetColumn()` inner loop: O(R) per property + O(C) per column lookup

**Fixed:**
- `columns_map_` / `rows_map_`: `absl::flat_hash_map<std::string, size_t>` for O(1) index lookup
- Insert still appends to vector for ordered iteration; map gives O(1) "already seen" check

## Defective Snippet

```cpp
// src/core/channelz/property_list.cc, lines 28-36
namespace {
size_t GetIndex(std::vector<std::string>& vec, absl::string_view value) {
  auto it = std::find(vec.begin(), vec.end(), value);  // O(N) linear scan
  if (it == vec.end()) {
    vec.emplace_back(value);
    return vec.size() - 1;
  } else {
    return it - vec.begin();
  }
}
} // namespace

// Called in PropertyGrid::SetInternal (line 259-260):
int c = GetIndex(columns_, column);  // O(C) per call → O(C²) total
int r = GetIndex(rows_, row);        // O(R) per call → O(R²) total

// Called in PropertyGrid::SetColumn (line 270-273):
int c = GetIndex(columns_, column);
for (auto& [key, value] : values.property_list_) {
    grid_.emplace(std::pair(c, GetIndex(rows_, std::move(key))), ...); // O(R) each
}

// Called in PropertyTable::SetInternal (line 314):
int c = GetIndex(columns_, column);  // O(C) per call → O(C²) total
```

## Fixed Snippet

```cpp
// In property_list.h, add to PropertyGrid and PropertyTable private sections:
// absl::flat_hash_map<std::string, size_t> columns_map_;
// absl::flat_hash_map<std::string, size_t> rows_map_;  // PropertyGrid only

// Replace GetIndex free function with member-aware O(1) version:
namespace {
size_t GetIndex(std::vector<std::string>& vec,
                absl::flat_hash_map<std::string, size_t>& map,
                absl::string_view value) {
  auto it = map.find(value);            // O(1) hash lookup
  if (it == map.end()) {
    size_t idx = vec.size();
    vec.emplace_back(value);
    map.emplace(std::string(value), idx);
    return idx;
  }
  return it->second;
}
} // namespace

// All call sites replace: GetIndex(columns_, column)
// with: GetIndex(columns_, columns_map_, column)
// and:  GetIndex(rows_, row)
// with: GetIndex(rows_, rows_map_, row)
```

## Notes
- `absl::flat_hash_map` is already used in this file (see `grid_` member in PropertyGrid/PropertyTable)
- The ordered `std::vector` is still needed for serialization order in `FillUpbProto` / `TakeJsonObject`
- Impact: every call to channelz `Set()` on a grid/table with >10 distinct column/row names pays O(N) unnecessarily; at 1000 RPC/s with 50 metrics per call = 50,000 `GetIndex` calls/second, each scanning up to 50 entries = 2.5M comparisons/second
- This is a 2025 addition (copyright header), suggesting it was written without awareness of the existing `absl::flat_hash_map` pattern used elsewhere in the same file
