Repository: VictoriaMetrics/VictoriaMetrics
Pull request #3238 (2022-10-14 17:05:49)

vmui: remove rounding of axis limits

codecov[bot]: # [Codecov](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/3238?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics) Report
Base: **57.60**% // Head: **57.70**% // Increases project coverage by **`+0.10%`** :tada:
> Coverage data is based on head [(`884c5cb`)](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/3238?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics) compared to base [(`2e3be68`)](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/commit/2e3be68617182f2777b232906854b221ab234b3f?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics).
> Patch has no changes to coverable lines.

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


```diff
@@            Coverage Diff             @@
##           master    #3238      +/-   ##
==========================================
+ Coverage   57.60%   57.70%   +0.10%     
==========================================
  Files         285      285              
  Lines       48386    48386              
==========================================
+ Hits        27872    27921      +49     
+ Misses      18717    18665      -52     
- Partials     1797     1800       +3     
```


| [Impacted Files](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/3238?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics) | Coverage Δ | |
|---|---|---|
| [lib/fasttime/fasttime.go](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/3238/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics#diff-bGliL2Zhc3R0aW1lL2Zhc3R0aW1lLmdv) | `50.00% <0.00%> (-21.43%)` | :arrow_down: |
| [lib/storage/storage.go](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/3238/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics#diff-bGliL3N0b3JhZ2Uvc3RvcmFnZS5nbw==) | `69.60% <0.00%> (-0.45%)` | :arrow_down: |
| [lib/mergeset/table.go](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/3238/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics#diff-bGliL21lcmdlc2V0L3RhYmxlLmdv) | `67.50% <0.00%> (+2.32%)` | :arrow_up: |
| [app/vmselect/promql/aggr\_incremental.go](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/3238/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics#diff-YXBwL3Ztc2VsZWN0L3Byb21xbC9hZ2dyX2luY3JlbWVudGFsLmdv) | `86.48% <0.00%> (+3.60%)` | :arrow_up: |
| [lib/mergeset/block\_stream\_reader.go](https://codecov.io/gh/VictoriaMetrics/VictoriaMetrics/pull/3238/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VictoriaMetrics#diff-bGliL21lcmdlc2V0L2Jsb2NrX3N0cmVhbV9yZWFkZXIuZ28=) | `65.88% <0.00%> (+11.21%)` | :arrow_up: |

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/3238?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/app/vmalert/config/config.go b/app/vmalert/config/config.go
index fbdd9ab58..d0a2ef68a 100644
--- a/app/vmalert/config/config.go
+++ b/app/vmalert/config/config.go
@@ -243,9 +243,12 @@ func Parse(pathPatterns []string, validateTplFn ValidateTplFn, validateExpressio
 func parseFile(path string) ([]Group, error) {
 	data, err := os.ReadFile(path)
 	if err != nil {
-		return nil, fmt.Errorf("error reading alert rule file: %w", err)
+		return nil, fmt.Errorf("error reading alert rule file %q: %w", path, err)
+	}
+	data, err = envtemplate.Replace(data)
+	if err != nil {
+		return nil, fmt.Errorf("cannot expand environment vars in %q: %w", path, err)
 	}
-	data = envtemplate.Replace(data)
 	g := struct {
 		Groups []Group `yaml:"groups"`
 		// Catches all undefined fields and must be empty after parsing.
diff --git a/app/vmauth/auth_config.go b/app/vmauth/auth_config.go
index e19086069..46eee7524 100644
--- a/app/vmauth/auth_config.go
+++ b/app/vmauth/auth_config.go
@@ -250,7 +250,11 @@ func readAuthConfig(path string) (map[string]*UserInfo, error) {
 }
 
 func parseAuthConfig(data []byte) (map[string]*UserInfo, error) {
-	data = envtemplate.Replace(data)
+	var err error
+	data, err = envtemplate.Replace(data)
+	if err != nil {
+		return nil, fmt.Errorf("cannot expand environment vars: %w", err)
+	}
 	var ac AuthConfig
 	if err := yaml.UnmarshalStrict(data, &ac); err != nil {
 		return nil, fmt.Errorf("cannot unmarshal AuthConfig data: %w", err)
diff --git a/app/vmui/packages/vmui/src/utils/uplot/axes.ts b/app/vmui/packages/vmui/src/utils/uplot/axes.ts
index 148e0b10f..4dc0f2b1f 100644
--- a/app/vmui/packages/vmui/src/utils/uplot/axes.ts
+++ b/app/vmui/packages/vmui/src/utils/uplot/axes.ts
@@ -48,7 +48,7 @@ export const getMinMaxBuffer = (min: number | null, max: number | null): [number
   }
   const valueRange = Math.abs(max - min) || Math.abs(min) || 1;
   const padding = 0.02*valueRange;
-  return [Math.floor(min - padding), Math.ceil(max + padding)];
+  return [min - padding, max + padding];
 };
 
 export const getLimitsYAxis = (values: { [key: string]: number[] }): AxisRange => {
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index e86098217..553a38ff2 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -39,6 +39,7 @@ The following tip changes can be tested by building VictoriaMetrics components f
 
 * FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): allow controlling staleness tracking on a per-[scrape_config](https://docs.victoriametrics.com/sd_configs.html#scrape_configs) basis by specifying `no_stale_markers: true` or `no_stale_markers: false` option in the corresponding [scrape_config](https://docs.victoriametrics.com/sd_configs.html#scrape_configs).
 * FEATURE: [vmui](https://docs.victoriametrics.com/#vmui): limit the number of plotted series. This should prevent from browser crashes or hangs when the query returns big number of time series. See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3155).
+* FEATURE: log error if some environment variables referred at `-promscrape.config` via `%{ENV_VAR}` aren't found. This should prevent from using incorrect config files.
 
 * BUGFIX: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): properly merge buckets with identical `le` values, but with different string representation of these values when calculating [histogram_quantile](https://docs.victoriametrics.com/MetricsQL.html#histogram_quantile) and [histogram_share](https://docs.victoriametrics.com/MetricsQL.html#histogram_share). For example, `http_request_duration_seconds_bucket{le="5"}` and `http_requests_duration_seconds_bucket{le="5.0"}`. Such buckets may be returned from distinct targets. Thanks to @647-coder for the [pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/3225).
 
diff --git a/go.mod b/go.mod
index 856887f8b..4ef3aa504 100644
--- a/go.mod
+++ b/go.mod
@@ -26,7 +26,7 @@ require (
 	github.com/urfave/cli/v2 v2.19.2
 	github.com/valyala/fastjson v1.6.3
 	github.com/valyala/fastrand v1.1.0
-	github.com/valyala/fasttemplate v1.2.1
+	github.com/valyala/fasttemplate v1.2.2
 	github.com/valyala/gozstd v1.17.0
 	github.com/valyala/quicktemplate v1.7.0
 	golang.org/x/net v0.0.0-20221014081412-f15817d10f9b
diff --git a/go.sum b/go.sum
index 687c2e7d1..00db19c03 100644
--- a/go.sum
+++ b/go.sum
@@ -893,8 +893,8 @@ github.com/valyala/fastjson v1.6.3 h1:tAKFnnwmeMGPbwJ7IwxcTPCNr3uIzoIj3/Fh90ra4x
 github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
 github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8=
 github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ=
-github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
-github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
+github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
+github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
 github.com/valyala/gozstd v1.17.0 h1:M4Ds4MIrw+pD+s6vYtuFZ8D3iEw9htzfdytOV3C3iQU=
 github.com/valyala/gozstd v1.17.0/go.mod h1:y5Ew47GLlP37EkTB+B4s7r6A5rdaeB7ftbl9zoYiIPQ=
 github.com/valyala/histogram v1.2.0 h1:wyYGAZZt3CpwUiIb9AU/Zbllg1llXyrtApRS815OLoQ=
diff --git a/lib/envtemplate/envtemplate.go b/lib/envtemplate/envtemplate.go
index c92eb827f..14a84a90c 100644
--- a/lib/envtemplate/envtemplate.go
+++ b/lib/envtemplate/envtemplate.go
@@ -2,6 +2,7 @@ package envtemplate
 
 import (
 	"bytes"
+	"fmt"
 	"io"
 	"os"
 
@@ -9,17 +10,22 @@ import (
 )
 
 // Replace replaces `%{ENV_VAR}` placeholders in b with the corresponding ENV_VAR values.
-func Replace(b []byte) []byte {
+//
+// Error is returned if ENV_VAR isn't set for some `%{ENV_VAR}` placeholder.
+func Replace(b []byte) ([]byte, error) {
 	if !bytes.Contains(b, []byte("%{")) {
 		// Fast path - nothing to replace.
-		return b
+		return b, nil
 	}
-	s := fasttemplate.ExecuteFuncString(string(b), "%{", "}", func(w io.Writer, tag string) (int, error) {
-		v := os.Getenv(tag)
-		if v == "" {
-			v = "%{" + tag + "}"
+	s, err := fasttemplate.ExecuteFuncStringWithErr(string(b), "%{", "}", func(w io.Writer, tag string) (int, error) {
+		v, ok := os.LookupEnv(tag)
+		if !ok {
+			return 0, fmt.Errorf("missing %q environment variable", tag)
 		}
 		return w.Write([]byte(v))
 	})
-	return []byte(s)
+	if err != nil {
+		return nil, err
+	}
+	return []byte(s), nil
 }
diff --git a/lib/mergeset/table.go b/lib/mergeset/table.go
index ed44357f7..043260961 100644
--- a/lib/mergeset/table.go
+++ b/lib/mergeset/table.go
@@ -204,7 +204,7 @@ func (ris *rawItemsShard) addItems(tb *Table, items [][]byte) error {
 			ibs[i] = nil
 		}
 		ris.ibs = ibs[:0]
-		ris.lastFlushTime = fasttime.UnixTimestamp()
+		atomic.StoreUint64(&ris.lastFlushTime, fasttime.UnixTimestamp())
 	}
 	ris.mu.Unlock()
 
@@ -632,19 +632,18 @@ func (ris *rawItemsShard) appendBlocksToFlush(dst []*inmemoryBlock, tb *Table, i
 	if flushSeconds <= 0 {
 		flushSeconds = 1
 	}
-
-	ris.mu.Lock()
-	if isFinal || currentTime-ris.lastFlushTime > uint64(flushSeconds) {
+	lastFlushTime := atomic.LoadUint64(&ris.lastFlushTime)
+	if isFinal || currentTime-lastFlushTime > uint64(flushSeconds) {
+		ris.mu.Lock()
 		ibs := ris.ibs
 		dst = append(dst, ibs...)
 		for i := range ibs {
 			ibs[i] = nil
 		}
 		ris.ibs = ibs[:0]
-		ris.lastFlushTime = currentTime
+		atomic.StoreUint64(&ris.lastFlushTime, currentTime)
+		ris.mu.Unlock()
 	}
-	ris.mu.Unlock()
-
 	return dst
 }
 
diff --git a/lib/promrelabel/config.go b/lib/promrelabel/config.go
index eee9f002c..16bf3e9e9 100644
--- a/lib/promrelabel/config.go
+++ b/lib/promrelabel/config.go
@@ -152,7 +152,10 @@ func LoadRelabelConfigs(path string, relabelDebug bool) (*ParsedConfigs, error)
 	if err != nil {
 		return nil, fmt.Errorf("cannot read `relabel_configs` from %q: %w", path, err)
 	}
-	data = envtemplate.Replace(data)
+	data, err = envtemplate.Replace(data)
+	if err != nil {
+		return nil, fmt.Errorf("cannot expand environment vars at %q: %w", path, err)
+	}
 	pcs, err := ParseRelabelConfigsData(data, relabelDebug)
 	if err != nil {
 		return nil, fmt.Errorf("cannot unmarshal `relabel_configs` from %q: %w", path, err)
diff --git a/lib/promscrape/config.go b/lib/promscrape/config.go
index 30cee5016..4f7cc0351 100644
--- a/lib/promscrape/config.go
+++ b/lib/promscrape/config.go
@@ -92,8 +92,11 @@ type Config struct {
 }
 
 func (cfg *Config) unmarshal(data []byte, isStrict bool) error {
-	data = envtemplate.Replace(data)
 	var err error
+	data, err = envtemplate.Replace(data)
+	if err != nil {
+		return fmt.Errorf("cannot expand environment variables: %w", err)
+	}
 	if isStrict {
 		if err = yaml.UnmarshalStrict(data, cfg); err != nil {
 			err = fmt.Errorf("%w; pass -promscrape.config.strictParse=false command-line flag for ignoring unknown fields in yaml config", err)
@@ -372,7 +375,10 @@ func loadStaticConfigs(path string) ([]StaticConfig, error) {
 	if err != nil {
 		return nil, fmt.Errorf("cannot read `static_configs` from %q: %w", path, err)
 	}
-	data = envtemplate.Replace(data)
+	data, err = envtemplate.Replace(data)
+	if err != nil {
+		return nil, fmt.Errorf("cannot expand environment vars in %q: %w", path, err)
+	}
 	var stcs []StaticConfig
 	if err := yaml.UnmarshalStrict(data, &stcs); err != nil {
 		return nil, fmt.Errorf("cannot unmarshal `static_configs` from %q: %w", path, err)
@@ -413,7 +419,10 @@ func loadScrapeConfigFiles(baseDir string, scrapeConfigFiles []string) ([]*Scrap
 			if err != nil {
 				return nil, nil, fmt.Errorf("cannot load %q: %w", path, err)
 			}
-			data = envtemplate.Replace(data)
+			data, err = envtemplate.Replace(data)
+			if err != nil {
+				return nil, nil, fmt.Errorf("cannot expand environment vars in %q: %w", path, err)
+			}
 			var scs []*ScrapeConfig
 			if err = yaml.UnmarshalStrict(data, &scs); err != nil {
 				return nil, nil, fmt.Errorf("cannot parse %q: %w", path, err)
diff --git a/lib/storage/partition.go b/lib/storage/partition.go
index d7af8a17a..85568ee95 100644
--- a/lib/storage/partition.go
+++ b/lib/storage/partition.go
@@ -64,7 +64,7 @@ const finalPartsToMerge = 3
 // The number of shards for rawRow entries per partition.
 //
 // Higher number of shards reduces CPU contention and increases the max bandwidth on multi-core systems.
-var rawRowsShardsPerPartition = (cgroup.AvailableCPUs() + 3) / 4
+var rawRowsShardsPerPartition = (cgroup.AvailableCPUs() + 1) / 2
 
 // getMaxRawRowsPerShard returns the maximum number of rows that haven't been converted into parts yet.
 func getMaxRawRowsPerShard() int {
@@ -477,7 +477,7 @@ func (rrs *rawRowsShard) addRows(pt *partition, rows []rawRow) {
 		rowsToFlush = append(rowsToFlush, rrs.rows...)
 		rowsToFlush = append(rowsToFlush, rows...)
 		rrs.rows = rrs.rows[:0]
-		rrs.lastFlushTime = fasttime.UnixTimestamp()
+		atomic.StoreUint64(&rrs.lastFlushTime, fasttime.UnixTimestamp())
 	}
 	rrs.mu.Unlock()
 
@@ -726,14 +726,14 @@ func (rrs *rawRowsShard) appendRawRowsToFlush(dst []rawRow, pt *partition, isFin
 	if flushSeconds <= 0 {
 		flushSeconds = 1
 	}
-
-	rrs.mu.Lock()
-	if isFinal || currentTime-rrs.lastFlushTime > uint64(flushSeconds) {
+	lastFlushTime := atomic.LoadUint64(&rrs.lastFlushTime)
+	if isFinal || currentTime-lastFlushTime > uint64(flushSeconds) {
+		rrs.mu.Lock()
 		dst = append(dst, rrs.rows...)
 		rrs.rows = rrs.rows[:0]
+		atomic.StoreUint64(&rrs.lastFlushTime, currentTime)
+		rrs.mu.Unlock()
 	}
-	rrs.mu.Unlock()
-
 	return dst
 }
 
diff --git a/vendor/github.com/valyala/fasttemplate/template.go b/vendor/github.com/valyala/fasttemplate/template.go
index 186200134..f2d3261f8 100644
--- a/vendor/github.com/valyala/fasttemplate/template.go
+++ b/vendor/github.com/valyala/fasttemplate/template.go
@@ -112,8 +112,7 @@ func ExecuteFuncString(template, startTag, endTag string, f TagFunc) string {
 // but when f returns an error, ExecuteFuncStringWithErr won't panic like ExecuteFuncString
 // it just returns an empty string and the error f returned
 func ExecuteFuncStringWithErr(template, startTag, endTag string, f TagFunc) (string, error) {
-	tagsCount := bytes.Count(unsafeString2Bytes(template), unsafeString2Bytes(startTag))
-	if tagsCount == 0 {
+	if n := bytes.Index(unsafeString2Bytes(template), unsafeString2Bytes(startTag)); n < 0 {
 		return template, nil
 	}
 
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 68e389ec6..24ae20872 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -342,7 +342,7 @@ github.com/valyala/fastjson/fastfloat
 # github.com/valyala/fastrand v1.1.0
 ## explicit
 github.com/valyala/fastrand
-# github.com/valyala/fasttemplate v1.2.1
+# github.com/valyala/fasttemplate v1.2.2
 ## explicit; go 1.12
 github.com/valyala/fasttemplate
 # github.com/valyala/gozstd v1.17.0


Test patch:
diff --git a/lib/envtemplate/envtemplate_test.go b/lib/envtemplate/envtemplate_test.go
index 4ba4633cd..db899c80d 100644
--- a/lib/envtemplate/envtemplate_test.go
+++ b/lib/envtemplate/envtemplate_test.go
@@ -1,19 +1,36 @@
 package envtemplate
 
 import (
+	"os"
 	"testing"
 )
 
-func TestReplace(t *testing.T) {
+func TestReplaceSuccess(t *testing.T) {
+	if err := os.Setenv("foo", "bar"); err != nil {
+		t.Fatalf("cannot set env var: %s", err)
+	}
 	f := func(s, resultExpected string) {
 		t.Helper()
-		result := Replace([]byte(s))
+		result, err := Replace([]byte(s))
+		if err != nil {
+			t.Fatalf("unexpected error: %s", err)
+		}
 		if string(result) != resultExpected {
 			t.Fatalf("unexpected result;\ngot\n%q\nwant\n%q", result, resultExpected)
 		}
 	}
 	f("", "")
 	f("foo", "foo")
-	f("%{foo}", "%{foo}")
-	f("foo %{bar} %{baz}", "foo %{bar} %{baz}")
+	f("a %{foo}-x", "a bar-x")
+}
+
+func TestReplaceFailure(t *testing.T) {
+	f := func(s string) {
+		t.Helper()
+		_, err := Replace([]byte(s))
+		if err == nil {
+			t.Fatalf("expecting non-nil error")
+		}
+	}
+	f("foo %{bar} %{baz}")
 }
diff --git a/lib/promrelabel/relabel_test.go b/lib/promrelabel/relabel_test.go
index a412eed8a..8c1d34064 100644
--- a/lib/promrelabel/relabel_test.go
+++ b/lib/promrelabel/relabel_test.go
@@ -115,6 +115,12 @@ func TestApplyRelabelConfigs(t *testing.T) {
   source_labels: ["foo"]
   target_label: "bar"
   regex: ".+"
+`, `{xxx="yyy"}`, false, `{xxx="yyy"}`)
+		f(`
+- action: replace
+  source_labels: ["foo"]
+  target_label: "xxx"
+  regex: ".+"
 `, `{xxx="yyy"}`, false, `{xxx="yyy"}`)
 	})
 	t.Run("replace-if-miss", func(t *testing.T) {
@@ -133,6 +139,16 @@ func TestApplyRelabelConfigs(t *testing.T) {
   target_label: "bar"
   replacement: "a-$1-b"
 `, `{xxx="yyy"}`, false, `{bar="a-yyy;-b",xxx="yyy"}`)
+		f(`
+- action: replace
+  source_labels: ["xxx", "foo"]
+  target_label: "xxx"
+`, `{xxx="yyy"}`, false, `{xxx="yyy;"}`)
+		f(`
+- action: replace
+  source_labels: ["foo"]
+  target_label: "xxx"
+`, `{xxx="yyy"}`, false, `{}`)
 	})
 	t.Run("replace-if-hit", func(t *testing.T) {
 		f(`