Repository: VictoriaMetrics/VictoriaMetrics
Pull request #3788 (2023-02-08 12:43:08)

Vmalert fixes
See commit messages.

codecov[bot]: # [Codecov](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/3788?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics) Report
Base: **57.51**% // Head: **57.50**% // Decreases project coverage by **`-0.01%`** :warning:
> Coverage data is based on head [(`35dc7b8`)](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/3788?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics) compared to base [(`146fd2e`)](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/commit/146fd2eca3dc440f488f4153c880c84ee6f97e0f?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics).
> Patch coverage: 70.00% of modified lines in pull request are covered.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #3788      +/-   ##
==========================================
- Coverage   57.51%   57.50%   -0.01%     
==========================================
  Files         312      312              
  Lines       52380    52384       +4     
==========================================
  Hits        30126    30126              
- Misses      20358    20362       +4     
  Partials     1896     1896              
```


| [Impacted Files](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/3788?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics) | Coverage Δ | |
|---|---|---|
| [app/vmalert/web\_types.go](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/3788?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics#diff-YXBwL3ZtYWxlcnQvd2ViX3R5cGVzLmdv) | `100.00% <ø> (ø)` | |
| [app/vmalert/web.qtpl.go](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/3788?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics#diff-YXBwL3ZtYWxlcnQvd2ViLnF0cGwuZ28=) | `57.14% <50.00%> (+0.93%)` | :arrow_up: |
| [app/vmalert/alerting.go](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/3788?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics#diff-YXBwL3ZtYWxlcnQvYWxlcnRpbmcuZ28=) | `78.05% <100.00%> (+0.13%)` | :arrow_up: |
| [app/vmalert/rule.go](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/3788?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics#diff-YXBwL3ZtYWxlcnQvcnVsZS5nbw==) | `100.00% <100.00%> (ø)` | |
| [app/vmalert/remotewrite/remotewrite.go](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/3788?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics#diff-YXBwL3ZtYWxlcnQvcmVtb3Rld3JpdGUvcmVtb3Rld3JpdGUuZ28=) | `60.27% <0.00%> (-5.48%)` | :arrow_down: |
| [lib/mergeset/table.go](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/3788?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics#diff-bGliL21lcmdlc2V0L3RhYmxlLmdv) | `63.28% <0.00%> (-0.33%)` | :arrow_down: |
| [lib/storage/storage.go](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/3788?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics#diff-bGliL3N0b3JhZ2Uvc3RvcmFnZS5nbw==) | `70.97% <0.00%> (-0.30%)` | :arrow_down: |
| [app/vmselect/promql/transform.go](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/3788?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics#diff-YXBwL3Ztc2VsZWN0L3Byb21xbC90cmFuc2Zvcm0uZ28=) | `88.05% <0.00%> (-0.16%)` | :arrow_down: |
| [lib/storage/partition.go](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/3788?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics#diff-bGliL3N0b3JhZ2UvcGFydGl0aW9uLmdv) | `61.39% <0.00%> (+0.79%)` | :arrow_up: |
| ... and [2 more](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/3788?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics) | |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics)

</details>

[:umbrella: View full report at Codecov](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/3788?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics).


Patch:
diff --git a/.github/workflows/check-licenses.yml b/.github/workflows/check-licenses.yml
index 7078127d4..adc287e32 100644
--- a/.github/workflows/check-licenses.yml
+++ b/.github/workflows/check-licenses.yml
@@ -17,7 +17,7 @@ jobs:
       - name: Setup Go
         uses: actions/setup-go@main
         with:
-          go-version: 1.19.5
+          go-version: 1.20.0
         id: go
       - name: Code checkout
         uses: actions/checkout@master
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 8ecc3067c..c0e3c4cbb 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -57,7 +57,7 @@ jobs:
       - name: Set up Go
         uses: actions/setup-go@v3
         with:
-          go-version: 1.19.5
+          go-version: 1.20.0
           check-latest: true
           cache: true
         if: ${{ matrix.language == 'go' }}
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index b99f4dc8d..46fb7cdec 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -32,7 +32,7 @@ jobs:
       - name: Setup Go
         uses: actions/setup-go@v3
         with:
-          go-version: 1.19.5
+          go-version: 1.20.0
           check-latest: true
           cache: true
 
@@ -56,7 +56,7 @@ jobs:
       - name: Setup Go
         uses: actions/setup-go@v3
         with:
-          go-version: 1.19.5
+          go-version: 1.20.0
           check-latest: true
           cache: true
 
@@ -81,7 +81,7 @@ jobs:
         id: go
         uses: actions/setup-go@v3
         with:
-          go-version: 1.19.5
+          go-version: 1.20.0
           check-latest: true
           cache: true
 
diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml
index 81ff42ac5..834fc693e 100644
--- a/.github/workflows/nightly-build.yml
+++ b/.github/workflows/nightly-build.yml
@@ -20,7 +20,7 @@ jobs:
       - name: Setup Go
         uses: actions/setup-go@main
         with:
-          go-version: 1.19.5
+          go-version: 1.20.0
         id: go
 
       - name: Setup docker scan
diff --git a/app/vmalert/alerting.go b/app/vmalert/alerting.go
index bc4d97efc..6d3970258 100644
--- a/app/vmalert/alerting.go
+++ b/app/vmalert/alerting.go
@@ -421,7 +421,9 @@ func (ar *AlertingRule) UpdateWith(r Rule) error {
 	ar.Labels = nr.Labels
 	ar.Annotations = nr.Annotations
 	ar.EvalInterval = nr.EvalInterval
+	ar.Debug = nr.Debug
 	ar.q = nr.q
+	ar.state = nr.state
 	return nil
 }
 
@@ -498,6 +500,7 @@ func (ar *AlertingRule) ToAPI() APIRule {
 		LastSamples:    lastState.samples,
 		MaxUpdates:     ar.state.size(),
 		Updates:        ar.state.getAll(),
+		Debug:          ar.Debug,
 
 		// encode as strings to avoid rounding in JSON
 		ID:      fmt.Sprintf("%d", ar.ID()),
diff --git a/app/vmalert/rule.go b/app/vmalert/rule.go
index ee854c52a..a46f44b15 100644
--- a/app/vmalert/rule.go
+++ b/app/vmalert/rule.go
@@ -37,8 +37,6 @@ type ruleState struct {
 	sync.RWMutex
 	entries []ruleStateEntry
 	cur     int
-	// disabled defines whether ruleState tracks ruleStateEntry
-	disabled bool
 }
 
 type ruleStateEntry struct {
@@ -61,7 +59,7 @@ type ruleStateEntry struct {
 
 func newRuleState(size int) *ruleState {
 	if size < 1 {
-		return &ruleState{disabled: true}
+		size = 1
 	}
 	return &ruleState{
 		entries: make([]ruleStateEntry, size),
@@ -69,10 +67,6 @@ func newRuleState(size int) *ruleState {
 }
 
 func (s *ruleState) getLast() ruleStateEntry {
-	if s.disabled {
-		return ruleStateEntry{}
-	}
-
 	s.RLock()
 	defer s.RUnlock()
 	return s.entries[s.cur]
@@ -85,10 +79,6 @@ func (s *ruleState) size() int {
 }
 
 func (s *ruleState) getAll() []ruleStateEntry {
-	if s.disabled {
-		return nil
-	}
-
 	entries := make([]ruleStateEntry, 0)
 
 	s.RLock()
@@ -111,10 +101,6 @@ func (s *ruleState) getAll() []ruleStateEntry {
 }
 
 func (s *ruleState) add(e ruleStateEntry) {
-	if s.disabled {
-		return
-	}
-
 	s.Lock()
 	defer s.Unlock()
 
diff --git a/app/vmalert/web.qtpl b/app/vmalert/web.qtpl
index dbeb2bd74..80c53c255 100644
--- a/app/vmalert/web.qtpl
+++ b/app/vmalert/web.qtpl
@@ -40,9 +40,9 @@
             for _, g := range groups {
                 for _, r := range g.Rules {
                     if r.LastError != "" {
-                        rNotOk[g.Name]++
+                        rNotOk[g.ID]++
                     } else {
-                        rOk[g.Name]++
+                        rOk[g.ID]++
                     }
                 }
             }
@@ -50,11 +50,11 @@
          <a class="btn btn-primary" role="button" onclick="collapseAll()">Collapse All</a>
          <a class="btn btn-primary" role="button" onclick="expandAll()">Expand All</a>
         {% for _, g := range groups  %}
-              <div class="group-heading{% if rNotOk[g.Name] > 0 %} alert-danger{% endif %}"  data-bs-target="rules-{%s g.ID %}">
+              <div class="group-heading{% if rNotOk[g.ID] > 0 %} alert-danger{% endif %}"  data-bs-target="rules-{%s g.ID %}">
                 <span class="anchor" id="group-{%s g.ID %}"></span>
                 <a href="#group-{%s g.ID %}">{%s g.Name %}{% if g.Type != "prometheus" %} ({%s g.Type %}){% endif %} (every {%f.0 g.Interval %}s)</a>
-                 {% if rNotOk[g.Name] > 0 %}<span class="badge bg-danger" title="Number of rules with status Error">{%d rNotOk[g.Name] %}</span> {% endif %}
-                <span class="badge bg-success" title="Number of rules withs status Ok">{%d rOk[g.Name] %}</span>
+                 {% if rNotOk[g.ID] > 0 %}<span class="badge bg-danger" title="Number of rules with status Error">{%d rNotOk[g.ID] %}</span> {% endif %}
+                <span class="badge bg-success" title="Number of rules withs status Ok">{%d rOk[g.ID] %}</span>
                 <p class="fs-6 fw-lighter">{%s g.File %}</p>
                 {% if len(g.Params) > 0 %}
                     <div class="fs-6 fw-lighter">Extra params
@@ -427,6 +427,16 @@
         </div>
       </div>
     </div>
+    <div class="container border-bottom p-2">
+      <div class="row">
+        <div class="col-2">
+          Debug
+        </div>
+        <div class="col">
+           {%v rule.Debug %}
+        </div>
+      </div>
+    </div>
     {% endif %}
     <div class="container border-bottom p-2">
       <div class="row">
diff --git a/app/vmalert/web.qtpl.go b/app/vmalert/web.qtpl.go
index a295cd4d2..2a7489371 100644
--- a/app/vmalert/web.qtpl.go
+++ b/app/vmalert/web.qtpl.go
@@ -171,9 +171,9 @@ func StreamListGroups(qw422016 *qt422016.Writer, r *http.Request, groups []APIGr
 		for _, g := range groups {
 			for _, r := range g.Rules {
 				if r.LastError != "" {
-					rNotOk[g.Name]++
+					rNotOk[g.ID]++
 				} else {
-					rOk[g.Name]++
+					rOk[g.ID]++
 				}
 			}
 		}
@@ -189,7 +189,7 @@ func StreamListGroups(qw422016 *qt422016.Writer, r *http.Request, groups []APIGr
 			qw422016.N().S(`
               <div class="group-heading`)
 //line app/vmalert/web.qtpl:53
