Commit 250ecac
committed
Adds fleet-event-monitor
`fleet-event-monitor` is a separate binary, Docker image, and Helm chart containing **read-only monitoring controllers**. These controllers:
- Mirror the exact watch configuration of Fleet's production controllers (same `SetupWithManager` logic)
- Log detailed diffs (spec, status, annotations, labels) when controllers are triggered
- Perform **no reconciliation or write operations**
- Are enabled/disabled per controller via environment variables or Helm values
- Use read-only RBAC permissions (only `get`, `list`, `watch` — except leases for leader election)
**Problem solved**: Understanding why Fleet controllers are triggered repeatedly or what specific changes cause reconciliation loops, without impacting production workloads.
---
```
fleet/
├── cmd/fleeteventmonitor/main.go # Entry point
├── internal/cmd/monitor/
│ ├── root.go # CLI / cobra setup, env var parsing
│ ├── operator.go # controller-runtime manager, reconciler wiring
│ └── reconciler/
│ ├── monitor.go # Shared logging utilities (logSpecChange, logStatusChange, etc.)
│ ├── stats.go # EventType constants, StatsTracker, Summary (JSON)
│ ├── filter.go # EventTypeFilters struct, ResourceFilter, ShouldLog/ShouldLogTrigger logic
│ ├── cache.go # ObjectCache (thread-safe, namespace/name keyed)
│ ├── predicate.go # TypedResourceVersionUnchangedPredicate
│ ├── bundle_monitor.go # Bundle controller (watches BD + Cluster)
│ ├── bundle_query.go # BundleQuery interface + impl (cluster→bundle mapping)
│ ├── cluster_monitor.go # Cluster controller (watches BD)
│ ├── bundledeployment_monitor.go # BundleDeployment controller
│ ├── gitrepo_monitor.go # GitRepo controller (watches Job)
│ └── helmop_monitor.go # HelmOp controller
├── package/Dockerfile.event-monitor # Multi-arch Docker image (BCI 15.7, non-root)
├── charts/fleet-event-monitor/
│ ├── Chart.yaml
│ ├── values.yaml
│ └── templates/
│ ├── _helpers.tpl
│ ├── deployment.yaml
│ └── rbac.yaml
└── .goreleaser.yaml # Build/release config (fleet-event-monitor added)
```
**Original production controllers (for watch pattern reference)**:
- `internal/cmd/controller/reconciler/bundle_controller.go`
- `internal/cmd/controller/reconciler/cluster_controller.go`
- `internal/cmd/controller/reconciler/bundledeployment_controller.go`
- `internal/cmd/controller/gitops/reconciler/gitjob_controller.go`
- `internal/cmd/controller/helmops/reconciler/helmapp_controller.go`
---
Each monitor controller:
1. Copies `SetupWithManager()` from the original controller (identical watches, predicates, event filters)
2. Replaces `Reconcile()` with logging-only logic
3. Uses an in-memory `ObjectCache` to detect changes between events
1. Reconcile triggered by watch event
2. `Get()` current object from Kubernetes API
3. Look up previous version from `ObjectCache`
4. If first time → log "create", cache object, return
5. If seen before → compare and log: spec diff, status diff, annotation/label/resourceVersion changes
6. Update cache with new version
| Controller | Primary Watch | Secondary Watches |
|---|---|---|
| Bundle | Bundle | BundleDeployment (status changes), Cluster (all changes) |
| Cluster | Cluster | BundleDeployment (spec/status changes) |
| BundleDeployment | BundleDeployment | — |
| GitRepo | GitRepo | Job (status changes) |
| HelmOp | HelmOp | — |
---
Each controller independently operates in one of two modes, controlled by a per-controller `detailed` flag:
| Mode | `detailed` value | Behavior |
|---|---|---|
| **Summary** (default) | `false` | Counts events; prints periodic JSON summaries. No per-event log lines. |
| **Detailed** | `true` | Emits a structured log line for every event with diffs included. |
The summary printer **always runs** regardless of mode, so you always get aggregate statistics. Setting `detailed=true` adds verbose per-event logs on top.
**Default**: all controllers default to `false` (summary only).
| Environment Variable | Helm Value | Default |
|---|---|---|
| `FLEET_EVENT_MONITOR_BUNDLE_DETAILED` | `logging.bundle.detailed` | `false` |
| `FLEET_EVENT_MONITOR_BUNDLEDEPLOYMENT_DETAILED` | `logging.bundleDeployment.detailed` | `false` |
| `FLEET_EVENT_MONITOR_CLUSTER_DETAILED` | `logging.cluster.detailed` | `false` |
| `FLEET_EVENT_MONITOR_GITREPO_DETAILED` | `logging.gitRepo.detailed` | `false` |
| `FLEET_EVENT_MONITOR_HELMOP_DETAILED` | `logging.helmOp.detailed` | `false` |
> **Note**: The wrangler command framework does not parse boolean env vars automatically. They are manually parsed in `root.go` using `strconv.ParseBool()`. Valid values: `true`/`false`, `1`/`0`, `True`/`False`, `TRUE`/`FALSE`.
| Environment Variable | Helm Value | Default | Description |
|---|---|---|---|
| `FLEET_EVENT_MONITOR_SUMMARY_INTERVAL` | `logging.summary.interval` | `"30s"` | How often to print the JSON summary |
| `FLEET_EVENT_MONITOR_SUMMARY_RESET` | `logging.summary.resetOnPrint` | `false` | Reset counters after each print (false = cumulative) |
| Event Type | Env var suffix / Helm key | Description |
|---|---|---|
| `generation-change` | `GENERATION_CHANGE` / `generationChange` | Spec modifications (generation bump) |
| `status-change` | `STATUS_CHANGE` / `statusChange` | Status field updates |
| `annotation-change` | `ANNOTATION_CHANGE` / `annotationChange` | Annotation modifications |
| `label-change` | `LABEL_CHANGE` / `labelChange` | Label modifications |
| `resourceversion-change` | `RESVER_CHANGE` / `resourceVersionChange` | Cache sync / metadata updates (finalizers, ownerRefs, managedFields) |
| `triggered-by` | `TRIGGERED_BY` / `triggeredBy` | Trigger source breakdown by resource type |
| `deletion` | `DELETION` / `deletion` | Resource being deleted |
| `not-found` | `NOT_FOUND` / `notFound` | Resource not found (likely deleted) |
| `create` | `CREATE` / `create` | First observation of resource |
```json
{
"timestamp": "2026-02-09T10:00:30Z",
"interval_seconds": 30,
"summary": {
"Bundle": {
"fleet-local/test-bundle": {
"generation-change": 5,
"status-change": 20,
"triggered-by": { "BundleDeployment": 12, "Cluster": 3 },
"total_events": 41
}
}
},
"totals": { "total_resources_monitored": 3, "total_events": 63 }
}
```
```bash
kubectl logs -n cattle-fleet-system deploy/fleet-event-monitor | \
grep "Fleet Monitor Summary" | tail -1 | \
jq -r '.summary.Bundle | to_entries[] | select(.value.total_events > 50) | "\(.key): \(.value.total_events) events"'
kubectl logs -n cattle-fleet-system deploy/fleet-event-monitor | \
grep "Fleet Monitor Summary" | tail -1 | \
jq '.summary.Bundle["fleet-local/test-bundle"]["triggered-by"]'
kubectl logs -n cattle-fleet-system deploy/fleet-event-monitor | \
grep "Fleet Monitor Summary" | tail -1 | \
jq -r '.summary | to_entries[] | .key as $t | .value | to_entries[] | select(.value["status-change"] > 0) | "\($t)/\(.key): \(.value["status-change"]) status changes"'
kubectl logs -n cattle-fleet-system deploy/fleet-event-monitor | grep "parsed per-controller"
kubectl logs -n cattle-fleet-system deploy/fleet-event-monitor | grep "registered monitor controller"
```
---
When a controller is in detailed mode (`detailed=true`), event type filters let you restrict which event types produce a log line. **Statistics are always tracked** regardless of filters — filters only affect the verbosity of the per-event log output.
**Default behavior**: if all event filter flags are `false`, **all event types are logged** (backwards compatible). To restrict output, set the specific types you want to `true`. Any `true` flag activates selective filtering.
`EventTypeFilters.IsEmpty()` returns true when all fields are false → `ShouldLog()` returns true for every event type. Once any field is set to `true`, only enabled types pass through.
The env var pattern is:
- Bundle: `FLEET_EVENT_MONITOR_BUNDLE_EVENT_<TYPE>`
- BundleDeployment: `FLEET_EVENT_MONITOR_BD_EVENT_<TYPE>`
- Cluster: `FLEET_EVENT_MONITOR_CLUSTER_EVENT_<TYPE>`
- GitRepo: `FLEET_EVENT_MONITOR_GITREPO_EVENT_<TYPE>`
- HelmOp: `FLEET_EVENT_MONITOR_HELMOP_EVENT_<TYPE>`
Where `<TYPE>` is one of: `GENERATION_CHANGE`, `STATUS_CHANGE`, `ANNOTATION_CHANGE`, `LABEL_CHANGE`, `RESVER_CHANGE`, `DELETION`, `NOT_FOUND`, `CREATE`, `TRIGGERED_BY`.
```yaml
logging:
bundle:
detailed: true # Must be true for event filters to have any effect
eventFilters:
generationChange: false # Set true to see spec diffs
statusChange: false # Set true to see status diffs
annotationChange: false
labelChange: false
resourceVersionChange: false # Set true to see cache-sync/metadata events
deletion: false
notFound: false
create: false
triggeredBy: false # Set true to see which resource triggered reconciliation
```
The same structure applies for `bundleDeployment`, `cluster`, `gitRepo`, and `helmOp`.
**Example 1: Only watch generation changes (spec diffs) for Bundle**
```bash
helm upgrade fleet-event-monitor ./charts/fleet-event-monitor \
--set logging.bundle.detailed=true \
--set logging.bundle.eventFilters.generationChange=true
```
**Example 2: Focus on reconciliation trigger sources only**
```bash
helm upgrade fleet-event-monitor ./charts/fleet-event-monitor \
--set logging.bundle.detailed=true \
--set logging.bundle.eventFilters.triggeredBy=true
```
**Example 3: See everything for Bundle (all filters false = log all)**
```bash
helm upgrade fleet-event-monitor ./charts/fleet-event-monitor \
--set logging.bundle.detailed=true
```
**Example 4: Via environment variables**
```bash
FLEET_EVENT_MONITOR_BUNDLE_DETAILED=true \
FLEET_EVENT_MONITOR_BUNDLE_EVENT_GENERATION_CHANGE=true \
FLEET_EVENT_MONITOR_BUNDLE_EVENT_TRIGGERED_BY=true \
./fleeteventmonitor --kubeconfig ~/.kube/config
```
**Example 5: Debug only cache-sync/metadata noise**
```bash
helm upgrade fleet-event-monitor ./charts/fleet-event-monitor \
--set logging.cluster.detailed=true \
--set logging.cluster.eventFilters.resourceVersionChange=true
```
---
Resource filters allow you to restrict monitoring to a specific subset of resources by namespace and/or name. This is useful in large deployments (100+ bundles) where you only care about specific resources and want to reduce log volume.
**Filters apply to both detailed logs AND statistics** — filtered-out resources do not appear in the JSON summary either.
At the top of each controller's `Reconcile()`, the resource namespace and name are tested against the compiled regex patterns. Resources that do not match are skipped entirely — no logs, no statistics.
- Both patterns are **regular expressions** (Go `regexp` syntax)
- An **empty pattern matches all** values for that field (backwards compatible)
- Patterns are compiled at startup; an **invalid regex causes the binary to exit** with a clear error message
- Namespace and name patterns are ANDed — a resource must match both to be monitored
- Filters are orthogonal to event type filtering — both can be combined
| Controller | Namespace Pattern | Name Pattern |
|---|---|---|
| Bundle | `FLEET_EVENT_MONITOR_BUNDLE_RESOURCE_FILTER_NAMESPACE` | `FLEET_EVENT_MONITOR_BUNDLE_RESOURCE_FILTER_NAME` |
| BundleDeployment | `FLEET_EVENT_MONITOR_BUNDLEDEPLOYMENT_RESOURCE_FILTER_NAMESPACE` | `FLEET_EVENT_MONITOR_BUNDLEDEPLOYMENT_RESOURCE_FILTER_NAME` |
| Cluster | `FLEET_EVENT_MONITOR_CLUSTER_RESOURCE_FILTER_NAMESPACE` | `FLEET_EVENT_MONITOR_CLUSTER_RESOURCE_FILTER_NAME` |
| GitRepo | `FLEET_EVENT_MONITOR_GITREPO_RESOURCE_FILTER_NAMESPACE` | `FLEET_EVENT_MONITOR_GITREPO_RESOURCE_FILTER_NAME` |
| HelmOp | `FLEET_EVENT_MONITOR_HELMOP_RESOURCE_FILTER_NAMESPACE` | `FLEET_EVENT_MONITOR_HELMOP_RESOURCE_FILTER_NAME` |
```yaml
logging:
bundle:
resourceFilter:
namespace: "" # Regular expression for namespace matching (e.g., "^fleet-local$")
name: "" # Regular expression for name matching (e.g., "^test-.*")
```
The same structure applies for `bundleDeployment`, `cluster`, `gitRepo`, and `helmOp`.
**Example 1: Monitor only a specific bundle**
```bash
helm upgrade fleet-event-monitor ./charts/fleet-event-monitor \
--set logging.bundle.detailed=true \
--set "logging.bundle.resourceFilter.namespace=^fleet-local$" \
--set "logging.bundle.resourceFilter.name=^my-app$"
```
**Example 2: Monitor all bundles in a namespace**
```bash
helm upgrade fleet-event-monitor ./charts/fleet-event-monitor \
--set logging.bundle.detailed=true \
--set "logging.bundle.resourceFilter.namespace=^fleet-local$"
```
**Example 3: Monitor bundles matching a name prefix**
```bash
helm upgrade fleet-event-monitor ./charts/fleet-event-monitor \
--set logging.bundle.detailed=true \
--set "logging.bundle.resourceFilter.name=^payment-.*"
```
**Example 4: Via environment variables**
```bash
FLEET_EVENT_MONITOR_BUNDLE_DETAILED=true \
FLEET_EVENT_MONITOR_BUNDLE_RESOURCE_FILTER_NAMESPACE="^fleet-local$" \
FLEET_EVENT_MONITOR_BUNDLE_RESOURCE_FILTER_NAME="^my-app$" \
./fleeteventmonitor --kubeconfig ~/.kube/config
```
**Example 5: Combine resource filter with event type filter**
```bash
FLEET_EVENT_MONITOR_BUNDLE_DETAILED=true \
FLEET_EVENT_MONITOR_BUNDLE_RESOURCE_FILTER_NAMESPACE="^fleet-local$" \
FLEET_EVENT_MONITOR_BUNDLE_RESOURCE_FILTER_NAME="^my-app$" \
FLEET_EVENT_MONITOR_BUNDLE_EVENT_STATUS_CHANGE=true \
./fleeteventmonitor --kubeconfig ~/.kube/config
```
---
The Bundle monitor's Cluster watch handler queries which bundles are affected by a cluster change, logging the correct bundle name and namespace in trigger events.
`internal/cmd/monitor/reconciler/bundle_query.go` — adapted from `internal/cmd/controller/target/`:
```go
type BundleQuery interface {
BundlesForCluster(context.Context, *fleet.Cluster) ([]*fleet.Bundle, []*fleet.Bundle, error)
}
```
Supports: basic targeting, label-based cluster matching, ClusterGroups, BundleNamespaceMapping (cross-namespace), Fleet agent bundles, deduplicated results.
**Without the query**: `Bundle reconciliation triggered Bundle= Namespace= Name= TriggeredBy=Cluster:my-cluster:fleet-default`
**With the query**: `Bundle reconciliation triggered Bundle=fleet-default/my-app Namespace=fleet-default Name=my-app TriggeredBy=Cluster:my-cluster:fleet-default`
---
Full `values.yaml` structure as shipped:
```yaml
image:
repository: rancher/fleet-event-monitor
tag: dev
imagePullPolicy: IfNotPresent
namespace: cattle-fleet-system
controllers:
bundle: false
bundledeployment: false
cluster: false
gitrepo: false
helmop: false
workers:
bundle: 5
bundledeployment: 5
cluster: 5
gitrepo: 5
helmop: 5
logFormat: json
logLevel: info
debug: false
debugLevel: 0
shardID: ""
nodeSelector: {}
tolerations: []
priorityClassName: ""
leaderElection:
enabled: true
leaseDuration: 30s
retryPeriod: 10s
renewDeadline: 25s
resources:
limits:
cpu: 500m
memory: 256Mi
requests:
cpu: 100m
memory: 128Mi
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
extraEnv: []
logging:
bundle:
detailed: false
resourceFilter:
namespace: ""
name: ""
eventFilters:
generationChange: false
statusChange: false
annotationChange: false
labelChange: false
resourceVersionChange: false
deletion: false
notFound: false
create: false
triggeredBy: false
bundleDeployment:
detailed: false
resourceFilter:
namespace: ""
name: ""
eventFilters:
generationChange: false
statusChange: false
annotationChange: false
labelChange: false
resourceVersionChange: true
deletion: false
notFound: false
create: false
triggeredBy: false
cluster:
detailed: false
resourceFilter:
namespace: ""
name: ""
eventFilters:
generationChange: false
statusChange: false
annotationChange: false
labelChange: false
resourceVersionChange: false
deletion: false
notFound: false
create: false
triggeredBy: false
gitRepo:
detailed: false
resourceFilter:
namespace: ""
name: ""
eventFilters:
# all false
helmOp:
detailed: false
resourceFilter:
namespace: ""
name: ""
eventFilters:
# all false
summary:
interval: "30s"
resetOnPrint: false
```
---
```bash
go build -o bin/fleeteventmonitor ./cmd/fleeteventmonitor
```
Set environment variables before running. At minimum, enable at least one controller:
```bash
export ENABLE_BUNDLE_EVENT_MONITOR=true
export NAMESPACE=cattle-fleet-system
./bin/fleeteventmonitor --kubeconfig ~/.kube/config
```
For detailed logging with filters:
```bash
export ENABLE_BUNDLE_EVENT_MONITOR=true
export NAMESPACE=cattle-fleet-system
export FLEET_EVENT_MONITOR_BUNDLE_DETAILED=true
export FLEET_EVENT_MONITOR_BUNDLE_EVENT_STATUS_CHANGE=true
export FLEET_EVENT_MONITOR_BUNDLE_EVENT_TRIGGERED_BY=true
./bin/fleeteventmonitor --kubeconfig ~/.kube/config
```
To narrow down to a specific resource:
```bash
export ENABLE_BUNDLE_EVENT_MONITOR=true
export NAMESPACE=cattle-fleet-system
export FLEET_EVENT_MONITOR_BUNDLE_DETAILED=true
export FLEET_EVENT_MONITOR_BUNDLE_RESOURCE_FILTER_NAMESPACE="^fleet-local$"
export FLEET_EVENT_MONITOR_BUNDLE_RESOURCE_FILTER_NAME="^my-app$"
./bin/fleeteventmonitor --kubeconfig ~/.kube/config
```
```bash
helm install fleet-event-monitor ./charts/fleet-event-monitor \
--namespace cattle-fleet-system \
--set controllers.bundle=true \
--set controllers.bundledeployment=true \
--set controllers.cluster=true \
--set controllers.gitrepo=true \
--set controllers.helmop=true
helm install fleet-event-monitor ./charts/fleet-event-monitor \
--namespace cattle-fleet-system \
--set controllers.bundle=true \
--set logging.bundle.detailed=true
helm install fleet-event-monitor ./charts/fleet-event-monitor \
--namespace cattle-fleet-system \
--set controllers.bundle=true \
--set logging.bundle.detailed=true \
--set logging.bundle.eventFilters.generationChange=true \
--set logging.bundle.eventFilters.triggeredBy=true
```
```bash
helm upgrade fleet-event-monitor ./charts/fleet-event-monitor \
--reuse-values \
--set logging.cluster.detailed=true \
--set logging.cluster.eventFilters.statusChange=true
```
```bash
helm install fleet-event-monitor-shard0 ./charts/fleet-event-monitor --set shardID=shard0
helm install fleet-event-monitor-shard1 ./charts/fleet-event-monitor --set shardID=shard1
```
---
ClusterRole: `get`, `list`, `watch` on Fleet resources, core resources, RBAC resources, Jobs, Deployments.
Role (namespaced): `get`, `list`, `watch`, `create`, `update`, `patch`, `delete` on `coordination.k8s.io/leases` (leader election only).
No write access to any Fleet or Kubernetes resources.
---
| Limitation | Workaround |
|---|---|
| Controller-runtime doesn't expose which watch triggered a reconciliation | Log at fan-out mapping functions (Cluster→Bundle handler, BD→Bundle handler) |
| `TypedResourceVersionUnchangedPredicate` causes cache-sync noise | Filter using `eventFilters.resourceVersionChange=false` to suppress in detailed mode |
---
Several scripts in `dev/` help parse and visualize monitor output. They all read from stdin, a pipe, or a file argument and require `jq`.
Parses all `Fleet Monitor Summary` lines from a log stream and renders the last (or cumulative) summary as a human-readable table. Also computes the time range covered if multiple summaries are present.
```bash
kubectl logs -n cattle-fleet-system deploy/fleet-event-monitor | ./dev/format-monitor-summary.sh
./dev/format-monitor-summary.sh logs.json
```
Output example:
```
================================================================================
FLEET MONITOR SUMMARY
================================================================================
Timestamp: 2026-02-09T10:00:30Z
Interval: 30s
Total Resources: 3
Total Events: 63
================================================================================
▼ Bundle
-------------------------------------------------------------------------------
RESOURCE CREATE DELETE N-FOUND STATUS GEN-CHG ANNOT LABEL RESVER EVENTS
---------------------- ------ -------- ------- -------- ------- ----- ----- ------ ------
fleet-local/my-app 1 0 0 20 5 0 0 0 41
└─ triggered-by: BundleDeployment = 12
└─ triggered-by: Cluster = 3
================================================================================
Time range: ...
================================================================================
```
Filters `status-change` events from detailed log output and renders each diff with colour-coded `+`/`-` lines.
Requires the controller to be running in detailed mode with `statusChange` enabled:
```bash
export FLEET_EVENT_MONITOR_BUNDLE_DETAILED=true
export FLEET_EVENT_MONITOR_BUNDLE_EVENT_STATUS_CHANGE=true
```
Usage:
```bash
kubectl logs -n cattle-fleet-system deploy/fleet-event-monitor | ./dev/parse-status-log.sh
```
Filters `resourceversion-change` events and renders each event with version numbers, change reason, metadata change list, and colour-coded diff output. Useful for identifying which SSA managers or finalizer changes are causing metadata-only reconciliation loops.
Requires the controller to be running in detailed mode with `resourceVersionChange` enabled:
```bash
export FLEET_EVENT_MONITOR_BUNDLE_DETAILED=true
export FLEET_EVENT_MONITOR_BUNDLE_EVENT_RESVER_CHANGE=true
```
Usage:
```bash
kubectl logs -n cattle-fleet-system deploy/fleet-event-monitor | ./dev/parse-resourceversion-log.sh
```
The output includes a `Changed:` line listing which metadata fields changed (`finalizers`, `ownerReferences`, `managedFields`) and, for `managedFields`, a manager-level summary (added/removed/changed SSA managers) followed by a field-level diff of their `FieldsV1` entries.
Signed-off-by: Xavi Garcia <xavi.garcia@suse.com>1 parent 7afa158 commit 250ecac
30 files changed
Lines changed: 5863 additions & 0 deletions
File tree
- charts/fleet-event-monitor
- templates
- cmd/fleeteventmonitor
- dev
- internal/cmd/monitor
- reconciler
- package
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
0 commit comments