-			if rNotOk[g.Name] > 0 {
+			if rNotOk[g.ID] > 0 {
 //line app/vmalert/web.qtpl:53
 				qw422016.N().S(` alert-danger`)
 //line app/vmalert/web.qtpl:53
@@ -230,11 +230,11 @@ func StreamListGroups(qw422016 *qt422016.Writer, r *http.Request, groups []APIGr
 			qw422016.N().S(`s)</a>
                  `)
 //line app/vmalert/web.qtpl:56
-			if rNotOk[g.Name] > 0 {
+			if rNotOk[g.ID] > 0 {
 //line app/vmalert/web.qtpl:56
 				qw422016.N().S(`<span class="badge bg-danger" title="Number of rules with status Error">`)
 //line app/vmalert/web.qtpl:56
-				qw422016.N().D(rNotOk[g.Name])
+				qw422016.N().D(rNotOk[g.ID])
 //line app/vmalert/web.qtpl:56
 				qw422016.N().S(`</span> `)
 //line app/vmalert/web.qtpl:56
@@ -243,7 +243,7 @@ func StreamListGroups(qw422016 *qt422016.Writer, r *http.Request, groups []APIGr
 			qw422016.N().S(`
                 <span class="badge bg-success" title="Number of rules withs status Ok">`)
 //line app/vmalert/web.qtpl:57
-			qw422016.N().D(rOk[g.Name])
+			qw422016.N().D(rOk[g.ID])
 //line app/vmalert/web.qtpl:57
 			qw422016.N().S(`</span>
                 <p class="fs-6 fw-lighter">`)
@@ -1313,10 +1313,24 @@ func StreamRuleDetails(qw422016 *qt422016.Writer, r *http.Request, rule APIRule)
         </div>
       </div>
     </div>
+    <div class="container border-bottom p-2">
+      <div class="row">
+        <div class="col-2">
+          Debug
+        </div>
+        <div class="col">
+           `)
+//line app/vmalert/web.qtpl:436
+		qw422016.E().V(rule.Debug)
+//line app/vmalert/web.qtpl:436
+		qw422016.N().S(`
+        </div>
+      </div>
+    </div>
     `)
-//line app/vmalert/web.qtpl:430
+//line app/vmalert/web.qtpl:440
 	}
-//line app/vmalert/web.qtpl:430
+//line app/vmalert/web.qtpl:440
 	qw422016.N().S(`
     <div class="container border-bottom p-2">
       <div class="row">
@@ -1325,17 +1339,17 @@ func StreamRuleDetails(qw422016 *qt422016.Writer, r *http.Request, rule APIRule)
         </div>
         <div class="col">
            <a target="_blank" href="`)
-//line app/vmalert/web.qtpl:437
+//line app/vmalert/web.qtpl:447
 	qw422016.E().S(prefix)
-//line app/vmalert/web.qtpl:437
+//line app/vmalert/web.qtpl:447
 	qw422016.N().S(`groups#group-`)
-//line app/vmalert/web.qtpl:437
+//line app/vmalert/web.qtpl:447
 	qw422016.E().S(rule.GroupID)
-//line app/vmalert/web.qtpl:437
+//line app/vmalert/web.qtpl:447
 	qw422016.N().S(`">`)
-//line app/vmalert/web.qtpl:437
+//line app/vmalert/web.qtpl:447
 	qw422016.E().S(rule.GroupID)
-//line app/vmalert/web.qtpl:437
+//line app/vmalert/web.qtpl:447
 	qw422016.N().S(`</a>
         </div>
       </div>
@@ -1343,13 +1357,13 @@ func StreamRuleDetails(qw422016 *qt422016.Writer, r *http.Request, rule APIRule)
 
     <br>
     <div class="display-6 pb-3">Last `)
-//line app/vmalert/web.qtpl:443
+//line app/vmalert/web.qtpl:453
 	qw422016.N().D(len(rule.Updates))
-//line app/vmalert/web.qtpl:443
+//line app/vmalert/web.qtpl:453
 	qw422016.N().S(`/`)
-//line app/vmalert/web.qtpl:443
+//line app/vmalert/web.qtpl:453
 	qw422016.N().D(rule.MaxUpdates)
-//line app/vmalert/web.qtpl:443
+//line app/vmalert/web.qtpl:453
 	qw422016.N().S(` updates</span>:</div>
         <table class="table table-striped table-hover table-sm">
             <thead>
@@ -1364,201 +1378,201 @@ func StreamRuleDetails(qw422016 *qt422016.Writer, r *http.Request, rule APIRule)
             <tbody>
 
      `)
-//line app/vmalert/web.qtpl:456
+//line app/vmalert/web.qtpl:466
 	for _, u := range rule.Updates {
-//line app/vmalert/web.qtpl:456
+//line app/vmalert/web.qtpl:466
 		qw422016.N().S(`
              <tr`)
-//line app/vmalert/web.qtpl:457
+//line app/vmalert/web.qtpl:467
 		if u.err != nil {
-//line app/vmalert/web.qtpl:457
+//line app/vmalert/web.qtpl:467
 			qw422016.N().S(` class="alert-danger"`)
-//line app/vmalert/web.qtpl:457
+//line app/vmalert/web.qtpl:467
 		}
-//line app/vmalert/web.qtpl:457
+//line app/vmalert/web.qtpl:467
 		qw422016.N().S(`>
                  <td>
                     <span class="badge bg-primary rounded-pill me-3" title="Updated at">`)
-//line app/vmalert/web.qtpl:459
+//line app/vmalert/web.qtpl:469
 		qw422016.E().S(u.time.Format(time.RFC3339))
-//line app/vmalert/web.qtpl:459
+//line app/vmalert/web.qtpl:469
 		qw422016.N().S(`</span>
                  </td>
                  <td class="text-center" wi>`)
-//line app/vmalert/web.qtpl:461
+//line app/vmalert/web.qtpl:471
 		qw422016.N().D(u.samples)
-//line app/vmalert/web.qtpl:461
+//line app/vmalert/web.qtpl:471
 		qw422016.N().S(`</td>
                  <td class="text-center">`)
-//line app/vmalert/web.qtpl:462
+//line app/vmalert/web.qtpl:472
 		qw422016.N().FPrec(u.duration.Seconds(), 3)
-//line app/vmalert/web.qtpl:462
+//line app/vmalert/web.qtpl:472
 		qw422016.N().S(`s</td>
                  <td class="text-center">`)
-//line app/vmalert/web.qtpl:463
+//line app/vmalert/web.qtpl:473
 		qw422016.E().S(u.at.Format(time.RFC3339))
-//line app/vmalert/web.qtpl:463
+//line app/vmalert/web.qtpl:473
 		qw422016.N().S(`</td>
                  <td>
                     <textarea class="curl-area" rows="1" onclick="this.focus();this.select()">`)
-//line app/vmalert/web.qtpl:465
+//line app/vmalert/web.qtpl:475
 		qw422016.E().S(u.curl)
-//line app/vmalert/web.qtpl:465
+//line app/vmalert/web.qtpl:475
 		qw422016.N().S(`</textarea>
                 </td>
              </tr>
           </li>
           `)
-//line app/vmalert/web.qtpl:469
+//line app/vmalert/web.qtpl:479
 		if u.err != nil {
-//line app/vmalert/web.qtpl:469
+//line app/vmalert/web.qtpl:479
 			qw422016.N().S(`
              <tr`)
-//line app/vmalert/web.qtpl:470
+//line app/vmalert/web.qtpl:480
 			if u.err != nil {
-//line app/vmalert/web.qtpl:470
+//line app/vmalert/web.qtpl:480
 				qw422016.N().S(` class="alert-danger"`)
-//line app/vmalert/web.qtpl:470
+//line app/vmalert/web.qtpl:480
 			}
-//line app/vmalert/web.qtpl:470
+//line app/vmalert/web.qtpl:480
 			qw422016.N().S(`>
                <td colspan="5">
                    <span class="alert-danger">`)
-//line app/vmalert/web.qtpl:472
+//line app/vmalert/web.qtpl:482
 			qw422016.E().V(u.err)
-//line app/vmalert/web.qtpl:472
+//line app/vmalert/web.qtpl:482
 			qw422016.N().S(`</span>
                </td>
              </tr>
           `)
-//line app/vmalert/web.qtpl:475
+//line app/vmalert/web.qtpl:485
 		}
-//line app/vmalert/web.qtpl:475
+//line app/vmalert/web.qtpl:485
 		qw422016.N().S(`
      `)
-//line app/vmalert/web.qtpl:476
+//line app/vmalert/web.qtpl:486
 	}
-//line app/vmalert/web.qtpl:476
+//line app/vmalert/web.qtpl:486
 	qw422016.N().S(`
 
     `)
-//line app/vmalert/web.qtpl:478
+//line app/vmalert/web.qtpl:488
 	tpl.StreamFooter(qw422016, r)
-//line app/vmalert/web.qtpl:478
+//line app/vmalert/web.qtpl:488
 	qw422016.N().S(`
 `)
-//line app/vmalert/web.qtpl:479
+//line app/vmalert/web.qtpl:489
 }
 
-//line app/vmalert/web.qtpl:479
+//line app/vmalert/web.qtpl:489
 func WriteRuleDetails(qq422016 qtio422016.Writer, r *http.Request, rule APIRule) {
-//line app/vmalert/web.qtpl:479
+//line app/vmalert/web.qtpl:489
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line app/vmalert/web.qtpl:479
+//line app/vmalert/web.qtpl:489
 	StreamRuleDetails(qw422016, r, rule)
-//line app/vmalert/web.qtpl:479
+//line app/vmalert/web.qtpl:489
 	qt422016.ReleaseWriter(qw422016)
-//line app/vmalert/web.qtpl:479
+//line app/vmalert/web.qtpl:489
 }
 
-//line app/vmalert/web.qtpl:479
+//line app/vmalert/web.qtpl:489
 func RuleDetails(r *http.Request, rule APIRule) string {
-//line app/vmalert/web.qtpl:479
+//line app/vmalert/web.qtpl:489
 	qb422016 := qt422016.AcquireByteBuffer()
-//line app/vmalert/web.qtpl:479
+//line app/vmalert/web.qtpl:489
 	WriteRuleDetails(qb422016, r, rule)
-//line app/vmalert/web.qtpl:479
+//line app/vmalert/web.qtpl:489
 	qs422016 := string(qb422016.B)
-//line app/vmalert/web.qtpl:479
+//line app/vmalert/web.qtpl:489
 	qt422016.ReleaseByteBuffer(qb422016)
-//line app/vmalert/web.qtpl:479
+//line app/vmalert/web.qtpl:489
 	return qs422016
-//line app/vmalert/web.qtpl:479
+//line app/vmalert/web.qtpl:489
 }
 
-//line app/vmalert/web.qtpl:483
+//line app/vmalert/web.qtpl:493
 func streambadgeState(qw422016 *qt422016.Writer, state string) {
-//line app/vmalert/web.qtpl:483
+//line app/vmalert/web.qtpl:493
 	qw422016.N().S(`
 `)
-//line app/vmalert/web.qtpl:485
+//line app/vmalert/web.qtpl:495
 	badgeClass := "bg-warning text-dark"
 	if state == "firing" {
 		badgeClass = "bg-danger"
 	}
 
-//line app/vmalert/web.qtpl:489
+//line app/vmalert/web.qtpl:499
 	qw422016.N().S(`
 <span class="badge `)
-//line app/vmalert/web.qtpl:490
+//line app/vmalert/web.qtpl:500
 	qw422016.E().S(badgeClass)
-//line app/vmalert/web.qtpl:490
+//line app/vmalert/web.qtpl:500
 	qw422016.N().S(`">`)
-//line app/vmalert/web.qtpl:490
+//line app/vmalert/web.qtpl:500
 	qw422016.E().S(state)
-//line app/vmalert/web.qtpl:490
+//line app/vmalert/web.qtpl:500
 	qw422016.N().S(`</span>
 `)
-//line app/vmalert/web.qtpl:491
+//line app/vmalert/web.qtpl:501
 }
 
-//line app/vmalert/web.qtpl:491
+//line app/vmalert/web.qtpl:501
 func writebadgeState(qq422016 qtio422016.Writer, state string) {
-//line app/vmalert/web.qtpl:491
+//line app/vmalert/web.qtpl:501
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line app/vmalert/web.qtpl:491
+//line app/vmalert/web.qtpl:501
 	streambadgeState(qw422016, state)
-//line app/vmalert/web.qtpl:491
+//line app/vmalert/web.qtpl:501
 	qt422016.ReleaseWriter(qw422016)
-//line app/vmalert/web.qtpl:491
+//line app/vmalert/web.qtpl:501
 }
 
-//line app/vmalert/web.qtpl:491
+//line app/vmalert/web.qtpl:501
 func badgeState(state string) string {
-//line app/vmalert/web.qtpl:491
+//line app/vmalert/web.qtpl:501
 	qb422016 := qt422016.AcquireByteBuffer()
-//line app/vmalert/web.qtpl:491
+//line app/vmalert/web.qtpl:501
 	writebadgeState(qb422016, state)
-//line app/vmalert/web.qtpl:491
+//line app/vmalert/web.qtpl:501
 	qs422016 := string(qb422016.B)
-//line app/vmalert/web.qtpl:491
+//line app/vmalert/web.qtpl:501
 	qt422016.ReleaseByteBuffer(qb422016)
-//line app/vmalert/web.qtpl:491
+//line app/vmalert/web.qtpl:501
 	return qs422016
-//line app/vmalert/web.qtpl:491
+//line app/vmalert/web.qtpl:501
 }
 
-//line app/vmalert/web.qtpl:493
+//line app/vmalert/web.qtpl:503
 func streambadgeRestored(qw422016 *qt422016.Writer) {
-//line app/vmalert/web.qtpl:493
+//line app/vmalert/web.qtpl:503
 	qw422016.N().S(`
 <span class="badge bg-warning text-dark" title="Alert state was restored after the service restart from remote storage">restored</span>
 `)
-//line app/vmalert/web.qtpl:495
+//line app/vmalert/web.qtpl:505
 }
 
-//line app/vmalert/web.qtpl:495
+//line app/vmalert/web.qtpl:505
 func writebadgeRestored(qq422016 qtio422016.Writer) {
-//line app/vmalert/web.qtpl:495
+//line app/vmalert/web.qtpl:505
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line app/vmalert/web.qtpl:495
+//line app/vmalert/web.qtpl:505
 	streambadgeRestored(qw422016)
-//line app/vmalert/web.qtpl:495
+//line app/vmalert/web.qtpl:505
 	qt422016.ReleaseWriter(qw422016)
-//line app/vmalert/web.qtpl:495
+//line app/vmalert/web.qtpl:505
 }
 
-//line app/vmalert/web.qtpl:495
+//line app/vmalert/web.qtpl:505
 func badgeRestored() string {
-//line app/vmalert/web.qtpl:495
+//line app/vmalert/web.qtpl:505
 	qb422016 := qt422016.AcquireByteBuffer()
-//line app/vmalert/web.qtpl:495
+//line app/vmalert/web.qtpl:505
 	writebadgeRestored(qb422016)
-//line app/vmalert/web.qtpl:495
+//line app/vmalert/web.qtpl:505
 	qs422016 := string(qb422016.B)
-//line app/vmalert/web.qtpl:495
+//line app/vmalert/web.qtpl:505
 	qt422016.ReleaseByteBuffer(qb422016)
-//line app/vmalert/web.qtpl:495
+//line app/vmalert/web.qtpl:505
 	return qs422016
-//line app/vmalert/web.qtpl:495
+//line app/vmalert/web.qtpl:505
 }
diff --git a/app/vmalert/web_types.go b/app/vmalert/web_types.go
index fcd6e0b3c..48b5a298a 100644
--- a/app/vmalert/web_types.go
+++ b/app/vmalert/web_types.go
@@ -113,18 +113,20 @@ type APIRule struct {
 
 	// Additional fields
 
-	// Type of the rule: recording or alerting
+	// DatasourceType of the rule: prometheus or graphite
 	DatasourceType string `json:"datasourceType"`
 	LastSamples    int    `json:"lastSamples"`
 	// ID is a unique Alert's ID within a group
 	ID string `json:"id"`
 	// GroupID is an unique Group's ID
 	GroupID string `json:"group_id"`
+	// Debug shows whether debug mode is enabled
+	Debug bool `json:"debug"`
 
 	// MaxUpdates is the max number of recorded ruleStateEntry objects
 	MaxUpdates int `json:"max_updates_entries"`
 	// Updates contains the ordered list of recorded ruleStateEntry objects
-	Updates []ruleStateEntry `json:"updates"`
+	Updates []ruleStateEntry `json:"-"`
 }
 
 // WebLink returns a link to the alert which can be used in UI.
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index 480c4c046..e39bda14c 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -21,6 +21,9 @@ The following tip changes can be tested by building VictoriaMetrics components f
 * FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): optimize VMUI for use from smarthones and tablets. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3707).
 
 * BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent.html): fix panic in nomad watcher because of improperly initialized object. Thanks to @mr-karan for the [pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/3784)
+* BUGFIX: [vmalert](https://docs.victoriametrics.com/vmalert.html): fix display of rules number per-group for groups with identical names in UI.
+* BUGFIX: [vmalert](https://docs.victoriametrics.com/vmalert.html): prevent disabling state updates tracking per rule via setting values < 1. The minimum number of update states to track is now set to 1.
+* BUGFIX: [vmalert](https://docs.victoriametrics.com/vmalert.html): properly update `debug` and `update_entries_limit` rule's params on config's hot-reload.
 
 ## [v1.87.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.87.0)
 
@@ -102,7 +105,7 @@ Released at 2023-01-10
 * FEATURE: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): allow using unicode letters in identifiers. For example, `температура{город="Киев"}` is a valid MetricsQL expression now. Previously every non-ascii letters should be escaped with `\` char when used inside MetricsQL expression: `\т\е\м\п\е\р\а\т\у\р\а{\г\о\р\о\д="Киев"}`. Now both expressions are equivalent. Thanks to @hzwwww for the [pull request](https://github.com/VictoriaMetrics/metricsql/pull/7).
 * FEATURE: [relabeling](https://docs.victoriametrics.com/vmagent.html#relabeling): add support for `keepequal` and `dropequal` relabeling actions, which are supported by Prometheus starting from [v2.41.0](https://github.com/prometheus/prometheus/releases/tag/v2.41.0). These relabeling actions are almost identical to `keep_if_equal` and `drop_if_equal` relabeling actions supported by VictoriaMetrics since `v1.38.0` - see [these docs](https://docs.victoriametrics.com/vmagent.html#relabeling-enhancements) - so it is recommended sticking to `keep_if_equal` and `drop_if_equal` actions instead of switching to `keepequal` and `dropequal`.
 * FEATURE: [csvimport](https://docs.victoriametrics.com/#how-to-import-csv-data): support empty values for imported metrics. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3540).
-* FEATURE: [vmalert](httpпоs://docs.victoriametrics.com/vmalert.html): allow configuring the default number of stored rule's update states in memory via global `-rule.updateEntriesLimit` command-line flag or per-rule via rule's `update_entries_limit` configuration param. See [these docs](https://docs.victoriametrics.com/vmalert.html#rules) and [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/3556).
+* FEATURE: [vmalert](https://docs.victoriametrics.com/vmalert.html): allow configuring the default number of stored rule's update states in memory via global `-rule.updateEntriesLimit` command-line flag or per-rule via rule's `update_entries_limit` configuration param. See [these docs](https://docs.victoriametrics.com/vmalert.html#rules) and [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/3556).
 * FEATURE: improve the logic benhind `-maxConcurrentInserts` command-line flag. Previously this flag was limiting the number of concurrent connections from clients, which write data to VictoriaMetrics or [vmagent](https://docs.victoriametrics.com/vmagent.html). Some of these connections could be idle for some time. These connections do not need significant amounts of CPU and memory, so there is no sense in limiting their count. The updated logic behind `-maxConcurrentInserts` limits the number of **active** insert requests, not counting idle connections.
 * FEATURE: protect all the http endpoints with `-httpAuth.*` command-line flag. Previously endpoints protected by `-*AuthKey` command-line flags weren't protected by `-httpAuth.*`. This could complicate the proper security setup. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3060).
 * FEATURE: [VictoriaMetrics cluster](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html): add `-maxConcurrentInserts` and `-insert.maxQueueDuration` command-line flags to `vmstorage`, so they could be tuned if needed in the same way as at `vminsert` nodes.


Test patch:
diff --git a/app/vmalert/rule_test.go b/app/vmalert/rule_test.go
index 28205da90..15aed5c21 100644
--- a/app/vmalert/rule_test.go
+++ b/app/vmalert/rule_test.go
@@ -14,13 +14,13 @@ func TestRule_stateDisabled(t *testing.T) {
 	}
 
 	state.add(ruleStateEntry{at: time.Now()})
-	if !e.at.IsZero() {
-		t.Fatalf("expected entry to be zero")
-	}
+	state.add(ruleStateEntry{at: time.Now()})
+	state.add(ruleStateEntry{at: time.Now()})
 
-	if len(state.getAll()) != 0 {
+	if len(state.getAll()) != 1 {
+		// state should store at least one update at any circumstances
 		t.Fatalf("expected for state to have %d entries; got %d",
-			0, len(state.getAll()),
+			1, len(state.getAll()),
 		)
 	}
 }